Files
bitvid/docs/storage-r2.md
T

105 lines
5.5 KiB
Markdown

# Cloudflare R2 integration (r2Service)
## Purpose and scope
`js/services/r2Service.js` is the client-side coordinator for Cloudflare R2 uploads. It owns:
- Local persistence of Cloudflare credentials and bucket metadata used by the Upload modal.
- Ensuring the target bucket exists, has CORS rules, and has a public domain (custom or managed).
- Uploading the selected video/HLS asset via multipart S3 uploads and publishing the resulting
public URL into the video note payload.
The service is used from the Upload modal UI (via `js/ui/components/UploadModal.js`) and is not a
backend integration.
## Assets stored in R2
R2 is used as the URL-first playback source. The upload path supports a single file per upload, but
file types include:
- Video files (MP4/WebM/MOV/MKV/MPG/MPEG).
- HLS/DASH assets (manifest `.m3u8`/`.mpd` and segment files like `.ts`/`.m4s`).
- Related static media if the operator chooses to upload them separately (images, subtitles such as
`.vtt`/`.srt`), which benefit from cache-friendly headers set in the upload flow.
The object key format is generated by `buildR2Key()` in `js/r2.js` and looks like:
`u/<npub>/<YYYY>/<MM>/<slug>.<ext>`, which keeps uploads scoped by user and month.
## Upload lifecycle
1. **Settings saved locally**
- The Upload modal calls `r2Service.saveSettings()` to persist Cloudflare credentials and
optional domain configuration. Settings are stored in IndexedDB (`bitvidSettings` → `kv`)
with a localStorage fallback (`bitvid:r2Settings`).
2. **Bucket and domain preparation** (`ensureBucketConfigForNpub`)
- If an API token is provided, the service:
- Creates the bucket if it does not exist.
- Applies CORS rules for the current origin.
- Attempts to attach a custom domain using the configured `zoneId` and `baseDomain`.
- If the custom domain cannot be activated or already exists, it falls back to the managed
`*.r2.dev` domain.
- If no API token is available, it:
- Uses the last-known bucket/domain (or builds a managed `*.r2.dev` URL),
- Tries to set CORS via S3 keys when possible.
3. **Upload and publish**
- The service builds the R2 object key and public URL.
- `multipartUpload()` uploads the file using S3 multipart uploads.
- On success, `publishVideoNote()` is called with the hosted URL, so playback can be URL-first
with magnet fallback.
4. **UI feedback**
- The Upload modal receives progress, status, and managed-domain fallback warnings via
`r2Service` events.
## Download / playback flow
There is no direct “download” API for R2 in `r2Service`. Instead, R2 provides the public URL that
gets saved into the video note payload. Playback uses that URL as the primary source and only falls
back to magnet/WebTorrent when needed, preserving the URL-first playback promise.
## Configuration keys and where they live
**Stored locally (IndexedDB / localStorage):**
- `accountId`: Cloudflare account ID.
- `accessKeyId`: R2 S3 access key ID.
- `secretAccessKey`: R2 S3 secret access key.
- `apiToken`: Cloudflare API token used for bucket/domain management.
- `zoneId`: Cloudflare zone ID (required for custom domain attachment).
- `baseDomain`: Base domain used to derive per-user subdomains (e.g., `videos.example.com`).
- `buckets`: Cached map of `npub` → `{ bucket, publicBaseUrl, domainType, lastUpdated }`.
**Environment / global flags:**
- `globalThis.__BITVID_DISABLE_NETWORK_IMPORTS__`: disables the AWS SDK network import used by the
R2 S3 client loader (`js/storage/r2-s3.js`). When true, uploads will fail with a load error until
the SDK is bundled or otherwise provided.
**Config files:**
- There are no R2-specific keys in `config/` today; all R2 settings are entered through the UI and
stored locally in the browser.
## Retry and fallback behavior
- **Bucket/domain setup:**
- Custom domain attachment is attempted when `apiToken`, `zoneId`, and `baseDomain` are present.
- If custom domain attachment fails or is unavailable, the service falls back to the managed
`*.r2.dev` domain and surfaces a warning to the user.
- **CORS:**
- CORS setup is attempted via the API token or (secondarily) via S3 credentials. Failures are
logged and do not block the upload, but missing CORS may prevent playback from browsers.
- **Multipart uploads:**
- There is no custom retry loop inside `multipartUpload()`. Failures propagate to the UI; the
caller can retry by re-initiating the upload.
## Security considerations
- **Credential storage:** R2 credentials and API tokens are stored in browser storage. Treat them as
sensitive and avoid sharing browser profiles. Clear settings via the Upload modal when done.
- **Public exposure:** The generated URLs are public; uploaded assets should be safe for public
access. Use signed URLs only if you add a separate access layer.
- **CORS and origins:** CORS rules are scoped to the current origin to avoid overly broad exposure.
- **Domain ownership:** Custom domain attachment requires `zoneId` and a Cloudflare API token with
the necessary R2 and DNS permissions. Keep tokens scoped to least privilege.
## Impact on uploads and playback
- **Uploads:** Successful R2 uploads produce a hosted URL stored in the video note payload, enabling
URL-first playback. Metadata such as MIME type and the public URL are also stitched into the NIP-71
metadata block during publish.
- **Playback:** The hosted URL is the primary stream source. If it fails, playback can still fall
back to the magnet/WebTorrent path, so the R2 integration improves reliability without removing
P2P fallback.