@multiplayer-app/session-recorder-react-native 0.0.1-alpha.1 → 0.0.1-alpha.10
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/RRWEB_INTEGRATION.md +336 -0
- package/VIEWSHOT_INTEGRATION_TEST.md +123 -0
- package/copy-react-native-dist.sh +38 -0
- 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.d.ts +6 -0
- package/dist/components/GestureCaptureWrapper.js +1 -0
- package/dist/components/GestureCaptureWrapper.js.map +1 -0
- 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/config/constants.d.ts +18 -0
- package/dist/config/constants.js +1 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/defaults.d.ts +4 -0
- package/dist/config/defaults.js +1 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +1 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/masking.d.ts +2 -30
- package/dist/config/masking.js +1 -1
- package/dist/config/masking.js.map +1 -1
- package/dist/config/session-recorder.d.ts +2 -0
- package/dist/config/session-recorder.js +1 -0
- package/dist/config/session-recorder.js.map +1 -0
- package/dist/config/validators.d.ts +10 -0
- package/dist/config/validators.js +1 -0
- package/dist/config/validators.js.map +1 -0
- package/dist/context/SessionRecorderContext.d.ts +12 -0
- package/dist/context/SessionRecorderContext.js +1 -0
- package/dist/context/SessionRecorderContext.js.map +1 -0
- package/dist/expo.d.ts +5 -9
- package/dist/expo.js +1 -1
- package/dist/expo.js.map +1 -1
- package/dist/index.d.ts +6 -10
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/otel/helpers.d.ts +45 -3
- package/dist/otel/helpers.js +1 -1
- package/dist/otel/helpers.js.map +1 -1
- package/dist/otel/index.d.ts +4 -25
- package/dist/otel/index.js +1 -1
- 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 +3 -4
- package/dist/otel/instrumentations/index.js +1 -1
- package/dist/otel/instrumentations/index.js.map +1 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.js +1 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +1 -1
- package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +1 -0
- package/dist/otel/instrumentations/reactNavigationInstrumentation.js +1 -1
- package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +1 -1
- package/dist/patch/index.d.ts +1 -0
- package/dist/patch/index.js +1 -0
- package/dist/patch/index.js.map +1 -0
- package/dist/patch/xhr.d.ts +2 -0
- package/dist/patch/xhr.js +1 -0
- package/dist/patch/xhr.js.map +1 -0
- package/dist/recorder/eventExporter.d.ts +21 -0
- package/dist/recorder/eventExporter.js +1 -0
- package/dist/recorder/eventExporter.js.map +1 -0
- package/dist/recorder/gestureHandlerRecorder.d.ts +19 -0
- package/dist/recorder/gestureHandlerRecorder.js +1 -0
- package/dist/recorder/gestureHandlerRecorder.js.map +1 -0
- package/dist/recorder/gestureRecorder.d.ts +68 -11
- package/dist/recorder/gestureRecorder.js +1 -1
- package/dist/recorder/gestureRecorder.js.map +1 -1
- package/dist/recorder/index.d.ts +60 -6
- package/dist/recorder/index.js +1 -1
- package/dist/recorder/index.js.map +1 -1
- package/dist/recorder/navigationTracker.js +1 -1
- package/dist/recorder/navigationTracker.js.map +1 -1
- package/dist/recorder/screenRecorder.d.ts +79 -10
- package/dist/recorder/screenRecorder.js +1 -1
- package/dist/recorder/screenRecorder.js.map +1 -1
- package/dist/services/api.service.d.ts +62 -10
- package/dist/services/api.service.js +1 -1
- package/dist/services/api.service.js.map +1 -1
- package/dist/services/storage.service.d.ts +23 -16
- package/dist/services/storage.service.js +1 -1
- package/dist/services/storage.service.js.map +1 -1
- package/dist/session-recorder.d.ts +166 -0
- package/dist/session-recorder.js +1 -0
- package/dist/session-recorder.js.map +1 -0
- package/dist/types/index.d.ts +15 -76
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/rrweb.d.ts +118 -0
- package/dist/types/rrweb.js +1 -0
- package/dist/types/rrweb.js.map +1 -0
- package/dist/types/session-recorder.d.ts +366 -0
- package/dist/types/session-recorder.js +1 -0
- package/dist/types/session-recorder.js.map +1 -0
- package/dist/types/session.d.ts +59 -0
- package/dist/types/session.js +1 -0
- package/dist/types/session.js.map +1 -0
- 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/index.d.ts +7 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +112 -0
- package/dist/utils/logger.js +1 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/platform.d.ts +37 -0
- package/dist/utils/platform.js +1 -1
- package/dist/utils/platform.js.map +1 -1
- package/dist/utils/request-utils.d.ts +21 -0
- package/dist/utils/request-utils.js +1 -0
- package/dist/utils/request-utils.js.map +1 -0
- package/dist/utils/rrweb-events.d.ts +65 -0
- package/dist/utils/rrweb-events.js +1 -0
- package/dist/utils/rrweb-events.js.map +1 -0
- package/dist/utils/session.d.ts +5 -0
- package/dist/utils/session.js +1 -0
- package/dist/utils/session.js.map +1 -0
- package/dist/utils/time.d.ts +4 -0
- package/dist/utils/time.js +1 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/utils/type-utils.d.ts +16 -0
- package/dist/utils/type-utils.js +1 -0
- package/dist/utils/type-utils.js.map +1 -0
- 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 +10 -9
- package/scripts/generate-app-metadata.js +173 -0
- package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +86 -0
- 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/config/constants.ts +60 -0
- package/src/config/defaults.ts +82 -0
- package/src/config/index.ts +6 -0
- package/src/config/masking.ts +10 -61
- package/src/config/session-recorder.ts +55 -0
- package/src/config/validators.ts +31 -0
- package/src/context/SessionRecorderContext.tsx +75 -0
- package/src/expo.ts +7 -37
- package/src/index.ts +14 -17
- package/src/otel/helpers.ts +265 -11
- package/src/otel/index.ts +37 -247
- package/src/otel/instrumentations/index.ts +82 -53
- package/src/patch/index.ts +1 -0
- package/src/patch/xhr.ts +142 -0
- package/src/recorder/eventExporter.ts +141 -0
- package/src/recorder/gestureRecorder.ts +194 -125
- package/src/recorder/index.ts +132 -24
- package/src/recorder/navigationTracker.ts +12 -10
- package/src/recorder/screenRecorder.ts +242 -155
- package/src/services/api.service.ts +170 -45
- package/src/services/storage.service.ts +102 -74
- package/src/session-recorder.ts +600 -0
- package/src/types/index.ts +19 -79
- package/src/types/session-recorder.ts +423 -0
- package/src/types/session.ts +65 -0
- package/src/utils/app-metadata.ts +31 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +225 -0
- package/src/utils/platform.ts +321 -6
- package/src/utils/request-utils.ts +61 -0
- package/src/utils/rrweb-events.ts +309 -0
- package/src/utils/session.ts +18 -0
- package/src/utils/time.ts +17 -0
- package/src/utils/type-utils.ts +75 -0
- package/src/version.ts +1 -1
- package/dist/sessionRecorder.d.ts +0 -54
- package/dist/sessionRecorder.js +0 -1
- package/dist/sessionRecorder.js.map +0 -1
- package/examples/sample-expo-app/README.md +0 -142
- package/examples/sample-expo-app/app/(tabs)/_layout.tsx +0 -60
- package/examples/sample-expo-app/app/(tabs)/explore.tsx +0 -110
- package/examples/sample-expo-app/app/(tabs)/index.tsx +0 -125
- package/examples/sample-expo-app/app/(tabs)/posts.tsx +0 -96
- package/examples/sample-expo-app/app/(tabs)/users.tsx +0 -131
- package/examples/sample-expo-app/app/+not-found.tsx +0 -32
- package/examples/sample-expo-app/app/_layout.tsx +0 -53
- package/examples/sample-expo-app/app/post/[id].tsx +0 -199
- package/examples/sample-expo-app/app/user/[id].tsx +0 -270
- package/examples/sample-expo-app/app.json +0 -42
- package/examples/sample-expo-app/assets/fonts/SpaceMono-Regular.ttf +0 -0
- package/examples/sample-expo-app/assets/images/adaptive-icon.png +0 -0
- package/examples/sample-expo-app/assets/images/favicon.png +0 -0
- package/examples/sample-expo-app/assets/images/icon.png +0 -0
- package/examples/sample-expo-app/assets/images/partial-react-logo.png +0 -0
- package/examples/sample-expo-app/assets/images/react-logo.png +0 -0
- package/examples/sample-expo-app/assets/images/react-logo@2x.png +0 -0
- package/examples/sample-expo-app/assets/images/react-logo@3x.png +0 -0
- package/examples/sample-expo-app/assets/images/splash-icon.png +0 -0
- package/examples/sample-expo-app/components/Collapsible.tsx +0 -45
- package/examples/sample-expo-app/components/ErrorView.tsx +0 -52
- package/examples/sample-expo-app/components/ExternalLink.tsx +0 -24
- package/examples/sample-expo-app/components/HapticTab.tsx +0 -18
- package/examples/sample-expo-app/components/HelloWave.tsx +0 -40
- package/examples/sample-expo-app/components/LoadingSpinner.tsx +0 -34
- package/examples/sample-expo-app/components/ParallaxScrollView.tsx +0 -82
- package/examples/sample-expo-app/components/ThemedText.tsx +0 -60
- package/examples/sample-expo-app/components/ThemedView.tsx +0 -14
- package/examples/sample-expo-app/components/ui/IconSymbol.ios.tsx +0 -32
- package/examples/sample-expo-app/components/ui/IconSymbol.tsx +0 -41
- package/examples/sample-expo-app/components/ui/TabBarBackground.ios.tsx +0 -19
- package/examples/sample-expo-app/components/ui/TabBarBackground.tsx +0 -6
- package/examples/sample-expo-app/constants/Colors.ts +0 -26
- package/examples/sample-expo-app/eslint.config.js +0 -10
- package/examples/sample-expo-app/hooks/useApi.ts +0 -41
- package/examples/sample-expo-app/hooks/useColorScheme.ts +0 -1
- package/examples/sample-expo-app/hooks/useColorScheme.web.ts +0 -21
- package/examples/sample-expo-app/hooks/useThemeColor.ts +0 -21
- package/examples/sample-expo-app/metro.config.js +0 -26
- package/examples/sample-expo-app/package-lock.json +0 -26296
- package/examples/sample-expo-app/package.json +0 -59
- package/examples/sample-expo-app/scripts/reset-project.js +0 -112
- package/examples/sample-expo-app/services/api.ts +0 -98
- package/examples/sample-expo-app/tsconfig.json +0 -17
- package/examples/sample-expo-app/utils/navigation.ts +0 -19
- package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
- package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -164
- package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -114
- package/src/sessionRecorder.ts +0 -367
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { Dimensions } from 'react-native'
|
|
2
|
+
import { EventType, eventWithTime, NodeType, serializedNodeWithId, IncrementalSource, mutationData } from '@rrweb/types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a meta event to mark the start of recording
|
|
6
|
+
* @param sessionId - The session ID
|
|
7
|
+
* @param sessionType - The type of session (PLAIN or CONTINUOUS)
|
|
8
|
+
* @param additionalData - Additional data to include in the meta event
|
|
9
|
+
* @returns MetaEvent object
|
|
10
|
+
*/
|
|
11
|
+
export function createRecordingMetaEvent(): eventWithTime {
|
|
12
|
+
const screenDimensions = Dimensions.get('window')
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
type: EventType.Meta,
|
|
16
|
+
data: {
|
|
17
|
+
href: 'https://go.multiplayer.app/session-recorder-react-native',
|
|
18
|
+
width: screenDimensions.width,
|
|
19
|
+
height: screenDimensions.height,
|
|
20
|
+
},
|
|
21
|
+
timestamp: Date.now(),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a full snapshot event with the given base64 image
|
|
27
|
+
* @param base64Image - Base64 encoded image data
|
|
28
|
+
* @param width - Screen width
|
|
29
|
+
* @param height - Screen height
|
|
30
|
+
* @param captureFormat - Image format (png, jpg, etc.)
|
|
31
|
+
* @param nodeIdCounter - Starting node ID counter (will be modified)
|
|
32
|
+
* @returns Full snapshot event
|
|
33
|
+
*/
|
|
34
|
+
export function createFullSnapshotEvent(
|
|
35
|
+
base64Image: string,
|
|
36
|
+
width: number,
|
|
37
|
+
height: number,
|
|
38
|
+
captureFormat: string = 'jpg',
|
|
39
|
+
nodeIdCounter: { current: number },
|
|
40
|
+
): eventWithTime {
|
|
41
|
+
// Create a virtual DOM node representing the screen as an image
|
|
42
|
+
const imageNode: serializedNodeWithId = {
|
|
43
|
+
type: NodeType.Element,
|
|
44
|
+
id: 0,
|
|
45
|
+
tagName: 'img',
|
|
46
|
+
attributes: {
|
|
47
|
+
src: `data:image/${captureFormat};base64,${base64Image}`,
|
|
48
|
+
width: width.toString(),
|
|
49
|
+
height: height.toString(),
|
|
50
|
+
style: `width: ${width}px; height: ${height}px;`,
|
|
51
|
+
},
|
|
52
|
+
childNodes: [],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Create the root container
|
|
56
|
+
const rootNode: serializedNodeWithId = {
|
|
57
|
+
type: NodeType.Element,
|
|
58
|
+
id: nodeIdCounter.current++,
|
|
59
|
+
tagName: 'div',
|
|
60
|
+
attributes: {
|
|
61
|
+
style: `width: ${width}px; height: ${height}px; position: relative;`,
|
|
62
|
+
},
|
|
63
|
+
childNodes: [imageNode],
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const domNode: serializedNodeWithId = {
|
|
67
|
+
type: NodeType.Document,
|
|
68
|
+
childNodes: [
|
|
69
|
+
{
|
|
70
|
+
type: NodeType.DocumentType,
|
|
71
|
+
name: 'html',
|
|
72
|
+
publicId: '',
|
|
73
|
+
systemId: '',
|
|
74
|
+
id: nodeIdCounter.current++,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: NodeType.Element,
|
|
78
|
+
tagName: 'html',
|
|
79
|
+
attributes: {},
|
|
80
|
+
childNodes: [
|
|
81
|
+
{
|
|
82
|
+
type: NodeType.Element,
|
|
83
|
+
tagName: 'head',
|
|
84
|
+
attributes: {},
|
|
85
|
+
childNodes: [
|
|
86
|
+
{
|
|
87
|
+
type: NodeType.Element,
|
|
88
|
+
tagName: 'meta',
|
|
89
|
+
attributes: { charset: 'utf-8' },
|
|
90
|
+
childNodes: [],
|
|
91
|
+
id: nodeIdCounter.current++,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: NodeType.Element,
|
|
95
|
+
tagName: 'meta',
|
|
96
|
+
attributes: {
|
|
97
|
+
name: 'viewport',
|
|
98
|
+
content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no',
|
|
99
|
+
},
|
|
100
|
+
childNodes: [],
|
|
101
|
+
id: nodeIdCounter.current++,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
id: nodeIdCounter.current++,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: NodeType.Element,
|
|
108
|
+
tagName: 'body',
|
|
109
|
+
attributes: {},
|
|
110
|
+
childNodes: [rootNode],
|
|
111
|
+
id: nodeIdCounter.current++,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
id: nodeIdCounter.current++,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
id: nodeIdCounter.current++,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
type: EventType.FullSnapshot,
|
|
122
|
+
data: {
|
|
123
|
+
node: domNode,
|
|
124
|
+
initialOffset: { left: 0, top: 0 },
|
|
125
|
+
},
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create an incremental snapshot event with mutation data to update image src
|
|
132
|
+
* @param base64Image - New base64 encoded image data
|
|
133
|
+
* @param imageNodeId - ID of the image node to update
|
|
134
|
+
* @param captureFormat - Image format (png, jpg, etc.)
|
|
135
|
+
* @returns Incremental snapshot event with mutation data
|
|
136
|
+
*/
|
|
137
|
+
export function createIncrementalSnapshotWithImageUpdate(
|
|
138
|
+
base64Image: string,
|
|
139
|
+
captureFormat: string = 'jpg',
|
|
140
|
+
): eventWithTime {
|
|
141
|
+
const mutationData: mutationData = {
|
|
142
|
+
source: IncrementalSource.Mutation,
|
|
143
|
+
texts: [],
|
|
144
|
+
attributes: [
|
|
145
|
+
{
|
|
146
|
+
id: 0,
|
|
147
|
+
attributes: {
|
|
148
|
+
src: `data:image/${captureFormat};base64,${base64Image}`,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
removes: [],
|
|
153
|
+
adds: [],
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
type: EventType.IncrementalSnapshot,
|
|
158
|
+
data: mutationData,
|
|
159
|
+
timestamp: Date.now(),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Create a simple image node for React Native screen capture
|
|
165
|
+
* @param base64Image - Base64 encoded image data
|
|
166
|
+
* @param width - Image width
|
|
167
|
+
* @param height - Image height
|
|
168
|
+
* @param captureFormat - Image format (png, jpg, etc.)
|
|
169
|
+
* @param nodeId - Node ID for the image
|
|
170
|
+
* @returns Serialized node with ID
|
|
171
|
+
*/
|
|
172
|
+
export function createImageNode(
|
|
173
|
+
base64Image: string,
|
|
174
|
+
width: number,
|
|
175
|
+
height: number,
|
|
176
|
+
captureFormat: string = 'jpg',
|
|
177
|
+
nodeId: number,
|
|
178
|
+
): serializedNodeWithId {
|
|
179
|
+
return {
|
|
180
|
+
type: NodeType.Element,
|
|
181
|
+
id: nodeId,
|
|
182
|
+
tagName: 'img',
|
|
183
|
+
attributes: {
|
|
184
|
+
src: `data:image/${captureFormat};base64,${base64Image}`,
|
|
185
|
+
width: width.toString(),
|
|
186
|
+
height: height.toString(),
|
|
187
|
+
style: `width: ${width}px; height: ${height}px;`,
|
|
188
|
+
},
|
|
189
|
+
childNodes: [],
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Create a document node for React Native screen capture
|
|
195
|
+
* @param imageNode - The image node to include
|
|
196
|
+
* @param width - Screen width
|
|
197
|
+
* @param height - Screen height
|
|
198
|
+
* @param nodeIdCounter - Node ID counter (will be modified)
|
|
199
|
+
* @returns Document node
|
|
200
|
+
*/
|
|
201
|
+
export function createDocumentNode(
|
|
202
|
+
imageNode: serializedNodeWithId,
|
|
203
|
+
width: number,
|
|
204
|
+
height: number,
|
|
205
|
+
nodeIdCounter: { current: number },
|
|
206
|
+
): serializedNodeWithId {
|
|
207
|
+
// Create the root container
|
|
208
|
+
const rootNode: serializedNodeWithId = {
|
|
209
|
+
type: NodeType.Element,
|
|
210
|
+
id: nodeIdCounter.current++,
|
|
211
|
+
tagName: 'div',
|
|
212
|
+
attributes: {
|
|
213
|
+
style: `width: ${width}px; height: ${height}px; position: relative;`,
|
|
214
|
+
},
|
|
215
|
+
childNodes: [imageNode],
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
type: NodeType.Document,
|
|
220
|
+
childNodes: [
|
|
221
|
+
{
|
|
222
|
+
type: NodeType.DocumentType,
|
|
223
|
+
name: 'html',
|
|
224
|
+
publicId: '',
|
|
225
|
+
systemId: '',
|
|
226
|
+
id: nodeIdCounter.current++,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: NodeType.Element,
|
|
230
|
+
tagName: 'html',
|
|
231
|
+
attributes: {},
|
|
232
|
+
childNodes: [
|
|
233
|
+
{
|
|
234
|
+
type: NodeType.Element,
|
|
235
|
+
tagName: 'head',
|
|
236
|
+
attributes: {},
|
|
237
|
+
childNodes: [
|
|
238
|
+
{
|
|
239
|
+
type: NodeType.Element,
|
|
240
|
+
tagName: 'meta',
|
|
241
|
+
attributes: { charset: 'utf-8' },
|
|
242
|
+
childNodes: [],
|
|
243
|
+
id: nodeIdCounter.current++,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
type: NodeType.Element,
|
|
247
|
+
tagName: 'meta',
|
|
248
|
+
attributes: {
|
|
249
|
+
name: 'viewport',
|
|
250
|
+
content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no',
|
|
251
|
+
},
|
|
252
|
+
childNodes: [],
|
|
253
|
+
id: nodeIdCounter.current++,
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
id: nodeIdCounter.current++,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
type: NodeType.Element,
|
|
260
|
+
tagName: 'body',
|
|
261
|
+
attributes: {},
|
|
262
|
+
childNodes: [rootNode],
|
|
263
|
+
id: nodeIdCounter.current++,
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
id: nodeIdCounter.current++,
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
id: nodeIdCounter.current++,
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Generate a simple hash for screen comparison
|
|
275
|
+
* This is a lightweight hash that focuses on the beginning and end of the base64 string
|
|
276
|
+
* to detect changes without doing a full comparison
|
|
277
|
+
* @param base64Image - Base64 encoded image
|
|
278
|
+
* @param sampleSize - Number of characters to sample from each part
|
|
279
|
+
* @returns Hash string for comparison
|
|
280
|
+
*/
|
|
281
|
+
export function generateScreenHash(base64Image: string, sampleSize: number = 100): string {
|
|
282
|
+
// Use a simple hash that samples the beginning, middle, and end of the base64 string
|
|
283
|
+
// This is much faster than comparing the entire string
|
|
284
|
+
const start = base64Image.substring(0, sampleSize)
|
|
285
|
+
const middle = base64Image.substring(
|
|
286
|
+
Math.floor(base64Image.length / 2) - sampleSize / 2,
|
|
287
|
+
Math.floor(base64Image.length / 2) + sampleSize / 2,
|
|
288
|
+
)
|
|
289
|
+
const end = base64Image.substring(base64Image.length - sampleSize)
|
|
290
|
+
|
|
291
|
+
// Combine samples and create a simple hash
|
|
292
|
+
const combined = start + middle + end
|
|
293
|
+
return simpleHash(combined)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Simple hash function for string comparison
|
|
298
|
+
* @param str - String to hash
|
|
299
|
+
* @returns Hash value as string
|
|
300
|
+
*/
|
|
301
|
+
export function simpleHash(str: string): string {
|
|
302
|
+
let hash = 0
|
|
303
|
+
for (let i = 0; i < str.length; i++) {
|
|
304
|
+
const char = str.charCodeAt(i)
|
|
305
|
+
hash = (hash << 5) - hash + char
|
|
306
|
+
hash = hash & hash // Convert to 32-bit integer
|
|
307
|
+
}
|
|
308
|
+
return Math.abs(hash).toString(36)
|
|
309
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DEBUG_SESSION_MAX_DURATION_SECONDS } from '../config/constants'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Session-related utility functions for React Native
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const isSessionActive = (session: any, continuousRecording: boolean): boolean => {
|
|
8
|
+
if (!session) return false
|
|
9
|
+
if (continuousRecording) return true
|
|
10
|
+
const startedAt = new Date(session.startedAt || session.createdAt)
|
|
11
|
+
const now = new Date()
|
|
12
|
+
const diff = now.getTime() - startedAt.getTime()
|
|
13
|
+
return diff < DEBUG_SESSION_MAX_DURATION_SECONDS * 1000
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const isConsoleEvent = (event: any): boolean => {
|
|
17
|
+
return event.type === 'Plugin' && event.data?.plugin === 'rrweb/console@1'
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time and date utility functions for React Native
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const getFormattedDate = (date: number | Date, options?: any): string => {
|
|
6
|
+
return new Date(date).toLocaleDateString(
|
|
7
|
+
'en-US',
|
|
8
|
+
options || {
|
|
9
|
+
month: 'short',
|
|
10
|
+
year: 'numeric',
|
|
11
|
+
day: 'numeric',
|
|
12
|
+
hour: 'numeric',
|
|
13
|
+
minute: '2-digit',
|
|
14
|
+
second: '2-digit',
|
|
15
|
+
},
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const nativeIsArray = Array.isArray
|
|
2
|
+
const ObjProto = Object.prototype
|
|
3
|
+
export const hasOwnProperty = ObjProto.hasOwnProperty
|
|
4
|
+
const toString = ObjProto.toString
|
|
5
|
+
|
|
6
|
+
export const isArray =
|
|
7
|
+
nativeIsArray ||
|
|
8
|
+
function (obj: any): obj is any[] {
|
|
9
|
+
return toString.call(obj) === '[object Array]'
|
|
10
|
+
}
|
|
11
|
+
export const isUint8Array = function (x: unknown): x is Uint8Array {
|
|
12
|
+
return toString.call(x) === '[object Uint8Array]'
|
|
13
|
+
}
|
|
14
|
+
// from a comment on http://dbj.org/dbj/?p=286
|
|
15
|
+
// fails on only one very rare and deliberate custom object:
|
|
16
|
+
// let bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
|
|
17
|
+
export const isFunction = function (f: any): f is (...args: any[]) => any {
|
|
18
|
+
return typeof f === 'function'
|
|
19
|
+
}
|
|
20
|
+
// Underscore Addons
|
|
21
|
+
export const isObject = function (x: unknown): x is Record<string, any> {
|
|
22
|
+
return x === Object(x) && !isArray(x)
|
|
23
|
+
}
|
|
24
|
+
export const isEmptyObject = function (x: unknown): x is Record<string, any> {
|
|
25
|
+
if (isObject(x)) {
|
|
26
|
+
for (const key in x) {
|
|
27
|
+
if (hasOwnProperty.call(x, key)) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
export const isUndefined = function (x: unknown): x is undefined {
|
|
36
|
+
return x === void 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const isString = function (x: unknown): x is string {
|
|
40
|
+
return toString.call(x) == '[object String]'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const isEmptyString = function (x: unknown): boolean {
|
|
44
|
+
return isString(x) && x.trim().length === 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const isNull = function (x: unknown): x is null {
|
|
48
|
+
return x === null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
sometimes you want to check if something is null or undefined
|
|
53
|
+
that's what this is for
|
|
54
|
+
*/
|
|
55
|
+
export const isNullish = function (x: unknown): x is null | undefined {
|
|
56
|
+
return isUndefined(x) || isNull(x)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const isDate = function (x: unknown): x is Date {
|
|
60
|
+
return toString.call(x) == '[object Date]'
|
|
61
|
+
}
|
|
62
|
+
export const isNumber = function (x: unknown): x is number {
|
|
63
|
+
return toString.call(x) == '[object Number]'
|
|
64
|
+
}
|
|
65
|
+
export const isBoolean = function (x: unknown): x is boolean {
|
|
66
|
+
return toString.call(x) === '[object Boolean]'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const isFormData = (x: unknown): x is FormData => {
|
|
70
|
+
return x instanceof FormData
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const isFile = (x: unknown): x is File => {
|
|
74
|
+
return x instanceof File
|
|
75
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "0.0.1-alpha.
|
|
1
|
+
export const version = "0.0.1-alpha.10"
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { SessionRecorderOptions } from './types';
|
|
2
|
-
import { SessionType } from '@multiplayer-app/session-recorder-common';
|
|
3
|
-
export declare enum SessionState {
|
|
4
|
-
started = "2",
|
|
5
|
-
paused = "1",
|
|
6
|
-
stopped = "0"
|
|
7
|
-
}
|
|
8
|
-
export interface ISession {
|
|
9
|
-
id: string;
|
|
10
|
-
shortId: string;
|
|
11
|
-
type: SessionType;
|
|
12
|
-
state: SessionState;
|
|
13
|
-
createdAt: string;
|
|
14
|
-
updatedAt: string;
|
|
15
|
-
tempApiKey?: string;
|
|
16
|
-
metadata?: Record<string, any>;
|
|
17
|
-
}
|
|
18
|
-
export declare class SessionRecorder {
|
|
19
|
-
private _configs;
|
|
20
|
-
private _tracer;
|
|
21
|
-
private _recorder;
|
|
22
|
-
private _apiService;
|
|
23
|
-
private _storageService;
|
|
24
|
-
private _session;
|
|
25
|
-
private _isInitialized;
|
|
26
|
-
constructor();
|
|
27
|
-
init(options: SessionRecorderOptions): Promise<void>;
|
|
28
|
-
start(sessionId?: string, sessionType?: SessionType): Promise<void>;
|
|
29
|
-
stop(): Promise<void>;
|
|
30
|
-
pause(): Promise<void>;
|
|
31
|
-
resume(): Promise<void>;
|
|
32
|
-
cancel(): Promise<void>;
|
|
33
|
-
save(): Promise<void>;
|
|
34
|
-
setNavigationRef(ref: any): void;
|
|
35
|
-
enableGestureTracking(): void;
|
|
36
|
-
disableGestureTracking(): void;
|
|
37
|
-
recordTap(x: number, y: number, target?: string): void;
|
|
38
|
-
recordSwipe(direction: string, target?: string): void;
|
|
39
|
-
recordNavigate(routeName: string, params?: Record<string, any>): void;
|
|
40
|
-
recordGoBack(): void;
|
|
41
|
-
setSessionAttribute(key: string, value: any): void;
|
|
42
|
-
getSessionAttribute(key: string): any;
|
|
43
|
-
getCurrentSession(): ISession | null;
|
|
44
|
-
getStoredSessionData(): Promise<{
|
|
45
|
-
sessionId: string | null;
|
|
46
|
-
sessionType: SessionType | null;
|
|
47
|
-
sessionState: SessionState | null;
|
|
48
|
-
sessionObject: ISession | null;
|
|
49
|
-
}>;
|
|
50
|
-
startTrace(name: string, attributes?: Record<string, any>): any;
|
|
51
|
-
endTrace(span: any, status?: any): void;
|
|
52
|
-
captureException(error: Error, context?: Record<string, any>): void;
|
|
53
|
-
shutdown(): Promise<void>;
|
|
54
|
-
}
|
package/dist/sessionRecorder.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");Object.defineProperty(exports,"__esModule",{value:true});exports.SessionState=exports.SessionRecorder=void 0;var _regenerator=_interopRequireDefault(require("@babel/runtime/regenerator"));var _asyncToGenerator2=_interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));var _classCallCheck2=_interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));var _createClass2=_interopRequireDefault(require("@babel/runtime/helpers/createClass"));var _otel=require("./otel");var _recorder=require("./recorder");var _api=require("./services/api.service");var _storage=require("./services/storage.service");var _sessionRecorderCommon=require("@multiplayer-app/session-recorder-common");var SessionState;(function(SessionState){SessionState["started"]="2";SessionState["paused"]="1";SessionState["stopped"]="0";})(SessionState||(exports.SessionState=SessionState={}));var SessionRecorder=exports.SessionRecorder=function(){function SessionRecorder(){(0,_classCallCheck2["default"])(this,SessionRecorder);this._configs=null;this._session=null;this._isInitialized=false;this._tracer=new _otel.TracerReactNativeSDK();this._recorder=new _recorder.RecorderReactNativeSDK();this._apiService=new _api.ApiService();this._storageService=new _storage.StorageService();}return(0,_createClass2["default"])(SessionRecorder,[{key:"init",value:function(){var _init=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee(options){var tracerConfig,recorderConfig;return _regenerator["default"].wrap(function(_context){while(1)switch(_context.prev=_context.next){case 0:if(!this._isInitialized){_context.next=1;break;}console.warn('SessionRecorder already initialized');return _context.abrupt("return");case 1:this._configs=options;tracerConfig={apiKey:options.apiKey,application:options.application,version:options.version,environment:options.environment,exporterEndpoint:options.exporterEndpoint,ignoreUrls:options.ignoreUrls,captureBody:options.captureBody,captureHeaders:options.captureHeaders,sampleTraceRatio:options.sampleTraceRatio,httpMasking:options.httpMasking};this._tracer.init(tracerConfig);recorderConfig={apiKey:options.apiKey,apiBaseUrl:options.apiBaseUrl,recordScreen:options.recordScreen,recordGestures:options.recordGestures,recordNavigation:options.recordNavigation};this._recorder.init(recorderConfig);this._apiService.init(options);this._isInitialized=true;console.log('SessionRecorder initialized successfully');case 2:case"end":return _context.stop();}},_callee,this);}));function init(_x){return _init.apply(this,arguments);}return init;}()},{key:"start",value:function(){var _start=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee2(sessionId){var sessionType,_args2=arguments,_t;return _regenerator["default"].wrap(function(_context2){while(1)switch(_context2.prev=_context2.next){case 0:sessionType=_args2.length>1&&_args2[1]!==undefined?_args2[1]:_sessionRecorderCommon.SessionType.PLAIN;if(this._isInitialized){_context2.next=1;break;}throw new Error('SessionRecorder not initialized. Call init() first.');case 1:if(this._configs){_context2.next=2;break;}throw new Error('Configuration not found');case 2:_context2.prev=2;if(!sessionId){_context2.next=3;break;}this._session={id:sessionId,shortId:sessionId.substring(0,8),type:sessionType,state:SessionState.started,createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()};_context2.next=5;break;case 3:_context2.next=4;return this._apiService.startSession({application:this._configs.application,version:this._configs.version,environment:this._configs.environment});case 4:this._session=_context2.sent;case 5:if(this._session){_context2.next=6;break;}throw new Error('Failed to create session');case 6:this._tracer.setSessionId(this._session.id,this._session.type);this._recorder.start(this._session.id,this._session.type);_context2.next=7;return this._storageService.saveSessionId(this._session.id);case 7:_context2.next=8;return this._storageService.saveSessionType(this._session.type);case 8:_context2.next=9;return this._storageService.saveSessionState(this._session.state);case 9:_context2.next=10;return this._storageService.saveSessionObject(this._session);case 10:console.log('Session started:',this._session.id);_context2.next=12;break;case 11:_context2.prev=11;_t=_context2["catch"](2);console.error('Failed to start session:',_t);throw _t;case 12:case"end":return _context2.stop();}},_callee2,this,[[2,11]]);}));function start(_x2){return _start.apply(this,arguments);}return start;}()},{key:"stop",value:function(){var _stop=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee3(){var _t2;return _regenerator["default"].wrap(function(_context3){while(1)switch(_context3.prev=_context3.next){case 0:if(!(!this._isInitialized||!this._session)){_context3.next=1;break;}console.warn('Session not active');return _context3.abrupt("return");case 1:_context3.prev=1;this._recorder.stop();this._session.state=SessionState.stopped;this._session.updatedAt=new Date().toISOString();_context3.next=2;return this._storageService.saveSessionState(this._session.state);case 2:_context3.next=3;return this._storageService.saveSessionObject(this._session);case 3:_context3.next=4;return this._apiService.stopSession({sessionId:this._session.id});case 4:console.log('Session stopped:',this._session.id);_context3.next=6;break;case 5:_context3.prev=5;_t2=_context3["catch"](1);console.error('Failed to stop session:',_t2);throw _t2;case 6:case"end":return _context3.stop();}},_callee3,this,[[1,5]]);}));function stop(){return _stop.apply(this,arguments);}return stop;}()},{key:"pause",value:function(){var _pause=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee4(){var _t3;return _regenerator["default"].wrap(function(_context4){while(1)switch(_context4.prev=_context4.next){case 0:if(!(!this._isInitialized||!this._session)){_context4.next=1;break;}console.warn('Session not active');return _context4.abrupt("return");case 1:_context4.prev=1;this._recorder.pause();this._session.state=SessionState.paused;this._session.updatedAt=new Date().toISOString();_context4.next=2;return this._storageService.saveSessionState(this._session.state);case 2:_context4.next=3;return this._storageService.saveSessionObject(this._session);case 3:console.log('Session paused:',this._session.id);_context4.next=5;break;case 4:_context4.prev=4;_t3=_context4["catch"](1);console.error('Failed to pause session:',_t3);throw _t3;case 5:case"end":return _context4.stop();}},_callee4,this,[[1,4]]);}));function pause(){return _pause.apply(this,arguments);}return pause;}()},{key:"resume",value:function(){var _resume=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee5(){var _t4;return _regenerator["default"].wrap(function(_context5){while(1)switch(_context5.prev=_context5.next){case 0:if(!(!this._isInitialized||!this._session)){_context5.next=1;break;}console.warn('Session not active');return _context5.abrupt("return");case 1:_context5.prev=1;this._recorder.resume();this._session.state=SessionState.started;this._session.updatedAt=new Date().toISOString();_context5.next=2;return this._storageService.saveSessionState(this._session.state);case 2:_context5.next=3;return this._storageService.saveSessionObject(this._session);case 3:console.log('Session resumed:',this._session.id);_context5.next=5;break;case 4:_context5.prev=4;_t4=_context5["catch"](1);console.error('Failed to resume session:',_t4);throw _t4;case 5:case"end":return _context5.stop();}},_callee5,this,[[1,4]]);}));function resume(){return _resume.apply(this,arguments);}return resume;}()},{key:"cancel",value:function(){var _cancel=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee6(){var _t5;return _regenerator["default"].wrap(function(_context6){while(1)switch(_context6.prev=_context6.next){case 0:if(!(!this._isInitialized||!this._session)){_context6.next=1;break;}console.warn('Session not active');return _context6.abrupt("return");case 1:_context6.prev=1;this._recorder.stop();_context6.next=2;return this._storageService.clearSessionData();case 2:_context6.next=3;return this._apiService.stopSession({sessionId:this._session.id});case 3:this._session=null;console.log('Session cancelled');_context6.next=5;break;case 4:_context6.prev=4;_t5=_context6["catch"](1);console.error('Failed to cancel session:',_t5);throw _t5;case 5:case"end":return _context6.stop();}},_callee6,this,[[1,4]]);}));function cancel(){return _cancel.apply(this,arguments);}return cancel;}()},{key:"save",value:function(){var _save=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee7(){var _t6;return _regenerator["default"].wrap(function(_context7){while(1)switch(_context7.prev=_context7.next){case 0:if(!(!this._isInitialized||!this._session)){_context7.next=1;break;}console.warn('Session not active');return _context7.abrupt("return");case 1:_context7.prev=1;_context7.next=2;return this._apiService.saveSession(this._session.id);case 2:console.log('Session saved:',this._session.id);_context7.next=4;break;case 3:_context7.prev=3;_t6=_context7["catch"](1);console.error('Failed to save session:',_t6);throw _t6;case 4:case"end":return _context7.stop();}},_callee7,this,[[1,3]]);}));function save(){return _save.apply(this,arguments);}return save;}()},{key:"setNavigationRef",value:function setNavigationRef(ref){if(this._isInitialized){this._tracer.setNavigationRef(ref);this._recorder.setNavigationRef(ref);}}},{key:"enableGestureTracking",value:function enableGestureTracking(){if(this._isInitialized){this._tracer.enableGestureTracking();}}},{key:"disableGestureTracking",value:function disableGestureTracking(){if(this._isInitialized){this._tracer.disableGestureTracking();}}},{key:"recordTap",value:function recordTap(x,y,target){if(this._isInitialized){this._tracer.recordTap(x,y,target);}}},{key:"recordSwipe",value:function recordSwipe(direction,target){if(this._isInitialized){this._tracer.recordSwipe(direction,target);}}},{key:"recordNavigate",value:function recordNavigate(routeName,params){if(this._isInitialized){this._tracer.recordNavigate(routeName,params);}}},{key:"recordGoBack",value:function recordGoBack(){if(this._isInitialized){this._tracer.recordGoBack();}}},{key:"setSessionAttribute",value:function setSessionAttribute(key,value){if(this._session){if(!this._session.metadata){this._session.metadata={};}this._session.metadata[key]=value;this._session.updatedAt=new Date().toISOString();}}},{key:"getSessionAttribute",value:function getSessionAttribute(key){var _a,_b;return(_b=(_a=this._session)===null||_a===void 0?void 0:_a.metadata)===null||_b===void 0?void 0:_b[key];}},{key:"getCurrentSession",value:function getCurrentSession(){return this._session;}},{key:"getStoredSessionData",value:function(){var _getStoredSessionData=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee8(){return _regenerator["default"].wrap(function(_context8){while(1)switch(_context8.prev=_context8.next){case 0:_context8.next=1;return this._storageService.getAllSessionData();case 1:return _context8.abrupt("return",_context8.sent);case 2:case"end":return _context8.stop();}},_callee8,this);}));function getStoredSessionData(){return _getStoredSessionData.apply(this,arguments);}return getStoredSessionData;}()},{key:"startTrace",value:function startTrace(name,attributes){if(this._isInitialized){return this._tracer.startTrace(name,attributes);}return null;}},{key:"endTrace",value:function endTrace(span,status){if(this._isInitialized&&span){this._tracer.endTrace(span,status);}}},{key:"captureException",value:function captureException(error,context){if(this._isInitialized){this._tracer.captureException(error,context);}}},{key:"shutdown",value:function(){var _shutdown=(0,_asyncToGenerator2["default"])(_regenerator["default"].mark(function _callee9(){return _regenerator["default"].wrap(function(_context9){while(1)switch(_context9.prev=_context9.next){case 0:if(!this._isInitialized){_context9.next=3;break;}_context9.next=1;return this.stop();case 1:_context9.next=2;return this._tracer.shutdown();case 2:this._isInitialized=false;console.log('SessionRecorder shutdown');case 3:case"end":return _context9.stop();}},_callee9,this);}));function shutdown(){return _shutdown.apply(this,arguments);}return shutdown;}()}]);}();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sessionRecorder.js","sourceRoot":"","sources":["../src/sessionRecorder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3D,OAAO,EACL,WAAW,GACZ,MAAM,0CAA0C,CAAA;AAEjD,8DAA8D;AAC9D,MAAM,CAAN,IAAY,YAIX;AAJD,WAAY,YAAY;IACtB,6BAAa,CAAA;IACb,4BAAY,CAAA;IACZ,6BAAa,CAAA;AACf,CAAC,EAJW,YAAY,KAAZ,YAAY,QAIvB;AAaD,MAAM,OAAO,eAAe;IAS1B;QARQ,aAAQ,GAAkC,IAAI,CAAA;QAK9C,aAAQ,GAAoB,IAAI,CAAA;QAChC,mBAAc,GAAG,KAAK,CAAA;QAG5B,IAAI,CAAC,OAAO,GAAG,IAAI,oBAAoB,EAAE,CAAA;QACzC,IAAI,CAAC,SAAS,GAAG,IAAI,sBAAsB,EAAE,CAAA;QAC7C,IAAI,CAAC,WAAW,GAAG,IAAI,UAAU,EAAE,CAAA;QACnC,IAAI,CAAC,eAAe,GAAG,IAAI,cAAc,EAAE,CAAA;IAC7C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAA+B;QACxC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAA;YACnD,OAAM;QACR,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAA;QAEvB,oBAAoB;QACpB,MAAM,YAAY,GAA4B;YAC5C,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,WAAW,EAAE,OAAO,CAAC,WAAW;SACjC,CAAA;QAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAE/B,sBAAsB;QACtB,MAAM,cAAc,GAAmB;YACrC,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,cAAc,EAAE,OAAO,CAAC,cAAc;YACtC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;SAE3C,CAAA;QAED,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QAEnC,yBAAyB;QACzB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAE9B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;QAC1B,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAA;IACzD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,SAAkB,EAAE,cAA2B,WAAW,CAAC,KAAK;QAC1E,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;QACxE,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;QAC5C,CAAC;QAED,IAAI,CAAC;YACH,iCAAiC;YACjC,IAAI,SAAS,EAAE,CAAC;gBACd,IAAI,CAAC,QAAQ,GAAG;oBACd,EAAE,EAAE,SAAS;oBACb,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;oBAClC,IAAI,EAAE,WAAW;oBACjB,KAAK,EAAE,YAAY,CAAC,OAAO;oBAC3B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACnC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC,CAAA;YACH,CAAC;iBAAM,CAAC;gBACN,4BAA4B;gBAC5B,IAAI,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC;oBAClD,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW;oBACtC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO;oBAC9B,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW;iBACvC,CAAC,CAAA;YACJ,CAAC;YAED,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACnB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;YAC7C,CAAC;YAED,2BAA2B;YAC3B,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YAE/D,kBAAkB;YAClB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YAE1D,oBAAoB;YACpB,MAAM,IAAI,CAAC,eAAe,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;YAC1D,MAAM,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;YAC9D,MAAM,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YAChE,MAAM,IAAI,CAAC,eAAe,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YAE3D,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAA;YAChD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,iBAAiB;YACjB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;YAErB,uBAAuB;YACvB,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,YAAY,CAAC,OAAO,CAAA;YAC1C,IAAI,CAAC,QAAQ,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAElD,uBAAuB;YACvB,MAAM,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YAChE,MAAM,IAAI,CAAC,eAAe,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YAE3D,uBAAuB;YACvB,MAAM,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC;gBACjC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE;aAC5B,CAAC,CAAA;YAEF,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAA;YAC/C,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,kBAAkB;YAClB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAA;YAEtB,uBAAuB;YACvB,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,YAAY,CAAC,MAAM,CAAA;YACzC,IAAI,CAAC,QAAQ,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAElD,uBAAuB;YACvB,MAAM,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YAChE,MAAM,IAAI,CAAC,eAAe,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YAE3D,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,KAAK,CAAC,CAAA;YAChD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,mBAAmB;YACnB,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAA;YAEvB,uBAAuB;YACvB,IAAI,CAAC,QAAQ,CAAC,KAAK,GAAG,YAAY,CAAC,OAAO,CAAA;YAC1C,IAAI,CAAC,QAAQ,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAElD,uBAAuB;YACvB,MAAM,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YAChE,MAAM,IAAI,CAAC,eAAe,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YAE3D,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAA;YACjD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,iBAAiB;YACjB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;YAErB,qBAAqB;YACrB,MAAM,IAAI,CAAC,eAAe,CAAC,gBAAgB,EAAE,CAAA;YAE7C,yBAAyB;YACzB,MAAM,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC;gBACjC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE;aAC5B,CAAC,CAAA;YAEF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;YACpB,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAA;YACjD,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC3C,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;YAClC,OAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,uBAAuB;YACvB,MAAM,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;YAEpD,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACjD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAA;YAC/C,MAAM,KAAK,CAAA;QACb,CAAC;IACH,CAAC;IAED,qBAAqB;IACrB,gBAAgB,CAAC,GAAQ;QACvB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAA;YAClC,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAA;QACtC,CAAC;IACH,CAAC;IAED,kBAAkB;IAClB,qBAAqB;QACnB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAA;QACtC,CAAC;IACH,CAAC;IAED,sBAAsB;QACpB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,sBAAsB,EAAE,CAAA;QACvC,CAAC;IACH,CAAC;IAED,2BAA2B;IAC3B,SAAS,CAAC,CAAS,EAAE,CAAS,EAAE,MAAe;QAC7C,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAA;QACtC,CAAC;IACH,CAAC;IAED,WAAW,CAAC,SAAiB,EAAE,MAAe;QAC5C,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC;IAED,cAAc,CAAC,SAAiB,EAAE,MAA4B;QAC5D,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAChD,CAAC;IACH,CAAC;IAED,YAAY;QACV,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAA;QAC7B,CAAC;IACH,CAAC;IAED,qBAAqB;IACrB,mBAAmB,CAAC,GAAW,EAAE,KAAU;QACzC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBAC5B,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,EAAE,CAAA;YAC7B,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;YACnC,IAAI,CAAC,QAAQ,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACpD,CAAC;IACH,CAAC;IAED,mBAAmB,CAAC,GAAW;;QAC7B,OAAO,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,QAAQ,0CAAG,GAAG,CAAC,CAAA;IACvC,CAAC;IAED,sBAAsB;IACtB,iBAAiB;QACf,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;IAED,gCAAgC;IAChC,KAAK,CAAC,oBAAoB;QAMxB,OAAO,MAAM,IAAI,CAAC,eAAe,CAAC,iBAAiB,EAAE,CAAA;IACvD,CAAC;IAED,iCAAiC;IACjC,UAAU,CAAC,IAAY,EAAE,UAAgC;QACvD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAA;QAClD,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,QAAQ,CAAC,IAAS,EAAE,MAAY;QAC9B,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QACrC,CAAC;IACH,CAAC;IAED,gBAAgB,CAAC,KAAY,EAAE,OAA6B;QAC1D,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAC/C,CAAC;IACH,CAAC;IAED,WAAW;IACX,KAAK,CAAC,QAAQ;QACZ,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;YACjB,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAA;YAC7B,IAAI,CAAC,cAAc,GAAG,KAAK,CAAA;YAC3B,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAA;QACzC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# Enhanced Sample Expo React Native App
|
|
2
|
-
|
|
3
|
-
This is an enhanced sample Expo React Native app that demonstrates multiple pages, navigation, and API integration with JSONPlaceholder.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
### 🏠 Home Screen
|
|
8
|
-
|
|
9
|
-
- Welcome page with feature overview
|
|
10
|
-
- Quick navigation to Posts and Users sections
|
|
11
|
-
- Information about the app's capabilities
|
|
12
|
-
|
|
13
|
-
### 📝 Posts Tab
|
|
14
|
-
|
|
15
|
-
- Displays posts from JSONPlaceholder API
|
|
16
|
-
- Pull-to-refresh functionality
|
|
17
|
-
- Tap to view post details with comments
|
|
18
|
-
- Loading states and error handling
|
|
19
|
-
|
|
20
|
-
### 👥 Users Tab
|
|
21
|
-
|
|
22
|
-
- Shows user profiles from JSONPlaceholder API
|
|
23
|
-
- User avatars and basic information
|
|
24
|
-
- Tap to view detailed user profiles
|
|
25
|
-
- Displays user's posts
|
|
26
|
-
|
|
27
|
-
### 📄 Post Details
|
|
28
|
-
|
|
29
|
-
- Full post content with title and body
|
|
30
|
-
- Comments section with all post comments
|
|
31
|
-
- Navigation to user profile
|
|
32
|
-
- Back navigation
|
|
33
|
-
|
|
34
|
-
### 👤 User Details
|
|
35
|
-
|
|
36
|
-
- Complete user profile information
|
|
37
|
-
- Contact details (email, phone, website)
|
|
38
|
-
- Company information
|
|
39
|
-
- Address details
|
|
40
|
-
- User's posts list
|
|
41
|
-
- Navigation to individual posts
|
|
42
|
-
|
|
43
|
-
## API Integration
|
|
44
|
-
|
|
45
|
-
The app integrates with [JSONPlaceholder](https://jsonplaceholder.typicode.com/) API to demonstrate:
|
|
46
|
-
|
|
47
|
-
- **Posts API**: Fetch all posts, individual posts, and post comments
|
|
48
|
-
- **Users API**: Fetch all users, individual users, and user posts
|
|
49
|
-
- **Error Handling**: Proper error states with retry functionality
|
|
50
|
-
- **Loading States**: Loading spinners and progress indicators
|
|
51
|
-
- **Data Management**: Custom hooks for API state management
|
|
52
|
-
|
|
53
|
-
## Technical Stack
|
|
54
|
-
|
|
55
|
-
- **Expo Router**: File-based navigation
|
|
56
|
-
- **React Native**: Cross-platform mobile development
|
|
57
|
-
- **TypeScript**: Type-safe development
|
|
58
|
-
- **Axios**: HTTP client for API requests
|
|
59
|
-
- **Custom Hooks**: Reusable API state management
|
|
60
|
-
- **Themed Components**: Consistent UI with dark/light mode support
|
|
61
|
-
|
|
62
|
-
## Project Structure
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
app/
|
|
66
|
-
├── (tabs)/
|
|
67
|
-
│ ├── index.tsx # Home screen
|
|
68
|
-
│ ├── posts.tsx # Posts list
|
|
69
|
-
│ ├── users.tsx # Users list
|
|
70
|
-
│ ├── explore.tsx # Original explore screen
|
|
71
|
-
│ └── _layout.tsx # Tab navigation layout
|
|
72
|
-
├── post/
|
|
73
|
-
│ └── [id].tsx # Post detail page
|
|
74
|
-
├── user/
|
|
75
|
-
│ └── [id].tsx # User detail page
|
|
76
|
-
└── _layout.tsx # Root layout
|
|
77
|
-
|
|
78
|
-
components/
|
|
79
|
-
├── LoadingSpinner.tsx # Loading component
|
|
80
|
-
├── ErrorView.tsx # Error display component
|
|
81
|
-
└── ... # Other UI components
|
|
82
|
-
|
|
83
|
-
services/
|
|
84
|
-
└── api.ts # API service with JSONPlaceholder integration
|
|
85
|
-
|
|
86
|
-
hooks/
|
|
87
|
-
└── useApi.ts # Custom hook for API state management
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Getting Started
|
|
91
|
-
|
|
92
|
-
1. Install dependencies:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
npm install
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
2. Start the development server:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npm start
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
3. Run on your preferred platform:
|
|
105
|
-
```bash
|
|
106
|
-
npm run ios # iOS simulator
|
|
107
|
-
npm run android # Android emulator
|
|
108
|
-
npm run web # Web browser
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## API Endpoints Used
|
|
112
|
-
|
|
113
|
-
- `GET /posts` - Fetch all posts
|
|
114
|
-
- `GET /posts/:id` - Fetch specific post
|
|
115
|
-
- `GET /posts/:id/comments` - Fetch post comments
|
|
116
|
-
- `GET /users` - Fetch all users
|
|
117
|
-
- `GET /users/:id` - Fetch specific user
|
|
118
|
-
- `GET /users/:id/posts` - Fetch user's posts
|
|
119
|
-
|
|
120
|
-
## Navigation Flow
|
|
121
|
-
|
|
122
|
-
1. **Home** → **Posts** → **Post Detail** → **User Detail**
|
|
123
|
-
2. **Home** → **Users** → **User Detail** → **Post Detail**
|
|
124
|
-
3. **Posts** → **Post Detail** → **User Detail**
|
|
125
|
-
4. **Users** → **User Detail** → **Post Detail**
|
|
126
|
-
|
|
127
|
-
## Customization
|
|
128
|
-
|
|
129
|
-
The app is designed to be easily customizable:
|
|
130
|
-
|
|
131
|
-
- **API Service**: Modify `services/api.ts` to integrate with different APIs
|
|
132
|
-
- **UI Components**: Update components in the `components/` directory
|
|
133
|
-
- **Navigation**: Add new routes in the `app/` directory
|
|
134
|
-
- **Styling**: Modify styles in individual component files
|
|
135
|
-
|
|
136
|
-
## Session Recording Integration
|
|
137
|
-
|
|
138
|
-
This sample app includes the session recorder integration, demonstrating how to capture user interactions and API calls for debugging and analytics purposes.
|
|
139
|
-
|
|
140
|
-
## Contributing
|
|
141
|
-
|
|
142
|
-
Feel free to enhance this sample app with additional features, better error handling, or different API integrations.
|