@base44-preview/sdk 0.8.51-pr.294.a7ae332 → 0.8.52-pr.295.572d9e6

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.
@@ -61,7 +61,10 @@ export function createFunctionsModule(axios, appId, config) {
61
61
  formData = data;
62
62
  contentType = "application/json";
63
63
  }
64
- return axios.post(`/apps/${appId}/functions/${functionName}`, formData || data, { headers: { "Content-Type": contentType } });
64
+ // Return only the response fields: the axios response also carries the
65
+ // request config and raw request, both of which hold the auth headers.
66
+ const { data: responseData, status, statusText, headers } = await axios.post(`/apps/${appId}/functions/${functionName}`, formData || data, { headers: { "Content-Type": contentType } });
67
+ return { data: responseData, status, statusText, headers };
65
68
  },
66
69
  // Fetch a backend function endpoint directly.
67
70
  async fetch(path, init = {}) {
@@ -59,7 +59,7 @@ export interface FunctionsModule {
59
59
  *
60
60
  * @param functionName - The name of the function to invoke.
61
61
  * @param data - An object containing named parameters for the function.
62
- * @returns Promise resolving to the function's response. The `data` property contains the data returned by the function, if there is any.
62
+ * @returns Promise resolving to the function's response, with `data`, `status`, `statusText`, and `headers` properties. The `data` property contains the data returned by the function, if there is any. The request config isn't included, since it holds the auth headers.
63
63
  *
64
64
  * @example
65
65
  * ```typescript
@@ -1,4 +1,4 @@
1
- import axios from "axios";
1
+ import axios, { AxiosHeaders } from "axios";
2
2
  import { isInIFrame } from "./common.js";
3
3
  import { v4 as uuidv4 } from "uuid";
4
4
  import { getAnalyticsSessionId } from "../modules/analytics.js";
@@ -101,6 +101,134 @@ function safeErrorLog(prefix, error) {
101
101
  console.error(`${prefix} ${error instanceof Error ? error.message : String(error)}`);
102
102
  }
103
103
  }
104
+ const REDACTED = "[REDACTED]";
105
+ const CREDENTIAL_HEADERS = [
106
+ "Authorization",
107
+ "on-behalf-of",
108
+ "Base44-Service-Authorization",
109
+ // Signed proof that the caller passed the IP allowlist; replayable if logged.
110
+ "Base44-State",
111
+ ];
112
+ // Request body fields that hold secrets (see the bodies built in modules/auth.ts).
113
+ const SECRET_BODY_KEYS = new Set([
114
+ "password",
115
+ "current_password",
116
+ "new_password",
117
+ "reset_token",
118
+ "turnstile_token",
119
+ "otp_code",
120
+ "access_token",
121
+ "refresh_token",
122
+ "token",
123
+ ]);
124
+ /**
125
+ * Returns a copy of request headers with credential values replaced.
126
+ *
127
+ * @param headers - The request's headers, left unchanged
128
+ * @returns A new AxiosHeaders instance
129
+ * @internal
130
+ */
131
+ function redactHeaders(headers) {
132
+ // Not `AxiosHeaders.from`, which returns an AxiosHeaders argument as-is.
133
+ const copy = new AxiosHeaders(headers);
134
+ for (const name of CREDENTIAL_HEADERS) {
135
+ if (copy.has(name)) {
136
+ copy.set(name, REDACTED);
137
+ }
138
+ }
139
+ return copy;
140
+ }
141
+ function isPlainObject(value) {
142
+ if (value === null || typeof value !== "object")
143
+ return false;
144
+ const proto = Object.getPrototypeOf(value);
145
+ return proto === Object.prototype || proto === null;
146
+ }
147
+ function redactSecretKeys(value, seen) {
148
+ if (Array.isArray(value)) {
149
+ if (seen.has(value))
150
+ return value;
151
+ seen.add(value);
152
+ return value.map((item) => redactSecretKeys(item, seen));
153
+ }
154
+ if (!isPlainObject(value) || seen.has(value))
155
+ return value;
156
+ seen.add(value);
157
+ const copy = {};
158
+ for (const [key, item] of Object.entries(value)) {
159
+ copy[key] = SECRET_BODY_KEYS.has(key.toLowerCase())
160
+ ? REDACTED
161
+ : redactSecretKeys(item, seen);
162
+ }
163
+ return copy;
164
+ }
165
+ /**
166
+ * Returns a copy of a request body with secret fields replaced.
167
+ *
168
+ * Handles both a serialized JSON string (the usual case by the time a request
169
+ * has been sent) and a plain object. Other bodies, such as `FormData`, are
170
+ * returned unchanged.
171
+ *
172
+ * @param data - The request body, left unchanged
173
+ * @returns The redacted body
174
+ * @internal
175
+ */
176
+ function redactBody(data) {
177
+ if (typeof data === "string") {
178
+ let parsed;
179
+ try {
180
+ parsed = JSON.parse(data);
181
+ }
182
+ catch (_a) {
183
+ return data;
184
+ }
185
+ return typeof parsed === "object" && parsed !== null
186
+ ? JSON.stringify(redactSecretKeys(parsed, new WeakSet()))
187
+ : data;
188
+ }
189
+ return redactSecretKeys(data, new WeakSet());
190
+ }
191
+ /**
192
+ * Removes credentials from a failed request's error before app code sees it.
193
+ *
194
+ * An axios error carries its request config and the raw Node request, so
195
+ * logging it (for example `console.log(error)`) printed the bearer token.
196
+ * The config is replaced with a redacted copy, so the object axios sent is not
197
+ * changed, and the raw request is removed.
198
+ *
199
+ * Never throws: a failure here must not replace the original error.
200
+ *
201
+ * @param error - The rejection value to scrub
202
+ * @internal
203
+ */
204
+ function sanitizeError(error) {
205
+ var _a;
206
+ if (!error || typeof error !== "object")
207
+ return;
208
+ try {
209
+ const target = error;
210
+ const config = target.config;
211
+ if (config && typeof config === "object") {
212
+ const redactedConfig = {
213
+ ...config,
214
+ headers: redactHeaders(config.headers),
215
+ data: redactBody(config.data),
216
+ };
217
+ target.config = redactedConfig;
218
+ if (((_a = target.response) === null || _a === void 0 ? void 0 : _a.config) === config) {
219
+ target.response.config = redactedConfig;
220
+ }
221
+ }
222
+ // The Node request object also holds the raw header block (`_header`).
223
+ delete target.request;
224
+ if (target.response && typeof target.response === "object") {
225
+ delete target.response.request;
226
+ }
227
+ }
228
+ catch (_b) {
229
+ /* leave the error as it is */
230
+ }
231
+ }
104
232
  /**
105
233
  * Creates an axios client with default configuration and interceptors.
106
234
  *
@@ -128,6 +256,11 @@ export function createAxiosClient({ baseURL, headers = {}, token, interceptRespo
128
256
  if (token) {
129
257
  client.defaults.headers.common["Authorization"] = `Bearer ${token}`;
130
258
  }
259
+ // Registered first so it also covers clients that skip the Base44Error wrapper.
260
+ client.interceptors.response.use(undefined, (error) => {
261
+ sanitizeError(error);
262
+ return Promise.reject(error);
263
+ });
131
264
  // Add origin URL in browser environment
132
265
  client.interceptors.request.use((config) => {
133
266
  // `window.location` is absent on React Native (where `window` still exists),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44-preview/sdk",
3
- "version": "0.8.51-pr.294.a7ae332",
3
+ "version": "0.8.52-pr.295.572d9e6",
4
4
  "description": "JavaScript SDK for Base44 API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",