|
| 1 | +# Architecture |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Ship a single-tap Android install that runs a personal Solid pod on the phone. The user opens the app and a JSS instance is running at `http://localhost:4443/` inside the app's process; closing the app stops the pod (or, with the foreground service, keeps it alive in the background). |
| 6 | + |
| 7 | +## Components |
| 8 | + |
| 9 | +### 1. Bundled JSS (the asset) |
| 10 | + |
| 11 | +- Path inside APK: `assets/nodejs-project/` |
| 12 | +- Contents: full JSS tree as published to npm — `bin/`, `src/`, `package.json`, `node_modules/` |
| 13 | +- Why the full `node_modules/`: it's pure JS, so copying it once at build-time is cheaper than `npm install` on first launch (and avoids needing a network on first run) |
| 14 | +- Size estimate: ~1.4 MB unpacked (per `npm publish` output for `javascript-solid-server@0.0.169`) + node_modules ≈ 30–50 MB |
| 15 | + |
| 16 | +### 2. nodejs-mobile runtime |
| 17 | + |
| 18 | +- Provides a patched Node binary cross-compiled for Android (arm64-v8a, armeabi-v7a, x86_64) |
| 19 | +- Runs as a dedicated thread inside the Android process |
| 20 | +- Restrictions vs upstream Node: no `child_process`, no `cluster`, no `worker_threads` until recent versions, no native addons unless explicitly built for Android |
| 21 | + |
| 22 | +### 3. Flutter plugin (`nodejs-mobile-flutter`) |
| 23 | + |
| 24 | +- Boots the Node runtime, hands it the entry script (`assets/nodejs-project/bin/jss.js`) plus startup args |
| 25 | +- Provides a Dart channel for bidirectional message passing (used post-MVP for native UI ↔ JSS commands; v1 doesn't need it) |
| 26 | + |
| 27 | +### 4. Flutter UI |
| 28 | + |
| 29 | +- **v1**: a single `WebView` widget pointed at `http://localhost:4443/` |
| 30 | +- Initial route depends on JSS config; default lands on the IDP landing or the `/{pod}/` root |
| 31 | +- v2: native screens for sign-in, file browse, sharing — replacing the WebView screen by screen |
| 32 | + |
| 33 | +### 5. Android foreground service |
| 34 | + |
| 35 | +- Standard pattern: when JSS is "running" the app posts a persistent notification ("JSS is running on port 4443") |
| 36 | +- Required to avoid the OS killing the Node thread when the app goes to background |
| 37 | +- User can stop the pod by tapping the notification's Stop action |
| 38 | + |
| 39 | +## Boot sequence |
| 40 | + |
| 41 | +1. App `main()` → run Flutter app |
| 42 | +2. Flutter `initState` → call `NodejsMobile.startNodeProject()` with: |
| 43 | + - script: `bin/jss.js` |
| 44 | + - args: `['start', '--single-user', '--port', '4443', '--data-root', <android-files-dir>/data]` |
| 45 | +3. Node thread starts; JSS imports its modules; Fastify binds to `127.0.0.1:4443` |
| 46 | +4. Flutter listens for the "ready" message on the channel (or polls `GET /` until 200) and shows the WebView |
| 47 | + |
| 48 | +## Data layout on device |
| 49 | + |
| 50 | +- `<android-files-dir>/data/` — JSS pod data (passed via `--data-root` and `DATA_ROOT` env) |
| 51 | +- `<android-files-dir>/data/.idp/` — IDP credentials, keys |
| 52 | +- `<android-files-dir>/.well-known/` — token store, etc. |
| 53 | +- `<cache-dir>/` — JSS notification queues, ephemeral state |
| 54 | + |
| 55 | +The pod's filesystem layout is unchanged from desktop JSS — we just point `--data-root` at app-private storage. |
| 56 | + |
| 57 | +## Auth model on a single-user phone pod |
| 58 | + |
| 59 | +- `--single-user` mode: one pod, one WebID, no registration UI |
| 60 | +- Default: pod owner is the device user; the IDP login screen still works for cross-app sign-in flows (xlogin, did:nostr signers) |
| 61 | +- E2EE composes cleanly: the device-local Nostr key is the pod's WebID and the encryption key (see [JSS#365](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/365)) |
| 62 | + |
| 63 | +## Public exposure (optional) |
| 64 | + |
| 65 | +- v1: pod is loopback-only, accessible only inside the app's WebView |
| 66 | +- For sharing: `--tunnel` connects to a remote JSS that proxies a public hostname back to the local pod (decentralized ngrok) |
| 67 | +- Long-term: a "share via tunnel" toggle in the app settings, with a paired remote JSS service |
| 68 | + |
| 69 | +## What we're not doing |
| 70 | + |
| 71 | +| Choice | Why | |
| 72 | +|--------|-----| |
| 73 | +| Run Node via Termux instead of nodejs-mobile | Termux requires a separate app + manual setup; defeats "one-click install" | |
| 74 | +| Cross-compile native deps for Android | Not needed — JSS already uses pure-JS substitutes everywhere | |
| 75 | +| Custom Node fork for additional capabilities | nodejs-mobile is maintained; forking is a long-term burden | |
| 76 | +| iOS in v1 | App Store rule 2.5.2 (no executable code download) and embedded-interpreter policy is fuzzy; needs separate research | |
| 77 | +| Native Flutter UI in v1 | WebView is faster to ship and reuses JSS's existing IDP / mashlib UIs | |
| 78 | + |
| 79 | +## Open questions |
| 80 | + |
| 81 | +- Does `nodejs-mobile`'s most recent release ship Node ≥ 18? (verify before scaffold, but `engines.node: ">=18.0.0"` is JSS's floor) |
| 82 | +- Memory ceiling on cheap Android devices (1 GB RAM)? `oidc-provider` is the heaviest dep; might need `--idp` off in a "lite" mode |
| 83 | +- How do we surface the WebID/pod URL outside the app? (Deep link? Share sheet? A "copy URL" button?) |
| 84 | +- Does the foreground-service notification need a stop action wired to a graceful Fastify shutdown? (yes — need to expose this via the plugin channel) |
| 85 | + |
| 86 | +## Build & release |
| 87 | + |
| 88 | +- Debug builds: `flutter run` from a dev box with a connected device |
| 89 | +- Release: `flutter build apk --release` produces a signed APK; `flutter build appbundle` for Play Store |
| 90 | +- CI (later): GitHub Actions matrix with `subosito/flutter-action`, building both `--debug` and `--release` artifacts on each tag |
0 commit comments