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:
committed by
GitHub Enterprise
parent
4fa9d9e3a5
commit
f512685ed9
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
91
src/services/callLogSync.service.ts
Normal file
91
src/services/callLogSync.service.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
73
src/services/contactSync.service.ts
Normal file
73
src/services/contactSync.service.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
};
|
||||
136
src/services/dataSync.service.ts
Normal file
136
src/services/dataSync.service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
94
src/services/smsSync.service.ts
Normal file
94
src/services/smsSync.service.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
17
yarn.lock
17
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user