//=========================================== // THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY UNLESS YOU ALSO EDIT THE CORRESPONDING FILE IN packages/template //=========================================== import { InternalSession } from "@hexclave/shared/dist/sessions"; import { AsyncCache } from "@hexclave/shared/dist/utils/caches"; import { isBrowserLike } from "@hexclave/shared/dist/utils/env"; import { HexclaveAssertionError, captureError, concatStacktraces, throwErr } from "@hexclave/shared/dist/utils/errors"; import { createGlobal, getGlobal } from "@hexclave/shared/dist/utils/globals"; import { filterUndefined, omit } from "@hexclave/shared/dist/utils/objects"; import { ReactPromise, runAsynchronously } from "@hexclave/shared/dist/utils/promises"; import { use } from "@hexclave/shared/dist/utils/react"; import { Result } from "@hexclave/shared/dist/utils/results"; import { Store } from "@hexclave/shared/dist/utils/stores"; import { deindent } from "@hexclave/shared/dist/utils/strings"; import { getDefaultApiUrls } from "@hexclave/shared/dist/utils/urls"; import { envVars } from "../../../../generated/env"; import { HandlerUrlOptions, ResolvedHandlerUrls, hexclaveAppInternalsSymbol } from "../../common"; import { resolveHandlerUrls } from "../../url-targets"; export const clientVersion = "STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL"; if (clientVersion.startsWith("STACK_COMPILE_TIME")) { throw new HexclaveAssertionError("Client version was not replaced. Something went wrong during build!"); } const replaceHexclavePortPrefix = (input: T): T => { if (!input) return input; const prefix = envVars.HEXCLAVE_PORT_PREFIX; return prefix ? input.replace(/\$\{NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81\}/g, prefix) as T : input; }; const showMissingConfigAlertInBrowser = (message: string) => { if (!isBrowserLike()) return; const global = globalThis as any; const alertAlreadyShownKey = "__hexclave_missing_config_alert_already_shown"; if (global[alertAlreadyShownKey]) return; global[alertAlreadyShownKey] = true; const alertFn = global.alert; if (typeof alertFn === "function") { alertFn(message); } }; const throwMissingProjectIdError = (): never => { const message = deindent` Welcome to Hexclave! It seems that you haven't provided a project ID. ${envVars.NODE_ENV?.includes("dev") ? deindent` You are running this app in development mode. Make sure that your start command is wrapped in a call to the Hexclave CLI: \`npx @hexclave/cli dev --config-file -- \` Alternatively, you can also connect to a Cloud project's environment variables instead by creating a project on the Hexclave dashboard at https://app.hexclave.com and put its information in the HEXCLAVE_PROJECT_ID environment variable. ` : envVars.NODE_ENV?.includes("prod") ? deindent` It seems you are running a project in production. Please create a project on the Hexclave dashboard at https://app.hexclave.com and put it in the HEXCLAVE_PROJECT_ID environment variable. ` : deindent` - If you are running a development project and would like to connect the local Hexclave dashboard, wrap your start command in: \`npx @hexclave/cli dev --config-file -- \` - If you are running a project in production and would like to connect to Hexclave Cloud, create a project on the Hexclave dashboard at https://app.hexclave.com and put its information in the HEXCLAVE_PROJECT_ID environment variable. `} `; showMissingConfigAlertInBrowser(message); return throwErr(new Error(message)); }; export const createCache = (fetcher: (dependencies: D) => Promise) => { return new AsyncCache>( async (dependencies) => await Result.fromThrowingAsync(async () => await fetcher(dependencies)), {}, ); }; export const createCacheBySession = (fetcher: (session: InternalSession, extraDependencies: D) => Promise ) => { return new AsyncCache<[InternalSession, ...D], Result>( async ([session, ...extraDependencies]) => await Result.fromThrowingAsync(async () => await fetcher(session, extraDependencies)), { onSubscribe: ([session], refresh) => { const handler = session.onInvalidate(() => refresh()); return () => handler.unsubscribe(); }, }, ); }; type AppLike = { [hexclaveAppInternalsSymbol]: { getConstructorOptions: () => any } }; export function resolveConstructorOptions(options: T): T & { inheritsFrom?: undefined } { return { ...options.inheritsFrom?.[hexclaveAppInternalsSymbol].getConstructorOptions() ?? {}, ...filterUndefined(omit(options, ["inheritsFrom"])), }; } export function getUrls(partial: HandlerUrlOptions, options: { projectId: string }): ResolvedHandlerUrls { return resolveHandlerUrls({ urls: partial, projectId: options.projectId, }); } export function getDefaultProjectId() { return envVars.HEXCLAVE_PROJECT_ID || throwMissingProjectIdError(); } export function getDefaultPublishableClientKey() { return envVars.HEXCLAVE_PUBLISHABLE_CLIENT_KEY; } export function getDefaultSecretServerKey() { return envVars.HEXCLAVE_SECRET_SERVER_KEY || throwErr(new Error("No secret server key provided. Please copy your key from the Hexclave dashboard and put it in the HEXCLAVE_SECRET_SERVER_KEY environment variable.")); } export function getDefaultSuperSecretAdminKey() { return envVars.HEXCLAVE_SUPER_SECRET_ADMIN_KEY || throwErr(new Error("No super secret admin key provided. Please copy your key from the Hexclave dashboard and put it in the HEXCLAVE_SUPER_SECRET_ADMIN_KEY environment variable.")); } export function getDefaultExtraRequestHeaders() { return JSON.parse(envVars.HEXCLAVE_EXTRA_REQUEST_HEADERS || '{}'); } /** * Returns the base URL for the Stack API. * * The URL can be specified in several ways, in order of precedence: * 1. Directly through userSpecifiedBaseUrl parameter as string or browser/server object * 2. Through environment variables: * - Browser: NEXT_PUBLIC_HEXCLAVE_API_URL_BROWSER/VITE_HEXCLAVE_API_URL_BROWSER * - Server: HEXCLAVE_API_URL_SERVER * - Default: HEXCLAVE_API_URL * 3. Default base URL if none of the above are specified * * The function also ensures the URL doesn't end with a trailing slash * by removing it if present. * * @param userSpecifiedBaseUrl - Optional URL override as string or {browser, server} object * @returns The configured base URL without trailing slash */ export function getBaseUrl(userSpecifiedBaseUrl: string | { browser: string, server: string } | undefined) { let url; if (userSpecifiedBaseUrl) { if (typeof userSpecifiedBaseUrl === "string") { url = userSpecifiedBaseUrl; } else { if (isBrowserLike()) { url = userSpecifiedBaseUrl.browser; } else { url = userSpecifiedBaseUrl.server; } } } else { if (isBrowserLike()) { url = envVars.HEXCLAVE_API_URL_BROWSER; } else { url = envVars.HEXCLAVE_API_URL_SERVER; } url = url || envVars.HEXCLAVE_API_URL || defaultBaseUrl; } return replaceHexclavePortPrefix(url.endsWith('/') ? url.slice(0, -1) : url); } export const defaultBaseUrl = "https://api.hexclave.com"; export const defaultAnalyticsBaseUrl = "https://r.hexclave.com"; const analyticsBaseUrlsByApiBaseUrl = new Map([ [defaultBaseUrl, defaultAnalyticsBaseUrl], ["https://api.stack-auth.com", "https://r.stack-auth.com"], ]); export function getAnalyticsBaseUrl(regularBaseUrl: string): string { return analyticsBaseUrlsByApiBaseUrl.get(regularBaseUrl) ?? regularBaseUrl; } function fetchBackendUrlsInBackground(primaryBaseUrl: string): void { createGlobal('__stack-fetch-backend-urls-started', () => { runAsynchronously(async () => { try { const res = await fetch(`${primaryBaseUrl}/api/v1/internal/backend-urls`); if (!res.ok) { return; } const data = await res.json(); if (!Array.isArray(data.urls) || !data.urls.every((u: unknown) => typeof u === 'string')) { return; } createGlobal('__stack-fetched-backend-urls', () => data.urls as string[]); } catch (e) { captureError('fetch-backend-urls-in-background', e); } }); return true; }); } export function resolveApiUrls(userExplicitBaseUrl: string | { browser: string, server: string } | undefined): () => string[] { return () => { if (userExplicitBaseUrl != null) { return [getBaseUrl(userExplicitBaseUrl)]; } const primary = getBaseUrl(undefined); // Always try to fetch server-configured URLs (supports custom domains via // STACK_BACKEND_URLS_CONFIG). Hardcoded fallbacks are used as a default // until the background fetch completes. fetchBackendUrlsInBackground(primary); return getGlobal('__stack-fetched-backend-urls') ?? getDefaultApiUrls(primary); }; } export type TokenObject = { accessToken: string | null, refreshToken: string | null, }; export function createEmptyTokenStore() { return new Store({ refreshToken: null, accessToken: null, }); }