Skip to content

feat: cloud upload — Google Drive & S3#36

Merged
jvillegasd merged 27 commits intomainfrom
feat/cloud
Mar 5, 2026
Merged

feat: cloud upload — Google Drive & S3#36
jvillegasd merged 27 commits intomainfrom
feat/cloud

Conversation

@jvillegasd
Copy link
Copy Markdown
Owner

@jvillegasd jvillegasd commented Mar 5, 2026

Summary

  • Google Drive upload with user-provided OAuth client ID via launchWebAuthFlow — no Chrome Web Store required
  • S3/S3-compatible upload with SigV4 signing, multipart for large files, and crash-resilient cleanup of orphaned uploads
  • Upload UI in both popup (Downloads tab) and options (History page) with provider picker when both are configured
  • Progress tracking with water-fill cloud icon animation, cancel with confirmation dialog, and live re-rendering on state changes
  • S3 secret key encryption at rest with AES-GCM via passphrase
  • Crash resilience: orphaned S3 multipart uploads aborted on startup, stuck UPLOADING states restored to COMPLETED
  • Provider abstraction: BaseCloudProvider pattern — add new providers by extending the base class

Cloud providers

Provider Auth Upload strategy
Google Drive OAuth 2.0 (user-provided client ID, launchWebAuthFlow) Simple multipart ≤ 5 MB, resumable chunked > 5 MB
S3 / compatible Access key + secret (optional AES-GCM encryption) Single PUT < 10 MB, multipart ≥ 10 MB

Test plan

  • Configure Google Drive: enter OAuth client ID, sign in, verify auto-save enables provider
  • Configure S3: enter credentials, copy CORS config to bucket, save
  • Upload a file from History with only one provider enabled — should upload directly
  • Upload with both providers enabled — should show provider picker dialog
  • Cancel an in-progress upload — confirm dialog appears, upload stops, icon clears
  • Kill service worker mid-S3-multipart-upload → restart → orphaned upload is aborted
  • Verify upload progress icon updates in real time and clears on completion/failure

🤖 Generated with Claude Code

jvillegasd and others added 27 commits March 4, 2026 09:50
Adds an About page to the options sidebar showing the extension icon,
dynamic version (from chrome.runtime.getManifest()), description, and
a "Made with ♥ by jvillegasd" link to the GitHub profile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add S3Client with SigV4 signing (single-part and multipart upload)
- Rewrite UploadManager to support both Google Drive and S3 concurrently
- Wire onBlobReady hook through download handlers to upload before blob revocation
- Add UPLOADING stage, cloud action buttons, and deferred upload via file picker in popup
- Add autoUpload toggles, provider presets, test connection, and CORS helper in S3 options
- Fix S3 enable checkbox show/hide to match Google Drive behaviour
- Add tabs to Advanced options view (Retry & Reliability, Detection Caches, Performance)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pill design

- Add .seg-tabs/.seg-tab to shared.css as a reusable pill/segmented control
- Remove duplicate provider-tab and advanced-tab CSS from options.html
- Update popup tab bar to use seg-tabs with full-width stretch
- Dissolve popup tab container with surface-1→surface-0 gradient (no hard border)
- Increase seg-tabs padding and tab height for better breathing room
- Fix active tab using surface-3 so it's visually elevated above surface-2 container
- Remove browser focus ring from seg-tab buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds passphrase-based AES-GCM encryption for the S3 secret access key
stored in chrome.storage.local, closing the OWASP insecure storage gap.

- New SecureStorage class (Web Crypto API only, zero deps): PBKDF2
  key derivation (SHA-256, 100k iterations) + AES-GCM 256-bit encrypt/decrypt
- Passphrase cached in chrome.storage.session (auto-cleared on browser close)
- StorageConfig.s3 gains secretKeyEncrypted?: EncryptedBlob alongside
  the existing plaintext fallback for unencrypted mode (opt-in)
- Options S3 tab: passphrase + confirm fields; save encrypts and clears
  plaintext key; load shows encrypted placeholder; test connection
  decrypts via session cache or prompts the user
- Service worker resolveS3Secret() decrypts before upload; skips S3
  with a warning if passphrase is absent after a browser restart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Auto-upload was architecturally unsound — it held the full file in memory
across two browser contexts simultaneously (offscreen + service worker)
and blocked blob URL revocation until the upload completed.

Removed:
- performCloudUpload() and its onBlobReady hook from blob-utils
- onBlobReady option from BasePlaylistHandler, DownloadManager, and all
  format/recording handlers (HLS, M3U8, DASH, recording)
- autoUpload field from StorageConfig, AppSettings, loadSettings, and
  both options UI sections (Drive + S3 checkboxes)
- uploadToDrive/uploadToS3 from DownloadState, DownloadManager,
  handleDownloadRequest, and DownloadRequestMessage

