monitor-free.openapps.xyz

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.
Owned app · rev 11d38ef641b0
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.
← All files
src/watcher.ts
// A parent device watching one baby station: sends a connection offer, keeps the connection alive,
// reconnects on its own, and carries talk back, settings and alerts over the data channel.
import { set, subscribe, serverTimestamp } from "@bounded-sh/client";
import { StatsReader, gathered, iceConfig, randomId, type LinkStats } from "./lib/rtc";
import { parse, type FromBaby, type FromParent, type Sensitivity, type SoundName, type StationStatus } from "./lib/protocol";

export type LinkState =
  | "connecting"   // first try
  | "live"
  | "reconnecting" // the connection dropped; trying again
  | "off"          // the station was turned off
  | "noanswer"     // the station does not answer (closed, asleep or offline)
  | "blocked"      // the station answers, but the two devices cannot reach each other
  | "removed";     // the station removed this device

export interface WatchSnapshot {
  state: LinkState;
  stationName: string;
  status: StationStatus | null;
  stats: LinkStats | null;
  video: MediaStream | null;
  audio: MediaStream | null;
  talking: boolean;
}

const RETRY_MS = [1000, 2000, 4000, 8000, 15000, 30000];
const NOANSWER_RETRY_MS = [5000, 10000, 20000, 30000, 60000];

export class Watcher {
  private pc: RTCPeerConnection | null = null;
  private dc: RTCDataChannel | null = null;
  private audioTx: RTCRtpTransceiver | null = null;
  private talkTrack: MediaStreamTrack | null = null;
  private talkWanted = false;
  private session: string | null = null;
  private offerId = "";
  private answered = false;
  private attempt = 0;
  private failures = 0;
  private timers: number[] = [];
  private statsTimer: number | undefined;
  private reader = new StatsReader();
  private stops: (() => Promise<void>)[] = [];
  private listeners = new Set<() => void>();
  private stopped = false;
  private snap: WatchSnapshot;
  private state: LinkState = "connecting";
  private stationName: string;
  private status: StationStatus | null = null;
  private stats: LinkStats | null = null;
  private video: MediaStream | null = null;
  private audio: MediaStream | null = null;
  onLevel: ((v: number) => void) | null = null;
  onAlert: ((v: number) => void) | null = null;
  onName: ((name: string) => void) | null = null;

  constructor(
    readonly room: string,
    readonly me: string,
    name: string,
    private prefs: { video: boolean; sensitivity: Sensitivity },
  ) {
    this.stationName = name;
    this.snap = this.build();
  }

  async start() {
    this.stops.push(await subscribe(`rooms/${this.room}/joins/${this.me}`, { onData: (d: any) => { if (!d) this.removed(); } }));
    this.stops.push(await subscribe(`rooms/${this.room}/links/${this.me}`, { onData: (d: any) => void this.onLink(d) }));
    this.stops.push(await subscribe(`rooms/${this.room}`, { onData: (d: any) => this.onRoom(d) }));
    const wake = () => { if (document.visibilityState === "visible" && ["reconnecting", "noanswer", "blocked"].includes(this.state)) this.retryNow(); };
    document.addEventListener("visibilitychange", wake);
    window.addEventListener("online", wake);
    this.stops.push(async () => { document.removeEventListener("visibilitychange", wake); window.removeEventListener("online", wake); });
  }

  async stop() {
    this.stopped = true;
    this.clearTimers();
    this.stopTalk();
    this.closePc();
    for (const s of this.stops) await s();
  }

  subscribe = (fn: () => void) => {
    this.listeners.add(fn);
    return () => { this.listeners.delete(fn); };
  };
  snapshot = () => this.snap;

  retryNow() {
    if (this.stopped || this.state === "removed") return;
    this.attempt = 0;
    void this.connect();
  }

  setVideo(on: boolean) {
    this.prefs.video = on;
    this.send({ t: "video", on });
  }

