From 2e1b5f420a66789ad563d96017584a044689a999 Mon Sep 17 00:00:00 2001 From: Gauthier Petetin Date: Tue, 21 Apr 2026 15:16:47 +0200 Subject: [PATCH 1/3] feat(analytics-controller): optional skipUUIDv4Check on platform adapter Allow non-UUIDv4 analyticsId when adapter.skipUUIDv4Check is true. Tighten validateAnalyticsControllerState to a single guard. Add tests and changelog entry. Made-with: Cursor --- packages/analytics-controller/CHANGELOG.md | 4 +++ .../src/AnalyticsController.test.ts | 18 ++++++++++ .../src/AnalyticsController.ts | 5 ++- .../src/AnalyticsPlatformAdapter.types.ts | 6 ++++ .../analyticsControllerStateValidator.test.ts | 33 +++++++++++++++++++ .../src/analyticsControllerStateValidator.ts | 7 +++- 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/packages/analytics-controller/CHANGELOG.md b/packages/analytics-controller/CHANGELOG.md index 88710f6ee0f..da305613eb7 100644 --- a/packages/analytics-controller/CHANGELOG.md +++ b/packages/analytics-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Optional `skipUUIDv4Check` on `AnalyticsPlatformAdapter` to allow non-UUIDv4 `analyticsId` strings when constructing `AnalyticsController` + ### Changed - Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373)) diff --git a/packages/analytics-controller/src/AnalyticsController.test.ts b/packages/analytics-controller/src/AnalyticsController.test.ts index e6c9a43a32b..e72a7dc6224 100644 --- a/packages/analytics-controller/src/AnalyticsController.test.ts +++ b/packages/analytics-controller/src/AnalyticsController.test.ts @@ -377,6 +377,24 @@ describe('AnalyticsController', () => { }).toThrow('Invalid analyticsId'); }); + it('accepts non-UUID analyticsId when adapter skipUUIDv4Check is true', async () => { + const analyticsId = 'not-a-uuid'; + const platformAdapter = { + ...createMockAdapter(), + skipUUIDv4Check: true, + }; + const { controller } = await setupController({ + state: { + optedIn: false, + analyticsId, + }, + platformAdapter, + }); + + expect(controller.state.analyticsId).toBe(analyticsId); + expect(platformAdapter.onSetupCompleted).toHaveBeenCalledWith(analyticsId); + }); + it('accepts different valid UUIDv4 values', async () => { const analyticsId1 = '11111111-1111-4111-8111-111111111111'; const analyticsId2 = '22222222-2222-4222-9222-222222222222'; diff --git a/packages/analytics-controller/src/AnalyticsController.ts b/packages/analytics-controller/src/AnalyticsController.ts index 352b1702475..8f87062be92 100644 --- a/packages/analytics-controller/src/AnalyticsController.ts +++ b/packages/analytics-controller/src/AnalyticsController.ts @@ -219,7 +219,10 @@ export class AnalyticsController extends BaseController< ...state, }; - validateAnalyticsControllerState(initialState); + validateAnalyticsControllerState( + initialState, + platformAdapter.skipUUIDv4Check === true, + ); super({ name: controllerName, diff --git a/packages/analytics-controller/src/AnalyticsPlatformAdapter.types.ts b/packages/analytics-controller/src/AnalyticsPlatformAdapter.types.ts index 39d22ff5928..78485ba6f4f 100644 --- a/packages/analytics-controller/src/AnalyticsPlatformAdapter.types.ts +++ b/packages/analytics-controller/src/AnalyticsPlatformAdapter.types.ts @@ -33,6 +33,12 @@ export type AnalyticsTrackingEvent = { * Implementations should handle platform-specific details (Segment SDK, etc.) */ export type AnalyticsPlatformAdapter = { + /** + * When `true`, the controller accepts any non-empty `analyticsId` string + * instead of requiring UUIDv4 format. Defaults to validation against UUIDv4 when omitted or `false`. + */ + skipUUIDv4Check?: boolean; + /** * Track an analytics event. * diff --git a/packages/analytics-controller/src/analyticsControllerStateValidator.test.ts b/packages/analytics-controller/src/analyticsControllerStateValidator.test.ts index b36dee38163..169cb99690f 100644 --- a/packages/analytics-controller/src/analyticsControllerStateValidator.test.ts +++ b/packages/analytics-controller/src/analyticsControllerStateValidator.test.ts @@ -47,5 +47,38 @@ describe('analyticsControllerStateValidator', () => { expect(() => validateAnalyticsControllerState(state)).not.toThrow(); }); + + it('does not throw for non-UUID string when skipUUIDv4Check is true', () => { + const state: AnalyticsControllerState = { + optedIn: false, + analyticsId: 'not-a-uuid', + }; + + expect(() => + validateAnalyticsControllerState(state, true), + ).not.toThrow(); + }); + + it('throws when analyticsId is not UUIDv4 and skipUUIDv4Check is false', () => { + const state: AnalyticsControllerState = { + optedIn: false, + analyticsId: 'not-a-uuid', + }; + + expect(() => + validateAnalyticsControllerState(state, false), + ).toThrow('Invalid analyticsId'); + }); + + it('throws when analyticsId is empty string even if skipUUIDv4Check is true', () => { + const state: AnalyticsControllerState = { + optedIn: false, + analyticsId: '', + }; + + expect(() => + validateAnalyticsControllerState(state, true), + ).toThrow('Invalid analyticsId'); + }); }); }); diff --git a/packages/analytics-controller/src/analyticsControllerStateValidator.ts b/packages/analytics-controller/src/analyticsControllerStateValidator.ts index 1713a9731bb..f40773994d2 100644 --- a/packages/analytics-controller/src/analyticsControllerStateValidator.ts +++ b/packages/analytics-controller/src/analyticsControllerStateValidator.ts @@ -24,12 +24,17 @@ export function isValidUUIDv4(value: string): boolean { * Validates that the analytics state has a valid UUIDv4 analyticsId. * * @param state - The analytics controller state to validate + * @param skipUUIDv4Check - When `true`, skips UUIDv4 format validation * @throws Error if analyticsId is missing or not a valid UUIDv4 */ export function validateAnalyticsControllerState( state: AnalyticsControllerState, + skipUUIDv4Check?: boolean, ): void { - if (!state.analyticsId || !isValidUUIDv4(state.analyticsId)) { + if ( + !state.analyticsId || + (skipUUIDv4Check !== true && !isValidUUIDv4(state.analyticsId)) + ) { throw new Error('Invalid analyticsId'); } } From 67f8440fbb8464b7846e64f773e884618574d5e6 Mon Sep 17 00:00:00 2001 From: Gauthier Petetin Date: Tue, 21 Apr 2026 15:34:05 +0200 Subject: [PATCH 2/3] style(analytics-controller): apply oxfmt to test files Fix lint:misc --check (oxfmt) formatting in AnalyticsController and analyticsControllerStateValidator tests. Made-with: Cursor --- .../src/AnalyticsController.test.ts | 4 +++- .../analyticsControllerStateValidator.test.ts | 16 +++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/analytics-controller/src/AnalyticsController.test.ts b/packages/analytics-controller/src/AnalyticsController.test.ts index e72a7dc6224..e012047a27f 100644 --- a/packages/analytics-controller/src/AnalyticsController.test.ts +++ b/packages/analytics-controller/src/AnalyticsController.test.ts @@ -392,7 +392,9 @@ describe('AnalyticsController', () => { }); expect(controller.state.analyticsId).toBe(analyticsId); - expect(platformAdapter.onSetupCompleted).toHaveBeenCalledWith(analyticsId); + expect(platformAdapter.onSetupCompleted).toHaveBeenCalledWith( + analyticsId, + ); }); it('accepts different valid UUIDv4 values', async () => { diff --git a/packages/analytics-controller/src/analyticsControllerStateValidator.test.ts b/packages/analytics-controller/src/analyticsControllerStateValidator.test.ts index 169cb99690f..2e1dea9c964 100644 --- a/packages/analytics-controller/src/analyticsControllerStateValidator.test.ts +++ b/packages/analytics-controller/src/analyticsControllerStateValidator.test.ts @@ -54,9 +54,7 @@ describe('analyticsControllerStateValidator', () => { analyticsId: 'not-a-uuid', }; - expect(() => - validateAnalyticsControllerState(state, true), - ).not.toThrow(); + expect(() => validateAnalyticsControllerState(state, true)).not.toThrow(); }); it('throws when analyticsId is not UUIDv4 and skipUUIDv4Check is false', () => { @@ -65,9 +63,9 @@ describe('analyticsControllerStateValidator', () => { analyticsId: 'not-a-uuid', }; - expect(() => - validateAnalyticsControllerState(state, false), - ).toThrow('Invalid analyticsId'); + expect(() => validateAnalyticsControllerState(state, false)).toThrow( + 'Invalid analyticsId', + ); }); it('throws when analyticsId is empty string even if skipUUIDv4Check is true', () => { @@ -76,9 +74,9 @@ describe('analyticsControllerStateValidator', () => { analyticsId: '', }; - expect(() => - validateAnalyticsControllerState(state, true), - ).toThrow('Invalid analyticsId'); + expect(() => validateAnalyticsControllerState(state, true)).toThrow( + 'Invalid analyticsId', + ); }); }); }); From 9f6e59816de750dcc303fd1773fef1c7ad257abe Mon Sep 17 00:00:00 2001 From: Gauthier Petetin Date: Tue, 21 Apr 2026 15:34:44 +0200 Subject: [PATCH 3/3] docs(analytics-controller): link skipUUIDv4Check changelog to PR 8543 Satisfy check-changelog by referencing the current pull request. Made-with: Cursor --- packages/analytics-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/analytics-controller/CHANGELOG.md b/packages/analytics-controller/CHANGELOG.md index da305613eb7..a1e26a2aba6 100644 --- a/packages/analytics-controller/CHANGELOG.md +++ b/packages/analytics-controller/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Optional `skipUUIDv4Check` on `AnalyticsPlatformAdapter` to allow non-UUIDv4 `analyticsId` strings when constructing `AnalyticsController` +- Optional `skipUUIDv4Check` on `AnalyticsPlatformAdapter` to allow non-UUIDv4 `analyticsId` strings when constructing `AnalyticsController` ([#8543](https://github.com/MetaMask/core/pull/8543)) ### Changed