diff --git a/app.config.js b/app.config.js
index 554ab17fb..75248d028 100644
--- a/app.config.js
+++ b/app.config.js
@@ -280,6 +280,6 @@ export default {
palette,
},
...app,
- ...eas.build.development.env,
+ ...eas.build.local.env,
},
};
diff --git a/eas.json b/eas.json
index 8c015d7c8..33641b005 100644
--- a/eas.json
+++ b/eas.json
@@ -64,10 +64,10 @@
"distribution": "internal",
"env": {
"ENV": "local",
- "IRA_DOMAIN": "localhost:3000",
- "API_DOMAIN": "api.dev.monk.ai/v1",
+ "IRA_DOMAIN": "localhost:5000",
+ "API_DOMAIN": "localhost:5000/v1",
"AUTH_AUDIENCE": "https://api.monk.ai/v1/",
- "AUTH_CLIENT_ID": "ZH7GK6zgjyVDiHN0A6kY98PBWVeJfKvX",
+ "AUTH_CLIENT_ID": "rq1PDCY20CYLlW0TqDUH8zAvSjyscUjf",
"AUTH_DOMAIN": "idp.dev.monk.ai",
"SENTRY_DSN": "https://b883345bef184d588d038ed18a563170@sentry.dev.monk.ai/3",
"PDF_REPORT_CUSTOMER": "monk_QSBtYXJ0aW5pLiBTaGFrZW4sIG5vdCBzdGlycmVkLgo=",
diff --git a/package.json b/package.json
index 8c89b6695..eeff792f9 100644
--- a/package.json
+++ b/package.json
@@ -112,6 +112,7 @@
"react-webcam": "^7.0.0",
"screenfull": "^6.0.1",
"sentry-expo": "^4.0.0",
+ "socket.io-client": "^4.6.1",
"webpack": "5.0.0",
"webrtc-adapter": "^8.1.1",
"xmldom": "^0.6.0",
diff --git a/src/config/corejs.js b/src/config/corejs.js
index b1d0ca503..bf5eb58fd 100644
--- a/src/config/corejs.js
+++ b/src/config/corejs.js
@@ -2,7 +2,7 @@ import Constants from 'expo-constants';
import monk from '@monkvision/corejs';
const axiosConfig = {
- baseURL: `https://${Constants.manifest.extra.API_DOMAIN}`,
+ baseURL: `http://${Constants.manifest.extra.API_DOMAIN}`,
headers: { 'Access-Control-Allow-Origin': '*' },
};
diff --git a/src/context/socket.js b/src/context/socket.js
new file mode 100644
index 000000000..9b36e7f5b
--- /dev/null
+++ b/src/context/socket.js
@@ -0,0 +1,81 @@
+import React, { createContext, useContext, useCallback, useMemo, useState, useEffect } from 'react';
+import { io } from 'socket.io-client';
+
+/**
+ * Creates a socket.io client that connects to the server
+ * Make sure to enable Cross-Origin Resource Sharing (CORS) on the server
+ * The options parameter is optional and can be used to configure the connection
+ */
+const socket = io('http://localhost:5000', {}); // Replace url with your server URL
+
+// React context for web socket data
+const SocketContext = createContext({
+ socketID: null,
+ onSocketEvent: () => {},
+ emitSocketEvent: () => {},
+});
+
+function SocketProvider({ children }) {
+ const [socketID, setSocketID] = useState(null);
+
+ const onSocketEvent = useCallback((event, callback, off = true) => {
+ if (socket.connected) {
+ socket.on(event, (data) => {
+ if (off) {
+ socket.off(event);
+ }
+ callback(data);
+ });
+ }
+ }, []);
+
+ const emitSocketEvent = useCallback((event, args = {}, callback = () => {}) => {
+ if (socket.connected) {
+ socket.emit(event, args, () => {
+ console.log('[Socket] - emit event', event);
+ callback();
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ socket.on('connect', () => {
+ console.log('[Socket] - connected', socket);
+ console.log('[Socket] - socket id', socket.id);
+ setSocketID(socket.id);
+ socket.on('post_inspection', (data) => {
+ console.log('[Socket] - listen post_inspection event from server', data);
+ });
+ });
+ socket.on('disconnect', (reason) => {
+ console.log('[Socket] - disconnected due to', reason);
+ });
+ socket.on('connect_error', () => {
+ console.log('[Socket] - has a connection error');
+ setTimeout(() => {
+ socket.connect();
+ }, 1000);
+ });
+ }, []);
+
+ const value = useMemo(() => (
+ {
+ socketID,
+ onSocketEvent,
+ emitSocketEvent,
+ }
+ ), [socketID]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+const useWebSocket = () => useContext(SocketContext);
+
+export {
+ useWebSocket,
+ SocketProvider,
+};
diff --git a/src/main.js b/src/main.js
index ab4fade78..e91580151 100644
--- a/src/main.js
+++ b/src/main.js
@@ -4,10 +4,12 @@ import { registerRootComponent } from 'expo';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
import * as Sentry from 'sentry-expo';
+import { MonitoringProvider } from '@monkvision/corejs';
+
import { name, version } from '@package/json';
import App from 'components/App';
+import { SocketProvider } from './context/socket';
import './i18n';
-import { MonitoringProvider } from '@monkvision/corejs';
const config = {
dsn: Constants.manifest.extra.SENTRY_DSN,
@@ -20,7 +22,14 @@ const config = {
if (Platform.OS === 'web') {
const container = document.getElementById('root');
- render(, container);
+ render(
+
+
+
+
+ ,
+ container,
+ );
} else {
registerRootComponent(Sentry.Native.wrap(App));
}
diff --git a/src/screens/InspectionCreate/index.js b/src/screens/InspectionCreate/index.js
index 644d78f3f..6e58d1d62 100644
--- a/src/screens/InspectionCreate/index.js
+++ b/src/screens/InspectionCreate/index.js
@@ -15,6 +15,7 @@ import * as names from 'screens/names';
import useAuth from 'hooks/useAuth';
import useSignIn from 'hooks/useSignIn';
import useCreateInspection from './useCreateInspection';
+import { useWebSocket } from '../../context/socket';
const styles = StyleSheet.create({
root: {
@@ -43,6 +44,7 @@ export default function InspectionCreate() {
const { errorHandler } = useMonitoring();
const { t } = useTranslation();
const { colors, loaderDotsColors } = useTheme();
+ const { socketID } = useWebSocket();
const route = useRoute();
@@ -63,7 +65,7 @@ export default function InspectionCreate() {
},
});
- const createInspection = useCreateInspection({ ...vehicle, vin });
+ const createInspection = useCreateInspection({ ...vehicle, vin, socketID });
const handleCreate = useCallback(async () => {
if (isEmpty(inspectionId) && isAuthenticated && createInspection.state.count < 1) {
utils.log(['[Click] Inspection task chosen: ', selected]);
diff --git a/src/screens/InspectionCreate/useCreateInspection/index.js b/src/screens/InspectionCreate/useCreateInspection/index.js
index 3723d6f11..41c9fecad 100644
--- a/src/screens/InspectionCreate/useCreateInspection/index.js
+++ b/src/screens/InspectionCreate/useCreateInspection/index.js
@@ -4,20 +4,22 @@ import useAuth from 'hooks/useAuth';
import { useCallback, useState } from 'react';
import { useDispatch } from 'react-redux';
-export default function useCreateInspection(vehicle) {
+export default function useCreateInspection({ socketID, ...vehicle }) {
const dispatch = useDispatch();
const { isAuthenticated } = useAuth();
const [inspectionId, setInspectionId] = useState();
const axiosRequest = useCallback(async () => {
- const taskOptions = { status: monk.types.ProgressStatusUpdate.NOT_STARTED };
+ const taskOptions = {
+ status: monk.types.ProgressStatusUpdate.NOT_STARTED,
+ };
const tasks = {
wheelAnalysis: { ...taskOptions, useLongshots: true },
damageDetection: taskOptions,
...(vehicle?.vin ? {} : { imagesOcr: taskOptions }),
};
- return monk.entity.inspection.createOne({ tasks, vehicle });
+ return monk.entity.inspection.createOne({ tasks, vehicle, websocket_id: socketID });
}, []);
const handleRequestSuccess = useCallback(({ entities, result }) => {
diff --git a/src/screens/Landing/index.js b/src/screens/Landing/index.js
index 0b9344076..925d98ecd 100644
--- a/src/screens/Landing/index.js
+++ b/src/screens/Landing/index.js
@@ -1,8 +1,8 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import monk, { useMonitoring } from '@monkvision/corejs';
-import { useInterval, utils } from '@monkvision/toolkit';
+import { utils } from '@monkvision/toolkit';
import { Container } from '@monkvision/ui';
-import { useFocusEffect, useNavigation, useRoute } from '@react-navigation/native';
+import { useNavigation, useRoute } from '@react-navigation/native';
import Inspection from 'components/Inspection';
import Modal from 'components/Modal';
import ExpoConstants from 'expo-constants';
@@ -27,6 +27,7 @@ import useUpdateOneTask from './useUpdateOneTask';
import useVinModal from './useVinModal';
import VehicleType from './VehicleType';
import useUpdateInspectionVehicle from './useUpdateInspectionVehicle';
+import { useWebSocket } from '../../context/socket';
const ICON_BY_STATUS = {
NOT_STARTED: 'chevron-right',
@@ -42,8 +43,10 @@ export default function Landing() {
const { errorHandler } = useMonitoring();
const { t, i18n } = useTranslation();
const { setShowTranslatedMessage, Notice } = useSnackbar(true);
+ const { onSocketEvent, emitSocketEvent } = useWebSocket();
const [vehicleType, setVehicleType] = useState('');
+ const [currentPercentage, setCurrentPercentage] = useState(0);
const isPortrait = useMediaQuery({ query: '(orientation: portrait)' });
const route = useRoute();
@@ -103,7 +106,6 @@ export default function Landing() {
const isVin = value === 'vinNumber';
const vinOption = ExpoConstants.manifest.extra.options.find((option) => option.value === 'vinNumber');
if (isVin && vinOption?.mode.includes('manually')) { vinOptionsRef.current?.open(); return; }
-
const shouldSignIn = !isAuthenticated;
const to = shouldSignIn ? names.SIGN_IN : names.INSPECTION_CREATE;
navigation.navigate(to, { selectedMod: value, inspectionId, vehicle: { vehicleType } });
@@ -162,9 +164,12 @@ export default function Landing() {
onPress={handlePress}
disabled={disabled}
/>
+ {
+ item.taskName === 'damage_detection' &&
+ }
);
- }, [handleListItemPress, inspection]);
+ }, [handleListItemPress, currentPercentage, inspection]);
const start = useCallback(() => {
if (inspectionId && getInspection.state.loading !== true) {
@@ -172,14 +177,34 @@ export default function Landing() {
errorHandler(err);
});
}
- }, [inspectionId, getInspection]);
-
- const intervalId = useInterval(start, 1000);
+ }, [inspectionId]);
- useFocusEffect(useCallback(() => {
- start();
- return () => clearInterval(intervalId);
- }, [navigation, start, intervalId]));
+ useEffect(() => {
+ console.log('[Landing page] - [Use Effect]');
+ if (inspectionId) {
+ // Listen websocket server event to get the updated progress for damage_detection task
+ emitSocketEvent('join', {"room": inspectionId})
+ onSocketEvent('task_progress_update', (data) => {
+ console.log('[Socket] - [task_progress_update]', data);
+ console.log('[Socket] - [task_progress_update]', inspectionId);
+ if (data.task_name === 'damage_detection') {
+ console.log('[Socket] - [task_progress_update] in the if!');
+ console.log('[Socket] - [task_progress_update]', data.progress);
+ setCurrentPercentage(parseFloat(data.progress) * 100);
+ }
+ }, false);
+
+ // Listen websocket server event to get the updated status for each task
+ onSocketEvent('update_task_status', (data) => {
+ console.log('[Socket] - [update_task_status]', data);
+ console.log('[Socket] - [update_task_status]', inspectionId);
+ if (data.inspection_id === inspectionId) {
+ console.log('[Socket] - [update_task_status] in the if!');
+ start();
+ }
+ }, false);
+ }
+ }, [inspectionId]);
useEffect(() => {
if (inspectionId && !allTasksAreCompleted) {
diff --git a/src/screens/Landing/styles.js b/src/screens/Landing/styles.js
index bffa36c0e..a379d5db4 100644
--- a/src/screens/Landing/styles.js
+++ b/src/screens/Landing/styles.js
@@ -97,4 +97,9 @@ export default StyleSheet.create({
textAlignRight: {
alignItems: 'flex-end',
},
+ progress: {
+ backgroundColor: '#305ebf',
+ height: 3,
+ transition: 'width ease .4s',
+ },
});
diff --git a/src/screens/Landing/useGetPdfReport/index.js b/src/screens/Landing/useGetPdfReport/index.js
index 7c4643a05..8a1b64d51 100644
--- a/src/screens/Landing/useGetPdfReport/index.js
+++ b/src/screens/Landing/useGetPdfReport/index.js
@@ -5,6 +5,8 @@ import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Platform } from 'react-native';
+import { useWebSocket } from '../../../context/socket';
+
const webDownload = (url, inspectionId) => {
const link = document.createElement('a');
link.href = url;
@@ -28,6 +30,7 @@ export default function useGetPdfReport(inspectionId, onError) {
const [reportUrl, setReportUrl] = useState(null);
const [loading, setLoading] = useState(false);
const { i18n } = useTranslation();
+ const { onSocketEvent } = useWebSocket();
const requestPdfPayload = useMemo(() => ({
pricing: false,
@@ -55,17 +58,12 @@ export default function useGetPdfReport(inspectionId, onError) {
const preparePdf = useCallback(
async () => {
setLoading(true);
- await requestPdfReport();
- let done = false;
- while (!done) {
+ // Send/Listen an event from server
+ onSocketEvent('ready_inspection_pdf_url', async () => {
try {
- // eslint-disable-next-line no-await-in-loop
- await timeout(2000);
- // eslint-disable-next-line no-await-in-loop
const res = await getPdfUrl();
if (res.axiosResponse?.data?.pdfUrl) {
setReportUrl(res.axiosResponse.data.pdfUrl);
- done = true;
setLoading(false);
}
} catch (err) {
@@ -74,7 +72,9 @@ export default function useGetPdfReport(inspectionId, onError) {
if (onError) { onError(err); }
}
}
- }
+ });
+ // api call for pdf report
+ await requestPdfReport();
},
[inspectionId, requestPdfReport, getPdfUrl, setReportUrl, setLoading],
);
diff --git a/yarn.lock b/yarn.lock
index b604adf99..d698856dd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3738,6 +3738,11 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
+"@socket.io/component-emitter@~3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
+ integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
+
"@svgr/babel-plugin-add-jsx-attribute@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18"
@@ -7046,7 +7051,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.
dependencies:
ms "2.0.0"
-debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
+debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -7605,6 +7610,22 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
+engine.io-client@~6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.4.0.tgz#88cd3082609ca86d7d3c12f0e746d12db4f47c91"
+ integrity sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==
+ dependencies:
+ "@socket.io/component-emitter" "~3.1.0"
+ debug "~4.3.1"
+ engine.io-parser "~5.0.3"
+ ws "~8.11.0"
+ xmlhttprequest-ssl "~2.0.0"
+
+engine.io-parser@~5.0.3:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45"
+ integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==
+
enhanced-resolve@^4.1.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec"
@@ -16455,6 +16476,24 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
+socket.io-client@^4.6.1:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.6.1.tgz#80d97d5eb0feca448a0fb6d69a7b222d3d547eab"
+ integrity sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ==
+ dependencies:
+ "@socket.io/component-emitter" "~3.1.0"
+ debug "~4.3.2"
+ engine.io-client "~6.4.0"
+ socket.io-parser "~4.2.1"
+
+socket.io-parser@~4.2.1:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206"
+ integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==
+ dependencies:
+ "@socket.io/component-emitter" "~3.1.0"
+ debug "~4.3.1"
+
sockjs-client@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5"
@@ -18404,6 +18443,11 @@ ws@^7.4.6:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
+ws@~8.11.0:
+ version "8.11.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
+ integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
+
x-path@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/x-path/-/x-path-0.0.2.tgz#294d076bb97a7706cc070bbb2a6fd8c54df67b12"
@@ -18494,6 +18538,11 @@ xmldom@^0.6.0:
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f"
integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==
+xmlhttprequest-ssl@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"
+ integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==
+
xregexp@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"