W WRNexusJS
Native · Package reference

@wrnexus/mobile

SSR-safe compatibility access to Capacitor plugins.

v0.8.7Private registryNative

Install the package

After WorkRoot approves private registry access, install the release-aligned package:

bun add @wrnexus/mobile@0.8.7

Request 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 or undefined; 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() and platform() report the current Capacitor environment.

Unavailable required plugins throw MobileUnavailableError with an actionable message.

The package also provides portable application-facing primitives:

  • listenDeepLinks normalizes initial and live links with an allowed-scheme list.
  • PushNotifications performs permission gating and validates registrations.
  • SecureStorage namespaces and validates keys over an application-supplied encrypted
  • Keychain/Keystore adapter; it does not mislabel browser localStorage as secure.

  • OfflineQueue persists bounded sync batches through a pluggable durable store.

Requirements / Notes

  • Capacitor plugin imports must remain in browser-owned modules.
  • @wrnexus/mobile re-exports native from @wrnexus/native for 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 DeepLinkSource {
    current?(): Promise<string | undefined>;
    subscribe(listener: (url: string) => void): void | (() => void);
}
/** Normalize initial and live native links and ignore malformed/unapproved schemes. */
declare function listenDeepLinks(source: DeepLinkSource, listener: (link: DeepLink) => void, schemes?: string[]): () => void;
interface PushRegistration {
    token: string;
    platform?: string;
}
interface PushAdapter {
    permission(): Promise<"granted" | "denied" | "prompt" | "unavailable">;
    requestPermission?(): Promise<"granted" | "denied">;
    register(): Promise<PushRegistration>;
    subscribe?(listener: (notification: unknown) => void): () => void;
}
declare class PushNotifications {
    private readonly adapter;
    constructor(adapter: PushAdapter);
    register(): Promise<PushRegistration>;
    subscribe(listener: (notification: unknown) => void): () => void;
}
interface SecureStorageAdapter {
    get(key: string): Promise<string | null>;
    set(key: string, value: string): Promise<void>;
    remove(key: string): Promise<void>;
}
declare class SecureStorage {
    #private;
    private readonly adapter;
    private readonly namespace;
    constructor(adapter: SecureStorageAdapter, namespace?: string);
    get(key: string): Promise<string | null>;
    set(key: string, value: string): Promise<void>;
    remove(key: string): Promise<void>;
}
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 DeepLinkSource, type MobileEnvironment, type MobilePlatform, MobileUnavailableError, OfflineQueue, type OfflineTask, type OfflineTaskStore, type PushAdapter, PushNotifications, type PushRegistration, SecureStorage, type SecureStorageAdapter, invoke, isNative, listenDeepLinks, 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" };