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/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
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