lingo-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 8af89d2cc9f8
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 7efc97d0dde0b2a3e729ccf9
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/bounded.ts
// One Bounded session per tab: a guest account created on first use, or the learner's own account
// once they choose to keep their progress on all devices. No sign-up to start, ever.
import { init, getCurrentUser, getLoginReturnError, signInAnonymously, loginWithRedirect, logout } from "@bounded-sh/client";
import { APP_ID } from "../../functions/lib/config";

/** The app this page is served for: the host injects it, so a preview talks to its own data. Local dev falls back to config. */
export const SERVED_APP_ID =
  (typeof document !== "undefined" && document.getElementById("__bounded-first-live-bridge")?.getAttribute("data-app-id")) || APP_ID;

export interface Me { id: string; email: string | null; guest: boolean }

let session: Promise<Me> | null = null;
let loginError: string | null = null;

/** Resolves to this browser's learner: the signed-in account, or a guest created once. */
export function me(): Promise<Me> {
  session ??= (async () => {
    await init({ appId: SERVED_APP_ID });
    const err = getLoginReturnError();
    if (err) loginError = explain(err);
    const user = getCurrentUser() || (await signInAnonymously());
    if (!user?.id) throw new Error("Could not start a session.");
    return { id: user.id as string, email: (user.email as string | null) ?? null, guest: Boolean(user.isAnonymous) };
  })().catch((err) => {
    session = null;
    throw err;
  });
  return session;
}

/** A hosted sign-in that came back with an error, once. */
export function takeLoginError(): string | null {
  const e = loginError;
  loginError = null;
  return e;
}

/** Hosted email sign-in. The page leaves and comes back signed in. */
export async function signIn(): Promise<void> {
  await loginWithRedirect({ methods: ["email"] });
}

export async function signOut(): Promise<void> {
  await logout();
}

/** A sentence a person can read for a failed write or read. */
export function explain(err: unknown): string {
  const e = err as { message?: string; decline?: { message?: string; invariant?: string } };
  if (e?.decline?.invariant === "xp_per_hour") return "The league counts up to 600 XP an hour. Your progress is saved.";
  if (e?.decline?.message) return e.decline.message;
  const msg = String(e?.message || err || "");
  if (/network|fetch|Failed to fetch/i.test(msg)) return "You seem to be offline. Your progress is saved on this device.";
  if (/denied|403|forbidden/i.test(msg)) return "That change was not allowed.";
  return msg || "Something went wrong. Try again.";
}

Change history

Point OpenApps links to openapps.free
8af89d2cc9 · 17 hours ago
Bind agent address in policy
0018350275 · 19 hours ago
Use OpenApps service URLs and add contribution strip
805365daf4 · 19 hours ago
Bounded source sync
3f2fb3b265 · 2 days ago
Bounded source sync
7dcb580324 · 2 days ago
Bounded source sync
dcd05c0944 · 2 days ago