diff --git a/packages/analytics-uploader/src/analytics.ts b/packages/analytics-uploader/src/analytics.ts index 9343eceeff..31c7499618 100644 --- a/packages/analytics-uploader/src/analytics.ts +++ b/packages/analytics-uploader/src/analytics.ts @@ -13,6 +13,7 @@ export interface Analytics { disable: () => void; isEnabled: () => boolean; setUrl: (url?: string) => void; + setLoggerEnabled: (enabled: boolean) => void; report: (data: T, config?: ReportConfig) => void; } @@ -31,6 +32,7 @@ export class QueuedAnalytics implements Analytics { private isDev?: boolean; private environment?: InitOptions['environment']; private url?: string; + private loggerEnabled = false; private callbacks?: InitOptions['callbacks']; @@ -49,6 +51,7 @@ export class QueuedAnalytics implements Analytics { this.isDev = options.isDev; this.environment = options.environment; this.url = options.url ?? getUrl(this.app, options.isDev, options.environment); + this.loggerEnabled = options.loggerEnabled ?? false; this.callbacks = options.callbacks; // Call flushQueue only if 'enabled' is explicitly set (true or false). @@ -92,6 +95,10 @@ export class QueuedAnalytics implements Analytics { this.url = url ?? getUrl(this.app, this.isDev ?? false, this.environment); }; + public setLoggerEnabled = (enabled: boolean) => { + this.loggerEnabled = enabled; + }; + public report = (data: T, config?: ReportConfig) => { // Add a timestamp to each event to track its actual occurrence time, considering possible queuing delays. if (!data.timestamp) { @@ -146,6 +153,7 @@ export class QueuedAnalytics implements Analytics { keepalive: true, }, retry: true, + loggerEnabled: this.loggerEnabled, }); }; } diff --git a/packages/analytics-uploader/src/types.ts b/packages/analytics-uploader/src/types.ts index 45fc410768..7f0ce96162 100644 --- a/packages/analytics-uploader/src/types.ts +++ b/packages/analytics-uploader/src/types.ts @@ -9,6 +9,7 @@ export type InitOptions = { instanceId?: string; environment?: Environment; url?: string; + loggerEnabled?: boolean; isDev: boolean; commitId: string; callbacks?: { diff --git a/packages/analytics-uploader/src/utils.ts b/packages/analytics-uploader/src/utils.ts index 422ee333bf..0b11f068ba 100644 --- a/packages/analytics-uploader/src/utils.ts +++ b/packages/analytics-uploader/src/utils.ts @@ -78,9 +78,21 @@ interface ReportEventProps { url: string; options: RequestInit; retry: boolean; + loggerEnabled?: boolean; } -export const reportEvent = async ({ type, url, options, retry }: ReportEventProps) => { +export const reportEvent = async ({ + type, + url, + options, + retry, + loggerEnabled, +}: ReportEventProps) => { + if (loggerEnabled) { + // eslint-disable-next-line no-console + console.log(`[Analytics] '${type}': ${url}`); + } + try { const response = await fetch(url, options); @@ -91,7 +103,10 @@ export const reportEvent = async ({ type, url, options, retry }: ReportEventProp reportEventError(type, retry, err); if (retry) { - setTimeout(() => reportEvent({ type, url, options, retry: false }), 1000); + setTimeout( + () => reportEvent({ type, url, options, retry: false, loggerEnabled }), + 1000, + ); } } }; diff --git a/packages/suite/src/actions/suite/analyticsActions.ts b/packages/suite/src/actions/suite/analyticsActions.ts index 85c82464e2..b63c7145ca 100644 --- a/packages/suite/src/actions/suite/analyticsActions.ts +++ b/packages/suite/src/actions/suite/analyticsActions.ts @@ -11,6 +11,7 @@ import { selectHasUserAllowedTracking, selectIsAnalyticsConfirmed, selectIsAnalyticsEnabled, + selectLoggerEnabled, } from '@suite-common/analytics-redux'; import { ExtraDependencies } from '@suite-common/redux-utils'; import { type InitOptions, getTrackingRandomId } from '@trezor/analytics-uploader'; @@ -65,6 +66,7 @@ export const init = () => (dispatch: Dispatch, getState: GetState, extra: ExtraD const isAnalyticsEnabled = selectIsAnalyticsEnabled(getState()); const isAnalyticsConfirmed = selectIsAnalyticsConfirmed(getState()); const customAnalyticsUrl = selectCustomAnalyticsUrl(getState()); + const loggerEnabled = selectLoggerEnabled(getState()); const getOptions: (props: SendReportProps) => InitOptions = ({ sendReport, }: SendReportProps) => ({ @@ -72,6 +74,7 @@ export const init = () => (dispatch: Dispatch, getState: GetState, extra: ExtraD sessionId, environment: getEnvironment(), url: customAnalyticsUrl, + loggerEnabled, commitId: getCommitHash(), isDev: !isCodesignBuild(), callbacks: { diff --git a/packages/suite/src/actions/suite/storageActions.ts b/packages/suite/src/actions/suite/storageActions.ts index 497b8ccfdd..3034a80da4 100644 --- a/packages/suite/src/actions/suite/storageActions.ts +++ b/packages/suite/src/actions/suite/storageActions.ts @@ -429,6 +429,7 @@ export const saveAnalytics = () => (_dispatch: Dispatch, getState: GetState) => instanceId: analytics.instanceId, confirmed: analytics.confirmed, customAnalyticsUrl: analytics.customAnalyticsUrl, + loggerEnabled: analytics.loggerEnabled, }, 'suite', true, diff --git a/packages/suite/src/middlewares/wallet/storageMiddleware.ts b/packages/suite/src/middlewares/wallet/storageMiddleware.ts index 8181a05570..4f51306cdf 100644 --- a/packages/suite/src/middlewares/wallet/storageMiddleware.ts +++ b/packages/suite/src/middlewares/wallet/storageMiddleware.ts @@ -178,6 +178,7 @@ const storageMiddleware = (api: MiddlewareAPI) => { analyticsActions.enableAnalytics, analyticsActions.disableAnalytics, analyticsActions.setCustomAnalyticsUrl, + analyticsActions.setLoggerEnabled, )(action) ) { api.dispatch(storageActions.saveAnalytics()); diff --git a/packages/suite/src/views/settings/SettingsDebug/AnalyticsUrl.tsx b/packages/suite/src/views/settings/SettingsDebug/AnalyticsUrl.tsx index d11605deab..8b35894d97 100644 --- a/packages/suite/src/views/settings/SettingsDebug/AnalyticsUrl.tsx +++ b/packages/suite/src/views/settings/SettingsDebug/AnalyticsUrl.tsx @@ -1,7 +1,11 @@ import { useState } from 'react'; -import { analyticsActions, selectCustomAnalyticsUrl } from '@suite-common/analytics-redux'; -import { Button, Code, Column, Input, Text } from '@trezor/components'; +import { + analyticsActions, + selectCustomAnalyticsUrl, + selectLoggerEnabled, +} from '@suite-common/analytics-redux'; +import { Button, Code, Column, Input, Switch, Text } from '@trezor/components'; import { ActionColumn, SectionItem, TextColumn } from 'src/components/suite'; import { useDispatch, useSelector } from 'src/hooks/suite'; @@ -9,6 +13,7 @@ import { useAnalytics } from 'src/support/useAnalytics'; export const AnalyticsUrl = () => { const customAnalyticsUrl = useSelector(selectCustomAnalyticsUrl); + const loggerEnabled = useSelector(selectLoggerEnabled); const dispatch = useDispatch(); const analytics = useAnalytics(); @@ -28,45 +33,61 @@ export const AnalyticsUrl = () => { }; return ( - - - - - setInputValue(e.target.value)} - rightContent={ - - } + <> + + + + + setInputValue(e.target.value)} + rightContent={ + + } + /> + {customAnalyticsUrl && ( + + + Current: {customAnalyticsUrl} + + + + )} + + + + + + + { + const newValue = !loggerEnabled; + dispatch(analyticsActions.setLoggerEnabled(newValue)); + analytics.setLoggerEnabled(newValue); + }} /> - {customAnalyticsUrl && ( - - - Current: {customAnalyticsUrl} - - - - )} - - - + + + ); }; diff --git a/suite-common/analytics-redux/src/index.ts b/suite-common/analytics-redux/src/index.ts index 97a421af12..8769eea235 100644 --- a/suite-common/analytics-redux/src/index.ts +++ b/suite-common/analytics-redux/src/index.ts @@ -8,5 +8,6 @@ export { selectHasUserAllowedTracking, selectIsAnalyticsEnabled, selectCustomAnalyticsUrl, + selectLoggerEnabled, } from './redux/analyticsReducer'; export { disableAnalytics, analyticsActions, ACTION_PREFIX } from './redux/analyticsActions'; diff --git a/suite-common/analytics-redux/src/redux/analyticsActions.ts b/suite-common/analytics-redux/src/redux/analyticsActions.ts index fe433002a2..ea255d54a6 100644 --- a/suite-common/analytics-redux/src/redux/analyticsActions.ts +++ b/suite-common/analytics-redux/src/redux/analyticsActions.ts @@ -15,9 +15,12 @@ export const disableAnalytics = createAction(`${ACTION_PREFIX}/disableAnalytics` const setCustomAnalyticsUrl = createAction( `${ACTION_PREFIX}/setCustomAnalyticsUrl`, - (payload: string | undefined) => ({ - payload, - }), + (payload: string | undefined) => ({ payload }), +); + +const setLoggerEnabled = createAction( + `${ACTION_PREFIX}/setLoggerEnabled`, + (payload: boolean | undefined) => ({ payload }), ); export const analyticsActions = { @@ -25,4 +28,5 @@ export const analyticsActions = { enableAnalytics, disableAnalytics, setCustomAnalyticsUrl, + setLoggerEnabled, } as const; diff --git a/suite-common/analytics-redux/src/redux/analyticsReducer.ts b/suite-common/analytics-redux/src/redux/analyticsReducer.ts index dc3d4ad039..92448f6dd2 100644 --- a/suite-common/analytics-redux/src/redux/analyticsReducer.ts +++ b/suite-common/analytics-redux/src/redux/analyticsReducer.ts @@ -10,6 +10,7 @@ export type AnalyticsState = { enabled?: boolean | undefined; confirmed?: boolean | undefined; customAnalyticsUrl?: string | undefined; + loggerEnabled?: boolean | undefined; }; export type AnalyticsRootState = { @@ -21,6 +22,7 @@ const analyticsInitialState: AnalyticsState = { enabled: undefined, confirmed: false, customAnalyticsUrl: undefined, + loggerEnabled: undefined, }; export const prepareAnalyticsReducer = createReducerWithExtraDeps( @@ -45,6 +47,9 @@ export const prepareAnalyticsReducer = createReducerWithExtraDeps( .addCase(analyticsActions.setCustomAnalyticsUrl, (state, { payload }) => { state.customAnalyticsUrl = payload; }) + .addCase(analyticsActions.setLoggerEnabled, (state, { payload }) => { + state.loggerEnabled = payload; + }) .addMatcher( action => action.type === extra.actionTypes.storageLoad, (state, action: AnyAction) => action.payload.analytics || state, @@ -73,3 +78,6 @@ export const selectIsAnalyticsEnabled = (state: AnalyticsRootState): boolean => export const selectCustomAnalyticsUrl = (state: AnalyticsRootState): string | undefined => state.analytics.customAnalyticsUrl; + +export const selectLoggerEnabled = (state: AnalyticsRootState): boolean | undefined => + state.analytics.loggerEnabled; diff --git a/suite-common/analytics/README.md b/suite-common/analytics/README.md index 8f3aa68dfc..a1034fbbdd 100644 --- a/suite-common/analytics/README.md +++ b/suite-common/analytics/README.md @@ -177,12 +177,7 @@ export const sessionRequestThunk = createThunk = { isEnabled: () => true, disable: () => {}, enable: () => {}, + setUrl: () => {}, + setLoggerEnabled: () => {}, init: () => {}, }; diff --git a/suite-native/analytics-redux/package.json b/suite-native/analytics-redux/package.json index f3fd836793..8bf53bc7d5 100644 --- a/suite-native/analytics-redux/package.json +++ b/suite-native/analytics-redux/package.json @@ -10,6 +10,7 @@ "type-check": "yarn g:tsc --build" }, "dependencies": { + "@reduxjs/toolkit": "2.11.0", "@suite-common/analytics-redux": "workspace:*", "@suite-common/redux-utils": "workspace:*", "@suite-native/analytics": "workspace:*", diff --git a/suite-native/analytics-redux/redux.d.ts b/suite-native/analytics-redux/redux.d.ts new file mode 100644 index 0000000000..17044e16bc --- /dev/null +++ b/suite-native/analytics-redux/redux.d.ts @@ -0,0 +1,11 @@ +import { AsyncThunkAction, ThunkAction } from '@reduxjs/toolkit'; + +declare module 'redux' { + export interface Dispatch { + >(thunk: TThunk): ReturnType; + + ( + thunkAction: ThunkAction, + ): ReturnType; + } +} diff --git a/suite-native/analytics-redux/src/analyticsThunks.ts b/suite-native/analytics-redux/src/analyticsThunks.ts index e4dfe97e24..593a803804 100644 --- a/suite-native/analytics-redux/src/analyticsThunks.ts +++ b/suite-native/analytics-redux/src/analyticsThunks.ts @@ -5,6 +5,7 @@ import { selectHasUserAllowedTracking, selectIsAnalyticsConfirmed, selectIsAnalyticsEnabled, + selectLoggerEnabled, } from '@suite-common/analytics-redux'; import { createThunk } from '@suite-common/redux-utils'; import { EventType, asTypedNativeAnalytics } from '@suite-native/analytics'; @@ -50,12 +51,14 @@ export const initAnalyticsThunk = createThunk( const isAnalyticsConfirmed = selectIsAnalyticsConfirmed(getState()); const customAnalyticsUrl = selectCustomAnalyticsUrl(getState()); + const loggerEnabled = selectLoggerEnabled(getState()); const options: InitOptions = { instanceId, sessionId, environment: 'mobile', url: customAnalyticsUrl, + loggerEnabled, commitId: getCommitHash(), isDev: isDevelopEnv(), callbacks: { diff --git a/suite-native/analytics/package.json b/suite-native/analytics/package.json index fc1ab293c9..820b307e76 100644 --- a/suite-native/analytics/package.json +++ b/suite-native/analytics/package.json @@ -10,13 +10,13 @@ "type-check": "yarn g:tsc --build" }, "dependencies": { + "@reduxjs/toolkit": "2.11.0", "@suite-common/analytics": "workspace:*", "@suite-common/suite-constants": "workspace:*", "@suite-common/suite-types": "workspace:*", "@suite-common/trading": "workspace:*", "@suite-common/wallet-config": "workspace:*", "@suite-common/wallet-types": "workspace:*", - "@suite-native/config": "workspace:*", "@trezor/analytics-uploader": "workspace:*", "@trezor/blockchain-link-types": "workspace:*", "@trezor/connect": "workspace:*", diff --git a/suite-native/analytics/redux.d.ts b/suite-native/analytics/redux.d.ts new file mode 100644 index 0000000000..17044e16bc --- /dev/null +++ b/suite-native/analytics/redux.d.ts @@ -0,0 +1,11 @@ +import { AsyncThunkAction, ThunkAction } from '@reduxjs/toolkit'; + +declare module 'redux' { + export interface Dispatch { + >(thunk: TThunk): ReturnType; + + ( + thunkAction: ThunkAction, + ): ReturnType; + } +} diff --git a/suite-native/analytics/src/createAnalytics.ts b/suite-native/analytics/src/createAnalytics.ts index 1032132f45..b3294c9262 100644 --- a/suite-native/analytics/src/createAnalytics.ts +++ b/suite-native/analytics/src/createAnalytics.ts @@ -1,5 +1,4 @@ -import { isDebugEnv } from '@suite-native/config'; -import { Analytics, Event, QueuedAnalytics } from '@trezor/analytics-uploader'; +import { Analytics, QueuedAnalytics } from '@trezor/analytics-uploader'; import { getSuiteVersion } from '@trezor/env-utils'; import { AnalyticsNativeEvents } from './analyticsEvents'; @@ -14,16 +13,6 @@ const createAnalytics = (): Analytics => { app: 'suite', }); - if (isDebugEnv()) { - // Do not send analytics in development - newAnalytics.report = (event: Event) => { - if (process.env.EXPO_PUBLIC_IS_ANALYTICS_LOGGER_ENABLED === 'true') { - // eslint-disable-next-line no-console - console.log(`Analytics report '${event.type}':`, event); - } - }; - } - return newAnalytics; }; diff --git a/suite-native/analytics/tsconfig.json b/suite-native/analytics/tsconfig.json index a1f0edb561..f9e1fad777 100644 --- a/suite-native/analytics/tsconfig.json +++ b/suite-native/analytics/tsconfig.json @@ -18,7 +18,6 @@ { "path": "../../suite-common/wallet-types" }, - { "path": "../config" }, { "path": "../../packages/analytics-uploader" }, diff --git a/suite-native/app/README.md b/suite-native/app/README.md index 404c71493a..293c9e30d2 100644 --- a/suite-native/app/README.md +++ b/suite-native/app/README.md @@ -95,7 +95,6 @@ You can override ENV variables locally using `.env.development.local` (or `.env. > If you use `.env` file, it has the lowest priority. See [what other .env\* files you can use](https://github.com/bkeepers/dotenv/blob/c6e583a/README.md#what-other-env-files-can-i-use). -- `EXPO_PUBLIC_IS_ANALYTICS_LOGGER_ENABLED=true` in `.env.development.local` to debug analytics locally, - `EXPO_PUBLIC_IS_SENTRY_ON_DEBUG_BUILD_ENABLED=true` to debug Sentry locally and - `EXPO_PUBLIC_IS_NATIVE_USB_LOGGER_ENABLED=true` to debug @trezor/transport-native-usb locally. - `EXPO_PUBLIC_IS_NATIVE_BLUETOOTH_LOGGER_ENABLED=true` to debug @trezor/transport-native-bluetooth locally. diff --git a/suite-native/module-dev-utils/redux.d.ts b/suite-native/module-dev-utils/redux.d.ts new file mode 100644 index 0000000000..17044e16bc --- /dev/null +++ b/suite-native/module-dev-utils/redux.d.ts @@ -0,0 +1,11 @@ +import { AsyncThunkAction, ThunkAction } from '@reduxjs/toolkit'; + +declare module 'redux' { + export interface Dispatch { + >(thunk: TThunk): ReturnType; + + ( + thunkAction: ThunkAction, + ): ReturnType; + } +} diff --git a/suite-native/module-dev-utils/src/components/AnalyticsUrlControl.tsx b/suite-native/module-dev-utils/src/components/AnalyticsUrlControl.tsx index 2ab49c8964..a95847330c 100644 --- a/suite-native/module-dev-utils/src/components/AnalyticsUrlControl.tsx +++ b/suite-native/module-dev-utils/src/components/AnalyticsUrlControl.tsx @@ -1,9 +1,13 @@ import { useDispatch, useSelector } from 'react-redux'; -import { analyticsActions, selectCustomAnalyticsUrl } from '@suite-common/analytics-redux'; +import { + analyticsActions, + selectCustomAnalyticsUrl, + selectLoggerEnabled, +} from '@suite-common/analytics-redux'; import { yup } from '@suite-common/validators'; import { analytics } from '@suite-native/analytics'; -import { Button, Card, Text, VStack } from '@suite-native/atoms'; +import { Button, Card, CheckBox, Divider, HStack, Text, VStack } from '@suite-native/atoms'; import { Form, TextInputField, useForm } from '@suite-native/forms'; import { useToast } from '@suite-native/toasts'; @@ -11,6 +15,7 @@ const DEFAULT_CUSTOM_URL = ''; export const AnalyticsUrlControl = () => { const customUrl = useSelector(selectCustomAnalyticsUrl); + const loggerEnabled = useSelector(selectLoggerEnabled); const dispatch = useDispatch(); const { showToast } = useToast(); @@ -19,7 +24,7 @@ export const AnalyticsUrlControl = () => { analyticsUrl: customUrl ?? DEFAULT_CUSTOM_URL, }, validation: yup.object({ - analyticsUrl: yup.string(), + analyticsUrl: yup.string().url('Please enter a valid URL'), }), }); @@ -34,6 +39,7 @@ export const AnalyticsUrlControl = () => { const url = trimmedUrl || undefined; dispatch(analyticsActions.setCustomAnalyticsUrl(url)); analytics.setUrl(url); + reset({ analyticsUrl: trimmedUrl }); showToast({ message: url ? 'Analytics URL updated' : 'Analytics URL reset to default', variant: 'success', @@ -55,7 +61,7 @@ export const AnalyticsUrlControl = () => { Analytics URL - Override the analytics endpoint URL. Leave empty and save to use the default. + Point to your own analytics server for testing.
@@ -84,6 +90,19 @@ export const AnalyticsUrlControl = () => { )}
+ + + Console Logging + { + const newValue = !loggerEnabled; + dispatch(analyticsActions.setLoggerEnabled(newValue)); + analytics.setLoggerEnabled(newValue); + }} + /> +
); diff --git a/suite-native/state/src/reducers.ts b/suite-native/state/src/reducers.ts index 039ccfa762..4a409f1dff 100644 --- a/suite-native/state/src/reducers.ts +++ b/suite-native/state/src/reducers.ts @@ -199,7 +199,13 @@ export const prepareRootReducers = (deps: PrepareRootReducersDeps) => { const analyticsPersistedReducer = preparePersistReducer({ reducer: analyticsReducer, - persistedKeys: ['instanceId', 'enabled', 'confirmed', 'customAnalyticsUrl'], + persistedKeys: [ + 'instanceId', + 'enabled', + 'confirmed', + 'customAnalyticsUrl', + 'loggerEnabled', + ], key: 'analytics', version: 1, storage: deps.mmkvStorage, diff --git a/yarn.lock b/yarn.lock index ff7ae5d62a..1d41ab3635 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11797,6 +11797,7 @@ __metadata: version: 0.0.0-use.local resolution: "@suite-native/analytics-redux@workspace:suite-native/analytics-redux" dependencies: + "@reduxjs/toolkit": "npm:2.11.0" "@suite-common/analytics-redux": "workspace:*" "@suite-common/redux-utils": "workspace:*" "@suite-native/analytics": "workspace:*" @@ -11811,13 +11812,13 @@ __metadata: version: 0.0.0-use.local resolution: "@suite-native/analytics@workspace:suite-native/analytics" dependencies: + "@reduxjs/toolkit": "npm:2.11.0" "@suite-common/analytics": "workspace:*" "@suite-common/suite-constants": "workspace:*" "@suite-common/suite-types": "workspace:*" "@suite-common/trading": "workspace:*" "@suite-common/wallet-config": "workspace:*" "@suite-common/wallet-types": "workspace:*" - "@suite-native/config": "workspace:*" "@trezor/analytics-uploader": "workspace:*" "@trezor/blockchain-link-types": "workspace:*" "@trezor/connect": "workspace:*"