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:
| Environment | Wrapper | Backing |
|---|---|---|
| Node.js / Bun / desktop | IFM.create() → NativeRadio | Rust core via the N-API addon (crates/ffi) |
| Browser | getWebCore() → WebCoreRadio | The same Rust core compiled to WASM (crates/web-core → BrowserNode) |
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):
bun scripts/build-native.mjsIFM.nativeAvailable() reports whether the addon is present.
Usage
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)
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):
radio.mediaSend(pcmInt16, timestampMs);
const audio = radio.mediaPoll(audioClockMs);
console.log(radio.mediaStats()); // jitter / latency / live edgeBrowser (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
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)
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
npm install @ifm/sdk # or bun add / pnpm addRequirements: 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 buildinpackages/sdk(tsc + wasm asset copy) - Native addon:
bun scripts/build-native.mjs(aftercrates/ffichanges) - Selftest:
bun scripts/selftest.mjs - Rust tests:
cargo testat the repo root — the real verification