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 80ddb8c5df822e290afdd3c7
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/Start.tsx
// The first screen, in the landing hero and on the installed app's home: pick files to send, or
// type a 6-digit code to receive. Picking files starts a share and opens the sending screen.
import { useRef, useState, useSyncExternalStore, type FormEvent } from "react";
import { get, set, serverTimestamp } from "@bounded-sh/client";
import { me, explain } from "./lib/bounded";
import { navigate, usePath } from "./lib/router";
import { picksFromInput, type Pick } from "./lib/items";
import { isPhoneOrTablet } from "./lib/device";
import { files as nFiles } from "./lib/format";
import { Sender } from "./sender";
import { Icon } from "./ui";
// The share in progress lives as long as the page, so moving between screens does not stop it.
let active: Sender | null = null;
const watchers = new Set<() => void>();
const notify = () => { for (const fn of watchers) fn(); };
const watch = (fn: () => void) => { watchers.add(fn); return () => { watchers.delete(fn); }; };
export function currentSender(): Sender | null {
return active && active.snapshot().state !== "stopped" ? active : null;
}
export function useCurrentSender(): Sender | null {
return useSyncExternalStore(watch, currentSender);
}
/** The share this page started last, even once stopped, so the sending screen can say that it stopped. */
export function useLastSender(): Sender | null {
return useSyncExternalStore(watch, () => active);
}
/** Starts sharing these picks (or adds them to the share in progress) and opens the sending screen. */
export function send(picks: Pick[]) {
if (!picks.some((p) => p.files.length)) return;
const now = currentSender();
if (now && now.snapshot().state === "sharing") {
now.add(picks);
} else {
void now?.stop();
active = new Sender(picks);
void active.start();
notify();
}
navigate("/send");
}
/** A share that could not start begins again as a new share with the same files. */
export function retrySending() {
if (!active) return;
active = new Sender([...active.picks]);
void active.start();
notify();
}
export async function stopSending() {
await active?.stop();
notify();
}
/** A 6-digit code to the share it points to: one rate-limited try per code. */
async function resolveCode(code: string): Promise<string> {
const uid = await me();
const tryPath = `codetries/${uid}/t/${code}`;
if (!(await get(tryPath))) {
try {
await set(tryPath, { n: 1, at: serverTimestamp() });
} catch (err) {
const e = err as { decline?: { boundary?: { cause?: string } }; message?: string };
if (e?.decline?.boundary?.cause === "cap_exceeded" || /code_tries|invariant|409/.test(String(e?.message))) {
throw new Error("Too many codes tried. Wait a few minutes, or open the link or QR code instead.");
}
throw new Error(explain(err));
}
}
const doc = await get(`codes/${code}`);
if (!doc?.drop) throw new Error("That code did not work. Check the code on the sending device. Each code lasts 10 minutes.");
return String(doc.drop);
}
export function Start() {
usePath();
const sharing = useCurrentSender();
const fileInput = useRef<HTMLInputElement>(null);
const folderInput = useRef<HTMLInputElement>(null);
const [code, setCode] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const onPicked = (input: HTMLInputElement) => {
const picks = input.files ? picksFromInput(input.files) : [];
input.value = "";
send(picks);
};
const receive = 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 {
const drop = await resolveCode(digits);
navigate(`/r#${drop}`);
} catch (err) {
setError((err as Error).message);
setBusy(false);
}
};
const count = sharing?.snapshot().files.length ?? 0;
return (
<div className="start">
{sharing && (
<a className="resume" href="/send" onClick={(e) => { e.preventDefault(); navigate("/send"); }}>
<span>You are sharing {nFiles(count)}</span>
<span className="resume-go">Open</span>
</a>
)}
<div className="dropzone">
<span className="dz-icon"><Icon name="send" size={26} /></span>
<p className="dz-title">Send files</p>
<p className="dz-sub">Any size, any number. They go straight to the other device.</p>
<button className="btn btn-lg btn-block" type="button" onClick={() => fileInput.current?.click()}>Choose files</button>
{!isPhoneOrTablet && (
<>
<button className="btn btn-line btn-block" type="button" onClick={() => folderInput.current?.click()}><Icon name="folder" size={18} /> Choose a folder</button>
<p className="dz-hint">or drop files and folders here</p>
</>
)}
<input ref={fileInput} type="file" multiple hidden onChange={(e) => onPicked(e.currentTarget)} />
<input ref={folderInput} type="file" hidden {...{ webkitdirectory: "" }} onChange={(e) => onPicked(e.currentTarget)} />
</div>
<form className="receive-form" onSubmit={(e: FormEvent) => { e.preventDefault(); void receive(code); }} noValidate>
<label className="field" htmlFor="code">
<span>Receiving? Type the 6-digit code</span>
</label>
<div className="code-row">
<input
id="code" className="input code-input" inputMode="numeric" autoComplete="one-time-code" pattern="[0-9 ]*" maxLength={7}
placeholder="000 000" value={code} disabled={busy}
onChange={(e) => {
const v = e.target.value.replace(/[^\d ]/g, "");
setCode(v);
setError(null);
if (v.replace(/\D/g, "").length === 6) void receive(v);
}}
/>
<button className="btn btn-line" type="submit" disabled={busy}>{busy ? "Opening..." : "Receive"}</button>
</div>
{error && <p className="notice notice-error" role="alert">{error}</p>}
</form>
<p className="start-note">Free. No account. Nothing is uploaded.</p>
</div>
);
}
Change history
Point OpenApps links to openapps.free
Bind agent address in policy
Retry release capture
Use OpenApps service URLs and add contribution strip
Bounded source sync
Bounded source sync
Bounded source sync