@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.
Files changed (234) hide show
  1. package/RRWEB_INTEGRATION.md +336 -0
  2. package/VIEWSHOT_INTEGRATION_TEST.md +123 -0
  3. package/copy-react-native-dist.sh +38 -0
  4. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.d.ts +6 -0
  5. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js +1 -0
  6. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js.map +1 -0
  7. package/dist/components/GestureCaptureWrapper/index.d.ts +1 -0
  8. package/dist/components/GestureCaptureWrapper/index.js +1 -0
  9. package/dist/components/GestureCaptureWrapper/index.js.map +1 -0
  10. package/dist/components/GestureCaptureWrapper.d.ts +6 -0
  11. package/dist/components/GestureCaptureWrapper.js +1 -0
  12. package/dist/components/GestureCaptureWrapper.js.map +1 -0
  13. package/dist/components/ScreenRecorderView/ScreenRecorderView.d.ts +5 -0
  14. package/dist/components/ScreenRecorderView/ScreenRecorderView.js +1 -0
  15. package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
  16. package/dist/components/ScreenRecorderView/index.d.ts +1 -0
  17. package/dist/components/ScreenRecorderView/index.js +1 -0
  18. package/dist/components/ScreenRecorderView/index.js.map +1 -0
  19. package/dist/components/index.d.ts +1 -0
  20. package/dist/components/index.js +1 -0
  21. package/dist/components/index.js.map +1 -0
  22. package/dist/config/constants.d.ts +18 -0
  23. package/dist/config/constants.js +1 -0
  24. package/dist/config/constants.js.map +1 -0
  25. package/dist/config/defaults.d.ts +4 -0
  26. package/dist/config/defaults.js +1 -0
  27. package/dist/config/defaults.js.map +1 -0
  28. package/dist/config/index.d.ts +5 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/index.js.map +1 -0
  31. package/dist/config/masking.d.ts +2 -30
  32. package/dist/config/masking.js +1 -1
  33. package/dist/config/masking.js.map +1 -1
  34. package/dist/config/session-recorder.d.ts +2 -0
  35. package/dist/config/session-recorder.js +1 -0
  36. package/dist/config/session-recorder.js.map +1 -0
  37. package/dist/config/validators.d.ts +10 -0
  38. package/dist/config/validators.js +1 -0
  39. package/dist/config/validators.js.map +1 -0
  40. package/dist/context/SessionRecorderContext.d.ts +12 -0
  41. package/dist/context/SessionRecorderContext.js +1 -0
  42. package/dist/context/SessionRecorderContext.js.map +1 -0
  43. package/dist/expo.d.ts +5 -9
  44. package/dist/expo.js +1 -1
  45. package/dist/expo.js.map +1 -1
  46. package/dist/index.d.ts +6 -10
  47. package/dist/index.js +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/otel/helpers.d.ts +45 -3
  50. package/dist/otel/helpers.js +1 -1
  51. package/dist/otel/helpers.js.map +1 -1
  52. package/dist/otel/index.d.ts +4 -25
  53. package/dist/otel/index.js +1 -1
  54. package/dist/otel/index.js.map +1 -1
  55. package/dist/otel/instrumentations/gestureInstrumentation.js +1 -1
  56. package/dist/otel/instrumentations/gestureInstrumentation.js.map +1 -1
  57. package/dist/otel/instrumentations/index.d.ts +3 -4
  58. package/dist/otel/instrumentations/index.js +1 -1
  59. package/dist/otel/instrumentations/index.js.map +1 -1
  60. package/dist/otel/instrumentations/reactNativeInstrumentation.js +1 -1
  61. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +1 -1
  62. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +1 -0
  63. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +1 -1
  64. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +1 -1
  65. package/dist/patch/index.d.ts +1 -0
  66. package/dist/patch/index.js +1 -0
  67. package/dist/patch/index.js.map +1 -0
  68. package/dist/patch/xhr.d.ts +2 -0
  69. package/dist/patch/xhr.js +1 -0
  70. package/dist/patch/xhr.js.map +1 -0
  71. package/dist/recorder/eventExporter.d.ts +21 -0
  72. package/dist/recorder/eventExporter.js +1 -0
  73. package/dist/recorder/eventExporter.js.map +1 -0
  74. package/dist/recorder/gestureHandlerRecorder.d.ts +19 -0
  75. package/dist/recorder/gestureHandlerRecorder.js +1 -0
  76. package/dist/recorder/gestureHandlerRecorder.js.map +1 -0
  77. package/dist/recorder/gestureRecorder.d.ts +68 -11
  78. package/dist/recorder/gestureRecorder.js +1 -1
  79. package/dist/recorder/gestureRecorder.js.map +1 -1
  80. package/dist/recorder/index.d.ts +60 -6
  81. package/dist/recorder/index.js +1 -1
  82. package/dist/recorder/index.js.map +1 -1
  83. package/dist/recorder/navigationTracker.js +1 -1
  84. package/dist/recorder/navigationTracker.js.map +1 -1
  85. package/dist/recorder/screenRecorder.d.ts +79 -10
  86. package/dist/recorder/screenRecorder.js +1 -1
  87. package/dist/recorder/screenRecorder.js.map +1 -1
  88. package/dist/services/api.service.d.ts +62 -10
  89. package/dist/services/api.service.js +1 -1
  90. package/dist/services/api.service.js.map +1 -1
  91. package/dist/services/storage.service.d.ts +23 -16
  92. package/dist/services/storage.service.js +1 -1
  93. package/dist/services/storage.service.js.map +1 -1
  94. package/dist/session-recorder.d.ts +166 -0
  95. package/dist/session-recorder.js +1 -0
  96. package/dist/session-recorder.js.map +1 -0
  97. package/dist/types/index.d.ts +15 -76
  98. package/dist/types/index.js +1 -1
  99. package/dist/types/index.js.map +1 -1
  100. package/dist/types/rrweb.d.ts +118 -0
  101. package/dist/types/rrweb.js +1 -0
  102. package/dist/types/rrweb.js.map +1 -0
  103. package/dist/types/session-recorder.d.ts +366 -0
  104. package/dist/types/session-recorder.js +1 -0
  105. package/dist/types/session-recorder.js.map +1 -0
  106. package/dist/types/session.d.ts +59 -0
  107. package/dist/types/session.js +1 -0
  108. package/dist/types/session.js.map +1 -0
  109. package/dist/utils/app-metadata.d.ts +16 -0
  110. package/dist/utils/app-metadata.js +1 -0
  111. package/dist/utils/app-metadata.js.map +1 -0
  112. package/dist/utils/index.d.ts +7 -0
  113. package/dist/utils/index.js +1 -0
  114. package/dist/utils/index.js.map +1 -0
  115. package/dist/utils/logger.d.ts +112 -0
  116. package/dist/utils/logger.js +1 -0
  117. package/dist/utils/logger.js.map +1 -0
  118. package/dist/utils/platform.d.ts +37 -0
  119. package/dist/utils/platform.js +1 -1
  120. package/dist/utils/platform.js.map +1 -1
  121. package/dist/utils/request-utils.d.ts +21 -0
  122. package/dist/utils/request-utils.js +1 -0
  123. package/dist/utils/request-utils.js.map +1 -0
  124. package/dist/utils/rrweb-events.d.ts +65 -0
  125. package/dist/utils/rrweb-events.js +1 -0
  126. package/dist/utils/rrweb-events.js.map +1 -0
  127. package/dist/utils/session.d.ts +5 -0
  128. package/dist/utils/session.js +1 -0
  129. package/dist/utils/session.js.map +1 -0
  130. package/dist/utils/time.d.ts +4 -0
  131. package/dist/utils/time.js +1 -0
  132. package/dist/utils/time.js.map +1 -0
  133. package/dist/utils/type-utils.d.ts +16 -0
  134. package/dist/utils/type-utils.js +1 -0
  135. package/dist/utils/type-utils.js.map +1 -0
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/dist/version.js.map +1 -1
  139. package/docs/AUTO_METADATA_DETECTION.md +108 -0
  140. package/package.json +10 -9
  141. package/scripts/generate-app-metadata.js +173 -0
  142. package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +86 -0
  143. package/src/components/GestureCaptureWrapper/index.ts +1 -0
  144. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +72 -0
  145. package/src/components/ScreenRecorderView/index.ts +1 -0
  146. package/src/components/index.ts +1 -0
  147. package/src/config/constants.ts +60 -0
  148. package/src/config/defaults.ts +82 -0
  149. package/src/config/index.ts +6 -0
  150. package/src/config/masking.ts +10 -61
  151. package/src/config/session-recorder.ts +55 -0
  152. package/src/config/validators.ts +31 -0
  153. package/src/context/SessionRecorderContext.tsx +75 -0
  154. package/src/expo.ts +7 -37
  155. package/src/index.ts +14 -17
  156. package/src/otel/helpers.ts +265 -11
  157. package/src/otel/index.ts +37 -247
  158. package/src/otel/instrumentations/index.ts +82 -53
  159. package/src/patch/index.ts +1 -0
  160. package/src/patch/xhr.ts +142 -0
  161. package/src/recorder/eventExporter.ts +141 -0
  162. package/src/recorder/gestureRecorder.ts +194 -125
  163. package/src/recorder/index.ts +132 -24
  164. package/src/recorder/navigationTracker.ts +12 -10
  165. package/src/recorder/screenRecorder.ts +242 -155
  166. package/src/services/api.service.ts +170 -45
  167. package/src/services/storage.service.ts +102 -74
  168. package/src/session-recorder.ts +600 -0
  169. package/src/types/index.ts +19 -79
  170. package/src/types/session-recorder.ts +423 -0
  171. package/src/types/session.ts +65 -0
  172. package/src/utils/app-metadata.ts +31 -0
  173. package/src/utils/index.ts +8 -0
  174. package/src/utils/logger.ts +225 -0
  175. package/src/utils/platform.ts +321 -6
  176. package/src/utils/request-utils.ts +61 -0
  177. package/src/utils/rrweb-events.ts +309 -0
  178. package/src/utils/session.ts +18 -0
  179. package/src/utils/time.ts +17 -0
  180. package/src/utils/type-utils.ts +75 -0
  181. package/src/version.ts +1 -1
  182. package/dist/sessionRecorder.d.ts +0 -54
  183. package/dist/sessionRecorder.js +0 -1
  184. package/dist/sessionRecorder.js.map +0 -1
  185. package/examples/sample-expo-app/README.md +0 -142
  186. package/examples/sample-expo-app/app/(tabs)/_layout.tsx +0 -60
  187. package/examples/sample-expo-app/app/(tabs)/explore.tsx +0 -110
  188. package/examples/sample-expo-app/app/(tabs)/index.tsx +0 -125
  189. package/examples/sample-expo-app/app/(tabs)/posts.tsx +0 -96
  190. package/examples/sample-expo-app/app/(tabs)/users.tsx +0 -131
  191. package/examples/sample-expo-app/app/+not-found.tsx +0 -32
  192. package/examples/sample-expo-app/app/_layout.tsx +0 -53
  193. package/examples/sample-expo-app/app/post/[id].tsx +0 -199
  194. package/examples/sample-expo-app/app/user/[id].tsx +0 -270
  195. package/examples/sample-expo-app/app.json +0 -42
  196. package/examples/sample-expo-app/assets/fonts/SpaceMono-Regular.ttf +0 -0
  197. package/examples/sample-expo-app/assets/images/adaptive-icon.png +0 -0
  198. package/examples/sample-expo-app/assets/images/favicon.png +0 -0
  199. package/examples/sample-expo-app/assets/images/icon.png +0 -0
  200. package/examples/sample-expo-app/assets/images/partial-react-logo.png +0 -0
  201. package/examples/sample-expo-app/assets/images/react-logo.png +0 -0
  202. package/examples/sample-expo-app/assets/images/react-logo@2x.png +0 -0
  203. package/examples/sample-expo-app/assets/images/react-logo@3x.png +0 -0
  204. package/examples/sample-expo-app/assets/images/splash-icon.png +0 -0
  205. package/examples/sample-expo-app/components/Collapsible.tsx +0 -45
  206. package/examples/sample-expo-app/components/ErrorView.tsx +0 -52
  207. package/examples/sample-expo-app/components/ExternalLink.tsx +0 -24
  208. package/examples/sample-expo-app/components/HapticTab.tsx +0 -18
  209. package/examples/sample-expo-app/components/HelloWave.tsx +0 -40
  210. package/examples/sample-expo-app/components/LoadingSpinner.tsx +0 -34
  211. package/examples/sample-expo-app/components/ParallaxScrollView.tsx +0 -82
  212. package/examples/sample-expo-app/components/ThemedText.tsx +0 -60
  213. package/examples/sample-expo-app/components/ThemedView.tsx +0 -14
  214. package/examples/sample-expo-app/components/ui/IconSymbol.ios.tsx +0 -32
  215. package/examples/sample-expo-app/components/ui/IconSymbol.tsx +0 -41
  216. package/examples/sample-expo-app/components/ui/TabBarBackground.ios.tsx +0 -19
  217. package/examples/sample-expo-app/components/ui/TabBarBackground.tsx +0 -6
  218. package/examples/sample-expo-app/constants/Colors.ts +0 -26
  219. package/examples/sample-expo-app/eslint.config.js +0 -10
  220. package/examples/sample-expo-app/hooks/useApi.ts +0 -41
  221. package/examples/sample-expo-app/hooks/useColorScheme.ts +0 -1
  222. package/examples/sample-expo-app/hooks/useColorScheme.web.ts +0 -21
  223. package/examples/sample-expo-app/hooks/useThemeColor.ts +0 -21
  224. package/examples/sample-expo-app/metro.config.js +0 -26
  225. package/examples/sample-expo-app/package-lock.json +0 -26296
  226. package/examples/sample-expo-app/package.json +0 -59
  227. package/examples/sample-expo-app/scripts/reset-project.js +0 -112
  228. package/examples/sample-expo-app/services/api.ts +0 -98
  229. package/examples/sample-expo-app/tsconfig.json +0 -17
  230. package/examples/sample-expo-app/utils/navigation.ts +0 -19
  231. package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
  232. package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -164
  233. package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -114
  234. 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"
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
- }
@@ -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":"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"}
@@ -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.