react-native-inapp-inspector 2.3.15 → 2.3.17

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 (381) hide show
  1. package/README.md +1 -0
  2. package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +438 -7
  3. package/bin/cli.js +0 -0
  4. package/dist/commonjs/capture.d.ts +26 -0
  5. package/dist/commonjs/capture.js +32 -0
  6. package/dist/commonjs/components/AnalyticsEventCard.js +11 -11
  7. package/dist/commonjs/components/AnimatedEntrance.js +6 -70
  8. package/dist/commonjs/components/DomainHeader.js +3 -48
  9. package/dist/commonjs/components/EmptyState.js +2 -2
  10. package/dist/commonjs/components/HeadersSection.js +14 -34
  11. package/dist/commonjs/components/Inspector/AnalyticsTab.js +1 -24
  12. package/dist/commonjs/components/Inspector/ConsoleTab.js +1 -23
  13. package/dist/commonjs/components/Inspector/CrashTab.js +4 -25
  14. package/dist/commonjs/components/Inspector/DebuggingTab.js +2 -2
  15. package/dist/commonjs/components/Inspector/FeedbackModal.d.ts +7 -0
  16. package/dist/commonjs/components/Inspector/FeedbackModal.js +552 -0
  17. package/dist/commonjs/components/Inspector/InspectorHeader.js +675 -285
  18. package/dist/commonjs/components/Inspector/MainScreen.js +66 -69
  19. package/dist/commonjs/components/Inspector/MediaGalleryTab.d.ts +2 -0
  20. package/dist/commonjs/components/Inspector/MediaGalleryTab.js +307 -0
  21. package/dist/commonjs/components/Inspector/MediaPreviewModal.d.ts +11 -0
  22. package/dist/commonjs/components/Inspector/MediaPreviewModal.js +393 -0
  23. package/dist/commonjs/components/Inspector/NetworkTab.js +196 -206
  24. package/dist/commonjs/components/Inspector/NpmStarPrompt.d.ts +3 -0
  25. package/dist/commonjs/components/Inspector/NpmStarPrompt.js +494 -0
  26. package/dist/commonjs/components/Inspector/NpmUpdateToast.js +4 -9
  27. package/dist/commonjs/components/Inspector/PerformanceTab.js +8 -1
  28. package/dist/commonjs/components/Inspector/ReduxTab.js +4 -24
  29. package/dist/commonjs/components/Inspector/SettingsPanel.js +1280 -173
  30. package/dist/commonjs/components/Inspector/SkeletonPlaceholder.js +2 -2
  31. package/dist/commonjs/components/Inspector/StorageTab.js +3 -26
  32. package/dist/commonjs/components/Inspector/TabBar.js +55 -25
  33. package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +5 -10
  34. package/dist/commonjs/components/JsonViewer.js +43 -20
  35. package/dist/commonjs/components/LogCard.js +5 -5
  36. package/dist/commonjs/components/MetaAccordion.js +78 -80
  37. package/dist/commonjs/components/NetworkIcons.d.ts +13 -0
  38. package/dist/commonjs/components/NetworkIcons.js +83 -2
  39. package/dist/commonjs/components/SectionHeader.js +3 -48
  40. package/dist/commonjs/components/SkeletonPlaceholder.js +2 -2
  41. package/dist/commonjs/components/SourcePageCard.js +43 -45
  42. package/dist/commonjs/components/Toast.js +4 -4
  43. package/dist/commonjs/components/TouchableScale.js +9 -65
  44. package/dist/commonjs/components/TreeNode.js +2 -14
  45. package/dist/commonjs/constants/telemetryConfig.d.ts +3 -3
  46. package/dist/commonjs/constants/telemetryConfig.js +3 -3
  47. package/dist/commonjs/constants/version.d.ts +1 -1
  48. package/dist/commonjs/constants/version.js +1 -1
  49. package/dist/commonjs/customHooks/useAccordion.d.ts +3 -8
  50. package/dist/commonjs/customHooks/useAccordion.js +6 -36
  51. package/dist/commonjs/helpers/index.d.ts +3 -1
  52. package/dist/commonjs/helpers/index.js +11 -1
  53. package/dist/commonjs/helpers/remoteConfig.d.ts +4 -0
  54. package/dist/commonjs/helpers/remoteConfig.js +165 -0
  55. package/dist/commonjs/helpers/searchQueryParser.js +2 -0
  56. package/dist/commonjs/helpers/telemetry.d.ts +5 -0
  57. package/dist/commonjs/helpers/telemetry.js +339 -8
  58. package/dist/commonjs/i18n/locales/en.json +66 -2
  59. package/dist/commonjs/index.d.ts +2 -0
  60. package/dist/commonjs/index.js +70 -23
  61. package/dist/commonjs/native/NativeInspector.d.ts +60 -0
  62. package/dist/commonjs/native/NativeInspector.js +109 -2
  63. package/dist/commonjs/native/NativeNetworkInspector.d.ts +8 -0
  64. package/dist/commonjs/styles/AppColors.d.ts +36 -0
  65. package/dist/commonjs/styles/AppColors.js +36 -0
  66. package/dist/commonjs/styles/AppFonts.d.ts +2 -0
  67. package/dist/commonjs/styles/AppFonts.js +1 -0
  68. package/dist/commonjs/styles/index.d.ts +25 -1
  69. package/dist/commonjs/styles/index.js +22 -1
  70. package/dist/commonjs/types/enums.d.ts +2 -0
  71. package/dist/commonjs/types/enums.js +2 -0
  72. package/dist/commonjs/types/index.d.ts +2 -2
  73. package/dist/commonjs/types/interfaces.d.ts +12 -0
  74. package/dist/esm/capture.d.ts +26 -0
  75. package/dist/esm/capture.js +29 -0
  76. package/dist/esm/components/AnalyticsEventCard.js +11 -11
  77. package/dist/esm/components/AnimatedEntrance.js +4 -38
  78. package/dist/esm/components/DomainHeader.js +4 -16
  79. package/dist/esm/components/EmptyState.js +2 -2
  80. package/dist/esm/components/HeadersSection.js +16 -36
  81. package/dist/esm/components/Inspector/AnalyticsTab.js +3 -26
  82. package/dist/esm/components/Inspector/ConsoleTab.js +3 -25
  83. package/dist/esm/components/Inspector/CrashTab.js +6 -27
  84. package/dist/esm/components/Inspector/DebuggingTab.js +3 -3
  85. package/dist/esm/components/Inspector/FeedbackModal.d.ts +7 -0
  86. package/dist/esm/components/Inspector/FeedbackModal.js +515 -0
  87. package/dist/esm/components/Inspector/InspectorHeader.js +678 -288
  88. package/dist/esm/components/Inspector/MainScreen.js +67 -70
  89. package/dist/esm/components/Inspector/MediaGalleryTab.d.ts +2 -0
  90. package/dist/esm/components/Inspector/MediaGalleryTab.js +267 -0
  91. package/dist/esm/components/Inspector/MediaPreviewModal.d.ts +11 -0
  92. package/dist/esm/components/Inspector/MediaPreviewModal.js +353 -0
  93. package/dist/esm/components/Inspector/NetworkTab.js +199 -209
  94. package/dist/esm/components/Inspector/NpmStarPrompt.d.ts +3 -0
  95. package/dist/esm/components/Inspector/NpmStarPrompt.js +454 -0
  96. package/dist/esm/components/Inspector/NpmUpdateToast.js +5 -10
  97. package/dist/esm/components/Inspector/PerformanceTab.js +8 -1
  98. package/dist/esm/components/Inspector/ReduxTab.js +6 -26
  99. package/dist/esm/components/Inspector/SettingsPanel.js +1283 -176
  100. package/dist/esm/components/Inspector/SkeletonPlaceholder.js +2 -2
  101. package/dist/esm/components/Inspector/StorageTab.js +3 -26
  102. package/dist/esm/components/Inspector/TabBar.js +56 -26
  103. package/dist/esm/components/Inspector/UpdateAvailableModal.js +6 -11
  104. package/dist/esm/components/JsonViewer.js +43 -20
  105. package/dist/esm/components/LogCard.js +5 -5
  106. package/dist/esm/components/MetaAccordion.js +79 -81
  107. package/dist/esm/components/NetworkIcons.d.ts +13 -0
  108. package/dist/esm/components/NetworkIcons.js +68 -0
  109. package/dist/esm/components/SectionHeader.js +4 -16
  110. package/dist/esm/components/SkeletonPlaceholder.js +2 -2
  111. package/dist/esm/components/SourcePageCard.js +44 -46
  112. package/dist/esm/components/Toast.js +4 -4
  113. package/dist/esm/components/TouchableScale.js +7 -33
  114. package/dist/esm/components/TreeNode.js +4 -16
  115. package/dist/esm/constants/telemetryConfig.d.ts +3 -3
  116. package/dist/esm/constants/telemetryConfig.js +3 -3
  117. package/dist/esm/constants/version.d.ts +1 -1
  118. package/dist/esm/constants/version.js +1 -1
  119. package/dist/esm/customHooks/useAccordion.d.ts +3 -8
  120. package/dist/esm/customHooks/useAccordion.js +7 -37
  121. package/dist/esm/helpers/index.d.ts +3 -1
  122. package/dist/esm/helpers/index.js +9 -0
  123. package/dist/esm/helpers/remoteConfig.d.ts +4 -0
  124. package/dist/esm/helpers/remoteConfig.js +160 -0
  125. package/dist/esm/helpers/searchQueryParser.js +2 -0
  126. package/dist/esm/helpers/telemetry.d.ts +5 -0
  127. package/dist/esm/helpers/telemetry.js +332 -9
  128. package/dist/esm/i18n/locales/en.json +66 -2
  129. package/dist/esm/index.d.ts +2 -0
  130. package/dist/esm/index.js +66 -23
  131. package/dist/esm/native/NativeInspector.d.ts +60 -0
  132. package/dist/esm/native/NativeInspector.js +100 -1
  133. package/dist/esm/native/NativeNetworkInspector.d.ts +8 -0
  134. package/dist/esm/styles/AppColors.d.ts +36 -0
  135. package/dist/esm/styles/AppColors.js +36 -0
  136. package/dist/esm/styles/AppFonts.d.ts +2 -0
  137. package/dist/esm/styles/AppFonts.js +1 -0
  138. package/dist/esm/styles/index.d.ts +25 -1
  139. package/dist/esm/styles/index.js +22 -1
  140. package/dist/esm/types/enums.d.ts +2 -0
  141. package/dist/esm/types/enums.js +2 -0
  142. package/dist/esm/types/index.d.ts +2 -2
  143. package/dist/esm/types/interfaces.d.ts +12 -0
  144. package/ios/NetworkInspectorModule.h +1 -0
  145. package/ios/NetworkInspectorModule.mm +736 -7
  146. package/package.json +10 -2
  147. package/react-native-inapp-inspector.podspec +1 -0
  148. package/src/capture.ts +101 -0
  149. package/src/components/AnalyticsEventCard.tsx +15 -13
  150. package/src/components/AnimatedEntrance.tsx +3 -47
  151. package/src/components/DomainHeader.tsx +4 -17
  152. package/src/components/EmptyState.tsx +2 -2
  153. package/src/components/HeadersSection.tsx +31 -56
  154. package/src/components/Inspector/AnalyticsTab.tsx +2 -25
  155. package/src/components/Inspector/ConsoleTab.tsx +2 -24
  156. package/src/components/Inspector/CrashTab.tsx +4 -25
  157. package/src/components/Inspector/DebuggingTab.tsx +2 -2
  158. package/src/components/Inspector/FeedbackModal.tsx +626 -0
  159. package/src/components/Inspector/InspectorHeader.tsx +1230 -728
  160. package/src/components/Inspector/MainScreen.tsx +97 -82
  161. package/src/components/Inspector/MediaGalleryTab.tsx +355 -0
  162. package/src/components/Inspector/MediaPreviewModal.tsx +435 -0
  163. package/src/components/Inspector/NetworkTab.tsx +83 -93
  164. package/src/components/Inspector/NpmStarPrompt.tsx +532 -0
  165. package/src/components/Inspector/NpmUpdateToast.tsx +4 -9
  166. package/src/components/Inspector/PerformanceTab.tsx +8 -1
  167. package/src/components/Inspector/ReduxTab.tsx +5 -25
  168. package/src/components/Inspector/SettingsPanel.tsx +1493 -225
  169. package/src/components/Inspector/SkeletonPlaceholder.tsx +2 -2
  170. package/src/components/Inspector/StorageTab.tsx +3 -26
  171. package/src/components/Inspector/TabBar.tsx +63 -32
  172. package/src/components/Inspector/UpdateAvailableModal.tsx +6 -10
  173. package/src/components/JsonViewer.tsx +101 -60
  174. package/src/components/LogCard.tsx +10 -8
  175. package/src/components/MetaAccordion.tsx +6 -6
  176. package/src/components/NetworkIcons.tsx +242 -0
  177. package/src/components/SectionHeader.tsx +4 -16
  178. package/src/components/SkeletonPlaceholder.tsx +2 -2
  179. package/src/components/SourcePageCard.tsx +57 -55
  180. package/src/components/Toast.tsx +4 -4
  181. package/src/components/TouchableScale.tsx +8 -38
  182. package/src/components/TreeNode.tsx +5 -20
  183. package/src/constants/telemetryConfig.ts +3 -5
  184. package/src/constants/version.ts +1 -1
  185. package/src/customHooks/useAccordion.tsx +10 -43
  186. package/src/helpers/index.ts +11 -1
  187. package/src/helpers/remoteConfig.ts +212 -0
  188. package/src/helpers/searchQueryParser.ts +3 -0
  189. package/src/helpers/telemetry.ts +381 -10
  190. package/src/i18n/locales/en.json +69 -2
  191. package/src/index.tsx +93 -23
  192. package/src/native/NativeInspector.ts +186 -1
  193. package/src/native/NativeNetworkInspector.ts +9 -0
  194. package/src/styles/AppColors.ts +38 -0
  195. package/src/styles/AppFonts.ts +2 -0
  196. package/src/styles/index.ts +23 -2
  197. package/src/types/enums.ts +4 -0
  198. package/src/types/index.ts +2 -2
  199. package/src/types/interfaces.ts +12 -0
  200. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  201. package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
  202. package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
  203. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  204. package/android/.gradle/8.9/executionHistory/executionHistory.lock +0 -0
  205. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  206. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  207. package/android/.gradle/8.9/gc.properties +0 -0
  208. package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
  209. package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
  210. package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
  211. package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
  212. package/android/.gradle/9.2.0/gc.properties +0 -0
  213. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  214. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  215. package/android/.gradle/vcs-1/gc.properties +0 -0
  216. package/android/build/.transforms/6683d6eeaa3e1dfa689c3385111a0386/results.bin +0 -1
  217. package/android/build/.transforms/6683d6eeaa3e1dfa689c3385111a0386/transformed/classes/classes_dex/classes.dex +0 -0
  218. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/results.bin +0 -1
  219. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/BuildConfig.dex +0 -0
  220. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/InAppInspectorFloatingView$iconView$1.dex +0 -0
  221. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/InAppInspectorFloatingView.dex +0 -0
  222. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NativeNetworkInspectorSpec.dex +0 -0
  223. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorModule$Companion.dex +0 -0
  224. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorModule$frameCallback$1.dex +0 -0
  225. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorModule$shakeListener$1.dex +0 -0
  226. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorModule.dex +0 -0
  227. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorPackage.dex +0 -0
  228. package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  229. package/android/build/generated/source/buildConfig/debug/com/inappinspector/BuildConfig.java +0 -10
  230. package/android/build/generated/source/codegen/java/com/inappinspector/NativeNetworkInspectorSpec.java +0 -104
  231. package/android/build/generated/source/codegen/jni/CMakeLists.txt +0 -28
  232. package/android/build/generated/source/codegen/jni/RNInAppInspectorSpec-generated.cpp +0 -128
  233. package/android/build/generated/source/codegen/jni/RNInAppInspectorSpec.h +0 -31
  234. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/ComponentDescriptors.cpp +0 -22
  235. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/ComponentDescriptors.h +0 -24
  236. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/EventEmitters.cpp +0 -16
  237. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/EventEmitters.h +0 -17
  238. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/Props.cpp +0 -19
  239. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/Props.h +0 -18
  240. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/RNInAppInspectorSpecJSI.h +0 -177
  241. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/ShadowNodes.cpp +0 -17
  242. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/ShadowNodes.h +0 -23
  243. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/States.cpp +0 -16
  244. package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/States.h +0 -20
  245. package/android/build/generated/source/codegen/schema.json +0 -1
  246. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -7
  247. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
  248. package/android/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar +0 -0
  249. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
  250. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +0 -1
  251. package/android/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt +0 -0
  252. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  253. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  254. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  255. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
  256. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
  257. package/android/build/intermediates/incremental/debug-mergeJavaRes/merge-state +0 -0
  258. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
  259. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
  260. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +0 -2
  261. package/android/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/react-native-inapp-inspector_debug.kotlin_module +0 -0
  262. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/inappinspector/BuildConfig.class +0 -0
  263. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/inappinspector/NativeNetworkInspectorSpec.class +0 -0
  264. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
  265. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -7
  266. package/android/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-react-native-inapp-inspector.jar +0 -0
  267. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -7
  268. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
  269. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
  270. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/META-INF/react-native-inapp-inspector_debug.kotlin_module +0 -0
  271. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/BuildConfig.class +0 -0
  272. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/InAppInspectorFloatingView$iconView$1.class +0 -0
  273. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/InAppInspectorFloatingView.class +0 -0
  274. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NativeNetworkInspectorSpec.class +0 -0
  275. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorModule$Companion.class +0 -0
  276. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorModule$frameCallback$1.class +0 -0
  277. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorModule$shakeListener$1.class +0 -0
  278. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorModule.class +0 -0
  279. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorPackage.class +0 -0
  280. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  281. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
  282. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  283. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  284. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  285. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
  286. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  287. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  288. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
  289. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  290. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  291. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  292. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  293. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  294. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  295. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
  296. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  297. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  298. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  299. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  300. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  301. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  302. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
  303. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
  304. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
  305. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
  306. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
  307. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
  308. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
  309. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
  310. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  311. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  312. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  313. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  314. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  315. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  316. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
  317. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  318. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  319. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  320. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  321. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  322. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  323. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
  324. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  325. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  326. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  327. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
  328. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  329. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  330. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
  331. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  332. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
  333. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
  334. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
  335. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  336. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
  337. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
  338. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  339. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  340. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
  341. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
  342. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  343. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  344. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
  345. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +0 -2
  346. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  347. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  348. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  349. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
  350. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  351. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  352. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
  353. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  354. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  355. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  356. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  357. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  358. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  359. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
  360. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  361. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  362. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  363. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  364. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  365. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  366. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
  367. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  368. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  369. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  370. package/android/build/outputs/aar/react-native-inapp-inspector-debug.aar +0 -0
  371. package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -17
  372. package/android/build/reports/problems/problems-report.html +0 -659
  373. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  374. package/android/build/tmp/kotlin-classes/debug/META-INF/react-native-inapp-inspector_debug.kotlin_module +0 -0
  375. package/android/build/tmp/kotlin-classes/debug/com/inappinspector/InAppInspectorFloatingView$iconView$1.class +0 -0
  376. package/android/build/tmp/kotlin-classes/debug/com/inappinspector/InAppInspectorFloatingView.class +0 -0
  377. package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorModule$Companion.class +0 -0
  378. package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorModule$frameCallback$1.class +0 -0
  379. package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorModule$shakeListener$1.class +0 -0
  380. package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorModule.class +0 -0
  381. package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorPackage.class +0 -0
