webext-messenger 0.19.0 → 0.20.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ import { __getTabData } from "./thisTarget.js";
2
+ import { Method } from "./types.js";
3
+ declare global {
4
+ interface MessengerMethods {
5
+ __getTabData: typeof __getTabData;
6
+ }
7
+ }
8
+ export declare const privateMethods: (typeof __getTabData)[];
9
+ export declare const handlers: Map<string, Method>;
10
+ export declare function didUserRegisterMethods(): boolean;
@@ -0,0 +1,6 @@
1
+ import { __getTabData } from "./thisTarget.js";
2
+ export const privateMethods = [__getTabData];
3
+ export const handlers = new Map();
4
+ export function didUserRegisterMethods() {
5
+ return handlers.size > privateMethods.length;
6
+ }
@@ -1,8 +1,4 @@
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
+ // Imports must use the .js extension because ESM requires it and TS refuses to rewrite .ts to .js
6
2
  export * from "./receiver.js";
7
3
  export * from "./sender.js";
8
4
  export * from "./types.js";
@@ -1,8 +1,9 @@
1
1
  import { serializeError } from "serialize-error";
2
- import { messenger } from "./sender.js";
3
- import { handlers, isObject, MessengerError, debug, __webextMessenger, } from "./shared.js";
4
2
  import { getContextName, isBackground } from "webext-detect-page";
3
+ import { messenger } from "./sender.js";
4
+ import { isObject, MessengerError, debug, __webextMessenger, } from "./shared.js";
5
5
  import { getActionForMessage, nameThisTarget } from "./thisTarget.js";
6
+ import { didUserRegisterMethods, handlers } from "./handlers.js";
6
7
  export function isMessengerMessage(message) {
7
8
  return (isObject(message) &&
8
9
  typeof message["type"] === "string" &&
@@ -44,6 +45,11 @@ action) {
44
45
  });
45
46
  const localHandler = handlers.get(type);
46
47
  if (!localHandler) {
48
+ if (!didUserRegisterMethods()) {
49
+ // TODO: Test the handling of __getTabData in contexts that have no registered methods
50
+ // https://github.com/pixiebrix/webext-messenger/pull/82
51
+ throw new MessengerError(`No handlers registered in ${getContextName()}`);
52
+ }
47
53
  throw new MessengerError(`No handler registered for ${type} in ${getContextName()}`);
48
54
  }
49
55
  handleMessage = async () => localHandler.apply(meta, args);
@@ -2,7 +2,8 @@ import pRetry from "p-retry";
2
2
  import { isBackground } from "webext-detect-page";
3
3
  import { doesTabExist } from "webext-tools";
4
4
  import { deserializeError } from "serialize-error";
5
- import { isObject, MessengerError, __webextMessenger, handlers, debug, warn, } from "./shared.js";
5
+ import { isObject, MessengerError, __webextMessenger, debug, warn, } from "./shared.js";
6
+ import { handlers } from "./handlers.js";
6
7
  const _errorNonExistingTarget = "Could not establish connection. Receiving end does not exist.";
7
8
  // https://github.com/mozilla/webextension-polyfill/issues/384
8
9
  const _errorTargetClosedEarly = "A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received";
