@aurabase/core 0.6.1 → 0.6.2
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 +1 -1
- package/dist/index.cjs +103 -42
- package/dist/index.d.cts +18 -4
- package/dist/index.d.ts +18 -4
- package/dist/index.js +103 -42
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -149,7 +149,7 @@ function hydrateFromCookieHeader(cookieHeader: string) {
|
|
|
149
149
|
const store = hydrateFromCookieHeader(`${CLE}=%7B%22token%22%3Anull%7D`)
|
|
150
150
|
|
|
151
151
|
// Après une mutation de session, sérialiser l'état courant en Set-Cookie.
|
|
152
|
-
const setCookieValue = store.exportToCookie({ path: '/', secure: true, sameSite: '
|
|
152
|
+
const setCookieValue = store.exportToCookie({ path: '/', secure: true, sameSite: 'lax', httpOnly: true })
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
`store.persistent` indique si le store survit à un redirect pleine page (`false` pour `BaseAuthStore`, `true` pour `LocalAuthStore`) — c'est ce flag que le flux OAuth PKCE utilise pour décider s'il peut stocker un `code_verifier` de façon fiable.
|
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/AuraError.ts
|
|
4
|
+
var LEGACY_LOWERCASE_CODES = /* @__PURE__ */ new Set([
|
|
5
|
+
"NO_SESSION",
|
|
6
|
+
"TIMEOUT",
|
|
7
|
+
"NETWORK_ERROR",
|
|
8
|
+
"INVALID_RESPONSE",
|
|
9
|
+
"REFRESH_DISCARDED",
|
|
10
|
+
"UNVERIFIABLE_TOKEN",
|
|
11
|
+
"NO_CODE_VERIFIER",
|
|
12
|
+
"PKCE_GENERATION_FAILED",
|
|
13
|
+
"MISSING_EMAIL",
|
|
14
|
+
"JWKS_FETCH_FAILED",
|
|
15
|
+
"SIGNATURE_INVALID",
|
|
16
|
+
"TOKEN_EXPIRED",
|
|
17
|
+
"TOKEN_NOT_YET_VALID",
|
|
18
|
+
"INVALID_JWT",
|
|
19
|
+
"UNSUPPORTED_ALG",
|
|
20
|
+
"NO_MATCHING_KEY",
|
|
21
|
+
"INVALID_ISSUER",
|
|
22
|
+
"INVALID_AUDIENCE",
|
|
23
|
+
"INVALID_PATH",
|
|
24
|
+
"INVALID_CREDENTIALS",
|
|
25
|
+
"WEAK_PASSWORD",
|
|
26
|
+
"EMAIL_ALREADY_IN_USE",
|
|
27
|
+
"USER_NOT_FOUND",
|
|
28
|
+
"SESSION_EXPIRED",
|
|
29
|
+
"INVALID_OTP",
|
|
30
|
+
"SESSION_REVOKED"
|
|
31
|
+
]);
|
|
32
|
+
function normalizeErrorCode(code) {
|
|
33
|
+
return code.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
34
|
+
}
|
|
4
35
|
var AuraError = class _AuraError extends Error {
|
|
5
36
|
status;
|
|
6
37
|
code;
|
|
7
38
|
details;
|
|
8
39
|
retryAfter;
|
|
40
|
+
/**
|
|
41
|
+
* Piste de correction fournie par le serveur (ex. refus RLS : quelle politique créer). J1-010.
|
|
42
|
+
*/
|
|
43
|
+
hint;
|
|
9
44
|
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
10
45
|
__isAuraError = true;
|
|
11
46
|
constructor(status, body, headers) {
|
|
12
|
-
|
|
47
|
+
const serverMessage = body?.error?.message ?? body?.message;
|
|
48
|
+
let rawMessage = serverMessage ?? (status === 429 ? "Too many requests, please wait before retrying." : `HTTP ${status}`);
|
|
13
49
|
let retryAfterSec;
|
|
14
50
|
if (headers) {
|
|
15
51
|
const getHeader = (name) => {
|
|
@@ -24,8 +60,8 @@ var AuraError = class _AuraError extends Error {
|
|
|
24
60
|
const sec = parseInt(ra, 10);
|
|
25
61
|
if (!isNaN(sec)) {
|
|
26
62
|
retryAfterSec = sec;
|
|
27
|
-
if (status === 429) {
|
|
28
|
-
rawMessage = `
|
|
63
|
+
if (status === 429 && !serverMessage) {
|
|
64
|
+
rawMessage = `Too many requests, please wait ${sec} second${sec > 1 ? "s" : ""} before retrying.`;
|
|
29
65
|
}
|
|
30
66
|
}
|
|
31
67
|
}
|
|
@@ -37,46 +73,47 @@ var AuraError = class _AuraError extends Error {
|
|
|
37
73
|
if (retryAfterSec !== void 0) {
|
|
38
74
|
this.retryAfter = retryAfterSec;
|
|
39
75
|
}
|
|
40
|
-
const
|
|
76
|
+
const hint = body?.error?.hint;
|
|
77
|
+
if (typeof hint === "string" && hint) {
|
|
78
|
+
this.hint = hint;
|
|
79
|
+
}
|
|
80
|
+
const rawCode = typeof body?.error?.code === "string" ? body.error.code : void 0;
|
|
81
|
+
const canonical = rawCode ? normalizeErrorCode(rawCode) : void 0;
|
|
41
82
|
const lowerMsg = rawMessage.toLowerCase();
|
|
42
|
-
if (
|
|
43
|
-
this.code = "
|
|
44
|
-
} else if (
|
|
45
|
-
this.code = "
|
|
46
|
-
} else if (
|
|
47
|
-
this.code = "
|
|
48
|
-
} else if (
|
|
49
|
-
this.code =
|
|
50
|
-
} else if (
|
|
51
|
-
this.code = "
|
|
52
|
-
} else if (
|
|
53
|
-
this.code = "
|
|
54
|
-
} else if (
|
|
55
|
-
this.code = "
|
|
56
|
-
} else if (
|
|
57
|
-
this.code = "
|
|
83
|
+
if (canonical === "USER_ALREADY_EXISTS") {
|
|
84
|
+
this.code = "EMAIL_ALREADY_IN_USE";
|
|
85
|
+
} else if (canonical === "TOKEN_EXPIRED" && status === 401) {
|
|
86
|
+
this.code = "SESSION_EXPIRED";
|
|
87
|
+
} else if (canonical === "RATE_LIMIT_EXCEEDED") {
|
|
88
|
+
this.code = "RATE_LIMITED";
|
|
89
|
+
} else if (canonical && !isGenericCode(canonical)) {
|
|
90
|
+
this.code = canonical;
|
|
91
|
+
} else if (status === 429) {
|
|
92
|
+
this.code = "RATE_LIMITED";
|
|
93
|
+
} else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
|
|
94
|
+
this.code = "INVALID_CREDENTIALS";
|
|
95
|
+
} else if (status === 401 && (lowerMsg.includes("r\xE9voqu\xE9e") || lowerMsg.includes("revoked"))) {
|
|
96
|
+
this.code = "SESSION_REVOKED";
|
|
97
|
+
} else if (status === 401 && (lowerMsg.includes("expir\xE9") || lowerMsg.includes("expired"))) {
|
|
98
|
+
this.code = "SESSION_EXPIRED";
|
|
99
|
+
} else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
|
|
100
|
+
this.code = "WEAK_PASSWORD";
|
|
101
|
+
} else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
|
|
102
|
+
this.code = "EMAIL_ALREADY_IN_USE";
|
|
103
|
+
} else if (status === 404 && (lowerMsg.includes("utilisateur") || lowerMsg.includes("user not found"))) {
|
|
104
|
+
this.code = "USER_NOT_FOUND";
|
|
58
105
|
} else {
|
|
59
|
-
|
|
60
|
-
if (!isGenericHttpCode) {
|
|
61
|
-
this.code = rawCode;
|
|
62
|
-
} else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
|
|
63
|
-
this.code = "invalid_credentials";
|
|
64
|
-
} else if (status === 401 && lowerMsg.includes("r\xE9voqu\xE9e")) {
|
|
65
|
-
this.code = "session_revoked";
|
|
66
|
-
} else if (status === 401 && lowerMsg.includes("expir\xE9")) {
|
|
67
|
-
this.code = "session_expired";
|
|
68
|
-
} else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
|
|
69
|
-
this.code = "weak_password";
|
|
70
|
-
} else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
|
|
71
|
-
this.code = "email_already_in_use";
|
|
72
|
-
} else if (status === 404 && lowerMsg.includes("utilisateur")) {
|
|
73
|
-
this.code = "user_not_found";
|
|
74
|
-
} else {
|
|
75
|
-
this.code = rawCode ?? `http_${status}`;
|
|
76
|
-
}
|
|
106
|
+
this.code = canonical ?? `HTTP_${status}`;
|
|
77
107
|
}
|
|
78
108
|
Object.setPrototypeOf(this, _AuraError.prototype);
|
|
79
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* @deprecated Ancienne forme en minuscules de `code` (`invalid_credentials`, `no_session`…).
|
|
112
|
+
* Utilisez `error.code`, toujours en SCREAMING_SNAKE.
|
|
113
|
+
*/
|
|
114
|
+
get legacyCode() {
|
|
115
|
+
return LEGACY_LOWERCASE_CODES.has(this.code) ? this.code.toLowerCase() : this.code;
|
|
116
|
+
}
|
|
80
117
|
get isNotFound() {
|
|
81
118
|
return this.status === 404;
|
|
82
119
|
}
|
|
@@ -89,7 +126,17 @@ var AuraError = class _AuraError extends Error {
|
|
|
89
126
|
get isRateLimited() {
|
|
90
127
|
return this.status === 429;
|
|
91
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Quota mensuel du plan atteint (HTTP 403, `code: 'QUOTA_EXCEEDED'`). Le message nomme la
|
|
131
|
+
* métrique, l'usage, la limite et la date de remise à zéro ; ce n'est pas un refus d'autorisation.
|
|
132
|
+
*/
|
|
133
|
+
get isQuotaExceeded() {
|
|
134
|
+
return this.code === "QUOTA_EXCEEDED";
|
|
135
|
+
}
|
|
92
136
|
};
|
|
137
|
+
function isGenericCode(code) {
|
|
138
|
+
return code === "UNAUTHORIZED" || code === "BAD_REQUEST" || code === "CONFLICT" || code.startsWith("HTTP_");
|
|
139
|
+
}
|
|
93
140
|
function isAuraError(error) {
|
|
94
141
|
return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
|
|
95
142
|
}
|
|
@@ -354,6 +401,17 @@ var BaseAuthStore = class {
|
|
|
354
401
|
// src/fetch.ts
|
|
355
402
|
var REFRESH_MARGIN_SEC = 30;
|
|
356
403
|
var RETRY_AFTER_CAP_MS = 6e4;
|
|
404
|
+
function networkErrorMessage(err, url) {
|
|
405
|
+
const cause = err?.cause;
|
|
406
|
+
const reason = cause?.code ?? cause?.message ?? (err instanceof Error ? err.message : "request failed");
|
|
407
|
+
let target = url;
|
|
408
|
+
try {
|
|
409
|
+
const u = new URL(url);
|
|
410
|
+
target = `${u.origin}${u.pathname}`;
|
|
411
|
+
} catch {
|
|
412
|
+
}
|
|
413
|
+
return `Cannot reach ${target} (${reason}). Check the project URL (NEXT_PUBLIC_AURABASE_URL) and your network.`;
|
|
414
|
+
}
|
|
357
415
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
358
416
|
function unwrapEnvelope(body) {
|
|
359
417
|
if (body !== null && typeof body === "object" && "data" in body) {
|
|
@@ -475,7 +533,7 @@ async function doRefresh(client) {
|
|
|
475
533
|
error: new AuraError(0, {
|
|
476
534
|
error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
|
|
477
535
|
code: "network_error",
|
|
478
|
-
message: err
|
|
536
|
+
message: networkErrorMessage(err, `${client.baseUrl}/v1/auth/refresh`)
|
|
479
537
|
}
|
|
480
538
|
})
|
|
481
539
|
};
|
|
@@ -676,7 +734,7 @@ async function fetchWithAuth(url, init, client) {
|
|
|
676
734
|
error: new AuraError(0, {
|
|
677
735
|
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
|
|
678
736
|
code: "network_error",
|
|
679
|
-
message: err
|
|
737
|
+
message: networkErrorMessage(err, url)
|
|
680
738
|
}
|
|
681
739
|
})
|
|
682
740
|
};
|
|
@@ -825,13 +883,16 @@ function parseCookieHeader(header) {
|
|
|
825
883
|
}
|
|
826
884
|
return result;
|
|
827
885
|
}
|
|
886
|
+
function sameSiteAttribute(v) {
|
|
887
|
+
return v.charAt(0).toUpperCase() + v.slice(1);
|
|
888
|
+
}
|
|
828
889
|
function serializeCookieHeader(name, value, options = {}) {
|
|
829
890
|
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
830
891
|
if (options.path) parts.push(`Path=${options.path}`);
|
|
831
892
|
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
832
893
|
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
833
894
|
if (options.secure) parts.push("Secure");
|
|
834
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
895
|
+
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
835
896
|
if (options.httpOnly) parts.push("HttpOnly");
|
|
836
897
|
return parts.join("; ");
|
|
837
898
|
}
|
|
@@ -951,7 +1012,7 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
951
1012
|
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
952
1013
|
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
953
1014
|
if (options.secure) parts.push("Secure");
|
|
954
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
1015
|
+
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
955
1016
|
if (options.httpOnly) parts.push("HttpOnly");
|
|
956
1017
|
return parts.join("; ");
|
|
957
1018
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - storage/path.ts : invalid_path
|
|
15
15
|
* - db/QueryBuilder.ts: PGRST116 (parité PostgREST : `.single()` sur 0/plusieurs lignes)
|
|
16
16
|
*/
|
|
17
|
-
type AuraErrorCode = '
|
|
17
|
+
type AuraErrorCode = 'NO_SESSION' | 'TIMEOUT' | 'NETWORK_ERROR' | 'INVALID_RESPONSE' | 'REFRESH_DISCARDED' | 'UNVERIFIABLE_TOKEN' | 'NO_CODE_VERIFIER' | 'PKCE_GENERATION_FAILED' | 'MISSING_EMAIL' | 'JWKS_FETCH_FAILED' | 'SIGNATURE_INVALID' | 'TOKEN_EXPIRED' | 'TOKEN_NOT_YET_VALID' | 'INVALID_JWT' | 'UNSUPPORTED_ALG' | 'NO_MATCHING_KEY' | 'INVALID_ISSUER' | 'INVALID_AUDIENCE' | 'INVALID_PATH' | 'PGRST116' | 'INVALID_CREDENTIALS' | 'WEAK_PASSWORD' | 'EMAIL_ALREADY_IN_USE' | 'USER_NOT_FOUND' | 'RATE_LIMITED' | 'SESSION_EXPIRED' | 'INVALID_OTP' | 'SESSION_REVOKED' | (string & {});
|
|
18
18
|
/**
|
|
19
19
|
* Structured error thrown/returned by the Aurabase SDK.
|
|
20
20
|
* Mirrors the backend's `AuraError` JSON shape with semantic DX normalization.
|
|
@@ -24,6 +24,10 @@ declare class AuraError extends Error {
|
|
|
24
24
|
readonly code: AuraErrorCode;
|
|
25
25
|
readonly details: unknown;
|
|
26
26
|
readonly retryAfter?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Piste de correction fournie par le serveur (ex. refus RLS : quelle politique créer). J1-010.
|
|
29
|
+
*/
|
|
30
|
+
readonly hint?: string;
|
|
27
31
|
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
28
32
|
protected readonly __isAuraError = true;
|
|
29
33
|
constructor(status: number, body?: {
|
|
@@ -33,10 +37,20 @@ declare class AuraError extends Error {
|
|
|
33
37
|
};
|
|
34
38
|
message?: string;
|
|
35
39
|
} | null, headers?: Headers | Record<string, string | null | undefined>);
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Ancienne forme en minuscules de `code` (`invalid_credentials`, `no_session`…).
|
|
42
|
+
* Utilisez `error.code`, toujours en SCREAMING_SNAKE.
|
|
43
|
+
*/
|
|
44
|
+
get legacyCode(): string;
|
|
36
45
|
get isNotFound(): boolean;
|
|
37
46
|
get isUnauthorized(): boolean;
|
|
38
47
|
get isForbidden(): boolean;
|
|
39
48
|
get isRateLimited(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Quota mensuel du plan atteint (HTTP 403, `code: 'QUOTA_EXCEEDED'`). Le message nomme la
|
|
51
|
+
* métrique, l'usage, la limite et la date de remise à zéro ; ce n'est pas un refus d'autorisation.
|
|
52
|
+
*/
|
|
53
|
+
get isQuotaExceeded(): boolean;
|
|
40
54
|
}
|
|
41
55
|
declare function isAuraError(error: unknown): error is AuraError;
|
|
42
56
|
|
|
@@ -1927,7 +1941,8 @@ interface SerializeCookieOptions {
|
|
|
1927
1941
|
domain?: string;
|
|
1928
1942
|
maxAge?: number;
|
|
1929
1943
|
secure?: boolean;
|
|
1930
|
-
|
|
1944
|
+
/** Minuscules : forme acceptée par `ResponseCookie` de Next.js (`'lax' | 'strict' | 'none'`). */
|
|
1945
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
1931
1946
|
httpOnly?: boolean;
|
|
1932
1947
|
}
|
|
1933
1948
|
/**
|
|
@@ -1935,7 +1950,6 @@ interface SerializeCookieOptions {
|
|
|
1935
1950
|
*/
|
|
1936
1951
|
declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
|
|
1937
1952
|
|
|
1938
|
-
/** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
|
|
1939
1953
|
declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
1940
1954
|
/** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
|
|
1941
1955
|
declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
|
|
@@ -2010,7 +2024,7 @@ interface CookieOptions {
|
|
|
2010
2024
|
domain?: string;
|
|
2011
2025
|
maxAge?: number;
|
|
2012
2026
|
secure?: boolean;
|
|
2013
|
-
sameSite?: '
|
|
2027
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
2014
2028
|
httpOnly?: boolean;
|
|
2015
2029
|
}
|
|
2016
2030
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - storage/path.ts : invalid_path
|
|
15
15
|
* - db/QueryBuilder.ts: PGRST116 (parité PostgREST : `.single()` sur 0/plusieurs lignes)
|
|
16
16
|
*/
|
|
17
|
-
type AuraErrorCode = '
|
|
17
|
+
type AuraErrorCode = 'NO_SESSION' | 'TIMEOUT' | 'NETWORK_ERROR' | 'INVALID_RESPONSE' | 'REFRESH_DISCARDED' | 'UNVERIFIABLE_TOKEN' | 'NO_CODE_VERIFIER' | 'PKCE_GENERATION_FAILED' | 'MISSING_EMAIL' | 'JWKS_FETCH_FAILED' | 'SIGNATURE_INVALID' | 'TOKEN_EXPIRED' | 'TOKEN_NOT_YET_VALID' | 'INVALID_JWT' | 'UNSUPPORTED_ALG' | 'NO_MATCHING_KEY' | 'INVALID_ISSUER' | 'INVALID_AUDIENCE' | 'INVALID_PATH' | 'PGRST116' | 'INVALID_CREDENTIALS' | 'WEAK_PASSWORD' | 'EMAIL_ALREADY_IN_USE' | 'USER_NOT_FOUND' | 'RATE_LIMITED' | 'SESSION_EXPIRED' | 'INVALID_OTP' | 'SESSION_REVOKED' | (string & {});
|
|
18
18
|
/**
|
|
19
19
|
* Structured error thrown/returned by the Aurabase SDK.
|
|
20
20
|
* Mirrors the backend's `AuraError` JSON shape with semantic DX normalization.
|
|
@@ -24,6 +24,10 @@ declare class AuraError extends Error {
|
|
|
24
24
|
readonly code: AuraErrorCode;
|
|
25
25
|
readonly details: unknown;
|
|
26
26
|
readonly retryAfter?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Piste de correction fournie par le serveur (ex. refus RLS : quelle politique créer). J1-010.
|
|
29
|
+
*/
|
|
30
|
+
readonly hint?: string;
|
|
27
31
|
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
28
32
|
protected readonly __isAuraError = true;
|
|
29
33
|
constructor(status: number, body?: {
|
|
@@ -33,10 +37,20 @@ declare class AuraError extends Error {
|
|
|
33
37
|
};
|
|
34
38
|
message?: string;
|
|
35
39
|
} | null, headers?: Headers | Record<string, string | null | undefined>);
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Ancienne forme en minuscules de `code` (`invalid_credentials`, `no_session`…).
|
|
42
|
+
* Utilisez `error.code`, toujours en SCREAMING_SNAKE.
|
|
43
|
+
*/
|
|
44
|
+
get legacyCode(): string;
|
|
36
45
|
get isNotFound(): boolean;
|
|
37
46
|
get isUnauthorized(): boolean;
|
|
38
47
|
get isForbidden(): boolean;
|
|
39
48
|
get isRateLimited(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Quota mensuel du plan atteint (HTTP 403, `code: 'QUOTA_EXCEEDED'`). Le message nomme la
|
|
51
|
+
* métrique, l'usage, la limite et la date de remise à zéro ; ce n'est pas un refus d'autorisation.
|
|
52
|
+
*/
|
|
53
|
+
get isQuotaExceeded(): boolean;
|
|
40
54
|
}
|
|
41
55
|
declare function isAuraError(error: unknown): error is AuraError;
|
|
42
56
|
|
|
@@ -1927,7 +1941,8 @@ interface SerializeCookieOptions {
|
|
|
1927
1941
|
domain?: string;
|
|
1928
1942
|
maxAge?: number;
|
|
1929
1943
|
secure?: boolean;
|
|
1930
|
-
|
|
1944
|
+
/** Minuscules : forme acceptée par `ResponseCookie` de Next.js (`'lax' | 'strict' | 'none'`). */
|
|
1945
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
1931
1946
|
httpOnly?: boolean;
|
|
1932
1947
|
}
|
|
1933
1948
|
/**
|
|
@@ -1935,7 +1950,6 @@ interface SerializeCookieOptions {
|
|
|
1935
1950
|
*/
|
|
1936
1951
|
declare function serializeCookieHeader(name: string, value: string, options?: SerializeCookieOptions): string;
|
|
1937
1952
|
|
|
1938
|
-
/** Timeout par défaut d'une requête HTTP (borne un serveur qui « hang »). */
|
|
1939
1953
|
declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
1940
1954
|
/** Point d'entrée unique du refresh, dédupliqué par client (single-flight). */
|
|
1941
1955
|
declare function refreshOnce(client: AuraClientLike): Promise<AuraResponse<TokenPair>>;
|
|
@@ -2010,7 +2024,7 @@ interface CookieOptions {
|
|
|
2010
2024
|
domain?: string;
|
|
2011
2025
|
maxAge?: number;
|
|
2012
2026
|
secure?: boolean;
|
|
2013
|
-
sameSite?: '
|
|
2027
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
2014
2028
|
httpOnly?: boolean;
|
|
2015
2029
|
}
|
|
2016
2030
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,49 @@
|
|
|
1
1
|
// src/AuraError.ts
|
|
2
|
+
var LEGACY_LOWERCASE_CODES = /* @__PURE__ */ new Set([
|
|
3
|
+
"NO_SESSION",
|
|
4
|
+
"TIMEOUT",
|
|
5
|
+
"NETWORK_ERROR",
|
|
6
|
+
"INVALID_RESPONSE",
|
|
7
|
+
"REFRESH_DISCARDED",
|
|
8
|
+
"UNVERIFIABLE_TOKEN",
|
|
9
|
+
"NO_CODE_VERIFIER",
|
|
10
|
+
"PKCE_GENERATION_FAILED",
|
|
11
|
+
"MISSING_EMAIL",
|
|
12
|
+
"JWKS_FETCH_FAILED",
|
|
13
|
+
"SIGNATURE_INVALID",
|
|
14
|
+
"TOKEN_EXPIRED",
|
|
15
|
+
"TOKEN_NOT_YET_VALID",
|
|
16
|
+
"INVALID_JWT",
|
|
17
|
+
"UNSUPPORTED_ALG",
|
|
18
|
+
"NO_MATCHING_KEY",
|
|
19
|
+
"INVALID_ISSUER",
|
|
20
|
+
"INVALID_AUDIENCE",
|
|
21
|
+
"INVALID_PATH",
|
|
22
|
+
"INVALID_CREDENTIALS",
|
|
23
|
+
"WEAK_PASSWORD",
|
|
24
|
+
"EMAIL_ALREADY_IN_USE",
|
|
25
|
+
"USER_NOT_FOUND",
|
|
26
|
+
"SESSION_EXPIRED",
|
|
27
|
+
"INVALID_OTP",
|
|
28
|
+
"SESSION_REVOKED"
|
|
29
|
+
]);
|
|
30
|
+
function normalizeErrorCode(code) {
|
|
31
|
+
return code.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
32
|
+
}
|
|
2
33
|
var AuraError = class _AuraError extends Error {
|
|
3
34
|
status;
|
|
4
35
|
code;
|
|
5
36
|
details;
|
|
6
37
|
retryAfter;
|
|
38
|
+
/**
|
|
39
|
+
* Piste de correction fournie par le serveur (ex. refus RLS : quelle politique créer). J1-010.
|
|
40
|
+
*/
|
|
41
|
+
hint;
|
|
7
42
|
/** Marqueur runtime pour `isAuraError` (détection cross-realm/cross-bundle). */
|
|
8
43
|
__isAuraError = true;
|
|
9
44
|
constructor(status, body, headers) {
|
|
10
|
-
|
|
45
|
+
const serverMessage = body?.error?.message ?? body?.message;
|
|
46
|
+
let rawMessage = serverMessage ?? (status === 429 ? "Too many requests, please wait before retrying." : `HTTP ${status}`);
|
|
11
47
|
let retryAfterSec;
|
|
12
48
|
if (headers) {
|
|
13
49
|
const getHeader = (name) => {
|
|
@@ -22,8 +58,8 @@ var AuraError = class _AuraError extends Error {
|
|
|
22
58
|
const sec = parseInt(ra, 10);
|
|
23
59
|
if (!isNaN(sec)) {
|
|
24
60
|
retryAfterSec = sec;
|
|
25
|
-
if (status === 429) {
|
|
26
|
-
rawMessage = `
|
|
61
|
+
if (status === 429 && !serverMessage) {
|
|
62
|
+
rawMessage = `Too many requests, please wait ${sec} second${sec > 1 ? "s" : ""} before retrying.`;
|
|
27
63
|
}
|
|
28
64
|
}
|
|
29
65
|
}
|
|
@@ -35,46 +71,47 @@ var AuraError = class _AuraError extends Error {
|
|
|
35
71
|
if (retryAfterSec !== void 0) {
|
|
36
72
|
this.retryAfter = retryAfterSec;
|
|
37
73
|
}
|
|
38
|
-
const
|
|
74
|
+
const hint = body?.error?.hint;
|
|
75
|
+
if (typeof hint === "string" && hint) {
|
|
76
|
+
this.hint = hint;
|
|
77
|
+
}
|
|
78
|
+
const rawCode = typeof body?.error?.code === "string" ? body.error.code : void 0;
|
|
79
|
+
const canonical = rawCode ? normalizeErrorCode(rawCode) : void 0;
|
|
39
80
|
const lowerMsg = rawMessage.toLowerCase();
|
|
40
|
-
if (
|
|
41
|
-
this.code = "
|
|
42
|
-
} else if (
|
|
43
|
-
this.code = "
|
|
44
|
-
} else if (
|
|
45
|
-
this.code = "
|
|
46
|
-
} else if (
|
|
47
|
-
this.code =
|
|
48
|
-
} else if (
|
|
49
|
-
this.code = "
|
|
50
|
-
} else if (
|
|
51
|
-
this.code = "
|
|
52
|
-
} else if (
|
|
53
|
-
this.code = "
|
|
54
|
-
} else if (
|
|
55
|
-
this.code = "
|
|
81
|
+
if (canonical === "USER_ALREADY_EXISTS") {
|
|
82
|
+
this.code = "EMAIL_ALREADY_IN_USE";
|
|
83
|
+
} else if (canonical === "TOKEN_EXPIRED" && status === 401) {
|
|
84
|
+
this.code = "SESSION_EXPIRED";
|
|
85
|
+
} else if (canonical === "RATE_LIMIT_EXCEEDED") {
|
|
86
|
+
this.code = "RATE_LIMITED";
|
|
87
|
+
} else if (canonical && !isGenericCode(canonical)) {
|
|
88
|
+
this.code = canonical;
|
|
89
|
+
} else if (status === 429) {
|
|
90
|
+
this.code = "RATE_LIMITED";
|
|
91
|
+
} else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
|
|
92
|
+
this.code = "INVALID_CREDENTIALS";
|
|
93
|
+
} else if (status === 401 && (lowerMsg.includes("r\xE9voqu\xE9e") || lowerMsg.includes("revoked"))) {
|
|
94
|
+
this.code = "SESSION_REVOKED";
|
|
95
|
+
} else if (status === 401 && (lowerMsg.includes("expir\xE9") || lowerMsg.includes("expired"))) {
|
|
96
|
+
this.code = "SESSION_EXPIRED";
|
|
97
|
+
} else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
|
|
98
|
+
this.code = "WEAK_PASSWORD";
|
|
99
|
+
} else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
|
|
100
|
+
this.code = "EMAIL_ALREADY_IN_USE";
|
|
101
|
+
} else if (status === 404 && (lowerMsg.includes("utilisateur") || lowerMsg.includes("user not found"))) {
|
|
102
|
+
this.code = "USER_NOT_FOUND";
|
|
56
103
|
} else {
|
|
57
|
-
|
|
58
|
-
if (!isGenericHttpCode) {
|
|
59
|
-
this.code = rawCode;
|
|
60
|
-
} else if (status === 401 && (lowerMsg.includes("identifiants") || lowerMsg.includes("credentials") || lowerMsg.includes("mot de passe incorrect"))) {
|
|
61
|
-
this.code = "invalid_credentials";
|
|
62
|
-
} else if (status === 401 && lowerMsg.includes("r\xE9voqu\xE9e")) {
|
|
63
|
-
this.code = "session_revoked";
|
|
64
|
-
} else if (status === 401 && lowerMsg.includes("expir\xE9")) {
|
|
65
|
-
this.code = "session_expired";
|
|
66
|
-
} else if (status === 400 && (lowerMsg.includes("mot de passe") || lowerMsg.includes("password"))) {
|
|
67
|
-
this.code = "weak_password";
|
|
68
|
-
} else if (status === 409 && (lowerMsg.includes("d\xE9j\xE0 utilis\xE9") || lowerMsg.includes("already exists") || lowerMsg.includes("already in use") || lowerMsg.includes("email"))) {
|
|
69
|
-
this.code = "email_already_in_use";
|
|
70
|
-
} else if (status === 404 && lowerMsg.includes("utilisateur")) {
|
|
71
|
-
this.code = "user_not_found";
|
|
72
|
-
} else {
|
|
73
|
-
this.code = rawCode ?? `http_${status}`;
|
|
74
|
-
}
|
|
104
|
+
this.code = canonical ?? `HTTP_${status}`;
|
|
75
105
|
}
|
|
76
106
|
Object.setPrototypeOf(this, _AuraError.prototype);
|
|
77
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* @deprecated Ancienne forme en minuscules de `code` (`invalid_credentials`, `no_session`…).
|
|
110
|
+
* Utilisez `error.code`, toujours en SCREAMING_SNAKE.
|
|
111
|
+
*/
|
|
112
|
+
get legacyCode() {
|
|
113
|
+
return LEGACY_LOWERCASE_CODES.has(this.code) ? this.code.toLowerCase() : this.code;
|
|
114
|
+
}
|
|
78
115
|
get isNotFound() {
|
|
79
116
|
return this.status === 404;
|
|
80
117
|
}
|
|
@@ -87,7 +124,17 @@ var AuraError = class _AuraError extends Error {
|
|
|
87
124
|
get isRateLimited() {
|
|
88
125
|
return this.status === 429;
|
|
89
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Quota mensuel du plan atteint (HTTP 403, `code: 'QUOTA_EXCEEDED'`). Le message nomme la
|
|
129
|
+
* métrique, l'usage, la limite et la date de remise à zéro ; ce n'est pas un refus d'autorisation.
|
|
130
|
+
*/
|
|
131
|
+
get isQuotaExceeded() {
|
|
132
|
+
return this.code === "QUOTA_EXCEEDED";
|
|
133
|
+
}
|
|
90
134
|
};
|
|
135
|
+
function isGenericCode(code) {
|
|
136
|
+
return code === "UNAUTHORIZED" || code === "BAD_REQUEST" || code === "CONFLICT" || code.startsWith("HTTP_");
|
|
137
|
+
}
|
|
91
138
|
function isAuraError(error) {
|
|
92
139
|
return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
|
|
93
140
|
}
|
|
@@ -352,6 +399,17 @@ var BaseAuthStore = class {
|
|
|
352
399
|
// src/fetch.ts
|
|
353
400
|
var REFRESH_MARGIN_SEC = 30;
|
|
354
401
|
var RETRY_AFTER_CAP_MS = 6e4;
|
|
402
|
+
function networkErrorMessage(err, url) {
|
|
403
|
+
const cause = err?.cause;
|
|
404
|
+
const reason = cause?.code ?? cause?.message ?? (err instanceof Error ? err.message : "request failed");
|
|
405
|
+
let target = url;
|
|
406
|
+
try {
|
|
407
|
+
const u = new URL(url);
|
|
408
|
+
target = `${u.origin}${u.pathname}`;
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
return `Cannot reach ${target} (${reason}). Check the project URL (NEXT_PUBLIC_AURABASE_URL) and your network.`;
|
|
412
|
+
}
|
|
355
413
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
356
414
|
function unwrapEnvelope(body) {
|
|
357
415
|
if (body !== null && typeof body === "object" && "data" in body) {
|
|
@@ -473,7 +531,7 @@ async function doRefresh(client) {
|
|
|
473
531
|
error: new AuraError(0, {
|
|
474
532
|
error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
|
|
475
533
|
code: "network_error",
|
|
476
|
-
message: err
|
|
534
|
+
message: networkErrorMessage(err, `${client.baseUrl}/v1/auth/refresh`)
|
|
477
535
|
}
|
|
478
536
|
})
|
|
479
537
|
};
|
|
@@ -674,7 +732,7 @@ async function fetchWithAuth(url, init, client) {
|
|
|
674
732
|
error: new AuraError(0, {
|
|
675
733
|
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
|
|
676
734
|
code: "network_error",
|
|
677
|
-
message: err
|
|
735
|
+
message: networkErrorMessage(err, url)
|
|
678
736
|
}
|
|
679
737
|
})
|
|
680
738
|
};
|
|
@@ -823,13 +881,16 @@ function parseCookieHeader(header) {
|
|
|
823
881
|
}
|
|
824
882
|
return result;
|
|
825
883
|
}
|
|
884
|
+
function sameSiteAttribute(v) {
|
|
885
|
+
return v.charAt(0).toUpperCase() + v.slice(1);
|
|
886
|
+
}
|
|
826
887
|
function serializeCookieHeader(name, value, options = {}) {
|
|
827
888
|
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
828
889
|
if (options.path) parts.push(`Path=${options.path}`);
|
|
829
890
|
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
830
891
|
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
831
892
|
if (options.secure) parts.push("Secure");
|
|
832
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
893
|
+
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
833
894
|
if (options.httpOnly) parts.push("HttpOnly");
|
|
834
895
|
return parts.join("; ");
|
|
835
896
|
}
|
|
@@ -949,7 +1010,7 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
949
1010
|
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
950
1011
|
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
951
1012
|
if (options.secure) parts.push("Secure");
|
|
952
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
1013
|
+
if (options.sameSite) parts.push(`SameSite=${sameSiteAttribute(options.sameSite)}`);
|
|
953
1014
|
if (options.httpOnly) parts.push("HttpOnly");
|
|
954
1015
|
return parts.join("; ");
|
|
955
1016
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurabase/core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"author": "Aurabase",
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/
|
|
34
|
+
"url": "git+https://github.com/Aurabase-cloud/Aurabase.git",
|
|
35
35
|
"directory": "aurabase-js/packages/core"
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://aurabase.cloud",
|
|
38
38
|
"bugs": {
|
|
39
|
-
"url": "https://github.com/
|
|
39
|
+
"url": "https://github.com/Aurabase-cloud/Aurabase/issues"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"aurabase",
|