Skip to content

Conversation

@NicolasMassart
Copy link
Contributor

@NicolasMassart NicolasMassart commented Nov 6, 2025

Important

This is an unreleased package which will continue to be improved until ready for release.

analytics-controller update

  • Added onSetupCompleted lifecycle hook for post-initialization setup
  • Added UUIDv4 validation for analyticsId
  • Added getter methods: getAnalyticsId(), isEnabled(), isOptedIn(), isSocialOptedIn()
  • Added social opt-in/opt-out methods: socialOptIn(), socialOptOut()
  • Added AnalyticsPlatformAdapterSetupError for setup failures
  • Refactored state structure: enabled → computed via computeEnabledState(), optedInuser_optedIn, analyticsIduser_analyticsId, added user_socialOptedIn
  • Added analyticsStateComputer module with computeEnabledState() function to compute enabled state from user opt-in status
  • Removed enable() and disable() methods (replaced by computed enabled state)
  • Added AnalyticsTrackingEvent type with properties, sensitiveProperties, isAnonymous, hasProperties for anonymous/non-anonymous tracking
  • Updated trackEvent to accept AnalyticsTrackingEvent instead of separate eventName and properties parameters
  • Made identify and view required in AnalyticsPlatformAdapter
  • Renamed controller method: trackPagetrackView for platform agnostic usage
  • Renamed adapter type method: trackEventtrack to better match Segment SDKs
  • Refactored identify: removed userId parameter, uses analyticsId from state, added AnalyticsUserTraits type
  • Updated action types for new methods and renames
  • Updated tests for new API and lifecycle hook
  • Updated README with lifecycle hook documentation

References

fixes #7079

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

Note

Refactors controller and adapter APIs, introduces opt-in based enabled state, adds onSetupCompleted lifecycle hook and UUIDv4 validation, and updates actions, selectors, tests, and docs.

  • API Changes:
    • Controller: trackEvent(event: AnalyticsTrackingEvent) (replaces trackEvent(name, props)), trackView (renamed from trackPage), identify(traits?) (uses state analyticsId).
    • Adapter: track (renamed from trackEvent), view (replaces trackPage), identify required.
  • State & Selectors:
    • State refactor: optedInForRegularAccount, optedInForSocialAccount, analyticsId; enabled is derived via computeEnabledState.
    • New selectors: selectAnalyticsId, selectOptedIn, selectSocialOptedIn, selectEnabled.
    • UUIDv4 validation on init via validateAnalyticsState.
  • Lifecycle:
    • New onSetupCompleted(analyticsId) hook on adapter; non-fatal errors wrapped by AnalyticsPlatformAdapterSetupError.
  • Event Handling:
    • New AnalyticsTrackingEvent with properties, sensitiveProperties, hasProperties; dual-track sensitive payloads with isSensitive flag; omit props when none.
  • Messenger Actions:
    • Updated action types for trackView and per-account opt-in/out; removed enable/disable and old optIn/optOut.
  • Tests/Docs/Exports:
    • Added tests for state computer, validator, selectors; updated controller tests.
    • README updated with new adapter methods and lifecycle hook.
    • Exposed selectors and AnalyticsPlatformAdapterSetupError in index.ts.

Written by Cursor Bugbot for commit cc7e39b. This will update automatically on new commits. Configure here.

- Add onSetupCompleted lifecycle hook to AnalyticsPlatformAdapter interface
- Remove add method from interface (breaking change)
- Change AnalyticsPlatformAdapter from type alias to interface
- Add UUIDv4 validation for analyticsId with error handling
- Add AnalyticsUserTraits type for better semantic separation
- Rename trackEvent to track and trackPage to view in adapter interface
- Update controller to call onSetupCompleted after initialization
- Add comprehensive tests for lifecycle hook and validation
- Update documentation with lifecycle hook usage examples
@NicolasMassart NicolasMassart self-assigned this Nov 6, 2025
@NicolasMassart NicolasMassart changed the title feat(analytics-controller): update controller and adapter interface for mobile integration feat(analytics-controller): update controller and adapter interface for mobile integration [Phase 1.4] Nov 7, 2025
NicolasMassart and others added 7 commits November 7, 2025 11:49
…y method

