@sparkvault/sdk 1.11.1 → 1.11.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/identity/api.d.ts +11 -0
- package/dist/identity/renderer.d.ts +1 -0
- package/dist/sparkvault.cjs.js +127 -11
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +127 -11
- 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;
|
|
@@ -1648,6 +1686,7 @@ function getStyles(options) {
|
|
|
1648
1686
|
padding: 14px 20px;
|
|
1649
1687
|
border-bottom: 1px solid ${tokens.border};
|
|
1650
1688
|
background: ${tokens.bg};
|
|
1689
|
+
border-radius: 16px 16px 0 0;
|
|
1651
1690
|
flex-shrink: 0;
|
|
1652
1691
|
}
|
|
1653
1692
|
|
|
@@ -2540,6 +2579,8 @@ function getStyles(options) {
|
|
|
2540
2579
|
justify-content: center;
|
|
2541
2580
|
min-height: 200px;
|
|
2542
2581
|
flex: 1;
|
|
2582
|
+
padding-left: 32px;
|
|
2583
|
+
padding-right: 32px;
|
|
2543
2584
|
}
|
|
2544
2585
|
|
|
2545
2586
|
/* ========================================
|
|
@@ -4428,8 +4469,11 @@ class IdentityRenderer {
|
|
|
4428
4469
|
}
|
|
4429
4470
|
/**
|
|
4430
4471
|
* Close the modal and clean up.
|
|
4472
|
+
* Cancels all pending API requests to prevent orphaned operations.
|
|
4431
4473
|
*/
|
|
4432
4474
|
close() {
|
|
4475
|
+
// Cancel all pending API requests first
|
|
4476
|
+
this.api.abort();
|
|
4433
4477
|
if (this.focusTimeoutId !== null) {
|
|
4434
4478
|
clearTimeout(this.focusTimeoutId);
|
|
4435
4479
|
this.focusTimeoutId = null;
|
|
@@ -5615,9 +5659,30 @@ function isInitiateUploadResponse(data) {
|
|
|
5615
5659
|
}
|
|
5616
5660
|
class UploadApi {
|
|
5617
5661
|
constructor(config) {
|
|
5662
|
+
/** Abort controller for cancelling all pending requests on close */
|
|
5663
|
+
this.closeController = new AbortController();
|
|
5618
5664
|
this.config = config;
|
|
5619
5665
|
this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
5620
5666
|
}
|
|
5667
|
+
/**
|
|
5668
|
+
* Abort all pending requests.
|
|
5669
|
+
* Call this when the renderer is closed to prevent orphaned requests.
|
|
5670
|
+
*/
|
|
5671
|
+
abort() {
|
|
5672
|
+
this.closeController.abort();
|
|
5673
|
+
}
|
|
5674
|
+
/**
|
|
5675
|
+
* Check if the API has been aborted.
|
|
5676
|
+
*/
|
|
5677
|
+
isAborted() {
|
|
5678
|
+
return this.closeController.signal.aborted;
|
|
5679
|
+
}
|
|
5680
|
+
/**
|
|
5681
|
+
* Get the abort signal for external use (e.g., XHR uploads).
|
|
5682
|
+
*/
|
|
5683
|
+
getAbortSignal() {
|
|
5684
|
+
return this.closeController.signal;
|
|
5685
|
+
}
|
|
5621
5686
|
/**
|
|
5622
5687
|
* Get vault upload info (public endpoint).
|
|
5623
5688
|
*/
|
|
@@ -5670,8 +5735,26 @@ class UploadApi {
|
|
|
5670
5735
|
* Internal request method with timeout handling and error context.
|
|
5671
5736
|
*/
|
|
5672
5737
|
async request(url, options) {
|
|
5673
|
-
|
|
5674
|
-
|
|
5738
|
+
// Don't start new requests if already aborted (renderer closed)
|
|
5739
|
+
if (this.closeController.signal.aborted) {
|
|
5740
|
+
throw new UploadApiError('Request cancelled', 'cancelled', 0);
|
|
5741
|
+
}
|
|
5742
|
+
const timeoutController = new AbortController();
|
|
5743
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);
|
|
5744
|
+
// Combine timeout and close signals - abort on either
|
|
5745
|
+
let combinedSignal;
|
|
5746
|
+
if ('any' in AbortSignal) {
|
|
5747
|
+
combinedSignal = AbortSignal.any([
|
|
5748
|
+
timeoutController.signal,
|
|
5749
|
+
this.closeController.signal,
|
|
5750
|
+
]);
|
|
5751
|
+
}
|
|
5752
|
+
else {
|
|
5753
|
+
// Fallback for older browsers: link close signal to timeout controller
|
|
5754
|
+
combinedSignal = timeoutController.signal;
|
|
5755
|
+
const onClose = () => timeoutController.abort();
|
|
5756
|
+
this.closeController.signal.addEventListener('abort', onClose);
|
|
5757
|
+
}
|
|
5675
5758
|
try {
|
|
5676
5759
|
const response = await fetch(url, {
|
|
5677
5760
|
method: options.method,
|
|
@@ -5680,7 +5763,7 @@ class UploadApi {
|
|
|
5680
5763
|
'Accept': 'application/json',
|
|
5681
5764
|
},
|
|
5682
5765
|
body: options.body,
|
|
5683
|
-
signal:
|
|
5766
|
+
signal: combinedSignal,
|
|
5684
5767
|
});
|
|
5685
5768
|
const json = await response.json();
|
|
5686
5769
|
if (!response.ok) {
|
|
@@ -5706,8 +5789,12 @@ class UploadApi {
|
|
|
5706
5789
|
if (error instanceof SparkVaultError) {
|
|
5707
5790
|
throw error;
|
|
5708
5791
|
}
|
|
5709
|
-
// Convert AbortError to
|
|
5792
|
+
// Convert AbortError to appropriate error
|
|
5710
5793
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
5794
|
+
// Check which signal triggered the abort
|
|
5795
|
+
if (this.closeController.signal.aborted) {
|
|
5796
|
+
throw new UploadApiError('Request cancelled', 'cancelled', 0);
|
|
5797
|
+
}
|
|
5711
5798
|
throw new TimeoutError(`Request timed out after ${this.timeoutMs}ms`);
|
|
5712
5799
|
}
|
|
5713
5800
|
// Network errors with context
|
|
@@ -7569,6 +7656,8 @@ class UploadRenderer {
|
|
|
7569
7656
|
this.uploadModal = null;
|
|
7570
7657
|
this.isInlineMode = false;
|
|
7571
7658
|
this.hybridModalInitialized = false;
|
|
7659
|
+
// Track active XHR for cancellation on close
|
|
7660
|
+
this.activeXhr = null;
|
|
7572
7661
|
this.pasteHandler = null;
|
|
7573
7662
|
this.container = container;
|
|
7574
7663
|
this.api = api;
|
|
@@ -7601,8 +7690,16 @@ class UploadRenderer {
|
|
|
7601
7690
|
}
|
|
7602
7691
|
/**
|
|
7603
7692
|
* Close the upload flow.
|
|
7693
|
+
* Cancels all pending API requests and active uploads to prevent orphaned operations.
|
|
7604
7694
|
*/
|
|
7605
7695
|
close() {
|
|
7696
|
+
// Cancel all pending API requests first
|
|
7697
|
+
this.api.abort();
|
|
7698
|
+
// Abort any active XHR upload
|
|
7699
|
+
if (this.activeXhr) {
|
|
7700
|
+
this.activeXhr.abort();
|
|
7701
|
+
this.activeXhr = null;
|
|
7702
|
+
}
|
|
7606
7703
|
this.cleanupFileInput();
|
|
7607
7704
|
this.cleanupPasteHandler();
|
|
7608
7705
|
// Clean up modal if open (hybrid mode)
|
|
@@ -8201,11 +8298,19 @@ class UploadRenderer {
|
|
|
8201
8298
|
}
|
|
8202
8299
|
}
|
|
8203
8300
|
/**
|
|
8204
|
-
* Upload a single chunk using XMLHttpRequest for progress tracking
|
|
8301
|
+
* Upload a single chunk using XMLHttpRequest for progress tracking.
|
|
8302
|
+
* Tracks the XHR so it can be cancelled if the renderer is closed.
|
|
8205
8303
|
*/
|
|
8206
8304
|
uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, tusVersion, file, ingotId, requestId) {
|
|
8207
8305
|
return new Promise((resolve, reject) => {
|
|
8306
|
+
// Check if already aborted before starting
|
|
8307
|
+
if (this.api.isAborted()) {
|
|
8308
|
+
reject(new Error('Upload cancelled'));
|
|
8309
|
+
return;
|
|
8310
|
+
}
|
|
8208
8311
|
const xhr = new XMLHttpRequest();
|
|
8312
|
+
// Track this XHR so it can be cancelled on close
|
|
8313
|
+
this.activeXhr = xhr;
|
|
8209
8314
|
// Track progress within this chunk
|
|
8210
8315
|
xhr.upload.onprogress = (e) => {
|
|
8211
8316
|
if (e.lengthComputable) {
|
|
@@ -8229,6 +8334,7 @@ class UploadRenderer {
|
|
|
8229
8334
|
}
|
|
8230
8335
|
};
|
|
8231
8336
|
xhr.onload = () => {
|
|
8337
|
+
this.activeXhr = null;
|
|
8232
8338
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
8233
8339
|
// Get new offset from server response
|
|
8234
8340
|
const newOffsetHeader = xhr.getResponseHeader('Upload-Offset');
|
|
@@ -8239,8 +8345,18 @@ class UploadRenderer {
|
|
|
8239
8345
|
reject(new Error(`Chunk upload failed with status ${xhr.status}`));
|
|
8240
8346
|
}
|
|
8241
8347
|
};
|
|
8242
|
-
xhr.onerror = () =>
|
|
8243
|
-
|
|
8348
|
+
xhr.onerror = () => {
|
|
8349
|
+
this.activeXhr = null;
|
|
8350
|
+
reject(new Error('Chunk upload failed'));
|
|
8351
|
+
};
|
|
8352
|
+
xhr.ontimeout = () => {
|
|
8353
|
+
this.activeXhr = null;
|
|
8354
|
+
reject(new Error('Chunk upload timed out'));
|
|
8355
|
+
};
|
|
8356
|
+
xhr.onabort = () => {
|
|
8357
|
+
this.activeXhr = null;
|
|
8358
|
+
reject(new Error('Upload cancelled'));
|
|
8359
|
+
};
|
|
8244
8360
|
xhr.open('PATCH', uploadUrl);
|
|
8245
8361
|
xhr.setRequestHeader('Tus-Resumable', tusVersion);
|
|
8246
8362
|
xhr.setRequestHeader('Upload-Offset', String(chunkStart));
|