@metamask-previews/ramps-controller 3.0.0-preview-a4747b2b → 3.0.0-preview-fa81dffb
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 +0 -16
- package/dist/RampsController.cjs +74 -138
- package/dist/RampsController.cjs.map +1 -1
- package/dist/RampsController.d.cts +28 -36
- package/dist/RampsController.d.cts.map +1 -1
- package/dist/RampsController.d.mts +28 -36
- package/dist/RampsController.d.mts.map +1 -1
- package/dist/RampsController.mjs +74 -138
- package/dist/RampsController.mjs.map +1 -1
- package/dist/RampsService-method-action-types.cjs.map +1 -1
- package/dist/RampsService-method-action-types.d.cts +11 -1
- package/dist/RampsService-method-action-types.d.cts.map +1 -1
- package/dist/RampsService-method-action-types.d.mts +11 -1
- package/dist/RampsService-method-action-types.d.mts.map +1 -1
- package/dist/RampsService-method-action-types.mjs.map +1 -1
- package/dist/RampsService.cjs +12 -11
- package/dist/RampsService.cjs.map +1 -1
- package/dist/RampsService.d.cts +25 -8
- package/dist/RampsService.d.cts.map +1 -1
- package/dist/RampsService.d.mts +25 -8
- package/dist/RampsService.d.mts.map +1 -1
- package/dist/RampsService.mjs +12 -11
- package/dist/RampsService.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,22 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
- Add `preferredProvider` state and `setPreferredProvider()` method to RampsController ([#7617](https://github.com/MetaMask/core/pull/7617))
|
|
13
13
|
|
|
14
|
-
- Export `UserRegion` type ([#7646](https://github.com/MetaMask/core/pull/7646))
|
|
15
|
-
|
|
16
|
-
- Add `defaultAmount` and `quickAmounts` fields to the `Country` type ([#7645](https://github.com/MetaMask/core/pull/7645))
|
|
17
|
-
|
|
18
|
-
### Changed
|
|
19
|
-
|
|
20
|
-
- **BREAKING:** Change `userRegion` from `string | null` to `UserRegion | null`. Access region code via `userRegion.regionCode`. ([#7646](https://github.com/MetaMask/core/pull/7646))
|
|
21
|
-
|
|
22
|
-
- Update `getCountries()` endpoint to use v2 API (`v2/regions/countries`) ([#7645](https://github.com/MetaMask/core/pull/7645))
|
|
23
|
-
|
|
24
|
-
- Add `getApiPath()` helper function for versioned API paths with v2 default ([#7645](https://github.com/MetaMask/core/pull/7645))
|
|
25
|
-
|
|
26
|
-
### Removed
|
|
27
|
-
|
|
28
|
-
- **BREAKING:** Remove eligibility concept from RampsController. The `eligibility` state, `updateEligibility()` method, and `getEligibility()` service method have been removed. The `Eligibility` type and `RampsServiceGetEligibilityAction` are no longer exported. ([#7651](https://github.com/MetaMask/core/pull/7645))
|
|
29
|
-
|
|
30
14
|
## [3.0.0]
|
|
31
15
|
|
|
32
16
|
### Added
|
package/dist/RampsController.cjs
CHANGED
|
@@ -38,6 +38,12 @@ const rampsControllerMetadata = {
|
|
|
38
38
|
includeInStateLogs: true,
|
|
39
39
|
usedInUi: true,
|
|
40
40
|
},
|
|
41
|
+
eligibility: {
|
|
42
|
+
persist: true,
|
|
43
|
+
includeInDebugSnapshot: true,
|
|
44
|
+
includeInStateLogs: true,
|
|
45
|
+
usedInUi: true,
|
|
46
|
+
},
|
|
41
47
|
tokens: {
|
|
42
48
|
persist: true,
|
|
43
49
|
includeInDebugSnapshot: true,
|
|
@@ -63,64 +69,12 @@ function getDefaultRampsControllerState() {
|
|
|
63
69
|
return {
|
|
64
70
|
userRegion: null,
|
|
65
71
|
preferredProvider: null,
|
|
72
|
+
eligibility: null,
|
|
66
73
|
tokens: null,
|
|
67
74
|
requests: {},
|
|
68
75
|
};
|
|
69
76
|
}
|
|
70
77
|
exports.getDefaultRampsControllerState = getDefaultRampsControllerState;
|
|
71
|
-
// === HELPER FUNCTIONS ===
|
|
72
|
-
/**
|
|
73
|
-
* Finds a country and state from a region code string.
|
|
74
|
-
*
|
|
75
|
-
* @param regionCode - The region code (e.g., "us-ca" or "us").
|
|
76
|
-
* @param countries - Array of countries to search.
|
|
77
|
-
* @returns UserRegion object with country and state, or null if not found.
|
|
78
|
-
*/
|
|
79
|
-
function findRegionFromCode(regionCode, countries) {
|
|
80
|
-
const normalizedCode = regionCode.toLowerCase().trim();
|
|
81
|
-
const parts = normalizedCode.split('-');
|
|
82
|
-
const countryCode = parts[0];
|
|
83
|
-
const stateCode = parts[1];
|
|
84
|
-
const country = countries.find((countryItem) => {
|
|
85
|
-
if (countryItem.isoCode?.toLowerCase() === countryCode) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
88
|
-
if (countryItem.id) {
|
|
89
|
-
const id = countryItem.id.toLowerCase();
|
|
90
|
-
if (id.startsWith('/regions/')) {
|
|
91
|
-
const extractedCode = id.replace('/regions/', '').split('/')[0];
|
|
92
|
-
return extractedCode === countryCode;
|
|
93
|
-
}
|
|
94
|
-
return id === countryCode || id.endsWith(`/${countryCode}`);
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
});
|
|
98
|
-
if (!country) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
let state = null;
|
|
102
|
-
if (stateCode && country.states) {
|
|
103
|
-
state =
|
|
104
|
-
country.states.find((stateItem) => {
|
|
105
|
-
if (stateItem.stateId?.toLowerCase() === stateCode) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
if (stateItem.id) {
|
|
109
|
-
const stateId = stateItem.id.toLowerCase();
|
|
110
|
-
if (stateId.includes(`-${stateCode}`) ||
|
|
111
|
-
stateId.endsWith(`/${stateCode}`)) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
return false;
|
|
116
|
-
}) ?? null;
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
country,
|
|
120
|
-
state,
|
|
121
|
-
regionCode: normalizedCode,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
78
|
// === CONTROLLER DEFINITION ===
|
|
125
79
|
/**
|
|
126
80
|
* Manages cryptocurrency on/off ramps functionality.
|
|
@@ -254,110 +208,73 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
254
208
|
return this.state.requests[cacheKey];
|
|
255
209
|
}
|
|
256
210
|
/**
|
|
257
|
-
* Updates the user's region by fetching geolocation.
|
|
258
|
-
* This method calls the RampsService to get the geolocation
|
|
211
|
+
* Updates the user's region by fetching geolocation and eligibility.
|
|
212
|
+
* This method calls the RampsService to get the geolocation,
|
|
213
|
+
* then automatically fetches eligibility for that region.
|
|
259
214
|
*
|
|
260
215
|
* @param options - Options for cache behavior.
|
|
261
|
-
* @returns The user region
|
|
216
|
+
* @returns The user region string.
|
|
262
217
|
*/
|
|
263
218
|
async updateUserRegion(options) {
|
|
264
|
-
// If a userRegion already exists and forceRefresh is not requested,
|
|
265
|
-
// return it immediately without fetching geolocation.
|
|
266
|
-
// This ensures that once a region is set (either via geolocation or manual selection),
|
|
267
|
-
// it will not be overwritten by subsequent geolocation fetches.
|
|
268
|
-
if (this.state.userRegion && !options?.forceRefresh) {
|
|
269
|
-
return this.state.userRegion;
|
|
270
|
-
}
|
|
271
|
-
// When forceRefresh is true, clear the existing region and tokens before fetching
|
|
272
|
-
if (options?.forceRefresh) {
|
|
273
|
-
this.update((state) => {
|
|
274
|
-
state.userRegion = null;
|
|
275
|
-
state.tokens = null;
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
219
|
const cacheKey = (0, RequestCache_1.createCacheKey)('updateUserRegion', []);
|
|
279
|
-
const
|
|
220
|
+
const userRegion = await this.executeRequest(cacheKey, async () => {
|
|
280
221
|
const result = await this.messenger.call('RampsService:getGeolocation');
|
|
281
222
|
return result;
|
|
282
223
|
}, options);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
224
|
+
const normalizedRegion = userRegion
|
|
225
|
+
? userRegion.toLowerCase().trim()
|
|
226
|
+
: userRegion;
|
|
227
|
+
this.update((state) => {
|
|
228
|
+
state.userRegion = normalizedRegion;
|
|
229
|
+
state.tokens = null;
|
|
230
|
+
});
|
|
231
|
+
if (normalizedRegion) {
|
|
232
|
+
try {
|
|
233
|
+
await this.updateEligibility(normalizedRegion, options);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
295
236
|
this.update((state) => {
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (regionChanged) {
|
|
237
|
+
const currentUserRegion = state.userRegion?.toLowerCase().trim();
|
|
238
|
+
if (currentUserRegion === normalizedRegion) {
|
|
239
|
+
state.eligibility = null;
|
|
300
240
|
state.tokens = null;
|
|
301
241
|
}
|
|
302
242
|
});
|
|
303
|
-
return userRegion;
|
|
304
243
|
}
|
|
305
|
-
// Region not found in countries data
|
|
306
|
-
this.update((state) => {
|
|
307
|
-
state.userRegion = null;
|
|
308
|
-
state.tokens = null;
|
|
309
|
-
});
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
// If countries fetch fails, we can't create a valid UserRegion
|
|
314
|
-
// Return null to indicate we don't have valid country data
|
|
315
|
-
this.update((state) => {
|
|
316
|
-
state.userRegion = null;
|
|
317
|
-
state.tokens = null;
|
|
318
|
-
});
|
|
319
|
-
return null;
|
|
320
244
|
}
|
|
245
|
+
return normalizedRegion;
|
|
321
246
|
}
|
|
322
247
|
/**
|
|
323
248
|
* Sets the user's region manually (without fetching geolocation).
|
|
324
249
|
* This allows users to override the detected region.
|
|
325
250
|
*
|
|
326
251
|
* @param region - The region code to set (e.g., "US-CA").
|
|
327
|
-
* @param options - Options for cache behavior.
|
|
328
|
-
* @returns The
|
|
252
|
+
* @param options - Options for cache behavior when fetching eligibility.
|
|
253
|
+
* @returns The eligibility information for the region.
|
|
329
254
|
*/
|
|
330
255
|
async setUserRegion(region, options) {
|
|
331
256
|
const normalizedRegion = region.toLowerCase().trim();
|
|
257
|
+
this.update((state) => {
|
|
258
|
+
state.userRegion = normalizedRegion;
|
|
259
|
+
state.tokens = null;
|
|
260
|
+
});
|
|
332
261
|
try {
|
|
333
|
-
|
|
334
|
-
const userRegion = findRegionFromCode(normalizedRegion, countries);
|
|
335
|
-
if (userRegion) {
|
|
336
|
-
this.update((state) => {
|
|
337
|
-
state.userRegion = userRegion;
|
|
338
|
-
state.tokens = null;
|
|
339
|
-
});
|
|
340
|
-
return userRegion;
|
|
341
|
-
}
|
|
342
|
-
// Region not found in countries data
|
|
343
|
-
this.update((state) => {
|
|
344
|
-
state.userRegion = null;
|
|
345
|
-
state.tokens = null;
|
|
346
|
-
});
|
|
347
|
-
throw new Error(`Region "${normalizedRegion}" not found in countries data. Cannot set user region without valid country information.`);
|
|
262
|
+
return await this.updateEligibility(normalizedRegion, options);
|
|
348
263
|
}
|
|
349
264
|
catch (error) {
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// Countries fetch failed
|
|
265
|
+
// Eligibility fetch failed, but user region was successfully set.
|
|
266
|
+
// Don't let eligibility errors prevent user region state from being updated.
|
|
267
|
+
// Clear eligibility state to avoid showing stale data from a previous location.
|
|
268
|
+
// Only clear if the region still matches to avoid race conditions where a newer
|
|
269
|
+
// region change has already succeeded.
|
|
356
270
|
this.update((state) => {
|
|
357
|
-
state.userRegion
|
|
358
|
-
|
|
271
|
+
const currentUserRegion = state.userRegion?.toLowerCase().trim();
|
|
272
|
+
if (currentUserRegion === normalizedRegion) {
|
|
273
|
+
state.eligibility = null;
|
|
274
|
+
state.tokens = null;
|
|
275
|
+
}
|
|
359
276
|
});
|
|
360
|
-
throw
|
|
277
|
+
throw error;
|
|
361
278
|
}
|
|
362
279
|
}
|
|
363
280
|
/**
|
|
@@ -374,10 +291,8 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
374
291
|
/**
|
|
375
292
|
* Initializes the controller by fetching the user's region from geolocation.
|
|
376
293
|
* This should be called once at app startup to set up the initial region.
|
|
377
|
-
* After the region is set, tokens are fetched
|
|
378
|
-
*
|
|
379
|
-
* If a userRegion already exists (from persistence or manual selection),
|
|
380
|
-
* this method will skip geolocation fetch and only fetch tokens if needed.
|
|
294
|
+
* After the region is set and eligibility is determined, tokens are fetched
|
|
295
|
+
* and saved to state.
|
|
381
296
|
*
|
|
382
297
|
* @param options - Options for cache behavior.
|
|
383
298
|
* @returns Promise that resolves when initialization is complete.
|
|
@@ -389,19 +304,40 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
389
304
|
});
|
|
390
305
|
if (userRegion) {
|
|
391
306
|
try {
|
|
392
|
-
await this.getTokens(userRegion
|
|
307
|
+
await this.getTokens(userRegion, 'buy', options);
|
|
393
308
|
}
|
|
394
309
|
catch {
|
|
395
310
|
// Token fetch failed - error state will be available via selectors
|
|
396
311
|
}
|
|
397
312
|
}
|
|
398
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Updates the eligibility information for a given region.
|
|
316
|
+
*
|
|
317
|
+
* @param isoCode - The ISO code for the region (e.g., "us", "fr", "us-ny").
|
|
318
|
+
* @param options - Options for cache behavior.
|
|
319
|
+
* @returns The eligibility information.
|
|
320
|
+
*/
|
|
321
|
+
async updateEligibility(isoCode, options) {
|
|
322
|
+
const normalizedIsoCode = isoCode.toLowerCase().trim();
|
|
323
|
+
const cacheKey = (0, RequestCache_1.createCacheKey)('updateEligibility', [normalizedIsoCode]);
|
|
324
|
+
const eligibility = await this.executeRequest(cacheKey, async () => {
|
|
325
|
+
return this.messenger.call('RampsService:getEligibility', normalizedIsoCode);
|
|
326
|
+
}, options);
|
|
327
|
+
this.update((state) => {
|
|
328
|
+
const userRegion = state.userRegion?.toLowerCase().trim();
|
|
329
|
+
if (userRegion === undefined || userRegion === normalizedIsoCode) {
|
|
330
|
+
state.eligibility = eligibility;
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
return eligibility;
|
|
334
|
+
}
|
|
399
335
|
/**
|
|
400
336
|
* Fetches the list of supported countries for a given ramp action.
|
|
401
337
|
*
|
|
402
338
|
* @param action - The ramp action type ('buy' or 'sell').
|
|
403
339
|
* @param options - Options for cache behavior.
|
|
404
|
-
* @returns An array of countries.
|
|
340
|
+
* @returns An array of countries with their eligibility information.
|
|
405
341
|
*/
|
|
406
342
|
async getCountries(action = 'buy', options) {
|
|
407
343
|
const cacheKey = (0, RequestCache_1.createCacheKey)('getCountries', [action]);
|
|
@@ -419,7 +355,7 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
419
355
|
* @returns The tokens response containing topTokens and allTokens.
|
|
420
356
|
*/
|
|
421
357
|
async getTokens(region, action = 'buy', options) {
|
|
422
|
-
const regionToUse = region ?? this.state.userRegion
|
|
358
|
+
const regionToUse = region ?? this.state.userRegion;
|
|
423
359
|
if (!regionToUse) {
|
|
424
360
|
throw new Error('Region is required. Either provide a region parameter or ensure userRegion is set in controller state.');
|
|
425
361
|
}
|
|
@@ -429,8 +365,8 @@ class RampsController extends base_controller_1.BaseController {
|
|
|
429
365
|
return this.messenger.call('RampsService:getTokens', normalizedRegion, action);
|
|
430
366
|
}, options);
|
|
431
367
|
this.update((state) => {
|
|
432
|
-
const
|
|
433
|
-
if (
|
|
368
|
+
const userRegion = state.userRegion?.toLowerCase().trim();
|
|
369
|
+
if (userRegion === undefined || userRegion === normalizedRegion) {
|
|
434
370
|
state.tokens = tokens;
|
|
435
371
|
}
|
|
436
372
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RampsController.cjs","sourceRoot":"","sources":["../src/RampsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type { Json } from '@metamask/utils';\n\nimport type { Country, TokensResponse, Provider, State } from './RampsService';\nimport type {\n RampsServiceGetGeolocationAction,\n RampsServiceGetCountriesAction,\n RampsServiceGetTokensAction,\n} from './RampsService-method-action-types';\nimport type {\n RequestCache as RequestCacheType,\n RequestState,\n ExecuteRequestOptions,\n PendingRequest,\n} from './RequestCache';\nimport {\n DEFAULT_REQUEST_CACHE_TTL,\n DEFAULT_REQUEST_CACHE_MAX_SIZE,\n createCacheKey,\n isCacheExpired,\n createLoadingState,\n createSuccessState,\n createErrorState,\n} from './RequestCache';\n\n// === GENERAL ===\n\n/**\n * The name of the {@link RampsController}, used to namespace the\n * controller's actions and events and to namespace the controller's state data\n * when composed with other controllers.\n */\nexport const controllerName = 'RampsController';\n\n// === STATE ===\n\n/**\n * Represents the user's selected region with full country and state objects.\n */\nexport type UserRegion = {\n /**\n * The country object for the selected region.\n */\n country: Country;\n /**\n * The state object if a state was selected, null if only country was selected.\n */\n state: State | null;\n /**\n * The region code string (e.g., \"us-ut\" or \"fr\") used for API calls.\n */\n regionCode: string;\n};\n\n/**\n * Describes the shape of the state object for {@link RampsController}.\n */\nexport type RampsControllerState = {\n /**\n * The user's selected region with full country and state objects.\n * Initially set via geolocation fetch, but can be manually changed by the user.\n * Once set (either via geolocation or manual selection), it will not be overwritten\n * by subsequent geolocation fetches.\n */\n userRegion: UserRegion | null;\n /**\n * The user's preferred provider.\n * Can be manually set by the user.\n */\n preferredProvider: Provider | null;\n /**\n * Tokens fetched for the current region and action.\n * Contains topTokens and allTokens arrays.\n */\n tokens: TokensResponse | null;\n /**\n * Cache of request states, keyed by cache key.\n * This stores loading, success, and error states for API requests.\n */\n requests: RequestCacheType;\n};\n\n/**\n * The metadata for each property in {@link RampsControllerState}.\n */\nconst rampsControllerMetadata = {\n userRegion: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n preferredProvider: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n tokens: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n requests: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<RampsControllerState>;\n\n/**\n * Constructs the default {@link RampsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link RampsController} state.\n */\nexport function getDefaultRampsControllerState(): RampsControllerState {\n return {\n userRegion: null,\n preferredProvider: null,\n tokens: null,\n requests: {},\n };\n}\n\n// === MESSENGER ===\n\n/**\n * Retrieves the state of the {@link RampsController}.\n */\nexport type RampsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n RampsControllerState\n>;\n\n/**\n * Actions that {@link RampsControllerMessenger} exposes to other consumers.\n */\nexport type RampsControllerActions = RampsControllerGetStateAction;\n\n/**\n * Actions from other messengers that {@link RampsController} calls.\n */\ntype AllowedActions =\n | RampsServiceGetGeolocationAction\n | RampsServiceGetCountriesAction\n | RampsServiceGetTokensAction;\n\n/**\n * Published when the state of {@link RampsController} changes.\n */\nexport type RampsControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n RampsControllerState\n>;\n\n/**\n * Events that {@link RampsControllerMessenger} exposes to other consumers.\n */\nexport type RampsControllerEvents = RampsControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link RampsController} subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link RampsController}.\n */\nexport type RampsControllerMessenger = Messenger<\n typeof controllerName,\n RampsControllerActions | AllowedActions,\n RampsControllerEvents | AllowedEvents\n>;\n\n/**\n * Configuration options for the RampsController.\n */\nexport type RampsControllerOptions = {\n /** The messenger suited for this controller. */\n messenger: RampsControllerMessenger;\n /** The desired state with which to initialize this controller. */\n state?: Partial<RampsControllerState>;\n /** Time to live for cached requests in milliseconds. Defaults to 15 minutes. */\n requestCacheTTL?: number;\n /** Maximum number of entries in the request cache. Defaults to 250. */\n requestCacheMaxSize?: number;\n};\n\n// === HELPER FUNCTIONS ===\n\n/**\n * Finds a country and state from a region code string.\n *\n * @param regionCode - The region code (e.g., \"us-ca\" or \"us\").\n * @param countries - Array of countries to search.\n * @returns UserRegion object with country and state, or null if not found.\n */\nfunction findRegionFromCode(\n regionCode: string,\n countries: Country[],\n): UserRegion | null {\n const normalizedCode = regionCode.toLowerCase().trim();\n const parts = normalizedCode.split('-');\n const countryCode = parts[0];\n const stateCode = parts[1];\n\n const country = countries.find((countryItem) => {\n if (countryItem.isoCode?.toLowerCase() === countryCode) {\n return true;\n }\n if (countryItem.id) {\n const id = countryItem.id.toLowerCase();\n if (id.startsWith('/regions/')) {\n const extractedCode = id.replace('/regions/', '').split('/')[0];\n return extractedCode === countryCode;\n }\n return id === countryCode || id.endsWith(`/${countryCode}`);\n }\n return false;\n });\n\n if (!country) {\n return null;\n }\n\n let state: State | null = null;\n if (stateCode && country.states) {\n state =\n country.states.find((stateItem) => {\n if (stateItem.stateId?.toLowerCase() === stateCode) {\n return true;\n }\n if (stateItem.id) {\n const stateId = stateItem.id.toLowerCase();\n if (\n stateId.includes(`-${stateCode}`) ||\n stateId.endsWith(`/${stateCode}`)\n ) {\n return true;\n }\n }\n return false;\n }) ?? null;\n }\n\n return {\n country,\n state,\n regionCode: normalizedCode,\n };\n}\n\n// === CONTROLLER DEFINITION ===\n\n/**\n * Manages cryptocurrency on/off ramps functionality.\n */\nexport class RampsController extends BaseController<\n typeof controllerName,\n RampsControllerState,\n RampsControllerMessenger\n> {\n /**\n * Default TTL for cached requests.\n */\n readonly #requestCacheTTL: number;\n\n /**\n * Maximum number of entries in the request cache.\n */\n readonly #requestCacheMaxSize: number;\n\n /**\n * Map of pending requests for deduplication.\n * Key is the cache key, value is the pending request with abort controller.\n */\n readonly #pendingRequests: Map<string, PendingRequest> = new Map();\n\n /**\n * Constructs a new {@link RampsController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - The desired state with which to initialize this\n * controller. Missing properties will be filled in with defaults.\n * @param args.requestCacheTTL - Time to live for cached requests in milliseconds.\n * @param args.requestCacheMaxSize - Maximum number of entries in the request cache.\n */\n constructor({\n messenger,\n state = {},\n requestCacheTTL = DEFAULT_REQUEST_CACHE_TTL,\n requestCacheMaxSize = DEFAULT_REQUEST_CACHE_MAX_SIZE,\n }: RampsControllerOptions) {\n super({\n messenger,\n metadata: rampsControllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultRampsControllerState(),\n ...state,\n // Always reset requests cache on initialization (non-persisted)\n requests: {},\n },\n });\n\n this.#requestCacheTTL = requestCacheTTL;\n this.#requestCacheMaxSize = requestCacheMaxSize;\n }\n\n /**\n * Executes a request with caching and deduplication.\n *\n * If a request with the same cache key is already in flight, returns the\n * existing promise. If valid cached data exists, returns it without making\n * a new request.\n *\n * @param cacheKey - Unique identifier for this request.\n * @param fetcher - Function that performs the actual fetch. Receives an AbortSignal.\n * @param options - Options for cache behavior.\n * @returns The result of the request.\n */\n async executeRequest<TResult>(\n cacheKey: string,\n fetcher: (signal: AbortSignal) => Promise<TResult>,\n options?: ExecuteRequestOptions,\n ): Promise<TResult> {\n const ttl = options?.ttl ?? this.#requestCacheTTL;\n\n // Check for existing pending request - join it instead of making a duplicate\n const pending = this.#pendingRequests.get(cacheKey);\n if (pending) {\n return pending.promise as Promise<TResult>;\n }\n\n // Check cache validity (unless force refresh)\n if (!options?.forceRefresh) {\n const cached = this.state.requests[cacheKey];\n if (cached && !isCacheExpired(cached, ttl)) {\n return cached.data as TResult;\n }\n }\n\n // Create abort controller for this request\n const abortController = new AbortController();\n const lastFetchedAt = Date.now();\n\n // Update state to loading\n this.#updateRequestState(cacheKey, createLoadingState());\n\n // Create the fetch promise\n const promise = (async (): Promise<TResult> => {\n try {\n const data = await fetcher(abortController.signal);\n\n // Don't update state if aborted\n if (abortController.signal.aborted) {\n throw new Error('Request was aborted');\n }\n\n this.#updateRequestState(\n cacheKey,\n createSuccessState(data as Json, lastFetchedAt),\n );\n return data;\n } catch (error) {\n // Don't update state if aborted\n if (abortController.signal.aborted) {\n throw error;\n }\n\n const errorMessage = (error as Error)?.message;\n\n this.#updateRequestState(\n cacheKey,\n createErrorState(errorMessage ?? 'Unknown error', lastFetchedAt),\n );\n throw error;\n } finally {\n // Only delete if this is still our entry (not replaced by a new request)\n const currentPending = this.#pendingRequests.get(cacheKey);\n if (currentPending?.abortController === abortController) {\n this.#pendingRequests.delete(cacheKey);\n }\n }\n })();\n\n // Store pending request for deduplication\n this.#pendingRequests.set(cacheKey, { promise, abortController });\n\n return promise;\n }\n\n /**\n * Aborts a pending request if one exists.\n *\n * @param cacheKey - The cache key of the request to abort.\n * @returns True if a request was aborted.\n */\n abortRequest(cacheKey: string): boolean {\n const pending = this.#pendingRequests.get(cacheKey);\n if (pending) {\n pending.abortController.abort();\n this.#pendingRequests.delete(cacheKey);\n this.#removeRequestState(cacheKey);\n return true;\n }\n return false;\n }\n\n /**\n * Removes a request state from the cache.\n *\n * @param cacheKey - The cache key to remove.\n */\n #removeRequestState(cacheKey: string): void {\n this.update((state) => {\n const requests = state.requests as unknown as Record<\n string,\n RequestState | undefined\n >;\n delete requests[cacheKey];\n });\n }\n\n /**\n * Gets the state of a specific cached request.\n *\n * @param cacheKey - The cache key to look up.\n * @returns The request state, or undefined if not cached.\n */\n getRequestState(cacheKey: string): RequestState | undefined {\n return this.state.requests[cacheKey];\n }\n\n /**\n * Updates the state for a specific request.\n *\n * @param cacheKey - The cache key.\n * @param requestState - The new state for the request.\n */\n #updateRequestState(cacheKey: string, requestState: RequestState): void {\n const maxSize = this.#requestCacheMaxSize;\n\n this.update((state) => {\n const requests = state.requests as unknown as Record<\n string,\n RequestState | undefined\n >;\n requests[cacheKey] = requestState;\n\n // Evict oldest entries if cache exceeds max size\n const keys = Object.keys(requests);\n\n if (keys.length > maxSize) {\n // Sort by timestamp (oldest first)\n const sortedKeys = keys.sort((a, b) => {\n const aTime = requests[a]?.timestamp ?? 0;\n const bTime = requests[b]?.timestamp ?? 0;\n return aTime - bTime;\n });\n\n // Remove oldest entries until we're under the limit\n const entriesToRemove = keys.length - maxSize;\n for (let i = 0; i < entriesToRemove; i++) {\n const keyToRemove = sortedKeys[i];\n if (keyToRemove) {\n delete requests[keyToRemove];\n }\n }\n }\n });\n }\n\n /**\n * Updates the user's region by fetching geolocation.\n * This method calls the RampsService to get the geolocation.\n *\n * @param options - Options for cache behavior.\n * @returns The user region object.\n */\n async updateUserRegion(\n options?: ExecuteRequestOptions,\n ): Promise<UserRegion | null> {\n // If a userRegion already exists and forceRefresh is not requested,\n // return it immediately without fetching geolocation.\n // This ensures that once a region is set (either via geolocation or manual selection),\n // it will not be overwritten by subsequent geolocation fetches.\n if (this.state.userRegion && !options?.forceRefresh) {\n return this.state.userRegion;\n }\n\n // When forceRefresh is true, clear the existing region and tokens before fetching\n if (options?.forceRefresh) {\n this.update((state) => {\n state.userRegion = null;\n state.tokens = null;\n });\n }\n\n const cacheKey = createCacheKey('updateUserRegion', []);\n\n const regionCode = await this.executeRequest(\n cacheKey,\n async () => {\n const result = await this.messenger.call('RampsService:getGeolocation');\n return result;\n },\n options,\n );\n\n if (!regionCode) {\n this.update((state) => {\n state.userRegion = null;\n state.tokens = null;\n });\n return null;\n }\n\n const normalizedRegion = regionCode.toLowerCase().trim();\n\n try {\n const countries = await this.getCountries('buy', options);\n const userRegion = findRegionFromCode(normalizedRegion, countries);\n\n if (userRegion) {\n this.update((state) => {\n const regionChanged =\n state.userRegion?.regionCode !== userRegion.regionCode;\n state.userRegion = userRegion;\n // Clear tokens when region changes\n if (regionChanged) {\n state.tokens = null;\n }\n });\n\n return userRegion;\n }\n\n // Region not found in countries data\n this.update((state) => {\n state.userRegion = null;\n state.tokens = null;\n });\n\n return null;\n } catch {\n // If countries fetch fails, we can't create a valid UserRegion\n // Return null to indicate we don't have valid country data\n this.update((state) => {\n state.userRegion = null;\n state.tokens = null;\n });\n\n return null;\n }\n }\n\n /**\n * Sets the user's region manually (without fetching geolocation).\n * This allows users to override the detected region.\n *\n * @param region - The region code to set (e.g., \"US-CA\").\n * @param options - Options for cache behavior.\n * @returns The user region object.\n */\n async setUserRegion(\n region: string,\n options?: ExecuteRequestOptions,\n ): Promise<UserRegion> {\n const normalizedRegion = region.toLowerCase().trim();\n\n try {\n const countries = await this.getCountries('buy', options);\n const userRegion = findRegionFromCode(normalizedRegion, countries);\n\n if (userRegion) {\n this.update((state) => {\n state.userRegion = userRegion;\n state.tokens = null;\n });\n return userRegion;\n }\n\n // Region not found in countries data\n this.update((state) => {\n state.userRegion = null;\n state.tokens = null;\n });\n throw new Error(\n `Region \"${normalizedRegion}\" not found in countries data. Cannot set user region without valid country information.`,\n );\n } catch (error) {\n // If the error is \"not found\", re-throw it\n // Otherwise, it's from countries fetch failure\n if (error instanceof Error && error.message.includes('not found')) {\n throw error;\n }\n // Countries fetch failed\n this.update((state) => {\n state.userRegion = null;\n state.tokens = null;\n });\n throw new Error(\n 'Failed to fetch countries data. Cannot set user region without valid country information.',\n );\n }\n }\n\n /**\n * Sets the user's preferred provider.\n * This allows users to set their preferred ramp provider.\n *\n * @param provider - The provider object to set.\n */\n setPreferredProvider(provider: Provider | null): void {\n this.update((state) => {\n state.preferredProvider = provider;\n });\n }\n\n /**\n * Initializes the controller by fetching the user's region from geolocation.\n * This should be called once at app startup to set up the initial region.\n * After the region is set, tokens are fetched and saved to state.\n *\n * If a userRegion already exists (from persistence or manual selection),\n * this method will skip geolocation fetch and only fetch tokens if needed.\n *\n * @param options - Options for cache behavior.\n * @returns Promise that resolves when initialization is complete.\n */\n async init(options?: ExecuteRequestOptions): Promise<void> {\n const userRegion = await this.updateUserRegion(options).catch(() => {\n // User region fetch failed - error state will be available via selectors\n return null;\n });\n\n if (userRegion) {\n try {\n await this.getTokens(userRegion.regionCode, 'buy', options);\n } catch {\n // Token fetch failed - error state will be available via selectors\n }\n }\n }\n\n /**\n * Fetches the list of supported countries for a given ramp action.\n *\n * @param action - The ramp action type ('buy' or 'sell').\n * @param options - Options for cache behavior.\n * @returns An array of countries.\n */\n async getCountries(\n action: 'buy' | 'sell' = 'buy',\n options?: ExecuteRequestOptions,\n ): Promise<Country[]> {\n const cacheKey = createCacheKey('getCountries', [action]);\n\n return this.executeRequest(\n cacheKey,\n async () => {\n return this.messenger.call('RampsService:getCountries', action);\n },\n options,\n );\n }\n\n /**\n * Fetches the list of available tokens for a given region and action.\n * The tokens are saved in the controller state once fetched.\n *\n * @param region - The region code (e.g., \"us\", \"fr\", \"us-ny\"). If not provided, uses the user's region from controller state.\n * @param action - The ramp action type ('buy' or 'sell').\n * @param options - Options for cache behavior.\n * @returns The tokens response containing topTokens and allTokens.\n */\n async getTokens(\n region?: string,\n action: 'buy' | 'sell' = 'buy',\n options?: ExecuteRequestOptions,\n ): Promise<TokensResponse> {\n const regionToUse = region ?? this.state.userRegion?.regionCode;\n\n if (!regionToUse) {\n throw new Error(\n 'Region is required. Either provide a region parameter or ensure userRegion is set in controller state.',\n );\n }\n\n const normalizedRegion = regionToUse.toLowerCase().trim();\n const cacheKey = createCacheKey('getTokens', [normalizedRegion, action]);\n\n const tokens = await this.executeRequest(\n cacheKey,\n async () => {\n return this.messenger.call(\n 'RampsService:getTokens',\n normalizedRegion,\n action,\n );\n },\n options,\n );\n\n this.update((state) => {\n const userRegionCode = state.userRegion?.regionCode;\n\n if (userRegionCode === undefined || userRegionCode === normalizedRegion) {\n state.tokens = tokens;\n }\n });\n\n return tokens;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RampsController.cjs","sourceRoot":"","sources":["../src/RampsController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport type { Json } from '@metamask/utils';\n\nimport type {\n Country,\n Eligibility,\n TokensResponse,\n Provider,\n} from './RampsService';\nimport type {\n RampsServiceGetGeolocationAction,\n RampsServiceGetCountriesAction,\n RampsServiceGetEligibilityAction,\n RampsServiceGetTokensAction,\n} from './RampsService-method-action-types';\nimport type {\n RequestCache as RequestCacheType,\n RequestState,\n ExecuteRequestOptions,\n PendingRequest,\n} from './RequestCache';\nimport {\n DEFAULT_REQUEST_CACHE_TTL,\n DEFAULT_REQUEST_CACHE_MAX_SIZE,\n createCacheKey,\n isCacheExpired,\n createLoadingState,\n createSuccessState,\n createErrorState,\n} from './RequestCache';\n\n// === GENERAL ===\n\n/**\n * The name of the {@link RampsController}, used to namespace the\n * controller's actions and events and to namespace the controller's state data\n * when composed with other controllers.\n */\nexport const controllerName = 'RampsController';\n\n// === STATE ===\n\n/**\n * Describes the shape of the state object for {@link RampsController}.\n */\nexport type RampsControllerState = {\n /**\n * The user's selected region code (e.g., \"US-CA\").\n * Initially set via geolocation fetch, but can be manually changed by the user.\n */\n userRegion: string | null;\n /**\n * The user's preferred provider.\n * Can be manually set by the user.\n */\n preferredProvider: Provider | null;\n /**\n * Eligibility information for the user's current region.\n */\n eligibility: Eligibility | null;\n /**\n * Tokens fetched for the current region and action.\n * Contains topTokens and allTokens arrays.\n */\n tokens: TokensResponse | null;\n /**\n * Cache of request states, keyed by cache key.\n * This stores loading, success, and error states for API requests.\n */\n requests: RequestCacheType;\n};\n\n/**\n * The metadata for each property in {@link RampsControllerState}.\n */\nconst rampsControllerMetadata = {\n userRegion: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n preferredProvider: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n eligibility: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n tokens: {\n persist: true,\n includeInDebugSnapshot: true,\n includeInStateLogs: true,\n usedInUi: true,\n },\n requests: {\n persist: false,\n includeInDebugSnapshot: true,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<RampsControllerState>;\n\n/**\n * Constructs the default {@link RampsController} state. This allows\n * consumers to provide a partial state object when initializing the controller\n * and also helps in constructing complete state objects for this controller in\n * tests.\n *\n * @returns The default {@link RampsController} state.\n */\nexport function getDefaultRampsControllerState(): RampsControllerState {\n return {\n userRegion: null,\n preferredProvider: null,\n eligibility: null,\n tokens: null,\n requests: {},\n };\n}\n\n// === MESSENGER ===\n\n/**\n * Retrieves the state of the {@link RampsController}.\n */\nexport type RampsControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n RampsControllerState\n>;\n\n/**\n * Actions that {@link RampsControllerMessenger} exposes to other consumers.\n */\nexport type RampsControllerActions = RampsControllerGetStateAction;\n\n/**\n * Actions from other messengers that {@link RampsController} calls.\n */\ntype AllowedActions =\n | RampsServiceGetGeolocationAction\n | RampsServiceGetCountriesAction\n | RampsServiceGetEligibilityAction\n | RampsServiceGetTokensAction;\n\n/**\n * Published when the state of {@link RampsController} changes.\n */\nexport type RampsControllerStateChangeEvent = ControllerStateChangeEvent<\n typeof controllerName,\n RampsControllerState\n>;\n\n/**\n * Events that {@link RampsControllerMessenger} exposes to other consumers.\n */\nexport type RampsControllerEvents = RampsControllerStateChangeEvent;\n\n/**\n * Events from other messengers that {@link RampsController} subscribes to.\n */\ntype AllowedEvents = never;\n\n/**\n * The messenger restricted to actions and events accessed by\n * {@link RampsController}.\n */\nexport type RampsControllerMessenger = Messenger<\n typeof controllerName,\n RampsControllerActions | AllowedActions,\n RampsControllerEvents | AllowedEvents\n>;\n\n/**\n * Configuration options for the RampsController.\n */\nexport type RampsControllerOptions = {\n /** The messenger suited for this controller. */\n messenger: RampsControllerMessenger;\n /** The desired state with which to initialize this controller. */\n state?: Partial<RampsControllerState>;\n /** Time to live for cached requests in milliseconds. Defaults to 15 minutes. */\n requestCacheTTL?: number;\n /** Maximum number of entries in the request cache. Defaults to 250. */\n requestCacheMaxSize?: number;\n};\n\n// === CONTROLLER DEFINITION ===\n\n/**\n * Manages cryptocurrency on/off ramps functionality.\n */\nexport class RampsController extends BaseController<\n typeof controllerName,\n RampsControllerState,\n RampsControllerMessenger\n> {\n /**\n * Default TTL for cached requests.\n */\n readonly #requestCacheTTL: number;\n\n /**\n * Maximum number of entries in the request cache.\n */\n readonly #requestCacheMaxSize: number;\n\n /**\n * Map of pending requests for deduplication.\n * Key is the cache key, value is the pending request with abort controller.\n */\n readonly #pendingRequests: Map<string, PendingRequest> = new Map();\n\n /**\n * Constructs a new {@link RampsController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - The desired state with which to initialize this\n * controller. Missing properties will be filled in with defaults.\n * @param args.requestCacheTTL - Time to live for cached requests in milliseconds.\n * @param args.requestCacheMaxSize - Maximum number of entries in the request cache.\n */\n constructor({\n messenger,\n state = {},\n requestCacheTTL = DEFAULT_REQUEST_CACHE_TTL,\n requestCacheMaxSize = DEFAULT_REQUEST_CACHE_MAX_SIZE,\n }: RampsControllerOptions) {\n super({\n messenger,\n metadata: rampsControllerMetadata,\n name: controllerName,\n state: {\n ...getDefaultRampsControllerState(),\n ...state,\n // Always reset requests cache on initialization (non-persisted)\n requests: {},\n },\n });\n\n this.#requestCacheTTL = requestCacheTTL;\n this.#requestCacheMaxSize = requestCacheMaxSize;\n }\n\n /**\n * Executes a request with caching and deduplication.\n *\n * If a request with the same cache key is already in flight, returns the\n * existing promise. If valid cached data exists, returns it without making\n * a new request.\n *\n * @param cacheKey - Unique identifier for this request.\n * @param fetcher - Function that performs the actual fetch. Receives an AbortSignal.\n * @param options - Options for cache behavior.\n * @returns The result of the request.\n */\n async executeRequest<TResult>(\n cacheKey: string,\n fetcher: (signal: AbortSignal) => Promise<TResult>,\n options?: ExecuteRequestOptions,\n ): Promise<TResult> {\n const ttl = options?.ttl ?? this.#requestCacheTTL;\n\n // Check for existing pending request - join it instead of making a duplicate\n const pending = this.#pendingRequests.get(cacheKey);\n if (pending) {\n return pending.promise as Promise<TResult>;\n }\n\n // Check cache validity (unless force refresh)\n if (!options?.forceRefresh) {\n const cached = this.state.requests[cacheKey];\n if (cached && !isCacheExpired(cached, ttl)) {\n return cached.data as TResult;\n }\n }\n\n // Create abort controller for this request\n const abortController = new AbortController();\n const lastFetchedAt = Date.now();\n\n // Update state to loading\n this.#updateRequestState(cacheKey, createLoadingState());\n\n // Create the fetch promise\n const promise = (async (): Promise<TResult> => {\n try {\n const data = await fetcher(abortController.signal);\n\n // Don't update state if aborted\n if (abortController.signal.aborted) {\n throw new Error('Request was aborted');\n }\n\n this.#updateRequestState(\n cacheKey,\n createSuccessState(data as Json, lastFetchedAt),\n );\n return data;\n } catch (error) {\n // Don't update state if aborted\n if (abortController.signal.aborted) {\n throw error;\n }\n\n const errorMessage = (error as Error)?.message;\n\n this.#updateRequestState(\n cacheKey,\n createErrorState(errorMessage ?? 'Unknown error', lastFetchedAt),\n );\n throw error;\n } finally {\n // Only delete if this is still our entry (not replaced by a new request)\n const currentPending = this.#pendingRequests.get(cacheKey);\n if (currentPending?.abortController === abortController) {\n this.#pendingRequests.delete(cacheKey);\n }\n }\n })();\n\n // Store pending request for deduplication\n this.#pendingRequests.set(cacheKey, { promise, abortController });\n\n return promise;\n }\n\n /**\n * Aborts a pending request if one exists.\n *\n * @param cacheKey - The cache key of the request to abort.\n * @returns True if a request was aborted.\n */\n abortRequest(cacheKey: string): boolean {\n const pending = this.#pendingRequests.get(cacheKey);\n if (pending) {\n pending.abortController.abort();\n this.#pendingRequests.delete(cacheKey);\n this.#removeRequestState(cacheKey);\n return true;\n }\n return false;\n }\n\n /**\n * Removes a request state from the cache.\n *\n * @param cacheKey - The cache key to remove.\n */\n #removeRequestState(cacheKey: string): void {\n this.update((state) => {\n const requests = state.requests as unknown as Record<\n string,\n RequestState | undefined\n >;\n delete requests[cacheKey];\n });\n }\n\n /**\n * Gets the state of a specific cached request.\n *\n * @param cacheKey - The cache key to look up.\n * @returns The request state, or undefined if not cached.\n */\n getRequestState(cacheKey: string): RequestState | undefined {\n return this.state.requests[cacheKey];\n }\n\n /**\n * Updates the state for a specific request.\n *\n * @param cacheKey - The cache key.\n * @param requestState - The new state for the request.\n */\n #updateRequestState(cacheKey: string, requestState: RequestState): void {\n const maxSize = this.#requestCacheMaxSize;\n\n this.update((state) => {\n const requests = state.requests as unknown as Record<\n string,\n RequestState | undefined\n >;\n requests[cacheKey] = requestState;\n\n // Evict oldest entries if cache exceeds max size\n const keys = Object.keys(requests);\n\n if (keys.length > maxSize) {\n // Sort by timestamp (oldest first)\n const sortedKeys = keys.sort((a, b) => {\n const aTime = requests[a]?.timestamp ?? 0;\n const bTime = requests[b]?.timestamp ?? 0;\n return aTime - bTime;\n });\n\n // Remove oldest entries until we're under the limit\n const entriesToRemove = keys.length - maxSize;\n for (let i = 0; i < entriesToRemove; i++) {\n const keyToRemove = sortedKeys[i];\n if (keyToRemove) {\n delete requests[keyToRemove];\n }\n }\n }\n });\n }\n\n /**\n * Updates the user's region by fetching geolocation and eligibility.\n * This method calls the RampsService to get the geolocation,\n * then automatically fetches eligibility for that region.\n *\n * @param options - Options for cache behavior.\n * @returns The user region string.\n */\n async updateUserRegion(options?: ExecuteRequestOptions): Promise<string> {\n const cacheKey = createCacheKey('updateUserRegion', []);\n\n const userRegion = await this.executeRequest(\n cacheKey,\n async () => {\n const result = await this.messenger.call('RampsService:getGeolocation');\n return result;\n },\n options,\n );\n\n const normalizedRegion = userRegion\n ? userRegion.toLowerCase().trim()\n : userRegion;\n\n this.update((state) => {\n state.userRegion = normalizedRegion;\n state.tokens = null;\n });\n\n if (normalizedRegion) {\n try {\n await this.updateEligibility(normalizedRegion, options);\n } catch {\n this.update((state) => {\n const currentUserRegion = state.userRegion?.toLowerCase().trim();\n if (currentUserRegion === normalizedRegion) {\n state.eligibility = null;\n state.tokens = null;\n }\n });\n }\n }\n\n return normalizedRegion;\n }\n\n /**\n * Sets the user's region manually (without fetching geolocation).\n * This allows users to override the detected region.\n *\n * @param region - The region code to set (e.g., \"US-CA\").\n * @param options - Options for cache behavior when fetching eligibility.\n * @returns The eligibility information for the region.\n */\n async setUserRegion(\n region: string,\n options?: ExecuteRequestOptions,\n ): Promise<Eligibility> {\n const normalizedRegion = region.toLowerCase().trim();\n\n this.update((state) => {\n state.userRegion = normalizedRegion;\n state.tokens = null;\n });\n\n try {\n return await this.updateEligibility(normalizedRegion, options);\n } catch (error) {\n // Eligibility fetch failed, but user region was successfully set.\n // Don't let eligibility errors prevent user region state from being updated.\n // Clear eligibility state to avoid showing stale data from a previous location.\n // Only clear if the region still matches to avoid race conditions where a newer\n // region change has already succeeded.\n this.update((state) => {\n const currentUserRegion = state.userRegion?.toLowerCase().trim();\n if (currentUserRegion === normalizedRegion) {\n state.eligibility = null;\n state.tokens = null;\n }\n });\n throw error;\n }\n }\n\n /**\n * Sets the user's preferred provider.\n * This allows users to set their preferred ramp provider.\n *\n * @param provider - The provider object to set.\n */\n setPreferredProvider(provider: Provider | null): void {\n this.update((state) => {\n state.preferredProvider = provider;\n });\n }\n\n /**\n * Initializes the controller by fetching the user's region from geolocation.\n * This should be called once at app startup to set up the initial region.\n * After the region is set and eligibility is determined, tokens are fetched\n * and saved to state.\n *\n * @param options - Options for cache behavior.\n * @returns Promise that resolves when initialization is complete.\n */\n async init(options?: ExecuteRequestOptions): Promise<void> {\n const userRegion = await this.updateUserRegion(options).catch(() => {\n // User region fetch failed - error state will be available via selectors\n return null;\n });\n\n if (userRegion) {\n try {\n await this.getTokens(userRegion, 'buy', options);\n } catch {\n // Token fetch failed - error state will be available via selectors\n }\n }\n }\n\n /**\n * Updates the eligibility information for a given region.\n *\n * @param isoCode - The ISO code for the region (e.g., \"us\", \"fr\", \"us-ny\").\n * @param options - Options for cache behavior.\n * @returns The eligibility information.\n */\n async updateEligibility(\n isoCode: string,\n options?: ExecuteRequestOptions,\n ): Promise<Eligibility> {\n const normalizedIsoCode = isoCode.toLowerCase().trim();\n const cacheKey = createCacheKey('updateEligibility', [normalizedIsoCode]);\n\n const eligibility = await this.executeRequest(\n cacheKey,\n async () => {\n return this.messenger.call(\n 'RampsService:getEligibility',\n normalizedIsoCode,\n );\n },\n options,\n );\n\n this.update((state) => {\n const userRegion = state.userRegion?.toLowerCase().trim();\n\n if (userRegion === undefined || userRegion === normalizedIsoCode) {\n state.eligibility = eligibility;\n }\n });\n\n return eligibility;\n }\n\n /**\n * Fetches the list of supported countries for a given ramp action.\n *\n * @param action - The ramp action type ('buy' or 'sell').\n * @param options - Options for cache behavior.\n * @returns An array of countries with their eligibility information.\n */\n async getCountries(\n action: 'buy' | 'sell' = 'buy',\n options?: ExecuteRequestOptions,\n ): Promise<Country[]> {\n const cacheKey = createCacheKey('getCountries', [action]);\n\n return this.executeRequest(\n cacheKey,\n async () => {\n return this.messenger.call('RampsService:getCountries', action);\n },\n options,\n );\n }\n\n /**\n * Fetches the list of available tokens for a given region and action.\n * The tokens are saved in the controller state once fetched.\n *\n * @param region - The region code (e.g., \"us\", \"fr\", \"us-ny\"). If not provided, uses the user's region from controller state.\n * @param action - The ramp action type ('buy' or 'sell').\n * @param options - Options for cache behavior.\n * @returns The tokens response containing topTokens and allTokens.\n */\n async getTokens(\n region?: string,\n action: 'buy' | 'sell' = 'buy',\n options?: ExecuteRequestOptions,\n ): Promise<TokensResponse> {\n const regionToUse = region ?? this.state.userRegion;\n\n if (!regionToUse) {\n throw new Error(\n 'Region is required. Either provide a region parameter or ensure userRegion is set in controller state.',\n );\n }\n\n const normalizedRegion = regionToUse.toLowerCase().trim();\n const cacheKey = createCacheKey('getTokens', [normalizedRegion, action]);\n\n const tokens = await this.executeRequest(\n cacheKey,\n async () => {\n return this.messenger.call(\n 'RampsService:getTokens',\n normalizedRegion,\n action,\n );\n },\n options,\n );\n\n this.update((state) => {\n const userRegion = state.userRegion?.toLowerCase().trim();\n\n if (userRegion === undefined || userRegion === normalizedRegion) {\n state.tokens = tokens;\n }\n });\n\n return tokens;\n }\n}\n"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ControllerGetStateAction, ControllerStateChangeEvent } from "@metamask/base-controller";
|
|
2
2
|
import { BaseController } from "@metamask/base-controller";
|
|
3
3
|
import type { Messenger } from "@metamask/messenger";
|
|
4
|
-
import type { Country, TokensResponse, Provider
|
|
5
|
-
import type { RampsServiceGetGeolocationAction, RampsServiceGetCountriesAction, RampsServiceGetTokensAction } from "./RampsService-method-action-types.cjs";
|
|
4
|
+
import type { Country, Eligibility, TokensResponse, Provider } from "./RampsService.cjs";
|
|
5
|
+
import type { RampsServiceGetGeolocationAction, RampsServiceGetCountriesAction, RampsServiceGetEligibilityAction, RampsServiceGetTokensAction } from "./RampsService-method-action-types.cjs";
|
|
6
6
|
import type { RequestCache as RequestCacheType, RequestState, ExecuteRequestOptions } from "./RequestCache.cjs";
|
|
7
7
|
/**
|
|
8
8
|
* The name of the {@link RampsController}, used to namespace the
|
|
@@ -10,39 +10,24 @@ import type { RequestCache as RequestCacheType, RequestState, ExecuteRequestOpti
|
|
|
10
10
|
* when composed with other controllers.
|
|
11
11
|
*/
|
|
12
12
|
export declare const controllerName = "RampsController";
|
|
13
|
-
/**
|
|
14
|
-
* Represents the user's selected region with full country and state objects.
|
|
15
|
-
*/
|
|
16
|
-
export type UserRegion = {
|
|
17
|
-
/**
|
|
18
|
-
* The country object for the selected region.
|
|
19
|
-
*/
|
|
20
|
-
country: Country;
|
|
21
|
-
/**
|
|
22
|
-
* The state object if a state was selected, null if only country was selected.
|
|
23
|
-
*/
|
|
24
|
-
state: State | null;
|
|
25
|
-
/**
|
|
26
|
-
* The region code string (e.g., "us-ut" or "fr") used for API calls.
|
|
27
|
-
*/
|
|
28
|
-
regionCode: string;
|
|
29
|
-
};
|
|
30
13
|
/**
|
|
31
14
|
* Describes the shape of the state object for {@link RampsController}.
|
|
32
15
|
*/
|
|
33
16
|
export type RampsControllerState = {
|
|
34
17
|
/**
|
|
35
|
-
* The user's selected region
|
|
18
|
+
* The user's selected region code (e.g., "US-CA").
|
|
36
19
|
* Initially set via geolocation fetch, but can be manually changed by the user.
|
|
37
|
-
* Once set (either via geolocation or manual selection), it will not be overwritten
|
|
38
|
-
* by subsequent geolocation fetches.
|
|
39
20
|
*/
|
|
40
|
-
userRegion:
|
|
21
|
+
userRegion: string | null;
|
|
41
22
|
/**
|
|
42
23
|
* The user's preferred provider.
|
|
43
24
|
* Can be manually set by the user.
|
|
44
25
|
*/
|
|
45
26
|
preferredProvider: Provider | null;
|
|
27
|
+
/**
|
|
28
|
+
* Eligibility information for the user's current region.
|
|
29
|
+
*/
|
|
30
|
+
eligibility: Eligibility | null;
|
|
46
31
|
/**
|
|
47
32
|
* Tokens fetched for the current region and action.
|
|
48
33
|
* Contains topTokens and allTokens arrays.
|
|
@@ -74,7 +59,7 @@ export type RampsControllerActions = RampsControllerGetStateAction;
|
|
|
74
59
|
/**
|
|
75
60
|
* Actions from other messengers that {@link RampsController} calls.
|
|
76
61
|
*/
|
|
77
|
-
type AllowedActions = RampsServiceGetGeolocationAction | RampsServiceGetCountriesAction | RampsServiceGetTokensAction;
|
|
62
|
+
type AllowedActions = RampsServiceGetGeolocationAction | RampsServiceGetCountriesAction | RampsServiceGetEligibilityAction | RampsServiceGetTokensAction;
|
|
78
63
|
/**
|
|
79
64
|
* Published when the state of {@link RampsController} changes.
|
|
80
65
|
*/
|
|
@@ -149,22 +134,23 @@ export declare class RampsController extends BaseController<typeof controllerNam
|
|
|
149
134
|
*/
|
|
150
135
|
getRequestState(cacheKey: string): RequestState | undefined;
|
|
151
136
|
/**
|
|
152
|
-
* Updates the user's region by fetching geolocation.
|
|
153
|
-
* This method calls the RampsService to get the geolocation
|
|
137
|
+
* Updates the user's region by fetching geolocation and eligibility.
|
|
138
|
+
* This method calls the RampsService to get the geolocation,
|
|
139
|
+
* then automatically fetches eligibility for that region.
|
|
154
140
|
*
|
|
155
141
|
* @param options - Options for cache behavior.
|
|
156
|
-
* @returns The user region
|
|
142
|
+
* @returns The user region string.
|
|
157
143
|
*/
|
|
158
|
-
updateUserRegion(options?: ExecuteRequestOptions): Promise<
|
|
144
|
+
updateUserRegion(options?: ExecuteRequestOptions): Promise<string>;
|
|
159
145
|
/**
|
|
160
146
|
* Sets the user's region manually (without fetching geolocation).
|
|
161
147
|
* This allows users to override the detected region.
|
|
162
148
|
*
|
|
163
149
|
* @param region - The region code to set (e.g., "US-CA").
|
|
164
|
-
* @param options - Options for cache behavior.
|
|
165
|
-
* @returns The
|
|
150
|
+
* @param options - Options for cache behavior when fetching eligibility.
|
|
151
|
+
* @returns The eligibility information for the region.
|
|
166
152
|
*/
|
|
167
|
-
setUserRegion(region: string, options?: ExecuteRequestOptions): Promise<
|
|
153
|
+
setUserRegion(region: string, options?: ExecuteRequestOptions): Promise<Eligibility>;
|
|
168
154
|
/**
|
|
169
155
|
* Sets the user's preferred provider.
|
|
170
156
|
* This allows users to set their preferred ramp provider.
|
|
@@ -175,21 +161,27 @@ export declare class RampsController extends BaseController<typeof controllerNam
|
|
|
175
161
|
/**
|
|
176
162
|
* Initializes the controller by fetching the user's region from geolocation.
|
|
177
163
|
* This should be called once at app startup to set up the initial region.
|
|
178
|
-
* After the region is set, tokens are fetched
|
|
179
|
-
*
|
|
180
|
-
* If a userRegion already exists (from persistence or manual selection),
|
|
181
|
-
* this method will skip geolocation fetch and only fetch tokens if needed.
|
|
164
|
+
* After the region is set and eligibility is determined, tokens are fetched
|
|
165
|
+
* and saved to state.
|
|
182
166
|
*
|
|
183
167
|
* @param options - Options for cache behavior.
|
|
184
168
|
* @returns Promise that resolves when initialization is complete.
|
|
185
169
|
*/
|
|
186
170
|
init(options?: ExecuteRequestOptions): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Updates the eligibility information for a given region.
|
|
173
|
+
*
|
|
174
|
+
* @param isoCode - The ISO code for the region (e.g., "us", "fr", "us-ny").
|
|
175
|
+
* @param options - Options for cache behavior.
|
|
176
|
+
* @returns The eligibility information.
|
|
177
|
+
*/
|
|
178
|
+
updateEligibility(isoCode: string, options?: ExecuteRequestOptions): Promise<Eligibility>;
|
|
187
179
|
/**
|
|
188
180
|
* Fetches the list of supported countries for a given ramp action.
|
|
189
181
|
*
|
|
190
182
|
* @param action - The ramp action type ('buy' or 'sell').
|
|
191
183
|
* @param options - Options for cache behavior.
|
|
192
|
-
* @returns An array of countries.
|
|
184
|
+
* @returns An array of countries with their eligibility information.
|
|
193
185
|
*/
|
|
194
186
|
getCountries(action?: 'buy' | 'sell', options?: ExecuteRequestOptions): Promise<Country[]>;
|
|
195
187
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RampsController.d.cts","sourceRoot":"","sources":["../src/RampsController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAE3B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAGrD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"RampsController.d.cts","sourceRoot":"","sources":["../src/RampsController.ts"],"names":[],"mappings":"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"}
|