Skip to content

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

@melvincarvalho

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions