From e60f9170c3ce2db464e9d99a2906cfd89e14b5df Mon Sep 17 00:00:00 2001 From: Rafael Araujo Lehmkuhl Date: Mon, 10 Feb 2025 14:29:19 -0300 Subject: [PATCH 1/4] settings-sync: Register timestamp of changes and always use the newest one --- src/composables/settingsSyncer.ts | 217 +++++++++++++++++++++--------- 1 file changed, 155 insertions(+), 62 deletions(-) diff --git a/src/composables/settingsSyncer.ts b/src/composables/settingsSyncer.ts index 08c4f19a6..3a2254f9a 100644 --- a/src/composables/settingsSyncer.ts +++ b/src/composables/settingsSyncer.ts @@ -15,12 +15,79 @@ import { savedProfilesKey } from '@/stores/widgetManager' import { useInteractionDialog } from './interactionDialog' import { openSnackbar } from './snackbar' +/** + * Maps setting key to its last update timestamp, organized by user and vehicle ID + */ +export interface SettingsEpochTable { + [userId: string]: { + [vehicleId: string]: { + [key: string]: number + } + } +} + export const resetJustMadeKey = 'cockpit-reset-just-made' const resetJustMade = useStorage(resetJustMadeKey, false) setTimeout(() => { resetJustMade.value = false }, 10000) +// Store epochs for local settings +const localEpochTable = useStorage('cockpit-settings-epochs', {}) + +// Helper function to get/set epoch for a specific user, vehicle, and key +const getLocalEpoch = (username: string, vehicleId: string, key: string): number | undefined => { + return localEpochTable.value[username]?.[vehicleId]?.[key] || undefined +} + +const setLocalEpoch = (username: string, vehicleId: string, key: string, epoch: number): void => { + if (!localEpochTable.value[username]) { + localEpochTable.value[username] = {} + } + if (!localEpochTable.value[username][vehicleId]) { + localEpochTable.value[username][vehicleId] = {} + } + localEpochTable.value[username][vehicleId][key] = epoch +} + +const getSettingsEpochOnVehicle = async ( + vehicleAddress: string, + username: string, + key: string +): Promise => { + const url = `settings/${username}/epochs/${key}` + return (await getKeyDataFromCockpitVehicleStorage(vehicleAddress, url).catch(() => undefined)) as number | undefined +} + +const setSettingsEpochOnVehicle = async ( + vehicleAddress: string, + username: string, + key: string, + epoch: number +): Promise => { + const url = `settings/${username}/epochs/${key}` + await setKeyDataOnCockpitVehicleStorage(vehicleAddress, url, epoch) +} + +const getSettingsValueOnVehicle = async ( + vehicleAddress: string, + username: string, + key: string +): Promise => { + const url = `settings/${username}/${key}` + return (await getKeyDataFromCockpitVehicleStorage(vehicleAddress, url).catch(() => undefined)) as any | undefined +} + +const setSettingsValueOnVehicle = async ( + vehicleAddress: string, + username: string, + key: string, + value: any +): Promise => { + const url = `settings/${username}/${key}` + await setKeyDataOnCockpitVehicleStorage(vehicleAddress, url, value) +} + const getVehicleAddress = async (): Promise => { const vehicleStore = useMainVehicleStore() @@ -40,11 +107,11 @@ const getVehicleAddress = async (): Promise => { * stored value and keep trying to communicate with BlueOS to get it's value. * * Once the connection is stablished, if BlueOS doesn't have a value, it will use the local stored one and update - * BlueOS with it. On the other hand, if BlueOS has a value, it will ask the user if they want to use the value from - * BlueOS or the local one. Depending on the user's choice, it will update the local value or BlueOS. + * BlueOS with it. On the other hand, if BlueOS also has a value, it will use the most recent one, based on an epoch + * that stores the moment that value was last updated locally and on BlueOS. * * Once everything is in sync, if the local value changes, it will update the value on BlueOS. - * In resume, the initial source of truth is decided by the user, and once everything is in sync, the source of truth + * In resume, the initial source of truth is the most recent value, and once everything is in sync, the source of truth * is the local value. * @param { string } key * @param { T } defaultValue @@ -83,58 +150,31 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem return vehicleStore.currentlyConnectedVehicleId } - const getLastConnectedVehicleId = async (): Promise => { + const getLastConnectedVehicleId = (): string | undefined => { const vehicleStore = useMainVehicleStore() return vehicleStore.lastConnectedVehicleId } - const getLastConnectedUser = async (): Promise => { + const getLastConnectedUser = (): string | undefined => { const missoinStore = useMissionStore() return missoinStore.lastConnectedUser } - const askIfUserWantsToUseBlueOsValue = async (): Promise => { - let useBlueOsValue = true - - const preferBlueOs = (): void => { - useBlueOsValue = true - } - - const preferCockpit = (): void => { - useBlueOsValue = false - } - - await showDialog({ - maxWidth: 600, - title: 'Conflict with BlueOS', - message: ` - The value for '${key}' that is currently used in Cockpit differs from the one stored in BlueOS. What do you - want to do? - `, - variant: 'warning', - actions: [ - { text: 'Use the value from BlueOS', action: preferBlueOs }, - { text: "Keep Cockpit's value", action: preferCockpit }, - ], - }) - - closeDialog() - - return useBlueOsValue - } - - const updateValueOnBlueOS = async (newValue: T): Promise => { + const updateValueOnBlueOS = async (newValue: T, updateEpoch: number): Promise => { const vehicleAddress = await getVehicleAddress() const username = await getUsername() - console.debug(`Updating '${key}' on BlueOS.`) + console.debug(`Updating '${key}' on BlueOS. New value:`, newValue, 'New epoch:', updateEpoch) const tryToUpdateBlueOsValue = async (): Promise => { // Clear update routine if there's one left, as we are going to start a new one clearTimeout(blueOsUpdateTimeout) try { - await setKeyDataOnCockpitVehicleStorage(vehicleAddress, `settings/${username}/${key}`, newValue) + // Update the value of the key and its epoch on BlueOS + await setSettingsValueOnVehicle(vehicleAddress, username, key, newValue) + await setSettingsEpochOnVehicle(vehicleAddress, username, key, updateEpoch) + const message = `Success updating '${key}' on BlueOS.` openSnackbar({ message, duration: 3000, variant: 'success', closeButton: true }) console.info(message) @@ -157,50 +197,72 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem const vehicleAddress = await getVehicleAddress() const username = await getUsername() const currentVehicleId = await getCurrentVehicleId() - const lastConnectedVehicleId = await getLastConnectedVehicleId() - const lastConnectedUser = await getLastConnectedUser() // Clear initial sync routine if there's one left, as we are going to start a new one clearTimeout(initialSyncTimeout) try { - const valueOnBlueOS = await getKeyDataFromCockpitVehicleStorage(vehicleAddress, `settings/${username}/${key}`) + const valueOnBlueOS = await getSettingsValueOnVehicle(vehicleAddress, username, key) console.debug(`Success getting value of '${key}' from BlueOS:`, valueOnBlueOS) - // If the value on BlueOS is the same as the one we have locally, we don't need to bother the user + // If the value on BlueOS is the same as the one we have locally, we don't need to do anything if (isEqual(currentValue.value, valueOnBlueOS)) { console.debug(`Value for '${key}' on BlueOS is the same as the local one. No need to update.`) finishedInitialFetch.value = true return } - // By default, if there's a conflict, we use the value from BlueOS. - let useBlueOsValue = true + // Get epochs for both local and remote values + const remoteEpoch = await getSettingsEpochOnVehicle(vehicleAddress, username, key) + const localEpoch = getLocalEpoch(username, currentVehicleId, key) + + // By default, if there's a conflict, we use the value with the newest epoch + let useBlueOsValue = (remoteEpoch ?? 0) > (localEpoch ?? 0) - // If the connected vehicle is the same as the last connected vehicle, and the user is also the same, and there - // are conflicts, it means the user has made changes while offline, so we ask the user if they want to keep the - // local value or the one from BlueOS. - // If the connected vehicle is different from the last connected vehicle, we just use the value from BlueOS, as we - // don't want to overwrite the value on the new vehicle with the one from the previous vehicle. - if (resetJustMade.value) { + // Do nothing if both values are undefined + if (currentValue.value === undefined && valueOnBlueOS === undefined) { + console.debug(`Both local and remote values for '${key}' are undefined. No need to update.`) + finishedInitialFetch.value = true + return + } else if (currentValue.value === undefined) { + // If only the local value is undefined, use the value from BlueOS + console.debug(`Local value for '${key}' is undefined. Using value from BlueOS.`) + useBlueOsValue = true + } else if (valueOnBlueOS === undefined) { + // If only the remote value is undefined, use the value from local storage + console.debug(`Remote value for '${key}' is undefined. Using value from local storage.`) useBlueOsValue = false - } else if (lastConnectedUser === username && lastConnectedVehicleId === currentVehicleId) { - console.debug(`Conflict with BlueOS for key '${key}'. Asking user what to do.`) - useBlueOsValue = await askIfUserWantsToUseBlueOsValue() + } + + const msg = `Key: ${key} // Epochs: Remote: ${remoteEpoch}, Local: ${localEpoch} // Use BlueOS value: ${useBlueOsValue}` + console.debug(msg) + + // If the epochs are equal and the values are different, we use the value from BlueOS + if (remoteEpoch === localEpoch && !isEqual(currentValue.value, valueOnBlueOS)) { + useBlueOsValue = true } if (useBlueOsValue) { currentValue.value = valueOnBlueOS as T + const remoteEpochOrNow = remoteEpoch ?? Date.now() + + // Update local epoch to match remote + setLocalEpoch(username, currentVehicleId, key, remoteEpochOrNow) + + // Update epoch on BlueOS as well if it's not there yet + if (remoteEpoch === undefined) { + await setSettingsEpochOnVehicle(vehicleAddress, username, key, remoteEpochOrNow) + } + const message = `Fetched remote value of key ${key} from the vehicle.` openSnackbar({ message, duration: 3000, variant: 'success' }) - // TODO: This is a workaround to make the profiles work after an import. - // We need to find a better way to handle this, without reloading. + finishedInitialFetch.value = true + if (key === savedProfilesKey) { await showDialog({ title: 'Widget profiles imported', - message: `The widget profiles have been imported from the vehicle. We need to reload the page to apply the - changes.`, + message: `The widget profiles have been imported from the vehicle. We need to reload the page to apply the changes.`, variant: 'warning', actions: [{ text: 'OK', action: closeDialog }], timer: 3000, @@ -208,7 +270,16 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem reloadCockpit() } } else { - await updateValueOnBlueOS(currentValue.value) + // Update both value and epoch on BlueOS + const localEpochOrNow = localEpoch ?? Date.now() + + await updateValueOnBlueOS(currentValue.value, localEpochOrNow) + + // Update epoch locally if it's not there yet + if (localEpoch === undefined) { + setLocalEpoch(username, currentVehicleId, key, localEpochOrNow) + } + const message = `Pushed local value of key ${key} to the vehicle.` openSnackbar({ message, duration: 3000, variant: 'success' }) } @@ -221,7 +292,10 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem if ((initialSyncError as Error).name === NoPathInBlueOsErrorName) { console.debug(`No value for '${key}' on BlueOS. Using current value. Will push it to BlueOS.`) try { - await updateValueOnBlueOS(currentValue.value) + // Set initial epoch and push both value and epoch + const localEpochOrNow = getLocalEpoch(username, currentVehicleId, key) ?? Date.now() + setLocalEpoch(username, currentVehicleId, key, localEpochOrNow) + await updateValueOnBlueOS(currentValue.value, localEpochOrNow) finishedInitialFetch.value = true return } catch (fetchError) { @@ -256,7 +330,7 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem let valueBeforeDebouncedChange = structuredClone(toRaw(currentValue.value)) let valueUpdateMethodTimeout: ReturnType | undefined = undefined - const maybeUpdateValueOnBlueOs = async (newValue: T, oldValue: T): Promise => { + const maybeUpdateValueOnBlueOs = async (newValue: T, oldValue: T, epoch: number): Promise => { console.debug(`Detected changes in the local value for key '${key}'. Updating BlueOS.`) // Don't update the value on BlueOS if we haven't finished the initial fetch, so we don't overwrite the value there without user consent @@ -275,14 +349,33 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem const devStore = useDevelopmentStore() if (!devStore.enableBlueOsSettingsSync) return - updateValueOnBlueOS(newValue) + updateValueOnBlueOS(newValue, epoch) + } + + const updateEpochLocally = (epoch: number): void => { + const lastConnectedUser = getLastConnectedUser() + const lastConnectedVehicleId = getLastConnectedVehicleId() + + if (lastConnectedUser === undefined || lastConnectedVehicleId === undefined) { + console.error('Not able to update epoch locally. Last connected user or vehicle ID not found.') + return + } + + setLocalEpoch(lastConnectedUser, lastConnectedVehicleId, key, epoch) } watch( currentValue, async (newValue) => { + // Update the local epoch immediately + const epoch = Date.now() + updateEpochLocally(epoch) + + // Throttle remote value updates to avoid spamming BlueOS with requests clearTimeout(valueUpdateMethodTimeout) - valueUpdateMethodTimeout = setTimeout(() => maybeUpdateValueOnBlueOs(newValue, valueBeforeDebouncedChange), 1000) + valueUpdateMethodTimeout = setTimeout(() => { + maybeUpdateValueOnBlueOs(newValue, valueBeforeDebouncedChange, epoch) + }, 1000) }, { deep: true } ) From a912d87f67520c6c1fe847ea52394b67caebee62 Mon Sep 17 00:00:00 2001 From: Rafael Araujo Lehmkuhl Date: Wed, 12 Feb 2025 16:33:55 -0300 Subject: [PATCH 2/4] utils: Add function to help systems log consistently while identifing themselves --- src/libs/utils.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/libs/utils.ts b/src/libs/utils.ts index a3b8d00bf..cac4f0360 100644 --- a/src/libs/utils.ts +++ b/src/libs/utils.ts @@ -224,3 +224,19 @@ export const humanizeString = (str: string): string => { .trim() .replace(/\b\w/g, (match) => match.toUpperCase()) } + +/** + * Logs a message with the [systemName] prefix + * This should help us identify the source of the log message in debugs + * @param {string} systemName - The name of the system + * @returns {Record void>} A record of log functions + */ +export const systemLogger = (systemName: string): Record void> => { + return { + debug: (...args: any[]) => console.debug(`[${systemName}]`, ...args), + info: (...args: any[]) => console.info(`[${systemName}]`, ...args), + warn: (...args: any[]) => console.warn(`[${systemName}]`, ...args), + error: (...args: any[]) => console.error(`[${systemName}]`, ...args), + log: (...args: any[]) => console.log(`[${systemName}]`, ...args), + } +} From d46059c979f58888b360846242aa453391917276 Mon Sep 17 00:00:00 2001 From: Rafael Araujo Lehmkuhl Date: Wed, 12 Feb 2025 16:34:30 -0300 Subject: [PATCH 3/4] settings-sync: Move all log calls to the new system logger --- src/composables/settingsSyncer.ts | 49 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/composables/settingsSyncer.ts b/src/composables/settingsSyncer.ts index 3a2254f9a..ac18b177e 100644 --- a/src/composables/settingsSyncer.ts +++ b/src/composables/settingsSyncer.ts @@ -7,6 +7,7 @@ import { setKeyDataOnCockpitVehicleStorage, } from '@/libs/blueos' import { isEqual, reloadCockpit } from '@/libs/utils' +import { systemLogger } from '@/libs/utils' import { useDevelopmentStore } from '@/stores/development' import { useMainVehicleStore } from '@/stores/mainVehicle' import { useMissionStore } from '@/stores/mission' @@ -15,6 +16,8 @@ import { savedProfilesKey } from '@/stores/widgetManager' import { useInteractionDialog } from './interactionDialog' import { openSnackbar } from './snackbar' +const logger = systemLogger('SettingsSyncer') + /** * Maps setting key to its last update timestamp, organized by user and vehicle ID */ @@ -93,7 +96,7 @@ const getVehicleAddress = async (): Promise => { // Wait until we have a global address while (vehicleStore.globalAddress === undefined) { - console.debug('Waiting for vehicle global address on BlueOS sync routine.') + logger.debug('Waiting for vehicle global address on BlueOS sync routine.') await new Promise((r) => setTimeout(r, 1000)) } @@ -131,7 +134,7 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem // Wait until we have a username while (!missionStore.username) { - console.debug('Waiting for username on BlueOS sync routine.') + logger.debug('Waiting for username on BlueOS sync routine.') await new Promise((r) => setTimeout(r, 1000)) } @@ -143,7 +146,7 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem // Wait until we have a vehicle ID while (!vehicleStore.currentlyConnectedVehicleId) { - console.debug('Waiting for vehicle ID on BlueOS sync routine.') + logger.debug('Waiting for vehicle ID on BlueOS sync routine.') await new Promise((r) => setTimeout(r, 1000)) } @@ -164,7 +167,7 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem const vehicleAddress = await getVehicleAddress() const username = await getUsername() - console.debug(`Updating '${key}' on BlueOS. New value:`, newValue, 'New epoch:', updateEpoch) + logger.debug(`Updating '${key}' on BlueOS.`) const tryToUpdateBlueOsValue = async (): Promise => { // Clear update routine if there's one left, as we are going to start a new one @@ -177,12 +180,12 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem const message = `Success updating '${key}' on BlueOS.` openSnackbar({ message, duration: 3000, variant: 'success', closeButton: true }) - console.info(message) + logger.info(message) } catch (fetchError) { const message = `Failed updating '${key}' on BlueOS. Will keep trying.` openSnackbar({ message, duration: 3000, variant: 'error', closeButton: true }) - console.error(message) - console.error(fetchError) + logger.error(message) + logger.error(fetchError) // If we can't update the value on BlueOS, try again in 10 seconds blueOsUpdateTimeout = setTimeout(tryToUpdateBlueOsValue, 10000) @@ -203,11 +206,11 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem try { const valueOnBlueOS = await getSettingsValueOnVehicle(vehicleAddress, username, key) - console.debug(`Success getting value of '${key}' from BlueOS:`, valueOnBlueOS) + logger.debug(`Success getting value of '${key}' from BlueOS:`, valueOnBlueOS) // If the value on BlueOS is the same as the one we have locally, we don't need to do anything if (isEqual(currentValue.value, valueOnBlueOS)) { - console.debug(`Value for '${key}' on BlueOS is the same as the local one. No need to update.`) + logger.debug(`Value for '${key}' on BlueOS is the same as the local one. No need to update.`) finishedInitialFetch.value = true return } @@ -221,21 +224,21 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem // Do nothing if both values are undefined if (currentValue.value === undefined && valueOnBlueOS === undefined) { - console.debug(`Both local and remote values for '${key}' are undefined. No need to update.`) + logger.debug(`Both local and remote values for '${key}' are undefined. No need to update.`) finishedInitialFetch.value = true return } else if (currentValue.value === undefined) { // If only the local value is undefined, use the value from BlueOS - console.debug(`Local value for '${key}' is undefined. Using value from BlueOS.`) + logger.debug(`Local value for '${key}' is undefined. Using value from BlueOS.`) useBlueOsValue = true } else if (valueOnBlueOS === undefined) { // If only the remote value is undefined, use the value from local storage - console.debug(`Remote value for '${key}' is undefined. Using value from local storage.`) + logger.debug(`Remote value for '${key}' is undefined. Using value from local storage.`) useBlueOsValue = false } const msg = `Key: ${key} // Epochs: Remote: ${remoteEpoch}, Local: ${localEpoch} // Use BlueOS value: ${useBlueOsValue}` - console.debug(msg) + logger.debug(msg) // If the epochs are equal and the values are different, we use the value from BlueOS if (remoteEpoch === localEpoch && !isEqual(currentValue.value, valueOnBlueOS)) { @@ -284,13 +287,13 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem openSnackbar({ message, duration: 3000, variant: 'success' }) } - console.info(`Success syncing '${key}' with BlueOS.`) + logger.info(`Success syncing '${key}' with BlueOS.`) finishedInitialFetch.value = true } catch (initialSyncError) { // If the initial sync fails because there's no value for the key on BlueOS, we can just use the current value if ((initialSyncError as Error).name === NoPathInBlueOsErrorName) { - console.debug(`No value for '${key}' on BlueOS. Using current value. Will push it to BlueOS.`) + logger.debug(`No value for '${key}' on BlueOS. Using current value. Will push it to BlueOS.`) try { // Set initial epoch and push both value and epoch const localEpochOrNow = getLocalEpoch(username, currentVehicleId, key) ?? Date.now() @@ -299,8 +302,8 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem finishedInitialFetch.value = true return } catch (fetchError) { - console.error(`Not able to push current value of '${key}' to BlueOS. ${fetchError}`) - console.error(`Failed syncing '${key}' with BlueOS. Will keep trying.`) + logger.error(`Not able to push current value of '${key}' to BlueOS. ${fetchError}`) + logger.error(`Failed syncing '${key}' with BlueOS. Will keep trying.`) // If we can't update the value on BlueOS, try again in 10 seconds initialSyncTimeout = setTimeout(tryToDoInitialSync, 10000) @@ -311,7 +314,7 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem // If the initial sync fails because we can't connect to BlueOS, try again in 10 seconds initialSyncTimeout = setTimeout(tryToDoInitialSync, 10000) - console.error(`Failed syncing '${key}' with BlueOS. Will keep trying. Error: ${initialSyncError}`) + logger.error(`Failed syncing '${key}' with BlueOS. Will keep trying. Error: ${initialSyncError}`) } } @@ -319,7 +322,7 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem const devStore = useDevelopmentStore() if (!devStore.enableBlueOsSettingsSync) return - console.debug(`Started syncing '${key}' with BlueOS.`) + logger.debug(`Started syncing '${key}' with BlueOS.`) // Start initial sync routine tryToDoInitialSync() @@ -331,16 +334,16 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem let valueUpdateMethodTimeout: ReturnType | undefined = undefined const maybeUpdateValueOnBlueOs = async (newValue: T, oldValue: T, epoch: number): Promise => { - console.debug(`Detected changes in the local value for key '${key}'. Updating BlueOS.`) + logger.debug(`Detected changes in the local value for key '${key}'. Updating BlueOS.`) // Don't update the value on BlueOS if we haven't finished the initial fetch, so we don't overwrite the value there without user consent if (!finishedInitialFetch.value) { - console.debug(`Value of '${key}' changed, but we haven't finished the initial fetch. Not updating BlueOS.`) + logger.debug(`Value of '${key}' changed, but we haven't finished the initial fetch. Not updating BlueOS.`) return } if (isEqual(newValue, oldValue)) { - console.debug(`Old value for key ${key} is equal to the new one. Aborting update on BlueOS.`) + logger.debug(`Old value for key ${key} is equal to the new one. Aborting update on BlueOS.`) return } @@ -357,7 +360,7 @@ export function useBlueOsStorage(key: string, defaultValue: MaybeRef): Rem const lastConnectedVehicleId = getLastConnectedVehicleId() if (lastConnectedUser === undefined || lastConnectedVehicleId === undefined) { - console.error('Not able to update epoch locally. Last connected user or vehicle ID not found.') + logger.error('Not able to update epoch locally. Last connected user or vehicle ID not found.') return } From eed892aedcc9a95abcf7fc7be58aa53b268c33ac Mon Sep 17 00:00:00 2001 From: Rafael Araujo Lehmkuhl Date: Thu, 13 Feb 2025 16:57:20 -0300 Subject: [PATCH 4/4] blueos: Throw if JSON response is not available --- src/libs/blueos.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/blueos.ts b/src/libs/blueos.ts index 3a71680f9..712f4bd59 100644 --- a/src/libs/blueos.ts +++ b/src/libs/blueos.ts @@ -32,7 +32,11 @@ export const getBagOfHoldingFromVehicle = async ( ): Promise | any> => { try { const options = { timeout: defaultTimeout, retry: 0 } - return await ky.get(`http://${vehicleAddress}/bag/v1.0/get/${bagPath}`, options).json() + const res = await ky.get(`http://${vehicleAddress}/bag/v1.0/get/${bagPath}`, options) + if (res === undefined || !res.ok) { + throw new Error(`Could not get bag of holdings for ${bagPath}. ${res?.statusText}`) + } + return await res.json() } catch (error) { const errorBody = await (error as HTTPError).response.json() if (errorBody.detail === 'Invalid path') {