mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Add Capacitor native app migration plan
Comprehensive plan for wrapping bitvid as native apps using Capacitor for Android, iOS, and desktop (Windows/macOS/Linux via Electron). Covers architecture, phased implementation, CI/CD workflows, testing strategy, platform risks, and app store submission guidance. https://claude.ai/code/session_01RBdcFkjzXaawaLY68MMhxc
This commit is contained in:
1 file changed
+849
@@ -0,0 +1,849 @@
|
||||
# Capacitor Native App Plan
|
||||
|
||||
> **Status**: Planning
|
||||
> **Platforms**: Android, iOS, Windows, macOS, Linux
|
||||
> **Framework**: [Capacitor](https://capacitorjs.com/) by Ionic
|
||||
> **Goal**: Ship bitvid as a native app on all major platforms while keeping a single codebase with the existing web/PWA deployment.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Why Capacitor](#why-capacitor)
|
||||
2. [Architecture Overview](#architecture-overview)
|
||||
3. [Repository Structure](#repository-structure)
|
||||
4. [Phase 1 — Foundation](#phase-1--foundation)
|
||||
5. [Phase 2 — Android & iOS](#phase-2--android--ios)
|
||||
6. [Phase 3 — Desktop (Electron via Capacitor)](#phase-3--desktop-electron-via-capacitor)
|
||||
7. [Phase 4 — Platform-Specific Enhancements](#phase-4--platform-specific-enhancements)
|
||||
8. [Phase 5 — CI/CD for Native Builds](#phase-5--cicd-for-native-builds)
|
||||
9. [Phase 6 — Testing Strategy](#phase-6--testing-strategy)
|
||||
10. [Platform Considerations & Risks](#platform-considerations--risks)
|
||||
11. [App Store Strategy](#app-store-strategy)
|
||||
12. [Task Checklist](#task-checklist)
|
||||
|
||||
---
|
||||
|
||||
## Why Capacitor
|
||||
|
||||
### Fit for bitvid
|
||||
|
||||
bitvid is a fully static, client-side web application. There is no backend server — all authentication, data, and state management happens in the browser via Nostr relays and WebTorrent. This makes it an ideal candidate for Capacitor, which wraps an existing web app in a native WebView shell.
|
||||
|
||||
### Key reasons
|
||||
|
||||
| Reason | Detail |
|
||||
|--------|--------|
|
||||
| **Zero rewrite** | The existing `dist/` build output drops directly into Capacitor as the web layer. No framework migration needed. |
|
||||
| **Single codebase** | Web, PWA, Android, iOS, and desktop all share the same HTML/CSS/JS. Platform-specific behavior is handled via feature detection and Capacitor's plugin bridge. |
|
||||
| **Progressive native access** | Start with a pure web wrapper, then incrementally add native plugins (deep links, push notifications, share targets, file system) only when needed. |
|
||||
| **Mature mobile support** | Capacitor has first-class Android and iOS support with a large plugin ecosystem. |
|
||||
| **Desktop via Electron** | The `@nicepkg/capacitor-electron` community plugin (or the `@nicepkg/cap-electron` plugin) lets Capacitor drive an Electron shell for Windows/macOS/Linux using the same web build. |
|
||||
| **Existing PWA foundation** | bitvid already has `site.webmanifest`, a service worker (`sw.min.js`), and app icons — all of which Capacitor respects and builds on. |
|
||||
| **Active ecosystem** | Large community, official Ionic backing, regular releases, good documentation. |
|
||||
|
||||
### Alternatives considered
|
||||
|
||||
| Framework | Why not primary |
|
||||
|-----------|----------------|
|
||||
| **Tauri** | Excellent for desktop (small binaries, Rust backend), but mobile support is still in beta. Could be revisited later as a desktop-only option if Electron bundle size becomes a concern. |
|
||||
| **Electron standalone** | Bundles ~150MB of Chromium per platform. Capacitor+Electron gives us the same result but with a unified build pipeline across all platforms. |
|
||||
| **React Native / Flutter** | Would require a complete rewrite. bitvid is vanilla JS with no component framework — porting to RN or Flutter would be a ground-up rebuild. |
|
||||
| **PWA only** | Already partially implemented. Good for web-savvy users but lacks app store discoverability, has weaker iOS support (no push notifications, limited background execution), and misses users who expect a "real" app install. |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ bitvid codebase │
|
||||
│ │
|
||||
│ ┌───────────┐ npm run build ┌───────────────┐ │
|
||||
│ │ Source │ ────────────────→ │ dist/ │ │
|
||||
│ │ (js/, │ │ (static web │ │
|
||||
│ │ css/, │ │ assets) │ │
|
||||
│ │ views/) │ └──────┬────────┘ │
|
||||
│ └───────────┘ │ │
|
||||
│ │ │
|
||||
│ ┌────────────────┼─────┐ │
|
||||
│ │ npx cap sync │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────┐ ┌─────┐ ┌────────┐ │
|
||||
│ │ android/│ │ ios/│ │electron/│ │
|
||||
│ │ (native │ │ │ │ │ │
|
||||
│ │ project)│ │ │ │ │ │
|
||||
│ └─────────┘ └─────┘ └────────┘ │
|
||||
│ │
|
||||
│ Deploy targets: │
|
||||
│ • Web/PWA → dist/ served via Netlify/Vercel/etc │
|
||||
│ • Android → android/ → Play Store APK/AAB │
|
||||
│ • iOS → ios/ → App Store IPA │
|
||||
│ • Desktop → electron/ → Windows/macOS/Linux │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Data flow (unchanged)
|
||||
|
||||
The native shell is purely a container. All app logic remains in the web layer:
|
||||
|
||||
1. User action triggers a UI controller event handler
|
||||
2. Controller invokes callback provided by `bitvidApp`
|
||||
3. `bitvidApp` executes logic, communicates with Nostr relays via WebSocket
|
||||
4. WebTorrent handles P2P video streaming via WebRTC data channels
|
||||
5. UI updates via DOM manipulation
|
||||
|
||||
The only new data path is the **Capacitor bridge** — a thin JS-to-native channel used for:
|
||||
- Deep link handling (Nostr URIs, `nostr:` protocol)
|
||||
- Push notification tokens (optional, future)
|
||||
- Native share sheet integration
|
||||
- App lifecycle events (background/foreground)
|
||||
- Status bar / navigation bar styling
|
||||
|
||||
---
|
||||
|
||||
## Repository Structure
|
||||
|
||||
The native project directories live at the repo root alongside the existing web source. This is the standard Capacitor monorepo layout.
|
||||
|
||||
```
|
||||
bitvid/
|
||||
├── android/ # NEW — Native Android project (Android Studio)
|
||||
│ ├── app/
|
||||
│ │ ├── src/main/
|
||||
│ │ │ ├── AndroidManifest.xml
|
||||
│ │ │ ├── java/com/bitvid/app/
|
||||
│ │ │ └── res/
|
||||
│ │ └── build.gradle
|
||||
│ ├── capacitor.settings.gradle
|
||||
│ └── build.gradle
|
||||
├── ios/ # NEW — Native iOS project (Xcode)
|
||||
│ └── App/
|
||||
│ ├── App/
|
||||
│ │ ├── AppDelegate.swift
|
||||
│ │ ├── Info.plist
|
||||
│ │ └── capacitor.config.json (generated)
|
||||
│ ├── App.xcodeproj
|
||||
│ └── Podfile
|
||||
├── electron/ # NEW — Electron desktop shell
|
||||
│ ├── src/
|
||||
│ │ ├── index.ts
|
||||
│ │ └── preload.ts
|
||||
│ ├── capacitor.config.ts
|
||||
│ └── package.json
|
||||
├── capacitor.config.ts # NEW — Capacitor root config
|
||||
├── dist/ # Existing build output (web assets)
|
||||
├── js/ # Existing source
|
||||
├── css/ # Existing source
|
||||
├── config/
|
||||
│ └── instance-config.js # Extended with platform detection
|
||||
├── scripts/
|
||||
│ ├── build-dist.mjs # Existing web build
|
||||
│ ├── build-android.mjs # NEW — Android build helper
|
||||
│ ├── build-ios.mjs # NEW — iOS build helper
|
||||
│ └── build-desktop.mjs # NEW — Electron build helper
|
||||
├── tests/
|
||||
│ ├── unit/ # Existing
|
||||
│ ├── e2e/ # Existing (web)
|
||||
│ ├── visual/ # Existing
|
||||
│ ├── native/ # NEW — Native-specific tests
|
||||
│ │ ├── android/
|
||||
│ │ ├── ios/
|
||||
│ │ └── desktop/
|
||||
│ └── platform/ # NEW — Cross-platform behavior tests
|
||||
├── .github/workflows/
|
||||
│ ├── ci.yml # Existing (web CI)
|
||||
│ ├── ci-android.yml # NEW — Android build + test
|
||||
│ ├── ci-ios.yml # NEW — iOS build + test
|
||||
│ └── ci-desktop.yml # NEW — Electron build + test
|
||||
├── package.json # Extended with native scripts
|
||||
├── capacitor.config.ts # Capacitor root configuration
|
||||
└── site.webmanifest # Existing PWA manifest
|
||||
```
|
||||
|
||||
### What gets gitignored
|
||||
|
||||
```gitignore
|
||||
# Native build artifacts
|
||||
android/app/build/
|
||||
android/.gradle/
|
||||
android/capacitor-cordova-android-plugins/build/
|
||||
ios/App/Pods/
|
||||
ios/App/DerivedData/
|
||||
electron/dist/
|
||||
electron/node_modules/
|
||||
|
||||
# Capacitor copies of web assets (regenerated by cap sync)
|
||||
android/app/src/main/assets/public/
|
||||
ios/App/App/public/
|
||||
```
|
||||
|
||||
### What gets committed
|
||||
|
||||
The native project scaffolding (`android/`, `ios/`, `electron/`) **is committed** to the repo. This is the Capacitor convention — the native projects are source code, not generated artifacts. They contain platform-specific configuration (permissions, entitlements, splash screens, icons) that must be version-controlled.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Foundation
|
||||
|
||||
**Goal**: Initialize Capacitor, get the web app running inside native shells on Android and iOS simulators, and Electron on desktop.
|
||||
|
||||
### Tasks
|
||||
|
||||
1. **Install Capacitor core**
|
||||
```bash
|
||||
npm install @capacitor/core @capacitor/cli
|
||||
```
|
||||
|
||||
2. **Create `capacitor.config.ts`**
|
||||
```typescript
|
||||
import type { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'com.bitvid.app',
|
||||
appName: 'bitvid',
|
||||
webDir: 'dist',
|
||||
server: {
|
||||
// Allow WebSocket connections to Nostr relays
|
||||
allowNavigation: ['*'],
|
||||
},
|
||||
plugins: {
|
||||
SplashScreen: {
|
||||
launchShowDuration: 2000,
|
||||
backgroundColor: '#0f172a', // matches theme
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
3. **Add native platforms**
|
||||
```bash
|
||||
npx cap add android
|
||||
npx cap add ios
|
||||
```
|
||||
|
||||
4. **Update build pipeline** — extend `build-dist.mjs` or add a wrapper:
|
||||
```bash
|
||||
npm run build && npx cap sync
|
||||
```
|
||||
The `cap sync` command copies `dist/` into each native project and updates native dependencies.
|
||||
|
||||
5. **Add Electron support** via community plugin:
|
||||
```bash
|
||||
npm install @nicepkg/capacitor-electron
|
||||
npx cap add electron
|
||||
```
|
||||
|
||||
6. **Add npm scripts**
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"cap:sync": "npx cap sync",
|
||||
"cap:android": "npm run build && npx cap sync android && npx cap open android",
|
||||
"cap:ios": "npm run build && npx cap sync ios && npx cap open ios",
|
||||
"cap:electron": "npm run build && npx cap sync electron && npx cap open electron",
|
||||
"build:all": "npm run build && npx cap sync"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
7. **Verify basic functionality**
|
||||
- App loads in Android emulator
|
||||
- App loads in iOS simulator
|
||||
- App loads in Electron window
|
||||
- Nostr relay WebSocket connections work
|
||||
- Theme renders correctly
|
||||
- Navigation / SPA routing works
|
||||
|
||||
### Platform detection helper
|
||||
|
||||
Add a lightweight platform detection utility that the rest of the app can use:
|
||||
|
||||
```javascript
|
||||
// js/utils/platform.js
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
|
||||
export const platform = {
|
||||
isNative: Capacitor.isNativePlatform(),
|
||||
isAndroid: Capacitor.getPlatform() === 'android',
|
||||
isIos: Capacitor.getPlatform() === 'ios',
|
||||
isElectron: Capacitor.getPlatform() === 'electron',
|
||||
isWeb: Capacitor.getPlatform() === 'web',
|
||||
};
|
||||
```
|
||||
|
||||
This module is safe to import on web — `@capacitor/core` detects it's running in a browser and returns `'web'` for the platform.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Android & iOS
|
||||
|
||||
**Goal**: Production-quality mobile builds ready for app store submission.
|
||||
|
||||
### Android-specific work
|
||||
|
||||
1. **Permissions** in `AndroidManifest.xml`:
|
||||
- `INTERNET` (already default)
|
||||
- `ACCESS_NETWORK_STATE` (connectivity detection)
|
||||
- `CAMERA` / `MICROPHONE` — only if video recording is added later (not needed now)
|
||||
|
||||
2. **WebView configuration**:
|
||||
- Enable mixed content mode (for WebSocket `wss://` alongside `https://`)
|
||||
- Enable JavaScript (default in Capacitor)
|
||||
- Set `webSettings.mediaPlaybackRequiresUserGesture = false` for autoplay
|
||||
|
||||
3. **App icons and splash screens**:
|
||||
- Use `@capacitor/splash-screen` plugin
|
||||
- Generate from existing `assets/png/android-chrome-512x512.png`
|
||||
- Use `cordova-res` or `@capacitor/assets` to generate all density variants
|
||||
|
||||
4. **Deep links**:
|
||||
- Register `nostr:` URI scheme via Android intent filters
|
||||
- Register `https://bitvid.tv` domain for App Links (needs `.well-known/assetlinks.json`)
|
||||
|
||||
5. **Status bar and navigation bar**:
|
||||
- `@capacitor/status-bar` plugin to match the dark theme (`#0f172a`)
|
||||
- Handle safe areas / notch insets via CSS `env(safe-area-inset-*)`
|
||||
|
||||
### iOS-specific work
|
||||
|
||||
1. **Info.plist configuration**:
|
||||
- `NSAppTransportSecurity` — allow WebSocket connections
|
||||
- App description strings for any permission prompts
|
||||
|
||||
2. **WKWebView settings**:
|
||||
- `allowsInlineMediaPlayback: true`
|
||||
- `mediaTypesRequiringUserActionForPlayback: []` (enables autoplay)
|
||||
- These are configurable in `capacitor.config.ts` under `ios.webContentsConfiguration`
|
||||
|
||||
3. **Deep links**:
|
||||
- Universal Links (needs `apple-app-site-association` on the domain)
|
||||
- Custom URL scheme `bitvid://`
|
||||
|
||||
4. **Safe areas**:
|
||||
- iOS notch, Dynamic Island, home indicator
|
||||
- CSS `env(safe-area-inset-top)` etc. — update layout in `index.html` and key UI components
|
||||
|
||||
5. **App icons**:
|
||||
- Generate full icon set from existing 512x512 source
|
||||
- Must include 1024x1024 for App Store
|
||||
|
||||
### Shared mobile work
|
||||
|
||||
1. **Hardware back button** (Android):
|
||||
- Handle back navigation within the SPA instead of exiting the app
|
||||
- Use `@capacitor/app` plugin's `backButton` listener
|
||||
|
||||
2. **App lifecycle**:
|
||||
- Pause/resume WebSocket connections on app background/foreground
|
||||
- Use `@capacitor/app` `appStateChange` listener
|
||||
|
||||
3. **Keyboard handling**:
|
||||
- Adjust layout when virtual keyboard appears (search bar, comment inputs)
|
||||
- Capacitor provides `Keyboard` plugin for this
|
||||
|
||||
4. **Haptic feedback** (optional):
|
||||
- Light haptics on zap actions using `@capacitor/haptics`
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Desktop (Electron via Capacitor)
|
||||
|
||||
**Goal**: Ship bitvid as a standalone desktop app for Windows, macOS, and Linux.
|
||||
|
||||
### Approach
|
||||
|
||||
Use Capacitor's Electron integration so the desktop build uses the same `dist/` web assets and the same Capacitor plugin bridge as mobile.
|
||||
|
||||
### Desktop-specific work
|
||||
|
||||
1. **Window configuration**:
|
||||
- Default size: 1280x800 (fits well for video content)
|
||||
- Minimum size: 800x600
|
||||
- Remember window position/size across launches
|
||||
- Frameless window option with custom title bar (matches bitvid header)
|
||||
|
||||
2. **Menu bar**:
|
||||
- Minimal native menu (File, Edit, View, Help)
|
||||
- Or hide native menu and use the web app's own navigation
|
||||
|
||||
3. **Tray icon** (optional):
|
||||
- System tray icon for background seeding (WebTorrent)
|
||||
- "Minimize to tray" behavior
|
||||
|
||||
4. **Auto-updater**:
|
||||
- Electron's `autoUpdater` for seamless desktop updates
|
||||
- Or use GitHub Releases as the update feed
|
||||
|
||||
5. **Protocol handler**:
|
||||
- Register `nostr:` and `bitvid:` URI schemes at OS level
|
||||
|
||||
6. **Packaging**:
|
||||
- Windows: NSIS installer or MSIX
|
||||
- macOS: DMG + notarization
|
||||
- Linux: AppImage, .deb, .rpm
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Platform-Specific Enhancements
|
||||
|
||||
These are optional features that improve the native experience. Implement after core functionality is solid.
|
||||
|
||||
### Native share sheet
|
||||
|
||||
```javascript
|
||||
import { Share } from '@capacitor/share';
|
||||
|
||||
async function shareVideo(video) {
|
||||
if (platform.isNative) {
|
||||
await Share.share({
|
||||
title: video.title,
|
||||
text: video.description,
|
||||
url: `https://bitvid.tv/#video/${video.id}`,
|
||||
dialogTitle: 'Share video',
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Push notifications (future)
|
||||
|
||||
Nostr-based push could be implemented via:
|
||||
- A lightweight push relay that watches for mentions/zaps and sends FCM/APNs
|
||||
- `@capacitor/push-notifications` plugin to receive them
|
||||
|
||||
This is a significant feature and should be its own planning document.
|
||||
|
||||
### Offline support
|
||||
|
||||
- Cache the app shell and critical assets in the service worker
|
||||
- Cache recently viewed video metadata for offline browsing
|
||||
- Queue zaps and reactions for when connectivity returns
|
||||
|
||||
### Picture-in-picture
|
||||
|
||||
- Android: native PiP mode via custom Capacitor plugin
|
||||
- iOS: `AVPictureInPictureController` via the WebView's native video player
|
||||
- Desktop: Electron `BrowserWindow` PiP API
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — CI/CD for Native Builds
|
||||
|
||||
### New CI workflows
|
||||
|
||||
#### `ci-android.yml`
|
||||
|
||||
```yaml
|
||||
name: CI — Android
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'js/**'
|
||||
- 'css/**'
|
||||
- 'views/**'
|
||||
- 'components/**'
|
||||
- 'android/**'
|
||||
- 'capacitor.config.ts'
|
||||
- 'package.json'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'js/**'
|
||||
- 'css/**'
|
||||
- 'views/**'
|
||||
- 'components/**'
|
||||
- 'android/**'
|
||||
- 'capacitor.config.ts'
|
||||
|
||||
jobs:
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build web assets
|
||||
run: npm run build
|
||||
- name: Sync Capacitor
|
||||
run: npx cap sync android
|
||||
- name: Build Android debug APK
|
||||
working-directory: android
|
||||
run: ./gradlew assembleDebug
|
||||
- name: Upload APK artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bitvid-android-debug
|
||||
path: android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
#### `ci-ios.yml`
|
||||
|
||||
```yaml
|
||||
name: CI — iOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['js/**', 'css/**', 'views/**', 'ios/**', 'capacitor.config.ts']
|
||||
pull_request:
|
||||
paths: ['js/**', 'css/**', 'views/**', 'ios/**', 'capacitor.config.ts']
|
||||
|
||||
jobs:
|
||||
build-ios:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build web assets
|
||||
run: npm run build
|
||||
- name: Sync Capacitor
|
||||
run: npx cap sync ios
|
||||
- name: Install CocoaPods
|
||||
working-directory: ios/App
|
||||
run: pod install
|
||||
- name: Build iOS (no sign)
|
||||
run: |
|
||||
xcodebuild build \
|
||||
-workspace ios/App/App.xcworkspace \
|
||||
-scheme App \
|
||||
-destination 'generic/platform=iOS Simulator' \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO
|
||||
```
|
||||
|
||||
#### `ci-desktop.yml`
|
||||
|
||||
```yaml
|
||||
name: CI — Desktop
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['js/**', 'css/**', 'views/**', 'electron/**', 'capacitor.config.ts']
|
||||
pull_request:
|
||||
paths: ['js/**', 'css/**', 'views/**', 'electron/**', 'capacitor.config.ts']
|
||||
|
||||
jobs:
|
||||
build-desktop:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build web assets
|
||||
run: npm run build
|
||||
- name: Sync Capacitor
|
||||
run: npx cap sync electron
|
||||
- name: Build Electron
|
||||
working-directory: electron
|
||||
run: npm run build
|
||||
```
|
||||
|
||||
### Path-filtered triggers
|
||||
|
||||
Each native CI workflow uses `paths` filters so that:
|
||||
- A CSS-only change triggers Android + iOS + Desktop builds (the web layer changed)
|
||||
- A change to only `android/` triggers just the Android build
|
||||
- Existing web CI (`ci.yml`) continues to run independently
|
||||
|
||||
### CI cost management
|
||||
|
||||
Native CI is more expensive than web CI:
|
||||
- **iOS builds require `macos-latest` runners** (2-3x cost on GitHub Actions)
|
||||
- **Android builds need JDK + Gradle** (slower startup)
|
||||
- **Desktop builds run on 3 OS matrix** entries
|
||||
|
||||
To manage costs:
|
||||
- Only run native CI on PRs that touch relevant paths
|
||||
- Cache Gradle/CocoaPods/node_modules aggressively
|
||||
- Consider running iOS CI only on `main` merges (not every PR) initially
|
||||
- Use GitHub Actions concurrency groups with `cancel-in-progress: true`
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Testing Strategy
|
||||
|
||||
### Testing layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Unit Tests (existing) │ ← Platform-agnostic
|
||||
│ vitest — js logic, nostr, schemas, utils │ No changes needed
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Visual Regression (existing) │ ← Web viewport
|
||||
│ Playwright — screenshot comparison │ Add mobile viewports
|
||||
├─────────────────────────────────────────────┤
|
||||
│ E2E Web (existing) │ ← Browser-based
|
||||
│ Playwright — user journeys in Chrome │ No changes needed
|
||||
├─────────────────────────────────────────────┤
|
||||
│ E2E Mobile (NEW) │ ← Device/emulator
|
||||
│ Appium or Detox — native app user flows │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Platform Smoke Tests (NEW) │ ← Quick validation
|
||||
│ Per-platform build + launch + basic checks │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Cross-Platform Behavior Tests (NEW) │ ← Feature parity
|
||||
│ Verify features work consistently across │
|
||||
│ web, Android, iOS, and desktop │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### New test categories
|
||||
|
||||
#### Platform smoke tests (`tests/native/`)
|
||||
|
||||
Quick checks that run after each native build:
|
||||
- App launches without crash
|
||||
- Main view renders
|
||||
- WebSocket connection to a relay succeeds
|
||||
- Video playback starts (URL source)
|
||||
- Theme renders correctly (screenshot comparison)
|
||||
|
||||
#### Mobile viewport visual tests
|
||||
|
||||
Extend existing Playwright visual tests with mobile viewports:
|
||||
```javascript
|
||||
// playwright.config.ts addition
|
||||
projects: [
|
||||
// existing desktop viewports...
|
||||
{
|
||||
name: 'Mobile Android',
|
||||
use: { ...devices['Pixel 7'] },
|
||||
},
|
||||
{
|
||||
name: 'Mobile iOS',
|
||||
use: { ...devices['iPhone 14'] },
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This runs in Playwright's browser emulation (not on real devices) but catches responsive layout issues early.
|
||||
|
||||
#### WebView-specific E2E tests
|
||||
|
||||
For testing inside the actual native WebView (not just a browser), use:
|
||||
- **Android**: Espresso + WebView assertions, or Appium with WebView context switching
|
||||
- **iOS**: XCUITest + WebView inspection, or Appium
|
||||
|
||||
These are heavier to set up but catch WebView-specific issues (service worker behavior, WebRTC in WKWebView, etc).
|
||||
|
||||
#### What to test per platform
|
||||
|
||||
| Test | Web | Android | iOS | Desktop |
|
||||
|------|-----|---------|-----|---------|
|
||||
| Unit tests | yes | n/a | n/a | n/a |
|
||||
| Visual regression (browser) | yes | mobile viewport | mobile viewport | yes |
|
||||
| E2E web journeys | yes | — | — | — |
|
||||
| App launches | — | yes | yes | yes |
|
||||
| Relay WebSocket connects | yes | yes | yes | yes |
|
||||
| Video playback (URL) | yes | yes | yes | yes |
|
||||
| Video playback (WebTorrent) | yes | yes | **test carefully** | yes |
|
||||
| Deep links (`nostr:`) | — | yes | yes | yes |
|
||||
| Back button navigation | — | yes | — | — |
|
||||
| Safe area rendering | — | — | yes | — |
|
||||
| Keyboard interaction | — | yes | yes | — |
|
||||
| Offline behavior | yes | yes | yes | yes |
|
||||
|
||||
---
|
||||
|
||||
## Platform Considerations & Risks
|
||||
|
||||
### WebTorrent on iOS (HIGH RISK)
|
||||
|
||||
**Issue**: WKWebView's WebRTC support is more limited than desktop Chrome. WebTorrent relies on WebRTC data channels for peer-to-peer streaming.
|
||||
|
||||
**Mitigation**:
|
||||
- bitvid already has a URL-first playback fallback (`DEFAULT_PLAYBACK_SOURCE` in instance config)
|
||||
- For iOS builds, default to URL-first playback: `DEFAULT_PLAYBACK_SOURCE: 'url'`
|
||||
- Use platform detection to set this automatically:
|
||||
```javascript
|
||||
if (platform.isIos) {
|
||||
setUrlFirstEnabled(true);
|
||||
}
|
||||
```
|
||||
- Test WebTorrent streaming on real iOS devices — it may work in modern iOS versions (16+), but should not be the primary path
|
||||
|
||||
### Service worker in Capacitor WebView
|
||||
|
||||
**Issue**: The `sw.min.js` service worker intercepts fetch requests to proxy WebTorrent stream data. Service worker support in native WebViews varies.
|
||||
|
||||
**Mitigation**:
|
||||
- Android: WKWebView supports service workers (API level 33+). For older Android, the URL fallback handles it.
|
||||
- iOS: WKWebView supports service workers in iOS 14+, but with some caveats around scope and lifecycle.
|
||||
- Test the WebTorrent streaming service worker flow specifically in native WebViews.
|
||||
- If service worker is unreliable, consider a Capacitor plugin that bridges native HTTP interception as an alternative stream proxy.
|
||||
|
||||
### WebSocket connectivity
|
||||
|
||||
**Issue**: Native platforms may restrict background WebSocket connections.
|
||||
|
||||
**Mitigation**:
|
||||
- Use `@capacitor/app` lifecycle events to gracefully disconnect/reconnect:
|
||||
```javascript
|
||||
App.addListener('appStateChange', ({ isActive }) => {
|
||||
if (isActive) {
|
||||
nostrClient.reconnect();
|
||||
} else {
|
||||
nostrClient.pause();
|
||||
}
|
||||
});
|
||||
```
|
||||
- This also reduces battery drain from idle relay connections.
|
||||
|
||||
### Content Security Policy
|
||||
|
||||
**Issue**: Capacitor serves web assets from a local origin (`capacitor://localhost` on iOS, `https://localhost` on Android). CSP rules may need adjustment.
|
||||
|
||||
**Mitigation**:
|
||||
- Update `_headers` / CSP meta tags to allow the Capacitor local origin
|
||||
- Allow `wss://*` for Nostr relay connections
|
||||
- Allow `blob:` and `data:` for WebTorrent stream playback
|
||||
|
||||
### App Store rejection risk
|
||||
|
||||
See the [App Store Strategy](#app-store-strategy) section below.
|
||||
|
||||
---
|
||||
|
||||
## App Store Strategy
|
||||
|
||||
### Google Play Store
|
||||
|
||||
**Risk level**: Low-Medium
|
||||
|
||||
Google is generally more permissive. Key considerations:
|
||||
- **Content rating**: Declare user-generated content (UGC) in the rating questionnaire
|
||||
- **Moderation**: bitvid's existing moderation system (NIP-56 reports, NIP-51 mute lists, blur thresholds, autoplay blocking) demonstrates active content moderation
|
||||
- **Crypto/payments**: Lightning/Zap functionality may trigger financial services review. Frame it as "tips" or "donations", not as a payment system. Emphasize that bitvid does not custody funds.
|
||||
|
||||
### Apple App Store
|
||||
|
||||
**Risk level**: Medium-High
|
||||
|
||||
Apple has been unpredictable with Nostr/decentralized apps. Key considerations:
|
||||
- **Guideline 4.2 (Minimum Functionality)**: The app must provide functionality beyond a "repackaged website". Native features (deep links, share sheet, haptics, PiP) help differentiate.
|
||||
- **Guideline 1.2 (User Generated Content)**: Must have content reporting, blocking, and moderation. bitvid already has this.
|
||||
- **Guideline 3.1.1 (In-App Purchase)**: If Lightning zaps are interpreted as digital purchases, Apple may require IAP. Mitigation: frame zaps as peer-to-peer tips external to the app (similar to how other Nostr apps handle this).
|
||||
- **Review strategy**: Submit early with a clear App Review note explaining the decentralized architecture and content moderation approach. Engage with the review team proactively.
|
||||
|
||||
### Desktop distribution
|
||||
|
||||
**Risk level**: Low
|
||||
|
||||
- **Windows**: Distribute via GitHub Releases initially. Microsoft Store submission optional.
|
||||
- **macOS**: Notarize the DMG with an Apple Developer account. Distribute via GitHub Releases or the Mac App Store.
|
||||
- **Linux**: AppImage on GitHub Releases. Snap Store / Flathub optional.
|
||||
|
||||
---
|
||||
|
||||
## Task Checklist
|
||||
|
||||
### Phase 1 — Foundation
|
||||
- [ ] Install `@capacitor/core` and `@capacitor/cli`
|
||||
- [ ] Create `capacitor.config.ts` with bitvid app ID and config
|
||||
- [ ] Run `npx cap add android` and verify project structure
|
||||
- [ ] Run `npx cap add ios` and verify project structure
|
||||
- [ ] Set up Electron via community Capacitor plugin
|
||||
- [ ] Add `js/utils/platform.js` platform detection utility
|
||||
- [ ] Update `package.json` with Capacitor scripts (`cap:sync`, `cap:android`, `cap:ios`, `cap:electron`)
|
||||
- [ ] Update `.gitignore` for native build artifacts
|
||||
- [ ] Verify web app loads in Android emulator
|
||||
- [ ] Verify web app loads in iOS simulator
|
||||
- [ ] Verify web app loads in Electron window
|
||||
- [ ] Verify Nostr relay connections work on all platforms
|
||||
- [ ] Document local development setup for native builds
|
||||
|
||||
### Phase 2 — Android & iOS
|
||||
- [ ] Configure Android permissions in `AndroidManifest.xml`
|
||||
- [ ] Configure iOS `Info.plist` settings
|
||||
- [ ] Generate app icons for all densities (Android + iOS)
|
||||
- [ ] Generate splash screens
|
||||
- [ ] Implement safe area CSS for iOS notch / Dynamic Island
|
||||
- [ ] Add `@capacitor/status-bar` and theme it to match bitvid
|
||||
- [ ] Add `@capacitor/app` for lifecycle events (background/foreground)
|
||||
- [ ] Implement hardware back button handling (Android)
|
||||
- [ ] Set up deep link handling for `nostr:` URIs
|
||||
- [ ] Configure WebView settings for media autoplay
|
||||
- [ ] Test video playback (URL source) on both platforms
|
||||
- [ ] Test video playback (WebTorrent) on both platforms
|
||||
- [ ] Test service worker behavior in native WebViews
|
||||
- [ ] Set iOS default to URL-first playback if WebTorrent is unreliable
|
||||
|
||||
### Phase 3 — Desktop
|
||||
- [ ] Configure Electron window defaults (size, min size, position memory)
|
||||
- [ ] Set up native menu bar or hide it
|
||||
- [ ] Register `nostr:` protocol handler at OS level
|
||||
- [ ] Configure packaging (NSIS/DMG/AppImage)
|
||||
- [ ] Test on Windows, macOS, and Linux
|
||||
- [ ] Set up auto-updater with GitHub Releases feed
|
||||
|
||||
### Phase 4 — Platform Enhancements
|
||||
- [ ] Implement native share sheet integration
|
||||
- [ ] Add `@capacitor/keyboard` for mobile input handling
|
||||
- [ ] Explore picture-in-picture on each platform
|
||||
- [ ] Add haptic feedback for zap actions (optional)
|
||||
- [ ] Implement app shell caching in service worker for offline support
|
||||
|
||||
### Phase 5 — CI/CD
|
||||
- [ ] Create `ci-android.yml` workflow
|
||||
- [ ] Create `ci-ios.yml` workflow
|
||||
- [ ] Create `ci-desktop.yml` workflow (3-OS matrix)
|
||||
- [ ] Add path filters to limit when native CI runs
|
||||
- [ ] Set up artifact uploads for debug builds
|
||||
- [ ] Configure caching (Gradle, CocoaPods, node_modules)
|
||||
- [ ] Add concurrency groups to cancel stale runs
|
||||
- [ ] Document release signing process (keystores, certificates, notarization)
|
||||
|
||||
### Phase 6 — Testing
|
||||
- [ ] Add mobile viewport projects to Playwright config
|
||||
- [ ] Create platform smoke test suite (`tests/native/`)
|
||||
- [ ] Set up Android emulator tests in CI
|
||||
- [ ] Set up iOS simulator tests in CI
|
||||
- [ ] Test WebTorrent streaming in native WebViews on real devices
|
||||
- [ ] Test service worker behavior across platforms
|
||||
- [ ] Test deep link handling on all platforms
|
||||
- [ ] Create cross-platform feature parity test matrix
|
||||
- [ ] Document known platform-specific limitations in `KNOWN_ISSUES.md`
|
||||
|
||||
### App Store Submission
|
||||
- [ ] Create Google Play developer account
|
||||
- [ ] Prepare Play Store listing (screenshots, description, content rating)
|
||||
- [ ] Submit Android app for review
|
||||
- [ ] Create Apple Developer account (if not existing)
|
||||
- [ ] Prepare App Store listing (screenshots, description, review notes)
|
||||
- [ ] Submit iOS app for review
|
||||
- [ ] Set up GitHub Releases for desktop distribution
|
||||
- [ ] Notarize macOS DMG
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Playback Fallback Architecture](playback-fallback.md) — URL-first strategy that mitigates WebTorrent issues on mobile
|
||||
- [Instance Configuration](instance-config.md) — Platform-specific config overrides
|
||||
- [WebTorrent Architecture](webtorrent-architecture.md) — Service worker streaming details
|
||||
- [Moderation Service Overview](moderation-service-overview.md) — Content moderation (relevant for app store review)
|
||||
Reference in new issue
Block a user