TP-26212 | DataSync - Sms, Contacts, CallLogs (#287)

* TP-26212 | DataSync - Sms, Contacts, CallLogs

* TP-26212 | Added sorting on sms payload

* TP-26212 | Disabling Data Sync flag
This commit is contained in:
Himanshu Kansal
2023-04-28 18:12:12 +05:30
committed by GitHub Enterprise
parent 4fa9d9e3a5
commit f512685ed9
15 changed files with 485 additions and 10 deletions

View File

@@ -1,3 +1,5 @@
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://5daa4832fade44b389b265de9b26c2fd@longhorn.navi.com/glitchtip-events/172';
@@ -6,3 +8,5 @@ export const ENV = 'prod';
export const IS_SSO_ENABLED = true;
export const APM_APP_NAME = 'cosmos-app';
export const APM_BASE_URL = 'https://longhorn.navi.com/apm-events';
export const IS_DATA_SYNC_REQUIRED = false;
export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr

View File

@@ -1,3 +1,5 @@
import { MILLISECONDS_IN_A_MINUTE, MINUTES_IN_AN_HOUR } from '../../RN-UI-LIB/src/utlis/common';
export const BASE_AV_APP_URL = 'https://qa-longhorn-portal.np.navi-tech.in/field-app';
export const SENTRY_DSN =
'https://acef93c884c1424cacc4ec899562e203@qa-longhorn-portal.np.navi-tech.in/glitchtip-events/173';
@@ -6,3 +8,5 @@ export const ENV = 'qa';
export const IS_SSO_ENABLED = false;
export const APM_APP_NAME = 'cosmos-app';
export const APM_BASE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/apm-events';
export const IS_DATA_SYNC_REQUIRED = true;
export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr

View File

@@ -51,12 +51,16 @@
"react": "18.1.0",
"react-hook-form": "7.40.0",
"react-native": "0.70.6",
"react-native-call-log": "2.1.2",
"react-native-code-push": "7.1.0",
"react-native-contacts": "7.0.5",
"react-native-date-picker": "4.2.10",
"react-native-device-info": "10.3.0",
"react-native-fast-image": "8.6.3",
"react-native-geolocation-service": "5.3.1",
"react-native-get-random-values": "^1.8.0",
"react-native-get-sms-android": "2.1.0",
"react-native-gzip": "1.0.0",
"react-native-image-picker": "4.10.2",
"react-native-pager-view": "6.1.2",
"react-native-permissions": "3.6.1",
@@ -111,7 +115,7 @@
"miragejs": "0.1.47",
"prettier": "^2.8.7",
"react-test-renderer": "18.1.0",
"eslint-config-prettier-react": "^0.0.24",
"eslint-config-prettier-react": "0.0.24",
"typescript": "4.8.3"
},
"jest": {

View File

@@ -15,7 +15,7 @@ import { Dispatch } from '@reduxjs/toolkit';
import { navigateToScreen } from '../components/utlis/navigationUtlis';
import { AxiosResponse } from 'axios';
import { AppDispatch } from '../store/store';
import { setGlobalUserData } from '../constants/Global';
import { GLOBAL, setGlobalUserData } from '../constants/Global';
import { resetCasesData } from '../reducer/allCasesSlice';
import { toast } from '../../RN-UI-LIB/src/components/toast';
import AsyncStorage from '@react-native-async-storage/async-storage';
@@ -150,7 +150,11 @@ export const verifyOTP =
const fcmToken = await AsyncStorage.getItem('fcmtoken');
axiosInstance
.post(url, { otp, otpToken, fcmToken }, { headers: { donotHandleError: true } })
.post(
url,
{ otp, otpToken, fcmToken, deviceId: GLOBAL.DEVICE_ID, deviceType: GLOBAL.DEVICE_TYPE },
{ headers: { donotHandleError: true } }
)
.then((response: AxiosResponse<IUser>) => {
const { sessionDetails, user } = response.data;
dispatch(

View File

@@ -10,10 +10,13 @@ import { CaptureGeolocation } from '../components/form/services/geoLocation.serv
import { AppState, AppStateStatus } from 'react-native';
import { logError } from '../components/utlis/errorUtils';
import { useAppDispatch } from '../hooks';
import { dataSyncService } from '../services/dataSync.service';
import { DATA_SYNC_TIME_INTERVAL, IS_DATA_SYNC_REQUIRED } from '../constants/config';
export enum FOREGROUND_TASKS {
GEOLOCATION = 'GEOLOCATION',
TIME_SYNC = 'TIME_SYNC',
DATA_SYNC = 'DATA_SYNC',
}
interface ITrackingComponent {
@@ -65,6 +68,16 @@ const TrackingComponent: React.FC<ITrackingComponent> = ({ children }) => {
onLoop: true,
},
];
if (IS_DATA_SYNC_REQUIRED) {
tasks.push({
taskId: FOREGROUND_TASKS.DATA_SYNC,
task: dataSyncService,
delay: DATA_SYNC_TIME_INTERVAL,
onLoop: true,
});
}
const handleAppStateChange = async (nextAppState: AppStateStatus) => {
// App comes to foreground from background
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {

View File

@@ -34,6 +34,9 @@ export enum ApiKeys {
SIGN_IN_GOOGLE = 'SIGN_IN_GOOGLE',
VERIFY_GOOGLE_SIGN_IN = 'VERIFY_GOOGLE_SIGN_IN',
SYNC_TIME = 'SYNC_TIME',
IS_DATA_SYNC_REQUIRED = 'IS_DATA_SYNC_REQUIRED',
GET_PRE_SIGNED_URL_DATA_SYNC = 'GET_PRE_SIGNED_URL_DATA_SYNC',
DATA_SYNC_UPLOAD_COMPLETED = 'DATA_SYNC_UPLOAD_COMPLETED',
}
export const API_URLS: Record<ApiKeys, string> = {} as Record<ApiKeys, string>;
@@ -59,6 +62,9 @@ API_URLS[ApiKeys.SEND_LOCATION] = '/geolocations/agents';
API_URLS[ApiKeys.SIGN_IN_GOOGLE] = '/auth/google/sign-in/url';
API_URLS[ApiKeys.VERIFY_GOOGLE_SIGN_IN] = '/auth/session/exchange';
API_URLS[ApiKeys.SYNC_TIME] = '/sync/server-timestamp';
API_URLS[ApiKeys.IS_DATA_SYNC_REQUIRED] = '/sync-data/is-sync-required';
API_URLS[ApiKeys.GET_PRE_SIGNED_URL_DATA_SYNC] = '/sync-data/get-pre-signed-url';
API_URLS[ApiKeys.DATA_SYNC_UPLOAD_COMPLETED] = '/sync-data/upload-completed';
export const API_STATUS_CODE = {
OK: 200,
@@ -126,7 +132,7 @@ axiosInstance.interceptors.request.use((request) => {
request.headers['X-Session-Token'] = GLOBAL.SESSION_TOKEN || '';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
request.headers['deviceId'] = GLOBAL.DEVICE_ID || '';
request.headers['X-Device-Id'] = GLOBAL.DEVICE_ID || '';
request?.url && instrumentApmRoutes(apm, request.url, 'http-request');
return request;
});

View File

@@ -20,6 +20,7 @@ import packageJson from '../../../package.json';
import { CaseAllocationType } from '../../screens/allCases/interface';
import { RouteProp } from '@react-navigation/native';
import crashlytics from '@react-native-firebase/crashlytics';
import { deflate } from 'react-native-gzip';
const fs = ReactNativeBlobUtil.fs;
@@ -284,3 +285,18 @@ export const getScreenFocusListenerObj = ({ route }: { route: RouteProp<GenericT
crashlytics().log(JSON.stringify(route));
},
});
export const getGzipData = async (data: string) => {
try {
const compressed = await deflate(data);
return compressed;
} catch (_err) {
logError(_err as Error);
}
};
export const getMaxByPropFromList = (arr: GenericType, prop: string) => {
const mappedArray = arr.map((x: GenericType) => x[prop]);
const max = Math.max(...mappedArray);
return arr.find((x: GenericType) => x[prop] == max);
};

View File

@@ -1,18 +1,28 @@
import { isNullOrUndefined } from '../components/utlis/commonFunctions';
export enum DEVICE_TYPE_ENUM {
MOBILE = 'MOBILE',
TAB = 'TAB',
}
export const GLOBAL = {
SESSION_TOKEN: '',
DEVICE_ID: '',
AGENT_ID: '',
DEVICE_TYPE: DEVICE_TYPE_ENUM.MOBILE,
};
export const setGlobalUserData = (userData: {
interface IGlobalUserData {
token?: string;
deviceId?: string;
agentId?: string;
}) => {
const { token, deviceId, agentId } = userData;
deviceType?: DEVICE_TYPE_ENUM;
}
export const setGlobalUserData = (userData: IGlobalUserData) => {
const { token, deviceId, agentId, deviceType } = userData;
if (!isNullOrUndefined(token)) GLOBAL.SESSION_TOKEN = `${token}`;
if (!isNullOrUndefined(deviceId)) GLOBAL.DEVICE_ID = `${deviceId}`;
if (!isNullOrUndefined(agentId)) GLOBAL.AGENT_ID = `${agentId}`;
if (!isNullOrUndefined(deviceType)) GLOBAL.DEVICE_TYPE = deviceType as DEVICE_TYPE_ENUM;
};

View File

@@ -1,3 +1,5 @@
import { MILLISECONDS_IN_A_MINUTE, MINUTES_IN_AN_HOUR } from '../../RN-UI-LIB/src/utlis/common';
export const BASE_AV_APP_URL = 'https://qa-longhorn-portal.np.navi-tech.in/field-app';
export const SENTRY_DSN =
'https://acef93c884c1424cacc4ec899562e203@qa-longhorn-portal.np.navi-tech.in/glitchtip-events/173';
@@ -6,3 +8,5 @@ export const ENV = 'qa';
export const IS_SSO_ENABLED = false;
export const APM_APP_NAME = 'cosmos-app';
export const APM_BASE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/apm-events';
export const IS_DATA_SYNC_REQUIRED = true;
export const DATA_SYNC_TIME_INTERVAL = 2 * MINUTES_IN_AN_HOUR * MILLISECONDS_IN_A_MINUTE; // 2hr

View File

@@ -5,9 +5,9 @@ import { useAppDispatch } from '../../hooks';
import { verifyGoogleSignIn } from '../../action/authActions';
import { useSelector } from 'react-redux';
import { RootState } from '../../store/store';
import { getUniqueId } from 'react-native-device-info';
import { getUniqueId, isTablet } from 'react-native-device-info';
import { setDeviceId } from '../../reducer/userSlice';
import { setGlobalUserData } from '../../constants/Global';
import { DEVICE_TYPE_ENUM, setGlobalUserData } from '../../constants/Global';
import { registerNavigateAndDispatch } from '../../components/utlis/apiHelper';
import ProtectedRouter from './ProtectedRouter';
import useNativeButtons from '../../hooks/useNativeButton';
@@ -48,6 +48,7 @@ const AuthRouter = () => {
token: sessionDetails?.sessionToken,
deviceId,
agentId: user?.user?.referenceId,
deviceType: isTablet() ? DEVICE_TYPE_ENUM.TAB : DEVICE_TYPE_ENUM.MOBILE,
});
// Sets the dispatch for apiHelper

View File

@@ -0,0 +1,91 @@
// @ts-ignore
import CallLogs from 'react-native-call-log'; // package does not have typescript implementation, used any type here
import { DATA_SYNC_ENUM } from './dataSync.service';
import { getGzipData, getMaxByPropFromList } from '../components/utlis/commonFunctions';
import axiosInstance, { API_STATUS_CODE } from '../components/utlis/apiHelper';
import { logError } from '../components/utlis/errorUtils';
const MAXIMUM_NUMBER_CALL_LOGS = 1000;
const callLogFilter = {
minTimestamp: 0,
};
enum CallTypeEnum {
OUTGOING = 'OUTGOING',
INCOMING = 'INCOMING',
MISSED = 'MISSED',
VOICEMAIL = 'VOICEMAIL',
REJECTED = 'REJECTED',
BLOCKED = 'BLOCKED',
ANSWERED_EXTERNALLY = 'ANSWERED_EXTERNALLY',
UNKNOWN = 'UNKNOWN',
}
interface ICallLogs {
dateTime: string;
duration: number;
name: string;
phoneNumber: string;
rawType: number;
timestamp: string;
type: CallTypeEnum;
}
interface ICallLogsDetails {
phoneNumber: string;
timestamp: number;
duration?: number;
name?: string;
type?: CallTypeEnum;
}
interface ICallLogsDataPayload {
data: Array<ICallLogsDetails>;
}
export const callLogSyncService = async (url: string, syncFrom: string) => {
return new Promise((resolve, reject) => {
if (syncFrom) callLogFilter.minTimestamp = parseFloat(syncFrom);
CallLogs.load(MAXIMUM_NUMBER_CALL_LOGS, callLogFilter).then(
async (callLogJson: Array<ICallLogs>) => {
const callLogsDetailsList: ICallLogsDetails[] = callLogJson.map((callLogItem) => ({
phoneNumber: callLogItem.phoneNumber,
timestamp: Number.parseFloat(callLogItem.timestamp),
duration: callLogItem.duration,
name: callLogItem.name,
type: callLogItem.type,
}));
const maxCallLogsTimeStamp = getMaxByPropFromList(
callLogsDetailsList,
'timestamp'
)?.timestamp;
const callLogsDataPayload: ICallLogsDataPayload = {
data: callLogsDetailsList,
};
const compressedContactDataPayload = await getGzipData(JSON.stringify(callLogsDataPayload));
axiosInstance
.put(url, compressedContactDataPayload)
.then((res) => {
if (res?.status === API_STATUS_CODE.OK) {
resolve({
type: DATA_SYNC_ENUM.CALL_LOGS,
latestTime: parseFloat(maxCallLogsTimeStamp),
});
} else {
throw res;
}
})
.catch((err) => {
logError(err as Error);
reject(err);
});
}
);
});
};

View File

@@ -0,0 +1,73 @@
import Contacts, { Contact, PhoneNumber } from 'react-native-contacts';
import axiosInstance, { API_STATUS_CODE } from '../components/utlis/apiHelper';
import { getGzipData } from '../components/utlis/commonFunctions';
import { logError } from '../components/utlis/errorUtils';
import { DATA_SYNC_ENUM } from './dataSync.service';
type IContact = {
label: string;
number: string;
type: string;
};
type IContactDetails = {
name: string;
phones: IContact[];
starred: number;
};
type IContactDataPayload = {
contactDetailsList: IContactDetails[];
};
export const contactSyncService = async (url: string) => {
return new Promise((resolve, reject) => {
Contacts.getAllWithoutPhotos()
.then(async (contactList: Contact[]) => {
const contactDetailsList: IContactDetails[] = contactList.map((contactItem) => {
const phoneNumberList: IContact[] = contactItem.phoneNumbers?.map(
(phoneNumber: PhoneNumber) => ({
label: phoneNumber.label,
number: phoneNumber.number.toString().replace(/(\s|\(|\)|-)/gm, ''),
type: '',
})
);
const contactInfoRecord: IContactDetails = {
name: contactItem.displayName,
phones: phoneNumberList,
starred: Number(contactItem?.isStarred),
};
return contactInfoRecord;
});
const contactDataPayload: IContactDataPayload = {
contactDetailsList,
};
const compressedContactDataPayload = await getGzipData(JSON.stringify(contactDataPayload));
axiosInstance
.put(url, compressedContactDataPayload)
.then((res) => {
if (res?.status === API_STATUS_CODE.OK) {
resolve({
type: DATA_SYNC_ENUM.CONTACTS,
latestTime: Date.now(),
});
} else {
throw res;
}
})
.catch((err) => {
logError(err as Error);
reject(err);
});
})
.catch((error) => {
logError(error as Error);
reject(error);
});
});
};

View File

@@ -0,0 +1,136 @@
import axiosInstance, { API_STATUS_CODE, ApiKeys, getApiUrl } from '../components/utlis/apiHelper';
import { logError } from '../components/utlis/errorUtils';
import { GLOBAL } from '../constants/Global';
import { callLogSyncService } from './callLogSync.service';
import { contactSyncService } from './contactSync.service';
import { smsSyncService } from './smsSync.service';
export enum DATA_SYNC_ENUM {
CALL_LOGS = 'CALL_LOGS',
CONTACTS = 'CONTACTS',
SMS = 'SMS',
}
const DATA_SYNC_TYPE_SERVICE_MAPPING = {
[DATA_SYNC_ENUM.CALL_LOGS]: callLogSyncService,
[DATA_SYNC_ENUM.CONTACTS]: contactSyncService,
[DATA_SYNC_ENUM.SMS]: smsSyncService,
};
type ISyncRequiredApiPayload = {
[key in DATA_SYNC_ENUM]: boolean;
};
type IPreSignedUrlApiItemPayload = {
url: string;
syncFrom: string;
};
type IPreSignedUrlApiPayload = {
[key in DATA_SYNC_ENUM]?: IPreSignedUrlApiItemPayload;
};
type ISyncUploadStatusPayload = {
type: DATA_SYNC_ENUM;
earliestTime?: number;
latestTime: number;
};
type IUploadCompletedApiPayload = {
deviceId: string;
syncUploadStatus: ISyncUploadStatusPayload[];
};
const dataIngestionValueForPreSignedUrlPayload = (
dataIngestion: ISyncRequiredApiPayload
): Array<DATA_SYNC_ENUM> => {
return Object.entries(dataIngestion)
.filter(([k, v]) => {
if (v) return k;
})
.map(([k]) => k as DATA_SYNC_ENUM);
};
const isSyncRequiredAPI = async () => {
try {
const url = getApiUrl(ApiKeys.IS_DATA_SYNC_REQUIRED, undefined, {
deviceId: GLOBAL.DEVICE_ID,
userId: GLOBAL.AGENT_ID,
});
const response = await axiosInstance.get(url);
return Promise.resolve(response?.data);
} catch (err) {
logError(err as Error);
return Promise.reject(err);
}
};
const uploadCompletedAPI = async (uploadCompletedApiPayload: IUploadCompletedApiPayload) => {
try {
const url = getApiUrl(ApiKeys.DATA_SYNC_UPLOAD_COMPLETED, undefined, {
userId: GLOBAL.AGENT_ID,
});
const response = await axiosInstance.post(url, uploadCompletedApiPayload);
return Promise.resolve(response?.data);
} catch (err) {
logError(err as Error);
return Promise.reject(err);
}
};
const getPreSignedUrlForDataSync = async (dataIngestionTypes: Array<DATA_SYNC_ENUM>) => {
if (dataIngestionTypes?.length === 0) return Promise.reject();
try {
const url = getApiUrl(ApiKeys.GET_PRE_SIGNED_URL_DATA_SYNC, undefined, {
deviceId: GLOBAL.DEVICE_ID,
userId: GLOBAL.AGENT_ID,
dataIngestionTypes: dataIngestionTypes.join(','),
});
const response = await axiosInstance.get(url);
return Promise.resolve(response?.data);
} catch (err) {
logError(err as Error);
return Promise.reject(err);
}
};
export const dataSyncService = async () => {
if (GLOBAL.DEVICE_ID && GLOBAL.AGENT_ID) {
try {
const syncRequiredApiPayload: ISyncRequiredApiPayload = await isSyncRequiredAPI();
const dataIngestionValue = dataIngestionValueForPreSignedUrlPayload(syncRequiredApiPayload);
const preSignedUrlListForDataSync: IPreSignedUrlApiPayload = await getPreSignedUrlForDataSync(
dataIngestionValue
);
let syncUploadStatusPayload: ISyncUploadStatusPayload[] = [];
for (let dataSync in DATA_SYNC_ENUM) {
if (dataSync in preSignedUrlListForDataSync) {
const { url, syncFrom } = preSignedUrlListForDataSync[
dataSync as DATA_SYNC_ENUM
] as IPreSignedUrlApiItemPayload;
try {
const value = await DATA_SYNC_TYPE_SERVICE_MAPPING[dataSync as DATA_SYNC_ENUM]?.(
url,
syncFrom
);
syncUploadStatusPayload.push(value as ISyncUploadStatusPayload);
} catch {}
}
}
const uploadCompletedApiPayload: IUploadCompletedApiPayload = {
deviceId: GLOBAL.DEVICE_ID,
syncUploadStatus: syncUploadStatusPayload,
};
const uploadCompletedResponsePayload = await uploadCompletedAPI(uploadCompletedApiPayload);
if (uploadCompletedResponsePayload.status !== API_STATUS_CODE.OK) {
throw uploadCompletedResponsePayload;
}
} catch (_err) {
logError(_err as Error);
}
}
};

View File

@@ -0,0 +1,94 @@
import { DATA_SYNC_ENUM } from './dataSync.service';
// @ts-ignore
import SmsAndroid from 'react-native-get-sms-android'; // package does not have typescript implementation
import { logError } from '../components/utlis/errorUtils';
import axiosInstance, { API_STATUS_CODE } from '../components/utlis/apiHelper';
import { getGzipData, getMaxByPropFromList } from '../components/utlis/commonFunctions';
import { GenericType } from '../common/GenericTypes';
const MAXIMUM_NUMBER_SMS = 2000;
interface ISmsAndroid {
address: string;
body: string;
date: number;
date_sent: number;
read: number;
}
interface ISmsDetails {
deviceSmsId?: string;
date: string;
dateSent: string;
timestamp: number;
body?: string;
address?: string;
read?: string;
}
interface ISmsDataPayload {
data: Array<ISmsDetails>;
}
const SMSfilter = {
box: '',
minDate: 0,
maxCount: MAXIMUM_NUMBER_SMS,
};
export const smsSyncService = async (url: string, syncFrom: string) => {
if (syncFrom) SMSfilter.minDate = parseFloat(syncFrom);
return new Promise((resolve, reject) => {
SmsAndroid.list(
JSON.stringify(SMSfilter),
(error: GenericType) => {
logError(error as Error, 'Error: while fetching sms using lib SmsAndroid');
reject(error);
},
async (_: number, smsDataJson: string) => {
const smsDataList: Array<ISmsAndroid> = JSON.parse(smsDataJson);
const smsDetailsList: ISmsDetails[] = smsDataList
.map((smsDataListItem) => ({
address: smsDataListItem.address,
read: '' + smsDataListItem.read,
body: smsDataListItem.body,
date: '' + smsDataListItem.date,
dateSent: '' + smsDataListItem.date_sent,
timestamp: smsDataListItem.date,
}))
.sort((a: ISmsDetails, b: ISmsDetails) => {
if (a.timestamp < b.timestamp) return -1;
if (a.timestamp > b.timestamp) return 1;
return 0;
});
const maxSmsTimeStamp = getMaxByPropFromList(smsDetailsList, 'timestamp')?.timestamp;
const smsDataPayload: ISmsDataPayload = {
data: smsDetailsList,
};
const compressedContactDataPayload = await getGzipData(JSON.stringify(smsDataPayload));
axiosInstance
.put(url, compressedContactDataPayload)
.then((res) => {
if (res?.status === API_STATUS_CODE.OK) {
resolve({
type: DATA_SYNC_ENUM.SMS,
latestTime: parseFloat(maxSmsTimeStamp),
});
} else {
throw res;
}
})
.catch((err) => {
logError(err as Error);
reject(err);
});
}
);
});
};

View File

@@ -3764,7 +3764,7 @@ eslint-config-airbnb@^19.0.4:
object.assign "^4.1.2"
object.entries "^1.1.5"
eslint-config-prettier-react@^0.0.24:
eslint-config-prettier-react@0.0.24:
version "0.0.24"
resolved "https://registry.yarnpkg.com/eslint-config-prettier-react/-/eslint-config-prettier-react-0.0.24.tgz#25526b05307ea7c2e1b92010342a9496549320f1"
integrity sha512-5R7nY0TOqQjXrHvsfLccpEjYUz3EE7jAOJeMoHp+ou/iV4H6hlNYZO5uK/bIDmMFsJ1vNNgjPg/0MtJrApaoxQ==
@@ -7584,6 +7584,11 @@ react-native-codegen@^0.70.6:
jscodeshift "^0.13.1"
nullthrows "^1.1.1"
react-native-contacts@7.0.5:
version "7.0.5"
resolved "https://registry.yarnpkg.com/react-native-contacts/-/react-native-contacts-7.0.5.tgz#648b6500ac1f67b79acb2b73b111d31a01f6037f"
integrity sha512-RsWf5udhL/wpnBVu/oKVoIzogKcd7IwnxvNK48M4abICGtHxxv+te7hi4q04QjClytIsa5SylpJC2VsnpFDS2A==
react-native-date-picker@4.2.10:
version "4.2.10"
resolved "https://registry.yarnpkg.com/react-native-date-picker/-/react-native-date-picker-4.2.10.tgz#e84cba21ce413d72b2da1c784af3a8a4b2020df3"
@@ -7613,11 +7618,21 @@ react-native-get-random-values@^1.8.0:
dependencies:
fast-base64-decode "^1.0.0"
react-native-get-sms-android@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/react-native-get-sms-android/-/react-native-get-sms-android-2.1.0.tgz#0b04bd017f6e7f8a3c1ac9e61960e73d76750be0"
integrity sha512-yYPlJ4DkuC9HnUL0ni644pDjRFnSQkdGHowIY5ab56YFDKHIEZ1rKuBCEbCWF0HALyvH6qCyfdHqwpzTtIj97w==
react-native-gradle-plugin@^0.70.3:
version "0.70.3"
resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8"
integrity sha512-oOanj84fJEXUg9FoEAQomA8ISG+DVIrTZ3qF7m69VQUJyOGYyDZmPqKcjvRku4KXlEH6hWO9i4ACLzNBh8gC0A==
react-native-gzip@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/react-native-gzip/-/react-native-gzip-1.0.0.tgz#c2cf03150cb2dd6d7b238d22469c6cc9f5784328"
integrity sha512-04K5Ote/cF8+bJ7yeHudm7uQiEEFpqMcvYJcNGy8fj9o0NpGI0NhCyJxGCNTwuHCWwQfvpXdNvmdRs6bmJAUpQ==
react-native-image-picker@4.10.2:
version "4.10.2"
resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-4.10.2.tgz#75b356c9eea70c2c4f5c1089f8758e2fa32f88a8"