fix: proxy logic bugs and Docker build failure

- URL-encode proxy credentials to handle special characters in passwords
- Decode URL-encoded credentials during legacy proxy migration
- Fix HTTPS proxy default port (443 instead of 8080) in frontend and migration
- Add dispatcher cache invalidation when proxy config changes
- Cast proxy port to number for SQLite INTEGER column in proxy logger
- Fix redundant .replace("//", "") in migration protocol parsing
- Copy postinstall script in Dockerfile before npm install

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
duongvdo
2026-03-01 00:48:27 +07:00
parent e4d83e91bb
commit 0e207dc5d2
6 changed files with 26 additions and 9 deletions
+1
View File
@@ -2,6 +2,7 @@ FROM node:22-bookworm-slim AS builder
WORKDIR /app
COPY package*.json ./
COPY scripts/postinstall.mjs ./scripts/postinstall.mjs
RUN if [ -f package-lock.json ]; then npm ci --no-audit --no-fund; else npm install --no-audit --no-fund; fi
COPY . ./
+12 -3
View File
@@ -4,13 +4,22 @@ import { socksDispatcher } from "fetch-socks";
const DISPATCHER_CACHE_KEY = Symbol.for("omniroute.proxyDispatcher.cache");
const SUPPORTED_PROTOCOLS = new Set(["http:", "https:", "socks5:"]);
function getDispatcherCache() {
function getDispatcherCache(): Map<string, any> {
if (!globalThis[DISPATCHER_CACHE_KEY]) {
globalThis[DISPATCHER_CACHE_KEY] = new Map();
}
return globalThis[DISPATCHER_CACHE_KEY];
}
/**
* Clear all cached proxy dispatchers.
* Call this when proxy configuration changes to avoid stale connections.
*/
export function clearDispatcherCache() {
const cache = getDispatcherCache();
cache.clear();
}
function defaultPortForProtocol(protocol) {
if (protocol === "https:" || protocol === "wss:") return "443";
if (protocol === "socks5:") return "1080";
@@ -96,8 +105,8 @@ export function proxyConfigToUrl(proxyConfig, { allowSocks5 = isSocks5ProxyEnabl
const proxyUrl = new URL(`${type}://${proxyConfig.host}:${port}`);
if (proxyConfig.username) {
proxyUrl.username = proxyConfig.username;
proxyUrl.password = proxyConfig.password || "";
proxyUrl.username = encodeURIComponent(proxyConfig.username);
proxyUrl.password = proxyConfig.password ? encodeURIComponent(proxyConfig.password) : "";
}
return normalizeProxyUrl(proxyUrl.toString(), "context proxy", { allowSocks5 });
+3
View File
@@ -5,6 +5,7 @@ import {
deleteProxyForLevel,
resolveProxyForConnection,
} from "../../../../lib/localDb";
import { clearDispatcherCache } from "@omniroute/open-sse/utils/proxyDispatcher";
const BASE_SUPPORTED_PROXY_TYPES = new Set(["http", "https"]);
@@ -129,6 +130,7 @@ export async function PUT(request) {
const body = await request.json();
const normalizedBody = normalizeProxyPayload(body);
const updated = await setProxyConfig(normalizedBody);
clearDispatcherCache();
return Response.json(updated);
} catch (error) {
const status = Number(error?.status) || 500;
@@ -155,6 +157,7 @@ export async function DELETE(request) {
}
const updated = await deleteProxyForLevel(level, id);
clearDispatcherCache();
return Response.json(updated);
} catch (error) {
return Response.json(
+4 -4
View File
@@ -211,11 +211,11 @@ function migrateProxyEntry(value: any) {
try {
const url = new URL(value);
return {
type: url.protocol.replace(":", "").replace("//", "") || "http",
type: url.protocol.replace(":", "") || "http",
host: url.hostname,
port: url.port || (url.protocol === "socks5:" ? "1080" : "8080"),
username: url.username || "",
password: url.password || "",
port: url.port || (url.protocol === "socks5:" ? "1080" : url.protocol === "https:" ? "443" : "8080"),
username: url.username ? decodeURIComponent(url.username) : "",
password: url.password ? decodeURIComponent(url.password) : "",
};
} catch {
const parts = value.split(":");
+1 -1
View File
@@ -134,7 +134,7 @@ export function logProxyEvent(entry: Partial<ProxyLogEntry>) {
status: log.status,
proxyType: log.proxy?.type || null,
proxyHost: log.proxy?.host || null,
proxyPort: log.proxy?.port || null,
proxyPort: log.proxy?.port ? Number(log.proxy.port) : null,
level: log.level,
levelId: log.levelId,
provider: log.provider,
+5 -1
View File
@@ -48,7 +48,11 @@ export default function ProxyConfigModal({ isOpen, onClose, level, levelId, leve
const [hasOwnProxy, setHasOwnProxy] = useState(false);
const [formError, setFormError] = useState(null);
const getDefaultPort = (type) => (type === "socks5" ? "1080" : "8080");
const getDefaultPort = (type) => {
if (type === "socks5") return "1080";
if (type === "https") return "443";
return "8080";
};
// Load existing proxy config when modal opens
useEffect(() => {