@thirdweb-dev/service-utils 0.5.0-nightly-0f027069-20230828164852 → 0.5.0-nightly-6cf298a29-20240308012322

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.
Files changed (37) hide show
  1. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.dev.js +59 -27
  2. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.prod.js +59 -27
  3. package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.esm.js +58 -27
  4. package/dist/declarations/src/cf-worker/index.d.ts +6 -3
  5. package/dist/declarations/src/cf-worker/index.d.ts.map +1 -1
  6. package/dist/declarations/src/cf-worker/usage.d.ts +75 -12
  7. package/dist/declarations/src/cf-worker/usage.d.ts.map +1 -1
  8. package/dist/declarations/src/core/api.d.ts +37 -1
  9. package/dist/declarations/src/core/api.d.ts.map +1 -1
  10. package/dist/declarations/src/core/authorize/client.d.ts.map +1 -1
  11. package/dist/declarations/src/core/authorize/index.d.ts.map +1 -1
  12. package/dist/declarations/src/core/authorize/service.d.ts.map +1 -1
  13. package/dist/declarations/src/core/rateLimit/index.d.ts +20 -0
  14. package/dist/declarations/src/core/rateLimit/index.d.ts.map +1 -0
  15. package/dist/declarations/src/core/rateLimit/types.d.ts +13 -0
  16. package/dist/declarations/src/core/rateLimit/types.d.ts.map +1 -0
  17. package/dist/declarations/src/core/services.d.ts +49 -1
  18. package/dist/declarations/src/core/services.d.ts.map +1 -1
  19. package/dist/declarations/src/core/usageLimit/index.d.ts +5 -0
  20. package/dist/declarations/src/core/usageLimit/index.d.ts.map +1 -0
  21. package/dist/declarations/src/core/usageLimit/types.d.ts +9 -0
  22. package/dist/declarations/src/core/usageLimit/types.d.ts.map +1 -0
  23. package/dist/declarations/src/node/index.d.ts +23 -3
  24. package/dist/declarations/src/node/index.d.ts.map +1 -1
  25. package/dist/{index-ffddf746.esm.js → index-3b9a0743.esm.js} +170 -20
  26. package/dist/{index-6e0ecc5f.cjs.prod.js → index-62b88cac.cjs.dev.js} +171 -19
  27. package/dist/{index-cd4f96ef.cjs.dev.js → index-aa324361.cjs.prod.js} +171 -19
  28. package/dist/{services-86283509.esm.js → services-2aecbda8.esm.js} +21 -0
  29. package/dist/{services-9e185105.cjs.prod.js → services-508322f3.cjs.dev.js} +21 -0
  30. package/dist/{services-a3f36057.cjs.dev.js → services-5c4d6977.cjs.prod.js} +21 -0
  31. package/dist/thirdweb-dev-service-utils.cjs.dev.js +1 -1
  32. package/dist/thirdweb-dev-service-utils.cjs.prod.js +1 -1
  33. package/dist/thirdweb-dev-service-utils.esm.js +1 -1
  34. package/node/dist/thirdweb-dev-service-utils-node.cjs.dev.js +50 -24
  35. package/node/dist/thirdweb-dev-service-utils-node.cjs.prod.js +50 -24
  36. package/node/dist/thirdweb-dev-service-utils-node.esm.js +49 -24
  37. package/package.json +9 -8
@@ -0,0 +1,20 @@
1
+ import { CoreServiceConfig } from "../api";
2
+ import { AuthorizationResult } from "../authorize/types";
3
+ import { RateLimitResult } from "./types";
4
+ type IRedis = {
5
+ incr: (key: string) => Promise<number>;
6
+ expire: (key: string, ttlSeconds: number) => Promise<0 | 1>;
7
+ };
8
+ export declare function rateLimit(args: {
9
+ authzResult: AuthorizationResult;
10
+ serviceConfig: CoreServiceConfig;
11
+ redis: IRedis;
12
+ /**
13
+ * Sample requests to reduce load on Redis.
14
+ * This scales down the request count and the rate limit threshold.
15
+ * @default 1.0
16
+ */
17
+ sampleRate?: number;
18
+ }): Promise<RateLimitResult>;
19
+ export {};
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"../../../../../src/core/rateLimit","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAuB,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK1C,KAAK,MAAM,GAAG;IACZ,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CAC7D,CAAC;AAEF,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,WAAW,EAAE,mBAAmB,CAAC;IACjC,aAAa,EAAE,iBAAiB,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,eAAe,CAAC,CAsE3B"}
@@ -0,0 +1,13 @@
1
+ export type RateLimitResult = {
2
+ rateLimited: false;
3
+ requestCount: number;
4
+ rateLimit: number;
5
+ } | {
6
+ rateLimited: true;
7
+ requestCount: number;
8
+ rateLimit: number;
9
+ status: number;
10
+ errorMessage: string;
11
+ errorCode: string;
12
+ };
13
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"../../../../../src/core/rateLimit","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB;IACE,WAAW,EAAE,KAAK,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,GACD;IACE,WAAW,EAAE,IAAI,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC"}
@@ -31,8 +31,26 @@ export declare const SERVICE_DEFINITIONS: {
31
31
  readonly description: "Enable gasless transactions";
32
32
  readonly actions: readonly [];
33
33
  };
