diff --git a/src/lib/helpers/apiEndpoint.ts b/src/lib/helpers/apiEndpoint.ts new file mode 100644 index 0000000000..c19cefdbf5 --- /dev/null +++ b/src/lib/helpers/apiEndpoint.ts @@ -0,0 +1,85 @@ +import { + REGION_FRA, + REGION_NYC, + REGION_SFO, + REGION_SGP, + REGION_SYD, + REGION_TOR, + SUBDOMAIN_FRA, + SUBDOMAIN_NYC, + SUBDOMAIN_SFO, + SUBDOMAIN_SGP, + SUBDOMAIN_SYD, + SUBDOMAIN_TOR +} from '$lib/constants'; + +/** Ordered list of region DNS prefixes (e.g. `fra.`) for stripping from API hostnames. */ +const REGION_SUBDOMAIN_PREFIXES: readonly string[] = [ + SUBDOMAIN_FRA, + SUBDOMAIN_NYC, + SUBDOMAIN_SYD, + SUBDOMAIN_SFO, + SUBDOMAIN_SGP, + SUBDOMAIN_TOR +]; + +/** + * Removes leading Appwrite Cloud region label(s) from `hostname` (e.g. `fra.`). + * Strips repeatedly so a doubled prefix (`fra.fra.cloud...`) does not become + * `nyc.fra.cloud...` after prepending another region. + */ +export function stripLeadingRegionSubdomain(hostname: string): string { + let host = hostname; + let changed = true; + while (changed) { + changed = false; + for (const prefix of REGION_SUBDOMAIN_PREFIXES) { + if (host.startsWith(prefix)) { + host = host.slice(prefix.length); + changed = true; + break; + } + } + } + return host; +} + +/** Region prefix (e.g. `fra.`) used before the API hostname when multi-region is enabled. */ +export function getRegionSubdomain(region?: string): string { + switch (region) { + case REGION_FRA: + return SUBDOMAIN_FRA; + case REGION_SYD: + return SUBDOMAIN_SYD; + case REGION_NYC: + return SUBDOMAIN_NYC; + case REGION_SFO: + return SUBDOMAIN_SFO; + case REGION_SGP: + return SUBDOMAIN_SGP; + case REGION_TOR: + return SUBDOMAIN_TOR; + default: + return ''; + } +} + +/** + * Builds the `/v1` API base URL (protocol + host + `/v1`). + * When `isMultiRegion` is true, strips any known region prefix from the host, then prepends the requested region. + */ +export function buildRegionalV1Endpoint( + protocol: string, + hostname: string, + region: string | undefined, + isMultiRegion: boolean +): string { + if (!isMultiRegion) { + return `${protocol}//${hostname}/v1`; + } + + const hostWithoutRegion = stripLeadingRegionSubdomain(hostname); + const subdomain = getRegionSubdomain(region); + + return `${protocol}//${subdomain}${hostWithoutRegion}/v1`; +} diff --git a/src/lib/stores/sdk.ts b/src/lib/stores/sdk.ts index f2cd9920cc..d5ace1568e 100644 --- a/src/lib/stores/sdk.ts +++ b/src/lib/stores/sdk.ts @@ -28,21 +28,8 @@ import { Realtime, Organizations } from '@appwrite.io/console'; +import { buildRegionalV1Endpoint } from '$lib/helpers/apiEndpoint'; import { Sources } from '$lib/sdk/sources'; -import { - REGION_FRA, - REGION_NYC, - REGION_SYD, - REGION_SFO, - REGION_SGP, - REGION_TOR, - SUBDOMAIN_FRA, - SUBDOMAIN_NYC, - SUBDOMAIN_SFO, - SUBDOMAIN_SYD, - SUBDOMAIN_SGP, - SUBDOMAIN_TOR -} from '$lib/constants'; import { building } from '$app/environment'; export function getApiEndpoint(region?: string): string { @@ -50,37 +37,10 @@ export function getApiEndpoint(region?: string): string { const url = new URL( VARS.APPWRITE_ENDPOINT ? VARS.APPWRITE_ENDPOINT : globalThis?.location?.toString() ); - const protocol = url.protocol; - const hostname = url.host; // "hostname:port" (or just "hostname" if no port) - - // If instance supports multi-region, add the region subdomain. - let subdomain = isMultiRegionSupported(url) ? getSubdomain(region) : ''; - if (subdomain && hostname.startsWith(subdomain)) { - subdomain = ''; - } - return `${protocol}//${subdomain}${hostname}/v1`; + return buildRegionalV1Endpoint(url.protocol, url.host, region, isMultiRegionSupported(url)); } -const getSubdomain = (region?: string) => { - switch (region) { - case REGION_FRA: - return SUBDOMAIN_FRA; - case REGION_SYD: - return SUBDOMAIN_SYD; - case REGION_NYC: - return SUBDOMAIN_NYC; - case REGION_SFO: - return SUBDOMAIN_SFO; - case REGION_SGP: - return SUBDOMAIN_SGP; - case REGION_TOR: - return SUBDOMAIN_TOR; - default: - return ''; - } -}; - function createConsoleSdk(client: Client) { return { client,