webext-messenger
Advanced tools
Comparing version 0.12.0 to 0.13.0-0
@@ -6,5 +6,6 @@ /// <reference types="firefox-webext-browser" /> | ||
_: Method; | ||
__webextMessengerTargetRegistration: typeof _registerTarget; | ||
} | ||
} | ||
declare type WithTarget<Method> = Method extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: Target, ...args: PreviousArguments) => TReturnValue : never; | ||
declare type WithTarget<Method> = Method extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: Target | NamedTarget, ...args: PreviousArguments) => TReturnValue : never; | ||
declare type ActuallyOmitThisParameter<T> = T extends (...args: infer A) => infer R ? (...args: A) => R : T; | ||
@@ -26,2 +27,7 @@ /** Removes the `this` type and ensure it's always Promised */ | ||
} | ||
export interface NamedTarget { | ||
/** If the id is missing, it will use the sender’s tabId instead */ | ||
tabId?: number; | ||
name: string; | ||
} | ||
interface Options { | ||
@@ -51,2 +57,5 @@ /** | ||
declare function registerMethods(methods: Partial<MessengerMethods>): void; | ||
export { getMethod, getContentScriptMethod, registerMethods }; | ||
/** Register the current context so that it can be targeted with a name */ | ||
declare const registerTarget: (name: string) => Promise<void>; | ||
declare function _registerTarget(this: MessengerMeta, name: string): void; | ||
export { getMethod, getContentScriptMethod, registerMethods, registerTarget }; |
import pRetry from "p-retry"; | ||
import { deserializeError, serializeError } from "serialize-error"; | ||
import { isBackgroundPage } from "webext-detect-page"; | ||
const errorNonExistingTarget = "Could not establish connection. Receiving end does not exist."; | ||
export class MessengerError extends Error { | ||
@@ -44,4 +45,7 @@ constructor() { | ||
if (message.target) { | ||
const resolvedTarget = "name" in message.target | ||
? resolveNamedTarget(message.target, sender.trace[0]) | ||
: message.target; | ||
const publicMethod = getContentScriptMethod(message.type); | ||
return handleCall(message, sender, publicMethod(message.target, ...message.args)); | ||
return handleCall(message, sender, publicMethod(resolvedTarget, ...message.args)); | ||
} | ||
@@ -70,4 +74,3 @@ const handler = handlers.get(message.type); | ||
onFailedAttempt(error) { | ||
if ((error === null || error === void 0 ? void 0 : error.message) !== | ||
"Could not establish connection. Receiving end does not exist.") { | ||
if (!String(error === null || error === void 0 ? void 0 : error.message).startsWith(errorNonExistingTarget)) { | ||
throw error; | ||
@@ -104,12 +107,11 @@ } | ||
const publicMethod = (target, ...args) => { | ||
// eslint-disable-next-line no-negated-condition -- Looks better | ||
const sendMessage = !browser.tabs | ||
? async () => browser.runtime.sendMessage(makeMessage(type, args, target)) | ||
: async () => { | ||
var _a; | ||
return browser.tabs.sendMessage(target.tabId, makeMessage(type, args), | ||
// `frameId` must be specified. If missing, the message is sent to every frame | ||
{ frameId: (_a = target.frameId) !== null && _a !== void 0 ? _a : 0 }); | ||
}; | ||
return manageConnection(type, options, sendMessage); | ||
// Named targets and contexts without direct Tab access must go through background, unless we're already in it | ||
if (!browser.tabs || ("name" in target && !isBackgroundPage())) { | ||
return manageConnection(type, options, async () => browser.runtime.sendMessage(makeMessage(type, args, target))); | ||
} | ||
const resolvedTarget = "name" in target ? resolveNamedTarget(target) : target; | ||
// `frameId` must be specified. If missing, the message is sent to every frame | ||
const { tabId, frameId = 0 } = resolvedTarget; | ||
// Message tab directly | ||
return manageConnection(type, options, async () => browser.tabs.sendMessage(tabId, makeMessage(type, args), { frameId })); | ||
}; | ||
@@ -141,4 +143,47 @@ return publicMethod; | ||
} | ||
browser.runtime.onMessage.addListener(onMessageListener); | ||
// Use "chrome" because the polyfill might not be available when `_registerTarget` is registered | ||
if ("browser" in globalThis) { | ||
browser.runtime.onMessage.addListener(onMessageListener); | ||
} | ||
else { | ||
console.error("Messenger: webextension-polyfill was not loaded in time, this might cause a runtime error later"); | ||
// @ts-expect-error Temporary workaround until I drop the webextension-polyfill dependency | ||
chrome.runtime.onMessage.addListener(onMessageListener); | ||
} | ||
} | ||
export { getMethod, getContentScriptMethod, registerMethods }; | ||
// TODO: Remove targets after tab closes to avoid "memory leaks" | ||
const targets = new Map(); | ||
/** Register the current context so that it can be targeted with a name */ | ||
const registerTarget = getMethod("__webextMessengerTargetRegistration"); | ||
function _registerTarget(name) { | ||
const sender = this.trace[0]; | ||
const tabId = sender.tab.id; | ||
const { frameId } = sender; | ||
targets.set(`${tabId}%${name}`, { | ||
tabId, | ||
frameId, | ||
}); | ||
console.debug(`Messenger: Target "${name}" registered for tab ${tabId}`); | ||
} | ||
function resolveNamedTarget(target, sender) { | ||
var _a; | ||
if (!isBackgroundPage()) { | ||
throw new Error("Named targets can only be resolved in the background page"); | ||
} | ||
const { name, tabId = (_a = sender === null || sender === void 0 ? void 0 : sender.tab) === null || _a === void 0 ? void 0 : _a.id, // If not specified, try to use the sender’s | ||
} = target; | ||
if (typeof tabId === "undefined") { | ||
throw new TypeError(`${errorNonExistingTarget} The tab ID was not specified nor it was automatically determinable.`); | ||
} | ||
const resolvedTarget = targets.get(`${tabId}%${name}`); | ||
if (!resolvedTarget) { | ||
throw new Error(`${errorNonExistingTarget} Target named ${name} not registered for tab ${tabId}.`); | ||
} | ||
return resolvedTarget; | ||
} | ||
if (isBackgroundPage()) { | ||
registerMethods({ | ||
__webextMessengerTargetRegistration: _registerTarget, | ||
}); | ||
} | ||
export { getMethod, getContentScriptMethod, registerMethods, registerTarget }; |
{ | ||
"name": "webext-messenger", | ||
"version": "0.12.0", | ||
"version": "0.13.0-0", | ||
"description": "Browser Extension component messaging framework", | ||
@@ -5,0 +5,0 @@ "keywords": [], |
15527
243