import type { NativeAdapter, NativeCapability, NativePlatform, NativeRunOptions, NativeTarget, NativeBrowserRuntime, } from "./types.ts"; export class NativeUnavailableError extends Error { constructor(message: string) { super(message); this.name = "NativeUnavailableError"; } } function capacitor(): | { isNativePlatform?: () => boolean; getPlatform?: () => string; } | undefined { if (typeof globalThis === "undefined") return undefined; return (globalThis as typeof globalThis & { Capacitor?: object }).Capacitor; } export function isMobile(): boolean { const bridge = capacitor(); return ( bridge?.isNativePlatform?.() === true || ["ios", "android"].includes(bridge?.getPlatform?.() ?? "") ); } export function platform(): NativePlatform { const bridge = capacitor(); if (isMobile()) return bridge?.getPlatform?.() ?? "webview"; return typeof window === "undefined" ? "server" : "browser"; } const capabilities = new Map(); function browserRuntime(): NativeBrowserRuntime | undefined { if (typeof globalThis === "undefined") return undefined; return (globalThis as typeof globalThis & { WrNexusNative?: NativeBrowserRuntime }).WrNexusNative; } function pendingRegistrations(): Map | undefined { if (typeof window === "undefined") return undefined; const root = globalThis as typeof globalThis & { __WrNexusNativePending?: Map; }; return (root.__WrNexusNativePending ??= new Map()); } export function register( name: string, capability: NativeCapability, ): () => void { if (!/^[a-z][a-z0-9.-]*$/i.test(name)) throw new TypeError(`Invalid native capability name: ${name}`); const value = capability as NativeCapability; capabilities.set(name, value); const runtime = browserRuntime(); if (runtime) runtime.register(name, value); else pendingRegistrations()?.set(name, value); return () => { capabilities.delete(name); const activeRuntime = browserRuntime(); if (activeRuntime) activeRuntime.unregister(name); else pendingRegistrations()?.delete(name); }; } export function registered(): string[] { return [...capabilities.keys()].sort(); } function adapter(name: string, target: NativeTarget): NativeAdapter | undefined { return capabilities.get(name)?.[target]; } export function supports( name: string, target: NativeTarget = isMobile() ? "mobile" : "browser", ): boolean { const value = adapter(name, target); return !!value && (value.supported?.() ?? true); } export async function run( name: string, options?: unknown, runOptions: NativeRunOptions = {}, ): Promise { const target = runOptions.target ?? (isMobile() ? "mobile" : "browser"); const value = adapter(name, target); if (!value || !(value.supported?.() ?? true)) { throw new NativeUnavailableError(`Native capability "${name}" is unavailable on ${target}`); } return (await value.run(options)) as TResult; } export function clearRegistry(): void { capabilities.clear(); }