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/Watch.tsx
// The parent's monitor: live video and sound, a sound meter, noise alerts, talk back,
// soothing sounds, night view, and the station's battery and connection.
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
import { setMany } from "@bounded-sh/client";
import { me, explain } from "./lib/bounded";
import { navigate } from "./lib/router";
import { SOUNDS, THRESHOLDS, type Sensitivity, type SoundName } from "./lib/protocol";
import { chime } from "./lib/sound";
import { pairings, settings as stored, type Pairing, type WatchSettings } from "./lib/store";
import { Watcher, type WatchSnapshot } from "./watcher";
import { TopBar } from "./Baby";
import { Icon, Sheet, toast, useNightChrome, useWakeLock } from "./ui";

export function WatchPage({ pairKey }: { pairKey: string }) {
  useNightChrome();
  const pairing = pairings.byKey(pairKey);
  if (!pairing) {
    return (
      <div className="night">
        <div className="night-page">
          <TopBar title="Parent" />
          <h1 className="night-h1">This baby station is not paired here.</h1>
          <p className="night-lede">Pair this device with the code shown on the baby station.</p>
          <button className="btn btn-xl btn-block" type="button" onClick={() => navigate("/parent")}>Pair with a code</button>
        </div>
      </div>
    );
  }
  return <Watching key={pairing.room} pairing={pairing} />;
}

