@base44-preview/sdk 0.8.48-pr.282.081f33c → 0.8.48-pr.282.094dd5b
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 +48 -73
- package/dist/client.types.d.ts +0 -12
- package/dist/modules/actors.d.ts +3 -2
- package/dist/modules/agents.types.d.ts +7 -3
- package/dist/modules/auth.js +12 -15
- package/dist/modules/auth.types.d.ts +9 -15
- package/dist/utils/embed-session.d.ts +13 -41
- package/dist/utils/embed-session.js +17 -82
- package/dist/utils/fetch-with-auth.d.ts +0 -1
- package/dist/utils/socket-utils.d.ts +0 -1
- 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, 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";
|
|
@@ -58,19 +58,31 @@ export function createClient(config) {
|
|
|
58
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 embedOtt = isFramed() ? urlOtt : null;
|
|
67
|
-
if (embedOtt) {
|
|
68
|
-
markEmbeddedTab(appId);
|
|
61
|
+
const embedOtt = takeEmbedTokenFromUrl();
|
|
62
|
+
// A declaration, not a const: this block sits above the auth module.
|
|
63
|
+
function getToken() {
|
|
64
|
+
var _a;
|
|
65
|
+
return (_a = userAuthModule.getToken()) !== null && _a !== void 0 ? _a : (embedOtt ? null : getAccessToken());
|
|
69
66
|
}
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
const socketConfig = {
|
|
68
|
+
serverUrl,
|
|
69
|
+
mountPath: "/ws-user-apps/socket.io/",
|
|
70
|
+
transports: ["websocket"],
|
|
71
|
+
appId,
|
|
72
|
+
getToken,
|
|
73
|
+
};
|
|
74
|
+
let socket = null;
|
|
75
|
+
const getSocket = () => {
|
|
76
|
+
if (!socket) {
|
|
77
|
+
socket = RoomsSocket({
|
|
78
|
+
config: socketConfig,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return socket;
|
|
82
|
+
};
|
|
83
|
+
// Apps pass getAccessToken() in as `token`, which in a frame is the OTT —
|
|
84
|
+
// what the exchange trades for a session, never a bearer itself.
|
|
85
|
+
const token = embedOtt ? undefined : config.token;
|
|
74
86
|
const headers = {
|
|
75
87
|
...optionalHeaders,
|
|
76
88
|
"X-App-Id": String(appId),
|
|
@@ -123,53 +135,35 @@ export function createClient(config) {
|
|
|
123
135
|
appBaseUrl: normalizedAppBaseUrl,
|
|
124
136
|
serverUrl,
|
|
125
137
|
token,
|
|
126
|
-
embedded,
|
|
127
|
-
|
|
138
|
+
embedded: Boolean(embedOtt),
|
|
139
|
+
// The socket carries its token on the handshake, so it can only pick a
|
|
140
|
+
// new one up by redialling — or, on logout, by dropping what it has.
|
|
141
|
+
onSessionChange: (hasSession) => hasSession ? socket === null || socket === void 0 ? void 0 : socket.reconnect() : socket === null || socket === void 0 ? void 0 : socket.disconnect(),
|
|
128
142
|
});
|
|
129
143
|
// Apply the access token before any module that may issue authenticated
|
|
130
144
|
// requests during construction (notably analytics, which fires an init
|
|
131
145
|
// event whose flush calls auth.me()). Without this, the first User/me
|
|
132
146
|
// request is built before setToken runs and goes out unauthenticated.
|
|
133
|
-
//
|
|
134
|
-
if (typeof window !== "undefined" && !
|
|
147
|
+
// Not in a frame: a stored token there belongs to an earlier visitor.
|
|
148
|
+
if (typeof window !== "undefined" && !embedOtt) {
|
|
135
149
|
const accessToken = token || getAccessToken();
|
|
136
150
|
if (accessToken) {
|
|
137
151
|
userAuthModule.setToken(accessToken);
|
|
138
152
|
}
|
|
139
153
|
}
|
|
140
|
-
|
|
141
|
-
const getToken = () => { var _a; return (_a = userAuthModule.getToken()) !== null && _a !== void 0 ? _a : (embedded ? null : getAccessToken()); };
|
|
142
|
-
const socketConfig = {
|
|
143
|
-
serverUrl,
|
|
144
|
-
mountPath: "/ws-user-apps/socket.io/",
|
|
145
|
-
transports: ["websocket"],
|
|
146
|
-
appId,
|
|
147
|
-
getToken,
|
|
148
|
-
};
|
|
149
|
-
let socket = null;
|
|
150
|
-
const getSocket = () => {
|
|
151
|
-
if (!socket) {
|
|
152
|
-
socket = RoomsSocket({ config: socketConfig });
|
|
153
|
-
}
|
|
154
|
-
return socket;
|
|
155
|
-
};
|
|
156
|
-
const applyToken = (newToken, saveToStorage) => {
|
|
157
|
-
userAuthModule.setToken(newToken, saveToStorage);
|
|
158
|
-
socket === null || socket === void 0 ? void 0 : socket.reconnect();
|
|
159
|
-
};
|
|
160
|
-
// Settles once the exchanged session is applied (memory only); at once
|
|
161
|
-
// otherwise. Never rejects: every request waits on it, so a failure here
|
|
162
|
-
// must not turn into a rejection on each of them.
|
|
163
|
-
const authReady = embedOtt
|
|
154
|
+
const session = embedOtt
|
|
164
155
|
? exchangeEmbedToken({ serverUrl, appId, ott: embedOtt })
|
|
156
|
+
: null;
|
|
157
|
+
// Never rejects: every request waits on it, so one failure here must not
|
|
158
|
+
// become a rejection on each of them.
|
|
159
|
+
const authReady = session
|
|
160
|
+
? session
|
|
165
161
|
.then((sessionToken) => {
|
|
166
162
|
var _a;
|
|
167
163
|
if (sessionToken) {
|
|
168
|
-
|
|
164
|
+
userAuthModule.setToken(sessionToken, false);
|
|
169
165
|
return;
|
|
170
166
|
}
|
|
171
|
-
// The app is about to run anonymous. Say why, once, instead of
|
|
172
|
-
// leaving only the 401s that follow.
|
|
173
167
|
const error = new Error("Base44: the embed token was refused, so this app is not signed in.");
|
|
174
168
|
console.error(error.message);
|
|
175
169
|
(_a = options === null || options === void 0 ? void 0 : options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
|
|
@@ -178,24 +172,9 @@ export function createClient(config) {
|
|
|
178
172
|
console.error("Base44: applying the embedded session failed:", e);
|
|
179
173
|
})
|
|
180
174
|
: Promise.resolve();
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// instead of failing quietly.
|
|
185
|
-
let warnedEarlyRead = false;
|
|
186
|
-
const getTokenNow = () => {
|
|
187
|
-
const sessionToken = getToken();
|
|
188
|
-
if (sessionToken === null && embedOtt && !warnedEarlyRead) {
|
|
189
|
-
warnedEarlyRead = true;
|
|
190
|
-
console.warn("Base44: read a token before the embedded session was ready, so it is " +
|
|
191
|
-
"empty. Await a call such as base44.auth.me() before building a " +
|
|
192
|
-
"client or a URL that keeps the token.");
|
|
193
|
-
}
|
|
194
|
-
return sessionToken;
|
|
195
|
-
};
|
|
196
|
-
if (embedOtt) {
|
|
197
|
-
// Requests issued during the exchange wait for it. Registered after createAxiosClient's
|
|
198
|
-
// interceptors so it runs first and the anonymous-visitor header sees the Authorization.
|
|
175
|
+
if (session) {
|
|
176
|
+
// Registered after createAxiosClient's so it runs first (axios unshifts),
|
|
177
|
+
// letting the anonymous-visitor header see the Authorization we just set.
|
|
199
178
|
for (const client of [axiosClient, functionsAxiosClient]) {
|
|
200
179
|
client.interceptors.request.use(async (requestConfig) => {
|
|
201
180
|
await authReady;
|
|
@@ -260,13 +239,10 @@ export function createClient(config) {
|
|
|
260
239
|
getSocket,
|
|
261
240
|
appId,
|
|
262
241
|
serverUrl,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
aiGateway: createAiGatewayModule({
|
|
266
|
-
serverUrl,
|
|
267
|
-
getToken: getTokenNow,
|
|
268
|
-
appId,
|
|
242
|
+
// Sync, unlike everything else: these return a URL, not a promise.
|
|
243
|
+
getToken,
|
|
269
244
|
}),
|
|
245
|
+
aiGateway: createAiGatewayModule({ serverUrl, getToken, appId }),
|
|
270
246
|
appLogs: createAppLogsModule(axiosClient, appId),
|
|
271
247
|
app: createAppModule(axiosClient, appId),
|
|
272
248
|
users: createUsersModule(axiosClient, appId),
|
|
@@ -311,10 +287,9 @@ export function createClient(config) {
|
|
|
311
287
|
getSocket,
|
|
312
288
|
appId,
|
|
313
289
|
serverUrl,
|
|
314
|
-
// The user's token,
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
getToken: getTokenNow,
|
|
290
|
+
// The user's token, deliberately: this is read only for the `?token=` on
|
|
291
|
+
// a channel URL handed to that user. Never the service credential.
|
|
292
|
+
getToken,
|
|
318
293
|
}),
|
|
319
294
|
aiGateway: createAiGatewayModule({
|
|
320
295
|
serverUrl,
|
|
@@ -373,7 +348,7 @@ export function createClient(config) {
|
|
|
373
348
|
* ```
|
|
374
349
|
*/
|
|
375
350
|
setToken(newToken) {
|
|
376
|
-
|
|
351
|
+
userAuthModule.setToken(newToken, true);
|
|
377
352
|
},
|
|
378
353
|
/**
|
|
379
354
|
* Gets the current client configuration.
|
package/dist/client.types.d.ts
CHANGED
|
@@ -22,18 +22,6 @@ export interface CreateClientOptions {
|
|
|
22
22
|
* are usually {@linkcode Base44Error} instances — check `error.status`.
|
|
23
23
|
*/
|
|
24
24
|
onError?: (error: Error) => void;
|
|
25
|
-
/**
|
|
26
|
-
* Called when an embedded app's session ends, instead of the SDK's built-in
|
|
27
|
-
* notice.
|
|
28
|
-
*
|
|
29
|
-
* A platform-embedded app holds its session in memory and only the platform
|
|
30
|
-
* can mint the next one, so there is no login page to send the visitor to.
|
|
31
|
-
* When {@link AuthModule.redirectToLogin | redirectToLogin()} or
|
|
32
|
-
* {@link AuthModule.logout | logout()} is reached in that state the SDK
|
|
33
|
-
* covers the page with a plain "session ended" notice; pass this to render
|
|
34
|
-
* your own instead. See {@link AuthModule.isEmbedded | auth.isEmbedded()}.
|
|
35
|
-
*/
|
|
36
|
-
onEmbedSessionEnded?: () => void;
|
|
37
25
|
/**
|
|
38
26
|
* Forces the actors transport. `"auto"` (default) connects directly to the
|
|
39
27
|
* actor and falls back to the platform proxy when the app's actors don't
|
package/dist/modules/actors.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ interface ActorsConfig {
|
|
|
11
11
|
appId: string;
|
|
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
|
-
* anonymous connects omit it.
|
|
15
|
-
|
|
14
|
+
* anonymous connects omit it. Awaited per dial, so a session still being
|
|
15
|
+
* exchanged is in hand before the URL is built. */
|
|
16
|
+
getAuthToken(): Promise<string | null | undefined>;
|
|
16
17
|
/** Same semantics as function calls: editors with a non-prod version get the
|
|
17
18
|
* draft actor script; everyone else gets the published one. */
|
|
18
19
|
functionsVersion?: string;
|
|
@@ -161,7 +161,7 @@ export interface AgentsModuleConfig {
|
|
|
161
161
|
/** Server URL */
|
|
162
162
|
serverUrl?: string;
|
|
163
163
|
/** Returns the current authentication token, if any */
|
|
164
|
-
getToken: () => string | null
|
|
164
|
+
getToken: () => string | null;
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
167
|
* Agents module for managing AI agent conversations.
|
|
@@ -360,7 +360,9 @@ export interface AgentsModule {
|
|
|
360
360
|
* Gets WhatsApp connection URL for an agent.
|
|
361
361
|
*
|
|
362
362
|
* Generates a URL that users can use to connect with the agent through WhatsApp.
|
|
363
|
-
* The URL includes authentication if a token is available.
|
|
363
|
+
* The URL includes authentication if a token is available. In an app a
|
|
364
|
+
* platform has embedded, that is only once the session has been exchanged —
|
|
365
|
+
* await a call such as `base44.auth.me()` before building the URL.
|
|
364
366
|
*
|
|
365
367
|
* @param agentName - The name of the agent.
|
|
366
368
|
* @returns WhatsApp connection URL.
|
|
@@ -378,7 +380,9 @@ export interface AgentsModule {
|
|
|
378
380
|
* Gets Telegram connection URL for an agent.
|
|
379
381
|
*
|
|
380
382
|
* Generates a URL that users can use to connect with the agent through Telegram.
|
|
381
|
-
* The URL includes authentication if a token is available.
|
|
383
|
+
* The URL includes authentication if a token is available. In an app a
|
|
384
|
+
* platform has embedded, that is only once the session has been exchanged —
|
|
385
|
+
* await a call such as `base44.auth.me()` before building the URL. When the user opens
|
|
382
386
|
* this URL, they are redirected to the agent's Telegram bot with an activation
|
|
383
387
|
* code that securely links their account.
|
|
384
388
|
*
|
package/dist/modules/auth.js
CHANGED
|
@@ -83,17 +83,6 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
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
85
|
let accessToken = options.token || null;
|
|
86
|
-
// Where an embedded app goes when it has no session: nowhere. Only the host
|
|
87
|
-
// platform can mint the next one — a login here would sign the visitor in as
|
|
88
|
-
// an app user, a different identity from the platform user the frame is for.
|
|
89
|
-
// The app's own notice wins when it passed one.
|
|
90
|
-
const endEmbeddedSession = () => {
|
|
91
|
-
if (options.onEmbedSessionEnded) {
|
|
92
|
-
options.onEmbedSessionEnded();
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
showEmbedSessionEnded();
|
|
96
|
-
};
|
|
97
86
|
return {
|
|
98
87
|
hasToken() {
|
|
99
88
|
return accessToken !== null;
|
|
@@ -130,9 +119,10 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
130
119
|
// Only the host platform can sign a platform user in, so there is no
|
|
131
120
|
// login page to send them to. (The app's own login would work in the
|
|
132
121
|
// frame — `loginWithProvider` opens a popup — but it would mint a
|
|
133
|
-
// different, app-level identity.)
|
|
122
|
+
// different, app-level identity.) An app that wants its own notice
|
|
123
|
+
// checks `isEmbedded()` rather than asking for a login it cannot get.
|
|
134
124
|
if (options.embedded) {
|
|
135
|
-
|
|
125
|
+
showEmbedSessionEnded();
|
|
136
126
|
return;
|
|
137
127
|
}
|
|
138
128
|
// If nextUrl is not provided, use the current URL
|
|
@@ -171,13 +161,18 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
171
161
|
},
|
|
172
162
|
// Logout the current user
|
|
173
163
|
logout(redirectUrl) {
|
|
174
|
-
|
|
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.
|
|
175
168
|
delete axios.defaults.headers.common["Authorization"];
|
|
169
|
+
delete functionsAxiosClient.defaults.headers.common["Authorization"];
|
|
176
170
|
// Drop identity resolved under the previous session: a `me()` already in
|
|
177
171
|
// flight would otherwise resolve into callers that run after the logout.
|
|
178
172
|
clearPendingMe();
|
|
179
173
|
resetAnalyticsSessionContext();
|
|
180
174
|
accessToken = null;
|
|
175
|
+
(_a = options.onSessionChange) === null || _a === void 0 ? void 0 : _a.call(options, false);
|
|
181
176
|
// Only do the rest if in a browser environment
|
|
182
177
|
if (typeof window !== "undefined") {
|
|
183
178
|
// Remove token from localStorage
|
|
@@ -195,7 +190,7 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
195
190
|
// memory — and navigating a third-party frame to the logout endpoint
|
|
196
191
|
// would only break the frame. The state above is already cleared.
|
|
197
192
|
if (options.embedded) {
|
|
198
|
-
|
|
193
|
+
showEmbedSessionEnded();
|
|
199
194
|
return;
|
|
200
195
|
}
|
|
201
196
|
// Determine the from_url parameter
|
|
@@ -207,6 +202,7 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
207
202
|
},
|
|
208
203
|
// Set authentication token
|
|
209
204
|
setToken(token, saveToStorage = true) {
|
|
205
|
+
var _a;
|
|
210
206
|
if (!token)
|
|
211
207
|
return;
|
|
212
208
|
// An embedded session belongs to the frame the platform minted it for.
|
|
@@ -221,6 +217,7 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
|
221
217
|
// handle token change for axios clients
|
|
222
218
|
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
223
219
|
functionsAxiosClient.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
220
|
+
(_a = options.onSessionChange) === null || _a === void 0 ? void 0 : _a.call(options, true);
|
|
224
221
|
// Save token to localStorage if requested
|
|
225
222
|
if (persist && typeof window !== "undefined" && window.localStorage) {
|
|
226
223
|
try {
|
|
@@ -98,16 +98,13 @@ export interface AuthModuleOptions {
|
|
|
98
98
|
* which is how the server-side SDK reports a token it never sets explicitly.
|
|
99
99
|
*/
|
|
100
100
|
token?: string;
|
|
101
|
-
/**
|
|
102
|
-
* Whether this client runs embedded in a host platform's frame, where a
|
|
103
|
-
* session is minted by the platform and cannot be renewed by a login redirect.
|
|
104
|
-
*/
|
|
101
|
+
/** Whether a host platform embedded this client in a frame. */
|
|
105
102
|
embedded?: boolean;
|
|
106
103
|
/**
|
|
107
|
-
* Called
|
|
108
|
-
*
|
|
104
|
+
* Called when the identity changes: `true` on `setToken`, `false` on
|
|
105
|
+
* `logout`. Lets the client redial the socket, which holds its own copy.
|
|
109
106
|
*/
|
|
110
|
-
|
|
107
|
+
onSessionChange?: (hasSession: boolean) => void;
|
|
111
108
|
}
|
|
112
109
|
/**
|
|
113
110
|
* 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.
|
|
@@ -190,13 +187,13 @@ export interface AuthModule {
|
|
|
190
187
|
*/
|
|
191
188
|
redirectToLogin(nextUrl: string): void;
|
|
192
189
|
/**
|
|
193
|
-
* Whether
|
|
190
|
+
* Whether a host platform embedded this app and signed its user in.
|
|
194
191
|
*
|
|
195
|
-
*
|
|
192
|
+
* Only the platform can renew that session, so {@linkcode AuthModule.redirectToLogin | redirectToLogin()} and {@linkcode AuthModule.logout | logout()} show a "session ended" notice rather than a login page. Use this to render your own notice, or to hide sign-in controls that cannot work in the frame.
|
|
196
193
|
*
|
|
197
|
-
*
|
|
194
|
+
* The session lives in memory, so a reload inside the frame ends it and returns `false` here. Prefer client-side navigation.
|
|
198
195
|
*
|
|
199
|
-
* @returns `true` when
|
|
196
|
+
* @returns `true` when a host platform embedded this app.
|
|
200
197
|
*
|
|
201
198
|
* @example
|
|
202
199
|
* ```typescript
|
|
@@ -567,9 +564,6 @@ export interface InternalAuthModule extends AuthModule {
|
|
|
567
564
|
* could not succeed without a session, not to decide that one is valid.
|
|
568
565
|
*/
|
|
569
566
|
hasToken(): boolean;
|
|
570
|
-
/**
|
|
571
|
-
* The access token currently set on the client, or `null` when there is none.
|
|
572
|
-
* Follows {@linkcode AuthModule.setToken | setToken} and {@linkcode AuthModule.logout | logout}.
|
|
573
|
-
*/
|
|
567
|
+
/** The token currently set on the client, or `null`. */
|
|
574
568
|
getToken(): string | null;
|
|
575
569
|
}
|
|
@@ -7,57 +7,29 @@
|
|
|
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
|
-
|
|
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
|
+
* Taking the token off the URL is unconditional — a one-time token must not be
|
|
11
|
+
* left in the address bar, in history, or in a shared link — but it is only
|
|
12
|
+
* reported back inside a frame, the only place one is redeemed. It is gone once
|
|
13
|
+
* the first client has taken it, so the session belongs to that client — an app
|
|
14
|
+
* creates one.
|
|
17
15
|
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Whether this document is rendered inside a frame. Read at call time, not at
|
|
23
|
-
* import: a module-level answer would be fixed before a host (or a test) has
|
|
24
|
-
* set the window up.
|
|
16
|
+
* The exchange endpoint is rate limited per app, and its limiter refuses before
|
|
17
|
+
* the one-time token is redeemed — so a 429 leaves the token still valid and is
|
|
18
|
+
* worth retrying once.
|
|
25
19
|
*
|
|
26
20
|
* @internal
|
|
27
21
|
*/
|
|
22
|
+
export declare const EMBED_TOKEN_PARAM = "ott";
|
|
23
|
+
/** @internal */
|
|
24
|
+
export declare function takeEmbedTokenFromUrl(): string | null;
|
|
25
|
+
/** @internal */
|
|
28
26
|
export declare function isFramed(): boolean;
|
|
29
|
-
/**
|
|
30
|
-
* Whether this tab previously redeemed an embed token for this app. Counts only
|
|
31
|
-
* inside a frame: a top-level tab that once carried a token must fall back to
|
|
32
|
-
* the regular login, or it could never sign in again.
|
|
33
|
-
*
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
36
|
-
export declare function isEmbeddedTab(appId: string): boolean;
|
|
37
27
|
/** @internal */
|
|
38
|
-
export declare function markEmbeddedTab(appId: string): void;
|
|
39
|
-
/**
|
|
40
|
-
* Trades a one-time embed token for an app-user session token. Resolves to
|
|
41
|
-
* `null` when the platform refuses the token; never throws.
|
|
42
|
-
*
|
|
43
|
-
* @internal
|
|
44
|
-
*/
|
|
45
28
|
export declare function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl, }: {
|
|
46
29
|
serverUrl: string;
|
|
47
30
|
appId: string;
|
|
48
31
|
ott: string;
|
|
49
32
|
fetchImpl?: typeof fetch;
|
|
50
33
|
}): Promise<string | null>;
|
|
51
|
-
/**
|
|
52
|
-
* Covers the page with a plain "session ended" notice. An embedded session can
|
|
53
|
-
* only be renewed by the host platform, so this stands in for the login
|
|
54
|
-
* redirect: only the platform can mint the next session. The copy points at the
|
|
55
|
-
* host page: reloading the frame itself carries no token and lands here again.
|
|
56
|
-
*
|
|
57
|
-
* This is the default an app that predates embedding gets. An app that wants its
|
|
58
|
-
* own can pass `options.onEmbedSessionEnded` to {@linkcode createClient}, or
|
|
59
|
-
* check {@linkcode AuthModule.isEmbedded} before asking for a login.
|
|
60
|
-
*
|
|
61
|
-
* @internal
|
|
62
|
-
*/
|
|
34
|
+
/** @internal */
|
|
63
35
|
export declare function showEmbedSessionEnded(): void;
|
|
@@ -7,24 +7,26 @@
|
|
|
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
|
+
* Taking the token off the URL is unconditional — a one-time token must not be
|
|
11
|
+
* left in the address bar, in history, or in a shared link — but it is only
|
|
12
|
+
* reported back inside a frame, the only place one is redeemed. It is gone once
|
|
13
|
+
* the first client has taken it, so the session belongs to that client — an app
|
|
14
|
+
* creates one.
|
|
15
|
+
*
|
|
16
|
+
* The exchange endpoint is rate limited per app, and its limiter refuses before
|
|
17
|
+
* the one-time token is redeemed — so a 429 leaves the token still valid and is
|
|
18
|
+
* worth retrying once.
|
|
19
|
+
*
|
|
10
20
|
* @internal
|
|
11
21
|
*/
|
|
12
|
-
/** The query parameter a host platform puts the one-time token on. @internal */
|
|
13
22
|
export const EMBED_TOKEN_PARAM = "ott";
|
|
14
|
-
const EMBED_TAB_KEY_PREFIX = "base44_embed_session";
|
|
15
|
-
/** Per-app, so a second app framed on the same origin keeps its own answer. */
|
|
16
|
-
const embedTabKey = (appId) => `${EMBED_TAB_KEY_PREFIX}:${appId}`;
|
|
17
23
|
const GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
|
|
18
24
|
const SUBJECT_TOKEN_TYPE = "urn:base44:params:oauth:token-type:embed-ott";
|
|
19
25
|
const RATE_LIMITED = 429;
|
|
20
26
|
const RATE_LIMIT_RETRY_MS = 1000;
|
|
27
|
+
const EXCHANGE_TIMEOUT_MS = 15000;
|
|
21
28
|
const SESSION_ENDED_ELEMENT_ID = "base44-embed-session-ended";
|
|
22
|
-
/**
|
|
23
|
-
* Reads the one-time token off the current URL and strips it, so it is never
|
|
24
|
-
* left in the address bar, in history, or in a shared link.
|
|
25
|
-
*
|
|
26
|
-
* @internal
|
|
27
|
-
*/
|
|
29
|
+
/** @internal */
|
|
28
30
|
export function takeEmbedTokenFromUrl() {
|
|
29
31
|
if (typeof window === "undefined" || !window.location) {
|
|
30
32
|
return null;
|
|
@@ -40,73 +42,20 @@ export function takeEmbedTokenFromUrl() {
|
|
|
40
42
|
window.history.replaceState(window.history.state, "", url.toString());
|
|
41
43
|
}
|
|
42
44
|
catch (e) {
|
|
43
|
-
// The token was read but the URL could not be rewritten (history blocked,
|
|
44
|
-
// a sandboxed frame, replaceState rate-limited). Return it anyway: it is
|
|
45
|
-
// this load's identity, and dropping it here would leave `?ott=` on the
|
|
46
|
-
// URL for getAccessToken to hand back as if it were a session token.
|
|
47
45
|
console.error("Error retrieving embed token from URL:", e);
|
|
48
46
|
}
|
|
49
|
-
return ott;
|
|
47
|
+
return isFramed() ? ott : null;
|
|
50
48
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Whether this document is rendered inside a frame. Read at call time, not at
|
|
53
|
-
* import: a module-level answer would be fixed before a host (or a test) has
|
|
54
|
-
* set the window up.
|
|
55
|
-
*
|
|
56
|
-
* @internal
|
|
57
|
-
*/
|
|
49
|
+
/** @internal */
|
|
58
50
|
export function isFramed() {
|
|
59
|
-
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
try {
|
|
63
|
-
return window.self !== window.top;
|
|
64
|
-
}
|
|
65
|
-
catch (_a) {
|
|
66
|
-
// Reaching the top window can be refused outright; if we cannot see it,
|
|
67
|
-
// we are certainly not it.
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Whether this tab previously redeemed an embed token for this app. Counts only
|
|
73
|
-
* inside a frame: a top-level tab that once carried a token must fall back to
|
|
74
|
-
* the regular login, or it could never sign in again.
|
|
75
|
-
*
|
|
76
|
-
* @internal
|
|
77
|
-
*/
|
|
78
|
-
export function isEmbeddedTab(appId) {
|
|
79
|
-
if (typeof window === "undefined") {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
// A cross-site frame is third-party storage, which some browsers block
|
|
83
|
-
// outright — every access can throw, and the marker is then unavailable.
|
|
84
|
-
try {
|
|
85
|
-
return (isFramed() && window.sessionStorage.getItem(embedTabKey(appId)) === "1");
|
|
86
|
-
}
|
|
87
|
-
catch (_a) {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
51
|
+
return typeof window !== "undefined" && window.self !== window.top;
|
|
90
52
|
}
|
|
91
53
|
/** @internal */
|
|
92
|
-
export function markEmbeddedTab(appId) {
|
|
93
|
-
try {
|
|
94
|
-
window.sessionStorage.setItem(embedTabKey(appId), "1");
|
|
95
|
-
}
|
|
96
|
-
catch (_a) {
|
|
97
|
-
/* storage blocked */
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Trades a one-time embed token for an app-user session token. Resolves to
|
|
102
|
-
* `null` when the platform refuses the token; never throws.
|
|
103
|
-
*
|
|
104
|
-
* @internal
|
|
105
|
-
*/
|
|
106
54
|
export async function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl = fetch, }) {
|
|
107
55
|
const post = () => fetchImpl(`${serverUrl}/api/apps/${appId}/auth/embed/token`, {
|
|
108
56
|
method: "POST",
|
|
109
57
|
headers: { "X-App-Id": String(appId) },
|
|
58
|
+
signal: AbortSignal.timeout(EXCHANGE_TIMEOUT_MS),
|
|
110
59
|
body: new URLSearchParams({
|
|
111
60
|
grant_type: GRANT_TYPE,
|
|
112
61
|
subject_token: ott,
|
|
@@ -115,7 +64,6 @@ export async function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl = fe
|
|
|
115
64
|
});
|
|
116
65
|
try {
|
|
117
66
|
let response = await post();
|
|
118
|
-
// The limiter refuses before the token is redeemed, so it is still valid.
|
|
119
67
|
if (response.status === RATE_LIMITED) {
|
|
120
68
|
await new Promise((resolve) => setTimeout(resolve, RATE_LIMIT_RETRY_MS));
|
|
121
69
|
response = await post();
|
|
@@ -131,18 +79,7 @@ export async function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl = fe
|
|
|
131
79
|
return null;
|
|
132
80
|
}
|
|
133
81
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Covers the page with a plain "session ended" notice. An embedded session can
|
|
136
|
-
* only be renewed by the host platform, so this stands in for the login
|
|
137
|
-
* redirect: only the platform can mint the next session. The copy points at the
|
|
138
|
-
* host page: reloading the frame itself carries no token and lands here again.
|
|
139
|
-
*
|
|
140
|
-
* This is the default an app that predates embedding gets. An app that wants its
|
|
141
|
-
* own can pass `options.onEmbedSessionEnded` to {@linkcode createClient}, or
|
|
142
|
-
* check {@linkcode AuthModule.isEmbedded} before asking for a login.
|
|
143
|
-
*
|
|
144
|
-
* @internal
|
|
145
|
-
*/
|
|
82
|
+
/** @internal */
|
|
146
83
|
export function showEmbedSessionEnded() {
|
|
147
84
|
if (typeof document === "undefined" || !document.body) {
|
|
148
85
|
return;
|
|
@@ -150,8 +87,6 @@ export function showEmbedSessionEnded() {
|
|
|
150
87
|
if (document.getElementById(SESSION_ENDED_ELEMENT_ID)) {
|
|
151
88
|
return;
|
|
152
89
|
}
|
|
153
|
-
// Colors as custom properties in a stylesheet rather than inline, so the
|
|
154
|
-
// notice follows the reader's theme — including a change made while it is up.
|
|
155
90
|
const style = document.createElement("style");
|
|
156
91
|
style.textContent =
|
|
157
92
|
`#${SESSION_ENDED_ELEMENT_ID}{--b44-bg:#fff;--b44-fg:#0f172a;--b44-muted:#475569;}` +
|
|
@@ -40,6 +40,5 @@ export declare function createFetchWithAuth({ axios, serviceRoleAxios, appId, se
|
|
|
40
40
|
serverUrl: string;
|
|
41
41
|
functionsVersion?: string;
|
|
42
42
|
platformHeaders?: Record<string, string>;
|
|
43
|
-
/** Resolves once the client's session is settled. */
|
|
44
43
|
waitForAuth?: () => Promise<void>;
|
|
45
44
|
}): (path: string, init?: FetchWithAuthInit) => Promise<Response>;
|