From b6c2f123e8672cdc3a80ffeabc2d675e86d1ac38 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 12 Aug 2025 14:30:23 -0700 Subject: [PATCH] Add basic ws client --- server/hybridClientServer.ts | 76 ++++++++++++++++++++++++++++++++++++ server/index.ts | 9 ++++- server/lib/readConfigFile.ts | 8 +++- 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 server/hybridClientServer.ts diff --git a/server/hybridClientServer.ts b/server/hybridClientServer.ts new file mode 100644 index 00000000..939fc5fe --- /dev/null +++ b/server/hybridClientServer.ts @@ -0,0 +1,76 @@ +import next from "next"; +import express from "express"; +import { parse } from "url"; +import logger from "@server/logger"; +import config from "@server/lib/config"; +import { WebSocketClient, createWebSocketClient } from "./routers/ws/client"; +import { addPeer, deletePeer } from "./routers/gerbil/peers"; +import { db, exitNodes } from "./db"; + +export async function createHybridClientServer() { + if ( + !config.getRawConfig().hybrid?.id || + !config.getRawConfig().hybrid?.secret || + !config.getRawConfig().hybrid?.endpoint + ) { + throw new Error("Hybrid configuration is not defined"); + } + + // Create client + const client = createWebSocketClient( + "remoteExitNode", // or 'olm' + config.getRawConfig().hybrid!.id!, + config.getRawConfig().hybrid!.secret!, + config.getRawConfig().hybrid!.endpoint!, + { + reconnectInterval: 5000, + pingInterval: 30000, + pingTimeout: 10000 + } + ); + + // Register message handlers + client.registerHandler("remote/peers/add", async (message) => { + const { pubKey, allowedIps } = message.data; + + // TODO: we are getting the exit node twice here + // 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 || [] + }); + }); + + client.registerHandler("remote/peers/remove", async (message) => { + const { pubKey } = message.data; + + // TODO: we are getting the exit node twice here + // NOTE: there should only be one gerbil registered so... + const [exitNode] = await db.select().from(exitNodes).limit(1); + await deletePeer(exitNode.exitNodeId, pubKey); + }); + + // Listen to connection events + client.on("connect", () => { + console.log("Connected to WebSocket server"); + }); + + client.on("disconnect", () => { + console.log("Disconnected from WebSocket server"); + }); + + client.on("message", (message) => { + console.log("Received message:", message.type, message.data); + }); + + // Connect to the server + try { + await client.connect(); + console.log("Connection initiated"); + } catch (error) { + console.error("Failed to connect:", error); + } + + client.sendMessageInterval("heartbeat", { timestamp: Date.now() }, 10000); +} diff --git a/server/index.ts b/server/index.ts index d3f90281..b0d6d3d7 100644 --- a/server/index.ts +++ b/server/index.ts @@ -7,6 +7,7 @@ import { createNextServer } from "./nextServer"; import { createInternalServer } from "./internalServer"; import { ApiKey, ApiKeyOrg, Session, User, UserOrg } from "@server/db"; import { createIntegrationApiServer } from "./integrationApiServer"; +import { createHybridClientServer } from "./hybridClientServer"; import config from "@server/lib/config"; async function startServers() { @@ -18,6 +19,11 @@ async function startServers() { const internalServer = createInternalServer(); const nextServer = await createNextServer(); + let hybridClientServer; + if (config.getRawConfig().hybrid) { + hybridClientServer = createHybridClientServer(); + } + let integrationServer; if (config.getRawConfig().flags?.enable_integration_api) { integrationServer = createIntegrationApiServer(); @@ -27,7 +33,8 @@ async function startServers() { apiServer, nextServer, internalServer, - integrationServer + integrationServer, + hybridClientServer }; } diff --git a/server/lib/readConfigFile.ts b/server/lib/readConfigFile.ts index 1bc119fa..e6e7c548 100644 --- a/server/lib/readConfigFile.ts +++ b/server/lib/readConfigFile.ts @@ -4,6 +4,7 @@ import { configFilePath1, configFilePath2 } from "./consts"; import { z } from "zod"; import stoi from "./stoi"; import { build } from "@server/build"; +import { setAdminCredentials } from "@cli/commands/setAdminCredentials"; const portSchema = z.number().positive().gt(0).lte(65535); @@ -25,8 +26,13 @@ export const configSchema = z .optional() .default("info"), save_logs: z.boolean().optional().default(false), - log_failed_attempts: z.boolean().optional().default(false) + log_failed_attempts: z.boolean().optional().default(false), }), + hybrid: z.object({ + id: z.string().optional(), + secret: z.string().optional(), + endpoint: z.string().optional() + }).optional(), domains: z .record( z.string(),