mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
Require dashboard session cookies on protected management APIs and reject bearer API keys with explicit 403 responses to prevent privilege escalation across provider, settings, and model alias routes. Add a dedicated payload rules management surface with dashboard UI, OpenAPI documentation, route normalization, and tests for hot-reloaded runtime updates. Consolidate provider catalog metadata for dashboard pages, add Perplexity web-cookie provider support, retire the legacy provider creation page, and improve upstream proxy handling. Harden startup and runtime behavior by moving cloud sync bootstrap to server instrumentation, skipping background services during build/test, making models.dev sync abortable, pruning isolated build artifacts, and improving DB backup and recovery safeguards.
136 lines
3.3 KiB
TypeScript
136 lines
3.3 KiB
TypeScript
import { getConsistentMachineId } from "@/shared/utils/machineId";
|
|
import { isCloudEnabled } from "@/lib/localDb";
|
|
import { getRuntimePorts } from "@/lib/runtime/ports";
|
|
|
|
const { dashboardPort } = getRuntimePorts();
|
|
|
|
const INTERNAL_BASE_URL =
|
|
process.env.BASE_URL ||
|
|
process.env.NEXT_PUBLIC_BASE_URL ||
|
|
process.env.NEXT_PUBLIC_APP_URL ||
|
|
`http://localhost:${dashboardPort}`;
|
|
|
|
/**
|
|
* Cloud sync scheduler
|
|
*/
|
|
export class CloudSyncScheduler {
|
|
machineId: string | null;
|
|
intervalMinutes: number;
|
|
intervalId: ReturnType<typeof setInterval> | null;
|
|
|
|
constructor(machineId: string | null = null, intervalMinutes = 15) {
|
|
this.machineId = machineId;
|
|
this.intervalMinutes = intervalMinutes;
|
|
this.intervalId = null;
|
|
}
|
|
|
|
/**
|
|
* Initialize machine ID if not provided
|
|
*/
|
|
async initializeMachineId() {
|
|
if (!this.machineId) {
|
|
this.machineId = await getConsistentMachineId();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start periodic sync (delays first sync to allow server to be ready)
|
|
*/
|
|
async start() {
|
|
if (this.intervalId) {
|
|
return;
|
|
}
|
|
|
|
await this.initializeMachineId();
|
|
|
|
// Delay first sync by 30 seconds to ensure server is ready
|
|
const startupTimer = setTimeout(() => {
|
|
this.syncWithRetry().catch(() => {});
|
|
}, 30000);
|
|
startupTimer.unref?.();
|
|
|
|
// Then sync periodically
|
|
this.intervalId = setInterval(
|
|
() => {
|
|
this.syncWithRetry().catch(() => {});
|
|
},
|
|
this.intervalMinutes * 60 * 1000
|
|
);
|
|
this.intervalId.unref?.();
|
|
}
|
|
|
|
/**
|
|
* Stop periodic sync
|
|
*/
|
|
stop() {
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sync with retry logic (exponential backoff)
|
|
*/
|
|
async syncWithRetry(maxRetries = 1) {
|
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
try {
|
|
const result = await this.sync();
|
|
return result;
|
|
} catch (error) {
|
|
if (attempt === maxRetries) {
|
|
return null;
|
|
}
|
|
|
|
const delay = Math.min(1000 * Math.pow(2, attempt), 10000); // Max 10s
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform sync via internal API route (handles token update to db.json)
|
|
*/
|
|
async sync() {
|
|
// Check if cloud is enabled
|
|
const enabled = await isCloudEnabled();
|
|
if (!enabled) {
|
|
return null;
|
|
}
|
|
|
|
await this.initializeMachineId();
|
|
|
|
// Call internal API route which handles both sync and token update
|
|
const response = await fetch(`${INTERNAL_BASE_URL}/api/sync/cloud`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ machineId: this.machineId, action: "sync" }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
throw new Error(errorData.error || "Sync failed");
|
|
}
|
|
|
|
const result = await response.json();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Check if scheduler is running
|
|
*/
|
|
isRunning() {
|
|
return this.intervalId !== null;
|
|
}
|
|
}
|
|
|
|
// Export a singleton instance if needed
|
|
let cloudSyncScheduler: CloudSyncScheduler | null = null;
|
|
|
|
export async function getCloudSyncScheduler(machineId: string | null = null, intervalMinutes = 15) {
|
|
if (!cloudSyncScheduler) {
|
|
cloudSyncScheduler = new CloudSyncScheduler(machineId, intervalMinutes);
|
|
}
|
|
return cloudSyncScheduler;
|
|
}
|