react-native-inapp-inspector 2.3.16 → 2.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/inappinspector/NetworkInspectorModule.kt +438 -7
- package/bin/cli.js +0 -0
- package/dist/commonjs/capture.d.ts +26 -0
- package/dist/commonjs/capture.js +32 -0
- package/dist/commonjs/components/AnalyticsEventCard.js +11 -11
- package/dist/commonjs/components/AnimatedEntrance.js +6 -70
- package/dist/commonjs/components/DomainHeader.js +3 -48
- package/dist/commonjs/components/EmptyState.js +2 -2
- package/dist/commonjs/components/HeadersSection.js +14 -34
- package/dist/commonjs/components/Inspector/AnalyticsTab.js +1 -2
- package/dist/commonjs/components/Inspector/ConsoleTab.js +1 -1
- package/dist/commonjs/components/Inspector/CrashTab.js +4 -2
- package/dist/commonjs/components/Inspector/DebuggingTab.js +2 -2
- package/dist/commonjs/components/Inspector/InspectorHeader.js +651 -313
- package/dist/commonjs/components/Inspector/MainScreen.js +25 -70
- package/dist/commonjs/components/Inspector/MediaGalleryTab.d.ts +2 -0
- package/dist/commonjs/components/Inspector/MediaGalleryTab.js +307 -0
- package/dist/commonjs/components/Inspector/MediaPreviewModal.d.ts +11 -0
- package/dist/commonjs/components/Inspector/MediaPreviewModal.js +393 -0
- package/dist/commonjs/components/Inspector/NetworkTab.js +183 -182
- package/dist/commonjs/components/Inspector/NpmUpdateToast.js +4 -9
- package/dist/commonjs/components/Inspector/ReduxTab.js +4 -2
- package/dist/commonjs/components/Inspector/SettingsPanel.js +245 -0
- package/dist/commonjs/components/Inspector/SkeletonPlaceholder.js +2 -2
- package/dist/commonjs/components/Inspector/StorageTab.js +3 -3
- package/dist/commonjs/components/Inspector/TabBar.js +5 -10
- package/dist/commonjs/components/Inspector/UpdateAvailableModal.js +5 -10
- package/dist/commonjs/components/JsonViewer.js +43 -20
- package/dist/commonjs/components/LogCard.js +5 -5
- package/dist/commonjs/components/MetaAccordion.js +78 -80
- package/dist/commonjs/components/NetworkIcons.d.ts +8 -0
- package/dist/commonjs/components/NetworkIcons.js +51 -1
- package/dist/commonjs/components/SectionHeader.js +3 -48
- package/dist/commonjs/components/SkeletonPlaceholder.js +2 -2
- package/dist/commonjs/components/SourcePageCard.js +43 -45
- package/dist/commonjs/components/Toast.js +4 -4
- package/dist/commonjs/components/TouchableScale.js +9 -65
- package/dist/commonjs/components/TreeNode.js +2 -14
- package/dist/commonjs/constants/telemetryConfig.d.ts +3 -3
- package/dist/commonjs/constants/telemetryConfig.js +3 -3
- package/dist/commonjs/constants/version.d.ts +1 -1
- package/dist/commonjs/constants/version.js +1 -1
- package/dist/commonjs/customHooks/useAccordion.d.ts +3 -8
- package/dist/commonjs/customHooks/useAccordion.js +6 -36
- package/dist/commonjs/helpers/index.d.ts +2 -1
- package/dist/commonjs/helpers/index.js +10 -1
- package/dist/commonjs/i18n/locales/en.json +66 -2
- package/dist/commonjs/index.d.ts +1 -0
- package/dist/commonjs/index.js +39 -22
- package/dist/commonjs/native/NativeInspector.d.ts +60 -0
- package/dist/commonjs/native/NativeInspector.js +109 -2
- package/dist/commonjs/native/NativeNetworkInspector.d.ts +8 -0
- package/dist/commonjs/styles/AppColors.d.ts +36 -0
- package/dist/commonjs/styles/AppColors.js +36 -0
- package/dist/commonjs/styles/index.d.ts +3 -1
- package/dist/commonjs/styles/index.js +3 -1
- package/dist/commonjs/types/enums.d.ts +1 -0
- package/dist/commonjs/types/enums.js +1 -0
- package/dist/commonjs/types/index.d.ts +2 -2
- package/dist/esm/capture.d.ts +26 -0
- package/dist/esm/capture.js +29 -0
- package/dist/esm/components/AnalyticsEventCard.js +11 -11
- package/dist/esm/components/AnimatedEntrance.js +4 -38
- package/dist/esm/components/DomainHeader.js +4 -16
- package/dist/esm/components/EmptyState.js +2 -2
- package/dist/esm/components/HeadersSection.js +16 -36
- package/dist/esm/components/Inspector/AnalyticsTab.js +2 -3
- package/dist/esm/components/Inspector/ConsoleTab.js +2 -2
- package/dist/esm/components/Inspector/CrashTab.js +5 -3
- package/dist/esm/components/Inspector/DebuggingTab.js +3 -3
- package/dist/esm/components/Inspector/InspectorHeader.js +654 -316
- package/dist/esm/components/Inspector/MainScreen.js +25 -70
- package/dist/esm/components/Inspector/MediaGalleryTab.d.ts +2 -0
- package/dist/esm/components/Inspector/MediaGalleryTab.js +267 -0
- package/dist/esm/components/Inspector/MediaPreviewModal.d.ts +11 -0
- package/dist/esm/components/Inspector/MediaPreviewModal.js +353 -0
- package/dist/esm/components/Inspector/NetworkTab.js +185 -184
- package/dist/esm/components/Inspector/NpmUpdateToast.js +5 -10
- package/dist/esm/components/Inspector/ReduxTab.js +5 -3
- package/dist/esm/components/Inspector/SettingsPanel.js +246 -1
- package/dist/esm/components/Inspector/SkeletonPlaceholder.js +2 -2
- package/dist/esm/components/Inspector/StorageTab.js +3 -3
- package/dist/esm/components/Inspector/TabBar.js +6 -11
- package/dist/esm/components/Inspector/UpdateAvailableModal.js +6 -11
- package/dist/esm/components/JsonViewer.js +43 -20
- package/dist/esm/components/LogCard.js +5 -5
- package/dist/esm/components/MetaAccordion.js +79 -81
- package/dist/esm/components/NetworkIcons.d.ts +8 -0
- package/dist/esm/components/NetworkIcons.js +42 -0
- package/dist/esm/components/SectionHeader.js +4 -16
- package/dist/esm/components/SkeletonPlaceholder.js +2 -2
- package/dist/esm/components/SourcePageCard.js +44 -46
- package/dist/esm/components/Toast.js +4 -4
- package/dist/esm/components/TouchableScale.js +7 -33
- package/dist/esm/components/TreeNode.js +4 -16
- package/dist/esm/constants/telemetryConfig.d.ts +3 -3
- package/dist/esm/constants/telemetryConfig.js +3 -3
- package/dist/esm/constants/version.d.ts +1 -1
- package/dist/esm/constants/version.js +1 -1
- package/dist/esm/customHooks/useAccordion.d.ts +3 -8
- package/dist/esm/customHooks/useAccordion.js +7 -37
- package/dist/esm/helpers/index.d.ts +2 -1
- package/dist/esm/helpers/index.js +8 -0
- package/dist/esm/i18n/locales/en.json +66 -2
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +36 -21
- package/dist/esm/native/NativeInspector.d.ts +60 -0
- package/dist/esm/native/NativeInspector.js +100 -1
- package/dist/esm/native/NativeNetworkInspector.d.ts +8 -0
- package/dist/esm/styles/AppColors.d.ts +36 -0
- package/dist/esm/styles/AppColors.js +36 -0
- package/dist/esm/styles/index.d.ts +3 -1
- package/dist/esm/styles/index.js +3 -1
- package/dist/esm/types/enums.d.ts +1 -0
- package/dist/esm/types/enums.js +1 -0
- package/dist/esm/types/index.d.ts +2 -2
- package/ios/NetworkInspectorModule.h +1 -0
- package/ios/NetworkInspectorModule.mm +736 -7
- package/package.json +10 -2
- package/react-native-inapp-inspector.podspec +1 -0
- package/src/capture.ts +101 -0
- package/src/components/AnalyticsEventCard.tsx +15 -13
- package/src/components/AnimatedEntrance.tsx +3 -47
- package/src/components/DomainHeader.tsx +4 -17
- package/src/components/EmptyState.tsx +2 -2
- package/src/components/HeadersSection.tsx +31 -56
- package/src/components/Inspector/AnalyticsTab.tsx +2 -2
- package/src/components/Inspector/ConsoleTab.tsx +2 -1
- package/src/components/Inspector/CrashTab.tsx +4 -2
- package/src/components/Inspector/DebuggingTab.tsx +2 -2
- package/src/components/Inspector/InspectorHeader.tsx +1215 -789
- package/src/components/Inspector/MainScreen.tsx +44 -85
- package/src/components/Inspector/MediaGalleryTab.tsx +355 -0
- package/src/components/Inspector/MediaPreviewModal.tsx +435 -0
- package/src/components/Inspector/NetworkTab.tsx +14 -15
- package/src/components/Inspector/NpmUpdateToast.tsx +4 -9
- package/src/components/Inspector/ReduxTab.tsx +5 -2
- package/src/components/Inspector/SettingsPanel.tsx +303 -0
- package/src/components/Inspector/SkeletonPlaceholder.tsx +2 -2
- package/src/components/Inspector/StorageTab.tsx +3 -3
- package/src/components/Inspector/TabBar.tsx +6 -12
- package/src/components/Inspector/UpdateAvailableModal.tsx +6 -10
- package/src/components/JsonViewer.tsx +101 -60
- package/src/components/LogCard.tsx +10 -8
- package/src/components/MetaAccordion.tsx +6 -6
- package/src/components/NetworkIcons.tsx +150 -0
- package/src/components/SectionHeader.tsx +4 -16
- package/src/components/SkeletonPlaceholder.tsx +2 -2
- package/src/components/SourcePageCard.tsx +57 -55
- package/src/components/Toast.tsx +4 -4
- package/src/components/TouchableScale.tsx +8 -38
- package/src/components/TreeNode.tsx +5 -20
- package/src/constants/telemetryConfig.ts +3 -5
- package/src/constants/version.ts +1 -1
- package/src/customHooks/useAccordion.tsx +10 -43
- package/src/helpers/index.ts +10 -1
- package/src/i18n/locales/en.json +69 -2
- package/src/index.tsx +51 -23
- package/src/native/NativeInspector.ts +186 -1
- package/src/native/NativeNetworkInspector.ts +9 -0
- package/src/styles/AppColors.ts +38 -0
- package/src/styles/index.ts +3 -1
- package/src/types/enums.ts +3 -0
- package/src/types/index.ts +2 -2
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/checksums/md5-checksums.bin +0 -0
- package/android/.gradle/8.9/checksums/sha1-checksums.bin +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/executionHistory/executionHistory.lock +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/9.2.0/checksums/checksums.lock +0 -0
- package/android/.gradle/9.2.0/fileChanges/last-build.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.bin +0 -0
- package/android/.gradle/9.2.0/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/9.2.0/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build/.transforms/6683d6eeaa3e1dfa689c3385111a0386/results.bin +0 -1
- package/android/build/.transforms/6683d6eeaa3e1dfa689c3385111a0386/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/results.bin +0 -1
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/BuildConfig.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/InAppInspectorFloatingView$iconView$1.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/InAppInspectorFloatingView.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NativeNetworkInspectorSpec.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorModule$Companion.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorModule$frameCallback$1.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorModule$shakeListener$1.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorModule.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/inappinspector/NetworkInspectorPackage.dex +0 -0
- package/android/build/.transforms/9d2a3e5e0f0ea07cafb0c0bf290630ee/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
- package/android/build/generated/source/buildConfig/debug/com/inappinspector/BuildConfig.java +0 -10
- package/android/build/generated/source/codegen/java/com/inappinspector/NativeNetworkInspectorSpec.java +0 -104
- package/android/build/generated/source/codegen/jni/CMakeLists.txt +0 -28
- package/android/build/generated/source/codegen/jni/RNInAppInspectorSpec-generated.cpp +0 -128
- package/android/build/generated/source/codegen/jni/RNInAppInspectorSpec.h +0 -31
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/ComponentDescriptors.cpp +0 -22
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/ComponentDescriptors.h +0 -24
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/EventEmitters.cpp +0 -16
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/EventEmitters.h +0 -17
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/Props.cpp +0 -19
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/Props.h +0 -18
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/RNInAppInspectorSpecJSI.h +0 -177
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/ShadowNodes.cpp +0 -17
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/ShadowNodes.h +0 -23
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/States.cpp +0 -16
- package/android/build/generated/source/codegen/jni/react/renderer/components/RNInAppInspectorSpec/States.h +0 -20
- package/android/build/generated/source/codegen/schema.json +0 -1
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +0 -7
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +0 -18
- package/android/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar +0 -0
- package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +0 -6
- package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +0 -1
- package/android/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt +0 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +0 -1
- package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +0 -2
- package/android/build/intermediates/incremental/debug-mergeJavaRes/merge-state +0 -0
- package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +0 -2
- package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +0 -2
- package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +0 -2
- package/android/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/react-native-inapp-inspector_debug.kotlin_module +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/inappinspector/BuildConfig.class +0 -0
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/inappinspector/NativeNetworkInspectorSpec.class +0 -0
- package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +0 -2
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +0 -7
- package/android/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-react-native-inapp-inspector.jar +0 -0
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +0 -7
- package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +0 -1
- package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +0 -1
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/META-INF/react-native-inapp-inspector_debug.kotlin_module +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/BuildConfig.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/InAppInspectorFloatingView$iconView$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/InAppInspectorFloatingView.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NativeNetworkInspectorSpec.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorModule$Companion.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorModule$frameCallback$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorModule$shakeListener$1.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorModule.class +0 -0
- package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/inappinspector/NetworkInspectorPackage.class +0 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +0 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/constants.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +0 -2
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/aar/react-native-inapp-inspector-debug.aar +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +0 -17
- package/android/build/reports/problems/problems-report.html +0 -659
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/META-INF/react-native-inapp-inspector_debug.kotlin_module +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/inappinspector/InAppInspectorFloatingView$iconView$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/inappinspector/InAppInspectorFloatingView.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorModule$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorModule$frameCallback$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorModule$shakeListener$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/com/inappinspector/NetworkInspectorModule.class +0 -0
- 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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
649
|
-
|
|
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
|
+
|