ayiin 2.0.33 → 2.0.35
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/{index.js → dist/index.js} +1 -2
- package/{methods → dist/methods}/index.d.ts +3 -2
- package/{methods → dist/methods}/index.js +3 -1
- package/dist/methods/limiter.d.ts +6 -0
- package/dist/methods/limiter.js +84 -0
- package/{methods → dist/methods}/tools.d.ts +3 -0
- package/{methods → dist/methods}/tools.js +14 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.js +2 -0
- package/package.json +4 -3
- /package/{bin → dist/bin}/cli.d.ts +0 -0
- /package/{bin → dist/bin}/cli.js +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{methods → dist/methods}/crypt.d.ts +0 -0
- /package/{methods → dist/methods}/crypt.js +0 -0
- /package/{methods → dist/methods}/response.d.ts +0 -0
- /package/{methods → dist/methods}/response.js +0 -0
|
@@ -3,8 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
|
|
7
|
-
const package_json_1 = __importDefault(require("./package.json"));
|
|
6
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
8
7
|
const methods_1 = __importDefault(require("./methods"));
|
|
9
8
|
class Ayiin extends methods_1.default {
|
|
10
9
|
version = package_json_1.default.version;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import Crypt from "./crypt";
|
|
2
|
-
import
|
|
2
|
+
import { Limiter } from "./limiter";
|
|
3
3
|
import ResponseApi from "./response";
|
|
4
|
+
import Tools from "./tools";
|
|
4
5
|
declare class Methods {
|
|
5
6
|
}
|
|
6
|
-
interface Methods extends Crypt, Tools, ResponseApi {
|
|
7
|
+
interface Methods extends Crypt, Tools, ResponseApi, Limiter {
|
|
7
8
|
}
|
|
8
9
|
export default Methods;
|
|
@@ -4,12 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const crypt_1 = __importDefault(require("./crypt"));
|
|
7
|
-
const
|
|
7
|
+
const limiter_1 = require("./limiter");
|
|
8
8
|
const response_1 = __importDefault(require("./response"));
|
|
9
|
+
const tools_1 = __importDefault(require("./tools"));
|
|
9
10
|
class Methods {
|
|
10
11
|
}
|
|
11
12
|
// copy instance properties
|
|
12
13
|
Object.assign(Methods.prototype, new tools_1.default());
|
|
13
14
|
Object.assign(Methods.prototype, new crypt_1.default());
|
|
14
15
|
Object.assign(Methods.prototype, new response_1.default());
|
|
16
|
+
Object.assign(Methods.prototype, new limiter_1.Limiter());
|
|
15
17
|
exports.default = Methods;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Limiter = void 0;
|
|
7
|
+
const tools_1 = __importDefault(require("./tools"));
|
|
8
|
+
// 🔹 MemoryStore default
|
|
9
|
+
class MemoryStore {
|
|
10
|
+
store = new Map();
|
|
11
|
+
async get(key) {
|
|
12
|
+
const entry = this.store.get(key);
|
|
13
|
+
if (!entry)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (entry.expireAt && Date.now() > entry.expireAt) {
|
|
16
|
+
this.store.delete(key);
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return entry.value;
|
|
20
|
+
}
|
|
21
|
+
async set(key, value, ttlMs) {
|
|
22
|
+
this.store.set(key, {
|
|
23
|
+
value,
|
|
24
|
+
expireAt: ttlMs ? Date.now() + ttlMs : undefined,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async incr(key) {
|
|
28
|
+
const entry = this.store.get(key);
|
|
29
|
+
if (!entry) {
|
|
30
|
+
this.store.set(key, { value: "1", hits: 1 });
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
entry.hits = (entry.hits ?? parseInt(entry.value)) + 1;
|
|
34
|
+
entry.value = entry.hits.toString();
|
|
35
|
+
return entry.hits;
|
|
36
|
+
}
|
|
37
|
+
async expire(key, ttlMs) {
|
|
38
|
+
const entry = this.store.get(key);
|
|
39
|
+
if (entry) {
|
|
40
|
+
entry.expireAt = Date.now() + ttlMs;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// 🔹 Class Limiter
|
|
45
|
+
class Limiter extends tools_1.default {
|
|
46
|
+
createRateLimit = async (options) => {
|
|
47
|
+
const store = options.store ?? new MemoryStore(); // default ke MemoryStore
|
|
48
|
+
return async (req, res, next) => {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
const userId = options.identifyUser
|
|
51
|
+
? options.identifyUser(req, res)
|
|
52
|
+
: (req.ip ?? "unknown");
|
|
53
|
+
const floodKey = `${options.keyPrefix}:flood:${userId}`;
|
|
54
|
+
const blockedUntilStr = await store.get(floodKey);
|
|
55
|
+
if (blockedUntilStr) {
|
|
56
|
+
const blockedUntil = new Date(blockedUntilStr).getTime();
|
|
57
|
+
const remainingMs = blockedUntil - now;
|
|
58
|
+
if (remainingMs > 0) {
|
|
59
|
+
return res.status(429).json({
|
|
60
|
+
success: false,
|
|
61
|
+
error: `Floodwait aktif untuk ${options.keyPrefix}. Tunggu ${this.formatRemainingTime(remainingMs)}.`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const rateKey = `${options.keyPrefix}:rate:${userId}`;
|
|
66
|
+
const hits = await store.incr(rateKey);
|
|
67
|
+
const windowMs = this.toMilliseconds(options.windowMs);
|
|
68
|
+
const floodwaitMs = this.toMilliseconds(options.floodwaitMs);
|
|
69
|
+
if (hits === 1) {
|
|
70
|
+
await store.expire(rateKey, windowMs);
|
|
71
|
+
}
|
|
72
|
+
if (hits > options.limit) {
|
|
73
|
+
const untilDate = new Date(now + floodwaitMs).toISOString();
|
|
74
|
+
await store.set(floodKey, untilDate, floodwaitMs);
|
|
75
|
+
return res.status(429).json({
|
|
76
|
+
success: false,
|
|
77
|
+
error: `Terlalu banyak request ${options.keyPrefix}. Floodwait ${this.formatRemainingTime(floodwaitMs)}.`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
next();
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
exports.Limiter = Limiter;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DurationInput } from "../types";
|
|
1
2
|
declare class Tools {
|
|
2
3
|
/**
|
|
3
4
|
* Format money ke string sesuai locale
|
|
@@ -80,5 +81,7 @@ declare class Tools {
|
|
|
80
81
|
isEmptyObject: (obj: object) => boolean;
|
|
81
82
|
sleep: (ms: number) => Promise<void>;
|
|
82
83
|
retry: <T>(fn: () => Promise<T>, retries?: number) => Promise<T>;
|
|
84
|
+
toMilliseconds: ({ hours, minutes, seconds, }: DurationInput) => number;
|
|
85
|
+
formatRemainingTime: (ms: number) => string;
|
|
83
86
|
}
|
|
84
87
|
export default Tools;
|
|
@@ -174,5 +174,19 @@ class Tools {
|
|
|
174
174
|
}
|
|
175
175
|
throw error;
|
|
176
176
|
};
|
|
177
|
+
toMilliseconds = ({ hours = 0, minutes = 0, seconds = 0, }) => {
|
|
178
|
+
return (hours * 3600 + minutes * 60 + seconds) * 1000;
|
|
179
|
+
};
|
|
180
|
+
formatRemainingTime = (ms) => {
|
|
181
|
+
if (ms <= 0)
|
|
182
|
+
return "0 detik";
|
|
183
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
184
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
185
|
+
const seconds = totalSeconds % 60;
|
|
186
|
+
if (minutes > 0) {
|
|
187
|
+
return `${minutes} menit ${seconds} detik lagi`;
|
|
188
|
+
}
|
|
189
|
+
return `${seconds} detik lagi`;
|
|
190
|
+
};
|
|
177
191
|
}
|
|
178
192
|
exports.default = Tools;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Request, Response } from "express";
|
|
2
|
+
export interface DurationInput {
|
|
3
|
+
hours?: number;
|
|
4
|
+
minutes?: number;
|
|
5
|
+
seconds?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DurationInput {
|
|
8
|
+
hours?: number;
|
|
9
|
+
minutes?: number;
|
|
10
|
+
seconds?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface RateLimiterOptions {
|
|
13
|
+
keyPrefix: string;
|
|
14
|
+
limit: number;
|
|
15
|
+
windowMs: DurationInput;
|
|
16
|
+
floodwaitMs: DurationInput;
|
|
17
|
+
identifyUser?: (req: Request, res: Response) => string;
|
|
18
|
+
store?: RateLimitStore;
|
|
19
|
+
}
|
|
20
|
+
export interface RateLimitStore {
|
|
21
|
+
get(key: string): Promise<string | undefined>;
|
|
22
|
+
set(key: string, value: string, ttlMs?: number): Promise<void>;
|
|
23
|
+
incr(key: string): Promise<number>;
|
|
24
|
+
expire(key: string, ttlMs: number): Promise<void>;
|
|
25
|
+
}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ayiin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.35",
|
|
4
4
|
"description": "Library Pribadi Yang Gak Ada Isinya",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"axd": "./bin/cli.js"
|
|
7
|
+
"axd": "./dist/bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc"
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"license": "GPL-3.0-only",
|
|
24
24
|
"devDependencies": {
|
|
25
|
+
"@types/express": "^5.0.6",
|
|
25
26
|
"@types/node": "^26.3.0",
|
|
26
27
|
"typescript": "^7"
|
|
27
28
|
}
|
|
File without changes
|
/package/{bin → dist/bin}/cli.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|