@sparkvault/sdk 1.24.2 → 1.24.3
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/config.d.ts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/sparkvault.cjs.js +46 -12
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +46 -12
- package/dist/sparkvault.esm.js.map +1 -1
- package/dist/sparkvault.js +1 -1
- package/dist/sparkvault.js.map +1 -1
- package/package.json +1 -1
package/dist/sparkvault.esm.js
CHANGED
|
@@ -64,6 +64,11 @@ class PopupBlockedError extends SparkVaultError {
|
|
|
64
64
|
/**
|
|
65
65
|
* SparkVault SDK Configuration
|
|
66
66
|
*/
|
|
67
|
+
const DEFAULT_ALLOWED_DOWNLOAD_HOST_PATTERNS = [
|
|
68
|
+
/\.sparkvault\.(com|io)$/i,
|
|
69
|
+
/\.amazonaws\.com$/i,
|
|
70
|
+
/\.cloudfront\.net$/i,
|
|
71
|
+
];
|
|
67
72
|
const API_URL = 'https://api.sparkvault.com';
|
|
68
73
|
const IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity';
|
|
69
74
|
function normalizeApiBaseUrl(url) {
|
|
@@ -84,7 +89,7 @@ function resolveConfig(config) {
|
|
|
84
89
|
apiKey: config.apiKey,
|
|
85
90
|
preloadConfig: config.preloadConfig !== false, // Default: true
|
|
86
91
|
backdropBlur: config.backdropBlur !== false, // Default: true
|
|
87
|
-
allowedDownloadHostPatterns: config.allowedDownloadHostPatterns,
|
|
92
|
+
allowedDownloadHostPatterns: config.allowedDownloadHostPatterns ?? DEFAULT_ALLOWED_DOWNLOAD_HOST_PATTERNS,
|
|
88
93
|
};
|
|
89
94
|
}
|
|
90
95
|
function validateConfig(config) {
|
|
@@ -9786,14 +9791,27 @@ class VaultsModule {
|
|
|
9786
9791
|
*/
|
|
9787
9792
|
async downloadIngot(vault, ingotId) {
|
|
9788
9793
|
const cleanId = ingotId.startsWith('ing_') ? ingotId : `ing_${ingotId}`;
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
+
let lastError = null;
|
|
9795
|
+
for (let attempt = 1; attempt <= DOWNLOAD_MAX_ATTEMPTS; attempt++) {
|
|
9796
|
+
try {
|
|
9797
|
+
const response = await this.http.post(`/v1/vaults/${vault.id}/ingots/${cleanId}/download`, undefined, {
|
|
9798
|
+
headers: { 'X-Vault-Access-Token': vault.vatToken },
|
|
9799
|
+
});
|
|
9800
|
+
if (!response.data.download_url) {
|
|
9801
|
+
throw new ValidationError('Download endpoint did not return a download URL');
|
|
9802
|
+
}
|
|
9803
|
+
this.validateDownloadUrl(response.data.download_url);
|
|
9804
|
+
return await fetchBlobWithTimeout(response.data.download_url);
|
|
9805
|
+
}
|
|
9806
|
+
catch (err) {
|
|
9807
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
9808
|
+
if (!isRetryableDownloadError(lastError) || attempt === DOWNLOAD_MAX_ATTEMPTS) {
|
|
9809
|
+
throw lastError;
|
|
9810
|
+
}
|
|
9811
|
+
await delayExponential(attempt);
|
|
9812
|
+
}
|
|
9794
9813
|
}
|
|
9795
|
-
|
|
9796
|
-
return fetchBlobWithTimeout(response.data.download_url);
|
|
9814
|
+
throw lastError ?? new Error('Download failed');
|
|
9797
9815
|
}
|
|
9798
9816
|
/**
|
|
9799
9817
|
* List all ingots in an unsealed vault.
|
|
@@ -9854,9 +9872,6 @@ class VaultsModule {
|
|
|
9854
9872
|
throw new ValidationError('Download URL must use HTTPS');
|
|
9855
9873
|
}
|
|
9856
9874
|
const allowedHosts = this.config.allowedDownloadHostPatterns;
|
|
9857
|
-
if (!allowedHosts?.length) {
|
|
9858
|
-
return;
|
|
9859
|
-
}
|
|
9860
9875
|
if (!allowedHosts.some(pattern => pattern.test(parsed.hostname))) {
|
|
9861
9876
|
throw new ValidationError('Invalid download URL from server');
|
|
9862
9877
|
}
|
|
@@ -9940,13 +9955,32 @@ function uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, tusVer
|
|
|
9940
9955
|
xhr.send(chunk);
|
|
9941
9956
|
});
|
|
9942
9957
|
}
|
|
9958
|
+
const DOWNLOAD_MAX_ATTEMPTS = 3;
|
|
9959
|
+
const DOWNLOAD_RETRY_BASE_MS = 500;
|
|
9960
|
+
function delayExponential(attempt) {
|
|
9961
|
+
return new Promise(resolve => setTimeout(resolve, DOWNLOAD_RETRY_BASE_MS * 2 ** (attempt - 1)));
|
|
9962
|
+
}
|
|
9963
|
+
function isRetryableDownloadError(err) {
|
|
9964
|
+
// Server contract / client-side validation errors aren't fixed by retrying.
|
|
9965
|
+
if (err instanceof ValidationError)
|
|
9966
|
+
return false;
|
|
9967
|
+
// Network errors carry a status code only when the SDK HTTP client attached
|
|
9968
|
+
// one; treat any explicitly-4xx response as a permanent failure.
|
|
9969
|
+
const status = err.statusCode;
|
|
9970
|
+
if (typeof status === 'number' && status >= 400 && status < 500 && status !== 408 && status !== 429) {
|
|
9971
|
+
return false;
|
|
9972
|
+
}
|
|
9973
|
+
return true;
|
|
9974
|
+
}
|
|
9943
9975
|
async function fetchBlobWithTimeout(url, timeout = 300000) {
|
|
9944
9976
|
const controller = new AbortController();
|
|
9945
9977
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
9946
9978
|
try {
|
|
9947
9979
|
const response = await fetch(url, { signal: controller.signal });
|
|
9948
9980
|
if (!response.ok) {
|
|
9949
|
-
|
|
9981
|
+
const error = new Error(`Download failed with status ${response.status}`);
|
|
9982
|
+
error.statusCode = response.status;
|
|
9983
|
+
throw error;
|
|
9950
9984
|
}
|
|
9951
9985
|
return response.blob();
|
|
9952
9986
|
}
|