Skip to content

Native Android APK wrapper for JSS (Kotlin + nodejs-mobile, WebView shell) #366

Description

@melvincarvalho

Ship a single-tap Android APK that bundles JSS for non-technical users. Realizes part of #46 ("Solid Pod on Every Phone").

History: original framing was Flutter; pivoted to native Kotlin after research showed there's no maintained Flutter↔nodejs-mobile plugin, and that the plugin layer is unnecessary for a WebView-shell architecture. Decision rationale in this comment.

Architecture

  • Native Android Studio project (Kotlin), single MainActivity
  • libnode.so linked directly via JNI/CMake — vendored from nodejs-mobile/nodejs-mobile releases
  • ~80 lines of C++ (JNI shim + stdout/stderr → logcat)
  • Foreground service wrapping the Node thread (persistent notification)
  • WebView pointed at http://127.0.0.1:4443/
  • JSS bundled as APK assets (full node_modules/, copied to Context.filesDir on first launch — ~62 MB tree per scripts/bundle-jss.sh)
  • All deps are pure JS — see feasibility spike

Repo

https://github.com/JavaScriptSolidServer/jss-android (default branch: gh-pages)

v1 scope (Android only)

  • Repo scaffolded with README + ARCHITECTURE + LICENSE
  • scripts/bundle-jss.sh — pulls JSS from npm, installs prod deps, refuses to bundle native modules
  • Launch args verified end-to-end on dev box: bin/jss.js start --single-user --port 4443 --host 127.0.0.1 --root <files-dir>/data --idp
  • Android Studio Kotlin project bones (gradle, manifest, MainActivity, theme)
  • scripts/fetch-libnode.sh — pulls libnode.so for arm64-v8a + armeabi-v7a from nodejs-mobile releases
  • JNI shim (native-lib.cpp, ~80 LOC, lifted from JaneaSystems samples)
  • Foreground service + persistent notification ("JSS pod running")
  • WebView wired to http://127.0.0.1:4443/, with port-bind retry (4443 → 4444 → ...)
  • First-launch asset copy from APK assets to Context.filesDir
  • AAB build with ABI splits (arm64-v8a, armeabi-v7a)
  • Documented v1 caveat: --git, --terminal disabled (both shell out via child_process, not supported by nodejs-mobile)

v1 caveats

  • No --git, no --terminalnodejs-mobile's sandboxed Node disallows child_process.
  • Loopback only by default (HTTP, not HTTPS). Public exposure path is --tunnel against a remote JSS.
  • Node 18.20.4 is the upstream mobile build (Node 18 EOL'd April 2025). 127.0.0.1 bind cuts the attack surface materially; long-term watch for a community Node 20/22 mobile build.
  • APK size: libnode is ~30 MB/ABI. Ship as AAB so Play Store delivers per-device.

Out of scope (v1)

  • iOS — App Store rule 2.5.2 grey zone on dynamic JS eval; needs separate audit.
  • postmarketOS / Linux ARM64 — later.
  • A native (Compose/Views) UI replacing the WebView — v2.

Prior art for this pattern

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions