1import { DEFAULT_HOSTED_APP_URL } from "@t3tools/shared/connectAuth";
2
3import { getPairingTokenFromUrl, setPairingTokenOnUrl } from "./pairingUrl";
4
5export interface HostedPairingRequest {
6 readonly host: string;
7 readonly token: string;
8 readonly label: string;
9}
10
11export type HostedAppChannel = "latest" | "nightly";
12
13/** The hosted app origin shared links point at. */
14export function configuredHostedAppUrl(): string {
15 return import.meta.env.VITE_HOSTED_APP_URL?.trim() || DEFAULT_HOSTED_APP_URL;
16}
17
18function configuredBackendUrl(): string {
19 return import.meta.env.VITE_HTTP_URL?.trim() || import.meta.env.VITE_WS_URL?.trim() || "";
20}
21
22function configuredHostedAppChannel(): HostedAppChannel | null {
23 const channel = import.meta.env.VITE_HOSTED_APP_CHANNEL?.trim().toLowerCase();
24 return channel === "latest" || channel === "nightly" ? channel : null;
25}
26
27function originFromUrl(value: string): string | null {
28 try {
29 return new URL(value).origin;
30 } catch {
31 return null;
32 }
33}
34
35export function isHostedStaticApp(url?: URL): boolean {
36 if (configuredBackendUrl()) {
37 return false;
38 }
39
40 if (configuredHostedAppChannel()) {
41 return true;
42 }
43
44 // No window, or a window without a location (tests, static render), means
45 // no origin to be hosted at.
46 if (url === undefined && (typeof window === "undefined" || window.location === undefined)) {
47 return false;
48 }
49
50 const hostedOrigin = originFromUrl(configuredHostedAppUrl());
51 return hostedOrigin !== null && (url ?? new URL(window.location.href)).origin === hostedOrigin;
52}
53
54export function readHostedPairingRequest(url: URL = new URL(window.location.href)) {
55 const host = url.searchParams.get("host")?.trim() ?? "";
56 const token = getPairingTokenFromUrl(url)?.trim() ?? "";
57 const label = url.searchParams.get("label")?.trim() ?? "";
58
59 if (!host || !token) {
60 return null;
61 }
62
63 return {
64 host,
65 token,
66 label,
67 } satisfies HostedPairingRequest;
68}
69
70export function hasHostedPairingRequest(url: URL = new URL(window.location.href)): boolean {
71 return readHostedPairingRequest(url) !== null;
72}
73
74export function buildHostedPairingUrl(input: {
75 readonly host: string;
76 readonly token: string;
77 readonly label?: string | null;
78}): string {
79 const url = new URL("/pair", configuredHostedAppUrl());
80 url.searchParams.set("host", input.host);
81
82 const label = input.label?.trim();
83 if (label) {
84 url.searchParams.set("label", label);
85 }
86
87 return setPairingTokenOnUrl(url, input.token).toString();
88}
89
90export function buildHostedChannelSelectionUrl(input: {
91 readonly channel: HostedAppChannel;
92}): string {
93 const url = new URL("/__t3code/channel", configuredHostedAppUrl());
94 url.searchParams.set("channel", input.channel);
95 return url.toString();
96}