Merge branch 'IDTEAM-1.26.0' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / helpers / metrics.ts
blobfeb00ec87e50b77f265cd91598b3f1adaab3a77e
1 import { isFreeSubscription } from '@proton/payments';
2 import { getSilentApi } from '@proton/shared/lib/api/helpers/customConfig';
3 import { getIsB2BAudienceFromSubscription, getPlanName } from '@proton/shared/lib/helpers/subscription';
4 import type { Subscription, UserModel, UserSettings } from '@proton/shared/lib/interfaces';
6 import { metrics } from '../api/metrics';
7 import type { TelemetryReport } from '../api/telemetry';
8 import { sendMultipleTelemetryData, sendTelemetryData } from '../api/telemetry';
9 import type { METRICS_LOG } from '../constants';
10 import { SECOND } from '../constants';
11 import type { Api } from '../interfaces';
12 import { getAccountAgeForDimension } from './metrics.helpers';
13 import { wait } from './promise';
15 // Make the metrics false by default to avoid (rare) cases where we could have sendMetricReport or sendTelemetryReport
16 // before setting this metricsEnabled value with the user setting.
17 // In that scenario we would send something but the user might not want this.
18 let metricsEnabled = false;
20 /**
21  * Delay an operation by a random number of seconds between 1 second and the specified
22  * number of seconds. If none is provided, the default is 180 seconds, i.e. 3 minutes
23  */
24 export const randomDelay = async (delayInSeconds: number = 180) => {
25     await wait(SECOND * Math.floor(delayInSeconds * Math.random() + 1));
28 /**
29  * Send metrics report (/metrics endpoint)
30  */
31 export const sendMetricsReport = async (api: Api, Log: METRICS_LOG, Title?: string, Data?: any) => {
32     if (!metricsEnabled) {
33         return;
34     }
35     // We delay sending the metrics report because this helper is used in some privacy-sensitive
36     // use-cases, e.g. encrypted search, in which we don't want the server to be able to use the
37     // metric report as a distinguisher to correlate user actions, e.g. performing an encrypted
38     // search and fetching an email shortly after
39     await randomDelay();
40     void api(metrics({ Log, Title, Data }));
43 interface SendTelemetryReportArgs extends TelemetryReport {
44     api: Api;
45     silence?: boolean;
48 /**
49  * Send a telemetry report (/data/v1/stats endpoint)
50  */
51 export const sendTelemetryReport = async ({
52     api,
53     measurementGroup,
54     event,
55     values,
56     dimensions,
57     silence = true,
58 }: SendTelemetryReportArgs) => {
59     const possiblySilentApi = silence ? getSilentApi(api) : api;
61     if (!metricsEnabled) {
62         return;
63     }
65     try {
66         void (await possiblySilentApi(
67             sendTelemetryData({
68                 MeasurementGroup: measurementGroup,
69                 Event: event,
70                 Values: values,
71                 Dimensions: dimensions,
72             })
73         ));
74     } catch {
75         // fail silently
76     }
79 interface SendTelemetryReportWithBaseDimensionArgs extends SendTelemetryReportArgs {
80     api: Api;
81     user: UserModel;
82     subscription?: Subscription;
83     userSettings?: UserSettings;
84     silence?: boolean;
87 /**
88  * Send a telemetry report with basic dimensions already setup uses /data/v1/stats endpoint
89  *   - account_age
90  *   - user_locale
91  *   - subscription
92  *   - audience
93  *   - is_free
94  */
95 export const sendTelemetryReportWithBaseDimensions = async ({
96     api,
97     user,
98     subscription,
99     userSettings,
100     measurementGroup,
101     event,
102     values,
103     dimensions,
104     silence = true,
105 }: SendTelemetryReportWithBaseDimensionArgs) => {
106     const subscriptionName = isFreeSubscription(subscription) ? 'free' : getPlanName(subscription);
108     let audience = 'free';
109     if (getIsB2BAudienceFromSubscription(subscription)) {
110         audience = 'b2b';
111     } else if (!user.isFree) {
112         audience = 'b2c';
113     }
115     void sendTelemetryReport({
116         api,
117         measurementGroup,
118         event,
119         values,
120         silence,
121         dimensions: {
122             ...dimensions,
123             // Base dimensions used to help get basic knowledge about the user
124             accountAge: getAccountAgeForDimension(user),
125             userLocale: userSettings?.Locale ?? 'undefined',
126             subscription: String(subscriptionName),
127             audience,
128             isFree: subscriptionName === 'free' ? 'true' : 'false',
129         },
130     });
133 interface SendMultipleTelemetryReportsArgs {
134     api: Api;
135     reports: TelemetryReport[];
136     silence?: boolean;
140  * Send multiple telemetry reports (/data/v1/stats/multiple endpoint)
141  */
142 export const sendMultipleTelemetryReports = async ({
143     api,
144     reports,
145     silence = true,
146 }: SendMultipleTelemetryReportsArgs) => {
147     const possiblySilentApi = silence ? getSilentApi(api) : api;
149     if (!metricsEnabled) {
150         return;
151     }
153     try {
154         void (await possiblySilentApi(sendMultipleTelemetryData({ reports })));
155     } catch {
156         // fail silently
157     }
160 export const setMetricsEnabled = (enabled: boolean) => {
161     metricsEnabled = enabled;