webext-messenger 0.32.0 → 0.33.0-0

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.
@@ -1,5 +1,7 @@
1
- import { type Message, type MessengerMeta } from "./types.js";
1
+ import { type Message, type ExternalMessage, type MessengerMeta } from "./types.js";
2
2
  export declare function isMessengerMessage(message: unknown): message is Message;
3
+ export declare function isExternalMessengerMessage(message: unknown): message is ExternalMessage;
3
4
  export declare function registerMethods(methods: Partial<MessengerMethods>): void;
5
+ export declare function allowExternalUse(...types: Array<keyof MessengerMethods>): void;
4
6
  /** Ensure/document that the current function was called via Messenger */
5
7
  export declare function assertMessengerCall(_this: MessengerMeta): asserts _this is MessengerMeta;
@@ -1,17 +1,27 @@
1
1
  import { serializeError } from "serialize-error";
2
- import { getContextName } from "webext-detect";
2
+ import { getContextName, isBackground } from "webext-detect";
3
3
  import { messenger } from "./sender.js";
4
4
  import { isObject, MessengerError, __webextMessenger } from "./shared.js";
5
5
  import { log } from "./logging.js";
6
6
  import { getActionForMessage } from "./targetLogic.js";
7
7
  import { didUserRegisterMethods, handlers } from "./handlers.js";
8
8
  import { getTabDataStatus, thisTarget } from "./thisTarget.js";
9
+ const externalMethods = new Set();
9
10
  export function isMessengerMessage(message) {
10
11
  return (isObject(message) &&
11
12
  typeof message["type"] === "string" &&
12
13
  message["__webextMessenger"] === true &&
13
14
  Array.isArray(message["args"]));
14
15
  }
16
+ export function isExternalMessengerMessage(message) {
17
+ return (isObject(message) &&
18
+ typeof message["type"] === "string" &&
19
+ message["__webextMessenger"] === true &&
20
+ Array.isArray(message["args"]) &&
21
+ isObject(message["target"]) &&
22
+ Object.keys(message["target"]).length === 1 && // Ensure it's *only* `extensionId`
23
+ typeof message["target"]["extensionId"] === "string");
24
+ }
15
25
  /**
16
26
  * Decides what to do with a message and sends a response (value or error) back to the sender.
17
27
  *
@@ -63,6 +73,19 @@ function onMessageListener(message, sender, sendResponse) {
63
73
  // TODO: Just return a promise if this is ever implemented https://issues.chromium.org/issues/40753031
64
74
  return true;
65
75
  }
76
+ // Do not remove.
77
+ // Early validation to ensure that the message matches the specific allowed target
78
+ // before letting it flow into the rest of messenger.
79
+ function onMessageExternalListener(message, sender, sendResponse) {
80
+ if (isExternalMessengerMessage(message) &&
81
+ message.target.extensionId === chrome.runtime.id) {
82
+ return onMessageListener(message, sender, sendResponse);
83
+ }
84
+ log.debug("Ignored external message", {
85
+ message,
86
+ sender,
87
+ });
88
+ }
66
89
  /** Generates the value or error to return to the sender; does not include further messaging logic */
