mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
137 lines
3.9 KiB
JavaScript
137 lines
3.9 KiB
JavaScript
import { getAdapter } from "../driver.js";
|
|
import { parseJson, stringifyJson } from "../helpers/jsonCol.js";
|
|
|
|
const DEFAULT_MITM_ROUTER_BASE = "http://localhost:20128";
|
|
const DEFAULT_HEADROOM_URL = process.env.HEADROOM_URL || "http://localhost:8787";
|
|
|
|
const DEFAULT_SETTINGS = {
|
|
cloudEnabled: false,
|
|
tunnelEnabled: false,
|
|
tunnelUrl: "",
|
|
tunnelProvider: "cloudflare",
|
|
tailscaleEnabled: false,
|
|
tailscaleUrl: "",
|
|
stickyRoundRobinLimit: 3,
|
|
providerStrategies: {},
|
|
comboStrategy: "fallback",
|
|
comboStickyRoundRobinLimit: 1,
|
|
comboStrategies: {},
|
|
requireLogin: true,
|
|
tunnelDashboardAccess: true,
|
|
authMode: "password",
|
|
oidcIssuerUrl: "",
|
|
oidcClientId: "",
|
|
oidcClientSecret: "",
|
|
oidcScopes: "openid profile email",
|
|
oidcLoginLabel: "Sign in with OIDC",
|
|
enableObservability: true,
|
|
observabilityMaxRecords: 1000,
|
|
observabilityBatchSize: 20,
|
|
observabilityFlushIntervalMs: 5000,
|
|
observabilityMaxJsonSize: 5,
|
|
outboundProxyEnabled: false,
|
|
outboundProxyUrl: "",
|
|
outboundNoProxy: "",
|
|
mitmRouterBaseUrl: DEFAULT_MITM_ROUTER_BASE,
|
|
dnsToolEnabled: {},
|
|
rtkEnabled: true,
|
|
headroomEnabled: false,
|
|
headroomUrl: DEFAULT_HEADROOM_URL,
|
|
headroomCompressUserMessages: false,
|
|
cavemanEnabled: false,
|
|
cavemanLevel: "full",
|
|
ponytailEnabled: false,
|
|
ponytailLevel: "full",
|
|
pxpipeEnabled: false,
|
|
pxpipeAutoInstall: true,
|
|
pxpipeMinChars: 25000,
|
|
pxpipeTimeoutMs: 15000,
|
|
};
|
|
|
|
async function readRaw() {
|
|
const db = await getAdapter();
|
|
const row = db.get(`SELECT data FROM settings WHERE id = 1`);
|
|
return row ? parseJson(row.data, {}) : {};
|
|
}
|
|
|
|
// Merge raw settings with defaults; backward-compat for missing keys
|
|
function mergeWithDefaults(raw) {
|
|
const merged = { ...DEFAULT_SETTINGS, ...(raw || {}) };
|
|
for (const [key, defVal] of Object.entries(DEFAULT_SETTINGS)) {
|
|
if (merged[key] === undefined) {
|
|
if (
|
|
key === "outboundProxyEnabled" &&
|
|
typeof merged.outboundProxyUrl === "string" &&
|
|
merged.outboundProxyUrl.trim()
|
|
) {
|
|
merged[key] = true;
|
|
} else {
|
|
merged[key] = defVal;
|
|
}
|
|
}
|
|
}
|
|
return merged;
|
|
}
|
|
|
|
export async function getSettings() {
|
|
const raw = await readRaw();
|
|
return mergeWithDefaults(raw);
|
|
}
|
|
|
|
// Atomic read-merge-write inside transaction (prevents losing concurrent updates)
|
|
export async function updateSettings(updates) {
|
|
const db = await getAdapter();
|
|
let next;
|
|
db.transaction(() => {
|
|
const row = db.get(`SELECT data FROM settings WHERE id = 1`);
|
|
const current = row ? parseJson(row.data, {}) : {};
|
|
next = { ...current, ...updates };
|
|
db.run(
|
|
`INSERT INTO settings(id, data) VALUES(1, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`,
|
|
[stringifyJson(next)]
|
|
);
|
|
});
|
|
return mergeWithDefaults(next);
|
|
}
|
|
|
|
/**
|
|
* Atomically update one combo's strategy without replacing other combos'
|
|
* entries in the shared settings document.
|
|
*/
|
|
export async function updateComboStrategy(comboId, strategy) {
|
|
const db = await getAdapter();
|
|
let next;
|
|
db.transaction(() => {
|
|
const row = db.get(`SELECT data FROM settings WHERE id = 1`);
|
|
const current = row ? parseJson(row.data, {}) : {};
|
|
const comboStrategies = { ...(current.comboStrategies || {}) };
|
|
if (!strategy || Object.keys(strategy).length === 0) delete comboStrategies[comboId];
|
|
else comboStrategies[comboId] = strategy;
|
|
next = { ...current, comboStrategies };
|
|
db.run(
|
|
`INSERT INTO settings(id, data) VALUES(1, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`,
|
|
[stringifyJson(next)]
|
|
);
|
|
});
|
|
return mergeWithDefaults(next);
|
|
}
|
|
|
|
export async function isCloudEnabled() {
|
|
const settings = await getSettings();
|
|
return settings.cloudEnabled === true;
|
|
}
|
|
|
|
export async function getCloudUrl() {
|
|
const settings = await getSettings();
|
|
return (
|
|
settings.cloudUrl ||
|
|
process.env.CLOUD_URL ||
|
|
process.env.NEXT_PUBLIC_CLOUD_URL ||
|
|
""
|
|
);
|
|
}
|
|
|
|
export async function exportSettings() {
|
|
return await readRaw();
|
|
}
|