@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.
- package/README.md +20 -3
- package/dist/{analytics-facade-DwOtlXPP.d.cts → analytics-facade-CaluA4bW.d.cts} +26 -1
- package/dist/{analytics-facade-Nk6J9Ncm.d.ts → analytics-facade-Cp3d4QI-.d.ts} +26 -1
- package/dist/chunk-BNAGRUIQ.js +381 -0
- package/dist/chunk-BNAGRUIQ.js.map +1 -0
- package/dist/{chunk-NXEPAEF5.js → chunk-CFDU23TB.js} +3 -3
- package/dist/chunk-CFDU23TB.js.map +1 -0
- package/dist/chunk-OWLTZG2V.js +24 -0
- package/dist/chunk-OWLTZG2V.js.map +1 -0
- package/dist/{chunk-TGDS6VMT.js → chunk-XTQXFZUP.js} +68 -376
- package/dist/chunk-XTQXFZUP.js.map +1 -0
- package/dist/gen/cli.cjs +5 -5
- package/dist/gen/cli.cjs.map +1 -1
- package/dist/gen/cli.js +5 -5
- package/dist/gen/cli.js.map +1 -1
- package/dist/index.cjs +55 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -4
- package/dist/internal.cjs +57 -5
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +3 -3
- package/dist/internal.d.ts +3 -3
- package/dist/internal.js +3 -2
- package/dist/next/client.cjs +57 -5
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.js +3 -2
- package/dist/next/client.js.map +1 -1
- package/dist/next/index.cjs +61 -93
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.d.cts +4 -64
- package/dist/next/index.d.ts +4 -64
- package/dist/next/index.js +9 -103
- package/dist/next/index.js.map +1 -1
- package/dist/next/middleware.cjs +263 -0
- package/dist/next/middleware.cjs.map +1 -0
- package/dist/next/middleware.d.cts +101 -0
- package/dist/next/middleware.d.ts +101 -0
- package/dist/next/middleware.js +107 -0
- package/dist/next/middleware.js.map +1 -0
- package/dist/{pb-FG_nV7NR.d.ts → pb-DUK5qy81.d.ts} +1 -1
- package/dist/{pb-C9v5dEO6.d.cts → pb-DvV6ftmf.d.cts} +1 -1
- package/dist/react/index.cjs +10 -2
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +3 -2
- package/dist/react/index.js.map +1 -1
- package/package.json +11 -1
- package/dist/chunk-NXEPAEF5.js.map +0 -1
- 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-
|
|
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 = "
|
|
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-
|
|
9061
|
+
//# sourceMappingURL=chunk-XTQXFZUP.js.map
|