@palbase/web 4.0.1 → 5.0.0

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.
Files changed (52) hide show
  1. package/README.md +20 -3
  2. package/dist/{analytics-facade-DwOtlXPP.d.cts → analytics-facade-CaluA4bW.d.cts} +26 -1
  3. package/dist/{analytics-facade-Nk6J9Ncm.d.ts → analytics-facade-Cp3d4QI-.d.ts} +26 -1
  4. package/dist/chunk-BNAGRUIQ.js +381 -0
  5. package/dist/chunk-BNAGRUIQ.js.map +1 -0
  6. package/dist/{chunk-NXEPAEF5.js → chunk-CFDU23TB.js} +3 -3
  7. package/dist/chunk-CFDU23TB.js.map +1 -0
  8. package/dist/chunk-OWLTZG2V.js +24 -0
  9. package/dist/chunk-OWLTZG2V.js.map +1 -0
  10. package/dist/{chunk-TGDS6VMT.js → chunk-XTQXFZUP.js} +68 -376
  11. package/dist/chunk-XTQXFZUP.js.map +1 -0
  12. package/dist/gen/cli.cjs +5 -5
  13. package/dist/gen/cli.cjs.map +1 -1
  14. package/dist/gen/cli.js +5 -5
  15. package/dist/gen/cli.js.map +1 -1
  16. package/dist/index.cjs +55 -3
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +6 -4
  21. package/dist/internal.cjs +57 -5
  22. package/dist/internal.cjs.map +1 -1
  23. package/dist/internal.d.cts +3 -3
  24. package/dist/internal.d.ts +3 -3
  25. package/dist/internal.js +3 -2
  26. package/dist/next/client.cjs +57 -5
  27. package/dist/next/client.cjs.map +1 -1
  28. package/dist/next/client.js +3 -2
  29. package/dist/next/client.js.map +1 -1
  30. package/dist/next/index.cjs +61 -93
  31. package/dist/next/index.cjs.map +1 -1
  32. package/dist/next/index.d.cts +4 -64
  33. package/dist/next/index.d.ts +4 -64
  34. package/dist/next/index.js +9 -103
  35. package/dist/next/index.js.map +1 -1
  36. package/dist/next/middleware.cjs +263 -0
  37. package/dist/next/middleware.cjs.map +1 -0
  38. package/dist/next/middleware.d.cts +101 -0
  39. package/dist/next/middleware.d.ts +101 -0
  40. package/dist/next/middleware.js +107 -0
  41. package/dist/next/middleware.js.map +1 -0
  42. package/dist/{pb-FG_nV7NR.d.ts → pb-DUK5qy81.d.ts} +1 -1
  43. package/dist/{pb-C9v5dEO6.d.cts → pb-DvV6ftmf.d.cts} +1 -1
  44. package/dist/react/index.cjs +10 -2
  45. package/dist/react/index.cjs.map +1 -1
  46. package/dist/react/index.d.cts +1 -1
  47. package/dist/react/index.d.ts +1 -1
  48. package/dist/react/index.js +3 -2
  49. package/dist/react/index.js.map +1 -1
  50. package/package.json +11 -1
  51. package/dist/chunk-NXEPAEF5.js.map +0 -1
  52. package/dist/chunk-TGDS6VMT.js.map +0 -1
@@ -1,379 +1,21 @@
1
+ import {
2
+ BackendError,
3
+ HttpClient,
4
+ PalbaseError,
5
+ TokenManager,
6
+ asPalbaseError,
7
+ fromEnvelope,
8
+ fromPalbaseError,
9
+ isBackendError,
10
+ unwrap
11
+ } from "./chunk-BNAGRUIQ.js";
1
12
  import {
2
13
  environmentRefFromPublishableApiKey
3
- } from "./chunk-NXEPAEF5.js";
14
+ } from "./chunk-CFDU23TB.js";
4
15
  import {
5
16
  __export
6
17
  } from "./chunk-PZ5AY32C.js";
7
18
 
