fosrl.pangolin/server/hybridServer.ts

154 lines
5.6 KiB
TypeScript
Raw Normal View History

2025-08-12 14:30:23 -07:00
import logger from "@server/logger";
import config from "@server/lib/config";
2025-08-13 17:30:59 -07:00
import { createWebSocketClient } from "./routers/ws/client";
2025-08-12 14:30:23 -07:00
import { addPeer, deletePeer } from "./routers/gerbil/peers";
import { db, exitNodes } from "./db";
2025-08-12 16:31:53 -07:00
import { TraefikConfigManager } from "./lib/remoteTraefikConfig";
2025-08-13 17:30:59 -07:00
import { tokenManager } from "./lib/tokenManager";
2025-08-13 20:27:48 -07:00
import { APP_VERSION } from "./lib/consts";
2025-08-14 15:39:05 -07:00
import axios from "axios";
2025-08-12 14:30:23 -07:00
export async function createHybridClientServer() {
2025-08-14 11:58:08 -07:00
logger.info("Starting hybrid client server...");
// Start the token manager
await tokenManager.start();
const token = await tokenManager.getToken();
2025-08-12 16:31:53 -07:00
const monitor = new TraefikConfigManager();
await monitor.start();
2025-08-12 14:30:23 -07:00
// Create client
const client = createWebSocketClient(
2025-08-13 17:30:59 -07:00
token,
2025-08-12 14:30:23 -07:00
config.getRawConfig().hybrid!.endpoint!,
{
reconnectInterval: 5000,
pingInterval: 30000,
pingTimeout: 10000
}
);
// Register message handlers
2025-08-14 15:39:05 -07:00
client.registerHandler("remoteExitNode/peers/add", async (message) => {
2025-08-12 14:30:23 -07:00
const { pubKey, allowedIps } = message.data;
2025-08-12 16:31:53 -07:00
// TODO: we are getting the exit node twice here
2025-08-12 14:30:23 -07:00
// NOTE: there should only be one gerbil registered so...
const [exitNode] = await db.select().from(exitNodes).limit(1);
await addPeer(exitNode.exitNodeId, {
publicKey: pubKey,
allowedIps: allowedIps || []
});
});
2025-08-14 15:39:05 -07:00
client.registerHandler("remoteExitNode/peers/remove", async (message) => {
2025-08-12 14:30:23 -07:00
const { pubKey } = message.data;
2025-08-12 16:31:53 -07:00
// TODO: we are getting the exit node twice here
2025-08-12 14:30:23 -07:00
// NOTE: there should only be one gerbil registered so...
const [exitNode] = await db.select().from(exitNodes).limit(1);
await deletePeer(exitNode.exitNodeId, pubKey);
});
2025-08-14 15:39:05 -07:00
// /update-proxy-mapping
client.registerHandler("remoteExitNode/update-proxy-mapping", async (message) => {
try {
const [exitNode] = await db.select().from(exitNodes).limit(1);
if (!exitNode) {
logger.error("No exit node found for proxy mapping update");
return;
}
const response = await axios.post(`${exitNode.endpoint}/update-proxy-mapping`, message.data);
logger.info(`Successfully updated proxy mapping: ${response.status}`);
} catch (error) {
// Extract useful information from axios error without circular references
if (error && typeof error === 'object' && 'response' in error) {
const axiosError = error as any;
logger.error("Failed to update proxy mapping:", {
status: axiosError.response?.status,
statusText: axiosError.response?.statusText,
data: axiosError.response?.data,
message: axiosError.message,
url: axiosError.config?.url
});
} else {
logger.error("Failed to update proxy mapping:", {
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined
});
}
}
});
// /update-destinations
client.registerHandler("remoteExitNode/update-destinations", async (message) => {
try {
const [exitNode] = await db.select().from(exitNodes).limit(1);
if (!exitNode) {
logger.error("No exit node found for destinations update");
return;
}
const response = await axios.post(`${exitNode.endpoint}/update-destinations`, message.data);
logger.info(`Successfully updated destinations: ${response.status}`);
} catch (error) {
// Extract useful information from axios error without circular references
if (error && typeof error === 'object' && 'response' in error) {
const axiosError = error as any;
logger.error("Failed to update destinations:", {
status: axiosError.response?.status,
statusText: axiosError.response?.statusText,
data: axiosError.response?.data,
message: axiosError.message,
url: axiosError.config?.url
});
} else {
logger.error("Failed to update proxy mapping:", {
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined
});
}
}
});
client.registerHandler("remoteExitNode/traefik/reload", async (message) => {
2025-08-12 16:47:59 -07:00
await monitor.HandleTraefikConfig();
});
2025-08-12 14:30:23 -07:00
// Listen to connection events
client.on("connect", () => {
2025-08-14 12:01:07 -07:00
logger.info("Connected to WebSocket server");
2025-08-13 20:27:48 -07:00
client.sendMessage("remoteExitNode/register", {
remoteExitNodeVersion: APP_VERSION
});
2025-08-12 14:30:23 -07:00
});
client.on("disconnect", () => {
2025-08-14 12:01:07 -07:00
logger.info("Disconnected from WebSocket server");
2025-08-12 14:30:23 -07:00
});
client.on("message", (message) => {
2025-08-14 15:39:05 -07:00
logger.info(
`Received message: ${message.type} ${JSON.stringify(message.data)}`
);
2025-08-12 14:30:23 -07:00
});
// Connect to the server
try {
await client.connect();
2025-08-14 12:01:07 -07:00
logger.info("Connection initiated");
2025-08-12 14:30:23 -07:00
} catch (error) {
2025-08-14 12:01:07 -07:00
logger.error("Failed to connect:", error);
2025-08-12 14:30:23 -07:00
}
2025-08-13 20:48:54 -07:00
client.sendMessageInterval(
"remoteExitNode/ping",
{ timestamp: Date.now() / 1000 },
60000
); // send every minute
2025-08-12 14:30:23 -07:00
}