@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.
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.dev.js +59 -27
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.cjs.prod.js +59 -27
- package/cf-worker/dist/thirdweb-dev-service-utils-cf-worker.esm.js +58 -27
- package/dist/declarations/src/cf-worker/index.d.ts +6 -3
- package/dist/declarations/src/cf-worker/index.d.ts.map +1 -1
- package/dist/declarations/src/cf-worker/usage.d.ts +75 -12
- package/dist/declarations/src/cf-worker/usage.d.ts.map +1 -1
- package/dist/declarations/src/core/api.d.ts +37 -1
- package/dist/declarations/src/core/api.d.ts.map +1 -1
- package/dist/declarations/src/core/authorize/client.d.ts.map +1 -1
- package/dist/declarations/src/core/authorize/index.d.ts.map +1 -1
- package/dist/declarations/src/core/authorize/service.d.ts.map +1 -1
- package/dist/declarations/src/core/rateLimit/index.d.ts +20 -0
- package/dist/declarations/src/core/rateLimit/index.d.ts.map +1 -0
- package/dist/declarations/src/core/rateLimit/types.d.ts +13 -0
- package/dist/declarations/src/core/rateLimit/types.d.ts.map +1 -0
- package/dist/declarations/src/core/services.d.ts +49 -1
- package/dist/declarations/src/core/services.d.ts.map +1 -1
- package/dist/declarations/src/core/usageLimit/index.d.ts +5 -0
- package/dist/declarations/src/core/usageLimit/index.d.ts.map +1 -0
- package/dist/declarations/src/core/usageLimit/types.d.ts +9 -0
- package/dist/declarations/src/core/usageLimit/types.d.ts.map +1 -0
- package/dist/declarations/src/node/index.d.ts +23 -3
- package/dist/declarations/src/node/index.d.ts.map +1 -1
- package/dist/{index-ffddf746.esm.js → index-3b9a0743.esm.js} +170 -20
- package/dist/{index-6e0ecc5f.cjs.prod.js → index-62b88cac.cjs.dev.js} +171 -19
- package/dist/{index-cd4f96ef.cjs.dev.js → index-aa324361.cjs.prod.js} +171 -19
- package/dist/{services-86283509.esm.js → services-2aecbda8.esm.js} +21 -0
- package/dist/{services-9e185105.cjs.prod.js → services-508322f3.cjs.dev.js} +21 -0
- package/dist/{services-a3f36057.cjs.dev.js → services-5c4d6977.cjs.prod.js} +21 -0
- package/dist/thirdweb-dev-service-utils.cjs.dev.js +1 -1
- package/dist/thirdweb-dev-service-utils.cjs.prod.js +1 -1
- package/dist/thirdweb-dev-service-utils.esm.js +1 -1
- package/node/dist/thirdweb-dev-service-utils-node.cjs.dev.js +50 -24
- package/node/dist/thirdweb-dev-service-utils-node.cjs.prod.js +50 -24
- package/node/dist/thirdweb-dev-service-utils-node.esm.js +49 -24
- package/package.json +9 -8
@@ -4,9 +4,12 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
4
4
|
const {
|
5
5
|
apiUrl,
|
6
6
|
serviceScope,
|
7
|
-
serviceApiKey
|
7
|
+
serviceApiKey,
|
8
|
+
checkPolicy,
|
9
|
+
policyMetadata
|
8
10
|
} = config;
|
9
|
-
const
|
11
|
+
const policyQuery = checkPolicy && policyMetadata ? `&checkPolicy=true&policyMetadata=${encodeURIComponent(JSON.stringify(policyMetadata))}` : "";
|
12
|
+
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=true${policyQuery}`;
|
10
13
|
const response = await fetch(url, {
|
11
14
|
method: "GET",
|
12
15
|
headers: {
|
@@ -14,20 +17,20 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
14
17
|
"content-type": "application/json"
|
15
18
|
}
|
16
19
|
});
|
17
|
-
let
|
20
|
+
let text = "";
|
18
21
|
try {
|
19
|
-
|
22
|
+
text = await response.text();
|
23
|
+
return JSON.parse(text);
|
20
24
|
} catch (e) {
|
21
|
-
throw new Error(`Error fetching key metadata from API: ${response.status} - ${
|
25
|
+
throw new Error(`Error fetching key metadata from API: ${response.status} - ${text}`);
|
22
26
|
}
|
23
|
-
return json;
|
24
27
|
}
|
25
28
|
async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
26
29
|
const {
|
27
30
|
apiUrl,
|
28
31
|
serviceApiKey
|
29
32
|
} = config;
|
30
|
-
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me` : `${apiUrl}/v1/account/me`;
|
33
|
+
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me?includeUsage=true` : `${apiUrl}/v1/account/me?includeUsage=true`;
|
31
34
|
const response = await fetch(url, {
|
32
35
|
method: "GET",
|
33
36
|
headers: {
|
@@ -36,13 +39,32 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
36
39
|
authorization: `Bearer ${jwt}`
|
37
40
|
}
|
38
41
|
});
|
39
|
-
let
|
42
|
+
let text = "";
|
40
43
|
try {
|
41
|
-
|
44
|
+
text = await response.text();
|
45
|
+
return JSON.parse(text);
|
42
46
|
} catch (e) {
|
43
|
-
throw new Error(`Error fetching account from API: ${response.status} - ${
|
47
|
+
throw new Error(`Error fetching account from API: ${response.status} - ${text}`);
|
44
48
|
}
|
45
|
-
|
49
|
+
}
|
50
|
+
async function updateRateLimitedAt(apiKeyId, config) {
|
51
|
+
const {
|
52
|
+
apiUrl,
|
53
|
+
serviceScope: scope,
|
54
|
+
serviceApiKey
|
55
|
+
} = config;
|
56
|
+
const url = `${apiUrl}/usage/rateLimit`;
|
57
|
+
await fetch(url, {
|
58
|
+
method: "PUT",
|
59
|
+
headers: {
|
60
|
+
"x-service-api-key": serviceApiKey,
|
61
|
+
"content-type": "application/json"
|
62
|
+
},
|
63
|
+
body: JSON.stringify({
|
64
|
+
apiKeyId,
|
65
|
+
scope
|
66
|
+
})
|
67
|
+
});
|
46
68
|
}
|
47
69
|
|
48
70
|
function authorizeClient(authOptions, apiKeyMeta) {
|
@@ -63,7 +85,10 @@ function authorizeClient(authOptions, apiKeyMeta) {
|
|
63
85
|
id: apiKeyMeta.accountId,
|
64
86
|
// TODO update this later
|
65
87
|
name: "",
|
66
|
-
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
88
|
+
creatorWalletAddress: apiKeyMeta.creatorWalletAddress,
|
89
|
+
limits: apiKeyMeta.limits,
|
90
|
+
rateLimits: apiKeyMeta.rateLimits,
|
91
|
+
usage: apiKeyMeta.usage
|
67
92
|
}
|
68
93
|
};
|
69
94
|
|
@@ -193,12 +218,15 @@ function authorizeService(apiKeyMetadata, serviceConfig, authorizationPayload) {
|
|
193
218
|
}
|
194
219
|
return {
|
195
220
|
authorized: true,
|
221
|
+
apiKeyMeta: apiKeyMetadata,
|
196
222
|
accountMeta: {
|
197
223
|
id: apiKeyMetadata.accountId,
|
198
224
|
name: "",
|
199
|
-
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress
|
200
|
-
|
201
|
-
|
225
|
+
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress,
|
226
|
+
limits: apiKeyMetadata.limits,
|
227
|
+
rateLimits: apiKeyMetadata.rateLimits,
|
228
|
+
usage: apiKeyMetadata.usage
|
229
|
+
}
|
202
230
|
};
|
203
231
|
}
|
204
232
|
|
@@ -236,9 +264,9 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
236
264
|
// if the difference is greater than the cacheTtl we want to ignore the cached data
|
237
265
|
const now = Date.now();
|
238
266
|
const diff = now - parsed.updatedAt;
|
239
|
-
const
|
267
|
+
const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
|
240
268
|
// only if the diff is less than the cacheTtl do we want to use the cached key
|
241
|
-
if (diff <
|
269
|
+
if (diff < cacheTtlMs) {
|
242
270
|
accountMeta = parsed.apiKeyMeta;
|
243
271
|
}
|
244
272
|
} else {
|
@@ -322,9 +350,9 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
322
350
|
// if the difference is greater than the cacheTtl we want to ignore the cached data
|
323
351
|
const now = Date.now();
|
324
352
|
const diff = now - parsed.updatedAt;
|
325
|
-
const
|
353
|
+
const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
|
326
354
|
// only if the diff is less than the cacheTtl do we want to use the cached key
|
327
|
-
if (diff <
|
355
|
+
if (diff < cacheTtlMs) {
|
328
356
|
apiKeyMeta = parsed.apiKeyMeta;
|
329
357
|
}
|
330
358
|
} else {
|
@@ -416,9 +444,133 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
416
444
|
id: apiKeyMeta.accountId,
|
417
445
|
// TODO update this later
|
418
446
|
name: "",
|
447
|
+
limits: apiKeyMeta.limits,
|
448
|
+
rateLimits: apiKeyMeta.rateLimits,
|
449
|
+
usage: apiKeyMeta.usage,
|
419
450
|
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
420
451
|
}
|
421
452
|
};
|
422
453
|
}
|
423
454
|
|
455
|
+
const RATE_LIMIT_WINDOW_SECONDS = 10;
|
456
|
+
|
457
|
+
// Redis interface compatible with ioredis (Node) and upstash (Cloudflare Workers).
|
458
|
+
|
459
|
+
async function rateLimit(args) {
|
460
|
+
const {
|
461
|
+
authzResult,
|
462
|
+
serviceConfig,
|
463
|
+
redis,
|
464
|
+
sampleRate = 1.0
|
465
|
+
} = args;
|
466
|
+
const shouldSampleRequest = Math.random() < sampleRate;
|
467
|
+
if (!shouldSampleRequest || !authzResult.authorized) {
|
468
|
+
return {
|
469
|
+
rateLimited: false,
|
470
|
+
requestCount: 0,
|
471
|
+
rateLimit: 0
|
472
|
+
};
|
473
|
+
}
|
474
|
+
const {
|
475
|
+
apiKeyMeta,
|
476
|
+
accountMeta
|
477
|
+
} = authzResult;
|
478
|
+
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
479
|
+
const {
|
480
|
+
serviceScope
|
481
|
+
} = serviceConfig;
|
482
|
+
const limitPerSecond = apiKeyMeta?.rateLimits?.[serviceScope] ?? accountMeta?.rateLimits?.[serviceScope];
|
483
|
+
if (!limitPerSecond) {
|
484
|
+
// No rate limit is provided. Assume the request is not rate limited.
|
485
|
+
return {
|
486
|
+
rateLimited: false,
|
487
|
+
requestCount: 0,
|
488
|
+
rateLimit: 0
|
489
|
+
};
|
490
|
+
}
|
491
|
+
|
492
|
+
// Gets the 10-second window for the current timestamp.
|
493
|
+
const timestampWindow = Math.floor(Date.now() / (1000 * RATE_LIMIT_WINDOW_SECONDS)) * RATE_LIMIT_WINDOW_SECONDS;
|
494
|
+
const key = `rate-limit:${serviceScope}:${accountId}:${timestampWindow}`;
|
495
|
+
|
496
|
+
// Increment and get the current request count in this window.
|
497
|
+
const requestCount = await redis.incr(key);
|
498
|
+
if (requestCount === 1) {
|
499
|
+
// For the first increment, set an expiration to clean up this key.
|
500
|
+
await redis.expire(key, RATE_LIMIT_WINDOW_SECONDS);
|
501
|
+
}
|
502
|
+
|
503
|
+
// Get the limit for this window accounting for the sample rate.
|
504
|
+
const limitPerWindow = limitPerSecond * sampleRate * RATE_LIMIT_WINDOW_SECONDS;
|
505
|
+
if (requestCount > limitPerWindow) {
|
506
|
+
// Report rate limit hits.
|
507
|
+
if (apiKeyMeta?.id) {
|
508
|
+
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
509
|
+
}
|
510
|
+
|
511
|
+
// Reject requests when they've exceeded 2x the rate limit.
|
512
|
+
if (requestCount > 2 * limitPerWindow) {
|
513
|
+
return {
|
514
|
+
rateLimited: true,
|
515
|
+
requestCount,
|
516
|
+
rateLimit: limitPerWindow,
|
517
|
+
status: 429,
|
518
|
+
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.`,
|
519
|
+
errorCode: "RATE_LIMIT_EXCEEDED"
|
520
|
+
};
|
521
|
+
}
|
522
|
+
}
|
523
|
+
return {
|
524
|
+
rateLimited: false,
|
525
|
+
requestCount,
|
526
|
+
rateLimit: limitPerWindow
|
527
|
+
};
|
528
|
+
}
|
529
|
+
|
530
|
+
async function usageLimit(authzResult, serviceConfig) {
|
531
|
+
if (!authzResult.authorized) {
|
532
|
+
return {
|
533
|
+
usageLimited: false
|
534
|
+
};
|
535
|
+
}
|
536
|
+
const {
|
537
|
+
apiKeyMeta,
|
538
|
+
accountMeta
|
539
|
+
} = authzResult;
|
540
|
+
const {
|
541
|
+
limits,
|
542
|
+
usage
|
543
|
+
} = apiKeyMeta || accountMeta || {};
|
544
|
+
const {
|
545
|
+
serviceScope
|
546
|
+
} = serviceConfig;
|
547
|
+
if (!usage || !(serviceScope in usage) || !limits || !(serviceScope in limits)) {
|
548
|
+
// No usage limit is provided. Assume the request is not limited.
|
549
|
+
return {
|
550
|
+
usageLimited: false
|
551
|
+
};
|
552
|
+
}
|
553
|
+
if (serviceScope === "storage" && (usage.storage?.sumFileSizeBytes ?? 0) > (limits.storage ?? 0)) {
|
554
|
+
return {
|
555
|
+
usageLimited: true,
|
556
|
+
status: 403,
|
557
|
+
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.`,
|
558
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
559
|
+
};
|
560
|
+
}
|
561
|
+
if (serviceScope === "embeddedWallets" && (usage.embeddedWallets?.countWalletAddresses ?? 0) > (limits.embeddedWallets ?? 0)) {
|
562
|
+
return {
|
563
|
+
usageLimited: true,
|
564
|
+
status: 403,
|
565
|
+
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.`,
|
566
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
567
|
+
};
|
568
|
+
}
|
569
|
+
return {
|
570
|
+
usageLimited: false
|
571
|
+
};
|
572
|
+
}
|
573
|
+
|
424
574
|
exports.authorize = authorize;
|
575
|
+
exports.rateLimit = rateLimit;
|
576
|
+
exports.usageLimit = usageLimit;
|
@@ -4,9 +4,12 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
4
4
|
const {
|
5
5
|
apiUrl,
|
6
6
|
serviceScope,
|
7
|
-
serviceApiKey
|
7
|
+
serviceApiKey,
|
8
|
+
checkPolicy,
|
9
|
+
policyMetadata
|
8
10
|
} = config;
|
9
|
-
const
|
11
|
+
const policyQuery = checkPolicy && policyMetadata ? `&checkPolicy=true&policyMetadata=${encodeURIComponent(JSON.stringify(policyMetadata))}` : "";
|
12
|
+
const url = `${apiUrl}/v1/keys/use?clientId=${clientId}&scope=${serviceScope}&includeUsage=true${policyQuery}`;
|
10
13
|
const response = await fetch(url, {
|
11
14
|
method: "GET",
|
12
15
|
headers: {
|
@@ -14,20 +17,20 @@ async function fetchKeyMetadataFromApi(clientId, config) {
|
|
14
17
|
"content-type": "application/json"
|
15
18
|
}
|
16
19
|
});
|
17
|
-
let
|
20
|
+
let text = "";
|
18
21
|
try {
|
19
|
-
|
22
|
+
text = await response.text();
|
23
|
+
return JSON.parse(text);
|
20
24
|
} catch (e) {
|
21
|
-
throw new Error(`Error fetching key metadata from API: ${response.status} - ${
|
25
|
+
throw new Error(`Error fetching key metadata from API: ${response.status} - ${text}`);
|
22
26
|
}
|
23
|
-
return json;
|
24
27
|
}
|
25
28
|
async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
26
29
|
const {
|
27
30
|
apiUrl,
|
28
31
|
serviceApiKey
|
29
32
|
} = config;
|
30
|
-
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me` : `${apiUrl}/v1/account/me`;
|
33
|
+
const url = useWalletAuth ? `${apiUrl}/v1/wallet/me?includeUsage=true` : `${apiUrl}/v1/account/me?includeUsage=true`;
|
31
34
|
const response = await fetch(url, {
|
32
35
|
method: "GET",
|
33
36
|
headers: {
|
@@ -36,13 +39,32 @@ async function fetchAccountFromApi(jwt, config, useWalletAuth) {
|
|
36
39
|
authorization: `Bearer ${jwt}`
|
37
40
|
}
|
38
41
|
});
|
39
|
-
let
|
42
|
+
let text = "";
|
40
43
|
try {
|
41
|
-
|
44
|
+
text = await response.text();
|
45
|
+
return JSON.parse(text);
|
42
46
|
} catch (e) {
|
43
|
-
throw new Error(`Error fetching account from API: ${response.status} - ${
|
47
|
+
throw new Error(`Error fetching account from API: ${response.status} - ${text}`);
|
44
48
|
}
|
45
|
-
|
49
|
+
}
|
50
|
+
async function updateRateLimitedAt(apiKeyId, config) {
|
51
|
+
const {
|
52
|
+
apiUrl,
|
53
|
+
serviceScope: scope,
|
54
|
+
serviceApiKey
|
55
|
+
} = config;
|
56
|
+
const url = `${apiUrl}/usage/rateLimit`;
|
57
|
+
await fetch(url, {
|
58
|
+
method: "PUT",
|
59
|
+
headers: {
|
60
|
+
"x-service-api-key": serviceApiKey,
|
61
|
+
"content-type": "application/json"
|
62
|
+
},
|
63
|
+
body: JSON.stringify({
|
64
|
+
apiKeyId,
|
65
|
+
scope
|
66
|
+
})
|
67
|
+
});
|
46
68
|
}
|
47
69
|
|
48
70
|
function authorizeClient(authOptions, apiKeyMeta) {
|
@@ -63,7 +85,10 @@ function authorizeClient(authOptions, apiKeyMeta) {
|
|
63
85
|
id: apiKeyMeta.accountId,
|
64
86
|
// TODO update this later
|
65
87
|
name: "",
|
66
|
-
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
88
|
+
creatorWalletAddress: apiKeyMeta.creatorWalletAddress,
|
89
|
+
limits: apiKeyMeta.limits,
|
90
|
+
rateLimits: apiKeyMeta.rateLimits,
|
91
|
+
usage: apiKeyMeta.usage
|
67
92
|
}
|
68
93
|
};
|
69
94
|
|
@@ -193,12 +218,15 @@ function authorizeService(apiKeyMetadata, serviceConfig, authorizationPayload) {
|
|
193
218
|
}
|
194
219
|
return {
|
195
220
|
authorized: true,
|
221
|
+
apiKeyMeta: apiKeyMetadata,
|
196
222
|
accountMeta: {
|
197
223
|
id: apiKeyMetadata.accountId,
|
198
224
|
name: "",
|
199
|
-
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress
|
200
|
-
|
201
|
-
|
225
|
+
creatorWalletAddress: apiKeyMetadata.creatorWalletAddress,
|
226
|
+
limits: apiKeyMetadata.limits,
|
227
|
+
rateLimits: apiKeyMetadata.rateLimits,
|
228
|
+
usage: apiKeyMetadata.usage
|
229
|
+
}
|
202
230
|
};
|
203
231
|
}
|
204
232
|
|
@@ -236,9 +264,9 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
236
264
|
// if the difference is greater than the cacheTtl we want to ignore the cached data
|
237
265
|
const now = Date.now();
|
238
266
|
const diff = now - parsed.updatedAt;
|
239
|
-
const
|
267
|
+
const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
|
240
268
|
// only if the diff is less than the cacheTtl do we want to use the cached key
|
241
|
-
if (diff <
|
269
|
+
if (diff < cacheTtlMs) {
|
242
270
|
accountMeta = parsed.apiKeyMeta;
|
243
271
|
}
|
244
272
|
} else {
|
@@ -322,9 +350,9 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
322
350
|
// if the difference is greater than the cacheTtl we want to ignore the cached data
|
323
351
|
const now = Date.now();
|
324
352
|
const diff = now - parsed.updatedAt;
|
325
|
-
const
|
353
|
+
const cacheTtlMs = cacheOptions.cacheTtlSeconds * 1000;
|
326
354
|
// only if the diff is less than the cacheTtl do we want to use the cached key
|
327
|
-
if (diff <
|
355
|
+
if (diff < cacheTtlMs) {
|
328
356
|
apiKeyMeta = parsed.apiKeyMeta;
|
329
357
|
}
|
330
358
|
} else {
|
@@ -416,9 +444,133 @@ async function authorize(authData, serviceConfig, cacheOptions) {
|
|
416
444
|
id: apiKeyMeta.accountId,
|
417
445
|
// TODO update this later
|
418
446
|
name: "",
|
447
|
+
limits: apiKeyMeta.limits,
|
448
|
+
rateLimits: apiKeyMeta.rateLimits,
|
449
|
+
usage: apiKeyMeta.usage,
|
419
450
|
creatorWalletAddress: apiKeyMeta.creatorWalletAddress
|
420
451
|
}
|
421
452
|
};
|
422
453
|
}
|
423
454
|
|
455
|
+
const RATE_LIMIT_WINDOW_SECONDS = 10;
|
456
|
+
|
457
|
+
// Redis interface compatible with ioredis (Node) and upstash (Cloudflare Workers).
|
458
|
+
|
459
|
+
async function rateLimit(args) {
|
460
|
+
const {
|
461
|
+
authzResult,
|
462
|
+
serviceConfig,
|
463
|
+
redis,
|
464
|
+
sampleRate = 1.0
|
465
|
+
} = args;
|
466
|
+
const shouldSampleRequest = Math.random() < sampleRate;
|
467
|
+
if (!shouldSampleRequest || !authzResult.authorized) {
|
468
|
+
return {
|
469
|
+
rateLimited: false,
|
470
|
+
requestCount: 0,
|
471
|
+
rateLimit: 0
|
472
|
+
};
|
473
|
+
}
|
474
|
+
const {
|
475
|
+
apiKeyMeta,
|
476
|
+
accountMeta
|
477
|
+
} = authzResult;
|
478
|
+
const accountId = apiKeyMeta?.accountId || accountMeta?.id;
|
479
|
+
const {
|
480
|
+
serviceScope
|
481
|
+
} = serviceConfig;
|
482
|
+
const limitPerSecond = apiKeyMeta?.rateLimits?.[serviceScope] ?? accountMeta?.rateLimits?.[serviceScope];
|
483
|
+
if (!limitPerSecond) {
|
484
|
+
// No rate limit is provided. Assume the request is not rate limited.
|
485
|
+
return {
|
486
|
+
rateLimited: false,
|
487
|
+
requestCount: 0,
|
488
|
+
rateLimit: 0
|
489
|
+
};
|
490
|
+
}
|
491
|
+
|
492
|
+
// Gets the 10-second window for the current timestamp.
|
493
|
+
const timestampWindow = Math.floor(Date.now() / (1000 * RATE_LIMIT_WINDOW_SECONDS)) * RATE_LIMIT_WINDOW_SECONDS;
|
494
|
+
const key = `rate-limit:${serviceScope}:${accountId}:${timestampWindow}`;
|
495
|
+
|
496
|
+
// Increment and get the current request count in this window.
|
497
|
+
const requestCount = await redis.incr(key);
|
498
|
+
if (requestCount === 1) {
|
499
|
+
// For the first increment, set an expiration to clean up this key.
|
500
|
+
await redis.expire(key, RATE_LIMIT_WINDOW_SECONDS);
|
501
|
+
}
|
502
|
+
|
503
|
+
// Get the limit for this window accounting for the sample rate.
|
504
|
+
const limitPerWindow = limitPerSecond * sampleRate * RATE_LIMIT_WINDOW_SECONDS;
|
505
|
+
if (requestCount > limitPerWindow) {
|
506
|
+
// Report rate limit hits.
|
507
|
+
if (apiKeyMeta?.id) {
|
508
|
+
await updateRateLimitedAt(apiKeyMeta.id, serviceConfig);
|
509
|
+
}
|
510
|
+
|
511
|
+
// Reject requests when they've exceeded 2x the rate limit.
|
512
|
+
if (requestCount > 2 * limitPerWindow) {
|
513
|
+
return {
|
514
|
+
rateLimited: true,
|
515
|
+
requestCount,
|
516
|
+
rateLimit: limitPerWindow,
|
517
|
+
status: 429,
|
518
|
+
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.`,
|
519
|
+
errorCode: "RATE_LIMIT_EXCEEDED"
|
520
|
+
};
|
521
|
+
}
|
522
|
+
}
|
523
|
+
return {
|
524
|
+
rateLimited: false,
|
525
|
+
requestCount,
|
526
|
+
rateLimit: limitPerWindow
|
527
|
+
};
|
528
|
+
}
|
529
|
+
|
530
|
+
async function usageLimit(authzResult, serviceConfig) {
|
531
|
+
if (!authzResult.authorized) {
|
532
|
+
return {
|
533
|
+
usageLimited: false
|
534
|
+
};
|
535
|
+
}
|
536
|
+
const {
|
537
|
+
apiKeyMeta,
|
538
|
+
accountMeta
|
539
|
+
} = authzResult;
|
540
|
+
const {
|
541
|
+
limits,
|
542
|
+
usage
|
543
|
+
} = apiKeyMeta || accountMeta || {};
|
544
|
+
const {
|
545
|
+
serviceScope
|
546
|
+
} = serviceConfig;
|
547
|
+
if (!usage || !(serviceScope in usage) || !limits || !(serviceScope in limits)) {
|
548
|
+
// No usage limit is provided. Assume the request is not limited.
|
549
|
+
return {
|
550
|
+
usageLimited: false
|
551
|
+
};
|
552
|
+
}
|
553
|
+
if (serviceScope === "storage" && (usage.storage?.sumFileSizeBytes ?? 0) > (limits.storage ?? 0)) {
|
554
|
+
return {
|
555
|
+
usageLimited: true,
|
556
|
+
status: 403,
|
557
|
+
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.`,
|
558
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
559
|
+
};
|
560
|
+
}
|
561
|
+
if (serviceScope === "embeddedWallets" && (usage.embeddedWallets?.countWalletAddresses ?? 0) > (limits.embeddedWallets ?? 0)) {
|
562
|
+
return {
|
563
|
+
usageLimited: true,
|
564
|
+
status: 403,
|
565
|
+
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.`,
|
566
|
+
errorCode: "PAYMENT_METHOD_REQUIRED"
|
567
|
+
};
|
568
|
+
}
|
569
|
+
return {
|
570
|
+
usageLimited: false
|
571
|
+
};
|
572
|
+
}
|
573
|
+
|
424
574
|
exports.authorize = authorize;
|
575
|
+
exports.rateLimit = rateLimit;
|
576
|
+
exports.usageLimit = usageLimit;
|
@@ -33,6 +33,27 @@ const SERVICE_DEFINITIONS = {
|
|
33
33
|
description: "Enable gasless transactions",
|
34
34
|
// all actions allowed
|
35
35
|
actions: []
|
36
|
+
},
|
37
|
+
embeddedWallets: {
|
38
|
+
name: "embeddedWallets",
|
39
|
+
title: "Embedded Wallets",
|
40
|
+
description: "E-mail and social login wallets for easy web3 onboarding",
|
41
|
+
// all actions allowed
|
42
|
+
actions: []
|
43
|
+
},
|
44
|
+
checkout: {
|
45
|
+
name: "checkout",
|
46
|
+
title: "Checkouts",
|
47
|
+
description: "NFT Checkouts for easy web3 onboarding",
|
48
|
+
// all actions allowed
|
49
|
+
actions: []
|
50
|
+
},
|
51
|
+
pay: {
|
52
|
+
name: "pay",
|
53
|
+
title: "Pay",
|
54
|
+
description: "Pay for a blockchain transaction with any currency",
|
55
|
+
// all actions allowed
|
56
|
+
actions: []
|
36
57
|
}
|
37
58
|
};
|
38
59
|
const SERVICE_NAMES = Object.keys(SERVICE_DEFINITIONS);
|
@@ -35,6 +35,27 @@ const SERVICE_DEFINITIONS = {
|
|
35
35
|
description: "Enable gasless transactions",
|
36
36
|
// all actions allowed
|
37
37
|
actions: []
|
38
|
+
},
|
39
|
+
embeddedWallets: {
|
40
|
+
name: "embeddedWallets",
|
41
|
+
title: "Embedded Wallets",
|
42
|
+
description: "E-mail and social login wallets for easy web3 onboarding",
|
43
|
+
// all actions allowed
|
44
|
+
actions: []
|
45
|
+
},
|
46
|
+
checkout: {
|
47
|
+
name: "checkout",
|
48
|
+
title: "Checkouts",
|
49
|
+
description: "NFT Checkouts for easy web3 onboarding",
|
50
|
+
// all actions allowed
|
51
|
+
actions: []
|
52
|
+
},
|
53
|
+
pay: {
|
54
|
+
name: "pay",
|
55
|
+
title: "Pay",
|
56
|
+
description: "Pay for a blockchain transaction with any currency",
|
57
|
+
// all actions allowed
|
58
|
+
actions: []
|
38
59
|
}
|
39
60
|
};
|
40
61
|
const SERVICE_NAMES = Object.keys(SERVICE_DEFINITIONS);
|
@@ -35,6 +35,27 @@ const SERVICE_DEFINITIONS = {
|
|
35
35
|
description: "Enable gasless transactions",
|
36
36
|
// all actions allowed
|
37
37
|
actions: []
|
38
|
+
},
|
39
|
+
embeddedWallets: {
|
40
|
+
name: "embeddedWallets",
|
41
|
+
title: "Embedded Wallets",
|
42
|
+
description: "E-mail and social login wallets for easy web3 onboarding",
|
43
|
+
// all actions allowed
|
44
|
+
actions: []
|
45
|
+
},
|
46
|
+
checkout: {
|
47
|
+
name: "checkout",
|
48
|
+
title: "Checkouts",
|
49
|
+
description: "NFT Checkouts for easy web3 onboarding",
|
50
|
+
// all actions allowed
|
51
|
+
actions: []
|
52
|
+
},
|
53
|
+
pay: {
|
54
|
+
name: "pay",
|
55
|
+
title: "Pay",
|
56
|
+
description: "Pay for a blockchain transaction with any currency",
|
57
|
+
// all actions allowed
|
58
|
+
actions: []
|
38
59
|
}
|
39
60
|
};
|
40
61
|
const SERVICE_NAMES = Object.keys(SERVICE_DEFINITIONS);
|
@@ -1 +1 @@
|
|
1
|
-
export { b as SERVICES, S as SERVICE_DEFINITIONS, a as SERVICE_NAMES, g as getServiceByName } from './services-
|
1
|
+
export { b as SERVICES, S as SERVICE_DEFINITIONS, a as SERVICE_NAMES, g as getServiceByName } from './services-2aecbda8.esm.js';
|