@sparkvault/sdk 1.11.0 → 1.11.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/identity/api.d.ts +11 -0
- package/dist/identity/renderer.d.ts +1 -0
- package/dist/sparkvault.cjs.js +126 -13
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +126 -13
- package/dist/sparkvault.esm.js.map +1 -1
- package/dist/sparkvault.js +1 -1
- package/dist/sparkvault.js.map +1 -1
- package/dist/vaults/upload/api.d.ts +15 -0
- package/dist/vaults/upload/renderer.d.ts +4 -1
- package/package.json +1 -1
package/dist/sparkvault.esm.js
CHANGED
|
@@ -582,27 +582,61 @@ class IdentityApi {
|
|
|
582
582
|
constructor(config, timeoutMs = DEFAULT_TIMEOUT_MS$1) {
|
|
583
583
|
/** Cached config promise - allows preloading and deduplication */
|
|
584
584
|
this.configCache = null;
|
|
585
|
+
/** Abort controller for cancelling all pending requests on close */
|
|
586
|
+
this.closeController = new AbortController();
|
|
585
587
|
this.config = config;
|
|
586
588
|
this.timeoutMs = timeoutMs;
|
|
587
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Abort all pending requests.
|
|
592
|
+
* Call this when the renderer is closed to prevent orphaned requests.
|
|
593
|
+
*/
|
|
594
|
+
abort() {
|
|
595
|
+
this.closeController.abort();
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Check if the API has been aborted.
|
|
599
|
+
*/
|
|
600
|
+
isAborted() {
|
|
601
|
+
return this.closeController.signal.aborted;
|
|
602
|
+
}
|
|
588
603
|
get baseUrl() {
|
|
589
604
|
return `${this.config.identityBaseUrl}/${this.config.accountId}`;
|
|
590
605
|
}
|
|
591
606
|
async request(method, endpoint, body) {
|
|
607
|
+
// Don't start new requests if already aborted (renderer closed)
|
|
608
|
+
if (this.closeController.signal.aborted) {
|
|
609
|
+
throw new IdentityApiError('Request cancelled', 'cancelled', 0);
|
|
610
|
+
}
|
|
592
611
|
const url = `${this.baseUrl}${endpoint}`;
|
|
593
612
|
const headers = {
|
|
594
613
|
'Content-Type': 'application/json',
|
|
595
614
|
'Accept': 'application/json',
|
|
596
615
|
};
|
|
597
616
|
// Create abort controller for request timeout
|
|
598
|
-
const
|
|
599
|
-
const timeoutId = setTimeout(() =>
|
|
617
|
+
const timeoutController = new AbortController();
|
|
618
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);
|
|
619
|
+
// Combine timeout and close signals - abort on either
|
|
620
|
+
// Use AbortSignal.any if available, otherwise fall back to manual linking
|
|
621
|
+
let combinedSignal;
|
|
622
|
+
if ('any' in AbortSignal) {
|
|
623
|
+
combinedSignal = AbortSignal.any([
|
|
624
|
+
timeoutController.signal,
|
|
625
|
+
this.closeController.signal,
|
|
626
|
+
]);
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
// Fallback for older browsers: link close signal to timeout controller
|
|
630
|
+
combinedSignal = timeoutController.signal;
|
|
631
|
+
const onClose = () => timeoutController.abort();
|
|
632
|
+
this.closeController.signal.addEventListener('abort', onClose);
|
|
633
|
+
}
|
|
600
634
|
try {
|
|
601
635
|
const response = await fetch(url, {
|
|
602
636
|
method,
|
|
603
637
|
headers,
|
|
604
638
|
body: body ? JSON.stringify(body) : undefined,
|
|
605
|
-
signal:
|
|
639
|
+
signal: combinedSignal,
|
|
606
640
|
});
|
|
607
641
|
const json = await response.json();
|
|
608
642
|
if (!response.ok) {
|
|
@@ -617,8 +651,12 @@ class IdentityApi {
|
|
|
617
651
|
return (json.data ?? json);
|
|
618
652
|
}
|
|
619
653
|
catch (error) {
|
|
620
|
-
// Convert AbortError to a more descriptive
|
|
654
|
+
// Convert AbortError to a more descriptive error
|
|
621
655
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
656
|
+
// Check which signal triggered the abort
|
|
657
|
+
if (this.closeController.signal.aborted) {
|
|
658
|
+
throw new IdentityApiError('Request cancelled', 'cancelled', 0);
|
|
659
|
+
}
|
|
622
660
|
throw new IdentityApiError('Request timed out', 'timeout', 0);
|
|
623
661
|
}
|
|
624
662
|
throw error;
|
|
@@ -2076,9 +2114,7 @@ function getStyles(options) {
|
|
|
2076
2114
|
|
|
2077
2115
|
.sv-footer {
|
|
2078
2116
|
padding: 10px 20px;
|
|
2079
|
-
border-top: 1px solid ${tokens.border};
|
|
2080
2117
|
text-align: center;
|
|
2081
|
-
background: ${tokens.bgSubtle};
|
|
2082
2118
|
flex-shrink: 0;
|
|
2083
2119
|
}
|
|
2084
2120
|
|
|
@@ -2542,6 +2578,8 @@ function getStyles(options) {
|
|
|
2542
2578
|
justify-content: center;
|
|
2543
2579
|
min-height: 200px;
|
|
2544
2580
|
flex: 1;
|
|
2581
|
+
padding-left: 32px;
|
|
2582
|
+
padding-right: 32px;
|
|
2545
2583
|
}
|
|
2546
2584
|
|
|
2547
2585
|
/* ========================================
|
|
@@ -4430,8 +4468,11 @@ class IdentityRenderer {
|
|
|
4430
4468
|
}
|
|
4431
4469
|
/**
|
|
4432
4470
|
* Close the modal and clean up.
|
|
4471
|
+
* Cancels all pending API requests to prevent orphaned operations.
|
|
4433
4472
|
*/
|
|
4434
4473
|
close() {
|
|
4474
|
+
// Cancel all pending API requests first
|
|
4475
|
+
this.api.abort();
|
|
4435
4476
|
if (this.focusTimeoutId !== null) {
|
|
4436
4477
|
clearTimeout(this.focusTimeoutId);
|
|
4437
4478
|
this.focusTimeoutId = null;
|
|
@@ -5617,9 +5658,30 @@ function isInitiateUploadResponse(data) {
|
|
|
5617
5658
|
}
|
|
5618
5659
|
class UploadApi {
|
|
5619
5660
|
constructor(config) {
|
|
5661
|
+
/** Abort controller for cancelling all pending requests on close */
|
|
5662
|
+
this.closeController = new AbortController();
|
|
5620
5663
|
this.config = config;
|
|
5621
5664
|
this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
5622
5665
|
}
|
|
5666
|
+
/**
|
|
5667
|
+
* Abort all pending requests.
|
|
5668
|
+
* Call this when the renderer is closed to prevent orphaned requests.
|
|
5669
|
+
*/
|
|
5670
|
+
abort() {
|
|
5671
|
+
this.closeController.abort();
|
|
5672
|
+
}
|
|
5673
|
+
/**
|
|
5674
|
+
* Check if the API has been aborted.
|
|
5675
|
+
*/
|
|
5676
|
+
isAborted() {
|
|
5677
|
+
return this.closeController.signal.aborted;
|
|
5678
|
+
}
|
|
5679
|
+
/**
|
|
5680
|
+
* Get the abort signal for external use (e.g., XHR uploads).
|
|
5681
|
+
*/
|
|
5682
|
+
getAbortSignal() {
|
|
5683
|
+
return this.closeController.signal;
|
|
5684
|
+
}
|
|
5623
5685
|
/**
|
|
5624
5686
|
* Get vault upload info (public endpoint).
|
|
5625
5687
|
*/
|
|
@@ -5672,8 +5734,26 @@ class UploadApi {
|
|
|
5672
5734
|
* Internal request method with timeout handling and error context.
|
|
5673
5735
|
*/
|
|
5674
5736
|
async request(url, options) {
|
|
5675
|
-
|
|
5676
|
-
|
|
5737
|
+
// Don't start new requests if already aborted (renderer closed)
|
|
5738
|
+
if (this.closeController.signal.aborted) {
|
|
5739
|
+
throw new UploadApiError('Request cancelled', 'cancelled', 0);
|
|
5740
|
+
}
|
|
5741
|
+
const timeoutController = new AbortController();
|
|
5742
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);
|
|
5743
|
+
// Combine timeout and close signals - abort on either
|
|
5744
|
+
let combinedSignal;
|
|
5745
|
+
if ('any' in AbortSignal) {
|
|
5746
|
+
combinedSignal = AbortSignal.any([
|
|
5747
|
+
timeoutController.signal,
|
|
5748
|
+
this.closeController.signal,
|
|
5749
|
+
]);
|
|
5750
|
+
}
|
|
5751
|
+
else {
|
|
5752
|
+
// Fallback for older browsers: link close signal to timeout controller
|
|
5753
|
+
combinedSignal = timeoutController.signal;
|
|
5754
|
+
const onClose = () => timeoutController.abort();
|
|
5755
|
+
this.closeController.signal.addEventListener('abort', onClose);
|
|
5756
|
+
}
|
|
5677
5757
|
try {
|
|
5678
5758
|
const response = await fetch(url, {
|
|
5679
5759
|
method: options.method,
|
|
@@ -5682,7 +5762,7 @@ class UploadApi {
|
|
|
5682
5762
|
'Accept': 'application/json',
|
|
5683
5763
|
},
|
|
5684
5764
|
body: options.body,
|
|
5685
|
-
signal:
|
|
5765
|
+
signal: combinedSignal,
|
|
5686
5766
|
});
|
|
5687
5767
|
const json = await response.json();
|
|
5688
5768
|
if (!response.ok) {
|
|
@@ -5708,8 +5788,12 @@ class UploadApi {
|
|
|
5708
5788
|
if (error instanceof SparkVaultError) {
|
|
5709
5789
|
throw error;
|
|
5710
5790
|
}
|
|
5711
|
-
// Convert AbortError to
|
|
5791
|
+
// Convert AbortError to appropriate error
|
|
5712
5792
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
5793
|
+
// Check which signal triggered the abort
|
|
5794
|
+
if (this.closeController.signal.aborted) {
|
|
5795
|
+
throw new UploadApiError('Request cancelled', 'cancelled', 0);
|
|
5796
|
+
}
|
|
5713
5797
|
throw new TimeoutError(`Request timed out after ${this.timeoutMs}ms`);
|
|
5714
5798
|
}
|
|
5715
5799
|
// Network errors with context
|
|
@@ -7571,6 +7655,8 @@ class UploadRenderer {
|
|
|
7571
7655
|
this.uploadModal = null;
|
|
7572
7656
|
this.isInlineMode = false;
|
|
7573
7657
|
this.hybridModalInitialized = false;
|
|
7658
|
+
// Track active XHR for cancellation on close
|
|
7659
|
+
this.activeXhr = null;
|
|
7574
7660
|
this.pasteHandler = null;
|
|
7575
7661
|
this.container = container;
|
|
7576
7662
|
this.api = api;
|
|
@@ -7603,8 +7689,16 @@ class UploadRenderer {
|
|
|
7603
7689
|
}
|
|
7604
7690
|
/**
|
|
7605
7691
|
* Close the upload flow.
|
|
7692
|
+
* Cancels all pending API requests and active uploads to prevent orphaned operations.
|
|
7606
7693
|
*/
|
|
7607
7694
|
close() {
|
|
7695
|
+
// Cancel all pending API requests first
|
|
7696
|
+
this.api.abort();
|
|
7697
|
+
// Abort any active XHR upload
|
|
7698
|
+
if (this.activeXhr) {
|
|
7699
|
+
this.activeXhr.abort();
|
|
7700
|
+
this.activeXhr = null;
|
|
7701
|
+
}
|
|
7608
7702
|
this.cleanupFileInput();
|
|
7609
7703
|
this.cleanupPasteHandler();
|
|
7610
7704
|
// Clean up modal if open (hybrid mode)
|
|
@@ -8203,11 +8297,19 @@ class UploadRenderer {
|
|
|
8203
8297
|
}
|
|
8204
8298
|
}
|
|
8205
8299
|
/**
|
|
8206
|
-
* Upload a single chunk using XMLHttpRequest for progress tracking
|
|
8300
|
+
* Upload a single chunk using XMLHttpRequest for progress tracking.
|
|
8301
|
+
* Tracks the XHR so it can be cancelled if the renderer is closed.
|
|
8207
8302
|
*/
|
|
8208
8303
|
uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, tusVersion, file, ingotId, requestId) {
|
|
8209
8304
|
return new Promise((resolve, reject) => {
|
|
8305
|
+
// Check if already aborted before starting
|
|
8306
|
+
if (this.api.isAborted()) {
|
|
8307
|
+
reject(new Error('Upload cancelled'));
|
|
8308
|
+
return;
|
|
8309
|
+
}
|
|
8210
8310
|
const xhr = new XMLHttpRequest();
|
|
8311
|
+
// Track this XHR so it can be cancelled on close
|
|
8312
|
+
this.activeXhr = xhr;
|
|
8211
8313
|
// Track progress within this chunk
|
|
8212
8314
|
xhr.upload.onprogress = (e) => {
|
|
8213
8315
|
if (e.lengthComputable) {
|
|
@@ -8231,6 +8333,7 @@ class UploadRenderer {
|
|
|
8231
8333
|
}
|
|
8232
8334
|
};
|
|
8233
8335
|
xhr.onload = () => {
|
|
8336
|
+
this.activeXhr = null;
|
|
8234
8337
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
8235
8338
|
// Get new offset from server response
|
|
8236
8339
|
const newOffsetHeader = xhr.getResponseHeader('Upload-Offset');
|
|
@@ -8241,8 +8344,18 @@ class UploadRenderer {
|
|
|
8241
8344
|
reject(new Error(`Chunk upload failed with status ${xhr.status}`));
|
|
8242
8345
|
}
|
|
8243
8346
|
};
|
|
8244
|
-
xhr.onerror = () =>
|
|
8245
|
-
|
|
8347
|
+
xhr.onerror = () => {
|
|
8348
|
+
this.activeXhr = null;
|
|
8349
|
+
reject(new Error('Chunk upload failed'));
|
|
8350
|
+
};
|
|
8351
|
+
xhr.ontimeout = () => {
|
|
8352
|
+
this.activeXhr = null;
|
|
8353
|
+
reject(new Error('Chunk upload timed out'));
|
|
8354
|
+
};
|
|
8355
|
+
xhr.onabort = () => {
|
|
8356
|
+
this.activeXhr = null;
|
|
8357
|
+
reject(new Error('Upload cancelled'));
|
|
8358
|
+
};
|
|
8246
8359
|
xhr.open('PATCH', uploadUrl);
|
|
8247
8360
|
xhr.setRequestHeader('Tus-Resumable', tusVersion);
|
|
8248
8361
|
xhr.setRequestHeader('Upload-Offset', String(chunkStart));
|