Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/analytics-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` ([#8543](https://github.com/MetaMask/core/pull/8543))

### 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))
Expand Down
20 changes: 20 additions & 0 deletions packages/analytics-controller/src/AnalyticsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,26 @@ 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';
Expand Down
5 changes: 4 additions & 1 deletion packages/analytics-controller/src/AnalyticsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,10 @@ export class AnalyticsController extends BaseController<
...state,
};

validateAnalyticsControllerState(initialState);
validateAnalyticsControllerState(
initialState,
platformAdapter.skipUUIDv4Check === true,
);

super({
name: controllerName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,36 @@ 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',
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
Loading