Skip to content

Commit

Permalink
Implement task processing with abort functionality and visibility han…
Browse files Browse the repository at this point in the history
…dling
  • Loading branch information
b1ink0 committed Feb 12, 2025
1 parent 969f6b5 commit c1e245d
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 178 deletions.
140 changes: 85 additions & 55 deletions plugins/optimization-detective/prime-url-metrics-block-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
const { apiFetch } = wp;

let isProcessing = false;
let verificationToken = '';
let breakpoints = [];
let currentTasks = [];
let currentTaskIndex = 0;
let isTabHidden = false;
let abortController = null;

const iframe = document.createElement( 'iframe' );
iframe.id = 'od-prime-url-metrics-iframe';
Expand All @@ -27,89 +31,94 @@
* Primes the URL metrics for all breakpoints.
* @return {Promise<void>} The promise that resolves to void.
*/
async function primeURL() {
async function processTasks() {
try {
isProcessing = true;
if ( 0 === breakpoints.length ) {
breakpoints = await apiFetch( {
path: '/optimization-detective/v1/prime-urls-breakpoints',
method: 'GET',
} );
}

const postURL = select( 'core/editor' ).getPermalink();
const verificationToken = await apiFetch( {
const permalink = select( 'core/editor' ).getPermalink();
verificationToken = await apiFetch( {
path: '/optimization-detective/v1/prime-urls-verification-token',
method: 'GET',
} );

currentTasks = breakpoints.map( ( breakpoint ) => ( {
url: postURL,
url: permalink,
width: breakpoint.width,
height: breakpoint.height,
verificationToken,
} ) );

if ( ! isProcessing && currentTasks.length > 0 ) {
isProcessing = true;
processTasks();
while ( isProcessing && currentTaskIndex < currentTasks.length ) {
abortController = new AbortController();
await processTask(
currentTasks[ currentTaskIndex ],
abortController.signal
);
currentTaskIndex++;
}
isProcessing = false;
} catch ( error ) {
isProcessing = false;
}
}

/**
* Loads the iframe and waits for the message.
* @param {{url: string, width: number, height: number}} task - The breakpoint to set for the iframe.
* @param {AbortSignal} signal - The signal to abort the task.
* @return {Promise<void>} The promise that resolves to void.
*/
async function processTasks() {
const task = currentTasks.shift();

try {
await new Promise( ( resolve, reject ) => {
const handleMessage = ( event ) => {
if (
event.data === 'OD_PRIME_URL_METRICS_REQUEST_SUCCESS'
) {
cleanup();
resolve();
}
};

const cleanup = () => {
window.removeEventListener( 'message', handleMessage );
clearTimeout( timeoutId );
iframe.onerror = null;
};

const timeoutId = setTimeout( () => {
async function processTask( task, signal ) {
return new Promise( ( resolve, reject ) => {
const handleMessage = ( event ) => {
if ( event.data === 'OD_PRIME_URL_METRICS_REQUEST_SUCCESS' ) {
cleanup();
reject( new Error( 'Timeout waiting for message' ) );
}, 30000 ); // 30-second timeout

window.addEventListener( 'message', handleMessage );

iframe.onerror = () => {
cleanup();
reject( new Error( 'Iframe failed to load' ) );
};

// Load the iframe
iframe.src = task.url;
iframe.width = task.width.toString();
iframe.height = task.height.toString();
iframe.dataset.odPrimeUrlMetricsVerificationToken =
task.verificationToken;
} );

if ( currentTasks.length > 0 ) {
processTasks();
} else {
isProcessing = false;
resolve();
}
};

const abortHandler = () => {
cleanup();
reject( new Error( 'Task Aborted' ) );
};

const cleanup = () => {
window.removeEventListener( 'message', handleMessage );
clearTimeout( timeoutId );
iframe.onerror = null;
iframe.src = 'about:blank';
};

const timeoutId = setTimeout( () => {
cleanup();
reject( new Error( 'Timeout waiting for message' ) );
}, 30000 ); // 30-second timeout

if ( signal.aborted ) {
abortHandler();
return;
}
} catch ( error ) {
isProcessing = false;
}

signal.addEventListener( 'abort', abortHandler );
window.addEventListener( 'message', handleMessage );

iframe.onerror = () => {
cleanup();
reject( new Error( 'Iframe failed to load' ) );
};

// Load the iframe
iframe.src = task.url;
iframe.width = task.width.toString();
iframe.height = task.height.toString();
iframe.dataset.odPrimeUrlMetricsVerificationToken =
verificationToken;
} );
}

// Listen for post save/publish events.
Expand All @@ -120,12 +129,33 @@

// Trigger when saving transitions from true to false (save completed).
if ( wasSaving && ! isSaving && ! isAutosaving ) {
primeURL();
currentTaskIndex = 0;
processTasks();
}

wasSaving = isSaving;
} );

/**
* Pause processing when the tab/window becomes hidden.
*/
document.addEventListener( 'visibilitychange', () => {
if ( 'hidden' === document.visibilityState && isProcessing ) {
isProcessing = false;
isTabHidden = true;
if ( abortController ) {
abortController.abort();
abortController = null;
}
} else if ( 'visible' === document.visibilityState && isTabHidden ) {
isTabHidden = false;
if ( ! isProcessing ) {
isProcessing = true;
processTasks();
}
}
} );

/**
* Prevent the user from leaving the page while processing.
*/
Expand Down
73 changes: 59 additions & 14 deletions plugins/optimization-detective/prime-url-metrics-classic-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
const { apiFetch } = wp;

let isProcessing = false;
let verificationToken = '';
let breakpoints = [];
let currentTasks = [];
let currentTaskIndex = 0;
let isTabHidden = false;
let abortController = null;

const iframe = document.createElement( 'iframe' );
iframe.id = 'od-prime-url-metrics-iframe';
iframe.style.position = 'fixed';
Expand All @@ -30,28 +36,34 @@
* Primes the URL metrics for all breakpoints.
* @return {Promise<void>} The promise that resolves to void.
*/
async function primeURL() {
isProcessing = true;
async function processTasks() {
try {
isProcessing = true;
if ( 0 === breakpoints.length ) {
breakpoints = await apiFetch( {
path: '/optimization-detective/v1/prime-urls-breakpoints',
method: 'GET',
} );
}

const verificationToken = await apiFetch( {
verificationToken = await apiFetch( {
path: '/optimization-detective/v1/prime-urls-verification-token',
method: 'GET',
} );

for ( const breakpoint of breakpoints ) {
await processTask( {
url: permalink,
width: breakpoint.width,
height: breakpoint.height,
verificationToken,
} );
currentTasks = breakpoints.map( ( breakpoint ) => ( {
url: permalink,
width: breakpoint.width,
height: breakpoint.height,
} ) );

while ( isProcessing && currentTaskIndex < currentTasks.length ) {
abortController = new AbortController();
await processTask(
currentTasks[ currentTaskIndex ],
abortController.signal
);
currentTaskIndex++;
}
isProcessing = false;
} catch ( error ) {
Expand All @@ -61,10 +73,11 @@

/**
* Loads the iframe and waits for the message.
* @param {{url: string, width: number, height: number, verificationToken: string}} task - The breakpoint to set for the iframe.
* @param {{url: string, width: number, height: number}} task - The breakpoint to set for the iframe.
* @param {AbortSignal} signal - The signal to abort the task.
* @return {Promise<void>} The promise that resolves to void.
*/
function processTask( task ) {
function processTask( task, signal ) {
return new Promise( ( resolve, reject ) => {
const handleMessage = ( event ) => {
if ( event.data === 'OD_PRIME_URL_METRICS_REQUEST_SUCCESS' ) {
Expand All @@ -73,17 +86,29 @@
}
};

const abortHandler = () => {
cleanup();
reject( new Error( 'Task Aborted' ) );
};

const cleanup = () => {
window.removeEventListener( 'message', handleMessage );
clearTimeout( timeoutId );
iframe.onerror = null;
iframe.src = 'about:blank';
};

const timeoutId = setTimeout( () => {
cleanup();
reject( new Error( 'Timeout waiting for message' ) );
}, 30000 ); // 30-second timeout

if ( signal.aborted ) {
abortHandler();
return;
}

signal.addEventListener( 'abort', abortHandler );
window.addEventListener( 'message', handleMessage );

iframe.onerror = () => {
Expand All @@ -96,7 +121,7 @@
iframe.width = task.width.toString();
iframe.height = task.height.toString();
iframe.dataset.odPrimeUrlMetricsVerificationToken =
task.verificationToken;
verificationToken;
} );
}

Expand All @@ -105,7 +130,27 @@
* when the document is ready.
*/
document.addEventListener( 'DOMContentLoaded', () => {
primeURL();
processTasks();
} );

/**
* Pause processing when the tab/window becomes hidden.
*/
document.addEventListener( 'visibilitychange', () => {
if ( 'hidden' === document.visibilityState && isProcessing ) {
isProcessing = false;
isTabHidden = true;
if ( abortController ) {
abortController.abort();
abortController = null;
}
} else if ( 'visible' === document.visibilityState && isTabHidden ) {
isTabHidden = false;
if ( ! isProcessing ) {
isProcessing = true;
processTasks();
}
}
} );

/**
Expand Down
Loading

0 comments on commit c1e245d

Please sign in to comment.