Files
bitvid/docs/storage-r2.md
T

5.5 KiB

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.