Manual deferred upload (file picker → UPLOAD_REQUEST message →
handleUploadRequestMessage) is fully preserved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the single-PUT "resumable" upload with a real chunked loop:
- Use correct upload endpoint (/upload/drive/v3 vs /drive/v3)
- Send X-Upload-Content-Type/Length in session init request
- Upload in 8 MB chunks (multiple of 256 KB per spec) with Content-Range headers
- Read Range response header on 308 to advance offset from server-confirmed position
- Handle 404 (session expired) and 5xx as hard errors
- Wire onProgress per chunk so the UI reflects actual byte progress

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop MULTIPART_THRESHOLD from 100 MB to PART_SIZE (10 MB) so all files
≥ 10 MB get chunked progress instead of a single blocking PUT.

S3 enforces a 5 MB minimum per part (except the last), so files < 10 MB
must remain as single PUT — the threshold cannot go lower than PART_SIZE.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add CloudProvider type ('googleDrive' | 's3') to shared messages
- Show inline provider picker when both providers are configured;
  skip picker when only one is enabled; block upload with alert when none
- Validate selected file is video/* before reading bytes; reject otherwise
- Route uploadBlob to a single chosen provider instead of allSettled across both
- Thread provider param through download-actions → service-worker → UploadManager

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce BaseCloudProvider abstract base class so UploadManager no
longer hard-codes per-provider branching. GoogleDriveClient and S3Client
now extend BaseCloudProvider and expose a unified upload() method
returning Promise<string>. UploadManager replaces concrete fields with a
Map<CloudProvider, BaseCloudProvider> registry — adding a new provider
requires only a new subclass + one registration line.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Completed downloads in Options → History now show an "Upload to cloud"
menu item. Opens a file picker, sends the file to the service worker
for S3/Google Drive upload. Shows a provider chooser dialog when both
are configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enhances the history page to provide better feedback during cloud uploads.
- Displays a circular progress indicator while an upload is in progress.
- Adds an "UPLOADED" badge to history items once successfully uploaded.
- Keeps the "Upload to cloud" action available even after completion.
- Hides uploading items from the active downloads tab so they are only tracked in history.
…uplicate guard

- CANCEL_UPLOAD: async handler that awaits IDB ops and cleans up tracking
  maps immediately after abort, preventing zombie records on delete
- Duplicate upload guard: reject if upload already in progress for same item
- onStateUpdate: check abort signal to stop writes between abort and cleanup
- Wire upload progress: onProgress now updates percentage and fires
  onStateUpdate so the cloud fill icon animates from 0% to 100%
- Abort check before success write: prevent overwriting cancelled state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace circular progress spinner with a water-fill cloud icon: gray
base cloud outline + arrow fills from bottom up with accent color.
Cloud borders stroke with accent, arrow interior fills with accent,
both rising like water as upload percentage increases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Thread AbortSignal through BaseCloudProvider, GoogleDriveClient, and
  S3Client so uploads can be cancelled mid-flight
- Add CANCEL_UPLOAD message type to MessageType enum
- Add cancel button styling for history upload progress indicator

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Persist in-flight multipart uploadIds to chrome.storage.local so orphaned
uploads can be aborted on service worker restart, preventing storage cost
leaks from crashed uploads. Also restore downloads stuck in UPLOADING stage
back to COMPLETED on startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch from getAuthToken() to launchWebAuthFlow() so users supply their
  own OAuth client ID at runtime (no manifest oauth2 section needed)
- Add client ID input field with step-by-step setup instructions in options
- Show redirect URI dynamically so users can copy it to Google Cloud Console
- Auto-save Drive settings (enabled=true) after successful authentication
- Extract persistDriveSettings() to DRY save logic between button and auth
- Update README with full Google Drive and S3 cloud upload setup guide
- Update cloud icon to new design

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Options page now listens for UPLOAD_COMPLETE messages and UPLOADING
stage transitions so history items update in real time on upload
failure/completion without requiring a page refresh. Service worker
broadcasts DOWNLOAD_PROGRESS after upload failure. Also reverts cloud
icons back to original stroke style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…load

Was hitting the metadata API base URL instead of the upload base URL,
causing a parse error on file uploads under 5 MB.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip error logging and UploadError wrapping for AbortError (expected on
cancel). Also fix DOMException stringification producing [object DOMException].

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CLAUDE.md: Replace "(Planned)" cloud section with full documentation
covering upload flow, crash resilience, Google Drive OAuth setup,
provider abstraction, S3 secret encryption, and updated project structure.

README.md: Already updated in prior commit — no additional changes needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jvillegasd jvillegasd self-assigned this Mar 5, 2026
@jvillegasd jvillegasd merged commit 04f15f3 into main Mar 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant