webext-messenger 0.15.0-0 → 0.15.0-4
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.
- package/distribution/index.js +6 -1
- package/distribution/receiver.js +2 -2
- package/distribution/sender.js +12 -6
- package/distribution/shared.d.ts +10 -0
- package/distribution/shared.js +10 -0
- package/distribution/thisTarget.d.ts +7 -3
- package/distribution/thisTarget.js +20 -10
- package/distribution/types.d.ts +1 -1
- package/package.json +43 -19
- package/distribution/api.d.ts +0 -10
- package/distribution/api.js +0 -13
- package/distribution/namedTargets.d.ts +0 -14
- package/distribution/namedTargets.js +0 -40
package/distribution/index.js
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
// Imports must use the .js extension because of ESM requires it and TS refuses to rewrite .ts to .js
|
2
|
+
// This works in TS even if the .js doesn't exist, but it breaks Parcel (the tests builder)
|
3
|
+
// For this reason, there's an `alias` field in package.json to redirect these imports.
|
4
|
+
// If you see "@parcel/resolver-default: Cannot load file './yourNewFile.js'" you need to add it to the `alias` list
|
5
|
+
// 🥲
|
1
6
|
export * from "./receiver.js";
|
2
7
|
export * from "./sender.js";
|
3
8
|
export * from "./types.js";
|
4
|
-
import { initPrivateApi } from "./thisTarget";
|
9
|
+
import { initPrivateApi } from "./thisTarget.js";
|
5
10
|
initPrivateApi();
|
package/distribution/receiver.js
CHANGED
@@ -3,7 +3,7 @@ import { serializeError } from "serialize-error";
|
|
3
3
|
import { messenger } from "./sender.js";
|
4
4
|
import { handlers, isObject, MessengerError, debug, __webextMessenger, } from "./shared.js";
|
5
5
|
import { getContextName } from "webext-detect-page";
|
6
|
-
import { getActionForMessage, nameThisTarget } from "./thisTarget";
|
6
|
+
import { getActionForMessage, nameThisTarget } from "./thisTarget.js";
|
7
7
|
export function isMessengerMessage(message) {
|
8
8
|
return (isObject(message) &&
|
9
9
|
typeof message["type"] === "string" &&
|
@@ -17,7 +17,7 @@ function onMessageListener(message, sender) {
|
|
17
17
|
return;
|
18
18
|
}
|
19
19
|
// Target check must be synchronous (`await` means we're handing the message)
|
20
|
-
const action = getActionForMessage(message.target);
|
20
|
+
const action = getActionForMessage(sender, message.target);
|
21
21
|
if (action === "ignore") {
|
22
22
|
return;
|
23
23
|
}
|
package/distribution/sender.js
CHANGED
@@ -26,20 +26,26 @@ function manageConnection(type, options, sendMessage) {
|
|
26
26
|
});
|
27
27
|
}
|
28
28
|
async function manageMessage(type, sendMessage) {
|
29
|
-
const response = await pRetry(
|
29
|
+
const response = await pRetry(async () => {
|
30
|
+
const response = await sendMessage();
|
31
|
+
if (!isMessengerResponse(response)) {
|
32
|
+
throw new MessengerError(`No handler registered for ${type} in the receiving end`);
|
33
|
+
}
|
34
|
+
return response;
|
35
|
+
}, {
|
30
36
|
minTimeout: 100,
|
31
37
|
factor: 1.3,
|
32
38
|
maxRetryTime: 4000,
|
33
39
|
onFailedAttempt(error) {
|
34
|
-
if (
|
40
|
+
if (error instanceof MessengerError ||
|
41
|
+
String(error.message).startsWith(errorNonExistingTarget)) {
|
42
|
+
debug(type, "will retry. Attempt", error.attemptNumber);
|
43
|
+
}
|
44
|
+
else {
|
35
45
|
throw error;
|
36
46
|
}
|
37
|
-
debug(type, "will retry. Attempt", error.attemptNumber);
|
38
47
|
},
|
39
48
|
});
|
40
|
-
if (!isMessengerResponse(response)) {
|
41
|
-
throw new MessengerError(`No handler registered for ${type} in the receiving end`);
|
42
|
-
}
|
43
49
|
if ("error" in response) {
|
44
50
|
debug(type, "↘️ replied with error", response.error);
|
45
51
|
throw deserializeError(response.error);
|
package/distribution/shared.d.ts
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
+
import { JsonObject } from "type-fest";
|
1
2
|
import { Method } from "./types.js";
|
3
|
+
declare type ErrorObject = {
|
4
|
+
name?: string;
|
5
|
+
stack?: string;
|
6
|
+
message?: string;
|
7
|
+
code?: string;
|
8
|
+
} & JsonObject;
|
2
9
|
export declare const __webextMessenger = true;
|
3
10
|
export declare function isObject(value: unknown): value is Record<string, unknown>;
|
4
11
|
export declare class MessengerError extends Error {
|
@@ -7,3 +14,6 @@ export declare class MessengerError extends Error {
|
|
7
14
|
export declare const handlers: Map<string, Method>;
|
8
15
|
export declare const debug: (...args: any[]) => void;
|
9
16
|
export declare const warn: (...args: any[]) => void;
|
17
|
+
export declare function isErrorObject(error: unknown): error is ErrorObject;
|
18
|
+
export declare function delay(milliseconds: number): Promise<void>;
|
19
|
+
export {};
|
package/distribution/shared.js
CHANGED
@@ -17,3 +17,13 @@ export const handlers = new Map();
|
|
17
17
|
// .bind preserves the call location in the console
|
18
18
|
export const debug = console.debug.bind(console, "Messenger:");
|
19
19
|
export const warn = console.warn.bind(console, "Messenger:");
|
20
|
+
export function isErrorObject(error) {
|
21
|
+
var _a;
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a type guard function and it uses ?.
|
23
|
+
return typeof ((_a = error) === null || _a === void 0 ? void 0 : _a.message) === "string";
|
24
|
+
}
|
25
|
+
export async function delay(milliseconds) {
|
26
|
+
return new Promise((resolve) => {
|
27
|
+
setTimeout(resolve, milliseconds);
|
28
|
+
});
|
29
|
+
}
|
@@ -1,6 +1,10 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
|
1
|
+
import { MessengerMeta, Sender } from "./types.js";
|
2
|
+
interface AnyTarget {
|
3
|
+
tabId?: number | "this";
|
4
|
+
frameId?: number;
|
5
|
+
page?: string;
|
6
|
+
}
|
7
|
+
export declare function getActionForMessage(from: Sender, { ...to }: AnyTarget): "respond" | "forward" | "ignore";
|
4
8
|
export declare function nameThisTarget(): Promise<void>;
|
5
9
|
declare function __getTabData(this: MessengerMeta): AnyTarget;
|
6
10
|
declare global {
|
@@ -1,14 +1,15 @@
|
|
1
|
-
import { isBackgroundPage, isContentScript } from "webext-detect-page";
|
2
|
-
import { messenger } from "./
|
1
|
+
import { isBackgroundPage, isContentScript, isExtensionContext, } from "webext-detect-page";
|
2
|
+
import { messenger } from "./sender.js";
|
3
3
|
import { registerMethods } from "./receiver.js";
|
4
4
|
import { debug } from "./shared.js";
|
5
5
|
// Soft warning: Race conditions are possible.
|
6
6
|
// This CANNOT be awaited because waiting for it means "I will handle the message."
|
7
7
|
// If a message is received before this is ready, it will just have to be ignored.
|
8
8
|
let thisTarget;
|
9
|
-
//
|
10
|
-
|
11
|
-
|
9
|
+
export function getActionForMessage(from, { ...to } // Clone object because we're editing it
|
10
|
+
) {
|
11
|
+
var _a;
|
12
|
+
if (to.page === "any") {
|
12
13
|
return "respond";
|
13
14
|
}
|
14
15
|
// Content scripts only receive messages that are meant for them. In the future
|
@@ -17,7 +18,7 @@ export function getActionForMessage(target) {
|
|
17
18
|
return "respond";
|
18
19
|
}
|
19
20
|
// We're in an extension page, but the target is not one.
|
20
|
-
if (!
|
21
|
+
if (!to.page) {
|
21
22
|
return "forward";
|
22
23
|
}
|
23
24
|
if (!thisTarget) {
|
@@ -25,18 +26,24 @@ export function getActionForMessage(target) {
|
|
25
26
|
// If this *was* the target, then probably no one else answered
|
26
27
|
return "ignore";
|
27
28
|
}
|
29
|
+
// If requests "this" tab, then set it to allow the next condition
|
30
|
+
if (to.tabId === "this" && thisTarget.tabId === ((_a = from.tab) === null || _a === void 0 ? void 0 : _a.id)) {
|
31
|
+
to.tabId = thisTarget.tabId;
|
32
|
+
}
|
28
33
|
// Every `target` key must match `thisTarget`
|
29
|
-
const isThisTarget = Object.entries(
|
34
|
+
const isThisTarget = Object.entries(to).every(
|
30
35
|
// @ts-expect-error Optional properties
|
31
36
|
([key, value]) => thisTarget[key] === value);
|
32
37
|
if (!isThisTarget) {
|
33
|
-
debug("The message’s target is",
|
38
|
+
debug("The message’s target is", to, "but this is", thisTarget);
|
34
39
|
}
|
35
40
|
return isThisTarget ? "respond" : "ignore";
|
36
41
|
}
|
42
|
+
let nameRequested = false;
|
37
43
|
export async function nameThisTarget() {
|
38
44
|
// Same as above: CS receives messages correctly
|
39
|
-
if (!thisTarget && !isContentScript()) {
|
45
|
+
if (!nameRequested && !thisTarget && !isContentScript()) {
|
46
|
+
nameRequested = true;
|
40
47
|
thisTarget = await messenger("__getTabData", {}, { page: "any" });
|
41
48
|
thisTarget.page = location.pathname;
|
42
49
|
}
|
@@ -46,8 +53,11 @@ function __getTabData() {
|
|
46
53
|
return { tabId: (_b = (_a = this.trace[0]) === null || _a === void 0 ? void 0 : _a.tab) === null || _b === void 0 ? void 0 : _b.id, frameId: (_c = this.trace[0]) === null || _c === void 0 ? void 0 : _c.frameId };
|
47
54
|
}
|
48
55
|
export function initPrivateApi() {
|
56
|
+
if (isExtensionContext()) {
|
57
|
+
// Any context can handler this message
|
58
|
+
registerMethods({ __getTabData });
|
59
|
+
}
|
49
60
|
if (isBackgroundPage()) {
|
50
61
|
thisTarget = { page: "background" };
|
51
|
-
registerMethods({ __getTabData });
|
52
62
|
}
|
53
63
|
}
|
package/distribution/types.d.ts
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "webext-messenger",
|
3
|
-
"version": "0.15.0-
|
3
|
+
"version": "0.15.0-4",
|
4
4
|
"description": "Browser Extension component messaging framework",
|
5
5
|
"keywords": [],
|
6
6
|
"repository": "pixiebrix/webext-messenger",
|
@@ -13,7 +13,8 @@
|
|
13
13
|
"./source/sender.js": "./source/sender.ts",
|
14
14
|
"./source/receiver.js": "./source/receiver.ts",
|
15
15
|
"./source/types.js": "./source/types.ts",
|
16
|
-
"./source/shared.js": "./source/shared.ts"
|
16
|
+
"./source/shared.js": "./source/shared.ts",
|
17
|
+
"./source/thisTarget.js": "./source/thisTarget.ts"
|
17
18
|
},
|
18
19
|
"scripts": {
|
19
20
|
"build": "tsc",
|
@@ -45,6 +46,21 @@
|
|
45
46
|
"plugin:unicorn/recommended"
|
46
47
|
],
|
47
48
|
"rules": {
|
49
|
+
"no-restricted-imports": [
|
50
|
+
"error",
|
51
|
+
{
|
52
|
+
"paths": [
|
53
|
+
{
|
54
|
+
"name": "./index",
|
55
|
+
"message": "The index file is only used to re-export internal files. Use direct imports instead."
|
56
|
+
}
|
57
|
+
]
|
58
|
+
}
|
59
|
+
],
|
60
|
+
"import/extensions": [
|
61
|
+
"error",
|
62
|
+
"always"
|
63
|
+
],
|
48
64
|
"import/no-unresolved": "off",
|
49
65
|
"unicorn/filename-case": [
|
50
66
|
"error",
|
@@ -77,35 +93,43 @@
|
|
77
93
|
"@typescript-eslint/no-explicit-any": "off",
|
78
94
|
"@typescript-eslint/no-unsafe-member-access": "off"
|
79
95
|
}
|
96
|
+
},
|
97
|
+
{
|
98
|
+
"files": [
|
99
|
+
"source/test/**/*"
|
100
|
+
],
|
101
|
+
"rules": {
|
102
|
+
"import/extensions": "off"
|
103
|
+
}
|
80
104
|
}
|
81
105
|
]
|
82
106
|
},
|
83
107
|
"dependencies": {
|
84
|
-
"p-retry": "^
|
85
|
-
"serialize-error": "^
|
86
|
-
"type-fest": "^2.
|
108
|
+
"p-retry": "^5.0.0",
|
109
|
+
"serialize-error": "^9.0.0",
|
110
|
+
"type-fest": "^2.6.0",
|
87
111
|
"webext-detect-page": "^3.1.0",
|
88
112
|
"webextension-polyfill": "^0.8.0"
|
89
113
|
},
|
90
114
|
"devDependencies": {
|
91
|
-
"@parcel/config-webextension": "^2.0.
|
115
|
+
"@parcel/config-webextension": "^2.0.1",
|
92
116
|
"@sindresorhus/tsconfig": "^2.0.0",
|
93
|
-
"@types/chrome": "^0.0.
|
94
|
-
"@types/
|
95
|
-
"@
|
96
|
-
"@typescript-eslint/
|
97
|
-
"eslint": "^
|
117
|
+
"@types/chrome": "^0.0.164",
|
118
|
+
"@types/tape": "^4.13.2",
|
119
|
+
"@types/webextension-polyfill": "^0.8.2",
|
120
|
+
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
121
|
+
"@typescript-eslint/parser": "^5.4.0",
|
122
|
+
"eslint": "^8.3.0",
|
98
123
|
"eslint-config-prettier": "^8.3.0",
|
99
124
|
"eslint-config-xo": "^0.39.0",
|
100
|
-
"eslint-config-xo-typescript": "^0.
|
101
|
-
"eslint-plugin-import": "^2.25.
|
102
|
-
"eslint-plugin-unicorn": "^
|
103
|
-
"fresh-tape": "^5.3.1",
|
125
|
+
"eslint-config-xo-typescript": "^0.47.1",
|
126
|
+
"eslint-plugin-import": "^2.25.3",
|
127
|
+
"eslint-plugin-unicorn": "^39.0.0",
|
104
128
|
"npm-run-all": "^4.1.5",
|
105
|
-
"parcel": "^2.0.
|
106
|
-
"
|
107
|
-
"
|
108
|
-
"
|
129
|
+
"parcel": "^2.0.1",
|
130
|
+
"tape": "^5.3.2",
|
131
|
+
"typescript": "^4.5.2",
|
132
|
+
"webext-content-scripts": "^0.10.1"
|
109
133
|
},
|
110
134
|
"targets": {
|
111
135
|
"main": false,
|
package/distribution/api.d.ts
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
import { Target, MessengerMeta } from "./types";
|
2
|
-
declare function __getTabData(this: MessengerMeta): Target;
|
3
|
-
declare global {
|
4
|
-
interface MessengerMethods {
|
5
|
-
__getTabData: typeof __getTabData;
|
6
|
-
}
|
7
|
-
}
|
8
|
-
export declare const getTabData: () => Promise<Target>;
|
9
|
-
export declare function initPrivateApi(): void;
|
10
|
-
export {};
|
package/distribution/api.js
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
import { getMethod } from "./sender";
|
2
|
-
import { registerMethods } from "./receiver";
|
3
|
-
import { isBackgroundPage } from "webext-detect-page";
|
4
|
-
function __getTabData() {
|
5
|
-
return { tabId: this.trace[0].tab.id, frameId: this.trace[0].frameId };
|
6
|
-
}
|
7
|
-
// First page to respond wins. Any context has this piece of information.
|
8
|
-
export const getTabData = getMethod("__getTabData", { page: "any" });
|
9
|
-
export function initPrivateApi() {
|
10
|
-
if (isBackgroundPage()) {
|
11
|
-
registerMethods({ __getTabData });
|
12
|
-
}
|
13
|
-
}
|
@@ -1,14 +0,0 @@
|
|
1
|
-
/// <reference types="firefox-webext-browser" />
|
2
|
-
import { NamedTarget, Target, MessengerMeta } from "./types.js";
|
3
|
-
declare global {
|
4
|
-
interface MessengerMethods {
|
5
|
-
__webextMessengerTargetRegistration: typeof _registerTarget;
|
6
|
-
}
|
7
|
-
}
|
8
|
-
export declare function resolveNamedTarget(target: NamedTarget, sender?: browser.runtime.MessageSender): Target;
|
9
|
-
export declare const targets: Map<string, Target>;
|
10
|
-
/** Register the current context so that it can be targeted with a name */
|
11
|
-
export declare const registerTarget: (name: string) => Promise<void>;
|
12
|
-
declare function _registerTarget(this: MessengerMeta, name: string): Promise<void>;
|
13
|
-
export declare function initTargets(): void;
|
14
|
-
export {};
|
@@ -1,40 +0,0 @@
|
|
1
|
-
import { isBackgroundPage } from "webext-detect-page";
|
2
|
-
import { errorNonExistingTarget, getMethod } from "./sender.js";
|
3
|
-
import { registerMethods } from "./receiver.js";
|
4
|
-
export function resolveNamedTarget(target, sender) {
|
5
|
-
var _a;
|
6
|
-
if (!isBackgroundPage()) {
|
7
|
-
throw new Error("Named targets can only be resolved in the background page");
|
8
|
-
}
|
9
|
-
const { name, tabId = (_a = sender === null || sender === void 0 ? void 0 : sender.tab) === null || _a === void 0 ? void 0 : _a.id, // If not specified, try to use the sender’s
|
10
|
-
} = target;
|
11
|
-
if (typeof tabId === "undefined") {
|
12
|
-
throw new TypeError(`${errorNonExistingTarget} The tab ID was not specified nor it was automatically determinable.`);
|
13
|
-
}
|
14
|
-
const resolvedTarget = targets.get(`${tabId}%${name}`);
|
15
|
-
if (!resolvedTarget) {
|
16
|
-
throw new Error(`${errorNonExistingTarget} Target named ${name} not registered for tab ${tabId}.`);
|
17
|
-
}
|
18
|
-
return resolvedTarget;
|
19
|
-
}
|
20
|
-
// TODO: Remove targets after tab closes to avoid "memory leaks"
|
21
|
-
export const targets = new Map();
|
22
|
-
/** Register the current context so that it can be targeted with a name */
|
23
|
-
export const registerTarget = getMethod("__webextMessengerTargetRegistration");
|
24
|
-
async function _registerTarget(name) {
|
25
|
-
const sender = this.trace[0];
|
26
|
-
const tabId = sender.tab.id;
|
27
|
-
const { frameId } = sender;
|
28
|
-
targets.set(`${tabId}%${name}`, {
|
29
|
-
tabId,
|
30
|
-
frameId,
|
31
|
-
});
|
32
|
-
console.debug(`Messenger: Target "${name}" registered for tab ${tabId}`);
|
33
|
-
}
|
34
|
-
export function initTargets() {
|
35
|
-
if (isBackgroundPage()) {
|
36
|
-
registerMethods({
|
37
|
-
__webextMessengerTargetRegistration: _registerTarget,
|
38
|
-
});
|
39
|
-
}
|
40
|
-
}
|