gadget-drone now presents an ApiClient _id value as the Gadget Key, allowing gadget-code to reference the client, determine the associated User, and invoke logic on the User's behalf as an authenticated and authorized client.
208 lines
5.2 KiB
TypeScript
208 lines
5.2 KiB
TypeScript
// src/services/platform.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 path from "node:path";
|
|
import os from "node:os";
|
|
|
|
import { GadgetService } from "../lib/service.ts";
|
|
import {
|
|
DroneStatus,
|
|
IChatSession,
|
|
IChatTurn,
|
|
IDroneRegistration,
|
|
IUser,
|
|
Types,
|
|
} from "@gadget/api";
|
|
|
|
interface PlatformApiResponse {
|
|
success: boolean;
|
|
message?: string;
|
|
}
|
|
|
|
interface PlatformRegistrationResponse extends PlatformApiResponse {
|
|
data: IDroneRegistration;
|
|
}
|
|
|
|
interface ChatSessionContextResponse extends PlatformApiResponse {
|
|
data: IChatTurn[];
|
|
}
|
|
|
|
class PlatformService extends GadgetService {
|
|
registration: IDroneRegistration | undefined;
|
|
|
|
get name(): string {
|
|
return "PlatformService";
|
|
}
|
|
get slug(): string {
|
|
return "svc:platform";
|
|
}
|
|
|
|
async start(): Promise<void> {
|
|
this.log.info("started");
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
this.log.info("stopped");
|
|
}
|
|
|
|
async register(
|
|
email: string,
|
|
password: string,
|
|
workspaceDir: string,
|
|
workspaceId: string,
|
|
): Promise<IDroneRegistration> {
|
|
const url = this.getApiUrl("/drone/registration");
|
|
this.log.info("registering with Gadget Code Platform", {
|
|
workspaceId,
|
|
email,
|
|
url,
|
|
});
|
|
const body = JSON.stringify({
|
|
email,
|
|
password,
|
|
hostname: os.hostname(),
|
|
workspaceDir,
|
|
workspaceId,
|
|
});
|
|
const response = await fetch(url, {
|
|
method: "POST",
|
|
headers: {
|
|
Accept: "application/json",
|
|
"Content-Type": "application/json",
|
|
"Content-Length": body.length.toString(),
|
|
"X-Gadget-Key": env.platform.gadgetKey,
|
|
},
|
|
body,
|
|
});
|
|
|
|
const json = (await response.json()) as PlatformRegistrationResponse;
|
|
this.log.debug("platform registration response", json);
|
|
if (!json.success) {
|
|
const error = new Error("failed to register drone with Platform");
|
|
error.name = "PlatformError";
|
|
error.statusCode = response.status;
|
|
throw error;
|
|
}
|
|
|
|
if (!json.data || !json.data._id) {
|
|
const error = new Error(
|
|
"registration response did not contain required data",
|
|
);
|
|
error.name = "PlatformError";
|
|
throw error;
|
|
}
|
|
|
|
if (!json.data.user || !(json.data.user as IUser)._id) {
|
|
const error = new Error(
|
|
"registration response did not contain required user account data",
|
|
);
|
|
error.name = "PlatformError";
|
|
throw error;
|
|
}
|
|
|
|
this.registration = json.data;
|
|
|
|
return json.data;
|
|
}
|
|
|
|
async unregister(): Promise<void> {
|
|
if (!this.registration) {
|
|
return;
|
|
}
|
|
|
|
const url = this.getApiUrl(`/drone/registration/${this.registration._id}`);
|
|
const body = JSON.stringify({ registration: this.registration });
|
|
const response = await fetch(url, {
|
|
method: "DELETE",
|
|
headers: {
|
|
Accept: "application/json",
|
|
"Content-Type": "application/json",
|
|
"Content-Length": body.length.toString(),
|
|
"X-Gadget-Key": env.platform.gadgetKey,
|
|
},
|
|
body,
|
|
});
|
|
|
|
const json = (await response.json()) as PlatformRegistrationResponse;
|
|
if (!json.success) {
|
|
const error = new Error("failed to register drone with Platform");
|
|
error.name = "PlatformError";
|
|
error.statusCode = response.status;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async setStatus(status: DroneStatus): Promise<void> {
|
|
assert(
|
|
this.registration,
|
|
"must register with platform before setting status",
|
|
);
|
|
|
|
const url = this.getApiUrl(
|
|
`/drone/registration/${this.registration._id}/status`,
|
|
);
|
|
const body = JSON.stringify({ status });
|
|
const response = await fetch(url, {
|
|
method: "PUT",
|
|
headers: {
|
|
Accept: "application/json",
|
|
"Content-Type": "application/json",
|
|
"Content-Length": body.length.toString(),
|
|
"X-Gadget-Key": env.platform.gadgetKey,
|
|
},
|
|
body,
|
|
});
|
|
|
|
const json = (await response.json()) as PlatformRegistrationResponse;
|
|
if (!json.success) {
|
|
const error = new Error("failed to register drone with Platform");
|
|
error.name = "PlatformError";
|
|
error.statusCode = response.status;
|
|
throw error;
|
|
}
|
|
|
|
this.log.info("drone status updated on platform", { status });
|
|
}
|
|
|
|
async getChatSessionContext(
|
|
session: IChatSession,
|
|
): Promise<ChatSessionContextResponse> {
|
|
assert(
|
|
this.registration,
|
|
"must register with platform before setting status",
|
|
);
|
|
|
|
const url = this.getApiUrl(`/chat-sessions/${session._id}/turns`);
|
|
const response = await fetch(url, {
|
|
method: "GET",
|
|
headers: {
|
|
Accept: "application/json",
|
|
"X-Gadget-Key": env.platform.gadgetKey,
|
|
},
|
|
});
|
|
|
|
const json = (await response.json()) as ChatSessionContextResponse;
|
|
if (!json.success) {
|
|
const error = new Error("failed to retrieve chat session context");
|
|
error.name = "PlatformError";
|
|
error.statusCode = response.status;
|
|
throw error;
|
|
}
|
|
|
|
this.log.info("chat session context received", {
|
|
turnCount: json.data.length,
|
|
});
|
|
return json;
|
|
}
|
|
|
|
getApiUrl(url: string): string {
|
|
return `${env.platform.baseUrl}/api/v1${url}`;
|
|
}
|
|
}
|
|
|
|
export default new PlatformService();
|