Skip to content

Commit 5f4e216

Browse files
feat: show encryption progress on client
1 parent b21f2cb commit 5f4e216

3 files changed

Lines changed: 107 additions & 14 deletions

File tree

src/frontend/src/lib/functions/streams.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ interface DecryptionContext {
218218
allDoneReject: ((e: any) => void) | null;
219219
streamEnded: boolean;
220220
controllerRef: ReadableStreamDefaultController<Uint8Array> | null;
221+
processedTotal: number;
222+
originalSize?: number;
223+
onProgress?: (processed: number, total?: number) => void;
221224
}
222225

223226
async function handleDecryptionError(ctx: DecryptionContext, e: any): Promise<void> {
@@ -234,8 +237,12 @@ async function handleWorkerDecryptedMessage(ctx: DecryptionContext, data: any):
234237
ctx.decryptedMap.delete(ctx.nextToEnqueue);
235238
ctx.controllerRef!.enqueue(arr);
236239
ctx.nextToEnqueue++;
240+
241+
ctx.processedTotal += arr.byteLength;
242+
if (ctx.onProgress) ctx.onProgress(ctx.processedTotal, ctx.originalSize);
237243
}
238244
if (ctx.streamEnded && ctx.pendingCount === 0 && ctx.allDoneResolve) {
245+
if (ctx.onProgress) ctx.onProgress(ctx.originalSize ?? ctx.processedTotal, ctx.originalSize);
239246
ctx.allDoneResolve();
240247
}
241248
} else if (data?.type === 'error') {
@@ -302,9 +309,13 @@ async function decryptChunkFallback(
302309
ctx.decryptedMap.delete(ctx.nextToEnqueue);
303310
ctx.controllerRef.enqueue(arr);
304311
ctx.nextToEnqueue++;
312+
313+
ctx.processedTotal += arr.byteLength;
314+
if (ctx.onProgress) ctx.onProgress(ctx.processedTotal, ctx.originalSize);
305315
}
306316
}
307317
if (ctx.streamEnded && ctx.pendingCount === 0 && ctx.allDoneResolve) {
318+
if (ctx.onProgress) ctx.onProgress(ctx.originalSize ?? ctx.processedTotal, ctx.originalSize);
308319
ctx.allDoneResolve();
309320
}
310321
} catch (err) {
@@ -500,7 +511,9 @@ export async function createEncryptedStream(
500511
export async function createDecryptedStream(
501512
inputStream: ReadableStream<Uint8Array>,
502513
keySecret: string,
503-
password?: string
514+
password?: string,
515+
originalSize?: number,
516+
onProgress?: (processed: number, total?: number) => void
504517
) {
505518
const ikm = base64urlToBytes(keySecret);
506519
const { aesKey, baseIv } = await deriveSecrets(ikm, password);
@@ -521,7 +534,10 @@ export async function createDecryptedStream(
521534
allDoneResolve: null,
522535
allDoneReject: null,
523536
streamEnded: false,
524-
controllerRef: null
537+
controllerRef: null,
538+
processedTotal: 0,
539+
originalSize,
540+
onProgress
525541
};
526542

527543
const allDonePromise = new Promise<void>((res, rej) => {
@@ -574,6 +590,9 @@ export async function createDecryptedStream(
574590
ctx.decryptedMap.delete(ctx.nextToEnqueue);
575591
controller.enqueue(arr);
576592
ctx.nextToEnqueue++;
593+
594+
ctx.processedTotal += arr.byteLength;
595+
if (ctx.onProgress) ctx.onProgress(ctx.processedTotal, ctx.originalSize);
577596
}
578597
controller.close();
579598
}

src/frontend/src/routes/(needs_onboarding)/(navbar_and_footer)/reverse/[room_id]/client.svelte

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
// Client streaming
5858
let receiveState = $state<ReceiveState>({ type: 'idle' });
5959
let downloadedFiles = $state<DownloadedFile[]>([]);
60+
let decryptionProgress = $state(new Tween(0, { duration: 500, easing: cubicOut }));
61+
let isDecrypting = $state(false);
6062
6163
type DownloadPreference = 'eager' | 'manual' | null;
6264
let downloadPreference = $state<DownloadPreference>(null);
@@ -261,11 +263,25 @@
261263
try {
262264
let finalBlob = new Blob(chunks);
263265
if (roomKey) {
266+
isDecrypting = true;
267+
decryptionProgress = new Tween(0, { duration: 500, easing: cubicOut });
264268
const { stream: decryptedStream } = await createDecryptedStream(
265269
finalBlob.stream() as any,
266-
roomKey
270+
roomKey,
271+
undefined,
272+
finalBlob.size,
273+
(processed, total) => {
274+
if (total && total > 0) {
275+
decryptionProgress.target = Math.min(
276+
100,
277+
Math.round((processed / total) * 100)
278+
);
279+
}
280+
}
267281
);
268282
finalBlob = await new Response(decryptedStream as any).blob();
283+
isDecrypting = false;
284+
decryptionProgress.target = 100;
269285
}
270286
const objectUrl = URL.createObjectURL(finalBlob);
271287
downloadedFiles = [...downloadedFiles, { key, filename, size, objectUrl }];
@@ -280,6 +296,7 @@
280296
toast.error(`Decryption failed for ${filename}`);
281297
} finally {
282298
receiveState = { type: 'idle' };
299+
isDecrypting = false;
283300
}
284301
}
285302
break;
@@ -643,8 +660,13 @@
643660

644661
{#if isStreaming}
645662
<div class="flex items-center gap-2 text-xs text-muted-foreground">
646-
<span class="animate-pulse">Receiving…</span>
647-
<span class="font-mono">{streamProgress.toFixed(0)}%</span>
663+
{#if isDecrypting}
664+
<span class="animate-pulse">Decrypting…</span>
665+
<span class="font-mono">{decryptionProgress.current.toFixed(0)}%</span>
666+
{:else}
667+
<span class="animate-pulse">Receiving…</span>
668+
<span class="font-mono">{streamProgress.toFixed(0)}%</span>
669+
{/if}
648670
</div>
649671
{/if}
650672

@@ -697,7 +719,11 @@
697719
</div>
698720
</div>
699721
{#if isStreaming}
700-
<Progress value={streamProgress} max={100} class="mt-2 h-1" />
722+
{#if isDecrypting}
723+
<Progress value={decryptionProgress.current} max={100} class="mt-2 h-1" />
724+
{:else}
725+
<Progress value={streamProgress} max={100} class="mt-2 h-1" />
726+
{/if}
701727
{/if}
702728
</div>
703729
{/each}

src/frontend/src/routes/(needs_onboarding)/(navbar_and_footer)/reverse/[room_id]/host.svelte

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,14 @@
7373
let uploads = $state<UploadEntry[]>([]);
7474
let isUploading = $state(false);
7575
const overallProgress = new Tween(0, { duration: 400, easing: cubicOut });
76+
let encryptionProgress = $state(new Tween(0, { duration: 500, easing: cubicOut }));
77+
let isEncrypting = $state(false);
7678
7779
// Client streaming
7880
let receiveState = $state<ReceiveState>({ type: 'idle' });
7981
let downloadedFiles = $state<DownloadedFile[]>([]);
82+
let decryptionProgress = $state(new Tween(0, { duration: 500, easing: cubicOut }));
83+
let isDecrypting = $state(false);
8084
8185
// WebSocket
8286
let ws = $state<WebSocket | null>(null);
@@ -259,11 +263,25 @@
259263
try {
260264
let finalBlob = new Blob(chunks);
261265
if (roomKey) {
266+
isDecrypting = true;
267+
decryptionProgress = new Tween(0, { duration: 500, easing: cubicOut });
262268
const { stream: decryptedStream } = await createDecryptedStream(
263269
finalBlob.stream() as any,
264-
roomKey
270+
roomKey,
271+
undefined,
272+
finalBlob.size,
273+
(processed, total) => {
274+
if (total && total > 0) {
275+
decryptionProgress.target = Math.min(
276+
100,
277+
Math.round((processed / total) * 100)
278+
);
279+
}
280+
}
265281
);
266282
finalBlob = await new Response(decryptedStream as any).blob();
283+
isDecrypting = false;
284+
decryptionProgress.target = 100;
267285
}
268286
const objectUrl = URL.createObjectURL(finalBlob);
269287
downloadedFiles = [...downloadedFiles, { key, filename, size, objectUrl }];
@@ -277,6 +295,7 @@
277295
toast.error(`Decryption failed for ${filename}`);
278296
} finally {
279297
receiveState = { type: 'idle' };
298+
isDecrypting = false;
280299
}
281300
}
282301
break;
@@ -347,16 +366,26 @@
347366
uploads = [...uploads];
348367
349368
try {
369+
isEncrypting = true;
370+
encryptionProgress = new Tween(0, { duration: 500, easing: cubicOut });
371+
350372
const zipStream = await createZipStream([entry.file]);
351373
const { stream: encryptedStream } = await createEncryptedStream(
352374
zipStream,
353375
undefined,
354-
undefined,
355-
undefined,
376+
entry.file.size,
377+
(processed, total) => {
378+
if (total && total > 0) {
379+
encryptionProgress.target = Math.min(100, Math.round((processed / total) * 100));
380+
}
381+
},
356382
ikm
357383
);
358384
359385
const encryptedBlob = await new Response(encryptedStream).blob();
386+
isEncrypting = false;
387+
encryptionProgress.target = 100;
388+
360389
const filename = `${entry.file.name}.zip`;
361390
362391
const fileEntry = await uploadFileXhr(encryptedBlob, filename, (pct) => {
@@ -373,6 +402,8 @@
373402
} catch (e: any) {
374403
entry.status = 'error';
375404
toast.error(`Upload failed for ${entry.file.name}: ${e.message || String(e)}`);
405+
} finally {
406+
isEncrypting = false;
376407
}
377408
uploads = [...uploads];
378409
}
@@ -720,11 +751,19 @@
720751
{:else if u.status === 'error'}
721752
<X class="h-4 w-4 shrink-0 text-destructive" />
722753
{:else if u.status === 'uploading'}
723-
<LoaderCircle class="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
754+
{#if isEncrypting}
755+
<span class="animate-pulse text-xs text-muted-foreground">Encrypting…</span>
756+
{:else}
757+
<LoaderCircle class="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
758+
{/if}
724759
{/if}
725760
</div>
726761
{#if u.status === 'uploading' || u.status === 'pending'}
727-
<Progress value={u.progress.current} max={100} class="h-1" />
762+
{#if u.status === 'uploading' && isEncrypting}
763+
<Progress value={encryptionProgress.current} max={100} class="h-1" />
764+
{:else}
765+
<Progress value={u.progress.current} max={100} class="h-1" />
766+
{/if}
728767
{/if}
729768
</div>
730769
{/each}
@@ -813,8 +852,13 @@
813852

814853
{#if isStreaming}
815854
<div class="flex items-center gap-2 text-xs text-muted-foreground">
816-
<span class="animate-pulse">Receiving…</span>
817-
<span class="font-mono">{streamProgress.toFixed(0)}%</span>
855+
{#if isDecrypting}
856+
<span class="animate-pulse">Decrypting…</span>
857+
<span class="font-mono">{decryptionProgress.current.toFixed(0)}%</span>
858+
{:else}
859+
<span class="animate-pulse">Receiving…</span>
860+
<span class="font-mono">{streamProgress.toFixed(0)}%</span>
861+
{/if}
818862
</div>
819863
{/if}
820864

@@ -866,7 +910,11 @@
866910
</div>
867911
</div>
868912
{#if isStreaming}
869-
<Progress value={streamProgress} max={100} class="mt-2 h-1" />
913+
{#if isDecrypting}
914+
<Progress value={decryptionProgress.current} max={100} class="mt-2 h-1" />
915+
{:else}
916+
<Progress value={streamProgress} max={100} class="mt-2 h-1" />
917+
{/if}
870918
{/if}
871919
</div>
872920
{/each}

0 commit comments

Comments
 (0)