@aurabase/aurabase-js 0.3.1 → 0.4.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 +26 -795
- package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts → dist/AuraClient-BcFE8u1v.d.cts} +1 -1
- package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts → dist/AuraClient-BcFE8u1v.d.ts} +1 -1
- package/{packages/aurabase/dist → dist}/index.d.cts +1 -1
- package/{packages/aurabase/dist → dist}/index.d.ts +1 -1
- package/{packages/aurabase/dist → dist}/ssr.d.cts +1 -1
- package/{packages/aurabase/dist → dist}/ssr.d.ts +1 -1
- package/package.json +72 -82
- package/.changeset/config.json +0 -11
- package/CHANGELOG.md +0 -30
- package/packages/ai/LICENSE +0 -21
- package/packages/ai/README.md +0 -106
- package/packages/ai/dist/index.cjs +0 -229
- package/packages/ai/dist/index.d.cts +0 -139
- package/packages/ai/dist/index.d.ts +0 -139
- package/packages/ai/dist/index.js +0 -226
- package/packages/ai/package.json +0 -61
- package/packages/aurabase/LICENSE +0 -21
- package/packages/aurabase/README.md +0 -64
- package/packages/aurabase/package.json +0 -80
- package/packages/aurabase/vitest.e2e.config.ts +0 -9
- package/packages/auth/LICENSE +0 -21
- package/packages/auth/README.md +0 -215
- package/packages/auth/dist/index.cjs +0 -1175
- package/packages/auth/dist/index.d.cts +0 -397
- package/packages/auth/dist/index.d.ts +0 -397
- package/packages/auth/dist/index.js +0 -1170
- package/packages/auth/package.json +0 -61
- package/packages/core/LICENSE +0 -21
- package/packages/core/README.md +0 -132
- package/packages/core/dist/index.cjs +0 -695
- package/packages/core/dist/index.d.cts +0 -1137
- package/packages/core/dist/index.d.ts +0 -1137
- package/packages/core/dist/index.js +0 -673
- package/packages/core/package.json +0 -57
- package/packages/db/LICENSE +0 -21
- package/packages/db/README.md +0 -152
- package/packages/db/VENDORED.md +0 -93
- package/packages/db/dist/index.cjs +0 -544
- package/packages/db/dist/index.d.cts +0 -1111
- package/packages/db/dist/index.d.ts +0 -1111
- package/packages/db/dist/index.js +0 -541
- package/packages/db/package.json +0 -62
- package/packages/functions/LICENSE +0 -21
- package/packages/functions/README.md +0 -96
- package/packages/functions/dist/index.cjs +0 -173
- package/packages/functions/dist/index.d.cts +0 -81
- package/packages/functions/dist/index.d.ts +0 -81
- package/packages/functions/dist/index.js +0 -168
- package/packages/functions/package.json +0 -60
- package/packages/gen/LICENSE +0 -21
- package/packages/gen/README.md +0 -84
- package/packages/gen/dist/cli.cjs +0 -334
- package/packages/gen/dist/cli.d.cts +0 -1
- package/packages/gen/dist/cli.d.ts +0 -1
- package/packages/gen/dist/cli.js +0 -332
- package/packages/gen/dist/index.cjs +0 -257
- package/packages/gen/dist/index.d.cts +0 -12
- package/packages/gen/dist/index.d.ts +0 -12
- package/packages/gen/dist/index.js +0 -255
- package/packages/gen/package.json +0 -66
- package/packages/notifications/LICENSE +0 -21
- package/packages/notifications/README.md +0 -134
- package/packages/notifications/dist/index.cjs +0 -128
- package/packages/notifications/dist/index.d.cts +0 -81
- package/packages/notifications/dist/index.d.ts +0 -81
- package/packages/notifications/dist/index.js +0 -125
- package/packages/notifications/package.json +0 -60
- package/packages/realtime/LICENSE +0 -21
- package/packages/realtime/README.md +0 -183
- package/packages/realtime/dist/index.cjs +0 -977
- package/packages/realtime/dist/index.d.cts +0 -259
- package/packages/realtime/dist/index.d.ts +0 -259
- package/packages/realtime/dist/index.js +0 -973
- package/packages/realtime/package.json +0 -60
- package/packages/storage/LICENSE +0 -21
- package/packages/storage/README.md +0 -141
- package/packages/storage/dist/index.cjs +0 -333
- package/packages/storage/dist/index.d.cts +0 -185
- package/packages/storage/dist/index.d.ts +0 -185
- package/packages/storage/dist/index.js +0 -329
- package/packages/storage/package.json +0 -60
- /package/{packages/aurabase/dist → dist}/index.cjs +0 -0
- /package/{packages/aurabase/dist → dist}/index.js +0 -0
- /package/{packages/aurabase/dist → dist}/ssr.cjs +0 -0
- /package/{packages/aurabase/dist → dist}/ssr.js +0 -0
|
@@ -1,673 +0,0 @@
|
|
|
1
|
-
// src/AuraError.ts
|
|
2
|
-
var AuraError = class _AuraError extends Error {
|
|
3
|
-
status;
|
|
4
|
-
code;
|
|
5
|
-
details;
|
|
6
|
-
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
7
|
-
__isAuraError = true;
|
|
8
|
-
constructor(status, body) {
|
|
9
|
-
const message = body?.error?.message ?? body?.message ?? `HTTP ${status}`;
|
|
10
|
-
super(message);
|
|
11
|
-
this.name = "AuraError";
|
|
12
|
-
this.status = status;
|
|
13
|
-
this.code = body?.error?.code ?? `http_${status}`;
|
|
14
|
-
this.details = body;
|
|
15
|
-
Object.setPrototypeOf(this, _AuraError.prototype);
|
|
16
|
-
}
|
|
17
|
-
get isNotFound() {
|
|
18
|
-
return this.status === 404;
|
|
19
|
-
}
|
|
20
|
-
get isUnauthorized() {
|
|
21
|
-
return this.status === 401;
|
|
22
|
-
}
|
|
23
|
-
get isForbidden() {
|
|
24
|
-
return this.status === 403;
|
|
25
|
-
}
|
|
26
|
-
get isRateLimited() {
|
|
27
|
-
return this.status === 429;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
function isAuraError(error) {
|
|
31
|
-
return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// src/types.ts
|
|
35
|
-
function redactUrl(u) {
|
|
36
|
-
const cut = u.search(/[?#]/);
|
|
37
|
-
return cut === -1 ? u : u.slice(0, cut);
|
|
38
|
-
}
|
|
39
|
-
function emitAuraEvent(onEvent, debug, event) {
|
|
40
|
-
const safe = "url" in event && typeof event.url === "string" ? { ...event, url: redactUrl(event.url) } : event;
|
|
41
|
-
if (debug && typeof console !== "undefined" && typeof console.debug === "function") {
|
|
42
|
-
try {
|
|
43
|
-
console.debug("[aurabase]", safe);
|
|
44
|
-
} catch {
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (!onEvent) return;
|
|
48
|
-
try {
|
|
49
|
-
onEvent(safe);
|
|
50
|
-
} catch {
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/stores/BaseAuthStore.ts
|
|
55
|
-
function decodeJwtPayload(token) {
|
|
56
|
-
try {
|
|
57
|
-
const parts = token.split(".");
|
|
58
|
-
if (parts.length !== 3) return null;
|
|
59
|
-
let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
60
|
-
while (payload.length % 4 !== 0) payload += "=";
|
|
61
|
-
const json = atob(payload);
|
|
62
|
-
return JSON.parse(json);
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function isTokenExpired(token, marginSec = 0) {
|
|
68
|
-
const payload = decodeJwtPayload(token);
|
|
69
|
-
if (!payload || typeof payload.exp !== "number") return true;
|
|
70
|
-
const nowSec = Math.floor(Date.now() / 1e3);
|
|
71
|
-
return payload.exp - marginSec <= nowSec;
|
|
72
|
-
}
|
|
73
|
-
var BaseAuthStore = class {
|
|
74
|
-
/**
|
|
75
|
-
* True si le store survit à un redirect pleine page (persistance disque, ex.
|
|
76
|
-
* localStorage). Le store de base est en mémoire → non persistant. Exploité par
|
|
77
|
-
* le flux PKCE : le `code_verifier` doit survivre au redirect OAuth.
|
|
78
|
-
*/
|
|
79
|
-
persistent = false;
|
|
80
|
-
_token = null;
|
|
81
|
-
_refreshToken = null;
|
|
82
|
-
_user = null;
|
|
83
|
-
_listeners = /* @__PURE__ */ new Set();
|
|
84
|
-
_removalEpoch = 0;
|
|
85
|
-
/** Stockage générique clé/valeur (ex: PKCE `code_verifier`). En mémoire par défaut. */
|
|
86
|
-
_kv = /* @__PURE__ */ new Map();
|
|
87
|
-
/** Hook d'observabilité (LOT 3) — câblé par `AuraClient` via `setObservabilityHook`. */
|
|
88
|
-
_onEvent;
|
|
89
|
-
_debug = false;
|
|
90
|
-
// -- Getters ----------------------------------------------------------------
|
|
91
|
-
/** Compteur incrémenté à chaque `clear()` — sert au commit-guard anti-race du refresh. */
|
|
92
|
-
get removalEpoch() {
|
|
93
|
-
return this._removalEpoch;
|
|
94
|
-
}
|
|
95
|
-
get token() {
|
|
96
|
-
return this._token;
|
|
97
|
-
}
|
|
98
|
-
get refreshToken() {
|
|
99
|
-
return this._refreshToken;
|
|
100
|
-
}
|
|
101
|
-
get user() {
|
|
102
|
-
return this._user;
|
|
103
|
-
}
|
|
104
|
-
/** True if a non-expired access token is present. */
|
|
105
|
-
get isValid() {
|
|
106
|
-
return this._token !== null && !isTokenExpired(this._token);
|
|
107
|
-
}
|
|
108
|
-
// -- Mutators ---------------------------------------------------------------
|
|
109
|
-
save(token, refreshToken, user) {
|
|
110
|
-
this._token = token;
|
|
111
|
-
this._refreshToken = refreshToken;
|
|
112
|
-
this._user = user;
|
|
113
|
-
this._triggerChange();
|
|
114
|
-
}
|
|
115
|
-
clear() {
|
|
116
|
-
this._removalEpoch++;
|
|
117
|
-
this._token = null;
|
|
118
|
-
this._refreshToken = null;
|
|
119
|
-
this._user = null;
|
|
120
|
-
this._kv.clear();
|
|
121
|
-
this._triggerChange();
|
|
122
|
-
}
|
|
123
|
-
// -- Generic key/value storage ------------------------------------------------
|
|
124
|
-
// Utilisé par le flux PKCE (`code_verifier`) et tout autre besoin de stockage
|
|
125
|
-
// arbitraire lié à l'auth. Impl de base : en mémoire (ne survit pas à un
|
|
126
|
-
// redirect pleine page — voir `LocalAuthStore` pour la persistance).
|
|
127
|
-
setItem(key, value) {
|
|
128
|
-
this._kv.set(key, value);
|
|
129
|
-
}
|
|
130
|
-
getItem(key) {
|
|
131
|
-
return this._kv.get(key) ?? null;
|
|
132
|
-
}
|
|
133
|
-
removeItem(key) {
|
|
134
|
-
this._kv.delete(key);
|
|
135
|
-
}
|
|
136
|
-
// -- Observability (LOT 3) ----------------------------------------------------
|
|
137
|
-
/**
|
|
138
|
-
* Câble le hook d'observabilité (appelé par `AuraClient` à la construction).
|
|
139
|
-
* Permet à `LocalAuthStore` d'émettre `storage-blocked` quand `localStorage` est
|
|
140
|
-
* indisponible (navigation privée Safari, quota dépassé, storage désactivé…).
|
|
141
|
-
* Sans effet sur `BaseAuthStore` elle-même (en mémoire, jamais "bloquée").
|
|
142
|
-
*/
|
|
143
|
-
setObservabilityHook(onEvent, debug) {
|
|
144
|
-
this._onEvent = onEvent;
|
|
145
|
-
this._debug = !!debug;
|
|
146
|
-
}
|
|
147
|
-
/** Émet `{kind:'storage-blocked', op}` — ne throw jamais (voir `emitAuraEvent`). */
|
|
148
|
-
_emitStorageBlocked(op) {
|
|
149
|
-
emitAuraEvent(this._onEvent, this._debug, { kind: "storage-blocked", op });
|
|
150
|
-
}
|
|
151
|
-
// -- Event emitter ----------------------------------------------------------
|
|
152
|
-
/**
|
|
153
|
-
* Register a callback that fires whenever the auth state changes.
|
|
154
|
-
* Returns an unsubscribe function.
|
|
155
|
-
*/
|
|
156
|
-
onChange(callback) {
|
|
157
|
-
this._listeners.add(callback);
|
|
158
|
-
return () => {
|
|
159
|
-
this._listeners.delete(callback);
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
// -- Internal ---------------------------------------------------------------
|
|
163
|
-
_triggerChange() {
|
|
164
|
-
for (const cb of this._listeners) {
|
|
165
|
-
try {
|
|
166
|
-
cb(this._token, this._user);
|
|
167
|
-
} catch {
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
|
|
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
|
-
// src/fetch.ts
|
|
227
|
-
var REFRESH_MARGIN_SEC = 30;
|
|
228
|
-
var RETRY_AFTER_CAP_MS = 6e4;
|
|
229
|
-
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
230
|
-
function unwrapEnvelope(body) {
|
|
231
|
-
if (body !== null && typeof body === "object" && "data" in body) {
|
|
232
|
-
return body.data;
|
|
233
|
-
}
|
|
234
|
-
return body;
|
|
235
|
-
}
|
|
236
|
-
var inflightRefreshes = /* @__PURE__ */ new WeakMap();
|
|
237
|
-
async function doRefresh(client) {
|
|
238
|
-
const store = client.authStore;
|
|
239
|
-
const rt = store.refreshToken;
|
|
240
|
-
if (!rt) {
|
|
241
|
-
return {
|
|
242
|
-
data: null,
|
|
243
|
-
error: new AuraError(0, { error: { code: "no_session", message: "No refresh token available" } })
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
const epoch = store.removalEpoch;
|
|
247
|
-
let res;
|
|
248
|
-
try {
|
|
249
|
-
res = await client.fetchFn(`${client.baseUrl}/v1/auth/${client.projectId}/refresh`, {
|
|
250
|
-
method: "POST",
|
|
251
|
-
headers: { "Content-Type": "application/json" },
|
|
252
|
-
body: serializeBody({ refresh_token: rt })
|
|
253
|
-
});
|
|
254
|
-
} catch (err) {
|
|
255
|
-
return {
|
|
256
|
-
data: null,
|
|
257
|
-
error: new AuraError(0, {
|
|
258
|
-
error: { code: "network_error", message: err instanceof Error ? err.message : "Network request failed" }
|
|
259
|
-
})
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
if (!res.ok) {
|
|
263
|
-
let body2 = null;
|
|
264
|
-
try {
|
|
265
|
-
body2 = await res.json();
|
|
266
|
-
} catch {
|
|
267
|
-
}
|
|
268
|
-
if (res.status === 400 || res.status === 401 || res.status === 403) {
|
|
269
|
-
if (store.removalEpoch === epoch && store.refreshToken === rt) {
|
|
270
|
-
store.clear();
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return { data: null, error: new AuraError(res.status, body2) };
|
|
274
|
-
}
|
|
275
|
-
let body;
|
|
276
|
-
try {
|
|
277
|
-
body = await res.json();
|
|
278
|
-
} catch {
|
|
279
|
-
return {
|
|
280
|
-
data: null,
|
|
281
|
-
error: new AuraError(0, { error: { code: "invalid_response", message: "Invalid refresh response (non-JSON body)" } })
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
const data = unwrapEnvelope(body);
|
|
285
|
-
if (data?.access_token && data?.refresh_token && data?.user) {
|
|
286
|
-
if (store.removalEpoch !== epoch || store.refreshToken !== rt) {
|
|
287
|
-
emitAuraEvent(client.onEvent, client.debug, {
|
|
288
|
-
kind: "refresh-discarded",
|
|
289
|
-
reason: "Session changed during refresh (concurrent signIn/signOut/rotation)"
|
|
290
|
-
});
|
|
291
|
-
return {
|
|
292
|
-
data: null,
|
|
293
|
-
error: new AuraError(409, { error: { code: "refresh_discarded", message: "Session changed during refresh" } })
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
store.save(data.access_token, data.refresh_token, data.user);
|
|
297
|
-
emitAuraEvent(client.onEvent, client.debug, { kind: "refresh-success" });
|
|
298
|
-
return { data, error: null };
|
|
299
|
-
}
|
|
300
|
-
return {
|
|
301
|
-
data: null,
|
|
302
|
-
error: new AuraError(0, { error: { code: "invalid_response", message: "Invalid refresh response" } })
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
function withRefreshLock(client, fn) {
|
|
306
|
-
const browser = typeof window !== "undefined";
|
|
307
|
-
const nav = globalThis.navigator;
|
|
308
|
-
const locks = nav?.locks;
|
|
309
|
-
if (!browser || !locks || typeof locks.request !== "function") {
|
|
310
|
-
return fn();
|
|
311
|
-
}
|
|
312
|
-
const request = locks.request;
|
|
313
|
-
const lockName = `aura-refresh-${client.projectId}`;
|
|
314
|
-
const timeoutMs = (client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS) || DEFAULT_REQUEST_TIMEOUT_MS;
|
|
315
|
-
let timer;
|
|
316
|
-
const options = {};
|
|
317
|
-
if (typeof AbortController !== "undefined") {
|
|
318
|
-
const ctrl = new AbortController();
|
|
319
|
-
timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
320
|
-
timer.unref?.();
|
|
321
|
-
options.signal = ctrl.signal;
|
|
322
|
-
}
|
|
323
|
-
const clearTimer = () => {
|
|
324
|
-
if (timer) {
|
|
325
|
-
clearTimeout(timer);
|
|
326
|
-
timer = void 0;
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
try {
|
|
330
|
-
return Promise.resolve(
|
|
331
|
-
request(lockName, options, () => {
|
|
332
|
-
clearTimer();
|
|
333
|
-
return fn();
|
|
334
|
-
})
|
|
335
|
-
).catch(() => {
|
|
336
|
-
clearTimer();
|
|
337
|
-
return fn();
|
|
338
|
-
});
|
|
339
|
-
} catch {
|
|
340
|
-
clearTimer();
|
|
341
|
-
return fn();
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
function refreshOnce(client) {
|
|
345
|
-
const existing = inflightRefreshes.get(client);
|
|
346
|
-
if (existing) return existing;
|
|
347
|
-
const p = withRefreshLock(client, () => doRefresh(client)).finally(
|
|
348
|
-
() => inflightRefreshes.delete(client)
|
|
349
|
-
);
|
|
350
|
-
inflightRefreshes.set(client, p);
|
|
351
|
-
return p;
|
|
352
|
-
}
|
|
353
|
-
async function maybeRefresh(client) {
|
|
354
|
-
const store = client.authStore;
|
|
355
|
-
if (!store.token || !store.refreshToken) return;
|
|
356
|
-
if (!isTokenExpired(store.token, REFRESH_MARGIN_SEC)) return;
|
|
357
|
-
await refreshOnce(client);
|
|
358
|
-
}
|
|
359
|
-
async function fetchWithAuth(url, init, client) {
|
|
360
|
-
if (!init.skipAuthRefresh) {
|
|
361
|
-
await maybeRefresh(client);
|
|
362
|
-
}
|
|
363
|
-
const method = (init.method ?? "GET").toUpperCase();
|
|
364
|
-
const retryEnabled = !init.skipRetry && (client.retryConfig?.enabled ?? true) && isRetryableMethod(method);
|
|
365
|
-
const maxRetries = client.retryConfig?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
366
|
-
const store = client.authStore;
|
|
367
|
-
const isFormData = typeof FormData !== "undefined" && init.body instanceof FormData;
|
|
368
|
-
const timeoutMs = client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
369
|
-
const applyTimeout = timeoutMs > 0 && init.signal === void 0 && !isFormData;
|
|
370
|
-
let attempt = 0;
|
|
371
|
-
for (; ; ) {
|
|
372
|
-
const headers = { ...init.headers };
|
|
373
|
-
if (client.apiKey) {
|
|
374
|
-
headers["apikey"] = client.apiKey;
|
|
375
|
-
}
|
|
376
|
-
if (init.authToken) {
|
|
377
|
-
headers["Authorization"] = `Bearer ${init.authToken}`;
|
|
378
|
-
} else if (store.token) {
|
|
379
|
-
headers["Authorization"] = `Bearer ${store.token}`;
|
|
380
|
-
}
|
|
381
|
-
if (!isFormData && init.body !== void 0) {
|
|
382
|
-
headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
|
|
383
|
-
}
|
|
384
|
-
if (attempt > 0) {
|
|
385
|
-
headers["X-Retry-Count"] = String(attempt);
|
|
386
|
-
}
|
|
387
|
-
let timedOut = false;
|
|
388
|
-
let timeoutTimer;
|
|
389
|
-
let attemptSignal = init.signal;
|
|
390
|
-
if (applyTimeout) {
|
|
391
|
-
const ctrl = new AbortController();
|
|
392
|
-
timeoutTimer = setTimeout(() => {
|
|
393
|
-
timedOut = true;
|
|
394
|
-
ctrl.abort();
|
|
395
|
-
}, timeoutMs);
|
|
396
|
-
timeoutTimer.unref?.();
|
|
397
|
-
attemptSignal = ctrl.signal;
|
|
398
|
-
}
|
|
399
|
-
const clearTimeoutTimer = () => {
|
|
400
|
-
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
401
|
-
};
|
|
402
|
-
const requestInit = {
|
|
403
|
-
method,
|
|
404
|
-
headers,
|
|
405
|
-
signal: attemptSignal
|
|
406
|
-
};
|
|
407
|
-
if (init.body !== void 0) {
|
|
408
|
-
requestInit.body = isFormData ? init.body : serializeBody(init.body);
|
|
409
|
-
}
|
|
410
|
-
let res;
|
|
411
|
-
try {
|
|
412
|
-
res = await client.fetchFn(url, requestInit);
|
|
413
|
-
} catch (err) {
|
|
414
|
-
clearTimeoutTimer();
|
|
415
|
-
if (retryEnabled && attempt < maxRetries && !isAbortError(err)) {
|
|
416
|
-
const nextAttempt = attempt + 1;
|
|
417
|
-
emitAuraEvent(client.onEvent, client.debug, { kind: "retry", attempt: nextAttempt, url });
|
|
418
|
-
await abortableSleep(computeBackoff(attempt), init.signal);
|
|
419
|
-
attempt = nextAttempt;
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
if (timedOut) {
|
|
423
|
-
emitAuraEvent(client.onEvent, client.debug, { kind: "timeout", url });
|
|
424
|
-
}
|
|
425
|
-
return {
|
|
426
|
-
data: null,
|
|
427
|
-
error: new AuraError(0, {
|
|
428
|
-
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : { code: "network_error", message: err instanceof Error ? err.message : "Network request failed" }
|
|
429
|
-
})
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
clearTimeoutTimer();
|
|
433
|
-
if (retryEnabled && attempt < maxRetries && isRetryableStatus(res.status)) {
|
|
434
|
-
const delay = Math.min(
|
|
435
|
-
parseRetryAfter(res.headers.get("Retry-After")) ?? computeBackoff(attempt),
|
|
436
|
-
RETRY_AFTER_CAP_MS
|
|
437
|
-
);
|
|
438
|
-
await res.body?.cancel().catch(() => {
|
|
439
|
-
});
|
|
440
|
-
const nextAttempt = attempt + 1;
|
|
441
|
-
emitAuraEvent(client.onEvent, client.debug, { kind: "retry", attempt: nextAttempt, status: res.status, url });
|
|
442
|
-
await abortableSleep(delay, init.signal);
|
|
443
|
-
attempt = nextAttempt;
|
|
444
|
-
continue;
|
|
445
|
-
}
|
|
446
|
-
if (res.status === 401 && !init.skipAuthRefresh && !init._retried && client.authStore.refreshToken) {
|
|
447
|
-
const refreshed = await refreshOnce(client);
|
|
448
|
-
if (refreshed.data) {
|
|
449
|
-
await res.body?.cancel().catch(() => {
|
|
450
|
-
});
|
|
451
|
-
return fetchWithAuth(url, { ...init, _retried: true }, client);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
if (res.status === 204) {
|
|
455
|
-
return { data: null, error: null };
|
|
456
|
-
}
|
|
457
|
-
if (!res.ok) {
|
|
458
|
-
let body = null;
|
|
459
|
-
try {
|
|
460
|
-
body = await res.json();
|
|
461
|
-
} catch {
|
|
462
|
-
}
|
|
463
|
-
return { data: null, error: new AuraError(res.status, body) };
|
|
464
|
-
}
|
|
465
|
-
try {
|
|
466
|
-
const body = await res.json();
|
|
467
|
-
return {
|
|
468
|
-
data: unwrapEnvelope(body),
|
|
469
|
-
error: null,
|
|
470
|
-
meta: body !== null && typeof body === "object" && "meta" in body ? body.meta : void 0
|
|
471
|
-
};
|
|
472
|
-
} catch {
|
|
473
|
-
return { data: null, error: null };
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// src/BaseService.ts
|
|
479
|
-
var BaseService = class {
|
|
480
|
-
client;
|
|
481
|
-
constructor(client) {
|
|
482
|
-
this.client = client;
|
|
483
|
-
}
|
|
484
|
-
/** Build a full URL for the given path. */
|
|
485
|
-
url(path) {
|
|
486
|
-
return `${this.client.baseUrl}${path}`;
|
|
487
|
-
}
|
|
488
|
-
/** Convenience HTTP helper that delegates to fetchWithAuth. */
|
|
489
|
-
request(method, path, body, options) {
|
|
490
|
-
const init = {
|
|
491
|
-
method,
|
|
492
|
-
body,
|
|
493
|
-
headers: options?.headers,
|
|
494
|
-
signal: options?.signal,
|
|
495
|
-
skipAuthRefresh: options?.skipAuthRefresh,
|
|
496
|
-
authToken: options?.authToken
|
|
497
|
-
};
|
|
498
|
-
return fetchWithAuth(this.url(path), init, this.client);
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
// src/stores/LocalAuthStore.ts
|
|
503
|
-
var LocalAuthStore = class extends BaseAuthStore {
|
|
504
|
-
/** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
|
|
505
|
-
persistent = true;
|
|
506
|
-
key;
|
|
507
|
-
constructor(key = "aurabase_auth") {
|
|
508
|
-
super();
|
|
509
|
-
this.key = key;
|
|
510
|
-
this._loadFromStorage();
|
|
511
|
-
}
|
|
512
|
-
// -- Overrides --------------------------------------------------------------
|
|
513
|
-
save(token, refreshToken, user) {
|
|
514
|
-
super.save(token, refreshToken, user);
|
|
515
|
-
this._persist();
|
|
516
|
-
}
|
|
517
|
-
clear() {
|
|
518
|
-
super.clear();
|
|
519
|
-
this._removeStorage();
|
|
520
|
-
this._removePkceVerifiers();
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Supprime de localStorage tous les artefacts d'auth éphémères sur disque :
|
|
524
|
-
* - `code_verifier` PKCE (`aura-<projectId>-pkce-code-verifier`),
|
|
525
|
-
* - marqueur d'initiation OAuth implicit (`aura-<projectId>-oauth-implicit-initiated`,
|
|
526
|
-
* SEC-F3). Un `signOut`/`clear()` ne doit laisser aucun résidu réutilisable (parité
|
|
527
|
-
* `@supabase/ssr` : ces artefacts sont éphémères). On itère les clés préfixées pour
|
|
528
|
-
* couvrir un store partagé entre plusieurs projets, sans dépendre du `projectId` courant.
|
|
529
|
-
*/
|
|
530
|
-
_removePkceVerifiers() {
|
|
531
|
-
if (typeof localStorage === "undefined") return;
|
|
532
|
-
try {
|
|
533
|
-
const toRemove = [];
|
|
534
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
535
|
-
const k = localStorage.key(i);
|
|
536
|
-
if (k && /^aura-.*-(pkce-code-verifier|oauth-implicit-initiated)$/.test(k)) toRemove.push(k);
|
|
537
|
-
}
|
|
538
|
-
for (const k of toRemove) localStorage.removeItem(k);
|
|
539
|
-
} catch {
|
|
540
|
-
this._emitStorageBlocked("removePkceVerifiers");
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
// -- Generic key/value storage (override : persistance localStorage) --------
|
|
544
|
-
// CRITIQUE pour le flux PKCE : le `code_verifier` doit survivre au redirect
|
|
545
|
-
// pleine page vers le provider OAuth et retour — un store purement mémoire
|
|
546
|
-
// ne survivrait pas à ce cycle de navigation.
|
|
547
|
-
setItem(key, value) {
|
|
548
|
-
super.setItem(key, value);
|
|
549
|
-
if (typeof localStorage === "undefined") return;
|
|
550
|
-
try {
|
|
551
|
-
localStorage.setItem(key, value);
|
|
552
|
-
} catch {
|
|
553
|
-
this._emitStorageBlocked("setItem");
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
getItem(key) {
|
|
557
|
-
if (typeof localStorage === "undefined") return super.getItem(key);
|
|
558
|
-
try {
|
|
559
|
-
const v = localStorage.getItem(key);
|
|
560
|
-
if (v !== null) return v;
|
|
561
|
-
} catch {
|
|
562
|
-
this._emitStorageBlocked("getItem");
|
|
563
|
-
}
|
|
564
|
-
return super.getItem(key);
|
|
565
|
-
}
|
|
566
|
-
removeItem(key) {
|
|
567
|
-
super.removeItem(key);
|
|
568
|
-
if (typeof localStorage === "undefined") return;
|
|
569
|
-
try {
|
|
570
|
-
localStorage.removeItem(key);
|
|
571
|
-
} catch {
|
|
572
|
-
this._emitStorageBlocked("removeItem");
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
// -- Cookie helpers (SSR) ---------------------------------------------------
|
|
576
|
-
/**
|
|
577
|
-
* Populate the store from a raw `Cookie` header string (server-side).
|
|
578
|
-
* Looks for the key matching `this.key`.
|
|
579
|
-
*/
|
|
580
|
-
loadFromCookie(cookieString) {
|
|
581
|
-
const prefix = `${this.key}=`;
|
|
582
|
-
const entry = cookieString.split(";").map((c) => c.trim()).find((c) => c.startsWith(prefix));
|
|
583
|
-
if (!entry) return;
|
|
584
|
-
try {
|
|
585
|
-
const raw = decodeURIComponent(entry.slice(prefix.length));
|
|
586
|
-
const parsed = JSON.parse(raw);
|
|
587
|
-
if (parsed.token && parsed.refreshToken && parsed.user) {
|
|
588
|
-
super.save(parsed.token, parsed.refreshToken, parsed.user);
|
|
589
|
-
}
|
|
590
|
-
} catch {
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Serialize current auth state into a `Set-Cookie` value.
|
|
595
|
-
*/
|
|
596
|
-
exportToCookie(options = {}) {
|
|
597
|
-
const data = {
|
|
598
|
-
token: this.token,
|
|
599
|
-
refreshToken: this.refreshToken,
|
|
600
|
-
user: this.user
|
|
601
|
-
};
|
|
602
|
-
const encoded = encodeURIComponent(JSON.stringify(data));
|
|
603
|
-
const parts = [`${this.key}=${encoded}`];
|
|
604
|
-
if (options.path) parts.push(`Path=${options.path}`);
|
|
605
|
-
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
606
|
-
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
607
|
-
if (options.secure) parts.push("Secure");
|
|
608
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
609
|
-
if (options.httpOnly) parts.push("HttpOnly");
|
|
610
|
-
return parts.join("; ");
|
|
611
|
-
}
|
|
612
|
-
// -- Private ----------------------------------------------------------------
|
|
613
|
-
_loadFromStorage() {
|
|
614
|
-
if (typeof localStorage === "undefined") return;
|
|
615
|
-
try {
|
|
616
|
-
const raw = localStorage.getItem(this.key);
|
|
617
|
-
if (!raw) return;
|
|
618
|
-
const parsed = JSON.parse(raw);
|
|
619
|
-
if (parsed.token && parsed.refreshToken && parsed.user) {
|
|
620
|
-
super.save(parsed.token, parsed.refreshToken, parsed.user);
|
|
621
|
-
}
|
|
622
|
-
} catch {
|
|
623
|
-
this._emitStorageBlocked("loadFromStorage");
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
_persist() {
|
|
627
|
-
if (typeof localStorage === "undefined") return;
|
|
628
|
-
const data = {
|
|
629
|
-
token: this.token,
|
|
630
|
-
refreshToken: this.refreshToken,
|
|
631
|
-
user: this.user
|
|
632
|
-
};
|
|
633
|
-
try {
|
|
634
|
-
localStorage.setItem(this.key, JSON.stringify(data));
|
|
635
|
-
} catch {
|
|
636
|
-
this._emitStorageBlocked("persist");
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
_removeStorage() {
|
|
640
|
-
if (typeof localStorage === "undefined") return;
|
|
641
|
-
try {
|
|
642
|
-
localStorage.removeItem(this.key);
|
|
643
|
-
} catch {
|
|
644
|
-
this._emitStorageBlocked("removeStorage");
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
|
|
649
|
-
// src/cookie-utils.ts
|
|
650
|
-
function parseCookieHeader(header) {
|
|
651
|
-
const result = {};
|
|
652
|
-
if (!header) return result;
|
|
653
|
-
for (const pair of header.split(";")) {
|
|
654
|
-
const idx = pair.indexOf("=");
|
|
655
|
-
if (idx === -1) continue;
|
|
656
|
-
const key = pair.slice(0, idx).trim();
|
|
657
|
-
const value = pair.slice(idx + 1).trim();
|
|
658
|
-
if (key) result[key] = decodeURIComponent(value);
|
|
659
|
-
}
|
|
660
|
-
return result;
|
|
661
|
-
}
|
|
662
|
-
function serializeCookieHeader(name, value, options = {}) {
|
|
663
|
-
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
664
|
-
if (options.path) parts.push(`Path=${options.path}`);
|
|
665
|
-
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
666
|
-
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
667
|
-
if (options.secure) parts.push("Secure");
|
|
668
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
669
|
-
if (options.httpOnly) parts.push("HttpOnly");
|
|
670
|
-
return parts.join("; ");
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
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 };
|