diff --git a/gui/package.json b/gui/package.json index 8bc08e850..14722794b 100644 --- a/gui/package.json +++ b/gui/package.json @@ -20,8 +20,8 @@ "@tauri-apps/plugin-dialog": "^2.0.0", "@tauri-apps/plugin-fs": "2.4.1", "@tauri-apps/plugin-http": "^2.5.0", - "@tauri-apps/plugin-opener": "^2.4.0", "@tauri-apps/plugin-log": "~2", + "@tauri-apps/plugin-opener": "^2.4.0", "@tauri-apps/plugin-os": "^2.0.0", "@tauri-apps/plugin-shell": "^2.3.0", "@tauri-apps/plugin-store": "^2.4.1", @@ -52,6 +52,7 @@ "ts-pattern": "^5.4.0", "typescript": "^5.6.3", "use-double-tap": "^1.3.6", + "uuid": "^13.0.0", "yup": "^1.4.0" }, "scripts": { diff --git a/gui/src/components/onboarding/pages/body-proportions/ScaledProportions.tsx b/gui/src/components/onboarding/pages/body-proportions/ScaledProportions.tsx index 751a73ff1..bd9bf33fc 100644 --- a/gui/src/components/onboarding/pages/body-proportions/ScaledProportions.tsx +++ b/gui/src/components/onboarding/pages/body-proportions/ScaledProportions.tsx @@ -326,6 +326,14 @@ export function ScaledProportionsPage() { } ); + useEffect(() => { + if (lastUsed !== null) { + Sentry.metrics.count('scaled_proportions', 1, { + attributes: { calibration: lastUsed }, + }); + } + }, [lastUsed]); + useEffect(() => { sendRPCPacket( RpcMessage.SkeletonConfigRequest, @@ -334,11 +342,6 @@ export function ScaledProportionsPage() { return () => { cancel(); - if (lastUsed !== null) { - Sentry.metrics.count('scaled_proportions', 1, { - attributes: { calibration: lastUsed }, - }); - } }; }, []); diff --git a/gui/src/components/providers/ConfigContext.tsx b/gui/src/components/providers/ConfigContext.tsx index 0e8a690a0..88d3eb639 100644 --- a/gui/src/components/providers/ConfigContext.tsx +++ b/gui/src/components/providers/ConfigContext.tsx @@ -1,30 +1,16 @@ -import { ReactNode, useContext, useLayoutEffect } from 'react'; +import { ReactNode } from 'react'; import { ConfigContextC, loadConfig, useConfigProvider } from '@/hooks/config'; -import { DEFAULT_LOCALE, LangContext } from '@/i18n/config'; import { getSentryOrCompute } from '@/utils/sentry'; const config = await loadConfig(); if (config?.errorTracking !== undefined) { // load sentry ASAP to catch early errors - getSentryOrCompute(config.errorTracking ?? false); + getSentryOrCompute(config.errorTracking ?? false, config.uuid); } export function ConfigContextProvider({ children }: { children: ReactNode }) { const context = useConfigProvider(config); - const { changeLocales } = useContext(LangContext); - - useLayoutEffect(() => { - changeLocales([config?.lang || DEFAULT_LOCALE]); - }, []); - - useLayoutEffect(() => { - if (config?.errorTracking !== undefined) { - // Alows for sentry to refresh if user change the setting once the gui - // is initialized - getSentryOrCompute(config.errorTracking ?? false); - } - }, [config?.errorTracking]); return ( diff --git a/gui/src/components/settings/pages/HomeScreenSettings.tsx b/gui/src/components/settings/pages/HomeScreenSettings.tsx index c2df687d5..12ecb44ad 100644 --- a/gui/src/components/settings/pages/HomeScreenSettings.tsx +++ b/gui/src/components/settings/pages/HomeScreenSettings.tsx @@ -55,16 +55,10 @@ export function TrackingChecklistSettings({ // that prevent sending a packet for steps that didnt change if (!value && !ignoredSteps.includes(stepId)) { ignoreStep(stepId, true); - Sentry.metrics.count('mute_checklist_step', 1, { - attributes: { step: TrackingChecklistStepId[stepId] }, - }); } if (value && ignoredSteps.includes(stepId)) { ignoreStep(stepId, false); - Sentry.metrics.count('unmute_checklist_step', 1, { - attributes: { step: TrackingChecklistStepId[stepId] }, - }); } } }; diff --git a/gui/src/hooks/app.ts b/gui/src/hooks/app.ts index 60a8fb8a0..a1f32f313 100644 --- a/gui/src/hooks/app.ts +++ b/gui/src/hooks/app.ts @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from 'react'; +import { createContext, useContext, useEffect, useLayoutEffect, useState } from 'react'; import { DataFeedMessage, DataFeedUpdateT, @@ -12,8 +12,9 @@ import { useBonesDataFeedConfig, useDataFeedConfig } from './datafeed-config'; import { useWebsocketAPI } from './websocket-api'; import { useAtomValue, useSetAtom } from 'jotai'; import { bonesAtom, datafeedAtom, devicesAtom } from '@/store/app-store'; -import { updateSentryContext } from '@/utils/sentry'; +import { getSentryOrCompute, updateSentryContext } from '@/utils/sentry'; import { fetchCurrentFirmwareRelease, FirmwareRelease } from './firmware-update'; +import { DEFAULT_LOCALE, LangContext } from '@/i18n/config'; export interface AppContext { currentFirmwareRelease: FirmwareRelease | null; @@ -22,6 +23,7 @@ export interface AppContext { export function useProvideAppContext(): AppContext { const { useRPCPacket, sendDataFeedPacket, useDataFeedPacket, isConnected } = useWebsocketAPI(); + const { changeLocales } = useContext(LangContext); const { config } = useConfig(); const { dataFeedConfig } = useDataFeedConfig(); const bonesDataFeedConfig = useBonesDataFeedConfig(); @@ -58,14 +60,30 @@ export function useProvideAppContext(): AppContext { }); useEffect(() => { + if (!config) return; + const interval = setInterval(() => { - fetchCurrentFirmwareRelease().then((res) => setCurrentFirmwareRelease(res)); + fetchCurrentFirmwareRelease(config.uuid).then(setCurrentFirmwareRelease); }, 1000); return () => { clearInterval(interval); }; + }, [config?.uuid]); + + useLayoutEffect(() => { + changeLocales([config?.lang || DEFAULT_LOCALE]); }, []); + useLayoutEffect(() => { + if (!config) return; + if (config.errorTracking !== undefined) { + console.log('change'); + // Alows for sentry to refresh if user change the setting once the gui + // is initialized + getSentryOrCompute(config.errorTracking ?? false, config.uuid); + } + }, [config]); + return { currentFirmwareRelease, }; diff --git a/gui/src/hooks/config.ts b/gui/src/hooks/config.ts index b2727b217..e1f64b4e7 100644 --- a/gui/src/hooks/config.ts +++ b/gui/src/hooks/config.ts @@ -9,6 +9,7 @@ import { load, Store } from '@tauri-apps/plugin-store'; import { useIsTauri } from './breakpoint'; import { waitUntil } from '@/utils/a11y'; import { isTauri } from '@tauri-apps/api/core'; +import { v4 as uuidv4 } from 'uuid'; export interface WindowConfig { width: number; @@ -26,6 +27,7 @@ export enum AssignMode { } export interface Config { + uuid: string; debug: boolean; lang: string; doneOnboarding: boolean; @@ -57,6 +59,7 @@ export interface ConfigContext { } export const defaultConfig: Config = { + uuid: uuidv4(), lang: 'en', debug: false, doneOnboarding: false, @@ -117,13 +120,16 @@ export const loadConfig = async () => { if (!json) throw new Error('Config has ceased existing for some reason'); const loadedConfig = fallbackToDefaults(JSON.parse(json)); - // set(loadedConfig); - // setLoading(false); + + if (!loadedConfig.uuid) { + // Make sure the config always has a uuid + loadedConfig.uuid = uuidv4(); + await store.set('config.json', JSON.stringify(loadedConfig)); + } + return loadedConfig; } catch (e) { error(e); - // setConfig(defaultConfig); - // setLoading(false); return null; } }; diff --git a/gui/src/hooks/crypto.ts b/gui/src/hooks/crypto.ts index 6b857283c..c3ace622a 100644 --- a/gui/src/hooks/crypto.ts +++ b/gui/src/hooks/crypto.ts @@ -1,5 +1,5 @@ // implemetation of https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function -export function hash(str: string) { +export function normalizedHash(str: string) { let hash = 2166136261; for (let i = 0; i < str.length; i++) { hash ^= str.charCodeAt(i); diff --git a/gui/src/hooks/firmware-update.ts b/gui/src/hooks/firmware-update.ts index 35107a17d..17288f3e0 100644 --- a/gui/src/hooks/firmware-update.ts +++ b/gui/src/hooks/firmware-update.ts @@ -2,8 +2,7 @@ import { BoardType, DeviceDataT } from 'solarxr-protocol'; import { fetch as tauriFetch } from '@tauri-apps/plugin-http'; import { cacheWrap } from './cache'; import semver from 'semver'; -import { hash } from './crypto'; -import { getUserID } from './user'; +import { normalizedHash } from './crypto'; export interface FirmwareRelease { name: string; @@ -24,7 +23,7 @@ const todaysRange = (deployData: [number, Date][]): number => { return maxRange; }; -const checkUserCanUpdate = async (url: string, fwVersion: string) => { +const checkUserCanUpdate = async (uuid: string, url: string, fwVersion: string) => { const deployDataJson = JSON.parse( (await cacheWrap( `firmware-${fwVersion}-deploy`, @@ -56,12 +55,13 @@ const checkUserCanUpdate = async (url: string, fwVersion: string) => { const todayUpdateRange = todaysRange(deployData); if (!todayUpdateRange) return false; - const uniqueUserKey = await getUserID(); // Make it so the hash change every version. Prevent the same user from getting the same delay - return hash(`${uniqueUserKey}-${fwVersion}`) <= todayUpdateRange; + return normalizedHash(`${uuid}-${fwVersion}`) <= todayUpdateRange; }; -export async function fetchCurrentFirmwareRelease(): Promise { +export async function fetchCurrentFirmwareRelease( + uuid: string +): Promise { const releases: any[] | null = JSON.parse( (await cacheWrap( 'firmware-releases', @@ -93,6 +93,7 @@ export async function fetchCurrentFirmwareRelease(): Promise void) { req.bodyParts = parts; sendRPCPacket(RpcMessage.ResetRequest, req); - Sentry.metrics.count('reset_click', 1, { attributes: options }); + Sentry.metrics.count('reset_click', 1, { + attributes: { + resetType: ResetType[options.type], + group: options.type === ResetType.Mounting ? options.group : undefined, + }, + }); }; const onResetFinished = () => { diff --git a/gui/src/hooks/tracking-checklist.ts b/gui/src/hooks/tracking-checklist.ts index b63cae9c9..94f24c2a6 100644 --- a/gui/src/hooks/tracking-checklist.ts +++ b/gui/src/hooks/tracking-checklist.ts @@ -10,6 +10,7 @@ import { } from 'solarxr-protocol'; import { useWebsocketAPI } from './websocket-api'; import { createContext, useContext, useEffect, useMemo, useState } from 'react'; +import * as Sentry from '@sentry/react'; export const trackingchecklistIdtoLabel: Record = { [TrackingChecklistStepId.UNKNOWN]: '', @@ -165,6 +166,9 @@ export function provideTrackingChecklist() { res.stepId = step; res.ignore = ignore; sendRPCPacket(RpcMessage.IgnoreTrackingChecklistStepRequest, res); + Sentry.metrics.count(ignore ? 'mute_checklist_step' : 'unmute_checklist_step', 1, { + attributes: { step: TrackingChecklistStepId[step] }, + }); }; return { diff --git a/gui/src/hooks/user.ts b/gui/src/hooks/user.ts deleted file mode 100644 index e590bbfb3..000000000 --- a/gui/src/hooks/user.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { locale, hostname, platform, version } from '@tauri-apps/plugin-os'; -import { hash } from './crypto'; - -export async function getUserID() { - // FIXME: This does not support android. It currently return the same id for all android users - - return hash(`${await hostname()}-${await locale()}-${platform()}-${version()}`); -} diff --git a/gui/src/utils/sentry.ts b/gui/src/utils/sentry.ts index 4bf9f8e6a..087d3f1e0 100644 --- a/gui/src/utils/sentry.ts +++ b/gui/src/utils/sentry.ts @@ -8,9 +8,9 @@ import { useNavigationType, } from 'react-router-dom'; import { DeviceDataT } from 'solarxr-protocol'; -import { getUserID } from '@/hooks/user'; -export function getSentryOrCompute(enabled = false) { +export function getSentryOrCompute(enabled = false, uuid: string) { + Sentry.setUser({ id: uuid }); // if sentry is already initialized - SKIP if (enabled && Sentry.isInitialized()) return; @@ -63,10 +63,6 @@ export function getSentryOrCompute(enabled = false) { log('Initialized the Sentry client'); } - getUserID().then((id) => { - Sentry.setUser({ id }); - }); - return newClient; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fe8ff8c6..d5c980358 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -161,6 +161,9 @@ importers: use-double-tap: specifier: ^1.3.6 version: 1.3.6(react@18.3.1) + uuid: + specifier: ^13.0.0 + version: 13.0.0 yup: specifier: ^1.4.0 version: 1.4.0 @@ -4534,6 +4537,10 @@ packages: resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} engines: {node: '>= 4'} + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -9720,6 +9727,8 @@ snapshots: utility-types@3.11.0: {} + uuid@13.0.0: {} + uuid@9.0.1: {} vfile-message@4.0.2: