tools/awps-tunnel/server/dataHub.ts (184 lines of code) (raw):
// a command tool accepting parameters
// host the website
// start the server connection
import { Server, Socket } from "socket.io";
import { ConnectionStatus, ConnectionStatusPair, HttpHistoryItem, ConnectionStatusPairs, ServiceConfiguration } from "../client/src/models";
import http from "http";
import { HttpServerProxy } from "./serverProxies";
import { DataRepo } from "./dataRepo";
import { startUpstreamServer } from "./upstream";
import { printer } from "./output";
// singleton per hub?
export class DataHub {
// make sure only one server is there
public static upstreamServer?: http.Server;
public tunnelConnectionStatus = ConnectionStatus.Connecting;
public tunnelServerStatus = ConnectionStatusPairs.None;
public serviceConfiguration?: ServiceConfiguration = undefined;
public livetraceUrl = "";
public clientUrl = "";
public endpoint = "";
public upstreamServerUrl = "";
public hub = "";
private io: Server;
private repo: DataRepo;
constructor(server: http.Server, private tunnel: HttpServerProxy, upstreamUrl: URL, dbFile: string) {
const io = (this.io = new Server(server));
printer.log("Webview client connecting to get the latest status");
this.repo = new DataRepo(dbFile);
this.endpoint = tunnel.endpoint;
this.livetraceUrl = tunnel.getLiveTraceUrl();
this.hub = tunnel.hub;
this.upstreamServerUrl = upstreamUrl.toString();
// Socket.io event handling
io.on("connection", (socket: Socket) => {
printer.log("A webview client connected");
socket.on("startEmbeddedUpstream", async (callback) => {
if (DataHub.upstreamServer) {
const message = "Built-in Echo Server already started";
printer.status(`[Upstream] ${message}`);
callback({ success: true, message: message });
return;
}
const url = new URL(upstreamUrl);
try {
DataHub.upstreamServer = await startUpstreamServer(Number.parseInt(url.port), tunnel.hub, "/eventHandler");
this.io.emit("reportBuiltinUpstreamServerStarted", DataHub.upstreamServer !== undefined);
const message = "Built-in Echo Server started at port " + url.port;
printer.status(`[Upstream] ${message}`);
callback({ success: true, message: message });
} catch (err) {
const message = `Built-in Echo Server failed to start at port ${url.port}:${err}`;
this.io.emit("reportBuiltinUpstreamServerStarted", DataHub.upstreamServer !== undefined);
printer.error(`[Upstream] ${message}`);
callback({ success: true, message: message });
}
});
socket.on("stopEmbeddedUpstream", (callback) => {
try {
DataHub.upstreamServer?.close();
DataHub.upstreamServer = undefined;
const message = `Built-in Echo Server successfully stopped`;
this.io.emit("reportBuiltinUpstreamServerStarted", DataHub.upstreamServer !== undefined);
printer.status(`[Upstream] ${message}`);
callback({ success: true, message: message });
} catch (err) {
const message = `Built-in Echo Server failed to stop:${err}`;
this.io.emit("reportBuiltinUpstreamServerStarted", DataHub.upstreamServer !== undefined);
printer.error(`[Upstream] ${message}`);
callback({ success: true, message: message });
}
});
socket.on("getCurrentModel", async (callback) => {
callback({
ready: true,
state: {
endpoint: this.endpoint,
hub: this.hub,
clientUrl: await this.GetClientAccessUrl(),
liveTraceUrl: this.livetraceUrl,
upstreamServerUrl: this.upstreamServerUrl,
tunnelConnectionStatus: this.tunnelConnectionStatus,
tunnelServerStatus: this.tunnelServerStatus,
serviceConfiguration: this.serviceConfiguration,
builtinUpstreamServerStarted: DataHub.upstreamServer !== undefined,
},
trafficHistory: await this.getHttpHistory(),
logs: [],
});
});
socket.on("getClientAccessUrl", async (userId: string, roles: string[], groups: string[], callback) => {
const url = await this.GetClientAccessUrl(userId, roles, groups);
callback(url);
});
socket.on("generateLiveTraceToken", async (callback) => {
const token = await this.tunnel.getLiveTraceToken();
callback(token);
});
socket.on("getRestApiToken", async (url, callback) => {
const token = await this.tunnel.getRestApiToken(url);
callback(token);
});
socket.on("disconnect", () => {
printer.log("A webview client connected");
});
socket.on("clearTrafficHistory", async () => {
await this.repo.clearDataAsync();
this.io.emit("clearTraffic");
});
});
}
async GetClientAccessUrl(userId?: string, roles?: string[], groups?: string[]): Promise<string> {
try {
const url = (this.clientUrl = await this.tunnel.getClientAccessUrl(userId, roles || [], groups || []));
return url;
} catch (err) {
printer.error(`Unable to get client access URL: ${err}`);
return "";
}
}
async AddTraffic(item: HttpHistoryItem) {
item.id = await this.repo.insertDataAsync({
Request: {
TracingId: item.tracingId,
RequestAt: item.requestAtOffset,
MethodName: item.methodName,
Url: item.url,
RequestRaw: item.requestRaw,
},
});
this.io.emit("addTraffic", item);
}
async UpdateTraffic(item: HttpHistoryItem) {
if (!item.id) throw new Error("Id shouldn't be undefined when calling update");
await this.repo.updateDataAsync(
item.id,
JSON.stringify({
Code: item.code,
ResponseRaw: item.responseRaw,
RespondAt: item.responseAtOffset,
}),
);
this.io.emit("updateTraffic", item);
}
UpdateLogs(logs: string[]) {
this.io.emit("updateLogs", logs);
}
ReportLiveTraceUrl(url: string) {
this.livetraceUrl = url;
this.io.emit("reportLiveTraceUrl", url);
}
ReportServiceEndpoint(url: string) {
this.endpoint = url;
this.io.emit("reportServiceEndpoint", url);
}
ReportLocalServerUrl(url: string) {
this.upstreamServerUrl = url;
this.io.emit("reportLocalServerUrl", url);
}
ReportStatusChange(status: ConnectionStatus) {
this.tunnelConnectionStatus = status;
this.io.emit("reportStatusChange", status);
}
ReportTunnelToLocalServerStatus(status: ConnectionStatusPair) {
this.tunnelServerStatus = status;
this.io.emit("reportTunnelToLocalServerStatus", status);
}
ReportServiceConfiguration(config: ServiceConfiguration) {
this.serviceConfiguration = config;
this.io.emit("reportServiceConfiguration", config);
}
async getHttpHistory(): Promise<HttpHistoryItem[]> {
const data = await this.repo.getAsync(50);
const result: HttpHistoryItem[] = [];
for (const item of data) {
result.push({
id: item.Id,
tracingId: item.Request.TracingId,
requestAtOffset: item.Request.RequestAt,
responseAtOffset: item.Response?.RespondAt,
code: item.Response?.Code,
methodName: item.Request.MethodName,
url: item.Request.Url,
requestRaw: item.Request.RequestRaw,
responseRaw: item.Response?.ResponseRaw,
unread: false, //TODO: store in db?
});
}
return result;
}
}