diff --git a/.eslintrc.js b/.eslintrc.js index 6fd6d84c..4a14bbac 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -10,8 +10,8 @@ module.exports = { }, rules: { 'camelcase': 0, - 'class-methods-use-this': 'warn', - 'default-case': 'warn', + 'class-methods-use-this': 0, + 'default-case': 'error', 'import/prefer-default-export': 0, 'import/no-named-as-default': 0, 'react/destructuring-assignment': 0, @@ -28,15 +28,15 @@ module.exports = { 'react/require-default-props': 0, 'react/sort-comp': 0, 'max-len': 0, - 'no-await-in-loop': 'warn', - 'no-case-declarations': 'warn', + 'no-await-in-loop': 'error', + 'no-case-declarations': 'error', 'no-console': 0, 'no-else-return': 0, - 'no-empty': 'warn', + 'no-empty': 'error', 'no-multi-spaces': ['error', { ignoreEOLComments: true, }], - 'no-nested-ternary': 'warn', + 'no-nested-ternary': 0, 'no-plusplus': [ 'error', { @@ -49,7 +49,7 @@ module.exports = { 'no-unused-vars': ['error', { args: 'none', }], - 'no-use-before-define': 'warn', + 'no-use-before-define': 'error', 'object-curly-newline': [ 'error', { diff --git a/.gitignore b/.gitignore index 5866e640..07c676cc 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ .env.development.local .env.test.local .env.production.local +settings.json npm-debug.log* yarn-debug.log* diff --git a/src/actions/cached.js b/src/actions/cached.js index 19c8a4d6..704235d3 100644 --- a/src/actions/cached.js +++ b/src/actions/cached.js @@ -80,6 +80,26 @@ async function getCacheDB() { }); } +async function expireCacheItems(store) { + const db = await getCacheDB(); + if (!db) { + return; + } + + const transaction = db.transaction([store], 'readwrite'); + const objStore = transaction.objectStore(store); + + const idx = IDBKeyRange.upperBound(Math.floor(Date.now() / 1000)); + const req = objStore.index('expiry').openCursor(idx); + req.onsuccess = (ev) => { + const cursor = ev.target.result; + if (cursor) { + objStore.delete(cursor.primaryKey); + cursor.continue(); + } + }; +} + async function getCacheItem(store, key, version = undefined) { if (!hasExpired) { setTimeout(() => expireCacheItems(store), 5000); // TODO: better expire time @@ -125,26 +145,6 @@ async function setCacheItem(store, key, expiry, data, version = undefined) { }); } -async function expireCacheItems(store) { - const db = await getCacheDB(); - if (!db) { - return; - } - - const transaction = db.transaction([store], 'readwrite'); - const objStore = transaction.objectStore(store); - - const idx = IDBKeyRange.upperBound(Math.floor(Date.now() / 1000)); - const req = objStore.index('expiry').openCursor(idx); - req.onsuccess = (ev) => { - const cursor = ev.target.result; - if (cursor) { - objStore.delete(cursor.primaryKey); - cursor.continue(); - } - }; -} - function parseEvents(route, driveEvents) { // sort events driveEvents.sort((a, b) => { @@ -342,13 +342,6 @@ export function fetchEvents(route) { }; } -export function fetchLocations(route) { - return (dispatch, getState) => { - dispatch(fetchCoord(route, [route.start_lng, route.start_lat], 'startLocation')); - dispatch(fetchCoord(route, [route.end_lng, route.end_lat], 'endLocation')); - }; -} - export function fetchCoord(route, coord, locationKey) { return async (dispatch, getState) => { const state = getState(); @@ -415,6 +408,13 @@ export function fetchCoord(route, coord, locationKey) { }; } +export function fetchLocations(route) { + return (dispatch, getState) => { + dispatch(fetchCoord(route, [route.start_lng, route.start_lat], 'startLocation')); + dispatch(fetchCoord(route, [route.end_lng, route.end_lat], 'endLocation')); + }; +} + export function fetchDriveCoords(route) { return async (dispatch, getState) => { const state = getState(); diff --git a/src/actions/files.js b/src/actions/files.js index eebd4e53..a27d8e69 100644 --- a/src/actions/files.js +++ b/src/actions/files.js @@ -268,6 +268,7 @@ export function doUpload(dongleId, fileNames, paths, urls) { params: [paths[i], urls[i], { 'x-ms-blob-type': 'BlockBlob' }], expiry: Math.floor(Date.now() / 1000) + (86400 * 7), }; + // eslint-disable-next-line no-await-in-loop const resp = await athenaCall(dongleId, payload, 'files_actions_athena_upload'); if (!resp || resp.error) { const uploading = {}; diff --git a/src/actions/index.js b/src/actions/index.js index 9ac4a1cc..2795b6f1 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -11,6 +11,99 @@ import { getDeviceFromState, deviceVersionAtLeast } from '../utils'; let routesRequest = null; +export function checkRoutesData() { + return (dispatch, getState) => { + let state = getState(); + if (!state.dongleId) { + return; + } + if (hasRoutesData(state)) { + // already has metadata, don't bother + return; + } + if (routesRequest && routesRequest.dongleId === state.dongleId) { + return; + } + console.debug('We need to update the segment metadata...'); + const { dongleId } = state; + const fetchRange = getSegmentFetchRange(state); + + routesRequest = { + req: Drives.getRoutesSegments(dongleId, fetchRange.start, fetchRange.end), + dongleId, + }; + + routesRequest.req.then((routesData) => { + state = getState(); + const currentRange = getSegmentFetchRange(state); + if (currentRange.start !== fetchRange.start + || currentRange.end !== fetchRange.end + || state.dongleId !== dongleId) { + routesRequest = null; + dispatch(checkRoutesData()); + return; + } + if (routesData && routesData.length === 0 + && !MyCommaAuth.isAuthenticated()) { + window.location = `/?r=${encodeURI(window.location.pathname)}`; // redirect to login + return; + } + + const routes = routesData.map((r) => { + let startTime = r.segment_start_times[0]; + let endTime = r.segment_end_times[r.segment_end_times.length - 1]; + + // TODO: these will all be relative times soon + // fix segment boundary times for routes that have the wrong time at the start + if ((Math.abs(r.start_time_utc_millis - startTime) > 24 * 60 * 60 * 1000) + && (Math.abs(r.end_time_utc_millis - endTime) < 10 * 1000)) { + console.log('fixing %s', r.fullname); + startTime = r.start_time_utc_millis; + endTime = r.end_time_utc_millis; + r.segment_start_times = r.segment_numbers.map((x) => startTime + (x * 60 * 1000)); + r.segment_end_times = r.segment_numbers.map((x) => Math.min(startTime + ((x + 1) * 60 * 1000), endTime)); + } + return { + ...r, + url: r.url.replace('chffrprivate.blob.core.windows.net', 'chffrprivate.azureedge.net'), + offset: Math.round(startTime) - state.filter.start, + duration: endTime - startTime, + start_time_utc_millis: startTime, + end_time_utc_millis: endTime, + segment_offsets: r.segment_start_times.map((x) => x - state.filter.start), + }; + }); + + dispatch({ + type: Types.ACTION_ROUTES_METADATA, + dongleId, + start: fetchRange.start, + end: fetchRange.end, + routes, + }); + + routesRequest = null; + }).catch((err) => { + console.error('Failure fetching routes metadata', err); + Sentry.captureException(err, { fingerprint: 'timeline_fetch_routes' }); + routesRequest = null; + }); + }; +} + +export function urlForState(dongleId, start, end, prime) { + const path = [dongleId]; + + if (start && end) { + path.push(start); + path.push(end); + } else if (prime) { + path.push('prime'); + } + + return `/${path.join('/')}`; +} + function updateTimeline(state, dispatch, start, end, allowPathChange) { dispatch(checkRoutesData()); @@ -57,36 +150,11 @@ export function pushTimelineRange(start, end, allowPathChange = true) { }; } -export function selectDevice(dongleId, allowPathChange = true) { - return (dispatch, getState) => { - const state = getState(); - let device; - if (state.devices && state.devices.length > 1) { - device = state.devices.find((d) => d.dongle_id === dongleId); - } - if (!device && state.device && state.device.dongle_id === dongleId) { - device = state.device; - } - - dispatch({ - type: Types.ACTION_SELECT_DEVICE, - dongleId, - }); - - dispatch(pushTimelineRange(null, null, false)); - if ((device && !device.shared) || state.profile?.superuser) { - dispatch(primeFetchSubscription(dongleId, device)); - dispatch(fetchDeviceOnline(dongleId)); - } - - dispatch(checkRoutesData()); - - if (allowPathChange) { - const desiredPath = urlForState(dongleId, null, null, null); - if (window.location.pathname !== desiredPath) { - dispatch(push(desiredPath)); - } - } +export function primeGetSubscription(dongleId, subscription) { + return { + type: Types.ACTION_PRIME_SUBSCRIPTION, + dongleId, + subscription, }; } @@ -125,6 +193,52 @@ export function primeFetchSubscription(dongleId, device, profile) { }; } +export function fetchDeviceOnline(dongleId) { + return (dispatch) => { + Devices.fetchDevice(dongleId).then((resp) => { + dispatch({ + type: Types.ACTION_UPDATE_DEVICE_ONLINE, + dongleId, + last_athena_ping: resp.last_athena_ping, + fetched_at: Math.floor(Date.now() / 1000), + }); + }).catch(console.log); + }; +} + +export function selectDevice(dongleId, allowPathChange = true) { + return (dispatch, getState) => { + const state = getState(); + let device; + if (state.devices && state.devices.length > 1) { + device = state.devices.find((d) => d.dongle_id === dongleId); + } + if (!device && state.device && state.device.dongle_id === dongleId) { + device = state.device; + } + + dispatch({ + type: Types.ACTION_SELECT_DEVICE, + dongleId, + }); + + dispatch(pushTimelineRange(null, null, false)); + if ((device && !device.shared) || state.profile?.superuser) { + dispatch(primeFetchSubscription(dongleId, device)); + dispatch(fetchDeviceOnline(dongleId)); + } + + dispatch(checkRoutesData()); + + if (allowPathChange) { + const desiredPath = urlForState(dongleId, null, null, null); + if (window.location.pathname !== desiredPath) { + dispatch(push(desiredPath)); + } + } + }; +} + export function primeNav(nav, allowPathChange = true) { return (dispatch, getState) => { const state = getState(); @@ -167,19 +281,6 @@ export function fetchSharedDevice(dongleId) { }; } -export function fetchDeviceOnline(dongleId) { - return (dispatch) => { - Devices.fetchDevice(dongleId).then((resp) => { - dispatch({ - type: Types.ACTION_UPDATE_DEVICE_ONLINE, - dongleId, - last_athena_ping: resp.last_athena_ping, - fetched_at: Math.floor(Date.now() / 1000), - }); - }).catch(console.log); - }; -} - export function updateDeviceOnline(dongleId, lastAthenaPing) { return (dispatch) => { dispatch({ @@ -247,86 +348,6 @@ export function fetchDeviceNetworkStatus(dongleId) { }; } -export function checkRoutesData() { - return (dispatch, getState) => { - let state = getState(); - if (!state.dongleId) { - return; - } - if (hasRoutesData(state)) { - // already has metadata, don't bother - return; - } - if (routesRequest && routesRequest.dongleId === state.dongleId) { - return; - } - console.debug('We need to update the segment metadata...'); - const { dongleId } = state; - const fetchRange = getSegmentFetchRange(state); - - routesRequest = { - req: Drives.getRoutesSegments(dongleId, fetchRange.start, fetchRange.end), - dongleId, - }; - - routesRequest.req.then((routesData) => { - state = getState(); - const currentRange = getSegmentFetchRange(state); - if (currentRange.start !== fetchRange.start - || currentRange.end !== fetchRange.end - || state.dongleId !== dongleId) { - routesRequest = null; - dispatch(checkRoutesData()); - return; - } - if (routesData && routesData.length === 0 - && !MyCommaAuth.isAuthenticated()) { - window.location = `/?r=${encodeURI(window.location.pathname)}`; // redirect to login - return; - } - - const routes = routesData.map((r) => { - let startTime = r.segment_start_times[0]; - let endTime = r.segment_end_times[r.segment_end_times.length - 1]; - - // TODO: these will all be relative times soon - // fix segment boundary times for routes that have the wrong time at the start - if ((Math.abs(r.start_time_utc_millis - startTime) > 24 * 60 * 60 * 1000) - && (Math.abs(r.end_time_utc_millis - endTime) < 10 * 1000)) { - console.log('fixing %s', r.fullname); - startTime = r.start_time_utc_millis; - endTime = r.end_time_utc_millis; - r.segment_start_times = r.segment_numbers.map((x) => startTime + (x * 60 * 1000)); - r.segment_end_times = r.segment_numbers.map((x) => Math.min(startTime + ((x + 1) * 60 * 1000), endTime)); - } - return { - ...r, - url: r.url.replace('chffrprivate.blob.core.windows.net', 'chffrprivate.azureedge.net'), - offset: Math.round(startTime) - state.filter.start, - duration: endTime - startTime, - start_time_utc_millis: startTime, - end_time_utc_millis: endTime, - segment_offsets: r.segment_start_times.map((x) => x - state.filter.start), - }; - }); - - dispatch({ - type: Types.ACTION_ROUTES_METADATA, - dongleId, - start: fetchRange.start, - end: fetchRange.end, - routes, - }); - - routesRequest = null; - }).catch((err) => { - console.error('Failure fetching routes metadata', err); - Sentry.captureException(err, { fingerprint: 'timeline_fetch_routes' }); - routesRequest = null; - }); - }; -} - export function updateDevices(devices) { return { type: Types.ACTION_UPDATE_DEVICES, @@ -353,27 +374,6 @@ export function selectTimeFilter(start, end) { }; } -export function primeGetSubscription(dongleId, subscription) { - return { - type: Types.ACTION_PRIME_SUBSCRIPTION, - dongleId, - subscription, - }; -} - -export function urlForState(dongleId, start, end, prime) { - const path = [dongleId]; - - if (start && end) { - path.push(start); - path.push(end); - } else if (prime) { - path.push('prime'); - } - - return `/${path.join('/')}`; -} - export function analyticsEvent(name, parameters) { return { type: Types.ANALYTICS_EVENT, diff --git a/src/analytics.js b/src/analytics.js index deb1cb6c..4b78f5df 100644 --- a/src/analytics.js +++ b/src/analytics.js @@ -101,6 +101,7 @@ function logAction(action, prevState, state) { } } + // eslint-disable-next-line default-case switch (action.type) { case LOCATION_CHANGE: gtag('event', 'page_view', { diff --git a/src/components/AppHeader/TimeFilter.jsx b/src/components/AppHeader/TimeFilter.jsx index 097648b3..b9a0bc74 100644 --- a/src/components/AppHeader/TimeFilter.jsx +++ b/src/components/AppHeader/TimeFilter.jsx @@ -77,6 +77,7 @@ class TimeSelect extends Component { const d = new Date(); d.setHours(d.getHours() + 1, 0, 0, 0); + // eslint-disable-next-line default-case switch (selection) { case '24-hours': this.props.dispatch(selectTimeFilter(d.getTime() - (1000 * 60 * 60 * 24), d.getTime())); diff --git a/src/components/DeviceInfo/index.jsx b/src/components/DeviceInfo/index.jsx index 9e42b6b8..a7854aea 100644 --- a/src/components/DeviceInfo/index.jsx +++ b/src/components/DeviceInfo/index.jsx @@ -319,7 +319,9 @@ class DeviceInfo extends Component { if (error.length > 5 && error[5] === '{') { try { error = JSON.parse(error.substr(5)).error; - } catch { } + } catch { + //pass + } } } this.setState({ snapshot: { error } }); diff --git a/src/components/IosPwaPopup/index.jsx b/src/components/IosPwaPopup/index.jsx index 09f6063e..1c719c4f 100644 --- a/src/components/IosPwaPopup/index.jsx +++ b/src/components/IosPwaPopup/index.jsx @@ -104,7 +104,9 @@ class IosPwaPopup extends Component { hide() { try { localforage.setItem('hideIosPwaPopup', true); - } catch (err) {} + } catch (err) { + // pass + } this.setState({ show: false }); } diff --git a/src/reducers/globalState.js b/src/reducers/globalState.js index aee087e8..fc0fdb39 100644 --- a/src/reducers/globalState.js +++ b/src/reducers/globalState.js @@ -25,7 +25,7 @@ export default function reducer(_state, action) { let state = { ..._state }; let deviceIndex = null; switch (action.type) { - case Types.ACTION_STARTUP_DATA: + case Types.ACTION_STARTUP_DATA: { const devices = action.devices.map(populateFetchedAt).sort(deviceCompareFn); if (!state.dongleId && devices.length > 0) { @@ -48,6 +48,7 @@ export default function reducer(_state, action) { state.devices = devices; state.profile = action.profile; break; + } case Types.ACTION_SELECT_DEVICE: state = { ...state, @@ -135,7 +136,7 @@ export default function reducer(_state, action) { }; } break; - case Types.ACTION_UPDATE_ROUTE_EVENTS: + case Types.ACTION_UPDATE_ROUTE_EVENTS: { const firstFrame = action.events.find((ev) => ev.type === 'event' && ev.data.event_type === 'first_road_camera_frame'); const videoStartOffset = firstFrame ? firstFrame.route_offset_millis : null; if (state.routes) { @@ -158,6 +159,7 @@ export default function reducer(_state, action) { }; } break; + } case Types.ACTION_UPDATE_ROUTE_LOCATION: if (state.routes) { state.routes = state.routes.map((route) => {