Source
This app’s code and revision history are public. Download a copy or explore the files below.
Public
The app’s code and revision history are public.
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 9383f719c9b37ff76411858f
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
src/lib/rtc.ts
// WebRTC pieces shared by the baby station and parent devices.
/**
* How devices find a path to each other. Public STUN servers tell each device its address as the
* internet sees it, which is enough on most home networks. Some networks (strict NAT, many mobile
* carriers, office Wi-Fi) need a relay (TURN) instead; a relay carries the video itself and costs
* money per gigabyte, so Monitor has none yet. To add one, return its address and short-lived
* credentials here (fetched from a function, charged at cost); nothing else needs to change.
*/
export async function iceConfig(): Promise<RTCConfiguration> {
return {
iceServers: [{ urls: ["stun:stun.l.google.com:19302", "stun:stun.cloudflare.com:3478"] }],
};
}
/**
* Waits until the browser has found its network addresses (or 2.5 seconds pass), so one offer or
* answer carries every address and the devices need only two signaling writes per connection.
*/
export function gathered(pc: RTCPeerConnection, maxMs = 2500): Promise<void> {
if (pc.iceGatheringState === "complete") return Promise.resolve();
return new Promise((resolve) => {
const done = () => {
pc.removeEventListener("icegatheringstatechange", check);
clearTimeout(timer);
resolve();
};
const check = () => { if (pc.iceGatheringState === "complete") done(); };
const timer = setTimeout(done, maxMs);
pc.addEventListener("icegatheringstatechange", check);
});
}
const ALPHABET = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
/** A random id from an unambiguous alphabet; 20 characters is about 116 bits. */
export function randomId(length = 20): string {
const bytes = crypto.getRandomValues(new Uint8Array(length));
let out = "";
for (const b of bytes) out += ALPHABET[b % ALPHABET.length];
return out;
}
/** A 6-digit pairing code, never starting with 0 so it reads well. */
export function randomCode(): string {
const n = crypto.getRandomValues(new Uint32Array(1))[0] % 900000;
return String(100000 + n);
}
export type Quality = "good" | "fair" | "weak";
export type Route = "local" | "internet" | "relay";
export interface LinkStats { quality: Quality; route: Route | null }
/** Reads round-trip time, packet loss and the kind of path from a connection's stats. */
export class StatsReader {
private lastLost = 0;
private lastReceived = 0;
async read(pc: RTCPeerConnection): Promise<LinkStats> {
const report = await pc.getStats();
let pair: any = null;
let lost = 0;
let received = 0;
report.forEach((s: any) => {
if (s.type === "transport" && s.selectedCandidatePairId) pair = report.get(s.selectedCandidatePairId) || pair;
if (s.type === "inbound-rtp") { lost += s.packetsLost || 0; received += s.packetsReceived || 0; }
});
if (!pair) report.forEach((s: any) => { if (s.type === "candidate-pair" && s.nominated && s.state === "succeeded") pair = s; });
const local = pair ? report.get(pair.localCandidateId) : null;
const remote = pair ? report.get(pair.remoteCandidateId) : null;
const types = [local?.candidateType, remote?.candidateType];
const route: Route | null = !pair ? null : types.includes("relay") ? "relay" : types.every((t) => t === "host") ? "local" : "internet";
const rtt = (pair?.currentRoundTripTime ?? 0) * 1000;
const dLost = Math.max(0, lost - this.lastLost);
const dReceived = Math.max(0, received - this.lastReceived);
this.lastLost = lost;
this.lastReceived = received;
const loss = dLost + dReceived ? dLost / (dLost + dReceived) : 0;
const quality: Quality = rtt < 250 && loss < 0.02 ? "good" : rtt < 600 && loss < 0.08 ? "fair" : "weak";
return { quality, route };
}
}
Change history
Move store links to openapps.free
Release with preview
Bind agent address in policy
Use OpenApps service URLs and add contribution strip (retry)
Bounded source sync
Bounded source sync
Bounded source sync
Bounded source sync