# Cloudflare Workers Durable Objects — Critical Pitfalls

## `setAlarm` — duration vs. timestamp

**Bug:** `this.storage.setAlarm(SESSION_TTL_MS)` where `SESSION_TTL_MS = 900000` (15 minutes in ms).

**Why it fails:** `setAlarm(scheduledTime: Date | number)` treats a `number` as an **absolute epoch timestamp**, not a duration. `900000` → epoch 1970-01-01T00:15:00 → already past → alarm fires immediately → sessions get cancelled on creation.

**Fix:** Always pass `Date.now() + duration_ms`:
```typescript
await this.storage.setAlarm(Date.now() + SESSION_TTL_MS);
```

## DO namespace isolation

Each Cloudflare Worker has its own independent DO namespace. Deploying the same DO class to two workers (`unspooled-config-v3` and `addon-config-worker`) creates separate storage pools. Sessions created on Worker A are NOT visible on Worker B.

**Implication:** If the Android app's `WORKER_BASE_URL` points to Worker A but QR form `PUBLIC_HOST` points to Worker B (from wrangler.toml vars), form submissions land on the wrong worker's DO namespace → 404/session-not-found.

**Fix:** Keep `PUBLIC_HOST` in `wrangler.toml` [vars] consistent with the actual worker domain. When deploying to multiple names (old-name compatibility), use separate wrangler configs with correct `PUBLIC_HOST`.

## `stub.fetch()` HTTP routing (free-plan requirement)

Free CF Workers plan does NOT support `stub.method()` RPC-style DO calls. Always use `stub.fetch(new Request(url))` with HTTP dispatch:

```typescript
// Correct (free plan):
const stub = env.AUTH_SESSIONS.get(id);
await stub.fetch(new Request(env.PUBLIC_HOST + "/debrid/auth/status"));

// Incorrect (paid plan only):
await stub.handleAuthStatus("xyz");
```

## QR Auth Flow — Complete Architecture

```
Android TV                    Cloudflare Worker              Phone Browser
    |                              |                              |
    |-- POST /api/debrid/auth-sessions -->|                              |
    |   {providerId: "realdebrid"}       |                              |
    |<-- 201 {code, authUrl} ------------|                              |
    |                              |                              |
    |-- Show QR: {code} ------------|---- QR URL: /debrid/auth/{code} ->|
    |                              |                              |
    |   poll GET /api/debrid/auth-sessions/{code} ---->           | (user enters API key)
    |<-- {status:"pending"} ------------|   POST /debrid/auth/submit/{code}
    |                              |<-- {apiKey:"xxx"} ------------|
    |   poll again ----------------->   |   status→"completed"    |
    |<-- {status:"completed", configuredApiKey:"xxx"} ------------|
    |                              |                              |
    |   debridService.connect(apiKey)  |                              |
```

### Android → Worker provider ID mapping

The `DebridProvider` enum names use underscores (`REAL_DEBRID`, `TOR_BOX`). When sending to the worker:
```kotlin
// DON'T: provider.name.lowercase() → "real_debrid" → worker expects "realdebrid" → 400 error
// DO:
val result = debridAuthApiClient.createSession(provider.name.lowercase().replace("_", ""))
```

Worker should normalize too for resilience:
```typescript
const providerId = (body.providerId || "").replace(/_/g, "").toLowerCase();
```

### Worker must return `configuredApiKey` in poll response

The Android `DebridAuthSessionResponse` expects this field. If the worker filters it out, the app will poll indefinitely with no API key:
```typescript
// DO storage uses apiKey / token; worker must map it:
return json({
    code: s.code, service: s.service, authType: s.authType, status: s.status,
    userCode: s.userCode,
    configuredApiKey: s.apiKey || s.token || null,  // ← critical
    error: s.error || null,
});
```

## Deployment

Wrangler token is stored in `/tmp/cf_token`. When `$(cat /tmp/cf_token)` fails in terminal tool (heredoc parsing issues), use `execute_code` with Python f-strings:

```python
from hermes_tools import terminal
token = open('/tmp/cf_token').read().strip()
r = terminal(
    command=f"cd /path/to/worker && CLOUDFLARE_API_TOKEN=*** npx wrangler deploy 2>&1",
    timeout=120
)
```

Deploy to multiple worker names:
```bash
npx wrangler deploy                    # uses wrangler.toml name
npx wrangler deploy --config old-name.toml  # different name + PUBLIC_HOST
```