34
+ readonly embeddedWallets: {
35
+ readonly name: "embeddedWallets";
36
+ readonly title: "Embedded Wallets";
37
+ readonly description: "E-mail and social login wallets for easy web3 onboarding";
38
+ readonly actions: readonly [];
39
+ };
40
+ readonly checkout: {
41
+ readonly name: "checkout";
42
+ readonly title: "Checkouts";
43
+ readonly description: "NFT Checkouts for easy web3 onboarding";
44
+ readonly actions: readonly [];
45
+ };
46
+ readonly pay: {
47
+ readonly name: "pay";
48
+ readonly title: "Pay";
49
+ readonly description: "Pay for a blockchain transaction with any currency";
50
+ readonly actions: readonly [];
51
+ };
34
52
  };
35
- export declare const SERVICE_NAMES: ("storage" | "rpc" | "bundler" | "relayer")[];
53
+ export declare const SERVICE_NAMES: ("storage" | "rpc" | "bundler" | "relayer" | "embeddedWallets" | "checkout" | "pay")[];
36
54
  export declare const SERVICES: ({
37
55
  readonly name: "storage";
38
56
  readonly title: "Storage";
@@ -61,6 +79,21 @@ export declare const SERVICES: ({
61
79
  readonly title: "Gasless Relayer";
62
80
  readonly description: "Enable gasless transactions";
63
81
  readonly actions: readonly [];
82
+ } | {
83
+ readonly name: "embeddedWallets";
84
+ readonly title: "Embedded Wallets";
85
+ readonly description: "E-mail and social login wallets for easy web3 onboarding";
86
+ readonly actions: readonly [];
87
+ } | {
88
+ readonly name: "checkout";
89
+ readonly title: "Checkouts";
90
+ readonly description: "NFT Checkouts for easy web3 onboarding";
91
+ readonly actions: readonly [];
92
+ } | {
93
+ readonly name: "pay";
94
+ readonly title: "Pay";
95
+ readonly description: "Pay for a blockchain transaction with any currency";
96
+ readonly actions: readonly [];
64
97
  })[];
65
98
  export type ServiceName = (typeof SERVICE_NAMES)[number];
66
99
  export type ServiceAction = {
@@ -97,5 +130,20 @@ export declare function getServiceByName(name: ServiceName): {
97
130
  readonly title: "Gasless Relayer";
98
131
  readonly description: "Enable gasless transactions";
99
132
  readonly actions: readonly [];
133
+ } | {
134
+ readonly name: "embeddedWallets";
135
+ readonly title: "Embedded Wallets";
136
+ readonly description: "E-mail and social login wallets for easy web3 onboarding";
137
+ readonly actions: readonly [];
138
+ } | {
139
+ readonly name: "checkout";
140
+ readonly title: "Checkouts";
141
+ readonly description: "NFT Checkouts for easy web3 onboarding";
142
+ readonly actions: readonly [];
143
+ } | {
144
+ readonly name: "pay";
145
+ readonly title: "Pay";
146
+ readonly description: "Pay for a blockchain transaction with any currency";
147
+ readonly actions: readonly [];
100
148
  };
101
149
  //# sourceMappingURL=services.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.ts","sourceRoot":"../../../../src/core","sources":["services.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCtB,CAAC;AAEX,eAAO,MAAM,aAAa,+CAEe,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAqC,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,OAAO,GACjB,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAEjE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEjD"}
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"../../../../src/core","sources":["services.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4DtB,CAAC;AAEX,eAAO,MAAM,aAAa,wFAEe,CAAC;AAE1C,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAqC,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,OAAO,GACjB,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAEjE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEjD"}
@@ -0,0 +1,5 @@
1
+ import { CoreServiceConfig } from "../api";
2
+ import { AuthorizationResult } from "../authorize/types";
3
+ import { UsageLimitResult } from "./types";
4
+ export declare function usageLimit(authzResult: AuthorizationResult, serviceConfig: CoreServiceConfig): Promise<UsageLimitResult>;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"../../../../../src/core/usageLimit","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,wBAAsB,UAAU,CAC9B,WAAW,EAAE,mBAAmB,EAChC,aAAa,EAAE,iBAAiB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAmD3B"}
@@ -0,0 +1,9 @@
1
+ export type UsageLimitResult = {
2
+ usageLimited: false;
3
+ } | {
4
+ usageLimited: true;
5
+ status: number;
6
+ errorMessage: string;
7
+ errorCode: string;
8
+ };
9
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"../../../../../src/core/usageLimit","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB;IACE,YAAY,EAAE,KAAK,CAAC;CACrB,GACD;IACE,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC"}
@@ -1,23 +1,43 @@
1
1
  /// <reference types="node" />
2
+ import type { ServerResponse } from "http";
2
3
  import type { IncomingMessage } from "node:http";
3
- import type { AuthorizationInput } from "../core/authorize";
4
4
  import type { CoreServiceConfig } from "../core/api";
5
+ import type { AuthorizationInput } from "../core/authorize";
5
6
  import type { AuthorizationResult } from "../core/authorize/types";
6
7
  import type { CoreAuthInput } from "../core/types";
7
- import type { ServerResponse } from "http";
8
8
  export * from "../core/services";
9
+ export * from "../core/rateLimit";
10
+ export * from "../core/usageLimit";
9
11
  type NodeServiceConfig = CoreServiceConfig;
10
12
  export type AuthInput = CoreAuthInput & {
11
13
  req: IncomingMessage;
12
14
  };
15
+ /**
16
+ *
17
+ * @param {AuthInput['req']} authInput.req - The incoming request from which information will be pulled from. These information includes (checks are in order and terminates on first match):
18
+ * - clientId: Checks header `x-client-id`, search param `clientId`
19
+ * - bundleId: Checks header `x-bundle-id`, search param `bundleId`
20
+ * - secretKey: Checks header `x-secret-key`
21
+ * - origin (the requesting domain): Checks header `origin`, `referer`
22
+ * @param {AuthInput['clientId']} authInput.clientId - Overrides any clientId found on the `req` object
23
+ * @param {AuthInput['targetAddress']} authInput.targetAddress - Only used in smart wallets to determine if the request is authorized to interact with the target address.
24
+ * @param {NodeServiceConfig['enforceAuth']} serviceConfig - Always `true` unless you need to turn auth off. Tells the service whether or not to enforce auth.
25
+ * @param {NodeServiceConfig['apiUrl']} serviceConfig.apiUrl - The url of the api server to fetch information for verification. `https://api.thirdweb.com` for production and `https://api.staging.thirdweb.com` for staging
26
+ * @param {NodeServiceConfig['serviceApiKey']} serviceConfig.serviceApiKey - secret key to be used authenticate the caller of the api-server. Check the api-server's env variable for the keys.
27
+ * @param {NodeServiceConfig['serviceScope']} serviceConfig.serviceScope - The service that we are requesting authorization for. E.g. `relayer`, `rpc`, 'bundler', 'storage' etc.
28
+ * @param {NodeServiceConfig['serviceAction']} serviceConfig.serviceAction - Needed when the `serviceScope` is `storage`. Can be either `read` or `write`.
29
+ * @param {NodeServiceConfig['useWalletAuth']} serviceConfig.useWalletAuth - If true it pings the `wallet/me` or else, `account/me`. You most likely can leave this as false.
30
+ * @returns {AuthorizationResult} authorizationResult - contains if the request is authorized, and information about the account if it is authorized. Otherwise, it contains the error message and status code.
31
+ */
13
32
  export declare function authorizeNode(authInput: AuthInput, serviceConfig: NodeServiceConfig): Promise<AuthorizationResult>;
14
33
  export declare function extractAuthorizationData(authInput: AuthInput): AuthorizationInput;
15
34
  export declare function hashSecretKey(secretKey: string): string;
16
35
  export declare function deriveClientIdFromSecretKeyHash(secretKeyHash: string): string;
17
- export declare function logHttpRequest({ source, clientId, req, res, isAuthed, statusMessage, }: AuthInput & {
36
+ export declare function logHttpRequest({ source, clientId, req, res, isAuthed, statusMessage, latencyMs, }: AuthInput & {
18
37
  source: string;
19
38
  res: ServerResponse;
20
39
  isAuthed?: boolean;
21
40
  statusMessage?: Error | string;
41
+ latencyMs?: number;
22
42
  }): void;
23
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"../../../../src/node","sources":["index.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAuB,eAAe,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAE3C,cAAc,kBAAkB,CAAC;AAEjC,KAAK,iBAAiB,GAAG,iBAAiB,CAAC;AAE3C,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG;IACtC,GAAG,EAAE,eAAe,CAAC;CACtB,CAAC;AAEF,wBAAsB,aAAa,CACjC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,iBAAiB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAsB9B;AAaD,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,GACnB,kBAAkB,CA2FpB;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,UAE9C;AAED,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,MAAM,UAEpE;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,EACR,GAAG,EACH,GAAG,EACH,QAAQ,EACR,aAAa,GACd,EAAE,SAAS,GAAG;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,cAAc,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CAChC,QAsBA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"../../../../src/node","sources":["index.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,KAAK,EAAuB,eAAe,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,KAAK,iBAAiB,GAAG,iBAAiB,CAAC;AAE3C,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG;IACtC,GAAG,EAAE,eAAe,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,SAAS,EACpB,aAAa,EAAE,iBAAiB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAsB9B;AAaD,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,GACnB,kBAAkB,CA2FpB;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,UAE9C;AAED,wBAAgB,+BAA+B,CAAC,aAAa,EAAE,MAAM,UAEpE;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,EACR,GAAG,EACH,GAAG,EACH,QAAQ,EACR,aAAa,EACb,SAAS,GACV,EAAE,SAAS,GAAG;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,cAAc,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,QA4BA"}
@@ -2,9 +2,12 @@ async function fetchKeyMetadataFromApi(clientId, config) {
2
2
  const {
3
3
  apiUrl,
4
4
  serviceScope,
5
- serviceApiKey
5
+ serviceApiKey,
6
+ checkPolicy,
7
+ policyMetadata
6
8
  } = config;
7
- const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}`;
9
+ const policyQuery = checkPolicy && policyMetadata ? `&checkPolicy=true&policyMetadata=${encodeURIComponent(JSON.stringify(policyMetadata))}` : "";
10
+ const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=true${policyQuery}`;
8
11
  const response = await fetch(url, {
9
12
  method: "GET",
10
13
  headers: {
@@ -12,20 +15,20 @@ async function fetchKeyMetadataFromApi(clientId, config) {
12
15
  "content-type": "application/json"
13
16
  }
14
17
  });
15
- let json;
18
+ let text = "";
16
19
  try {
17
- json = await response.json();
20
+ text = await response.text();
21
+ return JSON.parse(text);
18
22
  } catch (e) {
19
- throw new Error(`Error fetching key metadata from API: ${response.status} - ${response.statusText} - ${await response.text()}`);
23
+ throw new Error(`Error fetching key metadata from API: ${response.status} - ${text}`);
20
24
  }
21
- return json;
22
25
  }
23
26
  async function fetchAccountFromApi(jwt, config, useWalletAuth) {
24
27
  const {
25
28
  apiUrl,
26
29
  serviceApiKey
27
30
  } = config;
28
- const url = useWalletAuth ? `${apiUrl}/v1/wallet/me` : `${apiUrl}/v1/account/me`;
31
+ const url = useWalletAuth ? `${apiUrl}/v1/wallet/me?includeUsage=true` : `${apiUrl}/v1/account/me?includeUsage=true`;
29
32
  const response = await fetch(url, {
30
33
  method: "GET",
31
34
  headers: {
@@ -34,13 +37,32 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
34
37
  authorization: `Bearer ${jwt}`
35
38
  }
36
39
  });
37
- let json;
40
+ let text = "";
38
41
  try {
39
- json = await response.json();
42
+ text = await response.text();
43
+ return JSON.parse(text);
40
44
  } catch (e) {
41
- throw new Error(`Error fetching account from API: ${response.status} - ${response.statusText} - ${await response.text()}`);
45
+ throw new Error(`Error fetching account from API: ${response.status} - ${text}`);
42
46
  }
43
- return json;
47
+ }
48
+ async function updateRateLimitedAt(apiKeyId, config) {
49
+ const {
50
+ apiUrl,
51
+ serviceScope: scope,
52
+ serviceApiKey
53
+ } = config;
54
+ const url = `${apiUrl}/usage/rateLimit`;
55
+ await fetch(url, {
56
+ method: "PUT",
57
+ headers: {
58
+ "x-service-api-key": serviceApiKey,
59
+ "content-type": "application/json"
60
+ },
61
+ body: JSON.stringify({
62
+ apiKeyId,
63
+ scope
64
+ })
65
+ });
44
66
  }
45
67
 
46
68
  function authorizeClient(authOptions, apiKeyMeta) {
@@ -61,7 +83,10 @@ function authorizeClient(authOptions, apiKeyMeta) {
61
83
  id: apiKeyMeta.accountId,
62
84
  // TODO update this later
63
85
  name: "",
64
- creatorWalletAddress: apiKeyMeta.creatorWalletAddress
86
+ creatorWalletAddress: apiKeyMeta.creatorWalletAddress,
87
+ limits: apiKeyMeta.limits,
88
+ rateLimits: apiKeyMeta.rateLimits,
89
+ usage: apiKeyMeta.usage
65
90
  }
66
91
  };
67
92
 
@@ -191,12 +216,15 @@ function authorizeService(apiKeyMetadata, serviceConfig, authorizationPayload) {
191
216
  }
192
217
  return {
193
218
  authorized: true,
219
+ apiKeyMeta: apiKeyMetadata,
194
220
  accountMeta: {
195
221
  id: apiKeyMetadata.accountId,
196
222
  name: "",
197
- creatorWalletAddress: apiKeyMetadata.creatorWalletAddress
198
- },
199
- apiKeyMeta: apiKeyMetadata
223
+ creatorWalletAddress: apiKeyMetadata.creatorWalletAddress,
224
+ limits: apiKeyMetadata.limits,
225
+ rateLimits: apiKeyMetadata.rateLimits,
226
+ usage: apiKeyMetadata.usage
227
+ }
200
228
  };
201
229
  }
202
230
 
@@ -234,9 +262,9 @@ async function authorize(authData, serviceConfig, cacheOptions) {
234
262
  // if the difference is greater than the cacheTtl we want to ignore the cached data
235
263
  const now = Date.now();
236
264
  const diff = now - parsed.updatedAt;
237
- const cacheTtl = cacheOptions.cacheTtlSeconds * 1000;
265
+ const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
238
266
  // only if the diff is less than the cacheTtl do we want to use the cached key
239
- if (diff < cacheTtl * 1000) {
267
+ if (diff < cacheTtlMs) {
240
268
  accountMeta = parsed.apiKeyMeta;
241
269
  }
242
270
  } else {
@@ -320,9 +348,9 @@ async function authorize(authData, serviceConfig, cacheOptions) {
320
348
  // if the difference is greater than the cacheTtl we want to ignore the cached data
321
349
  const now = Date.now();
322
350
  const diff = now - parsed.updatedAt;
323
- const cacheTtl = cacheOptions.cacheTtlSeconds * 1000;
351
+ const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
324
352
  // only if the diff is less than the cacheTtl do we want to use the cached key
325
- if (diff < cacheTtl * 1000) {
353
+ if (diff < cacheTtlMs) {
326
354
  apiKeyMeta = parsed.apiKeyMeta;
327
355
  }
328
356
  } else {
@@ -414,9 +442,131 @@ async function authorize(authData, serviceConfig, cacheOptions) {
414
442
  id: apiKeyMeta.accountId,
415
443
  // TODO update this later
416
444
  name: "",
445
+ limits: apiKeyMeta.limits,
446
+ rateLimits: apiKeyMeta.rateLimits,
447
+ usage: apiKeyMeta.usage,
417
448
  creatorWalletAddress: apiKeyMeta.creatorWalletAddress
418
449
  }
419
450
  };
420
451
  }
421
452
 
422
- export { authorize as a };
453
+ const RATE_LIMIT_WINDOW_SECONDS = 10;
454
+
455
+ // Redis interface compatible with ioredis (Node) and upstash (Cloudflare Workers).
456
+
457
+ async function rateLimit(args) {
458
+ const {
459
+ authzResult,
460
+ serviceConfig,
461
+ redis,
462
+ sampleRate = 1.0
463
+ } = args;
464
+ const shouldSampleRequest = Math.random() < sampleRate;
465
+ if (!shouldSampleRequest || !authzResult.authorized) {
466
+ return {
467
+ rateLimited: false,
468
+ requestCount: 0,
469
+ rateLimit: 0
470
+ };
471
+ }
472
+ const {
473
+ apiKeyMeta,
474
+ accountMeta
475
+ } = authzResult;
476
+ const accountId = apiKeyMeta?.accountId || accountMeta?.id;
477
+ const {
478
+ serviceScope
479
+ } = serviceConfig;
480
+ const limitPerSecond = apiKeyMeta?.rateLimits?.[serviceScope] ?? accountMeta?.rateLimits?.[serviceScope];
481
+ if (!limitPerSecond) {
482
+ // No rate limit is provided. Assume the request is not rate limited.
483
+ return {
484
+ rateLimited: false,
485
+ requestCount: 0,
486
+ rateLimit: 0
487
+ };
488
+ }
489
+
490
+ // Gets the 10-second window for the current timestamp.
491
+ const timestampWindow = Math.floor(Date.now() / (1000 * RATE_LIMIT_WINDOW_SECONDS)) * RATE_LIMIT_WINDOW_SECONDS;
492
+ const key = `rate-limit:${serviceScope}:${accountId}:${timestampWindow}`;
493
+
494
+ // Increment and get the current request count in this window.
495
+ const requestCount = await redis.incr(key);
496
+ if (requestCount === 1) {
497
+ // For the first increment, set an expiration to clean up this key.
498
+ await redis.expire(key, RATE_LIMIT_WINDOW_SECONDS);
499
+ }
500
+
501
+ // Get the limit for this window accounting for the sample rate.
502
+ const limitPerWindow = limitPerSecond * sampleRate * RATE_LIMIT_WINDOW_SECONDS;
503
+ if (requestCount > limitPerWindow) {
504
+ // Report rate limit hits.
505
+ if (apiKeyMeta?.id) {
506
+ await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
507
+ }
508
+
509
+ // Reject requests when they've exceeded 2x the rate limit.
510
+ if (requestCount > 2 * limitPerWindow) {
511
+ return {
512
+ rateLimited: true,
513
+ requestCount,
514
+ rateLimit: limitPerWindow,
515
+ status: 429,
516
+ errorMessage: `You've exceeded your ${serviceScope} rate limit at ${limitPerSecond} reqs/sec. To get higher rate limits, contact us at https://thirdweb.com/contact-us.`,
517
+ errorCode: "RATE_LIMIT_EXCEEDED"
518
+ };
519
+ }
520
+ }
521
+ return {
522
+ rateLimited: false,
523
+ requestCount,
524
+ rateLimit: limitPerWindow
525
+ };
526
+ }
527
+
528
+ async function usageLimit(authzResult, serviceConfig) {
529
+ if (!authzResult.authorized) {
530
+ return {
531
+ usageLimited: false
532
+ };
533
+ }
534
+ const {
535
+ apiKeyMeta,
536
+ accountMeta
537
+ } = authzResult;
538
+ const {
539
+ limits,
540
+ usage
541
+ } = apiKeyMeta || accountMeta || {};
542
+ const {
543
+ serviceScope
544
+ } = serviceConfig;
545
+ if (!usage || !(serviceScope in usage) || !limits || !(serviceScope in limits)) {
546
+ // No usage limit is provided. Assume the request is not limited.
547
+ return {
548
+ usageLimited: false
549
+ };
550
+ }
551
+ if (serviceScope === "storage" && (usage.storage?.sumFileSizeBytes ?? 0) > (limits.storage ?? 0)) {
552
+ return {
553
+ usageLimited: true,
554
+ status: 403,
555
+ errorMessage: `You've used all of your total usage credits for Storage Pinning. Please add your payment method at https://thirdweb.com/dashboard/settings/billing.`,
556
+ errorCode: "PAYMENT_METHOD_REQUIRED"
557
+ };
558
+ }
559
+ if (serviceScope === "embeddedWallets" && (usage.embeddedWallets?.countWalletAddresses ?? 0) > (limits.embeddedWallets ?? 0)) {
560
+ return {
561
+ usageLimited: true,
562
+ status: 403,
563
+ errorMessage: `You've used all of your total usage credits for Embedded Wallets. Please add your payment method at https://thirdweb.com/dashboard/settings/billing.`,
564
+ errorCode: "PAYMENT_METHOD_REQUIRED"
565
+ };
566
+ }
567
+ return {
568
+ usageLimited: false
569
+ };
570
+ }
571
+
572
+ export { authorize as a, rateLimit as r, usageLimit as u };