# Deploying Meshcall

This app is a static site — HTML/CSS/JS, no build step, no backend of your
own to run for the core calling feature. Here's exactly how to get it live,
what each piece costs, and where the honest limitations are.

---

## Step 1 — Push this to a Git repo

Cloudflare Pages deploys from GitHub or GitLab.

```bash
cd meshcall
git init
git add .
git commit -m "Initial Meshcall build"
# Create a repo on GitHub first, then:
git remote add origin https://github.com/YOUR_USERNAME/meshcall.git
git branch -M main
git push -u origin main
```

## Step 2 — Deploy on Cloudflare Pages

1. Go to the Cloudflare dashboard → **Workers & Pages** → **Create** → **Pages** → **Connect to Git**.
2. Pick your `meshcall` repo.
3. Build settings: **Framework preset: None**, **Build command: (leave blank)**, **Build output directory: /** (root — since this is plain static HTML with no build step).
4. Deploy. You'll get a `*.pages.dev` URL immediately. Add a custom domain later under the project's **Custom domains** tab if you own one — free on Cloudflare.

Cost: **$0** for normal traffic (Pages free tier is generous — 500 builds/month, unlimited bandwidth).

## Step 3 — Configure Google Sign-In for your real domain

Google Sign-In only works on origins you've explicitly authorized. This is a
Google security requirement — it is not something this code can work around,
and it's why the button will show a fallback notice on a `file://` preview.

1. Go to [Google Cloud Console](https://console.cloud.google.com/) → **APIs & Services** → **Credentials**.
2. Find the OAuth 2.0 Client ID you're using (currently
   `322463263880-4jvk5vsdjm08e8ksp8u46l0jflpuhi27.apps.googleusercontent.com`
   is wired into `js/auth.js`).
3. **Important:** since that client ID was shared in a chat conversation, I'd
   treat it as semi-public and create a **fresh OAuth Client ID** scoped only
   to your real domain, rather than reusing this one in production. Client
   IDs aren't secret by themselves, but there's no reason to carry one over
   from a scratch conversation.
4. Under **Authorized JavaScript origins**, add:
   - `https://your-project.pages.dev`
   - `https://yourdomain.com` (once you attach a custom domain)
5. Replace `GOOGLE_CLIENT_ID` in `js/auth.js` with your new client ID.
6. Redeploy (push to `main` — Cloudflare Pages auto-builds on push).

Cost: **$0.**

## Step 4 — Turn on real email verification (EmailJS)

The "sign in with email" flow needs something to actually send mail from the
browser, since there's no backend. [EmailJS](https://www.emailjs.com/) does
this on a free tier (200 emails/month).

1. Create a free EmailJS account.
2. Add an email service (Gmail, Outlook, or their default SMTP — a couple of clicks).
3. Create an email template with two variables: `{{verify_link}}` and `{{verify_code}}`.
4. Grab your **Public Key**, **Service ID**, and **Template ID** from the EmailJS dashboard.
5. In `js/auth.js`, replace:
   ```js
   const EMAILJS_PUBLIC_KEY = 'YOUR_EMAILJS_PUBLIC_KEY';
   const EMAILJS_SERVICE_ID = 'YOUR_EMAILJS_SERVICE_ID';
   const EMAILJS_TEMPLATE_ID = 'YOUR_EMAILJS_TEMPLATE_ID';
   ```
   with your real values.
6. Redeploy.

**Honest limitation:** this verifies "the person clicking this link has access
to this inbox right now, on this browser" — it does not create a real
cross-device account system. That's genuinely fine for a lightweight identity
layer; just don't market it as bank-grade auth.

Cost: **$0** up to 200 emails/month, then EmailJS's paid tiers.

## Step 5 — Turn on the $2 / 6-month Pro tier (Stripe, not Shopify)

**Why not Shopify:** Shopify is built to sell and ship products through a
storefront checkout — it doesn't have a clean way to gate feature access
inside a separate web app based on subscription status. Stripe Payment Links
are the right tool for exactly this and need zero backend code.

1. Create a [Stripe account](https://dashboard.stripe.com/register) (free — Stripe only takes a small % + flat fee per real transaction, no monthly cost).
2. Go to **Product catalog** → **Add product**. Name it "Meshcall Pro". Set pricing to **Recurring**, **$2.00**, billing period **Every 6 months**.
3. Go to **Payment links** → **Create payment link**, select the product you just made.
4. Under the link's settings, set the **after payment** redirect to:
   ```
   https://yourdomain.com/pro-success.html
   ```
   (Stripe automatically appends `?session_id=...` — the code already reads it.)
5. Copy the generated payment link URL (looks like `https://buy.stripe.com/xxxxx`).
6. In `js/pro.js`, replace:
   ```js
   const STRIPE_PAYMENT_LINK = 'https://buy.stripe.com/YOUR_PAYMENT_LINK_ID';
   ```
7. Redeploy.

**Honest limitation (read this one):** `pro-success.html` marks Pro as active
in the visitor's own browser's `localStorage` after Stripe redirects them
back — there's no server checking that the `session_id` in the URL is a real,
paid Stripe session. For a $2 side project this is a reasonable place to
start (nobody's going to reverse-engineer your billing for $2), but it does
mean a technically inclined person could fake Pro status locally, and it
won't sync Pro status to a second device. If that ever matters to you:

### Step 5b (optional hardening) — verify payment server-side, still serverless

A single Cloudflare Worker (their free tier: 100,000 requests/day, $0) closes
both gaps in about 40 lines:

```js
// worker.js — deploy via `wrangler deploy`
export default {
  async fetch(request, env) {
    const { session_id } = await request.json();
    const stripeRes = await fetch(
      `https://api.stripe.com/v1/checkout/sessions/${session_id}`,
      { headers: { Authorization: `Bearer ${env.STRIPE_SECRET_KEY}` } }
    );
    const session = await stripeRes.json();
    if (session.payment_status === 'paid') {
      // Sign a small token (e.g. with a Worker-side HMAC secret) proving Pro
      // status, return it, and have pro-success.html store THAT instead of
      // trusting the raw session_id.
      return new Response(JSON.stringify({ verified: true /* + signed token */ }));
    }
    return new Response(JSON.stringify({ verified: false }), { status: 402 });
  }
};
```
Set `STRIPE_SECRET_KEY` as a Worker secret (`wrangler secret put STRIPE_SECRET_KEY`),
never in client-side code. This is the only piece of this whole app that
needs a real secret key, which is exactly why it's isolated to one small
Worker rather than baked into the static site.

## Step 6 — (Optional) Run your own PeerJS broker instead of the public one

The free public PeerJS broker (`0.peerjs.com`) is fine to start and is what
this app uses by default. If you get heavy usage and want your own:

1. Deploy the open-source [PeerServer](https://github.com/peers/peerjs-server) to Render or Fly.io's free tier (`npm install -g peer` → `peerjs --port 9000`).
2. In `js/room-webrtc.js`, uncomment and fill in:
   ```js
   peer = new Peer(myId, {
     host: 'your-broker.onrender.com', port: 443, secure: true, path: '/peerjs'
   });
   ```
This server only ever sees connection metadata (like a phone switchboard) —
never video, audio, or whiteboard content, which all travel directly between
browsers once connected.

Cost: **$0** on Render/Fly free tiers for light-to-moderate traffic.

## Step 7 — (Optional, larger effort) Real screen co-control

The "request control" button currently sends a real request over the data
channel and shows a real confirm dialog, but doesn't yet relay actual mouse/
keyboard input into the presenter's OS — that needs either:
- A `<canvas>`-based remote-input protocol (send normalized coordinates +
  key codes over the data channel, dispatch synthetic events on the receiving
  end) — works within a browser tab's own content, not the OS beyond it, and
- Browser permission prompts are expected here for security reasons; don't
  try to suppress them.

This is a genuinely bigger feature (a full day of focused work, not an hour),
so it's left as a clearly-marked extension point rather than faked.

---

## What this app can't do, said plainly

- It cannot have literally zero coordination infrastructure and still let two
  strangers' browsers find each other — nothing can. What it has is the
  smallest possible amount of it (a free signaling broker), with zero video/
  audio/whiteboard traffic ever touching a server you run or pay for.
- Mesh (every-browser-to-every-browser) topology means call quality degrades
  past roughly 6-8 active video participants on typical home upload speeds —
  this is real bandwidth math, not an artificial cap. The free tier's
  12-person limit and Pro's "priority relay routing" reflect that honestly.
- The $2 Pro tier, as shipped, is enforced client-side only (see Step 5's
  limitation note). Fine for launch; harden with Step 5b if it starts to
  matter.
- Recording currently uses the browser's own screen-capture-based recorder
  (asks the user to pick a source, records that) rather than compositing all
  participants server-side — this is the honest serverless equivalent of
  Zoom's cloud recording.

## Total monthly cost to run this for a small/medium audience

| Piece | Cost |
|---|---|
| Cloudflare Pages hosting | $0 |
| PeerJS public broker | $0 |
| EmailJS (up to 200 emails/mo) | $0 |
| Stripe | $0/mo + ~2.9%+$0.30 per real transaction |
| Google OAuth | $0 |
| **Total** | **$0/month**, plus normal payment processing fees only when someone actually pays you |