@@ -1,12 +1,18 @@
1
1
  #import "NetworkInspectorModule.h"
2
2
  #import <execinfo.h>
3
3
  #import <signal.h>
4
+ #import <objc/runtime.h>
4
5
  #import <unistd.h>
6
+ #import <ReplayKit/ReplayKit.h>
7
+ #import <AVFoundation/AVFoundation.h>
8
+ #import <ImageIO/ImageIO.h>
9
+ #import <MobileCoreServices/MobileCoreServices.h>
5
10
 
6
11
  static NetworkInspectorModule *sharedInstance = nil;
7
12
  static NSUncaughtExceptionHandler *previousUncaughtExceptionHandler = NULL;
8
13
  static BOOL g_floatingButtonPressed = NO;
9
14
 
15
+
10
16
  @interface InAppInspectorFloatingView : UIView
11
17
  @property (nonatomic, copy) void (^onTapBlock)(void);
12
18
  @property (nonatomic, strong) UIView *badgeDot;
@@ -311,6 +317,7 @@ static BOOL g_floatingButtonPressed = NO;
311
317
  @end
312
318
 
313
319
  static InAppInspectorFloatingView *floatingButtonView = nil;
320
+ static BOOL g_floatingButtonDesiredVisible = NO;
314
321
 
315
322
  static void NativeSignalHandler(int signalNumber) {
316
323
  void* callstack[128];
@@ -352,16 +359,126 @@ static void NativeExceptionHandler(NSException *exception) {
352
359
  [sharedInstance emitCrashEventWithMessage:message stackTrace:stackTrace];
353
360
  }
354
361
 
355
- // Keep the runloop cycling so the UI thread doesn't terminate immediately
362
+ // Give the crash event a brief window to emit (up to 3 seconds),
363
+ // then delegate to the previous handler instead of freezing the UI forever.
364
+ // The old infinite-runloop approach permanently froze the main thread on any
365
+ // exception (including Fabric view recycling assertions), causing blank screens.
356
366
  CFRunLoopRef runLoop = CFRunLoopGetCurrent();
357
367
  CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
358
- while (true) {
368
+ NSTimeInterval deadline = [NSDate timeIntervalSinceReferenceDate] + 3.0;
369
+ while ([NSDate timeIntervalSinceReferenceDate] < deadline) {
359
370
  for (NSString *mode in (__bridge NSArray *)allModes) {
360
371
  CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
361
372
  }
362
373
  }
374
+ if (allModes) {
375
+ CFRelease(allModes);
376
+ }
377
+
378
+ // Delegate to the previous exception handler (e.g. React Native's own handler)
379
+ if (previousUncaughtExceptionHandler) {
380
+ previousUncaughtExceptionHandler(exception);
381
+ }
382
+ }
383
+
384
+ // ─── Fabric View Recycle Safeguard ───────────────────────────────────────────
385
+ // Custom NSAssertionHandler that catches the specific Fabric view recycling
386
+ // assertion ('Attempt to recycle a mounted view') and logs it as a warning
387
+ // instead of crashing. This prevents third-party native views (like
388
+ // BVLinearGradient from react-native-linear-gradient) from causing fatal
389
+ // assertion failures when Fabric's view recycler encounters views whose
390
+ // superview is still attached during conditional unmounting.
391
+
392
+ @interface InAppInspectorAssertionHandler : NSAssertionHandler
393
+ @property (nonatomic, strong) NSAssertionHandler *previousHandler;
394
+ @end
395
+
396
+ @implementation InAppInspectorAssertionHandler
397
+
398
+ + (void)install {
399
+ NSThread *mainThread = [NSThread mainThread];
400
+ NSMutableDictionary *threadDict = [mainThread threadDictionary];
401
+ NSAssertionHandler *current = threadDict[NSAssertionHandlerKey];
402
+
403
+ InAppInspectorAssertionHandler *handler = [[InAppInspectorAssertionHandler alloc] init];
404
+ handler.previousHandler = current;
405
+ threadDict[NSAssertionHandlerKey] = handler;
406
+ NSLog(@"[InAppInspector] Fabric view recycle safeguard (assertion handler) installed");
407
+ }
408
+
409
+ - (void)handleFailureInMethod:(SEL)selector
410
+ object:(id)object
411
+ file:(NSString *)fileName
412
+ lineNumber:(NSInteger)line
413
+ description:(NSString *)format, ... {
414
+ va_list args;
415
+ va_start(args, format);
416
+ NSString *desc = [[NSString alloc] initWithFormat:format arguments:args];
417
+ va_end(args);
418
+
419
+ // Intercept the specific Fabric recycle assertion
420
+ if ([desc containsString:@"Attempt to recycle a mounted view"]) {
421
+ NSLog(@"[InAppInspector] ⚠️ Suppressed Fabric view recycle assertion: %@ (in %@:%ld)",
422
+ desc, fileName, (long)line);
423
+ return; // Suppress — don't crash
424
+ }
425
+
426
+ // Forward all other assertions to the previous handler
427
+ if (self.previousHandler) {
428
+ va_start(args, format);
429
+ [self.previousHandler handleFailureInMethod:selector
430
+ object:object
431
+ file:fileName
432
+ lineNumber:line
433
+ description:@"%@", desc];
434
+ va_end(args);
435
+ } else {
436
+ // No previous handler — raise as exception (default behavior)
437
+ NSString *reason = [NSString stringWithFormat:
438
+ @"*** Assertion failure in %@, %@:%ld: %@",
439
+ NSStringFromSelector(selector), fileName, (long)line, desc];
440
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
441
+ reason:reason
442
+ userInfo:nil];
443
+ }
363
444
  }
