@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.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);
|
|
@@ -7106,7 +7140,7 @@ class UploadModalContainer {
|
|
|
7106
7140
|
<div class="svu-security-badges">
|
|
7107
7141
|
<span class="svu-tls-badge">TLS 1.3 In Transit</span>
|
|
7108
7142
|
<span class="svu-tls-badge">Encrypted At Rest</span>
|
|
7109
|
-
<span class="svu-tls-badge">Zero
|
|
7143
|
+
<span class="svu-tls-badge">Zero Trust</span>
|
|
7110
7144
|
</div>
|
|
7111
7145
|
`;
|
|
7112
7146
|
sidebar.appendChild(security);
|
|
@@ -7172,8 +7206,8 @@ class UploadModalContainer {
|
|
|
7172
7206
|
* Implements the UploadContainer interface for use with UploadRenderer.
|
|
7173
7207
|
*/
|
|
7174
7208
|
const DEFAULT_OPTIONS = {
|
|
7175
|
-
showHeader:
|
|
7176
|
-
showCloseButton:
|
|
7209
|
+
showHeader: false,
|
|
7210
|
+
showCloseButton: false,
|
|
7177
7211
|
showFooter: true,
|
|
7178
7212
|
};
|
|
7179
7213
|
class UploadInlineContainer {
|
|
@@ -7465,7 +7499,7 @@ class UploadInlineContainer {
|
|
|
7465
7499
|
<div class="svu-security-badges">
|
|
7466
7500
|
<span class="svu-tls-badge">TLS 1.3 In Transit</span>
|
|
7467
7501
|
<span class="svu-tls-badge">Encrypted At Rest</span>
|
|
7468
|
-
<span class="svu-tls-badge">Zero
|
|
7502
|
+
<span class="svu-tls-badge">Zero Trust</span>
|
|
7469
7503
|
</div>
|
|
7470
7504
|
`;
|
|
7471
7505
|
sidebar.appendChild(security);
|
|
@@ -8116,26 +8150,75 @@ class UploadRenderer {
|
|
|
8116
8150
|
}
|
|
8117
8151
|
}
|
|
8118
8152
|
async uploadWithTus(file, forgeUrl, ingotId, requestId) {
|
|
8119
|
-
//
|
|
8120
|
-
//
|
|
8121
|
-
const
|
|
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
|
+
},
|
|
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 with progress tracking
|
|
8188
|
+
let offset = 0;
|
|
8189
|
+
const totalSize = file.size;
|
|
8190
|
+
while (offset < totalSize) {
|
|
8191
|
+
const chunkStart = offset;
|
|
8192
|
+
const chunkEnd = Math.min(offset + chunkSize, totalSize);
|
|
8193
|
+
const chunk = file.slice(chunkStart, chunkEnd);
|
|
8194
|
+
// Upload chunk with XHR for progress events
|
|
8195
|
+
const newOffset = await this.uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, TUS_VERSION, file, ingotId, requestId);
|
|
8196
|
+
offset = newOffset;
|
|
8197
|
+
}
|
|
8198
|
+
}
|
|
8199
|
+
/**
|
|
8200
|
+
* Upload a single chunk using XMLHttpRequest for progress tracking
|
|
8201
|
+
*/
|
|
8202
|
+
uploadChunkWithProgress(uploadUrl, chunk, chunkStart, totalSize, tusVersion, file, ingotId, requestId) {
|
|
8122
8203
|
return new Promise((resolve, reject) => {
|
|
8123
8204
|
const xhr = new XMLHttpRequest();
|
|
8124
|
-
|
|
8205
|
+
// Track progress within this chunk
|
|
8125
8206
|
xhr.upload.onprogress = (e) => {
|
|
8126
8207
|
if (e.lengthComputable) {
|
|
8127
|
-
|
|
8208
|
+
// Calculate total progress: completed chunks + progress within current chunk
|
|
8209
|
+
const totalUploaded = chunkStart + e.loaded;
|
|
8210
|
+
const progress = Math.round((totalUploaded / totalSize) * 100);
|
|
8128
8211
|
this.setState({
|
|
8129
8212
|
view: 'uploading',
|
|
8130
8213
|
file,
|
|
8131
8214
|
ingotId,
|
|
8132
8215
|
requestId,
|
|
8133
8216
|
progress,
|
|
8134
|
-
bytesUploaded:
|
|
8217
|
+
bytesUploaded: totalUploaded,
|
|
8135
8218
|
});
|
|
8136
8219
|
this.callbacks.onProgress?.({
|
|
8137
|
-
bytesUploaded:
|
|
8138
|
-
bytesTotal:
|
|
8220
|
+
bytesUploaded: totalUploaded,
|
|
8221
|
+
bytesTotal: totalSize,
|
|
8139
8222
|
percentage: progress,
|
|
8140
8223
|
phase: 'uploading',
|
|
8141
8224
|
});
|
|
@@ -8143,17 +8226,22 @@ class UploadRenderer {
|
|
|
8143
8226
|
};
|
|
8144
8227
|
xhr.onload = () => {
|
|
8145
8228
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
8146
|
-
|
|
8229
|
+
// Get new offset from server response
|
|
8230
|
+
const newOffsetHeader = xhr.getResponseHeader('Upload-Offset');
|
|
8231
|
+
const newOffset = newOffsetHeader ? parseInt(newOffsetHeader, 10) : chunkStart + chunk.size;
|
|
8232
|
+
resolve(newOffset);
|
|
8147
8233
|
}
|
|
8148
8234
|
else {
|
|
8149
|
-
reject(new Error(`
|
|
8235
|
+
reject(new Error(`Chunk upload failed with status ${xhr.status}`));
|
|
8150
8236
|
}
|
|
8151
8237
|
};
|
|
8152
|
-
xhr.onerror = () => reject(new Error('
|
|
8153
|
-
xhr.ontimeout = () => reject(new Error('
|
|
8154
|
-
xhr.open('
|
|
8155
|
-
xhr.setRequestHeader('
|
|
8156
|
-
xhr.
|
|
8238
|
+
xhr.onerror = () => reject(new Error('Chunk upload failed'));
|
|
8239
|
+
xhr.ontimeout = () => reject(new Error('Chunk upload timed out'));
|
|
8240
|
+
xhr.open('PATCH', uploadUrl);
|
|
8241
|
+
xhr.setRequestHeader('Tus-Resumable', tusVersion);
|
|
8242
|
+
xhr.setRequestHeader('Upload-Offset', String(chunkStart));
|
|
8243
|
+
xhr.setRequestHeader('Content-Type', 'application/offset+octet-stream');
|
|
8244
|
+
xhr.send(chunk);
|
|
8157
8245
|
});
|
|
8158
8246
|
}
|
|
8159
8247
|
async runCeremony(file, ingotId, requestId) {
|