  setSensitivity(v: Sensitivity) {
    this.prefs.sensitivity = v;
    this.send({ t: "sensitivity", v });
  }

  playSound(name: SoundName | null, volume: number, minutes: number) {
    this.send({ t: "sound", name, volume, minutes });
  }

  /** Push to talk: the microphone is on only while the button is held. */
  async startTalk() {
    if (this.talkTrack || !this.audioTx || this.state !== "live") return;
    this.talkWanted = true;
    const s = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } });
    const track = s.getAudioTracks()[0];
    if (!this.talkWanted || !this.audioTx) { track.stop(); return; }
    this.talkTrack = track;
    await this.audioTx.sender.replaceTrack(track);
    this.send({ t: "talk", on: true });
    this.changed();
  }

  stopTalk() {
    this.talkWanted = false;
    if (!this.talkTrack) return;
    void this.audioTx?.sender.replaceTrack(null).catch(() => null);
    this.talkTrack.stop();
    this.talkTrack = null;
    this.send({ t: "talk", on: false });
    this.changed();
  }

  private send(m: FromParent) {
    if (this.dc?.readyState === "open") this.dc.send(JSON.stringify(m));
  }

  private onRoom(doc: any) {
    if (!doc || this.stopped || this.state === "removed") return;
    if (doc.name && doc.name !== this.stationName) {
      this.stationName = String(doc.name);
      this.onName?.(this.stationName);
      this.changed();
    }
    if (doc.online === false) {
      this.session = String(doc.session || "");
      this.clearTimers();
      this.closePc();
      this.setState("off");
      return;
    }
    if (doc.session !== this.session || this.state === "off") {
      this.session = String(doc.session || "");
      this.attempt = 0;
      this.failures = 0;
      void this.connect();
    }
  }

  private async onLink(doc: any) {
    const pc = this.pc;
    if (!doc || !pc || this.answered || doc.answerId !== this.offerId || pc.signalingState !== "have-local-offer") return;
    this.answered = true;
    this.clearTimers();
    try {
      await pc.setRemoteDescription(JSON.parse(String(doc.answer)));
    } catch {
      if (this.pc === pc) this.lost(false);
      return;
    }
    // The station answered; if the devices still cannot reach each other, ICE fails or stalls.
    this.later(() => { if (this.pc === pc && pc.connectionState !== "connected") this.lost(true); }, 20000);
  }

  private async connect() {
    const config = await iceConfig();
    if (this.stopped || this.state === "removed") return;
    this.clearTimers();
    this.closePc();
    if (this.state !== "noanswer" && this.state !== "blocked") this.setState(this.attempt ? "reconnecting" : "connecting");
    const pc = new RTCPeerConnection(config);
    this.pc = pc;
    const dc = pc.createDataChannel("monitor");
    this.dc = dc;
    pc.addTransceiver("video", { direction: "recvonly" });
    // Sends nothing until push to talk puts the microphone on it.
    this.audioTx = pc.addTransceiver("audio", { direction: "sendrecv" });
    pc.ontrack = (e) => {
      if (e.track.kind === "video") this.video = new MediaStream([e.track]);
      else this.audio = new MediaStream([e.track]);
      this.changed();
    };
    dc.onopen = () => this.send({ t: "hello", video: this.prefs.video, sensitivity: this.prefs.sensitivity });
    dc.onmessage = (m) => this.onMessage(m.data);
    dc.onclose = () => { if (this.pc === pc) this.lost(false); };
    pc.onconnectionstatechange = () => {
      if (this.pc !== pc) return;
      const s = pc.connectionState;
      if (s === "connected") {
        this.attempt = 0;
        this.failures = 0;
        this.clearTimers();
        this.setState("live");
        this.watchStats(pc);
      } else if (s === "failed") {
        this.lost(true);
      } else if (s === "disconnected") {
        this.later(() => { if (this.pc === pc && pc.connectionState !== "connected") this.lost(false); }, 5000);
      }
    };
    try {
      await pc.setLocalDescription(await pc.createOffer());
      await gathered(pc);
      if (this.pc !== pc) return;
      this.offerId = randomId(12);
      this.answered = false;
      await set(`rooms/${this.room}/links/${this.me}`, {
        offer: JSON.stringify(pc.localDescription), offerId: this.offerId, session: this.session || "", answer: "", answerId: "", at: serverTimestamp(),
      });
    } catch {
      if (this.pc === pc) this.lost(false);
      return;
    }
    this.later(() => { if (this.pc === pc && !this.answered) this.noAnswer(); }, 12000);
  }

  private lost(iceFailed: boolean) {
    if (this.stopped || this.state === "off" || this.state === "removed") return;
    const wasAnswered = this.answered;
    this.clearTimers();
    this.closePc();
    if (iceFailed && wasAnswered) this.failures++;
    this.attempt++;
    this.setState(this.failures >= 2 ? "blocked" : "reconnecting");
    this.later(() => void this.connect(), this.failures >= 2 ? 30000 : RETRY_MS[Math.min(this.attempt - 1, RETRY_MS.length - 1)]);
  }

  private noAnswer() {
    this.clearTimers();
    this.closePc();
    this.attempt++;
    this.setState("noanswer");
    this.later(() => void this.connect(), NOANSWER_RETRY_MS[Math.min(this.attempt - 1, NOANSWER_RETRY_MS.length - 1)]);
  }

  private removed() {
    this.clearTimers();
    this.stopTalk();
    this.closePc();
    this.setState("removed");
  }

  private onMessage(raw: unknown) {
    const m = parse<FromBaby>(raw);
    if (!m) return;
    if (m.t === "level") this.onLevel?.(m.v);
    else if (m.t === "alert") this.onAlert?.(m.v);
    else if (m.t === "status") { this.status = m; this.changed(); }
  }

  private watchStats(pc: RTCPeerConnection) {
    clearInterval(this.statsTimer);
    const read = async () => {
      if (this.pc !== pc) return clearInterval(this.statsTimer);
      this.stats = await this.reader.read(pc).catch(() => this.stats);
      this.changed();
    };
    void read();
    this.statsTimer = window.setInterval(read, 3000);
  }

  private closePc() {
    clearInterval(this.statsTimer);
    if (this.talkTrack) this.stopTalk();
    const pc = this.pc;
    this.pc = null;
    this.dc = null;
    this.audioTx = null;
    this.answered = false;
    this.video = null;
    this.audio = null;
    this.stats = null;
    pc?.close();
  }

  private later(fn: () => void, ms: number) {
    this.timers.push(window.setTimeout(fn, ms));
  }

  private clearTimers() {
    for (const t of this.timers) clearTimeout(t);
    this.timers = [];
  }

  private setState(s: LinkState) {
    this.state = s;
    this.changed();
  }

  private changed() {
    this.snap = this.build();
    for (const fn of this.listeners) fn();
  }

  private build(): WatchSnapshot {
    return {
      state: this.state,
      stationName: this.stationName,
      status: this.state === "live" ? this.status : null,
      stats: this.stats,
      video: this.video,
      audio: this.audio,
      talking: Boolean(this.talkTrack),
    };
  }
}

Change history

Move store links to openapps.free
11d38ef641 · 17 hours ago
Release with preview
41c8af3ee5 · 19 hours ago
Bind agent address in policy
5b2d792529 · 19 hours ago
Use OpenApps service URLs and add contribution strip (retry)
be4477dcb3 · 19 hours ago
Bounded source sync
5c9ec00c29 · 2 days ago
Bounded source sync
b33aa231f4 · 2 days ago
Bounded source sync
6cb67b3c8e · 2 days ago
Bounded source sync
e054acaa29 · 2 days ago