diff --git a/.github/workflows/inhouseCodePush.yml b/.github/workflows/inhouseCodePush.yml index 8f92feb3..4dd88758 100644 --- a/.github/workflows/inhouseCodePush.yml +++ b/.github/workflows/inhouseCodePush.yml @@ -1,4 +1,4 @@ -name: Navi CodePush Deployment +name: inhouse-code-push-cli on: workflow_dispatch: @@ -10,6 +10,13 @@ on: options: - QA - Prod + buildFlavor: + description: 'Build flavor (Field/Calling)' + required: true + type: choice + options: + - Field + - Calling minTargetVersion: description: 'Minimum target version' required: true @@ -24,7 +31,7 @@ on: type: string jobs: - deploy: + generate: runs-on: [default] outputs: package_version: ${{ steps.get_version.outputs.version }} @@ -49,13 +56,19 @@ jobs: echo "@navi:registry=https://nexus.cmd.navi-tech.in/repository/npm-packages/" >> .npmrc echo "//nexus.cmd.navi-tech.in/repository/npm-packages/:_authToken=${{ secrets.NEXUS_AUTH_TOKEN }}" >> .npmrc - - name: Get version from package.json + - name: Get version from build flavor id: get_version run: | - VERSION=$(node -p "require('./package.json').version") - BUILD_NUMBER=$(node -p "require('./package.json').buildNumber") + if [[ "${{ github.event.inputs.buildFlavor }}" == "Field" ]]; then + VERSION=$(cat buildFlavor/field/buildVersion.txt) + BUILD_NUMBER=$(cat buildFlavor/field/buildNumber.txt) + else + VERSION=$(cat buildFlavor/tele/buildVersion.txt) + BUILD_NUMBER=$(cat buildFlavor/tele/buildNumber.txt) + fi echo "version=$VERSION" >> $GITHUB_OUTPUT echo "buildNumber=$BUILD_NUMBER" >> $GITHUB_OUTPUT + echo "Using ${{ github.event.inputs.buildFlavor }} flavor: version=$VERSION, buildNumber=$BUILD_NUMBER" - name: Install yarn run: npm install --global yarn @@ -66,32 +79,191 @@ jobs: yarn add @navi/navi-codepush-cli@1.0.4 # Environment specific steps - - name: Prepare QA Environment - if: github.event.inputs.environment == 'QA' + - name: Prepare QA Field Environment + if: github.event.inputs.environment == 'QA' && github.event.inputs.buildFlavor == 'Field' run: yarn move:qa && yarn prepare-codepush-field-build - - name: Prepare Prod Environment - if: github.event.inputs.environment == 'Prod' + - name: Prepare QA Calling Environment + if: github.event.inputs.environment == 'QA' && github.event.inputs.buildFlavor == 'Calling' + run: yarn move:qa && yarn prepare-codepush-tele-build + + - name: Prepare Prod Field Environment + if: github.event.inputs.environment == 'Prod' && github.event.inputs.buildFlavor == 'Field' run: yarn move:prod && yarn prepare-codepush-field-build - - name: Deploy to QA - if: github.event.inputs.environment == 'QA' + - name: Prepare Prod Calling Environment + if: github.event.inputs.environment == 'Prod' && github.event.inputs.buildFlavor == 'Calling' + run: yarn move:prod && yarn prepare-codepush-tele-build + + - name: Deploy Field to QA + if: github.event.inputs.environment == 'QA' && github.event.inputs.buildFlavor == 'Field' run: | - navi-codepush-cli \ - --app cosmos \ + npx navi-codepush-cli \ + --app FIELD_COSMOS \ --versionMin ${{ github.event.inputs.minTargetVersion }} \ --versionMax ${{ github.event.inputs.maxTargetVersion }} \ --desc "${{ github.event.inputs.description }}" \ --platform android \ --env qa - - name: Deploy to Production - if: github.event.inputs.environment == 'Prod' + - name: Deploy Calling to QA + if: github.event.inputs.environment == 'QA' && github.event.inputs.buildFlavor == 'Calling' run: | - navi-codepush-cli \ - --app cosmos \ + npx navi-codepush-cli \ + --app TELE_COSMOS \ --versionMin ${{ github.event.inputs.minTargetVersion }} \ --versionMax ${{ github.event.inputs.maxTargetVersion }} \ --desc "${{ github.event.inputs.description }}" \ --platform android \ - --env prod \ No newline at end of file + --env qa + + - name: Deploy Field to Production + if: github.event.inputs.environment == 'Prod' && github.event.inputs.buildFlavor == 'Field' + run: | + npx navi-codepush-cli \ + --app FIELD_COSMOS \ + --versionMin ${{ github.event.inputs.minTargetVersion }} \ + --versionMax ${{ github.event.inputs.maxTargetVersion }} \ + --desc "${{ github.event.inputs.description }}" \ + --platform android \ + --env prod + + - name: Deploy Calling to Production + if: github.event.inputs.environment == 'Prod' && github.event.inputs.buildFlavor == 'Calling' + run: | + npx navi-codepush-cli \ + --app TELE_COSMOS \ + --versionMin ${{ github.event.inputs.minTargetVersion }} \ + --versionMax ${{ github.event.inputs.maxTargetVersion }} \ + --desc "${{ github.event.inputs.description }}" \ + --platform android \ + --env prod + + generate_source_map: + needs: generate + outputs: + package_version: ${{ needs.generate.outputs.package_version }} + build_number: ${{ needs.generate.outputs.build_number }} + runs-on: [default] + if: success() && (github.event.inputs.environment == 'Prod') # Only create source map for Prod releases + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + token: ${{ secrets.MY_REPO_PAT }} + submodules: recursive + - name: Set Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: 16.x + - name: Install yarn + run: npm install --global yarn + - name: Install dependency + run: yarn + - name: Generate Android Bundle and Source Map + run: | + npx react-native bundle \ + --dev false \ + --minify true \ + --platform android \ + --entry-file index.js \ + --reset-cache \ + --bundle-output index.android.bundle \ + --sourcemap-output index.android.bundle.map + + - name: Upload Source Map + uses: actions/upload-artifact@v4 + with: + name: source-map-${{needs.generate.outputs.package_version}} + path: index.android.bundle.map + + upload_sourcemap_cybertron: + needs: generate_source_map + runs-on: [default] + if: success() && (github.event.inputs.environment == 'Prod') + steps: + - name: Download Source Map + uses: actions/download-artifact@v4 + with: + name: source-map-${{needs.generate_source_map.outputs.package_version}} + path: ./artifacts + + - name: 'create release' + run: | + cd artifacts + ls -lh + echo creating release + response=$(curl --location --request POST '${{secrets.CYBERTRON_BASE_URL}}/api/v1/release' \ + --header 'Content-Type: application/json' \ + --data '{ + "releaseVersion": "${{ needs.generate_source_map.outputs.package_version }}", + "projectReferenceId": "${{ secrets.CYBERTRON_PROJECT_ID }}" + }') + echo $response + + - name: 'create presigned url' + run: | + presigned_url_source_map='${{secrets.CYBERTRON_BASE_URL}}/api/v1/get-sourcemap-upload-url?project_id=${{secrets.CYBERTRON_PROJECT_ID}}&release_id=${{ needs.generate_source_map.outputs.package_version }}&file_name=index.android.bundle.map' + response=$(curl --location $presigned_url_source_map) + echo "$response" + upload_url=$(echo "$response" | jq -r .url) + echo $upload_url + curl --location --request PUT --progress-bar --header "Content-Type: application/octet-stream" $upload_url --upload-file artifacts/index.android.bundle.map + + create_release_tag: + needs: generate_source_map + runs-on: [default] + if: success() && (github.event.inputs.environment == 'Prod') # Only create tag for Prod releases + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + token: ${{ secrets.MY_REPO_PAT }} + submodules: recursive + persist-credentials: true + - name: Check if tag exists + id: check_tag + run: | + TAG_NAME="${{ needs.generate_source_map.outputs.package_version }}" + EXISTING_TAG=$(git ls-remote --tags origin refs/tags/$TAG_NAME) + if [[ -z "$EXISTING_TAG" ]]; then + echo "Tag $TAG_NAME does not exist." + echo "tag_exists=false" >> $GITHUB_ENV + else + echo "Tag $TAG_NAME already exists." + echo "tag_exists=true" >> $GITHUB_ENV + fi + + - name: Create and push tag + if: env.tag_exists == 'false' + run: | + TAG_NAME="${{ needs.generate_source_map.outputs.package_version }}" + # git config --local user.email "${{ github.actor }}@github.com" + git config --local user.name "${{ github.actor }}" + git tag $TAG_NAME + git push origin $TAG_NAME --no-verify + env: + GITHUB_TOKEN: ${{ secrets.MY_REPO_PAT }} + - name: Create release tag + run: | + TAG_NAME="${{ needs.generate_source_map.outputs.package_version }}" + BUILD_NUMBER="${{ needs.generate.outputs.build_number }}" + RELEASE_NAME="$TAG_NAME (build $BUILD_NUMBER) code push" + DESCRIPTION="${{ github.event.inputs.description }}" + REPO="navi-medici/address-verification-app" + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + + curl -X POST \ + -H "Authorization: token ${{ secrets.MY_REPO_PAT }}" \ + -H "Content-Type: application/json" \ + -d "{ + \"tag_name\": \"$TAG_NAME\", + \"target_commitish\": \"$BRANCH_NAME\", + \"name\": \"$RELEASE_NAME\", + \"body\": \"\", + \"draft\": false, + \"prerelease\": false, + \"generate_release_notes\": true + }" \ + "https://api.github.com/repos/$REPO/releases" + shell: bash diff --git a/App.tsx b/App.tsx index 87bbf834..3740b542 100644 --- a/App.tsx +++ b/App.tsx @@ -9,13 +9,6 @@ import { StatusBar, type Permission, } from 'react-native'; -import { - default as codePush, - default as CodePush, - DownloadProgress, - DownloadProgressCallback, - SyncStatusChangedCallback, -} from 'react-native-code-push'; import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; import store, { persistor } from './src/store/store'; @@ -26,15 +19,14 @@ import { toastConfigs, ToastContainer } from './RN-UI-LIB/src/components/toast'; import { hydrateGlobalImageMap } from '@common/CachedImage'; import { CLICKSTREAM_EVENT_NAMES, LocalStorageKeys } from '@common/Constants'; -import { sendDeviceDetailsToClickstream } from '@components/utlis/commonFunctions'; +import { getAppVersion, sendDeviceDetailsToClickstream } from '@components/utlis/commonFunctions'; import { linkingConf } from '@components/utlis/deeplinkingUtils'; -import { getBuildFlavour } from '@components/utlis/DeviceUtils'; +import { getBuildFlavour, restartApp } from '@components/utlis/DeviceUtils'; import { initSentry } from '@components/utlis/sentry'; import { GLOBAL, setGlobalBuildFlavour } from '@constants/Global'; import { AppStates } from '@interfaces/appStates'; import analytics from '@react-native-firebase/analytics'; import { COLORS } from '@rn-ui-lib/colors'; -import { MILLISECONDS_IN_A_SECOND } from '@rn-ui-lib/utils/common'; import { type TDocumentObj } from '@screens/caseDetails/interface'; import { addClickstreamEvent } from '@services/clickstreamEventService'; import { setJsErrorHandler } from '@services/exception-handler.service'; @@ -45,11 +37,12 @@ import CodePushLoadingModal, { CodePushLoadingModalRef } from './CodePushModal'; import ErrorBoundary from './src/common/ErrorBoundary'; import ScreenshotBlocker from './src/components/utlis/ScreenshotBlocker'; import { setItem } from './src/components/utlis/storageHelper'; -import { ENV } from './src/constants/config'; +import { BASE_AV_APP_URL, ENV } from './src/constants/config'; import AuthRouter from './src/screens/auth/AuthRouter'; import Permissions from './src/screens/permissions/Permissions'; import fetchUpdatedRemoteConfig from './src/services/firebaseFetchAndUpdate.service'; import { StorageKeys } from './src/types/storageKeys'; +import { CodePushStorageKeys, useCheckVersion } from '@hooks/useCheckVersion'; if (!__DEV__) { initSentry(); @@ -64,37 +57,45 @@ LogBox.ignoreAllLogs(); export let GlobalDocumentMap: Record = {}; -async function checkCodePushAndSync( - onSyncStatusChange: SyncStatusChangedCallback, - onDownloadProgress: DownloadProgressCallback -) { - try { - await CodePush.sync( - { - installMode: codePush.InstallMode.IMMEDIATE, - }, - onSyncStatusChange, - onDownloadProgress - ); - } catch (error) {} -} - -function handleAppStateChange( - nextAppState: any, - onSyncStatusChange: SyncStatusChangedCallback, - onDownloadProgress: DownloadProgressCallback -) { - if (nextAppState == 'active') { - checkCodePushAndSync(onSyncStatusChange, onDownloadProgress); - } -} - -const PERMISSION_CHECK_POLL_INTERVAL = 5 * MILLISECONDS_IN_A_SECOND; - function App() { const [permissions, setPermissions] = React.useState(true); const modalRef = React.useRef(null); + const { checkForUpdates, applyUpdate } = useCheckVersion({ + apiConfig: { + baseUrl: BASE_AV_APP_URL, + appVersionParam: getAppVersion(), + }, + onUpdateAvailable: async (result) => { + const updateCheck = (await AsyncStorage.getItem(CodePushStorageKeys.CodePushVersionTag)) ?? 0; + if (+result.versionTag === +updateCheck) { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CODEPUSH_INFINITE_INSTALL_ERROR, {}); + return; + } + + if (result.isAvailable) { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_INSTALLING_CODEPUSH, {}); + modalRef.current?.show(); + applyUpdate(result.url, result.versionTag); + } + }, + onDownloadProgress: (received, total) => { + modalRef.current?.updateProgress({ + totalBytes: +total, + receivedBytes: +received, + }); + }, + onUpdateSuccess: () => { + setTimeout(() => { + restartApp(); + }, 4000); + }, + onUpdateFail: (message?: string) => { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CODEPUSH_UNKNOWN_ERROR, { message }); + modalRef.current?.hide(); + }, + }); + const askForPermissions = async () => { const permissionsToRequest = await getPermissionsToRequest(); @@ -116,34 +117,6 @@ function App() { } }; - const onSyncStatusChange = (syncStatus: codePush.SyncStatus) => { - switch (syncStatus) { - case codePush.SyncStatus.DOWNLOADING_PACKAGE: - modalRef.current?.show(); - break; - case codePush.SyncStatus.INSTALLING_UPDATE: - addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_INSTALLING_CODEPUSH, {}); - break; - case codePush.SyncStatus.UP_TO_DATE: - modalRef.current?.hide(); - break; - case codePush.SyncStatus.UNKNOWN_ERROR: - addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CODEPUSH_UNKNOWN_ERROR, {}); - modalRef.current?.hide(); - break; - default: - addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CODEPUSH_DEFAULT_STATUS, {}); - modalRef.current?.hide(); - break; - } - }; - - const onDownloadProgress = (downloadProgress: DownloadProgress) => { - if (downloadProgress) { - modalRef.current?.updateProgress(downloadProgress); - } - }; - const getActiveRouteName = (state) => { if (!state || typeof state.index !== 'number') { return 'Unknown'; @@ -179,8 +152,8 @@ function App() { if (GLOBAL.AGENT_ID) { syncSelfCallData(GLOBAL.AGENT_ID); } + checkForUpdates(); } - handleAppStateChange(change, onSyncStatusChange, onDownloadProgress); hydrateGlobalImageMap(); }); (async () => { @@ -190,7 +163,6 @@ function App() { GlobalDocumentMap = parsedData; } })(); - checkCodePushAndSync(onSyncStatusChange, onDownloadProgress); setForegroundTimeStampAndClickstream(); return () => { diff --git a/CodePushModal.tsx b/CodePushModal.tsx index 485bfc40..aec0effb 100644 --- a/CodePushModal.tsx +++ b/CodePushModal.tsx @@ -2,12 +2,16 @@ import { COLORS } from '@rn-ui-lib/colors'; import ProgressBar from './ProgressBar'; import React, { forwardRef, useImperativeHandle, useState } from 'react'; import { View, StyleSheet, ActivityIndicator } from 'react-native'; -import { DownloadProgress } from 'react-native-code-push'; import ModalWrapperForAlfredV2 from '@common/ModalWrapperForAlfredV2'; import Text from '@rn-ui-lib/components/Text'; import NaviLogoWithTextIcon from '@rn-ui-lib/icons/NaviLogoWithTextIcon'; import { GenericStyles } from '@rn-ui-lib/styles'; +interface DownloadProgress { + totalBytes: number; + receivedBytes: number; +} + export interface CodePushLoadingModalRef { show: () => void; hide: () => void; diff --git a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java index e9ec947f..ee3135e4 100644 --- a/android/app/src/main/java/com/avapp/DeviceUtilsModule.java +++ b/android/app/src/main/java/com/avapp/DeviceUtilsModule.java @@ -437,4 +437,27 @@ public class DeviceUtilsModule extends ReactContextBaseJavaModule { promise.reject(e); } } + + @ReactMethod + public void restartApp(Promise promise) { + try { + Activity activity = getCurrentActivity(); + if (activity != null) { + triggerRestart(activity); + promise.resolve(true); + } else { + promise.reject("RESTART_ERROR", "Activity is null."); + } + } catch (Exception e) { + promise.reject("RESTART_ERROR", "An unexpected error occurred: " + e.getMessage()); + } + } + + private void triggerRestart(Activity context) { + Intent intent = new Intent(context, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + context.startActivity(intent); + context.finish(); + Runtime.getRuntime().exit(0); + } } diff --git a/android/app/src/main/java/com/avapp/MainApplication.java b/android/app/src/main/java/com/avapp/MainApplication.java index d79b82ca..8163375e 100644 --- a/android/app/src/main/java/com/avapp/MainApplication.java +++ b/android/app/src/main/java/com/avapp/MainApplication.java @@ -34,7 +34,6 @@ import java.util.HashMap; import java.util.List; import com.github.anrwatchdog.ANRWatchDog; -import com.microsoft.codepush.react.CodePush; import com.navi.alfred.AlfredConfig; import com.navi.alfred.AlfredManager; import com.navi.alfred.network.AlfredApiLogsManager; @@ -45,6 +44,8 @@ import com.navi.pulse.PulseSDKConfig; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.otahotupdate.OtaHotUpdate; + import java.util.Map; @@ -85,7 +86,7 @@ public class MainApplication extends Application implements ReactApplication, Ap @Override protected String getJSBundleFile() { - return CodePush.getJSBundleFile(); + return OtaHotUpdate.Companion.getBundleJS(); } }; diff --git a/buildFlavor/field/buildNumber.txt b/buildFlavor/field/buildNumber.txt index 99bc3d51..dd4a9006 100644 --- a/buildFlavor/field/buildNumber.txt +++ b/buildFlavor/field/buildNumber.txt @@ -1 +1 @@ -253 \ No newline at end of file +254 \ No newline at end of file diff --git a/buildFlavor/field/buildVersion.txt b/buildFlavor/field/buildVersion.txt index 15fe5c0c..ac69dc88 100644 --- a/buildFlavor/field/buildVersion.txt +++ b/buildFlavor/field/buildVersion.txt @@ -1 +1 @@ -2.18.10 \ No newline at end of file +2.18.11 \ No newline at end of file diff --git a/buildFlavor/tele/buildNumber.txt b/buildFlavor/tele/buildNumber.txt index 3bac779c..815f89db 100644 --- a/buildFlavor/tele/buildNumber.txt +++ b/buildFlavor/tele/buildNumber.txt @@ -1 +1 @@ -310 \ No newline at end of file +311 \ No newline at end of file diff --git a/buildFlavor/tele/buildVersion.txt b/buildFlavor/tele/buildVersion.txt index 9f7b5569..a4097b80 100644 --- a/buildFlavor/tele/buildVersion.txt +++ b/buildFlavor/tele/buildVersion.txt @@ -1 +1 @@ -100.2.6 \ No newline at end of file +100.2.7 \ No newline at end of file diff --git a/config/dev/config.js b/config/dev/config.js index 4a455082..65eb0c44 100644 --- a/config/dev/config.js +++ b/config/dev/config.js @@ -8,5 +8,5 @@ export const APM_APP_NAME = 'cosmos-app'; export const APM_BASE_URL = 'https://dev-longhorn-portal.np.navi-tech.in/apm-events'; export const GOOGLE_SSO_CLIENT_ID = '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; -export const MS_CLARITY_PROJECT_ID = ''; export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone'; +export const CODEPUSH_KEY = 'test-get'; diff --git a/config/prod/config.js b/config/prod/config.js index 9e643662..c55bf27e 100644 --- a/config/prod/config.js +++ b/config/prod/config.js @@ -1,8 +1,10 @@ import { MILLISECONDS_IN_A_MINUTE, MINUTES_IN_AN_HOUR } from '../../RN-UI-LIB/src/utlis/common'; export const BASE_AV_APP_URL = 'https://longhorn.navi.com/field-app'; -export const SENTRY_DSN = 'https://c6c8bc6fab2d8a36b4075956d7f4a984@sa.navi.com/cybertron/290764845822352576225822235345509901926' -export const TUNNEL_URL = 'https://sa.navi.com/cybertron/api/290764845822352576225822235345509901926/envelope?sentry_key=c6c8bc6fab2d8a36b4075956d7f4a984'; +export const SENTRY_DSN = + 'https://c6c8bc6fab2d8a36b4075956d7f4a984@sa.navi.com/cybertron/290764845822352576225822235345509901926'; +export const TUNNEL_URL = + 'https://sa.navi.com/cybertron/api/290764845822352576225822235345509901926/envelope?sentry_key=c6c8bc6fab2d8a36b4075956d7f4a984'; export const JANUS_SERVICE_URL = 'https://longhorn.navi.com/api/events/json'; export const ENV = 'prod'; export const IS_SSO_ENABLED = true; @@ -12,5 +14,5 @@ export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr export const GOOGLE_SSO_CLIENT_ID = '136591056725-ev8db4hrlud2m23n0o03or3cmmp3a3cq.apps.googleusercontent.com'; -export const MS_CLARITY_PROJECT_ID = 'n2nsbu7o78'; -export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone'; \ No newline at end of file +export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone'; +export const CODEPUSH_KEY = 'tfzYXgI3bnNdaIe'; diff --git a/config/qa/config.js b/config/qa/config.js index 02baea71..b6f2d1d8 100644 --- a/config/qa/config.js +++ b/config/qa/config.js @@ -11,7 +11,6 @@ export const APM_BASE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/apm-even export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr export const GOOGLE_SSO_CLIENT_ID = - '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; -export const MS_CLARITY_PROJECT_ID = ''; + '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone'; - +export const CODEPUSH_KEY = 'test-get'; diff --git a/package.json b/package.json index f46a0c72..fee6cffe 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,9 @@ "copy-field-assets": "cp buildFlavor/field/buildNumber.txt ./ && cp buildFlavor/field/buildVersion.txt ./ ", "prepare-tele-build": "yarn copy-tele-assets && chmod +x ./scripts/buildScript.sh && ./scripts/buildScript.sh", "prepare-field-build": "yarn copy-field-assets && chmod +x ./scripts/buildScript.sh && ./scripts/buildScript.sh", - "prepare-codepush-field-build": "ls && yarn copy-field-assets && chmod +x ./scripts/codepushBuildScript.sh && ./scripts/codepushBuildScript.sh" + "prepare-codepush-field-build": "ls && yarn copy-field-assets && chmod +x ./scripts/codepushBuildScript.sh && ./scripts/codepushBuildScript.sh", + "prepare-codepush-tele-build": "ls && yarn copy-tele-assets && chmod +x ./scripts/codepushBuildScript.sh && ./scripts/codepushBuildScript.sh", + "export-android": "mkdir -p android/output && react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/output/index.android.bundle --assets-dest android/output && cd android && find output -type f | zip index.android.bundle.zip -@ && cd .. && rm -rf android/output" }, "dependencies": { "@bam.tech/react-native-image-resizer": "3.0.5", @@ -89,11 +91,13 @@ "react-native-gzip": "1.0.0", "react-native-image-picker": "4.10.2", "react-native-mmkv": "2.11.0", + "react-native-ota-hot-update": "2.1.5", "react-native-pager-view": "6.1.2", "react-native-pdf-renderer": "1.1.1", "react-native-permissions": "3.6.1", "react-native-qrcode-svg": "^6.2.0", "react-native-quick-base64": "2.1.2", + "react-native-reanimated": "3.6.3", "react-native-safe-area-context": "4.4.1", "react-native-screens": "3.18.2", "react-native-svg": "^13.9.0", @@ -103,8 +107,7 @@ "react-native-webview": "13.12.1", "react-redux": "8.0.5", "redux": "4.2.0", - "redux-persist": "6.0.0", - "react-native-reanimated": "3.6.3" + "redux-persist": "6.0.0" }, "devDependencies": { "@babel/core": "7.25.2", diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 1ca99d6b..2e3ca029 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -1336,6 +1336,14 @@ export const CLICKSTREAM_EVENT_NAMES = { name: 'FA_CODEPUSH_DEFAULT_STATUS', description: 'Codepush default fallback case', }, + FA_CODEPUSH_INFINITE_INSTALL_ERROR: { + name: 'FA_CODEPUSH_INFINITE_INSTALL_ERROR', + description: 'Codepush infinite install error', + }, + FA_CODEPUSH_API_ERROR: { + name: 'FA_CODEPUSH_API_ERROR', + description: 'Codepush API error', + }, FA_CODEPUSH_UNKNOWN_ERROR: { name: 'FA_CODEPUSH_UNKNOWN_ERROR', description: 'Codepush unknown error', diff --git a/src/components/utlis/DeviceUtils.ts b/src/components/utlis/DeviceUtils.ts index c62fb789..778de79f 100644 --- a/src/components/utlis/DeviceUtils.ts +++ b/src/components/utlis/DeviceUtils.ts @@ -75,3 +75,5 @@ export const isAppInstalled = (packageName: string): Promise => export const getAppInfo = (packageName: string): Promise => DeviceUtilsModule.getAppInfo(packageName); export const getAppInfoFromFilePath = (filePath: string): Promise => DeviceUtilsModule.getAppInfoFromFilePath(filePath); + +export const restartApp = ():Promise => DeviceUtilsModule.restartApp(); diff --git a/src/constants/config.js b/src/constants/config.js index 02baea71..b6f2d1d8 100644 --- a/src/constants/config.js +++ b/src/constants/config.js @@ -11,7 +11,6 @@ export const APM_BASE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/apm-even export const IS_DATA_SYNC_REQUIRED = true; export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr export const GOOGLE_SSO_CLIENT_ID = - '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; -export const MS_CLARITY_PROJECT_ID = ''; + '60755663443-40k0fbrbbqv4ci4hrjlbrphab5fj387b.apps.googleusercontent.com'; export const COSMOS_DIALER_PACKAGE_NAME = 'org.fossify.phone'; - +export const CODEPUSH_KEY = 'test-get'; diff --git a/src/hooks/useCheckVersion.ts b/src/hooks/useCheckVersion.ts new file mode 100644 index 00000000..8d8ef095 --- /dev/null +++ b/src/hooks/useCheckVersion.ts @@ -0,0 +1,109 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import axios from 'axios'; +import { getUniqueId } from 'react-native-device-info'; +import otaUpdate from 'react-native-ota-hot-update'; +import ReactNativeBlobUtil from 'react-native-blob-util'; +import { addClickstreamEvent } from '@services/clickstreamEventService'; +import { CLICKSTREAM_EVENT_NAMES } from '@common/Constants'; +import { getBuildFlavour } from '@components/utlis/DeviceUtils'; +import { CODEPUSH_KEY } from '@constants/config'; + +interface CheckForUpdateOptions { + onUpdateAvailable: (result: UpdateCheckResult) => void; + onDownloadProgress: (received: string, total: string) => void; + onUpdateSuccess: () => void; + onUpdateFail: (message?: string) => void; + apiConfig?: { + baseUrl: string; + appVersionParam?: string; + }; +} + +interface UpdateCheckResult { + versionTag: string; + isAvailable: boolean; + url: string; +} + +enum BuildFlavour { + fieldAgents = 'fieldAgents', + callingAgents = 'callingAgents', +} + +enum AppType { + FIELD_COSMOS = 'FIELD_COSMOS', + TELE_COSMOS = 'TELE_COSMOS', +} + +export enum CodePushStorageKeys { + CodePushVersionTag = 'codePushVersionTag', +} + +export const useCheckVersion = (props: CheckForUpdateOptions) => { + const { onUpdateAvailable, onDownloadProgress, onUpdateSuccess, onUpdateFail, apiConfig } = props; + + const checkForUpdates = async () => { + if (!apiConfig) { + return null; + } + try { + let deviceId = await getUniqueId(); + const buildFlavour = await getBuildFlavour(); + let appType = AppType.FIELD_COSMOS; + if (buildFlavour.includes(BuildFlavour.callingAgents)) { + appType = AppType.TELE_COSMOS; + } + if (!deviceId) { + deviceId = `device_${Math.random().toString(36).substring(2, 15)}`; + } + + const response = await axios.request({ + method: 'get', + url: `${apiConfig.baseUrl}/releases/latest-applicable?appVersion=${apiConfig.appVersionParam}&appType=${appType}`, + headers: { + ['X-Device-Id']: deviceId, + ['X-Client-Secret']: CODEPUSH_KEY, + }, + }); + const updateData = response.data; + + if (updateData.isNewVersionAvailable) { + const result = { + isAvailable: true, + versionTag: updateData.versionTag, + url: updateData.bundlePresignedUrl, + }; + onUpdateAvailable(result); + } + } catch (error) { + addClickstreamEvent(CLICKSTREAM_EVENT_NAMES.FA_CODEPUSH_API_ERROR, { error }); + return null; + } + }; + + const applyUpdate = (downloadUrl: string, version: string) => { + if (downloadUrl && version) { + try { + otaUpdate.downloadBundleUri(ReactNativeBlobUtil, downloadUrl, Number(version), { + updateSuccess: () => { + AsyncStorage.setItem(CodePushStorageKeys.CodePushVersionTag, String(version)); + onUpdateSuccess(); + }, + updateFail: (message?: string) => { + onUpdateFail(message); + }, + progress(received: string, total: string) { + onDownloadProgress(received, total); + }, + }); + } catch (error) { + onUpdateFail(`Update failed: ${error}`); + } + } + }; + + return { + checkForUpdates, + applyUpdate, + }; +}; diff --git a/yarn.lock b/yarn.lock index 59908995..f0976529 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3355,6 +3355,11 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-lock@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.4.1.tgz#56b8718915a9b68b10fce2f2a9a3dddf765ef53f" + integrity sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ== + async@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" @@ -3717,6 +3722,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + builtins@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" @@ -3886,6 +3899,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +clean-git-ref@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/clean-git-ref/-/clean-git-ref-2.0.1.tgz#dcc0ca093b90e527e67adb5a5e55b1af6816dcd9" + integrity sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw== + clean-stack@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -4175,6 +4193,11 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -4518,6 +4541,11 @@ diff-sequences@^26.6.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +diff3@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/diff3/-/diff3-0.0.3.tgz#d4e5c3a4cdf4e5fe1211ab42e693fcb4321580fc" + integrity sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g== + dijkstrajs@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257" @@ -5973,7 +6001,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -5983,6 +6011,11 @@ ignore@^5.1.1: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== +ignore@^5.1.4: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + ignore@^5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.1.tgz#c2b1f76cb999ede1502f3a226a9310fdfe88d46c" @@ -6052,7 +6085,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6436,6 +6469,24 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +isomorphic-git@1.27.3: + version "1.27.3" + resolved "https://registry.yarnpkg.com/isomorphic-git/-/isomorphic-git-1.27.3.tgz#f66dd35a0b5d5d1136e2d8b972073d4c54864e8a" + integrity sha512-pNUeske1tbFYAVSU/4cHryzEmr9XLCiIOL7Vr6JedCYnz6eMGpYf4f3KTnZYoHokuKyvq5JQ9BS2iksuH62zmw== + dependencies: + async-lock "^1.4.1" + clean-git-ref "^2.0.1" + crc-32 "^1.2.0" + diff3 "0.0.3" + ignore "^5.1.4" + minimisted "^2.0.0" + pako "^1.0.10" + path-browserify "^1.0.1" + pify "^4.0.1" + readable-stream "^3.4.0" + sha.js "^2.4.9" + simple-get "^4.0.1" + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -7914,6 +7965,13 @@ minimist@^1.2.0, minimist@^1.2.3: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minimisted@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/minimisted/-/minimisted-2.0.1.tgz#d059fb905beecf0774bc3b308468699709805cb1" + integrity sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA== + dependencies: + minimist "^1.2.5" + miragejs@0.1.47: version "0.1.47" resolved "https://registry.yarnpkg.com/miragejs/-/miragejs-0.1.47.tgz#c4a8dff21adfc0ce3181d78987f11848d74c6869" @@ -8464,6 +8522,11 @@ pac-resolver@^7.0.1: degenerator "^5.0.0" netmask "^2.0.2" +pako@^1.0.10: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -8525,6 +8588,11 @@ patch-package@8.0.0: tmp "^0.0.33" yaml "^2.2.2" +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -8946,6 +9014,14 @@ react-native-mmkv@2.11.0: resolved "https://registry.yarnpkg.com/react-native-mmkv/-/react-native-mmkv-2.11.0.tgz#51b9985f6a5c09fe9c16d8c1861cc2901856ace1" integrity sha512-28PdUHjZJmAw3q+8zJDAAdohnZMpDC7WgRUJxACOMkcmJeqS3u5cKS/lSq2bhf1CvaeIiHYHUWiyatUjMRCDQQ== +react-native-ota-hot-update@2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/react-native-ota-hot-update/-/react-native-ota-hot-update-2.1.5.tgz#8f1b97dea9e1febe426787225626029ffaaf30d8" + integrity sha512-9x5T/2Pe89NbHCXoGaFvcjX1mnKczF35sQ5rPCeRghvXqoHSHCNWpvX63ibnVhL8GCjLzGGCGDQfzdz1o7wrvg== + dependencies: + buffer "^6.0.3" + isomorphic-git "1.27.3" + react-native-pager-view@6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.1.2.tgz#3522079b9a9d6634ca5e8d153bc0b4d660254552" @@ -9608,6 +9684,14 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +sha.js@^2.4.9: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + shallow-clone@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" @@ -9677,7 +9761,7 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^4.0.0: +simple-get@^4.0.0, simple-get@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==