@metamask-previews/rate-limit-controller 7.0.0-preview-267e79c3 → 7.0.0-preview-d717276a
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/dist/RateLimitController.cjs +49 -54
- package/dist/RateLimitController.cjs.map +1 -1
- package/dist/RateLimitController.d.cts +1 -25
- package/dist/RateLimitController.d.cts.map +1 -1
- package/dist/RateLimitController.d.mts +1 -25
- package/dist/RateLimitController.d.mts.map +1 -1
- package/dist/RateLimitController.mjs +48 -53
- package/dist/RateLimitController.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
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
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _RateLimitController_instances, _RateLimitController_implementations, _RateLimitController_rateLimitTimeout, _RateLimitController_rateLimitCount, _RateLimitController_isRateLimited, _RateLimitController_recordRequest, _RateLimitController_resetRequestCount;
|
|
2
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
15
|
exports.RateLimitController = void 0;
|
|
4
16
|
const base_controller_1 = require("@metamask/base-controller");
|
|
@@ -37,9 +49,13 @@ class RateLimitController extends base_controller_1.BaseController {
|
|
|
37
49
|
messenger,
|
|
38
50
|
state: { ...defaultState, ...state },
|
|
39
51
|
});
|
|
40
|
-
this
|
|
41
|
-
this
|
|
42
|
-
this
|
|
52
|
+
_RateLimitController_instances.add(this);
|
|
53
|
+
_RateLimitController_implementations.set(this, void 0);
|
|
54
|
+
_RateLimitController_rateLimitTimeout.set(this, void 0);
|
|
55
|
+
_RateLimitController_rateLimitCount.set(this, void 0);
|
|
56
|
+
__classPrivateFieldSet(this, _RateLimitController_implementations, implementations, "f");
|
|
57
|
+
__classPrivateFieldSet(this, _RateLimitController_rateLimitTimeout, rateLimitTimeout, "f");
|
|
58
|
+
__classPrivateFieldSet(this, _RateLimitController_rateLimitCount, rateLimitCount, "f");
|
|
43
59
|
this.messenger.registerActionHandler(`${controllerName}:call`, (origin, type, ...args) => this.call(origin, type, ...args));
|
|
44
60
|
}
|
|
45
61
|
/**
|
|
@@ -51,66 +67,45 @@ class RateLimitController extends base_controller_1.BaseController {
|
|
|
51
67
|
* @returns The result of the API call.
|
|
52
68
|
*/
|
|
53
69
|
async call(origin, type, ...args) {
|
|
54
|
-
if (this.
|
|
70
|
+
if (__classPrivateFieldGet(this, _RateLimitController_instances, "m", _RateLimitController_isRateLimited).call(this, type, origin)) {
|
|
55
71
|
throw rpc_errors_1.rpcErrors.limitExceeded({
|
|
56
72
|
message: `"${type.toString()}" is currently rate-limited. Please try again later.`,
|
|
57
73
|
});
|
|
58
74
|
}
|
|
59
|
-
this.
|
|
60
|
-
const implementation = this
|
|
75
|
+
__classPrivateFieldGet(this, _RateLimitController_instances, "m", _RateLimitController_recordRequest).call(this, type, origin);
|
|
76
|
+
const implementation = __classPrivateFieldGet(this, _RateLimitController_implementations, "f")[type].method;
|
|
61
77
|
if (!implementation) {
|
|
62
78
|
throw new Error('Invalid api type');
|
|
63
79
|
}
|
|
64
80
|
return implementation(...args);
|
|
65
81
|
}
|
|
66
|
-
/**
|
|
67
|
-
* Checks whether an origin is rate limited for the a specific API.
|
|
68
|
-
*
|
|
69
|
-
* @param api - The API the origin is trying to access.
|
|
70
|
-
* @param origin - The origin trying to access the API.
|
|
71
|
-
* @returns `true` if rate-limited, and `false` otherwise.
|
|
72
|
-
*/
|
|
73
|
-
isRateLimited(api, origin) {
|
|
74
|
-
const rateLimitCount = this.implementations[api].rateLimitCount ?? this.rateLimitCount;
|
|
75
|
-
return this.state.requests[api][origin] >= rateLimitCount;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Records that an origin has made a request to call an API, for rate-limiting purposes.
|
|
79
|
-
*
|
|
80
|
-
* @param api - The API the origin is trying to access.
|
|
81
|
-
* @param origin - The origin trying to access the API.
|
|
82
|
-
*/
|
|
83
|
-
recordRequest(api, origin) {
|
|
84
|
-
const rateLimitTimeout = this.implementations[api].rateLimitTimeout ?? this.rateLimitTimeout;
|
|
85
|
-
const previous = this.state.requests[api][origin] ?? 0;
|
|
86
|
-
this.update((state) => {
|
|
87
|
-
if (previous === 0) {
|
|
88
|
-
setTimeout(() => this.resetRequestCount(api, origin), rateLimitTimeout);
|
|
89
|
-
}
|
|
90
|
-
Object.assign(state, {
|
|
91
|
-
requests: {
|
|
92
|
-
...state.requests,
|
|
93
|
-
[api]: { [origin]: previous + 1 },
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Resets the request count for a given origin and API combination, for rate-limiting purposes.
|
|
100
|
-
*
|
|
101
|
-
* @param api - The API in question.
|
|
102
|
-
* @param origin - The origin in question.
|
|
103
|
-
*/
|
|
104
|
-
resetRequestCount(api, origin) {
|
|
105
|
-
this.update((state) => {
|
|
106
|
-
Object.assign(state, {
|
|
107
|
-
requests: {
|
|
108
|
-
...state.requests,
|
|
109
|
-
[api]: { [origin]: 0 },
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
82
|
}
|
|
115
83
|
exports.RateLimitController = RateLimitController;
|
|
84
|
+
_RateLimitController_implementations = new WeakMap(), _RateLimitController_rateLimitTimeout = new WeakMap(), _RateLimitController_rateLimitCount = new WeakMap(), _RateLimitController_instances = new WeakSet(), _RateLimitController_isRateLimited = function _RateLimitController_isRateLimited(api, origin) {
|
|
85
|
+
const rateLimitCount = __classPrivateFieldGet(this, _RateLimitController_implementations, "f")[api].rateLimitCount ?? __classPrivateFieldGet(this, _RateLimitController_rateLimitCount, "f");
|
|
86
|
+
return this.state.requests[api][origin] >= rateLimitCount;
|
|
87
|
+
}, _RateLimitController_recordRequest = function _RateLimitController_recordRequest(api, origin) {
|
|
88
|
+
const rateLimitTimeout = __classPrivateFieldGet(this, _RateLimitController_implementations, "f")[api].rateLimitTimeout ?? __classPrivateFieldGet(this, _RateLimitController_rateLimitTimeout, "f");
|
|
89
|
+
const previous = this.state.requests[api][origin] ?? 0;
|
|
90
|
+
this.update((state) => {
|
|
91
|
+
if (previous === 0) {
|
|
92
|
+
setTimeout(() => __classPrivateFieldGet(this, _RateLimitController_instances, "m", _RateLimitController_resetRequestCount).call(this, api, origin), rateLimitTimeout);
|
|
93
|
+
}
|
|
94
|
+
Object.assign(state, {
|
|
95
|
+
requests: {
|
|
96
|
+
...state.requests,
|
|
97
|
+
[api]: { [origin]: previous + 1 },
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}, _RateLimitController_resetRequestCount = function _RateLimitController_resetRequestCount(api, origin) {
|
|
102
|
+
this.update((state) => {
|
|
103
|
+
Object.assign(state, {
|
|
104
|
+
requests: {
|
|
105
|
+
...state.requests,
|
|
106
|
+
[api]: { [origin]: 0 },
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
};
|
|
116
111
|
//# sourceMappingURL=RateLimitController.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimitController.cjs","sourceRoot":"","sources":["../src/RateLimitController.ts"],"names":[],"mappings":";;;AAAA,+DAA2D;AAM3D,qDAAiD;AACjD,2CAAwD;AAsCxD,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAwC7C,MAAM,QAAQ,GAAG;IACf,QAAQ,EAAE;QACR,kBAAkB,EAAE,KAAK;QACzB,OAAO,EAAE,KAAK;QACd,sBAAsB,EAAE,KAAK;QAC7B,QAAQ,EAAE,KAAK;KAChB;CACF,CAAC;AAEF;;GAEG;AACH,MAAa,mBAEX,SAAQ,gCAIT;IAOC;;;;;;;;;OASG;IACH,YAAY,EACV,gBAAgB,GAAG,IAAI,EACvB,cAAc,GAAG,CAAC,EAClB,SAAS,EACT,KAAK,EACL,eAAe,GAOhB;QACC,MAAM,YAAY,GAAG;YACnB,QAAQ,EAAE,IAAA,6BAAqB,EAAC,eAAe,CAAC,CAAC,MAAM,CAErD,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAW,CAAC;SACtD,CAAC;QACF,KAAK,CAAC;YACJ,IAAI,EAAE,cAAc;YACpB,QAAQ;YACR,SAAS;YACT,KAAK,EAAE,EAAE,GAAG,YAAY,EAAE,GAAG,KAAK,EAAE;SACrC,CAAC,CAAC;QACH,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QACzC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QAErC,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAClC,GAAG,cAAc,OAAO,EACxB,CACE,MAAc,EACd,IAA2B,EAC3B,GAAG,IAAwD,EAC3D,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,CACtC,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI,CACR,MAAc,EACd,IAAa,EACb,GAAG,IAAoD;QAEvD,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YACrC,MAAM,sBAAS,CAAC,aAAa,CAAC;gBAC5B,OAAO,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,sDAAsD;aACnF,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAEjC,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,MAEC,CAAC;QAEpD,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACtC,CAAC;QAED,OAAO,cAAc,CAAC,GAAG,IAAI,CAAC,CAAC;IACjC,CAAC;IAED;;;;;;OAMG;IACK,aAAa,CAAC,GAA0B,EAAE,MAAc;QAC9D,MAAM,cAAc,GAClB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,cAAc,IAAI,IAAI,CAAC,cAAc,CAAC;QAClE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,cAAc,CAAC;IAC5D,CAAC;IAED;;;;;OAKG;IACK,aAAa,CAAC,GAA0B,EAAE,MAAc;QAC9D,MAAM,gBAAgB,GACpB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,gBAAgB,IAAI,IAAI,CAAC,gBAAgB,CAAC;QACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBACnB,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAC1E,CAAC;YACD,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;gBACnB,QAAQ,EAAE;oBACR,GAAI,KAAK,CAAC,QAAiD;oBAC3D,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,CAAC,EAAE;iBAClC;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACK,iBAAiB,CAAC,GAA0B,EAAE,MAAc;QAClE,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;gBACnB,QAAQ,EAAE;oBACR,GAAI,KAAK,CAAC,QAAiD;oBAC3D,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE;iBACvB;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAhJD,kDAgJC","sourcesContent":["import { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport type { Messenger, ActionConstraint } from '@metamask/messenger';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport { getKnownPropertyNames } from '@metamask/utils';\n\n/**\n * A rate-limited API endpoint.\n *\n * @property method - The method that is rate-limited.\n * @property rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @property rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\nexport type RateLimitedApi = {\n method: ActionConstraint['handler'];\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n};\n\n/**\n * A map of rate-limited API types to APIs.\n */\nexport type RateLimitedApiMap = Record<string, RateLimitedApi>;\n\n/**\n * A map of rate-limited API types to the number of requests made in a given interval for each origin and api type combination.\n *\n * @template RateLimitedApis - A {@link RateLimitedApiMap} containing the rate-limited API endpoints that is used by the {@link RateLimitController}.\n */\nexport type RateLimitedRequests<RateLimitedApis extends RateLimitedApiMap> =\n Record<keyof RateLimitedApis, Record<string, number>>;\n\n/**\n * The state of the {@link RateLimitController}.\n *\n * @template RateLimitedApis - A {@link RateLimitedApiMap} containing the rate-limited API endpoints that is used by the {@link RateLimitController}.\n * @property requests - An object containing the number of requests made in a given interval for each origin and api type combination.\n */\nexport type RateLimitState<RateLimitedApis extends RateLimitedApiMap> = {\n requests: RateLimitedRequests<RateLimitedApis>;\n};\n\nconst controllerName = 'RateLimitController';\n\nexport type RateLimitControllerStateChangeEvent<\n RateLimitedApis extends RateLimitedApiMap,\n> = ControllerStateChangeEvent<\n typeof controllerName,\n RateLimitState<RateLimitedApis>\n>;\n\nexport type RateLimitControllerGetStateAction<\n RateLimitedApis extends RateLimitedApiMap,\n> = ControllerGetStateAction<\n typeof controllerName,\n RateLimitState<RateLimitedApis>\n>;\n\nexport type RateLimitControllerCallApiAction<\n RateLimitedApis extends RateLimitedApiMap,\n> = {\n type: `${typeof controllerName}:call`;\n handler: RateLimitController<RateLimitedApis>['call'];\n};\n\nexport type RateLimitControllerActions<\n RateLimitedApis extends RateLimitedApiMap,\n> =\n | RateLimitControllerGetStateAction<RateLimitedApis>\n | RateLimitControllerCallApiAction<RateLimitedApis>;\n\nexport type RateLimitControllerEvents<\n RateLimitedApis extends RateLimitedApiMap,\n> = RateLimitControllerStateChangeEvent<RateLimitedApis>;\n\nexport type RateLimitMessenger<RateLimitedApis extends RateLimitedApiMap> =\n Messenger<\n typeof controllerName,\n RateLimitControllerActions<RateLimitedApis>,\n RateLimitControllerEvents<RateLimitedApis>\n >;\n\nconst metadata = {\n requests: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: false,\n usedInUi: false,\n },\n};\n\n/**\n * Controller with logic for rate-limiting API endpoints per requesting origin.\n */\nexport class RateLimitController<\n RateLimitedApis extends RateLimitedApiMap,\n> extends BaseController<\n typeof controllerName,\n RateLimitState<RateLimitedApis>,\n RateLimitMessenger<RateLimitedApis>\n> {\n private readonly implementations;\n\n private readonly rateLimitTimeout;\n\n private readonly rateLimitCount;\n\n /**\n * Creates a RateLimitController instance.\n *\n * @param options - Constructor options.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n * @param options.implementations - Mapping from API type to API implementation.\n * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\n constructor({\n rateLimitTimeout = 5000,\n rateLimitCount = 1,\n messenger,\n state,\n implementations,\n }: {\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n messenger: RateLimitMessenger<RateLimitedApis>;\n state?: Partial<RateLimitState<RateLimitedApis>>;\n implementations: RateLimitedApis;\n }) {\n const defaultState = {\n requests: getKnownPropertyNames(implementations).reduce<\n RateLimitedRequests<RateLimitedApis>\n >((acc, key) => ({ ...acc, [key]: {} }), {} as never),\n };\n super({\n name: controllerName,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.implementations = implementations;\n this.rateLimitTimeout = rateLimitTimeout;\n this.rateLimitCount = rateLimitCount;\n\n this.messenger.registerActionHandler(\n `${controllerName}:call`,\n (\n origin: string,\n type: keyof RateLimitedApis,\n ...args: Parameters<RateLimitedApis[typeof type]['method']>\n ) => this.call(origin, type, ...args),\n );\n }\n\n /**\n * Calls an API if the requesting origin is not rate-limited.\n *\n * @param origin - The requesting origin.\n * @param type - The type of API call to make.\n * @param args - Arguments for the API call.\n * @returns The result of the API call.\n */\n async call<ApiType extends keyof RateLimitedApis>(\n origin: string,\n type: ApiType,\n ...args: Parameters<RateLimitedApis[ApiType]['method']>\n ): Promise<ReturnType<RateLimitedApis[ApiType]['method']>> {\n if (this.isRateLimited(type, origin)) {\n throw rpcErrors.limitExceeded({\n message: `\"${type.toString()}\" is currently rate-limited. Please try again later.`,\n });\n }\n this.recordRequest(type, origin);\n\n const implementation = this.implementations[type].method as (\n ...args: Parameters<RateLimitedApis[ApiType]['method']>\n ) => ReturnType<RateLimitedApis[ApiType]['method']>;\n\n if (!implementation) {\n throw new Error('Invalid api type');\n }\n\n return implementation(...args);\n }\n\n /**\n * Checks whether an origin is rate limited for the a specific API.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n * @returns `true` if rate-limited, and `false` otherwise.\n */\n private isRateLimited(api: keyof RateLimitedApis, origin: string) {\n const rateLimitCount =\n this.implementations[api].rateLimitCount ?? this.rateLimitCount;\n return this.state.requests[api][origin] >= rateLimitCount;\n }\n\n /**\n * Records that an origin has made a request to call an API, for rate-limiting purposes.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n */\n private recordRequest(api: keyof RateLimitedApis, origin: string) {\n const rateLimitTimeout =\n this.implementations[api].rateLimitTimeout ?? this.rateLimitTimeout;\n const previous = this.state.requests[api][origin] ?? 0;\n this.update((state) => {\n if (previous === 0) {\n setTimeout(() => this.resetRequestCount(api, origin), rateLimitTimeout);\n }\n Object.assign(state, {\n requests: {\n ...(state.requests as RateLimitedRequests<RateLimitedApis>),\n [api]: { [origin]: previous + 1 },\n },\n });\n });\n }\n\n /**\n * Resets the request count for a given origin and API combination, for rate-limiting purposes.\n *\n * @param api - The API in question.\n * @param origin - The origin in question.\n */\n private resetRequestCount(api: keyof RateLimitedApis, origin: string) {\n this.update((state) => {\n Object.assign(state, {\n requests: {\n ...(state.requests as RateLimitedRequests<RateLimitedApis>),\n [api]: { [origin]: 0 },\n },\n });\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RateLimitController.cjs","sourceRoot":"","sources":["../src/RateLimitController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport type { Messenger, ActionConstraint } from '@metamask/messenger';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport { getKnownPropertyNames } from '@metamask/utils';\n\n/**\n * A rate-limited API endpoint.\n *\n * @property method - The method that is rate-limited.\n * @property rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @property rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\nexport type RateLimitedApi = {\n method: ActionConstraint['handler'];\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n};\n\n/**\n * A map of rate-limited API types to APIs.\n */\nexport type RateLimitedApiMap = Record<string, RateLimitedApi>;\n\n/**\n * A map of rate-limited API types to the number of requests made in a given interval for each origin and api type combination.\n *\n * @template RateLimitedApis - A {@link RateLimitedApiMap} containing the rate-limited API endpoints that is used by the {@link RateLimitController}.\n */\nexport type RateLimitedRequests<RateLimitedApis extends RateLimitedApiMap> =\n Record<keyof RateLimitedApis, Record<string, number>>;\n\n/**\n * The state of the {@link RateLimitController}.\n *\n * @template RateLimitedApis - A {@link RateLimitedApiMap} containing the rate-limited API endpoints that is used by the {@link RateLimitController}.\n * @property requests - An object containing the number of requests made in a given interval for each origin and api type combination.\n */\nexport type RateLimitState<RateLimitedApis extends RateLimitedApiMap> = {\n requests: RateLimitedRequests<RateLimitedApis>;\n};\n\nconst controllerName = 'RateLimitController';\n\nexport type RateLimitControllerStateChangeEvent<\n RateLimitedApis extends RateLimitedApiMap,\n> = ControllerStateChangeEvent<\n typeof controllerName,\n RateLimitState<RateLimitedApis>\n>;\n\nexport type RateLimitControllerGetStateAction<\n RateLimitedApis extends RateLimitedApiMap,\n> = ControllerGetStateAction<\n typeof controllerName,\n RateLimitState<RateLimitedApis>\n>;\n\nexport type RateLimitControllerCallApiAction<\n RateLimitedApis extends RateLimitedApiMap,\n> = {\n type: `${typeof controllerName}:call`;\n handler: RateLimitController<RateLimitedApis>['call'];\n};\n\nexport type RateLimitControllerActions<\n RateLimitedApis extends RateLimitedApiMap,\n> =\n | RateLimitControllerGetStateAction<RateLimitedApis>\n | RateLimitControllerCallApiAction<RateLimitedApis>;\n\nexport type RateLimitControllerEvents<\n RateLimitedApis extends RateLimitedApiMap,\n> = RateLimitControllerStateChangeEvent<RateLimitedApis>;\n\nexport type RateLimitMessenger<RateLimitedApis extends RateLimitedApiMap> =\n Messenger<\n typeof controllerName,\n RateLimitControllerActions<RateLimitedApis>,\n RateLimitControllerEvents<RateLimitedApis>\n >;\n\nconst metadata = {\n requests: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: false,\n usedInUi: false,\n },\n};\n\n/**\n * Controller with logic for rate-limiting API endpoints per requesting origin.\n */\nexport class RateLimitController<\n RateLimitedApis extends RateLimitedApiMap,\n> extends BaseController<\n typeof controllerName,\n RateLimitState<RateLimitedApis>,\n RateLimitMessenger<RateLimitedApis>\n> {\n readonly #implementations;\n\n readonly #rateLimitTimeout;\n\n readonly #rateLimitCount;\n\n /**\n * Creates a RateLimitController instance.\n *\n * @param options - Constructor options.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n * @param options.implementations - Mapping from API type to API implementation.\n * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\n constructor({\n rateLimitTimeout = 5000,\n rateLimitCount = 1,\n messenger,\n state,\n implementations,\n }: {\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n messenger: RateLimitMessenger<RateLimitedApis>;\n state?: Partial<RateLimitState<RateLimitedApis>>;\n implementations: RateLimitedApis;\n }) {\n const defaultState = {\n requests: getKnownPropertyNames(implementations).reduce<\n RateLimitedRequests<RateLimitedApis>\n >((acc, key) => ({ ...acc, [key]: {} }), {} as never),\n };\n super({\n name: controllerName,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.#implementations = implementations;\n this.#rateLimitTimeout = rateLimitTimeout;\n this.#rateLimitCount = rateLimitCount;\n\n this.messenger.registerActionHandler(\n `${controllerName}:call`,\n (\n origin: string,\n type: keyof RateLimitedApis,\n ...args: Parameters<RateLimitedApis[typeof type]['method']>\n ) => this.call(origin, type, ...args),\n );\n }\n\n /**\n * Calls an API if the requesting origin is not rate-limited.\n *\n * @param origin - The requesting origin.\n * @param type - The type of API call to make.\n * @param args - Arguments for the API call.\n * @returns The result of the API call.\n */\n async call<ApiType extends keyof RateLimitedApis>(\n origin: string,\n type: ApiType,\n ...args: Parameters<RateLimitedApis[ApiType]['method']>\n ): Promise<ReturnType<RateLimitedApis[ApiType]['method']>> {\n if (this.#isRateLimited(type, origin)) {\n throw rpcErrors.limitExceeded({\n message: `\"${type.toString()}\" is currently rate-limited. Please try again later.`,\n });\n }\n this.#recordRequest(type, origin);\n\n const implementation = this.#implementations[type].method as (\n ...args: Parameters<RateLimitedApis[ApiType]['method']>\n ) => ReturnType<RateLimitedApis[ApiType]['method']>;\n\n if (!implementation) {\n throw new Error('Invalid api type');\n }\n\n return implementation(...args);\n }\n\n /**\n * Checks whether an origin is rate limited for a specific API.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n * @returns `true` if rate-limited, and `false` otherwise.\n */\n #isRateLimited(api: keyof RateLimitedApis, origin: string): boolean {\n const rateLimitCount =\n this.#implementations[api].rateLimitCount ?? this.#rateLimitCount;\n return this.state.requests[api][origin] >= rateLimitCount;\n }\n\n /**\n * Records that an origin has made a request to call an API, for rate-limiting purposes.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n */\n #recordRequest(api: keyof RateLimitedApis, origin: string): void {\n const rateLimitTimeout =\n this.#implementations[api].rateLimitTimeout ?? this.#rateLimitTimeout;\n const previous = this.state.requests[api][origin] ?? 0;\n this.update((state) => {\n if (previous === 0) {\n setTimeout(\n () => this.#resetRequestCount(api, origin),\n rateLimitTimeout,\n );\n }\n Object.assign(state, {\n requests: {\n ...(state.requests as RateLimitedRequests<RateLimitedApis>),\n [api]: { [origin]: previous + 1 },\n },\n });\n });\n }\n\n /**\n * Resets the request count for a given origin and API combination, for rate-limiting purposes.\n *\n * @param api - The API in question.\n * @param origin - The origin in question.\n */\n #resetRequestCount(api: keyof RateLimitedApis, origin: string): void {\n this.update((state) => {\n Object.assign(state, {\n requests: {\n ...(state.requests as RateLimitedRequests<RateLimitedApis>),\n [api]: { [origin]: 0 },\n },\n });\n });\n }\n}\n"]}
|
|
@@ -46,9 +46,7 @@ export type RateLimitMessenger<RateLimitedApis extends RateLimitedApiMap> = Mess
|
|
|
46
46
|
* Controller with logic for rate-limiting API endpoints per requesting origin.
|
|
47
47
|
*/
|
|
48
48
|
export declare class RateLimitController<RateLimitedApis extends RateLimitedApiMap> extends BaseController<typeof controllerName, RateLimitState<RateLimitedApis>, RateLimitMessenger<RateLimitedApis>> {
|
|
49
|
-
private
|
|
50
|
-
private readonly rateLimitTimeout;
|
|
51
|
-
private readonly rateLimitCount;
|
|
49
|
+
#private;
|
|
52
50
|
/**
|
|
53
51
|
* Creates a RateLimitController instance.
|
|
54
52
|
*
|
|
@@ -75,28 +73,6 @@ export declare class RateLimitController<RateLimitedApis extends RateLimitedApiM
|
|
|
75
73
|
* @returns The result of the API call.
|
|
76
74
|
*/
|
|
77
75
|
call<ApiType extends keyof RateLimitedApis>(origin: string, type: ApiType, ...args: Parameters<RateLimitedApis[ApiType]['method']>): Promise<ReturnType<RateLimitedApis[ApiType]['method']>>;
|
|
78
|
-
/**
|
|
79
|
-
* Checks whether an origin is rate limited for the a specific API.
|
|
80
|
-
*
|
|
81
|
-
* @param api - The API the origin is trying to access.
|
|
82
|
-
* @param origin - The origin trying to access the API.
|
|
83
|
-
* @returns `true` if rate-limited, and `false` otherwise.
|
|
84
|
-
*/
|
|
85
|
-
private isRateLimited;
|
|
86
|
-
/**
|
|
87
|
-
* Records that an origin has made a request to call an API, for rate-limiting purposes.
|
|
88
|
-
*
|
|
89
|
-
* @param api - The API the origin is trying to access.
|
|
90
|
-
* @param origin - The origin trying to access the API.
|
|
91
|
-
*/
|
|
92
|
-
private recordRequest;
|
|
93
|
-
/**
|
|
94
|
-
* Resets the request count for a given origin and API combination, for rate-limiting purposes.
|
|
95
|
-
*
|
|
96
|
-
* @param api - The API in question.
|
|
97
|
-
* @param origin - The origin in question.
|
|
98
|
-
*/
|
|
99
|
-
private resetRequestCount;
|
|
100
76
|
}
|
|
101
77
|
export {};
|
|
102
78
|
//# sourceMappingURL=RateLimitController.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimitController.d.cts","sourceRoot":"","sources":["../src/RateLimitController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,4BAA4B;AAIvE;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,CAAC,eAAe,SAAS,iBAAiB,IACvE,MAAM,CAAC,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,cAAc,CAAC,eAAe,SAAS,iBAAiB,IAAI;IACtE,QAAQ,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;CAChD,CAAC;AAEF,QAAA,MAAM,cAAc,wBAAwB,CAAC;AAE7C,MAAM,MAAM,mCAAmC,CAC7C,eAAe,SAAS,iBAAiB,IACvC,0BAA0B,CAC5B,OAAO,cAAc,EACrB,cAAc,CAAC,eAAe,CAAC,CAChC,CAAC;AAEF,MAAM,MAAM,iCAAiC,CAC3C,eAAe,SAAS,iBAAiB,IACvC,wBAAwB,CAC1B,OAAO,cAAc,EACrB,cAAc,CAAC,eAAe,CAAC,CAChC,CAAC;AAEF,MAAM,MAAM,gCAAgC,CAC1C,eAAe,SAAS,iBAAiB,IACvC;IACF,IAAI,EAAE,GAAG,OAAO,cAAc,OAAO,CAAC;IACtC,OAAO,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,0BAA0B,CACpC,eAAe,SAAS,iBAAiB,IAEvC,iCAAiC,CAAC,eAAe,CAAC,GAClD,gCAAgC,CAAC,eAAe,CAAC,CAAC;AAEtD,MAAM,MAAM,yBAAyB,CACnC,eAAe,SAAS,iBAAiB,IACvC,mCAAmC,CAAC,eAAe,CAAC,CAAC;AAEzD,MAAM,MAAM,kBAAkB,CAAC,eAAe,SAAS,iBAAiB,IACtE,SAAS,CACP,OAAO,cAAc,EACrB,0BAA0B,CAAC,eAAe,CAAC,EAC3C,yBAAyB,CAAC,eAAe,CAAC,CAC3C,CAAC;AAWJ;;GAEG;AACH,qBAAa,mBAAmB,CAC9B,eAAe,SAAS,iBAAiB,CACzC,SAAQ,cAAc,CACtB,OAAO,cAAc,EACrB,cAAc,CAAC,eAAe,CAAC,EAC/B,kBAAkB,CAAC,eAAe,CAAC,CACpC
|
|
1
|
+
{"version":3,"file":"RateLimitController.d.cts","sourceRoot":"","sources":["../src/RateLimitController.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,9 +46,7 @@ export type RateLimitMessenger<RateLimitedApis extends RateLimitedApiMap> = Mess
|
|
|
46
46
|
* Controller with logic for rate-limiting API endpoints per requesting origin.
|
|
47
47
|
*/
|
|
48
48
|
export declare class RateLimitController<RateLimitedApis extends RateLimitedApiMap> extends BaseController<typeof controllerName, RateLimitState<RateLimitedApis>, RateLimitMessenger<RateLimitedApis>> {
|
|
49
|
-
private
|
|
50
|
-
private readonly rateLimitTimeout;
|
|
51
|
-
private readonly rateLimitCount;
|
|
49
|
+
#private;
|
|
52
50
|
/**
|
|
53
51
|
* Creates a RateLimitController instance.
|
|
54
52
|
*
|
|
@@ -75,28 +73,6 @@ export declare class RateLimitController<RateLimitedApis extends RateLimitedApiM
|
|
|
75
73
|
* @returns The result of the API call.
|
|
76
74
|
*/
|
|
77
75
|
call<ApiType extends keyof RateLimitedApis>(origin: string, type: ApiType, ...args: Parameters<RateLimitedApis[ApiType]['method']>): Promise<ReturnType<RateLimitedApis[ApiType]['method']>>;
|
|
78
|
-
/**
|
|
79
|
-
* Checks whether an origin is rate limited for the a specific API.
|
|
80
|
-
*
|
|
81
|
-
* @param api - The API the origin is trying to access.
|
|
82
|
-
* @param origin - The origin trying to access the API.
|
|
83
|
-
* @returns `true` if rate-limited, and `false` otherwise.
|
|
84
|
-
*/
|
|
85
|
-
private isRateLimited;
|
|
86
|
-
/**
|
|
87
|
-
* Records that an origin has made a request to call an API, for rate-limiting purposes.
|
|
88
|
-
*
|
|
89
|
-
* @param api - The API the origin is trying to access.
|
|
90
|
-
* @param origin - The origin trying to access the API.
|
|
91
|
-
*/
|
|
92
|
-
private recordRequest;
|
|
93
|
-
/**
|
|
94
|
-
* Resets the request count for a given origin and API combination, for rate-limiting purposes.
|
|
95
|
-
*
|
|
96
|
-
* @param api - The API in question.
|
|
97
|
-
* @param origin - The origin in question.
|
|
98
|
-
*/
|
|
99
|
-
private resetRequestCount;
|
|
100
76
|
}
|
|
101
77
|
export {};
|
|
102
78
|
//# sourceMappingURL=RateLimitController.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimitController.d.mts","sourceRoot":"","sources":["../src/RateLimitController.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EACV,wBAAwB,EACxB,0BAA0B,EAC3B,kCAAkC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,4BAA4B;AAIvE;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,CAAC,eAAe,SAAS,iBAAiB,IACvE,MAAM,CAAC,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,cAAc,CAAC,eAAe,SAAS,iBAAiB,IAAI;IACtE,QAAQ,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;CAChD,CAAC;AAEF,QAAA,MAAM,cAAc,wBAAwB,CAAC;AAE7C,MAAM,MAAM,mCAAmC,CAC7C,eAAe,SAAS,iBAAiB,IACvC,0BAA0B,CAC5B,OAAO,cAAc,EACrB,cAAc,CAAC,eAAe,CAAC,CAChC,CAAC;AAEF,MAAM,MAAM,iCAAiC,CAC3C,eAAe,SAAS,iBAAiB,IACvC,wBAAwB,CAC1B,OAAO,cAAc,EACrB,cAAc,CAAC,eAAe,CAAC,CAChC,CAAC;AAEF,MAAM,MAAM,gCAAgC,CAC1C,eAAe,SAAS,iBAAiB,IACvC;IACF,IAAI,EAAE,GAAG,OAAO,cAAc,OAAO,CAAC;IACtC,OAAO,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,0BAA0B,CACpC,eAAe,SAAS,iBAAiB,IAEvC,iCAAiC,CAAC,eAAe,CAAC,GAClD,gCAAgC,CAAC,eAAe,CAAC,CAAC;AAEtD,MAAM,MAAM,yBAAyB,CACnC,eAAe,SAAS,iBAAiB,IACvC,mCAAmC,CAAC,eAAe,CAAC,CAAC;AAEzD,MAAM,MAAM,kBAAkB,CAAC,eAAe,SAAS,iBAAiB,IACtE,SAAS,CACP,OAAO,cAAc,EACrB,0BAA0B,CAAC,eAAe,CAAC,EAC3C,yBAAyB,CAAC,eAAe,CAAC,CAC3C,CAAC;AAWJ;;GAEG;AACH,qBAAa,mBAAmB,CAC9B,eAAe,SAAS,iBAAiB,CACzC,SAAQ,cAAc,CACtB,OAAO,cAAc,EACrB,cAAc,CAAC,eAAe,CAAC,EAC/B,kBAAkB,CAAC,eAAe,CAAC,CACpC
|
|
1
|
+
{"version":3,"file":"RateLimitController.d.mts","sourceRoot":"","sources":["../src/RateLimitController.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
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");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _RateLimitController_instances, _RateLimitController_implementations, _RateLimitController_rateLimitTimeout, _RateLimitController_rateLimitCount, _RateLimitController_isRateLimited, _RateLimitController_recordRequest, _RateLimitController_resetRequestCount;
|
|
1
13
|
import { BaseController } from "@metamask/base-controller";
|
|
2
14
|
import { rpcErrors } from "@metamask/rpc-errors";
|
|
3
15
|
import { getKnownPropertyNames } from "@metamask/utils";
|
|
@@ -34,9 +46,13 @@ export class RateLimitController extends BaseController {
|
|
|
34
46
|
messenger,
|
|
35
47
|
state: { ...defaultState, ...state },
|
|
36
48
|
});
|
|
37
|
-
this
|
|
38
|
-
this
|
|
39
|
-
this
|
|
49
|
+
_RateLimitController_instances.add(this);
|
|
50
|
+
_RateLimitController_implementations.set(this, void 0);
|
|
51
|
+
_RateLimitController_rateLimitTimeout.set(this, void 0);
|
|
52
|
+
_RateLimitController_rateLimitCount.set(this, void 0);
|
|
53
|
+
__classPrivateFieldSet(this, _RateLimitController_implementations, implementations, "f");
|
|
54
|
+
__classPrivateFieldSet(this, _RateLimitController_rateLimitTimeout, rateLimitTimeout, "f");
|
|
55
|
+
__classPrivateFieldSet(this, _RateLimitController_rateLimitCount, rateLimitCount, "f");
|
|
40
56
|
this.messenger.registerActionHandler(`${controllerName}:call`, (origin, type, ...args) => this.call(origin, type, ...args));
|
|
41
57
|
}
|
|
42
58
|
/**
|
|
@@ -48,65 +64,44 @@ export class RateLimitController extends BaseController {
|
|
|
48
64
|
* @returns The result of the API call.
|
|
49
65
|
*/
|
|
50
66
|
async call(origin, type, ...args) {
|
|
51
|
-
if (this.
|
|
67
|
+
if (__classPrivateFieldGet(this, _RateLimitController_instances, "m", _RateLimitController_isRateLimited).call(this, type, origin)) {
|
|
52
68
|
throw rpcErrors.limitExceeded({
|
|
53
69
|
message: `"${type.toString()}" is currently rate-limited. Please try again later.`,
|
|
54
70
|
});
|
|
55
71
|
}
|
|
56
|
-
this.
|
|
57
|
-
const implementation = this
|
|
72
|
+
__classPrivateFieldGet(this, _RateLimitController_instances, "m", _RateLimitController_recordRequest).call(this, type, origin);
|
|
73
|
+
const implementation = __classPrivateFieldGet(this, _RateLimitController_implementations, "f")[type].method;
|
|
58
74
|
if (!implementation) {
|
|
59
75
|
throw new Error('Invalid api type');
|
|
60
76
|
}
|
|
61
77
|
return implementation(...args);
|
|
62
78
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
*/
|
|
80
|
-
recordRequest(api, origin) {
|
|
81
|
-
const rateLimitTimeout = this.implementations[api].rateLimitTimeout ?? this.rateLimitTimeout;
|
|
82
|
-
const previous = this.state.requests[api][origin] ?? 0;
|
|
83
|
-
this.update((state) => {
|
|
84
|
-
if (previous === 0) {
|
|
85
|
-
setTimeout(() => this.resetRequestCount(api, origin), rateLimitTimeout);
|
|
86
|
-
}
|
|
87
|
-
Object.assign(state, {
|
|
88
|
-
requests: {
|
|
89
|
-
...state.requests,
|
|
90
|
-
[api]: { [origin]: previous + 1 },
|
|
91
|
-
},
|
|
92
|
-
});
|
|
79
|
+
}
|
|
80
|
+
_RateLimitController_implementations = new WeakMap(), _RateLimitController_rateLimitTimeout = new WeakMap(), _RateLimitController_rateLimitCount = new WeakMap(), _RateLimitController_instances = new WeakSet(), _RateLimitController_isRateLimited = function _RateLimitController_isRateLimited(api, origin) {
|
|
81
|
+
const rateLimitCount = __classPrivateFieldGet(this, _RateLimitController_implementations, "f")[api].rateLimitCount ?? __classPrivateFieldGet(this, _RateLimitController_rateLimitCount, "f");
|
|
82
|
+
return this.state.requests[api][origin] >= rateLimitCount;
|
|
83
|
+
}, _RateLimitController_recordRequest = function _RateLimitController_recordRequest(api, origin) {
|
|
84
|
+
const rateLimitTimeout = __classPrivateFieldGet(this, _RateLimitController_implementations, "f")[api].rateLimitTimeout ?? __classPrivateFieldGet(this, _RateLimitController_rateLimitTimeout, "f");
|
|
85
|
+
const previous = this.state.requests[api][origin] ?? 0;
|
|
86
|
+
this.update((state) => {
|
|
87
|
+
if (previous === 0) {
|
|
88
|
+
setTimeout(() => __classPrivateFieldGet(this, _RateLimitController_instances, "m", _RateLimitController_resetRequestCount).call(this, api, origin), rateLimitTimeout);
|
|
89
|
+
}
|
|
90
|
+
Object.assign(state, {
|
|
91
|
+
requests: {
|
|
92
|
+
...state.requests,
|
|
93
|
+
[api]: { [origin]: previous + 1 },
|
|
94
|
+
},
|
|
93
95
|
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.update((state) => {
|
|
103
|
-
Object.assign(state, {
|
|
104
|
-
requests: {
|
|
105
|
-
...state.requests,
|
|
106
|
-
[api]: { [origin]: 0 },
|
|
107
|
-
},
|
|
108
|
-
});
|
|
96
|
+
});
|
|
97
|
+
}, _RateLimitController_resetRequestCount = function _RateLimitController_resetRequestCount(api, origin) {
|
|
98
|
+
this.update((state) => {
|
|
99
|
+
Object.assign(state, {
|
|
100
|
+
requests: {
|
|
101
|
+
...state.requests,
|
|
102
|
+
[api]: { [origin]: 0 },
|
|
103
|
+
},
|
|
109
104
|
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
105
|
+
});
|
|
106
|
+
};
|
|
112
107
|
//# sourceMappingURL=RateLimitController.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimitController.mjs","sourceRoot":"","sources":["../src/RateLimitController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport type { Messenger, ActionConstraint } from '@metamask/messenger';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport { getKnownPropertyNames } from '@metamask/utils';\n\n/**\n * A rate-limited API endpoint.\n *\n * @property method - The method that is rate-limited.\n * @property rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @property rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\nexport type RateLimitedApi = {\n method: ActionConstraint['handler'];\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n};\n\n/**\n * A map of rate-limited API types to APIs.\n */\nexport type RateLimitedApiMap = Record<string, RateLimitedApi>;\n\n/**\n * A map of rate-limited API types to the number of requests made in a given interval for each origin and api type combination.\n *\n * @template RateLimitedApis - A {@link RateLimitedApiMap} containing the rate-limited API endpoints that is used by the {@link RateLimitController}.\n */\nexport type RateLimitedRequests<RateLimitedApis extends RateLimitedApiMap> =\n Record<keyof RateLimitedApis, Record<string, number>>;\n\n/**\n * The state of the {@link RateLimitController}.\n *\n * @template RateLimitedApis - A {@link RateLimitedApiMap} containing the rate-limited API endpoints that is used by the {@link RateLimitController}.\n * @property requests - An object containing the number of requests made in a given interval for each origin and api type combination.\n */\nexport type RateLimitState<RateLimitedApis extends RateLimitedApiMap> = {\n requests: RateLimitedRequests<RateLimitedApis>;\n};\n\nconst controllerName = 'RateLimitController';\n\nexport type RateLimitControllerStateChangeEvent<\n RateLimitedApis extends RateLimitedApiMap,\n> = ControllerStateChangeEvent<\n typeof controllerName,\n RateLimitState<RateLimitedApis>\n>;\n\nexport type RateLimitControllerGetStateAction<\n RateLimitedApis extends RateLimitedApiMap,\n> = ControllerGetStateAction<\n typeof controllerName,\n RateLimitState<RateLimitedApis>\n>;\n\nexport type RateLimitControllerCallApiAction<\n RateLimitedApis extends RateLimitedApiMap,\n> = {\n type: `${typeof controllerName}:call`;\n handler: RateLimitController<RateLimitedApis>['call'];\n};\n\nexport type RateLimitControllerActions<\n RateLimitedApis extends RateLimitedApiMap,\n> =\n | RateLimitControllerGetStateAction<RateLimitedApis>\n | RateLimitControllerCallApiAction<RateLimitedApis>;\n\nexport type RateLimitControllerEvents<\n RateLimitedApis extends RateLimitedApiMap,\n> = RateLimitControllerStateChangeEvent<RateLimitedApis>;\n\nexport type RateLimitMessenger<RateLimitedApis extends RateLimitedApiMap> =\n Messenger<\n typeof controllerName,\n RateLimitControllerActions<RateLimitedApis>,\n RateLimitControllerEvents<RateLimitedApis>\n >;\n\nconst metadata = {\n requests: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: false,\n usedInUi: false,\n },\n};\n\n/**\n * Controller with logic for rate-limiting API endpoints per requesting origin.\n */\nexport class RateLimitController<\n RateLimitedApis extends RateLimitedApiMap,\n> extends BaseController<\n typeof controllerName,\n RateLimitState<RateLimitedApis>,\n RateLimitMessenger<RateLimitedApis>\n> {\n private readonly implementations;\n\n private readonly rateLimitTimeout;\n\n private readonly rateLimitCount;\n\n /**\n * Creates a RateLimitController instance.\n *\n * @param options - Constructor options.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n * @param options.implementations - Mapping from API type to API implementation.\n * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\n constructor({\n rateLimitTimeout = 5000,\n rateLimitCount = 1,\n messenger,\n state,\n implementations,\n }: {\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n messenger: RateLimitMessenger<RateLimitedApis>;\n state?: Partial<RateLimitState<RateLimitedApis>>;\n implementations: RateLimitedApis;\n }) {\n const defaultState = {\n requests: getKnownPropertyNames(implementations).reduce<\n RateLimitedRequests<RateLimitedApis>\n >((acc, key) => ({ ...acc, [key]: {} }), {} as never),\n };\n super({\n name: controllerName,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.implementations = implementations;\n this.rateLimitTimeout = rateLimitTimeout;\n this.rateLimitCount = rateLimitCount;\n\n this.messenger.registerActionHandler(\n `${controllerName}:call`,\n (\n origin: string,\n type: keyof RateLimitedApis,\n ...args: Parameters<RateLimitedApis[typeof type]['method']>\n ) => this.call(origin, type, ...args),\n );\n }\n\n /**\n * Calls an API if the requesting origin is not rate-limited.\n *\n * @param origin - The requesting origin.\n * @param type - The type of API call to make.\n * @param args - Arguments for the API call.\n * @returns The result of the API call.\n */\n async call<ApiType extends keyof RateLimitedApis>(\n origin: string,\n type: ApiType,\n ...args: Parameters<RateLimitedApis[ApiType]['method']>\n ): Promise<ReturnType<RateLimitedApis[ApiType]['method']>> {\n if (this.isRateLimited(type, origin)) {\n throw rpcErrors.limitExceeded({\n message: `\"${type.toString()}\" is currently rate-limited. Please try again later.`,\n });\n }\n this.recordRequest(type, origin);\n\n const implementation = this.implementations[type].method as (\n ...args: Parameters<RateLimitedApis[ApiType]['method']>\n ) => ReturnType<RateLimitedApis[ApiType]['method']>;\n\n if (!implementation) {\n throw new Error('Invalid api type');\n }\n\n return implementation(...args);\n }\n\n /**\n * Checks whether an origin is rate limited for the a specific API.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n * @returns `true` if rate-limited, and `false` otherwise.\n */\n private isRateLimited(api: keyof RateLimitedApis, origin: string) {\n const rateLimitCount =\n this.implementations[api].rateLimitCount ?? this.rateLimitCount;\n return this.state.requests[api][origin] >= rateLimitCount;\n }\n\n /**\n * Records that an origin has made a request to call an API, for rate-limiting purposes.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n */\n private recordRequest(api: keyof RateLimitedApis, origin: string) {\n const rateLimitTimeout =\n this.implementations[api].rateLimitTimeout ?? this.rateLimitTimeout;\n const previous = this.state.requests[api][origin] ?? 0;\n this.update((state) => {\n if (previous === 0) {\n setTimeout(() => this.resetRequestCount(api, origin), rateLimitTimeout);\n }\n Object.assign(state, {\n requests: {\n ...(state.requests as RateLimitedRequests<RateLimitedApis>),\n [api]: { [origin]: previous + 1 },\n },\n });\n });\n }\n\n /**\n * Resets the request count for a given origin and API combination, for rate-limiting purposes.\n *\n * @param api - The API in question.\n * @param origin - The origin in question.\n */\n private resetRequestCount(api: keyof RateLimitedApis, origin: string) {\n this.update((state) => {\n Object.assign(state, {\n requests: {\n ...(state.requests as RateLimitedRequests<RateLimitedApis>),\n [api]: { [origin]: 0 },\n },\n });\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RateLimitController.mjs","sourceRoot":"","sources":["../src/RateLimitController.ts"],"names":[],"mappings":"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","sourcesContent":["import { BaseController } from '@metamask/base-controller';\nimport type {\n ControllerGetStateAction,\n ControllerStateChangeEvent,\n} from '@metamask/base-controller';\nimport type { Messenger, ActionConstraint } from '@metamask/messenger';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport { getKnownPropertyNames } from '@metamask/utils';\n\n/**\n * A rate-limited API endpoint.\n *\n * @property method - The method that is rate-limited.\n * @property rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @property rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\nexport type RateLimitedApi = {\n method: ActionConstraint['handler'];\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n};\n\n/**\n * A map of rate-limited API types to APIs.\n */\nexport type RateLimitedApiMap = Record<string, RateLimitedApi>;\n\n/**\n * A map of rate-limited API types to the number of requests made in a given interval for each origin and api type combination.\n *\n * @template RateLimitedApis - A {@link RateLimitedApiMap} containing the rate-limited API endpoints that is used by the {@link RateLimitController}.\n */\nexport type RateLimitedRequests<RateLimitedApis extends RateLimitedApiMap> =\n Record<keyof RateLimitedApis, Record<string, number>>;\n\n/**\n * The state of the {@link RateLimitController}.\n *\n * @template RateLimitedApis - A {@link RateLimitedApiMap} containing the rate-limited API endpoints that is used by the {@link RateLimitController}.\n * @property requests - An object containing the number of requests made in a given interval for each origin and api type combination.\n */\nexport type RateLimitState<RateLimitedApis extends RateLimitedApiMap> = {\n requests: RateLimitedRequests<RateLimitedApis>;\n};\n\nconst controllerName = 'RateLimitController';\n\nexport type RateLimitControllerStateChangeEvent<\n RateLimitedApis extends RateLimitedApiMap,\n> = ControllerStateChangeEvent<\n typeof controllerName,\n RateLimitState<RateLimitedApis>\n>;\n\nexport type RateLimitControllerGetStateAction<\n RateLimitedApis extends RateLimitedApiMap,\n> = ControllerGetStateAction<\n typeof controllerName,\n RateLimitState<RateLimitedApis>\n>;\n\nexport type RateLimitControllerCallApiAction<\n RateLimitedApis extends RateLimitedApiMap,\n> = {\n type: `${typeof controllerName}:call`;\n handler: RateLimitController<RateLimitedApis>['call'];\n};\n\nexport type RateLimitControllerActions<\n RateLimitedApis extends RateLimitedApiMap,\n> =\n | RateLimitControllerGetStateAction<RateLimitedApis>\n | RateLimitControllerCallApiAction<RateLimitedApis>;\n\nexport type RateLimitControllerEvents<\n RateLimitedApis extends RateLimitedApiMap,\n> = RateLimitControllerStateChangeEvent<RateLimitedApis>;\n\nexport type RateLimitMessenger<RateLimitedApis extends RateLimitedApiMap> =\n Messenger<\n typeof controllerName,\n RateLimitControllerActions<RateLimitedApis>,\n RateLimitControllerEvents<RateLimitedApis>\n >;\n\nconst metadata = {\n requests: {\n includeInStateLogs: false,\n persist: false,\n includeInDebugSnapshot: false,\n usedInUi: false,\n },\n};\n\n/**\n * Controller with logic for rate-limiting API endpoints per requesting origin.\n */\nexport class RateLimitController<\n RateLimitedApis extends RateLimitedApiMap,\n> extends BaseController<\n typeof controllerName,\n RateLimitState<RateLimitedApis>,\n RateLimitMessenger<RateLimitedApis>\n> {\n readonly #implementations;\n\n readonly #rateLimitTimeout;\n\n readonly #rateLimitCount;\n\n /**\n * Creates a RateLimitController instance.\n *\n * @param options - Constructor options.\n * @param options.messenger - A reference to the messaging system.\n * @param options.state - Initial state to set on this controller.\n * @param options.implementations - Mapping from API type to API implementation.\n * @param options.rateLimitTimeout - The time window in which the rate limit is applied (in ms).\n * @param options.rateLimitCount - The amount of calls an origin can make in the rate limit time window.\n */\n constructor({\n rateLimitTimeout = 5000,\n rateLimitCount = 1,\n messenger,\n state,\n implementations,\n }: {\n rateLimitTimeout?: number;\n rateLimitCount?: number;\n messenger: RateLimitMessenger<RateLimitedApis>;\n state?: Partial<RateLimitState<RateLimitedApis>>;\n implementations: RateLimitedApis;\n }) {\n const defaultState = {\n requests: getKnownPropertyNames(implementations).reduce<\n RateLimitedRequests<RateLimitedApis>\n >((acc, key) => ({ ...acc, [key]: {} }), {} as never),\n };\n super({\n name: controllerName,\n metadata,\n messenger,\n state: { ...defaultState, ...state },\n });\n this.#implementations = implementations;\n this.#rateLimitTimeout = rateLimitTimeout;\n this.#rateLimitCount = rateLimitCount;\n\n this.messenger.registerActionHandler(\n `${controllerName}:call`,\n (\n origin: string,\n type: keyof RateLimitedApis,\n ...args: Parameters<RateLimitedApis[typeof type]['method']>\n ) => this.call(origin, type, ...args),\n );\n }\n\n /**\n * Calls an API if the requesting origin is not rate-limited.\n *\n * @param origin - The requesting origin.\n * @param type - The type of API call to make.\n * @param args - Arguments for the API call.\n * @returns The result of the API call.\n */\n async call<ApiType extends keyof RateLimitedApis>(\n origin: string,\n type: ApiType,\n ...args: Parameters<RateLimitedApis[ApiType]['method']>\n ): Promise<ReturnType<RateLimitedApis[ApiType]['method']>> {\n if (this.#isRateLimited(type, origin)) {\n throw rpcErrors.limitExceeded({\n message: `\"${type.toString()}\" is currently rate-limited. Please try again later.`,\n });\n }\n this.#recordRequest(type, origin);\n\n const implementation = this.#implementations[type].method as (\n ...args: Parameters<RateLimitedApis[ApiType]['method']>\n ) => ReturnType<RateLimitedApis[ApiType]['method']>;\n\n if (!implementation) {\n throw new Error('Invalid api type');\n }\n\n return implementation(...args);\n }\n\n /**\n * Checks whether an origin is rate limited for a specific API.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n * @returns `true` if rate-limited, and `false` otherwise.\n */\n #isRateLimited(api: keyof RateLimitedApis, origin: string): boolean {\n const rateLimitCount =\n this.#implementations[api].rateLimitCount ?? this.#rateLimitCount;\n return this.state.requests[api][origin] >= rateLimitCount;\n }\n\n /**\n * Records that an origin has made a request to call an API, for rate-limiting purposes.\n *\n * @param api - The API the origin is trying to access.\n * @param origin - The origin trying to access the API.\n */\n #recordRequest(api: keyof RateLimitedApis, origin: string): void {\n const rateLimitTimeout =\n this.#implementations[api].rateLimitTimeout ?? this.#rateLimitTimeout;\n const previous = this.state.requests[api][origin] ?? 0;\n this.update((state) => {\n if (previous === 0) {\n setTimeout(\n () => this.#resetRequestCount(api, origin),\n rateLimitTimeout,\n );\n }\n Object.assign(state, {\n requests: {\n ...(state.requests as RateLimitedRequests<RateLimitedApis>),\n [api]: { [origin]: previous + 1 },\n },\n });\n });\n }\n\n /**\n * Resets the request count for a given origin and API combination, for rate-limiting purposes.\n *\n * @param api - The API in question.\n * @param origin - The origin in question.\n */\n #resetRequestCount(api: keyof RateLimitedApis, origin: string): void {\n this.update((state) => {\n Object.assign(state, {\n requests: {\n ...(state.requests as RateLimitedRequests<RateLimitedApis>),\n [api]: { [origin]: 0 },\n },\n });\n });\n }\n}\n"]}
|
package/package.json
CHANGED