function Watching({ pairing }: { pairing: Pairing }) {
  const [watcher, setWatcher] = useState<Watcher | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [prefs, setPrefs] = useState<WatchSettings>(() => stored.get(pairing.room));
  useEffect(() => {
    let w: Watcher | null = null;
    let cancelled = false;
    (async () => {
      const uid = await me();
      if (cancelled) return;
      w = new Watcher(pairing.room, uid, pairing.name, { video: !prefs.audioOnly, sensitivity: prefs.sensitivity });
      w.onName = (n) => pairings.rename(pairing.room, n);
      await w.start();
      if (cancelled) void w.stop(); else setWatcher(w);
    })().catch((err) => setError(explain(err)));
    return () => { cancelled = true; void w?.stop(); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [pairing.room]);

  const update = useCallback((patch: Partial<WatchSettings>) => {
    setPrefs((p) => {
      const next = { ...p, ...patch };
      stored.set(pairing.room, next);
      return next;
    });
  }, [pairing.room]);

  if (error) {
    return (
      <div className="night"><div className="night-page"><TopBar title={pairing.name} back="/parent" /><p className="notice notice-error">{error}</p></div></div>
    );
  }
  if (!watcher) {
    return (
      <div className="night"><div className="night-page"><TopBar title={pairing.name} back="/parent" /><div className="center-fill"><span className="spinner" /> Opening...</div></div></div>
    );
  }
  return <WatchView watcher={watcher} pairing={pairing} prefs={prefs} update={update} />;
}

const STATE_TEXT: Record<Exclude<WatchSnapshot["state"], "live">, (name: string) => string> = {
  connecting: (n) => `Connecting to ${n}...`,
  reconnecting: () => "The connection dropped. Reconnecting...",
  off: (n) => `${n} is turned off. This screen connects again as soon as it is back on.`,
  noanswer: (n) => `${n} is not answering. Check that Monitor is open on it, with its screen on and the phone online. Trying again...`,
  blocked: () => "These two devices cannot reach each other directly. Some networks, like mobile data or office Wi-Fi, block it. Put both on the same Wi-Fi, then try again.",
  removed: (n) => `${n} removed this device.`,
};

function WatchView({ watcher, pairing, prefs, update }: { watcher: Watcher; pairing: Pairing; prefs: WatchSettings; update: (p: Partial<WatchSettings>) => void }) {
  const snap = useSyncExternalStore(watcher.subscribe, watcher.snapshot);
  const [muted, setMuted] = useState(false);
  const [needsTap, setNeedsTap] = useState(false);
  const [alertAt, setAlertAt] = useState<Date | null>(null);
  const [sheet, setSheet] = useState<"sounds" | "alerts" | "details" | null>(null);
  const [ratio, setRatio] = useState(4 / 3);
  const videoEl = useRef<HTMLVideoElement>(null);
  const audioEl = useRef<HTMLAudioElement>(null);
  const meter = useRef<HTMLDivElement>(null);
  const ctx = useRef<AudioContext | null>(null);
  const prefsRef = useRef(prefs);
  prefsRef.current = prefs;
  const live = snap.state === "live";
  const name = snap.stationName;
  useWakeLock(!prefs.audioOnly);

  const audio = () => (ctx.current ??= new AudioContext());
  const unlock = () => {
    if (audio().state !== "running") void audio().resume();
    if (audioEl.current?.srcObject && audioEl.current.paused) void audioEl.current.play().then(() => setNeedsTap(false), () => null);
  };

  useEffect(() => {
    watcher.onLevel = (v) => meter.current?.style.setProperty("--level", `${v}%`);
    watcher.onAlert = () => {
      if (prefsRef.current.sensitivity === "off") return;
      setAlertAt(new Date());
      chime(audio());
      navigator.vibrate?.([300, 150, 300]);
      if (document.visibilityState === "hidden" && "Notification" in window && Notification.permission === "granted") {
        void navigator.serviceWorker?.ready.then((reg) => reg.showNotification(`Sound at ${watcher.snapshot().stationName}`, {
          body: "The baby station heard sound for a few seconds.", tag: "monitor-alert", icon: "/icons/icon-192.png",
        })).catch(() => null);
      }
    };
    return () => { watcher.onLevel = null; watcher.onAlert = null; };
  }, [watcher]);

  useEffect(() => {
    const v = videoEl.current;
    if (v && v.srcObject !== snap.video) v.srcObject = snap.video;
  }, [snap.video, prefs.audioOnly]);

  useEffect(() => {
    const a = audioEl.current;
    if (a && a.srcObject !== snap.audio) {
      a.srcObject = snap.audio;
      if (snap.audio) void a.play().then(() => setNeedsTap(false), () => setNeedsTap(true));
    }
  }, [snap.audio]);

  useEffect(() => {
    if (audioEl.current) audioEl.current.muted = muted || snap.talking;
  }, [muted, snap.talking]);

  const setAudioOnly = (on: boolean) => { update({ audioOnly: on }); watcher.setVideo(!on); };
  const setSensitivity = (v: Sensitivity) => { update({ sensitivity: v }); watcher.setSensitivity(v); };
  const status = snap.status;
  const threshold = prefs.sensitivity === "off" ? null : THRESHOLDS[prefs.sensitivity];

  const statusLine = live ? [
    status?.battery != null ? `Battery ${status.battery}%${status.charging ? ", charging" : ""}` : null,
    snap.stats ? `${snap.stats.quality === "good" ? "Good" : snap.stats.quality === "fair" ? "Fair" : "Weak"} connection` : null,
  ].filter(Boolean).join(" · ") : "";

  return (
    <div className={`night watch ${prefs.audioOnly ? "audio-only" : ""}`} onPointerDown={unlock}>
      <audio ref={audioEl} autoPlay />
      <div className="night-page watch-page">
        <TopBar
          title={name}
          back="/parent"
          right={
            <>
              <span className={`state-chip ${live ? "on" : ""}`}>{live ? "Live" : snap.state === "off" ? "Off" : "Not live"}</span>
              <button className="icon-btn" type="button" onClick={() => setMuted((m) => !m)} aria-label={muted ? "Turn sound on" : "Mute"} aria-pressed={muted}>
                <Icon name={muted ? "muted" : "speaker"} size={22} />
              </button>
              <button className="icon-btn" type="button" onClick={() => setSheet("details")} aria-label="Station details"><Icon name="dots" size={22} /></button>
            </>
          }
        />
        {statusLine && <p className="status-line">{statusLine}</p>}

        <div className="stage" style={{ "--ratio": live && !prefs.audioOnly ? ratio : 4 / 3 } as React.CSSProperties}>
          {!prefs.audioOnly && (
            <video ref={videoEl} className={prefs.night ? "night-view" : ""} muted playsInline autoPlay
              onResize={(e) => { const v = e.currentTarget; if (v.videoWidth && v.videoHeight) setRatio(v.videoWidth / v.videoHeight); }} />
          )}
          {prefs.audioOnly && live && (
            <div className="listening">
              <Icon name="ear" size={40} />
              <p>Listening</p>
              <small>Video is off to save battery. Sound and alerts keep working.</small>
            </div>
          )}
          {!live && (
            <div className="stage-overlay">
              {["connecting", "reconnecting"].includes(snap.state) && <span className="spinner" />}
              <p>{STATE_TEXT[snap.state as Exclude<WatchSnapshot["state"], "live">](name)}</p>
              {(snap.state === "blocked" || snap.state === "noanswer") && <button className="btn btn-line" type="button" onClick={() => watcher.retryNow()}>Try again now</button>}
              {snap.state === "removed" && <button className="btn" type="button" onClick={() => { pairings.remove(pairing.room); navigate("/parent"); }}>Forget it</button>}
            </div>
          )}
          {live && needsTap && (
            <button className="stage-overlay tap" type="button" onClick={unlock}><Icon name="speaker" size={32} /><p>Tap to hear the baby</p></button>
          )}
        </div>

        <p className="meter-label"><span>Sound at the baby</span><span>{threshold !== null ? <><i aria-hidden="true" />Alert line</> : "Alerts off"}</span></p>
        <div className="meter" ref={meter} aria-hidden="true">
          <span />
          {threshold !== null && <i style={{ left: `${threshold}%` }} />}
        </div>

        {alertAt && (
          <div className="alert-banner" role="alert">
            <Icon name="bell" size={22} />
            <span className="grow">Sound at the baby station, {alertAt.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })}</span>
            <button className="btn btn-sm" type="button" onClick={() => setAlertAt(null)}>OK</button>
          </div>
        )}
        {live && status && !status.listening && <p className="notice notice-warn">Sound alerts and soothing sounds are paused until someone taps the baby station's screen once.</p>}
        {live && status?.sound && (
          <p className="notice">{SOUNDS.find((s) => s.name === status.sound)?.label} is playing on the station{status.soundEnds ? ` until ${new Date(status.soundEnds).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" })}` : ""}.</p>
        )}

        {snap.state !== "removed" && <div className="controls">
          <div className="tiles">
            <button className="tile" type="button" aria-pressed={prefs.audioOnly} onClick={() => setAudioOnly(!prefs.audioOnly)}><Icon name="ear" size={24} /><span>Audio only</span></button>
            <button className="tile" type="button" aria-pressed={prefs.night} disabled={prefs.audioOnly} onClick={() => update({ night: !prefs.night })}><Icon name="moon" size={24} /><span>Night view</span></button>
            <button className="tile" type="button" aria-pressed={Boolean(status?.sound)} disabled={!live} onClick={() => setSheet("sounds")}><Icon name="music" size={24} /><span>Soothe</span></button>
            <button className="tile" type="button" aria-pressed={prefs.sensitivity !== "off"} onClick={() => setSheet("alerts")}><Icon name="bell" size={24} /><span>Alerts</span></button>
          </div>
          <TalkButton watcher={watcher} live={live} talking={snap.talking} />
        </div>}
      </div>

      {sheet === "sounds" && <SoundsSheet watcher={watcher} prefs={prefs} update={update} playing={status?.sound ?? null} onClose={() => setSheet(null)} />}
      {sheet === "alerts" && <AlertsSheet value={prefs.sensitivity} onChange={setSensitivity} onTest={() => chime(audio())} onClose={() => setSheet(null)} />}
      {sheet === "details" && <DetailsSheet snap={snap} pairing={pairing} onClose={() => setSheet(null)} />}
    </div>
  );
}

function TalkButton({ watcher, live, talking }: { watcher: Watcher; live: boolean; talking: boolean }) {
  const start = () => { void watcher.startTalk().catch(() => toast("Allow the microphone to talk to the baby.")); };
  const stop = () => watcher.stopTalk();
  return (
    <button
      className={`btn btn-xl btn-block talk ${talking ? "on" : ""}`} type="button" disabled={!live}
      onPointerDown={(e) => { e.currentTarget.setPointerCapture(e.pointerId); start(); }}
      onPointerUp={stop} onPointerCancel={stop} onLostPointerCapture={stop}
      onKeyDown={(e) => { if ((e.key === " " || e.key === "Enter") && !e.repeat) { e.preventDefault(); start(); } }}
      onKeyUp={(e) => { if (e.key === " " || e.key === "Enter") stop(); }}
      onContextMenu={(e) => e.preventDefault()}
    >
      <Icon name="mic" size={24} /> {talking ? "Talking... let go to stop" : "Hold to talk"}
    </button>
  );
}

const TIMERS = [[15, "15 min"], [30, "30 min"], [60, "1 hour"], [0, "No limit"]] as const;

function SoundsSheet({ watcher, prefs, update, playing, onClose }: { watcher: Watcher; prefs: WatchSettings; update: (p: Partial<WatchSettings>) => void; playing: SoundName | null; onClose: () => void }) {
  const play = (name: SoundName | null, patch: Partial<WatchSettings> = {}) => {
    const next = { ...prefs, ...patch };
    update(patch);
    watcher.playSound(name, next.soundVolume, next.soundMinutes);
  };
  return (
    <Sheet title="Soothe" onClose={onClose}>
      <p className="night-muted small">Plays on the baby station's speaker. Sounds are made on the station itself.</p>
      <ul className="sound-list">
        {SOUNDS.map((s) => (
          <li key={s.name}>
            <button className="sound-row" type="button" aria-pressed={playing === s.name} onClick={() => play(playing === s.name ? null : s.name)}>
              <span className="grow"><strong>{s.label}</strong><small>{s.hint}</small></span>
              <span className="sound-state">{playing === s.name ? "Playing. Tap to stop" : "Play"}</span>
            </button>
          </li>
        ))}
      </ul>
      <label className="field">
        <span>Volume</span>
        <input type="range" min={5} max={100} step={5} value={Math.round(prefs.soundVolume * 100)}
          onChange={(e) => { const v = Number(e.target.value) / 100; update({ soundVolume: v }); if (playing) watcher.playSound(playing, v, prefs.soundMinutes); }} />
      </label>
      <div className="field">
        <span>Stop after</span>
        <div className="segmented" role="radiogroup" aria-label="Stop after">
          {TIMERS.map(([m, label]) => (
            <button key={m} type="button" role="radio" aria-checked={prefs.soundMinutes === m} onClick={() => (playing ? play(playing, { soundMinutes: m }) : update({ soundMinutes: m }))}>{label}</button>
          ))}
        </div>
      </div>
      {playing && <button className="btn btn-lg btn-block btn-line" type="button" onClick={() => play(null)}>Stop the sound</button>}
    </Sheet>
  );
}

const LEVELS: [Sensitivity, string][] = [["off", "Off"], ["low", "Low"], ["medium", "Medium"], ["high", "High"]];

function AlertsSheet({ value, onChange, onTest, onClose }: { value: Sensitivity; onChange: (v: Sensitivity) => void; onTest: () => void; onClose: () => void }) {
  const [perm, setPerm] = useState(() => ("Notification" in window ? Notification.permission : "unsupported"));
  return (
    <Sheet title="Noise alerts" onClose={onClose}>
      <p className="night-muted small">When sound at the baby stays above the line on the meter for about 3 seconds, this device chimes and vibrates. High catches quieter sounds.</p>
      <div className="segmented four" role="radiogroup" aria-label="Sensitivity">
        {LEVELS.map(([v, label]) => <button key={v} type="button" role="radio" aria-checked={value === v} onClick={() => onChange(v)}>{label}</button>)}
      </div>
      <p className="night-muted small">While a soothing sound plays, alerts listen for sound louder than it.</p>
      <button className="btn btn-lg btn-block btn-line" type="button" onClick={onTest}>Hear the alert</button>
      {perm === "default" && (
        <button className="btn btn-lg btn-block btn-line" type="button" onClick={() => void Notification.requestPermission().then(setPerm)}>Also alert when Monitor is in the background</button>
      )}
      {perm === "denied" && <p className="night-muted small">Background alerts are blocked in this browser's settings. Keep Monitor open to hear alerts.</p>}
      <p className="night-muted small">On iPhone and iPad, keep Monitor open on the screen. They pause web pages when the screen locks.</p>
    </Sheet>
  );
}

function DetailsSheet({ snap, pairing, onClose }: { snap: WatchSnapshot; pairing: Pairing; onClose: () => void }) {
  const [busy, setBusy] = useState(false);
  const route = snap.stats?.route === "local" ? "Same network, device to device" : snap.stats?.route === "internet" ? "Over the internet, device to device" : snap.stats?.route === "relay" ? "Through a relay" : "Not connected";
  const forget = async () => {
    setBusy(true);
    try {
      const uid = await me();
      await setMany([
        { path: `rooms/${pairing.room}/links/${uid}`, document: null },
        { path: `rooms/${pairing.room}/joins/${uid}`, document: null },
      ]).catch(() => null);
      pairings.remove(pairing.room);
      navigate("/parent");
    } catch (err) {
      toast(explain(err));
      setBusy(false);
    }
  };
  return (
    <Sheet title={snap.stationName} onClose={onClose}>
      <dl className="facts">
        <div><dt>Connection</dt><dd>{route}</dd></div>
        <div><dt>Quality</dt><dd>{snap.stats ? snap.stats.quality[0].toUpperCase() + snap.stats.quality.slice(1) : "Unknown"}</dd></div>
        <div><dt>Station battery</dt><dd>{snap.status?.battery != null ? `${snap.status.battery}%${snap.status.charging ? ", charging" : ""}` : "Not shared by this browser"}</dd></div>
        <div><dt>Watching now</dt><dd>{snap.status ? `${snap.status.watching} device${snap.status.watching === 1 ? "" : "s"}` : "Unknown"}</dd></div>
      </dl>
      <p className="night-muted small">Video and sound travel straight between the two devices, encrypted, and are never recorded.</p>
      <button className="btn btn-lg btn-block btn-line danger" type="button" disabled={busy} onClick={() => void forget()}>Forget this baby station</button>
    </Sheet>
  );
}

Change history

Move store links to openapps.free
11d38ef641 · 18 hours ago
Release with preview
41c8af3ee5 · 20 hours ago
Bind agent address in policy
5b2d792529 · 20 hours ago
Use OpenApps service URLs and add contribution strip (retry)
be4477dcb3 · 20 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