gadget/gadget-drone/src/gadget-drone.ts
2026-04-28 09:20:37 -04:00

173 lines
4.3 KiB
TypeScript

// src/gadget-drone.ts
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0
import env from "./config/env.ts";
import assert from "node:assert";
import { io, ManagerOptions, SocketOptions, Socket } from "socket.io-client";
import AgentService, { IAgentWorkOrder } from "./services/agent.ts";
import AiService from "./services/ai.ts";
import PlatformService, {
DroneStatus,
PlatformRegistration,
} from "./services/platform.ts";
import { GadgetProcess } from "./lib/process.ts";
class GadgetDrone extends GadgetProcess {
private registration: PlatformRegistration | undefined;
private socket: Socket | undefined;
private isShuttingDown: boolean = false;
get name(): string {
return "GadgetDrone";
}
get slug(): string {
return "gadget-drone";
}
constructor() {
super();
}
async start(): Promise<void> {
/*
* Initialize the system
*/
this.hookProcessSignals();
await this.startServices();
/*
* Register this Drone with the Gadget Code web services platform.
*/
const email = "rob@digitaltelepresence.com";
const password = "ionfrali";
this.registration = await PlatformService.register(email, password);
this.log.info("registered with platform", {
registration: this.registration,
});
/*
* Connect to the Gadget Code web services platform and configure the real-
* time messaging system on Socket.IO.
*/
await this.connectSocket();
/*
* Mark this Drone as available and ready to accept work orders.
*/
await PlatformService.setStatus(DroneStatus.Available);
this.log.info(`Gadget Drone v${env.pkg.version} started`);
}
async stop(): Promise<number> {
this.log.info(`Gadget Drone v${env.pkg.version} shutting down`);
if (this.socket) {
this.socket.disconnect();
delete this.socket;
}
await PlatformService.unregister();
await this.stopServices();
return 0;
}
async startServices(): Promise<void> {
this.log.info("starting services");
await AgentService.start();
await AiService.start();
await PlatformService.start();
this.log.info("services started");
}
async stopServices(): Promise<void> {
this.log.info("stopping services");
await AgentService.stop();
await AiService.stop();
await PlatformService.stop();
this.log.info("services stopped");
}
async connectSocket(): Promise<void> {
return new Promise((resolve, reject) => {
assert(this.registration, "must be registered with Gadget Code platform");
const options: Partial<ManagerOptions & SocketOptions> = {
auth: { token: this.registration._id },
reconnectionAttempts: 10,
timeout: 5000,
transports: ["websocket"],
};
/*
* Allow self-signed certs in non-production environments
*/
if (env.NODE_ENV !== "production") {
options.rejectUnauthorized = false;
}
this.log.debug("connecting to Gadget Code platform...");
this.socket = io("https://code-dev.g4dge7.com:5174/", options);
this.socket.on("connect_error", (err) => {
this.log.error("socket connect error", { err });
reject(err);
});
this.socket.on("connect", () => {
this.log.info("connected to Gadget Code platform.");
resolve();
});
});
}
hookProcessSignals(): void {
process.title = this.name;
process.on("unhandledRejection", async (error: Error, p) => {
this.log.error("Unhandled rejection", {
error,
promise: p,
stack: error.stack,
});
const exitCode = await this.stop();
process.exit(exitCode);
});
process.on("warning", (error) => {
if (error.name === "DeprecationWarning") return;
this.log.alert("warning", { error });
});
process.on("SIGINT", async () => {
this.log.info("SIGINT received");
if (this.isShuttingDown) return;
this.log.info("requesting shutdown");
const exitCode = await this.stop();
process.exit(exitCode);
});
}
}
(async () => {
try {
const drone = new GadgetDrone();
await drone.start();
} catch (error) {
console.error("failed to start gadget-drone", error);
process.exit(-1);
}
})();