Host SDK (@crosslink/sdk-node)
createCrosslinkServer(options)
Creates a new Crosslink host server.
Options
CapabilityDef
pairing
PairingApproval is boolean | string[] | { approved: boolean; caps?: string[] } — true grants everything requested, false refuses, and an array or { caps } grants only that subset.
PairingApprovalRequest
Server methods
server.start()
Start listening for connections.
server.stop()
Gracefully shut down.
server.getPairingCode()
Generate a new pairing code.
server.expose(method, handler, options?)
Register an RPC method.
options also accepts inputSchema (a MiniSchema), validate (a custom validator function), idempotent (safe to auto-retry across reconnects), and timeoutMs. capability may be an array, in which case all of them are required.
The handler receives (input, ctx):
server.declareEvent(name, options?)
Declare an event that can be emitted. options.capability gates who may subscribe.
server.emit(name, payload)
Send an event to all connected clients.
server.listDevices()
List all paired devices (not only currently connected ones).
server.revokeDevice(deviceId)
Revoke a paired device’s access and close any of its active sessions. Returns true if the device existed.
server.revokeAllDevices()
Revoke every currently-paired device.
server.setDeviceCaps(deviceId, caps)
Replace a paired device’s granted capability set.
server.getConnectivity()
A moment-in-time snapshot of how reachable the host is, with a human-readable message.
server.connectionEndpoints(mode?)
Every route a client could use to reach the host right now, in the order a
client should try them. This is the single source of truth for pairing
endpoints — it never invents a route, and a wan entry appears only when one
genuinely exists.
kind is one of "lan" | "wan" | "sig" | "relay" | "tunnel". Passing mode
filters to what that network mode allows, without changing the host’s own mode.
await server.setNetworkMode(mode)
Changes the running host’s actual transport policy. Switching to "local-only"
releases router mappings, disconnects signaling and relay services, and
reconnects active devices through a permitted local route. Switching back to a
remote-capable mode starts its configured transports again.
Use this method for settings UI; changing server.config.networkMode directly
does not apply live transport lifecycle changes.
server.enableRemoteAccess()
Opens remote access now, if it is not already open. setNetworkMode("remote")
calls it for you; call it directly when a diagnostics UI needs to probe the
router without changing the host’s selected mode.
Requires the LAN listener to be bound to all interfaces (lan: { bind: "all" }).
server.status()
A structured snapshot for a diagnostics panel: application info, device id and
fingerprint, active transports, network mode, device count, secret-store
backend, and the permission policy in force.
server.getRemoteDiagnostics()
What the router said about the last port-mapping attempt (protocol tried, public address, CGNAT detection), or null if remote access was never attempted.
server.getInstallHandoff(handoffId)
Resolves a device-link handoff minted over RPC, used by the iOS “Add to Home
Screen” flow so an installed PWA inherits trust from the browser tab that
paired it. Returns null once it is used or expired.
Server events
Subscribe withserver.typedOn(event, callback) or the inherited EventEmitter#on.
Client SDK (@crosslink/sdk-browser)
createSecureCrosslinkClient(options?)
The preferred browser entry point. Identity and paired-app records are
encrypted at rest under a non-extractable WebCrypto key.
allowPlaintextFallback to permit unencrypted storage when
WebCrypto/IndexedDB is unavailable.
createCrosslinkClient(options?)
Synchronous factory that defaults storage to localStorage. Use it when you
supply your own storage, or need a client without awaiting.
CrosslinkClient.create(options?)
Factory method to create a client whose identity is encrypted at rest with WebCrypto/IndexedDB when available.
create() additionally accepts allowPlaintextFallback (boolean), which lets it fall back to unencrypted storage when WebCrypto/IndexedDB is unavailable.
new CrosslinkClient(options?)
Synchronous constructor for embedders that supply their own storage. Without an explicit storage, identity lives only in memory for the process lifetime — prefer CrosslinkClient.create() in browsers.
Options
Client methods
client.pairFromQr(text, requestedCaps?, codeOverride?)
Applications rarely call this.
CrosslinkMobileBootstrap (and therefore
mobile.entry) runs pairing, including SAS confirmation, the install handoff
and the error states. Call it directly only when you are driving a client with
no Crosslink UI — see Custom UI.client.connect(appId?)
Connect to a previously paired host. Without appId, connects to the first paired app on record. Returns the RpcClient once connected.
client.close()
Close the current connection.
client.forget(appId)
Remove a paired host.
client.listApps()
List all paired hosts.
client.onStateChange(listener)
Subscribe to connection-state changes; returns an unsubscribe function. This is the only way the client reports connection lifecycle — it is not an event emitter.
state is one of: "offline", "discovering", "pairing", "connecting", "direct", "turn-relayed", "crosslink-relayed", "reconnecting", "unauthorized", "revoked", "protocol-incompatible".
RPC interface
rpc.call(method, args?, options?)
Call an RPC method. options.timeoutMs overrides the default request timeout.
rpc.subscribe(event, callback)
Subscribe to an event. Returns an unsubscribe function.
rpc.cancel(requestId)
Cancel an in-flight request by id.
RpcClient has no built-in “connected” / “disconnected” / “error” events, and no hasCapability method — capability enforcement happens on the host, and a denied call rejects with a capability_denied error (see Error Codes).
Crosslink-owned UI
These are the APIs behind the pairing, bootstrap, install, offline and revoked experiences. You mount them; Crosslink implements them.createPairingCard(options)
@crosslink/sdk-browser — the canonical desktop pairing UI.
application block, delivered with every pairing session. The options below are
overrides, applied field by field over what the host reported.
Methods:
refresh(), setNetworkMode(mode), getNetworkMode(), update(state),
setBrand(theme), getBrand(), attachSource(source), destroy().
@crosslink/react exports CrosslinkPairingCard, a thin mount around the same
implementation.
CrosslinkMobileBootstrap
@crosslink/sdk-browser — the mobile lifecycle: first pair, code entry, SAS
confirmation, Add to Home Screen, Continue in Browser, install handoff, offline,
reconnecting, revoked, and the handoff into your app.
Constructed for you when the host has mobile.entry. Construct it directly only
when serving the page from outside a Crosslink host.
Methods: start(), getState(), getClient(), getEnvironment(), destroy().
describeBootstrapEnvironment()
@crosslink/sdk-browser — what the current origin permits: secureContext,
serviceWorkerAvailable, webCryptoAvailable, installable,
insecureTransportBlocked, and a limitations[] of plain sentences.
filterEndpointsForOrigin(endpoints, pageOrigin)
@crosslink/core — splits advertised endpoints into usable and blocked for a
given page origin. An https page cannot open ws://; this is what reports that
as a blocked route with a reason rather than a connection timeout.
Host: serving the mobile experience
host.createBootstrapHandler()
An HTTP handler serving your mobile.entry plus the manifest, service worker,
icons, browser SDK and install handoff. Mounted automatically on the transport
port when mobile.entry is set; call it to mount on a server you already run.
mobile.attribution configures the Crosslink attribution footer the bootstrap
mounts on the authorized mobile app shell — color, background, size,
offset, className. It participates in normal layout flow and there is no
field that removes it. See
Mobile bootstrap.
host.createControlHandler(options?)
The loopback-only system endpoints the pairing card consumes: /pairing,
/network-mode, /devices, /revoke, /events, /widget.js. Refuses
non-loopback peers itself. /pairing answers with the session and the host’s
application block, which is how the card renders your name, icon and colours
without the page repeating them. options.fallback handles everything outside the
Crosslink base path, so your own routes sit behind it.
host.describeMobileDelivery()
A deployment-level diagnostic with mode, directLanTransport,
secureWssTransport, dynamicEndpointDiscovery, bootstrapAssetsConfigured,
durableOrigin, and the
origin capabilities serviceWorkerOriginEligible, offlineShell, installable,
and encryptedDeviceIdentity. Its multiline message labels LAN HTTP versus a
secure published bootstrap. These are configuration/origin capabilities, not a
claim about a particular phone; use describeBootstrapEnvironment() in the
page for actual browser runtime support. See Durable Origins.
host.writeStaticBootstrap(outDir, overrides?)
Writes Crosslink’s bootstrap as a static site — index.html, crosslink-sdk.js,
crosslink-boot.js, sw.js, manifest.webmanifest, crosslink-mark.svg, two
generated PNG icons, .nojekyll, and any explicitly requested assets. All
framework links are relative, so ordinary static hosting and GitHub Pages
project sites work. It embeds public application branding/capability metadata,
not host endpoints, relay credentials, pairing codes, device keys, or trusted
device records. See Durable Origins for its traffic,
trust, and optional-service boundaries.
host.bootstrapOrigin()
The origin a phone should load the bootstrap from: the configured
pairing.bootstrapUrl when set, otherwise the best route this host advertises.