- Add isOptedIn method and corresponding action type
- Refactor identify method to use analyticsId from state instead of userId parameter
- Update tests to use realistic traits (ENABLE_OPENSEA_API, NFT_AUTODETECTION) instead of email
- Update UUID test example from v1 to v5
- Remove obsolete tests
- Fix formatting issues
…TrackingEvent object

- Add AnalyticsJsonValue and AnalyticsJsonMap types similar to legacy types for easier migration
- Add AnalyticsTrackingEvent type matching legacy ITrackingEvent structure
- Update trackEvent method to accept AnalyticsTrackingEvent instead of separate eventName and properties
- Implement same logic as legacy trackEvent (handles hasProperties, isAnonymous, sensitiveProperties)
- Remove redundant saveDataRecording parameter (now part of event object)
- Convert interfaces to type aliases for consistency
- Update tests
…m:MetaMask/core into refactor/7079_update_analytics_controller
@NicolasMassart

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@NicolasMassart NicolasMassart marked this pull request as ready for review November 17, 2025 18:00
@NicolasMassart NicolasMassart requested review from a team as code owners November 17, 2025 18:00
Copy link
Contributor

@mcmire mcmire left a comment

Choose a reason for hiding this comment

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

Hi @NicolasMassart! Thanks for the ping. I had some comments and suggestions below.

* Represents values that can be passed as properties to the event tracking function.
* Similar to JsonValue from Segment SDK but decoupled for platform agnosticism.
*/
type AnalyticsJsonValue =
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to simplify this type and use Json? I see that there are slight differences between this type and Json, namely that objects are allowed to have number keys. However if this data really gets serialized to JSON, wouldn't the keys get converted to strings anyway?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here are the resons why I used this explicit type:

Using Record<string, Json> (current):

✅ Explicit: must be an object whith named props of Json type value
✅ Type-safe: rejects arrays, primitives, null
✅ Consistent with BaseController.StateConstraint and ApprovalController

Using Json directly:

✅ Simpler
✅ Matches runtime JSON serialization behavior
❌ Less type-safe: allows arrays, primitives, null
❌ Less explicit intent as naming the AnalyticsEventProperties and AnalyticsUserTraits types allows to clearly identify the usage in controller.

My recommendation:

Keep Record<string, Json> for type safety and consistency. If you really prefers Json, we can switch, but it weakens compile-time guarantees.

NicolasMassart and others added 8 commits November 18, 2025 15:57
…and extract validation

- Remove isAnonymous field, derive sensitivity from sensitiveProperties presence
- Extract state validation to analyticsStateValidator module (called before super)
- Add selectors module for state access
- Add comprehensive unit tests for validator and selectors
- Update documentation for clarity
…m:MetaMask/core into refactor/7079_update_analytics_controller

# Conflicts:
#	packages/analytics-controller/src/selectors.test.ts
@NicolasMassart
Copy link
Contributor Author

@metamaskbot publish-preview

