From 097d3d545ad131f02139af0a4547af6e13cfaa7c Mon Sep 17 00:00:00 2001 From: Yug2801 Date: Mon, 17 Nov 2025 20:14:59 +0530 Subject: [PATCH 1/5] [skip ci] Release v2.2.1 This PR contains the automated release updates for version 2.2.1. Changes: - Updated sample app - Updated documentation - Updated README, CHANGELOG, and MIGRATION guide --- .gitignore | 27 - CHANGELOG.md | 51 + MIGRATION.md | 34 +- README.md | 507 ++++++++-- devrev-sdk-react-native-2.2.1.tgz | Bin 0 -> 39661 bytes sample/README.md | 79 -- sample/android/app/google-services.json | 83 -- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3056 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 5024 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2096 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2858 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4569 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 7098 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6464 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10676 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9250 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15523 -> 0 bytes .../app/src/main/res/values/strings.xml | 3 - sample/android/build.gradle | 35 - sample/android/settings.gradle | 7 - sample/app.json | 4 - sample/expo/.gitignore | 197 ++++ sample/expo/App.tsx | 32 + sample/expo/GoogleService-Info.plist | 16 + sample/expo/PushNotificationsService.tsx | 312 ++++++ sample/expo/app.json | 74 ++ sample/expo/assets/adaptive-icon.png | Bin 0 -> 17547 bytes sample/expo/assets/favicon.png | Bin 0 -> 1466 bytes sample/expo/assets/icon.png | Bin 0 -> 22380 bytes sample/expo/assets/sample.html | 56 ++ sample/expo/assets/splash-icon.png | Bin 0 -> 17547 bytes sample/expo/babel.config.js | 6 + .../components/TouchableOpacityButton.tsx | 46 + sample/expo/eas.json | 21 + sample/expo/google-services.json | 18 + sample/expo/index.ts | 8 + sample/expo/metro.config.js | 38 + sample/expo/navigator/Navigator.tsx | 108 +++ sample/expo/package.json | 49 + sample/expo/screens/DelayScreen.tsx | 30 + sample/expo/screens/FlatListScreen.tsx | 86 ++ sample/expo/screens/HomeScreen.tsx | 163 ++++ sample/expo/screens/IdentificationScreen.tsx | 138 +++ .../expo/screens/PushNotificationsScreen.tsx | 125 +++ .../expo/screens/SessionAnalyticsScreen.tsx | 247 +++++ sample/expo/screens/SupportChatScreen.tsx | 59 ++ sample/expo/screens/WebViewScreen.tsx | 22 + sample/expo/tsconfig.json | 10 + sample/expo/viewmodel/ViewModel.tsx | 68 ++ sample/expo/webpack.config.js | 20 + sample/ios/GoogleService-Info.plist | 32 - .../AppIcon.appiconset/Contents.json | 53 -- sample/ios/Sources/LaunchScreen.storyboard | 51 - sample/package.json | 2 +- sample/{ => react-native}/.npmrc.example | 0 sample/{ => react-native}/Gemfile | 0 .../android/app/build.gradle | 3 +- .../android/app/debug.keystore | Bin .../android/app/google-services.json | 18 + .../android/app/proguard-rules.pro | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../app/src/main/assets/index.android.bundle | 894 ++++++++++++++++++ .../bridge/reactnative/sample/MainActivity.kt | 2 +- .../reactnative/sample/MainApplication.kt | 2 +- ...avigation_elements_src_assets_backicon.png | Bin 0 -> 134 bytes ...avigation_elements_src_assets_backicon.png | Bin 0 -> 100 bytes ...ation_elements_src_assets_backiconmask.png | Bin 0 -> 913 bytes ...avigation_elements_src_assets_backicon.png | Bin 0 -> 134 bytes ...avigation_elements_src_assets_backicon.png | Bin 0 -> 167 bytes ...avigation_elements_src_assets_backicon.png | Bin 0 -> 207 bytes .../res/drawable/rn_edit_text_material.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1915 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3756 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1145 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2279 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2292 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5072 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 3556 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 7981 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 4974 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 11586 bytes .../src/main/res/raw/src_assets_sample.html | 56 ++ .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 0 sample/react-native/android/build.gradle | 42 + .../android/gradle.properties | 0 .../android/gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 sample/{ => react-native}/android/gradlew | 0 sample/{ => react-native}/android/gradlew.bat | 0 sample/react-native/android/settings.gradle | 9 + sample/react-native/app.json | 4 + sample/react-native/babel.config.js | 11 + sample/{ => react-native}/index.js | 0 sample/{ => react-native}/ios/.editorconfig | 0 sample/{ => react-native}/ios/.xcode.env | 0 .../ios/DevRevSDKSampleRN-Bridging-Header.h} | 0 .../ios/DevRevSDKSampleRN.entitlements} | 0 .../project.pbxproj | 130 +-- .../xcschemes/DevRevSDKSampleRN.xcscheme} | 30 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../react-native/ios/GoogleService-Info.plist | 16 + sample/{ => react-native}/ios/Podfile | 11 +- sample/{ => react-native}/ios/Podfile.lock | 53 +- .../ios/Sources/AppDelegate.swift | 38 +- .../AppIcon.appiconset/Contents.json | 14 + .../AppIcon.appiconset/React Native.png | Bin 0 -> 18364 bytes .../ios/Sources/Images.xcassets/Contents.json | 0 .../{ => react-native}/ios/Sources/Info.plist | 6 +- .../ios/Sources/LaunchScreen.storyboard | 51 + .../ios/Sources/PrivacyInfo.xcprivacy | 0 sample/{ => react-native}/jest.config.js | 0 sample/react-native/metro.config.js | 48 + sample/react-native/package.json | 76 ++ sample/react-native/react-native.config.js | 15 + sample/{ => react-native}/src/App.tsx | 72 +- .../src/PushNotificationsService.tsx | 99 ++ sample/react-native/src/assets/sample.html | 56 ++ .../src/components/AlertDialog.tsx | 6 +- .../src/components/RoundCheckbox.tsx | 7 +- .../src/components/TouchableOpacityButton.tsx | 32 + .../src/model/DevRevNotification.ts | 0 .../src/screens/DelayedScreen.tsx | 20 + .../src/screens/FlatListScreen.tsx | 86 ++ .../src/screens/HomeScreen.tsx | 44 +- .../src/screens/IdentificationScreen.tsx | 121 +++ .../src/screens/PushNotificationsScreen.tsx | 30 +- .../src/screens/SessionAnalyticsScreen.tsx | 218 +++++ .../src/screens/SupportChatScreen.tsx | 0 .../src/screens/WebViewScreen.tsx | 22 + .../{ => react-native}/src/styles/colors.ts | 5 +- .../{ => react-native}/src/styles/styles.ts | 6 +- .../react-native/src/viewmodel/ViewModel.tsx | 66 ++ sample/react-native/tsconfig.json | 19 + sample/src/NotificationService.tsx | 257 ----- .../src/components/TouchableOpacityButton.tsx | 31 - sample/src/screens/DelayedScreen.tsx | 25 - sample/src/screens/IdentificationScreen.tsx | 116 --- sample/src/screens/SessionAnalyticsScreen.tsx | 159 ---- sample/src/usePushNotifications.tsx | 83 -- sample/src/viewmodel/ViewModel.tsx | 62 -- 142 files changed, 4707 insertions(+), 1388 deletions(-) create mode 100644 devrev-sdk-react-native-2.2.1.tgz delete mode 100644 sample/README.md delete mode 100644 sample/android/app/google-services.json delete mode 100644 sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 sample/android/app/src/main/res/values/strings.xml delete mode 100644 sample/android/build.gradle delete mode 100644 sample/android/settings.gradle delete mode 100644 sample/app.json create mode 100644 sample/expo/.gitignore create mode 100644 sample/expo/App.tsx create mode 100644 sample/expo/GoogleService-Info.plist create mode 100644 sample/expo/PushNotificationsService.tsx create mode 100644 sample/expo/app.json create mode 100644 sample/expo/assets/adaptive-icon.png create mode 100644 sample/expo/assets/favicon.png create mode 100644 sample/expo/assets/icon.png create mode 100644 sample/expo/assets/sample.html create mode 100644 sample/expo/assets/splash-icon.png create mode 100644 sample/expo/babel.config.js create mode 100644 sample/expo/components/TouchableOpacityButton.tsx create mode 100644 sample/expo/eas.json create mode 100644 sample/expo/google-services.json create mode 100644 sample/expo/index.ts create mode 100644 sample/expo/metro.config.js create mode 100644 sample/expo/navigator/Navigator.tsx create mode 100644 sample/expo/package.json create mode 100644 sample/expo/screens/DelayScreen.tsx create mode 100644 sample/expo/screens/FlatListScreen.tsx create mode 100644 sample/expo/screens/HomeScreen.tsx create mode 100644 sample/expo/screens/IdentificationScreen.tsx create mode 100644 sample/expo/screens/PushNotificationsScreen.tsx create mode 100644 sample/expo/screens/SessionAnalyticsScreen.tsx create mode 100644 sample/expo/screens/SupportChatScreen.tsx create mode 100644 sample/expo/screens/WebViewScreen.tsx create mode 100644 sample/expo/tsconfig.json create mode 100644 sample/expo/viewmodel/ViewModel.tsx create mode 100644 sample/expo/webpack.config.js delete mode 100644 sample/ios/GoogleService-Info.plist delete mode 100644 sample/ios/Sources/Images.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 sample/ios/Sources/LaunchScreen.storyboard rename sample/{ => react-native}/.npmrc.example (100%) rename sample/{ => react-native}/Gemfile (100%) rename sample/{ => react-native}/android/app/build.gradle (97%) rename sample/{ => react-native}/android/app/debug.keystore (100%) create mode 100644 sample/react-native/android/app/google-services.json rename sample/{ => react-native}/android/app/proguard-rules.pro (100%) rename sample/{ => react-native}/android/app/src/debug/AndroidManifest.xml (100%) rename sample/{ => react-native}/android/app/src/main/AndroidManifest.xml (100%) create mode 100644 sample/react-native/android/app/src/main/assets/index.android.bundle rename sample/{ => react-native}/android/app/src/main/java/ai/devrev/sdk/bridge/reactnative/sample/MainActivity.kt (99%) rename sample/{ => react-native}/android/app/src/main/java/ai/devrev/sdk/bridge/reactnative/sample/MainApplication.kt (96%) create mode 100644 sample/react-native/android/app/src/main/res/drawable-hdpi/node_modules_reactnavigation_elements_src_assets_backicon.png create mode 100644 sample/react-native/android/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_elements_src_assets_backicon.png create mode 100644 sample/react-native/android/app/src/main/res/drawable-mdpi/node_modules_reactnavigation_elements_src_assets_backiconmask.png create mode 100644 sample/react-native/android/app/src/main/res/drawable-xhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png create mode 100644 sample/react-native/android/app/src/main/res/drawable-xxhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png create mode 100644 sample/react-native/android/app/src/main/res/drawable-xxxhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png rename sample/{ => react-native}/android/app/src/main/res/drawable/rn_edit_text_material.xml (100%) create mode 100644 sample/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sample/react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sample/react-native/android/app/src/main/res/raw/src_assets_sample.html create mode 100644 sample/react-native/android/app/src/main/res/values/strings.xml rename sample/{ => react-native}/android/app/src/main/res/values/styles.xml (100%) create mode 100644 sample/react-native/android/build.gradle rename sample/{ => react-native}/android/gradle.properties (100%) rename sample/{ => react-native}/android/gradle/wrapper/gradle-wrapper.jar (100%) rename sample/{ => react-native}/android/gradle/wrapper/gradle-wrapper.properties (100%) rename sample/{ => react-native}/android/gradlew (100%) rename sample/{ => react-native}/android/gradlew.bat (100%) create mode 100644 sample/react-native/android/settings.gradle create mode 100644 sample/react-native/app.json create mode 100644 sample/react-native/babel.config.js rename sample/{ => react-native}/index.js (100%) rename sample/{ => react-native}/ios/.editorconfig (100%) rename sample/{ => react-native}/ios/.xcode.env (100%) rename sample/{ios/DevRevSDKSample-Bridging-Header.h => react-native/ios/DevRevSDKSampleRN-Bridging-Header.h} (100%) rename sample/{ios/DevRevSDKSample.entitlements => react-native/ios/DevRevSDKSampleRN.entitlements} (100%) rename sample/{ios/DevRevSDKSample.xcodeproj => react-native/ios/DevRevSDKSampleRN.xcodeproj}/project.pbxproj (88%) rename sample/{ios/DevRevSDKSample.xcodeproj/xcshareddata/xcschemes/DevRevSDKSample.xcscheme => react-native/ios/DevRevSDKSampleRN.xcodeproj/xcshareddata/xcschemes/DevRevSDKSampleRN.xcscheme} (74%) rename sample/{ios/DevRevSDKSample.xcworkspace => react-native/ios/DevRevSDKSampleRN.xcworkspace}/xcshareddata/swiftpm/Package.resolved (76%) create mode 100644 sample/react-native/ios/GoogleService-Info.plist rename sample/{ => react-native}/ios/Podfile (69%) rename sample/{ => react-native}/ios/Podfile.lock (98%) rename sample/{ => react-native}/ios/Sources/AppDelegate.swift (55%) create mode 100644 sample/react-native/ios/Sources/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 sample/react-native/ios/Sources/Images.xcassets/AppIcon.appiconset/React Native.png rename sample/{ => react-native}/ios/Sources/Images.xcassets/Contents.json (100%) rename sample/{ => react-native}/ios/Sources/Info.plist (91%) create mode 100644 sample/react-native/ios/Sources/LaunchScreen.storyboard rename sample/{ => react-native}/ios/Sources/PrivacyInfo.xcprivacy (100%) rename sample/{ => react-native}/jest.config.js (100%) create mode 100644 sample/react-native/metro.config.js create mode 100644 sample/react-native/package.json create mode 100644 sample/react-native/react-native.config.js rename sample/{ => react-native}/src/App.tsx (57%) create mode 100644 sample/react-native/src/PushNotificationsService.tsx create mode 100644 sample/react-native/src/assets/sample.html rename sample/{ => react-native}/src/components/AlertDialog.tsx (88%) rename sample/{ => react-native}/src/components/RoundCheckbox.tsx (82%) create mode 100644 sample/react-native/src/components/TouchableOpacityButton.tsx rename sample/{ => react-native}/src/model/DevRevNotification.ts (100%) create mode 100644 sample/react-native/src/screens/DelayedScreen.tsx create mode 100644 sample/react-native/src/screens/FlatListScreen.tsx rename sample/{ => react-native}/src/screens/HomeScreen.tsx (76%) create mode 100644 sample/react-native/src/screens/IdentificationScreen.tsx rename sample/{ => react-native}/src/screens/PushNotificationsScreen.tsx (59%) create mode 100644 sample/react-native/src/screens/SessionAnalyticsScreen.tsx rename sample/{ => react-native}/src/screens/SupportChatScreen.tsx (100%) create mode 100644 sample/react-native/src/screens/WebViewScreen.tsx rename sample/{ => react-native}/src/styles/colors.ts (92%) rename sample/{ => react-native}/src/styles/styles.ts (97%) create mode 100644 sample/react-native/src/viewmodel/ViewModel.tsx create mode 100644 sample/react-native/tsconfig.json delete mode 100644 sample/src/NotificationService.tsx delete mode 100644 sample/src/components/TouchableOpacityButton.tsx delete mode 100644 sample/src/screens/DelayedScreen.tsx delete mode 100644 sample/src/screens/IdentificationScreen.tsx delete mode 100644 sample/src/screens/SessionAnalyticsScreen.tsx delete mode 100644 sample/src/usePushNotifications.tsx delete mode 100644 sample/src/viewmodel/ViewModel.tsx diff --git a/.gitignore b/.gitignore index ced8b27..365c296 100644 --- a/.gitignore +++ b/.gitignore @@ -8,22 +8,7 @@ !**/*.xcworkspacedata !**/*.xcsettings !**/*.xcscheme -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside DerivedData -*.hmap -*.ipa -*.xcuserstate -project.xcworkspace **/.xcode.env.local # Gradle @@ -68,10 +53,6 @@ buck-out .watchmanconfig # Android -.idea -.gradle -local.properties -*.iml /packages/react-native/android/* !/packages/react-native/android/README.md @@ -81,9 +62,6 @@ node_modules .nvm package-lock.json -# OS X -.DS_Store - # Test generated files *.js.meta @@ -152,7 +130,6 @@ vendor/ # Visual Studio Code (config dir - if present, this merges user defined # workspace settings on top of react-native.code-workspace) -/.vscode # Visual Studio .vs @@ -166,11 +143,7 @@ vendor/ # CircleCI .circleci/generated_config.yml -# XDE -.expo/ - # VSCode -.vscode/ jsconfig.json # Ruby diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af0b6e..7e907f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,57 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.1] - 2025-10-17 + +### Added +- Added the ability to pause and resume user interaction event tracking, offering more security on the confidential screens. + +### Changed +- Simplified identification flow by deprecating redundant anonymous API usage. + +### Fixed +- Resolved potential out of memory crashes. +- Corrected incorrect or missing device model names on certain iPhone versions. +- Fixed visual distortion issues when zooming inside web view. +- Fixed issues in the logout process to ensure complete session termination and improved reliability. + +## [2.2.0] - 2025-09-29 + +### Changed +- iOS only: Improved the infrastructure for custom masking. +- Unified the screen tracking feature naming across platforms. + +### Deprecated +- Deprecated the `startScreenTransition` and `endScreenTransition` functions, use the `setInScreenTransitioning` function instead. + +### Fixed +- iOS only: Fixed push notifications not opening the conversations. +- Android only: Resolved potential issues that could cause app crashes during network operations. +- Android only: When adding session properties before initialization, they are now properly queued and executed. +- Android only: Added extra safeguards to prevent crashes related to uninitialized properties. +- Android only: Enhanced the stability of session-recording flows for a more reliable experience. + +## [2.1.3] - 2025-09-05 + +## Added +- Added detailed observability for SDK configuration and identification steps. + +## [2.1.2] - 2025-09-01 + +## Added +- Added a caching mechanism for identification calls. + +## Fixed +- Improved the presentation of support widget in edge to edge scenarios. +- Improved the session analytics and metrics. +- Fixed an issue related to long session engagement times. + +## [2.1.1] - 2025-07-24 + +## Fixed +- Fixed an issue with manual unmasking of input components. +- Fixed an issue with session uploads when the app is rapidly killed. + ## [2.1.0] - 2025-06-27 ### Added diff --git a/MIGRATION.md b/MIGRATION.md index e50f41e..b337178 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,17 +1,19 @@ -# Migration Guide -This guide and chart should help facilitate the transition from the legacy UserExperior SDK to the new DevRev SDK in your React Native application, providing insights into feature equivalents and method changes. +# Migration guide -## Feature Equivalence Chart -| Feature | UserExperior SDK | DevRev SDK | -|-|-|-| -| Installation | `npm install react-native-userexperior` | `npm install @devrev/sdk-react-native` | -| Initialization | `UserExperior.startRecording(string)` | `DevRev.configure(appID: string)` | -| User Identification | `UserExperior.setUserIdentifier(string)` | `DevRev.identifyAnonymousUser(userID: string)`
`DevRev.identifyUnverifiedUser(identity: Identity)`
`DevRev.updateUser(identity: Identity)`
`DevRev.identifyVerifiedUser(userID: string, sessionToken: string)`
`DevRev.logout(deviceID: string)` | -| Event Tracking | `UserExperior.logEvent(string, Map)` | `DevRev.trackEvent(name: string, properties?: Map)` | -| Session Recording | `UserExperior.stopRecording()`
`UserExperior.pauseRecording()`
`UserExperior.resumeRecording()` | `DevRev.startRecording()`
`DevRev.stopRecording()`
`DevRev.pauseRecording()`
`DevRev.resumeRecording()`
`DevRev.processAllOnDemandSessions()` | -| Opting in/out | Not supported. | `DevRev.stopAllMonitoring()`
`DevRev.resumeAllMonitoring()` | -| Session Properties | `UserExperior.setUserProperties(Map)` | `DevRev.addSessionProperties(properties: Map)`
`DevRev.clearSessionProperties()` | -| Masking Sensitive Data | `UserExperior.addInSecureViewBucket(any[])`
`UserExperior.removeFromSecureViewBucket(any[])` | `DevRev.markSensitiveViews(tags: any[])`
`DevRev.unmarkSensitiveViews(tags: any[])` | -| Timers | Not supported. | `DevRev.startTimer()`
`DevRev.stopTimer()` | -| PLuG support chat | Not supported. | `DevRev.showSupport()`
`DevRev.createSupportConversation()`
`DevRev.setShouldDismissModalsOnOpenLink()`
`DevRev.setInAppLinkHandler()` | -| Push Notifications | Not supported. | `DevRev.registerDeviceToken()`
`DevRev.unregisterDevice(deviceID: string)`
`DevRev.processPushNotification()` | +This guide and chart should help facilitate the transition from the legacy UserExperior SDK to the new DevRev SDK in your React Native application, providing insights into feature equivalents and method changes. + +## Feature equivalence chart + +| Feature | UserExperior SDK | DevRev SDK | +|-|-|-| +| Installation | `npm install react-native-userexperior` | `npm install @devrev/sdk-react-native` | +| Initialization | `UserExperior.startRecording(string)` | `DevRev.configure(appID: string)` | +| User Identification | `UserExperior.setUserIdentifier(string)` | `DevRev.identifyAnonymousUser(userID: string)`
`DevRev.identifyUnverifiedUser(identity: Identity)`
`DevRev.updateUser(identity: Identity)`
`DevRev.identifyVerifiedUser(userID: string, sessionToken: string)`
`DevRev.logout(deviceID: string)` | +| Event Tracking | `UserExperior.logEvent(string, Map)` | `DevRev.trackEvent(name: string, properties?: { [key: string]: string })` | +| Session Recording | `UserExperior.stopRecording()`
`UserExperior.pauseRecording()`
`UserExperior.resumeRecording()` | `DevRev.startRecording()`
`DevRev.stopRecording()`
`DevRev.pauseRecording()`
`DevRev.resumeRecording()`
`DevRev.processAllOnDemandSessions()` | +| Opting in/out | Not supported. | `DevRev.stopAllMonitoring()`
`DevRev.resumeAllMonitoring()` | +| Session Properties | `UserExperior.setUserProperties(Map)` | `DevRev.addSessionProperties(properties: { [key: string]: string })`
`DevRev.clearSessionProperties()` | +| Masking Sensitive Data | `UserExperior.addInSecureViewBucket(any[])`
`UserExperior.removeFromSecureViewBucket(any[])` | `DevRev.markSensitiveViews(tags: any[])`
`DevRev.unmarkSensitiveViews(tags: any[])` | +| Timers | Not supported. | `DevRev.startTimer(name: string, properties: { [key: string]: string })`
`DevRev.endTimer(name: string, properties: { [key: string]: string })` | +| Support chat | Not supported. | `DevRev.showSupport()`
`DevRev.createSupportConversation()`
`DevRev.setShouldDismissModalsOnOpenLink()`
`DevRev.setInAppLinkHandler()` | +| Push Notifications | Not supported. | `DevRev.registerDeviceToken()`
`DevRev.unregisterDevice(deviceID: string)`
`DevRev.processPushNotification()` | diff --git a/README.md b/README.md index f93b6b3..c5cf993 100644 --- a/README.md +++ b/README.md @@ -1,141 +1,295 @@ -# DevRev SDK for React Native -DevRev SDK, used for integrating DevRev services into your React Native app. +# DevRev SDK for React Native and Expo -## Table of contents -- [DevRev SDK for React Native](#devrev-sdk-for-react-native) - - [Table of contents](#table-of-contents) +DevRev SDK, used for integrating DevRev services into your React Native and Expo apps. + +- [DevRev SDK for React Native and Expo](#devrev-sdk-for-react-native-and-expo) - [Quickstart](#quickstart) + - [Requirements](#requirements) - [Installation](#installation) + - [Expo](#expo) - [Set up the DevRev SDK](#set-up-the-devrev-sdk) - [Features](#features) - [Identification](#identification) - - [Anonymous identification](#anonymous-identification) - - [Unverified identification](#unverified-identification) - - [Verified identification](#verified-identification) - - [Updating the user](#updating-the-user) + - [Identify an unverified user](#identify-an-unverified-user) + - [Identify a verified user](#identify-a-verified-user) + - [Generate an AAT](#generate-an-aat) + - [Exchange your AAT for a session token](#exchange-your-aat-for-a-session-token) + - [Identify the verified user](#identify-the-verified-user) + - [Update the user](#update-the-user) - [Logout](#logout) - - [PLuG support chat](#plug-support-chat) - - [Creating a new support conversation](#creating-a-new-support-conversation) - - [In-app link handling](#in-app-link-handling) - - [In-app link callback](#in-app-link-callback) + - [Identity model](#identity-model) + - [Properties](#properties) + - [User traits](#user-traits) + - [Organization traits](#organization-traits) + - [Account traits](#account-traits) + - [Support chat](#support-chat) + - [Create a new support conversation](#create-a-new-support-conversation) + - [In-app link handling](#in-app-link-handling) + - [In-app link callback](#in-app-link-callback) - [Dynamic theme configuration](#dynamic-theme-configuration) - [Analytics](#analytics) - [Session analytics](#session-analytics) - - [Opting-in or out](#opting-in-or-out) + - [Opt in or out](#opt-in-or-out) - [Session recording](#session-recording) - [Session properties](#session-properties) - - [Masking sensitive data](#masking-sensitive-data) + - [Mask sensitive data](#mask-sensitive-data) + - [Mask elements inside web views](#mask-elements-inside-web-views) + - [User interaction tracking](#user-interaction-tracking) - [Timers](#timers) - - [Screen tracking](#screen-tracking) - - [Screen transition tracking (Android only)](#screen-transition-tracking-android-only) + - [Track screens](#track-screens) + - [Manage screen transitions (Android only)](#manage-screen-transitions-android-only) - [Push notifications](#push-notifications) - [Configuration](#configuration) - [Register for push notifications](#register-for-push-notifications) - [Unregister from push notifications](#unregister-from-push-notifications) - - [Processing push notifications](#processing-push-notifications) + - [Handle push notifications](#handle-push-notifications) - [Android](#android) - - [Example](#example) - [iOS](#ios) - - [Example](#example-1) - - [Sample app](#sample-app) + - [Sample app (without framework)](#sample-app-without-framework) + - [Sample app (Expo)](#sample-app-expo) - [Troubleshooting](#troubleshooting) + - [ProGuard (Android only)](#proguard-android-only) - [Migration Guide](#migration-guide) ## Quickstart ### Requirements -- Minimum deployment target Android SDK 24 or iOS 15.1. + +- React Native 0.79.0 or later. +- For Expo apps, Expo 50.0.0 or later. +- Android: minimum API level 24. +- iOS: minimum deployment target 15.1. +- (Recommended) An SSH key configured locally and registered with [GitHub](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh). ### Installation + To install the DevRev SDK, run the following command: -```sh +```bash npm install @devrev/sdk-react-native ``` +#### Expo + +1. To install the DevRev SDK, run the following command: + ```bash + npx expo install @devrev/sdk-react-native-expo-plugin + ``` +2. Configure the Expo config plugin in your `app.json` or `app.config.js`: + ```json + { + "expo": { + "plugins": [ + "@devrev/sdk-react-native-expo-plugin" + ] + } + } + ``` +3. Rebuild your app: + ```bash + npx expo prebuild --clean + ``` + ### Set up the DevRev SDK + 1. Open the DevRev web app at [https://app.devrev.ai](https://app.devrev.ai) and go to the **Settings** page. 2. Under **PLuG settings** copy the value under **Your unique App ID**. -3. After obtaining the credentials, you can configure the DevRev SDK in your app. +3. Configure the DevRev SDK in your app using the obtained credentials. + +> [!WARNING] +> The DevRev SDK must be configured before you can use any of its features. -The SDK will be ready for use once you execute the following configuration method. +The SDK becomes ready for use once the following configuration method is executed. ```typescript DevRev.configure(appID: string) ``` ## Features + ### Identification + To access certain features of the DevRev SDK, user identification is required. -The identification function should be placed appropriately in your app after the user logs in. If you have the user information available at app launch, call the function after the `DevRev.configure(appID:)` method. +The identification function should be placed appropriately in your app after the user logs in. If you have the user information available at app launch, call the function after the `DevRev.configure(appID: string)` method. -> [!IMPORTANT] -> On iOS, if you haven't previously identified the user, the DevRev SDK will automatically create an anonymous user for you immediately after the SDK is configured. +> [!TIP] +> If you haven't previously identified the user, the DevRev SDK will automatically create an anonymous user for you immediately after the SDK is configured. -> [!IMPORTANT] +> [!TIP] > The `Identity` structure allows for custom fields in the user, organization, and account traits. These fields must be configured through the DevRev app before they can be used. For more information, refer to [Object customization](https://devrev.ai/docs/product/object-customization). You can select from the following methods to identify users within your application: -#### Anonymous identification -The anonymous identification method allows you to create an anonymous user with an optional user identifier, ensuring that no other data is stored or associated with the user. +#### Identify an unverified user -```typescript -DevRev.identifyAnonymousUser(userID: string) -``` - -#### Unverified identification The unverified identification method identifies users with a unique identifier, but it does not verify their identity with the DevRev backend. ```typescript DevRev.identifyUnverifiedUser(userID: string, organizationID?: string) ``` -#### Verified identification -The verified identification method identifies users with a unique identifier and verifies their identity with the DevRev backend. +#### Identify a verified user + +The verified identification method is used to identify users with an identifier unique to your system within the DevRev platform. The verification is done through a token exchange process between you and the DevRev backend. + +The steps to identify a verified user are as follows: +1. Generate an AAT for your system (preferably through your backend). +2. Exchange your AAT for a session token for each user of your system. +3. Pass the user identifier and the exchanged session token to the `DevRev.identifyVerifiedUser(userID: string, sessionToken: string)` method. + +> [!WARNING] +> For security reasons, it is **strongly recommended** that the token exchange is executed on your backend to prevent exposing your application access token (AAT). + +##### Generate an AAT + +1. Open the DevRev web app at [https://app.devrev.ai](https://app.devrev.ai) and go to the **Settings** page. +2. Open the **PLuG Tokens** page. +3. Under the **Application access tokens** panel, click **New token** and copy the token that's displayed. + +> [!WARNING] +> Ensure that you copy the generated application access token, as you cannot view it again. + +##### Exchange your AAT for a session token + +To proceed with identifying the user, you need to exchange your AAT for a session token. This step helps you identify a user of your own system within the DevRev platform. + +Here is a simple example of an API request to the DevRev backend to exchange your AAT for a session token: + +> [!WARNING] +> Make sure that you replace the `` and `` with the actual values. + +```bash +curl \ +--location 'https://api.devrev.ai/auth-tokens.create' \ +--header 'accept: application/json, text/plain, */*' \ +--header 'content-type: application/json' \ +--header 'authorization: ' \ +--data '{ + "rev_info": { + "user_ref": "" + } +}' +``` + +The response of the API call contains a session token that you can use with the verified identification method in your app. + +> [!WARNING] +> As a good practice, **your** app should retrieve the exchanged session token from **your** backend at app launch or any relevant app lifecycle event. + +##### Identify the verified user + +Pass the user identifier and the exchanged session token to the verified identification method: ```typescript DevRev.identifyVerifiedUser(userID: string, sessionToken: string) ``` -#### Updating the user +#### Update the user + You can update the user's information using the following method: ```typescript DevRev.updateUser(identity: Identity) ``` -> [!IMPORTANT] +> [!WARNING] > The `userID` property cannot be updated. #### Logout + You can logout of the current user by using the following method: ```typescript DevRev.logout(deviceID: string) ``` -The user will be logged out by clearing their credentials, as well as unregistering the device from receiving push notifications, and stopping the session recording. +### Identity model + +The `Identity` interface is used to provide user, organization, and account information when identifying users or updating their details. This class is used primarily with the `identifyUnverifiedUser` and `updateUser` methods. + +#### Properties + +The `Identity` class contains the following properties: + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `userRef` | `string` | ✅ | A unique identifier for the user | +| `organizationRef` | `string?` | ❌ | An identifier for the user's organization | +| `accountRef` | `string?` | ❌ | An identifier for the user's account | +| `userTraits` | `UserTraits?` | ❌ | Additional information about the user | +| `organizationTraits` | `OrganizationTraits?` | ❌ | Additional information about the organization | +| `accountTraits` | `AccountTraits?` | ❌ | Additional information about the account | + +> [!NOTE] +> The custom fields properties defined as part of the user, organization and account traits, must be configured in the DevRev web app **before** they can be used. See [Object customization](https://devrev.ai/docs/product/object-customization) for more information. + +##### User traits + +The `UserTraits` class contains detailed information about the user: + +> [!NOTE] +> All properties in `UserTraits` are optional. + +| Property | Type | Description | +|----------|------|-------------| +| `displayName` | `string?` | The displayed name of the user | +| `email` | `string?` | The user's email address | +| `fullName` | `string?` | The user's full name | +| `description` | `string?` | A description of the user | +| `customFields` | `{ [key: string]: any }` | Dictionary of custom fields configured in DevRev | -### PLuG support chat -Once user identification is complete, you can start using the chat (conversations) dialog supported by our DevRev SDK. The support chat feature can be shown as a modal screen from the top-most screen. +##### Organization traits -> [!IMPORTANT] -> This feature requires the SDK to be configured and the user to be identified, whether they are unverified or anonymous. +The `OrganizationTraits` class contains detailed information about the organization: + +> [!NOTE] +> All properties in `OrganizationTraits` are optional. + +| Property | Type | Description | +|----------|------|-------------| +| `displayName` | `string?` | The displayed name of the organization | +| `domain` | `string?` | The organization's domain | +| `description` | `string?` | A description of the organization | +| `phoneNumbers` | `string[]?` | Array of the organization's phone numbers | +| `tier` | `string?` | The organization's tier or plan level | +| `customFields` | `{ [key: string]: any }` | Dictionary of custom fields configured in DevRev | + +##### Account traits + +The `AccountTraits` class contains detailed information about the account: + +> [!NOTE] +> All properties in `AccountTraits` are optional. + +| Property | Type | Description | +|----------|------|-------------| +| `displayName` | `string?` | The displayed name of the account | +| `domains` | `string[]?` | Array of domains associated with the account | +| `description` | `string?` | A description of the account | +| `phoneNumbers` | `string[]?` | Array of the account's phone numbers | +| `websites` | `string[]?` | Array of websites associated with the account | +| `tier` | `string?` | The account's tier or plan level | +| `customFields` | `{ [key: string]: any }` | Dictionary of custom fields configured in DevRev | + +### Support chat + +Once the user identification is complete, you can start using the chat (conversations) dialog supported by our DevRev SDK. The support chat feature can be shown as a modal screen from the top-most screen. ```typescript DevRev.showSupport() ``` -#### Creating a new support conversation +#### Create a new support conversation + You can initiate a new support conversation directly from your app. This method displays the support chat screen and simultaneously creates a new conversation. ```typescript DevRev.createSupportConversation() ``` -#### In-app link handling +### In-app link handling + In certain cases, the links opened from the support chat are opened in the app instead of a browser. You can control whether the chat modal should be dismissed after the link is opened by calling the following method: ```typescript @@ -144,9 +298,10 @@ DevRev.setShouldDismissModalsOnOpenLink(value: boolean) Setting this flag to true applies the system's default behavior for opening links, which includes dismissing any DevRev modal screens to facilitate handling your own deep links. -#### In-app link callback -> [!NOTE] -> This feature is for Android only. +### In-app link callback + +> [!TIP] +> This feature is supported only on Android. For scenarios where custom handling is needed, links from the support chat can be captured with the following method: @@ -157,15 +312,14 @@ DevRev.setInAppLinkHandler((url) => { ``` ### Dynamic theme configuration -The DevRev SDK allows you to configure the theme dynamically based on the system appearance, or use the theme configured on the DevRev portal. By default, the theme will be dynamic and follow the system appearance. -```swift +The DevRev SDK allows you to configure the theme dynamically based on the system appearance, or use the theme configured on the DevRev portal. By default, the theme is dynamic and follows the system appearance. + +```typescript DevRev.setPrefersSystemTheme(value: boolean) ``` ### Analytics -> [!IMPORTANT] -> This feature requires the SDK to be configured and the user to be identified, whether they are unverified or anonymous. The DevRev SDK allows you to send custom analytic events by using a properties map. You can track these events using the following function: @@ -174,9 +328,11 @@ DevRev.trackEvent(name: string, properties?: { [key: string]: string }) ``` ### Session analytics + The DevRev SDK offers session analytics features to help you understand how users interact with your app. -#### Opting-in or out +#### Opt in or out + Session analytics features are opted-in by default, enabling them from the start. However, you can opt-out using the following method: ```typescript @@ -190,24 +346,28 @@ DevRev.resumeAllMonitoring() ``` #### Session recording + You can enable session recording to record user interactions with your app. -> [!CAUTION] +> [!NOTE] > The session recording feature is opt-out and is enabled by default. The session recording feature includes the following methods to control the recording: -- `DevRev.startRecording()`: Starts the session recording. -- `DevRev.stopRecording()`: Stops the session recording and uploads it to the portal. -- `DevRev.pauseRecording()`: Pauses the ongoing session recording. -- `DevRev.resumeRecording()`: Resumes a paused session recording. -- `DevRev.processAllOnDemandSessions()`: Stops the ongoing session recording and uploads all offline sessions on demand, including the current one. +| Method | Action | +|--------------------------------------------------------------------|-----------------------------------------------------------| +|`DevRev.startRecording()` | Starts the session recording. | +|`DevRev.stopRecording()` | Stops the session recording and uploads it to the portal. | +|`DevRev.pauseRecording()` | Pauses the ongoing session recording. | +|`DevRev.resumeRecording()` | Resumes a paused session recording. | +|`DevRev.processAllOnDemandSessions()` | Stops the ongoing user recording and sends all on-demand sessions along with the current recording. | #### Session properties + You can add custom properties to the session recording to help you understand the context of the session. The properties are defined as a map of string values. ```typescript -DevRev.addSessionProperties(properties: { [key: string]: any }) +DevRev.addSessionProperties(properties: { [key: string]: string }) ``` To clear the session properties in scenarios such as user logout or when the session ends, use the following method: @@ -216,10 +376,11 @@ To clear the session properties in scenarios such as user logout or when the ses DevRev.clearSessionProperties() ``` -#### Masking sensitive data -To protect sensitive data, the DevRev SDK provides an auto-masking feature that masks data before sending to the server. Input views such as text fields, text views, and web views are automatically masked. +#### Mask sensitive data + +To protect sensitive data, the DevRev SDK provides an auto-masking feature that masks data before sending to the server. Input views such as password text fields are automatically masked. -While the auto-masking feature may be sufficient for most situations, you can manually mark additional views as sensitive using the following method: +While the auto-masking feature is sufficient for most situations, you can manually mark additional views as sensitive using the following method: ```typescript DevRev.markSensitiveViews(tags: any[]) @@ -231,7 +392,77 @@ If any previously masked views need to be unmasked, you can use the following me DevRev.unmarkSensitiveViews(tags: any[]) ``` +For example: + +```typescript +import * as DevRev from '@devrev/sdk-react-native'; +import { View, Text, findNodeHandle } from "react-native"; +import { useRef, useEffect } from "react"; + +const YourComponent = () => { + const sensitiveViewRef = useRef(null); + const insensitiveViewRef = useRef(null); + + useEffect(() => { + // Mark sensitive view + const sensitiveId = findNodeHandle(sensitiveViewRef.current); + if (sensitiveId) { + DevRev.markSensitiveViews([sensitiveId]); + } + + // Unmark insensitive view + const insensitiveId = findNodeHandle(insensitiveViewRef.current); + if (insensitiveId) { + DevRev.unmarkSensitiveViews([insensitiveId]); + } + }, []); + + return ( + + + Sensitive content (masked in recordings) + + + Insensitive content (visible in recordings) + + + ); +}; + +export default YourComponent; +``` + +#### Mask elements inside web views + +To mark elements as sensitive inside a web view (`WebView`), apply the `devrev-mask` CSS class. To unmark them, use `devrev-unmask`. + +- Mark an element as masked: + ```html + + ``` +- Mark an element as unmasked: + ```html + + ``` + +#### User interaction tracking + +The DevRev SDK automatically tracks user interactions such as taps, swipes, and scrolls. However, in some cases you may want to disable this tracking to prevent sensitive user actions from being recorded. + +To **temporarily disable** user interaction tracking, use the following method: + +```typescript +DevRev.pauseUserInteractionTracking() +``` + +To **resume** user interaction tracking, use the following method: + +```typescript +DevRev.resumeUserInteractionTracking() +``` + #### Timers + The DevRev SDK offers a timer mechanism to measure the time spent on specific tasks, allowing you to track events such as response time, loading time, or any other duration-based metrics. The mechanism uses balanced start and stop methods, both of which accept a timer name and an optional dictionary of properties. @@ -245,41 +476,43 @@ DevRev.startTimer(name: string, properties: { [key: string]: string }) To stop a timer, use the following method: ```typescript -DevRev.stopTimer(name: string, properties: { [key: string]: string }) +DevRev.endTimer(name: string, properties: { [key: string]: string }) ``` -#### Screen tracking -The DevRev SDK offers automatic screen tracking to help you understand how users navigate through your app. Although view controllers are automatically tracked, you can manually track screens using the following method: +#### Track screens + +The DevRev SDK offers automatic screen tracking to help you understand how users navigate through your app. Although screens are automatically tracked, you can manually track screens using the following method: ```typescript -DevRev.trackScreen(name: string) +DevRev.trackScreenName(name: string) ``` -#### Screen transition tracking (Android only) -On Android, the DevRev SDK provides methods to manually track the screen transitions. +#### Manage screen transitions (Android only) -When a screen transition begins, you must call the following method: +The DevRev SDK allows tracking of screen transitions to understand the user navigation within your app. +You can manually update the state using the following methods: -```typescript -DevRev.startScreenTransition() -``` +```javascript +// Mark the transition as started. +DevRev.setInScreenTransitioning(true) -When a screen transition ends, you must call the following method: - -```typescript -DevRev.endScreenTransition() +// Mark the transition as ended. +DevRev.setInScreenTransitioning(false) ``` ### Push notifications + You can configure your app to receive push notifications from the DevRev SDK. The SDK is able to handle push notifications and execute actions based on the notification's content. -The DevRev backend sends push notifications to your app to notify users about new messages in the PLuG support chat. +The DevRev backend sends push notifications to your app to notify users about new messages in the support chat. #### Configuration -To receive push notifications, you need to configure your DevRev organization by following the instructions in the [push notifications](https://developer.devrev.ai/public/sdks/mobile/push-notification) section. + +To receive push notifications, you need to configure your DevRev organization by following the instructions in the [push notifications](https://developer.devrev.ai/sdks/mobile/push-notifications) section. #### Register for push notifications -> [!IMPORTANT] + +> [!TIP] > Push notifications require that the SDK has been configured and the user has been identified, to ensure delivery to the correct user. The DevRev SDK offers a method to register your device for receiving push notifications. You can register for push notifications using the following method: @@ -288,9 +521,10 @@ The DevRev SDK offers a method to register your device for receiving push notifi DevRev.registerDeviceToken(deviceToken: string, deviceID: string) ``` -On Android devices, the `deviceToken` should be the Firebase Cloud Messaging (FCM) token value, while on iOS devices, it should be the Apple Push Notification Service (APNs) token. +On Android devices, the `deviceToken` should be the Firebase Cloud Messaging (FCM) token value, while on iOS devices, it should be the Apple Push Notification service (APNs) token. #### Unregister from push notifications + If your app no longer needs to receive push notifications, you can unregister the device. Use the following method to unregister the device: @@ -301,8 +535,10 @@ DevRev.unregisterDevice(deviceID: string) The method requires the device identifier, which should be the same as the one used when registering the device. -#### Processing push notifications +#### Handle push notifications + ##### Android + On Android, notifications are implemented as data messages to offer flexibility. However, this means that automatic click processing isn't available. To handle notification clicks, developers need to intercept the click event, extract the payload, and pass it to a designated method for processing. This custom approach enables tailored notification handling in Android applications. To process the notification, use the following method: @@ -313,7 +549,7 @@ DevRev.processPushNotification(payload: string) Here, the `message` object from the notification payload needs to be passed to this function. -###### Example +For example: ```typescript const notificationPayload = { @@ -327,50 +563,111 @@ DevRev.processPushNotification(JSON.stringify(messageJson)); ``` ##### iOS -On iOS devices, you must pass the received push notification payload to the DevRev SDK for processing. The SDK will then handle the notification and execute the necessary actions. + +On iOS devices, you must pass the received push notification payload to the DevRev SDK for processing. The SDK handles the notification and executes the necessary actions. ```typescript DevRev.processPushNotification(payload: string) ``` -###### Example +For example: ```typescript DevRev.processPushNotification(JSON.stringify(payload)); ``` -## Sample app +## Sample app (without framework) -A sample app with use cases for the DevRev React Native plugin has been provided as a part of our [public repository](https://github.com/devrev/devrev-sdk-react-native). To set up and run the sample app: +A sample app with use cases for the DevRev SDK for React Native has been provided as a part of our [public repository](https://github.com/devrev/devrev-sdk-react-native). To set up and run the sample app, follow these steps: 1. Go to the sample directory: - ```sh - cd sample + ```bash + cd sample/react-native ``` - -2. Install dependencies: - ```sh +2. Install the dependencies: + ```bash yarn install ``` - 3. For iOS, run: - ```sh - cd ios - pod install + ```bash + pod install --project-directory=ios --repo-update ``` - -4. Run the app on Android or iOS using: - ```sh +4. Start the React Native development server: + ```bash npx react-native start ``` +5. Run the app on Android using: + ```bash + npx react-native run-android + ``` + or open the `android` directory in Android Studio and run the app from there. +6. Run the app on iOS using: + ```bash + npx react-native run-ios + ``` + or open `ios/DevRevSDKSampleRN.xcworkspace` in Xcode and run the app from there. -Or open `android` directory in Android Studio or `ios/DevRevSDKSample.xcworkspace` in Xcode and run the app from there. +## Sample app (Expo) + +A sample app with use cases for the DevRev SDK for Expo has been provided as a part of our [public repository](https://github.com/devrev/devrev-sdk-react-native). To set up and run the sample app, follow these steps: + +1. Go to the sample directory: + ```bash + cd sample/expo + ``` +2. Install the dependencies: + ```bash + yarn install + ``` +3. Run clean and prebuild: + ```bash + npx expo prebuild --clean + ``` +4. Run the app on Android using: + ```bash + npx expo run:android + ``` + OR open the `android` directory in Android Studio and run the app. +5. Run the app on iOS: + ```bash + npx expo run:ios + ``` + OR open `ios/DevRevSDKSample.xcworkspace` in Xcode and run the app. ## Troubleshooting -- **Issue**: Support chat won't show. - **Solution**: Ensure you have correctly called one of the identification methods: `DevRev.identifyUnverifiedUser(...)`, `DevRev.identifyVerifiedUser(...)`, or `DevRev.identifyAnonymousUser(...)`. + +- **Issue**: Support chat doesn't show. + **Solution**: Ensure you have correctly called one of the identification methods: `DevRev.identifyUnverifiedUser(...)` or `DevRev.identifyVerifiedUser(...)`. - **Issue**: Not receiving push notifications. - **Solution**: Ensure that your app is configured to receive push notifications and that your device is registered with the DevRev SDK. + **Solution**: Ensure that your app is configured to receive push notifications and that your device is registered with the DevRev SDK. + +### ProGuard (Android only) + +When trying to build your app for Android with ProGuard enabled, refer to these common issues and their solutions. + +> [!NOTE] +> You can always refer to the [Android ProGuard documentation](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization#proguard) for more information. + +- **Issue**: Missing class `com.google.android.play.core.splitcompat.SplitCompatApplication`. + **Solution**: Add the following line to your `proguard-rules.pro` file: + ```proguard + -dontwarn com.google.android.play.core.** + ``` + +- **Issue**: Missing class issue due to transitive Flutter dependencies. + **Solution**: Add the following lines to your `proguard-rules.pro` file: + ```proguard + -keep class io.flutter.** { *; } + -keep class io.flutter.plugins.** { *; } + -keep class GeneratedPluginRegistrant { *; } + ``` + +- **Issue**: Missing class `org.s1f4j.impl.StaticLoggerBinder`. + **Solution**: Add the following line to your `proguard-rules.pro` file: + ```proguard + -dontwarn org.slf4j.impl.StaticLoggerBinder + ``` ## Migration Guide + If you are migrating from the legacy UserExperior SDK to the new DevRev SDK, please refer to the [Migration Guide](./MIGRATION.md) for detailed instructions and feature equivalence. diff --git a/devrev-sdk-react-native-2.2.1.tgz b/devrev-sdk-react-native-2.2.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..9b32ad1058309424f8455c0dc26e2c2406ac1bda GIT binary patch literal 39661 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 literal 0 HcmV?d00001 diff --git a/sample/README.md b/sample/README.md deleted file mode 100644 index 12470c3..0000000 --- a/sample/README.md +++ /dev/null @@ -1,79 +0,0 @@ -This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). - -# Getting Started - ->**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. - -## Step 1: Start the Metro Server - -First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. - -To start Metro, run the following command from the _root_ of your React Native project: - -```bash -# using npm -npm start - -# OR using Yarn -yarn start -``` - -## Step 2: Start your Application - -Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: - -### For Android - -```bash -# using npm -npm run android - -# OR using Yarn -yarn android -``` - -### For iOS - -```bash -# using npm -npm run ios - -# OR using Yarn -yarn ios -``` - -If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. - -This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. - -## Step 3: Modifying your App - -Now that you have successfully run the app, let's modify it. - -1. Open `App.tsx` in your text editor of choice and edit some lines. -2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! - - For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! - -## Congratulations! :tada: - -You've successfully run and modified your React Native App. :partying_face: - -### Now what? - -- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). -- If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). - -# Troubleshooting - -If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. - -# Learn More - -To learn more about React Native, take a look at the following resources: - -- [React Native Website](https://reactnative.dev) - learn more about React Native. -- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. -- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. -- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. -- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. diff --git a/sample/android/app/google-services.json b/sample/android/app/google-services.json deleted file mode 100644 index aca92f3..0000000 --- a/sample/android/app/google-services.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "project_info": { - "project_number": "1001237930288", - "firebase_url": "https://ue-wallet.firebaseio.com", - "project_id": "ue-wallet", - "storage_bucket": "ue-wallet.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:1001237930288:android:7b2c19eb3181f8c4ede8f5", - "android_client_info": { - "package_name": "ai.devrev.sdk.bridge.reactnative.sample" - } - }, - "oauth_client": [ - { - "client_id": "1001237930288-2ak63an8u4p9jjbtdusd90f04su3pdm9.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyBNEHH0CBGTCSh1R-QyeQC4t7U0HmDii9g" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "1001237930288-2ak63an8u4p9jjbtdusd90f04su3pdm9.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "1001237930288-hotgoru17eoib0brnplbtcd73h5c4oug.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "com.ue.wallet" - } - } - ] - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:1001237930288:android:70f638c00cf42f64ede8f5", - "android_client_info": { - "package_name": "com.userexperior.uewallet" - } - }, - "oauth_client": [ - { - "client_id": "1001237930288-2ak63an8u4p9jjbtdusd90f04su3pdm9.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyBNEHH0CBGTCSh1R-QyeQC4t7U0HmDii9g" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "1001237930288-2ak63an8u4p9jjbtdusd90f04su3pdm9.apps.googleusercontent.com", - "client_type": 3 - }, - { - "client_id": "1001237930288-hotgoru17eoib0brnplbtcd73h5c4oug.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "com.ue.wallet" - } - } - ] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index a2f5908281d070150700378b64a84c7db1f97aa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3056 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 axPAxe%7rDusX+w*00006jj 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⁣ zwiG+x#%j}{0gU~J8;<|!B1@-XaB@{KORFwrYg_8rOv({b0EO#DbeQRm;B6_9=mXGf z-x|VL{zd`)#@yN}HkCSJbjbNlE|zL3Wm9Q8HY`sV)}3%pgN>cL^67{Z;PPL(*wT8N zUjXU{@|*hvm}({wsAC=x0^ok0%UAz0;sogW{B!nDqk|JJ5x~4NfTDgP49^zeu`csl?5mY@JdQdISc zFs!E{^grmkLnUk9 zny~m)1vws@5BFI<-0Tuo2JWX(0v`W|t(wg;s--L47WTvTMz-8l#TL^=OJNRS2?_Qj z3AKT+gvbyBi#H*-tJ%tWD|>EV3wy|8qxfzS!5RW;Jpl5*zo&^UBU=fG#2}UvRyNkK zA06Dy9;K1ca@r2T>yThYgI!ont$(G{6q#2QT+00r_x0(b)gsE`lBB?2gr55gq^D3Fi&p%E(p9>U%bv zkg1Jco(RbyTX7FDHOnl7-O@ zI$AaIl?9NJKPm(WiBP`1-#CB1QzU>&hKm)fpa5DKE{2$X0hGz-0uZ?cyTk(YC!Y&| zL=1VrNERSA5NA2jq7FACfX4JfPyj5XXl1yv0>~s;eF7L2$>&oMqeTFT2m$y7FlkON z_yurD1yIOvA;5C6016pyxBznGUt0kJ&k5r#;&>Jow`r)sp9R~PmK~lz$3xH%LT*1U zJdOyABZ3!FvNoR*vN$5ykHS8f`jA4zV+|L}i1C4`B2c{R0;UdYxaU|H)2avz@ z=mEYc|2S<+(B2Tj+FkX+2D+yFI!k9lWMA61DJ{)e;lum$(;O87?vGJJe!KtK04+N_ zI*P~t@dUb>9Xh{dbyl{-ZQ(UMgz7$|QfL5XSPkskt^NgctYC#;4WcZB1@%@wy@2t3 z2z0DI7&%b$*Aw~abe?GxE`ez@+6hOh-6*8fHRV{1os$EL@}uUZeG4h1&Be`98q*7j z=3-v+lhIjfWVo12!<>%V^a6lTgW3+_#W6n|p*~==zOH7z$0{LSZk(Tpd7EaD04hnA zL;#fxS0aD{`5^&D`}>0Uq?byDD-l2=!wm_bLcUl4gc(% za1p|itVANvFF>hghAS07Im1;IK;|b*W)}VDyI;BIp2=K*yu2a)j?B|f<44NI$NbmJ z#dE0>jI$fMr&@>4kN8MLFb4&2O9fEKaQg%(QO$4_1rVQywG^CmBLh#}_7gKW3vd?| z2?1^&KWq8}8I^_S0|)MowU_pw$q@nl@Nkn$z>BQq_KA^9yaR`(R3u{{Ig;cwt z@AJ^{ODQCm^neroM9nKNUAXi9RCK`OsP_LuR0PUR(YZCCX5dNF6VzcoK&=b^r`W?ltt|*F zpkoae%ZT{C1h~EcFui~b7fF`vb<<~j_VquuUA$}QqIKYELPp#;{u?q8Dz}WAG-(3; zjrm$i%7UbyZMM(Y{>!uJ#vNB?R~B{6Htp=>e*<{fQQ5W7V(1coCWlOON!MzZxhum| ztZBQpGR z;~#ur^&PockKdV{Q6R>o`Pl{0x!DEbpZ7y9Y;*ZvE!*gU`V1W3znva{f=?WO5I&>B z&hw6}tjECtaghm5z|C#%M;Yf_*pI^};h}Vl=^r9EN=tVDj86D;C$jIJ?K7VP+00000NkvXXu0mjf D5i!M* diff --git a/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 459ca609d3ae0d3943ab44cdc27feef9256dc6d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7098 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 diff --git a/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 8ca12fe024be86e868d14e91120a6902f8e88ac6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6464 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 B0*L?s diff --git a/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e19b410a1b15ff180f3dacac19395fe3046cdec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10676 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 diff --git a/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 4c19a13c239cb67b8a2134ddd5f325db1d2d5bee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15523 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 BIP(Ai diff --git a/sample/android/app/src/main/res/values/strings.xml b/sample/android/app/src/main/res/values/strings.xml deleted file mode 100644 index ec18647..0000000 --- a/sample/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - DevRevSDKSample - diff --git a/sample/android/build.gradle b/sample/android/build.gradle deleted file mode 100644 index 0675088..0000000 --- a/sample/android/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -buildscript { - ext { - buildToolsVersion = "35.0.0" - minSdkVersion = 24 - compileSdkVersion = 35 - targetSdkVersion = 35 - ndkVersion = "26.1.10909125" - kotlinVersion = "2.0.21" - } - repositories { - google() - mavenCentral() - jcenter() - mavenLocal() - } - dependencies { - classpath("com.android.tools.build:gradle") - classpath("com.facebook.react:react-native-gradle-plugin") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") - classpath("com.google.gms:google-services:4.4.2") - } -} - -rootProject.allprojects { - repositories { - google() - mavenCentral() - mavenLocal() - maven { - url "https://s01.oss.sonatype.org/content/repositories/staging/" - } - } -} - -apply plugin: "com.facebook.react.rootproject" diff --git a/sample/android/settings.gradle b/sample/android/settings.gradle deleted file mode 100644 index 08e07cf..0000000 --- a/sample/android/settings.gradle +++ /dev/null @@ -1,7 +0,0 @@ -pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } -plugins { id("com.facebook.react.settings") } -extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } -rootProject.name = 'DevRevSDKSample' - -include ':app' -includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/sample/app.json b/sample/app.json deleted file mode 100644 index 654a6a0..0000000 --- a/sample/app.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "DevRevSDKSample", - "displayName": "DevRevSDKSample" -} diff --git a/sample/expo/.gitignore b/sample/expo/.gitignore new file mode 100644 index 0000000..45f5589 --- /dev/null +++ b/sample/expo/.gitignore @@ -0,0 +1,197 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Expo +.expo/ +dist/ +web-build/ +.expo-shared/ + +# Native build outputs +android/ +ios/ + +# But keep source files and configuration +!android/app/src/ +!android/app/build.gradle +!android/app/proguard-rules.pro +!android/app/debug.keystore +!android/build.gradle +!android/gradle.properties +!android/settings.gradle +!android/gradle/ +!android/gradlew* +!ios/*.plist +!ios/*.entitlements +!ios/Podfile* +!ios/Sources/ + +# OSX +.DS_Store + +# Xcode +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ +*.xccheckout +*.moved-aside +DerivedData/ +*.hmap +*.ipa +*.xcuserstate +*.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +!*.xcworkspace/xcshareddata/ + +# CocoaPods +ios/Pods/ +ios/.symlinks/ +Podfile.lock + +# Android +android/.gradle/ +android/app/build/ +android/build/ +android/gradle/ +android/gradlew +android/gradlew.bat +android/local.properties +android/.idea/ +android/*.iml +android/.cxx/ +*.keystore +!debug.keystore +*.hprof + +# React Native Metro +.metro-health-check* + +# BUCK +buck-out/ +\.buckd/ +*.keystore +!debug.keystore + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots +*/fastlane/test_output + +# Bundle artifacts +*.jsbundle + +# CocoaPods +/ios/Pods/ + +# Expo +.expo/ +dist/ +web-build/ +.expo-shared/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# Flipper +android/app/src/debug/ +ios/Flipper/ + +# Misc +*.swp +*.swo +.yarn/ +*~ diff --git a/sample/expo/App.tsx b/sample/expo/App.tsx new file mode 100644 index 0000000..8bc2a8e --- /dev/null +++ b/sample/expo/App.tsx @@ -0,0 +1,32 @@ +import * as DevRev from '@devrev/sdk-react-native'; +import { Linking } from 'react-native'; +import PushNotificationsService from './PushNotificationsService'; +import React from 'react'; +import firebase from '@react-native-firebase/app'; +import { NavigationContainer } from '@react-navigation/native'; +import Navigator from './navigator/Navigator'; + +const App = () => { + React.useEffect(() => { + try { + firebase.app(); + DevRev.configure('YOUR_APP_ID'); + DevRev.setShouldDismissModalsOnOpenLink(true); + DevRev.setInAppLinkHandler((url) => { + Linking.openURL(url); + }); + + PushNotificationsService.initialize(); + } catch (error) { + console.log(error); + } + }, []); + + return ( + + + + ); +}; + +export default App; diff --git a/sample/expo/GoogleService-Info.plist b/sample/expo/GoogleService-Info.plist new file mode 100644 index 0000000..4f25e41 --- /dev/null +++ b/sample/expo/GoogleService-Info.plist @@ -0,0 +1,16 @@ + + + + + API_KEY + DUMMY_API_KEY + CLIENT_ID + DUMMY_CLIENT_ID + GOOGLE_APP_ID + 1:1234567890:ios:abcdef123456 + PROJECT_ID + dummy-project + BUNDLE_ID + ai.devrev.sdk.bridge.reactnative.expo.sample + + diff --git a/sample/expo/PushNotificationsService.tsx b/sample/expo/PushNotificationsService.tsx new file mode 100644 index 0000000..ff1ba2a --- /dev/null +++ b/sample/expo/PushNotificationsService.tsx @@ -0,0 +1,312 @@ +import messaging, { + FirebaseMessagingTypes, +} from '@react-native-firebase/messaging'; +import { Platform } from 'react-native'; +import * as DevRev from '@devrev/sdk-react-native'; +import DeviceInfo from 'react-native-device-info'; +import * as Notifications from 'expo-notifications'; + +interface NotificationActor { + display_handle: string; + display_id: string; + display_name: string; + full_name: string; + id: string; + id_v1: string; + state: string; + thumbnail: string; + type: string; +} + +interface NotificationDevice { + android: { + channel: string; + channel_id: string; + }; + device_type: string; +} + +interface NotificationItem { + display_id: string; + id: string; + id_v1: string; + target: string; + title: string; + type: string; +} + +interface DevRevNotification { + actor: NotificationActor; + body: string; + device: NotificationDevice; + id: string; + item: NotificationItem; + notification_id: string; + notification_id_v1: string; + source_id: string; + state: string; + subtitle: string; + title: string; + type: string; + url: string; +} + +interface StaleNotificationData { + stale_notification_ids?: string[]; +} + +interface SilentNotificationData { + silent?: string; +} + +const isAndroid = Platform.OS === 'android'; + +Notifications.setNotificationHandler({ + handleNotification: async () => { + return { + shouldShowAlert: true, + shouldPlaySound: true, + shouldSetBadge: true, + }; + }, +}); + +const handleSilentNotification = async (data: SilentNotificationData) => { + try { + if (data.silent) { + const silentData: StaleNotificationData = JSON.parse(data.silent); + if (silentData.stale_notification_ids?.length) { + console.log( + 'Handling stale notifications:', + silentData.stale_notification_ids + ); + // Cancel stale notifications + for (const notificationId of silentData.stale_notification_ids) { + await Notifications.cancelScheduledNotificationAsync(notificationId); + } + } + } + } catch (error) { + console.error('Error handling silent notification:', error); + } +}; + +// Create default channel for Android +const createDefaultChannel = async (): Promise => { + if (Platform.OS === 'android') { + await Notifications.setNotificationChannelAsync('default', { + name: 'Default Channel', + importance: Notifications.AndroidImportance.HIGH, + vibrationPattern: [0, 250, 250, 250], + lightColor: '#FF231F7C', + }); + } +}; + +// Request user permission for notifications +const requestUserPermission = async (): Promise => { + // Check permissions + const { status: existingStatus } = await Notifications.getPermissionsAsync(); + let finalStatus = existingStatus; + + if (existingStatus !== 'granted') { + const { status } = await Notifications.requestPermissionsAsync(); + finalStatus = status; + } + + if (finalStatus !== 'granted') { + console.error('Failed to get the device token for push notifications!'); + return false; + } + return true; +}; + +const getDeviceToken = async (): Promise => { + try { + console.log('Device token: Starting token retrieval...'); + + if (isAndroid) { + console.log('Device token: Getting Android token...'); + const token = await Notifications.getDevicePushTokenAsync(); + console.log('Device token: Android token received:', token); + return token.data; + } else { + console.log('Device token: Getting iOS token...'); + const token = await Notifications.getDevicePushTokenAsync(); + console.log('Device token: iOS token received:', token); + return token.data; + } + } catch (error) { + console.error('Device token: Error in getDeviceToken:', error); + throw error; + } +}; + +// Register device with DevRev +const registerDevice = async (): Promise => { + try { + console.log('Starting device registration...'); + const hasPermission = await requestUserPermission(); + console.log('Permission status:', hasPermission); + + if (hasPermission) { + console.log('Getting device ID...'); + const deviceId = await DeviceInfo.getUniqueId(); + console.log('Device ID:', deviceId); + console.log('Getting device token...'); + const token = await getDeviceToken(); + console.log('Token received:', token); + + if (token) { + console.log('Registering with DevRev...'); + await DevRev.registerDeviceToken(token, deviceId); + console.log('Successfully registered with DevRev'); + } else { + console.warn('Failed to get device token'); + } + } else { + console.warn('Notification permission not granted'); + } + } catch (error) { + console.error('Error registering device:', error); + throw error; + } +}; + +// Display notification +const displayNotification = async ( + remoteMessage: FirebaseMessagingTypes.RemoteMessage +): Promise => { + try { + if (remoteMessage.data?.silent) { + await handleSilentNotification(remoteMessage.data); + return; + } + + let notification: DevRevNotification | null = null; + if (remoteMessage.data?.message) { + try { + notification = JSON.parse(remoteMessage.data.message as string); + } catch (parseError) { + console.error('Error parsing notification:', parseError); + } + } + + if (!notification) { + console.warn('No valid notification data'); + return; + } + + await Notifications.scheduleNotificationAsync({ + content: { + title: notification.title, + body: notification.body, + data: remoteMessage.data, + }, + trigger: null, // null means show immediately + }); + } catch (error) { + console.error('Error displaying notification:', error); + } +}; + +// Handle notification press +const handleNotificationPress = async (notification: any): Promise => { + console.log('Notification pressed:', notification); + + if (notification.notification?.request) { + console.log( + 'Unmarshalling: ', + notification.notification.request.content.data.message + ); + DevRev.processPushNotification(notification.data.message); + return; + } + + if (notification.data?.message) { + DevRev.processPushNotification(notification.data.message as string); + return; + } + + console.warn('Unexpected notification format:', notification); +}; + +// Setup notification listeners +const setupNotificationListeners = (): void => { + // Handle token refresh + messaging().onTokenRefresh(async (token) => { + try { + const deviceId = await DeviceInfo.getUniqueId(); + DevRev.registerDeviceToken(token, deviceId); + } catch (error) { + console.error('Error handling token refresh:', error); + } + }); + + // Handle background messages + messaging().setBackgroundMessageHandler(async (remoteMessage) => { + console.log('BACKGROUND MESSAGE: ', remoteMessage); + if (Platform.OS === 'android') { + await displayNotification(remoteMessage); + } + }); + + // Handle foreground messages + messaging().onMessage(async (remoteMessage) => { + console.log('FOREGROUND MESSAGE: ', remoteMessage); + await displayNotification(remoteMessage); + }); + + // Handle notification interaction + Notifications.addNotificationResponseReceivedListener((response) => { + handleNotificationPress(response); + }); + + // Handle foreground notifications + Notifications.addNotificationReceivedListener((notification) => { + console.log('Foreground notification received:', notification); + }); + + // Check if app was opened from a notification + messaging() + .getInitialNotification() + .then(async (remoteMessage) => { + if (remoteMessage) { + console.log('App opened from quit state:', remoteMessage); + await handleNotificationPress(remoteMessage); + } + }) + .catch((error) => { + console.error('Error checking initial notification:', error); + }); +}; + +// Cancel all notifications +const cancelAllNotifications = async (): Promise => { + try { + await Notifications.cancelAllScheduledNotificationsAsync(); + } catch (error) { + console.error('Error canceling notifications:', error); + } +}; + +// Initialize notification service +const initializeNotifications = async (): Promise => { + try { + if (isAndroid) { + await createDefaultChannel(); + } + setupNotificationListeners(); + } catch (error) { + console.error('Error initializing notifications:', error); + } +}; + +export const PushNotificationsService = { + initialize: initializeNotifications, + register: registerDevice, + displayNotification, + cancelAllNotifications, +}; + +export default PushNotificationsService; diff --git a/sample/expo/app.json b/sample/expo/app.json new file mode 100644 index 0000000..d33b3cd --- /dev/null +++ b/sample/expo/app.json @@ -0,0 +1,74 @@ +{ + "expo": { + "name": "DevRevSDK (Expo)", + "slug": "devrev-sdk-expo-sample", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "light", + "splash": { + "image": "./assets/splash-icon.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + }, + "ios": { + "supportsTablet": true, + "bundleIdentifier": "ai.devrev.sdk.bridge.reactnative.expo.sample", + "config": { + "usesNonExemptEncryption": false + }, + "infoPlist": { + "UIBackgroundModes": [ + "remote-notification" + ], + "UIUserInterfaceStyle": "light", + "NSAppTransportSecurity": { + "NSAllowsArbitraryLoads": true + } + }, + "googleServicesFile": "./GoogleService-Info.plist" + }, + "android": { + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#ffffff" + }, + "package": "ai.devrev.sdk.bridge.reactnative.expo.sample", + "googleServicesFile": "./google-services.json", + "permissions": [ + "NOTIFICATIONS" + ] + }, + "web": { + "favicon": "./assets/favicon.png" + }, + "plugins": [ + [ + "../../app.plugin.js" + ], + [ + "expo-notifications", + { + "icon": "./assets/splash-icon.png", + "enableBackgroundRemoteNotifications": true + } + ], + [ + "expo-build-properties", + { + "ios": { + "useFrameworks": "static" + }, + "podfileProperties": { + "use_modular_headers!": true + } + } + ] + ], + "extra": { + "eas": { + "projectId": "501ce10b-b735-428d-8429-6616662accd7" + } + } + } +} diff --git a/sample/expo/assets/adaptive-icon.png b/sample/expo/assets/adaptive-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..03d6f6b6c6727954aec1d8206222769afd178d8d GIT binary patch literal 17547 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 literal 0 HcmV?d00001 diff --git a/sample/expo/assets/icon.png b/sample/expo/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a0b1526fc7b78680fd8d733dbc6113e1af695487 GIT binary patch literal 22380 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 literal 0 HcmV?d00001 diff --git a/sample/expo/assets/sample.html b/sample/expo/assets/sample.html new file mode 100644 index 0000000..5204157 --- /dev/null +++ b/sample/expo/assets/sample.html @@ -0,0 +1,56 @@ + + + + + + Sample Page with Masking + + + +