67
90
  async function prepareResponse(message, action, meta) {
68
91
  const { type, target, args } = message;
@@ -71,6 +94,9 @@ async function prepareResponse(message, action, meta) {
71
94
  }
72
95
  const localHandler = handlers.get(type);
73
96
  if (localHandler) {
97
+ if ("extensionId" in target && !externalMethods.has(type)) {
98
+ throw new MessengerError(`${type} is not allowed to be called externally in ${getContextName()}`);
99
+ }
74
100
  return localHandler.apply(meta, args);
75
101
  }
76
102
  if (didUserRegisterMethods()) {
@@ -89,6 +115,15 @@ export function registerMethods(methods) {
89
115
  handlers.set(type, method);
90
116
  }
91
117
  chrome.runtime.onMessage.addListener(onMessageListener);
118
+ // Only handle direct-to-background messages for now
119
+ if (isBackground()) {
120
+ chrome.runtime.onMessageExternal.addListener(onMessageExternalListener);
121
+ }
122
+ }
123
+ export function allowExternalUse(...types) {
124
+ for (const type of types) {
125
+ externalMethods.add(type);
126
+ }
92
127
  }
93
128
  /** Ensure/document that the current function was called via Messenger */
94
129
  export function assertMessengerCall(_this) { }
@@ -1,15 +1,15 @@
1
- import { type PublicMethod, type PublicMethodWithTarget, type Options, type Target, type PageTarget } from "./types.js";
1
+ import { type PublicMethod, type PublicMethodWithTarget, type Options, type AnySpecificTarget, type PageTarget } from "./types.js";
2
2
  import { type Promisable, type SetReturnType } from "type-fest";
3
3
  export declare const errorTargetClosedEarly = "The target was closed before receiving a response";
4
4
  export declare const errorTabDoesntExist = "The tab doesn't exist";
5
5
  export declare const errorTabWasDiscarded = "The tab was discarded";
6
6
  declare function messenger<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type]>(type: Type, options: {
7
7
  isNotification: true;
8
- }, target: Target | PageTarget, ...args: Parameters<Method>): void;
9
- declare function messenger<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], ReturnValue extends Promise<ReturnType<Method>>>(type: Type, options: Options, target: Target | PageTarget, ...args: Parameters<Method>): ReturnValue;
10
- declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends PublicMethod<Method>>(type: Type, target: Promisable<Target | PageTarget>): PublicMethodType;
8
+ }, target: AnySpecificTarget, ...args: Parameters<Method>): void;
9
+ declare function messenger<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], ReturnValue extends Promise<ReturnType<Method>>>(type: Type, options: Options, target: AnySpecificTarget, ...args: Parameters<Method>): ReturnValue;
10
+ declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends PublicMethod<Method>>(type: Type, target: Promisable<AnySpecificTarget>): PublicMethodType;
11
11
  declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodWithDynamicTarget extends PublicMethodWithTarget<Method>>(type: Type): PublicMethodWithDynamicTarget;
12
- declare function getNotifier<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends SetReturnType<PublicMethod<Method>, void>>(type: Type, target: Promisable<Target | PageTarget>): PublicMethodType;
12
+ declare function getNotifier<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends SetReturnType<PublicMethod<Method>, void>>(type: Type, target: Promisable<AnySpecificTarget>): PublicMethodType;
13
13
  declare function getNotifier<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodWithDynamicTarget extends SetReturnType<PublicMethodWithTarget<Method>, void>>(type: Type): PublicMethodWithDynamicTarget;
14
14
  export { messenger, getMethod, getNotifier };
15
15
  export declare const backgroundTarget: PageTarget;
