@deskthing/cli
Version:
An emulator for the DeskThing Server
221 lines (186 loc) • 7.2 kB
text/typescript
import { AppSettings } from "@deskthing/types";
import { DeskThingClientConfig, LoggingLevel } from "../../../config/deskthing.config.types";
import { useClientStore } from "../stores/clientStore";
import { ClientLogger } from "./clientLogger";
import { ClientMessageBus } from "./clientMessageBus";
type callback = (data: any) => void | Promise<void>;
type log = { level: LoggingLevel, message: string, data: any[] }
export class ClientService {
private static responseHandlers: Record<string, callback[]> = {};
private static isInitialized = false;
private static logCache: log[] = []
static initialize() {
if (this.isInitialized) return;
this.isInitialized = true;
ClientLogger.debug("Initializing the wrapper...");
ClientMessageBus.subscribe("client:response", async (data: any) => {
const handlers = this.responseHandlers[data.type];
if (handlers && Array.isArray(handlers)) {
try {
await Promise.all(handlers.map((handler) => {
try {
handler(data.payload)
} catch (error) {
this.log('error', 'Error in settings callback:', error)
}
}));
} catch (error) {
this.log('error', 'Error when processing client:response with the data', error)
} finally {
ClientService.responseHandlers[data.type] = [];
}
}
});
let attempts = 0
const responseHandler = (config) => {
if (!config) {
this.logCache.forEach((log) => {
console.log(log.level, log.message, ...log.data)
})
if (attempts < 5) {
attempts++
ClientLogger.warn(`Failed to load client config, retrying... (${attempts}/5)`)
setTimeout(() => this.requestClientConfig(responseHandler, { hostname: 'localhost', port: 3000 }), 1000 * attempts)
}
return
}
if (this.logCache.length > 0) {
this.logCache.forEach((log) => {
this.log(log.level, log.message, ...log.data)
})
this.logCache = []
}
useClientStore.getState().updateConfig(config)
ClientMessageBus.initialize(`ws://${window.location.hostname}:${config?.linkPort || window.location.port}`);
ClientLogger.debug("Client config loaded:", config);
}
// Fetch client configuration on startup
this.requestClientConfig(responseHandler);
}
static requestClientConfig(callback: (data: DeskThingClientConfig) => void, options?: { hostname?: string, port?: number }) {
fetch(`http://${options?.hostname || window.location.hostname}:${options?.port || window.location.port}/config`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
})
.then(config => {
callback(config)
})
.catch(error => {
console.error('Error fetching client config:', error)
callback(null)
})
}
static requestServerData(callback: (data: any) => void) {
if (!this.responseHandlers["data"]) {
this.responseHandlers["data"] = [];
}
this.responseHandlers["data"].push(callback);
if (this.responseHandlers["data"].length === 1) {
ClientMessageBus.publish("client:request", { type: "getData" });
}
}
static saveSettings(settings: AppSettings) {
ClientMessageBus.publish("client:request", { type: "setSettings", payload: settings });
}
static requestManifest(callback: (data: any) => void) {
if (!this.responseHandlers["manifest"]) {
this.responseHandlers["manifest"] = [];
}
this.responseHandlers["manifest"].push(callback);
if (this.responseHandlers["manifest"].length === 1) {
ClientMessageBus.publish("client:request", { type: "getManifest" });
}
}
static requestSettings(callback: (data: AppSettings) => void) {
if (!this.responseHandlers["settings"]) {
this.responseHandlers["settings"] = [];
}
this.responseHandlers["settings"].push(callback);
// Only send the request if there are handlers and no request is pending
if (this.responseHandlers["settings"].length === 1) {
// Only send if this is the first handler
ClientLogger.debug("Requesting settings from server");
ClientMessageBus.publish("client:request", { type: "getSettings" });
} else {
ClientLogger.debug("Settings request already pending, adding another handler");
}
}
static sendToServer(data: any) {
ClientMessageBus.publish("client:request", data);
}
static sendToApp(data: any) {
if (!data.clientId) {
data.clientId = useClientStore.getState().clientId || '1234567890'
}
ClientMessageBus.publish("app:data", { ...data, clientId: data.clientId || '1234567890' });
}
private static shouldLog(
msgLevel: LoggingLevel,
configLevel: LoggingLevel
): boolean {
const levels = ["silent", "error", "warn", "info", "debug"];
const msgLevelIndex = levels.indexOf(msgLevel);
const configLevelIndex = levels.indexOf(configLevel);
// If either level is not found, default to showing the message
if (msgLevelIndex === -1 || configLevelIndex === -1) return true;
// Only show messages that are at or above the configured level
return msgLevelIndex <= configLevelIndex;
}
static log(level: LoggingLevel, message: string, ...data: any[]) {
// Only log if the level is appropriate based on config
const clientConfig = useClientStore.getState().config
if (clientConfig.logging.level == undefined) {
this.logCache.push({ level, message, data })
return
}
if (this.shouldLog(level, clientConfig.logging.level)) {
// Local console logging
switch (level) {
case "debug":
console.debug(
"\x1b[36m%s\x1b[0m",
`${clientConfig.logging.prefix} ${message}`,
...data
); // Cyan
break;
case "info":
console.info(
"\x1b[90m%s\x1b[0m",
`${clientConfig.logging.prefix} ${message}`,
...data
); // Gray
break;
case "warn":
console.warn(
"\x1b[33m%s\x1b[0m",
`${clientConfig.logging.prefix} ${message}`,
...data
); // Yellow
break;
case "error":
console.error(
"\x1b[31m%s\x1b[0m",
`${clientConfig.logging.prefix} ${message}`,
...data
); // Red
break;
default:
console.log(
"\x1b[32m%s\x1b[0m",
`${clientConfig.logging.prefix} ${message}`,
...data
); // Green
}
// Remote logging if enabled
// if (config.logging.enableRemoteLogging) {
// this.sendToServer({
// type: 'clientLog',
// payload: { level, message, data }
// })
// }
}
}
}