@aurabase/core 0.4.0 → 0.5.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 +10 -5
- package/dist/index.cjs +308 -103
- package/dist/index.d.cts +611 -72
- package/dist/index.d.ts +611 -72
- package/dist/index.js +306 -104
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -31,6 +31,70 @@ function isAuraError(error) {
|
|
|
31
31
|
return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// src/retry.ts
|
|
35
|
+
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
|
|
36
|
+
var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
37
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
38
|
+
var BASE_MS = 500;
|
|
39
|
+
var CAP_MS = 1e4;
|
|
40
|
+
function isRetryableStatus(status) {
|
|
41
|
+
return RETRYABLE_STATUS.has(status);
|
|
42
|
+
}
|
|
43
|
+
function isRetryableMethod(method) {
|
|
44
|
+
return RETRYABLE_METHODS.has(method.toUpperCase());
|
|
45
|
+
}
|
|
46
|
+
function computeBackoff(attempt, rand = Math.random) {
|
|
47
|
+
const bound = Math.min(BASE_MS * 2 ** attempt, CAP_MS);
|
|
48
|
+
return Math.floor(rand() * bound);
|
|
49
|
+
}
|
|
50
|
+
function parseRetryAfter(header, now = Date.now()) {
|
|
51
|
+
if (header === null) return null;
|
|
52
|
+
const trimmed = header.trim();
|
|
53
|
+
if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10) * 1e3;
|
|
54
|
+
const dateMs = Date.parse(trimmed);
|
|
55
|
+
if (Number.isNaN(dateMs)) return null;
|
|
56
|
+
return Math.max(0, dateMs - now);
|
|
57
|
+
}
|
|
58
|
+
function abortableSleep(ms, signal) {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
if (signal?.aborted) {
|
|
61
|
+
resolve();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const onAbort = () => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
resolve();
|
|
67
|
+
};
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
signal?.removeEventListener("abort", onAbort);
|
|
70
|
+
resolve();
|
|
71
|
+
}, ms);
|
|
72
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function isAbortError(err) {
|
|
76
|
+
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/scope.ts
|
|
80
|
+
function clientScope(baseUrl) {
|
|
81
|
+
let host;
|
|
82
|
+
try {
|
|
83
|
+
host = new URL(baseUrl).host;
|
|
84
|
+
} catch {
|
|
85
|
+
host = baseUrl;
|
|
86
|
+
}
|
|
87
|
+
return host.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/serialize.ts
|
|
91
|
+
function serializeBody(body) {
|
|
92
|
+
return JSON.stringify(
|
|
93
|
+
body,
|
|
94
|
+
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
34
98
|
// src/types.ts
|
|
35
99
|
function redactUrl(u) {
|
|
36
100
|
const cut = u.search(/[?#]/);
|
|
@@ -50,6 +114,60 @@ function emitAuraEvent(onEvent, debug, event) {
|
|
|
50
114
|
} catch {
|
|
51
115
|
}
|
|
52
116
|
}
|
|
117
|
+
var AUTH_SETTINGS_WRITABLE_FIELDS = [
|
|
118
|
+
"allow_signup",
|
|
119
|
+
"email_confirmation_required",
|
|
120
|
+
"password_min_length",
|
|
121
|
+
"mfa_enabled",
|
|
122
|
+
"allow_anonymous_users",
|
|
123
|
+
"jwt_expiry_seconds",
|
|
124
|
+
"refresh_token_expiry_days",
|
|
125
|
+
"site_url",
|
|
126
|
+
"oauth_providers",
|
|
127
|
+
"new_device_alerts_enabled",
|
|
128
|
+
"webhook_url",
|
|
129
|
+
"webhook_secret",
|
|
130
|
+
"smtp_host",
|
|
131
|
+
"smtp_port",
|
|
132
|
+
"smtp_username",
|
|
133
|
+
"smtp_password",
|
|
134
|
+
"smtp_from_email",
|
|
135
|
+
"smtp_from_name",
|
|
136
|
+
"smtp_clear",
|
|
137
|
+
"oauth_credentials",
|
|
138
|
+
"oauth_credentials_clear",
|
|
139
|
+
"captcha_enabled",
|
|
140
|
+
"captcha_provider",
|
|
141
|
+
"captcha_site_key",
|
|
142
|
+
"captcha_secret_key",
|
|
143
|
+
"password_check_hibp",
|
|
144
|
+
"sms_provider",
|
|
145
|
+
"sms_api_key",
|
|
146
|
+
"sms_api_secret",
|
|
147
|
+
"sms_from_number",
|
|
148
|
+
"sms_clear",
|
|
149
|
+
"redirect_urls",
|
|
150
|
+
"session_inactivity_timeout_seconds",
|
|
151
|
+
"hook_send_sms_url",
|
|
152
|
+
"hook_send_email_url",
|
|
153
|
+
"hook_mfa_verification_url",
|
|
154
|
+
"hook_password_verification_url",
|
|
155
|
+
"auto_link_by_email",
|
|
156
|
+
"rate_limit_login_per_hour",
|
|
157
|
+
"rate_limit_emails_per_hour",
|
|
158
|
+
"rate_limit_signup_per_hour",
|
|
159
|
+
"revoke_on_new_device",
|
|
160
|
+
"enforce_ip_binding",
|
|
161
|
+
"allowed_email_domains",
|
|
162
|
+
"blocked_email_domains",
|
|
163
|
+
"password_require_uppercase",
|
|
164
|
+
"password_require_number",
|
|
165
|
+
"password_require_special",
|
|
166
|
+
"hook_before_signup_url",
|
|
167
|
+
"hook_after_signup_url",
|
|
168
|
+
"hook_custom_access_token_url",
|
|
169
|
+
"hook_secret"
|
|
170
|
+
];
|
|
53
171
|
|
|
54
172
|
// src/stores/BaseAuthStore.ts
|
|
55
173
|
function decodeJwtPayload(token) {
|
|
@@ -170,59 +288,6 @@ var BaseAuthStore = class {
|
|
|
170
288
|
}
|
|
171
289
|
};
|
|
172
290
|
|
|
173
|
-
// src/retry.ts
|
|
174
|
-
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
|
|
175
|
-
var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
176
|
-
var DEFAULT_MAX_RETRIES = 3;
|
|
177
|
-
var BASE_MS = 500;
|
|
178
|
-
var CAP_MS = 1e4;
|
|
179
|
-
function isRetryableStatus(status) {
|
|
180
|
-
return RETRYABLE_STATUS.has(status);
|
|
181
|
-
}
|
|
182
|
-
function isRetryableMethod(method) {
|
|
183
|
-
return RETRYABLE_METHODS.has(method.toUpperCase());
|
|
184
|
-
}
|
|
185
|
-
function computeBackoff(attempt, rand = Math.random) {
|
|
186
|
-
const bound = Math.min(BASE_MS * 2 ** attempt, CAP_MS);
|
|
187
|
-
return Math.floor(rand() * bound);
|
|
188
|
-
}
|
|
189
|
-
function parseRetryAfter(header, now = Date.now()) {
|
|
190
|
-
if (header === null) return null;
|
|
191
|
-
const trimmed = header.trim();
|
|
192
|
-
if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10) * 1e3;
|
|
193
|
-
const dateMs = Date.parse(trimmed);
|
|
194
|
-
if (Number.isNaN(dateMs)) return null;
|
|
195
|
-
return Math.max(0, dateMs - now);
|
|
196
|
-
}
|
|
197
|
-
function abortableSleep(ms, signal) {
|
|
198
|
-
return new Promise((resolve) => {
|
|
199
|
-
if (signal?.aborted) {
|
|
200
|
-
resolve();
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const onAbort = () => {
|
|
204
|
-
clearTimeout(timer);
|
|
205
|
-
resolve();
|
|
206
|
-
};
|
|
207
|
-
const timer = setTimeout(() => {
|
|
208
|
-
signal?.removeEventListener("abort", onAbort);
|
|
209
|
-
resolve();
|
|
210
|
-
}, ms);
|
|
211
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
function isAbortError(err) {
|
|
215
|
-
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// src/serialize.ts
|
|
219
|
-
function serializeBody(body) {
|
|
220
|
-
return JSON.stringify(
|
|
221
|
-
body,
|
|
222
|
-
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
291
|
// src/fetch.ts
|
|
227
292
|
var REFRESH_MARGIN_SEC = 30;
|
|
228
293
|
var RETRY_AFTER_CAP_MS = 6e4;
|
|
@@ -233,6 +298,56 @@ function unwrapEnvelope(body) {
|
|
|
233
298
|
}
|
|
234
299
|
return body;
|
|
235
300
|
}
|
|
301
|
+
async function readTextBounded(res, timeoutMs) {
|
|
302
|
+
const body = res.body;
|
|
303
|
+
if (!body || typeof body.getReader !== "function" || !(timeoutMs > 0)) {
|
|
304
|
+
try {
|
|
305
|
+
return { ok: true, text: await res.text() };
|
|
306
|
+
} catch (err) {
|
|
307
|
+
return {
|
|
308
|
+
ok: false,
|
|
309
|
+
timedOut: false,
|
|
310
|
+
message: err instanceof Error ? err.message : "read failed"
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
let timer;
|
|
315
|
+
const TIMED_OUT = /* @__PURE__ */ Symbol("body-timeout");
|
|
316
|
+
try {
|
|
317
|
+
const reader = body.getReader();
|
|
318
|
+
const decoder = new TextDecoder();
|
|
319
|
+
let out = "";
|
|
320
|
+
const deadline = new Promise((resolve) => {
|
|
321
|
+
timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
|
|
322
|
+
timer.unref?.();
|
|
323
|
+
});
|
|
324
|
+
for (; ; ) {
|
|
325
|
+
const step = await Promise.race([reader.read(), deadline]);
|
|
326
|
+
if (step === TIMED_OUT) {
|
|
327
|
+
await reader.cancel().catch(() => {
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
timedOut: true,
|
|
332
|
+
message: `Response body not fully received within ${timeoutMs}ms`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (step.done) {
|
|
336
|
+
out += decoder.decode();
|
|
337
|
+
return { ok: true, text: out };
|
|
338
|
+
}
|
|
339
|
+
if (step.value) out += decoder.decode(step.value, { stream: true });
|
|
340
|
+
}
|
|
341
|
+
} catch (err) {
|
|
342
|
+
return {
|
|
343
|
+
ok: false,
|
|
344
|
+
timedOut: false,
|
|
345
|
+
message: err instanceof Error ? err.message : "read failed"
|
|
346
|
+
};
|
|
347
|
+
} finally {
|
|
348
|
+
if (timer) clearTimeout(timer);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
236
351
|
var inflightRefreshes = /* @__PURE__ */ new WeakMap();
|
|
237
352
|
async function doRefresh(client) {
|
|
238
353
|
const store = client.authStore;
|
|
@@ -240,32 +355,68 @@ async function doRefresh(client) {
|
|
|
240
355
|
if (!rt) {
|
|
241
356
|
return {
|
|
242
357
|
data: null,
|
|
243
|
-
error: new AuraError(0, {
|
|
358
|
+
error: new AuraError(0, {
|
|
359
|
+
error: { code: "no_session", message: "No refresh token available" }
|
|
360
|
+
})
|
|
244
361
|
};
|
|
245
362
|
}
|
|
246
363
|
const epoch = store.removalEpoch;
|
|
247
364
|
const headers = { "Content-Type": "application/json" };
|
|
248
365
|
if (client.apiKey) headers["apikey"] = client.apiKey;
|
|
366
|
+
const timeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
367
|
+
let timedOut = false;
|
|
368
|
+
let refreshTimer;
|
|
369
|
+
let refreshSignal;
|
|
370
|
+
if (timeoutMs > 0 && typeof AbortController !== "undefined") {
|
|
371
|
+
const ctrl = new AbortController();
|
|
372
|
+
refreshTimer = setTimeout(() => {
|
|
373
|
+
timedOut = true;
|
|
374
|
+
ctrl.abort();
|
|
375
|
+
}, timeoutMs);
|
|
376
|
+
refreshTimer.unref?.();
|
|
377
|
+
refreshSignal = ctrl.signal;
|
|
378
|
+
}
|
|
379
|
+
const clearRefreshTimer = () => {
|
|
380
|
+
if (refreshTimer) {
|
|
381
|
+
clearTimeout(refreshTimer);
|
|
382
|
+
refreshTimer = void 0;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
249
385
|
let res;
|
|
250
386
|
try {
|
|
251
|
-
res = await client.fetchFn(`${client.baseUrl}/v1/auth
|
|
387
|
+
res = await client.fetchFn(`${client.baseUrl}/v1/auth/refresh`, {
|
|
252
388
|
method: "POST",
|
|
253
389
|
headers,
|
|
254
|
-
body: serializeBody({ refresh_token: rt })
|
|
390
|
+
body: serializeBody({ refresh_token: rt }),
|
|
391
|
+
signal: refreshSignal
|
|
255
392
|
});
|
|
256
393
|
} catch (err) {
|
|
394
|
+
clearRefreshTimer();
|
|
395
|
+
if (timedOut) {
|
|
396
|
+
emitAuraEvent(client.onEvent, client.debug, {
|
|
397
|
+
kind: "timeout",
|
|
398
|
+
url: `${client.baseUrl}/v1/auth/refresh`
|
|
399
|
+
});
|
|
400
|
+
}
|
|
257
401
|
return {
|
|
258
402
|
data: null,
|
|
259
403
|
error: new AuraError(0, {
|
|
260
|
-
error: { code: "
|
|
404
|
+
error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
|
|
405
|
+
code: "network_error",
|
|
406
|
+
message: err instanceof Error ? err.message : "Network request failed"
|
|
407
|
+
}
|
|
261
408
|
})
|
|
262
409
|
};
|
|
263
410
|
}
|
|
411
|
+
clearRefreshTimer();
|
|
264
412
|
if (!res.ok) {
|
|
265
413
|
let body2 = null;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
414
|
+
const errRead = await readTextBounded(res, timeoutMs);
|
|
415
|
+
if (errRead.ok && errRead.text.trim() !== "") {
|
|
416
|
+
try {
|
|
417
|
+
body2 = JSON.parse(errRead.text);
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
269
420
|
}
|
|
270
421
|
const code = typeof body2?.error?.code === "string" ? body2.error.code : null;
|
|
271
422
|
const definitivelyRejected = res.status === 403 || res.status === 401 && code === "REFRESH_TOKEN_REJECTED";
|
|
@@ -277,12 +428,29 @@ async function doRefresh(client) {
|
|
|
277
428
|
return { data: null, error: new AuraError(res.status, body2) };
|
|
278
429
|
}
|
|
279
430
|
let body;
|
|
431
|
+
const okRead = await readTextBounded(res, timeoutMs);
|
|
432
|
+
if (!okRead.ok) {
|
|
433
|
+
if (okRead.timedOut) {
|
|
434
|
+
emitAuraEvent(client.onEvent, client.debug, {
|
|
435
|
+
kind: "timeout",
|
|
436
|
+
url: `${client.baseUrl}/v1/auth/refresh`
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
data: null,
|
|
441
|
+
error: new AuraError(0, {
|
|
442
|
+
error: okRead.timedOut ? { code: "timeout", message: `Refresh body not received within ${timeoutMs}ms` } : { code: "invalid_response", message: `Invalid refresh response (${okRead.message})` }
|
|
443
|
+
})
|
|
444
|
+
};
|
|
445
|
+
}
|
|
280
446
|
try {
|
|
281
|
-
body =
|
|
447
|
+
body = JSON.parse(okRead.text);
|
|
282
448
|
} catch {
|
|
283
449
|
return {
|
|
284
450
|
data: null,
|
|
285
|
-
error: new AuraError(0, {
|
|
451
|
+
error: new AuraError(0, {
|
|
452
|
+
error: { code: "invalid_response", message: "Invalid refresh response (non-JSON body)" }
|
|
453
|
+
})
|
|
286
454
|
};
|
|
287
455
|
}
|
|
288
456
|
const data = unwrapEnvelope(body);
|
|
@@ -294,7 +462,9 @@ async function doRefresh(client) {
|
|
|
294
462
|
});
|
|
295
463
|
return {
|
|
296
464
|
data: null,
|
|
297
|
-
error: new AuraError(409, {
|
|
465
|
+
error: new AuraError(409, {
|
|
466
|
+
error: { code: "refresh_discarded", message: "Session changed during refresh" }
|
|
467
|
+
})
|
|
298
468
|
};
|
|
299
469
|
}
|
|
300
470
|
store.save(data.access_token, data.refresh_token, data.user);
|
|
@@ -303,7 +473,9 @@ async function doRefresh(client) {
|
|
|
303
473
|
}
|
|
304
474
|
return {
|
|
305
475
|
data: null,
|
|
306
|
-
error: new AuraError(0, {
|
|
476
|
+
error: new AuraError(0, {
|
|
477
|
+
error: { code: "invalid_response", message: "Invalid refresh response" }
|
|
478
|
+
})
|
|
307
479
|
};
|
|
308
480
|
}
|
|
309
481
|
function withRefreshLock(client, fn) {
|
|
@@ -314,7 +486,7 @@ function withRefreshLock(client, fn) {
|
|
|
314
486
|
return fn();
|
|
315
487
|
}
|
|
316
488
|
const request = locks.request;
|
|
317
|
-
const lockName = `aura-refresh-${client.
|
|
489
|
+
const lockName = `aura-refresh-${clientScope(client.baseUrl)}`;
|
|
318
490
|
const timeoutMs = (client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS) || DEFAULT_REQUEST_TIMEOUT_MS;
|
|
319
491
|
let timer;
|
|
320
492
|
const options = {};
|
|
@@ -371,6 +543,7 @@ async function fetchWithAuth(url, init, client) {
|
|
|
371
543
|
const isFormData = typeof FormData !== "undefined" && init.body instanceof FormData;
|
|
372
544
|
const timeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
373
545
|
const applyTimeout = timeoutMs > 0 && init.signal === void 0 && !isFormData;
|
|
546
|
+
const bodyTimeoutMs = init.signal === void 0 ? timeoutMs : 0;
|
|
374
547
|
let attempt = 0;
|
|
375
548
|
for (; ; ) {
|
|
376
549
|
const headers = { ...init.headers };
|
|
@@ -429,7 +602,10 @@ async function fetchWithAuth(url, init, client) {
|
|
|
429
602
|
return {
|
|
430
603
|
data: null,
|
|
431
604
|
error: new AuraError(0, {
|
|
432
|
-
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
|
|
605
|
+
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
|
|
606
|
+
code: "network_error",
|
|
607
|
+
message: err instanceof Error ? err.message : "Network request failed"
|
|
608
|
+
}
|
|
433
609
|
})
|
|
434
610
|
};
|
|
435
611
|
}
|
|
@@ -442,7 +618,12 @@ async function fetchWithAuth(url, init, client) {
|
|
|
442
618
|
await res.body?.cancel().catch(() => {
|
|
443
619
|
});
|
|
444
620
|
const nextAttempt = attempt + 1;
|
|
445
|
-
emitAuraEvent(client.onEvent, client.debug, {
|
|
621
|
+
emitAuraEvent(client.onEvent, client.debug, {
|
|
622
|
+
kind: "retry",
|
|
623
|
+
attempt: nextAttempt,
|
|
624
|
+
status: res.status,
|
|
625
|
+
url
|
|
626
|
+
});
|
|
446
627
|
await abortableSleep(delay, init.signal);
|
|
447
628
|
attempt = nextAttempt;
|
|
448
629
|
continue;
|
|
@@ -460,26 +641,42 @@ async function fetchWithAuth(url, init, client) {
|
|
|
460
641
|
}
|
|
461
642
|
if (!res.ok) {
|
|
462
643
|
let body2 = null;
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
644
|
+
const errRead = await readTextBounded(res, bodyTimeoutMs);
|
|
645
|
+
if (errRead.ok && errRead.text.trim() !== "") {
|
|
646
|
+
try {
|
|
647
|
+
body2 = JSON.parse(errRead.text);
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
} else if (!errRead.ok && errRead.timedOut) {
|
|
651
|
+
emitAuraEvent(client.onEvent, client.debug, { kind: "timeout", url });
|
|
466
652
|
}
|
|
467
653
|
return { data: null, error: new AuraError(res.status, body2) };
|
|
468
654
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
655
|
+
const read = await readTextBounded(res, bodyTimeoutMs);
|
|
656
|
+
if (!read.ok) {
|
|
657
|
+
if (read.timedOut) {
|
|
658
|
+
emitAuraEvent(client.onEvent, client.debug, { kind: "timeout", url });
|
|
659
|
+
return {
|
|
660
|
+
data: null,
|
|
661
|
+
error: new AuraError(0, {
|
|
662
|
+
error: {
|
|
663
|
+
code: "timeout",
|
|
664
|
+
message: `Response body not fully received within ${bodyTimeoutMs}ms (HTTP ${res.status})`
|
|
665
|
+
}
|
|
666
|
+
})
|
|
667
|
+
};
|
|
668
|
+
}
|
|
473
669
|
return {
|
|
474
670
|
data: null,
|
|
475
671
|
error: new AuraError(0, {
|
|
476
672
|
error: {
|
|
477
673
|
code: "invalid_response",
|
|
478
|
-
message: `Unreadable response body (HTTP ${res.status}): ${
|
|
674
|
+
message: `Unreadable response body (HTTP ${res.status}): ${read.message}`
|
|
479
675
|
}
|
|
480
676
|
})
|
|
481
677
|
};
|
|
482
678
|
}
|
|
679
|
+
const raw = read.text;
|
|
483
680
|
if (raw.trim() === "") {
|
|
484
681
|
return { data: null, error: null };
|
|
485
682
|
}
|
|
@@ -532,12 +729,40 @@ var BaseService = class {
|
|
|
532
729
|
}
|
|
533
730
|
};
|
|
534
731
|
|
|
732
|
+
// src/cookie-utils.ts
|
|
733
|
+
function parseCookieHeader(header) {
|
|
734
|
+
const result = {};
|
|
735
|
+
if (!header) return result;
|
|
736
|
+
for (const pair of header.split(";")) {
|
|
737
|
+
const idx = pair.indexOf("=");
|
|
738
|
+
if (idx === -1) continue;
|
|
739
|
+
const key = pair.slice(0, idx).trim();
|
|
740
|
+
const value = pair.slice(idx + 1).trim();
|
|
741
|
+
if (key) result[key] = decodeURIComponent(value);
|
|
742
|
+
}
|
|
743
|
+
return result;
|
|
744
|
+
}
|
|
745
|
+
function serializeCookieHeader(name, value, options = {}) {
|
|
746
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
747
|
+
if (options.path) parts.push(`Path=${options.path}`);
|
|
748
|
+
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
749
|
+
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
750
|
+
if (options.secure) parts.push("Secure");
|
|
751
|
+
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
752
|
+
if (options.httpOnly) parts.push("HttpOnly");
|
|
753
|
+
return parts.join("; ");
|
|
754
|
+
}
|
|
755
|
+
|
|
535
756
|
// src/stores/LocalAuthStore.ts
|
|
536
757
|
var LocalAuthStore = class extends BaseAuthStore {
|
|
537
758
|
/** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
|
|
538
759
|
persistent = true;
|
|
539
760
|
key;
|
|
540
|
-
|
|
761
|
+
/**
|
|
762
|
+
* @param key Nom de l'entrée `localStorage` (et du cookie SSR). Scopez-le par projet :
|
|
763
|
+
* `aura-${clientScope(baseUrl)}-auth`.
|
|
764
|
+
*/
|
|
765
|
+
constructor(key) {
|
|
541
766
|
super();
|
|
542
767
|
this.key = key;
|
|
543
768
|
this._loadFromStorage();
|
|
@@ -554,11 +779,12 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
554
779
|
}
|
|
555
780
|
/**
|
|
556
781
|
* Supprime de localStorage tous les artefacts d'auth éphémères sur disque :
|
|
557
|
-
* - `code_verifier` PKCE (`aura-<
|
|
558
|
-
* - marqueur d'initiation OAuth implicit (`aura-<
|
|
782
|
+
* - `code_verifier` PKCE (`aura-<portée>-pkce-code-verifier`),
|
|
783
|
+
* - marqueur d'initiation OAuth implicit (`aura-<portée>-oauth-implicit-initiated`,
|
|
559
784
|
* SEC-F3). Un `signOut`/`clear()` ne doit laisser aucun résidu réutilisable (parité
|
|
560
785
|
* `@supabase/ssr` : ces artefacts sont éphémères). On itère les clés préfixées pour
|
|
561
|
-
* couvrir un store partagé entre plusieurs projets, sans dépendre
|
|
786
|
+
* couvrir un store partagé entre plusieurs projets, sans dépendre de la portée courante
|
|
787
|
+
* (`clientScope`, dérivée de l'URL du projet).
|
|
562
788
|
*/
|
|
563
789
|
_removePkceVerifiers() {
|
|
564
790
|
if (typeof localStorage === "undefined") return;
|
|
@@ -679,28 +905,4 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
679
905
|
}
|
|
680
906
|
};
|
|
681
907
|
|
|
682
|
-
|
|
683
|
-
function parseCookieHeader(header) {
|
|
684
|
-
const result = {};
|
|
685
|
-
if (!header) return result;
|
|
686
|
-
for (const pair of header.split(";")) {
|
|
687
|
-
const idx = pair.indexOf("=");
|
|
688
|
-
if (idx === -1) continue;
|
|
689
|
-
const key = pair.slice(0, idx).trim();
|
|
690
|
-
const value = pair.slice(idx + 1).trim();
|
|
691
|
-
if (key) result[key] = decodeURIComponent(value);
|
|
692
|
-
}
|
|
693
|
-
return result;
|
|
694
|
-
}
|
|
695
|
-
function serializeCookieHeader(name, value, options = {}) {
|
|
696
|
-
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
697
|
-
if (options.path) parts.push(`Path=${options.path}`);
|
|
698
|
-
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
699
|
-
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
700
|
-
if (options.secure) parts.push("Secure");
|
|
701
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
702
|
-
if (options.httpOnly) parts.push("HttpOnly");
|
|
703
|
-
return parts.join("; ");
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
export { AuraError, BaseAuthStore, BaseService, DEFAULT_MAX_RETRIES, LocalAuthStore, RETRYABLE_METHODS, RETRYABLE_STATUS, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
|
|
908
|
+
export { AUTH_SETTINGS_WRITABLE_FIELDS, AuraError, BaseAuthStore, BaseService, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, LocalAuthStore, RETRYABLE_METHODS, RETRYABLE_STATUS, abortableSleep, clientScope, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurabase/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Cœur du SDK Aurabase : fetch résilient, erreurs, stores auth, types de réponse (infrastructure interne)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"core"
|
|
46
46
|
],
|
|
47
47
|
"devDependencies": {
|
|
48
|
+
"@types/node": "20.14.15",
|
|
48
49
|
"tsup": "^8.0.0",
|
|
49
50
|
"typescript": "^5.5.0",
|
|
50
51
|
"vitest": "^4.1.6"
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"build": "tsup",
|
|
54
55
|
"test": "vitest run && tsc --noEmit -p tsconfig.test-d.json",
|
|
55
56
|
"test:types": "tsc --noEmit -p tsconfig.test-d.json",
|
|
56
|
-
"typecheck": "tsc --noEmit"
|
|
57
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
|
|
58
|
+
"typecheck:tests": "tsc --noEmit -p tsconfig.test.json"
|
|
57
59
|
}
|
|
58
60
|
}
|