New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

webext-messenger

Package Overview
Dependencies
Maintainers
1
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

webext-messenger - npm Package Compare versions

Comparing version 0.13.0-1 to 0.13.0-2

distribution/namedTargets.d.ts

62

distribution/index.d.ts

@@ -1,58 +0,4 @@

/// <reference types="firefox-webext-browser" />
import { Asyncify, SetReturnType, ValueOf } from "type-fest";
declare global {
interface MessengerMethods {
_: Method;
__webextMessengerTargetRegistration: typeof _registerTarget;
}
}
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;
/** Removes the `this` type and ensure it's always Promised */
declare type PublicMethod<Method extends ValueOf<MessengerMethods>> = Asyncify<ActuallyOmitThisParameter<Method>>;
declare type PublicMethodWithTarget<Method extends ValueOf<MessengerMethods>> = WithTarget<PublicMethod<Method>>;
export interface MessengerMeta {
trace: browser.runtime.MessageSender[];
}
declare type Arguments = any[];
declare type Method = (this: MessengerMeta, ...args: Arguments) => Promise<unknown>;
export declare class MessengerError extends Error {
name: string;
}
export interface Target {
tabId: number;
frameId?: number;
}
export interface NamedTarget {
/** If the id is missing, it will use the sender’s tabId instead */
tabId?: number;
name: string;
}
interface Options {
/**
* "Notifications" won't await the response, return values, attempt retries, nor throw errors
* @default false
*/
isNotification?: boolean;
}
/**
* Replicates the original method, including its types.
* To be called in the sender’s end.
*/
declare function getContentScriptMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethod extends PublicMethodWithTarget<Method>>(type: Type, options: {
isNotification: true;
}): SetReturnType<PublicMethod, void>;
declare function getContentScriptMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethod extends PublicMethodWithTarget<Method>>(type: Type, options?: Options): PublicMethod;
/**
* Replicates the original method, including its types.
* To be called in the sender’s end.
*/
declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends PublicMethod<Method>>(type: Type, options: {
isNotification: true;
}): SetReturnType<PublicMethodType, void>;
declare function getMethod<Type extends keyof MessengerMethods, Method extends MessengerMethods[Type], PublicMethodType extends PublicMethod<Method>>(type: Type, options?: Options): PublicMethodType;
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 { registerMethods } from "./receiver";
export { getContentScriptMethod, getMethod } from "./sender";
export { MessengerMeta, NamedTarget, Target } from "./types";
export { registerTarget } from "./namedTargets";

@@ -1,189 +0,5 @@

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
const __webext_messenger__ = true;
function isObject(value) {
return typeof value === "object" && value !== null;
}
function isMessengerMessage(message) {
return (isObject(message) &&
typeof message["type"] === "string" &&
message["__webext_messenger__"] === true &&
Array.isArray(message["args"]));
}
function isMessengerResponse(response) {
return isObject(response) && response["__webext_messenger__"] === true;
}
const handlers = new Map();
async function handleCall(message, sender, call) {
console.debug(`Messenger:`, message.type, message.args, "from", { sender });
// 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__ };
}
async function handleMessage(message, sender) {
if (message.target) {
if (!isBackgroundPage()) {
console.warn("Messenger:", message.type, "received but ignored; Wrong context");
return;
}
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));
}
const handler = handlers.get(message.type);
if (handler) {
return handleCall(message, sender, handler.apply(sender, message.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");
}
// Do not turn this into an `async` function; Notifications must turn `void`
function manageConnection(type, options, sendMessage) {
if (!options.isNotification) {
return manageMessage(type, sendMessage);
}
void sendMessage().catch((error) => {
console.debug("Messenger:", type, "notification failed", { error });
});
}
async function manageMessage(type, sendMessage) {
const response = await pRetry(sendMessage, {
minTimeout: 100,
factor: 1.3,
maxRetryTime: 4000,
onFailedAttempt(error) {
if (!String(error === null || error === void 0 ? void 0 : error.message).startsWith(errorNonExistingTarget)) {
throw error;
}
console.debug("Messenger:", type, "will retry");
},
});
if (!isMessengerResponse(response)) {
throw new MessengerError(`No handler for ${type} was registered in the receiving end`);
}
if ("error" in response) {
throw deserializeError(response.error);
}
return response.value;
}
// MUST NOT be `async` or Promise-returning-only
function onMessageListener(message, sender) {
if (isMessengerMessage(message)) {
return handleMessage(message, { trace: [sender] });
}
// TODO: Add test for this eventuality: ignore unrelated messages
}
function makeMessage(type, args, target) {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention -- Private key
__webext_messenger__,
type,
args,
target,
};
}
function getContentScriptMethod(type, options = {}) {
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 }));
};
return publicMethod;
}
function getMethod(type, options = {}) {
const publicMethod = (...args) => {
if (isBackgroundPage()) {
const handler = handlers.get(type);
if (handler) {
console.warn("Messenger:", type, "is being handled locally");
return handler.apply({ trace: [] }, args);
}
throw new MessengerError("No handler registered for " + type);
}
const sendMessage = async () => browser.runtime.sendMessage(makeMessage(type, args));
return manageConnection(type, options, sendMessage);
};
return publicMethod;
}
function registerMethods(methods) {
for (const [type, method] of Object.entries(methods)) {
if (handlers.has(type)) {
throw new MessengerError(`Handler already set for ${type}`);
}
console.debug(`Messenger: Registered`, type);
handlers.set(type, method);
}
// 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);
}
}
// 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 { registerMethods } from "./receiver";
export { getContentScriptMethod, getMethod } from "./sender";
export { registerTarget } from "./namedTargets";
import { initTargets } from "./namedTargets";
initTargets();
{
"name": "webext-messenger",
"version": "0.13.0-1",
"version": "0.13.0-2",
"description": "Browser Extension component messaging framework",

@@ -11,10 +11,6 @@ "keywords": [],

"main": "distribution/index.js",
"files": [
"distribution/index.js",
"distribution/index.d.ts"
],
"scripts": {
"build": "tsc",
"demo:watch": "parcel watch test/demo-extension/manifest.json --dist-dir test/dist --no-cache --no-hmr --detailed-report 0",
"demo:build": "parcel build test/demo-extension/manifest.json --dist-dir test/dist --no-cache --detailed-report 0 --no-optimize",
"demo:watch": "parcel watch --no-cache --no-hmr --detailed-report 0",
"demo:build": "parcel build --no-cache --detailed-report 0",
"prepack": "tsc --sourceMap false",

@@ -110,6 +106,12 @@ "test": "eslint . && tsc --noEmit",

"targets": {
"main": false
"main": false,
"default": {
"source": "source/test/manifest.json",
"sourceMap": {
"inline": true
}
}
},
"webExt": {
"sourceDir": "test/dist",
"sourceDir": "dist",
"run": {

@@ -116,0 +118,0 @@ "startUrl": [

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc