webext-messenger 0.22.0 โ 0.23.1
Sign up to get free protection for your applications and to get access to all the features.
- package/distribution/index.d.ts +1 -0
- package/distribution/index.js +1 -0
- package/distribution/receiver.d.ts +3 -1
- package/distribution/receiver.js +10 -6
- package/distribution/sender.d.ts +1 -0
- package/distribution/sender.js +41 -29
- package/distribution/shared.d.ts +5 -2
- package/distribution/shared.js +12 -14
- package/distribution/thisTarget.js +2 -2
- package/distribution/types.d.ts +3 -1
- package/package.json +17 -18
- package/readme.md +7 -2
package/distribution/index.d.ts
CHANGED
package/distribution/index.js
CHANGED
@@ -3,5 +3,6 @@ export * from "./receiver.js";
|
|
3
3
|
export * from "./sender.js";
|
4
4
|
export * from "./types.js";
|
5
5
|
export { getThisFrame, getTopLevelFrame } from "./thisTarget.js";
|
6
|
+
export { toggleLogging } from "./shared.js";
|
6
7
|
import { initPrivateApi } from "./thisTarget.js";
|
7
8
|
initPrivateApi();
|
@@ -1,3 +1,5 @@
|
|
1
|
-
import { type Message } from "./types.js";
|
1
|
+
import { type Message, type MessengerMeta } from "./types.js";
|
2
2
|
export declare function isMessengerMessage(message: unknown): message is Message;
|
3
3
|
export declare function registerMethods(methods: Partial<MessengerMethods>): void;
|
4
|
+
/** Ensure/document that the current function was called via Messenger */
|
5
|
+
export declare function assertMessengerCall(_this: MessengerMeta): asserts _this is MessengerMeta;
|
package/distribution/receiver.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { serializeError } from "serialize-error";
|
2
2
|
import { getContextName } from "webext-detect-page";
|
3
3
|
import { messenger } from "./sender.js";
|
4
|
-
import { isObject, MessengerError,
|
4
|
+
import { isObject, MessengerError, log, __webextMessenger } from "./shared.js";
|
5
5
|
import { getActionForMessage } from "./thisTarget.js";
|
6
6
|
import { didUserRegisterMethods, handlers } from "./handlers.js";
|
7
7
|
export function isMessengerMessage(message) {
|
@@ -29,16 +29,16 @@ async function handleMessage(message, sender,
|
|
29
29
|
// Once messages reach this function they cannot be "ignored", they're already being handled
|
30
30
|
action) {
|
31
31
|
const { type, target, args, options = {} } = message;
|
32
|
-
const { trace = [] } = options;
|
32
|
+
const { trace = [], seq } = options;
|
33
33
|
trace.push(sender);
|
34
34
|
const meta = { trace };
|
35
35
|
let handleMessage;
|
36
36
|
if (action === "forward") {
|
37
|
-
debug(type, "๐ forwarded", { sender, target });
|
37
|
+
log.debug(type, seq, "๐ forwarded", { sender, target });
|
38
38
|
handleMessage = async () => messenger(type, meta, target, ...args);
|
39
39
|
}
|
40
40
|
else {
|
41
|
-
debug(type, "โ๏ธ received in", getContextName(), {
|
41
|
+
log.debug(type, seq, "โ๏ธ received in", getContextName(), {
|
42
42
|
sender,
|
43
43
|
args,
|
44
44
|
wasForwarded: trace.length > 1,
|
@@ -59,7 +59,7 @@ action) {
|
|
59
59
|
// and https://github.com/mozilla/webextension-polyfill/issues/210
|
60
60
|
error: serializeError(error),
|
61
61
|
}));
|
62
|
-
debug(type, "โ๏ธ responding", response);
|
62
|
+
log.debug(type, seq, "โ๏ธ responding", response);
|
63
63
|
return { ...response, __webextMessenger };
|
64
64
|
}
|
65
65
|
export function registerMethods(methods) {
|
@@ -67,8 +67,12 @@ export function registerMethods(methods) {
|
|
67
67
|
if (handlers.has(type)) {
|
68
68
|
throw new MessengerError(`Handler already set for ${type}`);
|
69
69
|
}
|
70
|
-
debug("Registered", type);
|
70
|
+
log.debug("Registered", type);
|
71
71
|
handlers.set(type, method);
|
72
72
|
}
|
73
73
|
browser.runtime.onMessage.addListener(onMessageListener);
|
74
74
|
}
|
75
|
+
/** Ensure/document that the current function was called via Messenger */
|
76
|
+
export function assertMessengerCall(_this
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function -- TypeScript already does this, it's a documentation-only call
|
78
|
+
) { }
|
package/distribution/sender.d.ts
CHANGED
@@ -2,6 +2,7 @@ import { type PublicMethod, type PublicMethodWithTarget, type Options, type Targ
|
|
2
2
|
import { type SetReturnType } from "type-fest";
|
3
3
|
export declare const errorTargetClosedEarly = "The target was closed before receiving a response";
|
4
4
|
export declare const errorTabDoesntExist = "The tab doesn't exist";
|
5
|
+
export declare const errorTabWasDiscarded = "The tab was discarded";
|
5
6
|
declare function messenger<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type]>(type: Type, options: {
|
6
7
|
isNotification: true;
|
7
8
|
}, target: Target | PageTarget, ...args: Parameters<Method>): void;
|
package/distribution/sender.js
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
import pRetry from "p-retry";
|
2
2
|
import { isBackground } from "webext-detect-page";
|
3
|
-
import { doesTabExist } from "webext-tools";
|
4
3
|
import { deserializeError } from "serialize-error";
|
5
|
-
import { isObject, MessengerError, __webextMessenger,
|
4
|
+
import { isObject, MessengerError, __webextMessenger, log } from "./shared.js";
|
6
5
|
import { handlers } from "./handlers.js";
|
7
6
|
const _errorNonExistingTarget = "Could not establish connection. Receiving end does not exist.";
|
8
7
|
// https://github.com/mozilla/webextension-polyfill/issues/384
|
9
8
|
const _errorTargetClosedEarly = "A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received";
|
10
9
|
export const errorTargetClosedEarly = "The target was closed before receiving a response";
|
11
10
|
export const errorTabDoesntExist = "The tab doesn't exist";
|
11
|
+
export const errorTabWasDiscarded = "The tab was discarded";
|
12
12
|
function isMessengerResponse(response) {
|
13
13
|
return isObject(response) && response["__webextMessenger"] === true;
|
14
14
|
}
|
15
|
+
function attemptLog(attemptCount) {
|
16
|
+
return attemptCount > 1 ? `(try: ${attemptCount})` : "";
|
17
|
+
}
|
15
18
|
function makeMessage(type, args, target, options) {
|
16
19
|
return {
|
17
20
|
__webextMessenger,
|
@@ -22,17 +25,17 @@ function makeMessage(type, args, target, options) {
|
|
22
25
|
};
|
23
26
|
}
|
24
27
|
// Do not turn this into an `async` function; Notifications must turn `void`
|
25
|
-
function manageConnection(type,
|
26
|
-
if (!
|
27
|
-
return manageMessage(type, target, sendMessage);
|
28
|
+
function manageConnection(type, { seq, isNotification }, target, sendMessage) {
|
29
|
+
if (!isNotification) {
|
30
|
+
return manageMessage(type, target, seq, sendMessage);
|
28
31
|
}
|
29
|
-
void sendMessage().catch((error) => {
|
30
|
-
debug(type, "notification failed", { error });
|
32
|
+
void sendMessage(1).catch((error) => {
|
33
|
+
log.debug(type, seq, "notification failed", { error });
|
31
34
|
});
|
32
35
|
}
|
33
|
-
async function manageMessage(type, target, sendMessage) {
|
34
|
-
const response = await pRetry(async () => {
|
35
|
-
const response = await sendMessage();
|
36
|
+
async function manageMessage(type, target, seq, sendMessage) {
|
37
|
+
const response = await pRetry(async (attemptCount) => {
|
38
|
+
const response = await sendMessage(attemptCount);
|
36
39
|
if (isMessengerResponse(response)) {
|
37
40
|
return response;
|
38
41
|
}
|
@@ -62,23 +65,29 @@ async function manageMessage(type, target, sendMessage) {
|
|
62
65
|
if (error.message === _errorTargetClosedEarly) {
|
63
66
|
throw new Error(errorTargetClosedEarly);
|
64
67
|
}
|
65
|
-
if (
|
68
|
+
if (!(
|
69
|
+
// If NONE of these conditions is true, stop retrying
|
66
70
|
// Don't retry sending to the background page unless it really hasn't loaded yet
|
67
|
-
(target.page !== "background" &&
|
71
|
+
((target.page !== "background" &&
|
72
|
+
error instanceof MessengerError) ||
|
68
73
|
// Page or its content script not yet loaded
|
69
74
|
error.message === _errorNonExistingTarget ||
|
70
75
|
// `registerMethods` not yet loaded
|
71
|
-
String(error.message).startsWith("No handlers registered in ")) {
|
72
|
-
|
73
|
-
|
74
|
-
|
76
|
+
String(error.message).startsWith("No handlers registered in ")))) {
|
77
|
+
throw error;
|
78
|
+
}
|
79
|
+
if (browser.tabs && typeof target.tabId === "number") {
|
80
|
+
try {
|
81
|
+
const tabInfo = await browser.tabs.get(target.tabId);
|
82
|
+
if (tabInfo.discarded) {
|
83
|
+
throw new Error(errorTabWasDiscarded);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
catch {
|
75
87
|
throw new Error(errorTabDoesntExist);
|
76
88
|
}
|
77
|
-
debug(type, "will retry. Attempt", error.attemptNumber);
|
78
|
-
}
|
79
|
-
else {
|
80
|
-
throw error;
|
81
89
|
}
|
90
|
+
log.debug(type, seq, "will retry. Attempt", error.attemptNumber);
|
82
91
|
},
|
83
92
|
}).catch((error) => {
|
84
93
|
if (error?.message === _errorNonExistingTarget) {
|
@@ -87,41 +96,44 @@ async function manageMessage(type, target, sendMessage) {
|
|
87
96
|
throw error;
|
88
97
|
});
|
89
98
|
if ("error" in response) {
|
90
|
-
debug(type, "โ๏ธ replied with error", response.error);
|
99
|
+
log.debug(type, seq, "โ๏ธ replied with error", response.error);
|
91
100
|
throw deserializeError(response.error);
|
92
101
|
}
|
93
|
-
debug(type, "โ๏ธ replied successfully", response.value);
|
102
|
+
log.debug(type, seq, "โ๏ธ replied successfully", response.value);
|
94
103
|
return response.value;
|
95
104
|
}
|
96
105
|
function messenger(type, options, target, ...args) {
|
106
|
+
// Not a UID. Signal / console noise compromise. They repeat every 100 seconds
|
107
|
+
options.seq = Date.now() % 100000;
|
108
|
+
const { seq } = options;
|
97
109
|
// Message goes to extension page
|
98
110
|
if ("page" in target) {
|
99
111
|
if (target.page === "background" && isBackground()) {
|
100
112
|
const handler = handlers.get(type);
|
101
113
|
if (handler) {
|
102
|
-
warn(type, "is being handled locally");
|
114
|
+
log.warn(type, seq, "is being handled locally");
|
103
115
|
return handler.apply({ trace: [] }, args);
|
104
116
|
}
|
105
117
|
throw new MessengerError("No handler registered locally for " + type);
|
106
118
|
}
|
107
|
-
const sendMessage = async () => {
|
108
|
-
debug(type, "โ๏ธ sending message to runtime");
|
119
|
+
const sendMessage = async (attemptCount) => {
|
120
|
+
log.debug(type, seq, "โ๏ธ sending message to runtime", attemptLog(attemptCount));
|
109
121
|
return browser.runtime.sendMessage(makeMessage(type, args, target, options));
|
110
122
|
};
|
111
123
|
return manageConnection(type, options, target, sendMessage);
|
112
124
|
}
|
113
125
|
// Contexts without direct Tab access must go through background
|
114
126
|
if (!browser.tabs) {
|
115
|
-
return manageConnection(type, options, target, async () => {
|
116
|
-
debug(type, "โ๏ธ sending message to runtime");
|
127
|
+
return manageConnection(type, options, target, async (attemptCount) => {
|
128
|
+
log.debug(type, seq, "โ๏ธ sending message to runtime", attemptLog(attemptCount));
|
117
129
|
return browser.runtime.sendMessage(makeMessage(type, args, target, options));
|
118
130
|
});
|
119
131
|
}
|
120
132
|
// `frameId` must be specified. If missing, the message is sent to every frame
|
121
133
|
const { tabId, frameId = 0 } = target;
|
122
134
|
// Message tab directly
|
123
|
-
return manageConnection(type, options, target, async () => {
|
124
|
-
debug(type, "โ๏ธ sending message to tab", tabId, "frame", frameId);
|
135
|
+
return manageConnection(type, options, target, async (attemptCount) => {
|
136
|
+
log.debug(type, seq, "โ๏ธ sending message to tab", tabId, "frame", frameId, attemptLog(attemptCount));
|
125
137
|
return browser.tabs.sendMessage(tabId, makeMessage(type, args, target, options), {
|
126
138
|
frameId,
|
127
139
|
});
|
package/distribution/shared.d.ts
CHANGED
@@ -10,8 +10,11 @@ export declare function isObject(value: unknown): value is Record<string, unknow
|
|
10
10
|
export declare class MessengerError extends Error {
|
11
11
|
name: string;
|
12
12
|
}
|
13
|
-
export declare const
|
14
|
-
|
13
|
+
export declare const log: {
|
14
|
+
debug: (...args: any[]) => void;
|
15
|
+
warn: (...args: any[]) => void;
|
16
|
+
};
|
17
|
+
export declare function toggleLogging(enabled: boolean): void;
|
15
18
|
export declare function isErrorObject(error: unknown): error is ErrorObject;
|
16
19
|
export declare function delay(milliseconds: number): Promise<void>;
|
17
20
|
export declare function once<Callback extends (...arguments_: unknown[]) => unknown>(function_: Callback): Callback;
|
package/distribution/shared.js
CHANGED
@@ -1,20 +1,11 @@
|
|
1
1
|
import { errorConstructors } from "serialize-error";
|
2
|
-
const logging = (() => {
|
3
|
-
try {
|
4
|
-
// @ts-expect-error it would break Webpack
|
5
|
-
return process.env.WEBEXT_MESSENGER_LOGGING === "true";
|
6
|
-
}
|
7
|
-
catch {
|
8
|
-
return false;
|
9
|
-
}
|
10
|
-
})();
|
11
|
-
function noop() {
|
12
|
-
/* */
|
13
|
-
}
|
14
2
|
export const __webextMessenger = true;
|
15
3
|
export function isObject(value) {
|
16
4
|
return typeof value === "object" && value !== null;
|
17
5
|
}
|
6
|
+
function noop() {
|
7
|
+
/* */
|
8
|
+
}
|
18
9
|
export class MessengerError extends Error {
|
19
10
|
constructor() {
|
20
11
|
super(...arguments);
|
@@ -29,8 +20,15 @@ export class MessengerError extends Error {
|
|
29
20
|
// @ts-expect-error Wrong `errorConstructors` types
|
30
21
|
errorConstructors.set("MessengerError", MessengerError);
|
31
22
|
// .bind preserves the call location in the console
|
32
|
-
|
33
|
-
|
23
|
+
const debug = console.debug.bind(console, "Messenger:");
|
24
|
+
const warn = console.warn.bind(console, "Messenger:");
|
25
|
+
export const log = { debug, warn };
|
26
|
+
export function toggleLogging(enabled) {
|
27
|
+
log.debug = enabled ? debug : noop;
|
28
|
+
log.warn = enabled ? warn : noop;
|
29
|
+
}
|
30
|
+
// Default to "no logs"
|
31
|
+
toggleLogging(false);
|
34
32
|
export function isErrorObject(error) {
|
35
33
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a type guard function and it uses ?.
|
36
34
|
return typeof error?.message === "string";
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { isBackground, isContentScript, isExtensionContext, } from "webext-detect-page";
|
2
2
|
import { messenger } from "./sender.js";
|
3
3
|
import { registerMethods } from "./receiver.js";
|
4
|
-
import {
|
4
|
+
import { log, MessengerError, once } from "./shared.js";
|
5
5
|
/**
|
6
6
|
* @file This file exists because `runtime.sendMessage` acts as a broadcast to
|
7
7
|
* all open extension pages, so the receiver needs a way to figure out if the
|
@@ -84,7 +84,7 @@ export function getActionForMessage(from, message) {
|
|
84
84
|
// Every `target` key must match `thisTarget`
|
85
85
|
const isThisTarget = compareTargets(to, thisTarget);
|
86
86
|
if (!isThisTarget) {
|
87
|
-
debug(message.type, "๐คซ ignored due to target mismatch", {
|
87
|
+
log.debug(message.type, "๐คซ ignored due to target mismatch", {
|
88
88
|
requestedTarget: to,
|
89
89
|
thisTarget,
|
90
90
|
tabDataStatus,
|
package/distribution/types.d.ts
CHANGED
@@ -28,7 +28,7 @@ export type MessengerResponse = RawMessengerResponse & {
|
|
28
28
|
/** Guarantees that the message was handled by this library */
|
29
29
|
__webextMessenger: true;
|
30
30
|
};
|
31
|
-
type Arguments =
|
31
|
+
type Arguments = unknown[];
|
32
32
|
export type Method = (this: MessengerMeta, ...args: Arguments) => Promise<unknown>;
|
33
33
|
export interface Options {
|
34
34
|
/**
|
@@ -37,6 +37,8 @@ export interface Options {
|
|
37
37
|
*/
|
38
38
|
isNotification?: boolean;
|
39
39
|
trace?: Sender[];
|
40
|
+
/** Automatically generated internally */
|
41
|
+
seq?: number;
|
40
42
|
}
|
41
43
|
export type Message<LocalArguments extends Arguments = Arguments> = {
|
42
44
|
type: keyof MessengerMethods;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "webext-messenger",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.23.1",
|
4
4
|
"description": "Browser Extension component messaging framework",
|
5
5
|
"keywords": [],
|
6
6
|
"repository": "pixiebrix/webext-messenger",
|
@@ -10,7 +10,7 @@
|
|
10
10
|
"main": "distribution/index.js",
|
11
11
|
"scripts": {
|
12
12
|
"build": "tsc",
|
13
|
-
"demo:watch": "
|
13
|
+
"demo:watch": "parcel watch --no-cache --no-hmr",
|
14
14
|
"demo:build": "parcel build --no-cache",
|
15
15
|
"prepack": "tsc --sourceMap false",
|
16
16
|
"test": "eslint . && tsc --noEmit",
|
@@ -19,30 +19,29 @@
|
|
19
19
|
"watch": "tsc --watch"
|
20
20
|
},
|
21
21
|
"dependencies": {
|
22
|
-
"p-retry": "^
|
23
|
-
"serialize-error": "^11.0.
|
24
|
-
"type-fest": "^3.
|
25
|
-
"webext-detect-page": "^4.
|
26
|
-
"webext-tools": "^1.1.0"
|
22
|
+
"p-retry": "^6.0.0",
|
23
|
+
"serialize-error": "^11.0.2",
|
24
|
+
"type-fest": "^4.3.1",
|
25
|
+
"webext-detect-page": "^4.1.1"
|
27
26
|
},
|
28
27
|
"devDependencies": {
|
29
|
-
"@parcel/config-webextension": "^2.
|
30
|
-
"@sindresorhus/tsconfig": "^
|
31
|
-
"@types/chrome": "^0.0.
|
32
|
-
"@types/tape": "^
|
33
|
-
"@types/webextension-polyfill": "^0.
|
28
|
+
"@parcel/config-webextension": "^2.6.2",
|
29
|
+
"@sindresorhus/tsconfig": "^4.0.0",
|
30
|
+
"@types/chrome": "^0.0.245",
|
31
|
+
"@types/tape": "^5.6.1",
|
32
|
+
"@types/webextension-polyfill": "^0.10.2",
|
34
33
|
"buffer": "^6.0.3",
|
35
|
-
"eslint": "^8.
|
36
|
-
"eslint-config-pixiebrix": "^0.
|
34
|
+
"eslint": "^8.50.0",
|
35
|
+
"eslint-config-pixiebrix": "^0.27.2",
|
37
36
|
"events": "^3.3.0",
|
38
37
|
"npm-run-all": "^4.1.5",
|
39
|
-
"parcel": "^2.6.
|
38
|
+
"parcel": "^2.6.2",
|
40
39
|
"path-browserify": "^1.0.1",
|
41
40
|
"process": "^0.11.10",
|
42
41
|
"stream-browserify": "^3.0.0",
|
43
|
-
"tape": "^5.
|
44
|
-
"typescript": "^
|
45
|
-
"webext-content-scripts": "^
|
42
|
+
"tape": "^5.7.0",
|
43
|
+
"typescript": "^5.2.2",
|
44
|
+
"webext-content-scripts": "^2.5.5",
|
46
45
|
"webextension-polyfill": "^0.10.0"
|
47
46
|
},
|
48
47
|
"alias": {
|
package/readme.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[badge-gzip]: https://img.shields.io/bundlephobia/minzip/webext-messenger.svg?label=gzipped
|
4
4
|
[link-bundlephobia]: https://bundlephobia.com/result?p=webext-messenger
|
5
5
|
|
6
|
-
>
|
6
|
+
> Browser Extension component messaging framework
|
7
7
|
|
8
8
|
## Install
|
9
9
|
|
@@ -17,4 +17,9 @@ import messenger from "webext-messenger";
|
|
17
17
|
|
18
18
|
## Context
|
19
19
|
|
20
|
-
- [Initial
|
20
|
+
- [Initial considertions for this library](https://github.com/pixiebrix/webext-messenger/issues/1)
|
21
|
+
|
22
|
+
|
23
|
+
## npm publishing
|
24
|
+
|
25
|
+
Collaborators can publish a new version of what's on main [via "workflow_dispatch"](https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/) under [Actions ยป Publish](https://github.com/pixiebrix/webext-messenger/actions/workflows/npm-publish.yml)
|