@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/identity/api.d.ts
CHANGED
|
@@ -11,7 +11,18 @@ export declare class IdentityApi {
|
|
|
11
11
|
private readonly timeoutMs;
|
|
12
12
|
/** Cached config promise - allows preloading and deduplication */
|
|
13
13
|
private configCache;
|
|
14
|
+
/** Abort controller for cancelling all pending requests on close */
|
|
15
|
+
private closeController;
|
|
14
16
|
constructor(config: ResolvedConfig, timeoutMs?: number);
|
|
17
|
+
/**
|
|
18
|
+
* Abort all pending requests.
|
|
19
|
+
* Call this when the renderer is closed to prevent orphaned requests.
|
|
20
|
+
*/
|
|
21
|
+
abort(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Check if the API has been aborted.
|
|
24
|
+
*/
|
|
25
|
+
isAborted(): boolean;
|
|
15
26
|
private get baseUrl();
|
|
16
27
|
private request;
|
|
17
28
|
/**
|
package/dist/sparkvault.cjs.js
CHANGED
|
@@ -586,27 +586,61 @@ class IdentityApi {
|
|
|
586
586
|
constructor(config, timeoutMs = DEFAULT_TIMEOUT_MS$1) {
|
|
587
587
|
/** Cached config promise - allows preloading and deduplication */
|
|
588
588
|
this.configCache = null;
|
|
589
|
+
/** Abort controller for cancelling all pending requests on close */
|
|
590
|
+
this.closeController = new AbortController();
|
|
589
591
|
this.config = config;
|
|
590
592
|
this.timeoutMs = timeoutMs;
|
|
591
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Abort all pending requests.
|
|
596
|
+
* Call this when the renderer is closed to prevent orphaned requests.
|
|
597
|
+
*/
|
|
598
|
+
abort() {
|
|
599
|
+
this.closeController.abort();
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Check if the API has been aborted.
|
|
603
|
+
*/
|
|
604
|
+
isAborted() {
|
|
605
|
+
return this.closeController.signal.aborted;
|
|
606
|
+
}
|
|
592
607
|
get baseUrl() {
|
|
593
608
|
return `${this.config.identityBaseUrl}/${this.config.accountId}`;
|
|
594
609
|
}
|
|
595
610
|
async request(method, endpoint, body) {
|
|
611
|
+
// Don't start new requests if already aborted (renderer closed)
|
|
612
|
+
if (this.closeController.signal.aborted) {
|
|
613
|
+
throw new IdentityApiError('Request cancelled', 'cancelled', 0);
|
|
614
|
+
}
|
|
596
615
|
const url = `${this.baseUrl}${endpoint}`;
|
|
597
616
|
const headers = {
|
|
598
617
|
'Content-Type': 'application/json',
|
|
599
618
|
'Accept': 'application/json',
|
|
600
619
|
};
|
|
601
620
|
// Create abort controller for request timeout
|
|
602
|
-
const
|
|
603
|
-
const timeoutId = setTimeout(() =>
|
|
621
|
+
const timeoutController = new AbortController();
|
|
622
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);
|
|
623
|
+
// Combine timeout and close signals - abort on either
|
|
624
|
+
// Use AbortSignal.any if available, otherwise fall back to manual linking
|
|
625
|
+
let combinedSignal;
|
|
626
|
+
if ('any' in AbortSignal) {
|
|
627
|
+
combinedSignal = AbortSignal.any([
|
|
628
|
+
timeoutController.signal,
|
|
629
|
+
this.closeController.signal,
|
|
630
|
+
]);
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
// Fallback for older browsers: link close signal to timeout controller
|
|
634
|
+
combinedSignal = timeoutController.signal;
|
|
635
|
+
const onClose = () => timeoutController.abort();
|
|
636
|
+
this.closeController.signal.addEventListener('abort', onClose);
|
|
637
|
+
}
|
|
604
638
|
try {
|
|
605
639
|
const response = await fetch(url, {
|
|
606
640
|
method,
|
|
607
641
|
headers,
|
|
608
642
|
body: body ? JSON.stringify(body) : undefined,
|
|
609
|
-
signal:
|
|
643
|
+
signal: combinedSignal,
|
|
610
644
|
});
|
|
611
645
|
const json = await response.json();
|
|
612
646
|
if (!response.ok) {
|
|
@@ -621,8 +655,12 @@ class IdentityApi {
|
|
|
621
655
|
return (json.data ?? json);
|
|
622
656
|
}
|
|
623
657
|
catch (error) {
|
|
624
|
-
// Convert AbortError to a more descriptive
|
|
658
|
+
// Convert AbortError to a more descriptive error
|
|
625
659
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
660
|
+
// Check which signal triggered the abort
|
|
661
|
+
if (this.closeController.signal.aborted) {
|
|
662
|
+
throw new IdentityApiError('Request cancelled', 'cancelled', 0);
|
|
663
|
+
}
|
|
626
664
|
throw new IdentityApiError('Request timed out', 'timeout', 0);
|
|
627
665
|
}
|
|
628
666
|
throw error;
|
|
@@ -2080,9 +2118,7 @@ function getStyles(options) {
|
|
|
2080
2118
|
|
|
2081
2119
|
.sv-footer {
|
|
2082
2120
|
padding: 10px 20px;
|
|
2083
|
-
border-top: 1px solid ${tokens.border};
|
|
2084
2121
|
text-align: center;
|
|
2085
|
-
background: ${tokens.bgSubtle};
|
|
2086
2122
|
flex-shrink: 0;
|
|
2087
2123
|
}
|
|
2088
2124
|
|
|
@@ -2546,6 +2582,8 @@ function getStyles(options) {
|
|
|
2546
2582
|
justify-content: center;
|
|
2547
2583
|
min-height: 200px;
|
|
2548
2584
|
flex: 1;
|
|
2585
|
+
padding-left: 32px;
|
|
2586
|
+
padding-right: 32px;
|
|
2549
2587
|
}
|
|
2550
2588
|
|
|
2551
2589
|
/* ========================================
|
|
@@ -4434,8 +4472,11 @@ class IdentityRenderer {
|
|
|
4434
4472
|
}
|
|
4435
4473
|
/**
|
|
4436
4474
|
* Close the modal and clean up.
|
|
4475
|
+
* Cancels all pending API requests to prevent orphaned operations.
|
|
4437
4476
|
*/
|
|
4438
4477
|
close() {
|
|
4478
|
+
// Cancel all pending API requests first
|
|
4479
|
+
this.api.abort();
|
|
4439
4480
|
if (this.focusTimeoutId !== null) {
|
|
4440
4481
|
clearTimeout(this.focusTimeoutId);
|
|
4441
4482
|
this.focusTimeoutId = null;
|
|
@@ -5621,9 +5662,30 @@ function isInitiateUploadResponse(data) {
|
|
|
5621
5662
|
}
|
|
5622
5663
|
class UploadApi {
|
|
5623
5664
|
constructor(config) {
|
|
5665
|
+
/** Abort controller for cancelling all pending requests on close */
|
|
5666
|
+
this.closeController = new AbortController();
|
|
5624
5667
|
this.config = config;
|
|
5625
5668
|
this.timeoutMs = config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
5626
5669
|
}
|
|
5670
|
+
/**
|
|
5671
|
+
* Abort all pending requests.
|
|
5672
|
+
* Call this when the renderer is closed to prevent orphaned requests.
|
|
5673
|
+
*/
|
|
5674
|
+
abort() {
|
|
5675
|
+
this.closeController.abort();
|
|
5676
|
+
}
|
|
5677
|
+
/**
|
|
5678
|
+
* Check if the API has been aborted.
|
|
5679
|
+
*/
|
|
5680
|
+
isAborted() {
|
|
5681
|
+
return this.closeController.signal.aborted;
|
|
5682
|
+
}
|
|
5683
|
+
/**
|
|
5684
|
+
* Get the abort signal for external use (e.g., XHR uploads).
|
|
5685
|
+
*/
|
|
5686
|
+
getAbortSignal() {
|
|
5687
|
+
return this.closeController.signal;
|
|
5688
|
+
}
|
|
5627
5689
|
/**
|
|
5628
5690
|
* Get vault upload info (public endpoint).
|
|
5629
5691
|
*/
|
|
@@ -5676,8 +5738,26 @@ class UploadApi {
|
|
|
5676
5738
|
* Internal request method with timeout handling and error context.
|
|
5677
5739
|
*/
|
|
5678
5740
|
async request(url, options) {
|
|
5679
|
-
|
|
5680
|
-
|
|
5741
|
+
// Don't start new requests if already aborted (renderer closed)
|
|
5742
|
+
if (this.closeController.signal.aborted) {
|
|
5743
|
+
throw new UploadApiError('Request cancelled', 'cancelled', 0);
|
|
5744
|
+
}
|
|
5745
|
+
const timeoutController = new AbortController();
|
|
5746
|
+
const timeoutId = setTimeout(() => timeoutController.abort(), this.timeoutMs);
|
|
5747
|
+
// Combine timeout and close signals - abort on either
|
|
5748
|
+
let combinedSignal;
|
|
5749
|
+
if ('any' in AbortSignal) {
|
|
5750
|
+
combinedSignal = AbortSignal.any([
|
|
5751
|
+
timeoutController.signal,
|
|
5752
|
+
this.closeController.signal,
|
|
5753
|
+
]);
|
|
5754
|
+
}
|
|
5755
|
+
else {
|
|
5756
|
+
// Fallback for older browsers: link close signal to timeout controller
|
|
5757
|
+
combinedSignal = timeoutController.signal;
|
|
5758
|
+
const onClose = () => timeoutController.abort();
|
|
5759
|
+
this.closeController.signal.addEventListener('abort', onClose);
|
|
5760
|
+
}
|
|
5681
5761
|
try {
|
|
5682
5762
|
const response = await fetch(url, {
|
|
5683
5763
|
method: options.method,
|
|
@@ -5686,7 +5766,7 @@ class UploadApi {
|
|
|
5686
5766
|
'Accept': 'application/json',
|
|
5687
5767
|
},
|
|
5688
5768
|
body: options.body,
|
|
5689
|
-
signal:
|
|
5769
|
+
signal: combinedSignal,
|
|
5690
5770
|
});
|
|
5691
5771
|
const json = await response.json();
|
|
5692
5772
|
if (!response.ok) {
|
|
@@ -5712,8 +5792,12 @@ class UploadApi {
|
|
|
5712
5792
|
if (error instanceof SparkVaultError) {
|
|
5713
5793
|
throw error;
|
|
5714
5794
|
}
|
|
5715
|
-
// Convert AbortError to
|
|
5795
|
+
// Convert AbortError to appropriate error
|
|
5716
5796
|
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
5797
|
+
// Check which signal triggered the abort
|
|
5798
|
+
if (this.closeController.signal.aborted) {
|
|
5799
|
+
throw new UploadApiError('Request cancelled', 'cancelled', 0);
|
|
5800
|
+
}
|
|
5717
5801
|
throw new TimeoutError(`Request timed out after ${this.timeoutMs}ms`);
|
|
5718
5802
|
}
|
|
5719
5803
|
// Network errors with context
|
|
@@ -7575,6 +7659,8 @@ class UploadRenderer {
|
|
|
7575
7659
|
this.uploadModal = null;
|
|
7576
7660
|
this.isInlineMode = false;
|
|
7577
7661
|
this.hybridModalInitialized = false;
|
|
7662
|
+
// Track active XHR for cancellation on close
|
|
7663
|
+
this.activeXhr = null;
|
|
7578
7664
|
this.pasteHandler = null;
|
|
7579
7665
|
this.container = container;
|
|
7580
7666
|
this.api = api;
|
|
@@ -7607,8 +7693,16 @@ class UploadRenderer {
|
|
|
7607
7693
|
}
|
|
7608
7694
|
/**
|
|
7609
7695
|
* Close the upload flow.
|
|
7696
|
+
* Cancels all pending API requests and active uploads to prevent orphaned operations.
|
|
7610
7697
|
*/
|
|
7611
7698
|
close() {
|
|
7699
|
+
// Cancel all pending API requests first
|
|
7700
|
+
this.api.abort();
|
|
7701
|
+
// Abort any active XHR upload
|
|
7702
|
+
if (this.activeXhr) {
|
|
7703
|
+
this.activeXhr.abort();
|
|
7704
|
+
this.activeXhr = null;
|
|
7705
|
+
}
|
|
7612
7706
|
this.cleanupFileInput();
|
|
7613
7707
|
this.cleanupPasteHandler();
|
|
7614
7708
|
// Clean up modal if open (hybrid mode)
|
|
@@ -8207,11 +8301,19 @@ class UploadRenderer {
|
|
|
8207
8301
|
}
|
|
8208
8302
|
}
|
|
8209
8303
|
/**
|
|
8210
|
-
* Upload a single chunk using XMLHttpRequest for progress tracking
|
|
8304
|
+
* Upload a single chunk using XMLHttpRequest for progress tracking.
|
|
8305
|
+
* Tracks the XHR so it can be cancelled if the renderer is closed.
|
|
8211
8306
|
*/
|
|
8212
8307
|
uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, tusVersion, file, ingotId, requestId) {
|
|
8213
8308
|
return new Promise((resolve, reject) => {
|
|
8309
|
+
// Check if already aborted before starting
|
|
8310
|
+
if (this.api.isAborted()) {
|
|
8311
|
+
reject(new Error('Upload cancelled'));
|
|
8312
|
+
return;
|
|
8313
|
+
}
|
|
8214
8314
|
const xhr = new XMLHttpRequest();
|
|
8315
|
+
// Track this XHR so it can be cancelled on close
|
|
8316
|
+
this.activeXhr = xhr;
|
|
8215
8317
|
// Track progress within this chunk
|
|
8216
8318
|
xhr.upload.onprogress = (e) => {
|
|
8217
8319
|
if (e.lengthComputable) {
|
|
@@ -8235,6 +8337,7 @@ class UploadRenderer {
|
|
|
8235
8337
|
}
|
|
8236
8338
|
};
|
|
8237
8339
|
xhr.onload = () => {
|
|
8340
|
+
this.activeXhr = null;
|
|
8238
8341
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
8239
8342
|
// Get new offset from server response
|
|
8240
8343
|
const newOffsetHeader = xhr.getResponseHeader('Upload-Offset');
|
|
@@ -8245,8 +8348,18 @@ class UploadRenderer {
|
|
|
8245
8348
|
reject(new Error(`Chunk upload failed with status ${xhr.status}`));
|
|
8246
8349
|
}
|
|
8247
8350
|
};
|
|
8248
|
-
xhr.onerror = () =>
|
|
8249
|
-
|
|
8351
|
+
xhr.onerror = () => {
|
|
8352
|
+
this.activeXhr = null;
|
|
8353
|
+
reject(new Error('Chunk upload failed'));
|
|
8354
|
+
};
|
|
8355
|
+
xhr.ontimeout = () => {
|
|
8356
|
+
this.activeXhr = null;
|
|
8357
|
+
reject(new Error('Chunk upload timed out'));
|
|
8358
|
+
};
|
|
8359
|
+
xhr.onabort = () => {
|
|
8360
|
+
this.activeXhr = null;
|
|
8361
|
+
reject(new Error('Upload cancelled'));
|
|
8362
|
+
};
|
|
8250
8363
|
xhr.open('PATCH', uploadUrl);
|
|
8251
8364
|
xhr.setRequestHeader('Tus-Resumable', tusVersion);
|
|
8252
8365
|
xhr.setRequestHeader('Upload-Offset', String(chunkStart));
|