noibu-react-native
v1.0.1
Published
Noibu session replay and error monitoring SDK for React Native
Downloads
6,064
Readme
noibu-react-native (v2)
Noibu session replay and error monitoring for React Native. v2 is a thin JS layer over the same
native SDKs every Noibu mobile app uses: on Android the com.noibu.mobile.android:session-replay
module (100% Noibu-owned rrweb capture — replay, touches with DXA selectors, keyboard, network,
crashes), on iOS the NoibuSessionReplay Swift package.
See the CHANGELOG for the v0.x → v2 changes and breaking changes.
Install
npm install noibu-react-native
cd ios && pod install # iOSSupports both the old and the new React Native architecture — one legacy native module, which RN's interop layer bridges on bridgeless.
iOS Podfile
Autolinking picks the pod up; nothing needs adding to your target block. Three notes:
- Your app must target iOS 14.0 or newer. React Native's template Podfile uses
platform :ios, min_ios_version_supported, which is below 14.0 on every React Native this package supports (13.4 on 0.75), so a default app fails to resolve before it builds anything:Specs satisfying the NoibuSessionReplay dependency were found, but they required a higher minimum deployment target. Setplatform :ios, '14.0'(or higher) in yourPodfileand match it in your app target'sIPHONEOS_DEPLOYMENT_TARGET. - Keep your existing linkage. The iOS SDK vendors a dynamic
coreKit.xcframework, which CocoaPods embeds into your app under any linkage mode — includinguse_frameworks! :linkage => :static(the RN default) and nouse_frameworks!at all. Do not switch to bareuse_frameworks!: Hermes does not support dynamic frameworks. - Xcode 15+ blocks the embed.
ENABLE_USER_SCRIPT_SANDBOXINGdefaults toYES, which makes CocoaPods'[CP] Embed Pods Frameworksphase fail withSandbox: rsync ... Operation not permittedand the app then crashes at launch withLibrary not loaded: @rpath/coreKit.framework/coreKit. Set it toNoon your app target, or in thePodfile:
post_install do |installer|
installer.aggregate_targets.each do |aggregate_target|
aggregate_target.user_project.native_targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_USER_SCRIPT_SANDBOXING'] = 'NO'
end
end
aggregate_target.user_project.save
end
endAndroid Gradle
Autolinking picks the module up; it resolves com.noibu.mobile.android:session-replay from Maven
Central, which every React Native template already lists. One requirement:
minSdkVersionmust be at least 26 (Android 8.0). React Native itself builds at 23, and the app template inherits that, so a default app fails the build before it compiles anything:Manifest merger failed : uses-sdk:minSdkVersion 23 cannot be smaller than version 26 declared in library [com.noibu.mobile.android:session-replay]Raise
minSdkVersioninandroid/build.gradle. v0.x had no floor of its own — it inherited the app's — so this is an upgrade step for existing integrations.
Initialize
import { Noibu } from 'noibu-react-native';
Noibu.init({
domain: 'shop.example.com', // provided by Noibu
});Call it early (e.g. top of index.js / App.tsx). All capture starts natively — no further wiring
required. Optional config:
Noibu.init({
domain: 'shop.example.com',
logLevel: 'info', // integration-debugging diagnostics
trackTouches: true,
trackKeyboard: true,
trackNetwork: true, // kill switch for all HTTP capture
trackErrors: true, // JS handlers + native crash handler
autoTrackNavigation: false, // native page per Activity/Fragment (no-wiring fallback)
});Lifecycle
Noibu.isInitialized is true between a successful init and shutdown. shutdown() stops all
capture, flushes the native pipeline and re-opens init, so the SDK can be cycled inside one
process (a logout, an app that only records part of its flows):
await Noibu.shutdown();
// … later, a new session starts from scratch
await Noibu.init({ domain: 'shop.example.com' });It resolves once the native teardown has run and never rejects. Errors and attributes reported while
the SDK is down return NOT_INITIALIZED and — exactly as before the first init — are buffered
(bounded) and applied to the next session if one starts. What the ended session had buffered is
dropped rather than replayed into it.
Pages (react-navigation)
import { createNavigationContainerRef, NavigationContainer } from '@react-navigation/native';
import { useNoibuNavigation } from 'noibu-react-native';
const navigationRef = createNavigationContainerRef();
function App() {
useNoibuNavigation(navigationRef);
return <NavigationContainer ref={navigationRef}>…</NavigationContainer>;
}Other routers: call Noibu.trackNavigation('ScreenName') when a screen appears, or set
autoTrackNavigation: true.
Errors & attributes
Noibu.addError(new Error('Payment declined'));
Noibu.addError('Payment declined', stackString);
Noibu.addCustomAttribute('customerId', '42');You don't have to await Noibu.init first: attributes set while it is still in flight are held
(up to the 10-attribute limit) and applied when it lands, and the same is true of errors and page
names. They still return the v0.x NOT_INITIALIZED string in that window.
Up to 500 errors are reported per page visit (the SDK caps them natively, so the count resets on every page visit — including the ones it opens itself when the app returns from the background).
Uncaught JS exceptions, unhandled promise rejections (Hermes) and native crashes are captured
automatically. Wrap subtrees with ErrorBoundary to also catch React render errors:
import { ErrorBoundary } from 'noibu-react-native';
<ErrorBoundary fallback={<Oops />}>…</ErrorBoundary>The v0.x setupNoibu and NoibuJS.* entry points still work (deprecated — see the CHANGELOG).
Network capture
Both platforms capture JS fetch/XHR, remote <Image> loads and native requests with no JS
patching, on bare React Native and on Expo alike. Nothing to wire up: autolinking and pod install
are the whole integration.
Capture is installed as the app starts rather than when Noibu.init resolves, because the HTTP
clients an app sends through are built during startup — an Expo app's fetch and every remote image
went uncaptured while this waited for init. Requests are still only recorded while the SDK is
initialized and trackNetwork is on, so the early install changes nothing else. WebSocket
connections are not reported as requests: they carry no response, and a reconnecting socket would
otherwise arrive as a run of failed requests.
If your app installs its own factory/provider, add Noibu to it instead
(installNetworkInstrumentation() is idempotent, so doing both is safe):
OkHttpClientProvider.setOkHttpClientFactory {
OkHttpClientProvider.createClientBuilder(context)
.installNetworkInstrumentation() // com.noibu.mobile.sessionreplay.api
.build()
}Request/response bodies and header values are captured (up to 64 KB) and PII-redacted before they
touch the device cache: values under sensitive key names (password, email, cardNumber, …) are
replaced, then card / email / SSN / SIN / phone patterns are scrubbed from the remaining text — the
same rule the v0.x JS SDK applied.
GraphQL responses are checked for failures the status code hides: a 2xx JSON response at a
graphql URL (or application/graphql) carrying an errors array is reported as an error per
entry, so a declined payment isn't filed as a successful call.
RCTSetCustomNSURLSessionConfigurationProvider(^NSURLSessionConfiguration *{
NSURLSessionConfiguration *configuration = NSURLSessionConfiguration.defaultSessionConfiguration;
[[NoibuHTTPInterceptor shared] installNetworkInstrumentationOn:configuration]; // @import NoibuSessionReplay
return configuration;
});WebView capture (hybrid replay)
Wrap web content in NoibuWebView — a drop-in replacement for react-native-webview's WebView
that enables Noibu tracking on the underlying native webview BEFORE its first document loads
(replay, clicks, JS errors and HTTP of the page join the session timeline):
import { NoibuWebView } from 'noibu-react-native';
<NoibuWebView source={{ uri: 'https://shop.example.com/checkout' }} />It takes the same props as WebView (including ref) and fills its parent by default; pass
style to size it explicitly. Mount order doesn't matter — a webview on the opening screen waits
for Noibu.init to land before loading its content, and renders untracked as soon as init fails
(or after a short wait, if init is never called at all).
Input values inside a tracked webview are masked in replay (rrweb's own default masks passwords
only), and a click on a field reports the field's label, not its content — the same rule the native
walkers apply to <TextInput>.
Requires the optional peer dependency react-native-webview >= 13. Webviews rendered with the
plain WebView component are NOT captured — wrapping is the per-webview opt-in (mirror of the
native SDK's WebViewTracking.enable), so sensitive webviews (payment 3DS, SSO) stay untracked
by simply not wrapping them. trackWebViews: false in NoibuConfig disables webview capture
SDK-wide regardless of wrapping.
Known limitations
privacyModeis not selectable — both native SDKs now mask display text behind it, but the bridge does not pass the flag, so an RN app always gets the native defaultMASK_SENSITIVE: card/SSN/email/phone spans in rendered text are scrubbed, everything else replays as written.<TextInput>content is never captured on any mode — both platforms' walkers ship the field'splaceholder(or***when it has none) instead of what the user typed, and the keyboard monitor records the focused field, not its contents. Inputs inside aNoibuWebVieware masked too.- Android animated images: a GIF or animated WebP replays as its first frame (React Native
reuses one drawable object for the whole animation). Static images are captured when they load
and again whenever their
sourcechanges. <ActivityIndicator>: a spinner replays as a still image of its first frame — its presence is what the session records, not its rotation.- Animated SVGs: a continuously animating
react-native-svggraphic replays its first 30 redraws and then freezes — each distinct render is its own replay asset.<Svg>content that changes with state (an icon recolouring, a filled/outline toggle) is captured on every change. The same 30-change bound covers the other rasterized controls (<Switch>, a determinate progress bar, a slider), so a slider dragged across many values freezes once it is reached. - iOS truncated text: a
numberOfLines={1}title that ends in an ellipsis on device replays in full and can overrun the elements beside it — UIKit exposes no truncation state, so deciding it would mean measuring every text view on every capture. Android clips it back to its box. - iOS borders under the new architecture: React Native draws its own borders on iOS and keeps the widths/colours in C++ under Fabric, where the capture cannot read them — so a bordered view (outlined card, list separator, input underline) replays with no border on a Fabric host. Apps on the old architecture, and Android, capture them.
- RN < 0.72: corner radii of RN-drawn native views may not be captured in replay (backgrounds and borders are; older hosts degrade gracefully — text, layout and images still capture).
