webext-messenger 0.7.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,15 @@
1
1
  /// <reference types="firefox-webext-browser" />
2
- import { Asyncify } from "type-fest";
2
+ import { Asyncify, SetReturnType, ValueOf } from "type-fest";
3
3
  declare global {
4
4
  interface MessengerMethods {
5
5
  _: Method;
6
6
  }
7
7
  }
8
+ declare type WithTarget<TMethod> = TMethod extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: Target, ...args: PreviousArguments) => TReturnValue : never;
8
9
  declare type ActuallyOmitThisParameter<T> = T extends (...args: infer A) => infer R ? (...args: A) => R : T;
10
+ /** Removes the `this` type and ensure it's always Promised */
11
+ declare type PublicMethod<TMethod extends ValueOf<MessengerMethods>> = Asyncify<ActuallyOmitThisParameter<TMethod>>;
12
+ declare type PublicMethodWithTarget<TMethod extends ValueOf<MessengerMethods>> = WithTarget<PublicMethod<TMethod>>;
9
13
  export declare type MessengerMeta = browser.runtime.MessageSender;
10
14
  declare type Arguments = any[];
11
15
  declare type Method = (this: MessengerMeta, ...args: Arguments) => Promise<unknown>;
@@ -13,16 +17,28 @@ export interface Target {
13
17
  tabId: number;
14
18
  frameId?: number;
15
19
  }
16
- declare type WithTarget<TMethod> = TMethod extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: Target, ...args: PreviousArguments) => TReturnValue : never;
20
+ interface Options {
21
+ /**
22
+ * "Notifications" won't await the response, return values, attempt retries, nor throw errors
23
+ * @default false
24
+ */
25
+ isNotification?: boolean;
26
+ }
17
27
  /**
18
28
  * Replicates the original method, including its types.
19
29
  * To be called in the sender’s end.
20
30
  */
21
- export declare function getContentScriptMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], PublicMethod extends WithTarget<Asyncify<ActuallyOmitThisParameter<TMethod>>>>(type: TType): PublicMethod;
31
+ declare function getContentScriptMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], TPublicMethod extends PublicMethodWithTarget<TMethod>>(type: TType, options: {
32
+ isNotification: true;
33
+ }): SetReturnType<TPublicMethod, void>;
34
+ declare function getContentScriptMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], TPublicMethod extends PublicMethodWithTarget<TMethod>>(type: TType, options?: Options): TPublicMethod;
22
35
  /**
23
36
  * Replicates the original method, including its types.
24
37
  * To be called in the sender’s end.
25
38
  */
26
- export declare function getMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], PublicMethod extends Asyncify<ActuallyOmitThisParameter<TMethod>>>(type: TType): PublicMethod;
27
- export declare function registerMethods(methods: Partial<MessengerMethods>): void;
28
- export {};
39
+ declare function getMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], TPublicMethod extends PublicMethod<TMethod>>(type: TType, options: {
40
+ isNotification: true;
41
+ }): SetReturnType<TPublicMethod, void>;
42
+ declare function getMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], TPublicMethod extends PublicMethod<TMethod>>(type: TType, options?: Options): TPublicMethod;
43
+ declare function registerMethods(methods: Partial<MessengerMethods>): void;
44
+ export { getMethod, getContentScriptMethod, registerMethods };
@@ -1,3 +1,4 @@
1
+ import pRetry from "p-retry";
1
2
  import { deserializeError, serializeError } from "serialize-error";
2
3
  const __webext_messenger__ = true;