8
- // ../core/dist/index.js
9
- var CACHE_TTL_MS = 5 * 60 * 1e3;
10
- var PalbaseError = class extends Error {
11
- code;
12
- status;
13
- details;
14
- constructor(code, message, status, details) {
15
- super(message);
16
- this.name = "PalbaseError";
17
- this.code = code;
18
- this.status = status;
19
- this.details = details;
20
- }
21
- };
22
- var PALBASE_DEFAULT_HOST = "api.palbase.studio";
23
- var API_KEY_RE = /^pb_([a-z0-9]+)_c[A-Za-z0-9]{20}$/;
24
- function parseEnvironmentRef(apiKey) {
25
- return API_KEY_RE.exec(apiKey)?.[1] ?? null;
26
- }
27
- var MAX_RETRIES = 3;
28
- var INITIAL_BACKOFF_MS = 200;
29
- var MAX_RETRY_DELAY_MS = 1e4;
30
- var HttpClient = class _HttpClient {
31
- apiKey;
32
- options;
33
- tokenManager = null;
34
- /**
35
- * Admin JWT used for platform admin endpoints (/admin/*).
36
- * When set, takes precedence over tokenManager access token in the
37
- * Authorization header.
38
- */
39
- adminToken = null;
40
- interceptors = [];
41
- constructor(apiKey, options) {
42
- this.apiKey = apiKey;
43
- this.options = options;
44
- }
45
- /** Set (or clear) the admin JWT used on admin endpoints. */
46
- setAdminToken(token) {
47
- this.adminToken = token;
48
- }
49
- /**
50
- * Create a scoped HttpClient that adds the given extra headers to every
51
- * request. The returned client shares the admin token and token manager
52
- * with the parent at runtime — later changes on the parent propagate to
53
- * the scope and vice versa.
54
- *
55
- * Typical use: adding an Environment-routing header for an admin call.
56
- */
57
- withHeaders(extra) {
58
- const mergedHeaders = { ...this.options?.headers ?? {}, ...extra };
59
- const scoped = new _HttpClient(this.apiKey, {
60
- ...this.options,
61
- headers: mergedHeaders
62
- });
63
- scoped.tokenManager = this.tokenManager;
64
- Object.defineProperty(scoped, "adminToken", {
65
- get: () => this.adminToken,
66
- set: (v) => {
67
- this.adminToken = v;
68
- },
69
- configurable: true
70
- });
71
- return scoped;
72
- }
73
- /** Add a request interceptor. Runs before every request. */
74
- addInterceptor(interceptor) {
75
- this.interceptors.push(interceptor);
76
- }
77
- async request(method, path, options) {
78
- if (this.tokenManager?.isExpired() && this.tokenManager.getRefreshToken() && this.tokenManager.refreshFunction) {
79
- try {
80
- await this.tokenManager.refreshSession();
81
- } catch (e) {
82
- const status = e instanceof PalbaseError ? e.status : 0;
83
- if (status === 400 || status === 401 || status === 403) {
84
- this.tokenManager.clearSession();
85
- } else {
86
- throw e;
87
- }
88
- }
89
- }
90
- return this.executeWithRetry(method, path, options, 0);
91
- }
92
- getBaseUrl() {
93
- if (this.options?.url) {
94
- return this.options.url;
95
- }
96
- if (this.apiKey && parseEnvironmentRef(this.apiKey) === null) {
97
- throw new PalbaseError(
98
- "invalid_api_key",
99
- 'Invalid API key format. Expected pb_{environment_ref}_c{20_base62_chars}. For dev/staging pass `url: "https://api.dev.palbase.studio"` via options.',
100
- 0
101
- );
102
- }
103
- return `https://${PALBASE_DEFAULT_HOST}`;
104
- }
105
- buildHeaders(options) {
106
- const headers = {
107
- "Content-Type": "application/json"
108
- };
109
- const effectiveKey = this.apiKey;
110
- if (effectiveKey) {
111
- headers["apikey"] = effectiveKey;
112
- }
113
- const token = this.tokenManager?.getAccessToken();
114
- if (token) {
115
- headers["Authorization"] = `Bearer ${token}`;
116
- }
117
- if (this.adminToken) {
118
- headers["Authorization"] = `Bearer ${this.adminToken}`;
119
- }
120
- if (this.options?.headers) {
121
- Object.assign(headers, this.options.headers);
122
- }
123
- if (options?.headers) {
124
- Object.assign(headers, options.headers);
125
- }
126
- return headers;
127
- }
128
- async executeWithRetry(method, path, options, attempt) {
129
- const url = `${this.getBaseUrl()}${path}`;
130
- const headers = this.buildHeaders(options);
131
- for (const interceptor of this.interceptors) {
132
- await interceptor({ headers, method, path });
133
- }
134
- const fetchOptions = {
135
- method,
136
- headers,
137
- signal: options?.signal
138
- };
139
- if (options?.body !== void 0) {
140
- fetchOptions.body = JSON.stringify(options.body);
141
- }
142
- let response;
143
- try {
144
- response = await fetch(url, fetchOptions);
145
- } catch (error) {
146
- if (attempt < MAX_RETRIES - 1) {
147
- const backoff = INITIAL_BACKOFF_MS * 2 ** attempt;
148
- await this.delay(backoff);
149
- return this.executeWithRetry(method, path, options, attempt + 1);
150
- }
151
- throw new PalbaseError(
152
- "network_error",
153
- error instanceof Error ? error.message : "Network request failed",
154
- 0
155
- );
156
- }
157
- if (response.status === 429) {
158
- if (attempt < MAX_RETRIES - 1) {
159
- const retryAfter = response.headers.get("Retry-After");
160
- const parsed = retryAfter ? Number.parseInt(retryAfter, 10) : Number.NaN;
161
- const delayMs = Number.isNaN(parsed) ? INITIAL_BACKOFF_MS * 2 ** attempt : Math.min(parsed * 1e3, MAX_RETRY_DELAY_MS);
162
- await this.delay(delayMs);
163
- return this.executeWithRetry(method, path, options, attempt + 1);
164
- }
165
- }
166
- let data = null;
167
- let errorBody;
168
- const contentType = response.headers.get("Content-Type");
169
- if (method !== "HEAD" && contentType?.includes("json")) {
170
- const body = await response.json();
171
- if (response.ok) {
172
- data = body;
173
- } else {
174
- errorBody = body;
175
- }
176
- }
177
- if (!response.ok) {
178
- return {
179
- data: null,
180
- error: new PalbaseError(
181
- errorBody?.error ?? "unknown_error",
182
- errorBody?.error_description ?? response.statusText,
183
- response.status,
184
- errorBody
185
- ),
186
- status: response.status
187
- };
188
- }
189
- const contentRange = response.headers.get("Content-Range");
190
- let count;
191
- if (contentRange) {
192
- const slash = contentRange.lastIndexOf("/");
193
- if (slash >= 0) {
194
- const totalPart = contentRange.slice(slash + 1);
195
- if (totalPart !== "*") {
196
- const parsed = Number.parseInt(totalPart, 10);
197
- if (!Number.isNaN(parsed)) {
198
- count = parsed;
199
- }
200
- }
201
- }
202
- }
203
- return {
204
- data,
205
- error: null,
206
- status: response.status,
207
- ...count !== void 0 ? { count } : {}
208
- };
209
- }
210
- delay(ms) {
211
- return new Promise((resolve) => setTimeout(resolve, ms));
212
- }
213
- };
214
- var TokenManager = class {
215
- session = null;
216
- listeners = /* @__PURE__ */ new Set();
217
- refreshPromise = null;
218
- refreshing = false;
219
- refreshFunction = null;
220
- setSession(session) {
221
- this.session = session;
222
- this.notify("SESSION_SET", session);
223
- }
224
- getAccessToken() {
225
- return this.session?.accessToken ?? null;
226
- }
227
- getRefreshToken() {
228
- return this.session?.refreshToken ?? null;
229
- }
230
- clearSession() {
231
- this.session = null;
232
- this.notify("SESSION_CLEARED", null);
233
- }
234
- isExpired() {
235
- if (!this.session) return true;
236
- return Date.now() >= this.session.expiresAt;
237
- }
238
- async refreshSession() {
239
- if (!this.session?.refreshToken || !this.refreshFunction) {
240
- return;
241
- }
242
- if (this.refreshPromise) {
243
- return this.refreshPromise;
244
- }
245
- if (this.refreshing) {
246
- return;
247
- }
248
- this.refreshing = true;
249
- this.refreshPromise = this.executeRefresh(this.session.refreshToken);
250
- try {
251
- await this.refreshPromise;
252
- } finally {
253
- this.refreshPromise = null;
254
- this.refreshing = false;
255
- }
256
- }
257
- onAuthStateChange(callback) {
258
- this.listeners.add(callback);
259
- return () => {
260
- this.listeners.delete(callback);
261
- };
262
- }
263
- async executeRefresh(refreshToken) {
264
- if (!this.refreshFunction) return;
265
- const newSession = await this.refreshFunction(refreshToken);
266
- this.setSession(newSession);
267
- }
268
- notify(event, session) {
269
- for (const listener of this.listeners) {
270
- listener(event, session);
271
- }
272
- }
273
- };
274
-
275
- // src/errors.ts
276
- var BackendError = class _BackendError extends Error {
277
- kind;
278
- code;
279
- status;
280
- requestId;
281
- fields;
282
- retryAfter;
283
- data;
284
- constructor(kind, params) {
285
- super(params.message);
286
- this.name = "BackendError";
287
- this.kind = kind;
288
- this.code = params.code;
289
- this.status = params.status ?? 0;
290
- this.requestId = params.requestId;
291
- this.fields = params.fields;
292
- this.retryAfter = params.retryAfter;
293
- this.data = params.data;
294
- }
295
- static notConfigured() {
296
- return new _BackendError("notConfigured", {
297
- code: "not_configured",
298
- message: "Palbe is not configured. Run 'palbase web link' in your project and make sure palbe.gen.ts is imported once at app startup."
299
- });
300
- }
301
- };
302
- function isFieldErrorArray(value) {
303
- return Array.isArray(value) && value.length > 0 && value.every(
304
- (v) => typeof v === "object" && v !== null && typeof v.field === "string" && typeof v.message === "string"
305
- );
306
- }
307
- function pickField(value, key) {
308
- if (typeof value === "object" && value !== null) {
309
- return value[key];
310
- }
311
- return void 0;
312
- }
313
- function pickNumber(value, key) {
314
- const n = pickField(value, key);
315
- return typeof n === "number" ? n : void 0;
316
- }
317
- function pickString(value, key) {
318
- const s = pickField(value, key);
319
- return typeof s === "string" ? s : void 0;
320
- }
321
- function fromPalbaseError(err) {
322
- const base = {
323
- code: err.code,
324
- message: err.message,
325
- status: err.status,
326
- requestId: pickString(err.details, "request_id"),
327
- data: pickField(err.details, "data")
328
- };
329
- if (err.code === "network_error") return new BackendError("network", base);
330
- if (err.status === 401) return new BackendError("unauthorized", base);
331
- if (err.status === 429)
332
- return new BackendError("rateLimited", {
333
- ...base,
334
- retryAfter: pickNumber(err.details, "retry_after")
335
- });
336
- const nested = pickField(err.details, "details");
337
- if (err.status === 400 && isFieldErrorArray(nested))
338
- return new BackendError("validation", { ...base, fields: nested });
339
- return new BackendError("server", base);
340
- }
341
- function fromEnvelope(status, body) {
342
- const code = pickString(body, "error") ?? "http_error";
343
- const message = pickString(body, "error_description") ?? `HTTP ${status}`;
344
- const requestId = pickString(body, "request_id");
345
- const details = pickField(body, "details");
346
- const params = {
347
- code,
348
- message,
349
- status,
350
- requestId,
351
- data: pickField(body, "data")
352
- };
353
- if (status === 401) return new BackendError("unauthorized", params);
354
- if (status === 429)
355
- return new BackendError("rateLimited", {
356
- ...params,
357
- // Real 429 wire body has TOP-LEVEL retry_after; nested details is a fallback.
358
- retryAfter: pickNumber(body, "retry_after") ?? pickNumber(details, "retry_after")
359
- });
360
- if (status === 400 && isFieldErrorArray(details))
361
- return new BackendError("validation", { ...params, fields: details });
362
- return new BackendError("server", params);
363
- }
364
- function isBackendError(e) {
365
- return e instanceof BackendError || typeof e === "object" && e !== null && e.name === "BackendError" && typeof e.kind === "string";
366
- }
367
- function asPalbaseError(e) {
368
- if (e instanceof PalbaseError) return e;
369
- if (e instanceof Error && e.name === "PalbaseError") return e;
370
- return null;
371
- }
372
- function unwrap(res) {
373
- if (res.error) throw fromPalbaseError(res.error);
374
- return res.data;
375
- }
376
-
377
19
  // src/perf/url-redactor.ts
