ADRs
ADR-0007: Mobile native build & SDK
Native development build and the shared client SDK.
Status
Accepted
Context
Plan stage 15 requires the Expo client to reach feature parity with the Next.js web client: catalog, upload/download, envelopes, conflict/tombstone views, and the full Path A/B/C/D transfer chain. Two facts make that impossible with the current code shape:
- Path A/B are native capabilities. mDNS browsing and WebRTC require
react-native-zeroconfandreact-native-webrtc, which cannot load in Expo Go. ADR-0004 already chose Expo's managed workflow with Continuous Native Generation (CNG) and reserved a native escape hatch; reaching parity now requires that build. - The client logic lives in
apps/web/liband is welded to the browser. Catalog, sync-state, keys, envelopes, the uploader, and the transfer attempt-path talk directly to IndexedDB,localStorage,XMLHttpRequest,File/Blob, and Next route handlers. Copying that intoapps/mobilewould duplicate the most correctness-sensitive code in the project and guarantee the two clients drift.
The Relay also authenticates GET /ws from the HttpOnly session cookie only,
while native clients hold the opaque session ID rather than a browser cookie
jar (§13 / Phase 7a).
Decision
- Native development build, unconditionally.
apps/mobilemoves from Expo Go to an EAS/expo prebuildCNG development build. Config plugins/deps:react-native-webrtc(Path A/B),react-native-zeroconf(mDNS),expo-sqlite(local catalogue/queues/keys),expo-crypto(getRandomValues/randomUUID),expo-document-picker+expo-file-systemexpo-sharing(file I/O), andreact-native-sse(Path A trickle ICE, since React Native ships noEventSource). iOS declares_nodus._tcpinNSBonjourServices; Android grants the multicast/Wi-Fi permissions mDNS requires.
- New
packages/sdkis the single client implementation. It holds platform-agnostic services (auth/session, device identity, catalogue, folders, keys, envelopes, uploader/download, sync-state, pairing helpers, selectors) behind adapter interfaces:RelayHttp,LocalStore,SecureStore,FileSource/FileSink,WebRtcFactory,WebSocketFactory,CryptoRuntime,Connectivity, andPlatformCapabilities. The web app and the mobile app each supply one adapter set;apps/webmigrates onto the SDK in this phase so only one implementation exists. - Mobile session transport is the opaque session, not a token or cookie.
Mobile stores the session ID in
expo-secure-storeand sends it asAuthorization: Beareron HTTP. The Relay'sGET /wsis extended to resolve the same credential through the existingauth.AuthenticateRequest(cookie first, then bearer), so the WS gateway keeps exactly one session model. No JWT, refresh token, or?token=query parameter is introduced. - Path A stays foreground-only. Discovery and direct transfer run only while the app is foregrounded (ADR-0004). Background sync is deferred to stage 17.
- Pairing mirrors the web flow. Mobile mints a
NODUS-XXXX-XXXXcode viaPOST /pairing/codes, displays it with the operator'sPUBLIC_RELAY_URL, and pollsGET /nodesfor the new node. QR scanning remains a non-goal.
Consequences
- Positive: one implementation of the transfer/envelope/catalogue logic for both clients; native Path A/B become possible; WS and REST share one session credential; the web app gets a tested adapter boundary as a side effect.
- Negative: native builds require an Android/iOS toolchain or EAS; CI can
only typecheck, test the JS, and run
expo prebuild/expo export, not produce signed binaries. Theapps/webmigration is a broad, test-guarded refactor with regression risk. - Reversibility: the SDK adapters keep a browser-only future viable; if a native module proves incompatible with Expo SDK 57, the ADR-0004 escape hatch (bare workflow) remains available without touching the SDK boundary.
Non-goals
- No background local discovery or background sync (stage 17).
- No QR pairing.
- No second long-lived auth protocol, JWT, or refresh token.
- No shared mobile UI package yet;
@repo/uiis React-DOM/Tailwind and stays web-only, with mobile primitives local toapps/mobile.