TeaWeb/shared/js/connection/ClientInfo.ts

259 lines
9.0 KiB
TypeScript

import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {LogCategory, logError, logWarn} from "tc-shared/log";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ErrorCode} from "tc-shared/connection/ErrorCode";
export type ClientInfoResult = {
status: "success",
clientName: string,
clientUniqueId: string,
clientDatabaseId: number,
} | {
status: "not-found"
} | {
status: "error",
error: string
};
type PendingInfoRequest = {
promise: Promise<ClientInfoResult>,
resolve: (result: ClientInfoResult) => void,
fullFilled: boolean,
};
type ClientInfo = {
clientName: string,
clientUniqueId: string,
clientDatabaseId: number,
};
export class ClientInfoResolver {
private readonly handler: ConnectionHandler;
private readonly requestDatabaseIds: { [key: number]: PendingInfoRequest };
private readonly requestUniqueIds: { [key: string]: PendingInfoRequest };
private executed: boolean;
constructor(handler: ConnectionHandler) {
this.handler = handler;
this.executed = false;
this.requestDatabaseIds = {};
this.requestUniqueIds = {};
}
private registerRequest(type: "database-id", key: number) : Promise<ClientInfoResult>;
private registerRequest(type: "unique-id", key: string) : Promise<ClientInfoResult>;
private registerRequest(type, key) : Promise<ClientInfoResult> {
let targetObject;
switch (type) {
case "database-id":
targetObject = this.requestDatabaseIds;
break;
case "unique-id":
targetObject = this.requestUniqueIds;
break;
default:
return;
}
if(this.executed) {
throw tr("request already executed");
}
if(typeof targetObject[key] === "undefined") {
const handle: PendingInfoRequest = {
fullFilled: false,
resolve: undefined,
promise: undefined,
};
handle.promise = new Promise<ClientInfoResult>(resolve => handle.resolve = resolve);
targetObject[key] = handle;
}
return targetObject[key].promise;
}
private fullFullAllRequests(type: "database-id" | "unique-id", result: ClientInfoResult) {
let targetObject;
switch (type) {
case "database-id":
targetObject = this.requestDatabaseIds;
break;
case "unique-id":
targetObject = this.requestUniqueIds;
break;
default:
return;
}
Object.keys(targetObject).forEach(key => {
if(targetObject[key].fullFilled) {
return;
}
targetObject[key].fullFilled = true;
targetObject[key].resolve(result);
});
}
private static parseClientInfo(json: any[]) : ClientInfo[] {
const result: ClientInfo[] = [];
let index = 0;
for(const entry of json) {
index++;
if(typeof entry["cluid"] === "undefined") {
logWarn(LogCategory.NETWORKING, tr("Missing client unique id in client info result bulk %d."), index);
continue;
}
if(typeof entry["clname"] === "undefined") {
logWarn(LogCategory.NETWORKING, tr("Missing client name in client info result bulk %d."), index);
continue;
}
if(typeof entry["cldbid"] === "undefined") {
logWarn(LogCategory.NETWORKING, tr("Missing client database id in client info result bulk %d."), index);
continue;
}
const databaseId = parseInt(entry["cldbid"]);
if(isNaN(databaseId)) {
logWarn(LogCategory.NETWORKING, tr("Client database id (%s) in client info isn't parse able as integer in bulk %d."), entry["cldbid"], index);
continue;
}
result.push({ clientName: entry["clname"], clientUniqueId: entry["cluid"], clientDatabaseId: databaseId });
}
return result;
}
getInfoByDatabaseId(databaseId: number) : Promise<ClientInfoResult> {
return this.registerRequest("database-id", databaseId);
}
getInfoByUniqueId(uniqueId: string) : Promise<ClientInfoResult> {
return this.registerRequest("unique-id", uniqueId);
}
async executeQueries() {
this.executed = true;
let promises = [];
let handlers = [];
try {
const requestDatabaseIds = Object.keys(this.requestDatabaseIds);
if(requestDatabaseIds.length > 0) {
handlers.push(this.handler.serverConnection.command_handler_boss().register_explicit_handler("notifyclientgetnamefromdbid", command => {
ClientInfoResolver.parseClientInfo(command.arguments).forEach(info => {
if(this.requestDatabaseIds[info.clientDatabaseId].fullFilled) {
return;
}
this.requestDatabaseIds[info.clientDatabaseId].fullFilled = true;
this.requestDatabaseIds[info.clientDatabaseId].resolve({
status: "success",
clientName: info.clientName,
clientDatabaseId: info.clientDatabaseId,
clientUniqueId: info.clientUniqueId
});
});
}));
promises.push(this.handler.serverConnection.send_command("clientgetnamefromdbid",
requestDatabaseIds.map(entry => ({ cldbid: entry })),
{
process_result: false,
}
).catch(error => {
if(error instanceof CommandResult) {
if(error.id === ErrorCode.DATABASE_EMPTY_RESULT) {
return;
}
error = error.formattedMessage();
} else if(typeof error !== "string") {
logError(LogCategory.NETWORKING, tr("Failed to resolve client info from database id: %o"), error);
error = tr("lookup the console");
}
this.fullFullAllRequests("database-id", {
status: "error",
error: error
});
}));
}
const requestUniqueIds = Object.keys(this.requestUniqueIds);
if(requestUniqueIds.length > 0) {
handlers.push(this.handler.serverConnection.command_handler_boss().register_explicit_handler("notifyclientnamefromuid", command => {
ClientInfoResolver.parseClientInfo(command.arguments).forEach(info => {
if(this.requestUniqueIds[info.clientUniqueId].fullFilled) {
return;
}
this.requestUniqueIds[info.clientUniqueId].fullFilled = true;
this.requestUniqueIds[info.clientUniqueId].resolve({
status: "success",
clientName: info.clientName,
clientDatabaseId: info.clientDatabaseId,
clientUniqueId: info.clientUniqueId
});
});
}));
promises.push(this.handler.serverConnection.send_command("clientgetnamefromuid",
requestUniqueIds.map(entry => ({ cluid: entry })),
{
process_result: false,
}
).catch(error => {
if(error instanceof CommandResult) {
if(error.id === ErrorCode.DATABASE_EMPTY_RESULT) {
return;
}
error = error.formattedMessage();
} else if(typeof error !== "string") {
logError(LogCategory.NETWORKING, tr("Failed to resolve client info from unique id: %o"), error);
error = tr("lookup the console");
}
this.fullFullAllRequests("unique-id", {
status: "error",
error: error
});
}));
}
await Promise.all(promises);
this.fullFullAllRequests("unique-id", { status: "not-found" });
this.fullFullAllRequests("database-id", { status: "not-found" });
} finally {
handlers.forEach(callback => callback());
this.fullFullAllRequests("unique-id", {
status: "error",
error: tr("request failed")
});
this.fullFullAllRequests("database-id", {
status: "error",
error: tr("request failed")
});
}
}
}