225 lines
8.1 KiB
JavaScript
Executable File
225 lines
8.1 KiB
JavaScript
Executable File
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __reExport = (target, module2, copyDefault, desc) => {
|
|
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
|
|
for (let key of __getOwnPropNames(module2))
|
|
if (!__hasOwnProp.call(target, key) && (copyDefault || key !== "default"))
|
|
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
|
|
}
|
|
return target;
|
|
};
|
|
var __toCommonJS = /* @__PURE__ */ ((cache) => {
|
|
return (module2, temp) => {
|
|
return cache && cache.get(module2) || (temp = __reExport(__markAsModule({}), module2, 1), cache && cache.set(module2, temp), temp);
|
|
};
|
|
})(typeof WeakMap !== "undefined" ? /* @__PURE__ */ new WeakMap() : 0);
|
|
|
|
// source/index.ts
|
|
var source_exports = {};
|
|
__export(source_exports, {
|
|
default: () => lib_default,
|
|
rateLimit: () => lib_default
|
|
});
|
|
|
|
// source/memory-store.ts
|
|
var calculateNextResetTime = (windowMs) => {
|
|
const resetTime = new Date();
|
|
resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
|
|
return resetTime;
|
|
};
|
|
var MemoryStore = class {
|
|
init(options) {
|
|
this.windowMs = options.windowMs;
|
|
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
this.hits = {};
|
|
const interval = setInterval(async () => {
|
|
await this.resetAll();
|
|
}, this.windowMs);
|
|
if (interval.unref) {
|
|
interval.unref();
|
|
}
|
|
}
|
|
async increment(key) {
|
|
const totalHits = (this.hits[key] ?? 0) + 1;
|
|
this.hits[key] = totalHits;
|
|
return {
|
|
totalHits,
|
|
resetTime: this.resetTime
|
|
};
|
|
}
|
|
async decrement(key) {
|
|
const current = this.hits[key];
|
|
if (current) {
|
|
this.hits[key] = current - 1;
|
|
}
|
|
}
|
|
async resetKey(key) {
|
|
delete this.hits[key];
|
|
}
|
|
async resetAll() {
|
|
this.hits = {};
|
|
this.resetTime = calculateNextResetTime(this.windowMs);
|
|
}
|
|
};
|
|
|
|
// source/lib.ts
|
|
var isLegacyStore = (store) => typeof store.incr === "function" && typeof store.increment !== "function";
|
|
var promisifyStore = (passedStore) => {
|
|
if (!isLegacyStore(passedStore)) {
|
|
return passedStore;
|
|
}
|
|
const legacyStore = passedStore;
|
|
class PromisifiedStore {
|
|
async increment(key) {
|
|
return new Promise((resolve, reject) => {
|
|
legacyStore.incr(key, (error, totalHits, resetTime) => {
|
|
if (error)
|
|
reject(error);
|
|
resolve({ totalHits, resetTime });
|
|
});
|
|
});
|
|
}
|
|
async decrement(key) {
|
|
return Promise.resolve(legacyStore.decrement(key));
|
|
}
|
|
async resetKey(key) {
|
|
return Promise.resolve(legacyStore.resetKey(key));
|
|
}
|
|
async resetAll() {
|
|
if (typeof legacyStore.resetAll === "function")
|
|
return Promise.resolve(legacyStore.resetAll());
|
|
}
|
|
}
|
|
return new PromisifiedStore();
|
|
};
|
|
var parseOptions = (passedOptions) => {
|
|
const options = {
|
|
windowMs: 60 * 1e3,
|
|
store: new MemoryStore(),
|
|
max: 5,
|
|
message: "Too many requests, please try again later.",
|
|
statusCode: 429,
|
|
legacyHeaders: passedOptions.headers ?? true,
|
|
standardHeaders: passedOptions.draft_polli_ratelimit_headers ?? false,
|
|
requestPropertyName: "rateLimit",
|
|
skipFailedRequests: false,
|
|
skipSuccessfulRequests: false,
|
|
requestWasSuccessful: (_request, response) => response.statusCode < 400,
|
|
skip: (_request, _response) => false,
|
|
keyGenerator: (request, _response) => {
|
|
if (!request.ip) {
|
|
console.error("WARN | `express-rate-limit` | `request.ip` is undefined. You can avoid this by providing a custom `keyGenerator` function, but it may be indicative of a larger issue.");
|
|
}
|
|
return request.ip;
|
|
},
|
|
handler: (_request, response, _next, _optionsUsed) => {
|
|
response.status(options.statusCode).send(options.message);
|
|
},
|
|
onLimitReached: (_request, _response, _optionsUsed) => {
|
|
},
|
|
...passedOptions
|
|
};
|
|
if (typeof options.store.incr !== "function" && typeof options.store.increment !== "function" || typeof options.store.decrement !== "function" || typeof options.store.resetKey !== "function" || typeof options.store.resetAll !== "undefined" && typeof options.store.resetAll !== "function" || typeof options.store.init !== "undefined" && typeof options.store.init !== "function") {
|
|
throw new TypeError("An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface.");
|
|
}
|
|
options.store = promisifyStore(options.store);
|
|
return options;
|
|
};
|
|
var handleAsyncErrors = (fn) => async (request, response, next) => {
|
|
try {
|
|
await Promise.resolve(fn(request, response, next)).catch(next);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
var rateLimit = (passedOptions) => {
|
|
const options = parseOptions(passedOptions ?? {});
|
|
if (typeof options.store.init === "function")
|
|
options.store.init(options);
|
|
const middleware = handleAsyncErrors(async (request, response, next) => {
|
|
const skip = await options.skip(request, response);
|
|
if (skip) {
|
|
next();
|
|
return;
|
|
}
|
|
const augmentedRequest = request;
|
|
const key = await options.keyGenerator(request, response);
|
|
const { totalHits, resetTime } = await options.store.increment(key);
|
|
const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
|
|
const maxHits = await retrieveQuota;
|
|
augmentedRequest[options.requestPropertyName] = {
|
|
limit: maxHits,
|
|
current: totalHits,
|
|
remaining: Math.max(maxHits - totalHits, 0),
|
|
resetTime
|
|
};
|
|
if (options.legacyHeaders && !response.headersSent) {
|
|
response.setHeader("X-RateLimit-Limit", maxHits);
|
|
response.setHeader("X-RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
|
|
if (resetTime instanceof Date) {
|
|
response.setHeader("Date", new Date().toUTCString());
|
|
response.setHeader("X-RateLimit-Reset", Math.ceil(resetTime.getTime() / 1e3));
|
|
}
|
|
}
|
|
if (options.standardHeaders && !response.headersSent) {
|
|
response.setHeader("RateLimit-Limit", maxHits);
|
|
response.setHeader("RateLimit-Remaining", augmentedRequest[options.requestPropertyName].remaining);
|
|
if (resetTime) {
|
|
const deltaSeconds = Math.ceil((resetTime.getTime() - Date.now()) / 1e3);
|
|
response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
|
|
}
|
|
}
|
|
if (options.skipFailedRequests || options.skipSuccessfulRequests) {
|
|
let decremented = false;
|
|
const decrementKey = async () => {
|
|
if (!decremented) {
|
|
await options.store.decrement(key);
|
|
decremented = true;
|
|
}
|
|
};
|
|
if (options.skipFailedRequests) {
|
|
response.on("finish", async () => {
|
|
if (!options.requestWasSuccessful(request, response))
|
|
await decrementKey();
|
|
});
|
|
response.on("close", async () => {
|
|
if (!response.writableEnded)
|
|
await decrementKey();
|
|
});
|
|
response.on("error", async () => {
|
|
await decrementKey();
|
|
});
|
|
}
|
|
if (options.skipSuccessfulRequests) {
|
|
response.on("finish", async () => {
|
|
if (options.requestWasSuccessful(request, response))
|
|
await decrementKey();
|
|
});
|
|
}
|
|
}
|
|
if (maxHits && totalHits === maxHits + 1) {
|
|
options.onLimitReached(request, response, options);
|
|
}
|
|
if (maxHits && totalHits > maxHits) {
|
|
if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
|
|
response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
|
|
}
|
|
options.handler(request, response, next, options);
|
|
return;
|
|
}
|
|
next();
|
|
});
|
|
middleware.resetKey = options.store.resetKey.bind(options.store);
|
|
return middleware;
|
|
};
|
|
var lib_default = rateLimit;
|
|
module.exports = __toCommonJS(source_exports);
|
|
module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit;
|