spec(aiscript): Mk:apiの呼び出しにエンドポイントごとのレートリミットを設定 (MisskeyIO#522)
This commit is contained in:
parent
9d1cdadccc
commit
cd7ab5d0f9
@ -46,17 +46,17 @@
|
||||
"cleanall": "pnpm clean-all"
|
||||
},
|
||||
"resolutions": {
|
||||
"@tensorflow/tfjs-core": "4.17.0",
|
||||
"chokidar": "3.6.0",
|
||||
"lodash": "4.17.21",
|
||||
"sharp": "0.33.2",
|
||||
"@tensorflow/tfjs-core": "4.17.0"
|
||||
"sharp": "0.33.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "6.1.0",
|
||||
"execa": "8.0.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.35",
|
||||
"terser": "5.29.1",
|
||||
"terser": "5.29.2",
|
||||
"typescript": "5.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -66,8 +66,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@authenio/samlify-node-xmllint": "2.0.0",
|
||||
"@aws-sdk/client-s3": "3.533.0",
|
||||
"@aws-sdk/lib-storage": "3.533.0",
|
||||
"@aws-sdk/client-s3": "3.534.0",
|
||||
"@aws-sdk/lib-storage": "3.534.0",
|
||||
"@bull-board/api": "5.15.1",
|
||||
"@bull-board/fastify": "5.15.1",
|
||||
"@bull-board/ui": "5.15.1",
|
||||
@ -89,7 +89,7 @@
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@simplewebauthn/server": "9.0.3",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@smithy/node-http-handler": "2.4.3",
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
"@swc/cli": "0.1.65",
|
||||
"@swc/core": "1.3.107",
|
||||
"@twemoji/parser": "15.0.0",
|
||||
|
@ -19,6 +19,7 @@
|
||||
"dependencies": {
|
||||
"@discordapp/twemoji": "15.0.2",
|
||||
"@github/webauthn-json": "2.1.1",
|
||||
"@isaacs/ttlcache": "1.4.1",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@misskey-dev/browser-image-resizer": "2024.1.0",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
|
@ -11,6 +11,7 @@ import { miLocalStorage } from '@/local-storage.js';
|
||||
import { customEmojis } from '@/custom-emojis.js';
|
||||
import { url, lang } from '@/config.js';
|
||||
import { nyaize } from '@/scripts/nyaize.js';
|
||||
import { RateLimiter } from '@/scripts/rate-limiter.js';
|
||||
|
||||
export function aiScriptReadline(q: string): Promise<string> {
|
||||
return new Promise(ok => {
|
||||
@ -23,6 +24,8 @@ export function aiScriptReadline(q: string): Promise<string> {
|
||||
}
|
||||
|
||||
export function createAiScriptEnv(opts) {
|
||||
const rateLimiter = new RateLimiter<string>({ duration: 1000 * 15, max: 30 });
|
||||
|
||||
return {
|
||||
USER_ID: $i ? values.STR($i.id) : values.NULL,
|
||||
USER_NAME: $i ? values.STR($i.name) : values.NULL,
|
||||
@ -55,6 +58,7 @@ export function createAiScriptEnv(opts) {
|
||||
if (typeof token.value !== 'string') throw new Error('invalid token');
|
||||
}
|
||||
const actualToken: string|null = token?.value ?? opts.token ?? null;
|
||||
if (!rateLimiter.hit(ep.value)) return values.ERROR('rate_limited', values.NULL);
|
||||
return misskeyApi(ep.value, utils.valToJs(param), actualToken).then(res => {
|
||||
return utils.jsToVal(res);
|
||||
}, err => {
|
||||
|
71
packages/frontend/src/scripts/rate-limiter.ts
Normal file
71
packages/frontend/src/scripts/rate-limiter.ts
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: Isaac Z. Schlueter and Contributors of https://github.com/isaacs/ttlcache
|
||||
* SPDX-License-Identifier: ISC
|
||||
*
|
||||
* This file is derived from the project that has licensed under the ISC license.
|
||||
* This file SHOULD NOT be considered as a part of the this project that has licensed under AGPL-3.0-only
|
||||
* Adapted from https://github.com/isaacs/ttlcache/blob/b6002f971e122e3b35e23d00ac6a8365d505c14d/examples/rate-limiter-window.ts
|
||||
*/
|
||||
|
||||
import TTLCache from '@isaacs/ttlcache';
|
||||
import type { Options as TTLCacheOptions } from '@isaacs/ttlcache';
|
||||
|
||||
export interface Options {
|
||||
duration: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
interface RLEntryOptions extends TTLCacheOptions<number, boolean> {
|
||||
onEmpty: () => void;
|
||||
}
|
||||
|
||||
class RLEntry extends TTLCache<number, boolean> {
|
||||
onEmpty: () => void;
|
||||
|
||||
constructor(options: RLEntryOptions) {
|
||||
super(options);
|
||||
this.onEmpty = options.onEmpty;
|
||||
}
|
||||
|
||||
purgeStale() {
|
||||
const ret = super.purgeStale();
|
||||
if (this.size === 0 && ret) {
|
||||
this.onEmpty();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
export class RateLimiter<K> extends Map<K, TTLCache<number, boolean>> {
|
||||
duration: number;
|
||||
max: number;
|
||||
|
||||
constructor(options: Options) {
|
||||
super();
|
||||
this.duration = options.duration;
|
||||
this.max = options.max;
|
||||
}
|
||||
|
||||
hit(key: K) {
|
||||
const c =
|
||||
super.get(key) ??
|
||||
new RLEntry({
|
||||
ttl: this.duration,
|
||||
onEmpty: () => this.delete(key),
|
||||
});
|
||||
|
||||
this.set(key, c);
|
||||
|
||||
if (c.size > this.max) {
|
||||
// rejected, too many hits within window
|
||||
return false;
|
||||
}
|
||||
c.set(performance.now(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
count(key: K) {
|
||||
const c = super.get(key);
|
||||
return c ? c.size : 0;
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@
|
||||
"built"
|
||||
],
|
||||
"dependencies": {
|
||||
"esbuild": "0.20.1",
|
||||
"esbuild": "0.20.2",
|
||||
"eventemitter3": "5.0.1",
|
||||
"glob": "^10.3.10",
|
||||
"matter-js": "0.19.0",
|
||||
|
@ -34,7 +34,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"crc-32": "1.2.2",
|
||||
"esbuild": "0.20.1",
|
||||
"esbuild": "0.20.2",
|
||||
"glob": "10.3.10"
|
||||
},
|
||||
"files": [
|
||||
|
@ -9,7 +9,7 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"esbuild": "0.20.1",
|
||||
"esbuild": "0.20.2",
|
||||
"idb-keyval": "6.2.1",
|
||||
"misskey-js": "workspace:*"
|
||||
},
|
||||
|
1070
pnpm-lock.yaml
1070
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user