@oneuptime/react-native-replay 13.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/OneUptimeReactNativeReplay.podspec +20 -0
- package/README.md +264 -0
- package/android/build.gradle +36 -0
- package/android/consumer-rules.pro +26 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/oneuptime/replay/OneUptimeReplayPackage.java +29 -0
- package/android/src/main/java/com/oneuptime/replay/OneUptimeReplayViewTreeModule.java +541 -0
- package/dist/index.cjs +4111 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +4088 -0
- package/dist/index.js.map +7 -0
- package/dist/types/ChunkBuffer.d.ts +47 -0
- package/dist/types/ChunkBuffer.d.ts.map +1 -0
- package/dist/types/Config.d.ts +59 -0
- package/dist/types/Config.d.ts.map +1 -0
- package/dist/types/Contract.d.ts +176 -0
- package/dist/types/Contract.d.ts.map +1 -0
- package/dist/types/Events.d.ts +14 -0
- package/dist/types/Events.d.ts.map +1 -0
- package/dist/types/MobileReplayRecorder.d.ts +176 -0
- package/dist/types/MobileReplayRecorder.d.ts.map +1 -0
- package/dist/types/NativeViewTree.d.ts +49 -0
- package/dist/types/NativeViewTree.d.ts.map +1 -0
- package/dist/types/Outbox.d.ts +29 -0
- package/dist/types/Outbox.d.ts.map +1 -0
- package/dist/types/ReplayProvider.d.ts +28 -0
- package/dist/types/ReplayProvider.d.ts.map +1 -0
- package/dist/types/Sanitize.d.ts +29 -0
- package/dist/types/Sanitize.d.ts.map +1 -0
- package/dist/types/Storage.d.ts +30 -0
- package/dist/types/Storage.d.ts.map +1 -0
- package/dist/types/TouchPrivacy.d.ts +15 -0
- package/dist/types/TouchPrivacy.d.ts.map +1 -0
- package/dist/types/Transport.d.ts +36 -0
- package/dist/types/Transport.d.ts.map +1 -0
- package/dist/types/ViewTreeSerializer.d.ts +29 -0
- package/dist/types/ViewTreeSerializer.d.ts.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.d.ts.map +1 -0
- package/ios/OneUptimeReplayViewTreeModule.m +17 -0
- package/ios/OneUptimeReplayViewTreeModule.swift +239 -0
- package/package.json +66 -0
- package/react-native.config.js +15 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |spec|
|
|
6
|
+
spec.name = "OneUptimeReactNativeReplay"
|
|
7
|
+
spec.version = package["version"]
|
|
8
|
+
spec.summary = package["description"]
|
|
9
|
+
spec.homepage = "https://oneuptime.com"
|
|
10
|
+
spec.license = package["license"]
|
|
11
|
+
spec.author = package["author"]
|
|
12
|
+
spec.source = {
|
|
13
|
+
:git => "https://github.com/OneUptime/oneuptime.git",
|
|
14
|
+
:tag => "#{spec.version}"
|
|
15
|
+
}
|
|
16
|
+
spec.platforms = { :ios => "13.0" }
|
|
17
|
+
spec.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
|
+
spec.swift_version = "5.7"
|
|
19
|
+
spec.dependency "React-Core"
|
|
20
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# `@oneuptime/react-native-replay`
|
|
2
|
+
|
|
3
|
+
Privacy-first session replay for React Native. The SDK samples the native view
|
|
4
|
+
hierarchy into synthetic rrweb events and sends them through OneUptime's normal
|
|
5
|
+
session-replay ingest pipeline. It records layout, tightly allowlisted visual
|
|
6
|
+
styles, navigation, touches, custom events, and JavaScript errors without
|
|
7
|
+
capturing screenshots or readable native text.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- React Native 0.73 or newer.
|
|
12
|
+
- `@react-native-async-storage/async-storage` installed in the application.
|
|
13
|
+
- A OneUptime RUM application with session replay enabled.
|
|
14
|
+
- The Android application ID or iOS bundle ID added to that application's
|
|
15
|
+
replay origin allowlist as `app://com.example.yourapp`.
|
|
16
|
+
|
|
17
|
+
This package contains native modules. **Expo Go is not supported.** Expo apps
|
|
18
|
+
must use a development build or EAS build after prebuild. Bare iOS apps must
|
|
19
|
+
run `pod install` (or `npx pod-install`) after installation. Android and iOS
|
|
20
|
+
modules autolink on supported React Native versions.
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @oneuptime/react-native-replay \
|
|
24
|
+
@react-native-async-storage/async-storage
|
|
25
|
+
npx pod-install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Start recording
|
|
29
|
+
|
|
30
|
+
Wrap the application root so the SDK can register one non-collapsable native
|
|
31
|
+
root and observe bubbling touch events. The provider can start the default
|
|
32
|
+
client for you:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import {
|
|
36
|
+
OneUptimeReplayProvider,
|
|
37
|
+
ReplayMask,
|
|
38
|
+
} from "@oneuptime/react-native-replay";
|
|
39
|
+
|
|
40
|
+
export default function App() {
|
|
41
|
+
return (
|
|
42
|
+
<OneUptimeReplayProvider
|
|
43
|
+
options={{
|
|
44
|
+
host: "https://oneuptime.example.com",
|
|
45
|
+
token: "YOUR_TELEMETRY_INGESTION_KEY",
|
|
46
|
+
appIdentifier: "YOUR_RUM_APPLICATION_IDENTIFIER",
|
|
47
|
+
mobileAppIdentifier: "com.example.checkout",
|
|
48
|
+
userRef: "support-user-42", // optional record-next-session target
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<RootNavigator />
|
|
52
|
+
</OneUptimeReplayProvider>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`mobileAppIdentifier` is the installed binary's Android `applicationId` or
|
|
58
|
+
iOS bundle identifier. It is canonicalized to lowercase and must contain at
|
|
59
|
+
least two reverse-DNS labels. `appIdentifier` is the OneUptime RUM application
|
|
60
|
+
identifier; the two identifiers are intentionally different.
|
|
61
|
+
|
|
62
|
+
For manual lifecycle control, omit `options` from the provider and use the
|
|
63
|
+
singleton:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import OneUptimeReplay from "@oneuptime/react-native-replay";
|
|
67
|
+
|
|
68
|
+
await OneUptimeReplay.start({
|
|
69
|
+
host: "https://oneuptime.example.com",
|
|
70
|
+
token: "YOUR_TELEMETRY_INGESTION_KEY",
|
|
71
|
+
appIdentifier: "YOUR_RUM_APPLICATION_IDENTIFIER",
|
|
72
|
+
mobileAppIdentifier: "com.example.checkout",
|
|
73
|
+
appName: "Checkout", // optional metadata override
|
|
74
|
+
appVersion: "2.4.1", // optional metadata override
|
|
75
|
+
userRef: "support-user-42", // optional targeting reference
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await OneUptimeReplay.stop();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`host` must be an HTTP(S) origin, not a URL with credentials, a path, query,
|
|
82
|
+
or fragment. The SDK fetches policy before capturing and fails closed if the
|
|
83
|
+
policy request or native bridge is unavailable.
|
|
84
|
+
|
|
85
|
+
## Privacy model
|
|
86
|
+
|
|
87
|
+
Mobile replay is deliberately stricter than web replay:
|
|
88
|
+
|
|
89
|
+
- `<Text>` content and `<TextInput>` values are never read by native code and
|
|
90
|
+
never appear in an event, regardless of the web masking policy.
|
|
91
|
+
- `<ReplayMask>` is a hard traversal boundary. The wrapper's frame becomes one
|
|
92
|
+
opaque placeholder and no descendant is visited.
|
|
93
|
+
- Images, WebViews, Skia, Metal/OpenGL, Canvas, SurfaceView, and TextureView
|
|
94
|
+
content is opaque. Only the containing frame is recorded.
|
|
95
|
+
- Native output is allowlisted to parent-relative geometry, canonical hex
|
|
96
|
+
background/border colors, bounded border width/radius, opacity, and z-order.
|
|
97
|
+
Accessibility content, arbitrary props, pixels, and developer test IDs are
|
|
98
|
+
not serialized.
|
|
99
|
+
- Error messages and stacks can quote user input, so v1 records the JavaScript
|
|
100
|
+
error type only when it is a built-in safe name and a fixed `[masked]`
|
|
101
|
+
marker, never a custom name, original message, or stack.
|
|
102
|
+
|
|
103
|
+
Use `ReplayMask` for any subtree whose mere shape or presence should not be
|
|
104
|
+
recorded:
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<ReplayMask>
|
|
108
|
+
<PaymentCard />
|
|
109
|
+
</ReplayMask>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Consent
|
|
113
|
+
|
|
114
|
+
When policy requires explicit consent, capture stays only in the bounded
|
|
115
|
+
in-memory pre-roll until the host grants consent. Nothing is persisted or sent
|
|
116
|
+
before that call.
|
|
117
|
+
|
|
118
|
+
The first policy request is always anonymous. `grantConsent()` always refreshes
|
|
119
|
+
the current policy before anything is persisted or sent. If `userRef` is
|
|
120
|
+
supplied, the SDK only includes it in a targeting refresh after the anonymous
|
|
121
|
+
policy says consent is not required, or during that post-consent refresh. A
|
|
122
|
+
failed or disabled fresh policy stops recording rather than falling back to the
|
|
123
|
+
older anonymous policy.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
await OneUptimeReplay.grantConsent();
|
|
127
|
+
await OneUptimeReplay.revokeConsent();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Revocation stops capture and clears the in-memory buffer, session identifier,
|
|
131
|
+
anonymous visitor identifier, and AsyncStorage outbox. A later grant starts a
|
|
132
|
+
new visit.
|
|
133
|
+
|
|
134
|
+
## Identity, tags, events, and routes
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
OneUptimeReplay.identify("support-user-42", {
|
|
138
|
+
plan: "enterprise",
|
|
139
|
+
role: "admin",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
OneUptimeReplay.setTags({ build: "2026.09.13", experiment: "checkout-b" });
|
|
143
|
+
OneUptimeReplay.addTag("region", "eu-west");
|
|
144
|
+
OneUptimeReplay.track("checkout_opened", { cartSize: 3 });
|
|
145
|
+
|
|
146
|
+
// Call from the navigation container's state-change callback.
|
|
147
|
+
await OneUptimeReplay.setRoute("/checkout?payment_token=discarded");
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Calling `identify()` before `start()` preserves that reference for the
|
|
151
|
+
consent-safe targeting refresh. A non-empty `userRef` passed to `start()` takes
|
|
152
|
+
precedence over an earlier pre-start `identify()` call. Changing or clearing
|
|
153
|
+
the normalized reference while recording seals the previous user's replay and
|
|
154
|
+
starts a new session before applying the new identity. The SDK immediately
|
|
155
|
+
revalidates targeting for the new reference; a target or manual trigger from
|
|
156
|
+
the old user never carries into the new session.
|
|
157
|
+
|
|
158
|
+
Identity and traits are sent only when the RUM application's policy enables
|
|
159
|
+
user-identity capture. Under mobile's mandatory `MaskAllText`, trait values,
|
|
160
|
+
custom-event property values, and manual-capture reasons use the same coarse
|
|
161
|
+
text masks as the browser recorder. Event names and the explicitly supplied
|
|
162
|
+
user reference stay readable. Tag values deliberately stay readable and
|
|
163
|
+
searchable, matching the browser contract; do not use tags for secrets. All
|
|
164
|
+
maps remain count/length bounded. Routes always drop query strings and
|
|
165
|
+
fragments and are stored as `app://com.example.checkout/checkout`. The
|
|
166
|
+
remaining path stays readable, so pass stable screen templates rather than
|
|
167
|
+
account names, order numbers, or other sensitive path values.
|
|
168
|
+
|
|
169
|
+
## Manual and error capture
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
await OneUptimeReplay.captureSession("support-request");
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
await submitOrder();
|
|
176
|
+
} catch (error) {
|
|
177
|
+
await OneUptimeReplay.captureError(error);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
React Native's existing global JavaScript error handler is chained only when
|
|
183
|
+
it can be safely restored. The SDK never takes ownership of an application
|
|
184
|
+
handler it cannot restore. In error-triggered mode a bounded 60-second/2 MiB
|
|
185
|
+
pre-roll is uploaded after a manual or JavaScript error trigger.
|
|
186
|
+
|
|
187
|
+
## Diagnostics
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const diagnostics = OneUptimeReplay.getDiagnostics();
|
|
191
|
+
// status, sessionId, sampled, triggered, consentState, pendingEvents, events
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Diagnostics contain recorder decisions and fixed reason codes, not view text.
|
|
195
|
+
Pass `debug: true` at startup to mirror those codes to the development console.
|
|
196
|
+
|
|
197
|
+
Use the current consented session ID to correlate the application's own traces
|
|
198
|
+
and logs with replay. The listener fires immediately when a session exists,
|
|
199
|
+
again on every rotation, and with `null` when capture stops or consent is
|
|
200
|
+
withdrawn:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const unsubscribe = OneUptimeReplay.onSessionChange((sessionId) => {
|
|
204
|
+
updateOpenTelemetryResource({ "session.id": sessionId });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const sessionId = OneUptimeReplay.getSessionId();
|
|
208
|
+
unsubscribe();
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
For independent clients or tests, instantiate `new MobileReplayRecorder()` and
|
|
212
|
+
pass it to `<OneUptimeReplayProvider recorder={client}>`.
|
|
213
|
+
|
|
214
|
+
## Durability and lifecycle
|
|
215
|
+
|
|
216
|
+
The SDK takes a view-tree sample every 500 ms, creates a full checkout at
|
|
217
|
+
least every 60 seconds, and closes a chunk at 15 seconds or 256 KiB. Chunks are
|
|
218
|
+
gzip-compressed with `fflate.gzipSync`. Each chunk is placed in an ordered,
|
|
219
|
+
bounded AsyncStorage outbox before POST and removed only after a terminal
|
|
220
|
+
server response. AppState backgrounding records visibility, closes the current
|
|
221
|
+
chunk, drains the outbox only when policy and consent permit uploading, and
|
|
222
|
+
pauses sampling; foregrounding resumes with a fresh full snapshot.
|
|
223
|
+
|
|
224
|
+
Policy is revalidated without HTTP-cache reuse before every foreground resume,
|
|
225
|
+
after identity changes, and at five-minute intervals while the app remains
|
|
226
|
+
active. An explicit disable takes effect before more capture; a transient
|
|
227
|
+
network failure outside the consent transition keeps the last valid policy and
|
|
228
|
+
retries on the next bounded refresh cadence. Consent grants always fail closed
|
|
229
|
+
when a fresh policy cannot be obtained.
|
|
230
|
+
|
|
231
|
+
The SDK never sets a browser `Origin` header. It sends
|
|
232
|
+
`x-oneuptime-replay-recorder-kind: rn-view-tree` and the mobile app identifier;
|
|
233
|
+
the server validates the identifier and synthesizes its exact `app://` origin.
|
|
234
|
+
|
|
235
|
+
## Fidelity limitations
|
|
236
|
+
|
|
237
|
+
- Reanimated/UI-thread animations are sampled, not frame-accurate.
|
|
238
|
+
- Image pixels, WebView content, Skia, Metal/OpenGL, and other drawing surfaces
|
|
239
|
+
replay as disclosed opaque rectangles.
|
|
240
|
+
- JavaScript errors are captured reliably. Hard native crashes may terminate
|
|
241
|
+
the process before JavaScript can close or upload a chunk and are not a
|
|
242
|
+
reliable session-replay trigger.
|
|
243
|
+
- Mobile operating systems may suspend networking immediately after an app
|
|
244
|
+
enters the background. Unacknowledged chunks remain in AsyncStorage for the
|
|
245
|
+
next launch.
|
|
246
|
+
- This is a wireframe replay, not a screenshot or video recording.
|
|
247
|
+
- Touches beginning in a `ReplayMask`, image, WebView, Skia/canvas, or other
|
|
248
|
+
opaque surface are discarded for the entire pointer sequence. If a gesture
|
|
249
|
+
crosses into one later, that touch and the remainder of its pointer sequence
|
|
250
|
+
are discarded. If current native target ancestry or the coordinate basis
|
|
251
|
+
cannot be verified, touch telemetry fails closed and is discarded.
|
|
252
|
+
|
|
253
|
+
## Package development
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
npm run compile
|
|
257
|
+
npm test
|
|
258
|
+
npm run build
|
|
259
|
+
npm pack --dry-run
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`prepack` and `prepublishOnly` both build ESM, CommonJS, source maps, and TypeScript
|
|
263
|
+
declarations. The published package contains those artifacts plus Android and
|
|
264
|
+
iOS native sources, the podspec, autolinking configuration, and this README.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
apply plugin: "com.android.library"
|
|
9
|
+
|
|
10
|
+
def safeExtGet(prop, fallback) {
|
|
11
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
android {
|
|
15
|
+
namespace "com.oneuptime.replay"
|
|
16
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 35)
|
|
17
|
+
|
|
18
|
+
defaultConfig {
|
|
19
|
+
minSdkVersion safeExtGet("minSdkVersion", 23)
|
|
20
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 35)
|
|
21
|
+
consumerProguardFiles "consumer-rules.pro"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
lintOptions {
|
|
25
|
+
abortOnError false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
repositories {
|
|
30
|
+
google()
|
|
31
|
+
mavenCentral()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
dependencies {
|
|
35
|
+
implementation "com.facebook.react:react-android"
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# These React Native APIs are accessed reflectively so the same SDK binary can
|
|
2
|
+
# support both the RN 0.73 legacy drawable and newer background implementations.
|
|
3
|
+
-keepnames class com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
4
|
+
-keepclassmembers class com.facebook.react.views.view.ReactViewBackgroundDrawable {
|
|
5
|
+
public int getColor();
|
|
6
|
+
public float getFullBorderWidth();
|
|
7
|
+
public int getBorderColor(int);
|
|
8
|
+
public float getFullBorderRadius();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
-keepnames class com.facebook.react.uimanager.BackgroundStyleApplicator
|
|
12
|
+
-keepclassmembers class com.facebook.react.uimanager.BackgroundStyleApplicator {
|
|
13
|
+
public static *** getBackgroundColor(...);
|
|
14
|
+
public static *** getBorderWidth(...);
|
|
15
|
+
public static *** getBorderColor(...);
|
|
16
|
+
public static *** getBorderRadius(...);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
-keep class com.facebook.react.uimanager.Spacing {
|
|
20
|
+
public static int ALL;
|
|
21
|
+
}
|
|
22
|
+
-keep class com.facebook.react.uimanager.style.LogicalEdge { *; }
|
|
23
|
+
-keep class com.facebook.react.uimanager.style.BorderRadiusProp { *; }
|
|
24
|
+
-keepclassmembers class com.facebook.react.uimanager.LengthPercentage {
|
|
25
|
+
public float resolve(float);
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package com.oneuptime.replay;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.ReactPackage;
|
|
6
|
+
import com.facebook.react.bridge.NativeModule;
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
9
|
+
|
|
10
|
+
import java.util.Collections;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
|
|
13
|
+
public final class OneUptimeReplayPackage implements ReactPackage {
|
|
14
|
+
@NonNull
|
|
15
|
+
@Override
|
|
16
|
+
public List<NativeModule> createNativeModules(
|
|
17
|
+
@NonNull ReactApplicationContext reactContext
|
|
18
|
+
) {
|
|
19
|
+
return Collections.singletonList(new OneUptimeReplayViewTreeModule(reactContext));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@NonNull
|
|
23
|
+
@Override
|
|
24
|
+
public List<ViewManager> createViewManagers(
|
|
25
|
+
@NonNull ReactApplicationContext reactContext
|
|
26
|
+
) {
|
|
27
|
+
return Collections.emptyList();
|
|
28
|
+
}
|
|
29
|
+
}
|