webext-messenger 0.13.0-8 → 0.14.1

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,10 @@
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 {};
@@ -0,0 +1,13 @@
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,3 +1,3 @@
1
- export { registerMethods } from "./receiver.js";
2
- export { getContentScriptMethod, getMethod } from "./sender.js";
3
- export { MessengerMeta, Target } from "./types.js";
1
+ export * from "./receiver.js";
2
+ export * from "./sender.js";
3
+ export * from "./types.js";
@@ -1,2 +1,8 @@
1
- export { registerMethods } from "./receiver.js";
2
- export { getContentScriptMethod, getMethod } from "./sender.js";
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
+ // 🥲
6
+ export * from "./receiver.js";
7
+ export * from "./sender.js";
8
+ export * from "./types.js";
@@ -1,5 +1,6 @@
1
+ import browser from "webextension-polyfill";
1
2
  import { serializeError } from "serialize-error";
2
- import { getContentScriptMethod } from "./sender.js";
3
+ import { messenger } from "./sender.js";
3
4
  import { handlers, isObject, MessengerError, debug, warn, __webextMessenger, } from "./shared.js";
4
5
  export function isMessengerMessage(message) {
5
6
  return (isObject(message) &&
@@ -21,8 +22,7 @@ function onMessageListener(message, sender) {
21
22
  throw new MessengerError(`Message ${type} sent to wrong context, it can't be forwarded to ${JSON.stringify(target)}`);
22
23
  }
23
24
  debug(type, "🔀 forwarded", { sender, target });
24
- const publicMethod = getContentScriptMethod(type);
25
- handleMessage = async () => publicMethod(target, ...args);
25
+ handleMessage = async () => messenger(type, {}, target, ...args);
26
26
  }
27
27
  else {
28
28
  const localHandler = handlers.get(type);
@@ -50,13 +50,8 @@ export function registerMethods(methods) {
50
50
  if (handlers.has(type)) {
51
51
  throw new MessengerError(`Handler already set for ${type}`);
52
52
  }
53
- console.debug(`Messenger: Registered`, type);
53
+ console.debug("Messenger: Registered", type);
54
54
  handlers.set(type, method);
55
55
  }
56
- if ("browser" in globalThis) {
57
- browser.runtime.onMessage.addListener(onMessageListener);
58
- }
59
- else {
60
- throw new Error("`webext-messenger` requires `webextension");
61
- }
56
+ browser.runtime.onMessage.addListener(onMessageListener);
62
57
  }
@@ -1,20 +1,13 @@
1
+ import { PublicMethod, PublicMethodWithTarget, Options, Target, PageTarget } from "./types.js";
1
2
  import { SetReturnType } from "type-fest";
2
- import { PublicMethod, PublicMethodWithTarget, Options } from "./types.js";
3
3
  export declare const errorNonExistingTarget = "Could not establish connection. Receiving end does not exist.";
4
- /**
5
- * Replicates the original method, including its types.
6
- * To be called in the sender’s end.
7
- */
8
- declare function getContentScriptMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethod extends PublicMethodWithTarget<Method>>(type: Type, options: {
4
+ declare function messenger<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type]>(type: Type, options: {
9
5
  isNotification: true;
10
- }): SetReturnType<PublicMethod, void>;
11
- declare function getContentScriptMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethod extends PublicMethodWithTarget<Method>>(type: Type, options?: Options): PublicMethod;
12
- /**
13
- * Replicates the original method, including its types.
14
- * To be called in the sender’s end.
15
- */
16
- declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends PublicMethod<Method>>(type: Type, options: {
17
- isNotification: true;
18
- }): SetReturnType<PublicMethodType, void>;
19
- declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends PublicMethod<Method>>(type: Type, options?: Options): PublicMethodType;
20
- export { getContentScriptMethod, getMethod };
6
+ }, target: Target | PageTarget, ...args: Parameters<Method>): void;
7
+ declare function messenger<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], ReturnValue extends ReturnType<Method>>(type: Type, options: Options, target: Target | PageTarget, ...args: Parameters<Method>): ReturnValue;
8
+ declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends PublicMethod<Method>>(type: Type, target: Target | PageTarget): PublicMethodType;
9
+ declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodWithDynamicTarget extends PublicMethodWithTarget<Method>>(type: Type): PublicMethodWithDynamicTarget;
10
+ declare function getNotifier<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends SetReturnType<PublicMethod<Method>, void>>(type: Type, target: Target | PageTarget): PublicMethodType;
11
+ declare function getNotifier<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodWithDynamicTarget extends SetReturnType<PublicMethodWithTarget<Method>, void>>(type: Type): PublicMethodWithDynamicTarget;
12
+ export { messenger, getMethod, getNotifier };
13
+ export declare const backgroundTarget: PageTarget;
@@ -1,3 +1,4 @@
1
+ import browser from "webextension-polyfill";
1
2
  import pRetry from "p-retry";
