131 lines · 4.1 KB · TypeScript
RawDownload
import type {  AdvertisedEndpoint,  AdvertisedEndpointHostedHttpsCompatibility,  AdvertisedEndpointProvider,  AdvertisedEndpointReachability,  AdvertisedEndpointSource,  AdvertisedEndpointStatus,} from "@t3tools/contracts";import * as Schema from "effect/Schema"; import { getUrlDiagnostics } from "./urlDiagnostics.ts"; const AdvertisedEndpointInputDiagnosticFields = {  inputLength: Schema.Number,  inputProtocol: Schema.optional(Schema.String),  inputHostname: Schema.optional(Schema.String),}; function advertisedEndpointInputDiagnostics(input: string) {  const diagnostics = getUrlDiagnostics(input);  return {    inputLength: diagnostics.inputLength,    ...(diagnostics.protocol === undefined ? {} : { inputProtocol: diagnostics.protocol }),    ...(diagnostics.hostname === undefined ? {} : { inputHostname: diagnostics.hostname }),  };} export class AdvertisedEndpointUrlParseError extends Schema.TaggedErrorClass<AdvertisedEndpointUrlParseError>()(  "AdvertisedEndpointUrlParseError",  {    ...AdvertisedEndpointInputDiagnosticFields,    cause: Schema.Defect(),  },) {  override get message(): string {    return "Invalid advertised endpoint URL.";  }} export class AdvertisedEndpointProtocolError extends Schema.TaggedErrorClass<AdvertisedEndpointProtocolError>()(  "AdvertisedEndpointProtocolError",  {    ...AdvertisedEndpointInputDiagnosticFields,    protocol: Schema.String,  },) {  override get message(): string {    return `Endpoint must use HTTP or HTTPS. Received ${this.protocol}`;  }} export interface CreateAdvertisedEndpointInput {  readonly id: string;  readonly label: string;  readonly provider: AdvertisedEndpointProvider;  readonly httpBaseUrl: string;  readonly reachability: AdvertisedEndpointReachability;  readonly hostedHttpsCompatibility?: AdvertisedEndpointHostedHttpsCompatibility;  readonly desktopCompatibility?: "compatible" | "unknown";  readonly source: AdvertisedEndpointSource;  readonly status?: AdvertisedEndpointStatus;  readonly isDefault?: boolean;  readonly description?: string;} export function normalizeHttpBaseUrl(rawValue: string): string {  let url: URL;  try {    url = new URL(rawValue);  } catch (cause) {    throw new AdvertisedEndpointUrlParseError({      ...advertisedEndpointInputDiagnostics(rawValue),      cause,    });  }  if (url.protocol === "ws:") {    url.protocol = "http:";  } else if (url.protocol === "wss:") {    url.protocol = "https:";  }   if (url.protocol !== "http:" && url.protocol !== "https:") {    throw new AdvertisedEndpointProtocolError({      ...advertisedEndpointInputDiagnostics(rawValue),      protocol: url.protocol,    });  }   url.pathname = "/";  url.search = "";  url.hash = "";  return url.toString();} export function deriveWsBaseUrl(httpBaseUrl: string): string {  const url = new URL(normalizeHttpBaseUrl(httpBaseUrl));  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";  return url.toString();} export function classifyHostedHttpsCompatibility(  httpBaseUrl: string,  fallback: AdvertisedEndpointHostedHttpsCompatibility = "unknown",): AdvertisedEndpointHostedHttpsCompatibility {  const url = new URL(normalizeHttpBaseUrl(httpBaseUrl));  if (url.protocol === "http:") {    return "mixed-content-blocked";  }  return fallback === "mixed-content-blocked" ? "unknown" : fallback;} export function createAdvertisedEndpoint(input: CreateAdvertisedEndpointInput): AdvertisedEndpoint {  const httpBaseUrl = normalizeHttpBaseUrl(input.httpBaseUrl);  return {    id: input.id,    label: input.label,    provider: input.provider,    httpBaseUrl,    wsBaseUrl: deriveWsBaseUrl(httpBaseUrl),    reachability: input.reachability,    compatibility: {      hostedHttpsApp:        input.hostedHttpsCompatibility ?? classifyHostedHttpsCompatibility(httpBaseUrl),      desktopApp: input.desktopCompatibility ?? "compatible",    },    source: input.source,    status: input.status ?? "available",    ...(input.isDefault === undefined ? {} : { isDefault: input.isDefault }),    ...(input.description === undefined ? {} : { description: input.description }),  };}