Skip to content

feat(identity): create metametrics event library for profile-sync-controller #6044

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

fabiobozzo
Copy link
Contributor

@fabiobozzo fabiobozzo commented Jun 27, 2025

Explanation

This PR addresses the issue of magic strings being used inconsistently across MetaMask clients when tracking MetaMetrics events related to profile-sync-controller functionality. Currently, both extension and mobile clients use hardcoded string literals for feature names and actions, leading to inconsistencies (e.g., extension uses 'Backup And Sync' while mobile uses 'Contacts Sync' for the same feature).

The solution creates a centralized MetaMetrics event library within the profile-sync-controller package that exports standardized constants and helper functions. This library provides:

  • BackupAndSyncFeatureNames - Constants for all feature names used in profile-sync events
  • BackupAndSyncActions - Constants for all action names used in profile-sync events
  • BackupAndSyncEventProperties - Pre-built property sets for common events
  • createBackupAndSyncEventProperties() - Helper function for creating custom event properties

Clients can now import these type-safe constants instead of maintaining their own string definitions.

References

This PR is part of the effort to standardize MetaMetrics event tracking across MetaMask clients and improve maintainability of analytics code.

Changelog

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@fabiobozzo fabiobozzo requested a review from mathieuartu June 27, 2025 16:27
@fabiobozzo fabiobozzo marked this pull request as ready for review June 30, 2025 11:43
@fabiobozzo fabiobozzo requested review from a team as code owners June 30, 2025 11:43
@fabiobozzo fabiobozzo requested a review from mirceanis June 30, 2025 11:43
@fabiobozzo
Copy link
Contributor Author

@metamaskbot publish-preview

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-tree-controller": "0.4.0-preview-f9a5dd05",
  "@metamask-previews/accounts-controller": "31.0.0-preview-f9a5dd05",
  "@metamask-previews/address-book-controller": "6.1.0-preview-f9a5dd05",
  "@metamask-previews/announcement-controller": "7.0.3-preview-f9a5dd05",
  "@metamask-previews/app-metadata-controller": "1.0.0-preview-f9a5dd05",
  "@metamask-previews/approval-controller": "7.1.3-preview-f9a5dd05",
  "@metamask-previews/assets-controllers": "69.0.0-preview-f9a5dd05",
  "@metamask-previews/base-controller": "8.0.1-preview-f9a5dd05",
  "@metamask-previews/bridge-controller": "34.0.0-preview-f9a5dd05",
  "@metamask-previews/bridge-status-controller": "33.0.0-preview-f9a5dd05",
  "@metamask-previews/build-utils": "3.0.3-preview-f9a5dd05",
  "@metamask-previews/chain-agnostic-permission": "1.0.0-preview-f9a5dd05",
  "@metamask-previews/composable-controller": "11.0.0-preview-f9a5dd05",
  "@metamask-previews/controller-utils": "11.10.0-preview-f9a5dd05",
  "@metamask-previews/delegation-controller": "0.5.0-preview-f9a5dd05",
  "@metamask-previews/earn-controller": "2.0.1-preview-f9a5dd05",
  "@metamask-previews/eip1193-permission-middleware": "1.0.0-preview-f9a5dd05",
  "@metamask-previews/ens-controller": "17.0.0-preview-f9a5dd05",
  "@metamask-previews/error-reporting-service": "2.0.0-preview-f9a5dd05",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-f9a5dd05",
  "@metamask-previews/foundryup": "1.0.0-preview-f9a5dd05",
  "@metamask-previews/gas-fee-controller": "24.0.0-preview-f9a5dd05",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-f9a5dd05",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-f9a5dd05",
  "@metamask-previews/keyring-controller": "22.0.2-preview-f9a5dd05",
  "@metamask-previews/logging-controller": "6.0.4-preview-f9a5dd05",
  "@metamask-previews/message-manager": "12.0.1-preview-f9a5dd05",
  "@metamask-previews/multichain-api-middleware": "1.0.0-preview-f9a5dd05",
  "@metamask-previews/multichain-network-controller": "0.9.0-preview-f9a5dd05",
  "@metamask-previews/multichain-transactions-controller": "3.0.0-preview-f9a5dd05",
  "@metamask-previews/name-controller": "8.0.3-preview-f9a5dd05",
  "@metamask-previews/network-controller": "24.0.0-preview-f9a5dd05",
  "@metamask-previews/notification-services-controller": "12.0.0-preview-f9a5dd05",
  "@metamask-previews/permission-controller": "11.0.6-preview-f9a5dd05",
  "@metamask-previews/permission-log-controller": "3.0.3-preview-f9a5dd05",
  "@metamask-previews/phishing-controller": "12.6.0-preview-f9a5dd05",
  "@metamask-previews/polling-controller": "14.0.0-preview-f9a5dd05",
  "@metamask-previews/preferences-controller": "18.4.0-preview-f9a5dd05",
  "@metamask-previews/profile-sync-controller": "19.0.0-preview-f9a5dd05",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-f9a5dd05",
  "@metamask-previews/remote-feature-flag-controller": "1.6.0-preview-f9a5dd05",
  "@metamask-previews/sample-controllers": "1.0.0-preview-f9a5dd05",
  "@metamask-previews/seedless-onboarding-controller": "1.0.0-preview-f9a5dd05",
  "@metamask-previews/selected-network-controller": "23.0.0-preview-f9a5dd05",
  "@metamask-previews/signature-controller": "31.0.0-preview-f9a5dd05",
  "@metamask-previews/token-search-discovery-controller": "3.3.0-preview-f9a5dd05",
  "@metamask-previews/transaction-controller": "58.1.0-preview-f9a5dd05",
  "@metamask-previews/user-operation-controller": "37.0.0-preview-f9a5dd05"
}

