From 3725beca86a7f330e74ebebc813448603c3e19d0 Mon Sep 17 00:00:00 2001 From: Jiri Zbytovsky Date: Thu, 5 Dec 2024 15:46:50 +0100 Subject: [PATCH 1/2] feat(suite-desktop): add getSystemInformation ipc api --- .../src/__tests__/api.test.ts | 8 ++++++ packages/suite-desktop-api/src/api.ts | 4 +++ packages/suite-desktop-api/src/factory.ts | 2 ++ packages/suite-desktop-api/src/messages.ts | 6 +++++ .../suite-desktop-core/src/modules/index.ts | 2 ++ .../src/modules/system-information.ts | 25 +++++++++++++++++++ 6 files changed, 47 insertions(+) create mode 100644 packages/suite-desktop-core/src/modules/system-information.ts diff --git a/packages/suite-desktop-api/src/__tests__/api.test.ts b/packages/suite-desktop-api/src/__tests__/api.test.ts index c41cb66123d..5327d2269a5 100644 --- a/packages/suite-desktop-api/src/__tests__/api.test.ts +++ b/packages/suite-desktop-api/src/__tests__/api.test.ts @@ -304,5 +304,13 @@ describe('DesktopApi', () => { // @ts-expect-error param expected api.loadModules(); }); + + it('DesktopApi.getSystemInformation', async () => { + const spy = jest + .spyOn(ipcRenderer, 'invoke') + .mockImplementation(() => Promise.resolve()); + await api.getSystemInformation(); + expect(spy).toHaveBeenCalledWith('system/get-system-information'); + }); }); }); diff --git a/packages/suite-desktop-api/src/api.ts b/packages/suite-desktop-api/src/api.ts index 4838d294fec..6c810a72b39 100644 --- a/packages/suite-desktop-api/src/api.ts +++ b/packages/suite-desktop-api/src/api.ts @@ -18,6 +18,7 @@ import { TraySettings, ConnectPopupResponse, ConnectPopupCall, + GetSystemInformationResponse, } from './messages'; // Event messages from renderer to main process @@ -107,6 +108,7 @@ export interface InvokeChannels { 'connect-popup/enabled': () => boolean; 'connect-popup/ready': () => void; 'connect-popup/response': (response: ConnectPopupResponse) => void; + 'system/get-system-information': () => InvokeResult; } type DesktopApiListener = ListenerMethod; @@ -173,4 +175,6 @@ export interface DesktopApi { connectPopupEnabled: DesktopApiInvoke<'connect-popup/enabled'>; connectPopupReady: DesktopApiInvoke<'connect-popup/ready'>; connectPopupResponse: DesktopApiInvoke<'connect-popup/response'>; + //system + getSystemInformation: DesktopApiInvoke<'system/get-system-information'>; } diff --git a/packages/suite-desktop-api/src/factory.ts b/packages/suite-desktop-api/src/factory.ts index c3f53e49602..17269e44fa1 100644 --- a/packages/suite-desktop-api/src/factory.ts +++ b/packages/suite-desktop-api/src/factory.ts @@ -182,5 +182,7 @@ export const factory = >( connectPopupEnabled: () => ipcRenderer.invoke('connect-popup/enabled'), connectPopupReady: () => ipcRenderer.invoke('connect-popup/ready'), connectPopupResponse: response => ipcRenderer.invoke('connect-popup/response', response), + + getSystemInformation: () => ipcRenderer.invoke('system/get-system-information'), }; }; diff --git a/packages/suite-desktop-api/src/messages.ts b/packages/suite-desktop-api/src/messages.ts index 9b727689126..6eeb54c9562 100644 --- a/packages/suite-desktop-api/src/messages.ts +++ b/packages/suite-desktop-api/src/messages.ts @@ -140,3 +140,9 @@ export type ConnectPopupResponse = { success: boolean; payload: any; }; + +export type GetSystemInformationResponse = { + osVersion: string; + osName: string; + osArchitecture: string; +}; diff --git a/packages/suite-desktop-core/src/modules/index.ts b/packages/suite-desktop-core/src/modules/index.ts index 078cd9bc6eb..c643741dc19 100644 --- a/packages/suite-desktop-core/src/modules/index.ts +++ b/packages/suite-desktop-core/src/modules/index.ts @@ -34,6 +34,7 @@ import * as fileProtocol from './file-protocol'; import * as autoStart from './auto-start'; import * as tray from './tray'; import * as bridge from './bridge'; +import * as systemInformation from './system-information'; import { MainWindowProxy } from '../libs/main-window-proxy'; // General modules (both dev & prod) @@ -63,6 +64,7 @@ const MODULES: Module[] = [ coinjoin, autoStart, bridge, + systemInformation, // Modules used only in dev/prod mode ...(isDevEnv ? [] : [csp, fileProtocol]), ]; diff --git a/packages/suite-desktop-core/src/modules/system-information.ts b/packages/suite-desktop-core/src/modules/system-information.ts new file mode 100644 index 00000000000..d2830479066 --- /dev/null +++ b/packages/suite-desktop-core/src/modules/system-information.ts @@ -0,0 +1,25 @@ +import os from 'os'; + +import { validateIpcMessage } from '@trezor/ipc-proxy'; + +import { ipcMain } from '../typed-electron'; + +import type { ModuleInit } from './index'; + +export const SERVICE_NAME = 'system-information'; + +export const init: ModuleInit = () => { + ipcMain.handle('system/get-system-information', ipcEvent => { + validateIpcMessage(ipcEvent); + + try { + const osVersion = os.platform(); + const osName = os.release(); + const osArchitecture = os.arch(); + + return { success: true, payload: { osVersion, osName, osArchitecture } }; + } catch (error) { + return { success: false, error }; + } + }); +}; From 60daea73c093ab17c2a8d2612c5120b17d78cbd7 Mon Sep 17 00:00:00 2001 From: Jiri Zbytovsky Date: Thu, 5 Dec 2024 15:47:34 +0100 Subject: [PATCH 2/2] feat(suite): add optional system information to analytics --- .../middlewares/suite/analyticsMiddleware.ts | 9 +- .../src/middlewares/suite/sentryMiddleware.ts | 5 +- packages/suite/src/utils/suite/analytics.ts | 92 ++++++++++++------- 3 files changed, 68 insertions(+), 38 deletions(-) diff --git a/packages/suite/src/middlewares/suite/analyticsMiddleware.ts b/packages/suite/src/middlewares/suite/analyticsMiddleware.ts index ba751a7019f..c2296c3741c 100644 --- a/packages/suite/src/middlewares/suite/analyticsMiddleware.ts +++ b/packages/suite/src/middlewares/suite/analyticsMiddleware.ts @@ -95,9 +95,12 @@ const analyticsMiddleware = switch (action.type) { case SUITE.READY: // reporting can start when analytics is properly initialized and enabled - analytics.report({ - type: EventType.SuiteReady, - payload: getSuiteReadyPayload(state), + // it is done async because ipcMain is queried for system info, if available + getSuiteReadyPayload(state).then(payload => { + analytics.report({ + type: EventType.SuiteReady, + payload, + }); }); break; case TRANSPORT.START: diff --git a/packages/suite/src/middlewares/suite/sentryMiddleware.ts b/packages/suite/src/middlewares/suite/sentryMiddleware.ts index 6d761268aaf..1de721d47e0 100644 --- a/packages/suite/src/middlewares/suite/sentryMiddleware.ts +++ b/packages/suite/src/middlewares/suite/sentryMiddleware.ts @@ -97,7 +97,10 @@ const sentryMiddleware = switch (action.type) { case SUITE.READY: - setSentryContext('suite-ready', getSuiteReadyPayload(state)); + // done async because ipcMain is queried for system info, if available + getSuiteReadyPayload(state).then(payload => + setSentryContext('suite-ready', payload), + ); break; case DEVICE.CONNECT: { const { features, mode } = action.payload.device; diff --git a/packages/suite/src/utils/suite/analytics.ts b/packages/suite/src/utils/suite/analytics.ts index 07f633d4e2a..e51863e9e7d 100644 --- a/packages/suite/src/utils/suite/analytics.ts +++ b/packages/suite/src/utils/suite/analytics.ts @@ -9,10 +9,12 @@ import { getWindowWidth, getWindowHeight, getPlatformLanguages, + isDesktop, } from '@trezor/env-utils'; import { getCustomBackends } from '@suite-common/wallet-utils'; import { UNIT_ABBREVIATIONS } from '@suite-common/suite-constants'; -import type { UpdateInfo } from '@trezor/suite-desktop-api'; +import { desktopApi, UpdateInfo } from '@trezor/suite-desktop-api'; +import { GetSystemInformationResponse } from '@trezor/suite-desktop-api/src/messages'; import { selectRememberedStandardWalletsCount, selectRememberedHiddenWalletsCount, @@ -23,6 +25,17 @@ import { AppState } from 'src/types/suite'; import { getIsTorEnabled } from './tor'; +const getOptionalSystemInformation = async (): Promise => { + if (!isDesktop()) return null; + try { + const response = await desktopApi.getSystemInformation(); + + return response.success ? response.payload : null; + } catch { + return null; + } +}; + // redact transaction id from account transaction anchor export const redactTransactionIdFromAnchor = (anchor?: string) => { if (!anchor) { @@ -35,39 +48,50 @@ export const redactTransactionIdFromAnchor = (anchor?: string) => { // 1. replace coinjoin by taproot export const redactRouterUrl = (url: string) => url.replace(/coinjoin/g, 'taproot'); -export const getSuiteReadyPayload = (state: AppState) => ({ - language: state.suite.settings.language, - enabledNetworks: state.wallet.settings.enabledNetworks, - customBackends: getCustomBackends(state.wallet.blockchain) - .map(({ coin }) => coin) - .filter(coin => state.wallet.settings.enabledNetworks.includes(coin)), - localCurrency: state.wallet.settings.localCurrency, - bitcoinUnit: UNIT_ABBREVIATIONS[state.wallet.settings.bitcoinAmountUnit], - discreetMode: state.wallet.settings.discreetMode, - screenWidth: getScreenWidth(), - screenHeight: getScreenHeight(), - platformLanguages: getPlatformLanguages().join(','), - tor: getIsTorEnabled(state.suite.torStatus), - // todo: duplicated with suite/src/utils/suite/logUtils - labeling: state.metadata.enabled - ? state.metadata.providers.find(p => p.clientId === state.metadata.selectedProvider.labels) - ?.type || 'missing-provider' - : '', - rememberedStandardWallets: selectRememberedStandardWalletsCount(state), - rememberedHiddenWallets: selectRememberedHiddenWalletsCount(state), - theme: state.suite.settings.theme.variant, - suiteVersion: process.env.VERSION || '', - earlyAccessProgram: state.desktopUpdate.allowPrerelease, - experimentalFeatures: state.suite.settings.experimental, - browserName: getBrowserName(), - browserVersion: getBrowserVersion(), - osName: getOsName(), - osVersion: getOsVersion(), - windowWidth: getWindowWidth(), - windowHeight: getWindowHeight(), - autodetectLanguage: state.suite.settings.autodetect.language, - autodetectTheme: state.suite.settings.autodetect.theme, -}); +export const getSuiteReadyPayload = async (state: AppState) => { + const systemInformation = await getOptionalSystemInformation(); + + return { + language: state.suite.settings.language, + enabledNetworks: state.wallet.settings.enabledNetworks, + customBackends: getCustomBackends(state.wallet.blockchain) + .map(({ coin }) => coin) + .filter(coin => state.wallet.settings.enabledNetworks.includes(coin)), + localCurrency: state.wallet.settings.localCurrency, + bitcoinUnit: UNIT_ABBREVIATIONS[state.wallet.settings.bitcoinAmountUnit], + discreetMode: state.wallet.settings.discreetMode, + screenWidth: getScreenWidth(), + screenHeight: getScreenHeight(), + platformLanguages: getPlatformLanguages().join(','), + tor: getIsTorEnabled(state.suite.torStatus), + // todo: duplicated with suite/src/utils/suite/logUtils + labeling: state.metadata.enabled + ? state.metadata.providers.find( + p => p.clientId === state.metadata.selectedProvider.labels, + )?.type || 'missing-provider' + : '', + rememberedStandardWallets: selectRememberedStandardWalletsCount(state), + rememberedHiddenWallets: selectRememberedHiddenWalletsCount(state), + theme: state.suite.settings.theme.variant, + suiteVersion: process.env.VERSION || '', + earlyAccessProgram: state.desktopUpdate.allowPrerelease, + experimentalFeatures: state.suite.settings.experimental, + browserName: getBrowserName(), + browserVersion: getBrowserVersion(), + osName: getOsName(), + // version from UA parser, which includes only the most basic info as it runs in renderer process + osVersion: getOsVersion(), + // detailed info obtained in main process, if available + desktopOsVersion: systemInformation?.osVersion, + desktopOsName: systemInformation?.osName, + desktopOsArchitecture: systemInformation?.osArchitecture, + + windowWidth: getWindowWidth(), + windowHeight: getWindowHeight(), + autodetectLanguage: state.suite.settings.autodetect.language, + autodetectTheme: state.suite.settings.autodetect.theme, + }; +}; export const getAppUpdatePayload = ( status: AppUpdateEvent['status'],