webext-messenger
Advanced tools
Comparing version 0.12.0-3 to 0.12.0
@@ -6,6 +6,5 @@ /// <reference types="firefox-webext-browser" /> | ||
_: Method; | ||
__webextMessengerTargetRegistration: typeof _registerTarget; | ||
} | ||
} | ||
declare type WithTarget<Method> = Method extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: Target | NamedTarget, ...args: PreviousArguments) => TReturnValue : never; | ||
declare type WithTarget<Method> = Method extends (...args: infer PreviousArguments) => infer TReturnValue ? (target: Target, ...args: PreviousArguments) => TReturnValue : never; | ||
declare type ActuallyOmitThisParameter<T> = T extends (...args: infer A) => infer R ? (...args: A) => R : T; | ||
@@ -20,2 +19,5 @@ /** Removes the `this` type and ensure it's always Promised */ | ||
declare type Method = (this: MessengerMeta, ...args: Arguments) => Promise<unknown>; | ||
export declare class MessengerError extends Error { | ||
name: string; | ||
} | ||
export interface Target { | ||
@@ -25,7 +27,2 @@ tabId: number; | ||
} | ||
export interface NamedTarget { | ||
/** If the id is missing, it will use the sender’s tabId instead */ | ||
tabId?: number; | ||
name: string; | ||
} | ||
interface Options { | ||
@@ -55,5 +52,2 @@ /** | ||
declare function registerMethods(methods: Partial<MessengerMethods>): void; | ||
/** 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 }; | ||
export { getMethod, getContentScriptMethod, registerMethods }; |
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 { | ||
constructor() { | ||
super(...arguments); | ||
Object.defineProperty(this, "name", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: "MessengerError" | ||
}); | ||
} | ||
} | ||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Private key | ||
@@ -34,7 +44,4 @@ const __webext_messenger__ = true; | ||
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(resolvedTarget, ...message.args)); | ||
return handleCall(message, sender, publicMethod(message.target, ...message.args)); | ||
} | ||
@@ -45,3 +52,4 @@ const handler = handlers.get(message.type); | ||
} | ||
throw new Error("No handler registered for " + message.type); | ||
// More context in https://github.com/pixiebrix/webext-messenger/issues/45 | ||
console.warn("Messenger:", message.type, "received but ignored; No handlers were registered here"); | ||
} | ||
@@ -63,3 +71,4 @@ // Do not turn this into an `async` function; Notifications must turn `void` | ||
onFailedAttempt(error) { | ||
if (!String(error === null || error === void 0 ? void 0 : error.message).startsWith(errorNonExistingTarget)) { | ||
if ((error === null || error === void 0 ? void 0 : error.message) !== | ||
"Could not establish connection. Receiving end does not exist.") { | ||
throw error; | ||
@@ -71,4 +80,3 @@ } | ||
if (!isMessengerResponse(response)) { | ||
// If the response is `undefined`, `registerMethod` was never called | ||
throw new Error("No handlers registered in receiving end"); | ||
throw new MessengerError(`No handler for ${type} was registered in the receiving end`); | ||
} | ||
@@ -98,11 +106,12 @@ if ("error" in response) { | ||
const publicMethod = (target, ...args) => { | ||
// 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 })); | ||
// 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); | ||
}; | ||
@@ -119,3 +128,3 @@ return publicMethod; | ||
} | ||
throw new Error("No handler registered for " + type); | ||
throw new MessengerError("No handler registered for " + type); | ||
} | ||
@@ -130,3 +139,3 @@ const sendMessage = async () => browser.runtime.sendMessage(makeMessage(type, args)); | ||
if (handlers.has(type)) { | ||
throw new Error(`Handler already set for ${type}`); | ||
throw new MessengerError(`Handler already set for ${type}`); | ||
} | ||
@@ -136,47 +145,4 @@ console.debug(`Messenger: Registered`, type); | ||
} | ||
// 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); | ||
} | ||
browser.runtime.onMessage.addListener(onMessageListener); | ||
} | ||
// 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 }; | ||
export { getMethod, getContentScriptMethod, registerMethods }; |
{ | ||
"name": "webext-messenger", | ||
"version": "0.12.0-3", | ||
"version": "0.12.0", | ||
"description": "Browser Extension component messaging framework", | ||
@@ -46,2 +46,3 @@ "keywords": [], | ||
"@typescript-eslint/no-unsafe-member-access": "off", | ||
"import/no-unresolved": "off", | ||
"unicorn/filename-case": [ | ||
@@ -85,3 +86,3 @@ "error", | ||
"serialize-error": "^8.1.0", | ||
"type-fest": "^2.3.4", | ||
"type-fest": "^2.5.1", | ||
"webext-detect-page": "^3.0.2", | ||
@@ -91,18 +92,18 @@ "webextension-polyfill": "^0.8.0" | ||
"devDependencies": { | ||
"@parcel/config-webextension": "^2.0.0-rc.0", | ||
"@parcel/config-webextension": "^2.0.0", | ||
"@sindresorhus/tsconfig": "^2.0.0", | ||
"@types/chrome": "^0.0.158", | ||
"@types/firefox-webext-browser": "^82.0.1", | ||
"@typescript-eslint/eslint-plugin": "^4.33.0", | ||
"@typescript-eslint/parser": "^4.33.0", | ||
"eslint": "^7.32.0", | ||
"@types/chrome": "^0.0.159", | ||
"@types/firefox-webext-browser": "^94.0.0", | ||
"@typescript-eslint/eslint-plugin": "^5.1.0", | ||
"@typescript-eslint/parser": "^5.1.0", | ||
"eslint": "^8.1.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-config-xo": "^0.39.0", | ||
"eslint-config-xo-typescript": "^0.45.1", | ||
"eslint-plugin-import": "^2.24.2", | ||
"eslint-plugin-unicorn": "^36.0.0", | ||
"eslint-config-xo-typescript": "^0.45.2", | ||
"eslint-plugin-import": "^2.25.2", | ||
"eslint-plugin-unicorn": "^37.0.1", | ||
"fresh-tape": "^5.3.1", | ||
"npm-run-all": "^4.1.5", | ||
"parcel": "^2.0.0-rc.0", | ||
"typescript": "^4.4.3", | ||
"parcel": "^2.0.0", | ||
"typescript": "^4.4.4", | ||
"xo": "^0.45.0" | ||
@@ -109,0 +110,0 @@ }, |
12844
189
Updatedtype-fest@^2.5.1