mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
refactor(api): implement caching for tunnel and version status endpoints
This commit is contained in:
@@ -1,11 +1,23 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { getTunnelStatus, getTailscaleStatus, getDownloadStatus } from "@/lib/tunnel";
|
import { getTunnelStatus, getTailscaleStatus, getDownloadStatus } from "@/lib/tunnel";
|
||||||
|
|
||||||
|
const STATUS_CACHE_TTL_MS = 3000; // coalesce rapid polls; underlying probes already cache 10s
|
||||||
|
|
||||||
|
// Survive hot reload; one cache per process. Only tunnel/tailscale probes are cached —
|
||||||
|
// download progress stays live so the enable/download UI updates smoothly.
|
||||||
|
const statusCache = (global.__tunnelStatusCache ??= { value: null, fetchedAt: 0 });
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const [tunnel, tailscale] = await Promise.all([getTunnelStatus(), getTailscaleStatus()]);
|
let probes = statusCache.value;
|
||||||
|
if (!probes || Date.now() - statusCache.fetchedAt >= STATUS_CACHE_TTL_MS) {
|
||||||
|
const [tunnel, tailscale] = await Promise.all([getTunnelStatus(), getTailscaleStatus()]);
|
||||||
|
probes = { tunnel, tailscale };
|
||||||
|
statusCache.value = probes;
|
||||||
|
statusCache.fetchedAt = Date.now();
|
||||||
|
}
|
||||||
const download = getDownloadStatus();
|
const download = getDownloadStatus();
|
||||||
return NextResponse.json({ tunnel, tailscale, download });
|
return NextResponse.json({ ...probes, download });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Tunnel status error:", error);
|
console.error("Tunnel status error:", error);
|
||||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import https from "https";
|
|||||||
import pkg from "../../../../package.json" with { type: "json" };
|
import pkg from "../../../../package.json" with { type: "json" };
|
||||||
|
|
||||||
const NPM_PACKAGE_NAME = "9router";
|
const NPM_PACKAGE_NAME = "9router";
|
||||||
|
const VERSION_CACHE_TTL_MS = 3600000; // cache npm latest lookup for 1h
|
||||||
|
|
||||||
|
// Survive hot reload; one cache per process
|
||||||
|
const versionCache = (global.__npmVersionCache ??= { value: null, fetchedAt: 0 });
|
||||||
|
|
||||||
// Fetch latest version from npm registry
|
// Fetch latest version from npm registry
|
||||||
function fetchLatestVersion() {
|
function fetchLatestVersion() {
|
||||||
@@ -36,8 +40,20 @@ function compareVersions(a, b) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getLatestVersionCached() {
|
||||||
|
if (versionCache.value && Date.now() - versionCache.fetchedAt < VERSION_CACHE_TTL_MS) {
|
||||||
|
return versionCache.value;
|
||||||
|
}
|
||||||
|
const latest = await fetchLatestVersion();
|
||||||
|
if (latest) {
|
||||||
|
versionCache.value = latest;
|
||||||
|
versionCache.fetchedAt = Date.now();
|
||||||
|
}
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const latestVersion = await fetchLatestVersion();
|
const latestVersion = await getLatestVersionCached();
|
||||||
const currentVersion = pkg.version;
|
const currentVersion = pkg.version;
|
||||||
const hasUpdate = latestVersion ? compareVersions(latestVersion, currentVersion) > 0 : false;
|
const hasUpdate = latestVersion ? compareVersions(latestVersion, currentVersion) > 0 : false;
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ import { syncToJson as syncMitmAliasCache } from "@/lib/mitmAliasCache";
|
|||||||
|
|
||||||
process.setMaxListeners(20);
|
process.setMaxListeners(20);
|
||||||
|
|
||||||
|
// Defer heavy startup work so the first HTTP request (login → dashboard) isn't
|
||||||
|
// starved by DB cleanup, cloudflared download, lsof/DNS probes and OAuth pings.
|
||||||
|
const STARTUP_DEFER_MS = 3000;
|
||||||
|
|
||||||
// Survive Next.js hot reload
|
// Survive Next.js hot reload
|
||||||
const g = global.__appSingleton ??= {
|
const g = global.__appSingleton ??= {
|
||||||
signalHandlersRegistered: false,
|
signalHandlersRegistered: false,
|
||||||
@@ -47,23 +51,8 @@ const g = global.__appSingleton ??= {
|
|||||||
|
|
||||||
export async function initializeApp() {
|
export async function initializeApp() {
|
||||||
try {
|
try {
|
||||||
await cleanupProviderConnections();
|
// Register cleanup + exit-respawn callback immediately so signals and
|
||||||
const settings = await getSettings();
|
// unexpected cloudflared exits are handled even during the deferred window.
|
||||||
|
|
||||||
// Auto-resume tunnel (once per process)
|
|
||||||
if (settings.tunnelEnabled && !g.tunnelAutoResumed) {
|
|
||||||
g.tunnelAutoResumed = true;
|
|
||||||
console.log("[InitApp] Tunnel was enabled, auto-resuming...");
|
|
||||||
safeRestartTunnel("startup").catch((e) => console.log("[InitApp] Tunnel resume failed:", e.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-resume tailscale (once per process)
|
|
||||||
if (settings.tailscaleEnabled && !g.tailscaleAutoResumed) {
|
|
||||||
g.tailscaleAutoResumed = true;
|
|
||||||
console.log("[InitApp] Tailscale was enabled, auto-resuming...");
|
|
||||||
safeRestartTailscale("startup").catch((e) => console.log("[InitApp] Tailscale resume failed:", e.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g.signalHandlersRegistered) {
|
if (!g.signalHandlersRegistered) {
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
try { removeAllDNSEntriesSync(); } catch { /* best effort */ }
|
try { removeAllDNSEntriesSync(); } catch { /* best effort */ }
|
||||||
@@ -76,25 +65,48 @@ export async function initializeApp() {
|
|||||||
g.signalHandlersRegistered = true;
|
g.signalHandlersRegistered = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureCloudflared().catch(() => {});
|
|
||||||
|
|
||||||
// Sync mitmAlias DB → JSON cache so standalone MITM server can read it
|
|
||||||
syncMitmAliasCache().catch(() => {});
|
|
||||||
|
|
||||||
// Auto-respawn tunnel when cloudflared exits unexpectedly (e.g. network change drop)
|
|
||||||
setTunnelUnexpectedExitCallback(() => {
|
setTunnelUnexpectedExitCallback(() => {
|
||||||
safeRestartTunnel("unexpected-exit").catch(() => {});
|
safeRestartTunnel("unexpected-exit").catch(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
startWatchdog();
|
// Defer the heavy work — nothing here blocks incoming requests.
|
||||||
startNetworkMonitor();
|
setTimeout(() => {
|
||||||
autoStartMitm();
|
runHeavyStartup().catch((e) => console.error("[InitApp] deferred startup failed:", e.message));
|
||||||
startQuotaAutoPing();
|
}, STARTUP_DEFER_MS);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[InitApp] Error:", error);
|
console.error("[InitApp] Error:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runHeavyStartup() {
|
||||||
|
await cleanupProviderConnections();
|
||||||
|
const settings = await getSettings();
|
||||||
|
|
||||||
|
// Auto-resume tunnel (once per process)
|
||||||
|
if (settings.tunnelEnabled && !g.tunnelAutoResumed) {
|
||||||
|
g.tunnelAutoResumed = true;
|
||||||
|
console.log("[InitApp] Tunnel was enabled, auto-resuming...");
|
||||||
|
safeRestartTunnel("startup").catch((e) => console.log("[InitApp] Tunnel resume failed:", e.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-resume tailscale (once per process)
|
||||||
|
if (settings.tailscaleEnabled && !g.tailscaleAutoResumed) {
|
||||||
|
g.tailscaleAutoResumed = true;
|
||||||
|
console.log("[InitApp] Tailscale was enabled, auto-resuming...");
|
||||||
|
safeRestartTailscale("startup").catch((e) => console.log("[InitApp] Tailscale resume failed:", e.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureCloudflared().catch(() => {});
|
||||||
|
|
||||||
|
// Sync mitmAlias DB → JSON cache so standalone MITM server can read it
|
||||||
|
syncMitmAliasCache().catch(() => {});
|
||||||
|
|
||||||
|
startWatchdog();
|
||||||
|
startNetworkMonitor();
|
||||||
|
autoStartMitm();
|
||||||
|
startQuotaAutoPing();
|
||||||
|
}
|
||||||
|
|
||||||
async function autoStartMitm() {
|
async function autoStartMitm() {
|
||||||
if (g.mitmStartInProgress) return;
|
if (g.mitmStartInProgress) return;
|
||||||
g.mitmStartInProgress = true;
|
g.mitmStartInProgress = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user