@sparkvault/sdk 1.24.1 → 1.24.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/dist/config.d.ts +19 -0
- package/dist/health.d.ts +18 -0
- package/dist/http.d.ts +3 -0
- package/dist/index.d.ts +65 -11
- package/dist/sparkvault.cjs.js +265 -38
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +265 -38
- package/dist/sparkvault.esm.js.map +1 -1
- package/dist/sparkvault.js +1 -1
- package/dist/sparkvault.js.map +1 -1
- package/dist/vaults/index.d.ts +5 -3
- package/dist/vaults/types.d.ts +28 -16
- package/package.json +1 -1
package/dist/sparkvault.esm.js
CHANGED
|
@@ -66,14 +66,25 @@ class PopupBlockedError extends SparkVaultError {
|
|
|
66
66
|
*/
|
|
67
67
|
const API_URL = 'https://api.sparkvault.com';
|
|
68
68
|
const IDENTITY_URL = 'https://api.sparkvault.com/v1/apps/identity';
|
|
69
|
+
function normalizeApiBaseUrl(url) {
|
|
70
|
+
const trimmed = url.replace(/\/+$/, '');
|
|
71
|
+
return trimmed.endsWith('/v1') ? trimmed.slice(0, -3) : trimmed;
|
|
72
|
+
}
|
|
73
|
+
function normalizeBaseUrl(url) {
|
|
74
|
+
return url.replace(/\/+$/, '');
|
|
75
|
+
}
|
|
69
76
|
function resolveConfig(config) {
|
|
70
77
|
return {
|
|
71
78
|
accountId: config.accountId,
|
|
72
79
|
timeout: config.timeout ?? 30000,
|
|
73
|
-
apiBaseUrl: API_URL,
|
|
74
|
-
identityBaseUrl: IDENTITY_URL,
|
|
80
|
+
apiBaseUrl: normalizeApiBaseUrl(config.apiBaseUrl ?? API_URL),
|
|
81
|
+
identityBaseUrl: normalizeBaseUrl(config.identityBaseUrl ?? IDENTITY_URL),
|
|
82
|
+
accessToken: config.accessToken,
|
|
83
|
+
getAccessToken: config.getAccessToken,
|
|
84
|
+
apiKey: config.apiKey,
|
|
75
85
|
preloadConfig: config.preloadConfig !== false, // Default: true
|
|
76
86
|
backdropBlur: config.backdropBlur !== false, // Default: true
|
|
87
|
+
allowedDownloadHostPatterns: config.allowedDownloadHostPatterns,
|
|
77
88
|
};
|
|
78
89
|
}
|
|
79
90
|
function validateConfig(config) {
|
|
@@ -190,6 +201,31 @@ class HttpClient {
|
|
|
190
201
|
constructor(config) {
|
|
191
202
|
this.config = config;
|
|
192
203
|
}
|
|
204
|
+
async getDefaultHeaders(headers, hasJsonBody) {
|
|
205
|
+
const requestHeaders = {
|
|
206
|
+
Accept: 'application/json',
|
|
207
|
+
...headers,
|
|
208
|
+
};
|
|
209
|
+
if (hasJsonBody) {
|
|
210
|
+
requestHeaders['Content-Type'] = 'application/json';
|
|
211
|
+
}
|
|
212
|
+
if (!requestHeaders.Authorization) {
|
|
213
|
+
const token = await this.getAccessToken();
|
|
214
|
+
if (token) {
|
|
215
|
+
requestHeaders.Authorization = `Bearer ${token}`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (this.config.apiKey && !requestHeaders['X-API-Key']) {
|
|
219
|
+
requestHeaders['X-API-Key'] = this.config.apiKey;
|
|
220
|
+
}
|
|
221
|
+
return requestHeaders;
|
|
222
|
+
}
|
|
223
|
+
async getAccessToken() {
|
|
224
|
+
if (this.config.getAccessToken) {
|
|
225
|
+
return this.config.getAccessToken();
|
|
226
|
+
}
|
|
227
|
+
return this.config.accessToken ?? null;
|
|
228
|
+
}
|
|
193
229
|
async request(path, options = {}) {
|
|
194
230
|
const { retry } = options;
|
|
195
231
|
// Per CLAUDE.md §7: Wrap with retry logic if enabled
|
|
@@ -209,11 +245,7 @@ class HttpClient {
|
|
|
209
245
|
async executeRequest(path, options) {
|
|
210
246
|
const { method = 'GET', headers = {}, body, timeout = this.config.timeout, } = options;
|
|
211
247
|
const url = `${this.config.apiBaseUrl}${path}`;
|
|
212
|
-
const requestHeaders =
|
|
213
|
-
'Content-Type': 'application/json',
|
|
214
|
-
Accept: 'application/json',
|
|
215
|
-
...headers,
|
|
216
|
-
};
|
|
248
|
+
const requestHeaders = await this.getDefaultHeaders(headers, body !== undefined);
|
|
217
249
|
const controller = new AbortController();
|
|
218
250
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
219
251
|
try {
|
|
@@ -224,12 +256,12 @@ class HttpClient {
|
|
|
224
256
|
signal: controller.signal,
|
|
225
257
|
});
|
|
226
258
|
clearTimeout(timeoutId);
|
|
227
|
-
const
|
|
259
|
+
const parsed = await this.parseResponse(response);
|
|
228
260
|
if (!response.ok) {
|
|
229
|
-
throw this.createErrorFromResponse(response.status,
|
|
261
|
+
throw this.createErrorFromResponse(response.status, parsed);
|
|
230
262
|
}
|
|
231
263
|
return {
|
|
232
|
-
data,
|
|
264
|
+
data: this.unwrapApiResponse(parsed),
|
|
233
265
|
status: response.status,
|
|
234
266
|
headers: response.headers,
|
|
235
267
|
};
|
|
@@ -283,13 +315,23 @@ class HttpClient {
|
|
|
283
315
|
// Safely extract error fields with runtime type checking
|
|
284
316
|
const errorData = typeof data === 'object' && data !== null ? data : {};
|
|
285
317
|
const record = errorData;
|
|
286
|
-
const
|
|
318
|
+
const nestedError = typeof record.error === 'object' && record.error !== null
|
|
319
|
+
? record.error
|
|
320
|
+
: null;
|
|
321
|
+
const message = (typeof nestedError?.message === 'string' ? nestedError.message : null) ??
|
|
322
|
+
(typeof record.message === 'string' ? record.message : null) ??
|
|
287
323
|
(typeof record.error === 'string' ? record.error : null) ??
|
|
288
324
|
'Request failed';
|
|
289
|
-
const code = typeof
|
|
290
|
-
|
|
325
|
+
const code = (typeof nestedError?.code === 'string' ? nestedError.code : null) ??
|
|
326
|
+
(typeof record.code === 'string' ? record.code : null) ??
|
|
327
|
+
'api_error';
|
|
328
|
+
const nestedDetails = typeof nestedError?.details === 'object' && nestedError.details !== null
|
|
329
|
+
? nestedError.details
|
|
330
|
+
: undefined;
|
|
331
|
+
const topLevelDetails = typeof record.details === 'object' && record.details !== null
|
|
291
332
|
? record.details
|
|
292
333
|
: undefined;
|
|
334
|
+
const details = nestedDetails ?? topLevelDetails;
|
|
293
335
|
switch (status) {
|
|
294
336
|
case 400:
|
|
295
337
|
return new ValidationError(message, details);
|
|
@@ -301,6 +343,15 @@ class HttpClient {
|
|
|
301
343
|
return new SparkVaultError(message, code, status, details);
|
|
302
344
|
}
|
|
303
345
|
}
|
|
346
|
+
unwrapApiResponse(data) {
|
|
347
|
+
if (typeof data === 'object' && data !== null) {
|
|
348
|
+
const record = data;
|
|
349
|
+
if ('data' in record && ('meta' in record || 'error' in record)) {
|
|
350
|
+
return record.data;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return data;
|
|
354
|
+
}
|
|
304
355
|
get(path, options) {
|
|
305
356
|
return this.request(path, { ...options, method: 'GET' });
|
|
306
357
|
}
|
|
@@ -338,10 +389,7 @@ class HttpClient {
|
|
|
338
389
|
async executeRequestRaw(path, options) {
|
|
339
390
|
const { method = 'GET', headers = {}, body, timeout = this.config.timeout, } = options;
|
|
340
391
|
const url = `${this.config.apiBaseUrl}${path}`;
|
|
341
|
-
const requestHeaders =
|
|
342
|
-
'Content-Type': 'application/json',
|
|
343
|
-
...headers,
|
|
344
|
-
};
|
|
392
|
+
const requestHeaders = await this.getDefaultHeaders(headers, body !== undefined);
|
|
345
393
|
const controller = new AbortController();
|
|
346
394
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
347
395
|
try {
|
|
@@ -375,6 +423,63 @@ class HttpClient {
|
|
|
375
423
|
}
|
|
376
424
|
}
|
|
377
425
|
|
|
426
|
+
class HealthModule {
|
|
427
|
+
constructor(config) {
|
|
428
|
+
this.config = config;
|
|
429
|
+
}
|
|
430
|
+
async check(options = {}) {
|
|
431
|
+
const checkedAt = Math.floor(Date.now() / 1000);
|
|
432
|
+
const controller = new AbortController();
|
|
433
|
+
const timeoutId = setTimeout(() => controller.abort(), options.timeout ?? this.config.timeout);
|
|
434
|
+
try {
|
|
435
|
+
const response = await fetch(`${this.config.apiBaseUrl}/health`, {
|
|
436
|
+
method: 'GET',
|
|
437
|
+
headers: { Accept: 'application/json' },
|
|
438
|
+
signal: controller.signal,
|
|
439
|
+
});
|
|
440
|
+
return {
|
|
441
|
+
online: response.ok,
|
|
442
|
+
status: response.ok ? await this.readStatus(response) : 'unhealthy',
|
|
443
|
+
httpStatus: response.status,
|
|
444
|
+
checkedAt,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
return {
|
|
449
|
+
online: false,
|
|
450
|
+
status: 'unreachable',
|
|
451
|
+
checkedAt,
|
|
452
|
+
error: err instanceof Error ? err.message : String(err),
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
clearTimeout(timeoutId);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async isOnline(options = {}) {
|
|
460
|
+
const result = await this.check(options);
|
|
461
|
+
return result.online;
|
|
462
|
+
}
|
|
463
|
+
async readStatus(response) {
|
|
464
|
+
try {
|
|
465
|
+
const body = await response.json();
|
|
466
|
+
const data = typeof body === 'object' && body !== null && 'data' in body
|
|
467
|
+
? body.data
|
|
468
|
+
: body;
|
|
469
|
+
if (typeof data === 'object' && data !== null) {
|
|
470
|
+
const status = data.status;
|
|
471
|
+
if (typeof status === 'string' && status) {
|
|
472
|
+
return status;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
return response.ok ? 'healthy' : 'unhealthy';
|
|
478
|
+
}
|
|
479
|
+
return response.ok ? 'healthy' : 'unhealthy';
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
378
483
|
/**
|
|
379
484
|
* SparkVault API Base
|
|
380
485
|
*
|
|
@@ -9549,6 +9654,7 @@ class VaultUploadModule {
|
|
|
9549
9654
|
*/
|
|
9550
9655
|
class VaultsModule {
|
|
9551
9656
|
constructor(config, http) {
|
|
9657
|
+
this.config = config;
|
|
9552
9658
|
this.http = http;
|
|
9553
9659
|
this.uploadModule = new VaultUploadModule(config);
|
|
9554
9660
|
// Create callable that also has attach/close methods
|
|
@@ -9596,13 +9702,15 @@ class VaultsModule {
|
|
|
9596
9702
|
}
|
|
9597
9703
|
const cleanId = vaultId.startsWith('vlt_') ? vaultId : `vlt_${vaultId}`;
|
|
9598
9704
|
const response = await this.http.post(`/v1/vaults/${cleanId}/unseal`, { vmk });
|
|
9705
|
+
if (!response.data.vat) {
|
|
9706
|
+
throw new ValidationError('Unseal response did not include a vault access token');
|
|
9707
|
+
}
|
|
9599
9708
|
return {
|
|
9600
9709
|
id: response.data.vault_id,
|
|
9601
|
-
|
|
9602
|
-
|
|
9710
|
+
vatToken: response.data.vat,
|
|
9711
|
+
issuedAt: response.data.issued_at,
|
|
9603
9712
|
expiresAt: response.data.expires_at,
|
|
9604
|
-
|
|
9605
|
-
storageBytes: response.data.storage_bytes,
|
|
9713
|
+
ttlSeconds: response.data.ttl_seconds,
|
|
9606
9714
|
};
|
|
9607
9715
|
}
|
|
9608
9716
|
/**
|
|
@@ -9647,22 +9755,26 @@ class VaultsModule {
|
|
|
9647
9755
|
*/
|
|
9648
9756
|
async uploadIngot(vault, options) {
|
|
9649
9757
|
this.validateUploadOptions(options);
|
|
9650
|
-
const
|
|
9651
|
-
const
|
|
9758
|
+
const requestedName = options.name ?? getBlobName(options.file);
|
|
9759
|
+
const requestedContentType = options.contentType ?? options.file.type ?? 'application/octet-stream';
|
|
9652
9760
|
const createResponse = await this.http.post(`/v1/vaults/${vault.id}/ingots`, {
|
|
9653
|
-
name,
|
|
9654
|
-
content_type:
|
|
9761
|
+
name: requestedName,
|
|
9762
|
+
content_type: requestedContentType,
|
|
9655
9763
|
size_bytes: options.file.size,
|
|
9656
9764
|
}, {
|
|
9657
|
-
headers: {
|
|
9765
|
+
headers: { 'X-Vault-Access-Token': vault.vatToken },
|
|
9658
9766
|
});
|
|
9767
|
+
const created = createResponse.data;
|
|
9768
|
+
if (!created.forge_url) {
|
|
9769
|
+
throw new ValidationError('Upload endpoint did not return a Forge upload URL');
|
|
9770
|
+
}
|
|
9771
|
+
await uploadBlobWithTus(options.file, created.forge_url, created.name, created.content_type, options.onProgress);
|
|
9659
9772
|
return {
|
|
9660
|
-
id:
|
|
9661
|
-
name:
|
|
9662
|
-
contentType:
|
|
9663
|
-
size:
|
|
9664
|
-
|
|
9665
|
-
type: createResponse.data.ingot_type,
|
|
9773
|
+
id: created.ingot_id,
|
|
9774
|
+
name: created.name,
|
|
9775
|
+
contentType: created.content_type,
|
|
9776
|
+
size: created.size_bytes,
|
|
9777
|
+
uploadExpiresAt: created.expires_at,
|
|
9666
9778
|
};
|
|
9667
9779
|
}
|
|
9668
9780
|
/**
|
|
@@ -9674,10 +9786,14 @@ class VaultsModule {
|
|
|
9674
9786
|
*/
|
|
9675
9787
|
async downloadIngot(vault, ingotId) {
|
|
9676
9788
|
const cleanId = ingotId.startsWith('ing_') ? ingotId : `ing_${ingotId}`;
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
headers: { Authorization: `Bearer ${vault.vatToken}` },
|
|
9789
|
+
const response = await this.http.post(`/v1/vaults/${vault.id}/ingots/${cleanId}/download`, undefined, {
|
|
9790
|
+
headers: { 'X-Vault-Access-Token': vault.vatToken },
|
|
9680
9791
|
});
|
|
9792
|
+
if (!response.data.download_url) {
|
|
9793
|
+
throw new ValidationError('Download endpoint did not return a download URL');
|
|
9794
|
+
}
|
|
9795
|
+
this.validateDownloadUrl(response.data.download_url);
|
|
9796
|
+
return fetchBlobWithTimeout(response.data.download_url);
|
|
9681
9797
|
}
|
|
9682
9798
|
/**
|
|
9683
9799
|
* List all ingots in an unsealed vault.
|
|
@@ -9688,7 +9804,7 @@ class VaultsModule {
|
|
|
9688
9804
|
*/
|
|
9689
9805
|
async listIngots(vault) {
|
|
9690
9806
|
const response = await this.http.get(`/v1/vaults/${vault.id}/ingots`, {
|
|
9691
|
-
headers: {
|
|
9807
|
+
headers: { 'X-Vault-Access-Token': vault.vatToken },
|
|
9692
9808
|
});
|
|
9693
9809
|
return response.data.ingots.map((i) => ({
|
|
9694
9810
|
id: i.ingot_id,
|
|
@@ -9696,7 +9812,6 @@ class VaultsModule {
|
|
|
9696
9812
|
contentType: i.content_type,
|
|
9697
9813
|
size: i.size_bytes,
|
|
9698
9814
|
createdAt: i.created_at,
|
|
9699
|
-
type: i.ingot_type,
|
|
9700
9815
|
}));
|
|
9701
9816
|
}
|
|
9702
9817
|
/**
|
|
@@ -9708,7 +9823,7 @@ class VaultsModule {
|
|
|
9708
9823
|
async deleteIngot(vault, ingotId) {
|
|
9709
9824
|
const cleanId = ingotId.startsWith('ing_') ? ingotId : `ing_${ingotId}`;
|
|
9710
9825
|
await this.http.delete(`/v1/vaults/${vault.id}/ingots/${cleanId}`, {
|
|
9711
|
-
headers: {
|
|
9826
|
+
headers: { 'X-Vault-Access-Token': vault.vatToken },
|
|
9712
9827
|
});
|
|
9713
9828
|
}
|
|
9714
9829
|
validateCreateOptions(options) {
|
|
@@ -9727,6 +9842,117 @@ class VaultsModule {
|
|
|
9727
9842
|
throw new ValidationError('File cannot be empty');
|
|
9728
9843
|
}
|
|
9729
9844
|
}
|
|
9845
|
+
validateDownloadUrl(downloadUrl) {
|
|
9846
|
+
let parsed;
|
|
9847
|
+
try {
|
|
9848
|
+
parsed = new URL(downloadUrl);
|
|
9849
|
+
}
|
|
9850
|
+
catch {
|
|
9851
|
+
throw new ValidationError('Invalid download URL from server');
|
|
9852
|
+
}
|
|
9853
|
+
if (parsed.protocol !== 'https:') {
|
|
9854
|
+
throw new ValidationError('Download URL must use HTTPS');
|
|
9855
|
+
}
|
|
9856
|
+
const allowedHosts = this.config.allowedDownloadHostPatterns;
|
|
9857
|
+
if (!allowedHosts?.length) {
|
|
9858
|
+
return;
|
|
9859
|
+
}
|
|
9860
|
+
if (!allowedHosts.some(pattern => pattern.test(parsed.hostname))) {
|
|
9861
|
+
throw new ValidationError('Invalid download URL from server');
|
|
9862
|
+
}
|
|
9863
|
+
}
|
|
9864
|
+
}
|
|
9865
|
+
function getBlobName(file) {
|
|
9866
|
+
if (typeof File !== 'undefined' && file instanceof File && file.name) {
|
|
9867
|
+
return file.name;
|
|
9868
|
+
}
|
|
9869
|
+
return 'unnamed';
|
|
9870
|
+
}
|
|
9871
|
+
function base64Encode(value) {
|
|
9872
|
+
return btoa(unescape(encodeURIComponent(value)));
|
|
9873
|
+
}
|
|
9874
|
+
async function uploadBlobWithTus(file, forgeUrl, filename, contentType, onProgress) {
|
|
9875
|
+
if (typeof XMLHttpRequest === 'undefined') {
|
|
9876
|
+
throw new ValidationError('XMLHttpRequest is required for browser ingot uploads');
|
|
9877
|
+
}
|
|
9878
|
+
const tusVersion = '1.0.0';
|
|
9879
|
+
const defaultChunkSize = 50 * 1024 * 1024;
|
|
9880
|
+
const url = new URL(forgeUrl);
|
|
9881
|
+
const istk = url.searchParams.get('istk');
|
|
9882
|
+
const endpoint = `${url.origin}${url.pathname}`;
|
|
9883
|
+
if (!istk) {
|
|
9884
|
+
throw new ValidationError('Forge upload URL is missing ISTK');
|
|
9885
|
+
}
|
|
9886
|
+
const createResponse = await fetch(endpoint, {
|
|
9887
|
+
method: 'POST',
|
|
9888
|
+
headers: {
|
|
9889
|
+
'Tus-Resumable': tusVersion,
|
|
9890
|
+
'Upload-Length': String(file.size),
|
|
9891
|
+
'Upload-Metadata': `filename ${base64Encode(filename)},filetype ${base64Encode(contentType)}`,
|
|
9892
|
+
'X-ISTK': istk,
|
|
9893
|
+
},
|
|
9894
|
+
});
|
|
9895
|
+
if (!createResponse.ok) {
|
|
9896
|
+
const errorText = await createResponse.text();
|
|
9897
|
+
throw new Error(`Failed to create upload session: ${createResponse.status} ${errorText}`);
|
|
9898
|
+
}
|
|
9899
|
+
const location = createResponse.headers.get('Location');
|
|
9900
|
+
const chunkSizeHeader = createResponse.headers.get('X-Chunk-Size');
|
|
9901
|
+
const chunkSize = chunkSizeHeader ? parseInt(chunkSizeHeader, 10) : defaultChunkSize;
|
|
9902
|
+
if (!location) {
|
|
9903
|
+
throw new Error('Server did not return upload location');
|
|
9904
|
+
}
|
|
9905
|
+
const uploadUrl = location.startsWith('http') ? location : `${url.origin}${location}`;
|
|
9906
|
+
let offset = 0;
|
|
9907
|
+
while (offset < file.size) {
|
|
9908
|
+
const chunkStart = offset;
|
|
9909
|
+
const chunkEnd = Math.min(offset + chunkSize, file.size);
|
|
9910
|
+
const chunk = file.slice(chunkStart, chunkEnd);
|
|
9911
|
+
offset = await uploadChunkWithProgress(uploadUrl, chunk, chunkStart, file.size, tusVersion, istk, onProgress);
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9914
|
+
function uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, tusVersion, istk, onProgress) {
|
|
9915
|
+
return new Promise((resolve, reject) => {
|
|
9916
|
+
const xhr = new XMLHttpRequest();
|
|
9917
|
+
xhr.upload.onprogress = (event) => {
|
|
9918
|
+
if (event.lengthComputable) {
|
|
9919
|
+
onProgress?.(chunkStart + event.loaded, totalSize);
|
|
9920
|
+
}
|
|
9921
|
+
};
|
|
9922
|
+
xhr.onload = () => {
|
|
9923
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
9924
|
+
const newOffsetHeader = xhr.getResponseHeader('Upload-Offset');
|
|
9925
|
+
const newOffset = newOffsetHeader ? parseInt(newOffsetHeader, 10) : chunkStart + chunk.size;
|
|
9926
|
+
resolve(newOffset);
|
|
9927
|
+
}
|
|
9928
|
+
else {
|
|
9929
|
+
reject(new Error(`Chunk upload failed with status ${xhr.status}`));
|
|
9930
|
+
}
|
|
9931
|
+
};
|
|
9932
|
+
xhr.onerror = () => reject(new Error('Chunk upload failed'));
|
|
9933
|
+
xhr.ontimeout = () => reject(new Error('Chunk upload timed out'));
|
|
9934
|
+
xhr.onabort = () => reject(new Error('Upload cancelled'));
|
|
9935
|
+
xhr.open('PATCH', uploadUrl);
|
|
9936
|
+
xhr.setRequestHeader('Tus-Resumable', tusVersion);
|
|
9937
|
+
xhr.setRequestHeader('Upload-Offset', String(chunkStart));
|
|
9938
|
+
xhr.setRequestHeader('Content-Type', 'application/offset+octet-stream');
|
|
9939
|
+
xhr.setRequestHeader('X-ISTK', istk);
|
|
9940
|
+
xhr.send(chunk);
|
|
9941
|
+
});
|
|
9942
|
+
}
|
|
9943
|
+
async function fetchBlobWithTimeout(url, timeout = 300000) {
|
|
9944
|
+
const controller = new AbortController();
|
|
9945
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
9946
|
+
try {
|
|
9947
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
9948
|
+
if (!response.ok) {
|
|
9949
|
+
throw new Error(`Download failed with status ${response.status}`);
|
|
9950
|
+
}
|
|
9951
|
+
return response.blob();
|
|
9952
|
+
}
|
|
9953
|
+
finally {
|
|
9954
|
+
clearTimeout(timeoutId);
|
|
9955
|
+
}
|
|
9730
9956
|
}
|
|
9731
9957
|
|
|
9732
9958
|
/**
|
|
@@ -9913,6 +10139,7 @@ class SparkVault {
|
|
|
9913
10139
|
const http = new HttpClient(this.config);
|
|
9914
10140
|
this.identity = new IdentityModule(this.config);
|
|
9915
10141
|
this.vaults = new VaultsModule(this.config, http);
|
|
10142
|
+
this.health = new HealthModule(this.config);
|
|
9916
10143
|
}
|
|
9917
10144
|
/**
|
|
9918
10145
|
* Initialize the SparkVault SDK.
|