@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.cjs.js
CHANGED
|
@@ -5102,8 +5102,8 @@ class IdentityRenderer {
|
|
|
5102
5102
|
* Implements the Container interface for use with IdentityRenderer.
|
|
5103
5103
|
*/
|
|
5104
5104
|
const DEFAULT_OPTIONS$1 = {
|
|
5105
|
-
showHeader:
|
|
5106
|
-
showCloseButton:
|
|
5105
|
+
showHeader: false,
|
|
5106
|
+
showCloseButton: false,
|
|
5107
5107
|
showFooter: true,
|
|
5108
5108
|
};
|
|
5109
5109
|
class InlineContainer {
|
|
@@ -5294,6 +5294,36 @@ function onDomReady(callback) {
|
|
|
5294
5294
|
callback();
|
|
5295
5295
|
}
|
|
5296
5296
|
}
|
|
5297
|
+
/**
|
|
5298
|
+
* Execute a callback when document.body is available.
|
|
5299
|
+
* Handles edge cases where body might not exist even after DOMContentLoaded
|
|
5300
|
+
* (e.g., scripts with defer/async loading during parsing).
|
|
5301
|
+
*/
|
|
5302
|
+
function onBodyReady(callback) {
|
|
5303
|
+
if (document.body) {
|
|
5304
|
+
callback();
|
|
5305
|
+
}
|
|
5306
|
+
else {
|
|
5307
|
+
// Body not yet available - wait for DOMContentLoaded
|
|
5308
|
+
// or poll briefly as a fallback for edge cases
|
|
5309
|
+
const checkBody = () => {
|
|
5310
|
+
if (document.body) {
|
|
5311
|
+
callback();
|
|
5312
|
+
}
|
|
5313
|
+
else {
|
|
5314
|
+
// Rare edge case: poll for body availability
|
|
5315
|
+
window.requestAnimationFrame(checkBody);
|
|
5316
|
+
}
|
|
5317
|
+
};
|
|
5318
|
+
if (document.readyState === 'loading') {
|
|
5319
|
+
document.addEventListener('DOMContentLoaded', checkBody, { once: true });
|
|
5320
|
+
}
|
|
5321
|
+
else {
|
|
5322
|
+
// Document ready but no body yet - poll
|
|
5323
|
+
window.requestAnimationFrame(checkBody);
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5297
5327
|
|
|
5298
5328
|
/* global Element */
|
|
5299
5329
|
/**
|
|
@@ -5455,7 +5485,11 @@ class IdentityModule {
|
|
|
5455
5485
|
});
|
|
5456
5486
|
});
|
|
5457
5487
|
});
|
|
5458
|
-
|
|
5488
|
+
// Observe document.body for dynamic elements
|
|
5489
|
+
// Use onBodyReady to handle edge cases where body isn't available yet
|
|
5490
|
+
onBodyReady(() => {
|
|
5491
|
+
observer?.observe(document.body, { childList: true, subtree: true });
|
|
5492
|
+
});
|
|
5459
5493
|
};
|
|
5460
5494
|
// Defer attachment until DOM is ready
|
|
5461
5495
|
onDomReady(attachToElements);
|
|
@@ -5756,18 +5790,32 @@ function getUploadStyles(options) {
|
|
|
5756
5790
|
======================================== */
|
|
5757
5791
|
|
|
5758
5792
|
.svu-overlay {
|
|
5759
|
-
|
|
5760
|
-
|
|
5793
|
+
/* Force full-screen overlay regardless of page styles */
|
|
5794
|
+
position: fixed !important;
|
|
5795
|
+
top: 0 !important;
|
|
5796
|
+
left: 0 !important;
|
|
5797
|
+
right: 0 !important;
|
|
5798
|
+
bottom: 0 !important;
|
|
5799
|
+
width: 100vw !important;
|
|
5800
|
+
height: 100vh !important;
|
|
5801
|
+
max-width: none !important;
|
|
5802
|
+
max-height: none !important;
|
|
5803
|
+
margin: 0 !important;
|
|
5761
5804
|
background: rgba(0, 0, 0, 0.5);
|
|
5762
|
-
display: flex;
|
|
5805
|
+
display: flex !important;
|
|
5763
5806
|
align-items: center;
|
|
5764
5807
|
justify-content: center;
|
|
5765
|
-
z-index: 999999;
|
|
5808
|
+
z-index: 999999 !important;
|
|
5766
5809
|
padding: 16px;
|
|
5767
5810
|
box-sizing: border-box;
|
|
5768
5811
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
5769
5812
|
-webkit-font-smoothing: antialiased;
|
|
5770
5813
|
-moz-osx-font-smoothing: grayscale;
|
|
5814
|
+
/* Break out of any containing block that could affect fixed positioning */
|
|
5815
|
+
transform: none !important;
|
|
5816
|
+
filter: none !important;
|
|
5817
|
+
contain: none !important;
|
|
5818
|
+
isolation: isolate;
|
|
5771
5819
|
}
|
|
5772
5820
|
|
|
5773
5821
|
.svu-overlay.svu-blur {
|
|
@@ -6976,6 +7024,9 @@ class UploadModalContainer {
|
|
|
6976
7024
|
setDarkMode(enabled) {
|
|
6977
7025
|
if (!this.elements)
|
|
6978
7026
|
return;
|
|
7027
|
+
// Skip if already in the requested mode
|
|
7028
|
+
if (this.isDarkMode === enabled)
|
|
7029
|
+
return;
|
|
6979
7030
|
this.isDarkMode = enabled;
|
|
6980
7031
|
if (enabled) {
|
|
6981
7032
|
this.elements.modal.classList.add('svu-dark');
|
|
@@ -6983,7 +7034,7 @@ class UploadModalContainer {
|
|
|
6983
7034
|
else {
|
|
6984
7035
|
this.elements.modal.classList.remove('svu-dark');
|
|
6985
7036
|
}
|
|
6986
|
-
// Re-create header with correct logo
|
|
7037
|
+
// Re-create header with correct logo (only when mode actually changes)
|
|
6987
7038
|
const newHeader = this.createHeader(this.branding);
|
|
6988
7039
|
if (this.headerElement?.parentNode) {
|
|
6989
7040
|
this.headerElement.parentNode.replaceChild(newHeader, this.headerElement);
|
|
@@ -7089,7 +7140,7 @@ class UploadModalContainer {
|
|
|
7089
7140
|
<div class="svu-security-badges">
|
|
7090
7141
|
<span class="svu-tls-badge">TLS 1.3 In Transit</span>
|
|
7091
7142
|
<span class="svu-tls-badge">Encrypted At Rest</span>
|
|
7092
|
-
<span class="svu-tls-badge">Zero
|
|
7143
|
+
<span class="svu-tls-badge">Zero Trust</span>
|
|
7093
7144
|
</div>
|
|
7094
7145
|
`;
|
|
7095
7146
|
sidebar.appendChild(security);
|
|
@@ -7155,8 +7206,8 @@ class UploadModalContainer {
|
|
|
7155
7206
|
* Implements the UploadContainer interface for use with UploadRenderer.
|
|
7156
7207
|
*/
|
|
7157
7208
|
const DEFAULT_OPTIONS = {
|
|
7158
|
-
showHeader:
|
|
7159
|
-
showCloseButton:
|
|
7209
|
+
showHeader: false,
|
|
7210
|
+
showCloseButton: false,
|
|
7160
7211
|
showFooter: true,
|
|
7161
7212
|
};
|
|
7162
7213
|
class UploadInlineContainer {
|
|
@@ -7250,6 +7301,9 @@ class UploadInlineContainer {
|
|
|
7250
7301
|
setDarkMode(enabled) {
|
|
7251
7302
|
if (!this.container)
|
|
7252
7303
|
return;
|
|
7304
|
+
// Skip if already in the requested mode
|
|
7305
|
+
if (this.isDarkMode === enabled)
|
|
7306
|
+
return;
|
|
7253
7307
|
this.isDarkMode = enabled;
|
|
7254
7308
|
if (enabled) {
|
|
7255
7309
|
this.container.classList.add('svu-dark');
|
|
@@ -7257,7 +7311,7 @@ class UploadInlineContainer {
|
|
|
7257
7311
|
else {
|
|
7258
7312
|
this.container.classList.remove('svu-dark');
|
|
7259
7313
|
}
|
|
7260
|
-
// Re-create header with correct logo
|
|
7314
|
+
// Re-create header with correct logo (only when mode actually changes)
|
|
7261
7315
|
if (this.containerOptions.showHeader && this.header) {
|
|
7262
7316
|
const newHeader = this.createHeader(this.branding);
|
|
7263
7317
|
this.container.replaceChild(newHeader, this.header);
|
|
@@ -7445,7 +7499,7 @@ class UploadInlineContainer {
|
|
|
7445
7499
|
<div class="svu-security-badges">
|
|
7446
7500
|
<span class="svu-tls-badge">TLS 1.3 In Transit</span>
|
|
7447
7501
|
<span class="svu-tls-badge">Encrypted At Rest</span>
|
|
7448
|
-
<span class="svu-tls-badge">Zero
|
|
7502
|
+
<span class="svu-tls-badge">Zero Trust</span>
|
|
7449
7503
|
</div>
|
|
7450
7504
|
`;
|
|
7451
7505
|
sidebar.appendChild(security);
|
|
@@ -7512,6 +7566,7 @@ class UploadRenderer {
|
|
|
7512
7566
|
// Hybrid mode: modal overlay for upload/ceremony in inline mode
|
|
7513
7567
|
this.uploadModal = null;
|
|
7514
7568
|
this.isInlineMode = false;
|
|
7569
|
+
this.hybridModalInitialized = false;
|
|
7515
7570
|
this.pasteHandler = null;
|
|
7516
7571
|
this.container = container;
|
|
7517
7572
|
this.api = api;
|
|
@@ -7552,6 +7607,7 @@ class UploadRenderer {
|
|
|
7552
7607
|
if (this.uploadModal) {
|
|
7553
7608
|
this.uploadModal.destroy();
|
|
7554
7609
|
this.uploadModal = null;
|
|
7610
|
+
this.hybridModalInitialized = false;
|
|
7555
7611
|
}
|
|
7556
7612
|
this.container.destroy();
|
|
7557
7613
|
}
|
|
@@ -7571,10 +7627,16 @@ class UploadRenderer {
|
|
|
7571
7627
|
this.renderInModal();
|
|
7572
7628
|
return;
|
|
7573
7629
|
}
|
|
7574
|
-
// Close modal if we're leaving upload/ceremony states
|
|
7630
|
+
// Close modal if we're leaving upload/ceremony states (hybrid mode complete)
|
|
7575
7631
|
if (this.uploadModal) {
|
|
7576
7632
|
this.uploadModal.destroy();
|
|
7577
7633
|
this.uploadModal = null;
|
|
7634
|
+
this.hybridModalInitialized = false;
|
|
7635
|
+
// Restore inline container visibility
|
|
7636
|
+
const inlineBody = this.container.getBody();
|
|
7637
|
+
if (inlineBody?.parentElement) {
|
|
7638
|
+
inlineBody.parentElement.style.display = '';
|
|
7639
|
+
}
|
|
7578
7640
|
}
|
|
7579
7641
|
const body = this.container.getBody();
|
|
7580
7642
|
if (!body)
|
|
@@ -7613,7 +7675,7 @@ class UploadRenderer {
|
|
|
7613
7675
|
* This provides the full polished experience during upload.
|
|
7614
7676
|
*/
|
|
7615
7677
|
renderInModal() {
|
|
7616
|
-
// Create modal
|
|
7678
|
+
// Create and initialize modal only once
|
|
7617
7679
|
if (!this.uploadModal) {
|
|
7618
7680
|
this.uploadModal = new UploadModalContainer();
|
|
7619
7681
|
this.uploadModal.createLoading({ backdropBlur: this.options.backdropBlur ?? true }, () => { } // No close callback - user can't cancel during upload
|
|
@@ -7621,15 +7683,25 @@ class UploadRenderer {
|
|
|
7621
7683
|
if (this.config) {
|
|
7622
7684
|
this.uploadModal.updateBranding(this.config.branding);
|
|
7623
7685
|
}
|
|
7686
|
+
this.hybridModalInitialized = false;
|
|
7624
7687
|
}
|
|
7625
7688
|
const body = this.uploadModal.getBody();
|
|
7626
7689
|
if (!body)
|
|
7627
7690
|
return;
|
|
7691
|
+
// Initialize modal state only once (not on every progress update)
|
|
7692
|
+
if (!this.hybridModalInitialized) {
|
|
7693
|
+
// Hide inline container during hybrid mode to avoid CSS conflicts
|
|
7694
|
+
const inlineBody = this.container.getBody();
|
|
7695
|
+
if (inlineBody?.parentElement) {
|
|
7696
|
+
inlineBody.parentElement.style.display = 'none';
|
|
7697
|
+
}
|
|
7698
|
+
// Set dark mode and show sidebar once
|
|
7699
|
+
this.uploadModal.setDarkMode(true);
|
|
7700
|
+
this.uploadModal.toggleSidebar(true);
|
|
7701
|
+
this.hybridModalInitialized = true;
|
|
7702
|
+
}
|
|
7628
7703
|
// Clear and render current state
|
|
7629
7704
|
body.innerHTML = '';
|
|
7630
|
-
// Set dark mode and show sidebar
|
|
7631
|
-
this.uploadModal.setDarkMode(true);
|
|
7632
|
-
this.uploadModal.toggleSidebar(true);
|
|
7633
7705
|
if (this.viewState.view === 'uploading') {
|
|
7634
7706
|
body.appendChild(this.renderUploading(this.viewState));
|
|
7635
7707
|
}
|
|
@@ -8078,45 +8150,81 @@ class UploadRenderer {
|
|
|
8078
8150
|
}
|
|
8079
8151
|
}
|
|
8080
8152
|
async uploadWithTus(file, forgeUrl, ingotId, requestId) {
|
|
8081
|
-
//
|
|
8082
|
-
//
|
|
8083
|
-
const
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
percentage: progress,
|
|
8102
|
-
phase: 'uploading',
|
|
8103
|
-
});
|
|
8104
|
-
}
|
|
8105
|
-
};
|
|
8106
|
-
xhr.onload = () => {
|
|
8107
|
-
if (xhr.status >= 200 && xhr.status < 300) {
|
|
8108
|
-
resolve();
|
|
8109
|
-
}
|
|
8110
|
-
else {
|
|
8111
|
-
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
8112
|
-
}
|
|
8113
|
-
};
|
|
8114
|
-
xhr.onerror = () => reject(new Error('Upload failed'));
|
|
8115
|
-
xhr.ontimeout = () => reject(new Error('Upload timed out'));
|
|
8116
|
-
xhr.open('POST', forgeUrl);
|
|
8117
|
-
xhr.setRequestHeader('Content-Type', file.type || 'application/octet-stream');
|
|
8118
|
-
xhr.send(file);
|
|
8153
|
+
// tus resumable upload protocol v1.0.0
|
|
8154
|
+
// Uploads file in chunks to avoid Cloudflare's 100MB request limit
|
|
8155
|
+
const TUS_VERSION = '1.0.0';
|
|
8156
|
+
const DEFAULT_CHUNK_SIZE = 50 * 1024 * 1024; // 50MB default, server may override
|
|
8157
|
+
// Extract base URL and ISTK from forge URL
|
|
8158
|
+
const url = new URL(forgeUrl);
|
|
8159
|
+
const istk = url.searchParams.get('istk');
|
|
8160
|
+
const baseUrl = `${url.origin}${url.pathname}`;
|
|
8161
|
+
if (!istk) {
|
|
8162
|
+
throw new Error('Missing ISTK in forge URL');
|
|
8163
|
+
}
|
|
8164
|
+
// Step 1: Create tus upload session
|
|
8165
|
+
const createResponse = await fetch(baseUrl, {
|
|
8166
|
+
method: 'POST',
|
|
8167
|
+
headers: {
|
|
8168
|
+
'Tus-Resumable': TUS_VERSION,
|
|
8169
|
+
'Upload-Length': String(file.size),
|
|
8170
|
+
'X-ISTK': istk,
|
|
8171
|
+
'Content-Type': 'application/offset+octet-stream',
|
|
8172
|
+
},
|
|
8119
8173
|
});
|
|
8174
|
+
if (!createResponse.ok) {
|
|
8175
|
+
const errorText = await createResponse.text();
|
|
8176
|
+
throw new Error(`Failed to create upload session: ${createResponse.status} ${errorText}`);
|
|
8177
|
+
}
|
|
8178
|
+
// Get upload location and chunk size from response
|
|
8179
|
+
const location = createResponse.headers.get('Location');
|
|
8180
|
+
const chunkSizeHeader = createResponse.headers.get('X-Chunk-Size');
|
|
8181
|
+
const chunkSize = chunkSizeHeader ? parseInt(chunkSizeHeader, 10) : DEFAULT_CHUNK_SIZE;
|
|
8182
|
+
if (!location) {
|
|
8183
|
+
throw new Error('Server did not return upload location');
|
|
8184
|
+
}
|
|
8185
|
+
// Build full upload URL
|
|
8186
|
+
const uploadUrl = location.startsWith('http') ? location : `${url.origin}${location}`;
|
|
8187
|
+
// Step 2: Upload file in chunks
|
|
8188
|
+
let offset = 0;
|
|
8189
|
+
const totalSize = file.size;
|
|
8190
|
+
while (offset < totalSize) {
|
|
8191
|
+
const end = Math.min(offset + chunkSize, totalSize);
|
|
8192
|
+
const chunk = file.slice(offset, end);
|
|
8193
|
+
// Upload chunk
|
|
8194
|
+
const patchResponse = await fetch(uploadUrl, {
|
|
8195
|
+
method: 'PATCH',
|
|
8196
|
+
headers: {
|
|
8197
|
+
'Tus-Resumable': TUS_VERSION,
|
|
8198
|
+
'Upload-Offset': String(offset),
|
|
8199
|
+
'Content-Type': 'application/offset+octet-stream',
|
|
8200
|
+
},
|
|
8201
|
+
body: chunk,
|
|
8202
|
+
});
|
|
8203
|
+
if (!patchResponse.ok) {
|
|
8204
|
+
const errorText = await patchResponse.text();
|
|
8205
|
+
throw new Error(`Chunk upload failed: ${patchResponse.status} ${errorText}`);
|
|
8206
|
+
}
|
|
8207
|
+
// Update offset from server response
|
|
8208
|
+
const newOffsetHeader = patchResponse.headers.get('Upload-Offset');
|
|
8209
|
+
const newOffset = newOffsetHeader ? parseInt(newOffsetHeader, 10) : end;
|
|
8210
|
+
offset = newOffset;
|
|
8211
|
+
// Update progress
|
|
8212
|
+
const progress = Math.round((offset / totalSize) * 100);
|
|
8213
|
+
this.setState({
|
|
8214
|
+
view: 'uploading',
|
|
8215
|
+
file,
|
|
8216
|
+
ingotId,
|
|
8217
|
+
requestId,
|
|
8218
|
+
progress,
|
|
8219
|
+
bytesUploaded: offset,
|
|
8220
|
+
});
|
|
8221
|
+
this.callbacks.onProgress?.({
|
|
8222
|
+
bytesUploaded: offset,
|
|
8223
|
+
bytesTotal: totalSize,
|
|
8224
|
+
percentage: progress,
|
|
8225
|
+
phase: 'uploading',
|
|
8226
|
+
});
|
|
8227
|
+
}
|
|
8120
8228
|
}
|
|
8121
8229
|
async runCeremony(file, ingotId, requestId) {
|
|
8122
8230
|
for (let i = 0; i < CEREMONY_STEPS.length; i++) {
|