@multiplayer-app/session-recorder-react-native 0.0.1-alpha.8 → 0.0.1
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/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.d.ts +6 -0
- package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js +1 -0
- package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js.map +1 -0
- package/dist/components/GestureCaptureWrapper/index.d.ts +1 -0
- package/dist/components/GestureCaptureWrapper/index.js +1 -0
- package/dist/components/GestureCaptureWrapper/index.js.map +1 -0
- package/dist/components/GestureCaptureWrapper.js +1 -1
- package/dist/components/GestureCaptureWrapper.js.map +1 -1
- package/dist/components/ScreenRecorderView/ScreenRecorderView.d.ts +5 -0
- package/dist/components/ScreenRecorderView/ScreenRecorderView.js +1 -0
- package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
- package/dist/components/ScreenRecorderView/index.d.ts +1 -0
- package/dist/components/ScreenRecorderView/index.js +1 -0
- package/dist/components/ScreenRecorderView/index.js.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -0
- package/dist/context/SessionRecorderContext.js +1 -1
- package/dist/context/SessionRecorderContext.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/otel/index.d.ts +0 -2
- package/dist/otel/index.js.map +1 -1
- package/dist/otel/instrumentations/gestureInstrumentation.js +1 -1
- package/dist/otel/instrumentations/gestureInstrumentation.js.map +1 -1
- package/dist/otel/instrumentations/index.d.ts +0 -3
- package/dist/otel/instrumentations/index.js +1 -1
- package/dist/otel/instrumentations/index.js.map +1 -1
- package/dist/patch/xhr.js +1 -1
- package/dist/patch/xhr.js.map +1 -1
- package/dist/recorder/gestureRecorder.d.ts +0 -9
- package/dist/recorder/gestureRecorder.js +1 -1
- package/dist/recorder/gestureRecorder.js.map +1 -1
- package/dist/recorder/index.d.ts +4 -3
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.js.map +1 -1
- package/dist/recorder/screenRecorder.d.ts +1 -6
- package/dist/recorder/screenRecorder.js +1 -1
- package/dist/recorder/screenRecorder.js.map +1 -1
- package/dist/session-recorder.d.ts +3 -2
- package/dist/session-recorder.js.map +1 -1
- package/dist/types/index.d.ts +2 -16
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/app-metadata.d.ts +16 -0
- package/dist/utils/app-metadata.js +1 -0
- package/dist/utils/app-metadata.js.map +1 -0
- package/dist/utils/platform.d.ts +35 -3
- package/dist/utils/platform.js +1 -1
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/rrweb-events.d.ts +1 -1
- package/dist/utils/rrweb-events.js +1 -1
- package/dist/utils/rrweb-events.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/AUTO_METADATA_DETECTION.md +108 -0
- package/package.json +6 -9
- package/scripts/generate-app-metadata.js +173 -0
- package/src/components/{GestureCaptureWrapper.tsx → GestureCaptureWrapper/GestureCaptureWrapper.tsx} +1 -25
- package/src/components/GestureCaptureWrapper/index.ts +1 -0
- package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +72 -0
- package/src/components/ScreenRecorderView/index.ts +1 -0
- package/src/components/index.ts +1 -0
- package/src/context/SessionRecorderContext.tsx +21 -89
- package/src/index.ts +8 -0
- package/src/otel/index.ts +1 -12
- package/src/otel/instrumentations/index.ts +1 -6
- package/src/patch/xhr.ts +2 -2
- package/src/recorder/gestureRecorder.ts +10 -134
- package/src/recorder/index.ts +9 -7
- package/src/recorder/screenRecorder.ts +6 -14
- package/src/session-recorder.ts +2 -3
- package/src/types/index.ts +2 -20
- package/src/utils/app-metadata.ts +31 -0
- package/src/utils/platform.ts +303 -6
- package/src/utils/rrweb-events.ts +2 -4
- package/src/version.ts +1 -1
- package/example-usage.tsx +0 -174
- package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
- package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -77
- package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -119
- package/src/recorder/gestureHandlerRecorder.ts +0 -157
package/src/utils/platform.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { IResourceAttributes } from '../types'
|
|
2
2
|
import Constants from 'expo-constants'
|
|
3
|
-
import { Platform } from 'react-native'
|
|
3
|
+
import { Platform, Dimensions, PixelRatio } from 'react-native'
|
|
4
|
+
import { version } from '../version'
|
|
5
|
+
import { getAutoDetectedAppMetadata } from './app-metadata'
|
|
6
|
+
|
|
7
|
+
// Global app metadata configuration for non-Expo apps
|
|
8
|
+
let globalAppMetadata: { name?: string; version?: string; bundleId?: string } = {}
|
|
9
|
+
|
|
10
|
+
// Cache for auto-detected metadata to avoid repeated file reads
|
|
11
|
+
let autoDetectedMetadata: { name?: string; version?: string; bundleId?: string } | null = null
|
|
4
12
|
|
|
5
13
|
export interface PlatformInfo {
|
|
6
14
|
isExpo: boolean
|
|
@@ -52,7 +60,7 @@ export function getPlatformAttributes(): Record<string, any> {
|
|
|
52
60
|
const platformInfo = detectPlatform()
|
|
53
61
|
|
|
54
62
|
const attributes: Record<string, any> = {
|
|
55
|
-
|
|
63
|
+
platform: platformInfo.isExpo ? 'expo' : 'react-native',
|
|
56
64
|
'device.type': platformInfo.deviceType,
|
|
57
65
|
}
|
|
58
66
|
|
|
@@ -75,13 +83,302 @@ export function isReactNativeEnvironment(): boolean {
|
|
|
75
83
|
return detectPlatform().isReactNative
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Configure app metadata for non-Expo React Native apps
|
|
88
|
+
* Call this function in your app initialization to provide app information
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* import { configureAppMetadata } from '@multiplayer-app/session-recorder-react-native'
|
|
93
|
+
*
|
|
94
|
+
* // In your App.tsx or index.js
|
|
95
|
+
* configureAppMetadata({
|
|
96
|
+
* name: 'My Awesome App',
|
|
97
|
+
* version: '1.2.3',
|
|
98
|
+
* bundleId: 'com.mycompany.myapp',
|
|
99
|
+
* buildNumber: '123',
|
|
100
|
+
* displayName: 'My App',
|
|
101
|
+
* })
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function configureAppMetadata(metadata: {
|
|
105
|
+
name?: string
|
|
106
|
+
version?: string
|
|
107
|
+
bundleId?: string
|
|
108
|
+
buildNumber?: string
|
|
109
|
+
displayName?: string
|
|
110
|
+
}): void {
|
|
111
|
+
globalAppMetadata = { ...globalAppMetadata, ...metadata }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get configured app metadata
|
|
116
|
+
*/
|
|
117
|
+
export function getConfiguredAppMetadata(): { name?: string, version?: string, bundleId?: string, buildNumber?: string, displayName?: string, } {
|
|
118
|
+
return { ...globalAppMetadata }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Automatically detect app metadata from common configuration files
|
|
123
|
+
* This runs without developer intervention
|
|
124
|
+
*/
|
|
125
|
+
function autoDetectAppMetadata(): { name?: string; version?: string; bundleId?: string } {
|
|
126
|
+
if (autoDetectedMetadata) {
|
|
127
|
+
return autoDetectedMetadata
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Get auto-detected metadata from build-time generated file
|
|
132
|
+
const autoMetadata = getAutoDetectedAppMetadata()
|
|
133
|
+
|
|
134
|
+
// Filter out undefined values
|
|
135
|
+
const metadata: { name?: string; version?: string; bundleId?: string } = {}
|
|
136
|
+
if (autoMetadata.name) metadata.name = autoMetadata.name
|
|
137
|
+
if (autoMetadata.version) metadata.version = autoMetadata.version
|
|
138
|
+
if (autoMetadata.bundleId) metadata.bundleId = autoMetadata.bundleId
|
|
139
|
+
|
|
140
|
+
autoDetectedMetadata = metadata
|
|
141
|
+
return metadata
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// Silently fail - this is optional auto-detection
|
|
144
|
+
autoDetectedMetadata = {}
|
|
145
|
+
return {}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Enhanced app metadata detection with automatic fallbacks
|
|
151
|
+
*/
|
|
152
|
+
function getAppMetadata(): { name?: string; version?: string; bundleId?: string } {
|
|
153
|
+
// Priority order:
|
|
154
|
+
// 1. Expo config (if available)
|
|
155
|
+
// 2. Manually configured metadata
|
|
156
|
+
// 3. Auto-detected metadata
|
|
157
|
+
// 4. Fallbacks
|
|
158
|
+
|
|
159
|
+
const expoMetadata = getExpoMetadata()
|
|
160
|
+
const configuredMetadata = getConfiguredAppMetadata()
|
|
161
|
+
const autoMetadata = autoDetectAppMetadata()
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
name: expoMetadata.name || configuredMetadata.name || autoMetadata.name,
|
|
165
|
+
version: expoMetadata.version || configuredMetadata.version || autoMetadata.version,
|
|
166
|
+
bundleId: expoMetadata.bundleId || configuredMetadata.bundleId || autoMetadata.bundleId,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get metadata from Expo config
|
|
172
|
+
*/
|
|
173
|
+
function getExpoMetadata(): { name?: string; version?: string; bundleId?: string } {
|
|
174
|
+
const expoConfig = Constants.default?.expoConfig || Constants.expoConfig
|
|
175
|
+
if (!expoConfig) return {}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
name: expoConfig.name,
|
|
179
|
+
version: expoConfig.version,
|
|
180
|
+
bundleId: expoConfig.ios?.bundleIdentifier || expoConfig.android?.package,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
78
183
|
|
|
79
184
|
export const getNavigatorInfo = (): IResourceAttributes => {
|
|
185
|
+
const platformInfo = detectPlatform()
|
|
186
|
+
const screenData = Dimensions.get('window')
|
|
187
|
+
const screenDataScreen = Dimensions.get('screen')
|
|
188
|
+
const pixelRatio = PixelRatio.get()
|
|
189
|
+
|
|
190
|
+
// Get device type based on screen dimensions
|
|
191
|
+
const getDeviceType = (): string => {
|
|
192
|
+
const { width, height } = screenData
|
|
193
|
+
const minDimension = Math.min(width, height)
|
|
194
|
+
const maxDimension = Math.max(width, height)
|
|
195
|
+
|
|
196
|
+
// Rough device type detection based on screen size
|
|
197
|
+
if (maxDimension >= 1024) {
|
|
198
|
+
return 'Tablet'
|
|
199
|
+
} else if (minDimension >= 600) {
|
|
200
|
+
return 'Large Phone'
|
|
201
|
+
} else {
|
|
202
|
+
return 'Phone'
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Get orientation
|
|
207
|
+
const getOrientation = (): string => {
|
|
208
|
+
const { width, height } = screenData
|
|
209
|
+
return width > height ? 'Landscape' : 'Portrait'
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Get OS version details
|
|
213
|
+
const getOSInfo = (): string => {
|
|
214
|
+
if (platformInfo.isExpo) {
|
|
215
|
+
const platform = Constants.default?.platform || Constants.platform
|
|
216
|
+
if (platform?.ios) {
|
|
217
|
+
return `iOS ${Platform.Version}`
|
|
218
|
+
} else if (platform?.android) {
|
|
219
|
+
return `Android ${Platform.Version}`
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (Platform.OS === 'ios') {
|
|
224
|
+
return `iOS ${Platform.Version}`
|
|
225
|
+
} else if (Platform.OS === 'android') {
|
|
226
|
+
return `Android ${Platform.Version}`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return `${Platform.OS} ${Platform.Version || 'Unknown'}`
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Get device info string
|
|
233
|
+
const getDeviceInfo = (): string => {
|
|
234
|
+
const deviceType = getDeviceType()
|
|
235
|
+
const osInfo = getOSInfo()
|
|
236
|
+
return `${deviceType} - ${osInfo}`
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Get browser/runtime info
|
|
240
|
+
const getBrowserInfo = (): string => {
|
|
241
|
+
if (platformInfo.isExpo) {
|
|
242
|
+
return `Expo ${platformInfo.expoVersion || 'Unknown'} - React Native`
|
|
243
|
+
}
|
|
244
|
+
return 'React Native'
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Get screen size string
|
|
248
|
+
const getScreenSize = (): string => {
|
|
249
|
+
return `${Math.round(screenData.width)}x${Math.round(screenData.height)}`
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Get app info with fallbacks for non-Expo apps
|
|
253
|
+
const getAppInfo = (): string => {
|
|
254
|
+
const appName = getAppName()
|
|
255
|
+
const appVersion = getAppVersion()
|
|
256
|
+
return `${appName} v${appVersion}`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Get app name with automatic detection
|
|
260
|
+
const getAppName = (): string => {
|
|
261
|
+
const metadata = getAppMetadata()
|
|
262
|
+
if (metadata.name) return metadata.name
|
|
263
|
+
|
|
264
|
+
// Try configured display name as fallback
|
|
265
|
+
const configuredMetadata = getConfiguredAppMetadata()
|
|
266
|
+
if (configuredMetadata.displayName) return configuredMetadata.displayName
|
|
267
|
+
|
|
268
|
+
// Final fallback
|
|
269
|
+
return 'React Native App'
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Get app version with automatic detection
|
|
273
|
+
const getAppVersion = (): string => {
|
|
274
|
+
const metadata = getAppMetadata()
|
|
275
|
+
if (metadata.version) return metadata.version
|
|
276
|
+
|
|
277
|
+
// Final fallback
|
|
278
|
+
return 'Unknown'
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Get bundle ID with automatic detection
|
|
282
|
+
const getBundleId = (): string => {
|
|
283
|
+
const metadata = getAppMetadata()
|
|
284
|
+
if (metadata.bundleId) return metadata.bundleId
|
|
285
|
+
|
|
286
|
+
// Fallback
|
|
287
|
+
return 'com.reactnative.app'
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Get build number with multiple fallback strategies
|
|
291
|
+
const getBuildNumber = (): string => {
|
|
292
|
+
// Try Expo config first
|
|
293
|
+
const expoBuildNumber =
|
|
294
|
+
Constants.default?.expoConfig?.ios?.buildNumber ||
|
|
295
|
+
Constants.expoConfig?.ios?.buildNumber ||
|
|
296
|
+
Constants.default?.expoConfig?.android?.versionCode ||
|
|
297
|
+
Constants.expoConfig?.android?.versionCode
|
|
298
|
+
if (expoBuildNumber) return expoBuildNumber.toString()
|
|
299
|
+
|
|
300
|
+
// Try configured metadata for non-Expo apps
|
|
301
|
+
const configuredMetadata = getConfiguredAppMetadata()
|
|
302
|
+
if (configuredMetadata.buildNumber) return configuredMetadata.buildNumber
|
|
303
|
+
|
|
304
|
+
// Fallback
|
|
305
|
+
return '1'
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Get network info (basic)
|
|
309
|
+
const getNetworkInfo = (): string => {
|
|
310
|
+
// This is a basic implementation - in a real app you might want to use @react-native-community/netinfo
|
|
311
|
+
return 'Unknown'
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Get hardware info
|
|
315
|
+
const getHardwareInfo = (): string => {
|
|
316
|
+
const pixelRatioInfo = `Pixel Ratio: ${pixelRatio}`
|
|
317
|
+
const screenDensity = PixelRatio.getFontScale()
|
|
318
|
+
return `${pixelRatioInfo}, Font Scale: ${screenDensity}`
|
|
319
|
+
}
|
|
320
|
+
|
|
80
321
|
return {
|
|
81
|
-
platform
|
|
82
|
-
|
|
83
|
-
|
|
322
|
+
// Core platform info
|
|
323
|
+
platform: platformInfo.isExpo ? 'expo' : 'react-native',
|
|
324
|
+
userAgent: getBrowserInfo(),
|
|
325
|
+
language: 'en', // Could be enhanced with react-native-localize if available
|
|
84
326
|
timestamp: new Date().toISOString(),
|
|
327
|
+
|
|
328
|
+
// Device and OS information
|
|
329
|
+
deviceInfo: getDeviceInfo(),
|
|
330
|
+
osInfo: getOSInfo(),
|
|
331
|
+
browserInfo: getBrowserInfo(),
|
|
332
|
+
|
|
333
|
+
// Screen information
|
|
334
|
+
screenSize: getScreenSize(),
|
|
335
|
+
pixelRatio: pixelRatio,
|
|
336
|
+
orientation: getOrientation(),
|
|
337
|
+
screenWidth: Math.round(screenData.width),
|
|
338
|
+
screenHeight: Math.round(screenData.height),
|
|
339
|
+
screenScale: pixelRatio,
|
|
340
|
+
|
|
341
|
+
// Device capabilities
|
|
342
|
+
hardwareConcurrency: 1, // React Native doesn't expose CPU cores directly
|
|
343
|
+
cookiesEnabled: 'N/A', // Not applicable in React Native
|
|
344
|
+
|
|
345
|
+
// App information
|
|
346
|
+
packageVersion: version,
|
|
347
|
+
appInfo: getAppInfo(),
|
|
348
|
+
appName: getAppName(),
|
|
349
|
+
appVersion: getAppVersion(),
|
|
350
|
+
bundleId: getBundleId(),
|
|
351
|
+
buildNumber: getBuildNumber(),
|
|
352
|
+
|
|
353
|
+
// Platform specific
|
|
354
|
+
platformType: platformInfo.platform,
|
|
355
|
+
platformVersion: platformInfo.platformVersion,
|
|
356
|
+
expoVersion: platformInfo.expoVersion,
|
|
357
|
+
deviceType: getDeviceType(),
|
|
358
|
+
|
|
359
|
+
// Additional device info
|
|
360
|
+
deviceModel: Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
361
|
+
deviceManufacturer: Platform.OS === 'ios' ? 'Apple' : 'Android Manufacturer',
|
|
362
|
+
deviceBrand: Platform.OS === 'ios' ? 'Apple' : 'Android Brand',
|
|
363
|
+
|
|
364
|
+
// Performance and hardware
|
|
365
|
+
hardwareInfo: getHardwareInfo(),
|
|
366
|
+
networkInfo: getNetworkInfo(),
|
|
367
|
+
|
|
368
|
+
// Screen details
|
|
369
|
+
screenDensity: PixelRatio.getFontScale(),
|
|
370
|
+
screenScaleFactor: pixelRatio,
|
|
371
|
+
windowWidth: Math.round(screenData.width),
|
|
372
|
+
windowHeight: Math.round(screenData.height),
|
|
373
|
+
fullScreenWidth: Math.round(screenDataScreen.width),
|
|
374
|
+
fullScreenHeight: Math.round(screenDataScreen.height),
|
|
375
|
+
|
|
376
|
+
// Environment info
|
|
377
|
+
isExpo: platformInfo.isExpo,
|
|
378
|
+
isReactNative: platformInfo.isReactNative,
|
|
379
|
+
environment: platformInfo.isExpo ? 'expo' : 'react-native',
|
|
380
|
+
|
|
381
|
+
// Additional platform attributes
|
|
85
382
|
...getPlatformAttributes(),
|
|
86
383
|
}
|
|
87
|
-
}
|
|
384
|
+
}
|
|
@@ -39,10 +39,9 @@ export function createFullSnapshotEvent(
|
|
|
39
39
|
nodeIdCounter: { current: number },
|
|
40
40
|
): eventWithTime {
|
|
41
41
|
// Create a virtual DOM node representing the screen as an image
|
|
42
|
-
const imageNodeId = nodeIdCounter.current++
|
|
43
42
|
const imageNode: serializedNodeWithId = {
|
|
44
43
|
type: NodeType.Element,
|
|
45
|
-
id:
|
|
44
|
+
id: 0,
|
|
46
45
|
tagName: 'img',
|
|
47
46
|
attributes: {
|
|
48
47
|
src: `data:image/${captureFormat};base64,${base64Image}`,
|
|
@@ -137,7 +136,6 @@ export function createFullSnapshotEvent(
|
|
|
137
136
|
*/
|
|
138
137
|
export function createIncrementalSnapshotWithImageUpdate(
|
|
139
138
|
base64Image: string,
|
|
140
|
-
imageNodeId: number,
|
|
141
139
|
captureFormat: string = 'jpg',
|
|
142
140
|
): eventWithTime {
|
|
143
141
|
const mutationData: mutationData = {
|
|
@@ -145,7 +143,7 @@ export function createIncrementalSnapshotWithImageUpdate(
|
|
|
145
143
|
texts: [],
|
|
146
144
|
attributes: [
|
|
147
145
|
{
|
|
148
|
-
id:
|
|
146
|
+
id: 0,
|
|
149
147
|
attributes: {
|
|
150
148
|
src: `data:image/${captureFormat};base64,${base64Image}`,
|
|
151
149
|
},
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.0.1
|
|
1
|
+
export const version = "0.0.1"
|
package/example-usage.tsx
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example usage of the React Native Session Recorder with rrweb integration
|
|
3
|
-
* This file demonstrates how to use the updated session recorder system
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from 'react'
|
|
7
|
-
import { View, Text, Button, StyleSheet, Alert } from 'react-native'
|
|
8
|
-
import { SessionRecorderProvider, useSessionRecorder } from './src/context/SessionRecorderContext'
|
|
9
|
-
import { EventType } from './src/types'
|
|
10
|
-
|
|
11
|
-
// Example app component
|
|
12
|
-
function App() {
|
|
13
|
-
return (
|
|
14
|
-
<SessionRecorderProvider
|
|
15
|
-
options={{
|
|
16
|
-
apiKey: 'your-api-key-here',
|
|
17
|
-
version: '1.0.0',
|
|
18
|
-
application: 'ExampleApp',
|
|
19
|
-
environment: 'development',
|
|
20
|
-
recordScreen: true,
|
|
21
|
-
recordGestures: true,
|
|
22
|
-
recordNavigation: true
|
|
23
|
-
}}
|
|
24
|
-
>
|
|
25
|
-
<MainContent />
|
|
26
|
-
</SessionRecorderProvider>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Main content component that will be wrapped by TouchEventCapture
|
|
31
|
-
function MainContent() {
|
|
32
|
-
const { client } = useSessionRecorder()
|
|
33
|
-
|
|
34
|
-
const handleStartSession = () => {
|
|
35
|
-
try {
|
|
36
|
-
client.start()
|
|
37
|
-
Alert.alert('Session Started', 'Recording has begun!')
|
|
38
|
-
} catch (error) {
|
|
39
|
-
Alert.alert('Error', `Failed to start session: ${error}`)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const handleStopSession = () => {
|
|
44
|
-
try {
|
|
45
|
-
client.stop()
|
|
46
|
-
Alert.alert('Session Stopped', 'Recording has ended!')
|
|
47
|
-
} catch (error) {
|
|
48
|
-
Alert.alert('Error', `Failed to stop session: ${error}`)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const handleRecordCustomEvent = () => {
|
|
53
|
-
// Example of recording a custom rrweb event
|
|
54
|
-
const customEvent = {
|
|
55
|
-
type: EventType.Custom,
|
|
56
|
-
data: {
|
|
57
|
-
customType: 'button_click',
|
|
58
|
-
buttonId: 'example_button',
|
|
59
|
-
timestamp: Date.now()
|
|
60
|
-
},
|
|
61
|
-
timestamp: Date.now()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
client.recordEvent(customEvent)
|
|
65
|
-
Alert.alert('Custom Event', 'Custom event recorded!')
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const handleGetRecordingStats = () => {
|
|
69
|
-
// This would need to be implemented in the SessionRecorder
|
|
70
|
-
// const stats = client.getRecordingStats()
|
|
71
|
-
// Alert.alert('Recording Stats', `Events recorded: ${stats.totalEvents}`)
|
|
72
|
-
Alert.alert('Recording Stats', 'Feature coming soon!')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return (
|
|
76
|
-
<View style={styles.container}>
|
|
77
|
-
<Text style={styles.title}>Session Recorder Example</Text>
|
|
78
|
-
<Text style={styles.subtitle}>This app demonstrates rrweb-compatible session recording for React Native</Text>
|
|
79
|
-
|
|
80
|
-
<View style={styles.buttonContainer}>
|
|
81
|
-
<Button title='Start Recording' onPress={handleStartSession} color='#4CAF50' />
|
|
82
|
-
</View>
|
|
83
|
-
|
|
84
|
-
<View style={styles.buttonContainer}>
|
|
85
|
-
<Button title='Stop Recording' onPress={handleStopSession} color='#F44336' />
|
|
86
|
-
</View>
|
|
87
|
-
|
|
88
|
-
<View style={styles.buttonContainer}>
|
|
89
|
-
<Button title='Record Custom Event' onPress={handleRecordCustomEvent} color='#2196F3' />
|
|
90
|
-
</View>
|
|
91
|
-
|
|
92
|
-
<View style={styles.buttonContainer}>
|
|
93
|
-
<Button title='Get Recording Stats' onPress={handleGetRecordingStats} color='#FF9800' />
|
|
94
|
-
</View>
|
|
95
|
-
|
|
96
|
-
<Text style={styles.instructions}>
|
|
97
|
-
Recording is now AUTOMATIC! When you start a session, the system will automatically:
|
|
98
|
-
{'\n'}• Capture screen snapshots periodically
|
|
99
|
-
{'\n'}• Record all touch interactions (start, move, end)
|
|
100
|
-
{'\n'}• Generate rrweb-compatible events
|
|
101
|
-
{'\n'}• No manual setup required!
|
|
102
|
-
</Text>
|
|
103
|
-
</View>
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const styles = StyleSheet.create({
|
|
108
|
-
container: {
|
|
109
|
-
flex: 1,
|
|
110
|
-
padding: 20,
|
|
111
|
-
backgroundColor: '#f5f5f5',
|
|
112
|
-
justifyContent: 'center'
|
|
113
|
-
},
|
|
114
|
-
title: {
|
|
115
|
-
fontSize: 24,
|
|
116
|
-
fontWeight: 'bold',
|
|
117
|
-
textAlign: 'center',
|
|
118
|
-
marginBottom: 10,
|
|
119
|
-
color: '#333'
|
|
120
|
-
},
|
|
121
|
-
subtitle: {
|
|
122
|
-
fontSize: 16,
|
|
123
|
-
textAlign: 'center',
|
|
124
|
-
marginBottom: 30,
|
|
125
|
-
color: '#666'
|
|
126
|
-
},
|
|
127
|
-
buttonContainer: {
|
|
128
|
-
marginVertical: 10
|
|
129
|
-
},
|
|
130
|
-
instructions: {
|
|
131
|
-
fontSize: 14,
|
|
132
|
-
textAlign: 'center',
|
|
133
|
-
marginTop: 30,
|
|
134
|
-
color: '#888',
|
|
135
|
-
fontStyle: 'italic'
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
export default App
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* AUTOMATIC RECORDING INTEGRATION:
|
|
143
|
-
*
|
|
144
|
-
* 1. Screen Capture (AUTOMATIC with react-native-view-shot):
|
|
145
|
-
* - Install: npm install react-native-view-shot
|
|
146
|
-
* - iOS: Add to Podfile and run pod install
|
|
147
|
-
* - Android: No additional setup needed
|
|
148
|
-
* - Screen capture happens automatically when session starts
|
|
149
|
-
* - Captures the same View element that handles touch events
|
|
150
|
-
*
|
|
151
|
-
* 2. Touch Events (AUTOMATIC):
|
|
152
|
-
* - TouchEventCapture automatically wraps your app content
|
|
153
|
-
* - Touch events are automatically converted to rrweb MouseInteraction events
|
|
154
|
-
* - Coordinates are automatically mapped from React Native to rrweb format
|
|
155
|
-
* - No manual setup required!
|
|
156
|
-
*
|
|
157
|
-
* 3. Event Recording (AUTOMATIC):
|
|
158
|
-
* - All events are automatically stored in the RecorderReactNativeSDK
|
|
159
|
-
* - Events can be exported using getRecordedEvents()
|
|
160
|
-
* - Events are compatible with standard rrweb players
|
|
161
|
-
* - Recording starts/stops automatically with session
|
|
162
|
-
*
|
|
163
|
-
* 4. ViewShot Integration (AUTOMATIC):
|
|
164
|
-
* - The TouchEventCapture View is automatically used for screen capture
|
|
165
|
-
* - No need to manually set up viewshot refs
|
|
166
|
-
* - Screen captures include all touch interactions
|
|
167
|
-
* - Perfect synchronization between touch events and screen captures
|
|
168
|
-
*
|
|
169
|
-
* 5. Customization (Optional):
|
|
170
|
-
* - Modify capture intervals in ScreenRecorder
|
|
171
|
-
* - Adjust touch event throttling in GestureRecorder
|
|
172
|
-
* - Add custom event types as needed
|
|
173
|
-
* - All core functionality works automatically out of the box
|
|
174
|
-
*/
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { InstrumentationBase } from '@opentelemetry/instrumentation'
|
|
2
|
-
import { trace, SpanStatusCode } from '@opentelemetry/api'
|
|
3
|
-
|
|
4
|
-
export class GestureInstrumentation extends InstrumentationBase {
|
|
5
|
-
private _isEnabled = false
|
|
6
|
-
|
|
7
|
-
constructor() {
|
|
8
|
-
super('react-native-gesture', '1.0.0', {})
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
init(): void {
|
|
12
|
-
// Initialize the instrumentation
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
enable(): void {
|
|
16
|
-
this._isEnabled = true
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
disable(): void {
|
|
20
|
-
this._isEnabled = false
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
isEnabled(): boolean {
|
|
24
|
-
return this._isEnabled
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Manual gesture tracking methods
|
|
28
|
-
recordTap(x: number, y: number, target?: string) {
|
|
29
|
-
if (!this._isEnabled) return
|
|
30
|
-
|
|
31
|
-
const span = trace.getTracer('gesture').startSpan('Gesture.tap', {
|
|
32
|
-
attributes: {
|
|
33
|
-
'gesture.type': 'tap',
|
|
34
|
-
'gesture.coordinates.x': x,
|
|
35
|
-
'gesture.coordinates.y': y,
|
|
36
|
-
'gesture.timestamp': Date.now(),
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
if (target) {
|
|
41
|
-
span.setAttribute('gesture.target', target)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
span.setStatus({ code: SpanStatusCode.OK })
|
|
45
|
-
span.end()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
recordSwipe(direction: string, target?: string) {
|
|
49
|
-
if (!this._isEnabled) return
|
|
50
|
-
|
|
51
|
-
const span = trace.getTracer('gesture').startSpan('Gesture.swipe', {
|
|
52
|
-
attributes: {
|
|
53
|
-
'gesture.type': 'swipe',
|
|
54
|
-
'gesture.direction': direction,
|
|
55
|
-
'gesture.timestamp': Date.now(),
|
|
56
|
-
},
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
if (target) {
|
|
60
|
-
span.setAttribute('gesture.target', target)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
span.setStatus({ code: SpanStatusCode.OK })
|
|
64
|
-
span.end()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
recordPinch(scale: number, target?: string) {
|
|
68
|
-
if (!this._isEnabled) return
|
|
69
|
-
|
|
70
|
-
const span = trace.getTracer('gesture').startSpan('Gesture.pinch', {
|
|
71
|
-
attributes: {
|
|
72
|
-
'gesture.type': 'pinch',
|
|
73
|
-
'gesture.scale': scale,
|
|
74
|
-
'gesture.timestamp': Date.now(),
|
|
75
|
-
},
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
if (target) {
|
|
79
|
-
span.setAttribute('gesture.target', target)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
span.setStatus({ code: SpanStatusCode.OK })
|
|
83
|
-
span.end()
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
recordPan(deltaX: number, deltaY: number, target?: string) {
|
|
87
|
-
if (!this._isEnabled) return
|
|
88
|
-
|
|
89
|
-
const span = trace.getTracer('gesture').startSpan('Gesture.pan', {
|
|
90
|
-
attributes: {
|
|
91
|
-
'gesture.type': 'pan',
|
|
92
|
-
'gesture.delta_x': deltaX,
|
|
93
|
-
'gesture.delta_y': deltaY,
|
|
94
|
-
'gesture.timestamp': Date.now(),
|
|
95
|
-
},
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
if (target) {
|
|
99
|
-
span.setAttribute('gesture.target', target)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
span.setStatus({ code: SpanStatusCode.OK })
|
|
103
|
-
span.end()
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
recordLongPress(duration: number, target?: string) {
|
|
107
|
-
if (!this._isEnabled) return
|
|
108
|
-
|
|
109
|
-
const span = trace.getTracer('gesture').startSpan('Gesture.longPress', {
|
|
110
|
-
attributes: {
|
|
111
|
-
'gesture.type': 'longPress',
|
|
112
|
-
'gesture.duration': duration,
|
|
113
|
-
'gesture.timestamp': Date.now(),
|
|
114
|
-
},
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
if (target) {
|
|
118
|
-
span.setAttribute('gesture.target', target)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
span.setStatus({ code: SpanStatusCode.OK })
|
|
122
|
-
span.end()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Error tracking for gesture failures
|
|
126
|
-
recordGestureError(error: Error, gestureType: string) {
|
|
127
|
-
if (!this._isEnabled) return
|
|
128
|
-
|
|
129
|
-
const span = trace.getTracer('gesture').startSpan(`Gesture.${gestureType}.error`, {
|
|
130
|
-
attributes: {
|
|
131
|
-
'gesture.type': gestureType,
|
|
132
|
-
'gesture.error': true,
|
|
133
|
-
'gesture.timestamp': Date.now(),
|
|
134
|
-
},
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
|
|
138
|
-
span.recordException(error)
|
|
139
|
-
span.end()
|
|
140
|
-
}
|
|
141
|
-
}
|