fauxbase 0.5.6 → 0.5.8
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.cjs +205 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +205 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -264,8 +264,11 @@ declare class HttpDriver implements Driver {
|
|
|
264
264
|
private defaultHeaders;
|
|
265
265
|
private endpoints;
|
|
266
266
|
private authProvider;
|
|
267
|
+
private onUnauthorized;
|
|
267
268
|
constructor(config: HttpDriverOptions);
|
|
268
269
|
setAuthProvider(provider: AuthProvider$1): void;
|
|
270
|
+
/** @internal — set callback to refresh token on 401 */
|
|
271
|
+
setOnUnauthorized(handler: () => Promise<boolean>): void;
|
|
269
272
|
registerEndpoint(resource: string, endpoint: string): void;
|
|
270
273
|
private getEndpoint;
|
|
271
274
|
private buildUrl;
|
|
@@ -308,6 +311,8 @@ interface AuthState {
|
|
|
308
311
|
userName?: string;
|
|
309
312
|
role?: string;
|
|
310
313
|
token: string;
|
|
314
|
+
refreshToken?: string;
|
|
315
|
+
expiresAt?: number;
|
|
311
316
|
}
|
|
312
317
|
interface AuthContext {
|
|
313
318
|
userId: string;
|
|
@@ -318,6 +323,8 @@ declare abstract class AuthService<T extends Entity> extends Service<T> {
|
|
|
318
323
|
private saveState;
|
|
319
324
|
private httpDriver;
|
|
320
325
|
private authChangeListeners;
|
|
326
|
+
private refreshTimer;
|
|
327
|
+
private _isRefreshing;
|
|
321
328
|
/** @internal — called by createClient to wire persistence */
|
|
322
329
|
_initAuth(loadState: () => AuthState | null, saveState: (state: AuthState | null) => void): void;
|
|
323
330
|
/** @internal — called by createClient when using HttpDriver */
|
|
@@ -325,16 +332,33 @@ declare abstract class AuthService<T extends Entity> extends Service<T> {
|
|
|
325
332
|
login(credentials: LoginCredentials): Promise<T>;
|
|
326
333
|
register(data: Partial<T>): Promise<T>;
|
|
327
334
|
logout(): void;
|
|
335
|
+
/** Manually refresh the token. Returns the new token. */
|
|
336
|
+
refresh(): Promise<string>;
|
|
337
|
+
/**
|
|
338
|
+
* Ensure the token is valid before making a request.
|
|
339
|
+
* If expired, auto-refreshes. Safe to call concurrently.
|
|
340
|
+
*/
|
|
341
|
+
ensureValidToken(): Promise<void>;
|
|
328
342
|
get currentUser(): T | null;
|
|
329
343
|
get isLoggedIn(): boolean;
|
|
330
344
|
get token(): string | null;
|
|
345
|
+
get refreshToken(): string | null;
|
|
346
|
+
get expiresAt(): number | null;
|
|
347
|
+
get isExpired(): boolean;
|
|
331
348
|
hasRole(role: string): boolean;
|
|
332
349
|
getAuthContext(): AuthContext | null;
|
|
333
350
|
private localLogin;
|
|
334
351
|
private localRegister;
|
|
352
|
+
private localRefresh;
|
|
335
353
|
private httpLogin;
|
|
336
354
|
private httpRegister;
|
|
355
|
+
private httpRefresh;
|
|
356
|
+
private unwrapBody;
|
|
357
|
+
private setAuthFromResponse;
|
|
337
358
|
private generateToken;
|
|
359
|
+
private generateRefreshToken;
|
|
360
|
+
private scheduleRefresh;
|
|
361
|
+
private clearRefreshTimer;
|
|
338
362
|
/** @internal — called by createClient to listen for auth state changes */
|
|
339
363
|
_onAuthChange(listener: () => void): void;
|
|
340
364
|
private persistState;
|
|
@@ -454,7 +478,10 @@ interface Preset {
|
|
|
454
478
|
loginUrl: string;
|
|
455
479
|
registerUrl: string;
|
|
456
480
|
logoutUrl?: string;
|
|
481
|
+
refreshUrl?: string;
|
|
457
482
|
tokenField: string;
|
|
483
|
+
refreshTokenField?: string;
|
|
484
|
+
expiresInField?: string;
|
|
458
485
|
userField: string;
|
|
459
486
|
headerFormat: string;
|
|
460
487
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -264,8 +264,11 @@ declare class HttpDriver implements Driver {
|
|
|
264
264
|
private defaultHeaders;
|
|
265
265
|
private endpoints;
|
|
266
266
|
private authProvider;
|
|
267
|
+
private onUnauthorized;
|
|
267
268
|
constructor(config: HttpDriverOptions);
|
|
268
269
|
setAuthProvider(provider: AuthProvider$1): void;
|
|
270
|
+
/** @internal — set callback to refresh token on 401 */
|
|
271
|
+
setOnUnauthorized(handler: () => Promise<boolean>): void;
|
|
269
272
|
registerEndpoint(resource: string, endpoint: string): void;
|
|
270
273
|
private getEndpoint;
|
|
271
274
|
private buildUrl;
|
|
@@ -308,6 +311,8 @@ interface AuthState {
|
|
|
308
311
|
userName?: string;
|
|
309
312
|
role?: string;
|
|
310
313
|
token: string;
|
|
314
|
+
refreshToken?: string;
|
|
315
|
+
expiresAt?: number;
|
|
311
316
|
}
|
|
312
317
|
interface AuthContext {
|
|
313
318
|
userId: string;
|
|
@@ -318,6 +323,8 @@ declare abstract class AuthService<T extends Entity> extends Service<T> {
|
|
|
318
323
|
private saveState;
|
|
319
324
|
private httpDriver;
|
|
320
325
|
private authChangeListeners;
|
|
326
|
+
private refreshTimer;
|
|
327
|
+
private _isRefreshing;
|
|
321
328
|
/** @internal — called by createClient to wire persistence */
|
|
322
329
|
_initAuth(loadState: () => AuthState | null, saveState: (state: AuthState | null) => void): void;
|
|
323
330
|
/** @internal — called by createClient when using HttpDriver */
|
|
@@ -325,16 +332,33 @@ declare abstract class AuthService<T extends Entity> extends Service<T> {
|
|
|
325
332
|
login(credentials: LoginCredentials): Promise<T>;
|
|
326
333
|
register(data: Partial<T>): Promise<T>;
|
|
327
334
|
logout(): void;
|
|
335
|
+
/** Manually refresh the token. Returns the new token. */
|
|
336
|
+
refresh(): Promise<string>;
|
|
337
|
+
/**
|
|
338
|
+
* Ensure the token is valid before making a request.
|
|
339
|
+
* If expired, auto-refreshes. Safe to call concurrently.
|
|
340
|
+
*/
|
|
341
|
+
ensureValidToken(): Promise<void>;
|
|
328
342
|
get currentUser(): T | null;
|
|
329
343
|
get isLoggedIn(): boolean;
|
|
330
344
|
get token(): string | null;
|
|
345
|
+
get refreshToken(): string | null;
|
|
346
|
+
get expiresAt(): number | null;
|
|
347
|
+
get isExpired(): boolean;
|
|
331
348
|
hasRole(role: string): boolean;
|
|
332
349
|
getAuthContext(): AuthContext | null;
|
|
333
350
|
private localLogin;
|
|
334
351
|
private localRegister;
|
|
352
|
+
private localRefresh;
|
|
335
353
|
private httpLogin;
|
|
336
354
|
private httpRegister;
|
|
355
|
+
private httpRefresh;
|
|
356
|
+
private unwrapBody;
|
|
357
|
+
private setAuthFromResponse;
|
|
337
358
|
private generateToken;
|
|
359
|
+
private generateRefreshToken;
|
|
360
|
+
private scheduleRefresh;
|
|
361
|
+
private clearRefreshTimer;
|
|
338
362
|
/** @internal — called by createClient to listen for auth state changes */
|
|
339
363
|
_onAuthChange(listener: () => void): void;
|
|
340
364
|
private persistState;
|
|
@@ -454,7 +478,10 @@ interface Preset {
|
|
|
454
478
|
loginUrl: string;
|
|
455
479
|
registerUrl: string;
|
|
456
480
|
logoutUrl?: string;
|
|
481
|
+
refreshUrl?: string;
|
|
457
482
|
tokenField: string;
|
|
483
|
+
refreshTokenField?: string;
|
|
484
|
+
expiresInField?: string;
|
|
458
485
|
userField: string;
|
|
459
486
|
headerFormat: string;
|
|
460
487
|
};
|
package/dist/index.js
CHANGED
|
@@ -277,10 +277,15 @@ var AuthService = class extends Service {
|
|
|
277
277
|
saveState = null;
|
|
278
278
|
httpDriver = null;
|
|
279
279
|
authChangeListeners = [];
|
|
280
|
+
refreshTimer = null;
|
|
281
|
+
_isRefreshing = null;
|
|
280
282
|
/** @internal — called by createClient to wire persistence */
|
|
281
283
|
_initAuth(loadState, saveState) {
|
|
282
284
|
this.saveState = saveState;
|
|
283
285
|
this.authState = loadState();
|
|
286
|
+
if (this.authState?.expiresAt) {
|
|
287
|
+
this.scheduleRefresh();
|
|
288
|
+
}
|
|
284
289
|
}
|
|
285
290
|
/** @internal — called by createClient when using HttpDriver */
|
|
286
291
|
_setHttpMode(driver) {
|
|
@@ -299,9 +304,38 @@ var AuthService = class extends Service {
|
|
|
299
304
|
return this.localRegister(data);
|
|
300
305
|
}
|
|
301
306
|
logout() {
|
|
307
|
+
this.clearRefreshTimer();
|
|
302
308
|
this.authState = null;
|
|
303
309
|
this.persistState();
|
|
304
310
|
}
|
|
311
|
+
/** Manually refresh the token. Returns the new token. */
|
|
312
|
+
async refresh() {
|
|
313
|
+
if (!this.authState?.refreshToken) {
|
|
314
|
+
throw new ForbiddenError("No refresh token available");
|
|
315
|
+
}
|
|
316
|
+
if (this.httpDriver) {
|
|
317
|
+
return this.httpRefresh();
|
|
318
|
+
}
|
|
319
|
+
return this.localRefresh();
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Ensure the token is valid before making a request.
|
|
323
|
+
* If expired, auto-refreshes. Safe to call concurrently.
|
|
324
|
+
*/
|
|
325
|
+
async ensureValidToken() {
|
|
326
|
+
if (!this.authState) return;
|
|
327
|
+
if (!this.authState.expiresAt) return;
|
|
328
|
+
const buffer = 30 * 1e3;
|
|
329
|
+
if (Date.now() + buffer >= this.authState.expiresAt) {
|
|
330
|
+
if (!this._isRefreshing) {
|
|
331
|
+
this._isRefreshing = this.refresh().then(() => {
|
|
332
|
+
}).finally(() => {
|
|
333
|
+
this._isRefreshing = null;
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
await this._isRefreshing;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
305
339
|
get currentUser() {
|
|
306
340
|
return this.authState ? { id: this.authState.userId, email: this.authState.email } : null;
|
|
307
341
|
}
|
|
@@ -311,6 +345,16 @@ var AuthService = class extends Service {
|
|
|
311
345
|
get token() {
|
|
312
346
|
return this.authState?.token ?? null;
|
|
313
347
|
}
|
|
348
|
+
get refreshToken() {
|
|
349
|
+
return this.authState?.refreshToken ?? null;
|
|
350
|
+
}
|
|
351
|
+
get expiresAt() {
|
|
352
|
+
return this.authState?.expiresAt ?? null;
|
|
353
|
+
}
|
|
354
|
+
get isExpired() {
|
|
355
|
+
if (!this.authState?.expiresAt) return false;
|
|
356
|
+
return Date.now() >= this.authState.expiresAt;
|
|
357
|
+
}
|
|
314
358
|
hasRole(role) {
|
|
315
359
|
return this.authState?.role === role;
|
|
316
360
|
}
|
|
@@ -321,7 +365,7 @@ var AuthService = class extends Service {
|
|
|
321
365
|
userName: this.authState.userName
|
|
322
366
|
};
|
|
323
367
|
}
|
|
324
|
-
// --- Local mode
|
|
368
|
+
// --- Local mode ---
|
|
325
369
|
async localLogin(credentials) {
|
|
326
370
|
const { items } = await this.list({ filter: { email: credentials.email } });
|
|
327
371
|
if (items.length === 0) {
|
|
@@ -331,14 +375,18 @@ var AuthService = class extends Service {
|
|
|
331
375
|
if (user.password !== credentials.password) {
|
|
332
376
|
throw new ForbiddenError("Invalid email or password");
|
|
333
377
|
}
|
|
378
|
+
const expiresAt = Date.now() + 60 * 60 * 1e3;
|
|
334
379
|
this.authState = {
|
|
335
380
|
userId: user.id,
|
|
336
381
|
email: user.email,
|
|
337
382
|
userName: user.name || user.email,
|
|
338
383
|
role: user.role,
|
|
339
|
-
token: this.generateToken(user)
|
|
384
|
+
token: this.generateToken(user, expiresAt),
|
|
385
|
+
refreshToken: this.generateRefreshToken(user),
|
|
386
|
+
expiresAt
|
|
340
387
|
};
|
|
341
388
|
this.persistState();
|
|
389
|
+
this.scheduleRefresh();
|
|
342
390
|
return user;
|
|
343
391
|
}
|
|
344
392
|
async localRegister(data) {
|
|
@@ -351,16 +399,33 @@ var AuthService = class extends Service {
|
|
|
351
399
|
}
|
|
352
400
|
const { data: user } = await this.create(data);
|
|
353
401
|
const u = user;
|
|
402
|
+
const expiresAt = Date.now() + 60 * 60 * 1e3;
|
|
354
403
|
this.authState = {
|
|
355
404
|
userId: u.id,
|
|
356
405
|
email: u.email,
|
|
357
406
|
userName: u.name || u.email,
|
|
358
407
|
role: u.role,
|
|
359
|
-
token: this.generateToken(u)
|
|
408
|
+
token: this.generateToken(u, expiresAt),
|
|
409
|
+
refreshToken: this.generateRefreshToken(u),
|
|
410
|
+
expiresAt
|
|
360
411
|
};
|
|
361
412
|
this.persistState();
|
|
413
|
+
this.scheduleRefresh();
|
|
362
414
|
return user;
|
|
363
415
|
}
|
|
416
|
+
async localRefresh() {
|
|
417
|
+
const payload = JSON.parse(atob(this.authState.refreshToken));
|
|
418
|
+
const expiresAt = Date.now() + 60 * 60 * 1e3;
|
|
419
|
+
this.authState = {
|
|
420
|
+
...this.authState,
|
|
421
|
+
token: this.generateToken(payload, expiresAt),
|
|
422
|
+
refreshToken: this.generateRefreshToken(payload),
|
|
423
|
+
expiresAt
|
|
424
|
+
};
|
|
425
|
+
this.persistState();
|
|
426
|
+
this.scheduleRefresh();
|
|
427
|
+
return this.authState.token;
|
|
428
|
+
}
|
|
364
429
|
// --- HTTP mode ---
|
|
365
430
|
async httpLogin(credentials) {
|
|
366
431
|
const preset = this.httpDriver.preset;
|
|
@@ -382,17 +447,9 @@ var AuthService = class extends Service {
|
|
|
382
447
|
throw new ForbiddenError(body2.message ?? "Login failed");
|
|
383
448
|
}
|
|
384
449
|
const body = await response.json();
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
userId: user.id,
|
|
389
|
-
email: user.email ?? credentials.email,
|
|
390
|
-
userName: user.name || user.email || credentials.email,
|
|
391
|
-
role: user.role,
|
|
392
|
-
token
|
|
393
|
-
};
|
|
394
|
-
this.persistState();
|
|
395
|
-
return user;
|
|
450
|
+
this.setAuthFromResponse(body, preset, credentials.email);
|
|
451
|
+
const unwrapped = this.unwrapBody(body);
|
|
452
|
+
return unwrapped[preset.auth.userField] ?? unwrapped;
|
|
396
453
|
}
|
|
397
454
|
async httpRegister(data) {
|
|
398
455
|
const preset = this.httpDriver.preset;
|
|
@@ -411,27 +468,103 @@ var AuthService = class extends Service {
|
|
|
411
468
|
throw new ForbiddenError(body2.message ?? "Registration failed");
|
|
412
469
|
}
|
|
413
470
|
const body = await response.json();
|
|
414
|
-
|
|
415
|
-
const
|
|
471
|
+
this.setAuthFromResponse(body, preset, data.email);
|
|
472
|
+
const unwrapped = this.unwrapBody(body);
|
|
473
|
+
return unwrapped[preset.auth.userField] ?? unwrapped;
|
|
474
|
+
}
|
|
475
|
+
async httpRefresh() {
|
|
476
|
+
const preset = this.httpDriver.preset;
|
|
477
|
+
const baseUrl = this.httpDriver.baseUrl;
|
|
478
|
+
const refreshUrl = preset.auth.refreshUrl;
|
|
479
|
+
if (!refreshUrl) {
|
|
480
|
+
throw new ForbiddenError("Refresh URL not configured in preset");
|
|
481
|
+
}
|
|
482
|
+
const response = await fetch(`${baseUrl}${refreshUrl}`, {
|
|
483
|
+
method: "POST",
|
|
484
|
+
headers: { "Content-Type": "application/json" },
|
|
485
|
+
body: JSON.stringify({ refreshToken: this.authState.refreshToken })
|
|
486
|
+
});
|
|
487
|
+
if (!response.ok) {
|
|
488
|
+
this.logout();
|
|
489
|
+
throw new ForbiddenError("Session expired. Please log in again.");
|
|
490
|
+
}
|
|
491
|
+
const body = await response.json();
|
|
492
|
+
const unwrapped = this.unwrapBody(body);
|
|
493
|
+
const token = unwrapped[preset.auth.tokenField];
|
|
494
|
+
const refreshToken = preset.auth.refreshTokenField ? unwrapped[preset.auth.refreshTokenField] : this.authState.refreshToken;
|
|
495
|
+
const expiresIn = preset.auth.expiresInField ? unwrapped[preset.auth.expiresInField] : null;
|
|
496
|
+
const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
|
|
497
|
+
this.authState = {
|
|
498
|
+
...this.authState,
|
|
499
|
+
token,
|
|
500
|
+
refreshToken,
|
|
501
|
+
expiresAt
|
|
502
|
+
};
|
|
503
|
+
this.persistState();
|
|
504
|
+
this.scheduleRefresh();
|
|
505
|
+
return token;
|
|
506
|
+
}
|
|
507
|
+
unwrapBody(body) {
|
|
508
|
+
if (body.data && typeof body.data === "object" && !Array.isArray(body.data)) {
|
|
509
|
+
return body.data;
|
|
510
|
+
}
|
|
511
|
+
return body;
|
|
512
|
+
}
|
|
513
|
+
setAuthFromResponse(body, preset, fallbackEmail) {
|
|
514
|
+
const unwrapped = this.unwrapBody(body);
|
|
515
|
+
const token = unwrapped[preset.auth.tokenField];
|
|
516
|
+
const user = unwrapped[preset.auth.userField] ?? unwrapped;
|
|
517
|
+
const refreshToken = preset.auth.refreshTokenField ? unwrapped[preset.auth.refreshTokenField] : void 0;
|
|
518
|
+
const expiresIn = preset.auth.expiresInField ? unwrapped[preset.auth.expiresInField] : null;
|
|
519
|
+
const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
|
|
416
520
|
this.authState = {
|
|
417
521
|
userId: user.id,
|
|
418
|
-
email: user.email ??
|
|
419
|
-
userName: user.name || user.email ||
|
|
522
|
+
email: user.email ?? fallbackEmail,
|
|
523
|
+
userName: user.name || user.email || fallbackEmail,
|
|
420
524
|
role: user.role,
|
|
421
|
-
token
|
|
525
|
+
token,
|
|
526
|
+
refreshToken,
|
|
527
|
+
expiresAt
|
|
422
528
|
};
|
|
423
529
|
this.persistState();
|
|
424
|
-
|
|
530
|
+
this.scheduleRefresh();
|
|
425
531
|
}
|
|
426
|
-
|
|
532
|
+
// --- Token generation (local mode) ---
|
|
533
|
+
generateToken(user, expiresAt) {
|
|
427
534
|
return btoa(JSON.stringify({
|
|
428
|
-
userId: user.id,
|
|
535
|
+
userId: user.id ?? user.userId,
|
|
429
536
|
email: user.email,
|
|
430
537
|
role: user.role,
|
|
431
538
|
iat: Date.now(),
|
|
432
|
-
exp:
|
|
539
|
+
exp: expiresAt
|
|
433
540
|
}));
|
|
434
541
|
}
|
|
542
|
+
generateRefreshToken(user) {
|
|
543
|
+
return btoa(JSON.stringify({
|
|
544
|
+
userId: user.id ?? user.userId,
|
|
545
|
+
email: user.email,
|
|
546
|
+
role: user.role,
|
|
547
|
+
type: "refresh",
|
|
548
|
+
iat: Date.now()
|
|
549
|
+
}));
|
|
550
|
+
}
|
|
551
|
+
// --- Refresh scheduling ---
|
|
552
|
+
scheduleRefresh() {
|
|
553
|
+
this.clearRefreshTimer();
|
|
554
|
+
if (!this.authState?.expiresAt) return;
|
|
555
|
+
const delay = this.authState.expiresAt - Date.now() - 60 * 1e3;
|
|
556
|
+
if (delay <= 0) return;
|
|
557
|
+
this.refreshTimer = setTimeout(() => {
|
|
558
|
+
this.refresh().catch(() => {
|
|
559
|
+
});
|
|
560
|
+
}, delay);
|
|
561
|
+
}
|
|
562
|
+
clearRefreshTimer() {
|
|
563
|
+
if (this.refreshTimer) {
|
|
564
|
+
clearTimeout(this.refreshTimer);
|
|
565
|
+
this.refreshTimer = null;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
435
568
|
/** @internal — called by createClient to listen for auth state changes */
|
|
436
569
|
_onAuthChange(listener) {
|
|
437
570
|
this.authChangeListeners.push(listener);
|
|
@@ -1003,7 +1136,7 @@ var defaultPreset = definePreset({
|
|
|
1003
1136
|
var springBootPreset = definePreset({
|
|
1004
1137
|
name: "spring-boot",
|
|
1005
1138
|
response: {
|
|
1006
|
-
single: (raw) => ({ data: raw }),
|
|
1139
|
+
single: (raw) => ({ data: raw.data ?? raw }),
|
|
1007
1140
|
list: (raw) => ({
|
|
1008
1141
|
items: raw.content ?? [],
|
|
1009
1142
|
meta: {
|
|
@@ -1035,7 +1168,10 @@ var springBootPreset = definePreset({
|
|
|
1035
1168
|
auth: {
|
|
1036
1169
|
loginUrl: "/api/auth/login",
|
|
1037
1170
|
registerUrl: "/api/auth/register",
|
|
1171
|
+
refreshUrl: "/api/auth/refresh",
|
|
1038
1172
|
tokenField: "token",
|
|
1173
|
+
refreshTokenField: "refreshToken",
|
|
1174
|
+
expiresInField: "expiresIn",
|
|
1039
1175
|
userField: "user",
|
|
1040
1176
|
headerFormat: "Bearer {token}"
|
|
1041
1177
|
}
|
|
@@ -1086,7 +1222,7 @@ var laravelPreset = definePreset({
|
|
|
1086
1222
|
var djangoPreset = definePreset({
|
|
1087
1223
|
name: "django",
|
|
1088
1224
|
response: {
|
|
1089
|
-
single: (raw) => ({ data: raw }),
|
|
1225
|
+
single: (raw) => ({ data: raw.data ?? raw }),
|
|
1090
1226
|
list: (raw) => ({
|
|
1091
1227
|
items: raw.results ?? [],
|
|
1092
1228
|
meta: {
|
|
@@ -1275,6 +1411,7 @@ var HttpDriver = class {
|
|
|
1275
1411
|
defaultHeaders;
|
|
1276
1412
|
endpoints = /* @__PURE__ */ new Map();
|
|
1277
1413
|
authProvider = null;
|
|
1414
|
+
onUnauthorized = null;
|
|
1278
1415
|
constructor(config) {
|
|
1279
1416
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
1280
1417
|
this.preset = typeof config.preset === "string" ? getPreset(config.preset ?? "default") : config.preset ?? getPreset("default");
|
|
@@ -1286,6 +1423,10 @@ var HttpDriver = class {
|
|
|
1286
1423
|
setAuthProvider(provider) {
|
|
1287
1424
|
this.authProvider = provider;
|
|
1288
1425
|
}
|
|
1426
|
+
/** @internal — set callback to refresh token on 401 */
|
|
1427
|
+
setOnUnauthorized(handler) {
|
|
1428
|
+
this.onUnauthorized = handler;
|
|
1429
|
+
}
|
|
1289
1430
|
registerEndpoint(resource, endpoint) {
|
|
1290
1431
|
this.endpoints.set(resource, endpoint);
|
|
1291
1432
|
}
|
|
@@ -1319,6 +1460,12 @@ var HttpDriver = class {
|
|
|
1319
1460
|
});
|
|
1320
1461
|
clearTimeout(timer);
|
|
1321
1462
|
if (!response.ok) {
|
|
1463
|
+
if (response.status === 401 && retryCount === 0 && this.onUnauthorized) {
|
|
1464
|
+
const refreshed = await this.onUnauthorized();
|
|
1465
|
+
if (refreshed) {
|
|
1466
|
+
return this._fetch(url, options, 1);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1322
1469
|
if (response.status >= 500 && retryCount < this.maxRetries) {
|
|
1323
1470
|
const delay = this.baseDelay * Math.pow(2, retryCount);
|
|
1324
1471
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -1442,7 +1589,7 @@ var HttpDriver = class {
|
|
|
1442
1589
|
url += `?${params.toString()}`;
|
|
1443
1590
|
}
|
|
1444
1591
|
return this._fetch(url, {
|
|
1445
|
-
method: options?.method ?? "POST",
|
|
1592
|
+
method: options?.method ?? (options?.body !== void 0 ? "POST" : "GET"),
|
|
1446
1593
|
body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
|
|
1447
1594
|
});
|
|
1448
1595
|
}
|
|
@@ -1682,10 +1829,25 @@ function createClient(config) {
|
|
|
1682
1829
|
} else if (defaultDriver instanceof HttpDriver) {
|
|
1683
1830
|
authInstance._init(defaultDriver, resourceName);
|
|
1684
1831
|
defaultDriver.registerEndpoint(resourceName, authInstance.endpoint);
|
|
1832
|
+
const hasLocalStorage = typeof localStorage !== "undefined";
|
|
1833
|
+
const LS_AUTH_KEY = "fauxbase:auth";
|
|
1685
1834
|
let memoryAuthState = null;
|
|
1686
1835
|
authInstance._initAuth(
|
|
1687
|
-
() =>
|
|
1836
|
+
() => {
|
|
1837
|
+
if (hasLocalStorage) {
|
|
1838
|
+
const raw = localStorage.getItem(LS_AUTH_KEY);
|
|
1839
|
+
return raw ? JSON.parse(raw) : null;
|
|
1840
|
+
}
|
|
1841
|
+
return memoryAuthState;
|
|
1842
|
+
},
|
|
1688
1843
|
(state) => {
|
|
1844
|
+
if (hasLocalStorage) {
|
|
1845
|
+
if (state) {
|
|
1846
|
+
localStorage.setItem(LS_AUTH_KEY, JSON.stringify(state));
|
|
1847
|
+
} else {
|
|
1848
|
+
localStorage.removeItem(LS_AUTH_KEY);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1689
1851
|
memoryAuthState = state;
|
|
1690
1852
|
}
|
|
1691
1853
|
);
|
|
@@ -1694,6 +1856,14 @@ function createClient(config) {
|
|
|
1694
1856
|
const token = authInstance.token;
|
|
1695
1857
|
return token ? { token } : null;
|
|
1696
1858
|
});
|
|
1859
|
+
defaultDriver.setOnUnauthorized(async () => {
|
|
1860
|
+
try {
|
|
1861
|
+
await authInstance.refresh();
|
|
1862
|
+
return true;
|
|
1863
|
+
} catch {
|
|
1864
|
+
return false;
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1697
1867
|
}
|
|
1698
1868
|
client.auth = authInstance;
|
|
1699
1869
|
for (const driver of overrideDrivers.values()) {
|
|
@@ -1702,6 +1872,14 @@ function createClient(config) {
|
|
|
1702
1872
|
const token = client.auth?.token;
|
|
1703
1873
|
return token ? { token } : null;
|
|
1704
1874
|
});
|
|
1875
|
+
driver.setOnUnauthorized(async () => {
|
|
1876
|
+
try {
|
|
1877
|
+
await client.auth.refresh();
|
|
1878
|
+
return true;
|
|
1879
|
+
} catch {
|
|
1880
|
+
return false;
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1705
1883
|
}
|
|
1706
1884
|
}
|
|
1707
1885
|
}
|