webext-messenger
Advanced tools
Comparing version 0.26.0 to 0.27.0
@@ -6,4 +6,5 @@ import { serializeError } from "serialize-error"; | ||
import { log } from "./logging.js"; | ||
import { getActionForMessage } from "./thisTarget.js"; | ||
import { getActionForMessage } from "./targetLogic.js"; | ||
import { didUserRegisterMethods, handlers } from "./handlers.js"; | ||
import { getTabDataStatus, thisTarget } from "./thisTarget.js"; | ||
export function isMessengerMessage(message) { | ||
@@ -22,4 +23,9 @@ return (isObject(message) && | ||
// Target check must be synchronous (`await` means we're handling the message) | ||
const action = getActionForMessage(sender, message); | ||
const action = getActionForMessage(sender, message.target, thisTarget); | ||
if (action === "ignore") { | ||
log.debug(message.type, "🤫 ignored due to target mismatch", { | ||
requestedTarget: message.target, | ||
thisTarget, | ||
tabDataStatus: getTabDataStatus(), | ||
}); | ||
return; | ||
@@ -79,4 +85,2 @@ } | ||
/** Ensure/document that the current function was called via Messenger */ | ||
export function assertMessengerCall(_this | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function -- TypeScript already does this, it's a documentation-only call | ||
) { } | ||
export function assertMessengerCall(_this) { } |
@@ -138,3 +138,3 @@ import pRetry from "p-retry"; | ||
// Tab 2 sends: 12000, 12001, 12002 | ||
let globalSeq = (Date.now() % 100) * 10000; | ||
let globalSeq = (Date.now() % 100) * 10_000; | ||
function messenger(type, options, target, ...args) { | ||
@@ -141,0 +141,0 @@ options.seq = globalSeq++; |
@@ -1,3 +0,25 @@ | ||
import { type AnyTarget, type TopLevelFrame, type Message, type MessengerMeta, type Sender, type FrameTarget } from "./types.js"; | ||
export declare function getActionForMessage(from: Sender, message: Message): "respond" | "forward" | "ignore"; | ||
import { type AnyTarget, type KnownTarget, type TopLevelFrame, type MessengerMeta, type FrameTarget } from "./types.js"; | ||
/** | ||
* @file This file exists because `runtime.sendMessage` acts as a broadcast to | ||
* all open extension pages, so the receiver needs a way to figure out if the | ||
* message was intended for them. | ||
* | ||
* If the requested target only includes a `page` (URL), then it can be determined | ||
* immediately. If the target also specifies a tab, like `{tabId: 1, page: '/sidebar.html'}`, | ||
* then the receiving target needs to fetch the tab information via `__getTabData`. | ||
* | ||
* `__getTabData` is called automatically when `webext-messenger` is imported in | ||
* a context that requires this logic (most extension:// pages). | ||
* | ||
* If a broadcast message with `tabId` target is received before `__getTabData` is "received", | ||
* the message will be ignored and it can be retried. If `__getTabData` somehow fails, | ||
* the target will forever ignore any messages that require the `tabId`. In that case, | ||
* an error would be thrown once and will be visible in the console, uncaught. | ||
* | ||
* Content scripts do not use this logic at all at the moment because they're | ||
* always targeted via `tabId/frameId` combo and `tabs.sendMessage`. | ||
*/ | ||
export declare const thisTarget: KnownTarget; | ||
declare let tabDataStatus: "needed" | "pending" | "received" | "not-needed" | "error"; | ||
export declare function getTabDataStatus(): typeof tabDataStatus; | ||
export declare function __getTabData(this: MessengerMeta): AnyTarget; | ||
@@ -7,1 +29,2 @@ export declare function getThisFrame(): Promise<FrameTarget>; | ||
export declare function initPrivateApi(): void; | ||
export {}; |
@@ -1,6 +0,5 @@ | ||
import { getContextName, isBackground, isContentScript, isExtensionContext, } from "webext-detect-page"; | ||
import { getContextName, isBackground, isExtensionContext, } from "webext-detect-page"; | ||
import { messenger } from "./sender.js"; | ||
import { registerMethods } from "./receiver.js"; | ||
import { MessengerError, once } from "./shared.js"; | ||
import { log } from "./logging.js"; | ||
/** | ||
@@ -29,3 +28,3 @@ * @file This file exists because `runtime.sendMessage` acts as a broadcast to | ||
// If a message is received before this is ready, it will just have to be ignored. | ||
const thisTarget = isBackground() | ||
export const thisTarget = isBackground() | ||
? { page: "background" } | ||
@@ -45,54 +44,5 @@ : { | ||
isBackground() ? "not-needed" : "needed"; | ||
function compareTargets(to, thisTarget) { | ||
for (const [key, value] of Object.entries(to)) { | ||
if (thisTarget[key] === value) { | ||
continue; | ||
} | ||
if (key !== "page") { | ||
return false; | ||
} | ||
const toUrl = new URL(to.page, location.origin); | ||
const thisUrl = new URL(thisTarget.page, location.origin); | ||
if (toUrl.pathname !== thisUrl.pathname) { | ||
return false; | ||
} | ||
for (const [parameterKey, parameterValue] of toUrl.searchParams) { | ||
if (thisUrl.searchParams.get(parameterKey) !== parameterValue) { | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
export function getTabDataStatus() { | ||
return tabDataStatus; | ||
} | ||
// TODO: Test this in Jest, outside the browser | ||
export function getActionForMessage(from, message) { | ||
// Clone object because we're editing it | ||
const to = { ...message.target }; | ||
if (to.page === "any") { | ||
return "respond"; | ||
} | ||
// Content scripts only receive messages that are meant for them. In the future | ||
// they'll also forward them, but that still means they need to be handled here. | ||
if (isContentScript()) { | ||
return "respond"; | ||
} | ||
// We're in an extension page, but the target is not one. | ||
if (!to.page) { | ||
return "forward"; | ||
} | ||
// Set "this" tab to the current tabId | ||
if (to.tabId === "this" && thisTarget.tabId === from.tab?.id) { | ||
to.tabId = thisTarget.tabId; | ||
} | ||
// Every `target` key must match `thisTarget` | ||
const isThisTarget = compareTargets(to, thisTarget); | ||
if (!isThisTarget) { | ||
log.debug(message.type, "🤫 ignored due to target mismatch", { | ||
requestedTarget: to, | ||
thisTarget, | ||
tabDataStatus, | ||
}); | ||
} | ||
return isThisTarget ? "respond" : "ignore"; | ||
} | ||
const storeTabData = once(async () => { | ||
@@ -104,5 +54,3 @@ if (tabDataStatus !== "needed") { | ||
tabDataStatus = "pending"; | ||
Object.assign(thisTarget, { | ||
...(await messenger("__getTabData", {}, { page: "any" })), | ||
}); | ||
Object.assign(thisTarget, await messenger("__getTabData", {}, { page: "any" })); | ||
tabDataStatus = "received"; | ||
@@ -125,3 +73,4 @@ } | ||
try { | ||
moreInfo = `(context: ${getContextName()}, url: ${globalThis.location?.href})`; | ||
moreInfo = `(context: ${getContextName()}, url: ${globalThis.location | ||
?.href})`; | ||
} | ||
@@ -128,0 +77,0 @@ catch { } |
{ | ||
"name": "webext-messenger", | ||
"version": "0.26.0", | ||
"version": "0.27.0", | ||
"description": "Browser Extension component messaging framework", | ||
@@ -12,3 +12,3 @@ "keywords": [], | ||
".": { | ||
"types": "./distribution/index.js", | ||
"types": "./distribution/index.d.ts", | ||
"default": "./distribution/index.js" | ||
@@ -23,3 +23,4 @@ }, | ||
"prepack": "tsc --sourceMap false", | ||
"test": "eslint . && tsc --noEmit", | ||
"test": "run-p test:unit lint build demo:build", | ||
"test:unit": "vitest run", | ||
"lint": "eslint .", | ||
@@ -31,5 +32,5 @@ "fix": "eslint . --fix", | ||
"p-retry": "^6.2.0", | ||
"serialize-error": "^11.0.2", | ||
"type-fest": "^4.10.2", | ||
"webext-detect-page": "^5.0.0" | ||
"serialize-error": "^11.0.3", | ||
"type-fest": "^4.13.0", | ||
"webext-detect-page": "^5.0.1" | ||
}, | ||
@@ -42,8 +43,8 @@ "@parcel/resolver-default": { | ||
"@sindresorhus/tsconfig": "^5.0.0", | ||
"@types/chrome": "^0.0.259", | ||
"@types/chrome": "^0.0.263", | ||
"@types/tape": "^5.6.4", | ||
"@types/webextension-polyfill": "^0.10.7", | ||
"buffer": "^6.0.3", | ||
"eslint": "^8.56.0", | ||
"eslint-config-pixiebrix": "^0.34.1", | ||
"eslint": "^8.57.0", | ||
"eslint-config-pixiebrix": "^0.37.2", | ||
"events": "^3.3.0", | ||
@@ -55,4 +56,5 @@ "npm-run-all": "^4.1.5", | ||
"stream-browserify": "^3.0.0", | ||
"tape": "^5.7.4", | ||
"typescript": "^5.3.3", | ||
"tape": "^5.7.5", | ||
"typescript": "^5.4.2", | ||
"vitest": "^1.4.0", | ||
"webext-content-scripts": "^2.6.1", | ||
@@ -59,0 +61,0 @@ "webextension-polyfill": "^0.10.0" |
37210
25
731
19
Updatedserialize-error@^11.0.3
Updatedtype-fest@^4.13.0
Updatedwebext-detect-page@^5.0.1