Skip to content

feat: geo-enrich analytics events via GeolocationController - #9691

Merged
gauthierpetetin merged 10 commits into
mainfrom
feat/analytics-geo-enrichment
Jul 30, 2026
Merged

feat: geo-enrich analytics events via GeolocationController#9691
gauthierpetetin merged 10 commits into
mainfrom
feat/analytics-geo-enrichment

Conversation

@gauthierpetetin

@gauthierpetetin gauthierpetetin commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Explanation

Metric events need to carry the user's geolocation (country, region, timezone) before reaching downstream destinations, as decided in ADR-0008. Today Extension events reach Segment unenriched, and the underlying geolocation API only returned a country-level location code. This PR implements the MetaMask-native, client-side enrichment path the ADR selected.

geolocation-controller

  • GeolocationApiService now targets the v2 geolocation endpoint and exposes fetchGeolocationData(), returning a GeolocationData object ({ country, region, timezone }) where each field is independently validated and null when missing/invalid. The existing fetchGeolocation() location-code API is preserved, now derived from the data via a shared toLocationCode() helper. TTL caching and in-flight request deduplication are retained.
  • GeolocationController gains getGeolocationData() and three new (non-persisted) state fields: country, region, timezone.
  • New messenger actions: GeolocationController:getGeolocationData and GeolocationApiService:fetchGeolocationData.

analytics-controller

  • Optionally attaches country_code, region, and timezone to context.location on non-anonymous track, identify, and view payloads. Only fields the API could determine are included, and caller-provided location fields are preserved (resolved fields take precedence).
  • Enrichment is gated behind a new isGeolocationEnabled constructor option (default false), matching the existing isAnonymousEventsFeatureEnabled / isEventQueuePersistenceEnabled / isPreConsentQueueEnabled pattern. When disabled, the controller never calls the geolocation action, so compositions that don't opt in don't need to register it.
  • When enabled, geolocation is resolved once during init — now asynchronous (Promise<void>) — before any queued/pre-consent events are replayed, so replayed events carry the same location as new ones. Resolution is best-effort: failures are logged and events still deliver.
  • Per the ADR, anonymous payloads carry no location.

Both packages' changelogs are updated, and the monorepo dependency graph in the README reflects the new analytics-controller → geolocation-controller edge.

References

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
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

🤖 Generated with Claude Code


Note

Medium Risk
Breaking async init() and new geolocation API semantics affect all analytics consumers; opt-in enrichment touches PII-adjacent context on identified events, with documented wiring order when enabled.

Overview
Geolocation moves to the v2 JSON API: new fetchGeolocationData / getGeolocationData return validated country, region, and timezone (each nullable). Legacy fetchGeolocation / getGeolocation stay best-effort and derive ISO location codes via toLocationCode (US/CA still get region suffix). getGeolocationData rejects on failure so callers avoid stale enrichment. Controller state gains country, region, timezone.

Analytics optionally adds country_code, region, and timezone under context.location on non-anonymous track, identify, and view payloads when isGeolocationEnabled is true (default false). Location is resolved once in async init() (before queue replay) via GeolocationController:getGeolocationData; failures are logged and events still send without location. Anonymous split payloads still omit location; init() is now Promise<void> and deduplicates concurrent calls.

Adds @metamask/geolocation-controller to analytics-controller and documents the new dependency edge in the README.

Reviewed by Cursor Bugbot for commit 06cd92e. Bugbot is set up for automated code reviews on this repo. Configure here.

Implements ADR-0008 (extension/mobile metric geo-enrichment).

GeolocationController now resolves country, region, and timezone from
the v2 geolocation endpoint, exposed via new
GeolocationController:getGeolocationData and
GeolocationApiService:fetchGeolocationData actions (returning a
GeolocationData object) plus matching controller state fields.