@fabiobozzo
Copy link
Contributor Author

Copy link
Contributor

@mathieuartu mathieuartu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree with the intent, and the logic of everything is sound, I don't think it translates well into following the current segment schema definition, and we can probably deliver a better DX from this.

Some ideas, let me know what you think!

  1. BackupAndSyncFeatureNames makes little sense to me. This is not a dictionary that needs to exist in my mind. Also, you're listing things that don't exist yet in the segment schema definition. But I see the intent, so I think we should probably go in a direction like this instead:
export const BackupAndSyncEvents = {
    ACCOUNT_SYNCING: {
        ACCOUNTS_SYNC_ADDED: 'Accounts Sync Added',
        ...
    },
    ...
}

But more in 3.

  1. createBackupAndSyncEventProperties is too specific, as most currently used events for example don't have a feature_name and action field. Example event definition here (only has profile_id as property)

  2. Considering the difference in how MetaMetrics events are handled in both clients (extension example, mobile example), we should probably change our approach. In the end, we only need the event name and the corresponding properties. category is extension specific and doesn't matter in the end.

Below is only a suggestion!! Feel free to explore more on your own 😄
This gives type safety and intellisense directly in the properties argument, so that you're sure you're building the properties correctly + that you have the correct event name.
This is a bit convoluted maybe though lol. But in the end, you only need to import MetaMetrics.IDENTITY_EVENTS and MetaMetrics.buildIdentityEvent
LMK what you think!

export interface IdentityEvent {
  name: string;
  properties: {
    [key: string]: {
      required: boolean;
      type: 'string' | 'number' | 'boolean';
      fromObject?: Record<string, string>;
    };
  };
}

type PropertyType<T, FromObject> = FromObject extends Record<string, string>
  ? FromObject[keyof FromObject]
  : T extends 'string'
  ? string
  : T extends 'number'
  ? number
  : T extends 'boolean'
  ? boolean
  : never;

type EventProperties<T extends IdentityEvent> = {
  [K in keyof T['properties'] as T['properties'][K]['required'] extends true
    ? K
    : never]: PropertyType<
    T['properties'][K]['type'],
    T['properties'][K]['fromObject']
  >;
} & {
  [K in keyof T['properties'] as T['properties'][K]['required'] extends false
    ? K
    : never]?: PropertyType<
    T['properties'][K]['type'],
    T['properties'][K]['fromObject']
  >;
};

export const IDENTITY_EVENTS = {
  ACCOUNT_SYNCING: {
    ACCOUNT_ADDED: {
      name: 'Accounts Sync Account Added',
      properties: {
        profile_id: {
          required: true,
          type: 'string',
        },
      },
    },
  },
  PROFILE: {
    ACTIVITY_UPDATED: {
      name: 'Profile Activity Updated',
      properties: {
        profile_id: {
          required: false,
          type: 'string',
        },
        feature_name: {
          required: true,
          type: 'string',
          fromObject: {
            BACKUP_AND_SYNC: 'Backup and Sync',
            AUTHENTICATION: 'Authentication',
          },
        },
        action: {
          required: true,
          type: 'string',
          fromObject: {
            SETTINGS_TOGGLE_ENABLED: 'settings_toggle_enabled',
            SETTINGS_TOGGLE_DISABLED: 'settings_toggle_disabled',
          },
        },
        additional_description: {
          required: false,
          type: 'string',
        },
      },
    },
  },
} as const satisfies Record<string, Record<string, IdentityEvent>>;

export const buildIdentityEvent = <T extends IdentityEvent>(
  event: T,
  properties: EventProperties<T>,
): { name: (typeof event)['name']; properties: typeof properties } => ({
  name: event.name,
  properties,
});

const accountSyncingAccountAddedEvent = buildIdentityEvent(
  IDENTITY_EVENTS.ACCOUNT_SYNCING.ACCOUNT_ADDED,
  {
    profile_id: 'profile_123',
  },
);
console.log(accountSyncingAccountAddedEvent.name); // Outputs: "Accounts Sync Account Added"
console.log(accountSyncingAccountAddedEvent.properties); // Outputs: { profile_id: 'profile_123' }

const profileActivityUpdatedEvent = buildIdentityEvent(
  IDENTITY_EVENTS.PROFILE.ACTIVITY_UPDATED,
  {
    feature_name:
      IDENTITY_EVENTS.PROFILE.ACTIVITY_UPDATED.properties.feature_name
        .fromObject.BACKUP_AND_SYNC,
    action:
      IDENTITY_EVENTS.PROFILE.ACTIVITY_UPDATED.properties.action.fromObject
        .SETTINGS_TOGGLE_ENABLED,
  },
);

