@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/config.d.ts
CHANGED
|
@@ -26,8 +26,9 @@ export interface SparkVaultConfig {
|
|
|
26
26
|
/** Enable backdrop blur on dialogs (default: true) */
|
|
27
27
|
backdropBlur?: boolean;
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* Host allowlist for backend-issued ingot download URLs. Defaults to the
|
|
30
|
+
* canonical SparkVault Forge / S3 / CloudFront hosts. Override only when
|
|
31
|
+
* pointing at a custom deployment.
|
|
31
32
|
*/
|
|
32
33
|
allowedDownloadHostPatterns?: RegExp[];
|
|
33
34
|
}
|
|
@@ -41,7 +42,7 @@ export interface ResolvedConfig {
|
|
|
41
42
|
apiKey?: string;
|
|
42
43
|
preloadConfig: boolean;
|
|
43
44
|
backdropBlur: boolean;
|
|
44
|
-
allowedDownloadHostPatterns
|
|
45
|
+
allowedDownloadHostPatterns: RegExp[];
|
|
45
46
|
}
|
|
46
47
|
export declare function resolveConfig(config: SparkVaultConfig): ResolvedConfig;
|
|
47
48
|
export declare function validateConfig(config: SparkVaultConfig): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -26,8 +26,9 @@ interface SparkVaultConfig {
|
|
|
26
26
|
/** Enable backdrop blur on dialogs (default: true) */
|
|
27
27
|
backdropBlur?: boolean;
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* Host allowlist for backend-issued ingot download URLs. Defaults to the
|
|
30
|
+
* canonical SparkVault Forge / S3 / CloudFront hosts. Override only when
|
|
31
|
+
* pointing at a custom deployment.
|
|
31
32
|
*/
|
|
32
33
|
allowedDownloadHostPatterns?: RegExp[];
|
|
33
34
|
}
|
|
@@ -41,7 +42,7 @@ interface ResolvedConfig {
|
|
|
41
42
|
apiKey?: string;
|
|
42
43
|
preloadConfig: boolean;
|
|
43
44
|
backdropBlur: boolean;
|
|
44
|
-
allowedDownloadHostPatterns
|
|
45
|
+
allowedDownloadHostPatterns: RegExp[];
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
interface HealthCheckOptions {
|
package/dist/sparkvault.cjs.js
CHANGED
|
@@ -68,6 +68,11 @@ class PopupBlockedError extends SparkVaultError {
|
|
|
68
68
|
/**
|
|
69
69
|
* SparkVault SDK Configuration
|
|
70
70
|
*/
|
|
71
|
+
const DEFAULT_ALLOWED_DOWNLOAD_HOST_PATTERNS = [
|
|
72
|
+
/\.sparkvault\.(com|io)$/i,
|
|
73
|
+
/\.amazonaws\.com$/i,
|
|
74
|
+
/\.cloudfront\.net$/i,
|
|
75
|
+
];
|
|
71
76
|
const API_URL = 'https://api.sparkvault.com';
|
|
72
77
|
const IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity';
|
|
73
78
|
function normalizeApiBaseUrl(url) {
|
|
@@ -88,7 +93,7 @@ function resolveConfig(config) {
|
|
|
88
93
|
apiKey: config.apiKey,
|
|
89
94
|
preloadConfig: config.preloadConfig !== false, // Default: true
|
|
90
95
|
backdropBlur: config.backdropBlur !== false, // Default: true
|
|
91
|
-
allowedDownloadHostPatterns: config.allowedDownloadHostPatterns,
|
|
96
|
+
allowedDownloadHostPatterns: config.allowedDownloadHostPatterns ?? DEFAULT_ALLOWED_DOWNLOAD_HOST_PATTERNS,
|
|
92
97
|
};
|
|
93
98
|
}
|
|
94
99
|
function validateConfig(config) {
|
|
@@ -9790,14 +9795,27 @@ class VaultsModule {
|
|
|
9790
9795
|
*/
|
|
9791
9796
|
async downloadIngot(vault, ingotId) {
|
|
9792
9797
|
const cleanId = ingotId.startsWith('ing_') ? ingotId : `ing_${ingotId}`;
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
+
let lastError = null;
|
|
9799
|
+
for (let attempt = 1; attempt <= DOWNLOAD_MAX_ATTEMPTS; attempt++) {
|
|
9800
|
+
try {
|
|
9801
|
+
const response = await this.http.post(`/v1/vaults/${vault.id}/ingots/${cleanId}/download`, undefined, {
|
|
9802
|
+
headers: { 'X-Vault-Access-Token': vault.vatToken },
|
|
9803
|
+
});
|
|
9804
|
+
if (!response.data.download_url) {
|
|
9805
|
+
throw new ValidationError('Download endpoint did not return a download URL');
|
|
9806
|
+
}
|
|
9807
|
+
this.validateDownloadUrl(response.data.download_url);
|
|
9808
|
+
return await fetchBlobWithTimeout(response.data.download_url);
|
|
9809
|
+
}
|
|
9810
|
+
catch (err) {
|
|
9811
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
9812
|
+
if (!isRetryableDownloadError(lastError) || attempt === DOWNLOAD_MAX_ATTEMPTS) {
|
|
9813
|
+
throw lastError;
|
|
9814
|
+
}
|
|
9815
|
+
await delayExponential(attempt);
|
|
9816
|
+
}
|
|
9798
9817
|
}
|
|
9799
|
-
|
|
9800
|
-
return fetchBlobWithTimeout(response.data.download_url);
|
|
9818
|
+
throw lastError ?? new Error('Download failed');
|
|
9801
9819
|
}
|
|
9802
9820
|
/**
|
|
9803
9821
|
* List all ingots in an unsealed vault.
|
|
@@ -9858,9 +9876,6 @@ class VaultsModule {
|
|
|
9858
9876
|
throw new ValidationError('Download URL must use HTTPS');
|
|
9859
9877
|
}
|
|
9860
9878
|
const allowedHosts = this.config.allowedDownloadHostPatterns;
|
|
9861
|
-
if (!allowedHosts?.length) {
|
|
9862
|
-
return;
|
|
9863
|
-
}
|
|
9864
9879
|
if (!allowedHosts.some(pattern => pattern.test(parsed.hostname))) {
|
|
9865
9880
|
throw new ValidationError('Invalid download URL from server');
|
|
9866
9881
|
}
|
|
@@ -9944,13 +9959,32 @@ function uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, tusVer
|
|
|
9944
9959
|
xhr.send(chunk);
|
|
9945
9960
|
});
|
|
9946
9961
|
}
|
|
9962
|
+
const DOWNLOAD_MAX_ATTEMPTS = 3;
|
|
9963
|
+
const DOWNLOAD_RETRY_BASE_MS = 500;
|
|
9964
|
+
function delayExponential(attempt) {
|
|
9965
|
+
return new Promise(resolve => setTimeout(resolve, DOWNLOAD_RETRY_BASE_MS * 2 ** (attempt - 1)));
|
|
9966
|
+
}
|
|
9967
|
+
function isRetryableDownloadError(err) {
|
|
9968
|
+
// Server contract / client-side validation errors aren't fixed by retrying.
|
|
9969
|
+
if (err instanceof ValidationError)
|
|
9970
|
+
return false;
|
|
9971
|
+
// Network errors carry a status code only when the SDK HTTP client attached
|
|
9972
|
+
// one; treat any explicitly-4xx response as a permanent failure.
|
|
9973
|
+
const status = err.statusCode;
|
|
9974
|
+
if (typeof status === 'number' && status >= 400 && status < 500 && status !== 408 && status !== 429) {
|
|
9975
|
+
return false;
|
|
9976
|
+
}
|
|
9977
|
+
return true;
|
|
9978
|
+
}
|
|
9947
9979
|
async function fetchBlobWithTimeout(url, timeout = 300000) {
|
|
9948
9980
|
const controller = new AbortController();
|
|
9949
9981
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
9950
9982
|
try {
|
|
9951
9983
|
const response = await fetch(url, { signal: controller.signal });
|
|
9952
9984
|
if (!response.ok) {
|
|
9953
|
-
|
|
9985
|
+
const error = new Error(`Download failed with status ${response.status}`);
|
|
9986
|
+
error.statusCode = response.status;
|
|
9987
|
+
throw error;
|
|
9954
9988
|
}
|
|
9955
9989
|
return response.blob();
|
|
9956
9990
|
}
|