/// Single chrome.runtime.onMessage entry. Classifies the sender and dispatches /// to popup-only or content-callable handlers. Unauthorized senders are /// rejected with { ok: false, error: 'unauthorized_sender' }. import type { PopupMessage, Request, Response } from '../../shared/messages'; import { POPUP_ONLY_TYPES, CONTENT_CALLABLE_TYPES } from '../../shared/messages'; import type { Manifest } from '../../shared/types'; import type { GitHost } from '../git-host'; import * as popupOnly from './popup-only'; import * as contentCallable from './content-callable'; export interface RouterState { manifest: Manifest | null; gitHost: GitHost | null; // eslint-disable-next-line @typescript-eslint/no-explicit-any wasm: any; } /// Popup-only messages the setup tab is also allowed to send. /// - save_setup: wires vault config + image into chrome.storage.local at end of init. /// - rate_passphrase: drives the zxcvbn strength meter during passphrase entry. /// - is_unlocked: setup step-4 pings the extension to detect "save config to extension" availability. const SETUP_ALLOWED: ReadonlySet = new Set([ 'save_setup', 'rate_passphrase', 'is_unlocked', ]); export async function route( msg: Request, state: RouterState, sender: chrome.runtime.MessageSender, ): Promise { const popupUrl = chrome.runtime.getURL('popup.html'); const setupUrl = chrome.runtime.getURL('setup.html'); const senderUrl = sender.url ?? ''; const isPopup = senderUrl === popupUrl; const isSetup = senderUrl.startsWith(setupUrl); const isContent = sender.tab !== undefined && sender.frameId === 0 && sender.id === chrome.runtime.id; if (POPUP_ONLY_TYPES.has(msg.type as never)) { if (!(isPopup || (isSetup && SETUP_ALLOWED.has(msg.type as PopupMessage['type'])))) { // eslint-disable-next-line no-console console.warn('[relicario router] rejected popup-only message from wrong sender', { type: msg.type, senderUrl, isPopup, isSetup, isContent, }); return { ok: false, error: 'unauthorized_sender' }; } return popupOnly.handle(msg as never, state, sender); } if (CONTENT_CALLABLE_TYPES.has(msg.type as never)) { if (!isContent) { // eslint-disable-next-line no-console console.warn('[relicario router] rejected content-only message from wrong sender', { type: msg.type, senderUrl, isPopup, isSetup, isContent, frameId: sender.frameId, senderId: sender.id, }); return { ok: false, error: 'unauthorized_sender' }; } return contentCallable.handle(msg as never, state, sender); } // eslint-disable-next-line no-console console.warn('[relicario router] unknown message type', { type: (msg as { type: string }).type }); return { ok: false, error: 'unknown_message_type' }; }