@wrnexus/mobile
SSR-safe compatibility access to Capacitor plugins.
Install the package
After WorkRoot approves private registry access, install the release-aligned package:
bun add @wrnexus/mobile@0.5.12Request preview access. Never put registry tokens in source control.
SSR-safe access to Capacitor plugins from WRNexusJS browser code.
Overview
@wrnexus/mobile keeps optional native imports out of server rendering while giving browser-owned modules one consistent registry for Capacitor plugins. During SSR, mobile.isNative() is false and mobile.platform() is "web".
Install a plugin through the WRNexusJS CLI so the web and native projects stay aligned:
wrnexus mobile add @capacitor/camera @capacitor/haptics
Usage
Register and invoke a Capacitor plugin
Import Capacitor packages only from browser-owned code, never from API routes or SSR helpers.
import { Camera, CameraResultType } from "@capacitor/camera";
import { mobile } from "@wrnexus/mobile";
mobile.registerPlugin("Camera", Camera);
export async function takePhoto() {
if (!mobile.isNative()) return null;
return mobile.invoke("Camera", "getPhoto", {
quality: 85,
resultType: CameraResultType.Uri,
});
}
Provide a browser fallback
whenNative runs the first callback only in a Capacitor WebView and can return a web/SSR-safe fallback everywhere else.
import { Haptics, ImpactStyle } from "@capacitor/haptics";
import { mobile } from "@wrnexus/mobile";
mobile.registerPlugin("Haptics", Haptics);
export const confirmAction = () =>
mobile.whenNative(
() => mobile.invoke("Haptics", "impact", { style: ImpactStyle.Medium }),
() => navigator.vibrate?.(30),
);
Read an optional plugin without throwing
import type { NetworkPlugin } from "@capacitor/network";
import { mobile } from "@wrnexus/mobile";
const network = mobile.plugin<NetworkPlugin>("Network");
const status = network ? await network.getStatus() : { connected: true, connectionType: "unknown" };
API
registerPlugin(name, instance)registers a browser-imported plugin.plugin(name)returns a plugin orundefined;requirePlugin(name)throws when absent.invoke(plugin, method, options?)calls a registered method and returns its result.whenNative(native, fallback?)selects native behavior without breaking SSR.isNative()andplatform()report the current Capacitor environment.
Unavailable required plugins throw MobileUnavailableError with an actionable message.
Requirements / Notes
- Capacitor plugin imports must remain in browser-owned modules.
@wrnexus/mobilere-exportsnativefrom@wrnexus/nativefor applications that
prefer the higher-level cross-platform capability API.
Complete TypeScript API
Generated from the exact installed package declarations.
export { native } from '@wrnexus/native';
interface DeepLink {
url: URL;
path: string;
query: URLSearchParams;
}
declare function parseDeepLink(value: string, schemes?: string[]): DeepLink | null;
interface OfflineTask<T = unknown> {
id: string;
type: string;
payload: T;
createdAt: number;
attempts: number;
}
interface OfflineTaskStore {
load(): Promise<OfflineTask[]>;
save(tasks: OfflineTask[]): Promise<void>;
}
declare function memoryOfflineTaskStore(): OfflineTaskStore;
declare class OfflineQueue {
#private;
private readonly store;
constructor(store?: OfflineTaskStore);
process<T>(type: string, handler: (payload: T) => Promise<void>): void;
add<T>(type: string, payload: T): Promise<OfflineTask<T>>;
sync(limit?: number): Promise<{
completed: number;
failed: number;
}>;
size(): Promise<number>;
}
interface MobileEnvironment {
platform: string;
native: boolean;
online: boolean;
userAgent?: string;
}
declare function mobileEnvironment(): MobileEnvironment;
/** @wrnexus/mobile — SSR-safe access to Capacitor's native bridge. */
type MobilePlatform = "ios" | "android" | "web" | string;
interface CapacitorBridge {
isNativePlatform?: () => boolean;
getPlatform?: () => MobilePlatform;
Plugins?: Record<string, unknown>;
}
declare class MobileUnavailableError extends Error {
constructor(message?: string);
}
/** Register a plugin imported by browser-only application code. */
declare function registerPlugin<T extends object>(name: string, instance: T): T;
/** True only inside a native Capacitor iOS or Android WebView. SSR-safe. */
declare function isNative(): boolean;
/** Current Capacitor platform, falling back to `web` during SSR and in browsers. */
declare function platform(): MobilePlatform;
/** Return an injected Capacitor plugin, or undefined when it is unavailable. */
declare function plugin<T extends object>(name: string): T | undefined;
/** Require an installed native plugin and produce a useful error when absent. */
declare function requirePlugin<T extends object>(name: string): T;
/** Invoke a plugin method without importing native code into an SSR module. */
declare function invoke<TResult = unknown>(pluginName: string, method: string, options?: unknown): Promise<TResult>;
/** Run native behavior when available, with an optional SSR/web fallback. */
declare function whenNative<T>(native: () => T | Promise<T>, fallback?: () => T | Promise<T>): Promise<T | undefined>;
declare const mobile: {
isNative: typeof isNative;
platform: typeof platform;
registerPlugin: typeof registerPlugin;
plugin: typeof plugin;
requirePlugin: typeof requirePlugin;
invoke: typeof invoke;
whenNative: typeof whenNative;
};
export { type CapacitorBridge, type DeepLink, type MobileEnvironment, type MobilePlatform, MobileUnavailableError, OfflineQueue, type OfflineTask, type OfflineTaskStore, invoke, isNative, memoryOfflineTaskStore, mobile, mobileEnvironment, parseDeepLink, platform, plugin, registerPlugin, requirePlugin, whenNative };
Examples
Copy-ready examples from the installed package documentation.
Register and invoke a Capacitor plugin
import { Camera, CameraResultType } from "@capacitor/camera";
import { mobile } from "@wrnexus/mobile";
mobile.registerPlugin("Camera", Camera);
export async function takePhoto() {
if (!mobile.isNative()) return null;
return mobile.invoke("Camera", "getPhoto", {
quality: 85,
resultType: CameraResultType.Uri,
});
}Provide a browser fallback
import { Haptics, ImpactStyle } from "@capacitor/haptics";
import { mobile } from "@wrnexus/mobile";
mobile.registerPlugin("Haptics", Haptics);
export const confirmAction = () =>
mobile.whenNative(
() => mobile.invoke("Haptics", "impact", { style: ImpactStyle.Medium }),
() => navigator.vibrate?.(30),
);Read an optional plugin without throwing
import type { NetworkPlugin } from "@capacitor/network";
import { mobile } from "@wrnexus/mobile";
const network = mobile.plugin<NetworkPlugin>("Network");
const status = network ? await network.getStatus() : { connected: true, connectionType: "unknown" };