console.log(profileActivityUpdatedEvent.name); // Outputs: "Profile Activity Updated"
console.log(profileActivityUpdatedEvent.properties); // Outputs: { feature_name: 'Backup and Sync', action: 'settings_toggle_enabled' }

@fabiobozzo
Copy link
Contributor Author

@mathieuartu About point #2: I considered a future in which we (=identity) only rely on: https://github.com/Consensys/segment-schema/blob/main/libraries/events/metamask-identity/identity-event.yaml

@fabiobozzo fabiobozzo marked this pull request as draft July 1, 2025 13:38
@fabiobozzo
Copy link
Contributor Author

@mathieuartu despite metamask-mobile likely blocking this effort, I addressed all of your remarks. Please check it out 😉

@fabiobozzo fabiobozzo requested a review from mathieuartu July 1, 2025 13:39
@fabiobozzo
Copy link
Contributor Author

@metamaskbot publish-preview

Copy link
Contributor

github-actions bot commented Jul 1, 2025

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-tree-controller": "0.4.0-preview-bfad74b0",
  "@metamask-previews/accounts-controller": "31.0.0-preview-bfad74b0",
  "@metamask-previews/address-book-controller": "6.1.0-preview-bfad74b0",
  "@metamask-previews/announcement-controller": "7.0.3-preview-bfad74b0",
  "@metamask-previews/app-metadata-controller": "1.0.0-preview-bfad74b0",
  "@metamask-previews/approval-controller": "7.1.3-preview-bfad74b0",
  "@metamask-previews/assets-controllers": "69.0.0-preview-bfad74b0",
  "@metamask-previews/base-controller": "8.0.1-preview-bfad74b0",
  "@metamask-previews/bridge-controller": "34.0.0-preview-bfad74b0",
  "@metamask-previews/bridge-status-controller": "33.0.0-preview-bfad74b0",
  "@metamask-previews/build-utils": "3.0.3-preview-bfad74b0",
  "@metamask-previews/chain-agnostic-permission": "1.0.0-preview-bfad74b0",
  "@metamask-previews/composable-controller": "11.0.0-preview-bfad74b0",
  "@metamask-previews/controller-utils": "11.10.0-preview-bfad74b0",
  "@metamask-previews/delegation-controller": "0.5.0-preview-bfad74b0",
  "@metamask-previews/earn-controller": "2.0.1-preview-bfad74b0",
  "@metamask-previews/eip1193-permission-middleware": "1.0.0-preview-bfad74b0",
  "@metamask-previews/ens-controller": "17.0.0-preview-bfad74b0",
  "@metamask-previews/error-reporting-service": "2.0.0-preview-bfad74b0",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-bfad74b0",
  "@metamask-previews/foundryup": "1.0.0-preview-bfad74b0",
  "@metamask-previews/gas-fee-controller": "24.0.0-preview-bfad74b0",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-bfad74b0",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-bfad74b0",
  "@metamask-previews/keyring-controller": "22.0.2-preview-bfad74b0",
  "@metamask-previews/logging-controller": "6.0.4-preview-bfad74b0",
  "@metamask-previews/message-manager": "12.0.1-preview-bfad74b0",
  "@metamask-previews/multichain-api-middleware": "1.0.0-preview-bfad74b0",
  "@metamask-previews/multichain-network-controller": "0.9.0-preview-bfad74b0",
  "@metamask-previews/multichain-transactions-controller": "3.0.0-preview-bfad74b0",
  "@metamask-previews/name-controller": "8.0.3-preview-bfad74b0",
  "@metamask-previews/network-controller": "24.0.0-preview-bfad74b0",
  "@metamask-previews/notification-services-controller": "12.0.0-preview-bfad74b0",
  "@metamask-previews/permission-controller": "11.0.6-preview-bfad74b0",
  "@metamask-previews/permission-log-controller": "3.0.3-preview-bfad74b0",
  "@metamask-previews/phishing-controller": "12.6.0-preview-bfad74b0",
  "@metamask-previews/polling-controller": "14.0.0-preview-bfad74b0",
  "@metamask-previews/preferences-controller": "18.4.0-preview-bfad74b0",
  "@metamask-previews/profile-sync-controller": "19.0.0-preview-bfad74b0",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-bfad74b0",
  "@metamask-previews/remote-feature-flag-controller": "1.6.0-preview-bfad74b0",
  "@metamask-previews/sample-controllers": "1.0.0-preview-bfad74b0",
  "@metamask-previews/seedless-onboarding-controller": "1.0.0-preview-bfad74b0",
  "@metamask-previews/selected-network-controller": "23.0.0-preview-bfad74b0",
  "@metamask-previews/signature-controller": "31.0.0-preview-bfad74b0",
  "@metamask-previews/token-search-discovery-controller": "3.3.0-preview-bfad74b0",
  "@metamask-previews/transaction-controller": "58.1.0-preview-bfad74b0",
  "@metamask-previews/user-operation-controller": "37.0.0-preview-bfad74b0"
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants