diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md deleted file mode 100644 index 755d519..0000000 --- a/DEPLOYMENT.md +++ /dev/null @@ -1,521 +0,0 @@ -# RYS Premium Server Deployment Guide - -Complete guide to deploying the premium subscription server and testing the monetization flow. - -**Server URL:** `https://server.lawrencehook.com/rys/` - ---- - -## Table of Contents - -1. [Prerequisites](#prerequisites) -2. [Stripe Setup](#stripe-setup) -3. [AWS SES Setup](#aws-ses-setup) -4. [Server Deployment](#server-deployment) -5. [Apache Configuration](#apache-configuration) -6. [Extension Setup](#extension-setup) -7. [Testing](#testing) -8. [Troubleshooting](#troubleshooting) -9. [Maintenance](#maintenance) - ---- - -## Prerequisites - -- AWS Lightsail instance (Bitnami stack) -- Node.js v20+ -- Stripe account (test mode for development) -- AWS SES configured for sending emails - ---- - -## Stripe Setup - -### Create Products - -1. Go to https://dashboard.stripe.com/test/products -2. Click **"+ Add product"** - -**Monthly Plan:** -- Name: `RYS Premium Monthly` -- Pricing: `$3.00 USD` / `month` / `recurring` -- Save and copy the **Price ID** (starts with `price_...`) - -**Yearly Plan:** -- Name: `RYS Premium Yearly` -- Pricing: `$24.00 USD` / `year` / `recurring` -- Save and copy the **Price ID** - -### Get API Keys - -1. Go to https://dashboard.stripe.com/test/apikeys -2. Copy the **Secret key** (starts with `sk_test_...`) - -### Configure Billing Portal - -1. Go to Settings → Billing → Customer portal -2. Enable the customer portal -3. Configure allowed actions: - - [x] Update payment methods - - [x] View invoice history - - [x] Cancel subscriptions -4. Save changes - -### Create Webhook Endpoint - -1. Go to https://dashboard.stripe.com/test/webhooks -2. Click **"+ Add endpoint"** -3. Endpoint URL: `https://server.lawrencehook.com/rys/webhook/stripe` -4. Select events: - - `checkout.session.completed` - - `customer.subscription.created` - - `customer.subscription.updated` - - `customer.subscription.deleted` - - `invoice.payment_failed` - - `invoice.payment_succeeded` -5. Click **"Add endpoint"** -6. Copy the **Signing secret** (starts with `whsec_...`) - ---- - -## AWS SES Setup - -### Verify Sender Email/Domain - -1. In AWS Console → SES → Verified identities -2. Click "Create identity" -3. For email: click the verification link sent to you -4. For domain: add the DNS records provided - -### Request Production Access - -SES starts in sandbox mode (can only send to verified emails). - -1. Go to SES → Account dashboard -2. Click "Request production access" -3. Fill out the form explaining your use case -4. Wait for approval (usually 24-48 hours) - -### IAM Credentials - -If not using instance roles, create IAM credentials: - -1. Create a new user (e.g., `rys-premium-ses`) -2. Attach policy: `AmazonSESFullAccess` -3. Create access keys -4. Copy the **Access Key ID** and **Secret Access Key** - ---- - -## Server Deployment - -### SSH to Lightsail Instance - -```bash -ssh bitnami@your-instance-ip -``` - -### Install Node.js 20+ (if needed) - -```bash -curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - -sudo apt-get install -y nodejs -node --version # Should show v20.x.x -``` - -### Install PM2 - -```bash -sudo npm install -g pm2 -``` - -### Clone and Setup Repository - -```bash -cd ~/github -git clone https://github.com/lawrencehook/remove-youtube-suggestions.git -cd remove-youtube-suggestions -git checkout feature/monetize -cd server -npm install -``` - -### Create Environment File - -```bash -nano .env -``` - -Paste the following (fill in your values): - -```bash -# Server -PORT=3005 -BASE_URL=https://server.lawrencehook.com/rys - -# JWT (generate with: openssl rand -base64 32) -JWT_SECRET=your-random-secret-at-least-32-characters-long - -# Stripe -STRIPE_SECRET_KEY=sk_test_YOUR_KEY -STRIPE_PRICE_MONTHLY=price_YOUR_MONTHLY_ID -STRIPE_PRICE_YEARLY=price_YOUR_YEARLY_ID -STRIPE_WEBHOOK_SECRET=whsec_YOUR_WEBHOOK_SECRET - -# AWS SES (uses instance role, no explicit credentials needed) -AWS_REGION=us-east-1 -EMAIL_FROM=noreply@lawrencehook.com -``` - -Save: `Ctrl+O`, `Enter`, `Ctrl+X` - -### Create Data Directory - -```bash -mkdir -p data -echo '[]' > data/grandfathered.json -``` - -To add grandfathered users (past donors with lifetime access): -```bash -echo '["donor1@example.com", "donor2@example.com"]' > data/grandfathered.json -``` - -### Test Server Starts - -```bash -npm start -``` - -Expected output: -``` -Loaded X grandfathered emails -RYS Premium Server running on port 3005 -Base URL: https://server.lawrencehook.com/rys -``` - -Press `Ctrl+C` to stop. - -### Start with PM2 - -```bash -pm2 start src/index.js --name rys-premium -pm2 save -``` - -### Configure PM2 Auto-Start on Reboot - -```bash -pm2 startup -# Run the command it outputs (starts with: sudo env PATH=...) -pm2 save -``` - ---- - -## Apache Configuration - -Your Lightsail instance uses Bitnami's Apache with path-based proxying. - -### Edit SSL Config - -Edit your local deployments repo: - -```bash -nano ~/github/deployments/opt/bitnami/apache/conf/bitnami/bitnami-ssl.conf -``` - -Add these lines inside the `` block: - -```apache -ProxyPass /rys/ http://localhost:3005/ -ProxyPassReverse /rys/ http://localhost:3005/ -``` - -### Deploy Apache Config - -```bash -# Local machine -cd ~/github/deployments -git add -A && git commit -m "Add RYS Premium server proxy config" && git push - -# On server -cd ~/github/deployments && git pull -./update_apache_conf.sh -``` - ---- - -## Extension Setup - -### Verify Config Points to Production - -Check `src/shared/config.js`: - -```javascript -const PREMIUM_CONFIG = { - SERVER_URL: 'https://server.lawrencehook.com/rys', - // ... -}; -``` - -### Build the Extension - -**For Chrome:** -```bash -./make_chrome.sh -``` - -**For Firefox:** -```bash -./make_firefox.sh -``` - -### Load in Chrome - -1. Navigate to `chrome://extensions/` -2. Enable **"Developer mode"** (toggle in top-right) -3. Click **"Load unpacked"** -4. Select: `chrome_extension/` - -### Load in Firefox - -1. Navigate to `about:debugging#/runtime/this-firefox` -2. Click **"Load Temporary Add-on..."** -3. Select: `src/firefox_manifest.json` - ---- - -## Testing - -### Automated Tests - -```bash -cd server -npm test -``` - -All tests should pass (currently 46). - -### Health Check - -```bash -curl https://server.lawrencehook.com/rys/health -# Expected: {"status":"ok","timestamp":"..."} -``` - -### Magic Link Flow - -**Via curl:** -```bash -curl -X POST https://server.lawrencehook.com/rys/auth/send-magic-link \ - -H "Content-Type: application/json" \ - -d '{"email":"your-email@example.com"}' -# Expected: {"request_id":"abc123-..."} -``` - -**Via extension:** -1. Go to any YouTube page -2. Click the RYS extension icon -3. Click the gear icon (settings menu) -4. Click **"Sign In"** -5. Enter your email and click **"Send Sign-In Link"** -6. Check your email and click the magic link -7. Return to extension — should show "Signed in successfully" - -### License Check - -After signing in: -1. Click gear icon → **"Account"** -2. Should show your email and status: **"Free"** - -**For grandfathered users:** -- Add email to `data/grandfathered.json` on server -- Restart: `pm2 restart rys-premium` -- Sign out and sign back in -- Status should show: **"Premium (Lifetime)"** - -### Checkout Flow - -1. Open Account modal -2. Click **"Upgrade to Premium"** -3. Select a plan (Monthly or Yearly) -4. Click **"Continue to Checkout"** -5. Complete payment with test card: - - Card: `4242 4242 4242 4242` - - Expiry: Any future date (e.g., `12/34`) - - CVC: Any 3 digits (e.g., `123`) - - ZIP: Any valid ZIP (e.g., `12345`) -6. After success page, return to extension -7. Open Account — status should show **"Premium Active"** - -### Billing Portal - -1. With an active subscription, open Account modal -2. Click **"Manage Subscription"** -3. Stripe billing portal opens in new tab -4. Can cancel subscription here for testing - -### Webhook Verification - -1. Go to Stripe Dashboard → Webhooks → your endpoint -2. Click **"Send test webhook"** -3. Select `checkout.session.completed` -4. Check server logs: `pm2 logs rys-premium` - -### Extension Smoke Tests - -- **Sign-in flow:** Send magic link → click link → see "Signed in successfully" -- **Poll cancel:** Start sign-in → hit Cancel → spinner stops, no sign-in after cancel -- **Premium gate:** Mark option as premium → non-premium click opens upgrade modal -- **License refresh:** Open Account → premium label reflects correct status -- **Upgrade flow:** Choose plan → click checkout → Stripe checkout opens -- **Billing portal:** With subscription → click "Manage Subscription" → portal opens - -### Pre-Launch Checklist - -- [ ] All automated tests pass -- [ ] Server starts without errors -- [ ] `/health` endpoint returns OK -- [ ] Magic link emails are received -- [ ] Magic link verification works -- [ ] Grandfathered users show as premium -- [ ] Stripe checkout creates a session -- [ ] Stripe webhook is configured and verified -- [ ] Billing portal is accessible -- [ ] SSL certificate is valid -- [ ] CORS works from extension origins - -### Test Cards Reference - -| Card | Result | -|------|--------| -| `4242 4242 4242 4242` | Success | -| `4000 0000 0000 0002` | Declined | -| `4000 0025 0000 3155` | Requires authentication | - ---- - -## Troubleshooting - -### Server won't start - -```bash -pm2 logs rys-premium --lines 50 -``` - -Common issues: -- Missing .env variables -- JWT_SECRET too short (needs 32+ chars) -- Port already in use - -### Magic link emails not sending - -- Check SES is configured -- Verify EMAIL_FROM domain is verified in SES -- If in SES sandbox, recipient must also be verified - -### CORS errors in extension - -```bash -# Verify server is running -curl http://localhost:3005/health -``` - -### SSL certificate issues - -```bash -sudo /opt/bitnami/bncert-tool # regenerate if needed -``` - -### Extension can't connect - -```bash -# Test endpoint -curl https://server.lawrencehook.com/rys/health - -# Check Apache config -sudo apachectl configtest -``` - ---- - -## Maintenance - -### Update Server Code - -```bash -cd ~/github/remove-youtube-suggestions -git pull -git checkout feature/monetize -cd server -npm install -pm2 restart rys-premium -``` - -### View Logs - -```bash -pm2 logs rys-premium # Tail logs -pm2 logs rys-premium --lines 100 # Last 100 lines -``` - -### Server Commands - -```bash -pm2 restart rys-premium # Restart -pm2 stop rys-premium # Stop -pm2 status # Check status -``` - -### Add to Deployment Script - -Add to `~/github/deployments/run`: - -```bash -# RYS Premium Server (pm2) -cd /home/bitnami/github/remove-youtube-suggestions && git checkout -- . && git pull && git checkout feature/monetize; -cd server && npm install && npm audit fix; -pm2 restart rys-premium || pm2 start src/index.js --name rys-premium; -``` - -### Updating Grandfathered List - -1. Edit `data/grandfathered.json` -2. Restart: `pm2 restart rys-premium` - ---- - -## Environment Variables Reference - -| Variable | Description | Example | -|----------|-------------|---------| -| `PORT` | Server port | `3005` | -| `BASE_URL` | Public URL | `https://server.lawrencehook.com/rys` | -| `JWT_SECRET` | 32+ char secret | (generate with `openssl rand -base64 32`) | -| `STRIPE_SECRET_KEY` | Stripe API secret | `sk_test_...` | -| `STRIPE_PRICE_MONTHLY` | Monthly plan price ID | `price_...` | -| `STRIPE_PRICE_YEARLY` | Yearly plan price ID | `price_...` | -| `STRIPE_WEBHOOK_SECRET` | Webhook signing secret | `whsec_...` | -| `AWS_REGION` | AWS region for SES | `us-east-1` | -| `EMAIL_FROM` | Sender email address | `noreply@lawrencehook.com` | - ---- - -## API Endpoints Reference - -| Endpoint | Method | Auth | Description | -|----------|--------|------|-------------| -| `/health` | GET | No | Health check | -| `/auth/send-magic-link` | POST | No | Send magic link email | -| `/auth/verify` | GET | No | Magic link target (HTML) | -| `/auth/poll` | GET | No | Poll for auth status | -| `/license/check` | GET | Yes | Check premium status | -| `/checkout/create` | POST | Yes | Create Stripe checkout | -| `/checkout/success` | GET | No | Success page (HTML) | -| `/checkout/cancel` | GET | No | Cancel page (HTML) | -| `/billing/portal` | POST | Yes | Create billing portal session | -| `/billing/return` | GET | No | Return page (HTML) | -| `/webhook/stripe` | POST | No* | Stripe webhook | - -*Authenticated via Stripe signature diff --git a/TESTING.md b/TESTING.md deleted file mode 100644 index 6fb18f3..0000000 --- a/TESTING.md +++ /dev/null @@ -1,357 +0,0 @@ -# Manual Testing Guide — Premium Subscription - -This document covers all manual testing paths for the premium monetization feature. - -## Prerequisites - -- Extension loaded unpacked in Chrome (or Firefox) -- Server running locally or deployed to `server.lawrencehook.com` -- Stripe test mode configured with test price IDs -- AWS SES configured (or use a local email trap) -- At least one email in `data/grandfathered.json` for grandfathered testing -- A separate non-grandfathered email for regular user testing -- Stripe CLI installed for webhook testing (`stripe listen --forward-to localhost:3000/rys/webhook/stripe`) - ---- - -## 1. Initial State (Not Signed In) - -### 1.1 Options page loads correctly -- [ ] Open extension popup — settings menu shows "Sign In" (not "Account") -- [ ] "Donate" link visible in header, links to PayPal - -### 1.2 Free features work without sign-in -- [ ] Toggle a non-premium feature (e.g., "Hide homepage recommendations") -- [ ] Verify it takes effect on YouTube - -### 1.3 Premium feature click — not signed in -- [ ] Click any premium-gated toggle (marked with a lock/premium indicator) -- [ ] Verify the "Premium Required" modal appears -- [ ] Click "Cancel" — modal closes, setting unchanged -- [ ] Click the premium toggle again, then click "Sign In" in the modal -- [ ] Verify a new tab opens to `main.html?signin=1` and the popup closes - -### 1.4 Schedule and Password (premium settings menu items) -- [ ] Click "Schedule" in the settings menu — Premium Required modal appears -- [ ] Click "Password" in the settings menu — Premium Required modal appears - ---- - -## 2. Sign-In Flow - -### 2.1 Happy path — sign in from new tab -- [ ] From the options tab (`main.html?signin=1`), sign-in modal opens automatically -- [ ] Enter a valid email, press Enter (or click send button) -- [ ] Modal transitions to waiting state with countdown timer -- [ ] Check email — magic link email received from the configured sender -- [ ] Click magic link — browser opens server success page ("You're signed in!") -- [ ] Return to extension tab — modal closes, status shows "Signed in successfully" -- [ ] Header now shows "Account" instead of "Sign In" - -### 2.2 Sign in from popup -- [ ] Open extension popup, click "Sign In" -- [ ] Verify popup closes and a new tab opens to `main.html?signin=1` -- [ ] Complete the sign-in flow in the new tab - -### 2.3 Invalid email -- [ ] Enter an invalid email (no @ symbol) -- [ ] Verify "Please enter a valid email" status message appears -- [ ] No network request is made - -### 2.4 Rate limiting -- [ ] Send 5 magic link requests for the same email in rapid succession -- [ ] On the 6th attempt, verify a rate limit error message appears -- [ ] Wait for the rate limit window to pass (or clear rate limit data on server) - -### 2.5 Cancel during polling -- [ ] Start sign-in flow, reach waiting state -- [ ] Click "Cancel" button -- [ ] Verify modal closes and polling stops (no continued network requests) -- [ ] Verify user is still not signed in - -### 2.6 Poll timeout -- [ ] Start sign-in flow but do NOT click the magic link -- [ ] Wait for the 16-minute countdown to reach 0 -- [ ] Verify error state appears with "Verification timed out" message -- [ ] Click "Retry" — email form reappears - -### 2.7 Expired magic link -- [ ] Start sign-in flow, wait >15 minutes, then click the magic link -- [ ] Verify the server shows an error page (link expired) -- [ ] Extension should show error state when poll detects 404 - -### 2.8 Multiple sign-in attempts -- [ ] Start sign-in, reach waiting state -- [ ] Close modal, reopen, start a new sign-in with a different email -- [ ] Verify the previous polling is aborted (no interference) - ---- - -## 3. Signed-In, Free User - -### 3.1 Account modal -- [ ] Click "Account" in settings menu -- [ ] Verify Account modal opens showing: - - Your email address - - "Free Plan" status - - "Upgrade" button visible - - "Billing" button hidden - - "Sign Out" button visible - -### 3.2 Premium feature click — signed in, not premium -- [ ] Click a premium feature toggle -- [ ] Verify the Upgrade modal appears (NOT the Premium Required modal) - -### 3.3 Upgrade modal -- [ ] Verify Upgrade modal shows plan selection (Monthly / Yearly) -- [ ] Verify Yearly is selected by default -- [ ] Click Monthly — verify it becomes selected -- [ ] Click Yearly — verify it becomes selected -- [ ] Click "Cancel" — modal closes - -### 3.4 License check — no subscription -- [ ] Open DevTools, check console for `[license] email -> free` on server -- [ ] Verify `is_premium` attribute on `` is `"false"` - ---- - -## 4. Checkout / Purchase Flow - -### 4.1 Monthly subscription -- [ ] Click a premium feature → Upgrade modal -- [ ] Select "Monthly", click "Subscribe" -- [ ] Verify Stripe Checkout opens in a new tab -- [ ] Complete payment with Stripe test card (`4242 4242 4242 4242`) -- [ ] Verify Stripe success page appears -- [ ] Return to extension tab (click on it or close Stripe tab) -- [ ] Verify extension auto-refreshes license (visibility/focus event triggers refresh) -- [ ] Verify the premium feature you clicked is now enabled -- [ ] Verify "Donate" link changed to "Premium" in header - -### 4.2 Yearly subscription -- [ ] Sign out, sign in with a different email -- [ ] Repeat checkout flow selecting "Yearly" -- [ ] Verify Stripe Checkout shows the yearly price -- [ ] Complete payment and verify premium activates - -### 4.3 Cancel checkout -- [ ] Open Upgrade modal, click "Subscribe" -- [ ] On Stripe Checkout page, click back or close the tab -- [ ] Return to extension — verify user is still on Free Plan -- [ ] Verify no error messages appear - -### 4.4 Checkout with expired session -- [ ] Clear `session_token` from `chrome.storage.local` manually (DevTools → Application → Local Storage) -- [ ] Try to subscribe — verify error about session expired -- [ ] Verify user is signed out and prompted to sign in again - ---- - -## 5. Premium User Experience - -### 5.1 All premium features accessible -- [ ] Toggle several premium features on — verify they take effect on YouTube -- [ ] Toggle them off — verify they revert - -### 5.2 Account modal — premium subscriber -- [ ] Open Account modal -- [ ] Verify it shows: - - Your email - - "Premium Active" status - - "Billing" button visible - - "Upgrade" button hidden - - "Sign Out" button - -### 5.3 Billing portal -- [ ] Click "Billing" button in Account modal -- [ ] Verify Stripe billing portal opens in new tab -- [ ] Verify you can see subscription details, update payment method, cancel -- [ ] Close billing portal tab, return to extension - -### 5.4 Schedule (premium feature) -- [ ] Click "Schedule" in settings menu -- [ ] Verify Schedule modal opens (not blocked by premium gate) -- [ ] Configure a schedule and verify it works - -### 5.5 Password (premium feature) -- [ ] Click "Password" in settings menu -- [ ] Verify Password modal opens (not blocked by premium gate) -- [ ] Set a password, close settings, reopen — verify password prompt appears -- [ ] Enter correct password — verify unlock works -- [ ] Disable password — verify it's removed - ---- - -## 6. Grandfathered User - -### 6.1 Sign in with grandfathered email -- [ ] Sign in with an email that's in `data/grandfathered.json` -- [ ] Verify license check returns `grandfathered: true` - -### 6.2 Account modal — grandfathered -- [ ] Open Account modal -- [ ] Verify it shows: - - Your email - - "Lifetime Premium" status - - "Billing" button hidden (no subscription to manage) - - "Upgrade" button hidden - - "Sign Out" button - -### 6.3 Premium features work -- [ ] Verify all premium features are accessible -- [ ] Verify "Donate" link shows "Premium" - -### 6.4 Case-insensitive matching -- [ ] Add `test@example.com` to `grandfathered.json` -- [ ] Sign in with `Test@Example.com` (different case) -- [ ] Verify user is recognized as grandfathered - ---- - -## 7. Sign Out - -### 7.1 Sign out clears state -- [ ] While signed in (premium or free), click "Sign Out" in Account modal -- [ ] Verify header changes back to "Sign In" -- [ ] Verify "Donate" link reappears (if was showing "Premium") -- [ ] Verify premium features are re-gated - -### 7.2 Storage is cleared -- [ ] After sign-out, check `chrome.storage.local` in DevTools -- [ ] Verify `session_token`, `license_token`, and `user_email` are removed -- [ ] Verify YouTube settings (non-premium) are NOT affected - ---- - -## 8. Session Expiry - -### 8.1 Expired session token -- [ ] Sign in, then manually modify the `session_token` in storage to an expired JWT -- [ ] Open Account modal (or trigger a license check) -- [ ] Verify "Session expired. Please sign in again." message appears -- [ ] Verify user is automatically signed out -- [ ] Verify sign-in modal opens - -### 8.2 Invalid session token -- [ ] Replace `session_token` with garbage string in storage -- [ ] Trigger a license check -- [ ] Verify 401 from server causes automatic sign-out - ---- - -## 9. License Token Caching & Refresh - -### 9.1 Cached token is used -- [ ] Sign in as premium user, verify license check succeeds -- [ ] Disconnect from network (or stop the server) -- [ ] Close and reopen extension popup -- [ ] Verify premium features are still accessible (cached license token is valid) - -### 9.2 Expired cached token without network -- [ ] Manually set `license_token` to an expired JWT in storage -- [ ] With server unreachable, open extension -- [ ] Verify user shows as non-premium (expired cache, can't refresh) - -### 9.3 Token refresh near expiry -- [ ] Set a license token that expires within 24 hours (modify JWT payload) -- [ ] Open extension with server running -- [ ] Verify a fresh license check is triggered (not using cache) - -### 9.4 Force refresh after checkout -- [ ] Complete a checkout flow -- [ ] Verify the extension calls `checkLicense(true)` (force refresh) -- [ ] Monitor network tab — a `/license/check` request should fire immediately on tab focus - ---- - -## 10. Subscription Lifecycle - -### 10.1 Cancel subscription via billing portal -- [ ] As a premium user, open Billing portal -- [ ] Cancel the subscription in Stripe -- [ ] Return to extension, close and reopen popup -- [ ] Wait for license token to expire (or force refresh) -- [ ] Verify user shows as "Free Plan" after refresh -- [ ] Verify premium features are re-gated - -### 10.2 Resubscribe after cancellation -- [ ] After cancellation, click a premium feature -- [ ] Go through checkout flow again -- [ ] Verify premium re-activates - ---- - -## 11. Stripe Webhooks - -### 11.1 Webhook receives events -- [ ] With Stripe CLI forwarding, complete a checkout -- [ ] Verify `checkout.session.completed` event is logged on server -- [ ] Cancel subscription — verify `customer.subscription.deleted` is logged - -### 11.2 Invalid webhook signature -- [ ] Send a POST to `/webhook/stripe` with invalid/missing `Stripe-Signature` header -- [ ] Verify 400 response - ---- - -## 12. Cross-Browser - -### 12.1 Chrome -- [ ] Run through sign-in, checkout, and premium feature flows on Chrome - -### 12.2 Firefox -- [ ] Load extension in Firefox -- [ ] Verify sign-in flow works (popup-to-tab handoff, polling) -- [ ] Verify premium features gate correctly -- [ ] Verify `browser.storage.local` API works as expected - ---- - -## 13. Edge Cases - -### 13.1 Multiple tabs -- [ ] Open extension settings in two tabs -- [ ] Sign in on one tab -- [ ] Refresh the second tab — verify it reflects signed-in state - -### 13.2 Rapid feature toggle clicks -- [ ] Click a premium feature rapidly multiple times -- [ ] Verify only one modal opens, no duplicate modals or errors - -### 13.3 Network failure during sign-in -- [ ] Start sign-in, disconnect network after magic link is sent -- [ ] Verify polling handles network errors gracefully (retries silently) -- [ ] Reconnect network — verify polling resumes and succeeds when link is clicked - -### 13.4 Server down during license check -- [ ] Sign in successfully, then stop the server -- [ ] Trigger a license check (reopen popup) -- [ ] Verify cached license token is used if still valid -- [ ] Verify no error shown to user - -### 13.5 Extension update while signed in -- [ ] Sign in and get premium status -- [ ] Reload the extension (simulate update) -- [ ] Verify session and license tokens persist across reload -- [ ] Verify premium status is maintained - ---- - -## 14. Analytics Verification - -Use Mixpanel (or check console logs) to verify events fire correctly: - -- [ ] `Sign In Started` — when email is submitted -- [ ] `Magic Link Sent` — after server responds -- [ ] `Sign In Success` — after verification completes -- [ ] `Sign In Canceled` — when user cancels polling -- [ ] `Sign In Error` — on timeout or failure -- [ ] `Premium Feature Click` — with correct `signedIn` flag -- [ ] `Upgrade Modal Opened` — when upgrade modal shows -- [ ] `Checkout Started` — with correct `plan` value -- [ ] `Checkout Completed` — after returning from successful checkout -- [ ] `Account Modal Opened` — with `isPremium` and `source` -- [ ] `Billing Portal Opened` — when billing portal opens -- [ ] `Session Expired` — when 401 triggers sign-out -- [ ] `Sign Out` — when user signs out -- [ ] `License Check` — with `cached`, `offline`, `isPremium` properties