feat: Reject sendMessage returned promise when a onMessage listener returns a rejected promise.
This commit is contained in:
parent
831e355650
commit
6c8268f6fb
|
@ -104,6 +104,8 @@ if (typeof browser === "undefined") {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pluralizeArguments = (numArgs) => numArgs == 1 ? "argument" : "arguments";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a wrapper function for a method with the given name and metadata.
|
* Creates a wrapper function for a method with the given name and metadata.
|
||||||
*
|
*
|
||||||
|
@ -127,8 +129,6 @@ if (typeof browser === "undefined") {
|
||||||
* The generated wrapper function.
|
* The generated wrapper function.
|
||||||
*/
|
*/
|
||||||
const wrapAsyncFunction = (name, metadata) => {
|
const wrapAsyncFunction = (name, metadata) => {
|
||||||
const pluralizeArguments = (numArgs) => numArgs == 1 ? "argument" : "arguments";
|
|
||||||
|
|
||||||
return function asyncFunctionWrapper(target, ...args) {
|
return function asyncFunctionWrapper(target, ...args) {
|
||||||
if (args.length < metadata.minArgs) {
|
if (args.length < metadata.minArgs) {
|
||||||
throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
|
throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
|
||||||
|
@ -385,7 +385,12 @@ if (typeof browser === "undefined") {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = listener(message, sender, wrappedSendResponse);
|
let result;
|
||||||
|
try {
|
||||||
|
result = listener(message, sender, wrappedSendResponse);
|
||||||
|
} catch (err) {
|
||||||
|
result = Promise.reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
const isResultThenable = result !== true && isThenable(result);
|
const isResultThenable = result !== true && isThenable(result);
|
||||||
|
|
||||||
|
@ -396,26 +401,42 @@ if (typeof browser === "undefined") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A small helper to send the message if the promise resolves
|
||||||
|
// and an error if the promise rejects (a wrapped sendMessage has
|
||||||
|
// to translate the message into a resolved promise or a rejected
|
||||||
|
// promise).
|
||||||
|
const sendPromisedResult = (promise) => {
|
||||||
|
promise.then(msg => {
|
||||||
|
// send the message value.
|
||||||
|
sendResponse(msg);
|
||||||
|
}, error => {
|
||||||
|
// Send a JSON representation of the error if the rejected value
|
||||||
|
// is an instance of error, or the object itself otherwise.
|
||||||
|
let message;
|
||||||
|
if (error && (error instanceof Error ||
|
||||||
|
typeof error.message === "string")) {
|
||||||
|
message = error.message;
|
||||||
|
} else {
|
||||||
|
message = "An unexpected error occurred";
|
||||||
|
}
|
||||||
|
|
||||||
|
sendResponse({
|
||||||
|
__mozWebExtensionPolyfillReject__: true,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
// Print an error on the console if unable to send the response.
|
||||||
|
console.error("Failed to send onMessage rejected reply", err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// If the listener returned a Promise, send the resolved value as a
|
// If the listener returned a Promise, send the resolved value as a
|
||||||
// result, otherwise wait the promise related to the wrappedSendResponse
|
// result, otherwise wait the promise related to the wrappedSendResponse
|
||||||
// callback to resolve and send it as a response.
|
// callback to resolve and send it as a response.
|
||||||
if (isResultThenable) {
|
if (isResultThenable) {
|
||||||
result.then(sendResponse, error => {
|
sendPromisedResult(result);
|
||||||
console.error(error);
|
|
||||||
// TODO: the error object is not serializable and so for now we just send
|
|
||||||
// `undefined`. Nevertheless, as being discussed in #97, this is not yet
|
|
||||||
// providing the expected behavior (the promise received from the sender should
|
|
||||||
// be rejected when the promise returned by the listener is being rejected).
|
|
||||||
sendResponse(undefined);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
sendResponsePromise.then(sendResponse, error => {
|
sendPromisedResult(sendResponsePromise);
|
||||||
console.error(error);
|
|
||||||
// TODO: same as above, we are currently sending `undefined` in this scenario
|
|
||||||
// because the error oject is not serializable, but it is not yet the behavior
|
|
||||||
// that this scenario should present.
|
|
||||||
sendResponse(undefined);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let Chrome know that the listener is replying.
|
// Let Chrome know that the listener is replying.
|
||||||
|
@ -423,9 +444,42 @@ if (typeof browser === "undefined") {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const wrappedSendMessageCallback = ({reject, resolve}, reply) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
reject(chrome.runtime.lastError);
|
||||||
|
} else if (reply && reply.__mozWebExtensionPolyfillReject__) {
|
||||||
|
// Convert back the JSON representation of the error into
|
||||||
|
// an Error instance.
|
||||||
|
reject(new Error(reply.message));
|
||||||
|
} else {
|
||||||
|
resolve(reply);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrappedSendMessage = (name, metadata, apiNamespaceObj, ...args) => {
|
||||||
|
if (args.length < metadata.minArgs) {
|
||||||
|
throw new Error(`Expected at least ${metadata.minArgs} ${pluralizeArguments(metadata.minArgs)} for ${name}(), got ${args.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length > metadata.maxArgs) {
|
||||||
|
throw new Error(`Expected at most ${metadata.maxArgs} ${pluralizeArguments(metadata.maxArgs)} for ${name}(), got ${args.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const wrappedCb = wrappedSendMessageCallback.bind(null, {resolve, reject});
|
||||||
|
args.push(wrappedCb);
|
||||||
|
apiNamespaceObj.sendMessage(...args);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const staticWrappers = {
|
const staticWrappers = {
|
||||||
runtime: {
|
runtime: {
|
||||||
onMessage: wrapEvent(onMessageWrappers),
|
onMessage: wrapEvent(onMessageWrappers),
|
||||||
|
onMessageExternal: wrapEvent(onMessageWrappers),
|
||||||
|
sendMessage: wrappedSendMessage.bind(null, "sendMessage", {minArgs: 1, maxArgs: 3}),
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
sendMessage: wrappedSendMessage.bind(null, "sendMessage", {minArgs: 2, maxArgs: 3}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,12 @@ browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
// a reply from the second listener.
|
// a reply from the second listener.
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
case "test - sendMessage with returned rejected Promise with Error value":
|
||||||
|
return Promise.reject(new Error("rejected-error-value"));
|
||||||
|
|
||||||
|
case "test - sendMessage with returned rejected Promise with non-Error value":
|
||||||
|
return Promise.reject("rejected-non-error-value");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
`Unxpected message received by the background page: ${JSON.stringify(msg)}\n`);
|
`Unxpected message received by the background page: ${JSON.stringify(msg)}\n`);
|
||||||
|
|
|
@ -25,3 +25,28 @@ console.log(name, "content script loaded");
|
||||||
runTest().catch((err) => {
|
runTest().catch((err) => {
|
||||||
console.error("content script error", err);
|
console.error("content script error", err);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("sendMessage with returned rejected Promise with Error value", async (t) => {
|
||||||
|
try {
|
||||||
|
const reply = await browser.runtime.sendMessage(
|
||||||
|
"test - sendMessage with returned rejected Promise with Error value");
|
||||||
|
t.fail(`Unexpected successfully reply while expecting a rejected promise`);
|
||||||
|
t.equal(reply, undefined, "Unexpected successfully reply");
|
||||||
|
} catch (err) {
|
||||||
|
t.equal(err.message, "rejected-error-value", "Got an error rejection with the expected message");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sendMessage with returned rejected Promise with non-Error value", async (t) => {
|
||||||
|
try {
|
||||||
|
const reply = await browser.runtime.sendMessage(
|
||||||
|
"test - sendMessage with returned rejected Promise with non-Error value");
|
||||||
|
t.fail(`Unexpected successfully reply while expecting a rejected promise`);
|
||||||
|
t.equal(reply, undefined, "Unexpected successfully reply");
|
||||||
|
} catch (err) {
|
||||||
|
// Unfortunately Firefox currently reject an error with an undefined
|
||||||
|
// message, in the meantime we just check that the object rejected is
|
||||||
|
// an instance of Error.
|
||||||
|
t.ok(err instanceof Error, "Got an error object as expected");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue