You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
6.1 KiB
201 lines
6.1 KiB
2 years ago
|
import pify from 'pify';
|
||
|
import { isManifestV3 } from '../../../shared/modules/mv3.utils';
|
||
|
|
||
|
// // A simplified pify maybe?
|
||
|
// function pify(apiObject) {
|
||
|
// return Object.keys(apiObject).reduce((promisifiedAPI, key) => {
|
||
|
// if (apiObject[key].apply) { // depending on our browser support we might use a nicer check for functions here
|
||
|
// promisifiedAPI[key] = function (...args) {
|
||
|
// return new Promise((resolve, reject) => {
|
||
|
// return apiObject[key](
|
||
|
// ...args,
|
||
|
// (err, result) => {
|
||
|
// if (err) {
|
||
|
// reject(err);
|
||
|
// } else {
|
||
|
// resolve(result);
|
||
|
// }
|
||
|
// },
|
||
|
// );
|
||
|
// });
|
||
|
// };
|
||
|
// }
|
||
|
// return promisifiedAPI;
|
||
|
// }, {});
|
||
|
// }
|
||
|
|
||
|
let background = null;
|
||
|
let promisifiedBackground = null;
|
||
|
|
||
|
const actionRetryQueue = [];
|
||
|
|
||
|
function failQueue() {
|
||
|
actionRetryQueue.forEach(({ reject }) =>
|
||
|
reject(
|
||
|
Error('Background operation cancelled while waiting for connection.'),
|
||
|
),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Drops the entire actions queue. Rejects all actions in the queue unless silently==true
|
||
|
* Does not affect the single action that is currently being processed.
|
||
|
*
|
||
|
* @param {boolean} [silently]
|
||
|
*/
|
||
|
export function dropQueue(silently) {
|
||
|
if (!silently) {
|
||
|
failQueue();
|
||
|
}
|
||
|
actionRetryQueue.length = 0;
|
||
|
}
|
||
|
|
||
|
// add action to queue
|
||
|
const executeActionOrAddToRetryQueue = (item) => {
|
||
|
if (actionRetryQueue.some((act) => act.actionId === item.actionId)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (background.connectionStream.readable) {
|
||
|
executeAction({
|
||
|
action: item,
|
||
|
disconnectSideeffect: () => actionRetryQueue.push(item),
|
||
|
});
|
||
|
} else {
|
||
|
actionRetryQueue.push(item);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Promise-style call to background method
|
||
|
* In MV2: invokes promisifiedBackground method directly.
|
||
|
* In MV3: action is added to retry queue, along with resolve handler to be executed on completion,
|
||
|
* the queue is then immediately processed if background connection is available.
|
||
|
* On completion (successful or error) the action is removed from the retry queue.
|
||
|
*
|
||
|
* @param {string} method - name of the background method
|
||
|
* @param {Array} [args] - arguments to that method, if any
|
||
|
* @param {any} [actionId] - if an action with the === same id is submitted, it'll be ignored if already in queue waiting for a retry.
|
||
|
* @returns {Promise}
|
||
|
*/
|
||
|
export function submitRequestToBackground(
|
||
|
method,
|
||
|
args = [],
|
||
|
actionId = Date.now() + Math.random(), // current date is not guaranteed to be unique
|
||
|
) {
|
||
|
if (isManifestV3) {
|
||
|
return new Promise((resolve, reject) => {
|
||
|
executeActionOrAddToRetryQueue({
|
||
|
actionId,
|
||
|
request: { method, args },
|
||
|
resolve,
|
||
|
reject,
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
return promisifiedBackground[method](...args);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Callback-style call to background method
|
||
|
* In MV2: invokes promisifiedBackground method directly.
|
||
|
* In MV3: action is added to retry queue, along with resolve handler to be executed on completion,
|
||
|
* the queue is then immediately processed if background connection is available.
|
||
|
* On completion (successful or error) the action is removed from the retry queue.
|
||
|
*
|
||
|
* @param {string} method - name of the background method
|
||
|
* @param {Array} [args] - arguments to that method, if any
|
||
|
* @param callback - Node style (error, result) callback for finishing the operation
|
||
|
* @param {any} [actionId] - if an action with the === same id is submitted, it'll be ignored if already in queue.
|
||
|
*/
|
||
|
export const callBackgroundMethod = (
|
||
|
method,
|
||
|
args = [],
|
||
|
callback,
|
||
|
actionId = Date.now() + Math.random(), // current date is not guaranteed to be unique
|
||
|
) => {
|
||
|
if (isManifestV3) {
|
||
|
const resolve = (value) => callback(null, value);
|
||
|
const reject = (err) => callback(err);
|
||
|
executeActionOrAddToRetryQueue({
|
||
|
actionId,
|
||
|
request: { method, args },
|
||
|
resolve,
|
||
|
reject,
|
||
|
});
|
||
|
} else {
|
||
|
background[method](...args, callback);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
async function executeAction({ action, disconnectSideeffect }) {
|
||
|
const {
|
||
|
request: { method, args },
|
||
|
resolve,
|
||
|
reject,
|
||
|
} = action;
|
||
|
try {
|
||
|
resolve(await promisifiedBackground[method](...args));
|
||
|
} catch (err) {
|
||
|
if (
|
||
|
background.DisconnectError && // necessary to not break compatibility with background stubs or non-default implementations
|
||
|
err instanceof background.DisconnectError
|
||
|
) {
|
||
|
disconnectSideeffect(action);
|
||
|
} else {
|
||
|
reject(err);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let processingQueue = false;
|
||
|
|
||
|
// Clears list of pending action in actionRetryQueue
|
||
|
// The results of background calls are wired up to the original promises that's been returned
|
||
|
// The first method on the queue gets called synchronously to make testing and reasoning about
|
||
|
// a single request to an open connection easier.
|
||
|
async function processActionRetryQueue() {
|
||
|
if (processingQueue) {
|
||
|
return;
|
||
|
}
|
||
|
processingQueue = true;
|
||
|
try {
|
||
|
while (
|
||
|
background.connectionStream.readable &&
|
||
|
actionRetryQueue.length > 0
|
||
|
) {
|
||
|
// If background disconnects and fails the action, the next one will not be taken off the queue.
|
||
|
// Retrying an action that failed because of connection loss while it was processing is not supported.
|
||
|
const item = actionRetryQueue.shift();
|
||
|
await executeAction({
|
||
|
action: item,
|
||
|
disconnectSideeffect: () => actionRetryQueue.unshift(item),
|
||
|
});
|
||
|
}
|
||
|
} catch (e) {
|
||
|
// error in the queue mechanism itself, the action was malformed
|
||
|
console.error(e);
|
||
|
}
|
||
|
processingQueue = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets/replaces the background connection reference
|
||
|
* Under MV3 it also triggers queue processing if the new background is connected
|
||
|
*
|
||
|
* @param {*} backgroundConnection
|
||
|
*/
|
||
|
export async function _setBackgroundConnection(backgroundConnection) {
|
||
|
background = backgroundConnection;
|
||
|
promisifiedBackground = pify(background);
|
||
|
if (isManifestV3) {
|
||
|
if (processingQueue) {
|
||
|
console.warn(
|
||
|
'_setBackgroundConnection called while a queue was processing and not disconnected yet',
|
||
|
);
|
||
|
}
|
||
|
// Process all actions collected while connection stream was not available.
|
||
|
processActionRetryQueue();
|
||
|
}
|
||
|
}
|