iOS port of the PrivacySafe 3NWeb platform layer, structured for incremental
implementation by developers working from the Android reference source
(android-platform-poc-main) and the core protocol library
(core-3nweb-client-lib).
The Android app (android-platform-poc-main) runs two structurally different JS hosts:
web-guicomponents — visible app UI (Chat, Contacts, Treasure, etc.) run inandroid.webkit.WebView, one perGenericAppActivity.denocomponents and the platform core itself — headless service scripts and the core library run inandroidx.javascriptengine.JavaScriptSandbox/JavaScriptIsolate: a bare, no-DOM V8 isolate with nothing pre-installed except what the host explicitly injects.
The iOS mapping:
| Android | iOS | Notes |
|---|---|---|
WebView per web-gui component |
WKWebView per component |
Both are real browser-engine DOM contexts. WKWebView supports MessageChannel natively. |
JavaScriptSandbox/JavaScriptIsolate per core/deno component |
JSContext (JavaScriptCore) per component |
Both are bare, no-DOM JS engines populated entirely by host-injected globals. JSContext runs in-process (no separate-process isolation), which is why the named-port machinery in IpcIntoJSEngine.kt simplifies to direct Swift closures here. |
CoreRunnerService (START_STICKY foreground service) |
CoreSession (in-process singleton) |
iOS has no always-on foreground service concept. The core runs while the app is in the foreground. Background messaging notifications require a separate BGProcessingTask or push-notification-wakeup path — not yet implemented in this skeleton. |
addJavascriptInterface(_, "_ipc_") |
WKScriptMessageHandler + injected w3n-shim.js |
WKWebView cannot expose synchronously-callable native methods. The shim presents the same window._ipc_ surface the real setup-w3n.bundle.js expects, via async postMessage round-trips. |
js.createMessageChannel(portName, executor) |
Direct Swift closure exported into JSContext via JSValue |
JSContext runs in-process; there is no process-isolation reason for named ports. The full fn-calls.proto protobuf wire format is preserved. |
common-preload.js (polyfills URL, self, setTimeout) |
Resources/JSEngine/common-preload.js |
Same polyfill contract. setTimeout routes through a native Swift call instead of Android's delay port round-trip, since JSContext supports direct synchronous function export. |
core-load.js |
Resources/JSEngine/core-load.js |
Same wiring shape. Loads core-3nweb-client-lib bundle, passes native function references for crypto/FS/network. |
app-preload.js |
Resources/JSEngine/app-preload.js |
Same wiring shape. Wires a deno component's globalThis.w3n and the core-ipc channel. |
KeyStoreOps.kt — AndroidKeyStore AES-128-GCM, IV prepended |
Core/DeviceKeyCrypto.swift — Keychain, CryptoKit AES-GCM, same wire format |
Byte-compatible. |
RandomFn.kt — java.security.SecureRandom |
SecRandomCopyBytes in Core/CoreInjectedFns.swift |
Direct equivalence. |
RequestFn.kt — OkHttp, port name http_request |
URLSession in Core/CoreInjectedFns.swift |
Same proto shape (request.proto). Also handles WebSocket open (ws_open). |
FsOps.kt / FhOps — POSIX-style FS + file-descriptor layer |
Core/FsInjectedFns.swift |
Full port. All 21 port names preserved exactly. |
ecma-nacl-cryptors WASM crypto |
Same WASM bundle in JSContext |
No native crypto code needed. WASM runs natively in JavaScriptCore. |
ios-skeleton/
├── README.md
├── LICENSE # GNU AGPLv3
├── NOTICE # Third-party copyright notices
├── BUILDING.md # Build pipeline for core-3nweb-client-lib bundle
├── Package.swift # SPM package (SwiftProtobuf dependency)
├── docs/ # Technical documentation
│ ├── 00-index.md
│ ├── 01-architecture.md
│ ├── 02-ipc-transport.md
│ ├── 03-native-capabilities.md
│ ├── 04-lifecycle.md
│ └── 05-resource-serving.md
└── PrivacySafeiOS/
├── Protos/ # Verbatim .proto files from android-platform-poc-main
│ ├── fn-calls.proto # Core IPC message shapes
│ ├── fs-op.proto # Filesystem op arg/result shapes
│ ├── request.proto # HTTP/WebSocket shapes
│ ├── keystore-op.proto
│ ├── random-op.proto
│ ├── delay-op.proto
│ ├── connectivity.proto
│ ├── components.proto
│ └── init.proto
├── Core/
│ ├── Bundled.swift # App domain constants, mirrors utils.kt's Bundled companion
│ ├── CoreSession.swift # In-process core singleton, mirrors CoreRunnerService
│ ├── CoreInjectedFns.swift # Native handlers: random, connectivity, HTTP, WS, keystore
│ ├── FsInjectedFns.swift # Native handlers: all FS and file-descriptor ops
│ ├── DeviceKeyCrypto.swift # AES-GCM device-key wrap, mirrors KeyStoreOps.kt
│ └── WebSocketManager.swift # URLSessionWebSocketTask lifecycle, mirrors WebSockets.kt
├── JSBridge/
│ ├── JSEngineHost.swift # JSContext wrapper, mirrors IpcIntoJSEngine.kt
│ ├── CoreHost.swift # Core isolate, mirrors CoreRunner.kt
│ ├── DenoComponentHost.swift # Headless component isolate, mirrors AppComponentRunner.kt
│ └── IpcBridge.swift # WKWebView _ipc_ bridge, mirrors IpcIntoWebView.kt
├── GUI/
│ ├── RootViewController.swift # Navigation shell; permissions; app entry point
│ └── AppWebViewController.swift # WKWebView host per GUI component, mirrors GenericAppActivity.kt
└── Resources/
├── w3n-shim.js # Injected into WKWebView at document start
└── JSEngine/
├── common-preload.js # Polyfills for JSContext (no DOM, no timers)
├── core-load.js # Wires core-3nweb-client-lib into JSContext
└── app-preload.js # Wires a deno component's w3n + core-ipc port
SwiftProtobuf is declared in
Package.swift and fetched automatically:
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.28.0"),SwiftProtobuf is copyright Apple Inc. and contributors, licensed under the Apache License 2.0.
Run protoc against each .proto file in Protos/ to generate Swift types:
protoc --swift_out=PrivacySafeiOS/Generated \
--proto_path=PrivacySafeiOS/Protos \
fn-calls.proto fs-op.proto request.proto \
keystore-op.proto random-op.proto delay-op.proto \
connectivity.proto components.proto init.protoGenerated files go in PrivacySafeiOS/Generated/ (not tracked in this
repository — they are build artifacts).
See BUILDING.md for the complete build pipeline. The compiled
bundle and WASM files are generated artifacts and are not tracked in this
repository. Add the following to .gitignore:
PrivacySafeiOS/Resources/JSEngine/core-3nweb-client-lib.bundle.js
PrivacySafeiOS/Resources/JSEngine/*.wasm
PrivacySafeiOS/Generated/
1. The core-3nweb-client-lib compiled JS bundle.
Build from source following BUILDING.md, or obtain from the PrivacySafe
release distribution. Place as
PrivacySafeiOS/Resources/JSEngine/core-3nweb-client-lib.bundle.js.
2. WASM files.
Produced as part of the same build step (see BUILDING.md). Place
*.wasm files in PrivacySafeiOS/Resources/JSEngine/.
3. Generated protobuf Swift files.
Run protoc as described above. Place generated files in
PrivacySafeiOS/Generated/.
The real setup-w3n.bundle.js runtime-ipc calls window._ipc_.listObjPath(...)
synchronously — it is a plain function call with a return value, not a
Promise. WKScriptMessageHandler cannot fulfil a synchronous native call; every
JS→native communication is fire-and-forget.
Two viable approaches, neither pre-decided here:
Option A — Patch the bundle. In the platform-ts build, change the one
call site in runtime-ipc.ts that calls androidIPC.listObjPath to be
async/await and propagate async upward. This is a one-line change in that
file and a small ripple into makeClientSideConnector. This gives a correct
iOS-specific bundle.
Option B — Pre-warm cache. Before loading the page in WKWebView, make
the listObjPath result for ['w3n', ...] available synchronously by fetching
it from the core (via CoreSession) and storing it in the shim's closure scope
before the page script runs.
w3n-shim.js currently returns an empty array with a console warning for this
call. See docs/05-resource-serving.md §5 for full analysis.
Many app manifests declare launchOnSystemStartup components
(background-instance.mjs for Chat, contactDenoServices.js for Contacts,
treasureDenoServices.js for Treasure). Android starts these as
CoreRunnerService deno components on boot via InitOnBoot.
iOS has no equivalent always-on service. Three strategies are available
(see docs/04-lifecycle.md §6 for full discussion):
- Foreground-only — run in-process, stop when app is backgrounded.
- Push notifications — for Chat, pair with a push notification gateway.
- BGProcessingTask — for periodic background sync (Contacts, Treasure).
None are implemented in this skeleton.
- SwiftUI root view / launcher shell —
RootViewControlleris UIKit-based. A SwiftUI app shell usingUIViewControllerRepresentableis possible but not yet provided. BGProcessingTask/ push wakeup for background deno components — seedocs/04-lifecycle.md§6 for the three available strategies. None are implemented.listObjPathsynchrony gap — see the section above anddocs/05-resource-serving.md§5.
Detailed specifications for every layer of the platform are in docs/:
docs/01-architecture.md— System decomposition, two-runtime model, component taxonomydocs/02-ipc-transport.md— Wire protocol, port model, native dispatch, WebView bridgedocs/03-native-capabilities.md— Every injected port: contract, proto type, iOS implementationdocs/04-lifecycle.md— Boot sequence, login state machine, component lifecycledocs/05-resource-serving.md— App resource delivery, device FS layer, open-flag semantics
Copyright (C) 2026 Ivy Cyber LLC
This project is licensed under the GNU Affero General Public License version
3.0 or later. See LICENSE for the full license text.
This project is derived in part from upstream works by 3NSoft Inc., licensed
under the GNU General Public License version 3.0 or later. The Protocol Buffer
schema files in PrivacySafeiOS/Protos/ are verbatim copies from
android-platform-poc-main by 3NSoft Inc. The compiled
core-3nweb-client-lib bundle (generated; see BUILDING.md)
is derived from core-3nweb-client-lib by 3NSoft Inc., also GPL-3.0-or-later.
The GNU General Public License version 3.0 is compatible with the GNU Affero General Public License version 3.0 under AGPL-3.0 §13.
See NOTICE for the complete list of third-party components and
their copyright notices.