@sparkvault/sdk 1.9.2 → 1.10.1
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 +110 -22
- package/dist/sparkvault.cjs.js.map +1 -1
- package/dist/sparkvault.esm.js +110 -22
- 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 +4 -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);
|
|
@@ -7102,7 +7136,7 @@ class UploadModalContainer {
|
|
|
7102
7136
|
<div class="svu-security-badges">
|
|
7103
7137
|
<span class="svu-tls-badge">TLS 1.3 In Transit</span>
|
|
7104
7138
|
<span class="svu-tls-badge">Encrypted At Rest</span>
|
|
7105
|
-
<span class="svu-tls-badge">Zero
|
|
7139
|
+
<span class="svu-tls-badge">Zero Trust</span>
|
|
7106
7140
|
</div>
|
|
7107
7141
|
`;
|
|
7108
7142
|
sidebar.appendChild(security);
|
|
@@ -7168,8 +7202,8 @@ class UploadModalContainer {
|
|
|
7168
7202
|
* Implements the UploadContainer interface for use with UploadRenderer.
|
|
7169
7203
|
*/
|
|
7170
7204
|
const DEFAULT_OPTIONS = {
|
|
7171
|
-
showHeader:
|
|
7172
|
-
showCloseButton:
|
|
7205
|
+
showHeader: false,
|
|
7206
|
+
showCloseButton: false,
|
|
7173
7207
|
showFooter: true,
|
|
7174
7208
|
};
|
|
7175
7209
|
class UploadInlineContainer {
|
|
@@ -7461,7 +7495,7 @@ class UploadInlineContainer {
|
|
|
7461
7495
|
<div class="svu-security-badges">
|
|
7462
7496
|
<span class="svu-tls-badge">TLS 1.3 In Transit</span>
|
|
7463
7497
|
<span class="svu-tls-badge">Encrypted At Rest</span>
|
|
7464
|
-
<span class="svu-tls-badge">Zero
|
|
7498
|
+
<span class="svu-tls-badge">Zero Trust</span>
|
|
7465
7499
|
</div>
|
|
7466
7500
|
`;
|
|
7467
7501
|
sidebar.appendChild(security);
|
|
@@ -8112,26 +8146,75 @@ class UploadRenderer {
|
|
|
8112
8146
|
}
|
|
8113
8147
|
}
|
|
8114
8148
|
async uploadWithTus(file, forgeUrl, ingotId, requestId) {
|
|
8115
|
-
//
|
|
8116
|
-
//
|
|
8117
|
-
const
|
|
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
|
+
},
|
|
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 with progress tracking
|
|
8184
|
+
let offset = 0;
|
|
8185
|
+
const totalSize = file.size;
|
|
8186
|
+
while (offset < totalSize) {
|
|
8187
|
+
const chunkStart = offset;
|
|
8188
|
+
const chunkEnd = Math.min(offset + chunkSize, totalSize);
|
|
8189
|
+
const chunk = file.slice(chunkStart, chunkEnd);
|
|
8190
|
+
// Upload chunk with XHR for progress events
|
|
8191
|
+
const newOffset = await this.uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, TUS_VERSION, file, ingotId, requestId);
|
|
8192
|
+
offset = newOffset;
|
|
8193
|
+
}
|
|
8194
|
+
}
|
|
8195
|
+
/**
|
|
8196
|
+
* Upload a single chunk using XMLHttpRequest for progress tracking
|
|
8197
|
+
*/
|
|
8198
|
+
uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, tusVersion, file, ingotId, requestId) {
|
|
8118
8199
|
return new Promise((resolve, reject) => {
|
|
8119
8200
|
const xhr = new XMLHttpRequest();
|
|
8120
|
-
|
|
8201
|
+
// Track progress within this chunk
|
|
8121
8202
|
xhr.upload.onprogress = (e) => {
|
|
8122
8203
|
if (e.lengthComputable) {
|
|
8123
|
-
|
|
8204
|
+
// Calculate total progress: completed chunks + progress within current chunk
|
|
8205
|
+
const totalUploaded = chunkStart + e.loaded;
|
|
8206
|
+
const progress = Math.round((totalUploaded / totalSize) * 100);
|
|
8124
8207
|
this.setState({
|
|
8125
8208
|
view: 'uploading',
|
|
8126
8209
|
file,
|
|
8127
8210
|
ingotId,
|
|
8128
8211
|
requestId,
|
|
8129
8212
|
progress,
|
|
8130
|
-
bytesUploaded:
|
|
8213
|
+
bytesUploaded: totalUploaded,
|
|
8131
8214
|
});
|
|
8132
8215
|
this.callbacks.onProgress?.({
|
|
8133
|
-
bytesUploaded:
|
|
8134
|
-
bytesTotal:
|
|
8216
|
+
bytesUploaded: totalUploaded,
|
|
8217
|
+
bytesTotal: totalSize,
|
|
8135
8218
|
percentage: progress,
|
|
8136
8219
|
phase: 'uploading',
|
|
8137
8220
|
});
|
|
@@ -8139,17 +8222,22 @@ class UploadRenderer {
|
|
|
8139
8222
|
};
|
|
8140
8223
|
xhr.onload = () => {
|
|
8141
8224
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
8142
|
-
|
|
8225
|
+
// Get new offset from server response
|
|
8226
|
+
const newOffsetHeader = xhr.getResponseHeader('Upload-Offset');
|
|
8227
|
+
const newOffset = newOffsetHeader ? parseInt(newOffsetHeader, 10) : chunkStart + chunk.size;
|
|
8228
|
+
resolve(newOffset);
|
|
8143
8229
|
}
|
|
8144
8230
|
else {
|
|
8145
|
-
reject(new Error(`
|
|
8231
|
+
reject(new Error(`Chunk upload failed with status ${xhr.status}`));
|
|
8146
8232
|
}
|
|
8147
8233
|
};
|
|
8148
|
-
xhr.onerror = () => reject(new Error('
|
|
8149
|
-
xhr.ontimeout = () => reject(new Error('
|
|
8150
|
-
xhr.open('
|
|
8151
|
-
xhr.setRequestHeader('
|
|
8152
|
-
xhr.
|
|
8234
|
+
xhr.onerror = () => reject(new Error('Chunk upload failed'));
|
|
8235
|
+
xhr.ontimeout = () => reject(new Error('Chunk upload timed out'));
|
|
8236
|
+
xhr.open('PATCH', uploadUrl);
|
|
8237
|
+
xhr.setRequestHeader('Tus-Resumable', tusVersion);
|
|
8238
|
+
xhr.setRequestHeader('Upload-Offset', String(chunkStart));
|
|
8239
|
+
xhr.setRequestHeader('Content-Type', 'application/offset+octet-stream');
|
|
8240
|
+
xhr.send(chunk);
|
|
8153
8241
|
});
|
|
8154
8242
|
}
|
|
8155
8243
|
async runCeremony(file, ingotId, requestId) {
|