AnalyticsController optionally enriches non-anonymous track, identify,
and view payloads with country_code, region, and timezone under
context.location. Enrichment is gated behind a new isGeolocationEnabled
option (default false); when enabled, geolocation is resolved during the
now-async init before queued events are replayed. Anonymous payloads
carry no location, per the ADR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gauthierpetetin gauthierpetetin self-assigned this Jul 29, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gauthierpetetin

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@7.5.5-preview-6cd1bb2f5
@metamask-previews/accounts-controller@39.0.5-preview-6cd1bb2f5
@metamask-previews/address-book-controller@7.1.2-preview-6cd1bb2f5
@metamask-previews/ai-controllers@0.8.0-preview-6cd1bb2f5
@metamask-previews/analytics-controller@1.2.1-preview-6cd1bb2f5
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-6cd1bb2f5
@metamask-previews/announcement-controller@8.1.0-preview-6cd1bb2f5
@metamask-previews/app-metadata-controller@2.0.1-preview-6cd1bb2f5
@metamask-previews/approval-controller@9.0.2-preview-6cd1bb2f5
@metamask-previews/assets-controller@11.2.1-preview-6cd1bb2f5
@metamask-previews/assets-controllers@110.0.0-preview-6cd1bb2f5
@metamask-previews/authenticated-user-storage@3.0.1-preview-6cd1bb2f5
@metamask-previews/base-controller@9.1.0-preview-6cd1bb2f5
@metamask-previews/base-data-service@0.1.3-preview-6cd1bb2f5
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-6cd1bb2f5
@metamask-previews/bridge-controller@78.0.0-preview-6cd1bb2f5
@metamask-previews/bridge-status-controller@74.5.0-preview-6cd1bb2f5
@metamask-previews/build-utils@3.0.4-preview-6cd1bb2f5
@metamask-previews/chain-agnostic-permission@1.7.0-preview-6cd1bb2f5
@metamask-previews/chomp-api-service@4.0.0-preview-6cd1bb2f5
@metamask-previews/claims-controller@0.5.3-preview-6cd1bb2f5
@metamask-previews/client-controller@1.0.1-preview-6cd1bb2f5
@metamask-previews/client-utils@1.2.1-preview-6cd1bb2f5
@metamask-previews/compliance-controller@2.1.0-preview-6cd1bb2f5
@metamask-previews/composable-controller@12.0.1-preview-6cd1bb2f5
@metamask-previews/config-registry-controller@0.4.1-preview-6cd1bb2f5
@metamask-previews/connectivity-controller@0.3.0-preview-6cd1bb2f5
@metamask-previews/controller-utils@12.3.0-preview-6cd1bb2f5
@metamask-previews/core-backend@7.0.0-preview-6cd1bb2f5
@metamask-previews/delegation-controller@3.0.2-preview-6cd1bb2f5
@metamask-previews/earn-controller@12.2.2-preview-6cd1bb2f5
@metamask-previews/eip-5792-middleware@3.0.5-preview-6cd1bb2f5
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-6cd1bb2f5
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-6cd1bb2f5
@metamask-previews/ens-controller@19.1.5-preview-6cd1bb2f5
@metamask-previews/eth-block-tracker@15.0.1-preview-6cd1bb2f5
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-6cd1bb2f5
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-6cd1bb2f5
@metamask-previews/foundryup@1.0.1-preview-6cd1bb2f5
@metamask-previews/gas-fee-controller@26.3.0-preview-6cd1bb2f5
@metamask-previews/gator-permissions-controller@4.2.3-preview-6cd1bb2f5
@metamask-previews/geolocation-controller@0.1.3-preview-6cd1bb2f5
@metamask-previews/java-tron-up@1.0.0-preview-6cd1bb2f5
@metamask-previews/json-rpc-engine@10.5.0-preview-6cd1bb2f5
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-6cd1bb2f5
@metamask-previews/keyring-controller@27.1.0-preview-6cd1bb2f5
@metamask-previews/local-node-utils@1.0.0-preview-6cd1bb2f5
@metamask-previews/logging-controller@8.0.2-preview-6cd1bb2f5
@metamask-previews/message-manager@14.1.2-preview-6cd1bb2f5
@metamask-previews/messenger@2.0.0-preview-6cd1bb2f5
@metamask-previews/messenger-cli@0.2.0-preview-6cd1bb2f5
@metamask-previews/money-account-api-data-service@0.3.0-preview-6cd1bb2f5
@metamask-previews/money-account-balance-service@2.3.0-preview-6cd1bb2f5
@metamask-previews/money-account-controller@0.3.3-preview-6cd1bb2f5
@metamask-previews/money-account-upgrade-controller@3.0.0-preview-6cd1bb2f5
@metamask-previews/multichain-account-service@13.0.0-preview-6cd1bb2f5
@metamask-previews/multichain-api-middleware@4.0.1-preview-6cd1bb2f5
@metamask-previews/multichain-network-controller@3.2.1-preview-6cd1bb2f5
@metamask-previews/multichain-transactions-controller@7.1.1-preview-6cd1bb2f5
@metamask-previews/name-controller@9.1.2-preview-6cd1bb2f5
@metamask-previews/network-connection-banner-controller@0.1.0-preview-6cd1bb2f5
@metamask-previews/network-controller@34.0.0-preview-6cd1bb2f5
@metamask-previews/network-enablement-controller@5.6.0-preview-6cd1bb2f5
@metamask-previews/notification-services-controller@26.0.0-preview-6cd1bb2f5
@metamask-previews/passkey-controller@3.0.0-preview-6cd1bb2f5
@metamask-previews/permission-controller@13.1.1-preview-6cd1bb2f5
@metamask-previews/permission-log-controller@5.1.0-preview-6cd1bb2f5
@metamask-previews/perps-controller@10.0.0-preview-6cd1bb2f5
@metamask-previews/phishing-controller@17.3.0-preview-6cd1bb2f5
@metamask-previews/platform-api-docs@0.0.0-preview-6cd1bb2f5
@metamask-previews/polling-controller@16.0.8-preview-6cd1bb2f5
@metamask-previews/preferences-controller@23.1.0-preview-6cd1bb2f5
@metamask-previews/profile-metrics-controller@4.0.2-preview-6cd1bb2f5
@metamask-previews/profile-sync-controller@28.3.0-preview-6cd1bb2f5
@metamask-previews/ramps-controller@17.2.0-preview-6cd1bb2f5
@metamask-previews/rate-limit-controller@7.0.1-preview-6cd1bb2f5
@metamask-previews/react-data-query@0.2.2-preview-6cd1bb2f5
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-6cd1bb2f5
@metamask-previews/sample-controllers@5.0.3-preview-6cd1bb2f5
@metamask-previews/seedless-onboarding-controller@10.1.0-preview-6cd1bb2f5
@metamask-previews/selected-network-controller@26.1.5-preview-6cd1bb2f5
@metamask-previews/sentinel-api-service@1.0.0-preview-6cd1bb2f5
@metamask-previews/shield-controller@5.1.3-preview-6cd1bb2f5
@metamask-previews/signature-controller@39.2.7-preview-6cd1bb2f5
@metamask-previews/smart-transactions-controller@25.0.1-preview-6cd1bb2f5
@metamask-previews/snap-account-service@2.0.0-preview-6cd1bb2f5
@metamask-previews/social-controllers@2.6.0-preview-6cd1bb2f5
@metamask-previews/solana-test-validator-up@1.0.0-preview-6cd1bb2f5
@metamask-previews/stellar-quickstart-up@0.0.0-preview-6cd1bb2f5
@metamask-previews/storage-service@1.0.2-preview-6cd1bb2f5
@metamask-previews/subscription-controller@6.2.1-preview-6cd1bb2f5
@metamask-previews/transaction-controller@69.2.1-preview-6cd1bb2f5
@metamask-previews/transaction-pay-controller@26.0.0-preview-6cd1bb2f5
@metamask-previews/user-operation-controller@41.2.7-preview-6cd1bb2f5
@metamask-previews/wallet@8.1.0-preview-6cd1bb2f5
@metamask-previews/wallet-cli@0.0.0-preview-6cd1bb2f5

