@@ -25,8 +25,10 @@ import {
2525} from "matrix-js-sdk/src/matrix" ;
2626import { logger } from "matrix-js-sdk/src/logger" ;
2727import { CryptoEvent } from "matrix-js-sdk/src/crypto" ;
28- import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api" ;
28+ import { CryptoApi , KeyBackupInfo } from "matrix-js-sdk/src/crypto-api" ;
29+ import { CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange" ;
2930
31+ import { PosthogAnalytics } from "./PosthogAnalytics" ;
3032import dis from "./dispatcher/dispatcher" ;
3133import {
3234 hideToast as hideBulkUnverifiedSessionsToast ,
@@ -79,6 +81,10 @@ export default class DeviceListener {
7981 private enableBulkUnverifiedSessionsReminder = true ;
8082 private deviceClientInformationSettingWatcherRef : string | undefined ;
8183
84+ // Remember the current analytics state to avoid sending the same event multiple times.
85+ private analyticsVerificationState ?: string ;
86+ private analyticsRecoveryState ?: string ;
87+
8288 public static sharedInstance ( ) : DeviceListener {
8389 if ( ! window . mxDeviceListener ) window . mxDeviceListener = new DeviceListener ( ) ;
8490 return window . mxDeviceListener ;
@@ -301,6 +307,7 @@ export default class DeviceListener {
301307 const crossSigningReady = await crypto . isCrossSigningReady ( ) ;
302308 const secretStorageReady = await crypto . isSecretStorageReady ( ) ;
303309 const allSystemsReady = crossSigningReady && secretStorageReady ;
310+ await this . reportCryptoSessionStateToAnalytics ( crypto , cli , secretStorageReady ) ;
304311
305312 if ( this . dismissedThisDeviceToast || allSystemsReady ) {
306313 hideSetupEncryptionToast ( ) ;
@@ -407,6 +414,72 @@ export default class DeviceListener {
407414 this . displayingToastsForDeviceIds = newUnverifiedDeviceIds ;
408415 }
409416
417+ /**
418+ * Reports current recovery state to analytics.
419+ * Checks if the session is verified and if the recovery is correctly setup (i.e all secrets known locally and in 4S).
420+ * @param crypto - the crypto module
421+ * @param cli - the matrix client
422+ * @param secretStorageReady - if the secret storage is ready
423+ * @private
424+ */
425+ private async reportCryptoSessionStateToAnalytics (
426+ crypto : CryptoApi ,
427+ cli : MatrixClient ,
428+ secretStorageReady : boolean ,
429+ ) : Promise < void > {
430+ //
431+ const crossSigningStatus = await crypto . getCrossSigningStatus ( ) ;
432+ // const backupInfo = await crypto.getActiveSessionBackupVersion();
433+ const backupInfo = await this . getKeyBackupInfo ( ) ;
434+ const is4SEnabled = ( await cli . secretStorage . getDefaultKeyId ( ) ) != null ;
435+
436+ const verificationState = crossSigningStatus . publicKeysOnDevice ? "Verified" : "NotVerified" ;
437+
438+ let recoveryState : "Disabled" | "Enabled" | "Incomplete" ;
439+ if ( ! is4SEnabled ) {
440+ recoveryState = "Disabled" ;
441+ } else {
442+ const allCrossSigningSecretsCached =
443+ crossSigningStatus . privateKeysCachedLocally . masterKey &&
444+ crossSigningStatus . privateKeysCachedLocally . selfSigningKey &&
445+ crossSigningStatus . privateKeysCachedLocally . userSigningKey ;
446+ if ( backupInfo != null ) {
447+ // there is a backup check that all secrets are stored in 4s and known locally
448+ // if they are not, the backup is incomplete
449+ const backupPrivateKeyIsInCache = ( await crypto . getSessionBackupPrivateKey ( ) ) != null ;
450+ if ( secretStorageReady && allCrossSigningSecretsCached && backupPrivateKeyIsInCache ) {
451+ recoveryState = "Enabled" ;
452+ } else {
453+ recoveryState = "Incomplete" ;
454+ }
455+ } else {
456+ // no backup just consider cross-signing secrets
457+ if ( allCrossSigningSecretsCached && crossSigningStatus . privateKeysInSecretStorage ) {
458+ recoveryState = "Enabled" ;
459+ } else {
460+ recoveryState = "Incomplete" ;
461+ }
462+ }
463+ }
464+
465+ if ( this . analyticsVerificationState === verificationState && this . analyticsRecoveryState === recoveryState ) {
466+ // No changes, no need to send the event nor update the user properties
467+ return ;
468+ }
469+ this . analyticsRecoveryState = recoveryState ;
470+ this . analyticsVerificationState = verificationState ;
471+
472+ // Update user properties
473+ PosthogAnalytics . instance . setProperty ( "recoveryState" , recoveryState ) ;
474+ PosthogAnalytics . instance . setProperty ( "verificationState" , verificationState ) ;
475+
476+ PosthogAnalytics . instance . trackEvent < CryptoSessionStateChange > ( {
477+ eventName : "CryptoSessionState" ,
478+ verificationState : verificationState ,
479+ recoveryState : recoveryState ,
480+ } ) ;
481+ }
482+
410483 /**
411484 * Check if key backup is enabled, and if not, raise an `Action.ReportKeyBackupNotEnabled` event (which will
412485 * trigger an auto-rageshake).
0 commit comments