@base44-preview/sdk 0.8.48-pr.282.3c9bac2 → 0.8.48-pr.282.520fd69

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 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, isEmbeddedTab, markEmbeddedTab, takeEmbedTokenFromUrl, } from "./utils/embed-session.js";
8
+ import { exchangeEmbedToken, isEmbeddedTab, isFramed, markEmbeddedTab, 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,13 +58,19 @@ 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
- const embedOtt = takeEmbedTokenFromUrl();
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;
62
67
  if (embedOtt) {
63
68
  markEmbeddedTab();
64
69
  }
65
70
  const embedded = Boolean(embedOtt) || isEmbeddedTab();
66
- // In a frame the passed token is the OTT itself or a stale stored one, not this session.
67
- const token = embedded ? undefined : config.token;
71
+ // The passed token is the OTT itself whenever one came in on the URL, and a
72
+ // stale stored one in a frame. Neither of those is this session.
73
+ const token = urlOtt !== null || embedded ? undefined : config.token;
68
74
  const headers = {
69
75
  ...optionalHeaders,
70
76
  "X-App-Id": String(appId),
@@ -150,12 +156,25 @@ export function createClient(config) {
150
156
  userAuthModule.setToken(newToken, saveToStorage);
151
157
  socket === null || socket === void 0 ? void 0 : socket.reconnect();
152
158
  };
153
- // Settles once the exchanged session is applied (memory only); at once otherwise.
159
+ // Settles once the exchanged session is applied (memory only); at once
160
+ // otherwise. Never rejects: every request waits on it, so a failure here
161
+ // must not turn into a rejection on each of them.
154
162
  const authReady = embedOtt
155
- ? exchangeEmbedToken({ serverUrl, appId, ott: embedOtt }).then((sessionToken) => {
163
+ ? exchangeEmbedToken({ serverUrl, appId, ott: embedOtt })
164
+ .then((sessionToken) => {
165
+ var _a;
156
166
  if (sessionToken) {
157
167
  applyToken(sessionToken, false);
168
+ return;
158
169
  }
170
+ // The app is about to run anonymous. Say why, once, instead of
171
+ // leaving only the 401s that follow.
172
+ const error = new Error("Base44: the embed token was refused, so this app is not signed in.");
173
+ console.error(error.message);
174
+ (_a = options === null || options === void 0 ? void 0 : options.onError) === null || _a === void 0 ? void 0 : _a.call(options, error);
175
+ })
176
+ .catch((e) => {
177
+ console.error("Base44: applying the embedded session failed:", e);
159
178
  })
160
179
  : Promise.resolve();
161
180
  if (embedOtt) {
@@ -189,6 +189,10 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
189
189
  setToken(token, saveToStorage = true) {
190
190
  if (!token)
191
191
  return;
192
+ // An embedded session belongs to the frame the platform minted it for.
193
+ // Persisting it would let it outlive that frame and be picked up as the
194
+ // identity on a later top-level visit, so storage is refused outright.
195
+ const persist = saveToStorage && !options.embedded;
192
196
  // Same reasoning as in `logout`: the identity changes here, so anything
193
197
  // resolved for the previous one must not be handed to later callers.
194
198
  clearPendingMe();
@@ -198,9 +202,7 @@ export function createAuthModule(axios, functionsAxiosClient, appId, options) {
198
202
  axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
199
203
  functionsAxiosClient.defaults.headers.common["Authorization"] = `Bearer ${token}`;
200
204
  // Save token to localStorage if requested
201
- if (saveToStorage &&
202
- typeof window !== "undefined" &&
203
- window.localStorage) {
205
+ if (persist && typeof window !== "undefined" && window.localStorage) {
204
206
  try {
205
207
  window.localStorage.setItem("base44_access_token", token);
206
208
  // Set "token" that is set by the built-in SDK of platform version 2
@@ -18,6 +18,14 @@ export declare const EMBED_TOKEN_PARAM = "ott";
18
18
  * @internal
19
19
  */
20
20
  export declare function takeEmbedTokenFromUrl(): string | null;
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.
25
+ *
26
+ * @internal
27
+ */
28
+ export declare function isFramed(): boolean;
21
29
  /**
22
30
  * Whether this tab previously redeemed an embed token. Counts only inside a
23
31
  * frame: a top-level tab that once carried a token must fall back to the
@@ -42,8 +50,9 @@ export declare function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl, }
42
50
  }): Promise<string | null>;
43
51
  /**
44
52
  * 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.
53
+ * only be renewed by the host platform, so this stands in for the login
54
+ * redirect, which cannot complete inside a frame. The copy points at the host
55
+ * page: reloading the frame itself carries no token and lands here again.
47
56
  *
48
57
  * @internal
49
58
  */
@@ -27,19 +27,43 @@ export function takeEmbedTokenFromUrl() {
27
27
  if (typeof window === "undefined" || !window.location) {
28
28
  return null;
29
29
  }
30
+ let ott = null;
30
31
  try {
31
32
  const url = new URL(window.location.href);
32
- const ott = url.searchParams.get(EMBED_TOKEN_PARAM);
33
+ ott = url.searchParams.get(EMBED_TOKEN_PARAM);
33
34
  if (!ott) {
34
35
  return null;
35
36
  }
36
37
  url.searchParams.delete(EMBED_TOKEN_PARAM);
37
38
  window.history.replaceState(window.history.state, "", url.toString());
38
- return ott;
39
39
  }
40
40
  catch (e) {
41
+ // The token was read but the URL could not be rewritten (history blocked,
42
+ // a sandboxed frame, replaceState rate-limited). Return it anyway: it is
43
+ // this load's identity, and dropping it here would leave `?ott=` on the
44
+ // URL for getAccessToken to hand back as if it were a session token.
41
45
  console.error("Error retrieving embed token from URL:", e);
42
- return null;
46
+ }
47
+ return ott;
48
+ }
49
+ /**
50
+ * Whether this document is rendered inside a frame. Read at call time, not at
51
+ * import: a module-level answer would be fixed before a host (or a test) has
52
+ * set the window up.
53
+ *
54
+ * @internal
55
+ */
56
+ export function isFramed() {
57
+ if (typeof window === "undefined") {
58
+ return false;
59
+ }
60
+ try {
61
+ return window.self !== window.top;
62
+ }
63
+ catch (_a) {
64
+ // Reaching the top window can be refused outright; if we cannot see it,
65
+ // we are certainly not it.
66
+ return true;
43
67
  }
44
68
  }
45
69
  /**
@@ -56,8 +80,7 @@ export function isEmbeddedTab() {
56
80
  // A cross-site frame is third-party storage, which some browsers block
57
81
  // outright — every access can throw, and the marker is then unavailable.
58
82
  try {
59
- return (window.self !== window.top &&
60
- window.sessionStorage.getItem(EMBED_TAB_KEY) === "1");
83
+ return isFramed() && window.sessionStorage.getItem(EMBED_TAB_KEY) === "1";
61
84
  }
62
85
  catch (_a) {
63
86
  return false;
@@ -108,8 +131,9 @@ export async function exchangeEmbedToken({ serverUrl, appId, ott, fetchImpl = fe
108
131
  }
109
132
  /**
110
133
  * 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.
134
+ * only be renewed by the host platform, so this stands in for the login
135
+ * redirect, which cannot complete inside a frame. The copy points at the host
136
+ * page: reloading the frame itself carries no token and lands here again.
113
137
  *
114
138
  * @internal
115
139
  */
@@ -131,7 +155,7 @@ export function showEmbedSessionEnded() {
131
155
  title.textContent = "Session ended";
132
156
  title.style.cssText = "font-size:1.5rem;font-weight:700;margin:0 0 .75rem;";
133
157
  const body = document.createElement("p");
134
- body.textContent = "Refresh the page to continue.";
158
+ body.textContent = "Reload this page in your browser to start a new session.";
135
159
  body.style.cssText = "margin:0;color:#475569;";
136
160
  const card = document.createElement("div");
137
161
  card.append(title, body);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/sdk",
3
- "version": "0.8.48-pr.282.3c9bac2",
3
+ "version": "0.8.48-pr.282.520fd69",
4
4
  "description": "JavaScript SDK for Base44 API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",