webext-messenger 0.6.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
1
  /// <reference types="firefox-webext-browser" />
2
+ import { Asyncify } from "type-fest";
2
3
  declare global {
3
4
  interface MessengerMethods {
4
5
  _: Method;
@@ -9,19 +10,19 @@ export declare type MessengerMeta = browser.runtime.MessageSender;
9
10
  declare type Arguments = any[];
10
11
  declare type Method = (this: MessengerMeta, ...args: Arguments) => Promise<unknown>;
11
12
  export interface Target {
12
- tab: number;
13
- frame?: number;
13
+ tabId: number;
14
+ frameId?: number;
14
15
  }
15
16
  declare type WithTarget<TMethod> = TMethod extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: Target, ...args: PreviousArguments) => TReturnValue : never;
16
17
  /**
17
18
  * Replicates the original method, including its types.
18
19
  * To be called in the sender’s end.
19
20
  */
20
- export declare function getContentScriptMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], PublicMethod extends WithTarget<ActuallyOmitThisParameter<TMethod>>>(type: TType): PublicMethod;
21
+ export declare function getContentScriptMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], PublicMethod extends WithTarget<Asyncify<ActuallyOmitThisParameter<TMethod>>>>(type: TType): PublicMethod;
21
22
  /**
22
23
  * Replicates the original method, including its types.
23
24
  * To be called in the sender’s end.
24
25
  */
25
- export declare function getMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], PublicMethod extends ActuallyOmitThisParameter<TMethod>>(type: TType): PublicMethod;
26
+ export declare function getMethod<TType extends keyof MessengerMethods, TMethod extends MessengerMethods[TType], PublicMethod extends Asyncify<ActuallyOmitThisParameter<TMethod>>>(type: TType): PublicMethod;
26
27
  export declare function registerMethods(methods: Partial<MessengerMethods>): void;
27
28
  export {};
@@ -1,29 +1,49 @@
1
1
  import { deserializeError, serializeError } from "serialize-error";
2
- const errorKey = "__webext_messenger_error_response__";
2
+ const __webext_messenger__ = true;
3
3
  function isObject(value) {
4
4
  return typeof value === "object" && value !== null;
5
5
  }
6
- function isMessengerMessage(value) {
7
- return (isObject(value) &&
8
- typeof value["type"] === "string" &&
9
- typeof value["__webext_messenger__"] === "boolean" &&
10
- Array.isArray(value["args"]));
6
+ function isMessengerMessage(message) {
7
+ return (isObject(message) &&
8
+ typeof message["type"] === "string" &&
9
+ message["__webext_messenger__"] === true &&
10
+ Array.isArray(message["args"]));
11
+ }
12
+ function isMessengerResponse(response) {
13
+ return isObject(response) && response["__webext_messenger__"] === true;
11
14
  }
12
15
  const handlers = new Map();
16
+ async function handleMessage(message, sender) {
17
+ const handler = handlers.get(message.type);
18
+ if (!handler) {
19
+ throw new Error("No handler registered for " + message.type);
20
+ }
21
+ console.debug(`Messenger:`, message.type, message.args, "from", { sender });
22
+ // The handler could actually be a synchronous function
23
+ const response = await Promise.resolve(handler.call(sender, ...message.args)).then((value) => ({ value }), (error) => ({
24
+ // Errors must be serialized because the stacktraces are currently lost on Chrome and
25
+ // https://github.com/mozilla/webextension-polyfill/issues/210
26
+ error: serializeError(error),
27
+ }));
28
+ console.debug(`Messenger:`, message.type, "responds", response);
29
+ return { ...response, __webext_messenger__ };
30
+ }
31
+ async function handleResponse(response) {
32
+ if (!isMessengerResponse(response)) {
33
+ // If the response is `undefined`, `registerMethod` was never called
34
+ throw new Error("No handlers registered in receiving end");
35
+ }
36
+ if ("error" in response) {
37
+ throw deserializeError(response.error);
38
+ }
39
+ return response.value;
40
+ }
13
41
  // MUST NOT be `async` or Promise-returning-only
14
42
  function onMessageListener(message, sender) {
15
- if (!isMessengerMessage(message)) {
16
- return;
17
- }
18
- const handler = handlers.get(message.type);
19
- if (handler) {
20
- return handler.call(sender, ...message.args).catch((error) => ({
21
- // Errors must be serialized because the stacktraces are currently lost on Chrome and
22
- // https://github.com/mozilla/webextension-polyfill/issues/210
23
- [errorKey]: serializeError(error),
24
- }));
43
+ if (isMessengerMessage(message)) {
44
+ return handleMessage(message, sender);
25
45
  }
26
- throw new Error("No handler registered for " + message.type);
46
+ // TODO: Add test for this eventuality: ignore unrelated messages
27
47
  }
28
48
  /**
29
49
  * Replicates the original method, including its types.
@@ -32,21 +52,16 @@ function onMessageListener(message, sender) {
32
52
  export function getContentScriptMethod(type) {
33
53
  const publicMethod = async (target, ...args) => {
34
54
  var _a;
35
- // TODO: This will throw if the receiving end doesn't exist,
36
- // i.e. if registerMethods hasn't been called
37
- const response = await browser.tabs.sendMessage(target.tab, {
55
+ const response = await browser.tabs.sendMessage(target.tabId, {
38
56
  // Guarantees that a message is meant to be handled by this library
39
- __webext_messenger__: true,
57
+ __webext_messenger__,
40
58
  type,
41
59
  args,
42
60
  }, {
43
61
  // Must be specified. If missing, the message would be sent to every frame
44
- frameId: (_a = target.frame) !== null && _a !== void 0 ? _a : 0,
62
+ frameId: (_a = target.frameId) !== null && _a !== void 0 ? _a : 0,
45
63
  });
46
- if (isObject(response) && errorKey in response) {
47
- throw deserializeError(response[errorKey]);
48
- }
49
- return response;
64
+ return handleResponse(response);
50
65
  };
51
66
  return publicMethod;
52
67
  }
@@ -56,18 +71,13 @@ export function getContentScriptMethod(type) {
56
71
  */
57
72
  export function getMethod(type) {
58
73
  const publicMethod = async (...args) => {
59
- // TODO: This will throw if the receiving end doesn't exist,
60
- // i.e. if registerMethods hasn't been called
61
74
  const response = await browser.runtime.sendMessage({
62
75
  // Guarantees that a message is meant to be handled by this library
63
- __webext_messenger__: true,
76
+ __webext_messenger__,
64
77
  type,
65
78
  args,
66
79
  });
67
- if (isObject(response) && errorKey in response) {
68
- throw deserializeError(response[errorKey]);
69
- }
70
- return response;
80
+ return handleResponse(response);
71
81
  };
72
82
  return publicMethod;
73
83
  }
@@ -76,6 +86,7 @@ export function registerMethods(methods) {
76
86
  if (handlers.has(type)) {
77
87
  throw new Error(`Handler already set for ${type}`);
78
88
  }
89
+ console.debug(`Messenger: Registered`, type);
79
90
  handlers.set(type, method);
80
91
  }
81
92
  browser.runtime.onMessage.addListener(onMessageListener);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webext-messenger",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Browser Extension component messaging framework",
5
5
  "keywords": [],
6
6
  "repository": "pixiebrix/extension-messaging",
@@ -69,6 +69,7 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "serialize-error": "^8.1.0",
72
+ "type-fest": "^2.3.4",
72
73
  "webext-detect-page": "^3.0.2",
73
74
  "webextension-polyfill": "^0.8.0"
74
75
  },