Skip to content

Commit c871428

Browse files
Feat: new analytics data regulation controller (#7643)
## Explanation This PR introduces a new `@metamask/analytics-data-regulation-controller` package that provides GDPR/CCPA data deletion functionality for analytics data. The package allows to extract the logic from the mobile app (and will be compatible with extension too) **Current state:** MetaMask mobile app currently has a dedicated mechanism to handle user data deletion requests for analytics data in compliance with GDPR and CCPA regulations. **Solution:** This package introduces: - **AnalyticsDataRegulationController**: A controller that manages the lifecycle of data deletion requests, tracks whether new data has been recorded since the last deletion request, and stores deletion regulation metadata (ID and timestamp) - **AnalyticsDataRegulationService**: A service that communicates with Segment's Regulations API via a proxy endpoint to create deletion tasks and check their status - **State management**: Tracks `hasCollectedDataSinceDeletionRequest` flag, `deleteRegulationId`, and `deleteRegulationTimestamp` to support compliance workflows - **Selectors**: Provides reusable selectors for accessing controller state **Implementation details:** - The controller takes `analyticsId` as a constructor parameter (provided by the consumer) - It delegates to `AnalyticsDataRegulationService` via messenger actions to make HTTP requests to Segment's Regulations API - The service uses `createServicePolicy` from `@metamask/controller-utils` for retry logic and error handling - State is persisted and can be used to determine if new analytics events have been recorded since the last deletion request - The package includes comprehensive test coverage (100% branch, function, line, and statement coverage) ## References see also MetaMask/metamask-mobile#22016 Fixes #7618 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them <!-- CURSOR_SUMMARY --> --- > [!NOTE] > <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is generating a summary for commit 52f4a2c. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent e6acfe6 commit c871428

25 files changed

+2622
-0
lines changed

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
## Mobile Platform Team
6363
/packages/app-metadata-controller @MetaMask/mobile-platform
6464
/packages/analytics-controller @MetaMask/mobile-platform @MetaMask/extension-platform
65+
/packages/analytics-data-regulation-controller @MetaMask/mobile-platform @MetaMask/extension-platform
6566

6667
## Wallet Integrations Team
6768
/packages/chain-agnostic-permission @MetaMask/wallet-integrations
@@ -115,6 +116,8 @@
115116
/packages/accounts-controller/package.json @MetaMask/accounts-engineers @MetaMask/core-platform
116117
/packages/analytics-controller/package.json @MetaMask/mobile-platform @MetaMask/extension-platform @MetaMask/core-platform
117118
/packages/analytics-controller/CHANGELOG.md @MetaMask/mobile-platform @MetaMask/extension-platform @MetaMask/core-platform
119+
/packages/analytics-data-regulation-controller/package.json @MetaMask/mobile-platform @MetaMask/extension-platform @MetaMask/core-platform
120+
/packages/analytics-data-regulation-controller/CHANGELOG.md @MetaMask/mobile-platform @MetaMask/extension-platform @MetaMask/core-platform
118121
/packages/accounts-controller/CHANGELOG.md @MetaMask/accounts-engineers @MetaMask/core-platform
119122
/packages/address-book-controller/package.json @MetaMask/confirmations @MetaMask/core-platform
120123
/packages/address-book-controller/CHANGELOG.md @MetaMask/confirmations @MetaMask/core-platform

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Each package in this repository has its own README where you can find installati
2525
- [`@metamask/address-book-controller`](packages/address-book-controller)
2626
- [`@metamask/ai-controllers`](packages/ai-controllers)
2727
- [`@metamask/analytics-controller`](packages/analytics-controller)
28+
- [`@metamask/analytics-data-regulation-controller`](packages/analytics-data-regulation-controller)
2829
- [`@metamask/announcement-controller`](packages/announcement-controller)
2930
- [`@metamask/app-metadata-controller`](packages/app-metadata-controller)
3031
- [`@metamask/approval-controller`](packages/approval-controller)
@@ -103,6 +104,7 @@ linkStyle default opacity:0.5
103104
address_book_controller(["@metamask/address-book-controller"]);
104105
ai_controllers(["@metamask/ai-controllers"]);
105106
analytics_controller(["@metamask/analytics-controller"]);
107+
analytics_data_regulation_controller(["@metamask/analytics-data-regulation-controller"]);
106108
announcement_controller(["@metamask/announcement-controller"]);
107109
app_metadata_controller(["@metamask/app-metadata-controller"]);
108110
approval_controller(["@metamask/approval-controller"]);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
[Unreleased]: https://github.com/MetaMask/core/
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
MIT License
2+
3+
Copyright (c) 2026 MetaMask
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# `@metamask/analytics-data-regulation-controller`
2+
3+
Controller for managing analytics privacy and GDPR/CCPA data deletion functionality
4+
5+
## Installation
6+
7+
`yarn add @metamask/analytics-data-regulation-controller`
8+
9+
or
10+
11+
`npm install @metamask/analytics-data-regulation-controller`
12+
13+
## Contributing
14+
15+
This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme).
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* For a detailed explanation regarding each configuration property and type check, visit:
3+
* https://jestjs.io/docs/configuration
4+
*/
5+
6+
const merge = require('deepmerge');
7+
const path = require('path');
8+
9+
const baseConfig = require('../../jest.config.packages');
10+
11+
const displayName = path.basename(__dirname);
12+
13+
module.exports = merge(baseConfig, {
14+
// The display name when running multiple projects
15+
displayName,
16+
17+
// An object that configures minimum threshold enforcement for coverage results
18+
coverageThreshold: {
19+
global: {
20+
branches: 100,
21+
functions: 100,
22+
lines: 100,
23+
statements: 100,
24+
},
25+
},
26+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{
2+
"name": "@metamask/analytics-data-regulation-controller",
3+
"version": "0.0.0",
4+
"description": "Controller for managing analytics privacy and GDPR/CCPA data deletion functionality",
5+
"keywords": [
6+
"MetaMask",
7+
"Ethereum"
8+
],
9+
"homepage": "https://github.com/MetaMask/core/tree/main/packages/analytics-data-regulation-controller#readme",
10+
"bugs": {
11+
"url": "https://github.com/MetaMask/core/issues"
12+
},
13+
"repository": {
14+
"type": "git",
15+
"url": "https://github.com/MetaMask/core.git"
16+
},
17+
"license": "MIT",
18+
"sideEffects": false,
19+
"exports": {
20+
".": {
21+
"import": {
22+
"types": "./dist/index.d.mts",
23+
"default": "./dist/index.mjs"
24+
},
25+
"require": {
26+
"types": "./dist/index.d.cts",
27+
"default": "./dist/index.cjs"
28+
}
29+
},
30+
"./package.json": "./package.json"
31+
},
32+
"main": "./dist/index.cjs",
33+
"types": "./dist/index.d.cts",
34+
"files": [
35+
"dist/"
36+
],
37+
"scripts": {
38+
"build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references",
39+
"build:all": "ts-bridge --project tsconfig.build.json --verbose --clean",
40+
"build:docs": "typedoc",
41+
"changelog:update": "../../scripts/update-changelog.sh @metamask/analytics-data-regulation-controller",
42+
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/analytics-data-regulation-controller",
43+
"publish:preview": "yarn npm publish --tag preview",
44+
"since-latest-release": "../../scripts/since-latest-release.sh",
45+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
46+
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
47+
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
48+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
49+
},
50+
"dependencies": {
51+
"@metamask/base-controller": "^9.0.0",
52+
"@metamask/controller-utils": "^11.18.0",
53+
"@metamask/messenger": "^0.3.0",
54+
"@metamask/utils": "^11.9.0"
55+
},
56+
"devDependencies": {
57+
"@metamask/auto-changelog": "^3.4.4",
58+
"@ts-bridge/cli": "^0.6.4",
59+
"@types/jest": "^27.4.1",
60+
"deepmerge": "^4.2.2",
61+
"jest": "^27.5.1",
62+
"nock": "^13.3.1",
63+
"sinon": "^9.2.4",
64+
"ts-jest": "^27.1.4",
65+
"typedoc": "^0.24.8",
66+
"typedoc-plugin-missing-exports": "^2.0.0",
67+
"typescript": "~5.3.3"
68+
},
69+
"engines": {
70+
"node": "^18.18 || >=20"
71+
},
72+
"publishConfig": {
73+
"access": "public",
74+
"registry": "https://registry.npmjs.org/"
75+
}
76+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* This file is auto generated by `scripts/generate-method-action-types.ts`.
3+
* Do not edit manually.
4+
*/
5+
6+
import type { AnalyticsDataRegulationController } from './AnalyticsDataRegulationController';
7+
8+
/**
9+
* Creates a new delete regulation for the user.
10+
* This is necessary to respect the GDPR and CCPA regulations.
11+
*
12+
* @returns Promise containing the status of the request with regulateId
13+
* @throws Error if analytics ID is missing or if the service call fails
14+
*/
15+
export type AnalyticsDataRegulationControllerCreateDataDeletionTaskAction = {
16+
type: `AnalyticsDataRegulationController:createDataDeletionTask`;
17+
handler: AnalyticsDataRegulationController['createDataDeletionTask'];
18+
};
19+
20+
/**
21+
* Check the latest delete regulation status.
22+
*
23+
* @returns Promise containing the timestamp, delete status and collected data flag
24+
*/
25+
export type AnalyticsDataRegulationControllerCheckDataDeleteStatusAction = {
26+
type: `AnalyticsDataRegulationController:checkDataDeleteStatus`;
27+
handler: AnalyticsDataRegulationController['checkDataDeleteStatus'];
28+
};
29+
30+
/**
31+
* Update the data recording flag if needed.
32+
* This method should be called after tracking events to ensure
33+
* the data recording flag is properly updated for data deletion workflows.
34+
*
35+
* The flag can only be set to `true` (indicating data has been collected).
36+
* It cannot be explicitly set to `false` - it is only reset to `false` when
37+
* a new deletion task is created via `createDataDeletionTask`.
38+
*
39+
*/
40+
export type AnalyticsDataRegulationControllerUpdateDataRecordingFlagAction = {
41+
type: `AnalyticsDataRegulationController:updateDataRecordingFlag`;
42+
handler: AnalyticsDataRegulationController['updateDataRecordingFlag'];
43+
};
44+
45+
/**
46+
* Union of all AnalyticsDataRegulationController action types.
47+
*/
48+
export type AnalyticsDataRegulationControllerMethodActions =
49+
| AnalyticsDataRegulationControllerCreateDataDeletionTaskAction
50+
| AnalyticsDataRegulationControllerCheckDataDeleteStatusAction
51+
| AnalyticsDataRegulationControllerUpdateDataRecordingFlagAction;

0 commit comments

Comments
 (0)