@github-actions
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": "3.0.0-preview-21a5ddac",
  "@metamask-previews/accounts-controller": "34.0.0-preview-21a5ddac",
  "@metamask-previews/address-book-controller": "7.0.0-preview-21a5ddac",
  "@metamask-previews/analytics-controller": "0.0.0-preview-21a5ddac",
  "@metamask-previews/announcement-controller": "8.0.0-preview-21a5ddac",
  "@metamask-previews/app-metadata-controller": "2.0.0-preview-21a5ddac",
  "@metamask-previews/approval-controller": "8.0.0-preview-21a5ddac",
  "@metamask-previews/assets-controllers": "89.0.1-preview-21a5ddac",
  "@metamask-previews/base-controller": "9.0.0-preview-21a5ddac",
  "@metamask-previews/bridge-controller": "61.0.0-preview-21a5ddac",
  "@metamask-previews/bridge-status-controller": "61.0.0-preview-21a5ddac",
  "@metamask-previews/build-utils": "3.0.4-preview-21a5ddac",
  "@metamask-previews/chain-agnostic-permission": "1.2.2-preview-21a5ddac",
  "@metamask-previews/claims-controller": "0.2.0-preview-21a5ddac",
  "@metamask-previews/composable-controller": "12.0.0-preview-21a5ddac",
  "@metamask-previews/controller-utils": "11.15.0-preview-21a5ddac",
  "@metamask-previews/core-backend": "4.1.0-preview-21a5ddac",
  "@metamask-previews/delegation-controller": "1.0.0-preview-21a5ddac",
  "@metamask-previews/earn-controller": "10.0.0-preview-21a5ddac",
  "@metamask-previews/eip-5792-middleware": "2.0.0-preview-21a5ddac",
  "@metamask-previews/eip-7702-internal-rpc-middleware": "0.1.0-preview-21a5ddac",
  "@metamask-previews/eip1193-permission-middleware": "1.0.2-preview-21a5ddac",
  "@metamask-previews/ens-controller": "18.0.0-preview-21a5ddac",
  "@metamask-previews/error-reporting-service": "3.0.0-preview-21a5ddac",
  "@metamask-previews/eth-block-tracker": "14.0.0-preview-21a5ddac",
  "@metamask-previews/eth-json-rpc-middleware": "21.0.0-preview-21a5ddac",
  "@metamask-previews/eth-json-rpc-provider": "5.0.1-preview-21a5ddac",
  "@metamask-previews/foundryup": "1.0.1-preview-21a5ddac",
  "@metamask-previews/gas-fee-controller": "25.0.0-preview-21a5ddac",
  "@metamask-previews/gator-permissions-controller": "0.5.0-preview-21a5ddac",
  "@metamask-previews/json-rpc-engine": "10.1.1-preview-21a5ddac",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.8-preview-21a5ddac",
  "@metamask-previews/keyring-controller": "24.0.0-preview-21a5ddac",
  "@metamask-previews/logging-controller": "7.0.0-preview-21a5ddac",
  "@metamask-previews/message-manager": "14.0.0-preview-21a5ddac",
  "@metamask-previews/messenger": "0.3.0-preview-21a5ddac",
  "@metamask-previews/multichain-account-service": "3.0.0-preview-21a5ddac",
  "@metamask-previews/multichain-api-middleware": "1.2.4-preview-21a5ddac",
  "@metamask-previews/multichain-network-controller": "2.0.0-preview-21a5ddac",
  "@metamask-previews/multichain-transactions-controller": "6.0.0-preview-21a5ddac",
  "@metamask-previews/name-controller": "9.0.0-preview-21a5ddac",
  "@metamask-previews/network-controller": "25.0.0-preview-21a5ddac",
  "@metamask-previews/network-enablement-controller": "3.1.0-preview-21a5ddac",
  "@metamask-previews/notification-services-controller": "20.0.0-preview-21a5ddac",
  "@metamask-previews/permission-controller": "12.1.0-preview-21a5ddac",
  "@metamask-previews/permission-log-controller": "5.0.0-preview-21a5ddac",
  "@metamask-previews/phishing-controller": "15.0.1-preview-21a5ddac",
  "@metamask-previews/polling-controller": "15.0.0-preview-21a5ddac",
  "@metamask-previews/preferences-controller": "21.0.0-preview-21a5ddac",
  "@metamask-previews/profile-sync-controller": "26.0.0-preview-21a5ddac",
  "@metamask-previews/rate-limit-controller": "7.0.0-preview-21a5ddac",
  "@metamask-previews/remote-feature-flag-controller": "2.0.0-preview-21a5ddac",
  "@metamask-previews/sample-controllers": "3.0.0-preview-21a5ddac",
  "@metamask-previews/seedless-onboarding-controller": "6.1.0-preview-21a5ddac",
  "@metamask-previews/selected-network-controller": "25.0.0-preview-21a5ddac",
  "@metamask-previews/shield-controller": "2.1.1-preview-21a5ddac",
  "@metamask-previews/signature-controller": "36.0.0-preview-21a5ddac",
  "@metamask-previews/subscription-controller": "4.2.2-preview-21a5ddac",
  "@metamask-previews/token-search-discovery-controller": "4.0.0-preview-21a5ddac",
  "@metamask-previews/transaction-controller": "61.3.0-preview-21a5ddac",
  "@metamask-previews/transaction-pay-controller": "8.0.0-preview-21a5ddac",
  "@metamask-previews/user-operation-controller": "40.0.0-preview-21a5ddac"
}

