Skip to content

Wire upload resume: capture recovery_token, implement GET /status for cross-restart resume #65

@rubenhensen

Description

@rubenhensen

Cryptify shipped the upload-resume protocol (cryptify#148, cryptify#145, cryptify#144) as part of cryptify#136. pg-js already covers some of it but the cross-restart path is missing.

What pg-js already does

  • `storeChunkWithRetry` sends `prevToken` on retry → exercises cryptify's idempotent-retry path (#145).
  • `throwSessionExpiredOrNetworkError` parses the structured 404 body and surfaces `UploadSessionExpiredError` → uses cryptify#144.
  • In-process retries on a flaky connection work today.

What is missing

1. `recovery_token` is not captured from `initUpload`

`POST /fileupload/init` now returns a 32-byte hex `recovery_token` in the JSON body. `initUpload` in `src/api/cryptify.ts` only reads `uuid` from the JSON and the rolling token from the `cryptifytoken` header. The recovery token is silently dropped.

It needs to be returned from `initUpload` so the caller can persist it. Suggested shape:

```ts
interface FileState {
token: string;
uuid: string;
recoveryToken: string; // new — required for cross-restart resume
prevToken?: string;
}
```

2. No `GET /fileupload/{uuid}/status` call

When an addon/website is resumed after a refresh or process restart, it has `{uuid, recoveryToken}` from local storage but no `cryptify_token`. There's currently no pg-js function that calls `GET /fileupload/{uuid}/status` with `X-Recovery-Token` to rehydrate `FileState`.

Suggested API:

```ts
export async function resumeUpload(
cryptifyUrl: string,
uuid: string,
recoveryToken: string,
signal?: AbortSignal
): Promise<{ state: FileState; uploaded: number }>;
```

  • Returns the current `uploaded` byte offset and the rehydrated `FileState` (token, prevToken if present).
  • 404 `upload_session_not_found` → `UploadSessionExpiredError` (reuse the existing throw helper).

3. Persistence is consumer-owned

pg-js shouldn't try to persist `{uuid, recoveryToken}` itself — the storage layer differs per host (Office addin roamingSettings, Thunderbird `browser.storage`, website IndexedDB). Just expose the values and let the consumer decide. The consumer issues are tracked downstream:

Related — separate issue

Download-side Range support has its own bug: pg-js's `downloadRange` already requires `206 Partial Content`, but cryptify's `FileServer` never returns 206 (it uses Rocket's `sized_body`, which has no Range handling). That's tracked at cryptify#153 — server-side fix, no pg-js change needed once cryptify honours Range.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions