@sparkvault/sdk 1.9.1 → 1.10.0
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/sparkvault.cjs.js +164 -56
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +164 -56
- package/dist/sparkvault.esm.js.map +1 -1
- package/dist/sparkvault.js +1 -1
- package/dist/sparkvault.js.map +1 -1
- package/dist/utils/dom.d.ts +6 -0
- package/dist/vaults/upload/renderer.d.ts +1 -0
- package/package.json +1 -1
package/dist/sparkvault.esm.js
CHANGED
|
@@ -5098,8 +5098,8 @@ class IdentityRenderer {
|
|
|
5098
5098
|
* Implements the Container interface for use with IdentityRenderer.
|
|
5099
5099
|
*/
|
|
5100
5100
|
const DEFAULT_OPTIONS$1 = {
|
|
5101
|
-
showHeader:
|
|
5102
|
-
showCloseButton:
|
|
5101
|
+
showHeader: false,
|
|
5102
|
+
showCloseButton: false,
|
|
5103
5103
|
showFooter: true,
|
|
5104
5104
|
};
|
|
5105
5105
|
class InlineContainer {
|
|
@@ -5290,6 +5290,36 @@ function onDomReady(callback) {
|
|
|
5290
5290
|
callback();
|
|
5291
5291
|
}
|
|
5292
5292
|
}
|
|
5293
|
+
/**
|
|
5294
|
+
* Execute a callback when document.body is available.
|
|
5295
|
+
* Handles edge cases where body might not exist even after DOMContentLoaded
|
|
5296
|
+
* (e.g., scripts with defer/async loading during parsing).
|
|
5297
|
+
*/
|
|
5298
|
+
function onBodyReady(callback) {
|
|
5299
|
+
if (document.body) {
|
|
5300
|
+
callback();
|
|
5301
|
+
}
|
|
5302
|
+
else {
|
|
5303
|
+
// Body not yet available - wait for DOMContentLoaded
|
|
5304
|
+
// or poll briefly as a fallback for edge cases
|
|
5305
|
+
const checkBody = () => {
|
|
5306
|
+
if (document.body) {
|
|
5307
|
+
callback();
|
|
5308
|
+
}
|
|
5309
|
+
else {
|
|
5310
|
+
// Rare edge case: poll for body availability
|
|
5311
|
+
window.requestAnimationFrame(checkBody);
|
|
5312
|
+
}
|
|
5313
|
+
};
|
|
5314
|
+
if (document.readyState === 'loading') {
|
|
5315
|
+
document.addEventListener('DOMContentLoaded', checkBody, { once: true });
|
|
5316
|
+
}
|
|
5317
|
+
else {
|
|
5318
|
+
// Document ready but no body yet - poll
|
|
5319
|
+
window.requestAnimationFrame(checkBody);
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5293
5323
|
|
|
5294
5324
|
/* global Element */
|
|
5295
5325
|
/**
|
|
@@ -5451,7 +5481,11 @@ class IdentityModule {
|
|
|
5451
5481
|
});
|
|
5452
5482
|
});
|
|
5453
5483
|
});
|
|
5454
|
-
|
|
5484
|
+
// Observe document.body for dynamic elements
|
|
5485
|
+
// Use onBodyReady to handle edge cases where body isn't available yet
|
|
5486
|
+
onBodyReady(() => {
|
|
5487
|
+
observer?.observe(document.body, { childList: true, subtree: true });
|
|
5488
|
+
});
|
|
5455
5489
|
};
|
|
5456
5490
|
// Defer attachment until DOM is ready
|
|
5457
5491
|
onDomReady(attachToElements);
|
|
@@ -5752,18 +5786,32 @@ function getUploadStyles(options) {
|
|
|
5752
5786
|
======================================== */
|
|
5753
5787
|
|
|
5754
5788
|
.svu-overlay {
|
|
5755
|
-
|
|
5756
|
-
|
|
5789
|
+
/* Force full-screen overlay regardless of page styles */
|
|
5790
|
+
position: fixed !important;
|
|
5791
|
+
top: 0 !important;
|
|
5792
|
+
left: 0 !important;
|
|
5793
|
+
right: 0 !important;
|
|
5794
|
+
bottom: 0 !important;
|
|
5795
|
+
width: 100vw !important;
|
|
5796
|
+
height: 100vh !important;
|
|
5797
|
+
max-width: none !important;
|
|
5798
|
+
max-height: none !important;
|
|
5799
|
+
margin: 0 !important;
|
|
5757
5800
|
background: rgba(0, 0, 0, 0.5);
|
|
5758
|
-
display: flex;
|
|
5801
|
+
display: flex !important;
|
|
5759
5802
|
align-items: center;
|
|
5760
5803
|
justify-content: center;
|
|
5761
|
-
z-index: 999999;
|
|
5804
|
+
z-index: 999999 !important;
|
|
5762
5805
|
padding: 16px;
|
|
5763
5806
|
box-sizing: border-box;
|
|
5764
5807
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
5765
5808
|
-webkit-font-smoothing: antialiased;
|
|
5766
5809
|
-moz-osx-font-smoothing: grayscale;
|
|
5810
|
+
/* Break out of any containing block that could affect fixed positioning */
|
|
5811
|
+
transform: none !important;
|
|
5812
|
+
filter: none !important;
|
|
5813
|
+
contain: none !important;
|
|
5814
|
+
isolation: isolate;
|
|
5767
5815
|
}
|
|
5768
5816
|
|
|
5769
5817
|
.svu-overlay.svu-blur {
|
|
@@ -6972,6 +7020,9 @@ class UploadModalContainer {
|
|
|
6972
7020
|
setDarkMode(enabled) {
|
|
6973
7021
|
if (!this.elements)
|
|
6974
7022
|
return;
|
|
7023
|
+
// Skip if already in the requested mode
|
|
7024
|
+
if (this.isDarkMode === enabled)
|
|
7025
|
+
return;
|
|
6975
7026
|
this.isDarkMode = enabled;
|
|
6976
7027
|
if (enabled) {
|
|
6977
7028
|
this.elements.modal.classList.add('svu-dark');
|
|
@@ -6979,7 +7030,7 @@ class UploadModalContainer {
|
|
|
6979
7030
|
else {
|
|
6980
7031
|
this.elements.modal.classList.remove('svu-dark');
|
|
6981
7032
|
}
|
|
6982
|
-
// Re-create header with correct logo
|
|
7033
|
+
// Re-create header with correct logo (only when mode actually changes)
|
|
6983
7034
|
const newHeader = this.createHeader(this.branding);
|
|
6984
7035
|
if (this.headerElement?.parentNode) {
|
|
6985
7036
|
this.headerElement.parentNode.replaceChild(newHeader, this.headerElement);
|
|
@@ -7085,7 +7136,7 @@ class UploadModalContainer {
|
|
|
7085
7136
|
<div class="svu-security-badges">
|
|
7086
7137
|
<span class="svu-tls-badge">TLS 1.3 In Transit</span>
|
|
7087
7138
|
<span class="svu-tls-badge">Encrypted At Rest</span>
|
|
7088
|
-
<span class="svu-tls-badge">Zero
|
|
7139
|
+
<span class="svu-tls-badge">Zero Trust</span>
|
|
7089
7140
|
</div>
|
|
7090
7141
|
`;
|
|
7091
7142
|
sidebar.appendChild(security);
|
|
@@ -7151,8 +7202,8 @@ class UploadModalContainer {
|
|
|
7151
7202
|
* Implements the UploadContainer interface for use with UploadRenderer.
|
|
7152
7203
|
*/
|
|
7153
7204
|
const DEFAULT_OPTIONS = {
|
|
7154
|
-
showHeader:
|
|
7155
|
-
showCloseButton:
|
|
7205
|
+
showHeader: false,
|
|
7206
|
+
showCloseButton: false,
|
|
7156
7207
|
showFooter: true,
|
|
7157
7208
|
};
|
|
7158
7209
|
class UploadInlineContainer {
|
|
@@ -7246,6 +7297,9 @@ class UploadInlineContainer {
|
|
|
7246
7297
|
setDarkMode(enabled) {
|
|
7247
7298
|
if (!this.container)
|
|
7248
7299
|
return;
|
|
7300
|
+
// Skip if already in the requested mode
|
|
7301
|
+
if (this.isDarkMode === enabled)
|
|
7302
|
+
return;
|
|
7249
7303
|
this.isDarkMode = enabled;
|
|
7250
7304
|
if (enabled) {
|
|
7251
7305
|
this.container.classList.add('svu-dark');
|
|
@@ -7253,7 +7307,7 @@ class UploadInlineContainer {
|
|
|
7253
7307
|
else {
|
|
7254
7308
|
this.container.classList.remove('svu-dark');
|
|
7255
7309
|
}
|
|
7256
|
-
// Re-create header with correct logo
|
|
7310
|
+
// Re-create header with correct logo (only when mode actually changes)
|
|
7257
7311
|
if (this.containerOptions.showHeader && this.header) {
|
|
7258
7312
|
const newHeader = this.createHeader(this.branding);
|
|
7259
7313
|
this.container.replaceChild(newHeader, this.header);
|
|
@@ -7441,7 +7495,7 @@ class UploadInlineContainer {
|
|
|
7441
7495
|
<div class="svu-security-badges">
|
|
7442
7496
|
<span class="svu-tls-badge">TLS 1.3 In Transit</span>
|
|
7443
7497
|
<span class="svu-tls-badge">Encrypted At Rest</span>
|
|
7444
|
-
<span class="svu-tls-badge">Zero
|
|
7498
|
+
<span class="svu-tls-badge">Zero Trust</span>
|
|
7445
7499
|
</div>
|
|
7446
7500
|
`;
|
|
7447
7501
|
sidebar.appendChild(security);
|
|
@@ -7508,6 +7562,7 @@ class UploadRenderer {
|
|
|
7508
7562
|
// Hybrid mode: modal overlay for upload/ceremony in inline mode
|
|
7509
7563
|
this.uploadModal = null;
|
|
7510
7564
|
this.isInlineMode = false;
|
|
7565
|
+
this.hybridModalInitialized = false;
|
|
7511
7566
|
this.pasteHandler = null;
|
|
7512
7567
|
this.container = container;
|
|
7513
7568
|
this.api = api;
|
|
@@ -7548,6 +7603,7 @@ class UploadRenderer {
|
|
|
7548
7603
|
if (this.uploadModal) {
|
|
7549
7604
|
this.uploadModal.destroy();
|
|
7550
7605
|
this.uploadModal = null;
|
|
7606
|
+
this.hybridModalInitialized = false;
|
|
7551
7607
|
}
|
|
7552
7608
|
this.container.destroy();
|
|
7553
7609
|
}
|
|
@@ -7567,10 +7623,16 @@ class UploadRenderer {
|
|
|
7567
7623
|
this.renderInModal();
|
|
7568
7624
|
return;
|
|
7569
7625
|
}
|
|
7570
|
-
// Close modal if we're leaving upload/ceremony states
|
|
7626
|
+
// Close modal if we're leaving upload/ceremony states (hybrid mode complete)
|
|
7571
7627
|
if (this.uploadModal) {
|
|
7572
7628
|
this.uploadModal.destroy();
|
|
7573
7629
|
this.uploadModal = null;
|
|
7630
|
+
this.hybridModalInitialized = false;
|
|
7631
|
+
// Restore inline container visibility
|
|
7632
|
+
const inlineBody = this.container.getBody();
|
|
7633
|
+
if (inlineBody?.parentElement) {
|
|
7634
|
+
inlineBody.parentElement.style.display = '';
|
|
7635
|
+
}
|
|
7574
7636
|
}
|
|
7575
7637
|
const body = this.container.getBody();
|
|
7576
7638
|
if (!body)
|
|
@@ -7609,7 +7671,7 @@ class UploadRenderer {
|
|
|
7609
7671
|
* This provides the full polished experience during upload.
|
|
7610
7672
|
*/
|
|
7611
7673
|
renderInModal() {
|
|
7612
|
-
// Create modal
|
|
7674
|
+
// Create and initialize modal only once
|
|
7613
7675
|
if (!this.uploadModal) {
|
|
7614
7676
|
this.uploadModal = new UploadModalContainer();
|
|
7615
7677
|
this.uploadModal.createLoading({ backdropBlur: this.options.backdropBlur ?? true }, () => { } // No close callback - user can't cancel during upload
|
|
@@ -7617,15 +7679,25 @@ class UploadRenderer {
|
|
|
7617
7679
|
if (this.config) {
|
|
7618
7680
|
this.uploadModal.updateBranding(this.config.branding);
|
|
7619
7681
|
}
|
|
7682
|
+
this.hybridModalInitialized = false;
|
|
7620
7683
|
}
|
|
7621
7684
|
const body = this.uploadModal.getBody();
|
|
7622
7685
|
if (!body)
|
|
7623
7686
|
return;
|
|
7687
|
+
// Initialize modal state only once (not on every progress update)
|
|
7688
|
+
if (!this.hybridModalInitialized) {
|
|
7689
|
+
// Hide inline container during hybrid mode to avoid CSS conflicts
|
|
7690
|
+
const inlineBody = this.container.getBody();
|
|
7691
|
+
if (inlineBody?.parentElement) {
|
|
7692
|
+
inlineBody.parentElement.style.display = 'none';
|
|
7693
|
+
}
|
|
7694
|
+
// Set dark mode and show sidebar once
|
|
7695
|
+
this.uploadModal.setDarkMode(true);
|
|
7696
|
+
this.uploadModal.toggleSidebar(true);
|
|
7697
|
+
this.hybridModalInitialized = true;
|
|
7698
|
+
}
|
|
7624
7699
|
// Clear and render current state
|
|
7625
7700
|
body.innerHTML = '';
|
|
7626
|
-
// Set dark mode and show sidebar
|
|
7627
|
-
this.uploadModal.setDarkMode(true);
|
|
7628
|
-
this.uploadModal.toggleSidebar(true);
|
|
7629
7701
|
if (this.viewState.view === 'uploading') {
|
|
7630
7702
|
body.appendChild(this.renderUploading(this.viewState));
|
|
7631
7703
|
}
|
|
@@ -8074,45 +8146,81 @@ class UploadRenderer {
|
|
|
8074
8146
|
}
|
|
8075
8147
|
}
|
|
8076
8148
|
async uploadWithTus(file, forgeUrl, ingotId, requestId) {
|
|
8077
|
-
//
|
|
8078
|
-
//
|
|
8079
|
-
const
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
percentage: progress,
|
|
8098
|
-
phase: 'uploading',
|
|
8099
|
-
});
|
|
8100
|
-
}
|
|
8101
|
-
};
|
|
8102
|
-
xhr.onload = () => {
|
|
8103
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
8104
|
-
resolve();
|
|
8105
|
-
}
|
|
8106
|
-
else {
|
|
8107
|
-
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
8108
|
-
}
|
|
8109
|
-
};
|
|
8110
|
-
xhr.onerror = () => reject(new Error('Upload failed'));
|
|
8111
|
-
xhr.ontimeout = () => reject(new Error('Upload timed out'));
|
|
8112
|
-
xhr.open('POST', forgeUrl);
|
|
8113
|
-
xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
|
|
8114
|
-
xhr.send(file);
|
|
8149
|
+
// tus resumable upload protocol v1.0.0
|
|
8150
|
+
// Uploads file in chunks to avoid Cloudflare's 100MB request limit
|
|
8151
|
+
const TUS_VERSION = '1.0.0';
|
|
8152
|
+
const DEFAULT_CHUNK_SIZE = 50 * 1024 * 1024; // 50MB default, server may override
|
|
8153
|
+
// Extract base URL and ISTK from forge URL
|
|
8154
|
+
const url = new URL(forgeUrl);
|
|
8155
|
+
const istk = url.searchParams.get('istk');
|
|
8156
|
+
const baseUrl = `${url.origin}${url.pathname}`;
|
|
8157
|
+
if (!istk) {
|
|
8158
|
+
throw new Error('Missing ISTK in forge URL');
|
|
8159
|
+
}
|
|
8160
|
+
// Step 1: Create tus upload session
|
|
8161
|
+
const createResponse = await fetch(baseUrl, {
|
|
8162
|
+
method: 'POST',
|
|
8163
|
+
headers: {
|
|
8164
|
+
'Tus-Resumable': TUS_VERSION,
|
|
8165
|
+
'Upload-Length': String(file.size),
|
|
8166
|
+
'X-ISTK': istk,
|
|
8167
|
+
'Content-Type': 'application/offset+octet-stream',
|
|
8168
|
+
},
|
|
8115
8169
|
});
|
|
8170
|
+
if (!createResponse.ok) {
|
|
8171
|
+
const errorText = await createResponse.text();
|
|
8172
|
+
throw new Error(`Failed to create upload session: ${createResponse.status} ${errorText}`);
|
|
8173
|
+
}
|
|
8174
|
+
// Get upload location and chunk size from response
|
|
8175
|
+
const location = createResponse.headers.get('Location');
|
|
8176
|
+
const chunkSizeHeader = createResponse.headers.get('X-Chunk-Size');
|
|
8177
|
+
const chunkSize = chunkSizeHeader ? parseInt(chunkSizeHeader, 10) : DEFAULT_CHUNK_SIZE;
|
|
8178
|
+
if (!location) {
|
|
8179
|
+
throw new Error('Server did not return upload location');
|
|
8180
|
+
}
|
|
8181
|
+
// Build full upload URL
|
|
8182
|
+
const uploadUrl = location.startsWith('http') ? location : `${url.origin}${location}`;
|
|
8183
|
+
// Step 2: Upload file in chunks
|
|
8184
|
+
let offset = 0;
|
|
8185
|
+
const totalSize = file.size;
|
|
8186
|
+
while (offset < totalSize) {
|
|
8187
|
+
const end = Math.min(offset + chunkSize, totalSize);
|
|
8188
|
+
const chunk = file.slice(offset, end);
|
|
8189
|
+
// Upload chunk
|
|
8190
|
+
const patchResponse = await fetch(uploadUrl, {
|
|
8191
|
+
method: 'PATCH',
|
|
8192
|
+
headers: {
|
|
8193
|
+
'Tus-Resumable': TUS_VERSION,
|
|
8194
|
+
'Upload-Offset': String(offset),
|
|
8195
|
+
'Content-Type': 'application/offset+octet-stream',
|
|
8196
|
+
},
|
|
8197
|
+
body: chunk,
|
|
8198
|
+
});
|
|
8199
|
+
if (!patchResponse.ok) {
|
|
8200
|
+
const errorText = await patchResponse.text();
|
|
8201
|
+
throw new Error(`Chunk upload failed: ${patchResponse.status} ${errorText}`);
|
|
8202
|
+
}
|
|
8203
|
+
// Update offset from server response
|
|
8204
|
+
const newOffsetHeader = patchResponse.headers.get('Upload-Offset');
|
|
8205
|
+
const newOffset = newOffsetHeader ? parseInt(newOffsetHeader, 10) : end;
|
|
8206
|
+
offset = newOffset;
|
|
8207
|
+
// Update progress
|
|
8208
|
+
const progress = Math.round((offset / totalSize) * 100);
|
|
8209
|
+
this.setState({
|
|
8210
|
+
view: 'uploading',
|
|
8211
|
+
file,
|
|
8212
|
+
ingotId,
|
|
8213
|
+
requestId,
|
|
8214
|
+
progress,
|
|
8215
|
+
bytesUploaded: offset,
|
|
8216
|
+
});
|
|
8217
|
+
this.callbacks.onProgress?.({
|
|
8218
|
+
bytesUploaded: offset,
|
|
8219
|
+
bytesTotal: totalSize,
|
|
8220
|
+
percentage: progress,
|
|
8221
|
+
phase: 'uploading',
|
|
8222
|
+
});
|
|
8223
|
+
}
|
|
8116
8224
|
}
|
|
8117
8225
|
async runCeremony(file, ingotId, requestId) {
|
|
8118
8226
|
for (let i = 0; i < CEREMONY_STEPS.length; i++) {
|