...event.properties,
...event.sensitiveProperties,
});
}
Copy link

Choose a reason for hiding this comment

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

Bug: saveDataRecording property in event object is ignored

The AnalyticsTrackingEvent type includes a saveDataRecording property, but trackEvent ignores it and does not pass it to the platform adapter. This results in the property being silently discarded, potentially breaking intended functionality like session recording controls.

Fix in Cursor Fix in Web

...event.properties,
...event.sensitiveProperties,
});
}
Copy link

Choose a reason for hiding this comment

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

Bug: saveDataRecording property is ignored

The AnalyticsTrackingEvent type includes a saveDataRecording property, but the trackEvent method ignores it and does not pass it to the platform adapter or use it in any logic. This functionality appears to be unimplemented or lost during refactoring.

Fix in Cursor Fix in Web

Copy link
Contributor

@mcmire mcmire left a comment

Choose a reason for hiding this comment

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

More suggestions, but all of them are super minor, so I will approve.

* @param state - The analytics controller state to validate
* @throws {Error} If analyticsId is missing, invalid, or not a UUIDv4
*/
export function validateAnalyticsState(state: AnalyticsControllerState): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Perhaps we should call this validateAnalyticsControllerState?

* @param state - The current controller state
* @returns `true` if analytics tracking should be enabled, `false` otherwise
*/
export function computeEnabledState(state: AnalyticsControllerState): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Since we have a selector do we need this file? My thought is that the selector can be used not only in Redux but also on its own.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Thoughts on calling this file validateAnalyticsState.ts to match the function being exported? The current name of the file makes it sounds like this exports a class or something.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: If we want to keep this file, thoughts on renaming it computeEnabledState to match the function being exported? The current name makes it sound like this exports a class or something.

@@ -0,0 +1,44 @@
import type { AnalyticsControllerState } from './AnalyticsController';

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: If we do keep this function it is necessary to have so much documentation for it? I'm all for explaining how things work, but the code seems pretty simple to me, and there is a lot of redundant information here that can be easily gleaned already. (For instance, the fact that we are accessing state implies that whenever state changes, the return value of this function may change. And the code already says state.optedInForRegularAccount || state.optedInForSocialAccount, we don't need to repeat that in the documentation. Finally, we should not need to list all of the possible return values for this function; maintainers should be able to look at tests to discover this.)


it('uses default analyticsId when not provided in partial state', () => {
describe('trackEvent', () => {
it('tracks event when enabled via regular opt-in', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
it('tracks event when enabled via regular opt-in', () => {
it('calls platform adapter to track event when enabled via regular opt-in', () => {


it('uses provided analyticsId when passed in state at initialization', () => {
const customId = '550e8400-e29b-41d4-a716-446655440000';
it('tracks event when enabled via social opt-in', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
it('tracks event when enabled via social opt-in', () => {
it('tracks event via platform adapter when enabled via social opt-in', () => {

});

it('tracks event with default empty properties when properties not provided', () => {
it('does not track event when disabled', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
it('does not track event when disabled', () => {
it('does not call platform adapter when disabled', () => {

});

it('does not track page when disabled', () => {
it('does not track view when disabled', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like we are missing a test calls platform adapter to track view when enabled via social opt-in or something equivalent?

});

it('initializes with custom enabled/optedIn', () => {
it('returns controller when onSetupCompleted throws error', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: This is technically true, but what do you think about framing this in terms of not causing any issues?

Suggested change
it('returns controller when onSetupCompleted throws error', () => {
it('discards errors thrown by onSetupCompleted', () => {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(analytics-controller): update controller and adapter interface for mobile integration [Phase 1.4]

3 participants