@base44-preview/sdk 0.8.48-pr.282.6ea0687 → 0.8.48-pr.282.7c5a459
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 +42 -36
- package/dist/modules/actors.d.ts +2 -3
- package/dist/modules/agents.types.d.ts +3 -7
- package/dist/modules/auth.types.d.ts +16 -8
- package/dist/utils/embed-session.d.ts +3 -3
- package/dist/utils/embed-session.js +15 -3
- package/dist/utils/fetch-with-auth.d.ts +1 -0
- package/dist/utils/socket-utils.d.ts +1 -0
- 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, takeEmbedTokenFromUrl, } from "./utils/embed-session.js";
|
|
8
|
+
import { exchangeEmbedToken, frameSession, 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";
|
|
@@ -59,29 +59,7 @@ export function createClient(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
61
|
const embedOtt = takeEmbedTokenFromUrl();
|
|
62
|
-
//
|
|
63
|
-
function getToken() {
|
|
64
|
-
var _a;
|
|
65
|
-
return (_a = userAuthModule.getToken()) !== null && _a !== void 0 ? _a : (embedOtt ? null : getAccessToken());
|
|
66
|
-
}
|
|
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.
|
|
62
|
+
// In a frame the caller's token is the OTT itself, which is not a session.
|
|
85
63
|
const token = embedOtt ? undefined : config.token;
|
|
86
64
|
const headers = {
|
|
87
65
|
...optionalHeaders,
|
|
@@ -131,31 +109,55 @@ export function createClient(config) {
|
|
|
131
109
|
baseURL: `${serverUrl}/api`,
|
|
132
110
|
headers,
|
|
133
111
|
});
|
|
112
|
+
// Declared before the auth module so `onSessionChange` can reach it; the
|
|
113
|
+
// socket itself is still only built on first use, below.
|
|
114
|
+
let socket = null;
|
|
134
115
|
const userAuthModule = createAuthModule(axiosClient, functionsAxiosClient, appId, {
|
|
135
116
|
appBaseUrl: normalizedAppBaseUrl,
|
|
136
117
|
serverUrl,
|
|
137
118
|
token,
|
|
138
119
|
embedded: Boolean(embedOtt),
|
|
139
|
-
// The socket carries its token on the handshake, so
|
|
140
|
-
//
|
|
120
|
+
// The socket carries its token on the handshake, so an identity change
|
|
121
|
+
// only reaches it by opening a new connection — or, on logout, by
|
|
122
|
+
// dropping the one still running as the user who just left.
|
|
141
123
|
onSessionChange: (hasSession) => hasSession ? socket === null || socket === void 0 ? void 0 : socket.reconnect() : socket === null || socket === void 0 ? void 0 : socket.disconnect(),
|
|
142
124
|
});
|
|
143
125
|
// Apply the access token before any module that may issue authenticated
|
|
144
126
|
// requests during construction (notably analytics, which fires an init
|
|
145
127
|
// event whose flush calls auth.me()). Without this, the first User/me
|
|
146
128
|
// request is built before setToken runs and goes out unauthenticated.
|
|
147
|
-
// Not in a frame: a stored token there belongs to an earlier visitor
|
|
129
|
+
// Not in a frame: a stored token there belongs to an earlier visitor, not to
|
|
130
|
+
// this session, which only the exchange below can produce.
|
|
148
131
|
if (typeof window !== "undefined" && !embedOtt) {
|
|
149
132
|
const accessToken = token || getAccessToken();
|
|
150
133
|
if (accessToken) {
|
|
151
134
|
userAuthModule.setToken(accessToken);
|
|
152
135
|
}
|
|
153
136
|
}
|
|
137
|
+
// Live token for every module; outside a frame it still falls back to storage.
|
|
138
|
+
const getToken = () => { var _a; return (_a = userAuthModule.getToken()) !== null && _a !== void 0 ? _a : (embedOtt ? null : getAccessToken()); };
|
|
139
|
+
const socketConfig = {
|
|
140
|
+
serverUrl,
|
|
141
|
+
mountPath: "/ws-user-apps/socket.io/",
|
|
142
|
+
transports: ["websocket"],
|
|
143
|
+
appId,
|
|
144
|
+
getToken,
|
|
145
|
+
};
|
|
146
|
+
const getSocket = () => {
|
|
147
|
+
if (!socket) {
|
|
148
|
+
socket = RoomsSocket({ config: socketConfig });
|
|
149
|
+
}
|
|
150
|
+
return socket;
|
|
151
|
+
};
|
|
152
|
+
// The exchange belongs to the frame, not to one client: the token is off the
|
|
153
|
+
// URL after the first `createClient`, so a second one in the same document
|
|
154
|
+
// joins the session already being negotiated rather than going anonymous.
|
|
154
155
|
const session = embedOtt
|
|
155
|
-
? exchangeEmbedToken({ serverUrl, appId, ott: embedOtt })
|
|
156
|
-
:
|
|
157
|
-
//
|
|
158
|
-
//
|
|
156
|
+
? frameSession(appId, () => exchangeEmbedToken({ serverUrl, appId, ott: embedOtt }))
|
|
157
|
+
: frameSession(appId);
|
|
158
|
+
// Settles once the exchanged session is applied (memory only); at once
|
|
159
|
+
// otherwise. Never rejects: every request waits on it, so a failure here
|
|
160
|
+
// must not turn into a rejection on each of them.
|
|
159
161
|
const authReady = session
|
|
160
162
|
? session
|
|
161
163
|
.then((sessionToken) => {
|
|
@@ -164,6 +166,8 @@ export function createClient(config) {
|
|
|
164
166
|
userAuthModule.setToken(sessionToken, false);
|
|
165
167
|
return;
|
|
166
168
|
}
|
|
169
|
+
// The app is about to run anonymous. Say why, once, instead of
|
|
170
|
+
// leaving only the 401s that follow.
|
|
167
171
|
const error = new Error("Base44: the embed token was refused, so this app is not signed in.");
|
|
168
172
|
console.error(error.message);
|
|
169
173
|
(_a = options === null || options === void 0 ? void 0 : options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
|
|
@@ -173,8 +177,8 @@ export function createClient(config) {
|
|
|
173
177
|
})
|
|
174
178
|
: Promise.resolve();
|
|
175
179
|
if (session) {
|
|
176
|
-
//
|
|
177
|
-
//
|
|
180
|
+
// Requests issued during the exchange wait for it. Registered after createAxiosClient's
|
|
181
|
+
// interceptors so it runs first and the anonymous-visitor header sees the Authorization.
|
|
178
182
|
for (const client of [axiosClient, functionsAxiosClient]) {
|
|
179
183
|
client.interceptors.request.use(async (requestConfig) => {
|
|
180
184
|
await authReady;
|
|
@@ -239,7 +243,8 @@ export function createClient(config) {
|
|
|
239
243
|
getSocket,
|
|
240
244
|
appId,
|
|
241
245
|
serverUrl,
|
|
242
|
-
//
|
|
246
|
+
// Read synchronously, unlike everything else: these build a URL rather
|
|
247
|
+
// than issue a request, so during the exchange they see no token.
|
|
243
248
|
getToken,
|
|
244
249
|
}),
|
|
245
250
|
aiGateway: createAiGatewayModule({ serverUrl, getToken, appId }),
|
|
@@ -287,8 +292,9 @@ export function createClient(config) {
|
|
|
287
292
|
getSocket,
|
|
288
293
|
appId,
|
|
289
294
|
serverUrl,
|
|
290
|
-
// The user's token,
|
|
291
|
-
// a channel URL handed to that user
|
|
295
|
+
// The user's token, as on the user-scoped module: the only thing this is
|
|
296
|
+
// read for is the `?token=` on a channel URL handed to that user, which
|
|
297
|
+
// the app's service credential must never end up in.
|
|
292
298
|
getToken,
|
|
293
299
|
}),
|
|
294
300
|
aiGateway: createAiGatewayModule({
|
package/dist/modules/actors.d.ts
CHANGED
|
@@ -11,9 +11,8 @@ 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
|
-
|
|
16
|
-
getAuthToken(): Promise<string | null | undefined>;
|
|
14
|
+
* anonymous connects omit it. */
|
|
15
|
+
getAuthToken(): string | null | undefined | Promise<string | null | undefined>;
|
|
17
16
|
/** Same semantics as function calls: editors with a non-prod version get the
|
|
18
17
|
* draft actor script; everyone else gets the published one. */
|
|
19
18
|
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 | undefined;
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
167
|
* Agents module for managing AI agent conversations.
|
|
@@ -360,9 +360,7 @@ 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.
|
|
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.
|
|
363
|
+
* The URL includes authentication if a token is available.
|
|
366
364
|
*
|
|
367
365
|
* @param agentName - The name of the agent.
|
|
368
366
|
* @returns WhatsApp connection URL.
|
|
@@ -380,9 +378,7 @@ export interface AgentsModule {
|
|
|
380
378
|
* Gets Telegram connection URL for an agent.
|
|
381
379
|
*
|
|
382
380
|
* Generates a URL that users can use to connect with the agent through Telegram.
|
|
383
|
-
* The URL includes authentication if a token is available.
|
|
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
|
|
381
|
+
* The URL includes authentication if a token is available. When the user opens
|
|
386
382
|
* this URL, they are redirected to the agent's Telegram bot with an activation
|
|
387
383
|
* code that securely links their account.
|
|
388
384
|
*
|
|
@@ -98,11 +98,16 @@ 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
|
-
/**
|
|
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
|
+
*/
|
|
102
105
|
embedded?: boolean;
|
|
103
106
|
/**
|
|
104
|
-
* Called
|
|
105
|
-
*
|
|
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.
|
|
106
111
|
*/
|
|
107
112
|
onSessionChange?: (hasSession: boolean) => void;
|
|
108
113
|
}
|
|
@@ -187,13 +192,13 @@ export interface AuthModule {
|
|
|
187
192
|
*/
|
|
188
193
|
redirectToLogin(nextUrl: string): void;
|
|
189
194
|
/**
|
|
190
|
-
* Whether
|
|
195
|
+
* Whether the app is running embedded in a host platform.
|
|
191
196
|
*
|
|
192
|
-
*
|
|
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.
|
|
193
198
|
*
|
|
194
|
-
*
|
|
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.
|
|
195
200
|
*
|
|
196
|
-
* @returns `true` when a host platform
|
|
201
|
+
* @returns `true` when the app was embedded by a host platform, `false` otherwise.
|
|
197
202
|
*
|
|
198
203
|
* @example
|
|
199
204
|
* ```typescript
|
|
@@ -564,6 +569,9 @@ export interface InternalAuthModule extends AuthModule {
|
|
|
564
569
|
* could not succeed without a session, not to decide that one is valid.
|
|
565
570
|
*/
|
|
566
571
|
hasToken(): boolean;
|
|
567
|
-
/**
|
|
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
|
+
*/
|
|
568
576
|
getToken(): string | null;
|
|
569
577
|
}
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Taking the token off the URL is unconditional — a one-time token must not be
|
|
11
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.
|
|
13
|
-
* the first client has taken it, so the session belongs to that client — an app
|
|
14
|
-
* creates one.
|
|
12
|
+
* reported back inside a frame, the only place one is redeemed.
|
|
15
13
|
*
|
|
16
14
|
* The exchange endpoint is rate limited per app, and its limiter refuses before
|
|
17
15
|
* the one-time token is redeemed — so a 429 leaves the token still valid and is
|
|
@@ -21,6 +19,8 @@
|
|
|
21
19
|
*/
|
|
22
20
|
export declare const EMBED_TOKEN_PARAM = "ott";
|
|
23
21
|
/** @internal */
|
|
22
|
+
export declare function frameSession(appId: string, start?: () => Promise<string | null>): Promise<string | null> | null;
|
|
23
|
+
/** @internal */
|
|
24
24
|
export declare function takeEmbedTokenFromUrl(): string | null;
|
|
25
25
|
/** @internal */
|
|
26
26
|
export declare function isFramed(): boolean;
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Taking the token off the URL is unconditional — a one-time token must not be
|
|
11
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.
|
|
13
|
-
* the first client has taken it, so the session belongs to that client — an app
|
|
14
|
-
* creates one.
|
|
12
|
+
* reported back inside a frame, the only place one is redeemed.
|
|
15
13
|
*
|
|
16
14
|
* The exchange endpoint is rate limited per app, and its limiter refuses before
|
|
17
15
|
* the one-time token is redeemed — so a 429 leaves the token still valid and is
|
|
@@ -26,6 +24,20 @@ const RATE_LIMITED = 429;
|
|
|
26
24
|
const RATE_LIMIT_RETRY_MS = 1000;
|
|
27
25
|
const EXCHANGE_TIMEOUT_MS = 15000;
|
|
28
26
|
const SESSION_ENDED_ELEMENT_ID = "base44-embed-session-ended";
|
|
27
|
+
const FRAME_SESSIONS_KEY = "__base44EmbedSessions";
|
|
28
|
+
/** @internal */
|
|
29
|
+
export function frameSession(appId, start) {
|
|
30
|
+
var _a, _b;
|
|
31
|
+
var _c;
|
|
32
|
+
if (typeof window === "undefined") {
|
|
33
|
+
return start ? start() : null;
|
|
34
|
+
}
|
|
35
|
+
const sessions = ((_a = (_c = window)[FRAME_SESSIONS_KEY]) !== null && _a !== void 0 ? _a : (_c[FRAME_SESSIONS_KEY] = {}));
|
|
36
|
+
if (!sessions[appId] && start) {
|
|
37
|
+
sessions[appId] = start();
|
|
38
|
+
}
|
|
39
|
+
return (_b = sessions[appId]) !== null && _b !== void 0 ? _b : null;
|
|
40
|
+
}
|
|
29
41
|
/** @internal */
|
|
30
42
|
export function takeEmbedTokenFromUrl() {
|
|
31
43
|
if (typeof window === "undefined" || !window.location) {
|
|
@@ -40,5 +40,6 @@ 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. */
|
|
43
44
|
waitForAuth?: () => Promise<void>;
|
|
44
45
|
}): (path: string, init?: FetchWithAuthInit) => Promise<Response>;
|