diff --git a/src/lib/components/fallback/Decrypt.svelte b/src/lib/components/fallback/Decrypt.svelte index a2631ae..03ff210 100644 --- a/src/lib/components/fallback/Decrypt.svelte +++ b/src/lib/components/fallback/Decrypt.svelte @@ -104,11 +104,8 @@ }, }) - // pg-js auto-unwraps `data:`-mode payloads (single-entry zip - // with `data.bin`) into DecryptDataResult.plaintext. Email - // fallback uploads are always `data:` mode, so this branch is - // the expected one. If we ever receive a multi-file result - // here, fall back to the first entry's bytes. + // Email fallback uploads are `data:` mode → plaintext; + // `files[0]` is a defensive fallback for unexpected shapes. const plaintext = result.plaintext ?? new Uint8Array(await result.files[0].blob.arrayBuffer()) diff --git a/src/lib/components/filesharing/DecryptionProgress.svelte b/src/lib/components/filesharing/DecryptionProgress.svelte index e64d82b..d11101c 100644 --- a/src/lib/components/filesharing/DecryptionProgress.svelte +++ b/src/lib/components/filesharing/DecryptionProgress.svelte @@ -11,7 +11,7 @@
-

+

{$_('filesharing.decryptpanel.downloadingAndDecrypting')}

