Lynx/Modules/Dev Client/Usage
@sigx/lynx-dev-client · Stable

Using Dev Client#

Stream on-device console logs to your terminal, debug-only — auto-wired by the plugin, with a small JS API for the rare case you need to drive it yourself.

Basic usage#

You rarely call this package directly. Add it as a dev dependency and the rest happens for you:

Terminal
pnpm add -D @sigx/lynx-dev-client

@sigx/lynx-cli's autolinker picks the package up from node_modules, and @sigx/lynx-plugin auto-installs the console log streamer in dev builds by prepending the package's side-effect ./install entry to your background entry. Release builds drop the module entirely — it is marked debugOnly on both platforms — so nothing ships to production.

Once sigx dev is running, every console.log / info / warn / error / debug / trace on the device is mirrored in your terminal. The original console methods still run, so the on-device logbox and Chrome inspector keep working too. Pass --no-device-logs to sigx dev to opt out.

Device runtime exceptions#

On-device runtime exceptions — the native red-screen errors, a superset of the JS lynx.onError hook that also catches main-thread-script, template, render and native-module errors — also stream into the terminal's Logs tab, printed as 📱 <platform> … ERR … lines. So anything that lands on the on-device red screen is copyable in your terminal instead of trapped on the device. The native error sink POSTs to the dev log server (the dev port + 1), and errors that also travel the JS console path are de-duplicated server-side within a short window, so each one shows up once. The --no-device-logs opt-out still governs console.* streaming.

The only value you typically import is the version string:

TypeScript
import { DEV_CLIENT_VERSION } from '@sigx/lynx-dev-client';

Streaming console logs yourself#

If you are running outside the standard plugin flow — a custom harness, a test rig, or a non-Lynx host — you can install the streamer by hand. installConsoleStreamer patches the console, buffers entries, and ships batches over a single persistent WebSocket. It returns an uninstall function that restores the originals and closes the socket.

TypeScript
import { installConsoleStreamer } from '@sigx/lynx-dev-client';
import '@sigx/lynx-websocket'; // attaches a WHATWG WebSocket to globalThis

const uninstall = installConsoleStreamer('ws://localhost:3001/__sigx/logs');

// ...later, to restore the original console and tear down the socket:
uninstall();

The Lynx background runtime has no fetch or XMLHttpRequest, so the streamer needs a WebSocket. Importing @sigx/lynx-websocket (side-effect) attaches a native WebSocket — URLSessionWebSocketTask on iOS, OkHttp on Android — to globalThis, which the streamer consumes by default. The default endpoint used by the CLI is ws://<host>:<devPort+1>/__sigx/logs.

installConsoleStreamer is idempotent: a second call returns the previous uninstall function rather than patching twice. It also bails gracefully (returning a no-op uninstall) if the URL is falsy, globalThis.console is missing, or no WebSocket is available — so it never throws at install time.

Tuning buffering and reconnect#

All behavior is configurable through the options object. Common knobs: the bounded in-memory queue (so a log loop can't run away while the server is unreachable), the flush batch size and coalesce interval, and the reconnect backoff.

TypeScript
import { installConsoleStreamer } from '@sigx/lynx-dev-client';

const uninstall = installConsoleStreamer('ws://localhost:3001/__sigx/logs', {
  platform: 'ios',
  maxQueueSize: 500,
  flushBatchSize: 50,
  flushIntervalMs: 100,
  backoffInitialMs: 1000,
  backoffMaxMs: 30000,
});

Notes on the streamer's behavior:

  • Errors flush immediately; other levels coalesce on flushIntervalMs.
  • The queue is bounded by maxQueueSize (default 500) while disconnected.
  • Reconnect uses exponential backoff, doubling from backoffInitialMs up to backoffMaxMs.
  • WebSocket and network failures never route through the patched console.* — they use the captured originals to avoid infinite self-error loops.
  • Platform detection is lazy and best-effort, then upgraded by an async native probe shortly after install. Pass platform to skip the guess.

Patching extra console targets#

On the Lynx background runtime, globalThis.console and the tt.define factory-scope console parameter are different objects. To capture both you patch the extra target as well — this is exactly what the auto-install path does:

TypeScript
import { installConsoleStreamer } from '@sigx/lynx-dev-client';

// `factoryConsole` is the console passed into the BG factory function.
const uninstall = installConsoleStreamer('ws://localhost:3001/__sigx/logs', {
  extraTargets: [factoryConsole],
});

Serializing a single argument#

serializeArg is the same defensive serializer the streamer uses per console argument. It never throws — handy if you build your own log transport. It handles primitives, BigInt (with an n suffix), Symbol, functions, Error (via .stack), arrays, plain objects, and circular references, falling back to [Unserialisable] for anything it can't stringify.

TypeScript
import { serializeArg } from '@sigx/lynx-dev-client';

serializeArg(42);                 // "42"
serializeArg({ a: 1 });           // '{"a":1}'
serializeArg(new Error('boom'));  // the error's stack string

const obj: Record<string, unknown> = {};
obj.self = obj;
serializeArg(obj);                // contains "[Circular]"

Platform and permission notes#

This is a debug-only, auto-linked module — keep it under devDependencies so it is stripped from release builds along with its permissions. The on-device QR scanner needs camera access; the autolinker adds NSCameraUsageDescription to Info.plist ("Used to scan QR codes from the dev server.") and android.permission.CAMERA to the Android manifest. Native dev features — the dev menu, QR scanner, error overlay, perf HUD, shake-to-open, devtool wiring and in-place remote reload — are set up by sigx prebuild and are not part of the JS surface.

See also#