Skip to content
76 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

The phone app's scaffolding, in apps/mobile: Expo SDK 57 with Expo Router, Android first, with Chat, Code and Customize. It signs in with g1t's own OAuth 2.1 (PKCE, a client id that encodes "g1t for Android" and g1t://oauth/callback, so nothing is registered), keeps the server and credentials in the phone's keystore, refreshes the 30-day token a minute early and shares the single-use refresh between requests. A personal access token is the other way in, and a self-hosted g1t the other server.1/**
2 * Which g1t the app talks to. Hosted g1t is the site `https://g1t.sh` and
3 * the API `https://api.g1t.sh`; a self-hosted g1t sets its own (SITE_URL
4 * and API_URL, apps/api/src/addresses.rs), so the app keeps both.
5 *
6 * The site is where people sign in and approve the app, and where Chat's
7 * routes are; the API is the REST API and the OAuth issuer.
8 *
9 * Kept free of React Native imports so `npm test` runs it under Node.
10 */
11
12export type Server = {
13 /** An origin, no trailing slash: `https://g1t.sh`. */
14 site: string;
15 /** An origin, no trailing slash: `https://api.g1t.sh`. */
16 api: string;
17};
18
19export const HOSTED: Server = { site: "https://g1t.sh", api: "https://api.g1t.sh" };
20
21/**
22 * An origin as typed: http or https with a host, `https://` when no scheme
23 * is given; only the origin is kept. Null when it is not a web address.
24 * Plain http is accepted for a g1t on this network, as in development.
25 */
26export function parseOrigin(text: string): string | null {
27 const trimmed = text.trim();
28 if (!trimmed) return null;
29 const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
30 let url: URL;
31 try {
32 url = new URL(withScheme);
33 } catch {
34 return null;
35 }
36 if (url.protocol !== "https:" && url.protocol !== "http:") return null;
37 if (!url.hostname) return null;
38 return `${url.protocol}//${url.host}`;
39}
40
41/**
42 * The API's address for a site, when only the site was given: hosted
43 * g1t's own, or `api.` in front of a self-hosted site's host, which is
44 * how deploy/self-host names it.
45 */
46export function apiFor(site: string): string {
47 if (site === HOSTED.site) return HOSTED.api;
48 const url = new URL(site);
49 return `${url.protocol}//api.${url.host}`;
50}
51
52/** A server from what was typed: the site, and the API if it was given. */
53export function parseServer(siteText: string, apiText?: string): Server | null {
54 const site = parseOrigin(siteText);
55 if (!site) return null;
56 if (apiText && apiText.trim()) {
57 const api = parseOrigin(apiText);
58 if (!api) return null;
59 return { site, api };
60 }
61 return { site, api: apiFor(site) };
62}
63
64export function isHosted(server: Server): boolean {
65 return server.site === HOSTED.site && server.api === HOSTED.api;
66}
67
68/** The site's address for a path, such as a page to open in the browser. */
69export function siteUrl(server: Server, path: string): string {
70 return server.site + (path.startsWith("/") ? path : `/${path}`);
71}
72
73/** The socket address for one of the site's live paths. */
74export function socketUrl(server: Server, path: string): string {
75 return siteUrl(server, path).replace(/^http/, "ws");
76}