| 1 | import { DISCOVERY_PATH, parseDiscovery } from "@g1t/contracts/discovery"; |
| 2 | |
| 3 | import { type Server, parseOrigin, parseServer } from "./server"; |
| 4 | |
| 5 | /** |
| 6 | * A g1t from the address someone typed, or a `g1t://connect` link gave: |
| 7 | * its site, and its API as the site says (`/.well-known/g1t.json`, |
| 8 | * packages/contracts src/discovery.ts), so a self-hosted g1t's API can be |
| 9 | * anywhere. A g1t from before the phone app has no such file: its API is |
| 10 | * then taken to be `api.` in front of the site, and Chat may need a |
| 11 | * website-enabled access token there. |
| 12 | */ |
| 13 | export type Found = { server: Server; known: boolean; push: boolean; chat: boolean }; |
| 14 | |
| 15 | export async function discover(siteText: string, apiText?: string): Promise<Found> { |
| 16 | const site = parseOrigin(siteText); |
| 17 | if (!site) throw new Error("That is not a web address. It looks like https://g1t.example.com."); |
| 18 | if (apiText?.trim()) { |
| 19 | const given = parseServer(site, apiText); |
| 20 | if (!given) throw new Error("The API's address is not a web address."); |
| 21 | return { server: given, known: false, push: true, chat: true }; |
| 22 | } |
| 23 | const controller = new AbortController(); |
| 24 | const timer = setTimeout(() => controller.abort(), 6000); |
| 25 | try { |
| 26 | const response = await fetch(site + DISCOVERY_PATH, { headers: { accept: "application/json" }, signal: controller.signal }); |
| 27 | const found = response.ok ? parseDiscovery(await response.json()) : null; |
| 28 | if (found) { |
| 29 | return { server: { site: found.site, api: found.api, push: found.phone.push }, known: true, push: found.phone.push, chat: found.phone.chat }; |
| 30 | } |
| 31 | } catch { |
| 32 | // Unreachable, or not g1t: said below. |
| 33 | } finally { |
| 34 | clearTimeout(timer); |
| 35 | } |
| 36 | const guessed = parseServer(site); |
| 37 | if (!guessed) throw new Error("That is not a web address."); |
| 38 | // Reachable at all? A wrong address should say so now, not at sign-in. |
| 39 | try { |
| 40 | const metadata = await fetch(`${guessed.api}/.well-known/oauth-authorization-server`, { headers: { accept: "application/json" } }); |
| 41 | if (!metadata.ok) throw new Error(String(metadata.status)); |
| 42 | } catch { |
| 43 | throw new Error(`${new URL(site).host} did not answer as a g1t. Check the address, or give its API's address too.`); |
| 44 | } |
| 45 | return { server: guessed, known: false, push: true, chat: false }; |
| 46 | } |