# 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////.`, which keeps uploads scoped by user and month. ## Setup Guide: Getting your Cloudflare Credentials To enable R2 uploads, you must provide a Cloudflare API Token. This allows the app to automatically create buckets, configure CORS, and enable the public `r2.dev` domain for your videos. ### Step 1: Get your Account ID 1. Log in to the **Cloudflare Dashboard**. 2. Navigate to **R2** in the sidebar. 3. Copy the **Account ID** from the right-hand sidebar. ### Step 2: Create an S3 Access Key (for uploads) 1. In the R2 dashboard, click **Manage R2 API Tokens**. 2. Click **Create API token**. 3. Select **Admin Read & Write** permissions. 4. Click **Create API Token**. 5. Copy the **Access Key ID** and **Secret Access Key**. ### Step 3: Create a Cloudflare API Token (for automation) 1. Go to **My Profile** > **API Tokens**. 2. Click **Create Token** -> **Custom Token** -> **Get started**. 3. **Token name:** e.g., "BitVid R2 Automation". 4. **Permissions:** * **Account** -> **Workers R2 Storage** -> **Edit** 5. **Account Resources:** Select your account. 6. Click **Continue to summary** -> **Create Token**. 7. Copy the token string. ### Step 4: Configure the App 1. Open the Upload Modal in the app. 2. Expand **Storage Settings**. 3. Paste the **Account ID**. 4. Paste the **Access Key ID** and **Secret Access Key** (from Step 2). 5. Paste the **Cloudflare API Token** (from Step 3). 6. Click **Save**. The app is now ready to upload. It will automatically configure your bucket on the first upload. ## 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.