mirror of
https://github.com/Shineii86/MiruroAPI.git
synced 2026-10-22 10:15:10 +00:00
v1.4.0: Fix Vercel deployment, security headers, rate limiter, cache cleanup
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.4.0
|
||||||
|
- Fixed Vercel deployment: replaced legacy `builds`/`routes` config with modern `functions` config
|
||||||
|
- Added `api/index.js` serverless entry point (Vercel requires this for proper function detection)
|
||||||
|
- Security headers: removed deprecated `X-XSS-Protection`, added `Strict-Transport-Security` (HSTS)
|
||||||
|
- Rate limiter: uses `x-forwarded-for` header for correct IP detection on Vercel serverless
|
||||||
|
- Rate limiter: added periodic IP entry pruning when >200 IPs tracked (prevents memory leak)
|
||||||
|
- Cache: added periodic expired entry cleanup when cache reaches 80% capacity (prevents memory leak)
|
||||||
|
- Version synced to 1.4.0 across all files (server.js, package.json, health endpoint, OpenAPI spec)
|
||||||
|
|
||||||
## v1.3.0
|
## v1.3.0
|
||||||
- Full landing page upgrade to match AniKotoAPI style
|
- Full landing page upgrade to match AniKotoAPI style
|
||||||
- Added canvas particle effects with cyan-colored floating particles
|
- Added canvas particle effects with cyan-colored floating particles
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require("../server");
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "miruro-api",
|
"name": "miruro-api",
|
||||||
"version": "1.3.0",
|
"version": "1.4.0",
|
||||||
"description": "RESTful API for miruro.tv - Anime streaming data powered by AniList GraphQL and Miruro streaming providers",
|
"description": "RESTful API for miruro.tv - Anime streaming data powered by AniList GraphQL and Miruro streaming providers",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ app.use((req, res, next) => {
|
|||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
res.setHeader("X-Frame-Options", "DENY");
|
res.setHeader("X-Frame-Options", "DENY");
|
||||||
res.setHeader("X-Content-Type-Options", "nosniff");
|
res.setHeader("X-Content-Type-Options", "nosniff");
|
||||||
res.setHeader("X-XSS-Protection", "1; mode=block");
|
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
||||||
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
||||||
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
|
res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
|
||||||
next();
|
next();
|
||||||
@@ -128,7 +128,10 @@ const jsonError = (res, message = "Internal server error", status = 500) =>
|
|||||||
// ---- FEATURE: Rate limiting (100 requests per minute per IP) ----
|
// ---- FEATURE: Rate limiting (100 requests per minute per IP) ----
|
||||||
const requestCounts = new Map();
|
const requestCounts = new Map();
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
const ip = req.ip || req.connection.remoteAddress;
|
const ip =
|
||||||
|
req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
|
||||||
|
req.ip ||
|
||||||
|
req.connection.remoteAddress;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const windowMs = 60000;
|
const windowMs = 60000;
|
||||||
const maxRequests = 100;
|
const maxRequests = 100;
|
||||||
@@ -148,6 +151,16 @@ app.use((req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
timestamps.push(now);
|
timestamps.push(now);
|
||||||
|
|
||||||
|
// NOTE: Periodic cleanup — prune expired IP entries every 60s to prevent memory leak
|
||||||
|
if (requestCounts.size > 200) {
|
||||||
|
for (const [key, val] of requestCounts) {
|
||||||
|
if (val.filter((t) => now - t < windowMs).length === 0) {
|
||||||
|
requestCounts.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,15 @@ const setCache = (key, data, ttl = DEFAULT_TTL) => {
|
|||||||
cache.delete(firstKey);
|
cache.delete(firstKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: Periodic cleanup — prune all expired entries when cache reaches 80% capacity
|
||||||
|
// Prevents unbounded memory growth from stale entries that were never accessed
|
||||||
|
if (cache.size >= MAX_CACHE_SIZE * 0.8) {
|
||||||
|
const now = Date.now();
|
||||||
|
for (const [k, v] of cache) {
|
||||||
|
if (now > v.expiry) cache.delete(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cache.set(key, { data, expiry: Date.now() + ttl });
|
cache.set(key, { data, expiry: Date.now() + ttl });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ const createApiRoutes = (jsonResponse, jsonError) => {
|
|||||||
success: true,
|
success: true,
|
||||||
results: {
|
results: {
|
||||||
status: "healthy",
|
status: "healthy",
|
||||||
version: "1.2.0",
|
version: "1.4.0",
|
||||||
uptime: `${hours}h ${minutes}m ${seconds}s`,
|
uptime: `${hours}h ${minutes}m ${seconds}s`,
|
||||||
uptimeSeconds: uptime,
|
uptimeSeconds: uptime,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -404,7 +404,7 @@ const createApiRoutes = (jsonResponse, jsonError) => {
|
|||||||
info: {
|
info: {
|
||||||
title: "MiruroAPI",
|
title: "MiruroAPI",
|
||||||
description: "Free REST API for anime streaming data — AniList GraphQL + Miruro streaming providers",
|
description: "Free REST API for anime streaming data — AniList GraphQL + Miruro streaming providers",
|
||||||
version: "1.2.0",
|
version: "1.4.0",
|
||||||
contact: { name: "Shineii86", url: "https://github.com/Shineii86/MiruroAPI" },
|
contact: { name: "Shineii86", url: "https://github.com/Shineii86/MiruroAPI" },
|
||||||
},
|
},
|
||||||
servers: [
|
servers: [
|
||||||
|
|||||||
+5
-10
@@ -1,13 +1,8 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
"functions": {
|
||||||
"builds": [
|
"api/index.js": {
|
||||||
{
|
"includeFiles": "src/**",
|
||||||
"src": "server.js",
|
"maxDuration": 30
|
||||||
"use": "@vercel/node"
|
|
||||||
}
|
}
|
||||||
],
|
}
|
||||||
"routes": [
|
|
||||||
{ "src": "/api/(.*)", "dest": "server.js" },
|
|
||||||
{ "src": "/(.*)", "dest": "server.js" }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user