@multiplayer-app/session-recorder-react-native 1.0.1-beta.1 → 1.0.1-beta.11

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 (469) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +296 -156
  3. package/SessionRecorderNative.podspec +35 -14
  4. package/android/build.gradle +50 -54
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/com/sessionrecordernative/SessionRecorderNativeConfig.kt +52 -0
  7. package/android/src/main/java/com/sessionrecordernative/SessionRecorderNativeModule.kt +861 -0
  8. package/android/src/main/java/com/sessionrecordernative/SessionRecorderNativePackage.kt +33 -0
  9. package/android/src/main/java/com/sessionrecordernative/SessionRecorderNativeSpec.kt +79 -0
  10. package/android/src/main/java/com/sessionrecordernative/model/TargetInfo.kt +9 -0
  11. package/android/src/main/java/com/sessionrecordernative/util/ViewUtils.kt +72 -0
  12. package/ios/GestureTargetFinder.swift +50 -0
  13. package/ios/SessionRecorderNative-Bridging-Header.h +2 -0
  14. package/ios/{GestureRecorderNative.m → SessionRecorderNative.mm} +17 -6
  15. package/ios/SessionRecorderNative.swift +256 -4
  16. package/lib/module/SessionRecorderNativeSpec.js +5 -0
  17. package/lib/module/SessionRecorderNativeSpec.js.map +1 -0
  18. package/lib/module/components/ScreenRecorderView/ScreenRecorderView.js +23 -0
  19. package/lib/module/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
  20. package/lib/module/components/ScreenRecorderView/index.js +4 -0
  21. package/lib/module/components/ScreenRecorderView/index.js.map +1 -0
  22. package/lib/module/components/SessionRecorderWidget/ErrorBanner.js +64 -0
  23. package/lib/module/components/SessionRecorderWidget/ErrorBanner.js.map +1 -0
  24. package/lib/module/components/SessionRecorderWidget/FinalPopover.js +74 -0
  25. package/lib/module/components/SessionRecorderWidget/FinalPopover.js.map +1 -0
  26. package/lib/module/components/SessionRecorderWidget/FloatingButton.js +191 -0
  27. package/lib/module/components/SessionRecorderWidget/FloatingButton.js.map +1 -0
  28. package/lib/module/components/SessionRecorderWidget/InitialPopover.js +138 -0
  29. package/lib/module/components/SessionRecorderWidget/InitialPopover.js.map +1 -0
  30. package/lib/module/components/SessionRecorderWidget/ModalContainer.js +177 -0
  31. package/lib/module/components/SessionRecorderWidget/ModalContainer.js.map +1 -0
  32. package/lib/module/components/SessionRecorderWidget/ModalHeader.js +27 -0
  33. package/lib/module/components/SessionRecorderWidget/ModalHeader.js.map +1 -0
  34. package/lib/module/components/SessionRecorderWidget/SessionRecorderWidget.js +133 -0
  35. package/lib/module/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -0
  36. package/lib/module/components/SessionRecorderWidget/icons.js +93 -0
  37. package/lib/module/components/SessionRecorderWidget/icons.js.map +1 -0
  38. package/lib/module/components/SessionRecorderWidget/index.js +5 -0
  39. package/lib/module/components/SessionRecorderWidget/index.js.map +1 -0
  40. package/lib/module/components/SessionRecorderWidget/styles.js +173 -0
  41. package/lib/module/components/SessionRecorderWidget/styles.js.map +1 -0
  42. package/lib/module/components/index.js +5 -0
  43. package/lib/module/components/index.js.map +1 -0
  44. package/lib/module/config/constants.js +42 -0
  45. package/lib/module/config/constants.js.map +1 -0
  46. package/lib/module/config/defaults.js +81 -0
  47. package/lib/module/config/defaults.js.map +1 -0
  48. package/lib/module/config/index.js +9 -0
  49. package/lib/module/config/index.js.map +1 -0
  50. package/lib/module/config/masking.js +35 -0
  51. package/lib/module/config/masking.js.map +1 -0
  52. package/lib/module/config/session-recorder.js +44 -0
  53. package/lib/module/config/session-recorder.js.map +1 -0
  54. package/lib/module/config/validators.js +28 -0
  55. package/lib/module/config/validators.js.map +1 -0
  56. package/lib/module/config/widget.js +35 -0
  57. package/lib/module/config/widget.js.map +1 -0
  58. package/lib/module/context/SessionRecorderContext.js +93 -0
  59. package/lib/module/context/SessionRecorderContext.js.map +1 -0
  60. package/lib/module/context/SessionRecorderStore.js +12 -0
  61. package/lib/module/context/SessionRecorderStore.js.map +1 -0
  62. package/lib/module/context/useSessionRecorderStore.js +20 -0
  63. package/lib/module/context/useSessionRecorderStore.js.map +1 -0
  64. package/lib/module/context/useStoreSelector.js +27 -0
  65. package/lib/module/context/useStoreSelector.js.map +1 -0
  66. package/lib/module/index.js +16 -0
  67. package/lib/module/index.js.map +1 -0
  68. package/lib/module/native/SessionRecorderNative.js +79 -0
  69. package/lib/module/native/SessionRecorderNative.js.map +1 -0
  70. package/lib/module/native/index.js +4 -0
  71. package/lib/module/native/index.js.map +1 -0
  72. package/lib/module/otel/helpers.js +218 -0
  73. package/lib/module/otel/helpers.js.map +1 -0
  74. package/lib/module/otel/index.js +95 -0
  75. package/lib/module/otel/index.js.map +1 -0
  76. package/lib/module/otel/instrumentations/index.js +102 -0
  77. package/lib/module/otel/instrumentations/index.js.map +1 -0
  78. package/lib/module/package.json +1 -0
  79. package/lib/module/patch/index.js +4 -0
  80. package/lib/module/patch/index.js.map +1 -0
  81. package/lib/module/patch/xhr.js +116 -0
  82. package/lib/module/patch/xhr.js.map +1 -0
  83. package/lib/module/recorder/eventExporter.js +130 -0
  84. package/lib/module/recorder/eventExporter.js.map +1 -0
  85. package/lib/module/recorder/gestureRecorder.js +641 -0
  86. package/lib/module/recorder/gestureRecorder.js.map +1 -0
  87. package/lib/module/recorder/index.js +168 -0
  88. package/lib/module/recorder/index.js.map +1 -0
  89. package/lib/module/recorder/navigationTracker.js +228 -0
  90. package/lib/module/recorder/navigationTracker.js.map +1 -0
  91. package/lib/module/recorder/screenRecorder.js +495 -0
  92. package/lib/module/recorder/screenRecorder.js.map +1 -0
  93. package/lib/module/services/api.service.js +149 -0
  94. package/lib/module/services/api.service.js.map +1 -0
  95. package/lib/module/services/network.service.js +178 -0
  96. package/lib/module/services/network.service.js.map +1 -0
  97. package/lib/module/services/screenMaskingService.js +107 -0
  98. package/lib/module/services/screenMaskingService.js.map +1 -0
  99. package/lib/module/services/storage.service.js +179 -0
  100. package/lib/module/services/storage.service.js.map +1 -0
  101. package/lib/module/session-recorder.js +541 -0
  102. package/lib/module/session-recorder.js.map +1 -0
  103. package/lib/module/types/configs.js +4 -0
  104. package/lib/module/types/configs.js.map +1 -0
  105. package/lib/module/types/expo-constants.d.js +2 -0
  106. package/lib/module/types/expo-constants.d.js.map +1 -0
  107. package/lib/module/types/index.js +11 -0
  108. package/lib/module/types/index.js.map +1 -0
  109. package/lib/module/types/session-recorder.js +68 -0
  110. package/lib/module/types/session-recorder.js.map +1 -0
  111. package/lib/module/types/session.js +9 -0
  112. package/lib/module/types/session.js.map +1 -0
  113. package/lib/module/utils/app-metadata.js +28 -0
  114. package/lib/module/utils/app-metadata.js.map +1 -0
  115. package/lib/module/utils/constants.optional.expo.js +6 -0
  116. package/lib/module/utils/constants.optional.expo.js.map +1 -0
  117. package/lib/module/utils/constants.optional.js +8 -0
  118. package/lib/module/utils/constants.optional.js.map +1 -0
  119. package/lib/module/utils/createStore.js +27 -0
  120. package/lib/module/utils/createStore.js.map +1 -0
  121. package/lib/module/utils/index.js +11 -0
  122. package/lib/module/utils/index.js.map +1 -0
  123. package/lib/module/utils/logger.js +185 -0
  124. package/lib/module/utils/logger.js.map +1 -0
  125. package/lib/module/utils/platform.js +340 -0
  126. package/lib/module/utils/platform.js.map +1 -0
  127. package/lib/module/utils/request-utils.js +58 -0
  128. package/lib/module/utils/request-utils.js.map +1 -0
  129. package/lib/module/utils/rrweb-events.js +276 -0
  130. package/lib/module/utils/rrweb-events.js.map +1 -0
  131. package/lib/module/utils/session.js +21 -0
  132. package/lib/module/utils/session.js.map +1 -0
  133. package/lib/module/utils/shallowEqual.js +17 -0
  134. package/lib/module/utils/shallowEqual.js.map +1 -0
  135. package/lib/module/utils/time.js +17 -0
  136. package/lib/module/utils/time.js.map +1 -0
  137. package/lib/module/utils/type-utils.js +69 -0
  138. package/lib/module/utils/type-utils.js.map +1 -0
  139. package/lib/module/version.js +4 -0
  140. package/lib/module/version.js.map +1 -0
  141. package/lib/typescript/package.json +1 -0
  142. package/{dist/native/SessionRecorderNativeTurboSpec.d.ts → lib/typescript/src/SessionRecorderNativeSpec.d.ts} +14 -6
  143. package/lib/typescript/src/SessionRecorderNativeSpec.d.ts.map +1 -0
  144. package/lib/typescript/src/components/ScreenRecorderView/ScreenRecorderView.d.ts +6 -0
  145. package/lib/typescript/src/components/ScreenRecorderView/ScreenRecorderView.d.ts.map +1 -0
  146. package/lib/typescript/src/components/ScreenRecorderView/index.d.ts +2 -0
  147. package/lib/typescript/src/components/ScreenRecorderView/index.d.ts.map +1 -0
  148. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/ErrorBanner.d.ts +1 -0
  149. package/lib/typescript/src/components/SessionRecorderWidget/ErrorBanner.d.ts.map +1 -0
  150. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/FinalPopover.d.ts +2 -1
  151. package/lib/typescript/src/components/SessionRecorderWidget/FinalPopover.d.ts.map +1 -0
  152. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/FloatingButton.d.ts +1 -0
  153. package/lib/typescript/src/components/SessionRecorderWidget/FloatingButton.d.ts.map +1 -0
  154. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/InitialPopover.d.ts +2 -1
  155. package/lib/typescript/src/components/SessionRecorderWidget/InitialPopover.d.ts.map +1 -0
  156. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/ModalContainer.d.ts +1 -0
  157. package/lib/typescript/src/components/SessionRecorderWidget/ModalContainer.d.ts.map +1 -0
  158. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/ModalHeader.d.ts +1 -0
  159. package/lib/typescript/src/components/SessionRecorderWidget/ModalHeader.d.ts.map +1 -0
  160. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/SessionRecorderWidget.d.ts +1 -0
  161. package/lib/typescript/src/components/SessionRecorderWidget/SessionRecorderWidget.d.ts.map +1 -0
  162. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/icons.d.ts +1 -0
  163. package/lib/typescript/src/components/SessionRecorderWidget/icons.d.ts.map +1 -0
  164. package/lib/typescript/src/components/SessionRecorderWidget/index.d.ts +3 -0
  165. package/lib/typescript/src/components/SessionRecorderWidget/index.d.ts.map +1 -0
  166. package/{dist → lib/typescript/src}/components/SessionRecorderWidget/styles.d.ts +4 -3
  167. package/lib/typescript/src/components/SessionRecorderWidget/styles.d.ts.map +1 -0
  168. package/lib/typescript/src/components/index.d.ts +3 -0
  169. package/lib/typescript/src/components/index.d.ts.map +1 -0
  170. package/{dist → lib/typescript/src}/config/constants.d.ts +1 -0
  171. package/lib/typescript/src/config/constants.d.ts.map +1 -0
  172. package/{dist → lib/typescript/src}/config/defaults.d.ts +2 -1
  173. package/lib/typescript/src/config/defaults.d.ts.map +1 -0
  174. package/{dist → lib/typescript/src}/config/index.d.ts +1 -0
  175. package/lib/typescript/src/config/index.d.ts.map +1 -0
  176. package/lib/typescript/src/config/masking.d.ts +3 -0
  177. package/lib/typescript/src/config/masking.d.ts.map +1 -0
  178. package/lib/typescript/src/config/session-recorder.d.ts +3 -0
  179. package/lib/typescript/src/config/session-recorder.d.ts.map +1 -0
  180. package/{dist → lib/typescript/src}/config/validators.d.ts +1 -0
  181. package/lib/typescript/src/config/validators.d.ts.map +1 -0
  182. package/{dist → lib/typescript/src}/config/widget.d.ts +2 -1
  183. package/lib/typescript/src/config/widget.d.ts.map +1 -0
  184. package/{dist → lib/typescript/src}/context/SessionRecorderContext.d.ts +3 -2
  185. package/lib/typescript/src/context/SessionRecorderContext.d.ts.map +1 -0
  186. package/{dist → lib/typescript/src}/context/SessionRecorderStore.d.ts +2 -1
  187. package/lib/typescript/src/context/SessionRecorderStore.d.ts.map +1 -0
  188. package/{dist → lib/typescript/src}/context/useSessionRecorderStore.d.ts +4 -3
  189. package/lib/typescript/src/context/useSessionRecorderStore.d.ts.map +1 -0
  190. package/{dist → lib/typescript/src}/context/useStoreSelector.d.ts +2 -1
  191. package/lib/typescript/src/context/useStoreSelector.d.ts.map +1 -0
  192. package/{dist → lib/typescript/src}/index.d.ts +2 -0
  193. package/lib/typescript/src/index.d.ts.map +1 -0
  194. package/lib/typescript/src/native/SessionRecorderNative.d.ts +27 -0
  195. package/lib/typescript/src/native/SessionRecorderNative.d.ts.map +1 -0
  196. package/lib/typescript/src/native/index.d.ts +2 -0
  197. package/lib/typescript/src/native/index.d.ts.map +1 -0
  198. package/{dist → lib/typescript/src}/otel/helpers.d.ts +3 -2
  199. package/lib/typescript/src/otel/helpers.d.ts.map +1 -0
  200. package/{dist → lib/typescript/src}/otel/index.d.ts +2 -2
  201. package/lib/typescript/src/otel/index.d.ts.map +1 -0
  202. package/{dist → lib/typescript/src}/otel/instrumentations/index.d.ts +2 -1
  203. package/lib/typescript/src/otel/instrumentations/index.d.ts.map +1 -0
  204. package/lib/typescript/src/patch/index.d.ts +2 -0
  205. package/lib/typescript/src/patch/index.d.ts.map +1 -0
  206. package/{dist → lib/typescript/src}/patch/xhr.d.ts +1 -0
  207. package/lib/typescript/src/patch/xhr.d.ts.map +1 -0
  208. package/{dist → lib/typescript/src}/recorder/eventExporter.d.ts +2 -1
  209. package/lib/typescript/src/recorder/eventExporter.d.ts.map +1 -0
  210. package/{dist → lib/typescript/src}/recorder/gestureRecorder.d.ts +3 -2
  211. package/lib/typescript/src/recorder/gestureRecorder.d.ts.map +1 -0
  212. package/{dist → lib/typescript/src}/recorder/index.d.ts +3 -2
  213. package/lib/typescript/src/recorder/index.d.ts.map +1 -0
  214. package/{dist → lib/typescript/src}/recorder/navigationTracker.d.ts +2 -1
  215. package/lib/typescript/src/recorder/navigationTracker.d.ts.map +1 -0
  216. package/{dist → lib/typescript/src}/recorder/screenRecorder.d.ts +4 -4
  217. package/lib/typescript/src/recorder/screenRecorder.d.ts.map +1 -0
  218. package/{dist → lib/typescript/src}/services/api.service.d.ts +2 -1
  219. package/lib/typescript/src/services/api.service.d.ts.map +1 -0
  220. package/{dist → lib/typescript/src}/services/network.service.d.ts +1 -0
  221. package/lib/typescript/src/services/network.service.d.ts.map +1 -0
  222. package/{dist → lib/typescript/src}/services/screenMaskingService.d.ts +2 -1
  223. package/lib/typescript/src/services/screenMaskingService.d.ts.map +1 -0
  224. package/{dist → lib/typescript/src}/services/storage.service.d.ts +2 -1
  225. package/lib/typescript/src/services/storage.service.d.ts.map +1 -0
  226. package/{dist → lib/typescript/src}/session-recorder.d.ts +3 -2
  227. package/lib/typescript/src/session-recorder.d.ts.map +1 -0
  228. package/{dist → lib/typescript/src}/types/configs.d.ts +3 -2
  229. package/lib/typescript/src/types/configs.d.ts.map +1 -0
  230. package/{dist → lib/typescript/src}/types/index.d.ts +1 -0
  231. package/lib/typescript/src/types/index.d.ts.map +1 -0
  232. package/{dist → lib/typescript/src}/types/session-recorder.d.ts +7 -6
  233. package/lib/typescript/src/types/session-recorder.d.ts.map +1 -0
  234. package/{dist → lib/typescript/src}/types/session.d.ts +1 -0
  235. package/lib/typescript/src/types/session.d.ts.map +1 -0
  236. package/{dist → lib/typescript/src}/utils/app-metadata.d.ts +1 -0
  237. package/lib/typescript/src/utils/app-metadata.d.ts.map +1 -0
  238. package/{dist → lib/typescript/src}/utils/constants.optional.d.ts +1 -0
  239. package/lib/typescript/src/utils/constants.optional.d.ts.map +1 -0
  240. package/{dist → lib/typescript/src}/utils/constants.optional.expo.d.ts +1 -0
  241. package/lib/typescript/src/utils/constants.optional.expo.d.ts.map +1 -0
  242. package/{dist → lib/typescript/src}/utils/createStore.d.ts +1 -0
  243. package/lib/typescript/src/utils/createStore.d.ts.map +1 -0
  244. package/lib/typescript/src/utils/index.d.ts +8 -0
  245. package/lib/typescript/src/utils/index.d.ts.map +1 -0
  246. package/{dist → lib/typescript/src}/utils/logger.d.ts +2 -1
  247. package/lib/typescript/src/utils/logger.d.ts.map +1 -0
  248. package/{dist → lib/typescript/src}/utils/platform.d.ts +2 -1
  249. package/lib/typescript/src/utils/platform.d.ts.map +1 -0
  250. package/{dist → lib/typescript/src}/utils/request-utils.d.ts +1 -0
  251. package/lib/typescript/src/utils/request-utils.d.ts.map +1 -0
  252. package/{dist → lib/typescript/src}/utils/rrweb-events.d.ts +2 -1
  253. package/lib/typescript/src/utils/rrweb-events.d.ts.map +1 -0
  254. package/{dist → lib/typescript/src}/utils/session.d.ts +1 -0
  255. package/lib/typescript/src/utils/session.d.ts.map +1 -0
  256. package/{dist → lib/typescript/src}/utils/shallowEqual.d.ts +1 -0
  257. package/lib/typescript/src/utils/shallowEqual.d.ts.map +1 -0
  258. package/{dist → lib/typescript/src}/utils/time.d.ts +1 -0
  259. package/lib/typescript/src/utils/time.d.ts.map +1 -0
  260. package/{dist → lib/typescript/src}/utils/type-utils.d.ts +1 -0
  261. package/lib/typescript/src/utils/type-utils.d.ts.map +1 -0
  262. package/lib/typescript/src/version.d.ts +2 -0
  263. package/lib/typescript/src/version.d.ts.map +1 -0
  264. package/package.json +154 -37
  265. package/react-native.config.js +14 -0
  266. package/src/SessionRecorderNativeSpec.ts +33 -0
  267. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +20 -0
  268. package/src/components/ScreenRecorderView/index.ts +1 -0
  269. package/src/components/SessionRecorderWidget/ErrorBanner.tsx +58 -0
  270. package/src/components/SessionRecorderWidget/FinalPopover.tsx +96 -0
  271. package/src/components/SessionRecorderWidget/FloatingButton.tsx +176 -0
  272. package/src/components/SessionRecorderWidget/InitialPopover.tsx +167 -0
  273. package/src/components/SessionRecorderWidget/ModalContainer.tsx +189 -0
  274. package/src/components/SessionRecorderWidget/ModalHeader.tsx +26 -0
  275. package/src/components/SessionRecorderWidget/SessionRecorderWidget.tsx +150 -0
  276. package/src/components/SessionRecorderWidget/icons.tsx +80 -0
  277. package/src/components/SessionRecorderWidget/index.ts +3 -0
  278. package/src/components/SessionRecorderWidget/styles.ts +168 -0
  279. package/src/config/constants.ts +67 -0
  280. package/src/config/defaults.ts +105 -0
  281. package/src/config/index.ts +6 -0
  282. package/src/config/masking.ts +60 -0
  283. package/src/config/session-recorder.ts +87 -0
  284. package/src/config/validators.ts +54 -0
  285. package/src/config/widget.ts +47 -0
  286. package/src/context/SessionRecorderContext.tsx +138 -0
  287. package/src/context/SessionRecorderStore.ts +22 -0
  288. package/src/context/useSessionRecorderStore.ts +34 -0
  289. package/src/context/useStoreSelector.ts +36 -0
  290. package/src/index.ts +13 -0
  291. package/src/native/SessionRecorderNative.ts +120 -0
  292. package/src/native/index.ts +5 -0
  293. package/src/otel/helpers.ts +290 -0
  294. package/src/otel/index.ts +132 -0
  295. package/src/otel/instrumentations/index.ts +118 -0
  296. package/src/patch/xhr.ts +148 -0
  297. package/src/recorder/eventExporter.ts +150 -0
  298. package/src/recorder/gestureRecorder.ts +828 -0
  299. package/src/recorder/index.ts +203 -0
  300. package/src/recorder/navigationTracker.ts +268 -0
  301. package/src/recorder/screenRecorder.ts +600 -0
  302. package/src/services/api.service.ts +216 -0
  303. package/src/services/network.service.ts +191 -0
  304. package/src/services/screenMaskingService.ts +153 -0
  305. package/src/services/storage.service.ts +248 -0
  306. package/src/session-recorder.ts +647 -0
  307. package/src/types/configs.ts +118 -0
  308. package/src/types/expo-constants.d.ts +7 -0
  309. package/src/types/index.ts +27 -0
  310. package/src/types/session-recorder.ts +381 -0
  311. package/src/types/session.ts +65 -0
  312. package/src/utils/app-metadata.ts +31 -0
  313. package/src/utils/constants.optional.expo.ts +5 -0
  314. package/src/utils/constants.optional.ts +18 -0
  315. package/src/utils/createStore.ts +32 -0
  316. package/{dist/utils/index.d.ts → src/utils/index.ts} +1 -0
  317. package/src/utils/logger.ts +245 -0
  318. package/src/utils/platform.ts +401 -0
  319. package/src/utils/request-utils.ts +61 -0
  320. package/src/utils/rrweb-events.ts +329 -0
  321. package/src/utils/session.ts +22 -0
  322. package/src/utils/shallowEqual.ts +20 -0
  323. package/src/utils/time.ts +20 -0
  324. package/src/utils/type-utils.ts +75 -0
  325. package/src/version.ts +1 -0
  326. package/copy-react-native-dist.sh +0 -56
  327. package/dist/components/ScreenRecorderView/ScreenRecorderView.d.ts +0 -5
  328. package/dist/components/ScreenRecorderView/ScreenRecorderView.js +0 -1
  329. package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +0 -1
  330. package/dist/components/ScreenRecorderView/index.d.ts +0 -1
  331. package/dist/components/ScreenRecorderView/index.js +0 -1
  332. package/dist/components/ScreenRecorderView/index.js.map +0 -1
  333. package/dist/components/SessionRecorderWidget/ErrorBanner.js +0 -1
  334. package/dist/components/SessionRecorderWidget/ErrorBanner.js.map +0 -1
  335. package/dist/components/SessionRecorderWidget/FinalPopover.js +0 -1
  336. package/dist/components/SessionRecorderWidget/FinalPopover.js.map +0 -1
  337. package/dist/components/SessionRecorderWidget/FloatingButton.js +0 -1
  338. package/dist/components/SessionRecorderWidget/FloatingButton.js.map +0 -1
  339. package/dist/components/SessionRecorderWidget/InitialPopover.js +0 -1
  340. package/dist/components/SessionRecorderWidget/InitialPopover.js.map +0 -1
  341. package/dist/components/SessionRecorderWidget/ModalContainer.js +0 -1
  342. package/dist/components/SessionRecorderWidget/ModalContainer.js.map +0 -1
  343. package/dist/components/SessionRecorderWidget/ModalHeader.js +0 -1
  344. package/dist/components/SessionRecorderWidget/ModalHeader.js.map +0 -1
  345. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js +0 -1
  346. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js.map +0 -1
  347. package/dist/components/SessionRecorderWidget/icons.js +0 -1
  348. package/dist/components/SessionRecorderWidget/icons.js.map +0 -1
  349. package/dist/components/SessionRecorderWidget/index.d.ts +0 -2
  350. package/dist/components/SessionRecorderWidget/index.js +0 -1
  351. package/dist/components/SessionRecorderWidget/index.js.map +0 -1
  352. package/dist/components/SessionRecorderWidget/styles.js +0 -1
  353. package/dist/components/SessionRecorderWidget/styles.js.map +0 -1
  354. package/dist/components/index.js +0 -1
  355. package/dist/components/index.js.map +0 -1
  356. package/dist/config/constants.js +0 -1
  357. package/dist/config/constants.js.map +0 -1
  358. package/dist/config/defaults.js +0 -1
  359. package/dist/config/defaults.js.map +0 -1
  360. package/dist/config/index.js +0 -1
  361. package/dist/config/index.js.map +0 -1
  362. package/dist/config/masking.d.ts +0 -2
  363. package/dist/config/masking.js +0 -1
  364. package/dist/config/masking.js.map +0 -1
  365. package/dist/config/session-recorder.d.ts +0 -2
  366. package/dist/config/session-recorder.js +0 -1
  367. package/dist/config/session-recorder.js.map +0 -1
  368. package/dist/config/validators.js +0 -1
  369. package/dist/config/validators.js.map +0 -1
  370. package/dist/config/widget.js +0 -1
  371. package/dist/config/widget.js.map +0 -1
  372. package/dist/context/SessionRecorderContext.js +0 -1
  373. package/dist/context/SessionRecorderContext.js.map +0 -1
  374. package/dist/context/SessionRecorderStore.js +0 -1
  375. package/dist/context/SessionRecorderStore.js.map +0 -1
  376. package/dist/context/useSessionRecorderStore.js +0 -1
  377. package/dist/context/useSessionRecorderStore.js.map +0 -1
  378. package/dist/context/useStoreSelector.js +0 -1
  379. package/dist/context/useStoreSelector.js.map +0 -1
  380. package/dist/index.js +0 -1
  381. package/dist/index.js.map +0 -1
  382. package/dist/native/GestureRecorderNative.d.ts +0 -57
  383. package/dist/native/GestureRecorderNative.js +0 -1
  384. package/dist/native/GestureRecorderNative.js.map +0 -1
  385. package/dist/native/GestureRecorderNativeTurboSpec.d.ts +0 -31
  386. package/dist/native/GestureRecorderNativeTurboSpec.js +0 -1
  387. package/dist/native/GestureRecorderNativeTurboSpec.js.map +0 -1
  388. package/dist/native/SessionRecorderNative.d.ts +0 -33
  389. package/dist/native/SessionRecorderNative.js +0 -1
  390. package/dist/native/SessionRecorderNative.js.map +0 -1
  391. package/dist/native/SessionRecorderNativeTurboSpec.js +0 -1
  392. package/dist/native/SessionRecorderNativeTurboSpec.js.map +0 -1
  393. package/dist/native/index.d.ts +0 -2
  394. package/dist/native/index.js +0 -1
  395. package/dist/native/index.js.map +0 -1
  396. package/dist/otel/helpers.js +0 -1
  397. package/dist/otel/helpers.js.map +0 -1
  398. package/dist/otel/index.js +0 -1
  399. package/dist/otel/index.js.map +0 -1
  400. package/dist/otel/instrumentations/index.js +0 -1
  401. package/dist/otel/instrumentations/index.js.map +0 -1
  402. package/dist/patch/index.js +0 -1
  403. package/dist/patch/index.js.map +0 -1
  404. package/dist/patch/xhr.js +0 -1
  405. package/dist/patch/xhr.js.map +0 -1
  406. package/dist/recorder/eventExporter.js +0 -1
  407. package/dist/recorder/eventExporter.js.map +0 -1
  408. package/dist/recorder/gestureRecorder.js +0 -1
  409. package/dist/recorder/gestureRecorder.js.map +0 -1
  410. package/dist/recorder/index.js +0 -1
  411. package/dist/recorder/index.js.map +0 -1
  412. package/dist/recorder/navigationTracker.js +0 -1
  413. package/dist/recorder/navigationTracker.js.map +0 -1
  414. package/dist/recorder/screenRecorder.js +0 -1
  415. package/dist/recorder/screenRecorder.js.map +0 -1
  416. package/dist/services/api.service.js +0 -1
  417. package/dist/services/api.service.js.map +0 -1
  418. package/dist/services/network.service.js +0 -1
  419. package/dist/services/network.service.js.map +0 -1
  420. package/dist/services/screenMaskingService.js +0 -1
  421. package/dist/services/screenMaskingService.js.map +0 -1
  422. package/dist/services/storage.service.js +0 -1
  423. package/dist/services/storage.service.js.map +0 -1
  424. package/dist/session-recorder.js +0 -1
  425. package/dist/session-recorder.js.map +0 -1
  426. package/dist/types/configs.js +0 -1
  427. package/dist/types/configs.js.map +0 -1
  428. package/dist/types/index.js +0 -1
  429. package/dist/types/index.js.map +0 -1
  430. package/dist/types/session-recorder.js +0 -1
  431. package/dist/types/session-recorder.js.map +0 -1
  432. package/dist/types/session.js +0 -1
  433. package/dist/types/session.js.map +0 -1
  434. package/dist/utils/app-metadata.js +0 -1
  435. package/dist/utils/app-metadata.js.map +0 -1
  436. package/dist/utils/constants.optional.expo.js +0 -1
  437. package/dist/utils/constants.optional.expo.js.map +0 -1
  438. package/dist/utils/constants.optional.js +0 -1
  439. package/dist/utils/constants.optional.js.map +0 -1
  440. package/dist/utils/createStore.js +0 -1
  441. package/dist/utils/createStore.js.map +0 -1
  442. package/dist/utils/index.js +0 -1
  443. package/dist/utils/index.js.map +0 -1
  444. package/dist/utils/logger.js +0 -1
  445. package/dist/utils/logger.js.map +0 -1
  446. package/dist/utils/platform.js +0 -1
  447. package/dist/utils/platform.js.map +0 -1
  448. package/dist/utils/request-utils.js +0 -1
  449. package/dist/utils/request-utils.js.map +0 -1
  450. package/dist/utils/rrweb-events.js +0 -1
  451. package/dist/utils/rrweb-events.js.map +0 -1
  452. package/dist/utils/session.js +0 -1
  453. package/dist/utils/session.js.map +0 -1
  454. package/dist/utils/shallowEqual.js +0 -1
  455. package/dist/utils/shallowEqual.js.map +0 -1
  456. package/dist/utils/time.js +0 -1
  457. package/dist/utils/time.js.map +0 -1
  458. package/dist/utils/type-utils.js +0 -1
  459. package/dist/utils/type-utils.js.map +0 -1
  460. package/dist/version.d.ts +0 -1
  461. package/dist/version.js +0 -1
  462. package/dist/version.js.map +0 -1
  463. package/docs/AUTO_METADATA_DETECTION.md +0 -108
  464. package/ios/GestureRecorderNative.swift +0 -316
  465. package/ios/SessionRecorderNative.m +0 -17
  466. package/ios/SessionRecorderNative.podspec +0 -26
  467. package/turbo.json +0 -41
  468. /package/{dist/components/index.d.ts → src/components/index.ts} +0 -0
  469. /package/{dist/patch/index.d.ts → src/patch/index.ts} +0 -0
