# QR Auth + Addon URL Entry via Cloudflare Worker

## Overview

The Cloudflare Worker at `~/Unspooled/services/addon-config-worker/` handles three QR-based flows:

1. **Debrid OAuth** (Real-Debrid, Premiumize) — device code grant
2. **Debrid API key entry** (TorBox, AllDebrid, Debrider, Offcloud) — phone web form
3. **Addon URL entry** (any Stremio addon) — phone web form

All flows share the same pattern: TV shows QR → phone scans → enters credential on phone → TV polls worker → completes.

## Why Cloudflare Workers

- Globally distributed (<50ms response), free tier (100k req/day)
- Durable Objects for session state with automatic TTL expiry
- No video scraping = no CF ToS issues
- Already had the addon-config-worker pattern established

## Architecture

```
TV App                              Cloudflare Worker                  Provider API
  │                                      │                                 │
  │── GET /debrid/oauth/start/rd ──────→│── POST /oauth/v2/device/code ─→│
  │←─ { user_code, verification_url } ──│←─ device_code/user_code ───────│
  │ (shows QR + user code on TV)        │                                 │
  │                                     │   Phone: user browses verification_url, enters user_code
  │── GET /debrid/auth/status/{id} ────→│── POST /oauth/v2/token ────────→│
  │←─ { status: "completed", token } ──│←─ access_token ─────────────────│
  │ (connects debrid via API)           │                                 │
```

## Durable Object: AuthSessionDO

File: `~/Unspooled/services/addon-config-worker/src/auth_session_do.ts`
Models: `src/auth_models.ts`
HTML pages: `src/auth_page.ts`

Three session types share one DO:

| Session | authType | Flow |
|---------|----------|------|
| Real-Debrid | `oauth` | `startOAuth("realdebrid")` → `pollOAuth()` |
| Premiumize | `oauth` | `startOAuth("premiumize")` → `pollOAuth()` |
| TorBox | `apikey` | `createKeySession("torbox")` → `submitKey(key)` |
| AllDebrid | `apikey` | `createKeySession("alldebrid")` → `submitKey(key)` |
| Addon URL | — | `createUrlSession()` → `submitUrl(url)` |

## Android Client

### DebridAuthApiClient.kt
File: `~/Unspooled/.../data/addon/cloud/DebridAuthApiClient.kt`

```kotlin
val client = DebridAuthApiClient()
val session = client.createSession("realdebrid")  // POST to worker
val qrUrl = client.buildAuthUrl(session.code)      // QR encodes this URL
val pollJob = client.pollForCompletion(session.code) { result ->
    DebridService.connect(provider, result.token)
}
```

### AddonUrlApiClient.kt
File: `~/Unspooled/.../data/addon/cloud/AddonUrlApiClient.kt`

```kotlin
val client = AddonUrlApiClient()
val session = client.createSession()               // POST to worker
val qrUrl = client.buildUrlEntryUrl(session.code)  // QR encodes this URL
val pollJob = client.pollForCompletion(session.code) { result ->
    AddonRepository.addAddon(result.configuredUrl)
}
```

## Routes (added to addon-config-worker/src/index.ts)

| Route | Method | Handler | Description |
|-------|--------|---------|-------------|
| `/debrid/oauth/start/{service}` | GET | `handleOAuthStart` | Initiate OAuth for RD/PM |
| `/debrid/key/start/{service}` | GET | `handleKeyStart` | Create API key session |
| `/debrid/auth/form/{code}` | GET | `handleAuthForm` | Serve HTML form (OAuth or key) |
| `/debrid/auth/submit/{code}` | POST | `handleAuthSubmit` | Submit API key |
| `/debrid/auth/status/{code}` | GET | `handleAuthStatus` | Poll result |
| `/debrid/auth/cancel/{code}` | GET | `handleAuthCancel` | Cancel |
| `/addon/url/start` | GET | `handleAddonUrlStart` | Create URL session |
| `/addon/url/form/{code}` | GET | `handleAddonUrlForm` | Serve addon URL entry form |
| `/addon/url/submit/{code}` | POST | `handleAddonUrlSubmit` | Submit + validate manifest URL |
| `/addon/url/status/{code}` | GET | `handleAddonUrlStatus` | Poll result |
| `/addon/url/cancel/{code}` | GET | `handleAddonUrlCancel` | Cancel |

## OAuth Provider Endpoints

### Real-Debrid
- Device code: `POST https://api.real-debrid.com/oauth/v2/device/code` with `client_id=X245J4A8I7W40`
- Token: `POST https://api.real-debrid.com/oauth/v2/token` with `client_id`, `code`, `grant_type=http://oauth.net/grant_type/device/1.0`
- Response includes `user_code` (shown to user), `device_code` (used for polling), `interval` (polling interval), `expires_in` (TTL)

### Premiumize
- Device code: `POST https://www.premiumize.me/device/code` with `client_id=unspooled_tv`
- Token: `POST https://www.premiumize.me/device/token` with same params

## Deploy

```bash
cd ~/Unspooled/services/addon-config-worker
npx wrangler secret put BACKEND_SECRET    # Required for authenticating Android app requests
npx wrangler deploy
```

The `PUBLIC_HOST` var in `wrangler.toml` must match the deployed Worker URL (e.g., `https://addon-config-worker.rurouni.workers.dev`).

## Related Android Files

- `DebridAuthViewModel.kt` — manages QR auth state in debrid settings
- `DebridServicesScreen.kt` — UI with QR overlay for auth
- `AddonsViewModel.kt` — manages QR addon URL entry state
- `AddonsScreen.kt` — UI with "📱 Scan QR" button + overlay
- `QrCodeGenerator.kt` — ZXing QR code bitmap generator
- `DeviceIpAddress.kt` — local IP detection for QR URL