{#if determinate} @@ -35,8 +35,6 @@ class="bar-track" role="progressbar" aria-labelledby="decryption-progress-label" - aria-valuemin="0" - aria-valuemax="100" >
@@ -106,4 +104,10 @@ font-family: var(--pg-font-family); font-size: var(--pg-font-size-sm); } + + @media (prefers-reduced-motion: reduce) { + .bar-indeterminate { + animation: none; + } + } diff --git a/src/lib/components/filesharing/ReportErrorButton.svelte b/src/lib/components/filesharing/ReportErrorButton.svelte new file mode 100644 index 0000000..8f99831 --- /dev/null +++ b/src/lib/components/filesharing/ReportErrorButton.svelte @@ -0,0 +1,109 @@ + + +{#if reportingEnabled} +
+ + + +
+ {#if sendState === 'failed'} +

{$_('filesharing.crash.failed')}

+ {/if} +
+
+{/if} + + diff --git a/src/routes/(app)/debug/download-flow/+page.svelte b/src/routes/(app)/debug/download-flow/+page.svelte new file mode 100644 index 0000000..e8acf7d --- /dev/null +++ b/src/routes/(app)/debug/download-flow/+page.svelte @@ -0,0 +1,1087 @@ + + +
+ + +
+ +
+
+

+ {#if downloadState === 'Fail'} + {$_('filesharing.decryptpanel.notFoundTitle')} + {:else if downloadState === 'SessionExpired'} + {$_('filesharing.decryptpanel.sessionExpiredTitle')} + {:else if downloadState === 'ServerError'} + {$_('filesharing.decryptpanel.serverErrorTitle')} + {:else if downloadState === 'IdentityMismatch'} + {$_('filesharing.decryptpanel.identityMismatchTitle')} + {:else} + {$_('filesharing.decryptpanel.header')} + {/if} +

+ + {#if downloadState === 'Downloading'} +
+ + + +
+ {#if showRetry} +

+ {$_('filesharing.encryptPanel.retrying', { + values: { + attempt: retryAttempt + 1, + max: retryMax, + }, + })} +

+ {/if} + {:else if downloadState === 'Recipients'} +

+ {$_('filesharing.decryptpanel.pageDescription')} +

+
+

+ {$_( + 'filesharing.decryptpanel.irmaInstructionHeaderQr' + )} +

+

+ Please select which email belongs to you: +

+ + setStateManual('Ready')} + size="lg" + variant="dark" + disabled={!recipientKey} + /> +
+ {:else if downloadState === 'Ready'} +
+

+ {$_('filesharing.decryptpanel.pageDescription')} +

+
+

+ {$_( + 'filesharing.decryptpanel.irmaInstructionHeaderQr' + )} +

+

+ {$_( + 'filesharing.decryptpanel.irmaInstructionQr' + )} +

+ +
+ + {#if showSenderIdentity} +
+ + + +

+ {$_( + 'filesharing.decryptpanel.verifiedEmail' + )} +

+ + {mockSenderIdentity.email} + +
+ {/if} +
+ {:else if downloadState === 'Decrypting' || downloadState === 'Done'} +
+
+ + {#if downloadState === 'Decrypting'} +
+ +
+ {/if} +
+ {#if downloadState === 'Decrypting' && showRetry} +

+ {$_('filesharing.encryptPanel.retrying', { + values: { + attempt: retryAttempt + 1, + max: retryMax, + }, + })} +

+ {/if} + + {#if downloadState === 'Done'} +
+ +
+ {/if} + + {#if downloadState === 'Done' && showSenderIdentity} +
+ + + +

+ {$_( + 'filesharing.decryptpanel.verifiedEmail' + )} +

+ + {mockSenderIdentity.email} + +
+ {#each mockSenderIdentity.attributes.filter((a) => !a.type.includes('email') && a.value) as attr (attr.type)} + + {attr.value} + + {/each} +
+
+ {/if} +
+ {:else if downloadState === 'SessionExpired'} +

+ {$_('filesharing.decryptpanel.sessionExpiredSubtitle')} +

+

+ + {@html $_( + 'filesharing.decryptpanel.sessionExpiredMessage' + )} +

+ {:else if downloadState === 'ServerError'} +

+ {$_('filesharing.decryptpanel.serverErrorSubtitle')} +

+

+ + {@html $_( + 'filesharing.decryptpanel.serverErrorMessage' + )} +

+
+ +
+ {:else if downloadState === 'Fail'} +

+ {$_('filesharing.decryptpanel.notFoundSubtitle')} +

+

+ + {@html $_('filesharing.decryptpanel.notFoundMessage')} +

+ {:else if downloadState === 'IdentityMismatch'} +

+ {$_( + 'filesharing.decryptpanel.identityMismatchSubtitle' + )} +

+

+ {$_('filesharing.decryptpanel.identityMismatchMessage')} +

+
+ setStateManual('Ready')} + size="lg" + variant="dark" + /> +
+ {/if} +
+
+
+
+ + diff --git a/src/routes/(app)/download/+page.svelte b/src/routes/(app)/download/+page.svelte index d1f4001..04d9d0d 100644 --- a/src/routes/(app)/download/+page.svelte +++ b/src/routes/(app)/download/+page.svelte @@ -17,6 +17,7 @@ import YiviQRCode from '$lib/components/filesharing/YiviQRCode.svelte' import FileList from '$lib/components/filesharing/FileList.svelte' import DecryptionProgress from '$lib/components/filesharing/DecryptionProgress.svelte' + import ReportErrorButton from '$lib/components/filesharing/ReportErrorButton.svelte' import { isMobile } from '$lib/browser-detect' import Chip from '$lib/components/Chip.svelte' import HelpToggle from '$lib/components/HelpToggle.svelte' @@ -42,6 +43,7 @@ let senderIdentity: FriendlySender | null = $state(null) let fileList: string[] = $state([]) let decryptPct: number | undefined = $state(undefined) + let lastError: unknown = $state(null) let opened: Awaited> | null = null @@ -81,6 +83,7 @@ checkRecipients(info.recipients) } catch (e) { retryStatus.set(null) + lastError = e if (e instanceof UploadSessionExpiredError) { downloadState = 'SessionExpired' } else if (e instanceof NetworkError && e.status >= 500) { @@ -153,6 +156,7 @@ } catch (e) { if (dev) console.error('[download] decrypt error:', e) retryStatus.set(null) + lastError = e if (e instanceof IdentityMismatchError) { downloadState = 'IdentityMismatch' } else if (e instanceof UploadSessionExpiredError) { @@ -326,7 +330,7 @@ /> {/if} -

+

{downloadState === 'Done' ? $_( 'filesharing.decryptpanel.doneMessageComplete' @@ -408,6 +412,12 @@ {@html $_('filesharing.decryptpanel.serverErrorMessage')}

+
+ +
{:else if downloadState === 'Fail'}

{$_('filesharing.decryptpanel.notFoundSubtitle')} @@ -647,9 +657,11 @@ background: var(--pg-general-background); } - .retry-wrapper { + .retry-wrapper, + .report-wrapper { display: flex; justify-content: center; + margin-top: 0.5rem; } .error-description {