import type { AuthBearerBootstrapResult, AuthSessionState, AuthWebSocketTokenResult, ExecutionEnvironmentDescriptor,} from "@t3tools/contracts"; const PAIRING_TOKEN_PARAM = "token"; function readHashParams(url: URL): URLSearchParams { return new URLSearchParams(url.hash.startsWith("#") ? url.hash.slice(1) : url.hash);} function normalizeRemoteBaseUrl(rawValue: string): URL { const trimmed = rawValue.trim(); if (!trimmed) { throw new Error("Enter a backend URL."); } const normalizedInput = /^[a-zA-Z][a-zA-Z\d+-]*:\/\//.test(trimmed) || trimmed.startsWith("//") ? trimmed : `https://${trimmed}`; const url = new URL(normalizedInput); url.pathname = "/"; url.search = ""; url.hash = ""; return url;} function toHttpBaseUrl(url: URL): string { const next = new URL(url.toString()); if (next.protocol === "ws:") { next.protocol = "http:"; } else if (next.protocol === "wss:") { next.protocol = "https:"; } next.pathname = "/"; next.search = ""; next.hash = ""; return next.toString();} function toWsBaseUrl(url: URL): string { const next = new URL(url.toString()); if (next.protocol === "http:") { next.protocol = "ws:"; } else if (next.protocol === "https:") { next.protocol = "wss:"; } next.pathname = "/"; next.search = ""; next.hash = ""; return next.toString();} function remoteEndpointUrl(httpBaseUrl: string, pathname: string): string { const url = new URL(httpBaseUrl); url.pathname = pathname; url.search = ""; url.hash = ""; return url.toString();} async function readRemoteAuthErrorMessage( response: Response, fallbackMessage: string,): Promise<string> { const text = await response.text(); if (!text) { return fallbackMessage; } try { const parsed = JSON.parse(text) as { readonly error?: string }; if (typeof parsed.error === "string" && parsed.error.length > 0) { return parsed.error; } } catch { // Fall back to raw text below. } return text;} async function fetchRemoteJson<T>(input: { readonly httpBaseUrl: string; readonly pathname: string; readonly method?: "GET" | "POST"; readonly bearerToken?: string; readonly body?: unknown;}): Promise<T> { const requestUrl = remoteEndpointUrl(input.httpBaseUrl, input.pathname); let response: Response; try { response = await fetch(requestUrl, { method: input.method ?? "GET", headers: { ...(input.body !== undefined ? { "content-type": "application/json" } : {}), ...(input.bearerToken ? { authorization: `Bearer ${input.bearerToken}` } : {}), }, ...(input.body !== undefined ? { body: JSON.stringify(input.body) } : {}), }); } catch (error) { throw new Error( `Failed to fetch remote auth endpoint ${requestUrl} (${(error as Error).message}).`, { cause: error }, ); } if (!response.ok) { throw new RemoteEnvironmentAuthHttpError( await readRemoteAuthErrorMessage( response, `Remote auth request failed (${response.status}).`, ), response.status, ); } return (await response.json()) as T;} export class RemoteEnvironmentAuthHttpError extends Error { readonly status: number; constructor(message: string, status: number) { super(message); this.name = "RemoteEnvironmentAuthHttpError"; this.status = status; }} export interface ResolvedRemotePairingTarget { readonly credential: string; readonly httpBaseUrl: string; readonly wsBaseUrl: string;} export function getPairingTokenFromUrl(url: URL): string | null { const hashToken = readHashParams(url).get(PAIRING_TOKEN_PARAM)?.trim() ?? ""; if (hashToken.length > 0) { return hashToken; } const searchToken = url.searchParams.get(PAIRING_TOKEN_PARAM)?.trim() ?? ""; return searchToken.length > 0 ? searchToken : null;} export function stripPairingTokenFromUrl(url: URL): URL { const next = new URL(url.toString()); const hashParams = readHashParams(next); if (hashParams.has(PAIRING_TOKEN_PARAM)) { hashParams.delete(PAIRING_TOKEN_PARAM); next.hash = hashParams.toString(); } next.searchParams.delete(PAIRING_TOKEN_PARAM); return next;} export function setPairingTokenOnUrl(url: URL, credential: string): URL { const next = new URL(url.toString()); next.searchParams.delete(PAIRING_TOKEN_PARAM); next.hash = new URLSearchParams([[PAIRING_TOKEN_PARAM, credential]]).toString(); return next;} export function resolveRemotePairingTarget(input: { readonly pairingUrl?: string; readonly host?: string; readonly pairingCode?: string;}): ResolvedRemotePairingTarget { const pairingUrl = input.pairingUrl?.trim() ?? ""; if (pairingUrl.length > 0) { const url = new URL(pairingUrl); const credential = getPairingTokenFromUrl(url) ?? ""; if (!credential) { throw new Error("Pairing URL is missing its token."); } return { credential, httpBaseUrl: toHttpBaseUrl(url), wsBaseUrl: toWsBaseUrl(url), }; } const host = input.host?.trim() ?? ""; const pairingCode = input.pairingCode?.trim() ?? ""; if (!host) { throw new Error("Enter a backend URL."); } if (!pairingCode) { throw new Error("Enter a pairing code."); } const normalizedHost = normalizeRemoteBaseUrl(host); return { credential: pairingCode, httpBaseUrl: toHttpBaseUrl(normalizedHost), wsBaseUrl: toWsBaseUrl(normalizedHost), };} export async function bootstrapRemoteBearerSession(input: { readonly httpBaseUrl: string; readonly credential: string;}): Promise<AuthBearerBootstrapResult> { return fetchRemoteJson<AuthBearerBootstrapResult>({ httpBaseUrl: input.httpBaseUrl, pathname: "/api/auth/bootstrap/bearer", method: "POST", body: { credential: input.credential, }, });} export async function fetchRemoteSessionState(input: { readonly httpBaseUrl: string; readonly bearerToken: string;}): Promise<AuthSessionState> { return fetchRemoteJson<AuthSessionState>({ httpBaseUrl: input.httpBaseUrl, pathname: "/api/auth/session", bearerToken: input.bearerToken, });} export async function fetchRemoteEnvironmentDescriptor(input: { readonly httpBaseUrl: string;}): Promise<ExecutionEnvironmentDescriptor> { return fetchRemoteJson<ExecutionEnvironmentDescriptor>({ httpBaseUrl: input.httpBaseUrl, pathname: "/.well-known/t3/environment", });} export async function issueRemoteWebSocketToken(input: { readonly httpBaseUrl: string; readonly bearerToken: string;}): Promise<AuthWebSocketTokenResult> { return fetchRemoteJson<AuthWebSocketTokenResult>({ httpBaseUrl: input.httpBaseUrl, pathname: "/api/auth/ws-token", method: "POST", bearerToken: input.bearerToken, });} export async function resolveRemoteWebSocketConnectionUrl(input: { readonly wsBaseUrl: string; readonly httpBaseUrl: string; readonly bearerToken: string;}): Promise<string> { const issued = await issueRemoteWebSocketToken({ httpBaseUrl: input.httpBaseUrl, bearerToken: input.bearerToken, }); const url = new URL(input.wsBaseUrl); url.pathname = "/ws"; url.searchParams.set("wsToken", issued.token); return url.toString();}
import type {
AuthBearerBootstrapResult,
AuthSessionState,
AuthWebSocketTokenResult,
ExecutionEnvironmentDescriptor,
} from "@t3tools/contracts";
const PAIRING_TOKEN_PARAM = "token";
function readHashParams(url: URL): URLSearchParams {
return new URLSearchParams(url.hash.startsWith("#") ? url.hash.slice(1) : url.hash);
}
function normalizeRemoteBaseUrl(rawValue: string): URL {
const trimmed = rawValue.trim();
if (!trimmed) {
throw new Error("Enter a backend URL.");
}
const normalizedInput =
/^[a-zA-Z][a-zA-Z\d+-]*:\/\//.test(trimmed) || trimmed.startsWith("//")
? trimmed
: `https://${trimmed}`;
const url = new URL(normalizedInput);
url.pathname = "/";
url.search = "";
url.hash = "";
return url;
}
function toHttpBaseUrl(url: URL): string {
const next = new URL(url.toString());
if (next.protocol === "ws:") {
next.protocol = "http:";
} else if (next.protocol === "wss:") {
next.protocol = "https:";
}
next.pathname = "/";
next.search = "";
next.hash = "";
return next.toString();
}
function toWsBaseUrl(url: URL): string {
const next = new URL(url.toString());
if (next.protocol === "http:") {
next.protocol = "ws:";
} else if (next.protocol === "https:") {
next.protocol = "wss:";
}
next.pathname = "/";
next.search = "";
next.hash = "";
return next.toString();
}
function remoteEndpointUrl(httpBaseUrl: string, pathname: string): string {
const url = new URL(httpBaseUrl);
url.pathname = pathname;
url.search = "";
url.hash = "";
return url.toString();
}
async function readRemoteAuthErrorMessage(
response: Response,
fallbackMessage: string,
): Promise<string> {
const text = await response.text();
if (!text) {
return fallbackMessage;
}
try {
const parsed = JSON.parse(text) as { readonly error?: string };
if (typeof parsed.error === "string" && parsed.error.length > 0) {
return parsed.error;
}
} catch {
// Fall back to raw text below.
}
return text;
}
async function fetchRemoteJson<T>(input: {
readonly httpBaseUrl: string;
readonly pathname: string;
readonly method?: "GET" | "POST";
readonly bearerToken?: string;
readonly body?: unknown;
}): Promise<T> {
const requestUrl = remoteEndpointUrl(input.httpBaseUrl, input.pathname);
let response: Response;
try {
response = await fetch(requestUrl, {
method: input.method ?? "GET",
headers: {
...(input.body !== undefined ? { "content-type": "application/json" } : {}),
...(input.bearerToken ? { authorization: `Bearer ${input.bearerToken}` } : {}),
},
...(input.body !== undefined ? { body: JSON.stringify(input.body) } : {}),
});
} catch (error) {
throw new Error(
`Failed to fetch remote auth endpoint ${requestUrl} (${(error as Error).message}).`,
{ cause: error },
);
}
if (!response.ok) {
throw new RemoteEnvironmentAuthHttpError(
await readRemoteAuthErrorMessage(
response,
`Remote auth request failed (${response.status}).`,
),
response.status,
);
}
return (await response.json()) as T;
}
export class RemoteEnvironmentAuthHttpError extends Error {
readonly status: number;
constructor(message: string, status: number) {
super(message);
this.name = "RemoteEnvironmentAuthHttpError";
this.status = status;
}
}
export interface ResolvedRemotePairingTarget {
readonly credential: string;
readonly httpBaseUrl: string;
readonly wsBaseUrl: string;
}
export function getPairingTokenFromUrl(url: URL): string | null {
const hashToken = readHashParams(url).get(PAIRING_TOKEN_PARAM)?.trim() ?? "";
if (hashToken.length > 0) {
return hashToken;
}
const searchToken = url.searchParams.get(PAIRING_TOKEN_PARAM)?.trim() ?? "";
return searchToken.length > 0 ? searchToken : null;
}
export function stripPairingTokenFromUrl(url: URL): URL {
const next = new URL(url.toString());
const hashParams = readHashParams(next);
if (hashParams.has(PAIRING_TOKEN_PARAM)) {
hashParams.delete(PAIRING_TOKEN_PARAM);
next.hash = hashParams.toString();
}
next.searchParams.delete(PAIRING_TOKEN_PARAM);
return next;
}
export function setPairingTokenOnUrl(url: URL, credential: string): URL {
const next = new URL(url.toString());
next.searchParams.delete(PAIRING_TOKEN_PARAM);
next.hash = new URLSearchParams([[PAIRING_TOKEN_PARAM, credential]]).toString();
return next;
}
export function resolveRemotePairingTarget(input: {
readonly pairingUrl?: string;
readonly host?: string;
readonly pairingCode?: string;
}): ResolvedRemotePairingTarget {
const pairingUrl = input.pairingUrl?.trim() ?? "";
if (pairingUrl.length > 0) {
const url = new URL(pairingUrl);
const credential = getPairingTokenFromUrl(url) ?? "";
if (!credential) {
throw new Error("Pairing URL is missing its token.");
}
return {
credential,
httpBaseUrl: toHttpBaseUrl(url),
wsBaseUrl: toWsBaseUrl(url),
};
}
const host = input.host?.trim() ?? "";
const pairingCode = input.pairingCode?.trim() ?? "";
if (!host) {
throw new Error("Enter a backend URL.");
}
if (!pairingCode) {
throw new Error("Enter a pairing code.");
}
const normalizedHost = normalizeRemoteBaseUrl(host);
return {
credential: pairingCode,
httpBaseUrl: toHttpBaseUrl(normalizedHost),
wsBaseUrl: toWsBaseUrl(normalizedHost),
};
}
export async function bootstrapRemoteBearerSession(input: {
readonly httpBaseUrl: string;
readonly credential: string;
}): Promise<AuthBearerBootstrapResult> {
return fetchRemoteJson<AuthBearerBootstrapResult>({
httpBaseUrl: input.httpBaseUrl,
pathname: "/api/auth/bootstrap/bearer",
method: "POST",
body: {
credential: input.credential,
},
});
}
export async function fetchRemoteSessionState(input: {
readonly httpBaseUrl: string;
readonly bearerToken: string;
}): Promise<AuthSessionState> {
return fetchRemoteJson<AuthSessionState>({
httpBaseUrl: input.httpBaseUrl,
pathname: "/api/auth/session",
bearerToken: input.bearerToken,
});
}
export async function fetchRemoteEnvironmentDescriptor(input: {
readonly httpBaseUrl: string;
}): Promise<ExecutionEnvironmentDescriptor> {
return fetchRemoteJson<ExecutionEnvironmentDescriptor>({
httpBaseUrl: input.httpBaseUrl,
pathname: "/.well-known/t3/environment",
});
}
export async function issueRemoteWebSocketToken(input: {
readonly httpBaseUrl: string;
readonly bearerToken: string;
}): Promise<AuthWebSocketTokenResult> {
return fetchRemoteJson<AuthWebSocketTokenResult>({
httpBaseUrl: input.httpBaseUrl,
pathname: "/api/auth/ws-token",
method: "POST",
bearerToken: input.bearerToken,
});
}
export async function resolveRemoteWebSocketConnectionUrl(input: {
readonly wsBaseUrl: string;
readonly httpBaseUrl: string;
readonly bearerToken: string;
}): Promise<string> {
const issued = await issueRemoteWebSocketToken({
httpBaseUrl: input.httpBaseUrl,
bearerToken: input.bearerToken,
});
const url = new URL(input.wsBaseUrl);
url.pathname = "/ws";
url.searchParams.set("wsToken", issued.token);
return url.toString();
}