Sample HTML for WebView

+

This page demonstrates content masking within a WebView.

+
+ + +

This field (devrev-mask) is masked in recordings.

+
+ +
+ + +

This password field (type="password") is automatically masked by the SDK without requiring the devrev-mask class.

+
+ +
+ + +

This field (devrev-unmask) is visible in recordings.

+
+ +
+ + +

This number (devrev-mask) is masked in recordings.

+
+ +

This paragraph contains a masked phrase and an unmasked phrase within the text.

+ +
+

This entire div and its contents are masked:

+ + +
+ +
+ + + diff --git a/sample/expo/assets/splash-icon.png b/sample/expo/assets/splash-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..03d6f6b6c6727954aec1d8206222769afd178d8d GIT binary patch literal 17547 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 void; + buttonText: string; + buttonStyle?: ViewStyle; + textStyle?: TextStyle; +} + +const TouchableOpacityButton: React.FC = ({ + onPress, + buttonText, + buttonStyle, + textStyle, +}) => { + return ( + + {buttonText} + + ); +}; + +const styles = StyleSheet.create({ + button: { + margin: 4, + padding: 8, + paddingLeft: 4, + backgroundColor: '#FFFFFF', + width: '100%', + alignItems: 'flex-start', + }, + buttonText: { + color: '#000000', + fontSize: 16, + textAlign: 'left', + }, +}); + +export default TouchableOpacityButton; diff --git a/sample/expo/eas.json b/sample/expo/eas.json new file mode 100644 index 0000000..9a6f64d --- /dev/null +++ b/sample/expo/eas.json @@ -0,0 +1,21 @@ +{ + "cli": { + "version": ">= 14.4.1", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal" + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": {} + } +} diff --git a/sample/expo/google-services.json b/sample/expo/google-services.json new file mode 100644 index 0000000..375296b --- /dev/null +++ b/sample/expo/google-services.json @@ -0,0 +1,18 @@ +{ + "project_info": { + "project_number": "1234567890", + "project_id": "dummy-project" + }, + "client": [{ + "client_info": { + "mobilesdk_app_id": "1:1234567890:android:abcdef123456", + "android_client_info": { + "package_name": "ai.devrev.sdk.bridge.reactnative.expo.sample" + } + }, + "api_key": [{ + "current_key": "DUMMY_API_KEY" + }] + }], + "configuration_version": "1" +} diff --git a/sample/expo/index.ts b/sample/expo/index.ts new file mode 100644 index 0000000..1d6e981 --- /dev/null +++ b/sample/expo/index.ts @@ -0,0 +1,8 @@ +import { registerRootComponent } from 'expo'; + +import App from './App'; + +// registerRootComponent calls AppRegistry.registerComponent('main', () => App); +// It also ensures that whether you load the app in Expo Go or in a native build, +// the environment is set up appropriately +registerRootComponent(App); diff --git a/sample/expo/metro.config.js b/sample/expo/metro.config.js new file mode 100644 index 0000000..2a7734c --- /dev/null +++ b/sample/expo/metro.config.js @@ -0,0 +1,38 @@ +// Learn more https://docs.expo.io/guides/customizing-metro +const { getDefaultConfig } = require('expo/metro-config'); +const path = require('path'); + +const config = getDefaultConfig(__dirname); + +// npm v7+ will install ../node_modules/react and ../node_modules/react-native because of peerDependencies. +// To prevent the incompatible react-native between ./node_modules/react-native and ../node_modules/react-native, +// excludes the one from the parent folder when bundling. +config.resolver.blockList = [ + ...Array.from(config.resolver.blockList ?? []), + new RegExp(path.resolve('..', 'node_modules', 'react')), + new RegExp(path.resolve('..', 'node_modules', 'react-native')), +]; + +config.resolver.nodeModulesPaths = [ + path.resolve(__dirname, './node_modules'), + path.resolve(__dirname, '../node_modules'), + path.resolve(__dirname, '../../node_modules'), +]; + +config.resolver.extraNodeModules = { + 'devrev-sdk-react-native': '../..', +}; + +config.watchFolders = [ + path.resolve(__dirname, '..'), + path.resolve(__dirname, '../..'), +]; + +config.transformer.getTransformOptions = async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, +}); + +module.exports = config; diff --git a/sample/expo/navigator/Navigator.tsx b/sample/expo/navigator/Navigator.tsx new file mode 100644 index 0000000..466764b --- /dev/null +++ b/sample/expo/navigator/Navigator.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import { createStackNavigator } from '@react-navigation/stack'; +import IdentificationScreen from '../screens/IdentificationScreen'; +import PushNotificationsScreen from '../screens/PushNotificationsScreen'; +import SessionAnalyticsScreen from '../screens/SessionAnalyticsScreen'; +import SupportChatScreen from '../screens/SupportChatScreen'; +import HomeScreen from '../screens/HomeScreen'; +import DelayedScreen from '../screens/DelayScreen'; +import WebViewScreen from '../screens/WebViewScreen'; +import FlatListScreen from '../screens/FlatListScreen'; +import { TouchableOpacity, StyleSheet, Text } from 'react-native'; + +export type RootStackParamList = { + Home: undefined; + Identification: undefined; + PushNotifications: undefined; + SessionAnalytics: undefined; + SupportChat: undefined; + DelayScreen: undefined; + WebViewScreen: undefined; + FlatListScreen: undefined; +}; + +const Stack = createStackNavigator(); + +const screens = [ + { name: 'Home', component: HomeScreen, title: 'DevRev SDK' }, + { + name: 'Identification', + component: IdentificationScreen, + title: 'Identification', + }, + { + name: 'PushNotifications', + component: PushNotificationsScreen, + title: 'Push Notifications', + }, + { + name: 'SessionAnalytics', + component: SessionAnalyticsScreen, + title: 'Session Analytics', + }, + { + name: 'SupportChat', + component: SupportChatScreen, + title: 'Support Chat', + }, + { + name: 'DelayScreen', + component: DelayedScreen, + title: 'Delayed Screen', + }, + { + name: 'WebViewScreen', + component: WebViewScreen, + title: 'Web View', + }, + { + name: 'FlatListScreen', + component: FlatListScreen, + title: 'Large Scrollable List', + }, +] as const; + +const createScreen = ( + name: keyof RootStackParamList, + component: React.ComponentType, + title: string, + index: number +) => ( + ({ + title, + headerRight: () => ( + navigation.replace(name)} + style={styles.refreshButton} + > + + + ), + })} + /> +); + +export default function Navigator() { + return ( + + {screens.map((screen, index) => + createScreen(screen.name, screen.component, screen.title, index) + )} + + ); +} + +const styles = StyleSheet.create({ + refreshButton: { + marginRight: 20, + }, + refreshIcon: { + fontSize: 24, + fontWeight: 'bold', + transform: [{ rotate: '90deg' }], + }, +}); diff --git a/sample/expo/package.json b/sample/expo/package.json new file mode 100644 index 0000000..3bbe00b --- /dev/null +++ b/sample/expo/package.json @@ -0,0 +1,49 @@ +{ + "name": "@devrev/sdk-react-native-expo-sample", + "version": "2.2.0", + "main": "index.ts", + "description": "DevRev SDK for Expo sample app.", + "scripts": { + "start": "expo start", + "android": "expo run:android", + "ios": "expo run:ios", + "web": "expo start --web", + "prebuild": "expo prebuild --clean" + }, + "dependencies": { + "@devrev/sdk-react-native": "workspace:*", + "@react-native-community/cli-server-api": "^17.0.0", + "@react-native-firebase/app": "^21.7.1", + "@react-native-firebase/installations": "^21.0.0", + "@react-native-firebase/messaging": "^21.7.1", + "@react-navigation/native": "^7.0.15", + "@react-navigation/native-stack": "^7.3.14", + "@react-navigation/stack": "^7.1.2", + "expo": "^53.0.0", + "expo-build-properties": "~0.14.8", + "expo-firebase-messaging": "^2.0.0", + "expo-notifications": "~0.31.4", + "expo-system-ui": "~5.0.11", + "react": "19.0.0", + "react-native": "0.79.5", + "react-native-device-info": "^14.0.2", + "react-native-gesture-handler": "~2.24.0", + "react-native-safe-area-context": "5.4.0", + "react-native-screens": "~4.11.1", + "react-native-webview": "13.13.5" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@types/react": "~19.0.10", + "typescript": "~5.8.3" + }, + "private": true, + "expo": { + "autolinking": { + "nativeModulesDir": "../../", + "ios": { + "deploymentTarget": "15.1" + } + } + } +} diff --git a/sample/expo/screens/DelayScreen.tsx b/sample/expo/screens/DelayScreen.tsx new file mode 100644 index 0000000..54ee81b --- /dev/null +++ b/sample/expo/screens/DelayScreen.tsx @@ -0,0 +1,30 @@ +import React, { useEffect } from 'react'; +import { Text, View, StyleSheet } from 'react-native'; +import * as DevRev from '@devrev/sdk-react-native'; + +const DelayScreen: React.FC = () => { + useEffect(() => { + try { + DevRev.endScreenTransition(); + } catch (error) { + console.error('Error ending screen transition:', error); + } + }, []); + + return ( + + Delayed Screen + This screen opened after a 2-second delay + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); + +export default DelayScreen; diff --git a/sample/expo/screens/FlatListScreen.tsx b/sample/expo/screens/FlatListScreen.tsx new file mode 100644 index 0000000..c6f810b --- /dev/null +++ b/sample/expo/screens/FlatListScreen.tsx @@ -0,0 +1,86 @@ +import React, { useRef } from 'react'; +import { View, Text, FlatList, StyleSheet, findNodeHandle } from 'react-native'; +import * as DevRev from '@devrev/sdk-react-native'; + +const DATA = Array.from({ length: 100 }, (_, i) => ({ + id: i.toString(), + title: `Item #${i}`, +})); + +const refs = DATA.map(() => React.createRef()); + +const CardView = React.forwardRef(({ title }: { title: string }, ref) => ( + + {title} + +)); + +const FlatListScreen: React.FC = () => { + const lastSensitiveTagsRef = useRef([]); + + const onViewableItemsChanged = React.useCallback(({ viewableItems }) => { + const sensitiveTags: number[] = []; + viewableItems.forEach(({ index }) => { + if (index % 2 === 0 && refs[index]?.current) { + const tag = findNodeHandle(refs[index].current); + if (tag) sensitiveTags.push(tag); + } + }); + + // Determine views to unmark and mark based on visibility changes + const currentlyMarked = lastSensitiveTagsRef.current; + const toUnmark = currentlyMarked.filter( + (tag) => !sensitiveTags.includes(tag) + ); + const toMark = sensitiveTags.filter( + (tag) => !currentlyMarked.includes(tag) + ); + + // Unmark views that are no longer visible or will be remarked + if (toUnmark.length > 0) { + DevRev.unmarkSensitiveViews(toUnmark); + } + + // Mark newly visible sensitive views + if (toMark.length > 0) { + DevRev.markSensitiveViews(toMark); + } + + // Update the record of currently marked sensitive views + // This includes views that remained visible and newly marked views + lastSensitiveTagsRef.current = sensitiveTags.filter( + (tag) => toUnmark.indexOf(tag) === -1 || toMark.indexOf(tag) !== -1 + ); + }, []); + + return ( + item.id} + renderItem={({ item, index }) => ( + + )} + contentContainerStyle={styles.list} + onViewableItemsChanged={onViewableItemsChanged} + viewabilityConfig={{ itemVisiblePercentThreshold: 50 }} + /> + ); +}; + +const styles = StyleSheet.create({ + list: { padding: 16 }, + card: { + backgroundColor: '#fff', + borderRadius: 8, + elevation: 3, + shadowColor: '#000', + shadowOpacity: 0.1, + shadowRadius: 4, + shadowOffset: { width: 0, height: 2 }, + padding: 16, + marginBottom: 12, + }, + cardText: { fontSize: 16, color: '#333' }, +}); + +export default FlatListScreen; diff --git a/sample/expo/screens/HomeScreen.tsx b/sample/expo/screens/HomeScreen.tsx new file mode 100644 index 0000000..8e627c6 --- /dev/null +++ b/sample/expo/screens/HomeScreen.tsx @@ -0,0 +1,163 @@ +import * as React from 'react'; + +import { + StyleSheet, + View, + Text, + Animated, + Platform, + TouchableOpacity, +} from 'react-native'; +import TouchableOpacityButton from '../components/TouchableOpacityButton'; +import viewModel from '../viewmodel/ViewModel'; +import { useRef, useState } from 'react'; + +const navigateButtons = [ + { text: 'Identification', route: 'Identification' }, + { text: 'Support Chat', route: 'SupportChat' }, + { text: 'Push Notifications', route: 'PushNotifications' }, + { text: 'Session Analytics', route: 'SessionAnalytics' }, +] as const; + +const testButtons = [ + { + text: 'Simulate ANR', + onPress: viewModel.simulateANR, + platform: 'android', + }, + { + text: 'Simulate Crash', + onPress: viewModel.simulateCrash, + platform: 'both', + }, +] as const; + +const buttons = testButtons.filter( + (button) => button.platform === 'both' || button.platform === Platform.OS +); + +const HomeScreen = ({ navigation }: { navigation: any }) => { + const [isAnimating, setIsAnimating] = useState(false); + const scaleValue = useRef(new Animated.Value(1)).current; + const animation = useRef(null); + + const startAnimation = () => { + if (isAnimating) return; + + setIsAnimating(true); + scaleValue.setValue(1); + + animation.current = Animated.loop( + Animated.sequence([ + Animated.spring(scaleValue, { + toValue: 1.2, + friction: 2, + tension: 60, + useNativeDriver: true, + }), + Animated.spring(scaleValue, { + toValue: 1, + friction: 2, + tension: 60, + useNativeDriver: true, + }), + ]), + { iterations: 4 } + ); + + animation.current.start(({ finished: _finished }) => { + setIsAnimating(false); + }); + }; + + return ( + + Features + {navigateButtons.map((button, index) => ( + navigation.navigate(button.route)} + buttonStyle={styles.button} + textStyle={styles.buttonText} + /> + ))} + Debug + {buttons.map((button, index) => ( + + ))} + Animation + + + + Play an Animation + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'flex-start', + paddingLeft: 8, + paddingRight: 16, + backgroundColor: '#FFFFFF', + }, + textbox: { + width: '80%', + padding: 8, + color: '#000000', + }, + titleBar: { + width: '100%', + padding: 20, + }, + title: { + color: '#000000', + fontSize: 24, + fontWeight: 'bold', + }, + heading: { + color: '#454141', + fontSize: 14, + textAlign: 'left', + paddingTop: 8, + margin: 8, + }, + buttonContainer: { + width: '100%', + }, + button: { + backgroundColor: '#D3D3D3', + padding: 12, + borderRadius: 12, + marginVertical: 4, + alignItems: 'flex-start', + paddingBottom: 8, + }, + buttonText: { + color: '#000000', + fontSize: 16, + paddingLeft: 8, + }, + buttonTouchable: { + width: '100%', + }, +}); + +export default HomeScreen; diff --git a/sample/expo/screens/IdentificationScreen.tsx b/sample/expo/screens/IdentificationScreen.tsx new file mode 100644 index 0000000..3103a55 --- /dev/null +++ b/sample/expo/screens/IdentificationScreen.tsx @@ -0,0 +1,138 @@ +import React from 'react'; + +import * as DevRev from '@devrev/sdk-react-native'; +import type { Identity } from '@devrev/sdk-react-native'; +import { View, Text, TextInput, StyleSheet } from 'react-native'; +import viewModel from '../viewmodel/ViewModel'; +import TouchableOpacityButton from '../components/TouchableOpacityButton'; + +const IdentificationScreen: React.FC = () => { + const [userID, setUserID] = React.useState(''); + const [sessionToken, setSessionToken] = React.useState(''); + const [userEmail, setUserEmail] = React.useState(''); + + const handleUpdateUser = () => { + const identity: Identity = { + userRef: userID, + userTraits: { + email: userEmail, + }, + }; + DevRev.updateUser(identity); + }; + + return ( + + Unverified User + + + { + console.log('Identifying user with:', userID); + DevRev.identifyUnverifiedUser(userID); + }} + buttonText="Identify Unverified User" + buttonStyle={styles.button} + textStyle={styles.buttonText} + /> + + Verified User + + + + { + console.log( + 'Identifying user with:', + userID, + 'and session token:', + sessionToken + ); + DevRev.identifyVerifiedUser(userID, sessionToken); + }} + buttonText="Identify Verified User" + buttonStyle={styles.button} + textStyle={styles.buttonText} + /> + + Update User + + + { + console.log('Updating user...'); + handleUpdateUser(); + }} + buttonText="Update User" + buttonStyle={styles.button} + textStyle={styles.buttonText} + /> + + Logout + { + viewModel.logout(); + }} + buttonText="Logout" + buttonStyle={styles.button} + textStyle={styles.buttonText} + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#FFFFFF', + paddingHorizontal: 8, + paddingRight: 16, + }, + heading: { + color: '#454141', + fontSize: 12, + marginTop: 16, + paddingHorizontal: 8, + paddingTop: 16, + }, + input: { + backgroundColor: '#FFFFFF', + borderWidth: 1, + borderColor: '#D3D3D3', + borderRadius: 4, + padding: 12, + marginVertical: 4, + fontSize: 16, + }, + button: { + backgroundColor: '#D3D3D3', + padding: 12, + borderRadius: 12, + marginVertical: 4, + alignItems: 'flex-start', + paddingBottom: 8, + }, + buttonText: { + color: '#000000', + fontSize: 16, + paddingLeft: 8, + }, +}); + +export default IdentificationScreen; diff --git a/sample/expo/screens/PushNotificationsScreen.tsx b/sample/expo/screens/PushNotificationsScreen.tsx new file mode 100644 index 0000000..8f7939b --- /dev/null +++ b/sample/expo/screens/PushNotificationsScreen.tsx @@ -0,0 +1,125 @@ +import viewModel from '../viewmodel/ViewModel'; +import React, { useState } from 'react'; +import { View, Text, Button, StyleSheet } from 'react-native'; +import TouchableOpacityButton from '../components/TouchableOpacityButton'; + +const PushNotificationsScreen: React.FC = () => { + const [showDialog, setShowDialog] = useState<{ + title: string; + message: string; + } | null>(null); + + const handleRegister = async () => { + const isRegistered = viewModel.registerDeviceToken(); + if (await isRegistered) { + setShowDialog({ + title: 'Success', + message: 'Successfully registered for push notifications.', + }); + } else { + setShowDialog({ + title: 'Error', + message: 'Could not register the device.', + }); + } + }; + + const handleUnregister = async () => { + const isUnregistered = viewModel.unregisterDevice(); + if (await isUnregistered) { + setShowDialog({ + title: 'Success', + message: 'Successfully unregistered for push notifications.', + }); + } else { + setShowDialog({ + title: 'Error', + message: 'Could not unregister the device.', + }); + return; + } + }; + + const buttons = [ + { text: 'Register for push notifications', onPress: handleRegister }, + { text: 'Unregister for push notifications', onPress: handleUnregister }, + ] as const; + + return ( + + {buttons.map((button, index) => ( + + ))} + + {showDialog && ( + setShowDialog(null)} + /> + )} + + ); +}; + +const AlertDialog: React.FC<{ + title: string; + message: string; + onDismiss: () => void; +}> = ({ title, message, onDismiss }) => { + return ( + + {title} + {message} + + + diff --git a/sample/react-native/android/app/src/main/res/values/strings.xml b/sample/react-native/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..efc9115 --- /dev/null +++ b/sample/react-native/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + DevRevSDK (React Native) + diff --git a/sample/android/app/src/main/res/values/styles.xml b/sample/react-native/android/app/src/main/res/values/styles.xml similarity index 100% rename from sample/android/app/src/main/res/values/styles.xml rename to sample/react-native/android/app/src/main/res/values/styles.xml diff --git a/sample/react-native/android/build.gradle b/sample/react-native/android/build.gradle new file mode 100644 index 0000000..f37f1ec --- /dev/null +++ b/sample/react-native/android/build.gradle @@ -0,0 +1,42 @@ +buildscript { + repositories { + google() + mavenCentral() + jcenter() + mavenLocal() + } + dependencies { + classpath("com.android.tools.build:gradle:8.6.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21") + classpath("com.google.gms:google-services:4.4.2") + } +} + +plugins { + id "com.facebook.react.rootproject" apply false +} + +ext { + buildToolsVersion = "35.0.0" + minSdkVersion = 24 + compileSdkVersion = 35 + targetSdkVersion = 35 + ndkVersion = "26.1.10909125" + kotlinVersion = "2.0.21" +} + +apply plugin: "com.facebook.react.rootproject" + +rootProject.allprojects { + repositories { + google() + mavenCentral() + mavenLocal() + maven { + url "https://central.sonatype.com/repository/maven-snapshots/" + mavenContent { + snapshotsOnly() + } + } + } +} diff --git a/sample/android/gradle.properties b/sample/react-native/android/gradle.properties similarity index 100% rename from sample/android/gradle.properties rename to sample/react-native/android/gradle.properties diff --git a/sample/android/gradle/wrapper/gradle-wrapper.jar b/sample/react-native/android/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from sample/android/gradle/wrapper/gradle-wrapper.jar rename to sample/react-native/android/gradle/wrapper/gradle-wrapper.jar diff --git a/sample/android/gradle/wrapper/gradle-wrapper.properties b/sample/react-native/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from sample/android/gradle/wrapper/gradle-wrapper.properties rename to sample/react-native/android/gradle/wrapper/gradle-wrapper.properties diff --git a/sample/android/gradlew b/sample/react-native/android/gradlew similarity index 100% rename from sample/android/gradlew rename to sample/react-native/android/gradlew diff --git a/sample/android/gradlew.bat b/sample/react-native/android/gradlew.bat similarity index 100% rename from sample/android/gradlew.bat rename to sample/react-native/android/gradlew.bat diff --git a/sample/react-native/android/settings.gradle b/sample/react-native/android/settings.gradle new file mode 100644 index 0000000..454405a --- /dev/null +++ b/sample/react-native/android/settings.gradle @@ -0,0 +1,9 @@ +pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } +plugins { id("com.facebook.react.settings") } +reactSettings { autolinkLibrariesFromCommand() } +rootProject.name = 'DevRevSDKSample' + +include ':app' + +include ':react-native-notifications' +project(':react-native-notifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/lib/android/app') diff --git a/sample/react-native/app.json b/sample/react-native/app.json new file mode 100644 index 0000000..0b791a2 --- /dev/null +++ b/sample/react-native/app.json @@ -0,0 +1,4 @@ +{ + "name": "DevRevSDKSampleRN", + "displayName": "DevRevSDK (React Native)" +} diff --git a/sample/react-native/babel.config.js b/sample/react-native/babel.config.js new file mode 100644 index 0000000..565292e --- /dev/null +++ b/sample/react-native/babel.config.js @@ -0,0 +1,11 @@ +module.exports = { + presets: ['module:@react-native/babel-preset'], + plugins: [ + [ + 'module-resolver', + { + extensions: ['.tsx', '.ts', '.js', '.json'], + }, + ], + ], +}; diff --git a/sample/index.js b/sample/react-native/index.js similarity index 100% rename from sample/index.js rename to sample/react-native/index.js diff --git a/sample/ios/.editorconfig b/sample/react-native/ios/.editorconfig similarity index 100% rename from sample/ios/.editorconfig rename to sample/react-native/ios/.editorconfig diff --git a/sample/ios/.xcode.env b/sample/react-native/ios/.xcode.env similarity index 100% rename from sample/ios/.xcode.env rename to sample/react-native/ios/.xcode.env diff --git a/sample/ios/DevRevSDKSample-Bridging-Header.h b/sample/react-native/ios/DevRevSDKSampleRN-Bridging-Header.h similarity index 100% rename from sample/ios/DevRevSDKSample-Bridging-Header.h rename to sample/react-native/ios/DevRevSDKSampleRN-Bridging-Header.h diff --git a/sample/ios/DevRevSDKSample.entitlements b/sample/react-native/ios/DevRevSDKSampleRN.entitlements similarity index 100% rename from sample/ios/DevRevSDKSample.entitlements rename to sample/react-native/ios/DevRevSDKSampleRN.entitlements diff --git a/sample/ios/DevRevSDKSample.xcodeproj/project.pbxproj b/sample/react-native/ios/DevRevSDKSampleRN.xcodeproj/project.pbxproj similarity index 88% rename from sample/ios/DevRevSDKSample.xcodeproj/project.pbxproj rename to sample/react-native/ios/DevRevSDKSampleRN.xcodeproj/project.pbxproj index 34740b4..203aae0 100644 --- a/sample/ios/DevRevSDKSample.xcodeproj/project.pbxproj +++ b/sample/react-native/ios/DevRevSDKSampleRN.xcodeproj/project.pbxproj @@ -12,21 +12,21 @@ 1CA606002D13FC7000B2293B /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1CA605FF2D13FC7000B2293B /* GoogleService-Info.plist */; }; 343065452DDCF35A009F6083 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 343065442DDCF35A009F6083 /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - D17B58719925A0DC175BCCAB /* Pods_DevRevSDKSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8281D88BA7622C9E8AE79A31 /* Pods_DevRevSDKSample.framework */; }; + EF1AA09255E944CC774FB0C1 /* Pods_DevRevSDKSampleRN.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E3EE8C8BC611A9CAA5574249 /* Pods_DevRevSDKSampleRN.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 13B07F961A680F5B00A75B9A /* DevRevSDKSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DevRevSDKSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07F961A680F5B00A75B9A /* DevRevSDKSampleRN.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DevRevSDKSampleRN.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Sources/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/Info.plist; sourceTree = ""; }; 1CA605FF2D13FC7000B2293B /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; - 1CA606012D13FCE400B2293B /* DevRevSDKSample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DevRevSDKSample.entitlements; sourceTree = ""; }; + 1CA606012D13FCE400B2293B /* DevRevSDKSampleRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DevRevSDKSampleRN.entitlements; sourceTree = ""; }; 343065442DDCF35A009F6083 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Sources/AppDelegate.swift; sourceTree = ""; }; - 6D78A72DEE821DC6473B0DE8 /* Pods-DevRevSDKSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DevRevSDKSample.debug.xcconfig"; path = "Target Support Files/Pods-DevRevSDKSample/Pods-DevRevSDKSample.debug.xcconfig"; sourceTree = ""; }; + 60508B310A9753744EE0782C /* Pods-DevRevSDKSampleRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DevRevSDKSampleRN.release.xcconfig"; path = "Target Support Files/Pods-DevRevSDKSampleRN/Pods-DevRevSDKSampleRN.release.xcconfig"; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = Sources/LaunchScreen.storyboard; sourceTree = ""; }; - 8281D88BA7622C9E8AE79A31 /* Pods_DevRevSDKSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DevRevSDKSample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8AC6DBD62B5701538664BE92 /* Pods-DevRevSDKSampleRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DevRevSDKSampleRN.debug.xcconfig"; path = "Target Support Files/Pods-DevRevSDKSampleRN/Pods-DevRevSDKSampleRN.debug.xcconfig"; sourceTree = ""; }; A2B83FA1ACA1BE40552EEA27 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Sources/PrivacyInfo.xcprivacy; sourceTree = ""; }; - BDFEDCAEC4000AD1D0CC60AE /* Pods-DevRevSDKSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DevRevSDKSample.release.xcconfig"; path = "Target Support Files/Pods-DevRevSDKSample/Pods-DevRevSDKSample.release.xcconfig"; sourceTree = ""; }; + E3EE8C8BC611A9CAA5574249 /* Pods_DevRevSDKSampleRN.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DevRevSDKSampleRN.framework; sourceTree = BUILT_PRODUCTS_DIR; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -35,7 +35,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D17B58719925A0DC175BCCAB /* Pods_DevRevSDKSample.framework in Frameworks */, + EF1AA09255E944CC774FB0C1 /* Pods_DevRevSDKSampleRN.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -59,7 +59,7 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 8281D88BA7622C9E8AE79A31 /* Pods_DevRevSDKSample.framework */, + E3EE8C8BC611A9CAA5574249 /* Pods_DevRevSDKSampleRN.framework */, ); name = Frameworks; sourceTree = ""; @@ -74,7 +74,7 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( - 1CA606012D13FCE400B2293B /* DevRevSDKSample.entitlements */, + 1CA606012D13FCE400B2293B /* DevRevSDKSampleRN.entitlements */, 13B07FAE1A68108700A75B9A /* Sample */, 832341AE1AAA6A7D00B99B32 /* Libraries */, 83CBBA001A601CBA00E9B192 /* Products */, @@ -90,7 +90,7 @@ 83CBBA001A601CBA00E9B192 /* Products */ = { isa = PBXGroup; children = ( - 13B07F961A680F5B00A75B9A /* DevRevSDKSample.app */, + 13B07F961A680F5B00A75B9A /* DevRevSDKSampleRN.app */, ); name = Products; sourceTree = ""; @@ -105,8 +105,8 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 6D78A72DEE821DC6473B0DE8 /* Pods-DevRevSDKSample.debug.xcconfig */, - BDFEDCAEC4000AD1D0CC60AE /* Pods-DevRevSDKSample.release.xcconfig */, + 8AC6DBD62B5701538664BE92 /* Pods-DevRevSDKSampleRN.debug.xcconfig */, + 60508B310A9753744EE0782C /* Pods-DevRevSDKSampleRN.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -114,26 +114,26 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 13B07F861A680F5B00A75B9A /* DevRevSDKSample */ = { + 13B07F861A680F5B00A75B9A /* DevRevSDKSampleRN */ = { isa = PBXNativeTarget; - buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "DevRevSDKSample" */; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "DevRevSDKSampleRN" */; buildPhases = ( - 1C09263E11E9B37D983C0685 /* [CP] Check Pods Manifest.lock */, + 79D99AFFD42C79566699A194 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - AE323BC7D371C3E3133066BC /* [CP] Embed Pods Frameworks */, - 48158488B74EA6A93A67A6F5 /* [CP] Copy Pods Resources */, - D43ADF06748524F5D3925B6E /* [CP-User] [RNFB] Core Configuration */, + 9EEBE2A4BA4AAA08EBC649BE /* [CP] Embed Pods Frameworks */, + 2DB4C6E723704CE6C198EFF3 /* [CP] Copy Pods Resources */, + 84F618E3FF9BDBCFDD8C71E6 /* [CP-User] [RNFB] Core Configuration */, ); buildRules = ( ); dependencies = ( ); - name = DevRevSDKSample; - productName = DevRevSDKSample; - productReference = 13B07F961A680F5B00A75B9A /* DevRevSDKSample.app */; + name = DevRevSDKSampleRN; + productName = DevRevSDKSampleRN; + productReference = 13B07F961A680F5B00A75B9A /* DevRevSDKSampleRN.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -149,7 +149,7 @@ }; }; }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "DevRevSDKSample" */; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "DevRevSDKSampleRN" */; compatibilityVersion = "Xcode 12.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -162,7 +162,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 13B07F861A680F5B00A75B9A /* DevRevSDKSample */, + 13B07F861A680F5B00A75B9A /* DevRevSDKSampleRN */, ); }; /* End PBXProject section */ @@ -198,7 +198,24 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 1C09263E11E9B37D983C0685 /* [CP] Check Pods Manifest.lock */ = { + 2DB4C6E723704CE6C198EFF3 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSampleRN/Pods-DevRevSDKSampleRN-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSampleRN/Pods-DevRevSDKSampleRN-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSampleRN/Pods-DevRevSDKSampleRN-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 79D99AFFD42C79566699A194 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -213,60 +230,43 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-DevRevSDKSample-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-DevRevSDKSampleRN-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 48158488B74EA6A93A67A6F5 /* [CP] Copy Pods Resources */ = { + 84F618E3FF9BDBCFDD8C71E6 /* [CP-User] [RNFB] Core Configuration */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSample/Pods-DevRevSDKSample-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSample/Pods-DevRevSDKSample-resources-${CONFIGURATION}-output-files.xcfilelist", + inputPaths = ( + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", ); + name = "[CP-User] [RNFB] Core Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSample/Pods-DevRevSDKSample-resources.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; }; - AE323BC7D371C3E3133066BC /* [CP] Embed Pods Frameworks */ = { + 9EEBE2A4BA4AAA08EBC649BE /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSample/Pods-DevRevSDKSample-frameworks-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSampleRN/Pods-DevRevSDKSampleRN-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSample/Pods-DevRevSDKSample-frameworks-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSampleRN/Pods-DevRevSDKSampleRN-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSample/Pods-DevRevSDKSample-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DevRevSDKSampleRN/Pods-DevRevSDKSampleRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - D43ADF06748524F5D3925B6E /* [CP-User] [RNFB] Core Configuration */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", - ); - name = "[CP-User] [RNFB] Core Configuration"; - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -283,13 +283,13 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6D78A72DEE821DC6473B0DE8 /* Pods-DevRevSDKSample.debug.xcconfig */; + baseConfigurationReference = 8AC6DBD62B5701538664BE92 /* Pods-DevRevSDKSampleRN.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = DevRevSDKSample.entitlements; + CODE_SIGN_ENTITLEMENTS = DevRevSDKSampleRN.entitlements; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = NJDA6Y3XRS; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; INFOPLIST_FILE = Sources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -302,8 +302,8 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = ai.devrev.sdk.bridge.reactnative.DevRevSDKSample; - PRODUCT_NAME = DevRevSDKSample; + PRODUCT_BUNDLE_IDENTIFIER = ai.devrev.sdk.bridge.reactnative.sample; + PRODUCT_NAME = DevRevSDKSampleRN; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -312,13 +312,13 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BDFEDCAEC4000AD1D0CC60AE /* Pods-DevRevSDKSample.release.xcconfig */; + baseConfigurationReference = 60508B310A9753744EE0782C /* Pods-DevRevSDKSampleRN.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = DevRevSDKSample.entitlements; + CODE_SIGN_ENTITLEMENTS = DevRevSDKSampleRN.entitlements; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = NJDA6Y3XRS; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Sources/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -330,8 +330,8 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = ai.devrev.sdk.bridge.reactnative.DevRevSDKSample; - PRODUCT_NAME = DevRevSDKSample; + PRODUCT_BUNDLE_IDENTIFIER = ai.devrev.sdk.bridge.reactnative.sample; + PRODUCT_NAME = DevRevSDKSampleRN; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; @@ -399,7 +399,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", ); - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD = ""; LDPLUSPLUS = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -487,7 +487,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", ); - IPHONEOS_DEPLOYMENT_TARGET = 13.4; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD = ""; LDPLUSPLUS = ""; LD_RUNPATH_SEARCH_PATHS = ( @@ -522,7 +522,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "DevRevSDKSample" */ = { + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "DevRevSDKSampleRN" */ = { isa = XCConfigurationList; buildConfigurations = ( 13B07F941A680F5B00A75B9A /* Debug */, @@ -531,7 +531,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "DevRevSDKSample" */ = { + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "DevRevSDKSampleRN" */ = { isa = XCConfigurationList; buildConfigurations = ( 83CBBA201A601CBA00E9B192 /* Debug */, diff --git a/sample/ios/DevRevSDKSample.xcodeproj/xcshareddata/xcschemes/DevRevSDKSample.xcscheme b/sample/react-native/ios/DevRevSDKSampleRN.xcodeproj/xcshareddata/xcschemes/DevRevSDKSampleRN.xcscheme similarity index 74% rename from sample/ios/DevRevSDKSample.xcodeproj/xcshareddata/xcschemes/DevRevSDKSample.xcscheme rename to sample/react-native/ios/DevRevSDKSampleRN.xcodeproj/xcshareddata/xcschemes/DevRevSDKSampleRN.xcscheme index d83aa1b..d3c6ac5 100644 --- a/sample/ios/DevRevSDKSample.xcodeproj/xcshareddata/xcschemes/DevRevSDKSample.xcscheme +++ b/sample/react-native/ios/DevRevSDKSampleRN.xcodeproj/xcshareddata/xcschemes/DevRevSDKSampleRN.xcscheme @@ -1,10 +1,11 @@ + LastUpgradeVersion = "1640" + version = "1.7"> + buildImplicitDependencies = "YES" + buildArchitectures = "Automatic"> + BuildableName = "DevRevSDKSampleRN.app" + BlueprintName = "DevRevSDKSampleRN" + ReferencedContainer = "container:DevRevSDKSampleRN.xcodeproj"> @@ -26,9 +27,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + BuildableName = "DevRevSDKSampleRN.app" + BlueprintName = "DevRevSDKSampleRN" + ReferencedContainer = "container:DevRevSDKSampleRN.xcodeproj"> @@ -62,9 +62,9 @@ + BuildableName = "DevRevSDKSampleRN.app" + BlueprintName = "DevRevSDKSampleRN" + ReferencedContainer = "container:DevRevSDKSampleRN.xcodeproj"> diff --git a/sample/ios/DevRevSDKSample.xcworkspace/xcshareddata/swiftpm/Package.resolved b/sample/react-native/ios/DevRevSDKSampleRN.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 76% rename from sample/ios/DevRevSDKSample.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to sample/react-native/ios/DevRevSDKSampleRN.xcworkspace/xcshareddata/swiftpm/Package.resolved index 036f31a..ae2d07c 100644 --- a/sample/ios/DevRevSDKSample.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/sample/react-native/ios/DevRevSDKSampleRN.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "git@github.com:devrev/devrev-sdk-ios.git", "state" : { - "revision" : "21d5a4e3d61b8cac599d95af2f75bc8442c8e3a0", - "version" : "2.0.2" + "revision" : "b394b096d9b8dc015adbe3f58218c985a6a71787", + "version" : "2.2.4" } } ], diff --git a/sample/react-native/ios/GoogleService-Info.plist b/sample/react-native/ios/GoogleService-Info.plist new file mode 100644 index 0000000..ba10bf1 --- /dev/null +++ b/sample/react-native/ios/GoogleService-Info.plist @@ -0,0 +1,16 @@ + + + + + API_KEY + DUMMY_API_KEY + CLIENT_ID + DUMMY_CLIENT_ID + GOOGLE_APP_ID + 1:1234567890:ios:abcdef123456 + PROJECT_ID + dummy-project + BUNDLE_ID + ai.devrev.sdk.bridge.reactnative.sample + + diff --git a/sample/ios/Podfile b/sample/react-native/ios/Podfile similarity index 69% rename from sample/ios/Podfile rename to sample/react-native/ios/Podfile index 377a38d..90c2000 100644 --- a/sample/ios/Podfile +++ b/sample/react-native/ios/Podfile @@ -1,30 +1,29 @@ -# Resolve react_native_pods.rb with node to allow for hoisting require Pod::Executable.execute_command('node', ['-p', 'require.resolve( "react-native/scripts/react_native_pods.rb", {paths: [process.argv[1]]}, )', __dir__]).strip -platform :ios, "15.0" +platform :ios, '15.1' prepare_react_native_project! -use_frameworks! :linkage => :static +use_frameworks! :linkage => :dynamic # !!!! Uncomment the following line for development !!!! # source 'git@github.com:devrev/ios-podspecs-test.git' source 'https://cdn.cocoapods.org/' -target 'DevRevSDKSample' do +project 'DevRevSDKSampleRN.xcodeproj' + +target 'DevRevSDKSampleRN' do config = use_native_modules! use_react_native!( :path => config[:reactNativePath], - # An absolute path to your application root. :app_path => "#{Pod::Config.instance.installation_root}/.." ) post_install do |installer| - # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 react_native_post_install( installer, config[:reactNativePath], diff --git a/sample/ios/Podfile.lock b/sample/react-native/ios/Podfile.lock similarity index 98% rename from sample/ios/Podfile.lock rename to sample/react-native/ios/Podfile.lock index 24f3cd8..51f5824 100644 --- a/sample/ios/Podfile.lock +++ b/sample/react-native/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - boost (1.84.0) - - devrev-sdk-react-native (2.0.0): + - devrev-sdk-react-native (2.2.1): - DoubleConversion - glog - hermes-engine @@ -1419,6 +1419,8 @@ PODS: - React-jsiexecutor - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core + - react-native-notifications (5.1.0): + - React-Core - react-native-safe-area-context (5.4.0): - DoubleConversion - glog @@ -1494,6 +1496,30 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga + - react-native-webview (13.16.0): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - React-NativeModulesApple (0.79.2): - glog - hermes-engine @@ -1855,17 +1881,12 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNNotifee (9.1.8): - - React-Core - - RNNotifee/NotifeeCore (= 9.1.8) - - RNNotifee/NotifeeCore (9.1.8): - - React-Core - SocketRocket (0.7.1) - Yoga (0.0.0) DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - devrev-sdk-react-native (from `../..`) + - devrev-sdk-react-native (from `../../..`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) @@ -1905,7 +1926,9 @@ DEPENDENCIES: - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-notifications (from `../node_modules/react-native-notifications`) - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - react-native-webview (from `../node_modules/react-native-webview`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`) - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) @@ -1942,7 +1965,6 @@ DEPENDENCIES: - "RNFBInstallations (from `../node_modules/@react-native-firebase/installations`)" - "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)" - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - - "RNNotifee (from `../node_modules/@notifee/react-native`)" - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -1963,7 +1985,7 @@ EXTERNAL SOURCES: boost: :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" devrev-sdk-react-native: - :path: "../.." + :path: "../../.." DoubleConversion: :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" fast_float: @@ -2039,8 +2061,12 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon" React-microtasksnativemodule: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-notifications: + :path: "../node_modules/react-native-notifications" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" + react-native-webview: + :path: "../node_modules/react-native-webview" React-NativeModulesApple: :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-oscompat: @@ -2113,14 +2139,12 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-firebase/messaging" RNGestureHandler: :path: "../node_modules/react-native-gesture-handler" - RNNotifee: - :path: "../node_modules/@notifee/react-native" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - devrev-sdk-react-native: 8338db515da7a10a8681e1e54c9ef8b46864f632 + devrev-sdk-react-native: 1444b3f76c3954dcf104e92c73a9b96d69ec0738 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 84b955f7b4da8b895faf5946f73748267347c975 @@ -2168,7 +2192,9 @@ SPEC CHECKSUMS: React-logger: 8edfcedc100544791cd82692ca5a574240a16219 React-Mapbuffer: da73f30b000114058d6bc41490dcce204a8ede32 React-microtasksnativemodule: 444c5701aece79629bb73bd9e7ad8937ae65238c + react-native-notifications: 3bafa1237ae8a47569a84801f17d80242fe9f6a5 react-native-safe-area-context: 5928d84c879db2f9eb6969ca70e68f58623dbf25 + react-native-webview: f0d598682cbc88a97d66da4121873d3467f76635 React-NativeModulesApple: df8e5bc59e78ca3040ffbf41336889f3bd0fad68 React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c React-perflogger: 9a151e0b4c933c9205fd648c246506a83f31395d @@ -2205,10 +2231,9 @@ SPEC CHECKSUMS: RNFBInstallations: e44e81b6eb5966481315f4576134748591c9dd8e RNFBMessaging: c1dc973d87a82fd9e58bd8c3a8c269dcca337669 RNGestureHandler: 381f81c675883fec9f8f10976aa278babc48d8eb - RNNotifee: 5e3b271e8ea7456a36eec994085543c9adca9168 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: 50518ade05048235d91a78b803336dbb5b159d5d -PODFILE CHECKSUM: 4a89df2116af06f5e357ab5fb1908d851492d825 +PODFILE CHECKSUM: 77a3bbff7d76f2ae1551ede8ef62e45eff2a9e3b COCOAPODS: 1.16.2 diff --git a/sample/ios/Sources/AppDelegate.swift b/sample/react-native/ios/Sources/AppDelegate.swift similarity index 55% rename from sample/ios/Sources/AppDelegate.swift rename to sample/react-native/ios/Sources/AppDelegate.swift index a7a0411..70e4ddc 100644 --- a/sample/ios/Sources/AppDelegate.swift +++ b/sample/react-native/ios/Sources/AppDelegate.swift @@ -3,19 +3,24 @@ import React import React_RCTAppDelegate import ReactAppDependencyProvider import Firebase +import RNNotifications @main class AppDelegate: UIResponder, UIApplicationDelegate { - var window: UIWindow? + // MARK: - Properties + var window: UIWindow? var reactNativeDelegate: ReactNativeDelegate? var reactNativeFactory: RCTReactNativeFactory? + // MARK: - App lifecycle + func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { FirebaseApp.configure() + RNNotifications.startMonitorNotifications() let delegate = ReactNativeDelegate() let factory = RCTReactNativeFactory(delegate: delegate) @@ -27,18 +32,45 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window = UIWindow(frame: UIScreen.main.bounds) factory.startReactNative( - withModuleName: "DevRevSDKSample", + withModuleName: "DevRevSDKSampleRN", in: window, launchOptions: launchOptions ) return true } + + // MARK: - Push notifications + + func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { + RNNotifications.didRegisterForRemoteNotifications(withDeviceToken: deviceToken) + } + + func application( + _ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: any Error + ) { + RNNotifications.didFailToRegisterForRemoteNotificationsWithError(error) + } + + func application( + _ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable : Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void + ) { + RNNotifications.didReceiveBackgroundNotification( + userInfo, + withCompletionHandler: completionHandler + ) + } } class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { override func sourceURL(for bridge: RCTBridge) -> URL? { - self.bundleURL() + bundleURL() } override func bundleURL() -> URL? { diff --git a/sample/react-native/ios/Sources/Images.xcassets/AppIcon.appiconset/Contents.json b/sample/react-native/ios/Sources/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..4812f00 --- /dev/null +++ b/sample/react-native/ios/Sources/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "React Native.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/sample/react-native/ios/Sources/Images.xcassets/AppIcon.appiconset/React Native.png b/sample/react-native/ios/Sources/Images.xcassets/AppIcon.appiconset/React Native.png new file mode 100644 index 0000000000000000000000000000000000000000..0c1b4bb40ed23052d1211702857c58b15e6fa2a9 GIT binary patch literal 18364 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 en CFBundleDisplayName - DevRevSDKSample + DevRev SDK (React Native) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -33,6 +33,10 @@ NSLocationWhenInUseUsageDescription + UIBackgroundModes + + remote-notification + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities diff --git a/sample/react-native/ios/Sources/LaunchScreen.storyboard b/sample/react-native/ios/Sources/LaunchScreen.storyboard new file mode 100644 index 0000000..ec2b117 --- /dev/null +++ b/sample/react-native/ios/Sources/LaunchScreen.storyboard @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sample/ios/Sources/PrivacyInfo.xcprivacy b/sample/react-native/ios/Sources/PrivacyInfo.xcprivacy similarity index 100% rename from sample/ios/Sources/PrivacyInfo.xcprivacy rename to sample/react-native/ios/Sources/PrivacyInfo.xcprivacy diff --git a/sample/jest.config.js b/sample/react-native/jest.config.js similarity index 100% rename from sample/jest.config.js rename to sample/react-native/jest.config.js diff --git a/sample/react-native/metro.config.js b/sample/react-native/metro.config.js new file mode 100644 index 0000000..f4acabb --- /dev/null +++ b/sample/react-native/metro.config.js @@ -0,0 +1,48 @@ +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); +const path = require('path'); +const escape = require('escape-string-regexp'); +const exclusionList = require('metro-config/src/defaults/exclusionList'); +const pak = require('../../package.json'); + +const root = path.resolve(__dirname, '../..'); +const modules = Object.keys({ ...pak.peerDependencies }); + +const extraNodeModules = modules.reduce((acc, name) => { + acc[name] = path.join(__dirname, 'node_modules', name); + return acc; +}, {}); +extraNodeModules[pak.name] = root; + +/** + * Metro configuration + * https://facebook.github.io/metro/docs/configuration + * + * @type {import('@react-native/metro-config').MetroConfig} + */ +const config = { + watchFolders: [root], + + // We need to make sure that only one version is loaded for peerDependencies + // So we block them at the root, and alias them to the versions in samples's node_modules + resolver: { + blacklistRE: exclusionList( + modules.map( + (m) => + new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) + ) + ), + + extraNodeModules, + }, + + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, +}; + +module.exports = mergeConfig(getDefaultConfig(__dirname), config); diff --git a/sample/react-native/package.json b/sample/react-native/package.json new file mode 100644 index 0000000..fb31339 --- /dev/null +++ b/sample/react-native/package.json @@ -0,0 +1,76 @@ +{ + "name": "@devrev/sdk-react-native-sample", + "version": "2.2.1", + "description": "DevRev SDK for React Native sample app.", + "scripts": { + "android": "react-native run-android", + "ios": "react-native run-ios", + "start": "react-native start", + "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", + "build:ios": "react-native build-ios --scheme DevRevSDKSample --mode Debug --extra-params \"-sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO\"" + }, + "dependencies": { + "@devrev/sdk-react-native": "workspace:*", + "@react-native-firebase/app": "^21.0.0", + "@react-native-firebase/installations": "^21.0.0", + "@react-native-firebase/messaging": "^21.0.0", + "react-native-device-info": "^14.0.1", + "react-native-notifications": "^5.1.0", + "react-native-webview": "^13.15.0" + }, + "devDependencies": { + "@babel/core": "^7.20.0", + "@babel/preset-env": "^7.20.0", + "@babel/runtime": "^7.20.0", + "@react-native-community/cli": "^18.0.0", + "@react-native-community/cli-platform-android": "^18.0.0", + "@react-native-community/cli-platform-ios": "^18.0.0", + "@react-native/babel-preset": "^0.79.0", + "@react-native/metro-config": "^0.79.0", + "@react-native/typescript-config": "0.74.85", + "@react-navigation/native": "^6.0.8", + "@react-navigation/stack": "^6.3.21", + "babel-plugin-module-resolver": "^5.0.0", + "react": "19.0.0", + "react-native": "^0.79.0", + "react-native-gesture-handler": "^2.24.0", + "react-native-safe-area-context": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "rnx-kit": { + "kitType": "library", + "alignDeps": { + "requirements": { + "development": [ + "react-native@0.79" + ], + "production": [ + "react-native@0.79" + ] + }, + "capabilities": [ + "babel-preset-react-native", + "community/cli-ios", + "core", + "core-android", + "core-ios", + "core/metro-config", + "gestures", + "navigation/native", + "navigation/stack", + "react", + "safe-area" + ] + } + }, + "peerDependencies": { + "@react-navigation/native": "^6.0.8", + "@react-navigation/stack": "^6.3.21", + "react": "19.0.0", + "react-native": "^0.79.0", + "react-native-gesture-handler": "^2.24.0", + "react-native-safe-area-context": "^5.1.0" + } +} diff --git a/sample/react-native/react-native.config.js b/sample/react-native/react-native.config.js new file mode 100644 index 0000000..cecf7a3 --- /dev/null +++ b/sample/react-native/react-native.config.js @@ -0,0 +1,15 @@ +const path = require('path'); +const pak = require('./package.json'); + +module.exports = { + project: { + ios: { + automaticPodsInstallation: true, + }, + }, + dependencies: { + '@devrev/sdk-react-native': { + root: path.join(__dirname, '../..'), + }, + }, +}; diff --git a/sample/src/App.tsx b/sample/react-native/src/App.tsx similarity index 57% rename from sample/src/App.tsx rename to sample/react-native/src/App.tsx index 4e59957..c729d62 100644 --- a/sample/src/App.tsx +++ b/sample/react-native/src/App.tsx @@ -1,8 +1,8 @@ import * as React from 'react'; -import { Text, Linking, TouchableOpacity, StyleSheet } from 'react-native'; +import { Text, TouchableOpacity } from 'react-native'; import * as DevRev from '@devrev/sdk-react-native'; -import NotificationService from './NotificationService'; +import PushNotificationsService from './PushNotificationsService'; import { createStackNavigator } from '@react-navigation/stack'; import { NavigationContainer } from '@react-navigation/native'; import IdentificationScreen from './screens/IdentificationScreen'; @@ -11,6 +11,8 @@ import SessionAnalyticsScreen from './screens/SessionAnalyticsScreen'; import SupportChatScreen from './screens/SupportChatScreen'; import HomeScreen from './screens/HomeScreen'; import DelayedScreen from './screens/DelayedScreen'; +import WebViewScreen from './screens/WebViewScreen'; +import FlatListScreen from './screens/FlatListScreen'; import { commonStyles } from './styles/styles'; export type RootStackParamList = { @@ -20,17 +22,49 @@ export type RootStackParamList = { SessionAnalytics: undefined; SupportChat: undefined; DelayedScreen: undefined; + WebViewScreen: undefined; + FlatListScreen: undefined; }; const Stack = createStackNavigator(); const screens = [ { name: 'Home', component: HomeScreen, title: 'DevRev SDK' }, - { name: 'Identification', component: IdentificationScreen, title: 'Identification' }, - { name: 'PushNotifications', component: PushNotificationsScreen, title: 'Push Notifications' }, - { name: 'SessionAnalytics', component: SessionAnalyticsScreen, title: 'Session Analytics' }, - { name: 'SupportChat', component: SupportChatScreen, title: 'Support Chat' }, - { name: 'DelayedScreen', component: DelayedScreen, title: 'Delayed Screen' }, + { + name: 'Identification', + component: IdentificationScreen, + title: 'Identification', + }, + { + name: 'PushNotifications', + component: PushNotificationsScreen, + title: 'Push Notifications', + }, + { + name: 'SessionAnalytics', + component: SessionAnalyticsScreen, + title: 'Session Analytics', + }, + { + name: 'SupportChat', + component: SupportChatScreen, + title: 'Support Chat', + }, + { + name: 'DelayedScreen', + component: DelayedScreen, + title: 'Delayed Screen', + }, + { + name: 'WebViewScreen', + component: WebViewScreen, + title: 'Web View', + }, + { + name: 'FlatListScreen', + component: FlatListScreen, + title: 'Large Scrollable List', + }, ] as const; const createScreen = ( @@ -47,25 +81,24 @@ const createScreen = ( options={({ navigation }) => ({ title: title, headerRight: () => ( - navigation.replace(name)} + navigation.replace(name)} style={commonStyles.refreshButton} > ), - })} /> - ) -} + })} + /> + ); +}; const App = () => { React.useEffect(() => { try { - DevRev.configure( - 'YOUR_APP_ID', - ); + DevRev.configure('YOUR_APP_ID'); DevRev.setShouldDismissModalsOnOpenLink(true); - - NotificationService.initialize(); + PushNotificationsService.configure(); } catch (error) { console.log(error); } @@ -75,7 +108,12 @@ const App = () => { {screens.map((screen, index) => - createScreen(screen.name as keyof RootStackParamList, screen.component, screen.title, index) + createScreen( + screen.name as keyof RootStackParamList, + screen.component, + screen.title, + index + ) )} diff --git a/sample/react-native/src/PushNotificationsService.tsx b/sample/react-native/src/PushNotificationsService.tsx new file mode 100644 index 0000000..abe5722 --- /dev/null +++ b/sample/react-native/src/PushNotificationsService.tsx @@ -0,0 +1,99 @@ +import { + NotificationBackgroundFetchResult, + Notifications, + Registered, + RegistrationError, + Notification, + NotificationCompletion, +} from 'react-native-notifications'; +import * as DevRev from '@devrev/sdk-react-native'; +import DeviceInfo from 'react-native-device-info'; +import { Platform } from 'react-native'; + +const register = async (): Promise => { + Notifications.registerRemoteNotifications(); +}; + +// Configure the notification service. +const configure = async (): Promise => { + try { + register(); + + if (Platform.OS === 'android') { + Notifications.setNotificationChannel({ + channelId: 'default', + name: 'Default Channel', + enableLights: true, + enableVibration: true, + showBadge: true, + importance: 4, + }); + } + + Notifications.events().registerRemoteNotificationsRegistered( + (event: Registered) => { + console.log('Device token received:', event.deviceToken); + const deviceToken = event.deviceToken; + + DeviceInfo.getUniqueId() + .then((deviceId) => DevRev.registerDeviceToken(deviceToken, deviceId)) + .catch((error) => { + console.error('Could not register the device token!', error); + }); + } + ); + + Notifications.events().registerRemoteNotificationsRegistrationFailed( + (event: RegistrationError) => { + console.error('Failed to register the device token!', event); + } + ); + + Notifications.events().registerNotificationReceivedForeground( + ( + notification: Notification, + completion: (response: NotificationCompletion) => void + ) => { + processPushNotification(notification); + + completion({ alert: true, badge: true, sound: true }); + } + ); + + Notifications.events().registerNotificationReceivedBackground( + ( + notification: Notification, + completion: (response: NotificationBackgroundFetchResult) => void + ) => { + processPushNotification(notification); + + completion(NotificationBackgroundFetchResult.NO_DATA); + } + ); + + Notifications.events().registerNotificationOpened(processPushNotification); + Notifications.getInitialNotification().then(processPushNotification); + } catch (error) { + console.error('Error initializing notifications:', error); + } +}; + +const processPushNotification = (notification: Notification | undefined) => { + if (!notification) { + return; + } + + const message = JSON.stringify(notification.payload); + if (!message) { + return; + } + + DevRev.processPushNotification(message); +}; + +export const PushNotificationsService = { + configure, + register, +}; + +export default PushNotificationsService; diff --git a/sample/react-native/src/assets/sample.html b/sample/react-native/src/assets/sample.html new file mode 100644 index 0000000..5204157 --- /dev/null +++ b/sample/react-native/src/assets/sample.html @@ -0,0 +1,56 @@ + + + + + + Sample Page with Masking + + + +

