// Runtime web-channel plugin helpers expose web-channel tools through activated plugin runtimes. import { getDefaultLocalRoots as getDefaultLocalRootsImpl, loadWebMedia as loadWebMediaImpl, loadWebMediaRaw as loadWebMediaRawImpl, optimizeImageToJpeg as optimizeImageToJpegImpl, } from "../../media/web-media.js"; import { createPluginModuleLoaderCache, type PluginModuleLoaderCache, } from "../plugin-module-loader-cache.js"; import type { PluginOrigin } from "../plugin-origin.types.js"; import { loadPluginBoundaryModule, resolvePluginRuntimeRecordByEntryBaseNames, resolvePluginRuntimeModulePath, } from "./runtime-plugin-boundary.js"; type WebChannelPluginRecord = { origin?: PluginOrigin; rootDir?: string; source: string; }; type WebChannelConnectionWaitOptions = | { timeout: "none"; } | { timeoutMs: number; }; type WebChannelLightRuntimeModule = { getActiveWebListener: (accountId?: string | null) => unknown; getWebAuthAgeMs: (authDir?: string) => number | null; logWebSelfId: (authDir?: string, runtime?: unknown, includeChannelPrefix?: boolean) => void; logoutWeb: (params: { authDir?: string; isLegacyAuthDir?: boolean; runtime?: unknown; }) => Promise; readWebSelfId: (authDir?: string) => { e164: string | null; jid: string | null; lid: string | null; }; webAuthExists: (authDir?: string) => Promise; formatError: (error: unknown) => string; getStatusCode: (error: unknown) => number | undefined; pickWebChannel: (pref: string, authDir?: string) => Promise; resolveDefaultWebAuthDir?: () => string; WA_WEB_AUTH_DIR?: string; }; type WebChannelHeavyRuntimeModule = { loginWeb: ( verbose: boolean, waitForConnection?: (sock: unknown) => Promise, runtime?: unknown, accountId?: string, ) => Promise; monitorWebChannel: (...args: unknown[]) => Promise; monitorWebInbox: (...args: unknown[]) => Promise; startWebLoginWithQr: (...args: unknown[]) => Promise; waitForWaConnection: (sock: unknown, options: WebChannelConnectionWaitOptions) => Promise; waitForWebLogin: (...args: unknown[]) => Promise; extractMediaPlaceholder: (...args: unknown[]) => unknown; extractText: (...args: unknown[]) => unknown; }; type WebChannelRuntimeModuleKind = "heavy" | "light"; type CachedWebChannelRuntimeModule = { modulePath: string; module: WebChannelHeavyRuntimeModule | WebChannelLightRuntimeModule; }; const webChannelRuntimeModuleCache = new Map< WebChannelRuntimeModuleKind, CachedWebChannelRuntimeModule >(); const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache(); /** Resolves the active web-channel plugin record that provides runtime APIs. */ function resolveWebChannelPluginRecord(): WebChannelPluginRecord { return resolvePluginRuntimeRecordByEntryBaseNames(["light-runtime-api", "runtime-api"], () => { throw new Error( "web channel plugin runtime is unavailable: missing plugin that provides light-runtime-api and runtime-api", ); }) as WebChannelPluginRecord; } function resolveWebChannelRuntimeModulePath( record: WebChannelPluginRecord, entryBaseName: "light-runtime-api" | "runtime-api", ): string { const modulePath = resolvePluginRuntimeModulePath(record, entryBaseName, () => { throw new Error(`web channel plugin runtime is unavailable: missing ${entryBaseName}`); }); if (!modulePath) { throw new Error(`web channel plugin runtime is unavailable: missing ${entryBaseName}`); } return modulePath; } function loadCurrentHeavyModuleSync(): WebChannelHeavyRuntimeModule { const record = resolveWebChannelPluginRecord(); const modulePath = resolveWebChannelRuntimeModulePath(record, "runtime-api"); return loadPluginBoundaryModule(modulePath, moduleLoaders, { origin: record.origin, }); } function getCachedWebChannelRuntimeModule( kind: WebChannelRuntimeModuleKind, modulePath: string, load: () => T, ): T { const cached = webChannelRuntimeModuleCache.get(kind); if (cached?.modulePath === modulePath) { return cached.module as T; } const loaded = load(); webChannelRuntimeModuleCache.set(kind, { modulePath, module: loaded }); return loaded; } function loadWebChannelLightModule(): WebChannelLightRuntimeModule { const record = resolveWebChannelPluginRecord(); const modulePath = resolveWebChannelRuntimeModulePath(record, "light-runtime-api"); return getCachedWebChannelRuntimeModule("light", modulePath, () => loadPluginBoundaryModule(modulePath, moduleLoaders, { origin: record.origin, }), ); } async function loadWebChannelHeavyModule(): Promise { const record = resolveWebChannelPluginRecord(); const modulePath = resolveWebChannelRuntimeModulePath(record, "runtime-api"); return getCachedWebChannelRuntimeModule("heavy", modulePath, () => loadPluginBoundaryModule(modulePath, moduleLoaders, { origin: record.origin, }), ); } function getLightExport( exportName: K, ): NonNullable { const loaded = loadWebChannelLightModule(); const value = loaded[exportName]; if (value == null) { throw new Error(`web channel plugin runtime is missing export '${exportName}'`); } return value as NonNullable; } async function getHeavyExport( exportName: K, ): Promise> { const loaded = await loadWebChannelHeavyModule(); const value = loaded[exportName]; if (value == null) { throw new Error(`web channel plugin runtime is missing export '${exportName}'`); } return value as NonNullable; } /** Returns the active web channel listener from the light runtime API. */ export function getActiveWebListener( ...args: Parameters ): ReturnType { return getLightExport("getActiveWebListener")(...args); } /** Returns web-auth age from the light runtime API. */ export function getWebAuthAgeMs( ...args: Parameters ): ReturnType { return getLightExport("getWebAuthAgeMs")(...args); } /** Logs the active web account self id through the light runtime API. */ export function logWebSelfId( ...args: Parameters ): ReturnType { return getLightExport("logWebSelfId")(...args); } /** Starts web-channel login through the heavy runtime API. */ export function loginWeb( ...args: Parameters ): ReturnType { return loadWebChannelHeavyModule().then((loaded) => loaded.loginWeb(...args)); } /** Logs out the web-channel account through the light runtime API. */ export function logoutWeb( ...args: Parameters ): ReturnType { return getLightExport("logoutWeb")(...args); } /** Reads the web-channel self id through the light runtime API. */ export function readWebSelfId( ...args: Parameters ): ReturnType { return getLightExport("readWebSelfId")(...args); } /** Checks whether web-channel auth exists through the light runtime API. */ export function webAuthExists( ...args: Parameters ): ReturnType { return getLightExport("webAuthExists")(...args); } /** Formats a web-channel runtime error through the light runtime API. */ export function formatError( ...args: Parameters ): ReturnType { return getLightExport("formatError")(...args); } /** Reads a web-channel status code from the light runtime API. */ export function getStatusCode( ...args: Parameters ): ReturnType { return getLightExport("getStatusCode")(...args); } /** Picks the active web channel through the light runtime API. */ export function pickWebChannel( ...args: Parameters ): ReturnType { return getLightExport("pickWebChannel")(...args); } /** Resolves the default web-channel auth directory from the light runtime API. */ export function resolveWebChannelAuthDir(): ReturnType< NonNullable > { const loaded = loadWebChannelLightModule(); if (loaded.resolveDefaultWebAuthDir) { return loaded.resolveDefaultWebAuthDir(); } // Older light runtimes expose the default auth dir as a primitive string. // Do not accept string-like objects here; Node path APIs reject them before // coercion. if (typeof loaded.WA_WEB_AUTH_DIR === "string") { return loaded.WA_WEB_AUTH_DIR; } throw new Error("web channel plugin runtime is missing export 'resolveDefaultWebAuthDir'"); } /** Loads web media through the core media helper. */ export async function loadWebMedia( ...args: Parameters ): ReturnType { return await loadWebMediaImpl(...args); } /** Loads raw web media through the core media helper. */ export async function loadWebMediaRaw( ...args: Parameters ): ReturnType { return await loadWebMediaRawImpl(...args); } /** Starts web-channel monitoring through the heavy runtime API. */ export function monitorWebChannel( ...args: Parameters ): ReturnType { return loadWebChannelHeavyModule().then((loaded) => loaded.monitorWebChannel(...args)); } /** Starts web inbox monitoring through the heavy runtime API. */ export async function monitorWebInbox( ...args: Parameters ): ReturnType { return (await getHeavyExport("monitorWebInbox"))(...args); } /** Optimizes an image to JPEG through the core media helper. */ export async function optimizeImageToJpeg( ...args: Parameters ): ReturnType { return await optimizeImageToJpegImpl(...args); } /** Starts QR login through the heavy runtime API. */ export async function startWebLoginWithQr( ...args: Parameters ): ReturnType { return (await getHeavyExport("startWebLoginWithQr"))(...args); } /** Waits for web-channel socket connection through the heavy runtime API. */ export async function waitForWebChannelConnection( ...args: Parameters ): ReturnType { return (await getHeavyExport("waitForWaConnection"))(...args); } /** Waits for web-channel login through the heavy runtime API. */ export async function waitForWebLogin( ...args: Parameters ): ReturnType { return (await getHeavyExport("waitForWebLogin"))(...args); } /** Extracts media placeholders through the heavy runtime API. */ export const extractMediaPlaceholder = ( ...args: Parameters ) => loadCurrentHeavyModuleSync().extractMediaPlaceholder(...args); /** Extracts text through the heavy runtime API. */ export const extractText = (...args: Parameters) => loadCurrentHeavyModuleSync().extractText(...args); /** Returns default local media roots through the core media helper. */ export function getDefaultLocalRoots( ...args: Parameters ): ReturnType { return getDefaultLocalRootsImpl(...args); }