@@ -47,7 +48,10 @@ async function manageMessage(type, target, sendMessage) {
47
48
  if (
48
49
  // Don't retry sending to the background page unless it really hasn't loaded yet
49
50
  (target.page !== "background" && error instanceof MessengerError) ||
50
- String(error.message).startsWith(_errorNonExistingTarget)) {
51
+ // Page or its content script not yet loaded
52
+ String(error.message).startsWith(_errorNonExistingTarget) ||
53
+ // `registerMethods` not yet loaded
54
+ String(error.message).startsWith("No handlers registered in ")) {
51
55
  if (browser.tabs &&
52
56
  typeof target.tabId === "number" &&
53
57
  !(await doesTabExist(target.tabId))) {
@@ -1,5 +1,4 @@
1
1
  import { JsonObject } from "type-fest";
2
- import { Method } from "./types.js";
3
2
  declare type ErrorObject = {
4
3
  name?: string;
5
4
  stack?: string;
@@ -11,7 +10,6 @@ export declare function isObject(value: unknown): value is Record<string, unknow
11
10
  export declare class MessengerError extends Error {
12
11
  name: string;
13
12
  }
14
- export declare const handlers: Map<string, Method>;
15
13
  export declare const debug: (...args: any[]) => void;
16
14
  export declare const warn: (...args: any[]) => void;
17
15
  export declare function isErrorObject(error: unknown): error is ErrorObject;
@@ -25,13 +25,12 @@ export class MessengerError extends Error {
25
25
  });
26
26
  }
27
27
  }
28
- export const handlers = new Map();
29
28
  // .bind preserves the call location in the console
30
29
  export const debug = logging ? console.debug.bind(console, "Messenger:") : noop;
31
30
  export const warn = logging ? console.warn.bind(console, "Messenger:") : noop;
32
31
  export function isErrorObject(error) {
33
32
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a type guard function and it uses ?.
34
- return typeof (error === null || error === void 0 ? void 0 : error.message) === "string";
33
+ return typeof error?.message === "string";
35
34
  }
36
35
  export async function delay(milliseconds) {
37
36
  return new Promise((resolve) => {
@@ -1,11 +1,5 @@
1
1
  import { AnyTarget, MessengerMeta, Sender } from "./types.js";
2
2
  export declare function getActionForMessage(from: Sender, { ...to }: AnyTarget): "respond" | "forward" | "ignore";
3
3
  export declare function nameThisTarget(): Promise<void>;
4
- declare function __getTabData(this: MessengerMeta): AnyTarget;
5
- declare global {
6
- interface MessengerMethods {
7
- __getTabData: typeof __getTabData;
8
- }
9
- }
4
+ export declare function __getTabData(this: MessengerMeta): AnyTarget;
10
5
  export declare function initPrivateApi(): void;
11
- export {};
@@ -5,7 +5,9 @@ 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
- let thisTarget;
8
+ let thisTarget = isBackground()
9
+ ? { page: "background" }
10
+ : undefined;
9
11
  function compareTargets(to, thisTarget) {
10
12
  for (const [key, value] of Object.entries(to)) {
11
13
  if (thisTarget[key] === value) {
@@ -29,7 +31,6 @@ function compareTargets(to, thisTarget) {
29
31
  }
30
32
  export function getActionForMessage(from, { ...to } // Clone object because we're editing it
31
33
  ) {
32
- var _a;
33
34
  if (to.page === "any") {
34
35
  return "respond";
35
36
  }
@@ -48,7 +49,7 @@ export function getActionForMessage(from, { ...to } // Clone object because we'r
48
49
  return "ignore";
49
50
  }
50
51
  // Set "this" tab to the current tabId
51
- if (to.tabId === "this" && thisTarget.tabId === ((_a = from.tab) === null || _a === void 0 ? void 0 : _a.id)) {
52
+ if (to.tabId === "this" && thisTarget.tabId === from.tab?.id) {
52
53
  to.tabId = thisTarget.tabId;
53
54
  }
54
55
  // Every `target` key must match `thisTarget`
@@ -67,16 +68,14 @@ export async function nameThisTarget() {
67
68
  thisTarget.page = location.pathname + location.search;
68
69
  }
69
70
  }
70
- function __getTabData() {
71
- var _a, _b, _c;
72
- 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 };
71
+ export function __getTabData() {
72
+ return { tabId: this.trace[0]?.tab?.id, frameId: this.trace[0]?.frameId };
73
73
  }
74
74
  export function initPrivateApi() {
75
- if (isBackground()) {
76
- thisTarget = { page: "background" };
77
- }
78
75
  if (isExtensionContext()) {
79
- // Any context can handler this message
76
+ // Only `runtime` pages can handle this message but I can't remove it because its listener
77
+ // also serves the purpose of throwing a specific error when no methods have been registered.
78
+ // https://github.com/pixiebrix/webext-messenger/pull/80
80
79
  registerMethods({ __getTabData });
81
80
  }
82
81
  }
@@ -40,7 +40,9 @@ export declare type Message<LocalArguments extends Arguments = Arguments> = {
40
40
  /** If the message is being sent to an intermediary receiver, also set the options */
41
41
  options?: Options;
42
42
  };
43
- export declare type Sender = Runtime.MessageSender;
43
+ export declare type Sender = Runtime.MessageSender & {
44
+ origin?: string;
45
+ };
44
46
  export declare type MessengerMessage = Message & {
45
47
  /** Guarantees that a message is meant to be handled by this library */
46
48
  __webextMessenger: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webext-messenger",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "description": "Browser Extension component messaging framework",
5
5
  "keywords": [],
6
6
  "repository": "pixiebrix/webext-messenger",
@@ -18,124 +18,42 @@
18
18
  "fix": "eslint . --fix",
19
19
  "watch": "tsc --watch"
20
20
  },
21
- "eslintConfig": {
22
- "env": {
23
- "browser": true
24
- },
25
- "parserOptions": {
26
- "project": "tsconfig.json"
27
- },
28
- "plugins": [
29
- "import"
30
- ],
31
- "extends": [
32
- "plugin:@typescript-eslint/recommended",
33
- "xo",
34
- "xo-typescript",
35
- "prettier",
36
- "plugin:import/recommended",
37
- "plugin:import/typescript",
38
- "plugin:unicorn/recommended"
39
- ],
40
- "rules": {
41
- "no-restricted-imports": [
42
- "error",
43
- {
44
- "paths": [
45
- {
46
- "name": "./index",
47
- "message": "The index file is only used to re-export internal files. Use direct imports instead."
48
- }
49
- ]
50
- }
51
- ],
52
- "import/extensions": [
53
- "error",
54
- "always"
55
- ],
56
- "import/no-unresolved": "off",
57
- "unicorn/filename-case": [
58
- "error",
59
- {
60
- "case": "camelCase"
61
- }
62
- ],
63
- "unicorn/no-useless-undefined": [
64
- "error",
65
- {
66
- "checkArguments": false
67
- }
68
- ],
69
- "unicorn/prevent-abbreviations": [
70
- "error",
71
- {
72
- "allowList": {
73
- "args": true
74
- }
75
- }
76
- ]
77
- },
78
- "overrides": [
79
- {
80
- "files": [
81
- "*.test.ts"
82
- ],
83
- "rules": {
84
- "@typescript-eslint/no-explicit-any": "off",
85
- "@typescript-eslint/no-non-null-assertion": "off",
86
- "@typescript-eslint/no-unsafe-member-access": "off"
87
- }
88
- },
89
- {
90
- "files": [
91
- "source/test/**/*"
92
- ],
93
- "rules": {
94
- "import/extensions": "off"
95
- }
96
- }
97
- ]
98
- },
99
21
  "dependencies": {
100
- "p-retry": "^5.1.0",
22
+ "p-retry": "^5.1.1",
101
23
  "serialize-error": "^11.0.0",
102
- "type-fest": "^2.12.1",
24
+ "type-fest": "^2.13.0",
103
25
  "webext-detect-page": "^4.0.1",
104
26
  "webext-tools": "^1.1.0"
105
27
  },
106
28
  "devDependencies": {
107
- "@parcel/config-webextension": "^2.4.0",
108
- "@sindresorhus/tsconfig": "^2.0.0",
109
- "@types/chrome": "^0.0.180",
29
+ "@parcel/config-webextension": "^2.6.0",
30
+ "@sindresorhus/tsconfig": "^3.0.1",
31
+ "@types/chrome": "^0.0.188",
110
32
  "@types/tape": "^4.13.2",
111
- "@types/webextension-polyfill": "^0.8.3",
112
- "@typescript-eslint/eslint-plugin": "^5.17.0",
113
- "@typescript-eslint/parser": "^5.17.0",
33
+ "@types/webextension-polyfill": "^0.9.0",
34
+ "@typescript-eslint/eslint-plugin": "^5.27.0",
35
+ "@typescript-eslint/parser": "^5.27.0",
114
36
  "buffer": "^6.0.3",
115
- "eslint": "^8.12.0",
37
+ "eslint": "^8.17.0",
116
38
  "eslint-config-prettier": "^8.5.0",
117
- "eslint-config-xo": "^0.40.0",
118
- "eslint-config-xo-typescript": "^0.50.0",
119
- "eslint-plugin-import": "^2.25.4",
120
- "eslint-plugin-unicorn": "^41.0.1",
39
+ "eslint-config-xo": "^0.41.0",
40
+ "eslint-config-xo-typescript": "^0.51.1",
41
+ "eslint-plugin-import": "^2.26.0",
42
+ "eslint-plugin-unicorn": "^42.0.0",
121
43
  "events": "^3.3.0",
122
44
  "npm-run-all": "^4.1.5",
123
- "parcel": "^2.4.0",
45
+ "parcel": "^2.6.0",
124
46
  "path-browserify": "^1.0.1",
125
47
  "process": "^0.11.10",
126
48
  "stream-browserify": "^3.0.0",
127
- "tape": "^5.5.2",
128
- "typescript": "^4.6.3",
129
- "webext-content-scripts": "^1.0.1",
49
+ "tape": "^5.5.3",
50
+ "typescript": "^4.7.3",
51
+ "webext-content-scripts": "^1.0.2",
130
52
  "webextension-polyfill": "^0.9.0"
131
53
  },
132
54
  "alias": {
133
- "./this-stuff-is-just-for-local-parcel-tests": "./package.json",
134
- "./source/sender.js": "./source/sender.ts",
135
- "./source/receiver.js": "./source/receiver.ts",
136
- "./source/types.js": "./source/types.ts",
137
- "./source/shared.js": "./source/shared.ts",
138
- "./source/thisTarget.js": "./source/thisTarget.ts"
55
+ "./this-stuff-is-just-for-local-parcel-tests": "https://github.com/parcel-bundler/parcel/issues/4936",
56
+ "./source/**/*.js": "./source/$1/$2.ts"
139
57
  },
140
58
  "targets": {
141
59
  "main": false,