Skip to content

Commit b250c51

Browse files
committed
Improve handling code
1 parent 3c0d47a commit b250c51

File tree

2 files changed

+67
-60
lines changed

2 files changed

+67
-60
lines changed

source/receiver.ts

Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { getActionForMessage } from "./targetLogic.js";
1414
import { didUserRegisterMethods, handlers } from "./handlers.js";
1515
import { getTabDataStatus, thisTarget } from "./thisTarget.js";
1616

17+
type SendResponse = (response: unknown) => void;
18+
1719
export function isMessengerMessage(message: unknown): message is Message {
1820
return (
1921
isObject(message) &&
@@ -23,10 +25,17 @@ export function isMessengerMessage(message: unknown): message is Message {
2325
);
2426
}
2527

28+
/**
29+
* Decides what to do with a message and sends a response (value or error) back to the sender.
30+
*
31+
* @warn This function cannot return a Promise.
32+
* @warn Limit the amount of logic here because errors won't make it to `sendResponse`
33+
*/
34+
//
2635
function onMessageListener(
2736
message: unknown,
2837
sender: Sender,
29-
sendResponse: (response: unknown) => void,
38+
sendResponse: SendResponse,
3039
): true | undefined {
3140
if (!isMessengerMessage(message)) {
3241
// TODO: Add test for this eventuality: ignore unrelated messages
@@ -44,74 +53,64 @@ function onMessageListener(
4453
return;
4554
}
4655

56+
const { type, target, args, options = {} } = message;
57+
const { trace = [], seq } = options;
58+
59+
if (action === "forward") {
60+
log.debug(type, seq, "🔀 forwarded", { sender, target });
61+
} else {
62+
log.debug(type, seq, "↘️ received in", getContextName(), {
63+
sender,
64+
args,
65+
wasForwarded: trace.length > 1,
66+
});
67+
}
68+
69+
// Prepare the response asynchronously because the listener must return `true` synchronously
4770
(async () => {
4871
try {
49-
sendResponse(await handleMessage(message, sender, action));
72+
trace.push(sender);
73+
74+
const value = await prepareResponse(message, action, { trace });
75+
log.debug(type, seq, "↗️ responding", { value });
76+
sendResponse({ __webextMessenger, value });
5077
} catch (error) {
51-
sendResponse({ __webextMessenger: true, error: serializeError(error) });
78+
log.debug(type, seq, "↗️ responding", { error });
79+
sendResponse({ __webextMessenger, error: serializeError(error) });
5280
}
5381
})();
5482

55-
// Make `sendMessage` wait for an async response. This stops other `onMessage` listeners from being called.
83+
// This indicates that the message is being handled and a response will be sent asynchronously
5684
// TODO: Just return a promise if this is ever implemented https://issues.chromium.org/issues/40753031
5785
return true;
5886
}
5987

60-
// This function can only be called when the message *will* be handled locally.
61-
// Returning "undefined" or throwing an error will still handle it.
62-
async function handleMessage(
88+
/** Generates the value or error to return to the sender; does not include further messaging logic */
89+
async function prepareResponse(
6390
message: Message,
64-
sender: Sender,
65-
66-
// Once messages reach this function they cannot be "ignored", they're already being handled
6791
action: "respond" | "forward",
92+
meta: MessengerMeta,
6893
): Promise<unknown> {
69-
const { type, target, args, options = {} } = message;
70-
71-
const { trace = [], seq } = options;
72-
trace.push(sender);
73-
const meta: MessengerMeta = { trace };
74-
75-
let handleMessage: () => Promise<unknown>;
94+
const { type, target, args } = message;
7695

7796
if (action === "forward") {
78-
log.debug(type, seq, "🔀 forwarded", { sender, target });
79-
handleMessage = async () => messenger(type, meta, target, ...args);
80-
} else {
81-
log.debug(type, seq, "↘️ received in", getContextName(), {
82-
sender,
83-
args,
84-
wasForwarded: trace.length > 1,
85-
});
86-
87-
const localHandler = handlers.get(type);
88-
if (!localHandler) {
89-
if (!didUserRegisterMethods()) {
90-
// TODO: Test the handling of __getTabData in contexts that have no registered methods
91-
// https://github.com/pixiebrix/webext-messenger/pull/82
92-
throw new MessengerError(
93-
`No handlers registered in ${getContextName()}`,
94-
);
95-
}
96-
97-
throw new MessengerError(
98-
`No handler registered for ${type} in ${getContextName()}`,
99-
);
100-
}
97+
return messenger(type, meta, target, ...args);
98+
}
10199

102-
handleMessage = async () => localHandler.apply(meta, args);
100+
const localHandler = handlers.get(type);
101+
if (localHandler) {
102+
return localHandler.apply(meta, args);
103103
}
104104

105-
const response = await handleMessage().then(
106-
(value) => ({ value }),
107-
(error: unknown) => ({
108-
// Errors must be serialized because the stack traces are currently lost on Chrome
109-
error: serializeError(error),
110-
}),
111-
);
105+
if (didUserRegisterMethods()) {
106+
throw new MessengerError(
107+
`No handler registered for ${type} in ${getContextName()}`,
108+
);
109+
}
112110

113-
log.debug(type, seq, "↗️ responding", response);
114-
return { ...response, __webextMessenger };
111+
// TODO: Test the handling of __getTabData in contexts that have no registered methods
112+
// https://github.com/pixiebrix/webext-messenger/pull/82
113+
throw new MessengerError(`No handlers registered in ${getContextName()}`);
115114
}
116115

117116
export function registerMethods(methods: Partial<MessengerMethods>): void {

source/test/helpers.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,39 @@ export async function trackSettleTime(
3838
return performance.now() - startTime;
3939
}
4040

41+
function asSeconds(milliseconds: number): number {
42+
return Number((milliseconds / 1000).toFixed(2));
43+
}
44+
4145
export function expectDuration(
4246
t: test.Test,
4347
actualDuration: number,
4448
expectedDuration: number,
4549
maximumDuration?: number,
4650
) {
47-
console.log({ actualDuration, expectedDuration, maximumDuration });
51+
console.log({
52+
actualDuration: Math.round(actualDuration),
53+
expectedDuration,
54+
maximumDuration,
55+
});
4856
if (maximumDuration) {
4957
t.ok(
5058
actualDuration >= expectedDuration && actualDuration <= maximumDuration,
5159
expectedDuration > 0
52-
? `It should take between ${expectedDuration / 1000} and ${
53-
maximumDuration / 1000
54-
} seconds (took ${actualDuration / 1000}s)`
55-
: `It should take less than ${maximumDuration / 1000} seconds (took ${
56-
actualDuration / 1000
57-
}s)`,
60+
? `It should take between ${asSeconds(expectedDuration)} and ${asSeconds(
61+
maximumDuration,
62+
)} seconds (took ${asSeconds(actualDuration)}s)`
63+
: `It should take less than ${asSeconds(maximumDuration)} seconds (took ${asSeconds(
64+
actualDuration,
65+
)}s)`,
5866
);
5967
} else {
6068
t.ok(
6169
actualDuration > expectedDuration - 100 &&
6270
actualDuration < expectedDuration + 100,
63-
`It should take about ${expectedDuration / 1000}s (took ${
64-
actualDuration / 1000
65-
}s)`,
71+
`It should take about ${asSeconds(expectedDuration)}s (took ${asSeconds(
72+
actualDuration,
73+
)}s)`,
6674
);
6775
}
6876
}

0 commit comments

Comments
 (0)