@@ -141,6 +141,13 @@ let globalSeq = (Date.now() % 100) * 10_000;
141
141
  function messenger(type, options, target, ...args) {
142
142
  options.seq = globalSeq++;
143
143
  const { seq } = options;
144
+ if ("extensionId" in target) {
145
+ const sendMessage = async (attemptCount) => {
146
+ log.debug(type, seq, "↗️ sending message to extension", attemptLog(attemptCount));
147
+ return chrome.runtime.sendMessage(target.extensionId, makeMessage(type, args, target, options));
148
+ };
149
+ return manageConnection(type, options, target, sendMessage);
150
+ }
144
151
  // Message goes to extension page
145
152
  if ("page" in target) {
146
153
  if (target.page === "background" && isBackground()) {
@@ -23,6 +23,10 @@ export function compareTargets(to, thisTarget) {
23
23
  export function getActionForMessage(from, target, thisTarget) {
24
24
  // Clone object because we're editing it
25
25
  const to = { ...target };
26
+ if (to.extensionId) {
27
+ // Only handle external messages in the background page
28
+ return isBackground() ? "respond" : "ignore";
29
+ }
26
30
  if (to.page === "any") {
27
31
  return "respond";
28
32
  }
@@ -18,9 +18,8 @@ const tab = {
18
18
  groupId: -1,
19
19
  };
20
20
  const senders = {
21
- background: { page: "background" },
22
21
  contentScript: { tab },
23
- somePage: { page: "/page.html" },
22
+ // TODO: Test more senders
24
23
  };
25
24
  const targets = {
26
25
  background: { page: "background" },
@@ -10,7 +10,7 @@ declare global {
10
10
  _: Method;
11
11
  }
12
12
  }
13
- type WithTarget<Method> = Method extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: Target | PageTarget, ...args: PreviousArguments) => TReturnValue : never;
13
+ type WithTarget<Method> = Method extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: AnySpecificTarget, ...args: PreviousArguments) => TReturnValue : never;
14
14
  type ActuallyOmitThisParameter<T> = T extends (...args: infer A) => infer R ? (...args: A) => R : T;
15
15
  /** Removes the `this` type and ensure it's always Promised */
16
16
  export type PublicMethod<Method extends ValueOf<MessengerMethods>> = Asyncify<ActuallyOmitThisParameter<Method>>;
@@ -43,10 +43,13 @@ export interface Options {
43
43
  export type Message<LocalArguments extends Arguments = Arguments> = {
44
44
  type: keyof MessengerMethods;
45
45
  args: LocalArguments;
46
- target: Target | PageTarget;
46
+ target: AnySpecificTarget;
47
47
  /** If the message is being sent to an intermediary receiver, also set the options */
48
48
  options?: Options;
49
49
  };
50
+ export type ExternalMessage = Message & {
51
+ target: ExtensionTarget;
52
+ };
50
53
  export type Sender = chrome.runtime.MessageSender;
51
54
  export type MessengerMessage = Message & {
52
55
  /** Guarantees that a message is meant to be handled by this library */
@@ -56,6 +59,7 @@ export interface AnyTarget {
56
59
  tabId?: number | "this";
57
60
  frameId?: number | "allFrames";
58
61
  page?: string;
62
+ extensionId?: string;
59
63
  }
60
64
  export interface TopLevelFrame {
61
65
  tabId: number;
@@ -78,4 +82,8 @@ export interface PageTarget {
78
82
  tabId?: number | "this";
79
83
  page: string;
80
84
  }
85
+ export interface ExtensionTarget {
86
+ extensionId: string;
87
+ }
88
+ export type AnySpecificTarget = Target | PageTarget | ExtensionTarget;
81
89
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webext-messenger",
3
- "version": "0.32.0",
3
+ "version": "0.33.0-0",
4
4
  "description": "Browser Extension component messaging framework",
5
5
  "keywords": [],
6
6
  "repository": "pixiebrix/webext-messenger",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "scripts": {
18
18
  "build": "tsc",
19
- "demo:watch": "parcel watch --no-cache --no-hmr",
19
+ "demo:watch": "parcel serve --no-cache --no-hmr",
20
20
  "demo:build": "parcel build --no-cache --no-scope-hoist",
21
21
  "prepack": "tsc --sourceMap false",
22
22
  "test": "run-p test:unit lint build demo:build",
@@ -55,11 +55,14 @@
55
55
  },
56
56
  "targets": {
57
57
  "main": false,
58
- "default": {
58
+ "external": {
59
+ "source": "source/test/external.html"
60
+ },
61
+ "extension": {
62
+ "source": "source/test/manifest.json",
59
63
  "engines": {
60
64
  "browsers": "Chrome 110"
61
65
  },
62
- "source": "source/test/manifest.json",
63
66
  "sourceMap": {
64
67
  "inline": true
65
68
  }
@@ -71,7 +74,8 @@
71
74
  "startUrl": [
72
75
  "https://fregante.github.io/pixiebrix-testing-ground/Will-call-background-methods",
73
76
  "https://fregante.github.io/pixiebrix-testing-ground/Will-call-other-CS-via-background",
74
- "https://fregante.github.io/pixiebrix-testing-ground/Will-call-offscreen-methods"
77
+ "https://fregante.github.io/pixiebrix-testing-ground/Will-call-offscreen-methods",
78
+ "http://localhost:1234/external.html"
75
79
  ]
76
80
  }
77
81
  }