364
445
 
446
+ - (void)handleFailureInFunction:(NSString *)functionName
447
+ file:(NSString *)fileName
448
+ lineNumber:(NSInteger)line
449
+ description:(NSString *)format, ... {
450
+ va_list args;
451
+ va_start(args, format);
452
+ NSString *desc = [[NSString alloc] initWithFormat:format arguments:args];
453
+ va_end(args);
454
+
455
+ // Intercept the specific Fabric recycle assertion
456
+ if ([desc containsString:@"Attempt to recycle a mounted view"]) {
457
+ NSLog(@"[InAppInspector] ⚠️ Suppressed Fabric view recycle assertion: %@ (in %@:%ld)",
458
+ desc, fileName, (long)line);
459
+ return; // Suppress — don't crash
460
+ }
461
+
462
+ // Forward all other assertions to the previous handler
463
+ if (self.previousHandler) {
464
+ va_start(args, format);
465
+ [self.previousHandler handleFailureInFunction:functionName
466
+ file:fileName
467
+ lineNumber:line
468
+ description:@"%@", desc];
469
+ va_end(args);
470
+ } else {
471
+ NSString *reason = [NSString stringWithFormat:
472
+ @"*** Assertion failure in %@, %@:%ld: %@",
473
+ functionName, fileName, (long)line, desc];
474
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException
475
+ reason:reason
476
+ userInfo:nil];
477
+ }
478
+ }
479
+
480
+ @end
481
+
365
482
  @implementation NetworkInspectorModule {
366
483
  bool hasListeners;
367
484
  CADisplayLink *displayLink;
@@ -378,6 +495,14 @@ static void NativeExceptionHandler(NSException *exception) {
378
495
  NSMutableArray *_nativeConsoleLogs;
379
496
  NSMutableArray *_nativeAnalyticsEvents;
380
497
  NSMutableArray *_nativeCrashRecords;
498
+ AVAssetWriter *_softwareAssetWriter;
499
+ AVAssetWriterInput *_softwareWriterInput;
500
+ AVAssetWriterInputPixelBufferAdaptor *_softwarePixelBufferAdaptor;
501
+ dispatch_source_t _softwareRecordingTimerSource;
502
+ BOOL _isSoftwareRecordingActive;
503
+ NSInteger _softwareFrameIndex;
504
+ NSTimeInterval _softwareRecordingStartTime;
505
+ NSString *_softwareRecordingFilePath;
381
506
  }
382
507
 
383
508
  RCT_EXPORT_MODULE(NetworkInspectorModule);
@@ -451,6 +576,18 @@ RCT_EXPORT_MODULE(NetworkInspectorModule);
451
576
  sigaction(SIGFPE, &sa, NULL);
452
577
  sigaction(SIGPIPE, &sa, NULL);
453
578
  sigaction(SIGTRAP, &sa, NULL);
579
+
580
+ // Fabric view recycle safeguard: Install a custom NSAssertionHandler on the
581
+ // main thread that catches the specific 'Attempt to recycle a mounted view'
582
+ // assertion from RCTComponentViewRegistry, instead of crashing the app.
583
+ // This is a defense-in-depth approach — the primary fix is on the JS side
584
+ // (using LinearGradient as absolute background, not as a container parent).
585
+ static dispatch_once_t onceToken;
586
+ dispatch_once(&onceToken, ^{
587
+ dispatch_async(dispatch_get_main_queue(), ^{
588
+ [InAppInspectorAssertionHandler install];
589
+ });
590
+ });
454
591
  }
