@metamask-previews/geolocation-controller 0.1.3-preview-68fc14e1c → 0.1.3-preview-a511f243f

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
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Changed
11
11
 
12
+ - Point `GeolocationApiService` at API Platform's `geolocation-api` service instead of the legacy Ramps-owned `on-ramp` geolocation endpoint, which is slated for deprecation ([#9417](https://github.com/MetaMask/core/pull/9417))
13
+ - UAT temporarily resolves to the production URL since API Platform has not yet provisioned a dedicated UAT deployment for this service
12
14
  - Bump `@metamask/controller-utils` from `^12.0.0` to `^12.3.0` ([#8774](https://github.com/MetaMask/core/pull/8774), [#9058](https://github.com/MetaMask/core/pull/9058), [#9083](https://github.com/MetaMask/core/pull/9083), [#9218](https://github.com/MetaMask/core/pull/9218))
13
15
  - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392))
14
16
 
@@ -16,7 +16,7 @@ exports.GeolocationApiService = exports.UNKNOWN_LOCATION = exports.serviceName =
16
16
  const controller_utils_1 = require("@metamask/controller-utils");
17
17
  const types_1 = require("../types.cjs");
18
18
  const DEFAULT_TTL_MS = 5 * 60 * 1000;
19
- const ENDPOINT_PATH = '/geolocation';
19
+ const ENDPOINT_PATH = '/v1/geolocation';
20
20
  // === GENERAL ===
21
21
  /**
22
22
  * The name of the {@link GeolocationApiService}, used to namespace the
@@ -34,12 +34,17 @@ const MESSENGER_EXPOSED_METHODS = ['fetchGeolocation'];
34
34
  /**
35
35
  * Returns the base URL for the geolocation API for the given environment.
36
36
  *
37
+ * Served by API Platform's `geolocation-api` service, not the legacy
38
+ * Ramps-owned `on-ramp` endpoint this previously pointed to. API Platform has
39
+ * not yet provisioned a dedicated UAT deployment for this service, so UAT
40
+ * temporarily resolves to the production URL until one exists.
41
+ *
37
42
  * @param env - The environment to get the URL for.
38
43
  * @returns The full URL for the geolocation endpoint.
39
44
  */
40
45
  function getGeolocationUrl(env) {
41
- const envPrefix = env === types_1.Env.PRD ? '' : `${env}-`;
42
- return `https://on-ramp.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;
46
+ const envPrefix = env === types_1.Env.DEV ? 'dev-' : '';
47
+ return `https://geolocation.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;
43
48
  }