@@ -0,0 +1,861 @@
1
+ package com.sessionrecordernative
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.Activity
5
+ import android.graphics.*
6
+ import android.os.Build
7
+ import android.os.Handler
8
+ import android.os.HandlerThread
9
+ import android.util.Base64
10
+ import android.view.PixelCopy
11
+ import android.view.View
12
+ import android.view.ViewGroup
13
+ import android.view.Window
14
+ import android.webkit.WebView
15
+ import android.widget.*
16
+ import com.facebook.react.bridge.*
17
+ import com.facebook.react.modules.core.DeviceEventManagerModule
18
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
19
+ import com.sessionrecordernative.util.ViewUtils
20
+ import java.io.ByteArrayOutputStream
21
+ import java.util.concurrent.CountDownLatch
22
+ import java.util.concurrent.TimeUnit
23
+ import com.facebook.react.bridge.ReactApplicationContext
24
+ import com.facebook.react.module.annotations.ReactModule
25
+
26
+ @ReactModule(name = SessionRecorderNativeModule.NAME)
27
+ class SessionRecorderNativeModule(reactContext: ReactApplicationContext) :
28
+ ReactContextBaseJavaModule(reactContext) {
29
+
30
+ override fun getName(): String {
31
+ return NAME
32
+ }
33
+
34
+ companion object {
35
+ const val NAME = "SessionRecorderNative"
36
+ }
37
+
38
+ private val reactContext: ReactApplicationContext = reactContext
39
+
40
+ // Configuration object for masking behavior
41
+ private var config: SessionRecorderNativeConfig = SessionRecorderNativeConfig()
42
+
43
+ // Gesture recording state
44
+ private var isRecording = false
45
+ private var rootView: ViewGroup? = null
46
+ private var gestureCallback: Callback? = null
47
+ private var originalWindowCallback: android.view.Window.Callback? = null
48
+ private var initialTouchTime = 0L
49
+ private var initialTouchX = 0f
50
+ private var initialTouchY = 0f
51
+ private var lastTouchX = 0f
52
+ private var lastTouchY = 0f
53
+ private var isPanning = false
54
+ private var isLongPressTriggered = false
55
+ private var longPressRunnable: Runnable? = null
56
+ private var handler: android.os.Handler? = null
57
+
58
+
59
+ @ReactMethod
60
+ fun captureAndMask(promise: Promise) {
61
+ val activity =
62
+ reactContext.currentActivity
63
+ ?: return promise.reject("NO_ACTIVITY", "No activity found")
64
+
65
+ try {
66
+ val maskedImage = captureAndMaskScreen(activity, null)
67
+ promise.resolve(maskedImage)
68
+ } catch (e: Exception) {
69
+ promise.reject("CAPTURE_FAILED", "Failed to capture and mask screen", e)
70
+ }
71
+ }
72
+
73
+ @ReactMethod
74
+ fun captureAndMaskWithOptions(options: ReadableMap, promise: Promise) {
75
+ val activity =
76
+ reactContext.currentActivity
77
+ ?: return promise.reject("NO_ACTIVITY", "No activity found")
78
+
79
+ try {
80
+ val maskedImage = captureAndMaskScreen(activity, options)
81
+ promise.resolve(maskedImage)
82
+ } catch (e: Exception) {
83
+ promise.reject("CAPTURE_FAILED", "Failed to capture and mask screen", e)
84
+ }
85
+ }
86
+
87
+ // --- Gesture recording API (merged from GestureRecorderModule) ---
88
+ @ReactMethod
89
+ fun startGestureRecording(promise: Promise) {
90
+ val activity =
91
+ reactContext.currentActivity
92
+ ?: return promise.reject("NO_ACTIVITY", "No activity found")
93
+
94
+ try {
95
+ if (isRecording) {
96
+ promise.resolve(null)
97
+ return
98
+ }
99
+
100
+ setupGestureListener(activity)
101
+ isRecording = true
102
+ promise.resolve(null)
103
+ } catch (e: Exception) {
104
+ promise.reject("SETUP_FAILED", "Failed to setup gesture recording", e)
105
+ }
106
+ }
107
+
108
+ @ReactMethod
109
+ fun stopGestureRecording(promise: Promise) {
110
+ try {
111
+ if (!isRecording) {
112
+ promise.resolve(null)
113
+ return
114
+ }
115
+
116
+ removeGestureListener()
117
+ isRecording = false
118
+ promise.resolve(null)
119
+ } catch (e: Exception) {
120
+ promise.reject("CLEANUP_FAILED", "Failed to cleanup gesture recording", e)
121
+ }
122
+ }
123
+
124
+ @ReactMethod
125
+ fun isGestureRecordingActive(promise: Promise) {
126
+ promise.resolve(isRecording)
127
+ }
128
+
129
+ @ReactMethod
130
+ fun setGestureCallback(callback: Callback) {
131
+ this.gestureCallback = callback
132
+ }
133
+
134
+ @ReactMethod
135
+ fun recordGesture(
136
+ gestureType: String,
137
+ x: Double,
138
+ y: Double,
139
+ target: String?,
140
+ metadata: ReadableMap?
141
+ ) {
142
+ val gestureEvent =
143
+ Arguments.createMap().apply {
144
+ putString("type", gestureType)
145
+ putDouble("timestamp", System.currentTimeMillis().toDouble())
146
+ putDouble("x", x)
147
+ putDouble("y", y)
148
+ putString("target", target ?: "")
149
+ putMap("metadata", metadata ?: Arguments.createMap())
150
+ }
151
+
152
+ sendEvent("onGestureDetected", gestureEvent)
153
+ gestureCallback?.invoke(gestureEvent)
154
+ }
155
+
156
+ private fun setupGestureListener(activity: Activity) {
157
+ // Resolve content view for target detection
158
+ rootView =
159
+ activity.findViewById<ViewGroup>(android.R.id.content)
160
+ ?: throw RuntimeException("Could not get content view")
161
+
162
+ handler = android.os.Handler(android.os.Looper.getMainLooper())
163
+
164
+ // Intercept MotionEvents at the Window level to observe all touches
165
+ val window = activity.window
166
+ val delegate = window.callback ?: throw RuntimeException("Window callback is null")
167
+ originalWindowCallback = delegate
168
+
169
+ window.callback =
170
+ object : android.view.Window.Callback by delegate {
171
+ override fun dispatchTouchEvent(event: android.view.MotionEvent): Boolean {
172
+ if (isRecording) {
173
+ // Use screen pixel coordinates for hit-testing target
174
+ val screenPointPx = android.graphics.PointF(event.rawX, event.rawY)
175
+ val target = ViewUtils.findTargetView(screenPointPx, rootView)
176
+ when (event.action) {
177
+ android.view.MotionEvent.ACTION_DOWN -> handleTouchDown(event, target)
178
+ android.view.MotionEvent.ACTION_MOVE -> handleTouchMove(event, target)
179
+ android.view.MotionEvent.ACTION_UP -> handleTouchUp(event, target)
180
+ android.view.MotionEvent.ACTION_CANCEL -> handleTouchCancel(event, target)
181
+ }
182
+ lastTouchX = event.x
183
+ lastTouchY = event.y
184
+ }
185
+ // Always forward to original callback so we don't block the app
186
+ return delegate.dispatchTouchEvent(event)
187
+ }
188
+ }
189
+ }
190
+
191
+ private fun removeGestureListener() {
192
+ // Restore original Window callback
193
+ reactContext.currentActivity?.let { activity ->
194
+ val window = activity.window
195
+ originalWindowCallback?.let { original ->
196
+ if (window.callback !== original) {
197
+ window.callback = original
198
+ }
199
+ }
200
+ }
201
+ originalWindowCallback = null
202
+
203
+ // Cleanup root
204
+ rootView = null
205
+
206
+ // Cancel any pending long press
207
+ longPressRunnable?.let { handler?.removeCallbacks(it) }
208
+ longPressRunnable = null
209
+ handler = null
210
+ }
211
+
212
+ private fun handleTouchDown(
213
+ event: android.view.MotionEvent,
214
+ target: com.sessionrecordernative.model.TargetInfo
215
+ ) {
216
+ val dp = getScreenDpPoint(event)
217
+ initialTouchTime = System.currentTimeMillis()
218
+ initialTouchX = dp.x
219
+ initialTouchY = dp.y
220
+ lastTouchX = dp.x
221
+ lastTouchY = dp.y
222
+ isPanning = false
223
+ isLongPressTriggered = false
224
+
225
+ // Schedule long press detection
226
+ longPressRunnable = Runnable {
227
+ if (!isLongPressTriggered && !isPanning) {
228
+ isLongPressTriggered = true
229
+ sendGestureEvent("long_press", dp.x, dp.y, target, createLongPressMetadata())
230
+ }
231
+ }
232
+ handler?.postDelayed(longPressRunnable!!, 500) // 0.5 seconds like iOS
233
+ }
234
+
235
+ private fun handleTouchMove(
236
+ event: android.view.MotionEvent,
237
+ target: com.sessionrecordernative.model.TargetInfo
238
+ ) {
239
+ val dp = getScreenDpPoint(event)
240
+ val deltaX = dp.x - initialTouchX
241
+ val deltaY = dp.y - initialTouchY
242
+ val distance = kotlin.math.sqrt(deltaX * deltaX + deltaY * deltaY)
243
+
244
+ // Start panning if moved more than 10 pixels
245
+ if (!isPanning && distance > 10) {
246
+ isPanning = true
247
+ // Cancel long press if we start panning
248
+ longPressRunnable?.let { handler?.removeCallbacks(it) }
249
+ sendGestureEvent("pan_start", dp.x, dp.y, target, createPanMetadata(event, deltaX, deltaY))
250
+ } else if (isPanning) {
251
+ sendGestureEvent("pan_move", dp.x, dp.y, target, createPanMetadata(event, deltaX, deltaY))
252
+ }
253
+
254
+ lastTouchX = dp.x
255
+ lastTouchY = dp.y
256
+ }
257
+
258
+ private fun handleTouchUp(
259
+ event: android.view.MotionEvent,
260
+ target: com.sessionrecordernative.model.TargetInfo
261
+ ) {
262
+ val dp = getScreenDpPoint(event)
263
+ val touchDuration = System.currentTimeMillis() - initialTouchTime
264
+ val deltaX = dp.x - initialTouchX
265
+ val deltaY = dp.y - initialTouchY
266
+ val distance = kotlin.math.sqrt(deltaX * deltaX + deltaY * deltaY)
267
+
268
+ // Cancel long press
269
+ longPressRunnable?.let { handler?.removeCallbacks(it) }
270
+
271
+ if (isPanning) {
272
+ // End pan gesture
273
+ sendGestureEvent("pan_end", dp.x, dp.y, target, createPanMetadata(event, deltaX, deltaY))
274
+ } else if (distance < 10 && touchDuration < 500 && !isLongPressTriggered) {
275
+ // Single tap (short duration, small movement)
276
+ sendGestureEvent("tap", dp.x, dp.y, target, createTapMetadata())
277
+ }
278
+
279
+ // Reset state
280
+ isPanning = false
281
+ isLongPressTriggered = false
282
+ }
283
+
284
+ private fun handleTouchCancel(
285
+ event: android.view.MotionEvent,
286
+ target: com.sessionrecordernative.model.TargetInfo
287
+ ) {
288
+ // Cancel long press
289
+ longPressRunnable?.let { handler?.removeCallbacks(it) }
290
+
291
+ if (isPanning) {
292
+ val dp = getScreenDpPoint(event)
293
+ val deltaX = dp.x - initialTouchX
294
+ val deltaY = dp.y - initialTouchY
295
+ sendGestureEvent("pan_end", dp.x, dp.y, target, createPanMetadata(event, deltaX, deltaY))
296
+ }
297
+
298
+ // Reset state
299
+ isPanning = false
300
+ isLongPressTriggered = false
301
+ }
302
+
303
+ private fun getScreenDpPoint(event: android.view.MotionEvent): android.graphics.PointF {
304
+ val density = reactContext.resources.displayMetrics.density
305
+ return android.graphics.PointF(event.rawX / density, event.rawY / density)
306
+ }
307
+
308
+ private fun sendGestureEvent(
309
+ gestureType: String,
310
+ x: Float,
311
+ y: Float,
312
+ target: com.sessionrecordernative.model.TargetInfo,
313
+ metadata: WritableMap
314
+ ) {
315
+ val targetInfo =
316
+ Arguments.createMap().apply {
317
+ putString("identifier", target.identifier)
318
+ putString("label", target.label)
319
+ putString("role", target.role)
320
+ putString("testId", target.testId)
321
+ putString("text", target.text)
322
+ }
323
+
324
+ val gestureEvent =
325
+ Arguments.createMap().apply {
326
+ putString("type", gestureType)
327
+ putDouble("timestamp", System.currentTimeMillis().toDouble())
328
+ putDouble("x", x.toDouble())
329
+ putDouble("y", y.toDouble())
330
+ putString("target", target.identifier)
331
+ putMap("targetInfo", targetInfo)
332
+ putMap("metadata", metadata)
333
+ }
334
+
335
+ sendEvent("onGestureDetected", gestureEvent)
336
+ gestureCallback?.invoke(gestureEvent)
337
+ }
338
+
339
+ private fun createTapMetadata(): WritableMap {
340
+ return Arguments.createMap().apply { putDouble("pressure", 1.0) }
341
+ }
342
+
343
+ private fun createLongPressMetadata(): WritableMap {
344
+ return Arguments.createMap().apply {
345
+ putDouble("duration", 0.5)
346
+ putDouble("pressure", 1.0)
347
+ }
348
+ }
349
+
350
+ private fun createPanMetadata(event: android.view.MotionEvent, deltaX: Float, deltaY: Float): WritableMap {
351
+ val velocity = kotlin.math.sqrt(deltaX * deltaX + deltaY * deltaY)
352
+ return Arguments.createMap().apply {
353
+ putDouble("velocity", velocity.toDouble())
354
+ putDouble("deltaX", deltaX.toDouble())
355
+ putDouble("deltaY", deltaY.toDouble())
356
+ putDouble("pressure", event.pressure.toDouble())
357
+ }
358
+ }
359
+
360
+ private fun sendEvent(eventName: String, params: WritableMap) {
361
+ reactContext
362
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
363
+ .emit(eventName, params)
364
+ }
365
+
366
+ @ReactMethod
367
+ fun addListener(eventName: String) {
368
+ // Required for RN event emitter contracts
369
+ }
370
+
371
+ @ReactMethod
372
+ fun removeListeners(count: Int) {
373
+ // Required for RN event emitter contracts
374
+ }
375
+
376
+ private fun captureAndMaskScreen(activity: Activity, options: ReadableMap?): String {
377
+ // Update configuration from options
378
+ updateConfiguration(options)
379
+
380
+ val rootView = activity.window.decorView.rootView
381
+ val window = activity.window
382
+
383
+ // Capture bitmap
384
+ val bitmap =
385
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
386
+ captureWithPixelCopy(rootView, window)
387
+ } else {
388
+ captureWithDrawingCache(rootView)
389
+ }
390
+
391
+ bitmap ?: throw RuntimeException("Failed to capture screen")
392
+
393
+ // Apply masking
394
+ val maskedBitmap = applyMasking(bitmap, rootView, options)
395
+
396
+ // Apply optional scaling (resolution downsample)
397
+ val finalBitmap =
398
+ if (config.scale < 1.0f) resizeBitmap(maskedBitmap, config.scale) else maskedBitmap
399
+
400
+ // Convert to base64 with compression
401
+ val output = ByteArrayOutputStream()
402
+ val quality = (config.imageQuality * 100).toInt().coerceIn(1, 100)
403
+ finalBitmap.compress(Bitmap.CompressFormat.JPEG, quality, output)
404
+ val base64 = Base64.encodeToString(output.toByteArray(), Base64.DEFAULT)
405
+
406
+ // Clean up memory
407
+ if (finalBitmap != maskedBitmap) maskedBitmap.recycle()
408
+
409
+ return base64
410
+ }
411
+
412
+ private fun resizeBitmap(bitmap: Bitmap, scale: Float): Bitmap {
413
+ val width = (bitmap.width * scale).toInt().coerceAtLeast(1)
414
+ val height = (bitmap.height * scale).toInt().coerceAtLeast(1)
415
+ return Bitmap.createScaledBitmap(bitmap, width, height, true)
416
+ }
417
+
418
+ @SuppressLint("NewApi")
419
+ private fun captureWithPixelCopy(rootView: View, window: Window): Bitmap? {
420
+ val bitmap = Bitmap.createBitmap(rootView.width, rootView.height, Bitmap.Config.ARGB_8888)
421
+ val latch = CountDownLatch(1)
422
+ var success = false
423
+
424
+ val thread = HandlerThread("SessionRecorderScreenshot")
425
+ thread.start()
426
+ val handler = Handler(thread.looper)
427
+
428
+ try {
429
+ PixelCopy.request(
430
+ window,
431
+ bitmap,
432
+ { copyResult ->
433
+ try {
434
+ success = copyResult == PixelCopy.SUCCESS
435
+ } catch (e: Exception) {
436
+ success = false
437
+ } finally {
438
+ latch.countDown()
439
+ }
440
+ },
441
+ handler
442
+ )
443
+
444
+ // Wait for capture to complete (max 2 seconds)
445
+ latch.await(2, TimeUnit.SECONDS)
446
+
447
+ return if (success) bitmap else null
448
+ } catch (e: Exception) {
449
+ bitmap.recycle()
450
+ return null
451
+ } finally {
452
+ thread.quit()
453
+ }
454
+ }
455
+
456
+ @Suppress("DEPRECATION")
457
+ private fun captureWithDrawingCache(rootView: View): Bitmap? {
458
+ return try {
459
+ // Enable drawing cache
460
+ rootView.isDrawingCacheEnabled = true
461
+ rootView.buildDrawingCache()
462
+
463
+ val bitmap = Bitmap.createBitmap(rootView.drawingCache)
464
+ rootView.isDrawingCacheEnabled = false
465
+ bitmap
466
+ } catch (e: Exception) {
467
+ rootView.isDrawingCacheEnabled = false
468
+ null
469
+ }
470
+ }
471
+
472
+ private fun updateConfiguration(options: ReadableMap?) {
473
+ options?.let { opts ->
474
+ config =
475
+ SessionRecorderNativeConfig(
476
+ maskTextInputs =
477
+ if (opts.hasKey("maskTextInputs"))
478
+ opts.getBoolean("maskTextInputs")
479
+ else config.maskTextInputs,
480
+ maskImages =
481
+ if (opts.hasKey("maskImages")) opts.getBoolean("maskImages")
482
+ else config.maskImages,
483
+ maskButtons =
484
+ if (opts.hasKey("maskButtons")) opts.getBoolean("maskButtons")
485
+ else config.maskButtons,
486
+ maskWebViews =
487
+ if (opts.hasKey("maskWebViews")) opts.getBoolean("maskWebViews")
488
+ else config.maskWebViews,
489
+ imageQuality =
490
+ if (opts.hasKey("quality")) opts.getDouble("quality").toFloat()
491
+ else config.imageQuality,
492
+ noCaptureLabel =
493
+ if (opts.hasKey("noCaptureLabel"))
494
+ opts.getString("noCaptureLabel") ?: "no-capture"
495
+ else config.noCaptureLabel,
496
+ scale =
497
+ if (opts.hasKey("scale")) opts.getDouble("scale").toFloat()
498
+ else config.scale
499
+ )
500
+ }
501
+ }
502
+
503
+ private fun applyMasking(bitmap: Bitmap, rootView: View, options: ReadableMap?): Bitmap {
504
+ val maskedBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
505
+ val canvas = Canvas(maskedBitmap)
506
+
507
+ // Find maskable widgets
508
+ val maskableWidgets = mutableListOf<Rect>()
509
+ findMaskableWidgets(rootView, rootView, maskableWidgets)
510
+
511
+ for (frame in maskableWidgets) {
512
+ // Skip zero rects (which indicate invalid coordinates)
513
+ if (frame.isEmpty) continue
514
+
515
+ // Validate frame dimensions before processing
516
+ if (!frame.isValid()) continue
517
+
518
+ // Clip the frame to the image bounds to avoid drawing outside context
519
+ val clippedFrame =
520
+ Rect(
521
+ frame.left.coerceAtLeast(0),
522
+ frame.top.coerceAtLeast(0),
523
+ frame.right.coerceAtMost(bitmap.width),
524
+ frame.bottom.coerceAtMost(bitmap.height)
525
+ )
526
+
527
+ if (clippedFrame.isEmpty) continue
528
+
529
+ applyCleanMask(canvas, clippedFrame)
530
+ }
531
+
532
+ return maskedBitmap
533
+ }
534
+
535
+ private fun findMaskableWidgets(
536
+ view: View,
537
+ rootView: View,
538
+ maskableWidgets: MutableList<Rect>,
539
+ visitedViews: MutableSet<Int> = mutableSetOf()
540
+ ) {
541
+ val viewId = System.identityHashCode(view)
542
+
543
+ // Check for cycles to prevent stack overflow
544
+ if (viewId in visitedViews) {
545
+ return
546
+ }
547
+ visitedViews.add(viewId)
548
+
549
+ // Skip hidden or transparent views
550
+ if (!view.isVisible()) {
551
+ return
552
+ }
553
+
554
+ // Masking logic - clean and organized
555
+ when {
556
+ view is EditText && view.shouldMaskEditText() -> {
557
+ maskableWidgets.add(view.toAbsoluteRect(rootView))
558
+ return
559
+ }
560
+ view is Button && view.shouldMaskButton() -> {
561
+ maskableWidgets.add(view.toAbsoluteRect(rootView))
562
+ return
563
+ }
564
+ view is ImageView && view.shouldMaskImage() -> {
565
+ maskableWidgets.add(view.toAbsoluteRect(rootView))
566
+ return
567
+ }
568
+ view is WebView && view.shouldMaskWebView() -> {
569
+ maskableWidgets.add(view.toAbsoluteRect(rootView))
570
+ return
571
+ }
572
+ }
573
+
574
+ // Detect React Native views
575
+ if (isReactNativeView(view)) {
576
+ if (shouldMaskReactNativeView(view)) {
577
+ maskableWidgets.add(view.toAbsoluteRect(rootView))
578
+ return
579
+ }
580
+ }
581
+
582
+ // Recursively check subviews
583
+ if (view is ViewGroup) {
584
+ try {
585
+ val childCount = view.childCount
586
+ for (i in 0 until childCount) {
587
+ try {
588
+ val child = view.getChildAt(i)
589
+ if (child != null && child.isVisible()) {
590
+ findMaskableWidgets(child, rootView, maskableWidgets, visitedViews)
591
+ }
592
+ } catch (e: IndexOutOfBoundsException) {
593
+ // Skip this child if it's no longer valid
594
+ continue
595
+ } catch (e: Exception) {
596
+ // Skip this child if any other error occurs
597
+ continue
598
+ }
599
+ }
600
+ } catch (e: Exception) {
601
+ // If we can't iterate through children, just skip this view group
602
+ return
603
+ }
604
+ }
605
+ }
606
+
607
+ // MARK: - Sensitive Content Detection Methods
608
+
609
+ private fun View.isAnyInputSensitive(): Boolean {
610
+ return this.isTextInputSensitive() || config.maskImages
611
+ }
612
+
613
+ private fun View.isTextInputSensitive(): Boolean {
614
+ return config.maskTextInputs && this is EditText
615
+ }
616
+
617
+ // Masking methods for different view types
618
+ private fun Button.shouldMaskButton(): Boolean {
619
+ return config.maskButtons || this.isExplicitlyMasked(config.noCaptureLabel)
620
+ }
621
+
622
+ private fun ImageView.shouldMaskImage(): Boolean {
623
+ return config.maskImages || this.isExplicitlyMasked(config.noCaptureLabel)
624
+ }
625
+
626
+ private fun WebView.shouldMaskWebView(): Boolean {
627
+ return config.maskWebViews || this.isExplicitlyMasked(config.noCaptureLabel)
628
+ }
629
+
630
+ private fun EditText.shouldMaskEditText(): Boolean {
631
+ return this.isTextInputSensitive() || this.isExplicitlyMasked(config.noCaptureLabel)
632
+ }
633
+
634
+ // Check if view is explicitly marked as sensitive
635
+ private fun View.isExplicitlyMasked(noCaptureLabel: String = "no-capture"): Boolean {
636
+ return (tag as? String)?.lowercase()?.contains(noCaptureLabel.lowercase()) == true ||
637
+ contentDescription?.toString()?.lowercase()?.contains(noCaptureLabel.lowercase()) ==
638
+ true
639
+ }
640
+
641
+ private fun hasText(text: String?): Boolean {
642
+ return !text.isNullOrEmpty()
643
+ }
644
+
645
+ private fun isReactNativeView(view: View): Boolean {
646
+ // Check for React Native view class names
647
+ val className = view.javaClass.simpleName
648
+ return className.contains("React") || className.contains("RCT")
649
+ }
650
+
651
+ private fun shouldMaskReactNativeView(view: View): Boolean {
652
+ val className = view.javaClass.simpleName
653
+
654
+ // React Native input views: only treat EditText-like classes as inputs.
655
+ // Examples: ReactEditText, EditText, TextInputEditText
656
+ if (className.contains("EditText") ||
657
+ className.contains("ReactEditText") ||
658
+ className.contains("TextInputEditText") ||
659
+ // Some RN implementations may expose TextInput class names without TextView
660
+ // suffix
661
+ (className.contains("TextInput") && !className.contains("TextView"))
662
+ ) {
663
+ return config.maskTextInputs
664
+ }
665
+
666
+ // Do NOT mask generic ReactTextView labels when maskTextInputs is enabled
667
+ // Only mask images when explicitly configured
668
+ if (className.contains("ImageView") || className.contains("Image")) {
669
+ return config.maskImages
670
+ }
671
+
672
+ return false
673
+ }
674
+
675
+ private fun applyCleanMask(canvas: Canvas, frame: Rect) {
676
+ // Final validation before drawing to prevent Canvas errors
677
+ if (!frame.isValid()) return
678
+
679
+ // Clean, consistent solid color masking approach
680
+ // Use system gray colors that adapt to light/dark mode
681
+ val paint =
682
+ Paint().apply {
683
+ color = Color.parseColor("#F5F5F5") // Light gray background
684
+ }
685
+ canvas.drawRect(frame, paint)
686
+
687
+ // Add subtle border for visual definition
688
+ paint.apply {
689
+ color = Color.parseColor("#E0E0E0") // Slightly darker gray border
690
+ style = Paint.Style.STROKE
691
+ strokeWidth = 1f
692
+ }
693
+ canvas.drawRect(frame, paint)
694
+ }
695
+
696
+ private enum class MaskingType {
697
+ BLUR,
698
+ RECTANGLE,
699
+ PIXELATE,
700
+ NONE
701
+ }
702
+ }
703
+
704
+ // Extension functions for View
705
+ private fun View.isVisible(): Boolean {
706
+ try {
707
+ // Check for NaN values in dimensions
708
+ val width = width.toFloat()
709
+ val height = height.toFloat()
710
+
711
+ // Validate that dimensions are finite numbers (not NaN or infinite)
712
+ if (width.isNaN() || height.isNaN() || width.isInfinite() || height.isInfinite()) {
713
+ return false
714
+ }
715
+
716
+ return visibility == View.VISIBLE && alpha > 0.01f && width > 0 && height > 0
717
+ } catch (e: Exception) {
718
+ // If we can't determine visibility, assume it's not visible
719
+ return false
720
+ }
721
+ }
722
+
723
+ private fun View.isViewStateStableForMatrixOperations(): Boolean {
724
+ return try {
725
+ isAttachedToWindow &&
726
+ isLaidOut &&
727
+ // Check if view has valid dimensions
728
+ width > 0 &&
729
+ height > 0 &&
730
+ // Check if view is not in layout transition
731
+ !isInLayout &&
732
+ // Check if view doesn't have transient state (animations, etc.)
733
+ !hasTransientState() &&
734
+ // Check if view is not currently being animated
735
+ !isAnimationRunning() &&
736
+ // Check if view tree is not currently computing layout
737
+ !isComputingLayout() &&
738
+ // Check if view hierarchy is stable
739
+ rootView?.isAttachedToWindow == true
740
+ } catch (e: Throwable) {
741
+ // If any check fails, assume unstable state
742
+ false
743
+ }
744
+ }
745
+
746
+ private fun View.isAnimationRunning(): Boolean {
747
+ return try {
748
+ animation?.hasStarted() == true && animation?.hasEnded() != true
749
+ } catch (e: Throwable) {
750
+ false
751
+ }
752
+ }
753
+
754
+ private fun View.isComputingLayout(): Boolean {
755
+ return try {
756
+ // Check if direct parent ViewGroup is in layout
757
+ (parent as? ViewGroup)?.isInLayout == true
758
+ } catch (e: Throwable) {
759
+ false
760
+ }
761
+ }
762
+
763
+ private fun View.toAbsoluteRect(rootView: View): Rect {
764
+ try {
765
+ val location = IntArray(2)
766
+
767
+ // Use stable view state check before accessing location
768
+ if (isViewStateStableForMatrixOperations()) {
769
+ getLocationOnScreen(location)
770
+ } else {
771
+ // Use zero coordinates as fallback when view state is unstable
772
+ location[0] = 0
773
+ location[1] = 0
774
+ }
775
+
776
+ val rootLocation = IntArray(2)
777
+ rootView.getLocationOnScreen(rootLocation)
778
+
779
+ // Convert to relative coordinates within the root view
780
+ val relativeX = location[0] - rootLocation[0]
781
+ val relativeY = location[1] - rootLocation[1]
782
+
783
+ // Validate bounds before conversion to prevent NaN values
784
+ val width = this.width.toFloat()
785
+ val height = this.height.toFloat()
786
+
787
+ if (width.isNaN() ||
788
+ height.isNaN() ||
789
+ width.isInfinite() ||
790
+ height.isInfinite() ||
791
+ relativeX.toFloat().isNaN() ||
792
+ relativeY.toFloat().isNaN() ||
793
+ relativeX.toFloat().isInfinite() ||
794
+ relativeY.toFloat().isInfinite()
795
+ ) {
796
+ return Rect()
797
+ }
798
+
799
+ return Rect(relativeX, relativeY, relativeX + width.toInt(), relativeY + height.toInt())
800
+ } catch (e: Exception) {
801
+ // If we can't get the rect, return empty rect
802
+ return Rect()
803
+ }
804
+ }
805
+
806
+ private fun View.isNoCapture(): Boolean {
807
+ // Check for common patterns that indicate sensitive content
808
+
809
+ // Check content description for sensitive keywords
810
+ contentDescription?.toString()?.lowercase()?.let { desc ->
811
+ val sensitiveKeywords = listOf("password", "secret", "private", "sensitive", "confidential")
812
+ if (sensitiveKeywords.any { desc.contains(it) }) {
813
+ return true
814
+ }
815
+ }
816
+
817
+ // Check for secure text entry in EditText
818
+ if (this is EditText) {
819
+ val inputType = inputType
820
+ return (inputType and android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD) != 0 ||
821
+ (inputType and android.text.InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) != 0
822
+ }
823
+
824
+ // Check for password-related tag or accessibility identifier
825
+ tag?.toString()?.lowercase()?.let { tag ->
826
+ val sensitiveIdentifiers =
827
+ listOf("password", "secret", "private", "sensitive", "confidential")
828
+ if (sensitiveIdentifiers.any { tag.contains(it) }) {
829
+ return true
830
+ }
831
+ }
832
+
833
+ return false
834
+ }
835
+
836
+ private fun View.isSensitiveText(): Boolean {
837
+ // Check if this view contains sensitive text content
838
+ if (this is EditText) {
839
+ return isSecureTextEntry() || isNoCapture()
840
+ }
841
+
842
+ return false
843
+ }
844
+
845
+ private fun EditText.isSecureTextEntry(): Boolean {
846
+ val inputType = inputType
847
+ return (inputType and android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD) != 0 ||
848
+ (inputType and android.text.InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) != 0
849
+ }
850
+
851
+ private fun Rect.isValid(): Boolean {
852
+ return left >= 0 && top >= 0 && right > left && bottom > top && width() > 0 && height() > 0
853
+ }
854
+
855
+ private fun Int.isFinite(): Boolean {
856
+ return this != Int.MAX_VALUE && this != Int.MIN_VALUE
857
+ }
858
+
859
+ private fun Float.isFinite(): Boolean {
860
+ return !this.isNaN() && !this.isInfinite()
861
+ }