first commit
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
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<string, NativeCapability>();
|
||||
|
||||
function browserRuntime(): NativeBrowserRuntime | undefined {
|
||||
if (typeof globalThis === "undefined") return undefined;
|
||||
return (globalThis as typeof globalThis & { WrNexusNative?: NativeBrowserRuntime }).WrNexusNative;
|
||||
}
|
||||
|
||||
function pendingRegistrations(): Map<string, NativeCapability> | undefined {
|
||||
if (typeof window === "undefined") return undefined;
|
||||
const root = globalThis as typeof globalThis & {
|
||||
__WrNexusNativePending?: Map<string, NativeCapability>;
|
||||
};
|
||||
return (root.__WrNexusNativePending ??= new Map());
|
||||
}
|
||||
|
||||
export function register<TOptions = unknown, TResult = unknown>(
|
||||
name: string,
|
||||
capability: NativeCapability<TOptions, TResult>,
|
||||
): () => 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<TResult = unknown>(
|
||||
name: string,
|
||||
options?: unknown,
|
||||
runOptions: NativeRunOptions = {},
|
||||
): Promise<TResult> {
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user