webext-messenger 0.13.0-8 → 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,