record-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 1c51a6b34b9a
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 46e773698d4102c284d30369
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/lib/store.ts
// Recordings are kept in this browser's IndexedDB until the person deletes them. Nothing is sent anywhere.
// While a recording runs, its pieces are written here as the browser hands them over (every second or
// two), so a closed or crashed tab loses only the last few seconds: the next visit puts the pieces back
// together (see recover() in Recorder.tsx).
import type { Mode } from "./capture";

export type Recording = {
  id: string;
  name: string;
  created: number;
  mode: Mode;
  /** The file's media type, such as video/mp4 or audio/webm. */
  type: string;
  /** Seconds; 0 when it could not be read. */
  duration: number;
  size: number;
  /** True while the recording is being made, or when it was cut off before it was saved. */
  partial: boolean;
};

type Store = "recordings" | "files" | "chunks";

let opening: Promise<IDBDatabase> | null = null;
function db(): Promise<IDBDatabase> {
  opening ??= new Promise<IDBDatabase>((resolve, reject) => {
    const req = indexedDB.open("record", 1);
    req.onupgradeneeded = () => {
      req.result.createObjectStore("recordings", { keyPath: "id" });
      req.result.createObjectStore("files"); // the video or audio, by recording id
      req.result.createObjectStore("chunks"); // pieces of a recording in progress, by [recording id, piece number]
    };
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  }).catch((err) => {
    opening = null;
    throw err;
  });
  return opening;
}

async function run<T = undefined>(stores: Store[], mode: IDBTransactionMode, op: (t: IDBTransaction) => IDBRequest<T> | void): Promise<T> {
  const tx = (await db()).transaction(stores, mode);
  const req = op(tx);
  return new Promise<T>((resolve, reject) => {
    tx.oncomplete = () => resolve(req ? req.result : (undefined as T));
    tx.onerror = tx.onabort = () => reject(tx.error);
  });
}

const pieces = (id: string) => IDBKeyRange.bound([id, 0], [id, Infinity]);

export const allRecordings = () => run<Recording[]>(["recordings"], "readonly", (t) => t.objectStore("recordings").getAll());
export const getFile = (id: string) => run<Blob | undefined>(["files"], "readonly", (t) => t.objectStore("files").get(id));
export const putRecording = (r: Recording) => run(["recordings"], "readwrite", (t) => void t.objectStore("recordings").put(r));

/** Saves a finished recording and its file, and drops the pieces it was made from. */
export const saveRecording = (r: Recording, file: Blob) =>
  run(["recordings", "files", "chunks"], "readwrite", (t) => {
    t.objectStore("recordings").put(r);
    t.objectStore("files").put(file, r.id);
    t.objectStore("chunks").delete(pieces(r.id));
  });

export const deleteRecording = (id: string) =>
  run(["recordings", "files", "chunks"], "readwrite", (t) => {
    t.objectStore("recordings").delete(id);
    t.objectStore("files").delete(id);
    t.objectStore("chunks").delete(pieces(id));
  });

export const putChunk = (id: string, n: number, data: Blob) => run(["chunks"], "readwrite", (t) => void t.objectStore("chunks").put(data, [id, n]));
export const chunksOf = (id: string) => run<Blob[]>(["chunks"], "readonly", (t) => t.objectStore("chunks").getAll(pieces(id)));

export const lockName = (id: string) => `record-${id}`;

/** Asks the browser not to clear recordings when the device runs low on space. */
export function keepStored() {
  void navigator.storage?.persist?.().catch(() => false);
}

Change history

Point store links to openapps.free
1c51a6b34b · 18 hours ago
Bind agent address in policy
50f7c610b7 · 19 hours ago
Link OpenApps pages and add contribution strip
99dc7c6d18 · 19 hours ago
Bounded source sync
16c8400957 · 2 days ago
Bounded source sync
c07b7aea64 · 2 days ago
Bounded source sync
f19b8997d3 · 2 days ago