From 26d6ca9d28dedbc2c3d336583f56177eaa62e0e7 Mon Sep 17 00:00:00 2001
From: "dobby-yivi-agent[bot]"
<275734547+dobby-yivi-agent[bot]@users.noreply.github.com>
Date: Wed, 13 May 2026 21:41:41 +0000
Subject: [PATCH] docs(js-sdk): document resumeUpload, FileState and
recoveryToken
Adds a 'Resume an interrupted upload' section to the JS SDK encryption
page covering the new exports from postguard-js#66:
- FileState shape and persistable fields (uuid, recoveryToken)
- resumeUpload signature, behaviour, and wire-field mapping
- 404 -> UploadSessionExpiredError; unknown UUID and wrong token collapse
Amends the js-errors page so the 'no resume path' line scopes to the
post-expiry case and cross-links the new section.
Source links pinned to postguard-js merge commit 6205fc309a.
Refs encryption4all/postguard-docs#83
Refs encryption4all/postguard-js#68
---
docs/sdk/js-encryption.md | 46 +++++++++++++++++++++++++++++++++++++++
docs/sdk/js-errors.md | 2 +-
2 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/docs/sdk/js-encryption.md b/docs/sdk/js-encryption.md
index bee3959..d73f4f0 100644
--- a/docs/sdk/js-encryption.md
+++ b/docs/sdk/js-encryption.md
@@ -126,6 +126,52 @@ What gets retried: 5xx responses, fetch-level network errors (`TypeError` from `
The same `retry` config governs downloads. See [Decryption — Retries and resumable downloads](/sdk/js-decryption#retries-and-resumable-downloads).
+## Resume an interrupted upload
+
+A long-running upload can be interrupted by a page refresh, tab crash, navigation away, or process restart. The SDK exposes two primitives for rehydrating an in-flight session from Cryptify rather than starting over: the `FileState` type and the `resumeUpload` function.
+
+### `FileState`
+
+`FileState` carries everything Cryptify needs to accept the next chunk for an in-flight upload. The two persistable fields are `uuid` and `recoveryToken`; the rest can be reconstructed by calling `resumeUpload`.
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `token` | `string` | Current rolling token sent on the next chunk PUT |
+| `prevToken` | `string \| undefined` | Token from the most recent committed chunk. Used on retry so Cryptify's idempotent-retry path can replay a lost response. `undefined` until the first chunk is committed |
+| `uuid` | `string` | Upload UUID issued at init |
+| `recoveryToken` | `string` | Bearer credential issued by `POST /fileupload/init` (wire field `recovery_token`). Persist alongside `uuid` in consumer-owned storage |
+
+[Source: cryptify.ts#L13-L33](https://github.com/encryption4all/postguard-js/blob/6205fc309aaf954e82937beae723912812604f2e/src/api/cryptify.ts#L13-L33)
+
+### `resumeUpload`
+
+```ts
+import { resumeUpload, type FileState } from '@e4a/pg-js';
+
+const { state, uploaded } = await resumeUpload(
+ cryptifyUrl,
+ uuid,
+ recoveryToken,
+ signal
+);
+```
+
+Calls `GET /fileupload/{uuid}/status` with the `X-Recovery-Token` header and returns `{ state: FileState; uploaded: number }`:
+
+- `cryptify_token` from the response is mapped to `state.token`.
+- `prev_token` is mapped to `state.prevToken` and is omitted before the first committed chunk.
+- `uploaded` is the byte offset to resume from.
+
+[Source: cryptify.ts#L143-L178](https://github.com/encryption4all/postguard-js/blob/6205fc309aaf954e82937beae723912812604f2e/src/api/cryptify.ts#L143-L178)
+
+### Failure mode
+
+A 404 response with Cryptify's structured `upload_session_not_found` body surfaces as `UploadSessionExpiredError`. Cryptify deliberately collapses "unknown UUID" and "wrong recovery token" into the same response, so callers should treat both the same way: the session is gone, start a new upload. See [`UploadSessionExpiredError`](/sdk/js-errors#uploadsessionexpirederror) in the error reference.
+
+### Current limitation
+
+`resumeUpload` and `FileState` are exported by `@e4a/pg-js`, but the high-level `pg.encrypt(...).upload()` does not yet surface `recoveryToken` on its `UploadResult`. Capturing the token from the public API requires a follow-up on postguard-js to plumb it through. Track at [encryption4all/postguard-js#68](https://github.com/encryption4all/postguard-js/issues/68).
+
### Notify options
The upload is silent by default. Both recipient and sender mails are opt-in. Pass `notify` to enable either or both.
diff --git a/docs/sdk/js-errors.md b/docs/sdk/js-errors.md
index e904e77..7705fc5 100644
--- a/docs/sdk/js-errors.md
+++ b/docs/sdk/js-errors.md
@@ -80,7 +80,7 @@ try {
[Source: errors.ts#L31-L40](https://github.com/encryption4all/postguard-js/blob/a60716e0b4eaaed0f3763a2eebbcf6c39fc0560d/src/errors.ts#L31-L40)
-The user must start a new upload — there is no resume path once a session is gone. Catch this case before a generic `NetworkError` branch so the message is specific.
+Once this error fires, the session is gone and there is no resume path; the user must start a new upload. While the session is still valid, an in-flight upload can be rehydrated with [`resumeUpload`](/sdk/js-encryption#resume-an-interrupted-upload). Catch this case before a generic `NetworkError` branch so the message is specific.
## `YiviNotInstalledError`