webext-messenger
Advanced tools
Comparing version 0.13.0-5 to 0.13.0-6
export { registerMethods } from "./receiver.js"; | ||
export { getContentScriptMethod, getMethod } from "./sender.js"; | ||
export { MessengerMeta, NamedTarget, Target } from "./types.js"; | ||
export { registerTarget } from "./namedTargets.js"; | ||
export { MessengerMeta, Target } from "./types.js"; |
export { registerMethods } from "./receiver.js"; | ||
export { getContentScriptMethod, getMethod } from "./sender.js"; | ||
export { registerTarget } from "./namedTargets.js"; | ||
import { initTargets } from "./namedTargets.js"; | ||
initTargets(); |
import { serializeError } from "serialize-error"; | ||
import { isBackgroundPage } from "webext-detect-page"; | ||
import { getContentScriptMethod } from "./sender.js"; | ||
import { resolveNamedTarget } from "./namedTargets.js"; | ||
import { handlers, isObject, MessengerError, __webext_messenger__, } from "./shared.js"; | ||
import { handlers, isObject, MessengerError, debug, warn, __webextMessenger, } from "./shared.js"; | ||
export function isMessengerMessage(message) { | ||
return (isObject(message) && | ||
typeof message["type"] === "string" && | ||
message["__webext_messenger__"] === true && | ||
message["__webextMessenger"] === true && | ||
Array.isArray(message["args"])); | ||
} | ||
async function handleCall(message, meta, call) { | ||
console.debug(`Messenger:`, message.type, message.args, "from", { meta }); | ||
// The handler could actually be a synchronous function | ||
const response = await Promise.resolve(call).then((value) => ({ value }), (error) => ({ | ||
// Errors must be serialized because the stacktraces are currently lost on Chrome and | ||
// https://github.com/mozilla/webextension-polyfill/issues/210 | ||
error: serializeError(error), | ||
})); | ||
console.debug(`Messenger:`, message.type, "responds", response); | ||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Private key | ||
return { ...response, __webext_messenger__ }; | ||
} | ||
function getHandler(message, sender) { | ||
if (message.target && !isBackgroundPage()) { | ||
console.warn("Messenger:", message.type, "received but ignored; Wrong context"); | ||
return; | ||
} | ||
if (message.target) { | ||
const resolvedTarget = "name" in message.target | ||
? resolveNamedTarget(message.target, sender) | ||
: message.target; | ||
const publicMethod = getContentScriptMethod(message.type); | ||
// @ts-expect-error You're wrong, TypeScript | ||
return publicMethod.bind(undefined, resolvedTarget); | ||
} | ||
const handler = handlers.get(message.type); | ||
if (handler) { | ||
return handler; | ||
} | ||
} | ||
// MUST NOT be `async` or Promise-returning-only | ||
@@ -48,9 +16,33 @@ function onMessageListener(message, sender) { | ||
} | ||
const handler = getHandler(message, sender); | ||
if (handler) { | ||
const { type, target, args } = message; | ||
debug(type, "↘️ received", { sender, args }); | ||
let handleMessage; | ||
if (target) { | ||
if (!browser.tabs) { | ||
throw new MessengerError(`Message ${type} sent to wrong context, it can't be forwarded to ${JSON.stringify(target)}`); | ||
} | ||
debug(type, "🔀 forwarded", { sender, target }); | ||
const publicMethod = getContentScriptMethod(type); | ||
handleMessage = async () => publicMethod(target, ...args); | ||
} | ||
else { | ||
const localHandler = handlers.get(type); | ||
if (!localHandler) { | ||
warn(type, "⚠️ ignored, can't be handled here"); | ||
return; | ||
} | ||
debug(type, "➡️ will be handled here"); | ||
const meta = { trace: [sender] }; | ||
return handleCall(message, meta, handler.apply(meta, message.args)); | ||
handleMessage = async () => localHandler.apply(meta, args); | ||
} | ||
// More context in https://github.com/pixiebrix/webext-messenger/issues/45 | ||
console.warn("Messenger:", message.type, "received but ignored; No handlers were registered here"); | ||
return handleMessage() | ||
.then((value) => ({ value }), (error) => ({ | ||
// Errors must be serialized because the stacktraces are currently lost on Chrome | ||
// and https://github.com/mozilla/webextension-polyfill/issues/210 | ||
error: serializeError(error), | ||
})) | ||
.then((response) => { | ||
debug(type, "↗️ responding", response); | ||
return { ...response, __webextMessenger }; | ||
}); | ||
} | ||
@@ -65,3 +57,2 @@ export function registerMethods(methods) { | ||
} | ||
// Use "chrome" because the polyfill might not be available when `_registerTarget` is registered | ||
if ("browser" in globalThis) { | ||
@@ -71,6 +62,4 @@ 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); | ||
throw new Error("`webext-messenger` requires `webextension"); | ||
} | ||
} |
import pRetry from "p-retry"; | ||
import { isBackgroundPage } from "webext-detect-page"; | ||
import { deserializeError } from "serialize-error"; | ||
import { isObject, MessengerError, __webext_messenger__, handlers, } from "./shared.js"; | ||
import { resolveNamedTarget } from "./namedTargets.js"; | ||
import { isObject, MessengerError, __webextMessenger, handlers, debug, warn, } from "./shared.js"; | ||
export const errorNonExistingTarget = "Could not establish connection. Receiving end does not exist."; | ||
function isMessengerResponse(response) { | ||
return isObject(response) && response["__webext_messenger__"] === true; | ||
return isObject(response) && response["__webextMessenger"] === true; | ||
} | ||
function makeMessage(type, args, target) { | ||
return { | ||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Private key | ||
__webext_messenger__, | ||
__webextMessenger, | ||
type, | ||
@@ -25,3 +23,3 @@ args, | ||
void sendMessage().catch((error) => { | ||
console.debug("Messenger:", type, "notification failed", { error }); | ||
debug(type, "notification failed", { error }); | ||
}); | ||
@@ -38,3 +36,3 @@ } | ||
} | ||
console.debug("Messenger:", type, "will retry"); | ||
debug(type, "will retry"); | ||
}, | ||
@@ -52,9 +50,8 @@ }); | ||
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())) { | ||
// Contexts without direct Tab access must go through background | ||
if (!browser.tabs) { | ||
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; | ||
const { tabId, frameId = 0 } = target; | ||
// Message tab directly | ||
@@ -70,3 +67,3 @@ return manageConnection(type, options, async () => browser.tabs.sendMessage(tabId, makeMessage(type, args), { frameId })); | ||
if (handler) { | ||
console.warn("Messenger:", type, "is being handled locally"); | ||
warn(type, "is being handled locally"); | ||
return handler.apply({ trace: [] }, args); | ||
@@ -73,0 +70,0 @@ } |
import { Method } from "./types.js"; | ||
export declare const __webext_messenger__ = true; | ||
export declare const __webextMessenger = true; | ||
export declare function isObject(value: unknown): value is Record<string, unknown>; | ||
@@ -8,1 +8,3 @@ export declare class MessengerError extends Error { | ||
export declare const handlers: Map<string, Method>; | ||
export declare const debug: (...args: any[]) => void; | ||
export declare const warn: (...args: any[]) => void; |
@@ -1,3 +0,2 @@ | ||
// eslint-disable-next-line @typescript-eslint/naming-convention -- Private key | ||
export const __webext_messenger__ = true; | ||
export const __webextMessenger = true; | ||
export function isObject(value) { | ||
@@ -18,1 +17,4 @@ return typeof value === "object" && value !== null; | ||
export const handlers = new Map(); | ||
// .bind preserves the call location in the console | ||
export const debug = console.debug.bind(console, "Messenger:"); | ||
export const warn = console.warn.bind(console, "Messenger:"); |
@@ -9,3 +9,3 @@ /// <reference types="firefox-webext-browser" /> | ||
} | ||
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; | ||
@@ -25,3 +25,3 @@ /** Removes the `this` type and ensure it's always Promised */ | ||
/** Guarantees that the message was handled by this library */ | ||
__webext_messenger__: true; | ||
__webextMessenger: true; | ||
}; | ||
@@ -41,3 +41,3 @@ declare type Arguments = any[]; | ||
/** If the message is being sent to an intermediary receiver, also set the target */ | ||
target?: Target | NamedTarget; | ||
target?: Target; | ||
/** If the message is being sent to an intermediary receiver, also set the options */ | ||
@@ -48,3 +48,3 @@ options?: Target; | ||
/** Guarantees that a message is meant to be handled by this library */ | ||
__webext_messenger__: true; | ||
__webextMessenger: true; | ||
}; | ||
@@ -55,7 +55,2 @@ export interface Target { | ||
} | ||
export interface NamedTarget { | ||
/** If the id is missing, it will use the sender’s tabId instead */ | ||
tabId?: number; | ||
name: string; | ||
} | ||
export {}; |
{ | ||
"name": "webext-messenger", | ||
"version": "0.13.0-5", | ||
"version": "0.13.0-6", | ||
"description": "Browser Extension component messaging framework", | ||
@@ -15,3 +15,2 @@ "keywords": [], | ||
"./source/receiver.js": "./source/receiver.ts", | ||
"./source/namedTargets.js": "./source/namedTargets.ts", | ||
"./source/types.js": "./source/types.ts", | ||
@@ -50,3 +49,2 @@ "./source/shared.js": "./source/shared.ts" | ||
"rules": { | ||
"@typescript-eslint/no-unsafe-member-access": "off", | ||
"import/no-unresolved": "off", | ||
@@ -80,3 +78,5 @@ "unicorn/filename-case": [ | ||
"rules": { | ||
"@typescript-eslint/no-non-null-assertion": "off" | ||
"@typescript-eslint/no-non-null-assertion": "off", | ||
"@typescript-eslint/no-explicit-any": "off", | ||
"@typescript-eslint/no-unsafe-member-access": "off" | ||
} | ||
@@ -83,0 +83,0 @@ } |
17599
298