@metamask-previews/geolocation-controller 0.1.3-preview-0b2e3c9b4 → 0.1.3-preview-2296abeaf
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 +2 -0
- package/dist/GeolocationController-method-action-types.cjs.map +1 -1
- package/dist/GeolocationController-method-action-types.d.cts +13 -1
- package/dist/GeolocationController-method-action-types.d.cts.map +1 -1
- package/dist/GeolocationController-method-action-types.d.mts +13 -1
- package/dist/GeolocationController-method-action-types.d.mts.map +1 -1
- package/dist/GeolocationController-method-action-types.mjs.map +1 -1
- package/dist/GeolocationController.cjs +44 -6
- package/dist/GeolocationController.cjs.map +1 -1
- package/dist/GeolocationController.d.cts +18 -2
- package/dist/GeolocationController.d.cts.map +1 -1
- package/dist/GeolocationController.d.mts +18 -2
- package/dist/GeolocationController.d.mts.map +1 -1
- package/dist/GeolocationController.mjs +45 -7
- package/dist/GeolocationController.mjs.map +1 -1
- package/dist/geolocation-api-service/geolocation-api-service-method-action-types.cjs.map +1 -1
- package/dist/geolocation-api-service/geolocation-api-service-method-action-types.d.cts +17 -1
- package/dist/geolocation-api-service/geolocation-api-service-method-action-types.d.cts.map +1 -1
- package/dist/geolocation-api-service/geolocation-api-service-method-action-types.d.mts +17 -1
- package/dist/geolocation-api-service/geolocation-api-service-method-action-types.d.mts.map +1 -1
- package/dist/geolocation-api-service/geolocation-api-service-method-action-types.mjs.map +1 -1
- package/dist/geolocation-api-service/geolocation-api-service.cjs +124 -20
- package/dist/geolocation-api-service/geolocation-api-service.cjs.map +1 -1
- package/dist/geolocation-api-service/geolocation-api-service.d.cts +52 -5
- package/dist/geolocation-api-service/geolocation-api-service.d.cts.map +1 -1
- package/dist/geolocation-api-service/geolocation-api-service.d.mts +52 -5
- package/dist/geolocation-api-service/geolocation-api-service.d.mts.map +1 -1
- package/dist/geolocation-api-service/geolocation-api-service.mjs +121 -19
- package/dist/geolocation-api-service/geolocation-api-service.mjs.map +1 -1
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -19,8 +19,24 @@ export type GeolocationApiServiceFetchGeolocationAction = {
|
|
|
19
19
|
type: `GeolocationApiService:fetchGeolocation`;
|
|
20
20
|
handler: GeolocationApiService['fetchGeolocation'];
|
|
21
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* Returns the country, region, and timezone for the current client. Serves
|
|
24
|
+
* from cache when the TTL has not expired, otherwise performs a network
|
|
25
|
+
* fetch. Concurrent callers are deduplicated to a single in-flight request.
|
|
26
|
+
*
|
|
27
|
+
* @param options - Optional fetch options.
|
|
28
|
+
* @param options.bypassCache - When true, invalidates the TTL cache. If a
|
|
29
|
+
* request is already in-flight it will be reused (deduplication always
|
|
30
|
+
* applies).
|
|
31
|
+
* @returns The geolocation data, where each field is `null` when the API
|
|
32
|
+
* omits it or returns a value that fails validation.
|
|
33
|
+
*/
|
|
34
|
+
export type GeolocationApiServiceFetchGeolocationDataAction = {
|
|
35
|
+
type: `GeolocationApiService:fetchGeolocationData`;
|
|
36
|
+
handler: GeolocationApiService['fetchGeolocationData'];
|
|
37
|
+
};
|
|
22
38
|
/**
|
|
23
39
|
* Union of all GeolocationApiService action types.
|
|
24
40
|
*/
|
|
25
|
-
export type GeolocationApiServiceMethodActions = GeolocationApiServiceFetchGeolocationAction;
|
|
41
|
+
export type GeolocationApiServiceMethodActions = GeolocationApiServiceFetchGeolocationAction | GeolocationApiServiceFetchGeolocationDataAction;
|
|
26
42
|
//# sourceMappingURL=geolocation-api-service-method-action-types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geolocation-api-service-method-action-types.d.mts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,sCAAqC;AAE1E;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,2CAA2C,GAAG;IACxD,IAAI,EAAE,wCAAwC,CAAC;IAC/C,OAAO,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAC;CACpD,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kCAAkC,
|
|
1
|
+
{"version":3,"file":"geolocation-api-service-method-action-types.d.mts","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service-method-action-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,sCAAqC;AAE1E;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,2CAA2C,GAAG;IACxD,IAAI,EAAE,wCAAwC,CAAC;IAC/C,OAAO,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAC;CACpD,CAAC;AAEF;;;;;;;;;;;GAWG;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.mjs","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. 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 */\nexport type GeolocationApiServiceFetchGeolocationAction = {\n type: `GeolocationApiService:fetchGeolocation`;\n handler: GeolocationApiService['fetchGeolocation'];\n};\n\n/**\n * Union of all GeolocationApiService action types.\n */\nexport type GeolocationApiServiceMethodActions =\n GeolocationApiServiceFetchGeolocationAction;\n"]}
|
|
1
|
+
{"version":3,"file":"geolocation-api-service-method-action-types.mjs","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. 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 */\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. Concurrent callers are 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 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"]}
|
|
@@ -10,13 +10,16 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
10
10
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
11
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
12
|
};
|
|
13
|
-
var _GeolocationApiService_instances, _GeolocationApiService_messenger, _GeolocationApiService_fetch, _GeolocationApiService_url, _GeolocationApiService_ttlMs, _GeolocationApiService_policy,
|
|
13
|
+
var _GeolocationApiService_instances, _GeolocationApiService_messenger, _GeolocationApiService_fetch, _GeolocationApiService_url, _GeolocationApiService_ttlMs, _GeolocationApiService_policy, _GeolocationApiService_cachedGeolocation, _GeolocationApiService_lastFetchedAt, _GeolocationApiService_fetchPromise, _GeolocationApiService_isCacheValid, _GeolocationApiService_performFetch;
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.GeolocationApiService = exports.UNKNOWN_LOCATION = exports.serviceName = void 0;
|
|
15
|
+
exports.GeolocationApiService = exports.toLocationCode = exports.getUnknownGeolocationData = exports.UNKNOWN_LOCATION = exports.serviceName = void 0;
|
|
16
16
|
const controller_utils_1 = require("@metamask/controller-utils");
|
|
17
17
|
const types_js_1 = require("../types.cjs");
|
|
18
18
|
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
19
|
-
const ENDPOINT_PATH = '/
|
|
19
|
+
const ENDPOINT_PATH = '/v2/geolocation';
|
|
20
|
+
const COUNTRY_PATTERN = /^[A-Z]{2}$/u;
|
|
21
|
+
const REGION_PATTERN = /^[A-Z0-9]{1,3}$/u;
|
|
22
|
+
const TIMEZONE_PATTERN = /^[A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+)*$/u;
|
|
20
23
|
// === GENERAL ===
|
|
21
24
|
/**
|
|
22
25
|
* The name of the {@link GeolocationApiService}, used to namespace the
|
|
@@ -28,8 +31,49 @@ exports.serviceName = 'GeolocationApiService';
|
|
|
28
31
|
* the API returns an empty / invalid response.
|
|
29
32
|
*/
|
|
30
33
|
exports.UNKNOWN_LOCATION = 'UNKNOWN';
|
|
34
|
+
/**
|
|
35
|
+
* Constructs a {@link GeolocationData} with no known fields.
|
|
36
|
+
*
|
|
37
|
+
* @returns Geolocation data where every field is `null`.
|
|
38
|
+
*/
|
|
39
|
+
function getUnknownGeolocationData() {
|
|
40
|
+
return { country: null, region: null, timezone: null };
|
|
41
|
+
}
|
|
42
|
+
exports.getUnknownGeolocationData = getUnknownGeolocationData;
|
|
43
|
+
/**
|
|
44
|
+
* Country codes for which the location code includes the region. This mirrors
|
|
45
|
+
* the legacy `v1` geolocation endpoint, which only appended the subdivision for
|
|
46
|
+
* the United States and Canada (e.g. `US-NY`, `CA-ON`) and returned the country
|
|
47
|
+
* alone for everywhere else.
|
|
48
|
+
*/
|
|
49
|
+
const REGION_APPENDED_COUNTRIES = new Set(['US', 'CA']);
|
|
50
|
+
/**
|
|
51
|
+
* Converts geolocation data to a location code.
|
|
52
|
+
*
|
|
53
|
+
* To preserve backwards compatibility with the legacy `v1` endpoint, the region
|
|
54
|
+
* is appended only for {@link REGION_APPENDED_COUNTRIES} (e.g. `US-NY`,
|
|
55
|
+
* `CA-ON`); all other countries return the country code alone (e.g. `FR`), even
|
|
56
|
+
* when a region is known.
|
|
57
|
+
*
|
|
58
|
+
* @param data - The geolocation data to convert.
|
|
59
|
+
* @returns The location code (e.g. `US-NY`, `FR`), or
|
|
60
|
+
* {@link UNKNOWN_LOCATION} when the country is unknown.
|
|
61
|
+
*/
|
|
62
|
+
function toLocationCode(data) {
|
|
63
|
+
if (data.country === null) {
|
|
64
|
+
return exports.UNKNOWN_LOCATION;
|
|
65
|
+
}
|
|
66
|
+
if (data.region !== null && REGION_APPENDED_COUNTRIES.has(data.country)) {
|
|
67
|
+
return `${data.country}-${data.region}`;
|
|
68
|
+
}
|
|
69
|
+
return data.country;
|
|
70
|
+
}
|
|
71
|
+
exports.toLocationCode = toLocationCode;
|
|
31
72
|
// === MESSENGER ===
|
|
32
|
-
const MESSENGER_EXPOSED_METHODS = [
|
|
73
|
+
const MESSENGER_EXPOSED_METHODS = [
|
|
74
|
+
'fetchGeolocation',
|
|
75
|
+
'fetchGeolocationData',
|
|
76
|
+
];
|
|
33
77
|
// === SERVICE DEFINITION ===
|
|
34
78
|
/**
|
|
35
79
|
* Returns the base URL for the geolocation API for the given environment.
|
|
@@ -47,18 +91,66 @@ function getGeolocationUrl(env) {
|
|
|
47
91
|
return `https://geolocation.${envPrefix}api.cx.metamask.io${ENDPOINT_PATH}`;
|
|
48
92
|
}
|
|
49
93
|
/**
|
|
50
|
-
*
|
|
94
|
+
* Reads a string field from a parsed response body, keeping it only when it
|
|
95
|
+
* matches the expected format.
|
|
96
|
+
*
|
|
97
|
+
* @param body - The parsed response body.
|
|
98
|
+
* @param field - The name of the field to read.
|
|
99
|
+
* @param pattern - The pattern the field value must match.
|
|
100
|
+
* @returns The trimmed field value, or `null` when it is missing or invalid.
|
|
101
|
+
*/
|
|
102
|
+
function readValidatedField(body, field, pattern) {
|
|
103
|
+
const value = body[field];
|
|
104
|
+
if (typeof value !== 'string') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const trimmed = value.trim();
|
|
108
|
+
return pattern.test(trimmed) ? trimmed : null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parses and validates the geolocation API response body.
|
|
112
|
+
*
|
|
113
|
+
* The endpoint is expected to return JSON such as
|
|
114
|
+
* `{"country":"US","region":"WA","timezone":"America/Los_Angeles"}`. Anything
|
|
115
|
+
* that cannot be parsed, or any individual field that fails validation, is
|
|
116
|
+
* reported as unknown rather than throwing, so that consumers can keep working
|
|
117
|
+
* with partial or missing data.
|
|
118
|
+
*
|
|
119
|
+
* @param raw - The raw response body.
|
|
120
|
+
* @returns The validated geolocation data.
|
|
121
|
+
*/
|
|
122
|
+
function parseGeolocationResponse(raw) {
|
|
123
|
+
let body;
|
|
124
|
+
try {
|
|
125
|
+
body = JSON.parse(raw);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return getUnknownGeolocationData();
|
|
129
|
+
}
|
|
130
|
+
if (typeof body !== 'object' || body === null || Array.isArray(body)) {
|
|
131
|
+
return getUnknownGeolocationData();
|
|
132
|
+
}
|
|
133
|
+
const record = body;
|
|
134
|
+
return {
|
|
135
|
+
country: readValidatedField(record, 'country', COUNTRY_PATTERN),
|
|
136
|
+
region: readValidatedField(record, 'region', REGION_PATTERN),
|
|
137
|
+
timezone: readValidatedField(record, 'timezone', TIMEZONE_PATTERN),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Low-level data service that fetches geolocation details from the geolocation
|
|
142
|
+
* API.
|
|
51
143
|
*
|
|
52
144
|
* Responsibilities:
|
|
53
145
|
* - HTTP request to the geolocation endpoint (wrapped in a service policy)
|
|
54
|
-
* -
|
|
55
|
-
* e.g. `US`, `US-NY`, `CA-ON`)
|
|
146
|
+
* - Response validation of the country, region, and timezone fields
|
|
56
147
|
* - TTL-based in-memory cache
|
|
57
148
|
* - Promise deduplication (concurrent callers share a single in-flight request)
|
|
58
149
|
*
|
|
59
150
|
* This class is intentionally not a controller: it does not manage UI state.
|
|
60
|
-
* Its {@link fetchGeolocation}
|
|
61
|
-
* messenger so that controllers and other
|
|
151
|
+
* Its {@link fetchGeolocation} and {@link fetchGeolocationData} methods are
|
|
152
|
+
* automatically registered on the messenger so that controllers and other
|
|
153
|
+
* packages can call them directly.
|
|
62
154
|
*/
|
|
63
155
|
class GeolocationApiService {
|
|
64
156
|
/**
|
|
@@ -86,7 +178,7 @@ class GeolocationApiService {
|
|
|
86
178
|
* @see {@link createServicePolicy}
|
|
87
179
|
*/
|
|
88
180
|
_GeolocationApiService_policy.set(this, void 0);
|
|
89
|
-
|
|
181
|
+
_GeolocationApiService_cachedGeolocation.set(this, getUnknownGeolocationData());
|
|
90
182
|
_GeolocationApiService_lastFetchedAt.set(this, null);
|
|
91
183
|
_GeolocationApiService_fetchPromise.set(this, null);
|
|
92
184
|
this.name = exports.serviceName;
|
|
@@ -143,11 +235,26 @@ class GeolocationApiService {
|
|
|
143
235
|
* {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.
|
|
144
236
|
*/
|
|
145
237
|
async fetchGeolocation(options) {
|
|
238
|
+
return toLocationCode(await this.fetchGeolocationData(options));
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Returns the country, region, and timezone for the current client. Serves
|
|
242
|
+
* from cache when the TTL has not expired, otherwise performs a network
|
|
243
|
+
* fetch. Concurrent callers are deduplicated to a single in-flight request.
|
|
244
|
+
*
|
|
245
|
+
* @param options - Optional fetch options.
|
|
246
|
+
* @param options.bypassCache - When true, invalidates the TTL cache. If a
|
|
247
|
+
* request is already in-flight it will be reused (deduplication always
|
|
248
|
+
* applies).
|
|
249
|
+
* @returns The geolocation data, where each field is `null` when the API
|
|
250
|
+
* omits it or returns a value that fails validation.
|
|
251
|
+
*/
|
|
252
|
+
async fetchGeolocationData(options) {
|
|
146
253
|
if (options?.bypassCache) {
|
|
147
254
|
__classPrivateFieldSet(this, _GeolocationApiService_lastFetchedAt, null, "f");
|
|
148
255
|
}
|
|
149
256
|
if (__classPrivateFieldGet(this, _GeolocationApiService_instances, "m", _GeolocationApiService_isCacheValid).call(this)) {
|
|
150
|
-
return __classPrivateFieldGet(this,
|
|
257
|
+
return __classPrivateFieldGet(this, _GeolocationApiService_cachedGeolocation, "f");
|
|
151
258
|
}
|
|
152
259
|
if (__classPrivateFieldGet(this, _GeolocationApiService_fetchPromise, "f")) {
|
|
153
260
|
return __classPrivateFieldGet(this, _GeolocationApiService_fetchPromise, "f");
|
|
@@ -163,7 +270,7 @@ class GeolocationApiService {
|
|
|
163
270
|
}
|
|
164
271
|
}
|
|
165
272
|
exports.GeolocationApiService = GeolocationApiService;
|
|
166
|
-
_GeolocationApiService_messenger = new WeakMap(), _GeolocationApiService_fetch = new WeakMap(), _GeolocationApiService_url = new WeakMap(), _GeolocationApiService_ttlMs = new WeakMap(), _GeolocationApiService_policy = new WeakMap(),
|
|
273
|
+
_GeolocationApiService_messenger = new WeakMap(), _GeolocationApiService_fetch = new WeakMap(), _GeolocationApiService_url = new WeakMap(), _GeolocationApiService_ttlMs = new WeakMap(), _GeolocationApiService_policy = new WeakMap(), _GeolocationApiService_cachedGeolocation = new WeakMap(), _GeolocationApiService_lastFetchedAt = new WeakMap(), _GeolocationApiService_fetchPromise = new WeakMap(), _GeolocationApiService_instances = new WeakSet(), _GeolocationApiService_isCacheValid = function _GeolocationApiService_isCacheValid() {
|
|
167
274
|
return (__classPrivateFieldGet(this, _GeolocationApiService_lastFetchedAt, "f") !== null &&
|
|
168
275
|
Date.now() - __classPrivateFieldGet(this, _GeolocationApiService_lastFetchedAt, "f") < __classPrivateFieldGet(this, _GeolocationApiService_ttlMs, "f"));
|
|
169
276
|
}, _GeolocationApiService_performFetch =
|
|
@@ -171,7 +278,7 @@ _GeolocationApiService_messenger = new WeakMap(), _GeolocationApiService_fetch =
|
|
|
171
278
|
* Performs the actual HTTP fetch, wrapped in the service policy for automatic
|
|
172
279
|
* retry and circuit-breaking, and validates the response.
|
|
173
280
|
*
|
|
174
|
-
* @returns The
|
|
281
|
+
* @returns The validated geolocation data.
|
|
175
282
|
*/
|
|
176
283
|
async function _GeolocationApiService_performFetch() {
|
|
177
284
|
const response = await __classPrivateFieldGet(this, _GeolocationApiService_policy, "f").execute(async () => {
|
|
@@ -181,14 +288,11 @@ async function _GeolocationApiService_performFetch() {
|
|
|
181
288
|
}
|
|
182
289
|
return localResponse;
|
|
183
290
|
});
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
: exports.UNKNOWN_LOCATION;
|
|
188
|
-
if (location !== exports.UNKNOWN_LOCATION) {
|
|
189
|
-
__classPrivateFieldSet(this, _GeolocationApiService_cachedLocation, location, "f");
|
|
291
|
+
const geolocation = parseGeolocationResponse((await response.text()).trim());
|
|
292
|
+
if (geolocation.country !== null) {
|
|
293
|
+
__classPrivateFieldSet(this, _GeolocationApiService_cachedGeolocation, geolocation, "f");
|
|
190
294
|
__classPrivateFieldSet(this, _GeolocationApiService_lastFetchedAt, Date.now(), "f");
|
|
191
295
|
}
|
|
192
|
-
return
|
|
296
|
+
return geolocation;
|
|
193
297
|
};
|
|
194
298
|
//# sourceMappingURL=geolocation-api-service.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"geolocation-api-service.cjs","sourceRoot":"","sources":["../../src/geolocation-api-service/geolocation-api-service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAIA,iEAA4E;AAI5E,2CAAkC;AAGlC,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAErC,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAExC,kBAAkB;AAElB;;;GAGG;AACU,QAAA,WAAW,GAAG,uBAAuB,CAAC;AAEnD;;;GAGG;AACU,QAAA,gBAAgB,GAAG,SAAS,CAAC;AAE1C,oBAAoB;AAEpB,MAAM,yBAAyB,GAAG,CAAC,kBAAkB,CAAU,CAAC;AAkChE,6BAA6B;AAE7B;;;;;;;;;;GAUG;AACH,SAAS,iBAAiB,CAAC,GAAQ;IACjC,MAAM,SAAS,GAAG,GAAG,KAAK,cAAG,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,OAAO,uBAAuB,SAAS,qBAAqB,aAAa,EAAE,CAAC;AAC9E,CAAC;AAUD;;;;;;;;;;;;;GAaG;AACH,MAAa,qBAAqB;IA2BhC;;;;;;;;;;;;OAYG;IACH,YAAY,EACV,SAAS,EACT,GAAG,GAAG,cAAG,CAAC,GAAG,EACb,KAAK,EAAE,aAAa,GAAG,UAAU,CAAC,KAAK,EACvC,KAAK,EACL,aAAa,GAAG,EAAE,GAOnB;;QA9CQ,mDAA2C;QAE3C,+CAAgC;QAEhC,6CAAa;QAEb,+CAAe;QAExB;;;;WAIG;QACM,gDAAuB;QAEhC,gDAA0B,wBAAgB,EAAC;QAE3C,+CAAgC,IAAI,EAAC;QAErC,8CAAwC,IAAI,EAAC;QA4B3C,IAAI,CAAC,IAAI,GAAG,mBAAW,CAAC;QACxB,uBAAA,IAAI,oCAAc,SAAS,MAAA,CAAC;QAC5B,uBAAA,IAAI,8BAAQ,iBAAiB,CAAC,GAAG,CAAC,MAAA,CAAC;QACnC,uBAAA,IAAI,gCAAU,aAAa,MAAA,CAAC;QAC5B,uBAAA,IAAI,gCAAU,KAAK,IAAI,cAAc,MAAA,CAAC;QACtC,uBAAA,IAAI,iCAAW,IAAA,sCAAmB,EAAC,aAAa,CAAC,MAAA,CAAC;QAElD,uBAAA,IAAI,wCAAW,CAAC,4BAA4B,CAC1C,IAAI,EACJ,yBAAyB,CAC1B,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,QAAiD;QACvD,OAAO,uBAAA,IAAI,qCAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,QAAiD;QACvD,OAAO,uBAAA,IAAI,qCAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED;;;;;;;OAOG;IACH,UAAU,CACR,QAAoD;QAEpD,OAAO,uBAAA,IAAI,qCAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,gBAAgB,CAAC,OAAiC;QACtD,IAAI,OAAO,EAAE,WAAW,EAAE,CAAC;YACzB,uBAAA,IAAI,wCAAkB,IAAI,MAAA,CAAC;QAC7B,CAAC;QAED,IAAI,uBAAA,IAAI,6EAAc,MAAlB,IAAI,CAAgB,EAAE,CAAC;YACzB,OAAO,uBAAA,IAAI,6CAAgB,CAAC;QAC9B,CAAC;QAED,IAAI,uBAAA,IAAI,2CAAc,EAAE,CAAC;YACvB,OAAO,uBAAA,IAAI,2CAAc,CAAC;QAC5B,CAAC;QAED,MAAM,OAAO,GAAG,uBAAA,IAAI,6EAAc,MAAlB,IAAI,CAAgB,CAAC;QACrC,uBAAA,IAAI,uCAAiB,OAAO,MAAA,CAAC;QAE7B,IAAI,CAAC;YACH,OAAO,MAAM,OAAO,CAAC;QACvB,CAAC;gBAAS,CAAC;YACT,uBAAA,IAAI,uCAAiB,IAAI,MAAA,CAAC;QAC5B,CAAC;IACH,CAAC;CA4CF;AArLD,sDAqLC;;IApCG,OAAO,CACL,uBAAA,IAAI,4CAAe,KAAK,IAAI;QAC5B,IAAI,CAAC,GAAG,EAAE,GAAG,uBAAA,IAAI,4CAAe,GAAG,uBAAA,IAAI,oCAAO,CAC/C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,KAAK;IACH,MAAM,QAAQ,GAAG,MAAM,uBAAA,IAAI,qCAAQ,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;QACrD,MAAM,aAAa,GAAG,MAAM,uBAAA,IAAI,oCAAO,MAAX,IAAI,EAAQ,uBAAA,IAAI,kCAAK,CAAC,CAAC;QACnD,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC;YACtB,MAAM,IAAI,4BAAS,CACjB,aAAa,CAAC,MAAM,EACpB,6BAA6B,aAAa,CAAC,MAAM,EAAE,CACpD,CAAC;QACJ,CAAC;QACD,OAAO,aAAa,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,8BAA8B,CAAC,IAAI,CAAC,GAAG,CAAC;QACvD,CAAC,CAAC,GAAG;QACL,CAAC,CAAC,wBAAgB,CAAC;IAErB,IAAI,QAAQ,KAAK,wBAAgB,EAAE,CAAC;QAClC,uBAAA,IAAI,yCAAmB,QAAQ,MAAA,CAAC;QAChC,uBAAA,IAAI,wCAAkB,IAAI,CAAC,GAAG,EAAE,MAAA,CAAC;IACnC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC","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.js';\nimport type { GeolocationApiServiceMethodActions } from './geolocation-api-service-method-action-types.js';\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
|
+
{"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.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 */\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 * 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 * 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, 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 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 * - 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} 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 {\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 #cachedGeolocation: GeolocationData = getUnknownGeolocationData();\n\n #lastFetchedAt: number | null = null;\n\n #fetchPromise: Promise<GeolocationData> | 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 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. Concurrent callers are 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 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 if (options?.bypassCache) {\n this.#lastFetchedAt = null;\n }\n\n if (this.#isCacheValid()) {\n return this.#cachedGeolocation;\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 validated geolocation data.\n */\n async #performFetch(): Promise<GeolocationData> {\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 geolocation = parseGeolocationResponse(\n (await response.text()).trim(),\n );\n\n if (geolocation.country !== null) {\n this.#cachedGeolocation = geolocation;\n this.#lastFetchedAt = Date.now();\n }\n\n return geolocation;\n }\n}\n"]}
|
|
@@ -13,6 +13,39 @@ export declare const serviceName = "GeolocationApiService";
|
|
|
13
13
|
* the API returns an empty / invalid response.
|
|
14
14
|
*/
|
|
15
15
|
export declare const UNKNOWN_LOCATION = "UNKNOWN";
|
|
16
|
+
/**
|
|
17
|
+
* Geolocation details returned by the geolocation API.
|
|
18
|
+
*
|
|
19
|
+
* Each field is `null` when the API omits it or returns a value that fails
|
|
20
|
+
* validation.
|
|
21
|
+
*/
|
|
22
|
+
export type GeolocationData = {
|
|
23
|
+
/** ISO 3166-1 alpha-2 country code (e.g. `US`, `FR`). */
|
|
24
|
+
country: string | null;
|
|
25
|
+
/** ISO 3166-2 subdivision code without the country prefix (e.g. `WA`). */
|
|
26
|
+
region: string | null;
|
|
27
|
+
/** IANA time zone name (e.g. `America/Los_Angeles`). */
|
|
28
|
+
timezone: string | null;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Constructs a {@link GeolocationData} with no known fields.
|
|
32
|
+
*
|
|
33
|
+
* @returns Geolocation data where every field is `null`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getUnknownGeolocationData(): GeolocationData;
|
|
36
|
+
/**
|
|
37
|
+
* Converts geolocation data to a location code.
|
|
38
|
+
*
|
|
39
|
+
* To preserve backwards compatibility with the legacy `v1` endpoint, the region
|
|
40
|
+
* is appended only for {@link REGION_APPENDED_COUNTRIES} (e.g. `US-NY`,
|
|
41
|
+
* `CA-ON`); all other countries return the country code alone (e.g. `FR`), even
|
|
42
|
+
* when a region is known.
|
|
43
|
+
*
|
|
44
|
+
* @param data - The geolocation data to convert.
|
|
45
|
+
* @returns The location code (e.g. `US-NY`, `FR`), or
|
|
46
|
+
* {@link UNKNOWN_LOCATION} when the country is unknown.
|
|
47
|
+
*/
|
|
48
|
+
export declare function toLocationCode(data: GeolocationData): string;
|
|
16
49
|
/**
|
|
17
50
|
* Actions that {@link GeolocationApiService} exposes to other consumers.
|
|
18
51
|
*/
|
|
@@ -44,18 +77,19 @@ export type FetchGeolocationOptions = {
|
|
|
44
77
|
bypassCache?: boolean;
|
|
45
78
|
};
|
|
46
79
|
/**
|
|
47
|
-
* Low-level data service that fetches
|
|
80
|
+
* Low-level data service that fetches geolocation details from the geolocation
|
|
81
|
+
* API.
|
|
48
82
|
*
|
|
49
83
|
* Responsibilities:
|
|
50
84
|
* - HTTP request to the geolocation endpoint (wrapped in a service policy)
|
|
51
|
-
* -
|
|
52
|
-
* e.g. `US`, `US-NY`, `CA-ON`)
|
|
85
|
+
* - Response validation of the country, region, and timezone fields
|
|
53
86
|
* - TTL-based in-memory cache
|
|
54
87
|
* - Promise deduplication (concurrent callers share a single in-flight request)
|
|
55
88
|
*
|
|
56
89
|
* This class is intentionally not a controller: it does not manage UI state.
|
|
57
|
-
* Its {@link fetchGeolocation}
|
|
58
|
-
* messenger so that controllers and other
|
|
90
|
+
* Its {@link fetchGeolocation} and {@link fetchGeolocationData} methods are
|
|
91
|
+
* automatically registered on the messenger so that controllers and other
|
|
92
|
+
* packages can call them directly.
|
|
59
93
|
*/
|
|
60
94
|
export declare class GeolocationApiService {
|
|
61
95
|
#private;
|
|
@@ -123,6 +157,19 @@ export declare class GeolocationApiService {
|
|
|
123
157
|
* {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.
|
|
124
158
|
*/
|
|
125
159
|
fetchGeolocation(options?: FetchGeolocationOptions): Promise<string>;
|
|
160
|
+
/**
|
|
161
|
+
* Returns the country, region, and timezone for the current client. Serves
|
|
162
|
+
* from cache when the TTL has not expired, otherwise performs a network
|
|
163
|
+
* fetch. Concurrent callers are deduplicated to a single in-flight request.
|
|
164
|
+
*
|
|
165
|
+
* @param options - Optional fetch options.
|
|
166
|
+
* @param options.bypassCache - When true, invalidates the TTL cache. If a
|
|
167
|
+
* request is already in-flight it will be reused (deduplication always
|
|
168
|
+
* applies).
|
|
169
|
+
* @returns The geolocation data, where each field is `null` when the API
|
|
170
|
+
* omits it or returns a value that fails validation.
|
|
171
|
+
*/
|
|
172
|
+
fetchGeolocationData(options?: FetchGeolocationOptions): Promise<GeolocationData>;
|
|
126
173
|
}
|
|
127
174
|
export {};
|
|
128
175
|
//# sourceMappingURL=geolocation-api-service.d.cts.map
|
|
@@ -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,qBAAoB;AAClC,OAAO,KAAK,EAAE,kCAAkC,EAAE,0DAAyD;
|
|
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,qBAAoB;AAClC,OAAO,KAAK,EAAE,kCAAkC,EAAE,0DAAyD;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;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAU5D;AASD;;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;AA+EF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;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;IAI1E;;;;;;;;;;;OAWG;IACG,oBAAoB,CACxB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,eAAe,CAAC;CAgE5B"}
|
|
@@ -13,6 +13,39 @@ export declare const serviceName = "GeolocationApiService";
|
|
|
13
13
|
* the API returns an empty / invalid response.
|
|
14
14
|
*/
|
|
15
15
|
export declare const UNKNOWN_LOCATION = "UNKNOWN";
|
|
16
|
+
/**
|
|
17
|
+
* Geolocation details returned by the geolocation API.
|
|
18
|
+
*
|
|
19
|
+
* Each field is `null` when the API omits it or returns a value that fails
|
|
20
|
+
* validation.
|
|
21
|
+
*/
|
|
22
|
+
export type GeolocationData = {
|
|
23
|
+
/** ISO 3166-1 alpha-2 country code (e.g. `US`, `FR`). */
|
|
24
|
+
country: string | null;
|
|
25
|
+
/** ISO 3166-2 subdivision code without the country prefix (e.g. `WA`). */
|
|
26
|
+
region: string | null;
|
|
27
|
+
/** IANA time zone name (e.g. `America/Los_Angeles`). */
|
|
28
|
+
timezone: string | null;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Constructs a {@link GeolocationData} with no known fields.
|
|
32
|
+
*
|
|
33
|
+
* @returns Geolocation data where every field is `null`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getUnknownGeolocationData(): GeolocationData;
|
|
36
|
+
/**
|
|
37
|
+
* Converts geolocation data to a location code.
|
|
38
|
+
*
|
|
39
|
+
* To preserve backwards compatibility with the legacy `v1` endpoint, the region
|
|
40
|
+
* is appended only for {@link REGION_APPENDED_COUNTRIES} (e.g. `US-NY`,
|
|
41
|
+
* `CA-ON`); all other countries return the country code alone (e.g. `FR`), even
|
|
42
|
+
* when a region is known.
|
|
43
|
+
*
|
|
44
|
+
* @param data - The geolocation data to convert.
|
|
45
|
+
* @returns The location code (e.g. `US-NY`, `FR`), or
|
|
46
|
+
* {@link UNKNOWN_LOCATION} when the country is unknown.
|
|
47
|
+
*/
|
|
48
|
+
export declare function toLocationCode(data: GeolocationData): string;
|
|
16
49
|
/**
|
|
17
50
|
* Actions that {@link GeolocationApiService} exposes to other consumers.
|
|
18
51
|
*/
|
|
@@ -44,18 +77,19 @@ export type FetchGeolocationOptions = {
|
|
|
44
77
|
bypassCache?: boolean;
|
|
45
78
|
};
|
|
46
79
|
/**
|
|
47
|
-
* Low-level data service that fetches
|
|
80
|
+
* Low-level data service that fetches geolocation details from the geolocation
|
|
81
|
+
* API.
|
|
48
82
|
*
|
|
49
83
|
* Responsibilities:
|
|
50
84
|
* - HTTP request to the geolocation endpoint (wrapped in a service policy)
|
|
51
|
-
* -
|
|
52
|
-
* e.g. `US`, `US-NY`, `CA-ON`)
|
|
85
|
+
* - Response validation of the country, region, and timezone fields
|
|
53
86
|
* - TTL-based in-memory cache
|
|
54
87
|
* - Promise deduplication (concurrent callers share a single in-flight request)
|
|
55
88
|
*
|
|
56
89
|
* This class is intentionally not a controller: it does not manage UI state.
|
|
57
|
-
* Its {@link fetchGeolocation}
|
|
58
|
-
* messenger so that controllers and other
|
|
90
|
+
* Its {@link fetchGeolocation} and {@link fetchGeolocationData} methods are
|
|
91
|
+
* automatically registered on the messenger so that controllers and other
|
|
92
|
+
* packages can call them directly.
|
|
59
93
|
*/
|
|
60
94
|
export declare class GeolocationApiService {
|
|
61
95
|
#private;
|
|
@@ -123,6 +157,19 @@ export declare class GeolocationApiService {
|
|
|
123
157
|
* {@link UNKNOWN_LOCATION} when the API returns an empty or invalid body.
|
|
124
158
|
*/
|
|
125
159
|
fetchGeolocation(options?: FetchGeolocationOptions): Promise<string>;
|
|
160
|
+
/**
|
|
161
|
+
* Returns the country, region, and timezone for the current client. Serves
|
|
162
|
+
* from cache when the TTL has not expired, otherwise performs a network
|
|
163
|
+
* fetch. Concurrent callers are deduplicated to a single in-flight request.
|
|
164
|
+
*
|
|
165
|
+
* @param options - Optional fetch options.
|
|
166
|
+
* @param options.bypassCache - When true, invalidates the TTL cache. If a
|
|
167
|
+
* request is already in-flight it will be reused (deduplication always
|
|
168
|
+
* applies).
|
|
169
|
+
* @returns The geolocation data, where each field is `null` when the API
|
|
170
|
+
* omits it or returns a value that fails validation.
|
|
171
|
+
*/
|
|
172
|
+
fetchGeolocationData(options?: FetchGeolocationOptions): Promise<GeolocationData>;
|
|
126
173
|
}
|
|
127
174
|
export {};
|
|
128
175
|
//# sourceMappingURL=geolocation-api-service.d.mts.map
|
|
@@ -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,qBAAoB;AAClC,OAAO,KAAK,EAAE,kCAAkC,EAAE,0DAAyD;
|
|
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,qBAAoB;AAClC,OAAO,KAAK,EAAE,kCAAkC,EAAE,0DAAyD;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;AAUD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAU5D;AASD;;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;AA+EF;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;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;IAI1E;;;;;;;;;;;OAWG;IACG,oBAAoB,CACxB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,eAAe,CAAC;CAgE5B"}
|