| 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 | |
| 12 | export 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 | /** False when this g1t sends phones no notifications (its PHONE_PUSH=off, lib/discover.ts). */ |
| 18 | push?: boolean; |
| 19 | }; |
| 20 | |
| 21 | export const HOSTED: Server = { site: "https://g1t.sh", api: "https://api.g1t.sh" }; |
| 22 | |
| 23 | /** |
| 24 | * An origin as typed: http or https with a host, `https://` when no scheme |
| 25 | * is given; only the origin is kept. Null when it is not a web address. |
| 26 | * Plain http is accepted for a g1t on this network, as in development. |
| 27 | */ |
| 28 | export function parseOrigin(text: string): string | null { |
| 29 | const trimmed = text.trim(); |
| 30 | if (!trimmed) return null; |
| 31 | const withScheme = trimmed.includes("://") ? trimmed : `https://${trimmed}`; |
| 32 | let url: URL; |
| 33 | try { |
| 34 | url = new URL(withScheme); |
| 35 | } catch { |
| 36 | return null; |
| 37 | } |
| 38 | if (url.protocol !== "https:" && url.protocol !== "http:") return null; |
| 39 | if (!url.hostname) return null; |
| 40 | return `${url.protocol}//${url.host}`; |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * The API's address for a site, when only the site was given: hosted |
| 45 | * g1t's own, or `api.` in front of a self-hosted site's host, which is |
| 46 | * how deploy/self-host names it. |
| 47 | */ |
| 48 | export function apiFor(site: string): string { |
| 49 | if (site === HOSTED.site) return HOSTED.api; |
| 50 | const url = new URL(site); |
| 51 | return `${url.protocol}//api.${url.host}`; |
| 52 | } |
| 53 | |
| 54 | /** A server from what was typed: the site, and the API if it was given. */ |
| 55 | export function parseServer(siteText: string, apiText?: string): Server | null { |
| 56 | const site = parseOrigin(siteText); |
| 57 | if (!site) return null; |
| 58 | if (apiText && apiText.trim()) { |
| 59 | const api = parseOrigin(apiText); |
| 60 | if (!api) return null; |
| 61 | return { site, api }; |
| 62 | } |
| 63 | return { site, api: apiFor(site) }; |
| 64 | } |
| 65 | |
| 66 | export function isHosted(server: Server): boolean { |
| 67 | return server.site === HOSTED.site && server.api === HOSTED.api; |
| 68 | } |
| 69 | |
| 70 | /** The site's address for a path, such as a page to open in the browser. */ |
| 71 | export function siteUrl(server: Server, path: string): string { |
| 72 | return server.site + (path.startsWith("/") ? path : `/${path}`); |
| 73 | } |
| 74 | |
| 75 | /** The socket address for one of the site's live paths. */ |
| 76 | export function socketUrl(server: Server, path: string): string { |
| 77 | return siteUrl(server, path).replace(/^http/, "ws"); |
| 78 | } |