Sample HTML for WebView

+

This page demonstrates content masking within a WebView.

+
+ + +

This field (devrev-mask) is masked in recordings.

+
+ +
+ + +

This password field (type="password") is automatically masked by the SDK without requiring the devrev-mask class.

+
+ +
+ + +

This field (devrev-unmask) is visible in recordings.

+
+ +
+ + +

This number (devrev-mask) is masked in recordings.

+
+ +

This paragraph contains a masked phrase and an unmasked phrase within the text.

+ +
+

This entire div and its contents are masked:

+ + +
+ +
+ + + diff --git a/sample/src/components/AlertDialog.tsx b/sample/react-native/src/components/AlertDialog.tsx similarity index 88% rename from sample/src/components/AlertDialog.tsx rename to sample/react-native/src/components/AlertDialog.tsx index 01ebf0e..2c372b4 100644 --- a/sample/src/components/AlertDialog.tsx +++ b/sample/react-native/src/components/AlertDialog.tsx @@ -8,7 +8,11 @@ interface AlertDialogProps { onDismiss: () => void; } -const AlertDialog: React.FC = ({ title, message, onDismiss }) => { +const AlertDialog: React.FC = ({ + title, + message, + onDismiss, +}) => { return ( = ({ label, checked }) => { return ( {label} - + {checked && ( diff --git a/sample/react-native/src/components/TouchableOpacityButton.tsx b/sample/react-native/src/components/TouchableOpacityButton.tsx new file mode 100644 index 0000000..edc2c41 --- /dev/null +++ b/sample/react-native/src/components/TouchableOpacityButton.tsx @@ -0,0 +1,32 @@ +import React, { forwardRef } from 'react'; +import { + TouchableOpacity, + Text, + type TextStyle, + type ViewStyle, + View, +} from 'react-native'; +import { commonStyles } from '../styles/styles'; + +interface TouchableOpacityProps { + onPress: () => void; + buttonText: string; + buttonStyle?: ViewStyle; + textStyle?: TextStyle; +} + +const TouchableOpacityButton = forwardRef( + ({ onPress, buttonText, buttonStyle, textStyle }, ref) => { + return ( + + {buttonText} + + ); + } +); + +export default TouchableOpacityButton; diff --git a/sample/src/model/DevRevNotification.ts b/sample/react-native/src/model/DevRevNotification.ts similarity index 100% rename from sample/src/model/DevRevNotification.ts rename to sample/react-native/src/model/DevRevNotification.ts diff --git a/sample/react-native/src/screens/DelayedScreen.tsx b/sample/react-native/src/screens/DelayedScreen.tsx new file mode 100644 index 0000000..932f395 --- /dev/null +++ b/sample/react-native/src/screens/DelayedScreen.tsx @@ -0,0 +1,20 @@ +import * as React from 'react'; +import { useEffect } from 'react'; +import { View, Text } from 'react-native'; +import * as DevRev from '@devrev/sdk-react-native'; +import { commonStyles } from '../styles/styles'; + +const DelayedScreen: React.FC = () => { + useEffect(() => { + // End the screen transition when the screen is loaded (Android only) + DevRev.setInScreenTransitioning(false); + }, []); + + return ( + + Delayed Screen + + ); +}; + +export default DelayedScreen; diff --git a/sample/react-native/src/screens/FlatListScreen.tsx b/sample/react-native/src/screens/FlatListScreen.tsx new file mode 100644 index 0000000..c6f810b --- /dev/null +++ b/sample/react-native/src/screens/FlatListScreen.tsx @@ -0,0 +1,86 @@ +import React, { useRef } from 'react'; +import { View, Text, FlatList, StyleSheet, findNodeHandle } from 'react-native'; +import * as DevRev from '@devrev/sdk-react-native'; + +const DATA = Array.from({ length: 100 }, (_, i) => ({ + id: i.toString(), + title: `Item #${i}`, +})); + +const refs = DATA.map(() => React.createRef()); + +const CardView = React.forwardRef(({ title }: { title: string }, ref) => ( + + {title} + +)); + +const FlatListScreen: React.FC = () => { + const lastSensitiveTagsRef = useRef([]); + + const onViewableItemsChanged = React.useCallback(({ viewableItems }) => { + const sensitiveTags: number[] = []; + viewableItems.forEach(({ index }) => { + if (index % 2 === 0 && refs[index]?.current) { + const tag = findNodeHandle(refs[index].current); + if (tag) sensitiveTags.push(tag); + } + }); + + // Determine views to unmark and mark based on visibility changes + const currentlyMarked = lastSensitiveTagsRef.current; + const toUnmark = currentlyMarked.filter( + (tag) => !sensitiveTags.includes(tag) + ); + const toMark = sensitiveTags.filter( + (tag) => !currentlyMarked.includes(tag) + ); + + // Unmark views that are no longer visible or will be remarked + if (toUnmark.length > 0) { + DevRev.unmarkSensitiveViews(toUnmark); + } + + // Mark newly visible sensitive views + if (toMark.length > 0) { + DevRev.markSensitiveViews(toMark); + } + + // Update the record of currently marked sensitive views + // This includes views that remained visible and newly marked views + lastSensitiveTagsRef.current = sensitiveTags.filter( + (tag) => toUnmark.indexOf(tag) === -1 || toMark.indexOf(tag) !== -1 + ); + }, []); + + return ( + item.id} + renderItem={({ item, index }) => ( + + )} + contentContainerStyle={styles.list} + onViewableItemsChanged={onViewableItemsChanged} + viewabilityConfig={{ itemVisiblePercentThreshold: 50 }} + /> + ); +}; + +const styles = StyleSheet.create({ + list: { padding: 16 }, + card: { + backgroundColor: '#fff', + borderRadius: 8, + elevation: 3, + shadowColor: '#000', + shadowOpacity: 0.1, + shadowRadius: 4, + shadowOffset: { width: 0, height: 2 }, + padding: 16, + marginBottom: 12, + }, + cardText: { fontSize: 16, color: '#333' }, +}); + +export default FlatListScreen; diff --git a/sample/src/screens/HomeScreen.tsx b/sample/react-native/src/screens/HomeScreen.tsx similarity index 76% rename from sample/src/screens/HomeScreen.tsx rename to sample/react-native/src/screens/HomeScreen.tsx index e7dac80..6bea2b6 100644 --- a/sample/src/screens/HomeScreen.tsx +++ b/sample/react-native/src/screens/HomeScreen.tsx @@ -18,31 +18,35 @@ type Button = NavigationButton | ActionButton; // Feature Section Buttons const featureButtons: Button[] = [ - { text: "Identification", route: "Identification" }, - { text: "Support Chat", route: "SupportChat" }, - { text: "Push Notifications", route: "PushNotifications" }, - { text: "Session Analytics", route: "SessionAnalytics" }, + { text: 'Identification', route: 'Identification' }, + { text: 'Support Chat', route: 'SupportChat' }, + { text: 'Push Notifications', route: 'PushNotifications' }, + { text: 'Session Analytics', route: 'SessionAnalytics' }, ]; // Debug Section Buttons const debugButtons: Button[] = [ - ...(Platform.OS === 'android' ? [{ - text: "Simulate ANR", - onPress: () => viewModel.simulateANR() - }] : []), + ...(Platform.OS === 'android' + ? [ + { + text: 'Simulate ANR', + onPress: () => viewModel.simulateANR(), + }, + ] + : []), { - text: "Simulate Crash", - onPress: () => viewModel.simulateCrash() - } + text: 'Simulate Crash', + onPress: () => viewModel.simulateCrash(), + }, ]; const animationButton: Button[] = [ { - text: "Bounce Animation", + text: 'Bounce Animation', onPress: () => { console.log('Bounce Animation Triggered'); - } - } + }, + }, ]; const HomeScreen = ({ navigation }: { navigation: any }) => { @@ -59,12 +63,12 @@ const HomeScreen = ({ navigation }: { navigation: any }) => { toValue: 1, friction: 3, useNativeDriver: true, - }) + }), ]).start(); }; const renderButton = (button: Button, index: number) => { - if (button.text === "Bounce Animation") { + if (button.text === 'Bounce Animation') { return ( { navigation.navigate(button.route) : button.onPress} + onPress={ + 'route' in button + ? () => navigation.navigate(button.route) + : button.onPress + } buttonStyle={commonStyles.button} textStyle={commonStyles.buttonText} /> @@ -104,6 +112,6 @@ const HomeScreen = ({ navigation }: { navigation: any }) => { )} ); -} +}; export default HomeScreen; diff --git a/sample/react-native/src/screens/IdentificationScreen.tsx b/sample/react-native/src/screens/IdentificationScreen.tsx new file mode 100644 index 0000000..5cd9af8 --- /dev/null +++ b/sample/react-native/src/screens/IdentificationScreen.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import * as DevRev from '@devrev/sdk-react-native'; +import { View, Text, TextInput } from 'react-native'; +import viewModel from '../viewmodel/ViewModel'; +import TouchableOpacityButton from '../components/TouchableOpacityButton'; +import { commonStyles } from '../styles/styles'; + +const IdentificationScreen: React.FC = () => { + const [userID, setUserID] = React.useState(''); + const [sessionToken, setSessionToken] = React.useState(''); + const [email, setEmail] = React.useState(''); + const [currentUserId, setCurrentUserId] = React.useState(null); + + const handleUnverifiedUser = () => { + console.log('Identifying unverified user with:', userID); + DevRev.identifyUnverifiedUser(userID); + setCurrentUserId(userID); + }; + + const handleVerifiedUser = () => { + console.log( + 'Identifying verified user with:', + userID, + 'and session token:', + sessionToken + ); + DevRev.identifyVerifiedUser(userID, sessionToken); + setCurrentUserId(userID); + }; + + const handleUpdateUser = () => { + if (!currentUserId) { + console.error('No user ID found. Please identify a user first.'); + return; + } + + const identity = { + userRef: currentUserId, + userTraits: { + email: email, + }, + }; + + console.log('Updating user with:', identity); + DevRev.updateUser(identity); + }; + + const handleLogout = () => { + viewModel.logout(); + setCurrentUserId(null); + }; + + return ( + + Unverified User + + + + + + + Verified User + + + + + + + + Update User + + + + + + + Logout + + + ); +}; + +export default IdentificationScreen; diff --git a/sample/src/screens/PushNotificationsScreen.tsx b/sample/react-native/src/screens/PushNotificationsScreen.tsx similarity index 59% rename from sample/src/screens/PushNotificationsScreen.tsx rename to sample/react-native/src/screens/PushNotificationsScreen.tsx index cd96e7e..d53ff56 100644 --- a/sample/src/screens/PushNotificationsScreen.tsx +++ b/sample/react-native/src/screens/PushNotificationsScreen.tsx @@ -1,26 +1,38 @@ import React, { useState } from 'react'; import viewModel from '../viewmodel/ViewModel'; -import { View, Text, StyleSheet } from 'react-native'; +import { View } from 'react-native'; import TouchableOpacityButton from '../components/TouchableOpacityButton'; import AlertDialog from '../components/AlertDialog'; import { commonStyles } from '../styles/styles'; const PushNotificationsScreen: React.FC = () => { - const [showDialog, setShowDialog] = useState<{ title: string; message: string } | null>(null); + const [showDialog, setShowDialog] = useState<{ + title: string; + message: string; + } | null>(null); - const handleRegister = () => { - viewModel.registerDeviceToken(); - setShowDialog({ title: 'Success', message: 'Successfully registered for push notifications.' }); + const registerPushNotifications = () => { + viewModel.registerPushNotifications(); + setShowDialog({ + title: 'Success', + message: 'Successfully registered for push notifications!', + }); }; - const handleUnregister = () => { + const unregisterDevice = () => { viewModel.unregisterDevice(); - setShowDialog({ title: 'Success', message: 'Successfully unregistered for push notifications.' }); + setShowDialog({ + title: 'Success', + message: 'Successfully unregistered the device!', + }); }; const buttons = [ - { text: "Register for push notifications", onPress: handleRegister }, - { text: "Unregister for push notifications", onPress: handleUnregister }, + { + text: 'Register for push notifications', + onPress: registerPushNotifications, + }, + { text: 'Unregister the device', onPress: unregisterDevice }, ] as const; return ( diff --git a/sample/react-native/src/screens/SessionAnalyticsScreen.tsx b/sample/react-native/src/screens/SessionAnalyticsScreen.tsx new file mode 100644 index 0000000..dc35835 --- /dev/null +++ b/sample/react-native/src/screens/SessionAnalyticsScreen.tsx @@ -0,0 +1,218 @@ +import React, { useRef, useEffect } from 'react'; +import { + View, + Text, + TextInput, + ScrollView, + Platform, + findNodeHandle, +} from 'react-native'; +import * as DevRev from '@devrev/sdk-react-native'; +import TouchableOpacityButton from '../components/TouchableOpacityButton'; +import { commonStyles } from '../styles/styles'; + +const SessionMonitoringButtons = [ + { text: 'Stop All Monitoring', onPress: DevRev.stopAllMonitoring }, + { text: 'Resume All Monitoring', onPress: DevRev.resumeAllMonitoring }, +] as const; + +const SessionRecordingButtons = [ + { text: 'Start Recording', onPress: DevRev.startRecording }, + { text: 'Stop Recording', onPress: DevRev.stopRecording }, + { text: 'Pause Recording', onPress: DevRev.pauseRecording }, + { text: 'Resume Recording', onPress: DevRev.resumeRecording }, + { + text: 'Pause User Interaction Tracking', + onPress: DevRev.pauseUserInteractionTracking, + }, + { + text: 'Resume User Interaction Tracking', + onPress: DevRev.resumeUserInteractionTracking, + }, +] as const; + +const TimerButtons = [ + { + text: 'Start Timer', + onPress: () => + DevRev.startTimer('test-event', { test_key1: 'test-value1' }), + }, + { + text: 'Stop Timer', + onPress: () => DevRev.endTimer('test-event', { test_key2: 'test-value2' }), + }, +] as const; + +const OnDemandSessionButtons = [ + { + text: 'Process All On-Demand Sessions', + onPress: DevRev.processAllOnDemandSessions, + }, +] as const; + +const WebViewButton = [ + { + text: 'Open Web View', + }, +] as const; + +const ListViewButton = [ + { + text: 'Open Large Scrollable List', + }, +] as const; + +const SessionAnalyticsScreen: React.FC<{ navigation: any }> = ({ + navigation, +}) => { + const sensitiveLabelRef = useRef(null); + const textFieldRef = useRef(null); + + try { + DevRev.addSessionProperties({ + test_user_id: 'test_001', + }); + DevRev.trackScreenName('SessionAnalytics'); + } catch (error) { + console.error('Error in session tracking:', error); + } + + useEffect(() => { + // Mark sensitive view + if (sensitiveLabelRef.current) { + const viewTag = findNodeHandle(sensitiveLabelRef.current); + console.log('Marking sensitive view with tag:', viewTag); + if (viewTag) { + DevRev.markSensitiveViews([viewTag]); + } + } + + // Unmark text field + if (textFieldRef.current) { + const viewTag = findNodeHandle(textFieldRef.current); + console.log('Unmarking text field with tag:', viewTag); + if (viewTag) { + DevRev.unmarkSensitiveViews([viewTag]); + } + } + }, []); // Run once on mount + + const handleDelayedScreenNavigation = () => { + try { + DevRev.setInScreenTransitioning(true); + + setTimeout(() => { + navigation.navigate('DelayedScreen'); + }, 2000); + } catch (error) { + console.error('Error in delayed screen navigation:', error); + DevRev.setInScreenTransitioning(false); + navigation.navigate('DelayedScreen'); + } + }; + + return ( + + + Session Monitoring + {SessionMonitoringButtons.map((button, index) => ( + + ))} + + Session Recording + {SessionRecordingButtons.map((button, index) => ( + + ))} + + Timer + {TimerButtons.map((button, index) => ( + + ))} + + Manual Masking / Unmasking + {}} + buttonText="Manually Masked UI Item" + buttonStyle={commonStyles.button} + textStyle={commonStyles.buttonText} + /> + + + On-Demand Sessions + {OnDemandSessionButtons.map((button, index) => ( + + ))} + + Web View + {WebViewButton.map((button, index) => ( + navigation.navigate('WebViewScreen')} + buttonText={button.text} + buttonStyle={commonStyles.button} + textStyle={commonStyles.buttonText} + /> + ))} + + Large Scrollable List + {ListViewButton.map((button, index) => ( + navigation.navigate('FlatListScreen')} + buttonText={button.text} + buttonStyle={commonStyles.button} + textStyle={commonStyles.buttonText} + /> + ))} + + {Platform.OS === 'android' && ( + <> + + Navigate to the Delayed Screen + + + + )} + + + ); +}; + +export default SessionAnalyticsScreen; diff --git a/sample/src/screens/SupportChatScreen.tsx b/sample/react-native/src/screens/SupportChatScreen.tsx similarity index 100% rename from sample/src/screens/SupportChatScreen.tsx rename to sample/react-native/src/screens/SupportChatScreen.tsx diff --git a/sample/react-native/src/screens/WebViewScreen.tsx b/sample/react-native/src/screens/WebViewScreen.tsx new file mode 100644 index 0000000..d68e2d8 --- /dev/null +++ b/sample/react-native/src/screens/WebViewScreen.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { View, StyleSheet } from 'react-native'; +import { WebView } from 'react-native-webview'; + +const WebViewScreen: React.FC = () => { + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + container: { flex: 1 }, + webview: { flex: 1 }, +}); + +export default WebViewScreen; diff --git a/sample/src/styles/colors.ts b/sample/react-native/src/styles/colors.ts similarity index 92% rename from sample/src/styles/colors.ts rename to sample/react-native/src/styles/colors.ts index 5858ee6..6f86535 100644 --- a/sample/src/styles/colors.ts +++ b/sample/react-native/src/styles/colors.ts @@ -40,4 +40,7 @@ export const systemColors = { }; // Export the appropriate colors based on platform -export const colors = Platform.OS === 'ios' || Platform.OS === 'android' ? systemColors : customColors; +export const colors = + Platform.OS === 'ios' || Platform.OS === 'android' + ? systemColors + : customColors; diff --git a/sample/src/styles/styles.ts b/sample/react-native/src/styles/styles.ts similarity index 97% rename from sample/src/styles/styles.ts rename to sample/react-native/src/styles/styles.ts index 29e357d..1d90de8 100644 --- a/sample/src/styles/styles.ts +++ b/sample/react-native/src/styles/styles.ts @@ -92,8 +92,8 @@ export const commonStyles = StyleSheet.create({ }, refreshIcon: { fontSize: 24, - fontWeight: "bold", - transform: [{ rotate: '90deg' }] + fontWeight: 'bold', + transform: [{ rotate: '90deg' }], }, }); @@ -102,7 +102,7 @@ export const roundCheckboxStyles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', marginVertical: 8, - paddingRight: 16 + paddingRight: 16, }, label: { flex: 1, diff --git a/sample/react-native/src/viewmodel/ViewModel.tsx b/sample/react-native/src/viewmodel/ViewModel.tsx new file mode 100644 index 0000000..a9fe3be --- /dev/null +++ b/sample/react-native/src/viewmodel/ViewModel.tsx @@ -0,0 +1,66 @@ +import * as DevRev from '@devrev/sdk-react-native'; +import PushNotificationsService from '../PushNotificationsService'; +import { Platform } from 'react-native'; +import DeviceInfo from 'react-native-device-info'; + +function ViewModel() { + const registerPushNotifications = async () => { + try { + await PushNotificationsService.register(); + } catch (error) { + console.error('Error configuring the push notifications:', error); + } + }; + + const unregisterDevice = async () => { + DeviceInfo.getUniqueId() + .then((id) => DevRev.unregisterDevice(id)) + .catch((error) => { + console.error('Error unregistering the device:', error); + }); + }; + + const logout = async () => { + DeviceInfo.getUniqueId() + .then((id) => DevRev.logout(id)) + .catch((error) => { + console.error('Error logging out:', error); + }); + }; + + const simulateCrash = () => { + // Force a crash by accessing an undefined property. + console.log('Simulating a crash by accessing an undefined property.'); + const obj: any = undefined; + obj.nonExistentMethod(); + }; + + const simulateANR = () => { + if (Platform.OS === 'android') { + console.log( + 'Simulating an ANR on Android by blocking the main thread for 5 seconds.' + ); + // Simulate an ANR by blocking the main thread. + const startTime = Date.now(); + while (Date.now() - startTime < 5000) { + // Block the main thread for five seconds. + Math.random(); + } + + return; + } + + console.log('Simulating ANRs on iOS is not supported'); + }; + + return { + registerPushNotifications, + unregisterDevice, + logout, + simulateCrash, + simulateANR, + }; +} + +const viewModel = ViewModel(); +export default viewModel; diff --git a/sample/react-native/tsconfig.json b/sample/react-native/tsconfig.json new file mode 100644 index 0000000..535576e --- /dev/null +++ b/sample/react-native/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@devrev/sdk-react-native": [ + "../../lib/typescript/src" + ], + "@devrev/sdk-react-native/*": [ + "../../lib/typescript/src/*" + ] + }, + "jsx": "react", + }, + "include": [ + "./src", + "./index.ts", + "./index.tsx" + ] +} diff --git a/sample/src/NotificationService.tsx b/sample/src/NotificationService.tsx deleted file mode 100644 index 84d5e29..0000000 --- a/sample/src/NotificationService.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import notifee, { EventType, AndroidImportance } from '@notifee/react-native'; -import { getMessaging, FirebaseMessagingTypes } from '@react-native-firebase/messaging'; -import { getApp } from '@react-native-firebase/app'; -import { Platform, PermissionsAndroid } from 'react-native'; -import * as DevRev from '@devrev/sdk-react-native'; -import DeviceInfo from 'react-native-device-info'; -import type { DevRevNotification, StaleNotificationData, SilentNotificationData } from './model/DevRevNotification'; - -const messaging = getMessaging(getApp()); -const isAndroid = Platform.OS === 'android'; - -const handleSilentNotification = async (data: SilentNotificationData) => { - try { - if (data.silent) { - const silentData: StaleNotificationData = JSON.parse(data.silent); - if (silentData.stale_notification_ids?.length) { - console.log('Handling stale notifications:', silentData.stale_notification_ids); - // Cancel stale notifications - for (const notificationId of silentData.stale_notification_ids) { - await notifee.cancelNotification(notificationId); - } - } - } - } catch (error) { - console.error('Error handling silent notification:', error); - } -}; - -// Create default channel for Android -const createDefaultChannel = async (): Promise => { - if (Platform.OS === 'android') { - await notifee.createChannel({ - id: 'default', - name: 'Default Channel', - importance: AndroidImportance.HIGH, - }); - } -}; - -// Request user permission for notifications -const requestUserPermission = async (): Promise => { - if (Platform.OS === 'ios') { - const settings = await notifee.requestPermission(); - return settings.authorizationStatus >= 0; - } else { - const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS); - return granted === PermissionsAndroid.RESULTS.GRANTED; - } -}; - -const getDeviceToken = async (): Promise => { - try { - console.log('getDeviceToken: Starting token retrieval...'); - - if (isAndroid) { - console.log('getDeviceToken: Getting Android token...'); - const token = await messaging.getToken(); - console.log('getDeviceToken: Android token received:', token); - return token; - } else { - console.log('getDeviceToken: Getting iOS token...'); - const token = await messaging.getAPNSToken(); - console.log('getDeviceToken: iOS token received:', token); - return token; - } - } catch (error) { - console.error('getDeviceToken: Error in getDeviceToken:', error); - throw error; - } -}; - -// Register device with DevRev -const registerDevice = async (): Promise => { - try { - console.log('Starting device registration...'); - const hasPermission = await requestUserPermission(); - console.log('Permission status:', hasPermission); - - if (hasPermission) { - console.log('Getting device ID...'); - const deviceId = await DeviceInfo.getUniqueId(); - console.log('Device ID:', deviceId); - console.log('Getting device token...'); - const token = await getDeviceToken(); - console.log('Token received:', token); - - if (token) { - console.log('Registering with DevRev...'); - await DevRev.registerDeviceToken(token, deviceId); - console.log('Successfully registered with DevRev'); - } else { - console.warn('Failed to get device token'); - } - } else { - console.warn('Notification permission not granted'); - } - } catch (error) { - console.error('Error registering device:', error); - throw error; - } -}; - -// Display notification -const displayNotification = async ( - remoteMessage: FirebaseMessagingTypes.RemoteMessage -): Promise => { - try { - if (remoteMessage.data?.silent) { - await handleSilentNotification(remoteMessage.data); - return; - } - - let notification: DevRevNotification | null = null; - - if (remoteMessage.data?.message) { - try { - notification = JSON.parse(remoteMessage.data.message as string); - } catch (parseError) { - console.error('Error parsing notification:', parseError); - } - } - - if (!notification) { - console.warn('No valid notification data'); - return; - } - - await notifee.displayNotification({ - title: notification.title, - body: notification.body, - android: { - channelId: 'default', - pressAction: { - id: 'default', - }, - smallIcon: 'ic_launcher_round', - importance: AndroidImportance.HIGH, - }, - ios: { - foregroundPresentationOptions: { - badge: true, - sound: true, - banner: true, - list: true, - }, - }, - data: remoteMessage.data, - }); - } catch (error) { - console.error('Error displaying notification:', error); - } -}; - -// Handle notification press -const handleNotificationPress = async (notification: any): Promise => { - console.log('Notification pressed:', notification); - if (notification.data?.message) { - DevRev.processPushNotification(notification.data.message as string) - } -}; - -// Setup notification listeners -const setupNotificationListeners = (): void => { - // Handle token refresh - messaging.onTokenRefresh(async (token) => { - try { - const deviceId = await DeviceInfo.getUniqueId(); - DevRev.registerDeviceToken(token, deviceId); - } catch (error) { - console.error('Error handling token refresh:', error); - } - }); - - // Handle background messages - messaging.setBackgroundMessageHandler(async (remoteMessage) => { - console.log("BACKGROUND MESSAGE: ", remoteMessage); - if (Platform.OS === "android") { - await displayNotification(remoteMessage); - } - }); - - // Handle foreground messages - messaging.onMessage(async (remoteMessage) => { - console.log("FOREGROUND MESSAGE: ", remoteMessage); - await displayNotification(remoteMessage); - }); - - // Handle foreground notification press events - notifee.onForegroundEvent(async ({ type, detail }) => { - try { - switch (type) { - case EventType.PRESS: - await handleNotificationPress(detail.notification); - break; - case EventType.DISMISSED: - console.log('User dismissed notification', detail.notification); - break; - } - } catch (error) { - console.error('Error handling foreground event:', error); - } - }); - - // Handle background/quit state notification press - notifee.onBackgroundEvent(async ({ type, detail }) => { - try { - if (type === EventType.PRESS) { - await handleNotificationPress(detail.notification); - } - } catch (error) { - console.error('Error handling background event:', error); - } - }); - - // Check if app was opened from a notification - messaging - .getInitialNotification() - .then(async (remoteMessage) => { - if (remoteMessage) { - console.log('App opened from quit state:', remoteMessage); - await handleNotificationPress(remoteMessage); - } - }) - .catch((error) => { - console.error('Error checking initial notification:', error); - }); -}; - -// Cancel all notifications -const cancelAllNotifications = async (): Promise => { - try { - await notifee.cancelAllNotifications(); - } catch (error) { - console.error('Error canceling notifications:', error); - } -}; - -// Initialize notification service -const initializeNotifications = async (): Promise => { - try { - if (isAndroid) { - await createDefaultChannel(); - } - setupNotificationListeners(); - } catch (error) { - console.error('Error initializing notifications:', error); - } -}; - -export const NotificationService = { - initialize: initializeNotifications, - register: registerDevice, - displayNotification, - cancelAllNotifications, -}; - -export default NotificationService; diff --git a/sample/src/components/TouchableOpacityButton.tsx b/sample/src/components/TouchableOpacityButton.tsx deleted file mode 100644 index e32f69b..0000000 --- a/sample/src/components/TouchableOpacityButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { forwardRef } from 'react'; -import { TouchableOpacity, Text, type TextStyle, type ViewStyle, View } from 'react-native'; -import { commonStyles } from '../styles/styles'; - -interface TouchableOpacityProps { - onPress: () => void; - buttonText: string; - buttonStyle?: ViewStyle; - textStyle?: TextStyle; -} - -const TouchableOpacityButton = forwardRef(({ - onPress, - buttonText, - buttonStyle, - textStyle, -}, ref) => { - return ( - - - {buttonText} - - - ); -}); - -export default TouchableOpacityButton; diff --git a/sample/src/screens/DelayedScreen.tsx b/sample/src/screens/DelayedScreen.tsx deleted file mode 100644 index a3b7716..0000000 --- a/sample/src/screens/DelayedScreen.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { useEffect } from 'react'; -import { View, Text, Platform } from 'react-native'; -import * as DevRev from '@devrev/sdk-react-native'; -import { commonStyles } from '../styles/styles'; - -const DelayedScreen: React.FC = () => { - useEffect(() => { - // End the screen transition when the screen is loaded (Android only) - if (Platform.OS === 'android') { - try { - DevRev.endScreenTransition(); - } catch (error) { - console.error('Error ending screen transition:', error); - } - } - }, []); - - return ( - - Delayed Screen - - ); -}; - -export default DelayedScreen; diff --git a/sample/src/screens/IdentificationScreen.tsx b/sample/src/screens/IdentificationScreen.tsx deleted file mode 100644 index 09ce3df..0000000 --- a/sample/src/screens/IdentificationScreen.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React from 'react'; -import * as DevRev from '@devrev/sdk-react-native'; -import { View, Text, TextInput, type ViewStyle, type TextStyle } from 'react-native'; -import viewModel from '../viewmodel/ViewModel'; -import TouchableOpacityButton from '../components/TouchableOpacityButton'; -import { commonStyles } from '../styles/styles'; - -const IdentificationScreen: React.FC = () => { - const [userID, setUserID] = React.useState(''); - const [sessionToken, setSessionToken] = React.useState(''); - const [email, setEmail] = React.useState(''); - const [currentUserId, setCurrentUserId] = React.useState(null); - - const handleUnverifiedUser = () => { - console.log('Identifying unverified user with:', userID); - DevRev.identifyUnverifiedUser(userID); - setCurrentUserId(userID); - }; - - const handleVerifiedUser = () => { - console.log('Identifying verified user with:', userID, 'and session token:', sessionToken); - DevRev.identifyVerifiedUser(userID, sessionToken); - setCurrentUserId(userID); - }; - - const handleUpdateUser = () => { - if (!currentUserId) { - console.error('No user ID found. Please identify a user first.'); - return; - } - - const identity = { - userRef: currentUserId, - userTraits: { - email: email - } - }; - - console.log('Updating user with:', identity); - DevRev.updateUser(identity); - }; - - const handleLogout = () => { - viewModel.logout(); - setCurrentUserId(null); - }; - - return ( - - Unverified User - - - - - - - Verified User - - - - - - - - Update User - - - - - - - Logout - - - ); -}; - -export default IdentificationScreen; diff --git a/sample/src/screens/SessionAnalyticsScreen.tsx b/sample/src/screens/SessionAnalyticsScreen.tsx deleted file mode 100644 index e2ea9e0..0000000 --- a/sample/src/screens/SessionAnalyticsScreen.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React, { useRef, useEffect } from 'react'; -import { View, Text, TextInput, ScrollView, Platform, findNodeHandle } from 'react-native'; -import * as DevRev from '@devrev/sdk-react-native'; -import TouchableOpacityButton from '../components/TouchableOpacityButton'; -import { commonStyles } from '../styles/styles'; - -const SessionMonitoringButtons = [ - { text: "Stop All Monitoring", onPress: DevRev.stopAllMonitoring }, - { text: "Resume All Monitoring", onPress: DevRev.resumeAllMonitoring }, -] as const; - -const SessionRecordingButtons = [ - { text: "Start Recording", onPress: DevRev.startRecording }, - { text: "Stop Recording", onPress: DevRev.stopRecording }, - { text: "Pause Recording", onPress: DevRev.pauseRecording }, - { text: "Resume Recording", onPress: DevRev.resumeRecording }, -] as const; - -const TimerButtons = [ - { - text: "Start Timer", - onPress: () => DevRev.startTimer("test-event", { test_key1: "test-value1" }) - }, - { - text: "Stop Timer", - onPress: () => DevRev.endTimer("test-event", { test_key2: "test-value2" }) - }, -] as const; - -const OnDemandSessionButtons = [ - { text: "Process All On-Demand Sessions", onPress: DevRev.processAllOnDemandSessions }, -] as const; - -const SessionAnalyticsScreen: React.FC<{ navigation: any }> = ({ navigation }) => { - const sensitiveLabelRef = useRef(null); - const textFieldRef = useRef(null); - - try { - DevRev.addSessionProperties({ - test_user_id: "test_001" - }); - DevRev.trackScreen("SessionAnalytics"); - } catch (error) { - console.error('Error in session tracking:', error); - } - - useEffect(() => { - // Mark sensitive view - if (sensitiveLabelRef.current) { - const viewTag = findNodeHandle(sensitiveLabelRef.current); - console.log('Marking sensitive view with tag:', viewTag); - if (viewTag) { - DevRev.markSensitiveViews([viewTag]); - } - } - - // Unmark text field - if (textFieldRef.current) { - const viewTag = findNodeHandle(textFieldRef.current); - console.log('Unmarking text field with tag:', viewTag); - if (viewTag) { - DevRev.unmarkSensitiveViews([viewTag]); - } - } - }, []); // Run once on mount - - const handleDelayedScreenNavigation = () => { - try { - if (Platform.OS === 'android') { - DevRev.startScreenTransition(); - } - - setTimeout(() => { - navigation.navigate('DelayedScreen'); - }, 2000); - } catch (error) { - console.error('Error in delayed screen navigation:', error); - navigation.navigate('DelayedScreen'); - } - }; - - return ( - - - Session Monitoring - {SessionMonitoringButtons.map((button, index) => ( - - ))} - - Session Recording - {SessionRecordingButtons.map((button, index) => ( - - ))} - - Timer - {TimerButtons.map((button, index) => ( - - ))} - - Manual Masking / Unmasking - {}} - buttonText="Manually Masked UI Item" - buttonStyle={commonStyles.button} - textStyle={commonStyles.buttonText} - /> - - - On-Demand Sessions - {OnDemandSessionButtons.map((button, index) => ( - - ))} - - {Platform.OS === 'android' && ( - <> - Navigate to the Delayed Screen - - - )} - - - ); -}; - -export default SessionAnalyticsScreen; diff --git a/sample/src/usePushNotifications.tsx b/sample/src/usePushNotifications.tsx deleted file mode 100644 index 6166538..0000000 --- a/sample/src/usePushNotifications.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import messaging from '@react-native-firebase/messaging'; -import {PermissionsAndroid, Platform} from 'react-native'; - -const usePushNotification = () => { - const requestUserPermission = async () => { - if (Platform.OS === 'ios') { - //Request iOS permission - const authStatus = await messaging().requestPermission(); - const enabled = - authStatus === messaging.AuthorizationStatus.AUTHORIZED || - authStatus === messaging.AuthorizationStatus.PROVISIONAL; - - if (enabled) { - console.log('Authorization status:', authStatus); - } - } else if (Platform.OS === 'android') { - //Request Android permission (For API level 33+, for 32 or below is not required) - if (PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS) { - try { - await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS - ); - } catch (error) { - console.error('Error requesting notification permission:', error); - } - } else { - console.log('POST_NOTIFICATIONS permission is not available on this Android version'); - } - } - } - - const listenToForegroundNotifications = async () => { - const unsubscribe = messaging().onMessage(async remoteMessage => { - console.log( - 'A new message arrived! (FOREGROUND)', - JSON.stringify(remoteMessage), - ); - }); - return unsubscribe; - } - - const listenToBackgroundNotifications = async () => { - const unsubscribe = messaging().setBackgroundMessageHandler( - async remoteMessage => { - console.log( - 'A new message arrived! (BACKGROUND)', - JSON.stringify(remoteMessage), - ); - }, - ); - return unsubscribe; - } - - const onNotificationOpenedAppFromBackground = async () => { - const unsubscribe = messaging().onNotificationOpenedApp( - async remoteMessage => { - console.log( - 'App opened from BACKGROUND by tapping notification:', - JSON.stringify(remoteMessage), - ); - }, - ); - return unsubscribe; - }; - - const onNotificationOpenedAppFromQuit = async () => { - const message = await messaging().getInitialNotification(); - - if(message) { - console.log('App opened from QUIT by tapping notification:', JSON.stringify(message)); - } - }; - - return { - requestUserPermission, - listenToForegroundNotifications, - listenToBackgroundNotifications, - onNotificationOpenedAppFromBackground, - onNotificationOpenedAppFromQuit, - }; -}; - -export default usePushNotification; diff --git a/sample/src/viewmodel/ViewModel.tsx b/sample/src/viewmodel/ViewModel.tsx deleted file mode 100644 index a4896cf..0000000 --- a/sample/src/viewmodel/ViewModel.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import * as DevRev from '@devrev/sdk-react-native'; -import installations from '@react-native-firebase/installations'; -import NotificationService from '../NotificationService'; -import { Platform } from 'react-native'; - -function ViewModel() { - const registerDeviceToken = async () => { - try { - await NotificationService.register() - } catch (error) { - console.error('Error registering device:', error); - } - }; - - const unregisterDevice = async () => { - try { - const id = await installations().getId(); - DevRev.unregisterDevice(id); - console.log('Device unregistered with ID:', id); - } catch (error) { - console.error('Error registering device:', error); - } - } - - const logout = async () => { - try { - const id = await installations().getId(); - DevRev.logout(id); - console.log('Logged out'); - } catch (error) { - console.error('Error logging out:', error); - } - } - - const simulateCrash = () => { - // Force a crash by accessing undefined property - const obj: any = undefined; - obj.nonExistentMethod(); - }; - - const simulateANR = () => { - if (Platform.OS === 'android') { - // Simulate ANR by blocking the main thread - const startTime = Date.now(); - while (Date.now() - startTime < 5000) { - // Block the main thread for 5 seconds - Math.random(); - } - } - }; - - return { - registerDeviceToken, - unregisterDevice, - logout, - simulateCrash, - simulateANR - } -} - -const viewModel = ViewModel(); -export default viewModel; From 34fcdfb716bc62b4228c730cb0d103a4d148ae0c Mon Sep 17 00:00:00 2001 From: Yug2801 Date: Mon, 24 Nov 2025 14:47:33 +0530 Subject: [PATCH 2/5] Trigger CI From aa30608f2c0d1defb5f393385da3f92f03bb7ea7 Mon Sep 17 00:00:00 2001 From: Yug2801 Date: Mon, 24 Nov 2025 15:31:08 +0530 Subject: [PATCH 3/5] Add DevRev config and CODEOWNERS --- .devrev/repo.yaml | 1 + .github/CODEOWNERS | 0 2 files changed, 1 insertion(+) create mode 100644 .devrev/repo.yaml create mode 100644 .github/CODEOWNERS diff --git a/.devrev/repo.yaml b/.devrev/repo.yaml new file mode 100644 index 0000000..a1d97bf --- /dev/null +++ b/.devrev/repo.yaml @@ -0,0 +1 @@ +deployable: true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e69de29 From 84b550fef61b66684857b3d2688dea983fa72185 Mon Sep 17 00:00:00 2001 From: Yug2801 Date: Mon, 24 Nov 2025 15:34:48 +0530 Subject: [PATCH 4/5] Add global codeowner --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e69de29..4def115 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global code owner +* @rohan-devrev From ac544e4cb5d24597f944417f8fff49e9062f0e55 Mon Sep 17 00:00:00 2001 From: Yug2801 Date: Mon, 24 Nov 2025 15:36:43 +0530 Subject: [PATCH 5/5] Rename DevRev config --- .devrev/{repo.yaml => repo.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .devrev/{repo.yaml => repo.yml} (100%) diff --git a/.devrev/repo.yaml b/.devrev/repo.yml similarity index 100% rename from .devrev/repo.yaml rename to .devrev/repo.yml