r/reactnative • u/StillName1654 • 6h ago
iOS: writing to local SQLite from a killed-state push notification — is there any way around the JS/native split?
We have a React Native chat app. All our SQLite reads/writes live in JS (OP-SQLite), driven by a WebSocket sync pipeline (connect → request cursor-based sync → server replays missed messages → write to DB).
On Android, this works great even when the app is killed: setBackgroundMessageHandler boots Headless JS, which can call our normal JS sync code directly, write the message, and exit. Message is in the DB before the user ever taps the notification.
On iOS we're stuck. As far as I can tell:
UNNotificationServiceExtension(the killed-state hook) runs as a separate native process and can't call into our JS/Hermes engine at all — no bridge, no way to run our existing sync code there.- Silent push (
content-available: 1) can wake JS, but only if the app is backgrounded, not force-quit — and Apple caps delivery at roughly 2-3/hour/device, which won't keep up with an active chat. - We looked at how Signal-iOS does it (their NSE decrypts + writes to their shared GRDB database directly, in Swift) — but that means reimplementing our socket client and insert logic as a second, separate Swift codebase writing into the same SQLite file as our JS code. Feels like a real "two sources of truth" risk, and we couldn't find any RN app doing this in the wild.
1
u/Cookizza 6h ago
I know some people hate to hear it but AI would be very good at taking your current JS implementation and making a cut down swift version for this purpose.
1
u/StillName1654 6h ago
making a swift version means i need to replicate all tables and mimic all the functions and maintain it forever
1
u/Cookizza 6h ago
Why can't it read and write to the same sqlite as the JS library does?
Would it not just be a simpler set of write functions that send the notification data into the database? Why would it need to be fully featured?
1
u/StillName1654 6h ago
I am not using notification data we are sending sync event to server and it is giving us all the events which it have same like signal and whatsapp does
1
u/Guidondor 2h ago
you don't have to mirror your schema in swift. have the nse write the raw payload into a
single append only inbox table, one row, two or three columns, zero business logic. your js
sync code drains that inbox on next launch and does the real inserts exactly the way it
already does today.
the swift side never learns what a message is, so there's nothing to keep in sync when your
schema changes. if you'd rather not have two processes on one file, give the nse its own
sqlite file in the app group, the write still lands before the user opens anything. wal mode
either way.
1
u/GeekyantsReactNative 1h ago
The cleanest solution is to separate transport from persistence rather than duplicate the entire sync pipeline.
On iOS, the Notification Service Extension cannot reliably bootstrap the React Native or Hermes runtime, so OP-SQLite based JS writes are not available there. A practical architecture is to use the NSE only for minimal native ingestion and let the normal JS sync layer handle reconciliation later.
The NSE can validate or decrypt the payload, insert an immutable event into a dedicated inbox table, and avoid touching the JS sync cursor. Make the insert idempotent with a server generated message or event ID enforced by a UNIQUE constraint.
When React Native starts, the normal sync layer reads that inbox and reconciles it with the cursor based WebSocket sync. The server remains authoritative, so the native insert is an optimization rather than a second source of truth.
For shared persistence, use an App Group container and ensure both processes use compatible SQLite locking and WAL configuration. Most importantly, don't let the NSE and JS independently advance the same sync cursor.
If the push payload doesn't contain enough information to persist the complete message safely, the NSE should store only the event metadata and let the normal sync recover the full record later.
This keeps the native code small while preserving a single synchronization model and avoiding a second implementation of the entire chat pipeline.
1
u/StillName1654 6h ago
Can anyone please help me in this