Skip to content

IFM SDK Reference

Overview

@ifm/sdk is the JavaScript/TypeScript interface to the IFM protocol. The protocol is Rust: every protocol operation — packet signing/verification, frequency hashing, presence, the relay plane, discovery, real-time media — runs inside the Rust ifm-core crate. The SDK contains no protocol or mesh logic; it is a thin wrapper over the core.

There are two wrappers, one per environment:

EnvironmentWrapperBacking
Node.js / Bun / desktopIFM.create()NativeRadioRust core via the N-API addon (crates/ffi)
BrowsergetWebCore()WebCoreRadioThe same Rust core compiled to WASM (crates/web-coreBrowserNode)

Both join the same IFM mesh: the entry is always the rendezvous manifest JSON (the desktop apps use the same URL). Browsers cannot run QUIC, so the WASM node dials native nodes over WebSocket/WebTransport/WebRTC; browser↔ browser traffic flows over the WebRTC overlay (BrowserOverlay). There is no JS mesh and no JS relay hub anymore.


Native (Node.js / Bun)

Prerequisites

Build the N-API addon once (from packages/sdk):

bash
bun scripts/build-native.mjs

IFM.nativeAvailable() reports whether the addon is present.

Usage

typescript
import { IFM } from "@ifm/sdk";

const radio = await IFM.create();
await radio.connect();
await radio.tune("91.700");
radio.on("message", (m) => console.log(m));
await radio.broadcast("91.700", "Hello, IFM!");

await radio.close();

Entry mode (mirrors the desktop apps):

  • Remote (default): fetch the rendezvous manifest and dial its entry.
  • LAN: IFM_LAN=1 → mDNS is the entry, no rendezvous.
  • Override the manifest URL with IFM_MANIFEST_URL.

IFM.create() throws if the addon is missing — there is no JS fallback anymore. In the browser, use the WASM web core instead.

Native API surface (IFMRadio)

typescript
radio.on("message" | "hello" | "welcome" | "voice" | "joined" | "left" | "file:receive", handler);
radio.off(type, handler);
radio.connect(): Promise<void>;
radio.disconnect(): Promise<void>;
radio.tune(freq: string): Promise<Frequency>;
radio.leave(freq?): Promise<void>;
radio.broadcast(freq: string, text: string): Promise<void>;
radio.publish(freq: string, data: Uint8Array | string, type?: PayloadType): Promise<void>;
radio.send(peer: string, freq: string, text: string): Promise<void>;
radio.sendFile(freq: string, filename: string, data: Uint8Array, chunkSize?): Promise<void>;
radio.subscribe(freq: string, handler): Promise<() => void>;
radio.getFrequencies(): Promise<string[]>;
radio.scan(): Promise<ScanResult[]>;
radio.advertiseStation(opts: StationOptions): Promise<void>;
radio.peers(): Promise<PeerInfo[]>;
radio.stats(): Promise<NodeStats>;
radio.use(plugin: Plugin): void;
radio.close(): Promise<void>;
radio.nodeId(): string;

Real-time media (10 ms Opus frames, Rust encode→decode):

typescript
radio.mediaSend(pcmInt16, timestampMs);
const audio = radio.mediaPoll(audioClockMs);
console.log(radio.mediaStats()); // jitter / latency / live edge

Browser (WASM web core)

Browser apps boot the Rust core in WASM. There is exactly one wrapper — getWebCore() — used by the PWA and every example; never create a per-app copy.

Usage

typescript
import { getWebCore } from "@ifm/sdk";

const radio = getWebCore();
await radio.start(); // fetch rendezvous manifest → dial /wss entry → join mesh

radio.tune("91.700");
// Poll the event queue (packets arrive as JSON):
for (const p of radio.takeEvents()) {
  console.log(p.payload_type, new TextDecoder().decode(new Uint8Array(p.payload)));
}
radio.broadcast("91.700", "hi");
radio.close(); // stop_providing + drop (also on pagehide automatically)

Web core surface (WebCoreRadio)

typescript
radio.start(): Promise<void>;      // boot wasm + dial entry (idempotent)
radio.on(ev, cb);                  // "status" events
radio.nodeId(): string;
radio.peerCount(): number;
radio.stations(): MeshStation[];   // discovery plane (gossiped records)
radio.peers(): MeshPeer[];
radio.relays(): MeshRelay[];       // shared relay pool, nearest first
radio.bestPath(): MeshPath | null; // "direct" | "relay" (quality-scored)
radio.setSignalQuality(jitterMs, lossPct); // degraded-signal → relay assist
radio.tune(freq); radio.leave(freq);
radio.broadcast(freq, text);
radio.publish(freq, payload: Uint8Array, kind: string); // "voice" | "text" | …
radio.announceStation(name, channelId);
radio.dial(addr);                  // station's own ws endpoint, or a relay
radio.takeEvents(): MeshPacket[];
radio.close();

The listen path is the same as desktop listeners: connect to the station (or a listener peer) directly first; a relay is dialed only when the direct signal degrades.


Installation

bash
npm install @ifm/sdk      # or bun add / pnpm add

Requirements: Node.js ≥ 18 (native), Bun ≥ 1.0 (native), or a browser with WebAssembly + WebRTC (web core).

Examples

  • packages/sdk/examples/station/ — broadcaster studio (web core)
  • packages/sdk/examples/relay/ — mesh operator dashboard (web core)
  • packages/sdk/examples/listener/ — radio listener (web core)
  • packages/sdk/examples/smoke.mjs — native smoke station (Node, IFM.create)

Development

  • Build the SDK: bun run build in packages/sdk (tsc + wasm asset copy)
  • Native addon: bun scripts/build-native.mjs (after crates/ffi changes)
  • Selftest: bun scripts/selftest.mjs
  • Rust tests: cargo test at the repo root — the real verification

Released under the MIT License.