/
mentoster
/
vk-stilus
Обзор
Документация
Войти
/
mentoster
/
vk-stilus
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
3
CI/CD
Аналитика
Безопасность
master
src/platform/browser/runtime-message.ts
130 строк
5 KB
mentoster
feat(browser): add checked runtime messaging
24 июл 2026, 18:25
24 июл 2026, 18:25
1a196bc
Код
Авторство
О чём код?
import { isPlainObject } from './browser-input.js'; import type { BrowserDisposer, BrowserEventLike, BrowserRuntimeMessageListener, BrowserRuntimeSender, RawRecord, } from './browser-types.js'; import { registerBrowserListener } from './listener-registration.js'; /** @assertion-exception Pure Promise-like guard owns no lifecycle state. */ function isPromiseLike(value: unknown): value is PromiseLike<unknown> { return value !== null && typeof value === 'object' && 'then' in value && typeof value.then === 'function'; } /** @assertion-exception Pure nonnegative integer guard owns no lifecycle state. */ function integerValue(value: unknown): number | null { return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : null; } /** @assertion-exception Pure sender URL parser retains only one transient URL object. */ function parsedSenderUrl(value: unknown): URL | null { if (typeof value !== 'string') return null; try { return new URL(value); } catch { return null; } } /** @assertion-exception Pure runtime identifier reader owns no lifecycle state. */ export function runtimeIdentifier(runtime: RawRecord): string { return typeof runtime.id === 'string' ? runtime.id : ''; } /** @assertion-exception Pure sender normalizer drops the raw sender object after projection. */ export function normalizeRuntimeSender(value: unknown, extensionId: string): BrowserRuntimeSender { const sender: Readonly<Record<string, unknown>> = isPlainObject(value) ? value : Object.freeze({}); const tab: Readonly<Record<string, unknown>> = isPlainObject(sender.tab) ? sender.tab : Object.freeze({}); const tabId = integerValue(tab.id); const frameId = integerValue(sender.frameId) ?? -1; const normalized = { self: typeof sender.id === 'string' && sender.id === extensionId, frameId, senderUrl: parsedSenderUrl(sender.url), ...(tabId === null ? {} : { tabId }), }; return Object.freeze(normalized); } /** @assertion-exception Pure runtime-listener guard owns no lifecycle state. */ function runtimeListener(value: unknown): BrowserRuntimeMessageListener { if (typeof value !== 'function') throw new TypeError('runtime message listener is invalid'); return value as BrowserRuntimeMessageListener; } /** @assertion-exception Pure response callback guard owns no lifecycle state. */ function sendResponseCallback(value: unknown): ((response: unknown) => void) | null { return typeof value === 'function' ? (value as (response: unknown) => void) : null; } /** @assertion-exception Promise completion sends one normalized Chromium response. */ function settleChromiumPromise( promise: PromiseLike<unknown>, sendResponse: (response: unknown) => void, ): void { void Promise.resolve(promise).then( (value) => sendResponse(value), () => sendResponse(undefined), ); } /** @assertion-exception Chromium wrapper preserves callback/sendResponse semantics for one request. */ function chromiumRuntimeListener( listener: BrowserRuntimeMessageListener, extensionId: string, ) { return function receiveRuntimeMessage(...args: unknown[]): unknown { const sendResponse = sendResponseCallback(args[2]); if (!sendResponse) return false; try { const value = listener(args[0], normalizeRuntimeSender(args[1], extensionId)); if (isPromiseLike(value)) { settleChromiumPromise(value, sendResponse); return true; } sendResponse(value); return false; } catch { sendResponse(undefined); return false; } }; } /** @assertion-exception Firefox wrapper normalizes every listener result to one Promise. */ function firefoxRuntimeListener( listener: BrowserRuntimeMessageListener, extensionId: string, ) { return function receiveRuntimeMessage(...args: unknown[]): Promise<unknown> { try { return Promise.resolve(listener(args[0], normalizeRuntimeSender(args[1], extensionId))); } catch { return Promise.resolve(undefined); } }; } /** @assertion-exception Synchronous Chromium registration returns one exact disposer. */ export function registerChromiumRuntimeMessage( event: BrowserEventLike, extensionId: string, value: unknown, ): BrowserDisposer { return registerBrowserListener(event, chromiumRuntimeListener(runtimeListener(value), extensionId)); } /** @assertion-exception Synchronous Firefox registration returns one exact disposer. */ export function registerFirefoxRuntimeMessage( event: BrowserEventLike, extensionId: string, value: unknown, ): BrowserDisposer { return registerBrowserListener(event, firefoxRuntimeListener(runtimeListener(value), extensionId)); }