2
3
  import { isBackgroundPage } from "webext-detect-page";
3
4
  import { deserializeError } from "serialize-error";
@@ -45,30 +46,9 @@ async function manageMessage(type, sendMessage) {
45
46
  debug(type, "↘️ replied successfully", response.value);
46
47
  return response.value;
47
48
  }
48
- function getContentScriptMethod(type, options = {}) {
49
- const publicMethod = (target, ...args) => {
50
- // Contexts without direct Tab access must go through background
51
- if (!browser.tabs) {
52
- return manageConnection(type, options, async () => {
53
- debug(type, "↗️ sending message to runtime");
54
- return browser.runtime.sendMessage(makeMessage(type, args, target));
55
- });
56
- }
57
- // `frameId` must be specified. If missing, the message is sent to every frame
58
- const { tabId, frameId = 0 } = target;
59
- // Message tab directly
60
- return manageConnection(type, options, async () => {
61
- debug(type, "↗️ sending message to tab", tabId, "frame", frameId);
62
- return browser.tabs.sendMessage(tabId, makeMessage(type, args), {
63
- frameId,
64
- });
65
- });
66
- };
67
- return publicMethod;
68
- }
69
- function getMethod(type, options = {}) {
70
- const publicMethod = (...args) => {
71
- if (isBackgroundPage()) {
49
+ function messenger(type, options, target, ...args) {
50
+ if ("page" in target) {
51
+ if (target.page === "background" && isBackgroundPage()) {
72
52
  const handler = handlers.get(type);
73
53
  if (handler) {
74
54
  warn(type, "is being handled locally");
@@ -81,7 +61,39 @@ function getMethod(type, options = {}) {
81
61
  return browser.runtime.sendMessage(makeMessage(type, args));
82
62
  };
83
63
  return manageConnection(type, options, sendMessage);
84
- };
85
- return publicMethod;
64
+ }
65
+ // Contexts without direct Tab access must go through background
66
+ if (!browser.tabs) {
67
+ return manageConnection(type, options, async () => {
68
+ debug(type, "↗️ sending message to runtime");
69
+ return browser.runtime.sendMessage(makeMessage(type, args, target));
70
+ });
71
+ }
72
+ // `frameId` must be specified. If missing, the message is sent to every frame
73
+ const { tabId, frameId = 0 } = target;
74
+ // Message tab directly
75
+ return manageConnection(type, options, async () => {
76
+ debug(type, "↗️ sending message to tab", tabId, "frame", frameId);
77
+ return browser.tabs.sendMessage(tabId, makeMessage(type, args), {
78
+ frameId,
79
+ });
80
+ });
81
+ }
82
+ function getMethod(type, target) {
83
+ if (arguments.length === 1) {
84
+ return messenger.bind(undefined, type, {});
85
+ }
86
+ // @ts-expect-error `bind` types are junk
87
+ return messenger.bind(undefined, type, {}, target);
88
+ }
89
+ function getNotifier(type, target) {
90
+ const options = { isNotification: true };
91
+ if (arguments.length === 1) {
92
+ // @ts-expect-error `bind` types are junk
93
+ return messenger.bind(undefined, type, options);
94
+ }
95
+ // @ts-expect-error `bind` types are junk
96
+ return messenger.bind(undefined, type, options, target);
86
97
  }
87
- export { getContentScriptMethod, getMethod };
98
+ export { messenger, getMethod, getNotifier };
99
+ export const backgroundTarget = { page: "background" };
@@ -0,0 +1,12 @@
1
+ import { Target, PageTarget, MessengerMeta } from "./types.js";
2
+ declare type AnyTarget = Partial<Target & PageTarget>;
3
+ export declare function getActionForMessage(target: AnyTarget): "respond" | "forward" | "ignore";
4
+ export declare function nameThisTarget(): Promise<void>;
5
+ declare function __getTabData(this: MessengerMeta): AnyTarget;
6
+ declare global {
7
+ interface MessengerMethods {
8
+ __getTabData: typeof __getTabData;
9
+ }
10
+ }
11
+ export declare function initPrivateApi(): void;
12
+ export {};
@@ -0,0 +1,53 @@
1
+ import { isBackgroundPage, isContentScript } from "webext-detect-page";
2
+ import { messenger } from "./sender.js";
3
+ import { registerMethods } from "./receiver.js";
4
+ import { debug } from "./shared.js";
5
+ // Soft warning: Race conditions are possible.
6
+ // This CANNOT be awaited because waiting for it means "I will handle the message."
7
+ // If a message is received before this is ready, it will just have to be ignored.
8
+ let thisTarget;
9
+ //
10
+ export function getActionForMessage(target) {
11
+ if (target.page === "any") {
12
+ return "respond";
13
+ }
14
+ // Content scripts only receive messages that are meant for them. In the future
15
+ // they'll also forward them, but that still means they need to be handled here.
16
+ if (isContentScript()) {
17
+ return "respond";
18
+ }
19
+ // We're in an extension page, but the target is not one.
20
+ if (!("page" in target)) {
21
+ return "forward";
22
+ }
23
+ if (!thisTarget) {
24
+ console.warn("A message was received before this context was ready");
25
+ // If this *was* the target, then probably no one else answered
26
+ return "ignore";
27
+ }
28
+ // Every `target` key must match `thisTarget`
29
+ const isThisTarget = Object.entries(target).every(
30
+ // @ts-expect-error Optional properties
31
+ ([key, value]) => thisTarget[key] === value);
32
+ if (!isThisTarget) {
33
+ debug("The message’s target is", target, "but this is", thisTarget);
34
+ }
35
+ return isThisTarget ? "respond" : "ignore";
36
+ }
37
+ export async function nameThisTarget() {
38
+ // Same as above: CS receives messages correctly
39
+ if (!thisTarget && !isContentScript()) {
40
+ thisTarget = await messenger("__getTabData", {}, { page: "any" });
41
+ thisTarget.page = location.pathname;
42
+ }
43
+ }
44
+ function __getTabData() {
45
+ var _a, _b, _c;
46
+ 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
+ }
48
+ export function initPrivateApi() {
49
+ if (isBackgroundPage()) {
50
+ thisTarget = { page: "background" };
51
+ registerMethods({ __getTabData });
52
+ }
53
+ }
@@ -1,4 +1,4 @@
1
- /// <reference types="firefox-webext-browser" />
1
+ import { Runtime } from "webextension-polyfill";
2
2
  import { Asyncify, ValueOf } from "type-fest";
3
3
  import { ErrorObject } from "serialize-error";
4
4
  declare global {
@@ -12,7 +12,7 @@ declare type ActuallyOmitThisParameter<T> = T extends (...args: infer A) => infe
12
12
  export declare type PublicMethod<Method extends ValueOf<MessengerMethods>> = Asyncify<ActuallyOmitThisParameter<Method>>;
13
13
  export declare type PublicMethodWithTarget<Method extends ValueOf<MessengerMethods>> = WithTarget<PublicMethod<Method>>;
14
14
  export interface MessengerMeta {
15
- trace: browser.runtime.MessageSender[];
15
+ trace: Runtime.MessageSender[];
16
16
  }
17
17
  declare type RawMessengerResponse = {
18
18
  value: unknown;
@@ -48,4 +48,8 @@ export interface Target {
48
48
  tabId: number;
49
49
  frameId?: number;
50
50
  }
51
+ export interface PageTarget {
52
+ tabId?: number;
53
+ page: string;
54
+ }
51
55
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webext-messenger",
3
- "version": "0.13.0-8",
3
+ "version": "0.14.1",
4
4
  "description": "Browser Extension component messaging framework",
5
5
  "keywords": [],
6
6
  "repository": "pixiebrix/webext-messenger",
@@ -17,8 +17,8 @@
17
17
  },
18
18
  "scripts": {
19
19
  "build": "tsc",
20
- "demo:watch": "parcel watch --no-cache --no-hmr --detailed-report 0",
21
- "demo:build": "parcel build --no-cache --detailed-report 0",
20
+ "demo:watch": "parcel watch --no-cache --no-hmr",
21
+ "demo:build": "parcel build --no-cache",
22
22
  "prepack": "tsc --sourceMap false",
23
23
  "test": "eslint . && tsc --noEmit",
24
24
  "lint": "eslint .",
@@ -45,6 +45,21 @@
45
45
  "plugin:unicorn/recommended"
46
46
  ],
47
47
  "rules": {
48
+ "no-restricted-imports": [
49
+ "error",
50
+ {
51
+ "paths": [
52
+ {
53
+ "name": "./index",
54
+ "message": "The index file is only used to re-export internal files. Use direct imports instead."
55
+ }
56
+ ]
57
+ }
58
+ ],
59
+ "import/extensions": [
60
+ "error",
61
+ "always"
62
+ ],
48
63
  "import/no-unresolved": "off",
49
64
  "unicorn/filename-case": [
50
65
  "error",
@@ -77,37 +92,42 @@
77
92
  "@typescript-eslint/no-explicit-any": "off",
78
93
  "@typescript-eslint/no-unsafe-member-access": "off"
79
94
  }
95
+ },
96
+ {
97
+ "files": [
98
+ "source/test/**/*"
99
+ ],
100
+ "rules": {
101
+ "import/extensions": "off"
102
+ }
80
103
  }
81
- ],
82
- "globals": {
83
- "chrome": true
84
- }
104
+ ]
85
105
  },
86
106
  "dependencies": {
87
- "p-retry": "^4.6.1",
88
- "serialize-error": "^8.1.0",
89
- "type-fest": "^2.5.1",
90
- "webext-detect-page": "^3.0.2",
107
+ "p-retry": "^5.0.0",
108
+ "serialize-error": "^9.0.0",
109
+ "type-fest": "^2.6.0",
110
+ "webext-detect-page": "^3.1.0",
91
111
  "webextension-polyfill": "^0.8.0"
92
112
  },
93
113
  "devDependencies": {
94
- "@parcel/config-webextension": "^2.0.0",
114
+ "@parcel/config-webextension": "^2.0.1",
95
115
  "@sindresorhus/tsconfig": "^2.0.0",
96
- "@types/chrome": "^0.0.159",
97
- "@types/firefox-webext-browser": "^94.0.0",
98
- "@typescript-eslint/eslint-plugin": "^5.1.0",
99
- "@typescript-eslint/parser": "^5.1.0",
100
- "eslint": "^8.1.0",
116
+ "@types/chrome": "^0.0.164",
117
+ "@types/tape": "^4.13.2",
118
+ "@types/webextension-polyfill": "^0.8.2",
119
+ "@typescript-eslint/eslint-plugin": "^5.4.0",
120
+ "@typescript-eslint/parser": "^5.4.0",
121
+ "eslint": "^8.3.0",
101
122
  "eslint-config-prettier": "^8.3.0",
102
123
  "eslint-config-xo": "^0.39.0",
103
- "eslint-config-xo-typescript": "^0.45.2",
104
- "eslint-plugin-import": "^2.25.2",
105
- "eslint-plugin-unicorn": "^37.0.1",
106
- "fresh-tape": "^5.3.1",
124
+ "eslint-config-xo-typescript": "^0.47.1",
125
+ "eslint-plugin-import": "^2.25.3",
126
+ "eslint-plugin-unicorn": "^39.0.0",
107
127
  "npm-run-all": "^4.1.5",
108
- "parcel": "^2.0.0",
109
- "typescript": "^4.4.4",
110
- "xo": "^0.45.0"
128
+ "parcel": "^2.0.1",
129
+ "tape": "^5.3.2",
130
+ "typescript": "^4.5.2"
111
131
  },
112
132
  "targets": {
113
133
  "main": false,