From 902dea7b8f5ac1c8f82aa3615884446ec2321e6f Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:53:19 +0000 Subject: [PATCH 1/5] docs: document pg-js 2.0 DecryptFileResult + onDownloadProgress + cause-preserving IdentityMismatchError Sources: - encryption4all/postguard-js#86 (DecryptFileResult shape change, onDownloadProgress) - encryption4all/postguard-js#84 (IdentityMismatchError cause preservation) --- docs/sdk/js-decryption.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/sdk/js-decryption.md b/docs/sdk/js-decryption.md index b81858d..3cced17 100644 --- a/docs/sdk/js-decryption.md +++ b/docs/sdk/js-decryption.md @@ -31,7 +31,7 @@ Pass `{ uuid }` to `pg.open()`, then call `decrypt()` with `element` (a CSS sele The return type is the union `DecryptResult = DecryptFileResult | DecryptDataResult`. Which variant you get mirrors how the payload reached Cryptify: -- Uploaded via `Sealed.upload({ files })` → `DecryptFileResult` with `files`, `blob`, and a `download()` helper. +- Uploaded via `Sealed.upload({ files })` → `DecryptFileResult` with a `files` array of `{ name, blob }` entries, the raw ZIP as `blob`, and a `download()` helper that fans out one browser download per entry. - Uploaded via `Sealed.upload({ data })` → `DecryptDataResult` with `plaintext` as a `Uint8Array`. This keeps the round-trip `pg.encrypt({ data }).upload()` → `pg.open({ uuid }).decrypt()` symmetric: raw bytes go in, raw bytes come out. Internally, `Sealed.upload({ data })` wraps the bytes as a single-entry zip named `data.bin`; on decrypt the SDK inspects the inner zip's central directory and, when the entries are exactly `['data.bin']`, unwraps that entry and returns `DecryptDataResult`. @@ -62,10 +62,14 @@ Returned when the payload was uploaded with `Sealed.upload({ files })`. | Property | Type | Description | |----------|------|-------------| -| `files` | `string[]` | Filenames inside the ZIP | +| `files` | `Array<{ name: string; blob: Blob }>` | One entry per file inside the ZIP, with the entry's filename and decoded `Blob` | | `sender` | `FriendlySender \| null` | Verified sender identity with `.email` and `.attributes` | -| `blob` | `Blob` | The decrypted ZIP blob | -| `download` | `(filename?: string) => void` | Trigger a browser download | +| `blob` | `Blob` | The raw decrypted ZIP, as an escape hatch for callers that want to re-upload, hand-process, or offer a single "Download as ZIP" button | +| `download` | `() => void` | Triggers one browser download per entry in `files` | + +::: warning Breaking change in v2 +Prior to `@e4a/pg-js@2.0.0`, `files` was `string[]` (filenames only) and `download` took an optional `filename` argument that selected a single entry. The new `download()` takes no arguments and fans out per entry; callers that want a single combined download can pipe `blob` through their own anchor. Source: [encryption4all/postguard-js#86](https://github.com/encryption4all/postguard-js/pull/86). +::: ## Decrypt from raw data @@ -105,6 +109,7 @@ Returned from `{ data }` decryption, and from `{ uuid }` when the payload was up | `session` | `SessionCallback` | No* | Custom session callback for non-browser environments | | `recipient` | `string` | No | Email of the recipient to decrypt for (required if multiple recipients) | | `enableCache` | `boolean` | No | Cache the Yivi JWT so repeated decryptions don't require re-scanning the QR code | +| `onDownloadProgress` | `(pct: number \| undefined) => void` | No | Progress callback for the streaming download+decrypt pipeline. Fires on every chunk with `0–100` when the server set `Content-Length`, or `undefined` when it didn't (consumer should render an indeterminate indicator). Available since `@e4a/pg-js@2.0.0` ([#86](https://github.com/encryption4all/postguard-js/pull/86)) | *Provide either `element` or `session`. If neither is provided, the SDK throws a `DecryptionError`. @@ -171,6 +176,8 @@ Decryption can throw: Catch `IdentityMismatchError` first to show a recipient-mismatch message, then `YiviSessionError` to surface a friendly "session cancelled" message instead of a generic decryption failure, then fall through to a generic error branch for everything else. +Since `@e4a/pg-js@2.0.0`, `IdentityMismatchError` preserves the underlying error on `.cause` when the failure was not a real identity mismatch (network drop during streaming, WASM panic, malformed ciphertext). Inspect `err.cause` when debugging a transient failure that surfaces as a mismatch. An `AbortError` from a caller-supplied `signal` passes through as-is — it is not rewrapped. Source: [encryption4all/postguard-js#84](https://github.com/encryption4all/postguard-js/pull/84). + ```ts import { YiviSessionError, IdentityMismatchError } from '@e4a/pg-js'; From 07ce079686b2fd40bc74c4868bd1da53050c28a6 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:53:23 +0000 Subject: [PATCH 2/5] docs: clarify createEnvelope tier 2 vs tier 3 upload-failure semantics Source: encryption4all/postguard-js#82 (re-throw on tier 3, console.warn on tier 2) --- docs/sdk/js-email-helpers.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/sdk/js-email-helpers.md b/docs/sdk/js-email-helpers.md index 17ce995..1eb7ed3 100644 --- a/docs/sdk/js-email-helpers.md +++ b/docs/sdk/js-email-helpers.md @@ -142,6 +142,15 @@ const envelope = await pg.email.createEnvelope({ Callers that handle the result must null-check `envelope.attachment` before reading it, since tier 3 envelopes carry no attachment. +### Failures during upload + +Tier 2 and tier 3 envelopes both attempt a Cryptify upload. Their failure semantics differ: + +- **Tier 2** falls back to the local attachment when the upload rejects. The promise resolves and the envelope still contains the `postguard.encrypted` file; the failure is logged via `console.warn` so it isn't silent. The recipient can still decrypt from the attachment. +- **Tier 3** has no local fallback (the attachment is omitted by design), so the original error is re-thrown. `createEnvelope` rejects with the underlying typed error (`NetworkError`, `UploadSessionExpiredError`, or a generic `PostGuardError`) and callers should branch on the same error classes they already use around `Sealed.upload()`. + +Prior to `@e4a/pg-js@2.0.0`, a tier 3 upload failure was swallowed and `createEnvelope` returned an envelope with `attachment: null` plus a body whose fallback link pointed at a non-existent Cryptify UUID. Source: [encryption4all/postguard-js#82](https://github.com/encryption4all/postguard-js/pull/82). + ### Parameters | Parameter | Type | Required | Description | From 6a1176473f9dd2490f663ebb406b76d2df9b283b Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:53:26 +0000 Subject: [PATCH 3/5] docs: add Runtime config section for postguard-website APP_CONFIG keys Sources: - encryption4all/postguard-website#244 (STAGING) - encryption4all/postguard-website#247 (GLITCHTIP_DSN) - encryption4all/postguard-website#255 (SITE_URL) --- docs/repos/postguard-website.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/repos/postguard-website.md b/docs/repos/postguard-website.md index 9dedfc8..0d21005 100644 --- a/docs/repos/postguard-website.md +++ b/docs/repos/postguard-website.md @@ -123,6 +123,8 @@ adb reverse tcp:8080 tcp:8080 # PostGuard website ## Environment Variables +`VITE_*` variables are read at build time and baked into the bundle. + | Variable | Default | Description | |---|---|---| | `VITE_FILEHOST_URL` | `http://localhost:8000` | Cryptify file hosting service URL | @@ -131,6 +133,20 @@ adb reverse tcp:8080 tcp:8080 # PostGuard website | `VITE_UPLOAD_CHUNK_SIZE` | none | Upload chunk size in bytes | | `VITE_FILEREAD_CHUNK_SIZE` | none | File read chunk size in bytes | +## Runtime config + +A second tier of flags is read at page load from a global `APP_CONFIG` object served by `static/config.js`. Deployed environments overwrite the file via the Terraform ConfigMap; local Docker Compose uses the in-repo copy. Each key is optional, with a fallback baked into `src/lib/env.ts`. + +| Key | Type | Default | Description | +|---|---|---|---| +| `SITE_URL` | string | `https://postguard.eu` | Base URL used by sitemap, RSS, JSON-LD, and the `(app)` routes. Marketing pages are prerendered, so the baked-in fallback is what ships in their static HTML; a runtime override only takes effect on non-prerendered routes such as `/decrypt` and `/fileshare`. Added in [encryption4all/postguard-website#255](https://github.com/encryption4all/postguard-website/pull/255). | +| `BUSINESS_URL` | string | `https://business.postguard.eu` | Link target for the "for Business" entry point. | +| `FF_BUSINESS` | boolean | `false` | Feature flag that controls whether the business entry point is shown. | +| `STAGING` | boolean | `false` | True on staging/dev where cryptify runs with `staging_mode = true` and does not send notification mail. The website renders an in-page preview of the would-be recipient email so developers can grab the download link without trawling cryptify logs. Added in [encryption4all/postguard-website#244](https://github.com/encryption4all/postguard-website/pull/244). | +| `GLITCHTIP_DSN` | string | `""` | DSN for a Sentry-compatible error reporter (PostGuard runs GlitchTip). An empty value disables reporting at module load. The fileshare flow's `CrashReport` panel uses this to POST captured exceptions on user opt-in. Added in [encryption4all/postguard-website#247](https://github.com/encryption4all/postguard-website/pull/247). | + +The runtime tier exists because these values change per environment without rebuilding the static bundle. Override them by replacing `config.js` in the served container. + ## Releasing This repository uses [Release-please](https://github.com/googleapis/release-please) for automated versioning. Merging a release PR triggers a multi-architecture Docker image build pushed to GHCR. From 1f83dfe5926cfdb5b149a0b43f019ac507af97c2 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Sat, 13 Jun 2026 22:48:15 +0000 Subject: [PATCH 4/5] docs: surface private signing attributes on FriendlySender (from encryption4all/postguard-js#89) --- docs/sdk/js-decryption.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sdk/js-decryption.md b/docs/sdk/js-decryption.md index 3cced17..1ebec70 100644 --- a/docs/sdk/js-decryption.md +++ b/docs/sdk/js-decryption.md @@ -165,6 +165,8 @@ The full `FriendlySender` type: | `attributes` | `Array<{ type, value? }>` | All identity attributes | | `raw` | `SenderIdentity` | Raw identity structure for advanced use | +Since [encryption4all/postguard-js#89](https://github.com/encryption4all/postguard-js/pull/89), `attributes` also carries the attributes the sender signed under their private signing identity (name, mobile, dateofbirth, and so on), not just the public email. Earlier versions discarded the unseal result and surfaced only the email, so recipients saw a single attribute even when the sender had disclosed more. Read the extra attributes from `attributes` to show the recipient exactly who signed the message. + ## Error handling Decryption can throw: From e7b2b4d61bbadf2bc2f24b1e1e22ce16ad4ec895 Mon Sep 17 00:00:00 2001 From: "dobby-yivi-agent[bot]" <275734547+dobby-yivi-agent[bot]@users.noreply.github.com> Date: Sat, 13 Jun 2026 22:48:15 +0000 Subject: [PATCH 5/5] docs: document /download trust-confirmation gate (from encryption4all/postguard-website#258) --- docs/repos/postguard-website.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/repos/postguard-website.md b/docs/repos/postguard-website.md index 0d21005..2c7e408 100644 --- a/docs/repos/postguard-website.md +++ b/docs/repos/postguard-website.md @@ -49,6 +49,10 @@ if (uuidParam) { [Source: src/routes/(app)/decrypt/+page.svelte#L86-L106](https://github.com/encryption4all/postguard-website/blob/0398c87d113ab7b9fc518f4eb9aaf7059745d54a/src/routes/%28app%29/decrypt/%2Bpage.svelte#L86-L106) +### Download confirmation gate + +Since [encryption4all/postguard-website#258](https://github.com/encryption4all/postguard-website/pull/258), the `/download` page decrypts into in-memory blobs and pauses on a confirmation step before any file is written to disk. The recipient sees the file list and the verified sender, then chooses to keep or discard the files. Declining drops the blobs without writing them; accepting starts the browser downloads. When the sender disclosed nothing beyond their email, the confirmation panel warns that email alone is a weak identity claim, since anyone with control of that mailbox could have signed the message. + ## Development ### Quick Start with Docker Compose (recommended)