chore: Fix app
This commit is contained in:
@@ -31,10 +31,6 @@ npm run cap:sync
|
||||
npm run cap:open:android
|
||||
npm run cap:open:ios
|
||||
|
||||
### Linux: Android Studio path
|
||||
|
||||
Capacitor defaults to `/usr/local/android-studio/bin/studio.sh`. If Android Studio is installed elsewhere (common with **Flatpak** from Flathub), `npm run cap:open:android` uses `tools/resolve-android-studio-path.js` to locate `studio.sh` (Flatpak `active` symlink, Toolbox, snap, `/opt`, etc.). Override anytime with `CAPACITOR_ANDROID_STUDIO_PATH`.
|
||||
|
||||
# Convenience (build + sync + open)
|
||||
npm run cap:build:android
|
||||
npm run cap:build:ios
|
||||
@@ -44,6 +40,10 @@ npm run cap:apk:android
|
||||
# → toju-app/android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
### Linux: Android Studio path
|
||||
|
||||
Capacitor defaults to `/usr/local/android-studio/bin/studio.sh`. If Android Studio is installed elsewhere (common with **Flatpak** from Flathub), `npm run cap:open:android` uses `tools/resolve-android-studio-path.js` to locate `studio.sh` (Flatpak `active` symlink, Toolbox, snap, `/opt`, etc.). Override anytime with `CAPACITOR_ANDROID_STUDIO_PATH`.
|
||||
|
||||
Config: `toju-app/capacitor.config.ts` (`webDir: ../dist/client/browser`).
|
||||
|
||||
### CI (Gitea)
|
||||
@@ -85,13 +85,15 @@ Re-run `npm run cap:assets:android` whenever `images/icon-new-rounded.png` chang
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Push/local notifications | **Working (partial)** | Local notifications always available; remote push (FCM/APNs) registers only when Firebase/APNs is configured — app starts normally without `google-services.json` |
|
||||
| Chat message notifications | **Working** | `DesktopNotificationService` routes to `MobileNotificationsService.showMessage()` on Capacitor (LocalNotifications channel `toju-messages`); web `Notification` API is never used on native shells |
|
||||
| Server push dispatch | **Working (configured)** | Tokens persist in server SQLite; outbound FCM/APNs via env credentials |
|
||||
| In-call notifications | **Working (Capacitor)** | Persistent notification with answer/mute/hang-up actions |
|
||||
| Stream pop-out (PiP) | **Working (partial)** | Document PiP when WebView supports it; Android native PiP fallback via `MetoyouMobile` plugin |
|
||||
| Background voice | **Working (partial)** | Android foreground service; iOS `UIBackgroundModes` audio + CallKit active-call bridge |
|
||||
| iOS CallKit | **Working (partial)** | `MetoyouMobile.startCallKitSession` reports active calls; requires Xcode target wiring after `cap:sync` |
|
||||
| Screensharing | **Limited** | Disabled on iOS WebView; Android `getDisplayMedia` may work |
|
||||
| Screensharing | **Hidden on native mobile** | `getDisplayMedia` is unavailable in mobile WebViews; all screen-share buttons (private call, voice controls, floating controls, voice workspace) are gated behind `!viewport.isMobile() && !MobilePlatformService.isNativeMobile()` |
|
||||
| Composer attachments | **Working** | Mobile attachment button + hidden file input |
|
||||
| Attachment download/export | **Working** | `AttachmentDownloadService` delegates to `CapacitorAttachmentExportService` on native shells: copies disk-backed files (or fetches the object URL) into the public `Documents` directory with a timestamped name; anchor `download` links do nothing in the Android WebView |
|
||||
| Camera sharing | **Working** | Existing `getUserMedia` camera path in WebRTC stack |
|
||||
| Speakerphone | **Working (partial)** | Android `AudioManager` via `MetoyouMobile`; iOS `@capgo/capacitor-audio-session`; direct-call speaker toggle on native mobile |
|
||||
| Local DB (SQLite) | **Working** | `DatabaseService` routes Capacitor shells to `CapacitorDatabaseService` (native SQLite CRUD) |
|
||||
@@ -103,7 +105,7 @@ Re-run `npm run cap:assets:android` whenever `images/icon-new-rounded.png` chang
|
||||
- **iOS CallKit:** Plugin Swift source ships in `ios/App/App/MetoyouMobilePlugin.swift`; add it to the Xcode target if not auto-linked. Incoming-call UI is not fully bridged to WebRTC answer/hang-up yet.
|
||||
- **iOS screenshare:** `getDisplayMedia` is not available in WKWebView.
|
||||
- **Android PiP:** Native PiP enters activity-level PiP; WebView video may not always render inside PiP on all OEM WebViews.
|
||||
- **Production discovery:** `signal.toju.app` may not expose `/api/servers/featured` or `/trending`; client skips those calls for known hosts.
|
||||
- **Legacy discovery endpoints:** Older signal servers may not expose `/api/servers/featured` or `/trending` (they resolve as `/servers/:id` and return 404). The client still calls those routes on every online endpoint and **falls back per-endpoint to `GET /api/servers`** when 404 is returned — see [server-discovery.md](server-discovery.md).
|
||||
- **Push delivery:** Requires FCM service account and APNs key configuration on the signaling server.
|
||||
|
||||
## Push notification setup (FCM / APNs)
|
||||
@@ -133,8 +135,11 @@ Declared in `toju-app/android/app/src/main/AndroidManifest.xml`:
|
||||
| `BLUETOOTH_CONNECT` | Bluetooth headset routing during calls (Android 12+) |
|
||||
| `POST_NOTIFICATIONS` | Incoming/active call notifications |
|
||||
| `FOREGROUND_SERVICE` / `FOREGROUND_SERVICE_MICROPHONE` | Background voice session |
|
||||
| `READ_EXTERNAL_STORAGE` (maxSdk 32) / `WRITE_EXTERNAL_STORAGE` (maxSdk 29) | Attachment export to public `Documents` on Android 10 and below |
|
||||
|
||||
Before WebRTC capture, the client calls `MobileMediaService.ensureVoiceCapturePermissions()` / `ensureCameraCapturePermissions()`, which delegate to `MetoyouMobile.requestVoiceCapturePermissions()` / `requestCameraCapturePermissions()` on Capacitor shells. If the native plugin is unavailable or the bridge call fails, capture preflight defers to the WebView `getUserMedia` permission flow instead of aborting voice/camera joins.
|
||||
Before WebRTC capture, the client calls `MobileMediaService.ensureVoiceCapturePermissions()` / `ensureCameraCapturePermissions()`, which delegate to `MetoyouMobile.requestVoiceCapturePermissions()` / `requestCameraCapturePermissions()` on Capacitor shells. If the native plugin is unavailable or the bridge call fails, capture preflight defers to the WebView `getUserMedia` permission flow instead of aborting voice/camera joins. Preflight only blocks capture on an explicit native `denied` state (`mobile-media-permission.rules.ts`); a `prompt` state is deferred to the WebView so the user still gets the permission dialog.
|
||||
|
||||
Join and capture failures are surfaced in the UI instead of failing silently: `DirectCallService.joinCall` sets a `joinError` signal (`call.errors.*` i18n keys for signaling, capture-unsupported, mic permission, and mic unavailable cases), and the private-call and voice-controls components surface camera errors the same way.
|
||||
|
||||
On Capacitor startup, `MobileRuntimePermissionsService` (via `MobileAppLifecycleService.initialize()`) proactively prompts for microphone, camera, local-notification, and push-notification runtime permissions so Android 13+ shells do not keep every permission in the "Not allowed" state until the user joins voice or receives a call.
|
||||
|
||||
@@ -165,16 +170,19 @@ Tokens persist in server SQLite (`device_tokens` table). Outbound push uses repo
|
||||
| `APNS_BUNDLE_ID` | Defaults to `com.metoyou.app` |
|
||||
| `APNS_USE_SANDBOX` | `true` for development builds |
|
||||
|
||||
Manual dispatch (ops/testing):
|
||||
Manual dispatch (ops/testing). Requires `Authorization: Bearer`; `:userId` in the path **must match** the authenticated user (`403` otherwise):
|
||||
|
||||
```http
|
||||
POST /api/users/device-tokens/:userId/dispatch
|
||||
Authorization: Bearer <token>
|
||||
{ "title": "Incoming call", "body": "Alice is calling" }
|
||||
```
|
||||
|
||||
`POST /api/users/device-tokens` and `GET /api/users/device-tokens/:userId` apply the same rule: body/param `userId` must equal the bearer identity.
|
||||
|
||||
## Android foreground service
|
||||
|
||||
`VoiceCallForegroundService` starts when `MobileCallSessionService` begins an active call. Required manifest permissions:
|
||||
`VoiceCallForegroundService` starts when `MobileCallSessionService` begins an active call. The voice-channel path also starts/stops it directly: `MediaManager.enableVoice()` / `disableVoice()` call `startMobileVoiceForegroundSession()` / `stopMobileVoiceForegroundSession()` (`infrastructure/mobile/logic/mobile-voice-foreground-session.ts`) so channel voice keeps the mic alive when the app backgrounds. Required manifest permissions:
|
||||
|
||||
- `FOREGROUND_SERVICE`
|
||||
- `FOREGROUND_SERVICE_MICROPHONE`
|
||||
@@ -193,10 +201,13 @@ The service shows a low-importance ongoing notification while a call is active.
|
||||
- Routing: `infrastructure/persistence/database-backend.rules.ts` — Capacitor uses SQLite, not IndexedDB.
|
||||
- Per-user database files: `metoyou__<userId>` via `mobile-sqlite-database-name.rules.ts`.
|
||||
- First launch runs DDL migrations stored in the `meta` table. Schema init failures are cached per database file so the client does not retry in a loop.
|
||||
- **Custom emoji assets** persist in the `custom_emojis` table (`CapacitorDatabaseService.saveCustomEmoji` / `getCustomEmojis` / `deleteCustomEmoji`).
|
||||
- **Message revisions** persist in `meta` under keys `message-revision:<messageId>:<revision>` (JSON payload). See [message-integrity.md](message-integrity.md) and [custom-emoji.md](custom-emoji.md).
|
||||
|
||||
## Capacitor plugin loading
|
||||
|
||||
- `infrastructure/mobile/adapters/capacitor/capacitor-plugin-loader.ts` uses **static** `@capacitor/*` imports and `Capacitor.isPluginAvailable()` before returning a plugin. Do not `import()` plugin modules dynamically or `await` plugin objects (Capacitor proxies expose a throwing `.then()` stub).
|
||||
- `infrastructure/mobile/adapters/capacitor/capacitor-plugin-loader.ts` loads `@capacitor/*` modules via **dynamic `import()`** only when `isCapacitorNativeRuntime()` is true, and checks `Capacitor.isPluginAvailable()` before returning a plugin. Electron and browser shells never evaluate these imports at startup.
|
||||
- Do not `await` a Capacitor plugin proxy object directly — Capacitor proxies expose a throwing `.then()` stub; always call methods on the resolved plugin instance.
|
||||
- After adding or upgrading Capacitor plugins, run `npm run build:prod && npm run cap:sync` so Android/iOS native projects register `App`, `AppUpdate`, `LocalNotifications`, push, and SQLite.
|
||||
|
||||
## Safe area (Android)
|
||||
@@ -267,10 +278,16 @@ Phase 3 delivered:
|
||||
3. iOS CallKit bridge (partial) via `MetoyouMobile` plugin and `MobileCallKitService`.
|
||||
4. Android Firebase Gradle wiring with `google-services.json.example` (real file gitignored).
|
||||
5. Capacitor plugin availability checks to avoid hard failures when plugins are missing pre-sync.
|
||||
6. Discovery endpoint skip for production signal hosts without featured/trending routes.
|
||||
6. Discovery 404 fallback to public server listing on legacy signal hosts (see [server-discovery.md](server-discovery.md)).
|
||||
|
||||
Remaining work:
|
||||
|
||||
- Wire CallKit answer/end actions back into `DirectCallService`.
|
||||
- Migrate legacy IndexedDB mobile data into SQLite where needed.
|
||||
- Deploy featured/trending routes to production signal servers or add capability negotiation in health checks.
|
||||
|
||||
## Changelog
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-07-13 | Chat notifications routed to LocalNotifications (`toju-messages` channel + `ic_stat_metoyou` status icon); capture preflight blocks only on native `denied`; call join/camera errors surfaced via `call.errors.*`; voice channels start the foreground service; screen share hidden on native mobile; attachment export to `Documents`; full-screen overlays use `metoyou-fixed-safe-viewport` |
|
||||
| 2026-07-05 | Corrected discovery fallback (not host skip), plugin-loader dynamic imports, markdown fence; added Capacitor custom-emoji/revision persistence and dispatch auth rules |
|
||||
|
||||
Reference in New Issue
Block a user