webext-messenger 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi, } from "vitest";
|
|
2
|
+
import { isBackground, isExtensionContext } from "webext-detect";
|
|
3
|
+
import { errorTabDoesntExist, errorTabWasDiscarded, errorTargetClosedEarly, getMethod, getNotifier, messenger, } from "./sender.js";
|
|
4
|
+
import { handlers } from "./handlers.js";
|
|
5
|
+
import { events } from "./events.js";
|
|
6
|
+
import { ExtensionNotFoundError, MessengerError } from "./shared.js";
|
|
7
|
+
vi.mock("webext-detect");
|
|
8
|
+
// The verbatim strings the browser throws. `sender.ts` matches on them to decide
|
|
9
|
+
// whether an error is retryable, so a change in either place must break a test.
|
|
10
|
+
const browserErrorNoReceiver = "Could not establish connection. Receiving end does not exist.";
|
|
11
|
+
const browserErrorClosedEarly = "A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received";
|
|
12
|
+
/** `sender.ts` only unwraps responses carrying this marker; anything else is a third-party reply. */
|
|
13
|
+
function messengerReply(value) {
|
|
14
|
+
return { __webextMessenger: true, value };
|
|
15
|
+
}
|
|
16
|
+
function messengerErrorReply(name, message) {
|
|
17
|
+
return { __webextMessenger: true, error: { name, message } };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* @param tabs Contexts without the tabs API (content scripts, offscreen documents)
|
|
21
|
+
* take a different route through `messenger()`, so this must be switchable.
|
|
22
|
+
* @param invalidated Drops `runtime.id`, which is how the browser signals that the
|
|
23
|
+
* extension was reloaded out from under this context.
|
|
24
|
+
*/
|
|
25
|
+
function stubChrome({ tabs = true, invalidated = false } = {}) {
|
|
26
|
+
const chromeStub = {
|
|
27
|
+
runtime: {
|
|
28
|
+
id: invalidated ? undefined : "this-extension-id",
|
|
29
|
+
sendMessage: vi.fn(),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
if (tabs) {
|
|
33
|
+
chromeStub.tabs = {
|
|
34
|
+
sendMessage: vi.fn(),
|
|
35
|
+
get: vi
|
|
36
|
+
.fn()
|
|
37
|
+
.mockResolvedValue({ discarded: false }),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
vi.stubGlobal("chrome", chromeStub);
|
|
41
|
+
return chromeStub;
|
|
42
|
+
}
|
|
43
|
+
/** The message envelope is always the last argument of a `runtime.sendMessage` call. */
|
|
44
|
+
function sentEnvelope(mock, index = 0) {
|
|
45
|
+
return mock.mock.calls[index].at(-1);
|
|
46
|
+
}
|
|
47
|
+
const eventCleanups = [];
|
|
48
|
+
function trackEvent(name) {
|
|
49
|
+
const listener = vi.fn();
|
|
50
|
+
events.addEventListener(name, listener);
|
|
51
|
+
eventCleanups.push(() => {
|
|
52
|
+
events.removeEventListener(name, listener);
|
|
53
|
+
});
|
|
54
|
+
return listener;
|
|
55
|
+
}
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
vi.useFakeTimers();
|
|
58
|
+
});
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
vi.useRealTimers();
|
|
61
|
+
vi.unstubAllGlobals();
|
|
62
|
+
vi.resetAllMocks();
|
|
63
|
+
handlers.clear();
|
|
64
|
+
for (const cleanup of eventCleanups.splice(0)) {
|
|
65
|
+
cleanup();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
describe("messenger() routing", () => {
|
|
69
|
+
test("sends to another extension via runtime.sendMessage(extensionId, message)", async () => {
|
|
70
|
+
const chromeStub = stubChrome();
|
|
71
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply(3));
|
|
72
|
+
await expect(messenger("senderTestMethod", {}, { extensionId: "other-extension" }, 1)).resolves.toBe(3);
|
|
73
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
74
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledWith("other-extension", expect.objectContaining({
|
|
75
|
+
__webextMessenger: true,
|
|
76
|
+
type: "senderTestMethod",
|
|
77
|
+
args: [1],
|
|
78
|
+
target: { extensionId: "other-extension" },
|
|
79
|
+
}));
|
|
80
|
+
});
|
|
81
|
+
test("throws synchronously when the browser cannot message other extensions", () => {
|
|
82
|
+
vi.stubGlobal("chrome", {});
|
|
83
|
+
expect(() => {
|
|
84
|
+
void messenger("senderTestMethod", {}, { extensionId: "other-extension" }, 1);
|
|
85
|
+
}).toThrow(ExtensionNotFoundError);
|
|
86
|
+
});
|
|
87
|
+
test("invokes the local handler when the background page targets itself", async () => {
|
|
88
|
+
const chromeStub = stubChrome();
|
|
89
|
+
vi.mocked(isBackground).mockReturnValue(true);
|
|
90
|
+
const handler = vi.fn().mockResolvedValue(42);
|
|
91
|
+
handlers.set("senderTestMethod", handler);
|
|
92
|
+
await expect(messenger("senderTestMethod", {}, { page: "background" }, 1, 2)).resolves.toBe(42);
|
|
93
|
+
expect(handler).toHaveBeenCalledWith(1, 2);
|
|
94
|
+
expect(chromeStub.runtime.sendMessage).not.toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
test("throws when the background page targets itself without a registered handler", () => {
|
|
97
|
+
stubChrome();
|
|
98
|
+
vi.mocked(isBackground).mockReturnValue(true);
|
|
99
|
+
expect(() => {
|
|
100
|
+
void messenger("senderTestMethod", {}, { page: "background" }, 1);
|
|
101
|
+
}).toThrow("No handler registered locally for senderTestMethod");
|
|
102
|
+
});
|
|
103
|
+
test("sends to an extension page via runtime.sendMessage(message)", async () => {
|
|
104
|
+
const chromeStub = stubChrome();
|
|
105
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
106
|
+
await expect(messenger("senderTestMethod", {}, { page: "/options.html" }, 1)).resolves.toBe("ok");
|
|
107
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
108
|
+
// A single argument, otherwise the browser reads it as an extension id
|
|
109
|
+
expect(chromeStub.runtime.sendMessage.mock.calls[0]).toHaveLength(1);
|
|
110
|
+
});
|
|
111
|
+
test("routes tab targets through the runtime when the context has no tabs API", async () => {
|
|
112
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
113
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
114
|
+
await expect(messenger("senderTestMethod", {}, { tabId: 1, frameId: 3 }, 1)).resolves.toBe("ok");
|
|
115
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
116
|
+
});
|
|
117
|
+
test.each([
|
|
118
|
+
["defaults to the top frame", { tabId: 1 }, { frameId: 0 }],
|
|
119
|
+
["forwards an explicit frame", { tabId: 1, frameId: 5 }, { frameId: 5 }],
|
|
120
|
+
[
|
|
121
|
+
"drops frameId to reach every frame",
|
|
122
|
+
{ tabId: 1, frameId: "allFrames" },
|
|
123
|
+
{},
|
|
124
|
+
],
|
|
125
|
+
])("sends to a tab and %s", async (_name, target, expectedOptions) => {
|
|
126
|
+
const chromeStub = stubChrome();
|
|
127
|
+
chromeStub.tabs.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
128
|
+
await expect(messenger("senderTestMethod", {}, target, 1)).resolves.toBe("ok");
|
|
129
|
+
expect(chromeStub.tabs.sendMessage).toHaveBeenCalledWith(1, expect.objectContaining({ type: "senderTestMethod" }), expectedOptions);
|
|
130
|
+
});
|
|
131
|
+
test("stamps every message with the marker and an increasing seq", async () => {
|
|
132
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
133
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
134
|
+
await messenger("senderTestMethod", {}, { page: "/options.html" }, 1);
|
|
135
|
+
await messenger("senderTestMethod", {}, { page: "/options.html" }, 2);
|
|
136
|
+
const first = sentEnvelope(chromeStub.runtime.sendMessage, 0);
|
|
137
|
+
const second = sentEnvelope(chromeStub.runtime.sendMessage, 1);
|
|
138
|
+
expect(first.__webextMessenger).toBe(true);
|
|
139
|
+
expect(second.options.seq).toBe(first.options.seq + 1);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
/**
|
|
143
|
+
* Regression guard for #409: 0.35.0 hoisted a `compareTargets()` short-circuit onto
|
|
144
|
+
* the send path, which threw `TypeError: Invalid URL` in opaque-origin frames and
|
|
145
|
+
* mis-routed same-tab messages to the sending frame. Reverted in #410.
|
|
146
|
+
*/
|
|
147
|
+
describe("sending from a content script (regression: #409)", () => {
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
// A sandboxed `about:srcdoc` frame has an opaque origin, so `location.origin`
|
|
150
|
+
// is the literal string "null" and `new URL(page, location.origin)` throws.
|
|
151
|
+
vi.stubGlobal("location", {
|
|
152
|
+
origin: "null",
|
|
153
|
+
protocol: "about:",
|
|
154
|
+
pathname: "srcdoc",
|
|
155
|
+
search: "",
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
test("reaches the runtime from an opaque-origin frame", async () => {
|
|
159
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
160
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
161
|
+
const localHandler = vi.fn();
|
|
162
|
+
handlers.set("senderTestMethod", localHandler);
|
|
163
|
+
// `{page: "any"}` is the target used by `storeTabData()`, the first message
|
|
164
|
+
// every content script sends; in 0.35.0 it threw before routing.
|
|
165
|
+
await expect(messenger("senderTestMethod", {}, { page: "any" }, 1)).resolves.toBe("ok");
|
|
166
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
167
|
+
expect(localHandler).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
test("does not handle a same-tab target locally", async () => {
|
|
170
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
171
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
172
|
+
const localHandler = vi.fn();
|
|
173
|
+
handlers.set("senderTestMethod", localHandler);
|
|
174
|
+
// A frame sending to `{tabId}` means the *top* frame, never itself.
|
|
175
|
+
await expect(messenger("senderTestMethod", {}, { tabId: 1 }, 1)).resolves.toBe("ok");
|
|
176
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
177
|
+
expect(localHandler).not.toHaveBeenCalled();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe("messenger() responses", () => {
|
|
181
|
+
test("returns the value from a messenger reply", async () => {
|
|
182
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
183
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply({ a: 1 }));
|
|
184
|
+
await expect(messenger("senderTestMethod", {}, { page: "/options.html" }, 1)).resolves.toEqual({ a: 1 });
|
|
185
|
+
});
|
|
186
|
+
test("rethrows a deserialized error from a messenger reply", async () => {
|
|
187
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
188
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerErrorReply("TypeError", "Handler exploded"));
|
|
189
|
+
await expect(messenger("senderTestMethod", {}, { page: "/options.html" }, 1)).rejects.toThrow("Handler exploded");
|
|
190
|
+
// A genuine error from the target must not be retried
|
|
191
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
192
|
+
});
|
|
193
|
+
test("reports a missing page target when nobody answers", async () => {
|
|
194
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
195
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(undefined);
|
|
196
|
+
await expect(messenger("senderTestMethod", { retry: false }, { page: "/options.html" }, 1)).rejects.toThrow('The target {"page":"/options.html"} for senderTestMethod was not found');
|
|
197
|
+
});
|
|
198
|
+
test("reports a messenger-less tab when nobody answers", async () => {
|
|
199
|
+
const chromeStub = stubChrome();
|
|
200
|
+
chromeStub.tabs.sendMessage.mockResolvedValue(undefined);
|
|
201
|
+
await expect(messenger("senderTestMethod", { retry: false }, { tabId: 1 }, 1)).rejects.toThrow("Messenger was not available in the target");
|
|
202
|
+
});
|
|
203
|
+
test("reports a conflict when a third-party listener answers", async () => {
|
|
204
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
205
|
+
chromeStub.runtime.sendMessage.mockResolvedValue("not a messenger reply");
|
|
206
|
+
await expect(messenger("senderTestMethod", { retry: false }, { page: "/options.html" }, 1)).rejects.toThrow("Conflict: The message senderTestMethod was handled by a third-party listener");
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
describe("retry behaviour", () => {
|
|
210
|
+
test("retries until the receiving end exists", async () => {
|
|
211
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
212
|
+
chromeStub.runtime.sendMessage
|
|
213
|
+
.mockRejectedValueOnce(new Error(browserErrorNoReceiver))
|
|
214
|
+
.mockResolvedValueOnce(messengerReply("ok"));
|
|
215
|
+
const promise = messenger("senderTestMethod", {}, { page: "/options.html" }, 1);
|
|
216
|
+
const assertion = expect(promise).resolves.toBe("ok");
|
|
217
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
218
|
+
await assertion;
|
|
219
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledTimes(2);
|
|
220
|
+
});
|
|
221
|
+
test("retries a MessengerError, which means the target is still booting", async () => {
|
|
222
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
223
|
+
chromeStub.runtime.sendMessage
|
|
224
|
+
.mockResolvedValueOnce(messengerErrorReply("MessengerError", "No handlers registered in x"))
|
|
225
|
+
.mockResolvedValueOnce(messengerReply("ok"));
|
|
226
|
+
const promise = messenger("senderTestMethod", {}, { page: "/options.html" }, 1);
|
|
227
|
+
const assertion = expect(promise).resolves.toBe("ok");
|
|
228
|
+
await vi.advanceTimersByTimeAsync(500);
|
|
229
|
+
await assertion;
|
|
230
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledTimes(2);
|
|
231
|
+
});
|
|
232
|
+
test("makes a single attempt when retry is disabled", async () => {
|
|
233
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
234
|
+
chromeStub.runtime.sendMessage.mockRejectedValue(new Error(browserErrorNoReceiver));
|
|
235
|
+
await expect(messenger("senderTestMethod", { retry: false }, { page: "/options.html" }, 1)).rejects.toThrow('The target {"page":"/options.html"} for senderTestMethod was not found');
|
|
236
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
237
|
+
});
|
|
238
|
+
test("does not retry once the target has closed early", async () => {
|
|
239
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
240
|
+
chromeStub.runtime.sendMessage.mockRejectedValue(new Error(browserErrorClosedEarly));
|
|
241
|
+
await expect(messenger("senderTestMethod", {}, { page: "/options.html" }, 1)).rejects.toThrow(errorTargetClosedEarly);
|
|
242
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
243
|
+
});
|
|
244
|
+
test("does not retry a missing external extension", async () => {
|
|
245
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
246
|
+
chromeStub.runtime.sendMessage.mockRejectedValue(new Error(browserErrorNoReceiver));
|
|
247
|
+
await expect(messenger("senderTestMethod", {}, { extensionId: "other-extension" }, 1)).rejects.toThrow(ExtensionNotFoundError);
|
|
248
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
249
|
+
});
|
|
250
|
+
test("stops as soon as the extension context is invalidated", async () => {
|
|
251
|
+
const chromeStub = stubChrome({ tabs: false, invalidated: true });
|
|
252
|
+
vi.mocked(isExtensionContext).mockReturnValue(true);
|
|
253
|
+
chromeStub.runtime.sendMessage.mockRejectedValue(new Error(browserErrorNoReceiver));
|
|
254
|
+
await expect(messenger("senderTestMethod", {}, { page: "/options.html" }, 1)).rejects.toThrow("Extension context invalidated.");
|
|
255
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
256
|
+
});
|
|
257
|
+
test("stops retrying when the target tab is gone", async () => {
|
|
258
|
+
const chromeStub = stubChrome();
|
|
259
|
+
chromeStub.tabs.sendMessage.mockRejectedValue(new Error(browserErrorNoReceiver));
|
|
260
|
+
chromeStub.tabs.get.mockRejectedValue(new Error("No tab with id: 1."));
|
|
261
|
+
await expect(messenger("senderTestMethod", {}, { tabId: 1 }, 1)).rejects.toThrow(errorTabDoesntExist);
|
|
262
|
+
expect(chromeStub.tabs.sendMessage).toHaveBeenCalledOnce();
|
|
263
|
+
});
|
|
264
|
+
test("stops retrying when the target tab was discarded", async () => {
|
|
265
|
+
const chromeStub = stubChrome();
|
|
266
|
+
chromeStub.tabs.sendMessage.mockRejectedValue(new Error(browserErrorNoReceiver));
|
|
267
|
+
chromeStub.tabs.get.mockResolvedValue({ discarded: true });
|
|
268
|
+
await expect(messenger("senderTestMethod", {}, { tabId: 1 }, 1)).rejects.toThrow(errorTabWasDiscarded);
|
|
269
|
+
expect(chromeStub.tabs.sendMessage).toHaveBeenCalledOnce();
|
|
270
|
+
});
|
|
271
|
+
test("gives up after the retry window and announces attempts-exhausted", async () => {
|
|
272
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
273
|
+
chromeStub.runtime.sendMessage.mockRejectedValue(new Error("No handlers registered in background"));
|
|
274
|
+
const failedAttempt = trackEvent("failed-attempt");
|
|
275
|
+
const attemptsExhausted = trackEvent("attempts-exhausted");
|
|
276
|
+
const promise = messenger("senderTestMethod", {}, { page: "/options.html" }, 1);
|
|
277
|
+
const assertion = expect(promise).rejects.toThrow("No handlers registered in background");
|
|
278
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
279
|
+
await assertion;
|
|
280
|
+
expect(attemptsExhausted).toHaveBeenCalledOnce();
|
|
281
|
+
expect(failedAttempt.mock.calls.length).toBe(chromeStub.runtime.sendMessage.mock.calls.length);
|
|
282
|
+
expect(chromeStub.runtime.sendMessage.mock.calls.length).toBeGreaterThan(1);
|
|
283
|
+
});
|
|
284
|
+
test("backs off exponentially rather than hammering the target", async () => {
|
|
285
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
286
|
+
chromeStub.runtime.sendMessage.mockRejectedValue(new Error(browserErrorNoReceiver));
|
|
287
|
+
const promise = messenger("senderTestMethod", {}, { page: "/options.html" }, 1);
|
|
288
|
+
const assertion = expect(promise).rejects.toThrow(MessengerError);
|
|
289
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
290
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledTimes(2);
|
|
291
|
+
// The second wait is longer than the first, so no third attempt yet
|
|
292
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
293
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledTimes(2);
|
|
294
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
295
|
+
await assertion;
|
|
296
|
+
// The safety cap in `manageMessage` must never be the reason we stopped
|
|
297
|
+
expect(chromeStub.runtime.sendMessage.mock.calls.length).toBeLessThan(15);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
describe("notifications", () => {
|
|
301
|
+
test("return undefined, send once, and swallow errors", async () => {
|
|
302
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
303
|
+
chromeStub.runtime.sendMessage.mockRejectedValue(new Error(browserErrorNoReceiver));
|
|
304
|
+
// Re-typed to capture the runtime value: notifications are typed `void`, and
|
|
305
|
+
// callers rely on nothing awaitable coming back.
|
|
306
|
+
const send = messenger;
|
|
307
|
+
const result = send("senderTestMethod", { isNotification: true }, { page: "/options.html" }, 1);
|
|
308
|
+
expect(result).toBeUndefined();
|
|
309
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
310
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
describe("getMethod() and getNotifier()", () => {
|
|
314
|
+
test("getMethod binds a fixed target", async () => {
|
|
315
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
316
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
317
|
+
const method = getMethod("senderTestMethod", { page: "/options.html" });
|
|
318
|
+
await expect(method(1, 2)).resolves.toBe("ok");
|
|
319
|
+
expect(sentEnvelope(chromeStub.runtime.sendMessage)).toMatchObject({
|
|
320
|
+
args: [1, 2],
|
|
321
|
+
target: { page: "/options.html" },
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
test("getMethod awaits a promised target", async () => {
|
|
325
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
326
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
327
|
+
const method = getMethod("senderTestMethod", Promise.resolve({ tabId: 1, frameId: 2 }));
|
|
328
|
+
await expect(method(1)).resolves.toBe("ok");
|
|
329
|
+
expect(sentEnvelope(chromeStub.runtime.sendMessage)).toMatchObject({
|
|
330
|
+
target: { tabId: 1, frameId: 2 },
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
test("getMethod without a target takes it as the first argument", async () => {
|
|
334
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
335
|
+
chromeStub.runtime.sendMessage.mockResolvedValue(messengerReply("ok"));
|
|
336
|
+
const method = getMethod("senderTestMethod");
|
|
337
|
+
await expect(method({ page: "/options.html" }, 1)).resolves.toBe("ok");
|
|
338
|
+
expect(sentEnvelope(chromeStub.runtime.sendMessage)).toMatchObject({
|
|
339
|
+
args: [1],
|
|
340
|
+
target: { page: "/options.html" },
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
test("getNotifier returns void and never rejects", async () => {
|
|
344
|
+
const chromeStub = stubChrome({ tabs: false });
|
|
345
|
+
chromeStub.runtime.sendMessage.mockRejectedValue(new Error("boom"));
|
|
346
|
+
const notify = getNotifier("senderTestMethod", { page: "/options.html" });
|
|
347
|
+
const result = notify(1);
|
|
348
|
+
expect(result).toBeUndefined();
|
|
349
|
+
await vi.advanceTimersByTimeAsync(10_000);
|
|
350
|
+
expect(chromeStub.runtime.sendMessage).toHaveBeenCalledOnce();
|
|
351
|
+
});
|
|
352
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webext-messenger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Browser Extension component messaging framework",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"repository": "pixiebrix/webext-messenger",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"one-event": "^4.4.0",
|
|
31
|
-
"serialize-error": "^13.0.
|
|
32
|
-
"type-fest": "^5.
|
|
33
|
-
"webext-detect": "^
|
|
31
|
+
"serialize-error": "^13.0.2",
|
|
32
|
+
"type-fest": "^5.10.0",
|
|
33
|
+
"webext-detect": "^6.0.0"
|
|
34
34
|
},
|
|
35
35
|
"@parcel/resolver-default": {
|
|
36
36
|
"packageExports": true
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@parcel/config-webextension": "^2.16.3",
|
|
40
40
|
"@sindresorhus/tsconfig": "^8.1.0",
|
|
41
|
-
"@types/chrome": "^0.
|
|
41
|
+
"@types/chrome": "^0.3.0",
|
|
42
42
|
"@types/tape": "^5.8.1",
|
|
43
43
|
"buffer": "^6.0.3",
|
|
44
44
|
"eslint": "^8.57.0",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"process": "^0.11.10",
|
|
51
51
|
"stream-browserify": "^3.0.0",
|
|
52
52
|
"tape": "^5.10.2",
|
|
53
|
-
"typescript": "^
|
|
54
|
-
"vitest": "^
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"vitest": "^5.0.2"
|
|
55
55
|
},
|
|
56
56
|
"targets": {
|
|
57
57
|
"main": false,
|