◊ FallLink AI-Native Solutions · v1

Persistent P2P for the estate

Reusable WebRTC peer-discovery + connection layer. STUN NAT traversal · BroadcastChannel same-origin auto-discovery · manual offer/answer paste for cross-network. One connection layer every estate tool imports.

Node state

Own ID
—
Signal channel
fall-signal
Connected
0
Broadcasting
off
Sovereign. No signalling server. Public STUN only. All state lives in this browser. idle

Activity

Usage · one-liner

// import the library
import { FallLink } from './falllink.js';

// instantiate with sensible defaults (Google + Cloudflare STUN, 'fall-signal' channel)
const link = new FallLink({ ownId: 'my-node' });

// auto-discover peers on the same origin
link.startBroadcast();

// listen for peers + messages
link.on('peer', ({ peerId }) => console.log('peer up', peerId));
link.on('message', ({ peerId, data }) => console.log(peerId, data));

// broadcast to all connected peers
link.broadcast({ type: 'hello', from: 'me' });

Connected peers

Every connection is a live WebRTC data channel. Latency measured via ping every three seconds.

Signal exchange

Two paths: same-origin via BroadcastChannel (automatic) or cross-network via copy/paste of offer + answer bundles.

Same-origin (auto)

Open this page in another tab on the same device. Peers find each other on the fall-signal BroadcastChannel automatically.

off

Cross-network · Alice creates an offer

Cross-network · Bob accepts an offer

ICE gathering. After creating a bundle, wait for ICE candidates to be gathered (button will say "Ready"). The bundle is base64-encoded JSON containing SDP + ICE.

Import into your tool

Drop-in ESM

import { FallLink } from 'https://sjgant80-hub.github.io/falllink/falllink.js';

// or vendor it into your repo:
// curl -O https://sjgant80-hub.github.io/falllink/falllink.js

const link = new FallLink({
  ownId: crypto.randomUUID(),
  stunServers: [{ urls: 'stun:stun.l.google.com:19302' }],
  bootstrapPeers: [],
  signalChannel: 'fall-signal'
});

API surface

// discovery
link.startBroadcast() // same-origin auto-discovery via BroadcastChannel
link.stopBroadcast()

// manual signaling (cross-network paste)
const { bundle, peerId, wrapper } = await link.createOffer();
const { bundle } = await link.acceptOffer(offerBundle);
await link.acceptAnswer(answerBundle);

// direct control
const wrapper = await link.connect(peerId, offer?);
link.broadcast(msg) // -> number of peers reached
link.getPeers() // -> [{ peerId, state, latency, ... }]
await link.ping(peerId) // -> latency ms
link.destroy()

// events
link.on('peer', ({ peerId, wrapper }) => {})
link.on('message', ({ peerId, data }) => {})
link.on('disconnect', ({ peerId }) => {})
link.on('latency', ({ peerId, latency }) => {})