@metamask-previews/geolocation-controller 2.0.0-preview-9fd631f7d → 2.0.0-preview-d0145b59e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -17,9 +17,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
17
17
  - **BREAKING:** `GeolocationApiService` now extends `BaseDataService` from `@metamask/base-data-service` ([#10881](https://github.com/MetaMask/core/pull/10881))
18
18
  - `GeolocationApiServiceMessenger` now includes the `GeolocationApiService:invalidateQueries` action and `GeolocationApiService:cacheUpdated` events. Messengers built from `GeolocationApiServiceActions` / `GeolocationApiServiceEvents` pick these up automatically.
19
19
  - The hand-rolled TTL cache and in-flight deduplication are replaced by the `BaseDataService` query cache. `ttlMs` and `bypassCache` keep their existing behaviour.
20
- - `GeolocationController` now derives `status`, `error` and `lastFetchedAt` from the state of the service's query ([#11167](https://github.com/MetaMask/core/pull/11167))
21
- - `lastFetchedAt` is now the time the cached data was fetched rather than the time the controller received it.
22
- - Add `onQueryStateChange` to `FetchGeolocationOptions`, accepted by `GeolocationApiService:fetchGeolocation` and `GeolocationApiService:fetchGeolocationData` ([#11167](https://github.com/MetaMask/core/pull/11167))
23
20
  - Add dependency `@metamask/base-data-service` ([#10881](https://github.com/MetaMask/core/pull/10881))
24
21
  - Add dependency `@metamask/superstruct` ([#10881](https://github.com/MetaMask/core/pull/10881))
25
22
  - Bump `@metamask/base-controller` from `^10.0.0` to `^10.1.0` ([#10864](https://github.com/MetaMask/core/pull/10864))
@@ -1 +1 @@
1
- {"version":3,"file":"GeolocationController.d.ts","sourceRoot":"","sources":["../src/GeolocationController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,+CAA+C,EAAE,MAAM,0EAA0E,CAAC;AAChJ,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sDAAsD,CAAC;AAM5F,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,gDAAgD,CAAC;AACzG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;GAIG;AACH,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yGAAyG;IACzG,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,wBAAwB,CAAC;IACjC,iFAAiF;IACjF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAkDF;;;;;;;GAOG;AACH,wBAAgB,oCAAoC,IAAI,0BAA0B,CAQjF;AAQD;;GAEG;AACH,MAAM,MAAM,mCAAmC,GAAG,wBAAwB,CACxE,OAAO,cAAc,EACrB,0BAA0B,CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GACpC,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC;;GAEG;AACH,KAAK,cAAc,GAAG,+CAA+C,CAAC;AAEtE;;GAEG;AACH,MAAM,MAAM,qCAAqC,GAAG,0BAA0B,CAC5E,OAAO,cAAc,EACrB,0BAA0B,CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,qCAAqC,CAAC;AAEhF;;;GAGG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,SAAS,CACpD,OAAO,cAAc,EACrB,4BAA4B,GAAG,cAAc,EAC7C,2BAA2B,GAAG,aAAa,CAC5C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,wDAAwD;IACxD,SAAS,EAAE,8BAA8B,CAAC;IAC1C,sCAAsC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC7C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,SAAQ,cAAc,CACvD,OAAO,cAAc,EACrB,0BAA0B,EAC1B,8BAA8B,CAC/B;;IACC;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,4BAA4B,EAY7D;IAED;;;;;;;;;OASG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAOtC;IAED;;;;;;;;;;;;;OAaG;IACG,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC,CAEnD;IAED;;;;;;;OAOG;IACG,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAU1C;CA+DF"}
1
+ {"version":3,"file":"GeolocationController.d.ts","sourceRoot":"","sources":["../src/GeolocationController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,+CAA+C,EAAE,MAAM,0EAA0E,CAAC;AAChJ,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sDAAsD,CAAC;AAM5F,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,gDAAgD,CAAC;AACzG,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;GAIG;AACH,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yGAAyG;IACzG,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yDAAyD;IACzD,MAAM,EAAE,wBAAwB,CAAC;IACjC,iFAAiF;IACjF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,4DAA4D;IAC5D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AAkDF;;;;;;;GAOG;AACH,wBAAgB,oCAAoC,IAAI,0BAA0B,CAQjF;AAQD;;GAEG;AACH,MAAM,MAAM,mCAAmC,GAAG,wBAAwB,CACxE,OAAO,cAAc,EACrB,0BAA0B,CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GACpC,mCAAmC,GACnC,kCAAkC,CAAC;AAEvC;;GAEG;AACH,KAAK,cAAc,GAAG,+CAA+C,CAAC;AAEtE;;GAEG;AACH,MAAM,MAAM,qCAAqC,GAAG,0BAA0B,CAC5E,OAAO,cAAc,EACrB,0BAA0B,CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,qCAAqC,CAAC;AAEhF;;;GAGG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,SAAS,CACpD,OAAO,cAAc,EACrB,4BAA4B,GAAG,cAAc,EAC7C,2BAA2B,GAAG,aAAa,CAC5C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,wDAAwD;IACxD,SAAS,EAAE,8BAA8B,CAAC;IAC1C,sCAAsC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;CAC7C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,SAAQ,cAAc,CACvD,OAAO,cAAc,EACrB,0BAA0B,EAC1B,8BAA8B,CAC/B;;IACC;;;;;;;OAOG;IACH,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,4BAA4B,EAY7D;IAED;;;;;;;;;OASG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAOtC;IAED;;;;;;;;;;;;;OAaG;IACG,kBAAkB,IAAI,OAAO,CAAC,eAAe,CAAC,CAEnD;IAED;;;;;;;OAOG;IACG,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAU1C;CAgDF"}
@@ -161,56 +161,40 @@ export class GeolocationController extends BaseController {
161
161
  return this.state.location;
162
162
  }
163
163
  /**
164
- * Calls the geolocation service, deriving controller state from the state
165
- * of the service's query as it changes.
164
+ * Calls the geolocation service and updates controller state with the
165
+ * result.
166
166
  *
167
167
  * @param options - Options forwarded to the service.
168
168
  * @param options.bypassCache - When true, the service skips its TTL cache.
169
169
  * @returns The resolved geolocation data.
170
- * @throws Re-throws the service error (already recorded in state), so
170
+ * @throws Re-throws the service error after recording it in state, so
171
171
  * callers can react to a failed lookup instead of receiving a stale value.
172
172
  */
173
173
  async #fetchAndUpdate(options) {
174
- return await this.messenger.call('GeolocationApiService:fetchGeolocationData', {
175
- ...options,
176
- onQueryStateChange: (queryState) => this.#applyQueryState(queryState),
174
+ this.update((draft) => {
175
+ draft.status = 'loading';
176
+ draft.error = null;
177
177
  });
178
- }
179
- /**
180
- * Maps the state of the service's query onto controller state.
181
- *
182
- * @param queryState - The state of the geolocation query.
183
- */
184
- #applyQueryState(queryState) {
185
- // A fetch paused while offline still counts as loading.
186
- if (queryState.fetchStatus !== 'idle') {
187
- this.update((draft) => {
188
- draft.status = 'loading';
189
- draft.error = null;
190
- });
191
- return;
192
- }
193
- if (queryState.error) {
194
- const { message } = queryState.error;
195
- this.update((draft) => {
196
- draft.status = 'error';
197
- draft.error = message;
198
- });
199
- return;
200
- }
201
- // An invalidated entry is about to be refetched (for instance by
202
- // `refreshGeolocation`), so its old data and `dataUpdatedAt` are skipped.
203
- if (queryState.status === 'success' && !queryState.isInvalidated) {
204
- const geolocation = queryState.data;
178
+ try {
179
+ const geolocation = await this.messenger.call('GeolocationApiService:fetchGeolocationData', options);
205
180
  this.update((draft) => {
206
181
  draft.location = toLocationCode(geolocation);
207
182
  draft.country = geolocation.country;
208
183
  draft.region = geolocation.region;
209
184
  draft.timezone = geolocation.timezone;
210
185
  draft.status = 'complete';
211
- draft.lastFetchedAt = queryState.dataUpdatedAt;
186
+ draft.lastFetchedAt = Date.now();
212
187
  draft.error = null;
213
188
  });
189
+ return geolocation;
190
+ }
191
+ catch (error) {
192
+ const message = error instanceof Error ? error.message : String(error);
193
+ this.update((draft) => {
194
+ draft.status = 'error';
195
+ draft.error = message;
196
+ });
197
+ throw error;
214
198
  }
215
199
  }
216
200
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GeolocationController.js","sourceRoot":"","sources":["../src/GeolocationController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { DataServiceQueryState } from '@metamask/base-data-service';\nimport type { Messenger } from '@metamask/messenger';\n\nimport type { GeolocationApiServiceFetchGeolocationDataAction } from './geolocation-api-service/geolocation-api-service-method-action-types.js';\nimport type { GeolocationData } from './geolocation-api-service/geolocation-api-service.js';\nimport {\n getUnknownGeolocationData,\n toLocationCode,\n UNKNOWN_LOCATION,\n} from './geolocation-api-service/geolocation-api-service.js';\nimport type { GeolocationControllerMethodActions } from './GeolocationController-method-action-types.js';\nimport type { GeolocationRequestStatus } from './types.js';\n\n/**\n * The name of the {@link GeolocationController}, used to namespace the\n * controller's actions and events and to namespace the controller's state data\n * when composed with other controllers.\n */\nexport const controllerName = 'GeolocationController';\n\n/**\n * State for the {@link GeolocationController}.\n */\nexport type GeolocationControllerState = {\n /** ISO 3166-2 location code (e.g. \"US\", \"US-NY\", \"CA-ON\"), or \"UNKNOWN\" if not yet determined. */\n location: string;\n /** ISO 3166-1 alpha-2 country code (e.g. \"US\"), or null if not yet determined. */\n country: string | null;\n /** ISO 3166-2 subdivision code without the country prefix (e.g. \"NY\"), or null if not yet determined. */\n region: string | null;\n /** IANA time zone name (e.g. \"America/Los_Angeles\"), or null if not yet determined. */\n timezone: string | null;\n /** Current status of the geolocation fetch lifecycle. */\n status: GeolocationRequestStatus;\n /** Epoch milliseconds of the last successful fetch, or null if never fetched. */\n lastFetchedAt: number | null;\n /** Last error message, or null if no error has occurred. */\n error: string | null;\n};\n\n/**\n * The metadata for each property in {@link GeolocationControllerState}.\n */\nconst geolocationControllerMetadata = {\n location: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n country: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n region: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n timezone: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n status: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n lastFetchedAt: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n error: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n} satisfies StateMetadata<GeolocationControllerState>;\n\n/**\n * Constructs the default {@link GeolocationController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link GeolocationController} state.\n */\nexport function getDefaultGeolocationControllerState(): GeolocationControllerState {\n return {\n location: UNKNOWN_LOCATION,\n ...getUnknownGeolocationData(),\n status: 'idle',\n lastFetchedAt: null,\n error: null,\n };\n}\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'getGeolocation',\n 'getGeolocationData',\n 'refreshGeolocation',\n] as const;\n\n/**\n * Retrieves the state of the {@link GeolocationController}.\n */\nexport type GeolocationControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n GeolocationControllerState\n>;\n\n/**\n * Actions that {@link GeolocationControllerMessenger} exposes to other consumers.\n */\nexport type GeolocationControllerActions =\n | GeolocationControllerGetStateAction\n | GeolocationControllerMethodActions;\n\n/**\n * Actions from other messengers that {@link GeolocationControllerMessenger} calls.\n */\ntype AllowedActions = GeolocationApiServiceFetchGeolocationDataAction;\n\n/**\n * Published when the state of {@link GeolocationController} changes.\n */\nexport type GeolocationControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n GeolocationControllerState\n>;\n\n/**\n * Events that {@link GeolocationControllerMessenger} exposes to other consumers.\n */\nexport type GeolocationControllerEvents = GeolocationControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link GeolocationControllerMessenger}\n * subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link GeolocationController}.\n */\nexport type GeolocationControllerMessenger = Messenger<\n typeof controllerName,\n GeolocationControllerActions | AllowedActions,\n GeolocationControllerEvents | AllowedEvents\n>;\n\n/**\n * Options for constructing the {@link GeolocationController}.\n */\nexport type GeolocationControllerOptions = {\n /** The messenger for inter-controller communication. */\n messenger: GeolocationControllerMessenger;\n /** Optional partial initial state. */\n state?: Partial<GeolocationControllerState>;\n};\n\n/**\n * GeolocationController manages UI-facing geolocation state by delegating\n * the actual API interaction to {@link GeolocationApiService} via the\n * messenger.\n *\n * The service (registered externally as\n * `GeolocationApiService:fetchGeolocation`) handles HTTP requests, response\n * validation, TTL caching, and promise deduplication. This controller focuses\n * on state lifecycle (`idle` -> `loading` -> `complete` | `error`) and\n * exposes `getGeolocation` / `refreshGeolocation` as messenger actions.\n */\nexport class GeolocationController extends BaseController<\n typeof controllerName,\n GeolocationControllerState,\n GeolocationControllerMessenger\n> {\n /**\n * Constructs a new {@link GeolocationController}.\n *\n * @param args - The arguments to this controller.\n * @param args.messenger - The messenger suited for this controller. Must\n * have a `GeolocationApiService:fetchGeolocation` action handler registered.\n * @param args.state - Optional partial initial state.\n */\n constructor({ messenger, state }: GeolocationControllerOptions) {\n super({\n messenger,\n metadata: geolocationControllerMetadata,\n name: controllerName,\n state: { ...getDefaultGeolocationControllerState(), ...state },\n });\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Returns the geolocation code. Delegates to the\n * {@link GeolocationApiService} for network fetching and caching, then\n * updates controller state with the result.\n *\n * Best-effort: if the fetch fails, the last known location code (or\n * {@link UNKNOWN_LOCATION}) is returned rather than throwing.\n *\n * @returns The ISO 3166-2 location code string.\n */\n async getGeolocation(): Promise<string> {\n try {\n await this.#fetchAndUpdate();\n } catch {\n // Best-effort: fall back to the last known location code below.\n }\n return this.state.location;\n }\n\n /**\n * Returns the country, region, and timezone for the current client.\n * Delegates to the {@link GeolocationApiService} for network fetching and\n * caching, then updates controller state with the result.\n *\n * Unlike {@link getGeolocation}, this rejects when resolution fails instead\n * of returning a stale value, so callers can distinguish a fresh result from\n * a failed lookup (and, for example, omit location rather than enrich with a\n * previous session's data).\n *\n * @returns The geolocation data, where each field is `null` when it could\n * not be determined.\n * @throws When the geolocation service fails to resolve.\n */\n async getGeolocationData(): Promise<GeolocationData> {\n return this.#fetchAndUpdate();\n }\n\n /**\n * Forces a fresh geolocation fetch, bypassing the service's cache.\n *\n * Best-effort: if the fetch fails, the last known location code (or\n * {@link UNKNOWN_LOCATION}) is returned rather than throwing.\n *\n * @returns The ISO 3166-2 location code string.\n */\n async refreshGeolocation(): Promise<string> {\n this.update((draft) => {\n draft.lastFetchedAt = null;\n });\n try {\n await this.#fetchAndUpdate({ bypassCache: true });\n } catch {\n // Best-effort: fall back to the last known location code below.\n }\n return this.state.location;\n }\n\n /**\n * Calls the geolocation service, deriving controller state from the state\n * of the service's query as it changes.\n *\n * @param options - Options forwarded to the service.\n * @param options.bypassCache - When true, the service skips its TTL cache.\n * @returns The resolved geolocation data.\n * @throws Re-throws the service error (already recorded in state), so\n * callers can react to a failed lookup instead of receiving a stale value.\n */\n async #fetchAndUpdate(options?: {\n bypassCache?: boolean;\n }): Promise<GeolocationData> {\n return await this.messenger.call(\n 'GeolocationApiService:fetchGeolocationData',\n {\n ...options,\n onQueryStateChange: (queryState) => this.#applyQueryState(queryState),\n },\n );\n }\n\n /**\n * Maps the state of the service's query onto controller state.\n *\n * @param queryState - The state of the geolocation query.\n */\n #applyQueryState(queryState: DataServiceQueryState): void {\n // A fetch paused while offline still counts as loading.\n if (queryState.fetchStatus !== 'idle') {\n this.update((draft) => {\n draft.status = 'loading';\n draft.error = null;\n });\n return;\n }\n\n if (queryState.error) {\n const { message } = queryState.error;\n this.update((draft) => {\n draft.status = 'error';\n draft.error = message;\n });\n return;\n }\n\n // An invalidated entry is about to be refetched (for instance by\n // `refreshGeolocation`), so its old data and `dataUpdatedAt` are skipped.\n if (queryState.status === 'success' && !queryState.isInvalidated) {\n const geolocation = queryState.data as GeolocationData;\n this.update((draft) => {\n draft.location = toLocationCode(geolocation);\n draft.country = geolocation.country;\n draft.region = geolocation.region;\n draft.timezone = geolocation.timezone;\n draft.status = 'complete';\n draft.lastFetchedAt = queryState.dataUpdatedAt;\n draft.error = null;\n });\n }\n }\n}\n"]}
1
+ {"version":3,"file":"GeolocationController.js","sourceRoot":"","sources":["../src/GeolocationController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\n\nimport type { GeolocationApiServiceFetchGeolocationDataAction } from './geolocation-api-service/geolocation-api-service-method-action-types.js';\nimport type { GeolocationData } from './geolocation-api-service/geolocation-api-service.js';\nimport {\n getUnknownGeolocationData,\n toLocationCode,\n UNKNOWN_LOCATION,\n} from './geolocation-api-service/geolocation-api-service.js';\nimport type { GeolocationControllerMethodActions } from './GeolocationController-method-action-types.js';\nimport type { GeolocationRequestStatus } from './types.js';\n\n/**\n * The name of the {@link GeolocationController}, used to namespace the\n * controller's actions and events and to namespace the controller's state data\n * when composed with other controllers.\n */\nexport const controllerName = 'GeolocationController';\n\n/**\n * State for the {@link GeolocationController}.\n */\nexport type GeolocationControllerState = {\n /** ISO 3166-2 location code (e.g. \"US\", \"US-NY\", \"CA-ON\"), or \"UNKNOWN\" if not yet determined. */\n location: string;\n /** ISO 3166-1 alpha-2 country code (e.g. \"US\"), or null if not yet determined. */\n country: string | null;\n /** ISO 3166-2 subdivision code without the country prefix (e.g. \"NY\"), or null if not yet determined. */\n region: string | null;\n /** IANA time zone name (e.g. \"America/Los_Angeles\"), or null if not yet determined. */\n timezone: string | null;\n /** Current status of the geolocation fetch lifecycle. */\n status: GeolocationRequestStatus;\n /** Epoch milliseconds of the last successful fetch, or null if never fetched. */\n lastFetchedAt: number | null;\n /** Last error message, or null if no error has occurred. */\n error: string | null;\n};\n\n/**\n * The metadata for each property in {@link GeolocationControllerState}.\n */\nconst geolocationControllerMetadata = {\n location: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n country: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n region: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n timezone: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n status: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n lastFetchedAt: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n error: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: false,\n },\n} satisfies StateMetadata<GeolocationControllerState>;\n\n/**\n * Constructs the default {@link GeolocationController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link GeolocationController} state.\n */\nexport function getDefaultGeolocationControllerState(): GeolocationControllerState {\n return {\n location: UNKNOWN_LOCATION,\n ...getUnknownGeolocationData(),\n status: 'idle',\n lastFetchedAt: null,\n error: null,\n };\n}\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'getGeolocation',\n 'getGeolocationData',\n 'refreshGeolocation',\n] as const;\n\n/**\n * Retrieves the state of the {@link GeolocationController}.\n */\nexport type GeolocationControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n GeolocationControllerState\n>;\n\n/**\n * Actions that {@link GeolocationControllerMessenger} exposes to other consumers.\n */\nexport type GeolocationControllerActions =\n | GeolocationControllerGetStateAction\n | GeolocationControllerMethodActions;\n\n/**\n * Actions from other messengers that {@link GeolocationControllerMessenger} calls.\n */\ntype AllowedActions = GeolocationApiServiceFetchGeolocationDataAction;\n\n/**\n * Published when the state of {@link GeolocationController} changes.\n */\nexport type GeolocationControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n GeolocationControllerState\n>;\n\n/**\n * Events that {@link GeolocationControllerMessenger} exposes to other consumers.\n */\nexport type GeolocationControllerEvents = GeolocationControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link GeolocationControllerMessenger}\n * subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link GeolocationController}.\n */\nexport type GeolocationControllerMessenger = Messenger<\n typeof controllerName,\n GeolocationControllerActions | AllowedActions,\n GeolocationControllerEvents | AllowedEvents\n>;\n\n/**\n * Options for constructing the {@link GeolocationController}.\n */\nexport type GeolocationControllerOptions = {\n /** The messenger for inter-controller communication. */\n messenger: GeolocationControllerMessenger;\n /** Optional partial initial state. */\n state?: Partial<GeolocationControllerState>;\n};\n\n/**\n * GeolocationController manages UI-facing geolocation state by delegating\n * the actual API interaction to {@link GeolocationApiService} via the\n * messenger.\n *\n * The service (registered externally as\n * `GeolocationApiService:fetchGeolocation`) handles HTTP requests, response\n * validation, TTL caching, and promise deduplication. This controller focuses\n * on state lifecycle (`idle` -> `loading` -> `complete` | `error`) and\n * exposes `getGeolocation` / `refreshGeolocation` as messenger actions.\n */\nexport class GeolocationController extends BaseController<\n typeof controllerName,\n GeolocationControllerState,\n GeolocationControllerMessenger\n> {\n /**\n * Constructs a new {@link GeolocationController}.\n *\n * @param args - The arguments to this controller.\n * @param args.messenger - The messenger suited for this controller. Must\n * have a `GeolocationApiService:fetchGeolocation` action handler registered.\n * @param args.state - Optional partial initial state.\n */\n constructor({ messenger, state }: GeolocationControllerOptions) {\n super({\n messenger,\n metadata: geolocationControllerMetadata,\n name: controllerName,\n state: { ...getDefaultGeolocationControllerState(), ...state },\n });\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Returns the geolocation code. Delegates to the\n * {@link GeolocationApiService} for network fetching and caching, then\n * updates controller state with the result.\n *\n * Best-effort: if the fetch fails, the last known location code (or\n * {@link UNKNOWN_LOCATION}) is returned rather than throwing.\n *\n * @returns The ISO 3166-2 location code string.\n */\n async getGeolocation(): Promise<string> {\n try {\n await this.#fetchAndUpdate();\n } catch {\n // Best-effort: fall back to the last known location code below.\n }\n return this.state.location;\n }\n\n /**\n * Returns the country, region, and timezone for the current client.\n * Delegates to the {@link GeolocationApiService} for network fetching and\n * caching, then updates controller state with the result.\n *\n * Unlike {@link getGeolocation}, this rejects when resolution fails instead\n * of returning a stale value, so callers can distinguish a fresh result from\n * a failed lookup (and, for example, omit location rather than enrich with a\n * previous session's data).\n *\n * @returns The geolocation data, where each field is `null` when it could\n * not be determined.\n * @throws When the geolocation service fails to resolve.\n */\n async getGeolocationData(): Promise<GeolocationData> {\n return this.#fetchAndUpdate();\n }\n\n /**\n * Forces a fresh geolocation fetch, bypassing the service's cache.\n *\n * Best-effort: if the fetch fails, the last known location code (or\n * {@link UNKNOWN_LOCATION}) is returned rather than throwing.\n *\n * @returns The ISO 3166-2 location code string.\n */\n async refreshGeolocation(): Promise<string> {\n this.update((draft) => {\n draft.lastFetchedAt = null;\n });\n try {\n await this.#fetchAndUpdate({ bypassCache: true });\n } catch {\n // Best-effort: fall back to the last known location code below.\n }\n return this.state.location;\n }\n\n /**\n * Calls the geolocation service and updates controller state with the\n * result.\n *\n * @param options - Options forwarded to the service.\n * @param options.bypassCache - When true, the service skips its TTL cache.\n * @returns The resolved geolocation data.\n * @throws Re-throws the service error after recording it in state, so\n * callers can react to a failed lookup instead of receiving a stale value.\n */\n async #fetchAndUpdate(options?: {\n bypassCache?: boolean;\n }): Promise<GeolocationData> {\n this.update((draft) => {\n draft.status = 'loading';\n draft.error = null;\n });\n\n try {\n const geolocation = await this.messenger.call(\n 'GeolocationApiService:fetchGeolocationData',\n options,\n );\n\n this.update((draft) => {\n draft.location = toLocationCode(geolocation);\n draft.country = geolocation.country;\n draft.region = geolocation.region;\n draft.timezone = geolocation.timezone;\n draft.status = 'complete';\n draft.lastFetchedAt = Date.now();\n draft.error = null;\n });\n\n return geolocation;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n this.update((draft) => {\n draft.status = 'error';\n draft.error = message;\n });\n\n throw error;\n }\n }\n}\n"]}
@@ -10,8 +10,6 @@ import type { GeolocationApiService } from './geolocation-api-service.js';
10
10
  * @param options - Optional fetch options.
11
11
  * @param options.bypassCache - When true, ignores any cached result and
12
12
  * fetches fresh data.
13
- * @param options.onQueryStateChange - Called with the state of the
14
- * underlying query as it changes.
15
13
  * @returns An ISO 3166-2 location code (e.g. `US`, `US-NY`, `CA-ON`), or
16
14
  * {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.
17
15
  */
@@ -27,8 +25,6 @@ export type GeolocationApiServiceFetchGeolocationAction = {
27
25
  * @param options - Optional fetch options.
28
26
  * @param options.bypassCache - When true, ignores any cached result and
29
27
  * fetches fresh data.
30
- * @param options.onQueryStateChange - Called with the state of the
31
- * underlying query as it changes.
32
28
  * @returns The geolocation data, where each field is `null` when the API
33
29
  * omits it or returns a value that fails validation.
34
30
  */
@@ -1 +1 @@
1
- {"version":3,"file":"geolocation-api-service-method-action-types.d.ts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,2CAA2C,GAAG;IACxD,IAAI,EAAE,wCAAwC,CAAC;IAC/C,OAAO,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAC;CACpD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,+CAA+C,GAAG;IAC5D,IAAI,EAAE,4CAA4C,CAAC;IACnD,OAAO,EAAE,qBAAqB,CAAC,sBAAsB,CAAC,CAAC;CACxD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,GAC1C,2CAA2C,GAC3C,+CAA+C,CAAC"}
1
+ {"version":3,"file":"geolocation-api-service-method-action-types.d.ts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;;;;;GASG;AACH,MAAM,MAAM,2CAA2C,GAAG;IACxD,IAAI,EAAE,wCAAwC,CAAC;IAC/C,OAAO,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAC;CACpD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,+CAA+C,GAAG;IAC5D,IAAI,EAAE,4CAA4C,CAAC;IACnD,OAAO,EAAE,qBAAqB,CAAC,sBAAsB,CAAC,CAAC;CACxD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,GAC1C,2CAA2C,GAC3C,+CAA+C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"geolocation-api-service-method-action-types.js","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { GeolocationApiService } from './geolocation-api-service.js';\n\n/**\n * Returns the geolocation code. Serves from cache when the TTL has not\n * expired, otherwise performs a network fetch.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, ignores any cached result and\n * fetches fresh data.\n * @param options.onQueryStateChange - Called with the state of the\n * underlying query as it changes.\n * @returns An ISO 3166-2 location code (e.g. `US`, `US-NY`, `CA-ON`), or\n * {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.\n */\nexport type GeolocationApiServiceFetchGeolocationAction = {\n type: `GeolocationApiService:fetchGeolocation`;\n handler: GeolocationApiService['fetchGeolocation'];\n};\n\n/**\n * Returns the country, region, and timezone for the current client. Serves\n * from cache when the TTL has not expired, otherwise performs a network\n * fetch.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, ignores any cached result and\n * fetches fresh data.\n * @param options.onQueryStateChange - Called with the state of the\n * underlying query as it changes.\n * @returns The geolocation data, where each field is `null` when the API\n * omits it or returns a value that fails validation.\n */\nexport type GeolocationApiServiceFetchGeolocationDataAction = {\n type: `GeolocationApiService:fetchGeolocationData`;\n handler: GeolocationApiService['fetchGeolocationData'];\n};\n\n/**\n * Union of all GeolocationApiService action types.\n */\nexport type GeolocationApiServiceMethodActions =\n | GeolocationApiServiceFetchGeolocationAction\n | GeolocationApiServiceFetchGeolocationDataAction;\n"]}
1
+ {"version":3,"file":"geolocation-api-service-method-action-types.js","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG","sourcesContent":["/**\n * This file is auto generated.\n * Do not edit manually.\n */\n\nimport type { GeolocationApiService } from './geolocation-api-service.js';\n\n/**\n * Returns the geolocation code. Serves from cache when the TTL has not\n * expired, otherwise performs a network fetch.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, ignores any cached result and\n * fetches fresh data.\n * @returns An ISO 3166-2 location code (e.g. `US`, `US-NY`, `CA-ON`), or\n * {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.\n */\nexport type GeolocationApiServiceFetchGeolocationAction = {\n type: `GeolocationApiService:fetchGeolocation`;\n handler: GeolocationApiService['fetchGeolocation'];\n};\n\n/**\n * Returns the country, region, and timezone for the current client. Serves\n * from cache when the TTL has not expired, otherwise performs a network\n * fetch.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, ignores any cached result and\n * fetches fresh data.\n * @returns The geolocation data, where each field is `null` when the API\n * omits it or returns a value that fails validation.\n */\nexport type GeolocationApiServiceFetchGeolocationDataAction = {\n type: `GeolocationApiService:fetchGeolocationData`;\n handler: GeolocationApiService['fetchGeolocationData'];\n};\n\n/**\n * Union of all GeolocationApiService action types.\n */\nexport type GeolocationApiServiceMethodActions =\n | GeolocationApiServiceFetchGeolocationAction\n | GeolocationApiServiceFetchGeolocationDataAction;\n"]}
@@ -1,5 +1,5 @@
1
1
  import { BaseDataService } from '@metamask/base-data-service';
2
- import type { CreateServicePolicyOptions, DataServiceCacheUpdatedEvent, DataServiceGranularCacheUpdatedEvent, DataServiceInvalidateQueriesAction, QueryStateChangeHandler } from '@metamask/base-data-service';
2
+ import type { CreateServicePolicyOptions, DataServiceCacheUpdatedEvent, DataServiceGranularCacheUpdatedEvent, DataServiceInvalidateQueriesAction } from '@metamask/base-data-service';
3
3
  import type { Messenger } from '@metamask/messenger';
4
4
  import { Env } from '../types.js';
5
5
  import type { GeolocationApiServiceMethodActions } from './geolocation-api-service-method-action-types.js';
@@ -88,12 +88,6 @@ export type GeolocationApiServiceMessenger = Messenger<typeof serviceName, Geolo
88
88
  export type FetchGeolocationOptions = {
89
89
  /** When true, ignores any cached result and fetches fresh data. */
90
90
  bypassCache?: boolean;
91
- /**
92
- * Called with the state of the underlying query as it changes, starting
93
- * with a snapshot of the cached entry (if any). See
94
- * {@link QueryStateChangeHandler}.
95
- */
96
- onQueryStateChange?: QueryStateChangeHandler;
97
91
  };
98
92
  /**
99
93
  * Low-level data service that fetches geolocation details from the geolocation
@@ -138,8 +132,6 @@ export declare class GeolocationApiService extends BaseDataService<typeof servic
138
132
  * @param options - Optional fetch options.
139
133
  * @param options.bypassCache - When true, ignores any cached result and
140
134
  * fetches fresh data.
141
- * @param options.onQueryStateChange - Called with the state of the
142
- * underlying query as it changes.
143
135
  * @returns An ISO 3166-2 location code (e.g. `US`, `US-NY`, `CA-ON`), or
144
136
  * {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.
145
137
  */
@@ -152,8 +144,6 @@ export declare class GeolocationApiService extends BaseDataService<typeof servic
152
144
  * @param options - Optional fetch options.
153
145
  * @param options.bypassCache - When true, ignores any cached result and
154
146
  * fetches fresh data.
155
- * @param options.onQueryStateChange - Called with the state of the
156
- * underlying query as it changes.
157
147
  * @returns The geolocation data, where each field is `null` when the API
158
148
  * omits it or returns a value that fails validation.
159
149
  */
@@ -1 +1 @@
1
- {"version":3,"file":"geolocation-api-service.d.ts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC5B,oCAAoC,EACpC,kCAAkC,EAClC,uBAAuB,EACxB,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQrD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,kDAAkD,CAAC;AAc3G;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,yDAAyD;IACzD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,CAE3D;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAU5D;AASD;;GAEG;AACH,MAAM,MAAM,4CAA4C,GACtD,kCAAkC,CAAC,OAAO,WAAW,CAAC,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,4BAA4B,GACpC,kCAAkC,GAClC,4CAA4C,CAAC;AAEjD;;;GAGG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAChD,4BAA4B,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,8CAA8C,GACxD,oCAAoC,CAAC,OAAO,WAAW,CAAC,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,2BAA2B,GACnC,sCAAsC,GACtC,8CAA8C,CAAC;AAEnD;;;GAGG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,SAAS,CACpD,OAAO,WAAW,EAClB,4BAA4B,GAAG,cAAc,EAC7C,2BAA2B,GAAG,aAAa,CAC5C,CAAC;AA+EF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;CAC9C,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,qBAAsB,SAAQ,eAAe,CACxD,OAAO,WAAW,EAClB,8BAA8B,CAC/B;;IAOC;;;;;;;;;;;;;OAaG;IACH,YAAY,EACV,SAAS,EACT,GAAa,EACb,KAAK,EAAE,aAAgC,EACvC,KAAK,EACL,aAAkB,GACnB,EAAE;QACD,SAAS,EAAE,8BAA8B,CAAC;QAC1C,GAAG,CAAC,EAAE,GAAG,CAAC;QACV,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,aAAa,CAAC,EAAE,0BAA0B,CAAC;KAC5C,EAUA;IAED;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzE;IAED;;;;;;;;;;;;OAYG;IACG,oBAAoB,CACxB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,eAAe,CAAC,CAoC1B;CACF"}
1
+ {"version":3,"file":"geolocation-api-service.d.ts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC5B,oCAAoC,EACpC,kCAAkC,EACnC,MAAM,6BAA6B,CAAC;AAErC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQrD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,EAAE,kCAAkC,EAAE,MAAM,kDAAkD,CAAC;AAc3G;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,yDAAyD;IACzD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,0EAA0E;IAC1E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,CAE3D;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAU5D;AASD;;GAEG;AACH,MAAM,MAAM,4CAA4C,GACtD,kCAAkC,CAAC,OAAO,WAAW,CAAC,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,4BAA4B,GACpC,kCAAkC,GAClC,4CAA4C,CAAC;AAEjD;;;GAGG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,sCAAsC,GAChD,4BAA4B,CAAC,OAAO,WAAW,CAAC,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,8CAA8C,GACxD,oCAAoC,CAAC,OAAO,WAAW,CAAC,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,2BAA2B,GACnC,sCAAsC,GACtC,8CAA8C,CAAC;AAEnD;;;GAGG;AACH,KAAK,aAAa,GAAG,KAAK,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,8BAA8B,GAAG,SAAS,CACpD,OAAO,WAAW,EAClB,4BAA4B,GAAG,cAAc,EAC7C,2BAA2B,GAAG,aAAa,CAC5C,CAAC;AA+EF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,qBAAsB,SAAQ,eAAe,CACxD,OAAO,WAAW,EAClB,8BAA8B,CAC/B;;IAOC;;;;;;;;;;;;;OAaG;IACH,YAAY,EACV,SAAS,EACT,GAAa,EACb,KAAK,EAAE,aAAgC,EACvC,KAAK,EACL,aAAkB,GACnB,EAAE;QACD,SAAS,EAAE,8BAA8B,CAAC;QAC1C,GAAG,CAAC,EAAE,GAAG,CAAC;QACV,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,aAAa,CAAC,EAAE,0BAA0B,CAAC;KAC5C,EAUA;IAED;;;;;;;;;OASG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzE;IAED;;;;;;;;;;OAUG;IACG,oBAAoB,CACxB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,eAAe,CAAC,CAmC1B;CACF"}
@@ -175,8 +175,6 @@ export class GeolocationApiService extends BaseDataService {
175
175
  * @param options - Optional fetch options.
176
176
  * @param options.bypassCache - When true, ignores any cached result and
177
177
  * fetches fresh data.
178
- * @param options.onQueryStateChange - Called with the state of the
179
- * underlying query as it changes.
180
178
  * @returns An ISO 3166-2 location code (e.g. `US`, `US-NY`, `CA-ON`), or
181
179
  * {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.
182
180
  */
@@ -191,8 +189,6 @@ export class GeolocationApiService extends BaseDataService {
191
189
  * @param options - Optional fetch options.
192
190
  * @param options.bypassCache - When true, ignores any cached result and
193
191
  * fetches fresh data.
194
- * @param options.onQueryStateChange - Called with the state of the
195
- * underlying query as it changes.
196
192
  * @returns The geolocation data, where each field is `null` when the API
197
193
  * omits it or returns a value that fails validation.
198
194
  */
@@ -206,7 +202,6 @@ export class GeolocationApiService extends BaseDataService {
206
202
  staleTime: this.#ttlMs,
207
203
  responseStruct: GeolocationDataStruct,
208
204
  gcTime: this.#ttlMs,
209
- onQueryStateChange: options?.onQueryStateChange,
210
205
  queryFn: async () => {
211
206
  const response = await this.#fetch(this.#url);
212
207
  if (!response.ok) {
@@ -1 +1 @@
1
- {"version":3,"file":"geolocation-api-service.js","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseDataService } from '@metamask/base-data-service';\nimport type {\n CreateServicePolicyOptions,\n DataServiceCacheUpdatedEvent,\n DataServiceGranularCacheUpdatedEvent,\n DataServiceInvalidateQueriesAction,\n QueryStateChangeHandler,\n} from '@metamask/base-data-service';\nimport { HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport {\n nullable,\n object,\n pattern as matches,\n string,\n} from '@metamask/superstruct';\n\nimport { Env } from '../types.js';\nimport type { GeolocationApiServiceMethodActions } from './geolocation-api-service-method-action-types.js';\n\nconst DEFAULT_TTL_MS = 5 * 60 * 1000;\n\nconst ENDPOINT_PATH = '/v2/geolocation';\n\nconst COUNTRY_PATTERN = /^[A-Z]{2}$/u;\n\nconst REGION_PATTERN = /^[A-Z0-9]{1,3}$/u;\n\nconst TIMEZONE_PATTERN = /^[A-Za-z][A-Za-z0-9_+-]*(?:\\/[A-Za-z0-9_+-]+)*$/u;\n\n// === GENERAL ===\n\n/**\n * The name of the {@link GeolocationApiService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'GeolocationApiService';\n\n/**\n * Sentinel value used when the geolocation has not been determined yet or when\n * the API returns an empty / invalid response.\n */\nexport const UNKNOWN_LOCATION = 'UNKNOWN';\n\n/**\n * Geolocation details returned by the geolocation API.\n *\n * Each field is `null` when the API omits it or returns a value that fails\n * validation.\n */\nexport type GeolocationData = {\n /** ISO 3166-1 alpha-2 country code (e.g. `US`, `FR`). */\n country: string | null;\n /** ISO 3166-2 subdivision code without the country prefix (e.g. `WA`). */\n region: string | null;\n /** IANA time zone name (e.g. `America/Los_Angeles`). */\n timezone: string | null;\n};\n\n/**\n * Constructs a {@link GeolocationData} with no known fields.\n *\n * @returns Geolocation data where every field is `null`.\n */\nexport function getUnknownGeolocationData(): GeolocationData {\n return { country: null, region: null, timezone: null };\n}\n\n/**\n * Country codes for which the location code includes the region. This mirrors\n * the legacy `v1` geolocation endpoint, which only appended the subdivision for\n * the United States and Canada (e.g. `US-NY`, `CA-ON`) and returned the country\n * alone for everywhere else.\n */\n/**\n * Describes the validated shape of {@link GeolocationData}.\n */\nconst GeolocationDataStruct = object({\n country: nullable(matches(string(), COUNTRY_PATTERN)),\n region: nullable(matches(string(), REGION_PATTERN)),\n timezone: nullable(matches(string(), TIMEZONE_PATTERN)),\n});\n\nconst REGION_APPENDED_COUNTRIES = new Set(['US', 'CA']);\n\n/**\n * Converts geolocation data to a location code.\n *\n * To preserve backwards compatibility with the legacy `v1` endpoint, the region\n * is appended only for {@link REGION_APPENDED_COUNTRIES} (e.g. `US-NY`,\n * `CA-ON`); all other countries return the country code alone (e.g. `FR`), even\n * when a region is known.\n *\n * @param data - The geolocation data to convert.\n * @returns The location code (e.g. `US-NY`, `FR`), or\n * {@link UNKNOWN_LOCATION} when the country is unknown.\n */\nexport function toLocationCode(data: GeolocationData): string {\n if (data.country === null) {\n return UNKNOWN_LOCATION;\n }\n\n if (data.region !== null && REGION_APPENDED_COUNTRIES.has(data.country)) {\n return `${data.country}-${data.region}`;\n }\n\n return data.country;\n}\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'fetchGeolocation',\n 'fetchGeolocationData',\n] as const;\n\n/**\n * Invalidates cached queries for {@link GeolocationApiService}.\n */\nexport type GeolocationApiServiceInvalidateQueriesAction =\n DataServiceInvalidateQueriesAction<typeof serviceName>;\n\n/**\n * Actions that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceActions =\n | GeolocationApiServiceMethodActions\n | GeolocationApiServiceInvalidateQueriesAction;\n\n/**\n * Actions from other messengers that {@link GeolocationApiServiceMessenger}\n * calls.\n */\ntype AllowedActions = never;\n\n/**\n * Published when {@link GeolocationApiService}'s cache is updated.\n */\nexport type GeolocationApiServiceCacheUpdatedEvent =\n DataServiceCacheUpdatedEvent<typeof serviceName>;\n\n/**\n * Published when a key within {@link GeolocationApiService}'s cache is\n * updated.\n */\nexport type GeolocationApiServiceGranularCacheUpdatedEvent =\n DataServiceGranularCacheUpdatedEvent<typeof serviceName>;\n\n/**\n * Events that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceEvents =\n | GeolocationApiServiceCacheUpdatedEvent\n | GeolocationApiServiceGranularCacheUpdatedEvent;\n\n/**\n * Events from other messengers that {@link GeolocationApiService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link GeolocationApiService}.\n */\nexport type GeolocationApiServiceMessenger = Messenger<\n typeof serviceName,\n GeolocationApiServiceActions | AllowedActions,\n GeolocationApiServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * Returns the base URL for the geolocation API for the given environment.\n *\n * Served by API Platform's `geolocation-api` service, not the legacy\n * Ramps-owned `on-ramp` endpoint this previously pointed to. API Platform has\n * not yet provisioned a dedicated UAT deployment for this service, so UAT\n * temporarily resolves to the production URL until one exists.\n *\n * @param env - The environment to get the URL for.\n * @returns The full URL for the geolocation endpoint.\n */\nfunction getGeolocationUrl(env: Env): string {\n const envPrefix = env === Env.DEV ? 'dev-' : '';\n return `https://geolocation.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;\n}\n\n/**\n * Reads a string field from a parsed response body, keeping it only when it\n * matches the expected format.\n *\n * @param body - The parsed response body.\n * @param field - The name of the field to read.\n * @param pattern - The pattern the field value must match.\n * @returns The trimmed field value, or `null` when it is missing or invalid.\n */\nfunction readValidatedField(\n body: Record<string, unknown>,\n field: string,\n pattern: RegExp,\n): string | null {\n const value = body[field];\n\n if (typeof value !== 'string') {\n return null;\n }\n\n const trimmed = value.trim();\n\n return pattern.test(trimmed) ? trimmed : null;\n}\n\n/**\n * Parses and validates the geolocation API response body.\n *\n * The endpoint is expected to return JSON such as\n * `{\"country\":\"US\",\"region\":\"WA\",\"timezone\":\"America/Los_Angeles\"}`. Anything\n * that cannot be parsed, or any individual field that fails validation, is\n * reported as unknown rather than throwing, so that consumers can keep working\n * with partial or missing data.\n *\n * @param raw - The raw response body.\n * @returns The validated geolocation data.\n */\nfunction parseGeolocationResponse(raw: string): GeolocationData {\n let body: unknown;\n\n try {\n body = JSON.parse(raw);\n } catch {\n return getUnknownGeolocationData();\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return getUnknownGeolocationData();\n }\n\n const record = body as Record<string, unknown>;\n\n return {\n country: readValidatedField(record, 'country', COUNTRY_PATTERN),\n region: readValidatedField(record, 'region', REGION_PATTERN),\n timezone: readValidatedField(record, 'timezone', TIMEZONE_PATTERN),\n };\n}\n\n/**\n * Options accepted by {@link GeolocationApiService.fetchGeolocation}.\n */\nexport type FetchGeolocationOptions = {\n /** When true, ignores any cached result and fetches fresh data. */\n bypassCache?: boolean;\n /**\n * Called with the state of the underlying query as it changes, starting\n * with a snapshot of the cached entry (if any). See\n * {@link QueryStateChangeHandler}.\n */\n onQueryStateChange?: QueryStateChangeHandler;\n};\n\n/**\n * Low-level data service that fetches geolocation details from the geolocation\n * API.\n *\n * Responsibilities:\n * - HTTP request to the geolocation endpoint (wrapped in a service policy)\n * - Response validation of the country, region, and timezone fields\n *\n * This class is intentionally not a controller: it does not manage UI state.\n * Its {@link fetchGeolocation} and {@link fetchGeolocationData} methods are\n * automatically registered on the messenger so that controllers and other\n * packages can call them directly.\n */\nexport class GeolocationApiService extends BaseDataService<\n typeof serviceName,\n GeolocationApiServiceMessenger\n> {\n readonly #fetch: typeof globalThis.fetch;\n\n readonly #url: string;\n\n readonly #ttlMs: number;\n\n /**\n * Constructs a new {@link GeolocationApiService}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.env - The environment to determine the correct API endpoint.\n * Defaults to PRD.\n * @param args.fetch - A function that can be used to make an HTTP request.\n * Defaults to the global fetch.\n * @param args.ttlMs - How long a fetched result is served from cache, in\n * milliseconds. Defaults to 5 minutes.\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n */\n constructor({\n messenger,\n env = Env.PRD,\n fetch: fetchFunction = globalThis.fetch,\n ttlMs,\n policyOptions = {},\n }: {\n messenger: GeolocationApiServiceMessenger;\n env?: Env;\n fetch?: typeof fetch;\n ttlMs?: number;\n policyOptions?: CreateServicePolicyOptions;\n }) {\n super({ name: serviceName, messenger, policyOptions });\n this.#url = getGeolocationUrl(env);\n this.#fetch = fetchFunction;\n this.#ttlMs = ttlMs ?? DEFAULT_TTL_MS;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Returns the geolocation code. Serves from cache when the TTL has not\n * expired, otherwise performs a network fetch.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, ignores any cached result and\n * fetches fresh data.\n * @param options.onQueryStateChange - Called with the state of the\n * underlying query as it changes.\n * @returns An ISO 3166-2 location code (e.g. `US`, `US-NY`, `CA-ON`), or\n * {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.\n */\n async fetchGeolocation(options?: FetchGeolocationOptions): Promise<string> {\n return toLocationCode(await this.fetchGeolocationData(options));\n }\n\n /**\n * Returns the country, region, and timezone for the current client. Serves\n * from cache when the TTL has not expired, otherwise performs a network\n * fetch.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, ignores any cached result and\n * fetches fresh data.\n * @param options.onQueryStateChange - Called with the state of the\n * underlying query as it changes.\n * @returns The geolocation data, where each field is `null` when the API\n * omits it or returns a value that fails validation.\n */\n async fetchGeolocationData(\n options?: FetchGeolocationOptions,\n ): Promise<GeolocationData> {\n const queryKey = [`${this.name}:fetchGeolocationData`] as const;\n\n if (options?.bypassCache) {\n await this.invalidateQueries({ queryKey, refetchType: 'none' });\n }\n\n const geolocation = await this.fetchQuery({\n queryKey,\n staleTime: this.#ttlMs,\n responseStruct: GeolocationDataStruct,\n gcTime: this.#ttlMs,\n onQueryStateChange: options?.onQueryStateChange,\n queryFn: async () => {\n const response = await this.#fetch(this.#url);\n if (!response.ok) {\n throw new HttpError(\n response.status,\n `Geolocation fetch failed: ${response.status}`,\n );\n }\n return parseGeolocationResponse((await response.text()).trim());\n },\n });\n\n // A partially-known result is worth keeping for the whole TTL. A\n // fully-unknown one is marked stale so the next call retries it.\n if (\n geolocation.country === null &&\n geolocation.region === null &&\n geolocation.timezone === null\n ) {\n await this.invalidateQueries({ queryKey, refetchType: 'none' });\n }\n\n return geolocation;\n }\n}\n"]}
1
+ {"version":3,"file":"geolocation-api-service.js","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseDataService } from '@metamask/base-data-service';\nimport type {\n CreateServicePolicyOptions,\n DataServiceCacheUpdatedEvent,\n DataServiceGranularCacheUpdatedEvent,\n DataServiceInvalidateQueriesAction,\n} from '@metamask/base-data-service';\nimport { HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport {\n nullable,\n object,\n pattern as matches,\n string,\n} from '@metamask/superstruct';\n\nimport { Env } from '../types.js';\nimport type { GeolocationApiServiceMethodActions } from './geolocation-api-service-method-action-types.js';\n\nconst DEFAULT_TTL_MS = 5 * 60 * 1000;\n\nconst ENDPOINT_PATH = '/v2/geolocation';\n\nconst COUNTRY_PATTERN = /^[A-Z]{2}$/u;\n\nconst REGION_PATTERN = /^[A-Z0-9]{1,3}$/u;\n\nconst TIMEZONE_PATTERN = /^[A-Za-z][A-Za-z0-9_+-]*(?:\\/[A-Za-z0-9_+-]+)*$/u;\n\n// === GENERAL ===\n\n/**\n * The name of the {@link GeolocationApiService}, used to namespace the\n * service's actions and events.\n */\nexport const serviceName = 'GeolocationApiService';\n\n/**\n * Sentinel value used when the geolocation has not been determined yet or when\n * the API returns an empty / invalid response.\n */\nexport const UNKNOWN_LOCATION = 'UNKNOWN';\n\n/**\n * Geolocation details returned by the geolocation API.\n *\n * Each field is `null` when the API omits it or returns a value that fails\n * validation.\n */\nexport type GeolocationData = {\n /** ISO 3166-1 alpha-2 country code (e.g. `US`, `FR`). */\n country: string | null;\n /** ISO 3166-2 subdivision code without the country prefix (e.g. `WA`). */\n region: string | null;\n /** IANA time zone name (e.g. `America/Los_Angeles`). */\n timezone: string | null;\n};\n\n/**\n * Constructs a {@link GeolocationData} with no known fields.\n *\n * @returns Geolocation data where every field is `null`.\n */\nexport function getUnknownGeolocationData(): GeolocationData {\n return { country: null, region: null, timezone: null };\n}\n\n/**\n * Country codes for which the location code includes the region. This mirrors\n * the legacy `v1` geolocation endpoint, which only appended the subdivision for\n * the United States and Canada (e.g. `US-NY`, `CA-ON`) and returned the country\n * alone for everywhere else.\n */\n/**\n * Describes the validated shape of {@link GeolocationData}.\n */\nconst GeolocationDataStruct = object({\n country: nullable(matches(string(), COUNTRY_PATTERN)),\n region: nullable(matches(string(), REGION_PATTERN)),\n timezone: nullable(matches(string(), TIMEZONE_PATTERN)),\n});\n\nconst REGION_APPENDED_COUNTRIES = new Set(['US', 'CA']);\n\n/**\n * Converts geolocation data to a location code.\n *\n * To preserve backwards compatibility with the legacy `v1` endpoint, the region\n * is appended only for {@link REGION_APPENDED_COUNTRIES} (e.g. `US-NY`,\n * `CA-ON`); all other countries return the country code alone (e.g. `FR`), even\n * when a region is known.\n *\n * @param data - The geolocation data to convert.\n * @returns The location code (e.g. `US-NY`, `FR`), or\n * {@link UNKNOWN_LOCATION} when the country is unknown.\n */\nexport function toLocationCode(data: GeolocationData): string {\n if (data.country === null) {\n return UNKNOWN_LOCATION;\n }\n\n if (data.region !== null && REGION_APPENDED_COUNTRIES.has(data.country)) {\n return `${data.country}-${data.region}`;\n }\n\n return data.country;\n}\n\n// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = [\n 'fetchGeolocation',\n 'fetchGeolocationData',\n] as const;\n\n/**\n * Invalidates cached queries for {@link GeolocationApiService}.\n */\nexport type GeolocationApiServiceInvalidateQueriesAction =\n DataServiceInvalidateQueriesAction<typeof serviceName>;\n\n/**\n * Actions that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceActions =\n | GeolocationApiServiceMethodActions\n | GeolocationApiServiceInvalidateQueriesAction;\n\n/**\n * Actions from other messengers that {@link GeolocationApiServiceMessenger}\n * calls.\n */\ntype AllowedActions = never;\n\n/**\n * Published when {@link GeolocationApiService}'s cache is updated.\n */\nexport type GeolocationApiServiceCacheUpdatedEvent =\n DataServiceCacheUpdatedEvent<typeof serviceName>;\n\n/**\n * Published when a key within {@link GeolocationApiService}'s cache is\n * updated.\n */\nexport type GeolocationApiServiceGranularCacheUpdatedEvent =\n DataServiceGranularCacheUpdatedEvent<typeof serviceName>;\n\n/**\n * Events that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceEvents =\n | GeolocationApiServiceCacheUpdatedEvent\n | GeolocationApiServiceGranularCacheUpdatedEvent;\n\n/**\n * Events from other messengers that {@link GeolocationApiService} subscribes\n * to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link GeolocationApiService}.\n */\nexport type GeolocationApiServiceMessenger = Messenger<\n typeof serviceName,\n GeolocationApiServiceActions | AllowedActions,\n GeolocationApiServiceEvents | AllowedEvents\n>;\n\n// === SERVICE DEFINITION ===\n\n/**\n * Returns the base URL for the geolocation API for the given environment.\n *\n * Served by API Platform's `geolocation-api` service, not the legacy\n * Ramps-owned `on-ramp` endpoint this previously pointed to. API Platform has\n * not yet provisioned a dedicated UAT deployment for this service, so UAT\n * temporarily resolves to the production URL until one exists.\n *\n * @param env - The environment to get the URL for.\n * @returns The full URL for the geolocation endpoint.\n */\nfunction getGeolocationUrl(env: Env): string {\n const envPrefix = env === Env.DEV ? 'dev-' : '';\n return `https://geolocation.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;\n}\n\n/**\n * Reads a string field from a parsed response body, keeping it only when it\n * matches the expected format.\n *\n * @param body - The parsed response body.\n * @param field - The name of the field to read.\n * @param pattern - The pattern the field value must match.\n * @returns The trimmed field value, or `null` when it is missing or invalid.\n */\nfunction readValidatedField(\n body: Record<string, unknown>,\n field: string,\n pattern: RegExp,\n): string | null {\n const value = body[field];\n\n if (typeof value !== 'string') {\n return null;\n }\n\n const trimmed = value.trim();\n\n return pattern.test(trimmed) ? trimmed : null;\n}\n\n/**\n * Parses and validates the geolocation API response body.\n *\n * The endpoint is expected to return JSON such as\n * `{\"country\":\"US\",\"region\":\"WA\",\"timezone\":\"America/Los_Angeles\"}`. Anything\n * that cannot be parsed, or any individual field that fails validation, is\n * reported as unknown rather than throwing, so that consumers can keep working\n * with partial or missing data.\n *\n * @param raw - The raw response body.\n * @returns The validated geolocation data.\n */\nfunction parseGeolocationResponse(raw: string): GeolocationData {\n let body: unknown;\n\n try {\n body = JSON.parse(raw);\n } catch {\n return getUnknownGeolocationData();\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return getUnknownGeolocationData();\n }\n\n const record = body as Record<string, unknown>;\n\n return {\n country: readValidatedField(record, 'country', COUNTRY_PATTERN),\n region: readValidatedField(record, 'region', REGION_PATTERN),\n timezone: readValidatedField(record, 'timezone', TIMEZONE_PATTERN),\n };\n}\n\n/**\n * Options accepted by {@link GeolocationApiService.fetchGeolocation}.\n */\nexport type FetchGeolocationOptions = {\n /** When true, ignores any cached result and fetches fresh data. */\n bypassCache?: boolean;\n};\n\n/**\n * Low-level data service that fetches geolocation details from the geolocation\n * API.\n *\n * Responsibilities:\n * - HTTP request to the geolocation endpoint (wrapped in a service policy)\n * - Response validation of the country, region, and timezone fields\n *\n * This class is intentionally not a controller: it does not manage UI state.\n * Its {@link fetchGeolocation} and {@link fetchGeolocationData} methods are\n * automatically registered on the messenger so that controllers and other\n * packages can call them directly.\n */\nexport class GeolocationApiService extends BaseDataService<\n typeof serviceName,\n GeolocationApiServiceMessenger\n> {\n readonly #fetch: typeof globalThis.fetch;\n\n readonly #url: string;\n\n readonly #ttlMs: number;\n\n /**\n * Constructs a new {@link GeolocationApiService}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this service.\n * @param args.env - The environment to determine the correct API endpoint.\n * Defaults to PRD.\n * @param args.fetch - A function that can be used to make an HTTP request.\n * Defaults to the global fetch.\n * @param args.ttlMs - How long a fetched result is served from cache, in\n * milliseconds. Defaults to 5 minutes.\n * @param args.policyOptions - Options to pass to `createServicePolicy`, which\n * is used to wrap each request. See {@link CreateServicePolicyOptions}.\n */\n constructor({\n messenger,\n env = Env.PRD,\n fetch: fetchFunction = globalThis.fetch,\n ttlMs,\n policyOptions = {},\n }: {\n messenger: GeolocationApiServiceMessenger;\n env?: Env;\n fetch?: typeof fetch;\n ttlMs?: number;\n policyOptions?: CreateServicePolicyOptions;\n }) {\n super({ name: serviceName, messenger, policyOptions });\n this.#url = getGeolocationUrl(env);\n this.#fetch = fetchFunction;\n this.#ttlMs = ttlMs ?? DEFAULT_TTL_MS;\n\n this.messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Returns the geolocation code. Serves from cache when the TTL has not\n * expired, otherwise performs a network fetch.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, ignores any cached result and\n * fetches fresh data.\n * @returns An ISO 3166-2 location code (e.g. `US`, `US-NY`, `CA-ON`), or\n * {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.\n */\n async fetchGeolocation(options?: FetchGeolocationOptions): Promise<string> {\n return toLocationCode(await this.fetchGeolocationData(options));\n }\n\n /**\n * Returns the country, region, and timezone for the current client. Serves\n * from cache when the TTL has not expired, otherwise performs a network\n * fetch.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, ignores any cached result and\n * fetches fresh data.\n * @returns The geolocation data, where each field is `null` when the API\n * omits it or returns a value that fails validation.\n */\n async fetchGeolocationData(\n options?: FetchGeolocationOptions,\n ): Promise<GeolocationData> {\n const queryKey = [`${this.name}:fetchGeolocationData`] as const;\n\n if (options?.bypassCache) {\n await this.invalidateQueries({ queryKey, refetchType: 'none' });\n }\n\n const geolocation = await this.fetchQuery({\n queryKey,\n staleTime: this.#ttlMs,\n responseStruct: GeolocationDataStruct,\n gcTime: this.#ttlMs,\n queryFn: async () => {\n const response = await this.#fetch(this.#url);\n if (!response.ok) {\n throw new HttpError(\n response.status,\n `Geolocation fetch failed: ${response.status}`,\n );\n }\n return parseGeolocationResponse((await response.text()).trim());\n },\n });\n\n // A partially-known result is worth keeping for the whole TTL. A\n // fully-unknown one is marked stale so the next call retries it.\n if (\n geolocation.country === null &&\n geolocation.region === null &&\n geolocation.timezone === null\n ) {\n await this.invalidateQueries({ queryKey, refetchType: 'none' });\n }\n\n return geolocation;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/geolocation-controller",
3
- "version": "2.0.0-preview-9fd631f7d",
3
+ "version": "2.0.0-preview-d0145b59e",
4
4
  "description": "Centralised geolocation controller with TTL caching and request deduplication",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -58,7 +58,6 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "@metamask/auto-changelog": "^6.2.1",
61
- "@tanstack/query-core": "^5.103.2",
62
61
  "@typescript/native": "npm:typescript@^7.0.2",
63
62
  "rimraf": "^6.1.3",
64
63
  "typedoc": "^0.28.20",