@aurabase/core 0.4.1 → 0.5.1

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/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;
@@ -290,7 +355,9 @@ async function doRefresh(client) {
290
355
  if (!rt) {
291
356
  return {
292
357
  data: null,
293
- error: new AuraError(0, { error: { code: "no_session", message: "No refresh token available" } })
358
+ error: new AuraError(0, {
359
+ error: { code: "no_session", message: "No refresh token available" }
360
+ })
294
361
  };
295
362
  }
296
363
  const epoch = store.removalEpoch;
@@ -317,7 +384,7 @@ async function doRefresh(client) {
317
384
  };
318
385
  let res;
319
386
  try {
320
- res = await client.fetchFn(`${client.baseUrl}/v1/auth/${client.projectId}/refresh`, {
387
+ res = await client.fetchFn(`${client.baseUrl}/v1/auth/refresh`, {
321
388
  method: "POST",
322
389
  headers,
323
390
  body: serializeBody({ refresh_token: rt }),
@@ -328,13 +395,16 @@ async function doRefresh(client) {
328
395
  if (timedOut) {
329
396
  emitAuraEvent(client.onEvent, client.debug, {
330
397
  kind: "timeout",
331
- url: `${client.baseUrl}/v1/auth/${client.projectId}/refresh`
398
+ url: `${client.baseUrl}/v1/auth/refresh`
332
399
  });
333
400
  }
334
401
  return {
335
402
  data: null,
336
403
  error: new AuraError(0, {
337
- error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : { code: "network_error", message: err instanceof Error ? err.message : "Network request failed" }
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
+ }
338
408
  })
339
409
  };
340
410
  }
@@ -363,7 +433,7 @@ async function doRefresh(client) {
363
433
  if (okRead.timedOut) {
364
434
  emitAuraEvent(client.onEvent, client.debug, {
365
435
  kind: "timeout",
366
- url: `${client.baseUrl}/v1/auth/${client.projectId}/refresh`
436
+ url: `${client.baseUrl}/v1/auth/refresh`
367
437
  });
368
438
  }
369
439
  return {
@@ -378,7 +448,9 @@ async function doRefresh(client) {
378
448
  } catch {
379
449
  return {
380
450
  data: null,
381
- error: new AuraError(0, { error: { code: "invalid_response", message: "Invalid refresh response (non-JSON body)" } })
451
+ error: new AuraError(0, {
452
+ error: { code: "invalid_response", message: "Invalid refresh response (non-JSON body)" }
453
+ })
382
454
  };
383
455
  }
384
456
  const data = unwrapEnvelope(body);
@@ -390,7 +462,9 @@ async function doRefresh(client) {
390
462
  });
391
463
  return {
392
464
  data: null,
393
- error: new AuraError(409, { error: { code: "refresh_discarded", message: "Session changed during refresh" } })
465
+ error: new AuraError(409, {
466
+ error: { code: "refresh_discarded", message: "Session changed during refresh" }
467
+ })
394
468
  };
395
469
  }
396
470
  store.save(data.access_token, data.refresh_token, data.user);
@@ -399,7 +473,9 @@ async function doRefresh(client) {
399
473
  }
400
474
  return {
401
475
  data: null,
402
- error: new AuraError(0, { error: { code: "invalid_response", message: "Invalid refresh response" } })
476
+ error: new AuraError(0, {
477
+ error: { code: "invalid_response", message: "Invalid refresh response" }
478
+ })
403
479
  };
404
480
  }
405
481
  function withRefreshLock(client, fn) {
@@ -410,7 +486,7 @@ function withRefreshLock(client, fn) {
410
486
  return fn();
411
487
  }
412
488
  const request = locks.request;
413
- const lockName = `aura-refresh-${client.projectId}`;
489
+ const lockName = `aura-refresh-${clientScope(client.baseUrl)}`;
414
490
  const timeoutMs = (client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS) || DEFAULT_REQUEST_TIMEOUT_MS;
415
491
  let timer;
416
492
  const options = {};
@@ -526,7 +602,10 @@ async function fetchWithAuth(url, init, client) {
526
602
  return {
527
603
  data: null,
528
604
  error: new AuraError(0, {
529
- error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : { code: "network_error", message: err instanceof Error ? err.message : "Network request failed" }
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
+ }
530
609
  })
531
610
  };
532
611
  }
@@ -539,7 +618,12 @@ async function fetchWithAuth(url, init, client) {
539
618
  await res.body?.cancel().catch(() => {
540
619
  });
541
620
  const nextAttempt = attempt + 1;
542
- emitAuraEvent(client.onEvent, client.debug, { kind: "retry", attempt: nextAttempt, status: res.status, url });
621
+ emitAuraEvent(client.onEvent, client.debug, {
622
+ kind: "retry",
623
+ attempt: nextAttempt,
624
+ status: res.status,
625
+ url
626
+ });
543
627
  await abortableSleep(delay, init.signal);
544
628
  attempt = nextAttempt;
545
629
  continue;
@@ -645,12 +729,40 @@ var BaseService = class {
645
729
  }
646
730
  };
647
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
+
648
756
  // src/stores/LocalAuthStore.ts
649
757
  var LocalAuthStore = class extends BaseAuthStore {
650
758
  /** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
651
759
  persistent = true;
652
760
  key;
653
- constructor(key = "aurabase_auth") {
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) {
654
766
  super();
655
767
  this.key = key;
656
768
  this._loadFromStorage();
@@ -667,11 +779,12 @@ var LocalAuthStore = class extends BaseAuthStore {
667
779
  }
668
780
  /**
669
781
  * Supprime de localStorage tous les artefacts d'auth éphémères sur disque :
670
- * - `code_verifier` PKCE (`aura-<projectId>-pkce-code-verifier`),
671
- * - marqueur d'initiation OAuth implicit (`aura-<projectId>-oauth-implicit-initiated`,
782
+ * - `code_verifier` PKCE (`aura-<portée>-pkce-code-verifier`),
783
+ * - marqueur d'initiation OAuth implicit (`aura-<portée>-oauth-implicit-initiated`,
672
784
  * SEC-F3). Un `signOut`/`clear()` ne doit laisser aucun résidu réutilisable (parité
673
785
  * `@supabase/ssr` : ces artefacts sont éphémères). On itère les clés préfixées pour
674
- * couvrir un store partagé entre plusieurs projets, sans dépendre du `projectId` courant.
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).
675
788
  */
676
789
  _removePkceVerifiers() {
677
790
  if (typeof localStorage === "undefined") return;
@@ -792,28 +905,4 @@ var LocalAuthStore = class extends BaseAuthStore {
792
905
  }
793
906
  };
794
907
 
795
- // src/cookie-utils.ts
796
- function parseCookieHeader(header) {
797
- const result = {};
798
- if (!header) return result;
799
- for (const pair of header.split(";")) {
800
- const idx = pair.indexOf("=");
801
- if (idx === -1) continue;
802
- const key = pair.slice(0, idx).trim();
803
- const value = pair.slice(idx + 1).trim();
804
- if (key) result[key] = decodeURIComponent(value);
805
- }
806
- return result;
807
- }
808
- function serializeCookieHeader(name, value, options = {}) {
809
- const parts = [`${name}=${encodeURIComponent(value)}`];
810
- if (options.path) parts.push(`Path=${options.path}`);
811
- if (options.domain) parts.push(`Domain=${options.domain}`);
812
- if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
813
- if (options.secure) parts.push("Secure");
814
- if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
815
- if (options.httpOnly) parts.push("HttpOnly");
816
- return parts.join("; ");
817
- }
818
-
819
- export { AuraError, BaseAuthStore, BaseService, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, 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.4.1",
3
+ "version": "0.5.1",
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",
@@ -34,7 +34,7 @@
34
34
  "url": "git+https://github.com/daylami555/aurabase.git",
35
35
  "directory": "aurabase-js/packages/core"
36
36
  },
37
- "homepage": "https://github.com/daylami555/aurabase/tree/main/aurabase-js/packages/core#readme",
37
+ "homepage": "https://aurabase.cloud",
38
38
  "bugs": {
39
39
  "url": "https://github.com/daylami555/aurabase/issues"
40
40
  },
@@ -44,17 +44,18 @@
44
44
  "sdk",
45
45
  "core"
46
46
  ],
47
- "devDependencies": {
48
- "@types/node": "20.14.15",
49
- "tsup": "^8.0.0",
50
- "typescript": "^5.5.0",
51
- "vitest": "^4.1.6"
52
- },
53
47
  "scripts": {
54
48
  "build": "tsup",
55
49
  "test": "vitest run && tsc --noEmit -p tsconfig.test-d.json",
56
50
  "test:types": "tsc --noEmit -p tsconfig.test-d.json",
57
51
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
52
+ "prepack": "pnpm build",
58
53
  "typecheck:tests": "tsc --noEmit -p tsconfig.test.json"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "catalog:",
57
+ "tsup": "catalog:",
58
+ "typescript": "catalog:",
59
+ "vitest": "catalog:"
59
60
  }
60
- }
61
+ }