@realtime-inspector/react-native-gzip
v0.10.0
Published
Native, **off-thread** gzip for the Reincraft React Native SDK.
Readme
@realtime-inspector/react-native-gzip
Native, off-thread gzip for the Reincraft React Native SDK.
An optional companion to @realtime-inspector/react-native. Install it and pass its
gzipBody to the provider — the SDK then gzip-compresses every upload batch on a
native thread (zlib), cutting wire bytes ~×9 without touching the JS thread.
Don't install it and the SDK sends uncompressed bodies; the backend accepts both, so
this is a pure, fully backward-compatible bandwidth optimization with no
wire-contract change (the web SDK does the same via the platform
CompressionStream).
A pure-JS gzip (pako/fflate) was rejected because DEFLATE on the JS thread competes
with the app during a live experience (the observer-effect budget is a hard rule).
This module moves the compression off-thread via a Nitro HybridObject; only a cheap
TextEncoder runs on the JS thread.
Install & wire
bun add @realtime-inspector/react-native-gzip react-native-nitro-modules
cd ios && pod install # iOS
# Android: gradle sync (autolinked)import { ReincraftProvider } from '@realtime-inspector/react-native'
import { gzipBody } from '@realtime-inspector/react-native-gzip'
<ReincraftProvider config={{ compress: gzipBody }}>{children}</ReincraftProvider>The app drives the import on purpose: Metro resolves require() calls
statically at bundle time, so the always-bundled binding can't guard-require an
optional native package (an uninstalled one would fail the bundle, and a runtime
try/catch can't catch a build-time resolution error). Importing gzipBody here is
how Metro learns to bundle this module — only when you've actually installed it.
Requires the React Native New Architecture (Nitro/JSI). With it absent, the
native object won't register; gzipBody then resolves null per call and the SDK
falls back to uncompressed sends (a flush never fails on compression).
Implementation
One shared C++ implementation for both platforms (zlib: libz on iOS, the NDK
zlib on Android), because gzip is identical across platforms and Apple's
Compression framework emits raw DEFLATE rather than the gzip wrapper the backend's
gunzip expects.
- Spec:
src/specs/gzip.nitro.ts—gzip(data: ArrayBuffer, level?: number): Promise<ArrayBuffer>. - Impl:
cpp/HybridGzip.{hpp,cpp}— copies the input bytes on the JS thread (a borrowed JSArrayBufferis only valid there), thendeflates on Nitro's thread pool withwindowBits = 15 + 16(the gzip wrapper) and resolves an owningArrayBuffer.
Regenerate the Nitro glue after a spec change: bun run nitrogen (then pod
install / gradle sync). If a linked-dependency native change doesn't take, bust the
autolinking cache: rm -rf android/build/generated/autolinking.