378
20
  var ID_SEGMENT = /^(?:\d+|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
379
21
  var EMAIL_SEGMENT = /(?:@|%40)/i;
@@ -397,6 +39,11 @@ var PERF_EXCLUDED_PREFIX = "/v1/analytics/";
397
39
  function isSelfTraced(path) {
398
40
  return path.startsWith(PERF_EXCLUDED_PREFIX);
399
41
  }
42
+ function isSessionKilled(status, code) {
43
+ if (status === 401) return code === "session_revoked";
44
+ if (status === 403) return code === "subject_fenced" || code === "subject_erased";
45
+ return false;
46
+ }
400
47
  function nowMs() {
401
48
  return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
402
49
  }
@@ -432,7 +79,7 @@ async function palbeRequest(rt, method, path, spec = {}) {
432
79
  let res;
433
80
  try {
434
81
  res = await attempt();
435
- if (res.error?.status === 401 && rt.tokenManager.getRefreshToken() && rt.tokenManager.refreshFunction) {
82
+ if (res.error?.status === 401 && res.error.code !== "session_revoked" && rt.tokenManager.getRefreshToken() && rt.tokenManager.refreshFunction) {
436
83
  try {
437
84
  await rt.tokenManager.refreshSession();
438
85
  } catch (refreshErr) {
@@ -452,6 +99,9 @@ async function palbeRequest(rt, method, path, spec = {}) {
452
99
  if (!isAbort(e)) record(asPalbaseError(e)?.status ?? 0);
453
100
  throw e;
454
101
  }
102
+ if (res.error && isSessionKilled(res.error.status, res.error.code)) {
103
+ rt.auth.reactiveTeardown();
104
+ }
455
105
  record(res.error?.status ?? 200);
456
106
  return unwrap(res);
457
107
  }
@@ -1426,7 +1076,8 @@ var PalbeAuth = class {
1426
1076
  if (!this.signedInState) return;
1427
1077
  this.signedInState = false;
1428
1078
  this.cachedUser = null;
1429
- const reason = this.signingOut ? "userInitiated" : "sessionExpired";
1079
+ const reason = this.nextSignOutReason ?? (this.signingOut ? "userInitiated" : "sessionExpired");
1080
+ this.nextSignOutReason = null;
1430
1081
  this.emitState({ status: "signedOut" });
1431
1082
  this.emitEvent({ type: "signedOut", reason });
1432
1083
  } else if (event === "TOKEN_REFRESHED") {
@@ -1439,6 +1090,10 @@ var PalbeAuth = class {
1439
1090
  rt;
1440
1091
  cachedUser = null;
1441
1092
  signingOut = false;
1093
+ // Explicit signedOut reason pinned for the NEXT session clear (deleteAccount /
1094
+ // reactiveTeardown set it before clearSession; the SIGNED_OUT handler consumes
1095
+ // it exactly once, then resets to null → normal signOut/expiry split resumes).
1096
+ nextSignOutReason = null;
1442
1097
  signingIn = false;
1443
1098
  // suppresses AuthClient's TOKEN_REFRESHED during re-signIn
1444
1099
  signedInState = false;
@@ -1486,6 +1141,45 @@ var PalbeAuth = class {
1486
1141
  this.cachedUser = null;
1487
1142
  }
1488
1143
  }
1144
+ /**
1145
+ * Self-service account erasure ("right to be forgotten"). Calls palauth
1146
+ * `DELETE /auth/user` (session-authed + fresh re-auth): password users pass
1147
+ * `{ password }`; passwordless/OAuth users rely on a freshly stepped-up
1148
+ * session. On 202 the erasure workflow is durably queued AND every session is
1149
+ * already revoked server-side — so we tear down local state (mirroring
1150
+ * `signOut()`'s clear, minus the pointless `/logout`) and emit
1151
+ * `signedOut{reason:'accountDeleted'}`.
1152
+ *
1153
+ * On 401 `reauth_required` / 503 `not_configured` (or `erasure_unavailable`)
1154
+ * the request throws BEFORE the teardown line, so the local session is left
1155
+ * fully intact — the deletion did not happen and the user is still signed in.
1156
+ */
1157
+ async deleteAccount(params = {}) {
1158
+ const res = await palbeRequest(this.rt, "DELETE", "/auth/user", {
1159
+ body: params.password ? { password: params.password } : {}
1160
+ });
1161
+ this.nextSignOutReason = "accountDeleted";
1162
+ this.rt.tokenManager.clearSession();
1163
+ this.rt.storage.clear();
1164
+ this.cachedUser = null;
1165
+ return { erasureId: res.erasure_id };
1166
+ }
1167
+ /**
1168
+ * @internal — invoked by the transport choke point (`request.ts`), not app code.
1169
+ * Reactive teardown when the server reports the session is gone: a 403
1170
+ * `subject_fenced`/`subject_erased` (subject being erased) or a 401
1171
+ * `session_revoked`. Clears local state and emits
1172
+ * `signedOut{reason:'sessionInvalid'}` exactly once (deduped by
1173
+ * `signedInState`). An ordinary 403 authz denial does NOT reach here —
1174
+ * `request.ts` matches only the exact session-kill codes.
1175
+ */
1176
+ reactiveTeardown() {
1177
+ if (!this.signedInState) return;
1178
+ this.nextSignOutReason = "sessionInvalid";
1179
+ this.rt.tokenManager.clearSession();
1180
+ this.rt.storage.clear();
1181
+ this.cachedUser = null;
1182
+ }
1489
1183
  async getUser() {
1490
1184
  const raw = await palbeRequest(this.rt, "GET", "/auth/user");
1491
1185
  return mapWireUser(raw);
@@ -8939,7 +8633,7 @@ function defaultSessionStorage(key) {
8939
8633
  }
8940
8634
 
8941
8635
  // src/version.ts
8942
- var VERSION = "4.0.1";
8636
+ var VERSION = "5.0.0";
8943
8637
 
8944
8638
  // src/runtime.ts
8945
8639
  function buildRuntime(config) {
@@ -9343,8 +9037,6 @@ function __reset() {
9343
9037
  }
9344
9038
 
9345
9039
  export {
9346
- BackendError,
9347
- isBackendError,
9348
9040
  PalbeAnalytics,
9349
9041
  asWireAuthResult,
9350
9042
  PalbeAuth,
@@ -9366,4 +9058,4 @@ export {
9366
9058
  pb,
9367
9059
  createBoundClient
9368
9060
  };
9369
- //# sourceMappingURL=chunk-TGDS6VMT.js.map
9061
+ //# sourceMappingURL=chunk-XTQXFZUP.js.map