@multiplayer-app/session-recorder-react-native 1.0.1-beta.2 → 1.0.1-beta.4

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