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