Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ apps/mobile/ios/
*.mobileprovision
*.orig.*

# config signing — Ed25519 private key NEVER committed; public key is
.keys/*.key
!.keys/filmsnaps-ed25519.pub

# mobile build artifacts
*.log
build_commands.txt
Expand Down
3 changes: 3 additions & 0 deletions .keys/filmsnaps-ed25519.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAuL54sRMa4NpYZnl4TiQgl4Dib93hrSrWxcvqQ78Sv+Y=
-----END PUBLIC KEY-----
152 changes: 124 additions & 28 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ a provider, and ship changes across web, desktop, and mobile.
3. [Development workflow](#development-workflow)
4. [Project structure](#project-structure)
5. [Adding a new provider](#adding-a-new-provider)
6. [Editing `blocklist.json`](#editing-blocklistjson)
6. [Editing `providers.json` + `filters.txt` (v5)](#editing-providersjson--filterstxt-v5)
7. [Testing](#testing)
8. [Code style](#code-style)
9. [Common pitfalls](#common-pitfalls)
Expand All @@ -23,15 +23,17 @@ FilmSnaps is a pnpm + Turborepo monorepo. Read
[docs/architecture.md](docs/architecture.md) for the full picture, and
[docs/security.md](docs/security.md) before touching anything security-related.

| Directory | Package | What it is |
| --- | --- | --- |
| `apps/web` | `@filmsnaps/web` | Next.js web app (discovery + watch UI). |
| `apps/desktop` | `@filmsnaps/desktop` | Electron app wrapping the web app + hardened player. |
| `apps/mobile` | `@filmsnaps/mobile` | Expo / React Native app. |
| `apps/feedback` | `@filmsnaps/feedback` | Feedback portal (Cloudflare Workers + D1). |
| `packages/shared` | `@filmsnaps/shared` | Shared guards, provider registry, types, state. |
| `packages/adblock-config` | `@filmsnaps/adblock-config` | `blocklist.json` schema + validation. |
| `packages/filter-compiler` | `@filmsnaps/filter-compiler` | Adblock engine + mobile pattern artifacts. |
| Directory | Package | What it is |
| -------------------------- | ---------------------------- | -------------------------------------------------------- |
| `apps/web` | `@filmsnaps/web` | Next.js web app (discovery + watch UI). |
| `apps/desktop` | `@filmsnaps/desktop` | Electron app wrapping the web app + hardened player. |
| `apps/mobile` | `@filmsnaps/mobile` | Expo / React Native app. |
| `apps/feedback` | `@filmsnaps/feedback` | Feedback portal (Cloudflare Workers + D1). |
| `packages/shared` | `@filmsnaps/shared` | Shared guards, provider registry, types, state. |
| `packages/adblock-config` | `@filmsnaps/adblock-config` | v5 `providers.json` + `filters.txt` schema + validation. |
| `packages/filter-compiler` | `@filmsnaps/filter-compiler` | `@ghostery/adblocker` engine + mobile pattern export. |

---

## Setting up

Expand All @@ -47,6 +49,8 @@ If you change a filter/blocklist config, regenerate the adblock artifacts:
pnpm build:filters # recompiles compiled-engine.bin + android-adblock-patterns.json
```

---

## Development workflow

### Web
Expand Down Expand Up @@ -81,6 +85,8 @@ engine — see `apps/desktop/README.md`.
cd apps/feedback && pnpm dev # http://localhost:3001
```

---

## Project structure

```
Expand All @@ -91,16 +97,17 @@ apps/
feedback/ Next.js + Workers + D1
packages/
shared/ shared logic (security bundles, providers, state)
adblock-config/ blocklist.json schema + validation
filter-compiler/ engine + pattern export
blocklist.json provider + blocking rules (single source of truth)
adblock-config/ providers.json + filters.txt v5 schema + validation
filter-compiler/ @ghostery/adblocker engine + mobile pattern export
providers.json v5 config (providers) — single source of truth, Ed25519-signed
providers.json.sig Ed25519 signature over providers.json
filters.txt v5 config (uBO/EasyList rules)
blocklist.json legacy v4 fallback (backward compat)
```

## Adding a new provider
---

Providers are **only** registered in the shared package — there is no separate
web/mobile provider list. See `apps/desktop/README.md` and
`apps/mobile/README.md` for per-platform notes.
## Adding a new provider

### Step 1 — Register in `@filmsnaps/shared`

Expand All @@ -115,23 +122,89 @@ web/mobile provider list. See `apps/desktop/README.md` and
embed: {
movie: (id: string) => `/embed/movie/${id}`,
tv: (id, season, episode) => `/embed/tv/${id}/${season}/${episode}`,
}
}
```

### Step 2 — Add its domains to `providers.json` (v5)

**File:** `providers.json` (repo root, schema v5)

```json
{
"version": 5,
"providers": [
{
"id": "myprovider",
"embedDomains": ["example.com", "www.example.com"],
"cdnDomains": ["cdn.example.com"],
"enabled": true,
"allowServerRedirects": false,
"blockHomePaths": ["/go-home"],
"apiIntercepts": [],
"cosmeticRules": [],
"adblockDisabled": false
}
],
"providerProfiles": {
"example.com": {
"scripts": ["https://example.com/script.js"],
"iframes": ["https://cdn.example.com/frame.html"],
"images": ["https://example.com/image.png"]
}
},
"navigationGuard": {
"universalBlockPaths": ["/"]
},
"rules": {
"videoDetection": {
"extensions": [".mp4", ".m3u8", ".ts"],
"pathPatterns": ["seg-", "init-", "chunk-"],
"enableSessionTrust": true,
"trustTTLMs": 900000
},
"alwaysBlock": {
"domains": [],
"pathPatterns": []
}
}
}
```

### Step 2 — Add its domains to `blocklist.json`
### Step 3 — Add `filters.txt` entries (optional, for ad blocking)

**File:** `filters.txt` (repo root)

Standard uBO/EasyList syntax. Example rules:

Add a `providers[]` entry with `embedDomains` and `cdnDomains`, and any
`blockHomePaths` for its error-UI "Go Home" links. Run `pnpm build:filters`.
```
@@||example.com^ # allowlist the embed domain
||google-analytics.com^$3p # block 3rd-party trackers
##.ad-banner # cosmetic rule
```

### Step 3 — Test on each platform
### Step 4 — Regenerate compiler artifacts

```bash
pnpm build:filters # rebuilds compiled-engine.bin + android-adblock-patterns.json
```

### Step 5 — Test on each platform

- **Web** — iframe mounts the embed; check the video plays without 404s.
- **Desktop** — full R0–R8 cascade + L5 preload. Verify with
`FILMSNAPS_AUDIT=1` (see `docs/security.md` → Audit & diagnostics).
- **Mobile** — native `PlayerWebView` + `shouldInterceptRequest`. Verify no ads,
popups, or fullscreen issues.

### Step 6 — Sign the config (for OTA)

Run the signing step to generate `providers.json.sig`:

```bash
pnpm sign:providers # Ed25519-signs providers.json; .key in .keys/ (gitignored), .pub committed
```

### Providers needing custom handling

If the provider doesn't work with the standard pipeline (Cloudflare challenge,
Expand All @@ -140,12 +213,24 @@ the mobile `VideoWebView.tsx` / `PlayerWebViewOverlayView.kt` and the desktop
preload, then test on all platforms. Do **not** weaken shared guards to make a
provider work — prefer per-provider allowlist entries.

## Editing `blocklist.json`
---

`blocklist.json` is the single source of truth (v4 schema). See
[docs/security.md](docs/security.md) → Configuration for the sections. After
editing, run `pnpm build:filters` so the compiled engine and mobile patterns
regenerate.
## Editing `providers.json` + `filters.txt` (v5)

> The v5 config lives in `providers.json` (app logic) + `filters.txt` (uBO
> syntax), both Ed25519-signed (`providers.json.sig`). A legacy `blocklist.json`
> (v4) is kept for backward compatibility.

**Workflow:**

1. Edit `providers.json` (add/update provider entries, allowlists, nav-guard,
apiIntercepts, cosmetics, `allowServerRedirects`).
2. Edit `filters.txt` (uBO/EasyList rules — exact/suffix matching only, e.g.
`@@||cloudfront.net^`, `||doubleclick.net^$3p`, `##.ad-banner`).
3. Run `pnpm build:filters` — regenerates `compiled-engine.bin` (desktop) and
`android-adblock-patterns.json` (mobile).
4. Run `pnpm sign:providers` — Ed25519-signs `providers.json` → `providers.json.sig`.
5. Commit all four files. OTA clients will pull and verify the updated config.

**Safety rules:**

Expand All @@ -155,6 +240,11 @@ regenerate.
the cascade on every provider.
- `blockHomePaths` are per-provider deny-lists — append new home-page shapes as
discovered.
- `allowServerRedirects: true` is only for redirect-mesh providers (vidsrc→viduki.net,
videasy→videasy.to). Enabling it on a non-redirect provider could let an ad
redirect through.

---

## Testing

Expand All @@ -174,6 +264,8 @@ Current suites:
If you change the R0–R8 cascade or the navigation guard, add/extend tests in
these files.

---

## Code style

- TypeScript, Prettier-formatted (`pnpm format`).
Expand All @@ -185,10 +277,14 @@ these files.
at runtime — reproduce shared logic there with a comment pointing at the
canonical source (see `provider-config.ts`, `navigation-guard.ts`).

---

## Common pitfalls

- **Forgetting `pnpm build:filters`** after editing `blocklist.json` — the
desktop engine and mobile patterns go stale.
- **Forgetting `pnpm build:filters`** after editing `providers.json` or
`filters.txt` — the desktop engine and mobile patterns go stale.
- **Forgetting `pnpm sign:providers`** after editing `providers.json` — OTA
clients will reject the unsigned config and keep the last-known-good version.
- **Adding a provider to only one platform.** Registration lives in
`@filmsnaps/shared`; each app consumes the same registry.
- **Weakening guards.** If a provider breaks, investigate the allowlist /
Expand Down
54 changes: 31 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,30 @@ Expo/React Native mobile app, and a feedback portal.

## Apps

| App | Package | Stack | Description |
| ----------------------------------- | --------------------- | ---------------------------------- | ---------------------------------------- |
| [Web](apps/web/README.md) | `@filmsnaps/web` | Next.js 16 (App Router) + Tailwind | Discovery UI, watch pages, API routes |
| [Desktop](apps/desktop/README.md) | `@filmsnaps/desktop` | Electron 43 + Next.js standalone | Web UI + native hardened player |
| [Mobile](apps/mobile/README.md) | `@filmsnaps/mobile` | Expo SDK 55 / React Native 0.83 | Phone app with downloads + native player |
| [Feedback](apps/feedback/README.md) | `@filmsnaps/feedback` | Next.js 16 + Cloudflare Workers/D1 | Public feedback portal |
| App | Package | Stack | Description |
| ----------------------------------- | --------------------- | ---------------------------------- | -------------------------------------------------------- |
| [Web](apps/web/README.md) | `@filmsnaps/web` | Next.js 16 (App Router) + Tailwind | Discovery UI, watch pages, API routes |
| [Desktop](apps/desktop/README.md) | `@filmsnaps/desktop` | Electron 43 + Next.js standalone | Web UI + native hardened player (WebContentsView hybrid) |
| [Mobile](apps/mobile/README.md) | `@filmsnaps/mobile` | Expo SDK 55 / React Native 0.83 | Phone app with downloads + native player |
| [Feedback](apps/feedback/README.md) | `@filmsnaps/feedback` | Next.js 16 + Cloudflare Workers/D1 | Public feedback portal |

## Packages

| Package | Description |
| ---------------------------- | --------------------------------------------------------------------- |
| `@filmsnaps/shared` | Shared guard scripts, provider registry, types, state, design tokens. |
| `@filmsnaps/adblock-config` | `blocklist.json` schema + validation. |
| `@filmsnaps/filter-compiler` | Adblocker engine + mobile pattern export artifacts. |
| Package | Description |
| ---------------------------- | ------------------------------------------------------------------------------------ |
| `@filmsnaps/shared` | Shared guard scripts, provider registry, types, state, design tokens. |
| `@filmsnaps/adblock-config` | v5 `providers.json` + `filters.txt` schema + validation + Ed25519 OTA config loader. |
| `@filmsnaps/filter-compiler` | Adblocker engine (@ghostery/adblocker WASM) + mobile pattern export. |

---

## Documentation

- **[Security Architecture](docs/security.md)** — the full security stack: R0–R8
rule cascade and L2–L8 desktop layers, mobile native protection, and the
`blocklist.json` configuration.
rule cascade and L2–L8 desktop layers, mobile native protection, WebContentsView hybrid,
and the `providers.json` + `filters.txt` v5 configuration.
- **[Security Expert Review](docs/security-expert-review.md)** — external expert review
and implementation status.
- **[Architecture](docs/architecture.md)** — repository layout, data flow,
builds, and CI.
- **[Contributing](CONTRIBUTING.md)** — how to set up, develop, add a provider,
Expand Down Expand Up @@ -79,16 +81,17 @@ build profiles).

## Common commands

| Command | Purpose |
| ------------------------ | ------------------------------------------------------------ |
| `pnpm build` | Build all apps/packages (Turborepo). |
| `pnpm lint` | Lint everything. |
| `pnpm test` | Run the Vitest suites (shared + desktop security). |
| `pnpm typecheck:desktop` | Typecheck the desktop app. |
| `pnpm format` | Prettier across the repo. |
| `pnpm build:filters` | Regenerate adblocker/filter artifacts from `blocklist.json`. |
| `pnpm cf:deploy` | Deploy the web app to Cloudflare Pages. |
| `pnpm dist:desktop` | Build the desktop installer. |
| Command | Purpose |
| ------------------------ | ---------------------------------------------------------------------------- |
| `pnpm build` | Build all apps/packages (Turborepo). |
| `pnpm lint` | Lint everything. |
| `pnpm test` | Run the Vitest suites (shared + desktop security). |
| `pnpm typecheck:desktop` | Typecheck the desktop app. |
| `pnpm format` | Prettier across the repo. |
| `pnpm build:filters` | Regenerate adblocker/filter artifacts from `providers.json` + `filters.txt`. |
| `pnpm sign:providers` | Sign `providers.json` with Ed25519 for OTA. |
| `pnpm cf:deploy` | Deploy the web app to Cloudflare Pages. |
| `pnpm dist:desktop` | Build the desktop installer. |

---

Expand All @@ -102,6 +105,11 @@ build profiles).
- **Multi-provider player** — provider registry in `@filmsnaps/shared`; each
platform mounts embeds with native security layers (see
[docs/security.md](docs/security.md)).
- **Native hardened desktop player** — WebContentsView hybrid (Electron 43), L8 `Page.addScriptToEvaluateOnNewDocument` HTML-bytes injection (replaces disabled CDP-Fetch that dropped renderer headers → Cloudflare 403),
`@ghostery/adblocker` (adblock-rs WASM), session trust with MIME-based 15-min TTL, `allowServerRedirects` for redirect-mesh providers.
- **Native hardened mobile player** — `PlayerWebView` native Expo module with `shouldInterceptRequest` filtering (Aho-Corasick unified trie), Ed25519-verified OTA config with ring-buffer rollback, 3×-failure watchdog, NavGuard server-redirect fix, session trust with 15-min TTL, and cosmetic rules from config.
- **Signed OTA config** — `providers.json` + `filters.txt` v5, Ed25519-signed, ring-buffer rollback (3 configs),
3×-failure watchdog with local `heal-events.log` on both desktop and mobile.
- **Mobile downloads** — SQLite-backed episode/movie downloads with a native
downloader.
- **Feedback portal** — account-free bug reports, feature requests, roadmap,
Expand Down
Loading
Loading