455
592
 
456
593
  - (void)emitCrashEventWithMessage:(NSString *)message stackTrace:(NSString *)stackTrace {
@@ -568,6 +705,7 @@ RCT_EXPORT_METHOD(showFloatingButton:(NSDictionary *)options
568
705
  resolve:(RCTPromiseResolveBlock)resolve
569
706
  reject:(RCTPromiseRejectBlock)reject) {
570
707
  dispatch_async(dispatch_get_main_queue(), ^{
708
+ g_floatingButtonDesiredVisible = YES;
571
709
  UIWindow *targetWindow = GetAppActiveWindow();
572
710
  CGRect screenBounds = targetWindow ? targetWindow.bounds : [UIScreen mainScreen].bounds;
573
711
 
@@ -601,6 +739,11 @@ RCT_EXPORT_METHOD(showFloatingButton:(NSDictionary *)options
601
739
  __weak NetworkInspectorModule *weakSelf = self;
602
740
  floatingButtonView.onTapBlock = ^{
603
741
  g_floatingButtonPressed = YES;
742
+ g_floatingButtonDesiredVisible = NO;
743
+ if (floatingButtonView != nil) {
744
+ floatingButtonView.hidden = YES;
745
+ floatingButtonView.alpha = 0.0;
746
+ }
604
747
  NetworkInspectorModule *strongSelf = weakSelf ?: sharedInstance;
605
748
  if (strongSelf && strongSelf.bridge != nil) {
606
749
  [strongSelf safeSendEvent:@"onFloatingButtonPress" body:@{}];
@@ -608,6 +751,7 @@ RCT_EXPORT_METHOD(showFloatingButton:(NSDictionary *)options
608
751
  };
609
752
 
610
753
  void (^attachToWindow)(void) = ^{
754
+ if (!g_floatingButtonDesiredVisible) return;
611
755
  UIWindow *activeWin = GetAppActiveWindow();
612
756
  if (!activeWin || !floatingButtonView) return;
613
757
 
@@ -644,12 +788,10 @@ RCT_EXPORT_METHOD(showFloatingButton:(NSDictionary *)options
644
788
  RCT_EXPORT_METHOD(hideFloatingButton:(RCTPromiseResolveBlock)resolve
645
789
  reject:(RCTPromiseRejectBlock)reject) {
646
790
  dispatch_async(dispatch_get_main_queue(), ^{
791
+ g_floatingButtonDesiredVisible = NO;
647
792
  if (floatingButtonView != nil) {
648
- [UIView animateWithDuration:0.15 animations:^{
649
- floatingButtonView.alpha = 0.0;
650
- } completion:^(BOOL finished) {
651
- floatingButtonView.hidden = YES;
652
- }];
793
+ floatingButtonView.hidden = YES;
794
+ floatingButtonView.alpha = 0.0;
653
795
  }
654
796
  resolve(@(YES));
655
797
  });
@@ -941,6 +1083,592 @@ RCT_EXPORT_METHOD(getNativeCachedPage:(NSString *)pageKey
941
1083
  });
942
1084
  }
943
1085
 
1086
+ #pragma mark - Screen Capture & Video Recording
1087
+
1088
+ - (NSString *)getCapturesDirectory {
1089
+ NSString *tempDir = NSTemporaryDirectory();
1090
+ NSString *capturesDir = [tempDir stringByAppendingPathComponent:@"inspector_captures"];
1091
+ BOOL isDir = NO;
1092
+ if (![[NSFileManager defaultManager] fileExistsAtPath:capturesDir isDirectory:&isDir]) {
1093
+ [[NSFileManager defaultManager] createDirectoryAtPath:capturesDir withIntermediateDirectories:YES attributes:nil error:nil];
1094
+ }
1095
+ return capturesDir;
1096
+ }
1097
+
1098
+ - (UIWindow *)findActiveKeyWindow {
1099
+ UIWindow *foundWindow = nil;
1100
+ if (@available(iOS 13.0, *)) {
1101
+ for (UIScene *scene in [UIApplication sharedApplication].connectedScenes) {
1102
+ if ([scene isKindOfClass:[UIWindowScene class]]) {
1103
+ UIWindowScene *windowScene = (UIWindowScene *)scene;
1104
+ for (UIWindow *w in windowScene.windows) {
1105
+ if (w.isKeyWindow) {
1106
+ return w;
1107
+ }
1108
+ if (!foundWindow && !w.hidden && w.alpha > 0.01) {
1109
+ foundWindow = w;
1110
+ }
1111
+ }
1112
+ }
1113
+ }
1114
+ }
1115
+ if (!foundWindow) {
1116
+ #pragma clang diagnostic push
1117
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
1118
+ foundWindow = [UIApplication sharedApplication].keyWindow;
1119
+ if (!foundWindow && [UIApplication sharedApplication].windows.count > 0) {
1120
+ foundWindow = [UIApplication sharedApplication].windows.firstObject;
1121
+ }
1122
+ #pragma clang diagnostic pop
1123
+ }
1124
+ return foundWindow;
1125
+ }
1126
+
1127
+ - (UIImage *)captureWindowImage:(UIWindow *)window scale:(CGFloat)scale {
1128
+ if (!window) return nil;
1129
+ CGRect bounds = window.bounds;
1130
+ if (bounds.size.width <= 0 || bounds.size.height <= 0) {
1131
+ bounds = [UIScreen mainScreen].bounds;
1132
+ }
1133
+ CGFloat screenScale = [UIScreen mainScreen].scale;
1134
+ CGFloat finalScale = screenScale * scale;
1135
+ if (finalScale <= 0.0) finalScale = screenScale;
1136
+
1137
+ UIGraphicsBeginImageContextWithOptions(bounds.size, NO, finalScale);
1138
+ CGContextRef context = UIGraphicsGetCurrentContext();
1139
+
1140
+ BOOL drawn = NO;
1141
+ @try {
1142
+ drawn = [window drawViewHierarchyInRect:bounds afterScreenUpdates:NO];
1143
+ } @catch (NSException *e) {
1144
+ drawn = NO;
1145
+ }
1146
+
1147
+ if (!drawn && context) {
1148
+ @try {
1149
+ [window.layer renderInContext:context];
1150
+ } @catch (NSException *e) {}
1151
+ }
1152
+ UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
1153
+ UIGraphicsEndImageContext();
1154
+
1155
+ if (!image && window.rootViewController && window.rootViewController.view) {
1156
+ UIGraphicsBeginImageContextWithOptions(bounds.size, NO, finalScale);
1157
+ CGContextRef ctx = UIGraphicsGetCurrentContext();
1158
+ if (ctx) {
1159
+ @try {
1160
+ [window.rootViewController.view.layer renderInContext:ctx];
1161
+ } @catch (NSException *e) {}
1162
+ }
1163
+ image = UIGraphicsGetImageFromCurrentImageContext();
1164
+ UIGraphicsEndImageContext();
1165
+ }
1166
+ return image;
1167
+ }
1168
+
1169
+ - (CVPixelBufferRef)createPixelBufferFromCGImage:(CGImageRef)image size:(CGSize)size {
1170
+ NSDictionary *options = @{
1171
+ (id)kCVPixelBufferCGImageCompatibilityKey: @(YES),
1172
+ (id)kCVPixelBufferCGBitmapContextCompatibilityKey: @(YES)
1173
+ };
1174
+ CVPixelBufferRef pxbuffer = NULL;
1175
+ CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, (size_t)size.width, (size_t)size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef)options, &pxbuffer);
1176
+ if (status != kCVReturnSuccess || pxbuffer == NULL) return NULL;
1177
+
1178
+ CVPixelBufferLockBaseAddress(pxbuffer, 0);
1179
+ void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
1180
+ CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
1181
+ CGContextRef context = CGBitmapContextCreate(pxdata, (size_t)size.width, (size_t)size.height, 8, 4 * (size_t)size.width, rgbColorSpace, (CGBitmapInfo)kCGImageAlphaNoneSkipFirst);
1182
+ if (context) {
1183
+ CGContextDrawImage(context, CGRectMake(0, 0, size.width, size.height), image);
1184
+ CGContextRelease(context);
1185
+ }
1186
+ CGColorSpaceRelease(rgbColorSpace);
1187
+ CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
1188
+ return pxbuffer;
1189
+ }
1190
+
1191
+ - (void)startSoftwareRecordingWithOptions:(NSDictionary *)options
1192
+ resolve:(RCTPromiseResolveBlock)resolve
1193
+ reject:(RCTPromiseRejectBlock)reject {
1194
+ dispatch_async(dispatch_get_main_queue(), ^{
1195
+ UIWindow *window = [self findActiveKeyWindow];
1196
+ if (!window) {
1197
+ reject(@"RECORDER_ERROR", @"Unable to find active UIWindow for screen recording", nil);
1198
+ return;
1199
+ }
1200
+
1201
+ CGRect bounds = window.bounds;
1202
+ CGFloat screenScale = [UIScreen mainScreen].scale;
1203
+ NSInteger width = ((NSInteger)(bounds.size.width * screenScale * 0.5) / 2) * 2;
1204
+ NSInteger height = ((NSInteger)(bounds.size.height * screenScale * 0.5) / 2) * 2;
1205
+ if (width <= 0) width = 360;
1206
+ if (height <= 0) height = 640;
1207
+
1208
+ CGSize videoSize = CGSizeMake(width, height);
1209
+ long long timestamp = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
1210
+ NSString *filename = [NSString stringWithFormat:@"video_%lld.mp4", timestamp];
1211
+ NSString *filePath = [[self getCapturesDirectory] stringByAppendingPathComponent:filename];
1212
+ NSURL *outputUrl = [NSURL fileURLWithPath:filePath];
1213
+
1214
+ NSError *error = nil;
1215
+ self->_softwareAssetWriter = [AVAssetWriter assetWriterWithURL:outputUrl fileType:AVFileTypeMPEG4 error:&error];
1216
+ if (error || !self->_softwareAssetWriter) {
1217
+ reject(@"RECORDER_ERROR", error.localizedDescription ?: @"Failed to initialize AVAssetWriter", error);
1218
+ return;
1219
+ }
1220
+
1221
+ NSDictionary *videoSettings = @{
1222
+ AVVideoCodecKey: AVVideoCodecTypeH264,
1223
+ AVVideoWidthKey: @(videoSize.width),
1224
+ AVVideoHeightKey: @(videoSize.height),
1225
+ };
1226
+
1227
+ self->_softwareWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
1228
+ self->_softwareWriterInput.expectsMediaDataInRealTime = YES;
1229
+
1230
+ NSDictionary *sourcePixelBufferAttributes = @{
1231
+ (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32ARGB),
1232
+ (id)kCVPixelBufferWidthKey: @(videoSize.width),
1233
+ (id)kCVPixelBufferHeightKey: @(videoSize.height)
1234
+ };
1235
+
1236
+ self->_softwarePixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self->_softwareWriterInput sourcePixelBufferAttributes:sourcePixelBufferAttributes];
1237
+
1238
+ if ([self->_softwareAssetWriter canAddInput:self->_softwareWriterInput]) {
1239
+ [self->_softwareAssetWriter addInput:self->_softwareWriterInput];
1240
+ }
1241
+
1242
+ [self->_softwareAssetWriter startWriting];
1243
+ [self->_softwareAssetWriter startSessionAtSourceTime:kCMTimeZero];
1244
+
1245
+ self->_isSoftwareRecordingActive = YES;
1246
+ self->_softwareFrameIndex = 0;
1247
+ self->_softwareRecordingStartTime = [[NSDate date] timeIntervalSince1970];
1248
+ self->_softwareRecordingFilePath = filePath;
1249
+
1250
+ double fps = [options[@"fps"] doubleValue];
1251
+ if (fps <= 0.0 || fps > 30.0) fps = 15.0;
1252
+ double frameInterval = 1.0 / fps;
1253
+
1254
+ dispatch_queue_t queue = dispatch_queue_create("com.inappinspector.recording", DISPATCH_QUEUE_SERIAL);
1255
+ self->_softwareRecordingTimerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
1256
+ dispatch_source_set_timer(self->_softwareRecordingTimerSource, dispatch_time(DISPATCH_TIME_NOW, 0), (uint64_t)(frameInterval * NSEC_PER_SEC), (uint64_t)(frameInterval * 0.1 * NSEC_PER_SEC));
1257
+
1258
+ __weak NetworkInspectorModule *weakSelf = self;
1259
+ dispatch_source_set_event_handler(self->_softwareRecordingTimerSource, ^{
1260
+ NetworkInspectorModule *strongSelf = weakSelf;
1261
+ if (!strongSelf || !strongSelf->_isSoftwareRecordingActive) return;
1262
+
1263
+ dispatch_sync(dispatch_get_main_queue(), ^{
1264
+ if (!strongSelf || !strongSelf->_isSoftwareRecordingActive) return;
1265
+ UIWindow *w = [strongSelf findActiveKeyWindow];
1266
+ UIImage *frameImg = [strongSelf captureWindowImage:w scale:0.5];
1267
+ if (frameImg && frameImg.CGImage && strongSelf->_softwareWriterInput.isReadyForMoreMediaData) {
1268
+ CVPixelBufferRef buffer = [strongSelf createPixelBufferFromCGImage:frameImg.CGImage size:videoSize];
1269
+ if (buffer) {
1270
+ CMTime presentTime = CMTimeMake((int64_t)(strongSelf->_softwareFrameIndex * 1000 / fps), 1000);
1271
+ [strongSelf->_softwarePixelBufferAdaptor appendPixelBuffer:buffer withPresentationTime:presentTime];
1272
+ CVPixelBufferRelease(buffer);
1273
+ strongSelf->_softwareFrameIndex++;
1274
+ }
1275
+ }
1276
+ });
1277
+ });
1278
+
1279
+ dispatch_resume(self->_softwareRecordingTimerSource);
1280
+ resolve(@(YES));
1281
+ });
1282
+ }
1283
+
1284
+ RCT_EXPORT_METHOD(takeScreenshot:(NSDictionary *)options
1285
+ resolve:(RCTPromiseResolveBlock)resolve
1286
+ reject:(RCTPromiseRejectBlock)reject) {
1287
+ dispatch_async(dispatch_get_main_queue(), ^{
1288
+ @try {
1289
+ UIWindow *keyWindow = [self findActiveKeyWindow];
1290
+ if (!keyWindow) {
1291
+ reject(@"SCREENSHOT_ERROR", @"Unable to find active UIWindow for screen capture", nil);
1292
+ return;
1293
+ }
1294
+
1295
+ double scaleParam = [options[@"scale"] doubleValue];
1296
+ if (scaleParam <= 0.0 || scaleParam > 1.0) {
1297
+ scaleParam = 1.0;
1298
+ }
1299
+
1300
+ UIImage *image = [self captureWindowImage:keyWindow scale:(CGFloat)scaleParam];
1301
+ if (!image) {
1302
+ reject(@"SCREENSHOT_ERROR", @"Failed to render window hierarchy to image context", nil);
1303
+ return;
1304
+ }
1305
+
1306
+ NSString *format = [options[@"format"] isKindOfClass:[NSString class]] ? [options[@"format"] lowercaseString] : @"png";
1307
+ double quality = [options[@"quality"] doubleValue];
1308
+ if (quality <= 0.0 || quality > 1.0) {
1309
+ quality = 0.9;
1310
+ }
1311
+
1312
+ NSData *data = nil;
1313
+ NSString *ext = @"png";
1314
+ if ([format isEqualToString:@"jpeg"] || [format isEqualToString:@"jpg"]) {
1315
+ data = UIImageJPEGRepresentation(image, quality);
1316
+ ext = @"jpg";
1317
+ format = @"jpeg";
1318
+ } else if ([format isEqualToString:@"webp"]) {
1319
+ data = UIImageJPEGRepresentation(image, quality);
1320
+ ext = @"webp";
1321
+ format = @"webp";
1322
+ } else {
1323
+ data = UIImagePNGRepresentation(image);
1324
+ ext = @"png";
1325
+ format = @"png";
1326
+ }
1327
+
1328
+ if (!data) {
1329
+ reject(@"SCREENSHOT_ERROR", @"Failed to encode image data", nil);
1330
+ return;
1331
+ }
1332
+
1333
+ long long timestamp = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
1334
+ NSString *filename = [NSString stringWithFormat:@"screenshot_%lld.%@", timestamp, ext];
1335
+ NSString *filePath = [[self getCapturesDirectory] stringByAppendingPathComponent:filename];
1336
+
1337
+ [data writeToFile:filePath atomically:YES];
1338
+
1339
+ BOOL includeBase64 = [options[@"includeBase64"] boolValue];
1340
+ NSString *base64Str = includeBase64 ? [data base64EncodedStringWithOptions:0] : @"";
1341
+
1342
+ NSDictionary *result = @{
1343
+ @"uri": [NSURL fileURLWithPath:filePath].absoluteString,
1344
+ @"format": format,
1345
+ @"width": @((NSInteger)(image.size.width * image.scale)),
1346
+ @"height": @((NSInteger)(image.size.height * image.scale)),
1347
+ @"sizeBytes": @(data.length),
1348
+ @"timestamp": @(timestamp),
1349
+ @"base64": base64Str
1350
+ };
1351
+ resolve(result);
1352
+ } @catch (NSException *exception) {
1353
+ reject(@"SCREENSHOT_ERROR", exception.reason, nil);
1354
+ }
1355
+ });
1356
+ }
1357
+
1358
+ RCT_EXPORT_METHOD(startVideoRecording:(NSDictionary *)options
1359
+ resolve:(RCTPromiseResolveBlock)resolve
1360
+ reject:(RCTPromiseRejectBlock)reject) {
1361
+ if (self->_isSoftwareRecordingActive || [RPScreenRecorder sharedRecorder].isRecording) {
1362
+ resolve(@(YES));
1363
+ return;
1364
+ }
1365
+
1366
+ RPScreenRecorder *recorder = [RPScreenRecorder sharedRecorder];
1367
+ if (recorder.isAvailable) {
1368
+ NSString *audioSource = [options[@"audioSource"] isKindOfClass:[NSString class]] ? options[@"audioSource"] : @"none";
1369
+ BOOL enableMic = [audioSource isEqualToString:@"mic"] || [audioSource isEqualToString:@"mixed"];
1370
+ recorder.microphoneEnabled = enableMic;
1371
+
1372
+ [recorder startRecordingWithHandler:^(NSError * _Nullable error) {
1373
+ if (error) {
1374
+ // If ReplayKit fails to initialize (e.g. simulator or sandbox), fallback to software frame recorder
1375
+ [self startSoftwareRecordingWithOptions:options resolve:resolve reject:reject];
1376
+ } else {
1377
+ resolve(@(YES));
1378
+ }
1379
+ }];
1380
+ } else {
1381
+ // Fallback to software frame recorder (works everywhere including iOS Simulators)
1382
+ [self startSoftwareRecordingWithOptions:options resolve:resolve reject:reject];
1383
+ }
1384
+ }
1385
+
1386
+ RCT_EXPORT_METHOD(stopVideoRecording:(RCTPromiseResolveBlock)resolve
1387
+ reject:(RCTPromiseRejectBlock)reject) {
1388
+ if (self->_isSoftwareRecordingActive) {
1389
+ self->_isSoftwareRecordingActive = NO;
1390
+ if (self->_softwareRecordingTimerSource) {
1391
+ dispatch_source_cancel(self->_softwareRecordingTimerSource);
1392
+ self->_softwareRecordingTimerSource = nil;
1393
+ }
1394
+
1395
+ [self->_softwareWriterInput markAsFinished];
1396
+ NSString *filePath = self->_softwareRecordingFilePath;
1397
+ NSTimeInterval duration = [[NSDate date] timeIntervalSince1970] - self->_softwareRecordingStartTime;
1398
+ long long timestamp = (long long)(self->_softwareRecordingStartTime * 1000.0);
1399
+
1400
+ [self->_softwareAssetWriter finishWritingWithCompletionHandler:^{
1401
+ self->_softwareAssetWriter = nil;
1402
+ self->_softwareWriterInput = nil;
1403
+ self->_softwarePixelBufferAdaptor = nil;
1404
+
1405
+ unsigned long long fileSize = 0;
1406
+ NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1407
+ if (attrs) {
1408
+ fileSize = [attrs fileSize];
1409
+ }
1410
+
1411
+ NSDictionary *result = @{
1412
+ @"uri": [NSURL fileURLWithPath:filePath].absoluteString,
1413
+ @"format": @"mp4",
1414
+ @"durationMs": @(MAX(500.0, duration * 1000.0)),
1415
+ @"hasAudio": @(NO),
1416
+ @"width": @(720),
1417
+ @"height": @(1280),
1418
+ @"sizeBytes": @(fileSize),
1419
+ @"timestamp": @(timestamp)
1420
+ };
1421
+ resolve(result);
1422
+ }];
1423
+ return;
1424
+ }
1425
+
1426
+ RPScreenRecorder *recorder = [RPScreenRecorder sharedRecorder];
1427
+ if (!recorder.isRecording) {
1428
+ resolve([NSNull null]);
1429
+ return;
1430
+ }
1431
+
1432
+ long long timestamp = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
1433
+ NSString *filename = [NSString stringWithFormat:@"video_%lld.mp4", timestamp];
1434
+ NSString *filePath = [[self getCapturesDirectory] stringByAppendingPathComponent:filename];
1435
+ NSURL *outputUrl = [NSURL fileURLWithPath:filePath];
1436
+
1437
+ [recorder stopRecordingWithOutputURL:outputUrl completionHandler:^(NSError * _Nullable error) {
1438
+ if (error) {
1439
+ reject(@"RECORD_STOP_ERROR", error.localizedDescription, error);
1440
+ return;
1441
+ }
1442
+
1443
+ AVURLAsset *asset = [AVURLAsset URLAssetWithURL:outputUrl options:nil];
1444
+ CMTime duration = asset.duration;
1445
+ double durationMs = CMTimeGetSeconds(duration) * 1000.0;
1446
+ CGSize naturalSize = CGSizeMake(1080, 1920);
1447
+ NSArray<AVAssetTrack *> *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
1448
+ if (videoTracks.count > 0) {
1449
+ naturalSize = videoTracks.firstObject.naturalSize;
1450
+ }
1451
+
1452
+ unsigned long long fileSize = 0;
1453
+ NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1454
+ if (attrs) {
1455
+ fileSize = [attrs fileSize];
1456
+ }
1457
+
1458
+ NSDictionary *result = @{
1459
+ @"uri": outputUrl.absoluteString,
1460
+ @"format": @"mp4",
1461
+ @"durationMs": @(durationMs),
1462
+ @"hasAudio": @(recorder.isMicrophoneEnabled),
1463
+ @"width": @((NSInteger)naturalSize.width),
1464
+ @"height": @((NSInteger)naturalSize.height),
1465
+ @"sizeBytes": @(fileSize),
1466
+ @"timestamp": @(timestamp)
1467
+ };
1468
+ resolve(result);
1469
+ }];
1470
+ }
1471
+
1472
+ RCT_EXPORT_METHOD(isRecording:(RCTPromiseResolveBlock)resolve
1473
+ reject:(RCTPromiseRejectBlock)reject) {
1474
+ BOOL active = self->_isSoftwareRecordingActive || [RPScreenRecorder sharedRecorder].isRecording;
1475
+ resolve(@(active));
1476
+ }
1477
+
1478
+ RCT_EXPORT_METHOD(convertToGif:(NSString *)videoUri
1479
+ options:(NSDictionary *)options
1480
+ resolve:(RCTPromiseResolveBlock)resolve
1481
+ reject:(RCTPromiseRejectBlock)reject) {
1482
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
1483
+ @try {
1484
+ NSURL *videoUrl = [NSURL URLWithString:videoUri];
1485
+ if (!videoUrl || !videoUrl.scheme) {
1486
+ videoUrl = [NSURL fileURLWithPath:videoUri];
1487
+ }
1488
+
1489
+ AVURLAsset *asset = [AVURLAsset URLAssetWithURL:videoUrl options:nil];
1490
+ NSError *error = nil;
1491
+ AVAssetReader *reader = [AVAssetReader assetReaderWithAsset:asset error:&error];
1492
+ if (error || !reader) {
1493
+ reject(@"GIF_ERROR", @"Unable to initialize asset reader for video", error);
1494
+ return;
1495
+ }
1496
+
1497
+ NSArray<AVAssetTrack *> *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
1498
+ if (tracks.count == 0) {
1499
+ reject(@"GIF_ERROR", @"No video track found in media asset", nil);
1500
+ return;
1501
+ }
1502
+ AVAssetTrack *videoTrack = tracks.firstObject;
1503
+
1504
+ NSDictionary *outputSettings = @{
1505
+ (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)
1506
+ };
1507
+ AVAssetReaderTrackOutput *readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoTrack outputSettings:outputSettings];
1508
+ [reader addOutput:readerOutput];
1509
+ [reader startReading];
1510
+
1511
+ long long timestamp = (long long)([[NSDate date] timeIntervalSince1970] * 1000.0);
1512
+ NSString *filename = [NSString stringWithFormat:@"anim_%lld.gif", timestamp];
1513
+ NSString *filePath = [[self getCapturesDirectory] stringByAppendingPathComponent:filename];
1514
+ NSURL *gifUrl = [NSURL fileURLWithPath:filePath];
1515
+
1516
+ CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)gifUrl, kUTTypeGIF, 0, NULL);
1517
+ if (!destination) {
1518
+ reject(@"GIF_ERROR", @"Failed to create GIF image destination", nil);
1519
+ return;
1520
+ }
1521
+
1522
+ NSDictionary *gifProperties = @{
1523
+ (id)kCGImagePropertyGIFDictionary: @{
1524
+ (id)kCGImagePropertyGIFLoopCount: @(0)
1525
+ }
1526
+ };
1527
+ CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)gifProperties);
1528
+
1529
+ double targetFps = [options[@"fps"] doubleValue];
1530
+ if (targetFps <= 0.0 || targetFps > 30.0) targetFps = 12.0;
1531
+ double frameDelay = 1.0 / targetFps;
1532
+
1533
+ NSDictionary *frameProperties = @{
1534
+ (id)kCGImagePropertyGIFDictionary: @{
1535
+ (id)kCGImagePropertyGIFDelayTime: @(frameDelay)
1536
+ }
1537
+ };
1538
+
1539
+ NSInteger frameCount = 0;
1540
+ NSInteger skipInterval = (NSInteger)MAX(1, (videoTrack.nominalFrameRate / targetFps));
1541
+ NSInteger readIndex = 0;
1542
+ CGSize outputSize = videoTrack.naturalSize;
1543
+
1544
+ while (reader.status == AVAssetReaderStatusReading) {
1545
+ CMSampleBufferRef sampleBuffer = [readerOutput copyNextSampleBuffer];
1546
+ if (!sampleBuffer) break;
1547
+
1548
+ if (readIndex % skipInterval == 0) {
1549
+ CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
1550
+ if (pixelBuffer) {
1551
+ CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
1552
+ CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
1553
+ CIContext *ciContext = [CIContext contextWithOptions:nil];
1554
+ CGImageRef cgImage = [ciContext createCGImage:ciImage fromRect:ciImage.extent];
1555
+ if (cgImage) {
1556
+ CGImageDestinationAddImage(destination, cgImage, (__bridge CFDictionaryRef)frameProperties);
1557
+ CGImageRelease(cgImage);
1558
+ frameCount++;
1559
+ }
1560
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
1561
+ }
1562
+ }
1563
+ CFRelease(sampleBuffer);
1564
+ readIndex++;
1565
+ if (frameCount >= 150) break; // Limit GIF frames for memory safety
1566
+ }
1567
+
1568
+ CGImageDestinationFinalize(destination);
1569
+ CFRelease(destination);
1570
+
1571
+ unsigned long long gifSize = 0;
1572
+ NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1573
+ if (attrs) gifSize = [attrs fileSize];
1574
+
1575
+ NSDictionary *result = @{
1576
+ @"uri": gifUrl.absoluteString,
1577
+ @"format": @"gif",
1578
+ @"durationMs": @(frameCount * frameDelay * 1000.0),
1579
+ @"hasAudio": @(NO),
1580
+ @"width": @((NSInteger)outputSize.width),
1581
+ @"height": @((NSInteger)outputSize.height),
1582
+ @"sizeBytes": @(gifSize),
1583
+ @"timestamp": @(timestamp)
1584
+ };
1585
+ resolve(result);
1586
+ } @catch (NSException *ex) {
1587
+ reject(@"GIF_ERROR", ex.reason, nil);
1588
+ }
1589
+ });
1590
+ }
1591
+
1592
+ RCT_EXPORT_METHOD(getCapturedMedia:(RCTPromiseResolveBlock)resolve
1593
+ reject:(RCTPromiseRejectBlock)reject) {
1594
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
1595
+ NSString *dir = [self getCapturesDirectory];
1596
+ NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:nil];
1597
+ NSMutableArray *mediaItems = [NSMutableArray array];
1598
+
1599
+ for (NSString *file in (files ?: @[])) {
1600
+ NSString *fullPath = [dir stringByAppendingPathComponent:file];
1601
+ NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:nil];
1602
+ if (!attrs) continue;
1603
+
1604
+ NSString *ext = [file.pathExtension lowercaseString];
1605
+ NSString *type = @"image";
1606
+ if ([ext isEqualToString:@"mp4"] || [ext isEqualToString:@"mov"]) {
1607
+ type = @"video";
1608
+ } else if ([ext isEqualToString:@"gif"]) {
1609
+ type = @"gif";
1610
+ }
1611
+
1612
+ long long sizeBytes = [attrs fileSize];
1613
+ NSDate *modDate = [attrs fileModificationDate];
1614
+ long long timestamp = (long long)([modDate timeIntervalSince1970] * 1000.0);
1615
+
1616
+ [mediaItems addObject:@{
1617
+ @"id": file,
1618
+ @"type": type,
1619
+ @"format": ext,
1620
+ @"uri": [NSURL fileURLWithPath:fullPath].absoluteString,
1621
+ @"filename": file,
1622
+ @"sizeBytes": @(sizeBytes),
1623
+ @"timestamp": @(timestamp)
1624
+ }];
1625
+ }
1626
+
1627
+ [mediaItems sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
1628
+ return [obj2[@"timestamp"] compare:obj1[@"timestamp"]];
1629
+ }];
1630
+
1631
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:mediaItems options:0 error:nil];
1632
+ NSString *jsonStr = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
1633
+ resolve(jsonStr ?: @"[]");
1634
+ });
1635
+ }
1636
+
1637
+ RCT_EXPORT_METHOD(deleteCapturedMedia:(NSString *)uri
1638
+ resolve:(RCTPromiseResolveBlock)resolve
1639
+ reject:(RCTPromiseRejectBlock)reject) {
1640
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
1641
+ @try {
1642
+ NSURL *url = [NSURL URLWithString:uri];
1643
+ NSString *path = url ? url.path : uri;
1644
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
1645
+ [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
1646
+ resolve(@(YES));
1647
+ } else {
1648
+ resolve(@(NO));
1649
+ }
1650
+ } @catch (NSException *ex) {
1651
+ resolve(@(NO));
1652
+ }
1653
+ });
1654
+ }
1655
+
1656
+ RCT_EXPORT_METHOD(clearAllCapturedMedia:(RCTPromiseResolveBlock)resolve
1657
+ reject:(RCTPromiseRejectBlock)reject) {
1658
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
1659
+ @try {
1660
+ NSString *dir = [self getCapturesDirectory];
1661
+ NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:nil];
1662
+ for (NSString *file in (files ?: @[])) {
1663
+ [[NSFileManager defaultManager] removeItemAtPath:[dir stringByAppendingPathComponent:file] error:nil];
1664
+ }
1665
+ resolve(@(YES));
1666
+ } @catch (NSException *ex) {
1667
+ resolve(@(NO));
1668
+ }
1669
+ });
1670
+ }
1671
+
944
1672
  #ifdef RCT_NEW_ARCH_ENABLED
945
1673
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
946
1674
  (const facebook::react::ObjCTurboModule::InitParams &)params
@@ -950,3 +1678,4 @@ RCT_EXPORT_METHOD(getNativeCachedPage:(NSString *)pageKey
950
1678
  #endif
951
1679
 
952
1680
  @end
1681
+