44
49
  /**
45
50
  * Low-level data service that fetches a location code from the geolocation API.
@@ -1 +1 @@
1
- {"version":3,"file":"geolocation-api-service.cjs","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type { IDisposable } from 'cockatiel';\n\nimport { Env } from '../types';\nimport type { GeolocationApiServiceMethodActions } from './geolocation-api-service-method-action-types';\n\nconst DEFAULT_TTL_MS = 5 * 60 * 1000;\n\nconst ENDPOINT_PATH = '/geolocation';\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// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['fetchGeolocation'] as const;\n\n/**\n * Actions that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceActions = GeolocationApiServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link GeolocationApiServiceMessenger}\n * calls.\n */\ntype AllowedActions = never;\n\n/**\n * Events that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceEvents = never;\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 * @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.PRD ? '' : `${env}-`;\n return `https://on-ramp.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;\n}\n\n/**\n * Options accepted by {@link GeolocationApiService.fetchGeolocation}.\n */\nexport type FetchGeolocationOptions = {\n /** When true, the TTL cache is invalidated so the next request fetches fresh data. */\n bypassCache?: boolean;\n};\n\n/**\n * Low-level data service that fetches a location code from the geolocation API.\n *\n * Responsibilities:\n * - HTTP request to the geolocation endpoint (wrapped in a service policy)\n * - ISO 3166-2 response validation (country code with optional subdivision,\n * e.g. `US`, `US-NY`, `CA-ON`)\n * - TTL-based in-memory cache\n * - Promise deduplication (concurrent callers share a single in-flight request)\n *\n * This class is intentionally not a controller: it does not manage UI state.\n * Its {@link fetchGeolocation} method is automatically registered on the\n * messenger so that controllers and other packages can call it directly.\n */\nexport class GeolocationApiService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n readonly #messenger: GeolocationApiServiceMessenger;\n\n readonly #fetch: typeof globalThis.fetch;\n\n readonly #url: string;\n\n readonly #ttlMs: number;\n\n /**\n * The policy that wraps each HTTP request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n #cachedLocation: string = UNKNOWN_LOCATION;\n\n #lastFetchedAt: number | null = null;\n\n #fetchPromise: Promise<string> | null = null;\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 - Cache TTL in 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 this.name = serviceName;\n this.#messenger = messenger;\n this.#url = getGeolocationUrl(env);\n this.#fetch = fetchFunction;\n this.#ttlMs = ttlMs ?? DEFAULT_TTL_MS;\n this.#policy = createServicePolicy(policyOptions);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a 5xx\n * response, causing a retry.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called when requests are consistently\n * failing or when a successful request takes longer than the degraded\n * threshold.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Returns the geolocation code. Serves from cache when the TTL has not\n * expired, otherwise performs a network fetch. Concurrent callers are\n * deduplicated to a single in-flight request.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, invalidates the TTL cache. If a\n * request is already in-flight it will be reused (deduplication always\n * applies).\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 if (options?.bypassCache) {\n this.#lastFetchedAt = null;\n }\n\n if (this.#isCacheValid()) {\n return this.#cachedLocation;\n }\n\n if (this.#fetchPromise) {\n return this.#fetchPromise;\n }\n\n const promise = this.#performFetch();\n this.#fetchPromise = promise;\n\n try {\n return await promise;\n } finally {\n this.#fetchPromise = null;\n }\n }\n\n /**\n * Checks whether the cached geolocation is still within the TTL window.\n *\n * @returns True if the cache is valid.\n */\n #isCacheValid(): boolean {\n return (\n this.#lastFetchedAt !== null &&\n Date.now() - this.#lastFetchedAt < this.#ttlMs\n );\n }\n\n /**\n * Performs the actual HTTP fetch, wrapped in the service policy for automatic\n * retry and circuit-breaking, and validates the response.\n *\n * @returns The ISO 3166-2 location code string.\n */\n async #performFetch(): Promise<string> {\n const response = await this.#policy.execute(async () => {\n const localResponse = await this.#fetch(this.#url);\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Geolocation fetch failed: ${localResponse.status}`,\n );\n }\n return localResponse;\n });\n\n const raw = (await response.text()).trim();\n const location = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/u.test(raw)\n ? raw\n : UNKNOWN_LOCATION;\n\n if (location !== UNKNOWN_LOCATION) {\n this.#cachedLocation = location;\n this.#lastFetchedAt = Date.now();\n }\n\n return location;\n }\n}\n"]}
1
+ {"version":3,"file":"geolocation-api-service.cjs","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type { IDisposable } from 'cockatiel';\n\nimport { Env } from '../types';\nimport type { GeolocationApiServiceMethodActions } from './geolocation-api-service-method-action-types';\n\nconst DEFAULT_TTL_MS = 5 * 60 * 1000;\n\nconst ENDPOINT_PATH = '/v1/geolocation';\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// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['fetchGeolocation'] as const;\n\n/**\n * Actions that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceActions = GeolocationApiServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link GeolocationApiServiceMessenger}\n * calls.\n */\ntype AllowedActions = never;\n\n/**\n * Events that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceEvents = never;\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 * Options accepted by {@link GeolocationApiService.fetchGeolocation}.\n */\nexport type FetchGeolocationOptions = {\n /** When true, the TTL cache is invalidated so the next request fetches fresh data. */\n bypassCache?: boolean;\n};\n\n/**\n * Low-level data service that fetches a location code from the geolocation API.\n *\n * Responsibilities:\n * - HTTP request to the geolocation endpoint (wrapped in a service policy)\n * - ISO 3166-2 response validation (country code with optional subdivision,\n * e.g. `US`, `US-NY`, `CA-ON`)\n * - TTL-based in-memory cache\n * - Promise deduplication (concurrent callers share a single in-flight request)\n *\n * This class is intentionally not a controller: it does not manage UI state.\n * Its {@link fetchGeolocation} method is automatically registered on the\n * messenger so that controllers and other packages can call it directly.\n */\nexport class GeolocationApiService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n readonly #messenger: GeolocationApiServiceMessenger;\n\n readonly #fetch: typeof globalThis.fetch;\n\n readonly #url: string;\n\n readonly #ttlMs: number;\n\n /**\n * The policy that wraps each HTTP request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n #cachedLocation: string = UNKNOWN_LOCATION;\n\n #lastFetchedAt: number | null = null;\n\n #fetchPromise: Promise<string> | null = null;\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 - Cache TTL in 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 this.name = serviceName;\n this.#messenger = messenger;\n this.#url = getGeolocationUrl(env);\n this.#fetch = fetchFunction;\n this.#ttlMs = ttlMs ?? DEFAULT_TTL_MS;\n this.#policy = createServicePolicy(policyOptions);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a 5xx\n * response, causing a retry.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called when requests are consistently\n * failing or when a successful request takes longer than the degraded\n * threshold.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Returns the geolocation code. Serves from cache when the TTL has not\n * expired, otherwise performs a network fetch. Concurrent callers are\n * deduplicated to a single in-flight request.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, invalidates the TTL cache. If a\n * request is already in-flight it will be reused (deduplication always\n * applies).\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 if (options?.bypassCache) {\n this.#lastFetchedAt = null;\n }\n\n if (this.#isCacheValid()) {\n return this.#cachedLocation;\n }\n\n if (this.#fetchPromise) {\n return this.#fetchPromise;\n }\n\n const promise = this.#performFetch();\n this.#fetchPromise = promise;\n\n try {\n return await promise;\n } finally {\n this.#fetchPromise = null;\n }\n }\n\n /**\n * Checks whether the cached geolocation is still within the TTL window.\n *\n * @returns True if the cache is valid.\n */\n #isCacheValid(): boolean {\n return (\n this.#lastFetchedAt !== null &&\n Date.now() - this.#lastFetchedAt < this.#ttlMs\n );\n }\n\n /**\n * Performs the actual HTTP fetch, wrapped in the service policy for automatic\n * retry and circuit-breaking, and validates the response.\n *\n * @returns The ISO 3166-2 location code string.\n */\n async #performFetch(): Promise<string> {\n const response = await this.#policy.execute(async () => {\n const localResponse = await this.#fetch(this.#url);\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Geolocation fetch failed: ${localResponse.status}`,\n );\n }\n return localResponse;\n });\n\n const raw = (await response.text()).trim();\n const location = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/u.test(raw)\n ? raw\n : UNKNOWN_LOCATION;\n\n if (location !== UNKNOWN_LOCATION) {\n this.#cachedLocation = location;\n this.#lastFetchedAt = Date.now();\n }\n\n return location;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"geolocation-api-service.d.cts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,aAAa,EACd,mCAAmC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAE7C,OAAO,EAAE,GAAG,EAAE,qBAAiB;AAC/B,OAAO,KAAK,EAAE,kCAAkC,EAAE,0DAAsD;AAQxG;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAM1C;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAE9E;;;GAGG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEhD;;;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;AAeF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAqB;;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC;IAuBlC;;;;;;;;;;;;OAYG;gBACS,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;IAcD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;OAOG;IACH,UAAU,CACR,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GACnD,WAAW;IAId;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;CAiE3E"}
1
+ {"version":3,"file":"geolocation-api-service.d.cts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,aAAa,EACd,mCAAmC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAE7C,OAAO,EAAE,GAAG,EAAE,qBAAiB;AAC/B,OAAO,KAAK,EAAE,kCAAkC,EAAE,0DAAsD;AAQxG;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAM1C;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAE9E;;;GAGG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEhD;;;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;AAoBF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAqB;;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC;IAuBlC;;;;;;;;;;;;OAYG;gBACS,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;IAcD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;OAOG;IACH,UAAU,CACR,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GACnD,WAAW;IAId;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;CAiE3E"}
@@ -1 +1 @@
1
- {"version":3,"file":"geolocation-api-service.d.mts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,aAAa,EACd,mCAAmC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAE7C,OAAO,EAAE,GAAG,EAAE,qBAAiB;AAC/B,OAAO,KAAK,EAAE,kCAAkC,EAAE,0DAAsD;AAQxG;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAM1C;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAE9E;;;GAGG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEhD;;;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;AAeF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAqB;;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC;IAuBlC;;;;;;;;;;;;OAYG;gBACS,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;IAcD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;OAOG;IACH,UAAU,CACR,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GACnD,WAAW;IAId;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;CAiE3E"}
1
+ {"version":3,"file":"geolocation-api-service.d.mts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,aAAa,EACd,mCAAmC;AAEpC,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB;AAE7C,OAAO,EAAE,GAAG,EAAE,qBAAiB;AAC/B,OAAO,KAAK,EAAE,kCAAkC,EAAE,0DAAsD;AAQxG;;;GAGG;AACH,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAM1C;;GAEG;AACH,MAAM,MAAM,4BAA4B,GAAG,kCAAkC,CAAC;AAE9E;;;GAGG;AACH,KAAK,cAAc,GAAG,KAAK,CAAC;AAE5B;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEhD;;;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;AAoBF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAqB;;IAChC;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,WAAW,CAAC;IAuBlC;;;;;;;;;;;;OAYG;gBACS,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;IAcD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW;IAIvE;;;;;;;OAOG;IACH,UAAU,CACR,QAAQ,EAAE,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,GACnD,WAAW;IAId;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC;CAiE3E"}
@@ -13,7 +13,7 @@ var _GeolocationApiService_instances, _GeolocationApiService_messenger, _Geoloca
13
13
  import { createServicePolicy, HttpError } from "@metamask/controller-utils";
14
14
  import { Env } from "../types.mjs";
15
15
  const DEFAULT_TTL_MS = 5 * 60 * 1000;
16
- const ENDPOINT_PATH = '/geolocation';
16
+ const ENDPOINT_PATH = '/v1/geolocation';
17
17
  // === GENERAL ===
18
18
  /**
19
19
  * The name of the {@link GeolocationApiService}, used to namespace the
@@ -31,12 +31,17 @@ const MESSENGER_EXPOSED_METHODS = ['fetchGeolocation'];
31
31
  /**
32
32
  * Returns the base URL for the geolocation API for the given environment.
33
33
  *
34
+ * Served by API Platform's `geolocation-api` service, not the legacy
35
+ * Ramps-owned `on-ramp` endpoint this previously pointed to. API Platform has
36
+ * not yet provisioned a dedicated UAT deployment for this service, so UAT
37
+ * temporarily resolves to the production URL until one exists.
38
+ *
34
39
  * @param env - The environment to get the URL for.
35
40
  * @returns The full URL for the geolocation endpoint.
36
41
  */
37
42
  function getGeolocationUrl(env) {
38
- const envPrefix = env === Env.PRD ? '' : `${env}-`;
39
- return `https://on-ramp.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;
43
+ const envPrefix = env === Env.DEV ? 'dev-' : '';
44
+ return `https://geolocation.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;
40
45
  }
41
46
  /**
42
47
  * Low-level data service that fetches a location code from the geolocation API.
@@ -1 +1 @@
1
- {"version":3,"file":"geolocation-api-service.mjs","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type { IDisposable } from 'cockatiel';\n\nimport { Env } from '../types';\nimport type { GeolocationApiServiceMethodActions } from './geolocation-api-service-method-action-types';\n\nconst DEFAULT_TTL_MS = 5 * 60 * 1000;\n\nconst ENDPOINT_PATH = '/geolocation';\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// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['fetchGeolocation'] as const;\n\n/**\n * Actions that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceActions = GeolocationApiServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link GeolocationApiServiceMessenger}\n * calls.\n */\ntype AllowedActions = never;\n\n/**\n * Events that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceEvents = never;\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 * @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.PRD ? '' : `${env}-`;\n return `https://on-ramp.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;\n}\n\n/**\n * Options accepted by {@link GeolocationApiService.fetchGeolocation}.\n */\nexport type FetchGeolocationOptions = {\n /** When true, the TTL cache is invalidated so the next request fetches fresh data. */\n bypassCache?: boolean;\n};\n\n/**\n * Low-level data service that fetches a location code from the geolocation API.\n *\n * Responsibilities:\n * - HTTP request to the geolocation endpoint (wrapped in a service policy)\n * - ISO 3166-2 response validation (country code with optional subdivision,\n * e.g. `US`, `US-NY`, `CA-ON`)\n * - TTL-based in-memory cache\n * - Promise deduplication (concurrent callers share a single in-flight request)\n *\n * This class is intentionally not a controller: it does not manage UI state.\n * Its {@link fetchGeolocation} method is automatically registered on the\n * messenger so that controllers and other packages can call it directly.\n */\nexport class GeolocationApiService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n readonly #messenger: GeolocationApiServiceMessenger;\n\n readonly #fetch: typeof globalThis.fetch;\n\n readonly #url: string;\n\n readonly #ttlMs: number;\n\n /**\n * The policy that wraps each HTTP request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n #cachedLocation: string = UNKNOWN_LOCATION;\n\n #lastFetchedAt: number | null = null;\n\n #fetchPromise: Promise<string> | null = null;\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 - Cache TTL in 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 this.name = serviceName;\n this.#messenger = messenger;\n this.#url = getGeolocationUrl(env);\n this.#fetch = fetchFunction;\n this.#ttlMs = ttlMs ?? DEFAULT_TTL_MS;\n this.#policy = createServicePolicy(policyOptions);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a 5xx\n * response, causing a retry.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called when requests are consistently\n * failing or when a successful request takes longer than the degraded\n * threshold.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Returns the geolocation code. Serves from cache when the TTL has not\n * expired, otherwise performs a network fetch. Concurrent callers are\n * deduplicated to a single in-flight request.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, invalidates the TTL cache. If a\n * request is already in-flight it will be reused (deduplication always\n * applies).\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 if (options?.bypassCache) {\n this.#lastFetchedAt = null;\n }\n\n if (this.#isCacheValid()) {\n return this.#cachedLocation;\n }\n\n if (this.#fetchPromise) {\n return this.#fetchPromise;\n }\n\n const promise = this.#performFetch();\n this.#fetchPromise = promise;\n\n try {\n return await promise;\n } finally {\n this.#fetchPromise = null;\n }\n }\n\n /**\n * Checks whether the cached geolocation is still within the TTL window.\n *\n * @returns True if the cache is valid.\n */\n #isCacheValid(): boolean {\n return (\n this.#lastFetchedAt !== null &&\n Date.now() - this.#lastFetchedAt < this.#ttlMs\n );\n }\n\n /**\n * Performs the actual HTTP fetch, wrapped in the service policy for automatic\n * retry and circuit-breaking, and validates the response.\n *\n * @returns The ISO 3166-2 location code string.\n */\n async #performFetch(): Promise<string> {\n const response = await this.#policy.execute(async () => {\n const localResponse = await this.#fetch(this.#url);\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Geolocation fetch failed: ${localResponse.status}`,\n );\n }\n return localResponse;\n });\n\n const raw = (await response.text()).trim();\n const location = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/u.test(raw)\n ? raw\n : UNKNOWN_LOCATION;\n\n if (location !== UNKNOWN_LOCATION) {\n this.#cachedLocation = location;\n this.#lastFetchedAt = Date.now();\n }\n\n return location;\n }\n}\n"]}
1
+ {"version":3,"file":"geolocation-api-service.mjs","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CreateServicePolicyOptions,\n ServicePolicy,\n} from '@metamask/controller-utils';\nimport { createServicePolicy, HttpError } from '@metamask/controller-utils';\nimport type { Messenger } from '@metamask/messenger';\nimport type { IDisposable } from 'cockatiel';\n\nimport { Env } from '../types';\nimport type { GeolocationApiServiceMethodActions } from './geolocation-api-service-method-action-types';\n\nconst DEFAULT_TTL_MS = 5 * 60 * 1000;\n\nconst ENDPOINT_PATH = '/v1/geolocation';\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// === MESSENGER ===\n\nconst MESSENGER_EXPOSED_METHODS = ['fetchGeolocation'] as const;\n\n/**\n * Actions that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceActions = GeolocationApiServiceMethodActions;\n\n/**\n * Actions from other messengers that {@link GeolocationApiServiceMessenger}\n * calls.\n */\ntype AllowedActions = never;\n\n/**\n * Events that {@link GeolocationApiService} exposes to other consumers.\n */\nexport type GeolocationApiServiceEvents = never;\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 * Options accepted by {@link GeolocationApiService.fetchGeolocation}.\n */\nexport type FetchGeolocationOptions = {\n /** When true, the TTL cache is invalidated so the next request fetches fresh data. */\n bypassCache?: boolean;\n};\n\n/**\n * Low-level data service that fetches a location code from the geolocation API.\n *\n * Responsibilities:\n * - HTTP request to the geolocation endpoint (wrapped in a service policy)\n * - ISO 3166-2 response validation (country code with optional subdivision,\n * e.g. `US`, `US-NY`, `CA-ON`)\n * - TTL-based in-memory cache\n * - Promise deduplication (concurrent callers share a single in-flight request)\n *\n * This class is intentionally not a controller: it does not manage UI state.\n * Its {@link fetchGeolocation} method is automatically registered on the\n * messenger so that controllers and other packages can call it directly.\n */\nexport class GeolocationApiService {\n /**\n * The name of the service.\n */\n readonly name: typeof serviceName;\n\n readonly #messenger: GeolocationApiServiceMessenger;\n\n readonly #fetch: typeof globalThis.fetch;\n\n readonly #url: string;\n\n readonly #ttlMs: number;\n\n /**\n * The policy that wraps each HTTP request.\n *\n * @see {@link createServicePolicy}\n */\n readonly #policy: ServicePolicy;\n\n #cachedLocation: string = UNKNOWN_LOCATION;\n\n #lastFetchedAt: number | null = null;\n\n #fetchPromise: Promise<string> | null = null;\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 - Cache TTL in 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 this.name = serviceName;\n this.#messenger = messenger;\n this.#url = getGeolocationUrl(env);\n this.#fetch = fetchFunction;\n this.#ttlMs = ttlMs ?? DEFAULT_TTL_MS;\n this.#policy = createServicePolicy(policyOptions);\n\n this.#messenger.registerMethodActionHandlers(\n this,\n MESSENGER_EXPOSED_METHODS,\n );\n }\n\n /**\n * Registers a handler that will be called after a request returns a 5xx\n * response, causing a retry.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n * @see {@link createServicePolicy}\n */\n onRetry(listener: Parameters<ServicePolicy['onRetry']>[0]): IDisposable {\n return this.#policy.onRetry(listener);\n }\n\n /**\n * Registers a handler that will be called after a set number of retry rounds\n * prove that requests to the API endpoint consistently return a 5xx response.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n * @see {@link createServicePolicy}\n */\n onBreak(listener: Parameters<ServicePolicy['onBreak']>[0]): IDisposable {\n return this.#policy.onBreak(listener);\n }\n\n /**\n * Registers a handler that will be called when requests are consistently\n * failing or when a successful request takes longer than the degraded\n * threshold.\n *\n * @param listener - The handler to be called.\n * @returns An object that can be used to unregister the handler.\n */\n onDegraded(\n listener: Parameters<ServicePolicy['onDegraded']>[0],\n ): IDisposable {\n return this.#policy.onDegraded(listener);\n }\n\n /**\n * Returns the geolocation code. Serves from cache when the TTL has not\n * expired, otherwise performs a network fetch. Concurrent callers are\n * deduplicated to a single in-flight request.\n *\n * @param options - Optional fetch options.\n * @param options.bypassCache - When true, invalidates the TTL cache. If a\n * request is already in-flight it will be reused (deduplication always\n * applies).\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 if (options?.bypassCache) {\n this.#lastFetchedAt = null;\n }\n\n if (this.#isCacheValid()) {\n return this.#cachedLocation;\n }\n\n if (this.#fetchPromise) {\n return this.#fetchPromise;\n }\n\n const promise = this.#performFetch();\n this.#fetchPromise = promise;\n\n try {\n return await promise;\n } finally {\n this.#fetchPromise = null;\n }\n }\n\n /**\n * Checks whether the cached geolocation is still within the TTL window.\n *\n * @returns True if the cache is valid.\n */\n #isCacheValid(): boolean {\n return (\n this.#lastFetchedAt !== null &&\n Date.now() - this.#lastFetchedAt < this.#ttlMs\n );\n }\n\n /**\n * Performs the actual HTTP fetch, wrapped in the service policy for automatic\n * retry and circuit-breaking, and validates the response.\n *\n * @returns The ISO 3166-2 location code string.\n */\n async #performFetch(): Promise<string> {\n const response = await this.#policy.execute(async () => {\n const localResponse = await this.#fetch(this.#url);\n if (!localResponse.ok) {\n throw new HttpError(\n localResponse.status,\n `Geolocation fetch failed: ${localResponse.status}`,\n );\n }\n return localResponse;\n });\n\n const raw = (await response.text()).trim();\n const location = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/u.test(raw)\n ? raw\n : UNKNOWN_LOCATION;\n\n if (location !== UNKNOWN_LOCATION) {\n this.#cachedLocation = location;\n this.#lastFetchedAt = Date.now();\n }\n\n return location;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/geolocation-controller",
3
- "version": "0.1.3-preview-68fc14e1c",
3
+ "version": "0.1.3-preview-a511f243f",
4
4
  "description": "Centralised geolocation controller with TTL caching and request deduplication",
5
5
  "keywords": [
6
6
  "Ethereum",