naisys
Version:
NAISYS - Autonomous AI agent runner with built-in context management and cost tracking
112 lines • 4.43 kB
JavaScript
import { resolveHostAccessKey } from "@naisys/common-node";
import { HubConnectErrorDataSchema } from "@naisys/hub-protocol";
import { io } from "socket.io-client";
export function createHubConnection(hubClientConfig, hubClientLog, raiseEvent, onConnected, onDisconnected, onConnectError) {
const hubUrl = hubClientConfig.hubUrl;
// Extract origin and base path from hub URL (e.g. "http://localhost:3300/hub" → origin + "/hub")
// Socket.IO needs origin for connection and base path for its transport path
const hubUrlParsed = new URL(hubUrl);
const hubOrigin = hubUrlParsed.origin;
const hubBasePath = hubUrlParsed.pathname.replace(/\/$/, "");
let socket = null;
let connected = false;
function connect() {
hubClientLog.log(`[NAISYS:HubClient] Connecting to ${hubUrl}...`);
const accessKey = resolveHostAccessKey();
if (!accessKey) {
onConnectError({ message: "HOST_ACCESS_KEY is not set" });
return;
}
socket = io(hubOrigin, {
path: hubBasePath + "/socket.io",
extraHeaders: {
"ngrok-skip-browser-warning": "true",
},
auth: (cb) => {
// Re-read access key on each connection attempt so rotated keys are picked up
cb({
accessKey: resolveHostAccessKey(),
instanceId: hubClientConfig.instanceId,
startedAt: hubClientConfig.processStartedAt,
clientVersion: hubClientConfig.clientVersion,
environment: hubClientConfig.environment,
});
},
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 30000,
});
socket.on("connect", () => {
connected = true;
hubClientLog.log(`[NAISYS:HubClient] Connected to ${hubUrl}`);
onConnected();
});
socket.on("disconnect", (reason) => {
connected = false;
hubClientLog.log(`[NAISYS:HubClient] Disconnected from ${hubUrl}: ${reason}`);
onDisconnected();
// Server-initiated disconnects don't auto-reconnect in Socket.IO.
// Always retry; the server's connect_error (fatal vs non-fatal) decides
// whether to give up — covers key rotation, supersede, same-instance replace.
if (reason === "io server disconnect") {
socket?.connect();
}
});
socket.on("connect_error", (error) => {
const parsedData = HubConnectErrorDataSchema.safeParse(error.data);
const connectError = {
message: error.message,
data: parsedData.success ? parsedData.data : undefined,
};
hubClientLog.log(`[NAISYS:HubClient] Connection error to ${hubUrl}: ${connectError.message}${connectError.data ? ` (${connectError.data.code})` : ""}`);
onConnectError(connectError);
});
// Forward all socket events to hubClient's event handlers
socket.onAny((eventName, ...args) => {
hubClientLog.log(`[NAISYS:HubClient] Received ${eventName} from ${hubUrl}`);
raiseEvent(eventName, ...args);
});
}
function disconnect() {
const currentSocket = socket;
socket = null;
connected = false;
if (currentSocket) {
currentSocket.disconnect();
hubClientLog.log(`[NAISYS:HubClient] Disconnected from ${hubUrl}`);
}
}
function isConnected() {
return connected;
}
function getUrl() {
return hubUrl;
}
/**
* Send a message to the hub with optional acknowledgement callback.
* @param event - Event name
* @param payload - Message payload
* @param ack - Optional callback for acknowledgement
* @returns true if message was sent, false if not connected
*/
function sendMessage(event, payload, ack) {
if (!socket || !connected) {
return false;
}
if (ack) {
socket.emit(event, payload, ack);
}
else {
socket.emit(event, payload);
}
return true;
}
return {
connect,
disconnect,
isConnected,
getUrl,
sendMessage,
};
}
//# sourceMappingURL=hubConnection.js.map