Skip to content

Commit fb1867d

Browse files
felixkobcursoragent
andcommitted
fix(accounts): never read the app id as the active account from the URL
In the sandbox/preview the app is served under /<appId>/ (the dev-server base path), so getActiveAccountIdFromPath returned the app id and the request interceptor sent it as X-Active-Account-Id — the server then failed the membership lookup and account-scoped reads got no active account (the app hung on load). Pass the appId through createClient -> createAxiosClient and skip a leading app-id segment, reading the account from the next segment instead. Single-tenant and production multi-tenant (served under /<accountId>/) are unaffected. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 98c80bd commit fb1867d

4 files changed

Lines changed: 22 additions & 8 deletions

File tree

src/client.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,15 @@ export function createClient(config: CreateClientConfig): Base44Client {
112112
baseURL: `${serverUrl}/api`,
113113
headers,
114114
token,
115+
appId: String(appId),
115116
onError: options?.onError,
116117
});
117118

118119
const functionsAxiosClient = createAxiosClient({
119120
baseURL: `${serverUrl}/api`,
120121
headers: functionHeaders,
121122
token,
123+
appId: String(appId),
122124
interceptResponses: false,
123125
onError: options?.onError,
124126
});
@@ -132,13 +134,15 @@ export function createClient(config: CreateClientConfig): Base44Client {
132134
baseURL: `${serverUrl}/api`,
133135
headers: serviceRoleHeaders,
134136
token: serviceToken,
137+
appId: String(appId),
135138
onError: options?.onError,
136139
});
137140

138141
const serviceRoleFunctionsAxiosClient = createAxiosClient({
139142
baseURL: `${serverUrl}/api`,
140143
headers: functionHeaders,
141144
token: serviceToken,
145+
appId: String(appId),
142146
interceptResponses: false,
143147
});
144148

src/modules/accounts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function createAccountsModule(
2828

2929
return {
3030
getActiveAccountId(): string | undefined {
31-
return getActiveAccountIdFromPath();
31+
return getActiveAccountIdFromPath(appId);
3232
},
3333

3434
switchAccount(accountId: string, subPath = ""): void {

src/utils/axios-client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,14 @@ export function createAxiosClient({
149149
baseURL,
150150
headers = {},
151151
token,
152+
appId,
152153
interceptResponses = true,
153154
onError,
154155
}: {
155156
baseURL: string;
156157
headers?: Record<string, string>;
157158
token?: string;
159+
appId?: string;
158160
interceptResponses?: boolean;
159161
onError?: (error: Error) => void;
160162
}) {
@@ -180,8 +182,9 @@ export function createAxiosClient({
180182
// so account-scoped reads/writes stay isolated to the current tenant even
181183
// after a client-side account switch. The path is the canonical source, so
182184
// it overrides any stale default header (e.g. one frozen at module load);
183-
// no-op for single-tenant apps (no account segment in the path).
184-
const activeAccountId = getActiveAccountIdFromPath();
185+
// no-op for single-tenant apps (no account segment in the path). The app id
186+
// is passed so the sandbox base path (/<appId>/) is never read as an account.
187+
const activeAccountId = getActiveAccountIdFromPath(appId);
185188
if (activeAccountId) {
186189
config.headers.set("X-Active-Account-Id", activeAccountId);
187190
}

src/utils/common.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ export const isInIFrame = !isNode && window.self !== window.top;
77
// switches that don't reload the module.
88
const ACCOUNT_ID_RE = /^[a-f0-9]{24}$/;
99

10-
export function getActiveAccountIdFromPath(): string | undefined {
10+
/**
11+
* The active account id from the URL, or undefined.
12+
*
13+
* In the sandbox/preview the app is served under `/<appId>/` (the dev-server base
14+
* path), so the first segment is the app id — its own base, NOT an account. When
15+
* `appId` is supplied and matches the leading segment, it's skipped so the app id
16+
* is never sent as an account id; the account, if any, is the next segment.
17+
*/
18+
export function getActiveAccountIdFromPath(appId?: string): string | undefined {
1119
if (isNode) return undefined;
12-
const firstSegment = window.location.pathname.split("/").filter(Boolean)[0];
13-
return firstSegment && ACCOUNT_ID_RE.test(firstSegment)
14-
? firstSegment
15-
: undefined;
20+
const segments = window.location.pathname.split("/").filter(Boolean);
21+
const candidate = appId && segments[0] === appId ? segments[1] : segments[0];
22+
return candidate && ACCOUNT_ID_RE.test(candidate) ? candidate : undefined;
1623
}
1724

1825
export const generateUuid = () => {

0 commit comments

Comments
 (0)