@base44-preview/sdk 0.8.48-pr.282.8affbe6 → 0.8.48-pr.282.8f60b80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.js +97 -74
- package/dist/client.types.d.ts +1 -2
- package/dist/modules/actors.d.ts +1 -1
- package/dist/modules/actors.js +1 -1
- package/dist/modules/auth.js +32 -10
- package/dist/modules/auth.types.d.ts +15 -1
- package/dist/modules/functions.js +4 -0
- package/dist/modules/functions.types.d.ts +6 -0
- package/dist/utils/auth-utils.d.ts +1 -1
- package/dist/utils/auth-utils.js +15 -6
- package/dist/utils/embed-session.d.ts +10 -29
- package/dist/utils/embed-session.js +38 -53
- package/dist/utils/fetch-with-auth.d.ts +3 -1
- package/dist/utils/fetch-with-auth.js +4 -1
- package/dist/utils/socket-utils.d.ts +3 -4
- package/dist/utils/socket-utils.js +5 -10
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createAuthModule } from "./modules/auth.js";
|
|
|
5
5
|
import { createSsoModule } from "./modules/sso.js";
|
|
6
6
|
import { createConnectorsModule, createUserConnectorsModule, } from "./modules/connectors.js";
|
|
7
7
|
import { getAccessToken } from "./utils/auth-utils.js";
|
|
8
|
-
import { exchangeEmbedToken,
|
|
8
|
+
import { exchangeEmbedToken, frameSession, isFramed, takeEmbedTokenFromUrl, } from "./utils/embed-session.js";
|
|
9
9
|
import { createFetchWithAuth } from "./utils/fetch-with-auth.js";
|
|
10
10
|
import { createFunctionsModule } from "./modules/functions.js";
|
|
11
11
|
import { createAgentsModule } from "./modules/agents.js";
|
|
@@ -55,41 +55,19 @@ import { createActorsModule, resolveActorsHost, } from "./modules/actors.js";
|
|
|
55
55
|
*/
|
|
56
56
|
export function createClient(config) {
|
|
57
57
|
var _a, _b, _c, _d;
|
|
58
|
-
const { serverUrl = "https://base44.app", appId, analytics,
|
|
58
|
+
const { serverUrl = "https://base44.app", appId, analytics, serviceToken, requiresAuth = false, appBaseUrl, options, functionsVersion, headers: optionalHeaders, } = config;
|
|
59
59
|
// Normalize appBaseUrl to always be a string (empty if not provided or invalid)
|
|
60
60
|
const normalizedAppBaseUrl = typeof appBaseUrl === "string" ? appBaseUrl : "";
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// page loaded. In an embedded frame that is the one-time token itself, or a
|
|
72
|
-
// token an earlier visitor left in storage; neither is this session.
|
|
73
|
-
const configToken = embedded ? undefined : token;
|
|
74
|
-
let currentToken = configToken !== null && configToken !== void 0 ? configToken : null;
|
|
75
|
-
const socketConfig = {
|
|
76
|
-
serverUrl,
|
|
77
|
-
mountPath: "/ws-user-apps/socket.io/",
|
|
78
|
-
transports: ["websocket"],
|
|
79
|
-
appId,
|
|
80
|
-
// null (not undefined): an embedded frame's only identity is the one the
|
|
81
|
-
// platform minted, so the socket must not fall back to a stored token.
|
|
82
|
-
token: embedded ? null : configToken,
|
|
83
|
-
};
|
|
84
|
-
let socket = null;
|
|
85
|
-
const getSocket = () => {
|
|
86
|
-
if (!socket) {
|
|
87
|
-
socket = RoomsSocket({
|
|
88
|
-
config: socketConfig,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
return socket;
|
|
92
|
-
};
|
|
61
|
+
// Always taken off the URL, even outside a frame: a one-time token must not
|
|
62
|
+
// be left in the address bar, in history, or in a shared link.
|
|
63
|
+
const urlOtt = takeEmbedTokenFromUrl();
|
|
64
|
+
// Only redeemed inside a frame. Opened as a normal tab, the same URL falls
|
|
65
|
+
// back to the app's own login, which works there and cannot work in a frame.
|
|
66
|
+
const embedOtt = isFramed() ? urlOtt : null;
|
|
67
|
+
const embedded = Boolean(embedOtt);
|
|
68
|
+
// The passed token is the OTT itself whenever one came in on the URL. That is
|
|
69
|
+
// not this session: it is what the exchange below trades for one.
|
|
70
|
+
const token = urlOtt !== null ? undefined : config.token;
|
|
93
71
|
const headers = {
|
|
94
72
|
...optionalHeaders,
|
|
95
73
|
"X-App-Id": String(appId),
|
|
@@ -103,19 +81,19 @@ export function createClient(config) {
|
|
|
103
81
|
const axiosClient = createAxiosClient({
|
|
104
82
|
baseURL: `${serverUrl}/api`,
|
|
105
83
|
headers,
|
|
106
|
-
token
|
|
84
|
+
token,
|
|
107
85
|
onError: options === null || options === void 0 ? void 0 : options.onError,
|
|
108
86
|
});
|
|
109
87
|
const functionsAxiosClient = createAxiosClient({
|
|
110
88
|
baseURL: `${serverUrl}/api`,
|
|
111
89
|
headers: functionHeaders,
|
|
112
|
-
token
|
|
90
|
+
token,
|
|
113
91
|
interceptResponses: false,
|
|
114
92
|
onError: options === null || options === void 0 ? void 0 : options.onError,
|
|
115
93
|
});
|
|
116
94
|
const serviceRoleHeaders = {
|
|
117
95
|
...headers,
|
|
118
|
-
...(
|
|
96
|
+
...(token ? { "on-behalf-of": `Bearer ${token}` } : {}),
|
|
119
97
|
};
|
|
120
98
|
const serviceRoleAxiosClient = createAxiosClient({
|
|
121
99
|
baseURL: `${serverUrl}/api`,
|
|
@@ -138,56 +116,90 @@ export function createClient(config) {
|
|
|
138
116
|
baseURL: `${serverUrl}/api`,
|
|
139
117
|
headers,
|
|
140
118
|
});
|
|
119
|
+
// Declared before the auth module so `onSessionChange` can reach it; the
|
|
120
|
+
// socket itself is still only built on first use, below.
|
|
121
|
+
let socket = null;
|
|
141
122
|
const userAuthModule = createAuthModule(axiosClient, functionsAxiosClient, appId, {
|
|
142
123
|
appBaseUrl: normalizedAppBaseUrl,
|
|
143
124
|
serverUrl,
|
|
144
|
-
token
|
|
125
|
+
token,
|
|
145
126
|
embedded,
|
|
127
|
+
// The socket carries its token on the handshake, so an identity change
|
|
128
|
+
// only reaches it by opening a new connection — or, on logout, by
|
|
129
|
+
// dropping the one still running as the user who just left.
|
|
130
|
+
onSessionChange: (hasSession) => hasSession ? socket === null || socket === void 0 ? void 0 : socket.reconnect() : socket === null || socket === void 0 ? void 0 : socket.disconnect(),
|
|
146
131
|
});
|
|
147
132
|
// Apply the access token before any module that may issue authenticated
|
|
148
133
|
// requests during construction (notably analytics, which fires an init
|
|
149
134
|
// event whose flush calls auth.me()). Without this, the first User/me
|
|
150
135
|
// request is built before setToken runs and goes out unauthenticated.
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const accessToken = configToken || getAccessToken();
|
|
136
|
+
// Skipped in a frame: the session comes from the exchange below.
|
|
137
|
+
if (typeof window !== "undefined" && !embedded) {
|
|
138
|
+
const accessToken = token || getAccessToken();
|
|
155
139
|
if (accessToken) {
|
|
156
|
-
currentToken = accessToken;
|
|
157
140
|
userAuthModule.setToken(accessToken);
|
|
158
141
|
}
|
|
159
142
|
}
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
143
|
+
// Live token for every module; outside a frame it still falls back to storage.
|
|
144
|
+
const getToken = () => { var _a; return (_a = userAuthModule.getToken()) !== null && _a !== void 0 ? _a : (embedded ? null : getAccessToken()); };
|
|
145
|
+
const socketConfig = {
|
|
146
|
+
serverUrl,
|
|
147
|
+
mountPath: "/ws-user-apps/socket.io/",
|
|
148
|
+
transports: ["websocket"],
|
|
149
|
+
appId,
|
|
150
|
+
getToken,
|
|
168
151
|
};
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
socketConfig.token = newToken;
|
|
173
|
-
if (socket) {
|
|
174
|
-
socket.updateConfig({ token: newToken });
|
|
152
|
+
const getSocket = () => {
|
|
153
|
+
if (!socket) {
|
|
154
|
+
socket = RoomsSocket({ config: socketConfig });
|
|
175
155
|
}
|
|
156
|
+
return socket;
|
|
176
157
|
};
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
const
|
|
181
|
-
? exchangeEmbedToken({ serverUrl, appId, ott: embedOtt })
|
|
158
|
+
// The exchange belongs to the frame, not to one client: the token is off the
|
|
159
|
+
// URL after the first `createClient`, so a second one in the same document
|
|
160
|
+
// joins the session already being negotiated rather than going anonymous.
|
|
161
|
+
const session = embedOtt
|
|
162
|
+
? frameSession(appId, () => exchangeEmbedToken({ serverUrl, appId, ott: embedOtt }))
|
|
163
|
+
: frameSession(appId);
|
|
164
|
+
// Settles once the exchanged session is applied (memory only); at once
|
|
165
|
+
// otherwise. Never rejects: every request waits on it, so a failure here
|
|
166
|
+
// must not turn into a rejection on each of them.
|
|
167
|
+
const authReady = session
|
|
168
|
+
? session
|
|
169
|
+
.then((sessionToken) => {
|
|
170
|
+
var _a;
|
|
182
171
|
if (sessionToken) {
|
|
183
|
-
|
|
172
|
+
userAuthModule.setToken(sessionToken, false);
|
|
173
|
+
return;
|
|
184
174
|
}
|
|
175
|
+
// The app is about to run anonymous. Say why, once, instead of
|
|
176
|
+
// leaving only the 401s that follow.
|
|
177
|
+
const error = new Error("Base44: the embed token was refused, so this app is not signed in.");
|
|
178
|
+
console.error(error.message);
|
|
179
|
+
(_a = options === null || options === void 0 ? void 0 : options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
|
|
180
|
+
})
|
|
181
|
+
.catch((e) => {
|
|
182
|
+
console.error("Base44: applying the embedded session failed:", e);
|
|
185
183
|
})
|
|
186
184
|
: Promise.resolve();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
185
|
+
// Everything that can wait for the session does. `aiGateway.connection()`
|
|
186
|
+
// and the agents connect URLs cannot: they hand back a value, not a promise,
|
|
187
|
+
// and a value read now would stay wrong after the session arrives. They warn
|
|
188
|
+
// instead of failing quietly.
|
|
189
|
+
let warnedEarlyRead = false;
|
|
190
|
+
const getTokenNow = () => {
|
|
191
|
+
const sessionToken = getToken();
|
|
192
|
+
if (sessionToken === null && session && !warnedEarlyRead) {
|
|
193
|
+
warnedEarlyRead = true;
|
|
194
|
+
console.warn("Base44: read a token before the embedded session was ready, so it is " +
|
|
195
|
+
"empty. Await a call such as base44.auth.me() before building a " +
|
|
196
|
+
"client or a URL that keeps the token.");
|
|
197
|
+
}
|
|
198
|
+
return sessionToken;
|
|
199
|
+
};
|
|
200
|
+
if (session) {
|
|
201
|
+
// Requests issued during the exchange wait for it. Registered after createAxiosClient's
|
|
202
|
+
// interceptors so it runs first and the anonymous-visitor header sees the Authorization.
|
|
191
203
|
for (const client of [axiosClient, functionsAxiosClient]) {
|
|
192
204
|
client.interceptors.request.use(async (requestConfig) => {
|
|
193
205
|
await authReady;
|
|
@@ -205,7 +217,10 @@ export function createClient(config) {
|
|
|
205
217
|
// URL needs an absolute host, so fall back to the page origin.
|
|
206
218
|
host: resolveActorsHost(serverUrl, typeof window !== "undefined" ? (_a = window.location) === null || _a === void 0 ? void 0 : _a.origin : undefined),
|
|
207
219
|
functionsVersion,
|
|
208
|
-
getAuthToken:
|
|
220
|
+
getAuthToken: async () => {
|
|
221
|
+
await authReady;
|
|
222
|
+
return getToken();
|
|
223
|
+
},
|
|
209
224
|
mintConnectionToken: async (actorName, room, connectionId) => {
|
|
210
225
|
await authReady;
|
|
211
226
|
const authToken = getToken();
|
|
@@ -233,6 +248,7 @@ export function createClient(config) {
|
|
|
233
248
|
connectors: createUserConnectorsModule(axiosClient, appId),
|
|
234
249
|
auth: userAuthModule,
|
|
235
250
|
functions: createFunctionsModule(functionsAxiosClient, appId, {
|
|
251
|
+
waitForAuth: () => authReady,
|
|
236
252
|
getAuthHeaders: () => {
|
|
237
253
|
const headers = {};
|
|
238
254
|
const sessionToken = getToken();
|
|
@@ -248,9 +264,13 @@ export function createClient(config) {
|
|
|
248
264
|
getSocket,
|
|
249
265
|
appId,
|
|
250
266
|
serverUrl,
|
|
251
|
-
getToken,
|
|
267
|
+
getToken: getTokenNow,
|
|
268
|
+
}),
|
|
269
|
+
aiGateway: createAiGatewayModule({
|
|
270
|
+
serverUrl,
|
|
271
|
+
getToken: getTokenNow,
|
|
272
|
+
appId,
|
|
252
273
|
}),
|
|
253
|
-
aiGateway: createAiGatewayModule({ serverUrl, getToken, appId }),
|
|
254
274
|
appLogs: createAppLogsModule(axiosClient, appId),
|
|
255
275
|
app: createAppModule(axiosClient, appId),
|
|
256
276
|
users: createUsersModule(axiosClient, appId),
|
|
@@ -295,7 +315,10 @@ export function createClient(config) {
|
|
|
295
315
|
getSocket,
|
|
296
316
|
appId,
|
|
297
317
|
serverUrl,
|
|
298
|
-
|
|
318
|
+
// The user's token, as on the user-scoped module: the only thing this is
|
|
319
|
+
// read for is the `?token=` on a channel URL handed to that user, which
|
|
320
|
+
// the app's service credential must never end up in.
|
|
321
|
+
getToken: getTokenNow,
|
|
299
322
|
}),
|
|
300
323
|
aiGateway: createAiGatewayModule({
|
|
301
324
|
serverUrl,
|
|
@@ -336,12 +359,12 @@ export function createClient(config) {
|
|
|
336
359
|
serverUrl,
|
|
337
360
|
functionsVersion,
|
|
338
361
|
platformHeaders: optionalHeaders,
|
|
362
|
+
waitForAuth: () => authReady,
|
|
339
363
|
}),
|
|
340
364
|
/**
|
|
341
365
|
* Sets a new authentication token for all subsequent requests.
|
|
342
366
|
*
|
|
343
367
|
* @param newToken - The new authentication token
|
|
344
|
-
* @param saveToStorage - Whether to also keep the token in local storage. Defaults to `true`.
|
|
345
368
|
*
|
|
346
369
|
* @example
|
|
347
370
|
* ```typescript
|
|
@@ -353,8 +376,8 @@ export function createClient(config) {
|
|
|
353
376
|
* base44.setToken(access_token);
|
|
354
377
|
* ```
|
|
355
378
|
*/
|
|
356
|
-
setToken(newToken
|
|
357
|
-
|
|
379
|
+
setToken(newToken) {
|
|
380
|
+
userAuthModule.setToken(newToken, true);
|
|
358
381
|
},
|
|
359
382
|
/**
|
|
360
383
|
* Gets the current client configuration.
|
package/dist/client.types.d.ts
CHANGED
|
@@ -200,9 +200,8 @@ export interface Base44Client {
|
|
|
200
200
|
* Updates the token for both HTTP requests and WebSocket connections.
|
|
201
201
|
*
|
|
202
202
|
* @param newToken - The new authentication token.
|
|
203
|
-
* @param saveToStorage - Whether to also keep the token in local storage so it survives a page load. Defaults to `true`.
|
|
204
203
|
*/
|
|
205
|
-
setToken(newToken: string
|
|
204
|
+
setToken(newToken: string): void;
|
|
206
205
|
/**
|
|
207
206
|
* Gets the current client configuration.
|
|
208
207
|
* @internal
|
package/dist/modules/actors.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ interface ActorsConfig {
|
|
|
12
12
|
/** Current user access token, if authenticated. Rides the WS query on the
|
|
13
13
|
* proxy-fallback path so the platform proxy can authenticate the connection;
|
|
14
14
|
* anonymous connects omit it. */
|
|
15
|
-
getAuthToken(): string | null | undefined
|
|
15
|
+
getAuthToken(): string | null | undefined | Promise<string | null | undefined>;
|
|
16
16
|
/** Same semantics as function calls: editors with a non-prod version get the
|
|
17
17
|
* draft actor script; everyone else gets the published one. */
|
|
18
18
|
functionsVersion?: string;
|
package/dist/modules/actors.js
CHANGED
|
@@ -83,7 +83,7 @@ class Connection {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
// Rebuilt per attempt so a login/logout is picked up on reconnect.
|
|
86
|
-
return buildProxyActorUrl(config.host, actorName, instanceId, this.id, config.appId, config.getAuthToken(), config.functionsVersion);
|
|
86
|
+
return buildProxyActorUrl(config.host, actorName, instanceId, this.id, config.appId, await config.getAuthToken(), config.functionsVersion);
|
|
87
87
|
};
|
|
88
88
|
const ws = new ReconnectingWebSocket(urlProvider);
|
|
89
89
|
this.ws = ws;
|
package/dist/modules/auth.js
CHANGED
|
@@ -82,10 +82,13 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
82
82
|
// Tracked here rather than read off `axios.defaults` so the answer stays tied
|
|
83
83
|
// to the identity transitions below (`setToken`, `logout`) instead of to the
|
|
84
84
|
// header a caller may have set on the instance directly.
|
|
85
|
-
let
|
|
85
|
+
let accessToken = options.token || null;
|
|
86
86
|
return {
|
|
87
87
|
hasToken() {
|
|
88
|
-
return
|
|
88
|
+
return accessToken !== null;
|
|
89
|
+
},
|
|
90
|
+
getToken() {
|
|
91
|
+
return accessToken;
|
|
89
92
|
},
|
|
90
93
|
isEmbedded() {
|
|
91
94
|
return Boolean(options.embedded);
|
|
@@ -113,8 +116,11 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
113
116
|
if (typeof window === "undefined") {
|
|
114
117
|
throw new Error("Login method can only be used in a browser environment");
|
|
115
118
|
}
|
|
116
|
-
//
|
|
117
|
-
// login
|
|
119
|
+
// Only the host platform can sign a platform user in, so there is no
|
|
120
|
+
// login page to send them to. (The app's own login would work in the
|
|
121
|
+
// frame — `loginWithProvider` opens a popup — but it would mint a
|
|
122
|
+
// different, app-level identity.) An app that wants its own notice
|
|
123
|
+
// checks `isEmbedded()` rather than asking for a login it cannot get.
|
|
118
124
|
if (options.embedded) {
|
|
119
125
|
showEmbedSessionEnded();
|
|
120
126
|
return;
|
|
@@ -155,13 +161,18 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
155
161
|
},
|
|
156
162
|
// Logout the current user
|
|
157
163
|
logout(redirectUrl) {
|
|
158
|
-
|
|
164
|
+
var _a;
|
|
165
|
+
// Remove the token from both axios instances (always do this). Missing
|
|
166
|
+
// the functions one used to be hidden by the redirect below tearing the
|
|
167
|
+
// page down; an embedded logout returns instead, so the page lives on.
|
|
159
168
|
delete axios.defaults.headers.common["Authorization"];
|
|
169
|
+
delete functionsAxiosClient.defaults.headers.common["Authorization"];
|
|
160
170
|
// Drop identity resolved under the previous session: a `me()` already in
|
|
161
171
|
// flight would otherwise resolve into callers that run after the logout.
|
|
162
172
|
clearPendingMe();
|
|
163
173
|
resetAnalyticsSessionContext();
|
|
164
|
-
|
|
174
|
+
accessToken = null;
|
|
175
|
+
(_a = options.onSessionChange) === null || _a === void 0 ? void 0 : _a.call(options, false);
|
|
165
176
|
// Only do the rest if in a browser environment
|
|
166
177
|
if (typeof window !== "undefined") {
|
|
167
178
|
// Remove token from localStorage
|
|
@@ -175,6 +186,13 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
175
186
|
console.error("Failed to remove token from localStorage:", e);
|
|
176
187
|
}
|
|
177
188
|
}
|
|
189
|
+
// An embedded session holds no app cookie to clear — it lived in
|
|
190
|
+
// memory — and navigating a third-party frame to the logout endpoint
|
|
191
|
+
// would only break the frame. The state above is already cleared.
|
|
192
|
+
if (options.embedded) {
|
|
193
|
+
showEmbedSessionEnded();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
178
196
|
// Determine the from_url parameter
|
|
179
197
|
const fromUrl = redirectUrl || window.location.href;
|
|
180
198
|
// Redirect to server-side logout endpoint to clear HTTP-only cookies
|
|
@@ -184,20 +202,24 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
184
202
|
},
|
|
185
203
|
// Set authentication token
|
|
186
204
|
setToken(token, saveToStorage = true) {
|
|
205
|
+
var _a;
|
|
187
206
|
if (!token)
|
|
188
207
|
return;
|
|
208
|
+
// An embedded session belongs to the frame the platform minted it for.
|
|
209
|
+
// Persisting it would let it outlive that frame and be picked up as the
|
|
210
|
+
// identity on a later top-level visit, so storage is refused outright.
|
|
211
|
+
const persist = saveToStorage && !options.embedded;
|
|
189
212
|
// Same reasoning as in `logout`: the identity changes here, so anything
|
|
190
213
|
// resolved for the previous one must not be handed to later callers.
|
|
191
214
|
clearPendingMe();
|
|
192
215
|
resetAnalyticsSessionContext();
|
|
193
|
-
|
|
216
|
+
accessToken = token;
|
|
194
217
|
// handle token change for axios clients
|
|
195
218
|
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
196
219
|
functionsAxiosClient.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
220
|
+
(_a = options.onSessionChange) === null || _a === void 0 ? void 0 : _a.call(options, true);
|
|
197
221
|
// Save token to localStorage if requested
|
|
198
|
-
if (
|
|
199
|
-
typeof window !== "undefined" &&
|
|
200
|
-
window.localStorage) {
|
|
222
|
+
if (persist && typeof window !== "undefined" && window.localStorage) {
|
|
201
223
|
try {
|
|
202
224
|
window.localStorage.setItem("base44_access_token", token);
|
|
203
225
|
// Set "token" that is set by the built-in SDK of platform version 2
|
|
@@ -103,6 +103,13 @@ export interface AuthModuleOptions {
|
|
|
103
103
|
* session is minted by the platform and cannot be renewed by a login redirect.
|
|
104
104
|
*/
|
|
105
105
|
embedded?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Called whenever the identity changes: `true` when a token is set, `false`
|
|
108
|
+
* on logout. Lets the client reach what holds its own copy of the identity —
|
|
109
|
+
* the realtime socket, which carries a token on the handshake and so has to
|
|
110
|
+
* open a new connection to pick one up, or drop the one it has.
|
|
111
|
+
*/
|
|
112
|
+
onSessionChange?: (hasSession: boolean) => void;
|
|
106
113
|
}
|
|
107
114
|
/**
|
|
108
115
|
* Authentication module for managing user authentication and authorization. The module automatically stores tokens in local storage when available and manages authorization headers for API requests.
|
|
@@ -187,7 +194,9 @@ export interface AuthModule {
|
|
|
187
194
|
/**
|
|
188
195
|
* Whether the app is running embedded in a host platform.
|
|
189
196
|
*
|
|
190
|
-
* A platform embeds an app in an iframe and signs its user in by minting a one-time token onto the frame's URL; the SDK trades it for a session as the client is created. That session is held in memory only and can be renewed only by the platform, so when it ends there is no login page to send the user to — {@linkcode AuthModule.redirectToLogin | redirectToLogin()}
|
|
197
|
+
* A platform embeds an app in an iframe and signs its user in by minting a one-time token onto the frame's URL; the SDK trades it for a session as the client is created. That session is held in memory only and can be renewed only by the platform, so when it ends there is no login page to send the user to — {@linkcode AuthModule.redirectToLogin | redirectToLogin()} and {@linkcode AuthModule.logout | logout()} show a "session ended" notice instead. Use this to render your own notice or to hide sign-in and sign-out controls that cannot work inside the frame.
|
|
198
|
+
*
|
|
199
|
+
* Because the session lives in memory, it does not survive a full page load inside the frame: routing within the app keeps it, while a reload or a real navigation ends it and the platform has to embed the app again — and this returns `false` on that load, since nothing about it says it was embedded. Prefer client-side navigation in an embedded app.
|
|
191
200
|
*
|
|
192
201
|
* @returns `true` when the app was embedded by a host platform, `false` otherwise.
|
|
193
202
|
*
|
|
@@ -560,4 +569,9 @@ export interface InternalAuthModule extends AuthModule {
|
|
|
560
569
|
* could not succeed without a session, not to decide that one is valid.
|
|
561
570
|
*/
|
|
562
571
|
hasToken(): boolean;
|
|
572
|
+
/**
|
|
573
|
+
* The access token currently set on the client, or `null` when there is none.
|
|
574
|
+
* Follows {@linkcode AuthModule.setToken | setToken} and {@linkcode AuthModule.logout | logout}.
|
|
575
|
+
*/
|
|
576
|
+
getToken(): string | null;
|
|
563
577
|
}
|
|
@@ -65,8 +65,12 @@ export function createFunctionsModule(axios, appId, config) {
|
|
|
65
65
|
},
|
|
66
66
|
// Fetch a backend function endpoint directly.
|
|
67
67
|
async fetch(path, init = {}) {
|
|
68
|
+
var _a;
|
|
68
69
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
69
70
|
const primaryPath = `/functions${normalizedPath}`;
|
|
71
|
+
// Headers are read after this: a session still being negotiated must be
|
|
72
|
+
// in hand before the Authorization is built, not after.
|
|
73
|
+
await ((_a = config === null || config === void 0 ? void 0 : config.waitForAuth) === null || _a === void 0 ? void 0 : _a.call(config));
|
|
70
74
|
const headers = toHeaders(init.headers);
|
|
71
75
|
const requestInit = {
|
|
72
76
|
...init,
|
|
@@ -29,6 +29,12 @@ export type FunctionsFetchInit = RequestInit;
|
|
|
29
29
|
export interface FunctionsModuleConfig {
|
|
30
30
|
getAuthHeaders?: () => Record<string, string>;
|
|
31
31
|
baseURL?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves once the client's session is settled. `fetch` builds its headers
|
|
34
|
+
* by hand rather than through axios, so without this it would miss the gate
|
|
35
|
+
* every other request goes through.
|
|
36
|
+
*/
|
|
37
|
+
waitForAuth?: () => Promise<void>;
|
|
32
38
|
}
|
|
33
39
|
/**
|
|
34
40
|
* Functions module for invoking custom backend functions.
|
|
@@ -5,7 +5,7 @@ import { GetAccessTokenOptions, SaveAccessTokenOptions, RemoveAccessTokenOptions
|
|
|
5
5
|
* Low-level utility for manually retrieving tokens. In most cases, the Base44 client handles
|
|
6
6
|
* token management automatically. This function is useful for custom authentication flows or when you need direct access to stored tokens. Requires a browser environment and can't be used in the backend.
|
|
7
7
|
*
|
|
8
|
-
* When a host platform has embedded the app with a one-time token (`?ott=`), that token is returned as it stands: it is what {@linkcode createClient} trades for the session, so a page that gates on "is there a token?" as it loads sees one. It is neither stored nor removed from the URL here.
|
|
8
|
+
* When a host platform has embedded the app with a one-time token (`?ott=`), that token is returned as it stands: it is what {@linkcode createClient} trades for the session, so a page that gates on "is there a token?" as it loads sees one. It is neither stored nor removed from the URL here, and it is reported only inside a frame, where such a token is redeemed.
|
|
9
9
|
*
|
|
10
10
|
* @internal
|
|
11
11
|
*
|
package/dist/utils/auth-utils.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { EMBED_TOKEN_PARAM } from "./embed-session.js";
|
|
1
|
+
import { EMBED_TOKEN_PARAM, isFramed } from "./embed-session.js";
|
|
2
|
+
/** The URL parameter a Base44 session token arrives on. */
|
|
3
|
+
const DEFAULT_TOKEN_PARAM = "access_token";
|
|
2
4
|
/**
|
|
3
5
|
* Retrieves an access token from URL parameters or local storage.
|
|
4
6
|
*
|
|
5
7
|
* Low-level utility for manually retrieving tokens. In most cases, the Base44 client handles
|
|
6
8
|
* token management automatically. This function is useful for custom authentication flows or when you need direct access to stored tokens. Requires a browser environment and can't be used in the backend.
|
|
7
9
|
*
|
|
8
|
-
* When a host platform has embedded the app with a one-time token (`?ott=`), that token is returned as it stands: it is what {@linkcode createClient} trades for the session, so a page that gates on "is there a token?" as it loads sees one. It is neither stored nor removed from the URL here.
|
|
10
|
+
* When a host platform has embedded the app with a one-time token (`?ott=`), that token is returned as it stands: it is what {@linkcode createClient} trades for the session, so a page that gates on "is there a token?" as it loads sees one. It is neither stored nor removed from the URL here, and it is reported only inside a frame, where such a token is redeemed.
|
|
9
11
|
*
|
|
10
12
|
* @internal
|
|
11
13
|
*
|
|
@@ -38,7 +40,7 @@ import { EMBED_TOKEN_PARAM } from "./embed-session.js";
|
|
|
38
40
|
* ```
|
|
39
41
|
*/
|
|
40
42
|
export function getAccessToken(options = {}) {
|
|
41
|
-
const { storageKey = "base44_access_token", paramName =
|
|
43
|
+
const { storageKey = "base44_access_token", paramName = DEFAULT_TOKEN_PARAM, saveToStorage = true, removeFromUrl = true, } = options;
|
|
42
44
|
let token = null;
|
|
43
45
|
// Try to get token from URL parameters
|
|
44
46
|
if (typeof window !== "undefined" && window.location) {
|
|
@@ -63,9 +65,16 @@ export function getAccessToken(options = {}) {
|
|
|
63
65
|
// session yet — createClient takes it off the URL and exchanges it — but
|
|
64
66
|
// it is the identity this load arrives with, and callers that read this
|
|
65
67
|
// once as the page loads must not conclude there is none.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
//
|
|
69
|
+
// Only in a frame, and only for the parameter a session arrives on: a
|
|
70
|
+
// one-time token is redeemed nowhere else, so a top-level load still
|
|
71
|
+
// carrying one (a URL rewrite the browser refused) must not have it
|
|
72
|
+
// applied as a session — and never saved as one.
|
|
73
|
+
if (paramName === DEFAULT_TOKEN_PARAM && isFramed()) {
|
|
74
|
+
const embedToken = urlParams.get(EMBED_TOKEN_PARAM);
|
|
75
|
+
if (embedToken) {
|
|
76
|
+
return embedToken;
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
catch (e) {
|
|
@@ -7,44 +7,25 @@
|
|
|
7
7
|
* grant (RFC 8693), and keeps the result in memory only — never in storage — so
|
|
8
8
|
* the session lives and dies with the frame.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export declare const EMBED_TOKEN_PARAM = "ott";
|
|
14
|
-
/**
|
|
15
|
-
* Reads the one-time token off the current URL and strips it, so it is never
|
|
16
|
-
* left in the address bar, in history, or in a shared link.
|
|
10
|
+
* The exchange endpoint is rate limited per app, and its limiter refuses before
|
|
11
|
+
* the one-time token is redeemed — so a 429 leaves the token still valid and is
|
|
12
|
+
* worth retrying once.
|
|
17
13
|
*
|
|
18
14
|
* @internal
|
|
19
15
|
*/
|
|
16
|
+
export declare const EMBED_TOKEN_PARAM = "ott";
|
|
17
|
+
/** @internal */
|
|
18
|
+
export declare function frameSession(appId: string, start?: () => Promise<string | null>): Promise<string | null> | null;
|
|
19
|
+
/** @internal */
|
|
20
20
|
export declare function takeEmbedTokenFromUrl(): string | null;
|
|
21
|
-
/**
|
|
22
|
-
* Whether this tab previously redeemed an embed token. Counts only inside a
|
|
23
|
-
* frame: a top-level tab that once carried a token must fall back to the
|
|
24
|
-
* regular login, or it could never sign in again.
|
|
25
|
-
*
|
|
26
|
-
* @internal
|
|
27
|
-
*/
|
|
28
|
-
export declare function isEmbeddedTab(): boolean;
|
|
29
21
|
/** @internal */
|
|
30
|
-
export declare function
|
|
31
|
-
/**
|
|
32
|
-
* Trades a one-time embed token for an app-user session token. Resolves to
|
|
33
|
-
* `null` when the platform refuses the token; never throws.
|
|
34
|
-
*
|
|
35
|
-
* @internal
|
|
36
|
-
*/
|
|
22
|
+
export declare function isFramed(): boolean;
|
|
23
|
+
/** @internal */
|
|
37
24
|
export declare function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl, }: {
|
|
38
25
|
serverUrl: string;
|
|
39
26
|
appId: string;
|
|
40
27
|
ott: string;
|
|
41
28
|
fetchImpl?: typeof fetch;
|
|
42
29
|
}): Promise<string | null>;
|
|
43
|
-
/**
|
|
44
|
-
* Covers the page with a plain "session ended" notice. An embedded session can
|
|
45
|
-
* only be renewed by the host platform (by reloading its own page), so this
|
|
46
|
-
* stands in for the login redirect, which cannot complete inside a frame.
|
|
47
|
-
*
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
30
|
+
/** @internal */
|
|
50
31
|
export declare function showEmbedSessionEnded(): void;
|
|
@@ -7,81 +7,68 @@
|
|
|
7
7
|
* grant (RFC 8693), and keeps the result in memory only — never in storage — so
|
|
8
8
|
* the session lives and dies with the frame.
|
|
9
9
|
*
|
|
10
|
+
* The exchange endpoint is rate limited per app, and its limiter refuses before
|
|
11
|
+
* the one-time token is redeemed — so a 429 leaves the token still valid and is
|
|
12
|
+
* worth retrying once.
|
|
13
|
+
*
|
|
10
14
|
* @internal
|
|
11
15
|
*/
|
|
12
|
-
/** The query parameter a host platform puts the one-time token on. @internal */
|
|
13
16
|
export const EMBED_TOKEN_PARAM = "ott";
|
|
14
|
-
const EMBED_TAB_KEY = "base44_embed_session";
|
|
15
17
|
const GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
|
|
16
18
|
const SUBJECT_TOKEN_TYPE = "urn:base44:params:oauth:token-type:embed-ott";
|
|
17
19
|
const RATE_LIMITED = 429;
|
|
18
20
|
const RATE_LIMIT_RETRY_MS = 1000;
|
|
21
|
+
const EXCHANGE_TIMEOUT_MS = 15000;
|
|
19
22
|
const SESSION_ENDED_ELEMENT_ID = "base44-embed-session-ended";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const FRAME_SESSIONS_KEY = "__base44EmbedSessions";
|
|
24
|
+
/** @internal */
|
|
25
|
+
export function frameSession(appId, start) {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
var _c;
|
|
28
|
+
if (typeof window === "undefined") {
|
|
29
|
+
return start ? start() : null;
|
|
30
|
+
}
|
|
31
|
+
const sessions = ((_a = (_c = window)[FRAME_SESSIONS_KEY]) !== null && _a !== void 0 ? _a : (_c[FRAME_SESSIONS_KEY] = {}));
|
|
32
|
+
if (!sessions[appId] && start) {
|
|
33
|
+
sessions[appId] = start();
|
|
34
|
+
}
|
|
35
|
+
return (_b = sessions[appId]) !== null && _b !== void 0 ? _b : null;
|
|
36
|
+
}
|
|
37
|
+
/** @internal */
|
|
26
38
|
export function takeEmbedTokenFromUrl() {
|
|
27
39
|
if (typeof window === "undefined" || !window.location) {
|
|
28
40
|
return null;
|
|
29
41
|
}
|
|
42
|
+
let ott = null;
|
|
30
43
|
try {
|
|
31
44
|
const url = new URL(window.location.href);
|
|
32
|
-
|
|
45
|
+
ott = url.searchParams.get(EMBED_TOKEN_PARAM);
|
|
33
46
|
if (!ott) {
|
|
34
47
|
return null;
|
|
35
48
|
}
|
|
36
49
|
url.searchParams.delete(EMBED_TOKEN_PARAM);
|
|
37
50
|
window.history.replaceState(window.history.state, "", url.toString());
|
|
38
|
-
return ott;
|
|
39
51
|
}
|
|
40
52
|
catch (e) {
|
|
41
53
|
console.error("Error retrieving embed token from URL:", e);
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Whether this tab previously redeemed an embed token. Counts only inside a
|
|
47
|
-
* frame: a top-level tab that once carried a token must fall back to the
|
|
48
|
-
* regular login, or it could never sign in again.
|
|
49
|
-
*
|
|
50
|
-
* @internal
|
|
51
|
-
*/
|
|
52
|
-
export function isEmbeddedTab() {
|
|
53
|
-
if (typeof window === "undefined") {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
// A cross-site frame is third-party storage, which some browsers block
|
|
57
|
-
// outright — every access can throw, and the marker is then unavailable.
|
|
58
|
-
try {
|
|
59
|
-
return (window.self !== window.top &&
|
|
60
|
-
window.sessionStorage.getItem(EMBED_TAB_KEY) === "1");
|
|
61
|
-
}
|
|
62
|
-
catch (_a) {
|
|
63
|
-
return false;
|
|
64
54
|
}
|
|
55
|
+
return ott;
|
|
65
56
|
}
|
|
66
57
|
/** @internal */
|
|
67
|
-
export function
|
|
58
|
+
export function isFramed() {
|
|
68
59
|
try {
|
|
69
|
-
window
|
|
60
|
+
return typeof window !== "undefined" && window.self !== window.top;
|
|
70
61
|
}
|
|
71
62
|
catch (_a) {
|
|
72
|
-
|
|
63
|
+
return true;
|
|
73
64
|
}
|
|
74
65
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Trades a one-time embed token for an app-user session token. Resolves to
|
|
77
|
-
* `null` when the platform refuses the token; never throws.
|
|
78
|
-
*
|
|
79
|
-
* @internal
|
|
80
|
-
*/
|
|
66
|
+
/** @internal */
|
|
81
67
|
export async function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl = fetch, }) {
|
|
82
68
|
const post = () => fetchImpl(`${serverUrl}/api/apps/${appId}/auth/embed/token`, {
|
|
83
69
|
method: "POST",
|
|
84
70
|
headers: { "X-App-Id": String(appId) },
|
|
71
|
+
signal: AbortSignal.timeout(EXCHANGE_TIMEOUT_MS),
|
|
85
72
|
body: new URLSearchParams({
|
|
86
73
|
grant_type: GRANT_TYPE,
|
|
87
74
|
subject_token: ott,
|
|
@@ -90,7 +77,6 @@ export async function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl = fe
|
|
|
90
77
|
});
|
|
91
78
|
try {
|
|
92
79
|
let response = await post();
|
|
93
|
-
// The limiter refuses before the token is redeemed, so it is still valid.
|
|
94
80
|
if (response.status === RATE_LIMITED) {
|
|
95
81
|
await new Promise((resolve) => setTimeout(resolve, RATE_LIMIT_RETRY_MS));
|
|
96
82
|
response = await post();
|
|
@@ -106,13 +92,7 @@ export async function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl = fe
|
|
|
106
92
|
return null;
|
|
107
93
|
}
|
|
108
94
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Covers the page with a plain "session ended" notice. An embedded session can
|
|
111
|
-
* only be renewed by the host platform (by reloading its own page), so this
|
|
112
|
-
* stands in for the login redirect, which cannot complete inside a frame.
|
|
113
|
-
*
|
|
114
|
-
* @internal
|
|
115
|
-
*/
|
|
95
|
+
/** @internal */
|
|
116
96
|
export function showEmbedSessionEnded() {
|
|
117
97
|
if (typeof document === "undefined" || !document.body) {
|
|
118
98
|
return;
|
|
@@ -120,21 +100,26 @@ export function showEmbedSessionEnded() {
|
|
|
120
100
|
if (document.getElementById(SESSION_ENDED_ELEMENT_ID)) {
|
|
121
101
|
return;
|
|
122
102
|
}
|
|
103
|
+
const style = document.createElement("style");
|
|
104
|
+
style.textContent =
|
|
105
|
+
`#${SESSION_ENDED_ELEMENT_ID}{--b44-bg:#fff;--b44-fg:#0f172a;--b44-muted:#475569;}` +
|
|
106
|
+
`@media (prefers-color-scheme:dark){#${SESSION_ENDED_ELEMENT_ID}` +
|
|
107
|
+
`{--b44-bg:#0f172a;--b44-fg:#f8fafc;--b44-muted:#94a3b8;}}`;
|
|
123
108
|
const overlay = document.createElement("div");
|
|
124
109
|
overlay.id = SESSION_ENDED_ELEMENT_ID;
|
|
125
110
|
overlay.setAttribute("role", "alert");
|
|
126
111
|
overlay.style.cssText =
|
|
127
112
|
"position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;" +
|
|
128
|
-
"justify-content:center;background
|
|
113
|
+
"justify-content:center;background:var(--b44-bg);color:var(--b44-fg);" +
|
|
129
114
|
"font-family:ui-sans-serif,system-ui,sans-serif;text-align:center;padding:2rem;";
|
|
130
115
|
const title = document.createElement("h1");
|
|
131
116
|
title.textContent = "Session ended";
|
|
132
117
|
title.style.cssText = "font-size:1.5rem;font-weight:700;margin:0 0 .75rem;";
|
|
133
118
|
const body = document.createElement("p");
|
|
134
|
-
body.textContent = "
|
|
135
|
-
body.style.cssText = "margin:0;color
|
|
119
|
+
body.textContent = "Reload this page in your browser to start a new session.";
|
|
120
|
+
body.style.cssText = "margin:0;color:var(--b44-muted);";
|
|
136
121
|
const card = document.createElement("div");
|
|
137
122
|
card.append(title, body);
|
|
138
|
-
overlay.append(card);
|
|
123
|
+
overlay.append(style, card);
|
|
139
124
|
document.body.append(overlay);
|
|
140
125
|
}
|
|
@@ -33,11 +33,13 @@ export interface FetchWithAuthInit extends RequestInit {
|
|
|
33
33
|
* has none, so nothing is sent.
|
|
34
34
|
* @internal
|
|
35
35
|
*/
|
|
36
|
-
export declare function createFetchWithAuth({ axios, serviceRoleAxios, appId, serverUrl, functionsVersion, platformHeaders, }: {
|
|
36
|
+
export declare function createFetchWithAuth({ axios, serviceRoleAxios, appId, serverUrl, functionsVersion, platformHeaders, waitForAuth, }: {
|
|
37
37
|
axios: AxiosInstance;
|
|
38
38
|
serviceRoleAxios: AxiosInstance;
|
|
39
39
|
appId: string;
|
|
40
40
|
serverUrl: string;
|
|
41
41
|
functionsVersion?: string;
|
|
42
42
|
platformHeaders?: Record<string, string>;
|
|
43
|
+
/** Resolves once the client's session is settled. */
|
|
44
|
+
waitForAuth?: () => Promise<void>;
|
|
43
45
|
}): (path: string, init?: FetchWithAuthInit) => Promise<Response>;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* has none, so nothing is sent.
|
|
22
22
|
* @internal
|
|
23
23
|
*/
|
|
24
|
-
export function createFetchWithAuth({ axios, serviceRoleAxios, appId, serverUrl, functionsVersion, platformHeaders, }) {
|
|
24
|
+
export function createFetchWithAuth({ axios, serviceRoleAxios, appId, serverUrl, functionsVersion, platformHeaders, waitForAuth, }) {
|
|
25
25
|
const inherited = new Headers(platformHeaders);
|
|
26
26
|
const bearer = (client) => {
|
|
27
27
|
const header = client.defaults.headers.common["Authorization"];
|
|
@@ -31,6 +31,9 @@ export function createFetchWithAuth({ axios, serviceRoleAxios, appId, serverUrl,
|
|
|
31
31
|
};
|
|
32
32
|
return async function fetchWithAuth(path, init = {}) {
|
|
33
33
|
assertOwnOriginPath(path);
|
|
34
|
+
// The Authorization below is read off the axios defaults, which a session
|
|
35
|
+
// still being negotiated has not written yet.
|
|
36
|
+
await (waitForAuth === null || waitForAuth === void 0 ? void 0 : waitForAuth());
|
|
34
37
|
const { fetch: transport = fetch, ...requestInit } = init;
|
|
35
38
|
const headers = new Headers(init.headers);
|
|
36
39
|
// A caller-supplied value always wins, so a route can hand the callee a
|
|
@@ -4,9 +4,8 @@ export interface RoomsSocketConfig {
|
|
|
4
4
|
mountPath: string;
|
|
5
5
|
transports: string[];
|
|
6
6
|
appId: string;
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
token?: string | null;
|
|
7
|
+
/** Asked on every connect, so the socket always carries the current session. */
|
|
8
|
+
getToken: () => string | null;
|
|
10
9
|
}
|
|
11
10
|
export type TSocketRoom = string;
|
|
12
11
|
export type TJsonStr = string;
|
|
@@ -42,7 +41,7 @@ export declare function RoomsSocket({ config }: {
|
|
|
42
41
|
leave: (room: string) => void;
|
|
43
42
|
}>;
|
|
44
43
|
subscribeToRoom: (room: TSocketRoom, handlers: Partial<{ [k in TEvent]: THandler<k>; }>) => () => void;
|
|
45
|
-
|
|
44
|
+
reconnect: () => void;
|
|
46
45
|
updateModel: (room: string, data: any) => Promise<void>;
|
|
47
46
|
disconnect: () => void;
|
|
48
47
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { io } from "socket.io-client";
|
|
2
|
-
import { getAccessToken } from "./auth-utils.js";
|
|
3
2
|
import { getAnalyticsSessionId } from "../modules/analytics.js";
|
|
4
3
|
const ROOM_LEAVE_GRACE_MS = 250;
|
|
5
4
|
function initializeSocket(config, handlers) {
|
|
@@ -7,7 +6,7 @@ function initializeSocket(config, handlers) {
|
|
|
7
6
|
// handshake so the backend can verify room access for anonymous agent
|
|
8
7
|
// conversations (mirrors the X-Base44-Anonymous-Id HTTP header). Authenticated
|
|
9
8
|
// clients are identified by their token instead.
|
|
10
|
-
const resolvedToken = config.
|
|
9
|
+
const resolvedToken = config.getToken();
|
|
11
10
|
const query = {
|
|
12
11
|
app_id: config.appId,
|
|
13
12
|
token: resolvedToken,
|
|
@@ -41,7 +40,6 @@ function initializeSocket(config, handlers) {
|
|
|
41
40
|
return socket;
|
|
42
41
|
}
|
|
43
42
|
export function RoomsSocket({ config }) {
|
|
44
|
-
let currentConfig = { ...config };
|
|
45
43
|
const roomsToListeners = {};
|
|
46
44
|
const pendingRoomLeaves = {};
|
|
47
45
|
const handlers = {
|
|
@@ -83,13 +81,10 @@ export function RoomsSocket({ config }) {
|
|
|
83
81
|
socket.disconnect();
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
|
-
|
|
84
|
+
/** Drops the connection and opens a new one with the current token. */
|
|
85
|
+
function reconnect() {
|
|
87
86
|
cleanup();
|
|
88
|
-
|
|
89
|
-
...currentConfig,
|
|
90
|
-
...config,
|
|
91
|
-
};
|
|
92
|
-
socket = initializeSocket(currentConfig, handlers);
|
|
87
|
+
socket = initializeSocket(config, handlers);
|
|
93
88
|
}
|
|
94
89
|
function joinRoom(room) {
|
|
95
90
|
socket.emit("join", room);
|
|
@@ -162,7 +157,7 @@ export function RoomsSocket({ config }) {
|
|
|
162
157
|
return {
|
|
163
158
|
socket,
|
|
164
159
|
subscribeToRoom,
|
|
165
|
-
|
|
160
|
+
reconnect,
|
|
166
161
|
updateModel,
|
|
167
162
|
disconnect,
|
|
168
163
|
};
|