santa-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 b8b257a837b1
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 c7891873549c9ca1004894b3
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/data.ts
// Reads and writes shared by the pages: joining an exchange, opening a person's page with their key,
// and writing new people. Every rule behind these lives in policy.json.
import { get, set, setMany, serverTimestamp } from "@bounded-sh/client";
import { me, explain } from "./bounded";
import { randomId } from "../../functions/lib/santa";

export interface NewPerson { name: string; contact: string; mine?: boolean }
type Write = { path: string; document: Record<string, unknown> | null };

/** Signs in as a guest and joins the exchange on this device (knowing its id is the permission). False when there is no such exchange. */
export async function openExchange(id: string): Promise<{ uid: string; found: boolean }> {
  const uid = await me();
  if (await get(`exchanges/${id}/joins/${uid}`)) return { uid, found: true };
  try {
    await set(`exchanges/${id}/joins/${uid}`, { at: serverTimestamp() });
    return { uid, found: true };
  } catch (err) {
    // The join is refused only when there is no exchange with that id.
    if (/offline/.test(explain(err))) throw err;
    return { uid, found: false };
  }
}

/** The claim and its open record, written together, so every device that opens a page is listed on it. */
export function claimWrites(id: string, pid: string, key: string, uid: string): Write[] {
  const openId = `${pid}_${uid}`;
  return [
    { path: `personclaims/${pid}/c/${uid}`, document: { key, exchangeId: id, openId } },
    { path: `exchanges/${id}/opens/${openId}`, document: { pid, by: uid, via: "web", at: serverTimestamp() } },
  ];
}

/** A person, their key and their contact. `self` people joined from the link and hold their own key. */
export function personWrites(id: string, uid: string, p: { pid: string; key: string; name: string; contact: string; self: boolean }): Write[] {
  return [
    { path: `exchanges/${id}/people/${p.pid}`, document: { name: p.name, addedBy: uid, self: p.self, createdAt: serverTimestamp() } },
    { path: `personkeys/${p.pid}`, document: { key: p.key, exchangeId: id } },
    ...(p.contact ? [{ path: `exchanges/${id}/contacts/${p.pid}`, document: { contact: p.contact, by: uid } }] : []),
  ];
}

/** Writes groups in batches the data plane takes comfortably, never splitting a group (a person and their key go together). */
export async function writeGroups(groups: Write[][]) {
  let batch: Write[] = [];
  for (const g of groups) {
    if (batch.length && batch.length + g.length > 90) { await setMany(batch); batch = []; }
    batch.push(...g);
  }
  if (batch.length) await setMany(batch);
}

/** Makes an exchange owned by this device, with its people; the organizer's own person opens here. */
export async function createExchange(o: { name: string; budget: string; date: string; people: NewPerson[] }) {
  const uid = await me();
  const id = randomId(20);
  const key = randomId(24);
  const people = o.people.map((p) => ({ ...p, pid: randomId(20), key: randomId(24), self: false }));
  const mine = people.find((p) => p.mine);
  await writeGroups([[
    { path: `exchanges/${id}`, document: { name: o.name, budget: o.budget, date: o.date, notes: "", allowJoin: true, organizerSees: false, revealAt: 0, status: "open", round: 0, drawnAt: 0, createdBy: uid, createdAt: serverTimestamp() } },
    { path: `exchanges/${id}/joins/${uid}`, document: { at: serverTimestamp() } },
    { path: `exchanges/${id}/owners/${uid}`, document: { at: serverTimestamp() } },
    { path: `exchangekeys/${id}`, document: { key } },
    { path: `keepapart/${id}`, document: { pairs: "[]" } },
  ], ...people.map((p) => [...personWrites(id, uid, p), ...(p.mine ? claimWrites(id, p.pid, p.key, uid) : [])])]);
  return { id, key, mine: mine ? { pid: mine.pid, key: mine.key, name: mine.name } : null };
}

Change history

Point OpenApps links to openapps.free
b8b257a837 · 1 day ago
Bind agent address in policy constants
9a72d0200b · 1 day ago
Use functions.openapps.xyz alias, add OpenApp contribution strip
60038fa63d · 1 day ago
Bounded source sync
a2e0247232 · 2 days ago
Bounded source sync
c84c736f8b · 2 days ago
Bounded source sync
3a54a80cdc · 2 days ago
Bounded source sync
2eed41b4de · 2 days ago