@blinkdotnew/sdk 2.6.0 → 2.6.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.d.mts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +119 -55
- package/dist/index.mjs +119 -55
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -941,7 +941,17 @@ declare class HttpClient {
|
|
|
941
941
|
private readonly secretKey?;
|
|
942
942
|
private getToken;
|
|
943
943
|
private getValidToken?;
|
|
944
|
-
|
|
944
|
+
private forceRefreshToken?;
|
|
945
|
+
constructor(config: BlinkClientConfig, getToken: () => string | null, getValidToken?: () => Promise<string | null>, forceRefreshToken?: () => Promise<string | null>);
|
|
946
|
+
/**
|
|
947
|
+
* Whether a 401 on this request is worth recovering from by forcing a
|
|
948
|
+
* user-token refresh and retrying once.
|
|
949
|
+
*
|
|
950
|
+
* Only meaningful for user-JWT auth: server-side `secretKey` 401s are real
|
|
951
|
+
* authorization failures a refresh cannot fix, and publishable-key requests
|
|
952
|
+
* carry no user token to refresh.
|
|
953
|
+
*/
|
|
954
|
+
private canAttemptTokenRefresh;
|
|
945
955
|
private shouldAttachPublishableKey;
|
|
946
956
|
private shouldSkipSecretKey;
|
|
947
957
|
private getAuthorizationHeader;
|
|
@@ -1285,6 +1295,7 @@ declare class BlinkAuth {
|
|
|
1285
1295
|
private initializationPromise;
|
|
1286
1296
|
private isInitialized;
|
|
1287
1297
|
private storage;
|
|
1298
|
+
private refreshPromise;
|
|
1288
1299
|
constructor(config: BlinkClientConfig);
|
|
1289
1300
|
/**
|
|
1290
1301
|
* Generate project-scoped storage key
|
|
@@ -1672,6 +1683,18 @@ declare class BlinkAuth {
|
|
|
1672
1683
|
* Refresh access token using refresh token
|
|
1673
1684
|
*/
|
|
1674
1685
|
refreshToken(): Promise<boolean>;
|
|
1686
|
+
/**
|
|
1687
|
+
* Force a server-side token refresh and return the resulting access token.
|
|
1688
|
+
*
|
|
1689
|
+
* Used by the HTTP client to recover from a 401 even when the client-side
|
|
1690
|
+
* expiry heuristics believe the access token is still valid (clock skew, a
|
|
1691
|
+
* restored session with a stale `issued_at`, or a refresh race). Delegates to
|
|
1692
|
+
* {@link refreshToken} so it shares the single-flight guard.
|
|
1693
|
+
*
|
|
1694
|
+
* @returns The new access token, or null if refresh was not possible.
|
|
1695
|
+
*/
|
|
1696
|
+
forceRefreshAccessToken(): Promise<string | null>;
|
|
1697
|
+
private performTokenRefresh;
|
|
1675
1698
|
/**
|
|
1676
1699
|
* Add auth state change listener
|
|
1677
1700
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -941,7 +941,17 @@ declare class HttpClient {
|
|
|
941
941
|
private readonly secretKey?;
|
|
942
942
|
private getToken;
|
|
943
943
|
private getValidToken?;
|
|
944
|
-
|
|
944
|
+
private forceRefreshToken?;
|
|
945
|
+
constructor(config: BlinkClientConfig, getToken: () => string | null, getValidToken?: () => Promise<string | null>, forceRefreshToken?: () => Promise<string | null>);
|
|
946
|
+
/**
|
|
947
|
+
* Whether a 401 on this request is worth recovering from by forcing a
|
|
948
|
+
* user-token refresh and retrying once.
|
|
949
|
+
*
|
|
950
|
+
* Only meaningful for user-JWT auth: server-side `secretKey` 401s are real
|
|
951
|
+
* authorization failures a refresh cannot fix, and publishable-key requests
|
|
952
|
+
* carry no user token to refresh.
|
|
953
|
+
*/
|
|
954
|
+
private canAttemptTokenRefresh;
|
|
945
955
|
private shouldAttachPublishableKey;
|
|
946
956
|
private shouldSkipSecretKey;
|
|
947
957
|
private getAuthorizationHeader;
|
|
@@ -1285,6 +1295,7 @@ declare class BlinkAuth {
|
|
|
1285
1295
|
private initializationPromise;
|
|
1286
1296
|
private isInitialized;
|
|
1287
1297
|
private storage;
|
|
1298
|
+
private refreshPromise;
|
|
1288
1299
|
constructor(config: BlinkClientConfig);
|
|
1289
1300
|
/**
|
|
1290
1301
|
* Generate project-scoped storage key
|
|
@@ -1672,6 +1683,18 @@ declare class BlinkAuth {
|
|
|
1672
1683
|
* Refresh access token using refresh token
|
|
1673
1684
|
*/
|
|
1674
1685
|
refreshToken(): Promise<boolean>;
|
|
1686
|
+
/**
|
|
1687
|
+
* Force a server-side token refresh and return the resulting access token.
|
|
1688
|
+
*
|
|
1689
|
+
* Used by the HTTP client to recover from a 401 even when the client-side
|
|
1690
|
+
* expiry heuristics believe the access token is still valid (clock skew, a
|
|
1691
|
+
* restored session with a stale `issued_at`, or a refresh race). Delegates to
|
|
1692
|
+
* {@link refreshToken} so it shares the single-flight guard.
|
|
1693
|
+
*
|
|
1694
|
+
* @returns The new access token, or null if refresh was not possible.
|
|
1695
|
+
*/
|
|
1696
|
+
forceRefreshAccessToken(): Promise<string | null>;
|
|
1697
|
+
private performTokenRefresh;
|
|
1675
1698
|
/**
|
|
1676
1699
|
* Add auth state change listener
|
|
1677
1700
|
*/
|
package/dist/index.js
CHANGED
|
@@ -421,12 +421,25 @@ var HttpClient = class {
|
|
|
421
421
|
// Permanent, non-expiring key (like Stripe's sk_live_...)
|
|
422
422
|
getToken;
|
|
423
423
|
getValidToken;
|
|
424
|
-
|
|
424
|
+
forceRefreshToken;
|
|
425
|
+
constructor(config, getToken, getValidToken, forceRefreshToken) {
|
|
425
426
|
this.projectId = config.projectId;
|
|
426
427
|
this.publishableKey = config.publishableKey;
|
|
427
428
|
this.secretKey = config.secretKey || config.serviceToken;
|
|
428
429
|
this.getToken = getToken;
|
|
429
430
|
this.getValidToken = getValidToken;
|
|
431
|
+
this.forceRefreshToken = forceRefreshToken;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Whether a 401 on this request is worth recovering from by forcing a
|
|
435
|
+
* user-token refresh and retrying once.
|
|
436
|
+
*
|
|
437
|
+
* Only meaningful for user-JWT auth: server-side `secretKey` 401s are real
|
|
438
|
+
* authorization failures a refresh cannot fix, and publishable-key requests
|
|
439
|
+
* carry no user token to refresh.
|
|
440
|
+
*/
|
|
441
|
+
canAttemptTokenRefresh(token) {
|
|
442
|
+
return !!this.forceRefreshToken && !this.secretKey && !!token;
|
|
430
443
|
}
|
|
431
444
|
shouldAttachPublishableKey(path, method) {
|
|
432
445
|
if (method !== "GET" && method !== "POST") return false;
|
|
@@ -457,28 +470,37 @@ var HttpClient = class {
|
|
|
457
470
|
*/
|
|
458
471
|
async request(path, options = {}) {
|
|
459
472
|
const url = this.buildUrl(path, options.searchParams);
|
|
460
|
-
const token = this.getValidToken ? await this.getValidToken() : this.getToken();
|
|
461
473
|
const method = options.method || "GET";
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
headers["x-blink-publishable-key"]
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
474
|
+
const sendRequest = (token) => {
|
|
475
|
+
const headers = {
|
|
476
|
+
"Content-Type": "application/json",
|
|
477
|
+
...options.headers
|
|
478
|
+
};
|
|
479
|
+
const auth = this.getAuthorizationHeader(url, token);
|
|
480
|
+
if (auth) {
|
|
481
|
+
headers.Authorization = auth;
|
|
482
|
+
} else if (this.publishableKey && !headers["x-blink-publishable-key"] && this.shouldAttachPublishableKey(path, method)) {
|
|
483
|
+
headers["x-blink-publishable-key"] = this.publishableKey;
|
|
484
|
+
}
|
|
485
|
+
const requestInit = {
|
|
486
|
+
method,
|
|
487
|
+
headers,
|
|
488
|
+
signal: options.signal
|
|
489
|
+
};
|
|
490
|
+
if (options.body && method !== "GET") {
|
|
491
|
+
requestInit.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
|
|
492
|
+
}
|
|
493
|
+
return fetch(url, requestInit);
|
|
476
494
|
};
|
|
477
|
-
if (options.body && method !== "GET") {
|
|
478
|
-
requestInit.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
|
|
479
|
-
}
|
|
480
495
|
try {
|
|
481
|
-
const
|
|
496
|
+
const token = this.getValidToken ? await this.getValidToken() : this.getToken();
|
|
497
|
+
let response = await sendRequest(token);
|
|
498
|
+
if (response.status === 401 && this.canAttemptTokenRefresh(token)) {
|
|
499
|
+
const refreshedToken = await this.forceRefreshToken();
|
|
500
|
+
if (refreshedToken) {
|
|
501
|
+
response = await sendRequest(refreshedToken);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
482
504
|
if (!response.ok) {
|
|
483
505
|
await this.handleErrorResponse(response);
|
|
484
506
|
}
|
|
@@ -636,7 +658,6 @@ var HttpClient = class {
|
|
|
636
658
|
*/
|
|
637
659
|
async uploadFile(path, file, filePath, options = {}) {
|
|
638
660
|
const url = this.buildUrl(path);
|
|
639
|
-
const token = this.getValidToken ? await this.getValidToken() : this.getToken();
|
|
640
661
|
const formData = new FormData();
|
|
641
662
|
if (file instanceof File) {
|
|
642
663
|
formData.append("file", file);
|
|
@@ -650,41 +671,57 @@ var HttpClient = class {
|
|
|
650
671
|
throw new BlinkValidationError("Unsupported file type");
|
|
651
672
|
}
|
|
652
673
|
formData.append("path", filePath);
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (typeof XMLHttpRequest !== "undefined" && options.onProgress) {
|
|
662
|
-
return this.uploadWithProgress(url, formData, headers, options.onProgress);
|
|
663
|
-
}
|
|
664
|
-
const response = await fetch(url, {
|
|
665
|
-
method: "POST",
|
|
666
|
-
headers,
|
|
667
|
-
body: formData
|
|
668
|
-
});
|
|
669
|
-
if (!response.ok) {
|
|
670
|
-
await this.handleErrorResponse(response);
|
|
674
|
+
const attempt = async (allowRetry) => {
|
|
675
|
+
const token = this.getValidToken ? await this.getValidToken() : this.getToken();
|
|
676
|
+
const headers = {};
|
|
677
|
+
const auth = this.getAuthorizationHeader(url, token);
|
|
678
|
+
if (auth) {
|
|
679
|
+
headers.Authorization = auth;
|
|
680
|
+
} else if (this.publishableKey && path.includes("/api/storage/") && !headers["x-blink-publishable-key"]) {
|
|
681
|
+
headers["x-blink-publishable-key"] = this.publishableKey;
|
|
671
682
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
683
|
+
try {
|
|
684
|
+
if (typeof XMLHttpRequest !== "undefined" && options.onProgress) {
|
|
685
|
+
return await this.uploadWithProgress(url, formData, headers, options.onProgress);
|
|
686
|
+
}
|
|
687
|
+
const response = await fetch(url, {
|
|
688
|
+
method: "POST",
|
|
689
|
+
headers,
|
|
690
|
+
body: formData
|
|
691
|
+
});
|
|
692
|
+
if (response.status === 401 && allowRetry && this.canAttemptTokenRefresh(token)) {
|
|
693
|
+
const refreshedToken = await this.forceRefreshToken();
|
|
694
|
+
if (refreshedToken) {
|
|
695
|
+
return attempt(false);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (!response.ok) {
|
|
699
|
+
await this.handleErrorResponse(response);
|
|
700
|
+
}
|
|
701
|
+
const data = await this.parseResponse(response);
|
|
702
|
+
return {
|
|
703
|
+
data,
|
|
704
|
+
status: response.status,
|
|
705
|
+
headers: response.headers
|
|
706
|
+
};
|
|
707
|
+
} catch (error) {
|
|
708
|
+
if (allowRetry && error instanceof BlinkAuthError && this.canAttemptTokenRefresh(token)) {
|
|
709
|
+
const refreshedToken = await this.forceRefreshToken();
|
|
710
|
+
if (refreshedToken) {
|
|
711
|
+
return attempt(false);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (error instanceof BlinkError) {
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
throw new BlinkNetworkError(
|
|
718
|
+
`File upload failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
719
|
+
0,
|
|
720
|
+
{ originalError: error }
|
|
721
|
+
);
|
|
681
722
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
0,
|
|
685
|
-
{ originalError: error }
|
|
686
|
-
);
|
|
687
|
-
}
|
|
723
|
+
};
|
|
724
|
+
return attempt(true);
|
|
688
725
|
}
|
|
689
726
|
/**
|
|
690
727
|
* Upload with progress tracking using XMLHttpRequest
|
|
@@ -1376,6 +1413,7 @@ var BlinkAuth = class {
|
|
|
1376
1413
|
initializationPromise = null;
|
|
1377
1414
|
isInitialized = false;
|
|
1378
1415
|
storage;
|
|
1416
|
+
refreshPromise = null;
|
|
1379
1417
|
constructor(config) {
|
|
1380
1418
|
this.config = config;
|
|
1381
1419
|
if (!config.projectId) {
|
|
@@ -2823,10 +2861,35 @@ var BlinkAuth = class {
|
|
|
2823
2861
|
* Refresh access token using refresh token
|
|
2824
2862
|
*/
|
|
2825
2863
|
async refreshToken() {
|
|
2864
|
+
if (this.refreshPromise) {
|
|
2865
|
+
return this.refreshPromise;
|
|
2866
|
+
}
|
|
2826
2867
|
const refreshToken = this.authState.tokens?.refresh_token;
|
|
2827
2868
|
if (!refreshToken) {
|
|
2828
2869
|
return false;
|
|
2829
2870
|
}
|
|
2871
|
+
this.refreshPromise = this.performTokenRefresh(refreshToken);
|
|
2872
|
+
try {
|
|
2873
|
+
return await this.refreshPromise;
|
|
2874
|
+
} finally {
|
|
2875
|
+
this.refreshPromise = null;
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Force a server-side token refresh and return the resulting access token.
|
|
2880
|
+
*
|
|
2881
|
+
* Used by the HTTP client to recover from a 401 even when the client-side
|
|
2882
|
+
* expiry heuristics believe the access token is still valid (clock skew, a
|
|
2883
|
+
* restored session with a stale `issued_at`, or a refresh race). Delegates to
|
|
2884
|
+
* {@link refreshToken} so it shares the single-flight guard.
|
|
2885
|
+
*
|
|
2886
|
+
* @returns The new access token, or null if refresh was not possible.
|
|
2887
|
+
*/
|
|
2888
|
+
async forceRefreshAccessToken() {
|
|
2889
|
+
const refreshed = await this.refreshToken();
|
|
2890
|
+
return refreshed ? this.authState.tokens?.access_token || null : null;
|
|
2891
|
+
}
|
|
2892
|
+
async performTokenRefresh(refreshToken) {
|
|
2830
2893
|
try {
|
|
2831
2894
|
const response = await fetch(`${this.authUrl}/api/auth/refresh`, {
|
|
2832
2895
|
method: "POST",
|
|
@@ -6956,7 +7019,8 @@ var BlinkClientImpl = class {
|
|
|
6956
7019
|
this._httpClient = new HttpClient(
|
|
6957
7020
|
config,
|
|
6958
7021
|
() => this.auth.getToken(),
|
|
6959
|
-
() => this.auth.getValidToken()
|
|
7022
|
+
() => this.auth.getValidToken(),
|
|
7023
|
+
() => this.auth.forceRefreshAccessToken()
|
|
6960
7024
|
);
|
|
6961
7025
|
this.db = new BlinkDatabase(this._httpClient);
|
|
6962
7026
|
this.storage = new BlinkStorageImpl(this._httpClient);
|
package/dist/index.mjs
CHANGED
|
@@ -419,12 +419,25 @@ var HttpClient = class {
|
|
|
419
419
|
// Permanent, non-expiring key (like Stripe's sk_live_...)
|
|
420
420
|
getToken;
|
|
421
421
|
getValidToken;
|
|
422
|
-
|
|
422
|
+
forceRefreshToken;
|
|
423
|
+
constructor(config, getToken, getValidToken, forceRefreshToken) {
|
|
423
424
|
this.projectId = config.projectId;
|
|
424
425
|
this.publishableKey = config.publishableKey;
|
|
425
426
|
this.secretKey = config.secretKey || config.serviceToken;
|
|
426
427
|
this.getToken = getToken;
|
|
427
428
|
this.getValidToken = getValidToken;
|
|
429
|
+
this.forceRefreshToken = forceRefreshToken;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Whether a 401 on this request is worth recovering from by forcing a
|
|
433
|
+
* user-token refresh and retrying once.
|
|
434
|
+
*
|
|
435
|
+
* Only meaningful for user-JWT auth: server-side `secretKey` 401s are real
|
|
436
|
+
* authorization failures a refresh cannot fix, and publishable-key requests
|
|
437
|
+
* carry no user token to refresh.
|
|
438
|
+
*/
|
|
439
|
+
canAttemptTokenRefresh(token) {
|
|
440
|
+
return !!this.forceRefreshToken && !this.secretKey && !!token;
|
|
428
441
|
}
|
|
429
442
|
shouldAttachPublishableKey(path, method) {
|
|
430
443
|
if (method !== "GET" && method !== "POST") return false;
|
|
@@ -455,28 +468,37 @@ var HttpClient = class {
|
|
|
455
468
|
*/
|
|
456
469
|
async request(path, options = {}) {
|
|
457
470
|
const url = this.buildUrl(path, options.searchParams);
|
|
458
|
-
const token = this.getValidToken ? await this.getValidToken() : this.getToken();
|
|
459
471
|
const method = options.method || "GET";
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
headers["x-blink-publishable-key"]
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
472
|
+
const sendRequest = (token) => {
|
|
473
|
+
const headers = {
|
|
474
|
+
"Content-Type": "application/json",
|
|
475
|
+
...options.headers
|
|
476
|
+
};
|
|
477
|
+
const auth = this.getAuthorizationHeader(url, token);
|
|
478
|
+
if (auth) {
|
|
479
|
+
headers.Authorization = auth;
|
|
480
|
+
} else if (this.publishableKey && !headers["x-blink-publishable-key"] && this.shouldAttachPublishableKey(path, method)) {
|
|
481
|
+
headers["x-blink-publishable-key"] = this.publishableKey;
|
|
482
|
+
}
|
|
483
|
+
const requestInit = {
|
|
484
|
+
method,
|
|
485
|
+
headers,
|
|
486
|
+
signal: options.signal
|
|
487
|
+
};
|
|
488
|
+
if (options.body && method !== "GET") {
|
|
489
|
+
requestInit.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
|
|
490
|
+
}
|
|
491
|
+
return fetch(url, requestInit);
|
|
474
492
|
};
|
|
475
|
-
if (options.body && method !== "GET") {
|
|
476
|
-
requestInit.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
|
|
477
|
-
}
|
|
478
493
|
try {
|
|
479
|
-
const
|
|
494
|
+
const token = this.getValidToken ? await this.getValidToken() : this.getToken();
|
|
495
|
+
let response = await sendRequest(token);
|
|
496
|
+
if (response.status === 401 && this.canAttemptTokenRefresh(token)) {
|
|
497
|
+
const refreshedToken = await this.forceRefreshToken();
|
|
498
|
+
if (refreshedToken) {
|
|
499
|
+
response = await sendRequest(refreshedToken);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
480
502
|
if (!response.ok) {
|
|
481
503
|
await this.handleErrorResponse(response);
|
|
482
504
|
}
|
|
@@ -634,7 +656,6 @@ var HttpClient = class {
|
|
|
634
656
|
*/
|
|
635
657
|
async uploadFile(path, file, filePath, options = {}) {
|
|
636
658
|
const url = this.buildUrl(path);
|
|
637
|
-
const token = this.getValidToken ? await this.getValidToken() : this.getToken();
|
|
638
659
|
const formData = new FormData();
|
|
639
660
|
if (file instanceof File) {
|
|
640
661
|
formData.append("file", file);
|
|
@@ -648,41 +669,57 @@ var HttpClient = class {
|
|
|
648
669
|
throw new BlinkValidationError("Unsupported file type");
|
|
649
670
|
}
|
|
650
671
|
formData.append("path", filePath);
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
if (typeof XMLHttpRequest !== "undefined" && options.onProgress) {
|
|
660
|
-
return this.uploadWithProgress(url, formData, headers, options.onProgress);
|
|
661
|
-
}
|
|
662
|
-
const response = await fetch(url, {
|
|
663
|
-
method: "POST",
|
|
664
|
-
headers,
|
|
665
|
-
body: formData
|
|
666
|
-
});
|
|
667
|
-
if (!response.ok) {
|
|
668
|
-
await this.handleErrorResponse(response);
|
|
672
|
+
const attempt = async (allowRetry) => {
|
|
673
|
+
const token = this.getValidToken ? await this.getValidToken() : this.getToken();
|
|
674
|
+
const headers = {};
|
|
675
|
+
const auth = this.getAuthorizationHeader(url, token);
|
|
676
|
+
if (auth) {
|
|
677
|
+
headers.Authorization = auth;
|
|
678
|
+
} else if (this.publishableKey && path.includes("/api/storage/") && !headers["x-blink-publishable-key"]) {
|
|
679
|
+
headers["x-blink-publishable-key"] = this.publishableKey;
|
|
669
680
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
681
|
+
try {
|
|
682
|
+
if (typeof XMLHttpRequest !== "undefined" && options.onProgress) {
|
|
683
|
+
return await this.uploadWithProgress(url, formData, headers, options.onProgress);
|
|
684
|
+
}
|
|
685
|
+
const response = await fetch(url, {
|
|
686
|
+
method: "POST",
|
|
687
|
+
headers,
|
|
688
|
+
body: formData
|
|
689
|
+
});
|
|
690
|
+
if (response.status === 401 && allowRetry && this.canAttemptTokenRefresh(token)) {
|
|
691
|
+
const refreshedToken = await this.forceRefreshToken();
|
|
692
|
+
if (refreshedToken) {
|
|
693
|
+
return attempt(false);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (!response.ok) {
|
|
697
|
+
await this.handleErrorResponse(response);
|
|
698
|
+
}
|
|
699
|
+
const data = await this.parseResponse(response);
|
|
700
|
+
return {
|
|
701
|
+
data,
|
|
702
|
+
status: response.status,
|
|
703
|
+
headers: response.headers
|
|
704
|
+
};
|
|
705
|
+
} catch (error) {
|
|
706
|
+
if (allowRetry && error instanceof BlinkAuthError && this.canAttemptTokenRefresh(token)) {
|
|
707
|
+
const refreshedToken = await this.forceRefreshToken();
|
|
708
|
+
if (refreshedToken) {
|
|
709
|
+
return attempt(false);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (error instanceof BlinkError) {
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
throw new BlinkNetworkError(
|
|
716
|
+
`File upload failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
717
|
+
0,
|
|
718
|
+
{ originalError: error }
|
|
719
|
+
);
|
|
679
720
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
0,
|
|
683
|
-
{ originalError: error }
|
|
684
|
-
);
|
|
685
|
-
}
|
|
721
|
+
};
|
|
722
|
+
return attempt(true);
|
|
686
723
|
}
|
|
687
724
|
/**
|
|
688
725
|
* Upload with progress tracking using XMLHttpRequest
|
|
@@ -1374,6 +1411,7 @@ var BlinkAuth = class {
|
|
|
1374
1411
|
initializationPromise = null;
|
|
1375
1412
|
isInitialized = false;
|
|
1376
1413
|
storage;
|
|
1414
|
+
refreshPromise = null;
|
|
1377
1415
|
constructor(config) {
|
|
1378
1416
|
this.config = config;
|
|
1379
1417
|
if (!config.projectId) {
|
|
@@ -2821,10 +2859,35 @@ var BlinkAuth = class {
|
|
|
2821
2859
|
* Refresh access token using refresh token
|
|
2822
2860
|
*/
|
|
2823
2861
|
async refreshToken() {
|
|
2862
|
+
if (this.refreshPromise) {
|
|
2863
|
+
return this.refreshPromise;
|
|
2864
|
+
}
|
|
2824
2865
|
const refreshToken = this.authState.tokens?.refresh_token;
|
|
2825
2866
|
if (!refreshToken) {
|
|
2826
2867
|
return false;
|
|
2827
2868
|
}
|
|
2869
|
+
this.refreshPromise = this.performTokenRefresh(refreshToken);
|
|
2870
|
+
try {
|
|
2871
|
+
return await this.refreshPromise;
|
|
2872
|
+
} finally {
|
|
2873
|
+
this.refreshPromise = null;
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Force a server-side token refresh and return the resulting access token.
|
|
2878
|
+
*
|
|
2879
|
+
* Used by the HTTP client to recover from a 401 even when the client-side
|
|
2880
|
+
* expiry heuristics believe the access token is still valid (clock skew, a
|
|
2881
|
+
* restored session with a stale `issued_at`, or a refresh race). Delegates to
|
|
2882
|
+
* {@link refreshToken} so it shares the single-flight guard.
|
|
2883
|
+
*
|
|
2884
|
+
* @returns The new access token, or null if refresh was not possible.
|
|
2885
|
+
*/
|
|
2886
|
+
async forceRefreshAccessToken() {
|
|
2887
|
+
const refreshed = await this.refreshToken();
|
|
2888
|
+
return refreshed ? this.authState.tokens?.access_token || null : null;
|
|
2889
|
+
}
|
|
2890
|
+
async performTokenRefresh(refreshToken) {
|
|
2828
2891
|
try {
|
|
2829
2892
|
const response = await fetch(`${this.authUrl}/api/auth/refresh`, {
|
|
2830
2893
|
method: "POST",
|
|
@@ -6954,7 +7017,8 @@ var BlinkClientImpl = class {
|
|
|
6954
7017
|
this._httpClient = new HttpClient(
|
|
6955
7018
|
config,
|
|
6956
7019
|
() => this.auth.getToken(),
|
|
6957
|
-
() => this.auth.getValidToken()
|
|
7020
|
+
() => this.auth.getValidToken(),
|
|
7021
|
+
() => this.auth.forceRefreshAccessToken()
|
|
6958
7022
|
);
|
|
6959
7023
|
this.db = new BlinkDatabase(this._httpClient);
|
|
6960
7024
|
this.storage = new BlinkStorageImpl(this._httpClient);
|
package/package.json
CHANGED