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/Parent.tsx
// The parent's start: pair with a station by code or QR code, or open one paired before.
import { useEffect, useRef, useState, type FormEvent } from "react";
import { navigate } from "./lib/router";
import { deviceName, pairings } from "./lib/store";
import { joinRoom, resolveCode } from "./pair";
import { TopBar } from "./Baby";
import { Icon, Sheet, useNightChrome } from "./ui";
const ROOM_RE = /^[A-Za-z0-9]{20}$/;
/** The room id from a pairing QR code or link (https://.../pair#<room>). */
function roomFromLink(text: string): string | null {
try {
const hash = new URL(text, location.origin).hash.slice(1);
return ROOM_RE.test(hash) ? hash : null;
} catch {
return null;
}
}
function DeviceName({ value, onChange }: { value: string; onChange: (v: string) => void }) {
const [editing, setEditing] = useState(false);
if (editing) {
return (
<label className="field">
<span>Name this device</span>
<input className="input" autoFocus value={value} maxLength={40} onChange={(e) => onChange(e.target.value)} onBlur={() => setEditing(false)} />
<small>The baby station shows this name in its list of parents.</small>
</label>
);
}
return (
<p className="night-muted small">The baby station will list this device as <strong className="night-strong">{value || "Parent device"}</strong>. <button className="link-btn" type="button" onClick={() => setEditing(true)}>Change</button></p>
);
}
export function ParentPage() {
useNightChrome();
const [code, setCode] = useState("");
const [name, setName] = useState(deviceName.get);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const [scanning, setScanning] = useState(false);
const list = pairings.list();
const canScan = typeof window !== "undefined" && "BarcodeDetector" in window;
const pair = async (room: string) => {
const label = name.trim().slice(0, 40) || "Parent device";
deviceName.set(label);
const p = await joinRoom(room, label);
navigate(`/watch/${p.key}`);
};
const submit = async (value: string) => {
const digits = value.replace(/\D/g, "");
if (digits.length !== 6) return setError("The code has 6 digits.");
setBusy(true);
setError(null);
try {
await pair(await resolveCode(digits));
} catch (err) {
setError((err as Error).message);
setBusy(false);
}
};
const onScan = async (text: string) => {
setScanning(false);
const room = roomFromLink(text);
if (!room) return setError("That QR code is not a Monitor pairing code.");
setBusy(true);
try {
await pair(room);
} catch (err) {
setError((err as Error).message);
setBusy(false);
}
};
return (
<div className="night">
<div className="night-page">
<TopBar title="Parent" />
<h1 className="night-h1">Pair with the baby station</h1>
<form className="pair-form" onSubmit={(e: FormEvent) => { e.preventDefault(); void submit(code); }} noValidate>
<label className="field">
<span>Code shown on the baby station</span>
<input
className="input code-input" inputMode="numeric" autoComplete="one-time-code" pattern="[0-9 ]*" maxLength={7}
placeholder="000 000" value={code} disabled={busy} autoFocus={!list.length}
onChange={(e) => {
const v = e.target.value.replace(/[^\d ]/g, "");
setCode(v);
setError(null);
if (v.replace(/\D/g, "").length === 6) void submit(v);
}}
/>
</label>
{error && <p className="notice notice-error" role="alert">{error}</p>}
<button className="btn btn-xl btn-block" type="submit" disabled={busy}>{busy ? "Pairing..." : "Pair"}</button>
{canScan ? (
<button className="btn btn-xl btn-block btn-line" type="button" disabled={busy} onClick={() => setScanning(true)}><Icon name="qr" size={22} /> Scan the QR code</button>
) : (
<p className="night-muted small">Or point this phone's camera app at the QR code on the baby station.</p>
)}
<DeviceName value={name} onChange={setName} />
</form>
{list.length > 0 && (
<section className="panel-night">
<h2 className="night-h2">Paired before</h2>
<ul className="again-list">
{list.map((p) => (
<li key={p.key}>
<a className="again-item" href={`/watch/${p.key}`} onClick={(e) => { e.preventDefault(); navigate(`/watch/${p.key}`); }}>
<span className="again-name">{p.name}</span>
<span className="again-go">Watch</span>
</a>
</li>
))}
</ul>
</section>
)}
</div>
{scanning && <Scanner onResult={(t) => void onScan(t)} onClose={() => setScanning(false)} />}
</div>
);
}
interface Detector { detect(source: CanvasImageSource): Promise<{ rawValue: string }[]> }
function Scanner({ onResult, onClose }: { onResult: (text: string) => void; onClose: () => void }) {
const video = useRef<HTMLVideoElement>(null);
const result = useRef(onResult);
result.current = onResult;
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let stream: MediaStream | null = null;
let timer: number | undefined;
let done = false;
(async () => {
try {
stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
if (done) return stream.getTracks().forEach((t) => t.stop());
video.current!.srcObject = stream;
await video.current!.play();
const Ctor = (window as unknown as { BarcodeDetector: new (o: { formats: string[] }) => Detector }).BarcodeDetector;
const detector = new Ctor({ formats: ["qr_code"] });
const look = async () => {
if (done) return;
const found = await detector.detect(video.current!).catch(() => []);
if (found.length && !done) { done = true; result.current(found[0].rawValue); return; }
timer = window.setTimeout(look, 250);
};
void look();
} catch {
setError("Could not open the camera. Allow it, or type the code instead.");
}
})();
return () => { done = true; clearTimeout(timer); stream?.getTracks().forEach((t) => t.stop()); };
}, []);
return (
<Sheet title="Scan the QR code" onClose={onClose}>
{error ? <p className="notice notice-error">{error}</p> : (
<div className="scanner"><video ref={video} muted playsInline /><span className="scanner-frame" aria-hidden="true" /></div>
)}
<p className="night-muted small center">Point the camera at the QR code on the baby station.</p>
</Sheet>
);
}
/** Opened from a scanned QR code: pairs with the station named in the link's fragment. */
export function PairLinkPage() {
useNightChrome();
const [room] = useState(() => roomFromLink(location.href));
const [name, setName] = useState(deviceName.get);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(room ? null : "This pairing link is incomplete. Scan the QR code again, or type the code instead.");
useEffect(() => { history.replaceState(null, "", "/pair"); }, []);
const pair = async () => {
if (!room) return;
setBusy(true);
setError(null);
try {
const label = name.trim().slice(0, 40) || "Parent device";
deviceName.set(label);
const p = await joinRoom(room, label);
navigate(`/watch/${p.key}`);
} catch (err) {
setError((err as Error).message);
setBusy(false);
}
};
return (
<div className="night">
<div className="night-page">
<TopBar title="Parent" />
<h1 className="night-h1">Watch this baby station?</h1>
<p className="night-lede">This device will see and hear the baby station whenever it is on. You can remove it later from the station.</p>
{error && <p className="notice notice-error" role="alert">{error}</p>}
{room ? (
<div className="pair-form">
<button className="btn btn-xl btn-block" type="button" disabled={busy} onClick={() => void pair()}>{busy ? "Pairing..." : "Pair and watch"}</button>
<DeviceName value={name} onChange={setName} />
</div>
) : (
<button className="btn btn-xl btn-block" type="button" onClick={() => navigate("/parent")}>Type the code instead</button>
)}
</div>
</div>
);
}
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