Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / applications / drive / src / app / store / _crypto / integrityMetrics.ts
blob5b84c91b58c062483c25cfeb50b21e38cebe2979
1 import { fromUnixTime } from 'date-fns';
3 import metrics from '@proton/metrics';
5 import { UserAvailabilityTypes } from '../../utils/metrics/types/userSuccessMetricsTypes';
6 import { userSuccessMetrics } from '../../utils/metrics/userSuccessMetrics';
8 const REPORT_ERROR_USERS_EVERY = 5 * 60 * 1000; // 5 minutes,
10 // Share type string used in metrics context, do not confuse with ShareType enum.
11 type ShareTypeString = 'main' | 'device' | 'photo' | 'shared';
12 type ShareTypeStringWithPublic = ShareTypeString | 'shared_public';
13 export type VerificationKey = 'ShareAddress' | 'NameSignatureEmail' | 'SignatureEmail' | 'NodeKey' | 'other';
15 type OptionsDecryptionError = {
16     isPaid: boolean;
17     createTime: number | undefined;
19 type OptionsVerificationError = {
20     isPaid: boolean;
21     createTime: number | undefined;
22     addressMatchingDefaultShare: boolean | undefined;
24 type OptionsBlockVerificationError = {
25     isPaid: boolean;
26     retryHelped: boolean;
29 export class IntegrityMetrics {
30     protected metricsModule = metrics;
32     private lastErroringUserReport: number;
34     protected reportedShareIds: Set<string>;
36     protected reportedNodeIds: Set<string>;
38     constructor() {
39         this.lastErroringUserReport = 0;
40         this.reportedShareIds = new Set();
41         this.reportedNodeIds = new Set();
42     }
44     shareDecryptionError(shareId: string, shareType: ShareTypeStringWithPublic, options: OptionsDecryptionError) {
45         if (this.checkShareIdAlreadyReported(shareId)) {
46             return;
47         }
48         this.reportDecryptionError('share', shareType, options);
49     }
51     nodeDecryptionError(nodeId: string, shareType: ShareTypeStringWithPublic, options: OptionsDecryptionError) {
52         if (this.checkNodeIdAlreadyReported(nodeId)) {
53             return;
54         }
55         this.reportDecryptionError('node', shareType, options);
56     }
58     private reportDecryptionError(
59         entity: 'share' | 'node',
60         shareType: ShareTypeStringWithPublic,
61         options: OptionsDecryptionError
62     ) {
63         const fromBefore2024 = getFromBefore2024(options.createTime);
64         this.metricsModule.drive_integrity_decryption_errors_total.increment({
65             entity,
66             shareType,
67             fromBefore2024,
68         });
69         if (fromBefore2024 === 'no') {
70             userSuccessMetrics.mark(UserAvailabilityTypes.coreFeatureError);
71             this.reportErroringUser(shareType, options.isPaid);
72         }
73     }
75     signatureVerificationError(
76         nodeId: string,
77         shareType: ShareTypeString,
78         verificationKey: VerificationKey,
79         options: OptionsVerificationError
80     ) {
81         if (this.checkNodeIdAlreadyReported(nodeId)) {
82             return;
83         }
85         const fromBefore2024 = getFromBefore2024(options.createTime);
86         const addressMatchingDefaultShare = getAddressMatchingDefaultShare(options.addressMatchingDefaultShare);
87         this.metricsModule.drive_integrity_verification_errors_total.increment({
88             shareType,
89             verificationKey,
90             addressMatchingDefaultShare,
91             fromBefore2024,
92         });
93         if (fromBefore2024 === 'no' && addressMatchingDefaultShare === 'yes') {
94             userSuccessMetrics.mark(UserAvailabilityTypes.coreFeatureError);
95             this.reportErroringUser(shareType, options.isPaid);
96         }
97     }
99     nodeBlockVerificationError(
100         shareType: ShareTypeString,
101         realFileSize: number,
102         options: OptionsBlockVerificationError
103     ) {
104         const fileSize = getFileSize(realFileSize);
105         this.metricsModule.drive_integrity_block_verification_errors_total.increment({
106             shareType,
107             retryHelped: options.retryHelped ? 'yes' : 'no',
108             fileSize,
109         });
110         if (!options.retryHelped) {
111             userSuccessMetrics.mark(UserAvailabilityTypes.coreFeatureError);
112             this.reportErroringUser(shareType, options.isPaid);
113         }
114     }
116     private reportErroringUser(shareType: ShareTypeStringWithPublic, isPaid: boolean) {
117         if (Date.now() - this.lastErroringUserReport > REPORT_ERROR_USERS_EVERY) {
118             this.metricsModule.drive_integrity_erroring_users_total.increment({
119                 shareType,
120                 plan: isPaid ? 'paid' : 'free',
121             });
122             this.lastErroringUserReport = Date.now();
123         }
124     }
126     private checkShareIdAlreadyReported(shareId: string) {
127         if (this.reportedShareIds.has(shareId)) {
128             return true;
129         }
130         this.reportedShareIds.add(shareId);
131         return false;
132     }
134     private checkNodeIdAlreadyReported(nodeId: string) {
135         if (this.reportedNodeIds.has(nodeId)) {
136             return true;
137         }
138         this.reportedNodeIds.add(nodeId);
139         return false;
140     }
143 export default new IntegrityMetrics();
145 export function getFromBefore2024(timestamp?: number) {
146     // Both zero or undefined is considered unknown as zero is often used as unknown value.
147     if (!timestamp) {
148         return 'unknown';
149     }
150     const date = fromUnixTime(timestamp);
151     if (date.getFullYear() < 2024) {
152         return 'yes';
153     }
154     return 'no';
157 export function getAddressMatchingDefaultShare(addressMatchingDefaultShare?: boolean) {
158     if (addressMatchingDefaultShare !== false && addressMatchingDefaultShare !== true) {
159         return 'unknown';
160     }
161     if (addressMatchingDefaultShare) {
162         return 'yes';
163     }
164     return 'no';
167 export function getFileSize(realFileSize: number) {
168     if (realFileSize < 2 ** 10) {
169         return '2**10';
170     } else if (realFileSize < 2 ** 20) {
171         return '2**20';
172     } else if (realFileSize < 2 ** 22) {
173         return '2**22';
174     } else if (realFileSize < 2 ** 25) {
175         return '2**25';
176     } else if (realFileSize < 2 ** 30) {
177         return '2**30';
178     }
179     return 'xxxxl';