3
4
  function isObject(value) {
@@ -28,7 +29,28 @@ async function handleMessage(message, sender) {
28
29
  console.debug(`Messenger:`, message.type, "responds", response);
29
30
  return { ...response, __webext_messenger__ };
30
31
  }
31
- async function handleResponse(response) {
32
+ // Do not turn this into an `async` function; Notifications must turn `void`
33
+ function manageConnection(type, options, sendMessage) {
34
+ if (!options.isNotification) {
35
+ return manageMessage(type, sendMessage);
36
+ }
37
+ void sendMessage().catch((error) => {
38
+ console.debug("Messenger:", type, "notification failed", { error });
39
+ });
40
+ }
41
+ async function manageMessage(type, sendMessage) {
42
+ const response = await pRetry(sendMessage, {
43
+ minTimeout: 100,
44
+ factor: 1.3,
45
+ maxRetryTime: 4000,
46
+ onFailedAttempt(error) {
47
+ if ((error === null || error === void 0 ? void 0 : error.message) !==
48
+ "Could not establish connection. Receiving end does not exist.") {
49
+ throw error;
50
+ }
51
+ console.debug("Messenger:", type, "will retry");
52
+ },
53
+ });
32
54
  if (!isMessengerResponse(response)) {
33
55
  // If the response is `undefined`, `registerMethod` was never called
34
56
  throw new Error("No handlers registered in receiving end");
@@ -45,43 +67,33 @@ function onMessageListener(message, sender) {
45
67
  }
46
68
  // TODO: Add test for this eventuality: ignore unrelated messages
47
69
  }
48
- /**
49
- * Replicates the original method, including its types.
50
- * To be called in the sender’s end.
51
- */
52
- export function getContentScriptMethod(type) {
53
- const publicMethod = async (target, ...args) => {
54
- var _a;
55
- const response = await browser.tabs.sendMessage(target.tabId, {
56
- // Guarantees that a message is meant to be handled by this library
57
- __webext_messenger__,
58
- type,
59
- args,
60
- }, {
61
- // Must be specified. If missing, the message would be sent to every frame
62
- frameId: (_a = target.frameId) !== null && _a !== void 0 ? _a : 0,
63
- });
64
- return handleResponse(response);
70
+ function makeMessage(type, args) {
71
+ return {
72
+ __webext_messenger__,
73
+ type,
74
+ args,
75
+ };
76
+ }
77
+ function getContentScriptMethod(type, options = {}) {
78
+ const publicMethod = (target, ...args) => {
79
+ const sendMessage = async () => {
80
+ var _a;
81
+ return browser.tabs.sendMessage(target.tabId, makeMessage(type, args),
82
+ // `frameId` must be specified. If missing, the message would be sent to every frame
83
+ { frameId: (_a = target.frameId) !== null && _a !== void 0 ? _a : 0 });
84
+ };
85
+ return manageConnection(type, options, sendMessage);
65
86
  };
66
87
  return publicMethod;
67
88
  }
68
- /**
69
- * Replicates the original method, including its types.
70
- * To be called in the sender’s end.
71
- */
72
- export function getMethod(type) {
73
- const publicMethod = async (...args) => {
74
- const response = await browser.runtime.sendMessage({
75
- // Guarantees that a message is meant to be handled by this library
76
- __webext_messenger__,
77
- type,
78
- args,
79
- });
80
- return handleResponse(response);
89
+ function getMethod(type, options = {}) {
90
+ const publicMethod = (...args) => {
91
+ const sendMessage = async () => browser.runtime.sendMessage(makeMessage(type, args));
92
+ return manageConnection(type, options, sendMessage);
81
93
  };
82
94
  return publicMethod;
83
95
  }
84
- export function registerMethods(methods) {
96
+ function registerMethods(methods) {
85
97
  for (const [type, method] of Object.entries(methods)) {
86
98
  if (handlers.has(type)) {
87
99
  throw new Error(`Handler already set for ${type}`);
@@ -91,3 +103,4 @@ export function registerMethods(methods) {
91
103
  }
92
104
  browser.runtime.onMessage.addListener(onMessageListener);
93
105
  }
106
+ export { getMethod, getContentScriptMethod, registerMethods };
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "webext-messenger",
3
- "version": "0.7.1",
3
+ "version": "0.9.2",
4
4
  "description": "Browser Extension component messaging framework",
5
5
  "keywords": [],
6
6
  "repository": "pixiebrix/extension-messaging",
7
7
  "license": "MIT",
8
8
  "author": "Federico Brigante for PixieBrix <federico@pixiebrix.com> (https://www.pixiebrix.com)",
9
9
  "type": "module",
10
- "main": "distribution/index.js",
10
+ "exports": "./distribution/index.js",
11
+ "types": "distribution/index.d.ts",
11
12
  "files": [
12
13
  "distribution/index.js",
13
14
  "distribution/index.d.ts"
@@ -42,18 +43,19 @@
42
43
  "plugin:unicorn/recommended"
43
44
  ],
44
45
  "rules": {
45
- "@typescript-eslint/no-require-imports": "off",
46
46
  "@typescript-eslint/no-unsafe-member-access": "off",
47
- "import/extensions": "off",
48
- "import/no-unassigned-import": "off",
49
- "node/file-extension-in-import": "off",
50
47
  "unicorn/filename-case": [
51
48
  "error",
52
49
  {
53
50
  "case": "camelCase"
54
51
  }
55
52
  ],
56
- "unicorn/prefer-module": "off",
53
+ "unicorn/no-useless-undefined": [
54
+ "error",
55
+ {
56
+ "checkArguments": false
57
+ }
58
+ ],
57
59
  "unicorn/prevent-abbreviations": [
58
60
  "error",
59
61
  {
@@ -63,11 +65,22 @@
63
65
  }
64
66
  ]
65
67
  },
68
+ "overrides": [
69
+ {
70
+ "files": [
71
+ "*.test.ts"
72
+ ],
73
+ "rules": {
74
+ "@typescript-eslint/no-non-null-assertion": "off"
75
+ }
76
+ }
77
+ ],
66
78
  "globals": {
67
79
  "chrome": true
68
80
  }
69
81
  },
70
82
  "dependencies": {
83
+ "p-retry": "^4.6.1",
71
84
  "serialize-error": "^8.1.0",
72
85
  "type-fest": "^2.3.4",
73
86
  "webext-detect-page": "^3.0.2",
@@ -76,20 +89,20 @@
76
89
  "devDependencies": {
77
90
  "@parcel/config-webextension": "^2.0.0-rc.0",
78
91
  "@sindresorhus/tsconfig": "^2.0.0",
79
- "@types/chrome": "^0.0.154",
92
+ "@types/chrome": "^0.0.158",
80
93
  "@types/firefox-webext-browser": "^82.0.1",
81
- "@typescript-eslint/eslint-plugin": "^4.30.0",
82
- "@typescript-eslint/parser": "^4.30.0",
94
+ "@typescript-eslint/eslint-plugin": "^4.31.2",
95
+ "@typescript-eslint/parser": "^4.31.2",
83
96
  "eslint": "^7.32.0",
84
97
  "eslint-config-prettier": "^8.3.0",
85
98
  "eslint-config-xo": "^0.38.0",
86
99
  "eslint-config-xo-typescript": "^0.44.0",
87
100
  "eslint-plugin-import": "^2.24.2",
88
- "eslint-plugin-unicorn": "^35.0.0",
101
+ "eslint-plugin-unicorn": "^36.0.0",
89
102
  "fresh-tape": "^5.3.1",
90
103
  "npm-run-all": "^4.1.5",
91
104
  "parcel": "^2.0.0-rc.0",
92
- "typescript": "^4.4.2",
105
+ "typescript": "^4.4.3",
93
106
  "xo": "^0.44.0"
94
107
  },
95
108
  "targets": {