gauthierpetetin and others added 3 commits July 29, 2026 14:54
…nit ordering

When geolocation enrichment is enabled, AnalyticsController resolves the
location during init via GeolocationController:getGeolocationData. If the
GeolocationController is not registered/initialized first, that call fails
and enrichment is skipped for the session.

- Log an actionable message on resolution failure naming the likely cause
  (init ordering) instead of a generic one, so the otherwise-silent failure
  is diagnosable.
- Document the ordering requirement in the init() JSDoc and changelog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The v2 geolocation endpoint returns country and region separately and never
concatenates them. The legacy v1 endpoint appended the region only for the US
and Canada (e.g. US-NY, CA-ON) and returned the country alone elsewhere.

Update toLocationCode so the location code derived for
getGeolocation/fetchGeolocation preserves that v1 behavior: append the region
only for US and CA, return the country code alone for all other countries even
when a region is known. The separate country/region/timezone fields are
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gauthierpetetin

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

pull Bot pushed a commit to dmrazzy/core that referenced this pull request Jul 29, 2026
## Explanation

analytics-controller is enrolled in `lint:tsc` (it has a
`tsconfig.lint.json` and is listed in the root `tsconfig.lint.json`).
Once it starts importing geolocation-controller and controller-utils
(the geo enrichment work in MetaMask#9691), and neither of those is a `lint:tsc`
project yet, tsc pulls their source straight into the
analytics-controller project and fails with rootDir errors:

```
error TS6059: File '.../controller-utils/src/types.ts' is not under 'rootDir' '.../analytics-controller'
error TS6307: File '.../controller-utils/src/types.ts' is not listed within the file list of project '.../analytics-controller/tsconfig.lint.json'
```

This turns the `lint:tsc` job red on MetaMask#9691 and blocks the work.

This PR removes `packages/analytics-controller/tsconfig.lint.json` and
its entry in the root `tsconfig.lint.json`, so analytics-controller is
no longer type checked by `lint:tsc`. `yarn lint:tsc` is green with this
change.

This is a stopgap to unblock the current work, not the end state. The
proper fix is to make controller-utils and geolocation-controller
`lint:tsc` projects (give them their own `tsconfig.lint.json`) and
enroll analytics-controller again. That is a bigger change because it
also turns on type checking for those packages' test files, which
surfaces separate pre existing type errors that need their own cleanup.

## References

- Unblocks MetaMask#9691

## 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](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] 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
@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@7.5.5-preview-2296abeaf
@metamask-previews/accounts-controller@39.0.5-preview-2296abeaf
@metamask-previews/address-book-controller@7.1.2-preview-2296abeaf
@metamask-previews/ai-controllers@0.8.0-preview-2296abeaf
@metamask-previews/analytics-controller@1.2.1-preview-2296abeaf
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-2296abeaf
@metamask-previews/announcement-controller@8.1.0-preview-2296abeaf
@metamask-previews/app-metadata-controller@2.0.1-preview-2296abeaf
@metamask-previews/approval-controller@9.0.2-preview-2296abeaf
@metamask-previews/assets-controller@11.3.0-preview-2296abeaf
@metamask-previews/assets-controllers@110.0.1-preview-2296abeaf
@metamask-previews/authenticated-user-storage@3.0.1-preview-2296abeaf
@metamask-previews/base-controller@9.1.0-preview-2296abeaf
@metamask-previews/base-data-service@0.1.3-preview-2296abeaf
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-2296abeaf
@metamask-previews/bridge-controller@78.0.1-preview-2296abeaf
@metamask-previews/bridge-status-controller@74.6.0-preview-2296abeaf
@metamask-previews/build-utils@3.0.4-preview-2296abeaf
@metamask-previews/chain-agnostic-permission@1.7.0-preview-2296abeaf
@metamask-previews/chomp-api-service@4.0.0-preview-2296abeaf
@metamask-previews/claims-controller@0.5.3-preview-2296abeaf
@metamask-previews/client-controller@1.0.1-preview-2296abeaf
@metamask-previews/client-utils@1.3.1-preview-2296abeaf
@metamask-previews/compliance-controller@2.1.0-preview-2296abeaf
@metamask-previews/composable-controller@12.0.1-preview-2296abeaf
@metamask-previews/config-registry-controller@0.4.1-preview-2296abeaf
@metamask-previews/connectivity-controller@0.3.0-preview-2296abeaf
@metamask-previews/controller-utils@12.3.0-preview-2296abeaf
@metamask-previews/core-backend@8.0.0-preview-2296abeaf
@metamask-previews/delegation-controller@3.0.2-preview-2296abeaf
@metamask-previews/earn-controller@12.2.2-preview-2296abeaf
@metamask-previews/eip-5792-middleware@3.0.5-preview-2296abeaf
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-2296abeaf
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-2296abeaf
@metamask-previews/ens-controller@19.1.5-preview-2296abeaf
@metamask-previews/eth-block-tracker@15.0.1-preview-2296abeaf
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-2296abeaf
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-2296abeaf
@metamask-previews/foundryup@1.0.1-preview-2296abeaf
@metamask-previews/gas-fee-controller@26.3.0-preview-2296abeaf
@metamask-previews/gator-permissions-controller@4.2.3-preview-2296abeaf
@metamask-previews/geolocation-controller@0.1.3-preview-2296abeaf
@metamask-previews/java-tron-up@1.0.0-preview-2296abeaf
@metamask-previews/json-rpc-engine@10.5.0-preview-2296abeaf
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-2296abeaf
@metamask-previews/keyring-controller@27.1.0-preview-2296abeaf
@metamask-previews/local-node-utils@1.0.0-preview-2296abeaf
@metamask-previews/logging-controller@8.0.2-preview-2296abeaf
@metamask-previews/message-manager@14.1.2-preview-2296abeaf
@metamask-previews/messenger@2.0.0-preview-2296abeaf
@metamask-previews/messenger-cli@0.2.0-preview-2296abeaf
@metamask-previews/money-account-api-data-service@0.4.0-preview-2296abeaf
@metamask-previews/money-account-balance-service@2.3.0-preview-2296abeaf
@metamask-previews/money-account-controller@0.3.3-preview-2296abeaf
@metamask-previews/money-account-upgrade-controller@3.0.0-preview-2296abeaf
@metamask-previews/multichain-account-service@13.0.0-preview-2296abeaf
@metamask-previews/multichain-api-middleware@4.0.1-preview-2296abeaf
@metamask-previews/multichain-network-controller@3.2.1-preview-2296abeaf
@metamask-previews/multichain-transactions-controller@7.1.1-preview-2296abeaf
@metamask-previews/name-controller@9.1.2-preview-2296abeaf
@metamask-previews/network-connection-banner-controller@0.1.0-preview-2296abeaf
@metamask-previews/network-controller@34.0.0-preview-2296abeaf
@metamask-previews/network-enablement-controller@5.6.0-preview-2296abeaf
@metamask-previews/notification-services-controller@26.0.0-preview-2296abeaf
@metamask-previews/passkey-controller@3.0.0-preview-2296abeaf
@metamask-previews/permission-controller@13.1.1-preview-2296abeaf
@metamask-previews/permission-log-controller@5.1.0-preview-2296abeaf
@metamask-previews/perps-controller@10.0.0-preview-2296abeaf
@metamask-previews/phishing-controller@17.3.0-preview-2296abeaf
@metamask-previews/platform-api-docs@0.0.0-preview-2296abeaf
@metamask-previews/polling-controller@16.0.8-preview-2296abeaf
@metamask-previews/preferences-controller@23.1.0-preview-2296abeaf
@metamask-previews/profile-metrics-controller@4.0.2-preview-2296abeaf
@metamask-previews/profile-sync-controller@28.3.0-preview-2296abeaf
@metamask-previews/ramps-controller@18.0.0-preview-2296abeaf
@metamask-previews/rate-limit-controller@7.0.1-preview-2296abeaf
@metamask-previews/react-data-query@0.2.2-preview-2296abeaf
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-2296abeaf
@metamask-previews/sample-controllers@5.0.3-preview-2296abeaf
@metamask-previews/seedless-onboarding-controller@10.1.0-preview-2296abeaf
@metamask-previews/selected-network-controller@26.1.5-preview-2296abeaf
@metamask-previews/sentinel-api-service@1.0.0-preview-2296abeaf
@metamask-previews/shield-controller@5.1.3-preview-2296abeaf
@metamask-previews/signature-controller@39.2.7-preview-2296abeaf
@metamask-previews/smart-transactions-controller@25.0.1-preview-2296abeaf
@metamask-previews/snap-account-service@2.0.0-preview-2296abeaf
@metamask-previews/social-controllers@2.7.0-preview-2296abeaf
@metamask-previews/solana-test-validator-up@1.0.0-preview-2296abeaf
@metamask-previews/stellar-quickstart-up@0.0.0-preview-2296abeaf
@metamask-previews/storage-service@1.0.2-preview-2296abeaf
@metamask-previews/subscription-controller@6.2.1-preview-2296abeaf
@metamask-previews/transaction-controller@69.3.0-preview-2296abeaf
@metamask-previews/transaction-pay-controller@26.0.1-preview-2296abeaf
@metamask-previews/user-operation-controller@41.2.7-preview-2296abeaf
@metamask-previews/wallet@8.1.0-preview-2296abeaf
@metamask-previews/wallet-cli@0.0.0-preview-2296abeaf

@gauthierpetetin
gauthierpetetin marked this pull request as ready for review July 29, 2026 22:09
@gauthierpetetin
gauthierpetetin requested review from a team as code owners July 29, 2026 22:09
@gauthierpetetin
gauthierpetetin requested a review from Copilot July 29, 2026 22:10

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2296abe. Configure here.

Comment thread packages/analytics-controller/src/AnalyticsController.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR implements client-side geolocation enrichment for analytics events by upgrading @metamask/geolocation-controller to consume the /v2/geolocation API (returning { country, region, timezone }) and adding an opt-in enrichment path in @metamask/analytics-controller that attaches those fields under context.location for non-anonymous payloads.

Changes:

  • Add GeolocationData support (v2 endpoint), new service/controller actions, and controller state fields for country, region, and timezone while preserving the legacy location-code API via toLocationCode.
  • Add optional geolocation enrichment to analytics (gated by isGeolocationEnabled, default false) and make AnalyticsController.init asynchronous to resolve location before replaying queued/pre-consent events.
  • Wire up dependency/reference graph updates (dependency, TS project references, README graph) and update package changelogs.

Reviewed changes

Copilot reviewed 17 out of 18 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
yarn.lock Adds @metamask/geolocation-controller dependency to the lockfile.
README.md Updates monorepo dependency graph to include analytics-controller → geolocation-controller.
packages/geolocation-controller/src/index.ts Exports new geolocation data types/helpers and new action types.
packages/geolocation-controller/src/GeolocationController.ts Adds getGeolocationData and non-persisted state fields for country/region/timezone; switches controller to call the new service action.
packages/geolocation-controller/src/GeolocationController.test.ts Updates controller tests for v2 data shape and adds coverage for new method/state behavior.
packages/geolocation-controller/src/GeolocationController-method-action-types.ts Adds the GeolocationController:getGeolocationData messenger action type.
packages/geolocation-controller/src/geolocation-api-service/geolocation-api-service.ts Switches to /v2/geolocation, adds parsing/validation for { country, region, timezone }, and keeps legacy location-code behavior via toLocationCode.
packages/geolocation-controller/src/geolocation-api-service/geolocation-api-service.test.ts Updates service tests for v2 JSON responses and adds validation/caching/dedup coverage for new API.
packages/geolocation-controller/src/geolocation-api-service/geolocation-api-service-method-action-types.ts Adds the GeolocationApiService:fetchGeolocationData action type and union update.
packages/geolocation-controller/CHANGELOG.md Documents the breaking v2 geolocation data/actions/state changes.
packages/analytics-controller/tsconfig.json Adds TS project reference to geolocation-controller.
packages/analytics-controller/tsconfig.build.json Adds build-time TS project reference to geolocation-controller.
packages/analytics-controller/src/index.ts Exports new location-context type aliases.
packages/analytics-controller/src/AnalyticsPlatformAdapter.types.ts Adds AnalyticsLocationField and AnalyticsLocationContext types for context.location.
packages/analytics-controller/src/AnalyticsController.ts Adds opt-in geolocation enrichment, stores resolved location once per session, and makes init() async to resolve before queue replay.
packages/analytics-controller/src/AnalyticsController.test.ts Updates tests for async init and adds geolocation-enrichment behavior tests.
packages/analytics-controller/package.json Adds @metamask/geolocation-controller dependency.
packages/analytics-controller/CHANGELOG.md Documents breaking async init and optional enrichment behavior/dependency.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Previously GeolocationApiService only cached a fetched response when the
country was non-null. With the v2 response shape a response can be
partially known (e.g. a valid timezone without a valid country), which
was left uncached and re-fetched on every call within the TTL window.

Cache whenever at least one of country/region/timezone resolved; only a
fully-unknown response stays uncached so it can be retried.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gauthierpetetin

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@7.5.5-preview-d67bfdbad
@metamask-previews/accounts-controller@39.0.5-preview-d67bfdbad
@metamask-previews/address-book-controller@7.1.2-preview-d67bfdbad
@metamask-previews/ai-controllers@0.8.0-preview-d67bfdbad
@metamask-previews/analytics-controller@1.2.1-preview-d67bfdbad
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-d67bfdbad
@metamask-previews/announcement-controller@8.1.0-preview-d67bfdbad
@metamask-previews/app-metadata-controller@2.0.1-preview-d67bfdbad
@metamask-previews/approval-controller@9.0.2-preview-d67bfdbad
@metamask-previews/assets-controller@11.3.0-preview-d67bfdbad
@metamask-previews/assets-controllers@110.0.1-preview-d67bfdbad
@metamask-previews/authenticated-user-storage@3.0.1-preview-d67bfdbad
@metamask-previews/base-controller@9.1.0-preview-d67bfdbad
@metamask-previews/base-data-service@0.1.3-preview-d67bfdbad
@metamask-previews/bitcoin-regtest-up@1.0.0-preview-d67bfdbad
@metamask-previews/bridge-controller@78.0.1-preview-d67bfdbad
@metamask-previews/bridge-status-controller@74.6.0-preview-d67bfdbad
@metamask-previews/build-utils@3.0.4-preview-d67bfdbad
@metamask-previews/chain-agnostic-permission@1.7.0-preview-d67bfdbad
@metamask-previews/chomp-api-service@4.0.0-preview-d67bfdbad
@metamask-previews/claims-controller@0.5.3-preview-d67bfdbad
@metamask-previews/client-controller@1.0.1-preview-d67bfdbad
@metamask-previews/client-utils@1.3.1-preview-d67bfdbad
@metamask-previews/compliance-controller@2.1.0-preview-d67bfdbad
@metamask-previews/composable-controller@12.0.1-preview-d67bfdbad
@metamask-previews/config-registry-controller@0.4.1-preview-d67bfdbad
@metamask-previews/connectivity-controller@0.3.0-preview-d67bfdbad
@metamask-previews/controller-utils@12.3.0-preview-d67bfdbad
@metamask-previews/core-backend@8.0.0-preview-d67bfdbad
@metamask-previews/delegation-controller@3.0.2-preview-d67bfdbad
@metamask-previews/earn-controller@12.2.2-preview-d67bfdbad
@metamask-previews/eip-5792-middleware@3.0.5-preview-d67bfdbad
@metamask-previews/eip-7702-internal-rpc-middleware@0.1.1-preview-d67bfdbad
@metamask-previews/eip1193-permission-middleware@2.0.1-preview-d67bfdbad
@metamask-previews/ens-controller@19.1.5-preview-d67bfdbad
@metamask-previews/eth-block-tracker@15.0.1-preview-d67bfdbad
@metamask-previews/eth-json-rpc-middleware@23.1.3-preview-d67bfdbad
@metamask-previews/eth-json-rpc-provider@6.0.1-preview-d67bfdbad
@metamask-previews/foundryup@1.0.1-preview-d67bfdbad
@metamask-previews/gas-fee-controller@26.3.0-preview-d67bfdbad
@metamask-previews/gator-permissions-controller@4.2.3-preview-d67bfdbad
@metamask-previews/geolocation-controller@0.1.3-preview-d67bfdbad
@metamask-previews/java-tron-up@1.0.0-preview-d67bfdbad
@metamask-previews/json-rpc-engine@10.5.0-preview-d67bfdbad
@metamask-previews/json-rpc-middleware-stream@8.0.8-preview-d67bfdbad
@metamask-previews/keyring-controller@27.1.0-preview-d67bfdbad
@metamask-previews/local-node-utils@1.0.0-preview-d67bfdbad
@metamask-previews/logging-controller@8.0.2-preview-d67bfdbad
@metamask-previews/message-manager@14.1.2-preview-d67bfdbad
@metamask-previews/messenger@2.0.0-preview-d67bfdbad
@metamask-previews/messenger-cli@0.2.0-preview-d67bfdbad
@metamask-previews/money-account-api-data-service@0.4.0-preview-d67bfdbad
@metamask-previews/money-account-balance-service@2.3.0-preview-d67bfdbad
@metamask-previews/money-account-controller@0.3.3-preview-d67bfdbad
@metamask-previews/money-account-upgrade-controller@3.0.0-preview-d67bfdbad
@metamask-previews/multichain-account-service@13.0.0-preview-d67bfdbad
@metamask-previews/multichain-api-middleware@4.0.1-preview-d67bfdbad
@metamask-previews/multichain-network-controller@3.2.1-preview-d67bfdbad
@metamask-previews/multichain-transactions-controller@7.1.1-preview-d67bfdbad
@metamask-previews/name-controller@9.1.2-preview-d67bfdbad
@metamask-previews/network-connection-banner-controller@0.1.0-preview-d67bfdbad
@metamask-previews/network-controller@34.0.0-preview-d67bfdbad
@metamask-previews/network-enablement-controller@5.6.0-preview-d67bfdbad
@metamask-previews/notification-services-controller@26.0.0-preview-d67bfdbad
@metamask-previews/passkey-controller@3.0.0-preview-d67bfdbad
@metamask-previews/permission-controller@13.1.1-preview-d67bfdbad
@metamask-previews/permission-log-controller@5.1.0-preview-d67bfdbad
@metamask-previews/perps-controller@10.0.0-preview-d67bfdbad
@metamask-previews/phishing-controller@17.3.0-preview-d67bfdbad
@metamask-previews/platform-api-docs@0.0.0-preview-d67bfdbad
@metamask-previews/polling-controller@16.0.8-preview-d67bfdbad
@metamask-previews/preferences-controller@23.1.0-preview-d67bfdbad
@metamask-previews/profile-metrics-controller@4.0.2-preview-d67bfdbad
@metamask-previews/profile-sync-controller@28.3.0-preview-d67bfdbad
@metamask-previews/ramps-controller@18.0.0-preview-d67bfdbad
@metamask-previews/rate-limit-controller@7.0.1-preview-d67bfdbad
@metamask-previews/react-data-query@0.2.2-preview-d67bfdbad
@metamask-previews/remote-feature-flag-controller@4.2.2-preview-d67bfdbad
@metamask-previews/sample-controllers@5.0.3-preview-d67bfdbad
@metamask-previews/seedless-onboarding-controller@10.1.0-preview-d67bfdbad
@metamask-previews/selected-network-controller@26.1.5-preview-d67bfdbad
@metamask-previews/sentinel-api-service@1.0.0-preview-d67bfdbad
@metamask-previews/shield-controller@5.1.3-preview-d67bfdbad
@metamask-previews/signature-controller@39.2.7-preview-d67bfdbad
@metamask-previews/smart-transactions-controller@25.0.1-preview-d67bfdbad
@metamask-previews/snap-account-service@2.0.0-preview-d67bfdbad
@metamask-previews/social-controllers@2.7.0-preview-d67bfdbad
@metamask-previews/solana-test-validator-up@1.0.0-preview-d67bfdbad
@metamask-previews/stellar-quickstart-up@0.0.0-preview-d67bfdbad
@metamask-previews/storage-service@1.0.2-preview-d67bfdbad
@metamask-previews/subscription-controller@6.2.1-preview-d67bfdbad
@metamask-previews/transaction-controller@69.3.0-preview-d67bfdbad
@metamask-previews/transaction-pay-controller@26.0.1-preview-d67bfdbad
@metamask-previews/user-operation-controller@41.2.7-preview-d67bfdbad
@metamask-previews/wallet@8.1.0-preview-d67bfdbad
@metamask-previews/wallet-cli@0.0.0-preview-d67bfdbad

Comment thread packages/geolocation-controller/src/GeolocationController.ts
gauthierpetetin and others added 2 commits July 30, 2026 13:16
Previously `init()` set an `#initialized` flag before awaiting geolocation
resolution, onSetupCompleted, and queue replay, so an overlapping caller
could observe a completed init while that work was still pending, and early
events could miss enrichment.

Cache the initialization promise instead: `init()` runs the work once (in a
private `#performInit`) and returns the same in-flight (or settled) promise
on subsequent/overlapping calls. `init` is now a non-async method that
returns the cached `Promise<void>` directly. onSetupCompleted still runs at
most once.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tead of returning stale data

Previously a failed lookup was swallowed and the last-known country/region/
timezone was returned, so callers could not distinguish a fresh result from a
failure and might enrich with a previous session's location.

getGeolocationData now rejects when the service fails (after recording the
error in state), letting callers omit location on failure. The legacy
getGeolocation/refreshGeolocation string APIs keep their best-effort behavior:
they catch and return the last-known location code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The getGeolocationData failure-behavior JSDoc changes were not mirrored into
the generated method-action-types file, failing messenger-action-types:check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@NicolasMassart NicolasMassart left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me.

@gauthierpetetin
gauthierpetetin added this pull request to the merge queue Jul 30, 2026
Merged via the queue into main with commit de3190e Jul 30, 2026
210 of 211 checks passed
@gauthierpetetin
gauthierpetetin deleted the feat/analytics-geo-enrichment branch July 30, 2026 13:38
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.

3 participants