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 = {
17 createTime: number | undefined;
19 type OptionsVerificationError = {
21 createTime: number | undefined;
22 addressMatchingDefaultShare: boolean | undefined;
24 type OptionsBlockVerificationError = {
29 export class IntegrityMetrics {
30 protected metricsModule = metrics;
32 private lastErroringUserReport: number;
34 protected reportedShareIds: Set<string>;
36 protected reportedNodeIds: Set<string>;
39 this.lastErroringUserReport = 0;
40 this.reportedShareIds = new Set();
41 this.reportedNodeIds = new Set();
44 shareDecryptionError(shareId: string, shareType: ShareTypeStringWithPublic, options: OptionsDecryptionError) {
45 if (this.checkShareIdAlreadyReported(shareId)) {
48 this.reportDecryptionError('share', shareType, options);
51 nodeDecryptionError(nodeId: string, shareType: ShareTypeStringWithPublic, options: OptionsDecryptionError) {
52 if (this.checkNodeIdAlreadyReported(nodeId)) {
55 this.reportDecryptionError('node', shareType, options);
58 private reportDecryptionError(
59 entity: 'share' | 'node',
60 shareType: ShareTypeStringWithPublic,
61 options: OptionsDecryptionError
63 const fromBefore2024 = getFromBefore2024(options.createTime);
64 this.metricsModule.drive_integrity_decryption_errors_total.increment({
69 if (fromBefore2024 === 'no') {
70 userSuccessMetrics.mark(UserAvailabilityTypes.coreFeatureError);
71 this.reportErroringUser(shareType, options.isPaid);
75 signatureVerificationError(
77 shareType: ShareTypeString,
78 verificationKey: VerificationKey,
79 options: OptionsVerificationError
81 if (this.checkNodeIdAlreadyReported(nodeId)) {
85 const fromBefore2024 = getFromBefore2024(options.createTime);
86 const addressMatchingDefaultShare = getAddressMatchingDefaultShare(options.addressMatchingDefaultShare);
87 this.metricsModule.drive_integrity_verification_errors_total.increment({
90 addressMatchingDefaultShare,
93 if (fromBefore2024 === 'no' && addressMatchingDefaultShare === 'yes') {
94 userSuccessMetrics.mark(UserAvailabilityTypes.coreFeatureError);
95 this.reportErroringUser(shareType, options.isPaid);
99 nodeBlockVerificationError(
100 shareType: ShareTypeString,
101 realFileSize: number,
102 options: OptionsBlockVerificationError
104 const fileSize = getFileSize(realFileSize);
105 this.metricsModule.drive_integrity_block_verification_errors_total.increment({
107 retryHelped: options.retryHelped ? 'yes' : 'no',
110 if (!options.retryHelped) {
111 userSuccessMetrics.mark(UserAvailabilityTypes.coreFeatureError);
112 this.reportErroringUser(shareType, options.isPaid);
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({
120 plan: isPaid ? 'paid' : 'free',
122 this.lastErroringUserReport = Date.now();
126 private checkShareIdAlreadyReported(shareId: string) {
127 if (this.reportedShareIds.has(shareId)) {
130 this.reportedShareIds.add(shareId);
134 private checkNodeIdAlreadyReported(nodeId: string) {
135 if (this.reportedNodeIds.has(nodeId)) {
138 this.reportedNodeIds.add(nodeId);
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.
150 const date = fromUnixTime(timestamp);
151 if (date.getFullYear() < 2024) {
157 export function getAddressMatchingDefaultShare(addressMatchingDefaultShare?: boolean) {
158 if (addressMatchingDefaultShare !== false && addressMatchingDefaultShare !== true) {
161 if (addressMatchingDefaultShare) {
167 export function getFileSize(realFileSize: number) {
168 if (realFileSize < 2 ** 10) {
170 } else if (realFileSize < 2 ** 20) {
172 } else if (realFileSize < 2 ** 22) {
174 } else if (realFileSize < 2 ** 25) {
176 } else if (realFileSize < 2 ** 30) {