client sdk local emulator

This commit is contained in:
Bilal Godil 2026-03-12 20:05:13 -07:00
parent 612cb71a28
commit 01ca85027c
11 changed files with 169 additions and 29 deletions

View File

@ -141,7 +141,7 @@ async function getOrCreateCredentials(projectId: string) {
},
});
if (!keySet.secretServerKey || !keySet.superSecretAdminKey) {
if (!keySet.publishableClientKey || !keySet.secretServerKey || !keySet.superSecretAdminKey) {
throw new StackAssertionError("Local emulator key set is missing required keys.", {
projectId,
keySetId: keySet.id,
@ -149,6 +149,7 @@ async function getOrCreateCredentials(projectId: string) {
}
return {
publishableClientKey: keySet.publishableClientKey,
secretServerKey: keySet.secretServerKey,
superSecretAdminKey: keySet.superSecretAdminKey,
};
@ -178,6 +179,7 @@ export const POST = createSmartRouteHandler({
bodyType: yupString().oneOf(["json"]).defined(),
body: yupObject({
project_id: yupString().defined(),
publishable_client_key: yupString().defined(),
secret_server_key: yupString().defined(),
super_secret_admin_key: yupString().defined(),
branch_config_override_string: yupString().defined(),
@ -222,6 +224,7 @@ export const POST = createSmartRouteHandler({
bodyType: "json" as const,
body: {
project_id: projectId,
publishable_client_key: credentials.publishableClientKey,
secret_server_key: credentials.secretServerKey,
super_secret_admin_key: credentials.superSecretAdminKey,
branch_config_override_string: JSON.stringify(fileConfig),

View File

@ -6,6 +6,10 @@ import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
import { StatusError } from "@stackframe/stack-shared/dist/utils/errors";
import { globalPrismaClient } from "@/prisma-client";
export const LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY = "local-emulator-publishable-client-key";
export const LOCAL_EMULATOR_INTERNAL_SECRET_SERVER_KEY = "local-emulator-secret-server-key";
export const LOCAL_EMULATOR_INTERNAL_SUPER_SECRET_ADMIN_KEY = "local-emulator-super-secret-admin-key";
export const LOCAL_EMULATOR_ADMIN_USER_ID = "63abbc96-5329-454a-ba56-e0460173c6c1";
export const LOCAL_EMULATOR_OWNER_TEAM_ID = "5a0c858b-d9e9-49d4-9943-8ce385d86428";
export const LOCAL_EMULATOR_ADMIN_EMAIL = "local-emulator@stack-auth.com";

View File

@ -11,9 +11,15 @@ fi
# ============= ENV VARS =============
export STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=${STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY:-$(openssl rand -base64 32)}
export STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY:-$(openssl rand -base64 32)}
export STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY:-$(openssl rand -base64 32)}
if [ "$NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR" = "true" ]; then
export STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=${STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY:-local-emulator-publishable-client-key}
export STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY:-local-emulator-secret-server-key}
export STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY:-local-emulator-super-secret-admin-key}
else
export STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY=${STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY:-$(openssl rand -base64 32)}
export STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY=${STACK_SEED_INTERNAL_PROJECT_SECRET_SERVER_KEY:-$(openssl rand -base64 32)}
export STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY=${STACK_SEED_INTERNAL_PROJECT_SUPER_SECRET_ADMIN_KEY:-$(openssl rand -base64 32)}
fi
export NEXT_PUBLIC_STACK_PROJECT_ID=internal
export NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=${STACK_SEED_INTERNAL_PROJECT_PUBLISHABLE_CLIENT_KEY}

View File

@ -57,17 +57,26 @@ export type InternalApiKeyCreateCrudResponse = InternalApiKeysCrud["Admin"]["Rea
export class StackAdminInterface extends StackServerInterface {
protected _superSecretAdminKeyOverride?: string;
constructor(public readonly options: AdminAuthApplicationOptions) {
super(options);
}
override _updateEmulatorCredentials(opts: { projectId?: string, publishableClientKey?: string, secretServerKey?: string, superSecretAdminKey?: string }) {
super._updateEmulatorCredentials(opts);
if (opts.superSecretAdminKey) {
this._superSecretAdminKeyOverride = opts.superSecretAdminKey;
}
}
public async sendAdminRequest(path: string, options: RequestInit, session: InternalSession | null, requestType: "admin" = "admin") {
return await this.sendServerRequest(
path,
{
...options,
headers: {
"x-stack-super-secret-admin-key": "superSecretAdminKey" in this.options ? this.options.superSecretAdminKey : "",
"x-stack-super-secret-admin-key": this._superSecretAdminKeyOverride ?? ("superSecretAdminKey" in this.options ? this.options.superSecretAdminKey : ""),
...options.headers,
},
},

View File

@ -50,12 +50,24 @@ export type ClientInterfaceOptions = {
export class StackClientInterface {
private pendingNetworkDiagnostics?: ReturnType<StackClientInterface["_runNetworkDiagnosticsInner"]>;
protected _projectIdOverride?: string;
protected _publishableClientKeyOverride?: string;
constructor(public readonly options: ClientInterfaceOptions) {
// nothing here
}
get projectId() {
return this.options.projectId;
return this._projectIdOverride ?? this.options.projectId;
}
_updateEmulatorCredentials(opts: { projectId?: string, publishableClientKey?: string }) {
if (opts.projectId) {
this._projectIdOverride = opts.projectId;
}
if (opts.publishableClientKey) {
this._publishableClientKeyOverride = opts.publishableClientKey;
}
}
getApiUrl() {
@ -397,7 +409,9 @@ export class StackClientInterface {
"X-Stack-Refresh-Token": tokenObj.refreshToken.token,
} : {}),
"X-Stack-Allow-Anonymous-User": "true",
...("publishableClientKey" in this.options && this.options.publishableClientKey ? {
...(this._publishableClientKeyOverride ? {
"X-Stack-Publishable-Client-Key": this._publishableClientKeyOverride,
} : "publishableClientKey" in this.options && this.options.publishableClientKey ? {
"X-Stack-Publishable-Client-Key": this.options.publishableClientKey,
} : {}),
...(adminTokenObj ? {

View File

@ -39,17 +39,26 @@ export type ServerAuthApplicationOptions = (
);
export class StackServerInterface extends StackClientInterface {
protected _secretServerKeyOverride?: string;
constructor(public override options: ServerAuthApplicationOptions) {
super(options);
}
override _updateEmulatorCredentials(opts: { projectId?: string, publishableClientKey?: string, secretServerKey?: string }) {
super._updateEmulatorCredentials(opts);
if (opts.secretServerKey) {
this._secretServerKeyOverride = opts.secretServerKey;
}
}
protected async sendServerRequest(path: string, options: RequestInit, session: InternalSession | null, requestType: "server" | "admin" = "server") {
return await this.sendClientRequest(
path,
{
...options,
headers: {
"x-stack-secret-server-key": "secretServerKey" in this.options ? this.options.secretServerKey : "",
"x-stack-secret-server-key": this._secretServerKeyOverride ?? ("secretServerKey" in this.options ? this.options.secretServerKey : ""),
...options.headers,
},
},

View File

@ -22,7 +22,7 @@ import { AdminProjectPermission, AdminProjectPermissionDefinition, AdminProjectP
import { AdminOwnedProject, AdminProject, AdminProjectUpdateOptions, PushConfigOptions, adminProjectUpdateOptionsToCrud } from "../../projects";
import type { AdminSessionReplay, AdminSessionReplayChunk, ListSessionReplayChunksOptions, ListSessionReplayChunksResult, ListSessionReplaysOptions, ListSessionReplaysResult, SessionReplayAllEventsResult } from "../../session-replays";
import { ManagedEmailProviderListItem, ManagedEmailProviderSetupResult, ManagedEmailProviderStatus, EmailOutboxUpdateOptions, StackAdminApp, StackAdminAppConstructorOptions } from "../interfaces/admin-app";
import { clientVersion, createCache, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getDefaultSecretServerKey, getDefaultSuperSecretAdminKey, resolveConstructorOptions } from "./common";
import { LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY, clientVersion, createCache, fetchEmulatorProjectCredentials, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getDefaultSecretServerKey, getDefaultSuperSecretAdminKey, getLocalEmulatorConfigFilePath, resolveConstructorOptions } from "./common";
import { _StackServerAppImplIncomplete } from "./server-app-impl";
import { CompleteConfig, EnvironmentConfigOverrideOverride } from "@stackframe/stack-shared/dist/config/schema";
@ -128,24 +128,40 @@ export class _StackAdminAppImplIncomplete<HasTokenStore extends boolean, Project
constructor(options: StackAdminAppConstructorOptions<HasTokenStore, ProjectId>, extraOptions?: { uniqueIdentifier?: string, checkString?: string, interface?: StackAdminInterface }) {
const resolvedOptions = resolveConstructorOptions(options);
const publishableClientKey = resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey();
const emulatorConfigFilePath = getLocalEmulatorConfigFilePath(resolvedOptions.localEmulatorConfigFilePath);
const isEmulator = !!emulatorConfigFilePath;
const publishableClientKey = resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey() ?? (isEmulator ? LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY : undefined);
super(resolvedOptions, {
...extraOptions,
interface: extraOptions?.interface ?? new StackAdminInterface({
getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl),
projectId: resolvedOptions.projectId ?? getDefaultProjectId(),
getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl, { isEmulator }),
projectId: resolvedOptions.projectId ?? getDefaultProjectId({ isEmulator }),
extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? getDefaultExtraRequestHeaders(),
clientVersion,
...resolvedOptions.projectOwnerSession ? {
projectOwnerSession: resolvedOptions.projectOwnerSession,
} : {
...(publishableClientKey ? { publishableClientKey } : {}),
secretServerKey: resolvedOptions.secretServerKey ?? getDefaultSecretServerKey(),
superSecretAdminKey: resolvedOptions.superSecretAdminKey ?? getDefaultSuperSecretAdminKey(),
secretServerKey: resolvedOptions.secretServerKey ?? getDefaultSecretServerKey({ isEmulator }),
superSecretAdminKey: resolvedOptions.superSecretAdminKey ?? getDefaultSuperSecretAdminKey({ isEmulator }),
},
}),
});
if (isEmulator && !extraOptions?.interface) {
const iface = this._interface;
this._emulatorInitPromise = fetchEmulatorProjectCredentials(emulatorConfigFilePath!).then((data) => {
iface._updateEmulatorCredentials({
projectId: data.project_id,
publishableClientKey: data.publishable_client_key,
secretServerKey: data.secret_server_key,
superSecretAdminKey: data.super_secret_admin_key,
});
});
}
}
_adminConfigFromCrud(data: { config_string: string }): CompleteConfig {

View File

@ -52,7 +52,7 @@ import { EditableTeamMemberProfile, ReceivedTeamInvitation, SentTeamInvitation,
import { ActiveSession, Auth, BaseUser, CurrentUser, InternalUserExtra, OAuthProvider, ProjectCurrentUser, SyncedPartialUser, TokenPartialUser, UserExtra, UserUpdateOptions, userUpdateOptionsToCrud, withUserDestructureGuard } from "../../users";
import { StackClientApp, StackClientAppConstructorOptions, StackClientAppJson } from "../interfaces/client-app";
import { _StackAdminAppImplIncomplete } from "./admin-app-impl";
import { TokenObject, clientVersion, createCache, createCacheBySession, createEmptyTokenStore, getAnalyticsBaseUrl, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getUrls, resolveConstructorOptions } from "./common";
import { LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY, TokenObject, clientVersion, createCache, createCacheBySession, createEmptyTokenStore, fetchEmulatorProjectCredentials, getAnalyticsBaseUrl, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getLocalEmulatorConfigFilePath, getUrls, resolveConstructorOptions } from "./common";
import { EventTracker } from "./event-tracker";
import { AnalyticsOptions, SessionRecorder, analyticsOptionsFromJson, analyticsOptionsToJson } from "./session-replay";
@ -101,6 +101,7 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
private _sessionRecorder: SessionRecorder | null = null;
private _eventTracker: EventTracker | null = null;
protected _emulatorInitPromise: Promise<void> | null = null;
private __DEMO_ENABLE_SLIGHT_FETCH_DELAY = false;
private readonly _ownedAdminApps = new DependenciesMap<[InternalSession, string], _StackAdminAppImplIncomplete<false, string>>();
@ -499,29 +500,43 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
this._options = resolvedOptions;
this._extraOptions = extraOptions;
const projectId = resolvedOptions.projectId ?? getDefaultProjectId();
const emulatorConfigFilePath = getLocalEmulatorConfigFilePath(resolvedOptions.localEmulatorConfigFilePath);
const isEmulator = !!emulatorConfigFilePath;
const projectId = resolvedOptions.projectId ?? getDefaultProjectId({ isEmulator });
if (projectId !== "internal" && !(projectId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i))) {
throw new Error(`Invalid project ID: ${projectId}. Project IDs must be UUIDs. Please check your environment variables and/or your StackApp.`);
}
const publishableClientKey = resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey();
const publishableClientKey = resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey() ?? (isEmulator ? LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY : undefined);
if (extraOptions && extraOptions.interface) {
this._interface = extraOptions.interface;
} else {
this._interface = new StackClientInterface({
getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl),
getAnalyticsBaseUrl: () => getAnalyticsBaseUrl(getBaseUrl(resolvedOptions.baseUrl)),
getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl, { isEmulator }),
getAnalyticsBaseUrl: () => getAnalyticsBaseUrl(getBaseUrl(resolvedOptions.baseUrl, { isEmulator })),
extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? getDefaultExtraRequestHeaders(),
projectId,
clientVersion,
...(publishableClientKey != null ? { publishableClientKey } : {}),
prepareRequest: async () => {
if (this._emulatorInitPromise) await this._emulatorInitPromise;
await cookies?.(); // THIS_LINE_PLATFORM next
}
});
}
if (isEmulator && !(extraOptions && extraOptions.interface)) {
const iface = this._interface;
this._emulatorInitPromise = fetchEmulatorProjectCredentials(emulatorConfigFilePath!).then((data) => {
iface._updateEmulatorCredentials({
projectId: data.project_id,
publishableClientKey: data.publishable_client_key,
});
});
}
this._tokenStoreInit = resolvedOptions.tokenStore;
this._redirectMethod = resolvedOptions.redirectMethod || "none";
this._redirectMethod = resolvedOptions.redirectMethod || "nextjs"; // THIS_LINE_PLATFORM next

View File

@ -81,7 +81,44 @@ export function getUrls(partial: Partial<HandlerUrls>): HandlerUrls {
};
}
export function getDefaultProjectId() {
export const localEmulatorBaseUrl = "http://localhost:9999";
export const LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY = "local-emulator-publishable-client-key";
export const LOCAL_EMULATOR_INTERNAL_SECRET_SERVER_KEY = "local-emulator-secret-server-key";
export const LOCAL_EMULATOR_INTERNAL_SUPER_SECRET_ADMIN_KEY = "local-emulator-super-secret-admin-key";
export function getLocalEmulatorConfigFilePath(explicitOption?: string): string | undefined {
return explicitOption || process.env.NEXT_PUBLIC_STACK_LOCAL_EMULATOR_CONFIG_FILE_PATH || undefined;
}
export function fetchEmulatorProjectCredentials(emulatorConfigFilePath: string): Promise<{
project_id: string,
publishable_client_key: string,
secret_server_key: string,
super_secret_admin_key: string,
}> {
return (async () => {
const res = await fetch(`${localEmulatorBaseUrl}/api/v1/internal/local-emulator/project`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Stack-Project-Id": "internal",
"X-Stack-Access-Type": "client",
"X-Stack-Publishable-Client-Key": LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY,
},
body: JSON.stringify({ absolute_file_path: emulatorConfigFilePath }),
});
if (!res.ok) {
throw new Error(`Failed to initialize local emulator: ${res.status} ${await res.text()}`);
}
return await res.json();
})();
}
export function getDefaultProjectId(options?: { isEmulator?: boolean }) {
if (options?.isEmulator) {
return process.env.NEXT_PUBLIC_STACK_PROJECT_ID || process.env.STACK_PROJECT_ID || "internal";
}
return process.env.NEXT_PUBLIC_STACK_PROJECT_ID || process.env.STACK_PROJECT_ID || throwErr(new Error("Welcome to Stack Auth! It seems that you haven't provided a project ID. Please create a project on the Stack dashboard at https://app.stack-auth.com and put it in the NEXT_PUBLIC_STACK_PROJECT_ID environment variable."));
}
@ -89,11 +126,17 @@ export function getDefaultPublishableClientKey() {
return process.env.NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY || process.env.STACK_PUBLISHABLE_CLIENT_KEY;
}
export function getDefaultSecretServerKey() {
export function getDefaultSecretServerKey(options?: { isEmulator?: boolean }) {
if (options?.isEmulator) {
return process.env.STACK_SECRET_SERVER_KEY || LOCAL_EMULATOR_INTERNAL_SECRET_SERVER_KEY;
}
return process.env.STACK_SECRET_SERVER_KEY || throwErr(new Error("No secret server key provided. Please copy your key from the Stack dashboard and put it in the STACK_SECRET_SERVER_KEY environment variable."));
}
export function getDefaultSuperSecretAdminKey() {
export function getDefaultSuperSecretAdminKey(options?: { isEmulator?: boolean }) {
if (options?.isEmulator) {
return process.env.STACK_SUPER_SECRET_ADMIN_KEY || LOCAL_EMULATOR_INTERNAL_SUPER_SECRET_ADMIN_KEY;
}
return process.env.STACK_SUPER_SECRET_ADMIN_KEY || throwErr(new Error("No super secret admin key provided. Please copy your key from the Stack dashboard and put it in the STACK_SUPER_SECRET_ADMIN_KEY environment variable."));
}
@ -119,7 +162,7 @@ export function getDefaultExtraRequestHeaders() {
* @returns The configured base URL without trailing slash
*/
export function getBaseUrl(userSpecifiedBaseUrl: string | { browser: string, server: string } | undefined) {
export function getBaseUrl(userSpecifiedBaseUrl: string | { browser: string, server: string } | undefined, options?: { isEmulator?: boolean }) {
let url;
if (userSpecifiedBaseUrl) {
if (typeof userSpecifiedBaseUrl === "string") {
@ -138,7 +181,7 @@ export function getBaseUrl(userSpecifiedBaseUrl: string | { browser: string, ser
} else {
url = process.env.NEXT_PUBLIC_SERVER_STACK_API_URL || process.env.NEXT_PUBLIC_STACK_API_URL_SERVER || process.env.STACK_API_URL_SERVER;
}
url = url || process.env.NEXT_PUBLIC_STACK_API_URL || process.env.STACK_API_URL || process.env.NEXT_PUBLIC_STACK_URL || defaultBaseUrl;
url = url || process.env.NEXT_PUBLIC_STACK_API_URL || process.env.STACK_API_URL || process.env.NEXT_PUBLIC_STACK_URL || (options?.isEmulator ? localEmulatorBaseUrl : defaultBaseUrl);
}
return replaceStackPortPrefix(url.endsWith('/') ? url.slice(0, -1) : url);

View File

@ -35,7 +35,7 @@ import { EditableTeamMemberProfile, ReceivedTeamInvitation, SentTeamInvitation,
import { ProjectCurrentServerUser, ServerOAuthProvider, ServerUser, ServerUserCreateOptions, ServerUserUpdateOptions, serverUserCreateOptionsToCrud, serverUserUpdateOptionsToCrud, withUserDestructureGuard } from "../../users";
import { StackServerAppConstructorOptions } from "../interfaces/server-app";
import { _StackClientAppImplIncomplete } from "./client-app-impl";
import { clientVersion, createCache, createCacheBySession, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getDefaultSecretServerKey, resolveConstructorOptions } from "./common";
import { LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY, clientVersion, createCache, createCacheBySession, fetchEmulatorProjectCredentials, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getDefaultSecretServerKey, getLocalEmulatorConfigFilePath, resolveConstructorOptions } from "./common";
import { useAsyncCache } from "./common"; // THIS_LINE_PLATFORM react-like
@ -410,19 +410,33 @@ export class _StackServerAppImplIncomplete<HasTokenStore extends boolean, Projec
constructor(options: StackServerAppConstructorOptions<HasTokenStore, ProjectId>, extraOptions?: { uniqueIdentifier?: string, checkString?: string, interface?: StackServerInterface }) {
const resolvedOptions = resolveConstructorOptions(options);
const publishableClientKey = resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey();
const emulatorConfigFilePath = getLocalEmulatorConfigFilePath(resolvedOptions.localEmulatorConfigFilePath);
const isEmulator = !!emulatorConfigFilePath;
const publishableClientKey = resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey() ?? (isEmulator ? LOCAL_EMULATOR_INTERNAL_PUBLISHABLE_CLIENT_KEY : undefined);
super(resolvedOptions, {
...extraOptions,
interface: extraOptions?.interface ?? new StackServerInterface({
getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl),
projectId: resolvedOptions.projectId ?? getDefaultProjectId(),
getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl, { isEmulator }),
projectId: resolvedOptions.projectId ?? getDefaultProjectId({ isEmulator }),
extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? getDefaultExtraRequestHeaders(),
clientVersion,
...(publishableClientKey != null ? { publishableClientKey } : {}),
secretServerKey: resolvedOptions.secretServerKey ?? getDefaultSecretServerKey(),
secretServerKey: resolvedOptions.secretServerKey ?? getDefaultSecretServerKey({ isEmulator }),
}),
});
if (isEmulator && !extraOptions?.interface) {
const iface = this._interface;
this._emulatorInitPromise = fetchEmulatorProjectCredentials(emulatorConfigFilePath!).then((data) => {
iface._updateEmulatorCredentials({
projectId: data.project_id,
publishableClientKey: data.publishable_client_key,
secretServerKey: data.secret_server_key,
});
});
}
}

View File

@ -19,6 +19,13 @@ export type StackClientAppConstructorOptions<HasTokenStore extends boolean, Proj
redirectMethod?: RedirectMethod,
inheritsFrom?: StackClientApp<any, any>,
/**
* Path to the local emulator config file. When set, connects to the local
* emulator and automatically fetches project credentials.
* Defaults to NEXT_PUBLIC_STACK_LOCAL_EMULATOR_CONFIG_FILE_PATH env var.
*/
localEmulatorConfigFilePath?: string,
/**
* By default, the Stack app will automatically prefetch some data from Stack's server when this app is first
* constructed. This improves the performance of your app, but will create network requests that are unnecessary if