@deskthing/cli
Version:
An emulator for the DeskThing Server
1,511 lines (1,486 loc) • 60.1 kB
JavaScript
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
// src/config/deskthing.config.ts
import { join, resolve } from "path";
import { createRequire } from "module";
import { readFile } from "fs/promises";
import { pathToFileURL } from "url";
import { existsSync } from "fs";
var defaultConfig = {
development: {
logging: {
level: "info",
prefix: "[DeskThing Server]"
},
client: {
logging: {
level: "info",
prefix: "[DeskThing Client]",
enableRemoteLogging: true
},
clientPort: 3e3,
viteLocation: "http://localhost",
vitePort: 5173,
linkPort: 8080
},
server: {
editCooldownMs: 1e3,
refreshInterval: 0
}
}
};
async function loadTsConfig(path2, debug = false) {
try {
const require2 = createRequire(import.meta.url);
try {
require2("ts-node/register");
} catch (e) {
if (debug) console.log(`ts-node not available, continuing anyway...`);
}
try {
const configModule = require2(path2);
return configModule.default || configModule;
} catch (e) {
if (debug)
console.error(
"\x1B[91mError loading TypeScript config:",
path2,
e,
"\x1B[0m"
);
throw e;
}
} catch (error) {
if (debug) console.error("\x1B[91mError loading config:", error, "\x1B[0m");
throw error;
}
}
var manuallyParseConfig = async (path2, debug = false) => {
try {
if (debug)
console.log(
`(debug mode enabled) Manually loading config from ${path2} file and parsing manually (may cause errors)`
);
const fileContent = await readFile(path2, "utf-8");
const configMatch = fileContent.match(/defineConfig\s*\(\s*({[\s\S]*?})\s*\)/);
if (!configMatch) {
if (debug)
console.log("Could not find config definition in file using regex");
const objectMatch = fileContent.match(/\{[\s\S]*development[\s\S]*\}/);
if (objectMatch) {
try {
let jsonStr = objectMatch[0].replace(/process\.env\.[A-Z_]+/g, '"ENV_VARIABLE"').replace(/'/g, '"').replace(/,(\s*[}\]])/g, "$1").replace(/\/\/.*$/gm, "");
jsonStr = jsonStr.replace(
/process\.env\.[A-Z_]+/g,
'"ENV_PLACEHOLDER"'
);
const config = JSON.parse(jsonStr);
if (debug)
console.log("Successfully extracted config using fallback method");
return config;
} catch (e) {
if (debug) console.log("Failed to parse extracted object:", e);
}
}
throw new Error("Could not find or parse config definition in file");
}
try {
const configStr = configMatch[1]?.trim() || configMatch[2]?.trim();
if (debug)
console.log("Found config string, attempting to evaluate safely");
if (debug)
console.log(configStr);
const cleanConfigStr = configStr?.replace(/process\.env\.[A-Z_]+/g, '"ENV_VARIABLE"').replace(/,(\s*[}\]])/g, "$1").replace(/\/\/.*$/gm, "");
const config = JSON.parse(cleanConfigStr);
return config;
} catch (parseError) {
if (debug)
console.error("\x1B[91mError parsing config:", parseError, "\x1B[0m");
throw parseError;
}
} catch (e) {
if (debug)
console.error(
"\x1B[91m(debug mode enabled) Error loading config:",
e,
"\x1B[0m"
);
throw e;
}
};
async function parseTypeScriptConfig(filePath, debug = false) {
try {
const content = await readFile(filePath, "utf-8");
const configMatch = content.match(/defineConfig\s*\(\s*({[\s\S]*?})\s*\)/);
if (!configMatch || !configMatch[1]) {
return null;
}
let configStr = configMatch[1].replace(/process\.env\.[A-Z_]+/g, '"ENV_VARIABLE"').replace(/,(\s*[}\]])/g, "$1").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
try {
const fn = new Function(`return ${configStr}`);
return fn();
} catch (evalError) {
if (debug)
console.log("Failed to evaluate config as JavaScript:", evalError);
try {
configStr = configStr.replace(/'/g, '"');
return JSON.parse(configStr);
} catch (jsonError) {
if (debug)
console.log("Failed to parse as JSON:", jsonError);
return null;
}
}
} catch (error) {
if (debug)
console.log("Error reading or parsing config file:", error);
return null;
}
}
var directConfigImport = async (path2, debug = false) => {
try {
if (debug)
console.log(
`(debug mode enabled) Loading config from ${path2} file...`
);
const configModule = await import(`${path2}`);
if (debug) console.log(`Config loaded successfully from ${path2}`);
return configModule.default || configModule;
} catch (error) {
if (debug) console.log("Direct import failed:", error);
throw error;
}
};
var directConfigImportUrl = async (path2, debug = false) => {
try {
const tsConfigPath = pathToFileURL(path2).href;
if (debug)
console.log(
`(debug mode enabled) Loading config from ${tsConfigPath} file...`
);
const configModule = await import(tsConfigPath);
if (debug) console.log(`Config loaded successfully from ${tsConfigPath}`);
return configModule.default || configModule;
} catch (error) {
if (debug) console.log("Direct import failed:", error);
throw error;
}
};
var getConfigFromFile = async (debug = false) => {
try {
let rootUrl = resolve(process.cwd(), "deskthing.config.ts");
if (!existsSync(rootUrl)) {
if (debug)
console.log(
"deskthing.config.ts not found, trying deskthing.config.js"
);
rootUrl = resolve(process.cwd(), "deskthing.config.js");
if (!existsSync(rootUrl)) {
throw new Error("No config file found (tried both TS and JS files)");
}
}
try {
const config = await directConfigImport(rootUrl, debug);
if (config) {
if (debug) console.log("Config loaded successfully from TS file");
return config;
}
} catch (importError) {
if (debug)
console.log(`Direct import failed, trying alternative method...`);
}
try {
const config = await directConfigImportUrl(rootUrl, debug);
if (config) {
if (debug) console.log("Config loaded successfully from TS file");
return config;
}
} catch (importError) {
if (debug)
console.log(`Direct import failed, trying alternative method...`);
}
try {
const config = await loadTsConfig(rootUrl, debug);
if (config) {
if (debug) console.log("Config loaded successfully from TS file");
return config;
}
} catch (e) {
if (debug)
console.error("\x1B[91mError loading TS config:", e, "\x1B[0m");
}
if (debug) console.log("Trying to parse config manually...");
try {
const config = await manuallyParseConfig(rootUrl, debug);
return config;
} catch (e) {
if (debug)
console.error("\x1B[91mError parsing config manually:", e, "\x1B[0m");
}
if (debug) console.log("Trying to parse config and run as js...");
try {
const config = await parseTypeScriptConfig(rootUrl, debug);
return config;
} catch (error) {
if (debug) console.log("Error running as js:", error);
}
} catch (e) {
if (debug)
console.error(
"\x1B[91m(debug mode) Error loading config. Does it exist? :",
e,
"\x1B[0m"
);
return defaultConfig;
}
};
var DeskThingConfig = defaultConfig;
function isObject(item) {
return item && typeof item === "object" && !Array.isArray(item);
}
function deepmerge(target, source) {
if (!isObject(target) || !isObject(source)) {
return source;
}
const output = { ...target };
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key]) && isObject(target[key])) {
output[key] = deepmerge(target[key], source[key]);
} else {
output[key] = source[key];
}
}
}
return output;
}
var initConfig = async (options = {
silent: false,
debug: false
}) => {
try {
const userConfig = await getConfigFromFile(options.debug);
DeskThingConfig = deepmerge(defaultConfig, userConfig || {});
if (!options.silent || options.debug) {
if (userConfig) {
console.log(`
\x1B[32m\u2705 Config Loaded\x1B[0m
`);
} else {
console.log(`
\x1B[32m\u2705 No Config Found, Using Default\x1B[0m`);
if (options.debug) {
console.log(
`\x1B[3m\x1B[90mPath Checked: ${join(
process.cwd(),
"deskthing.config.ts"
)}\x1B[0m
`
);
}
}
}
} catch (e) {
console.warn("\x1B[93mWarning: Error loading config, using defaults\x1B[0m");
if (options.debug)
console.error("\x1B[91mError loading config:", e, "\x1B[0m");
DeskThingConfig = defaultConfig;
}
};
var serverConfig = {
...defaultConfig
};
// src/emulator/client/client.ts
import { createServer } from "http";
import { existsSync as existsSync2, readFileSync, statSync } from "fs";
import { extname, join as join2, normalize } from "path";
import { fileURLToPath } from "url";
import { URL as URL2 } from "url";
// node_modules/@deskthing/types/dist/clients/clientData.js
var ClientConnectionMethod;
(function(ClientConnectionMethod2) {
ClientConnectionMethod2[ClientConnectionMethod2["Unknown"] = 0] = "Unknown";
ClientConnectionMethod2[ClientConnectionMethod2["LAN"] = 1] = "LAN";
ClientConnectionMethod2[ClientConnectionMethod2["Localhost"] = 2] = "Localhost";
ClientConnectionMethod2[ClientConnectionMethod2["ADB"] = 3] = "ADB";
ClientConnectionMethod2[ClientConnectionMethod2["NDIS"] = 4] = "NDIS";
ClientConnectionMethod2[ClientConnectionMethod2["Bluetooth"] = 5] = "Bluetooth";
ClientConnectionMethod2[ClientConnectionMethod2["Internet"] = 6] = "Internet";
})(ClientConnectionMethod || (ClientConnectionMethod = {}));
var PlatformIDs;
(function(PlatformIDs2) {
PlatformIDs2["ADB"] = "adb";
PlatformIDs2["WEBSOCKET"] = "websocket";
PlatformIDs2["BLUETOOTH"] = "bluetooth";
PlatformIDs2["MAIN"] = "main";
})(PlatformIDs || (PlatformIDs = {}));
var ClientPlatformIDs;
(function(ClientPlatformIDs2) {
ClientPlatformIDs2[ClientPlatformIDs2["Unknown"] = 0] = "Unknown";
ClientPlatformIDs2[ClientPlatformIDs2["Desktop"] = 1] = "Desktop";
ClientPlatformIDs2[ClientPlatformIDs2["Tablet"] = 2] = "Tablet";
ClientPlatformIDs2[ClientPlatformIDs2["Iphone"] = 3] = "Iphone";
ClientPlatformIDs2[ClientPlatformIDs2["CarThing"] = 4] = "CarThing";
})(ClientPlatformIDs || (ClientPlatformIDs = {}));
var ConnectionState;
(function(ConnectionState2) {
ConnectionState2[ConnectionState2["Connected"] = 0] = "Connected";
ConnectionState2[ConnectionState2["Established"] = 1] = "Established";
ConnectionState2[ConnectionState2["Connecting"] = 2] = "Connecting";
ConnectionState2[ConnectionState2["Disconnecting"] = 3] = "Disconnecting";
ConnectionState2[ConnectionState2["Disconnected"] = 4] = "Disconnected";
ConnectionState2[ConnectionState2["Failed"] = 5] = "Failed";
})(ConnectionState || (ConnectionState = {}));
var ProviderCapabilities;
(function(ProviderCapabilities2) {
ProviderCapabilities2[ProviderCapabilities2["CONFIGURE"] = 0] = "CONFIGURE";
ProviderCapabilities2[ProviderCapabilities2["PING"] = 1] = "PING";
ProviderCapabilities2[ProviderCapabilities2["COMMUNICATE"] = 2] = "COMMUNICATE";
})(ProviderCapabilities || (ProviderCapabilities = {}));
var ViewMode;
(function(ViewMode2) {
ViewMode2["HIDDEN"] = "hidden";
ViewMode2["PEEK"] = "peek";
ViewMode2["FULL"] = "full";
})(ViewMode || (ViewMode = {}));
var VolMode;
(function(VolMode2) {
VolMode2["WHEEL"] = "wheel";
VolMode2["SLIDER"] = "slider";
VolMode2["BAR"] = "bar";
})(VolMode || (VolMode = {}));
// node_modules/@deskthing/types/dist/clients/clientTransit.js
var CLIENT_REQUESTS;
(function(CLIENT_REQUESTS2) {
CLIENT_REQUESTS2["GET"] = "get";
CLIENT_REQUESTS2["ACTION"] = "action";
CLIENT_REQUESTS2["BUTTON"] = "button";
CLIENT_REQUESTS2["KEY"] = "key";
CLIENT_REQUESTS2["LOG"] = "log";
})(CLIENT_REQUESTS || (CLIENT_REQUESTS = {}));
var DEVICE_CLIENT;
(function(DEVICE_CLIENT2) {
DEVICE_CLIENT2["MANIFEST"] = "manifest";
DEVICE_CLIENT2["MUSIC"] = "music";
DEVICE_CLIENT2["SETTINGS"] = "settings";
DEVICE_CLIENT2["APPS"] = "apps";
DEVICE_CLIENT2["ACTION"] = "action";
DEVICE_CLIENT2["TIME"] = "time";
DEVICE_CLIENT2["ICON"] = "icon";
})(DEVICE_CLIENT || (DEVICE_CLIENT = {}));
var DEVICE_DESKTHING;
(function(DEVICE_DESKTHING2) {
DEVICE_DESKTHING2["ACTION"] = "action";
DEVICE_DESKTHING2["SET"] = "set";
DEVICE_DESKTHING2["GET"] = "get";
DEVICE_DESKTHING2["PING"] = "ping";
DEVICE_DESKTHING2["PONG"] = "pong";
DEVICE_DESKTHING2["LOG"] = "log";
DEVICE_DESKTHING2["VIEW"] = "view";
DEVICE_DESKTHING2["APP_PAYLOAD"] = "app_payload";
DEVICE_DESKTHING2["MANIFEST"] = "manifest";
DEVICE_DESKTHING2["SETTINGS"] = "settings";
DEVICE_DESKTHING2["CONFIG"] = "config";
})(DEVICE_DESKTHING || (DEVICE_DESKTHING = {}));
// node_modules/@deskthing/types/dist/apps/appTransit.js
var APP_REQUESTS;
(function(APP_REQUESTS2) {
APP_REQUESTS2["DEFAULT"] = "default";
APP_REQUESTS2["GET"] = "get";
APP_REQUESTS2["SET"] = "set";
APP_REQUESTS2["DELETE"] = "delete";
APP_REQUESTS2["OPEN"] = "open";
APP_REQUESTS2["SEND"] = "send";
APP_REQUESTS2["TOAPP"] = "toApp";
APP_REQUESTS2["LOG"] = "log";
APP_REQUESTS2["KEY"] = "key";
APP_REQUESTS2["ACTION"] = "action";
APP_REQUESTS2["TASK"] = "task";
APP_REQUESTS2["STEP"] = "step";
APP_REQUESTS2["SONG"] = "song";
})(APP_REQUESTS || (APP_REQUESTS = {}));
// node_modules/@deskthing/types/dist/meta/logging.js
var LOGGING_LEVELS;
(function(LOGGING_LEVELS2) {
LOGGING_LEVELS2["MESSAGE"] = "message";
LOGGING_LEVELS2["LOG"] = "log";
LOGGING_LEVELS2["WARN"] = "warning";
LOGGING_LEVELS2["ERROR"] = "error";
LOGGING_LEVELS2["DEBUG"] = "debugging";
LOGGING_LEVELS2["FATAL"] = "fatal";
})(LOGGING_LEVELS || (LOGGING_LEVELS = {}));
// node_modules/@deskthing/types/dist/meta/music.js
var SongAbilities;
(function(SongAbilities2) {
SongAbilities2["LIKE"] = "like";
SongAbilities2["SHUFFLE"] = "shuffle";
SongAbilities2["REPEAT"] = "repeat";
SongAbilities2["PLAY"] = "play";
SongAbilities2["PAUSE"] = "pause";
SongAbilities2["STOP"] = "stop";
SongAbilities2["NEXT"] = "next";
SongAbilities2["PREVIOUS"] = "previous";
SongAbilities2["REWIND"] = "rewind";
SongAbilities2["FAST_FORWARD"] = "fast_forward";
SongAbilities2["CHANGE_VOLUME"] = "change_volume";
SongAbilities2["SET_OUTPUT"] = "set_output";
})(SongAbilities || (SongAbilities = {}));
var AUDIO_REQUESTS;
(function(AUDIO_REQUESTS2) {
AUDIO_REQUESTS2["NEXT"] = "next";
AUDIO_REQUESTS2["PREVIOUS"] = "previous";
AUDIO_REQUESTS2["REWIND"] = "rewind";
AUDIO_REQUESTS2["FAST_FORWARD"] = "fast_forward";
AUDIO_REQUESTS2["PLAY"] = "play";
AUDIO_REQUESTS2["PAUSE"] = "pause";
AUDIO_REQUESTS2["STOP"] = "stop";
AUDIO_REQUESTS2["SEEK"] = "seek";
AUDIO_REQUESTS2["LIKE"] = "like";
AUDIO_REQUESTS2["SONG"] = "song";
AUDIO_REQUESTS2["VOLUME"] = "volume";
AUDIO_REQUESTS2["REPEAT"] = "repeat";
AUDIO_REQUESTS2["SHUFFLE"] = "shuffle";
AUDIO_REQUESTS2["REFRESH"] = "refresh";
})(AUDIO_REQUESTS || (AUDIO_REQUESTS = {}));
var SongEvent;
(function(SongEvent3) {
SongEvent3["GET"] = "get";
SongEvent3["SET"] = "set";
})(SongEvent || (SongEvent = {}));
// node_modules/@deskthing/types/dist/deskthing/deskthingTransit.js
var DESKTHING_DEVICE;
(function(DESKTHING_DEVICE2) {
DESKTHING_DEVICE2["GLOBAL_SETTINGS"] = "global_settings";
DESKTHING_DEVICE2["MAPPINGS"] = "button_mappings";
DESKTHING_DEVICE2["CONFIG"] = "configuration";
DESKTHING_DEVICE2["GET"] = "get";
DESKTHING_DEVICE2["ERROR"] = "error";
DESKTHING_DEVICE2["PONG"] = "pong";
DESKTHING_DEVICE2["PING"] = "ping";
DESKTHING_DEVICE2["SETTINGS"] = "settings";
DESKTHING_DEVICE2["APPS"] = "apps";
DESKTHING_DEVICE2["TIME"] = "time";
DESKTHING_DEVICE2["HEARTBEAT"] = "heartbeat";
DESKTHING_DEVICE2["META_DATA"] = "meta_data";
DESKTHING_DEVICE2["MUSIC"] = "music";
DESKTHING_DEVICE2["ICON"] = "icon";
})(DESKTHING_DEVICE || (DESKTHING_DEVICE = {}));
var DESKTHING_EVENTS;
(function(DESKTHING_EVENTS2) {
DESKTHING_EVENTS2["MESSAGE"] = "message";
DESKTHING_EVENTS2["DATA"] = "data";
DESKTHING_EVENTS2["APPDATA"] = "appdata";
DESKTHING_EVENTS2["CALLBACK_DATA"] = "callback-data";
DESKTHING_EVENTS2["START"] = "start";
DESKTHING_EVENTS2["STOP"] = "stop";
DESKTHING_EVENTS2["PURGE"] = "purge";
DESKTHING_EVENTS2["INPUT"] = "input";
DESKTHING_EVENTS2["ACTION"] = "action";
DESKTHING_EVENTS2["CONFIG"] = "config";
DESKTHING_EVENTS2["SETTINGS"] = "settings";
DESKTHING_EVENTS2["TASKS"] = "tasks";
DESKTHING_EVENTS2["CLIENT_STATUS"] = "client_status";
})(DESKTHING_EVENTS || (DESKTHING_EVENTS = {}));
// src/emulator/services/logger.ts
var Logger = class {
static shouldLog(msgLevel) {
const levels = ["silent", "error", "warn", "info", "debug"];
const msgLevelIndex = levels.indexOf(msgLevel);
const configLevelIndex = levels.indexOf(DeskThingConfig.development.logging.level);
if (msgLevelIndex === -1 || configLevelIndex === -1) return true;
return msgLevelIndex <= configLevelIndex;
}
static log(level, ...args) {
const config = DeskThingConfig.development.logging;
const loggingLevel = config.level;
const prefix = config.prefix;
if (this.shouldLog(level)) {
switch (level) {
case "debug":
console.debug("\x1B[36m%s\x1B[0m", `${prefix} ${args[0]}`, ...args.slice(1));
break;
case "info":
console.info("\x1B[90m%s\x1B[0m", `${prefix} ${args[0]}`, ...args.slice(1));
break;
case "warn":
console.warn("\x1B[33m%s\x1B[0m", `${prefix} ${args[0]}`, ...args.slice(1));
break;
case "error":
console.error("\x1B[31m%s\x1B[0m", `${prefix} ${args[0]}`, ...args.slice(1));
break;
default:
console.log(`${prefix} ${args[0]}`, ...args.slice(1));
}
}
}
static debug(...args) {
this.log("debug", ...args);
}
static error(...args) {
this.log("error", ...args);
}
static info(...args) {
this.log("info", ...args);
}
static warn(...args) {
this.log("warn", ...args);
}
static table(data) {
console.table(data);
}
/**
* Client logs will always log
* @param type
* @param message
* @param data
*/
static clientLog(type, message, data) {
const prefix = `[App ${type.trim()}] `;
const dataStr = data ? ` ${JSON.stringify(data)}` : "";
switch (type) {
case LOGGING_LEVELS.LOG:
case "info":
console.log("\x1B[90m%s\x1B[0m", prefix + message + dataStr);
break;
case LOGGING_LEVELS.ERROR:
console.log("\x1B[31m%s\x1B[0m", prefix + message + dataStr);
break;
case LOGGING_LEVELS.WARN:
case "warn":
console.log("\x1B[33m%s\x1B[0m", prefix + message + dataStr);
break;
case LOGGING_LEVELS.MESSAGE:
console.log("\x1B[32m%s\x1B[0m", prefix + message + dataStr);
break;
case LOGGING_LEVELS.DEBUG:
case "debug":
console.log("\x1B[36m%s\x1B[0m", prefix + message + dataStr);
break;
default:
console.log("[CLIENT LOGGING: ]", type, message, data);
}
}
};
// src/emulator/server/serverMessageBus.ts
import WebSocket, { WebSocketServer } from "ws";
var ServerMessageBus = class {
static subscribers = /* @__PURE__ */ new Map();
static ws;
static initialize(port = 8080) {
if (this.ws) {
this.ws.close();
}
this.ws = new WebSocketServer({ port });
this.ws.on("connection", (socket) => {
socket.on("message", (message) => {
const { event, data } = JSON.parse(message.toString());
this.notify(event, data);
});
});
}
/**
* Notifies local listeners
* @param event
* @param data
*/
static notify(event, data) {
if (this.subscribers.has(event)) {
this.subscribers.get(event)?.forEach((callback) => callback(data));
}
}
static subscribe(event, callback) {
if (!this.subscribers.has(event)) {
this.subscribers.set(event, []);
}
this.subscribers.get(event)?.push(callback);
return () => this.unsubscribe(event, callback);
}
/**
* Notifies the websocket
* @param event
* @param data
*/
static publish(event, data) {
this.ws.clients.forEach((client) => {
if (client?.readyState === WebSocket.OPEN) {
Logger.debug("Sending data through messageBus", event, data);
client.send(JSON.stringify({ event, data }));
} else {
console.error("Unable to send data because readystate of client is ", client?.readyState);
}
});
}
static unsubscribe(event, callback) {
const callbacks = this.subscribers.get(event);
const index = callbacks?.indexOf(callback);
if (callbacks && index !== void 0 && index > -1) {
callbacks.splice(index, 1);
}
}
};
// src/emulator/client/callbackService.ts
var CallbackService = class {
static handleCallback(req, res) {
try {
const url = new URL(req.url, `http://${req.headers.host}`);
Logger.debug("Handling callback with URL: ", url);
const code = url.searchParams.get("code");
const appName = url.pathname.split("/callback/")[1];
if (!code || !appName) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(
JSON.stringify({ error: "Missing code or app name parameter" })
);
return;
}
ServerMessageBus.notify("auth:callback", {
code,
appName
});
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ success: true }));
} catch (error) {
Logger.error("Error handling callback:", error);
res.writeHead(500, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "Internal server error" }));
}
}
};
// src/emulator/client/client.ts
var DevClient = class {
deskthingPath;
constructor() {
this.deskthingPath = join2(process.cwd(), "deskthing");
if (!existsSync2(this.deskthingPath)) {
Logger.warn(`DeskThing path does not exist: ${this.deskthingPath}`);
Logger.warn("Please run `npx @deskthing/cli update --noOverwrite` to set up the environment.");
}
}
async start() {
const __dirname = fileURLToPath(new URL2(".", import.meta.url));
const staticPath = join2(__dirname, "./template");
Logger.debug(`Static files will be served from: ${staticPath}`);
const mimeTypes = {
".html": "text/html",
".js": "text/javascript",
".css": "text/css",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".json": "application/json",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".eot": "application/vnd.ms-fontobject",
".otf": "font/otf"
};
const server = createServer((req, res) => {
if (!["GET", "POST"].includes(req.method || "")) {
res.statusCode = 405;
res.end("Method Not Allowed");
return;
}
const url = new URL2(req.url || "/", `http://${req.headers.host}`);
let urlPath = url.pathname;
const queryParams = url.searchParams;
Logger.debug(`${req.method} ${urlPath}`);
if (this.handleSpecialRoutes(req, res, urlPath, queryParams)) {
return;
}
if (this.handleClientRoutes(req, res, urlPath, staticPath, mimeTypes)) {
return;
}
if (this.handleResourceRoutes(req, res, urlPath)) {
return;
}
if (this.handleProxyRoutes(req, res, urlPath, queryParams)) {
return;
}
if (urlPath === "/") {
urlPath = "/index.html";
}
const safePath = normalize(urlPath).replace(/^(\.\.(\/|\\|$))+/, "");
const filePath = join2(staticPath, safePath);
Logger.debug(`Serving ${urlPath} from template`);
this.serveStaticFile(res, filePath, mimeTypes, () => {
const indexPath = join2(staticPath, "index.html");
this.serveStaticFile(res, indexPath, mimeTypes, () => {
res.statusCode = 404;
res.end("Not Found");
});
});
});
const clientPort = DeskThingConfig.development.client.clientPort;
server.listen(clientPort, () => {
Logger.info(
`\x1B[36m\u{1F680} Development Server is running at http://localhost:${clientPort}\x1B[0m`
);
Logger.info(
`\x1B[33m\u{1F504} Callback Server is running at http://localhost:${clientPort}/callback \x1B[0m`
);
});
}
handleSpecialRoutes(req, res, urlPath, queryParams) {
if (urlPath.startsWith("/callback")) {
CallbackService.handleCallback(req, res);
return true;
}
if (urlPath === "/config") {
try {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(DeskThingConfig.development.client));
return true;
} catch (err) {
Logger.error("Error serving config:", err);
res.statusCode = 500;
res.end("Internal Server Error");
return true;
}
}
return false;
}
handleClientRoutes(req, res, urlPath, staticPath, mimeTypes) {
if (urlPath === "/manifest.json") {
res.writeHead(302, { "Location": "/client/manifest.json" });
res.end();
return true;
}
if (urlPath === "/client/manifest.json") {
const clientIp = req.headers.host?.split(":")[0] || "localhost";
const clientPort = DeskThingConfig.development.client.clientPort;
const mockManifest = {
id: "mock-client",
name: "Mock DeskThing Client",
short_name: "MockClient",
description: "Development mock client for DeskThing",
version: "1.0.0",
author: "DeskThing Dev",
repository: "https://github.com/deskthing/deskthing",
context: {
ip: clientIp,
port: clientPort,
method: "LAN",
id: "Desktop",
name: "Desktop"
},
connectionId: this.generateConnectionId(),
reactive: true,
compatibility: {
server: "1.0.0",
app: "1.0.0"
}
};
Logger.info("Client connected via manifest request");
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(mockManifest));
return true;
}
if (urlPath.startsWith("/client")) {
const subPath = urlPath.substring(7) || "/index.html";
const templateFilePath = join2(staticPath, subPath);
this.serveStaticFile(res, templateFilePath, mimeTypes, () => {
const templateIndexPath = join2(staticPath, "index.html");
this.serveStaticFile(res, templateIndexPath, mimeTypes, () => {
res.statusCode = 404;
res.end("App not found");
});
});
return true;
}
return false;
}
handleResourceRoutes(req, res, urlPath) {
if (urlPath.startsWith("/icons")) {
const iconPath = urlPath.substring(6);
const fullIconPath = join2(this.deskthingPath, "icons", iconPath);
this.serveStaticFile(res, fullIconPath, {}, () => {
res.statusCode = 404;
res.end("Icon not found");
}, {
maxAge: "1d",
immutable: true,
etag: true,
lastModified: true
});
return true;
}
if (urlPath.startsWith("/resource/icons")) {
const iconPath = urlPath.substring(15);
const fullIconPath = join2(this.deskthingPath, "icons", iconPath);
this.serveStaticFile(res, fullIconPath, {}, () => {
res.statusCode = 404;
res.end("Icon not found");
}, {
maxAge: "1d",
immutable: true,
etag: true,
lastModified: true
});
return true;
}
const imageMatch = urlPath.match(/^\/resource\/image\/([^\/]+)\/([^\/]+)$/);
if (imageMatch) {
const [, , imageName] = imageMatch;
if (!imageName) {
res.statusCode = 400;
res.end("Image name is required");
return true;
}
const imagePath = join2(this.deskthingPath, "images", imageName);
this.serveStaticFile(res, imagePath, {}, () => {
res.statusCode = 404;
res.end("Image not found");
});
return true;
}
return false;
}
handleProxyRoutes(req, res, urlPath, queryParams) {
const proxyFetchMatch = urlPath.match(/^\/proxy\/fetch\/(.+)$/);
if (proxyFetchMatch) {
const targetUrl = decodeURIComponent(proxyFetchMatch[1]);
this.proxyRequest(targetUrl, res);
return true;
}
if (urlPath === "/proxy/v1") {
const url = queryParams.get("url");
if (!url) {
res.statusCode = 400;
res.end("Missing url query parameter");
return true;
}
Logger.debug(`Proxying resource from: ${url}`);
this.proxyRequest(url, res);
return true;
}
return false;
}
serveStaticFile(res, filePath, mimeTypes, onNotFound, cacheOptions) {
if (existsSync2(filePath) && statSync(filePath).isFile()) {
try {
const ext = extname(filePath).toLowerCase();
const contentType = mimeTypes[ext] || this.getDefaultMimeType(ext);
const fileContent = readFileSync(filePath);
const headers = { "Content-Type": contentType };
if (cacheOptions) {
if (cacheOptions.maxAge) {
headers["Cache-Control"] = `max-age=${this.parseCacheMaxAge(cacheOptions.maxAge)}${cacheOptions.immutable ? ", immutable" : ""}`;
}
if (cacheOptions.etag) {
headers["ETag"] = `"${this.generateETag(fileContent)}"`;
}
if (cacheOptions.lastModified) {
const stats = statSync(filePath);
headers["Last-Modified"] = stats.mtime.toUTCString();
}
}
res.writeHead(200, headers);
res.end(fileContent);
} catch (err) {
Logger.error(`Error serving ${filePath}:`, err);
res.statusCode = 500;
res.end("Internal Server Error");
}
} else {
onNotFound();
}
}
async proxyRequest(targetUrl, res) {
try {
Logger.debug(`Proxying request to: ${targetUrl}`);
const response = await fetch(targetUrl);
if (!response.ok) {
res.statusCode = response.status;
res.end(`Upstream resource responded with ${response.status}`);
return;
}
const contentType = response.headers.get("content-type");
if (contentType) {
res.setHeader("Content-Type", contentType);
}
["content-length", "content-encoding", "cache-control"].forEach((header) => {
const value = response.headers.get(header);
if (value) {
res.setHeader(header, value);
}
});
if (!response.body) {
res.statusCode = 204;
res.end();
return;
}
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
res.write(value);
}
res.end();
} catch (error) {
Logger.error("Error proxying resource:", error);
res.statusCode = 500;
res.end(`Error fetching resource: ${error instanceof Error ? error.message : String(error)}`);
}
}
getDefaultMimeType(ext) {
const defaultMimeTypes = {
".html": "text/html",
".js": "text/javascript",
".css": "text/css",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".json": "application/json",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".eot": "application/vnd.ms-fontobject",
".otf": "font/otf"
};
return defaultMimeTypes[ext] || "application/octet-stream";
}
parseCacheMaxAge(maxAge) {
if (maxAge.endsWith("d")) {
return parseInt(maxAge.slice(0, -1)) * 24 * 60 * 60;
}
if (maxAge.endsWith("h")) {
return parseInt(maxAge.slice(0, -1)) * 60 * 60;
}
if (maxAge.endsWith("m")) {
return parseInt(maxAge.slice(0, -1)) * 60;
}
return parseInt(maxAge) || 0;
}
generateETag(content) {
const crypto = __require("crypto");
return crypto.createHash("md5").update(content).digest("hex");
}
generateConnectionId() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c == "x" ? r : r & 3 | 8;
return v.toString(16);
});
}
getDeviceType(userAgent, ip, port) {
if (!userAgent) {
return {
method: "LAN" /* LAN */,
ip,
port,
id: "Unknown" /* Unknown */,
name: "unknown"
};
}
userAgent = userAgent.toLowerCase();
const deviceMap = {
// Desktops
linux: { id: "Desktop" /* Desktop */, name: "linux" },
win: { id: "Desktop" /* Desktop */, name: "windows" },
mac: { id: "Desktop" /* Desktop */, name: "mac" },
chromebook: { id: "Desktop" /* Desktop */, name: "chromebook" },
// Tablets
ipad: { id: "Tablet" /* Tablet */, name: "tablet" },
webos: { id: "Tablet" /* Tablet */, name: "webos" },
kindle: { id: "Tablet" /* Tablet */, name: "kindle" },
// Mobile
iphone: { id: "Iphone" /* Iphone */, name: "iphone" },
"firefox os": { id: "Iphone" /* Iphone */, name: "firefox-os" },
blackberry: { id: "Iphone" /* Iphone */, name: "blackberry" },
"windows phone": { id: "Iphone" /* Iphone */, name: "windows-phone" }
};
if (userAgent.includes("android")) {
return {
method: "LAN" /* LAN */,
ip,
port,
id: userAgent.includes("mobile") ? "Iphone" /* Iphone */ : "Tablet" /* Tablet */,
name: userAgent.includes("mobile") ? "android" : "tablet"
};
}
const matchedDevice = Object.entries(deviceMap).find(([key]) => userAgent.includes(key));
if (matchedDevice) {
return { method: "LAN" /* LAN */, ip, port, ...matchedDevice[1] };
}
return {
method: "LAN" /* LAN */,
ip,
port,
id: "Unknown" /* Unknown */,
name: "unknown"
};
}
};
// src/emulator/server/server.ts
import { watch as fsWatch } from "fs";
// src/emulator/server/manifestDetails.ts
import path from "path";
import fs from "fs";
var getManifestDetails = () => {
const manifestPath = path.join(process.cwd(), "deskthing", "manifest.json");
const altManifestPath = path.join(process.cwd(), "public", "manifest.json");
let finalPath = manifestPath;
if (!fs.existsSync(finalPath)) {
finalPath = altManifestPath;
Logger.error("\u274C Failed to load manifest.json from deskthing/manifest.json, trying public/manifest.json");
if (!fs.existsSync(finalPath)) {
throw new Error("\x1B[31m\u274C Failed to load manifest.json from both locations\x1B[0m");
} else {
Logger.info("\x1B[32m\u2705 Successfully loaded manifest.json from public/manifest.json\x1B[0m");
}
}
const manifest = JSON.parse(fs.readFileSync(finalPath, "utf8"));
return {
...manifest,
// catch all for any new fields added in the future
id: manifest.id,
isWebApp: manifest.isWebApp,
requires: manifest.requires,
label: manifest.label,
version: manifest.version,
description: manifest.description,
author: manifest.author,
platforms: manifest.platforms,
homepage: manifest.homepage,
version_code: manifest.version_code,
compatible_server: manifest.compatible_server,
compatible_client: manifest.compatible_client,
repository: manifest.repository,
tags: manifest.tags,
requiredVersions: manifest.requiredVersions
};
};
// src/emulator/services/settingService.ts
var SettingService = class {
static currentSettings = {};
static sendSettings() {
ServerMessageBus.publish("client:request", {
type: DESKTHING_DEVICE.SETTINGS,
payload: this.currentSettings,
app: "client"
});
ServerMessageBus.notify("app:data", {
type: "settings",
payload: this.currentSettings
});
}
static getSettings() {
return this.currentSettings || {};
}
static async setSettings(appSettings) {
if (!this.currentSettings) {
this.currentSettings = {};
}
this.currentSettings = { ...this.currentSettings, ...appSettings };
const rebuiltSettings = Object.fromEntries(
Object.entries(appSettings).map(([key, setting]) => {
return [
key,
{
...setting,
value: DeskThingConfig.development?.server?.mockData?.settings[key] ?? setting.value
}
];
})
);
this.currentSettings = { ...this.currentSettings, ...rebuiltSettings };
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
Logger.debug(
"Rebuilt Settings with mocked data. Setting to: ",
rebuiltSettings
);
this.sendSettings();
}
static async initSettings(settings) {
if (!this.currentSettings) {
this.currentSettings = {};
}
for (const key in settings) {
const newSetting = settings[key];
const existingSetting = this.currentSettings[key];
if (!existingSetting) {
this.currentSettings[key] = newSetting;
continue;
}
if (newSetting && typeof newSetting.version !== "undefined") {
if (!existingSetting.version || existingSetting.version !== newSetting.version) {
this.currentSettings[key] = newSetting;
}
} else {
}
}
this.setSettings(this.currentSettings);
}
static delSettings(settingIds) {
if (!this.currentSettings) {
Logger.warn("No settings to delete from");
return;
}
settingIds.forEach((id) => {
delete this.currentSettings[id];
});
this.sendSettings();
}
static updateSettings(settings) {
this.currentSettings = { ...this.currentSettings, ...settings };
this.sendSettings();
}
};
// src/emulator/services/serverService.ts
var ServerService = class {
constructor() {
ServerMessageBus.subscribe("client:request", (data) => {
this.handleClientRequest(data);
});
}
handleClientRequest(data) {
Logger.debug(`Received request: ${JSON.stringify(data)}`);
switch (data.type) {
case "getData":
this.sendServerData();
break;
case "getManifest":
this.sendManifestData();
break;
case "getSettings":
this.sendSettingsData();
break;
case "setSettings":
SettingService.updateSettings(data.payload);
break;
case "getClientConfig":
this.sendClientConfig();
break;
case "log":
Logger.clientLog(data.level, "[CLIENT LOG] " + data.message);
break;
default:
if (data.type) {
ServerMessageBus.publish("client:response", {
type: data.type,
payload: data.payload
});
}
}
}
sendToClient(data) {
ServerMessageBus.publish("client:request", {
type: data.type,
payload: data.payload,
request: data.request,
app: data.app,
clientId: data.clientId
});
}
sendServerData() {
const data = getServerData();
ServerMessageBus.publish("client:response", {
type: "data",
payload: data.data
});
}
sendManifestData() {
const data = getManifestDetails();
ServerMessageBus.publish("client:response", {
type: "manifest",
payload: data
});
}
sendSettingsData() {
const settings = SettingService.getSettings();
ServerMessageBus.publish("client:response", {
type: "settings",
payload: settings
});
}
async sendClientConfig() {
const clientConfig = DeskThingConfig.development.client;
ServerMessageBus.publish("client:response", {
type: "clientConfig",
payload: clientConfig
// Send the client section of the config
});
}
};
// src/emulator/server/coms.ts
import { exec } from "child_process";
// src/emulator/services/musicService.ts
var MusicService = class {
refreshInterval = null;
currentSong = null;
start() {
this.stop();
const interval = DeskThingConfig.development.server.refreshInterval * 1e3;
if (interval <= 0) {
Logger.debug("Music service refresh disabled (interval <= 0)");
return;
}
Logger.debug(`Starting music service with ${interval}ms refresh interval`);
this.refreshInterval = setInterval(() => {
Logger.debug(`Refreshing music data...`);
ServerMessageBus.notify("app:data", {
type: "get",
request: "refresh"
});
}, interval);
}
sendSong() {
ServerMessageBus.publish("client:request", {
type: DESKTHING_DEVICE.MUSIC,
payload: this.currentSong,
app: "client"
});
}
setSong(song) {
this.currentSong = song;
this.sendSong();
}
stop() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
Logger.debug("Music service stopped");
}
}
};
// src/emulator/server/coms.ts
var serverService = new ServerService();
var Data = {
data: {}
};
var getServerData = () => Data;
var handleDataFromApp = async (app, appData) => {
if (Object.values(APP_REQUESTS).includes(appData.type)) {
try {
const handler = handleData[appData.type] || handleData["default"];
const requestHandler = handler[appData.request || "default"] || handler["default"];
if (!requestHandler) {
Logger.warn(
`No handler found for request ${appData.request} in ${appData.type}`
);
handleRequestMissing(app, appData);
return;
}
requestHandler(app, appData);
} catch (error) {
Logger.error("Error in handleDataFromApp:", error);
}
} else {
Logger.error(
"Unknown event type:",
appData.type,
" with request ",
appData.request
);
}
};
var handleRequestMissing = (app, appData) => {
Logger.warn(
`[handleComs]: App ${app} sent unknown data type: ${appData.type} and request: ${appData.request}, with payload ${appData.payload ? JSON.stringify(appData.payload).length > 1e3 ? "[Large Payload]" : JSON.stringify(appData.payload) : "undefined"}`,
app
);
};
var handleRequestSetSettings = async (app, appData) => {
Logger.info("Simulating adding settings");
Logger.debug("Settings being added: ", appData.payload);
const appSettings = appData.payload;
await SettingService.setSettings(appSettings);
};
var handleRequestInitSettings = async (app, appData) => {
Logger.info("Simulating initializing settings");
Logger.debug("Settings being initialized: ", appData.payload);
const appSettings = appData.payload;
await SettingService.initSettings(appSettings);
};
var handleRequestSetData = async (app, appData) => {
Logger.info("Simulating adding data");
Logger.debug("Data being added: ", Data.data);
Data.data = { ...Data.data, ...appData.payload };
};
var handleRequestSetAppData = async (app, appData) => {
if (!appData.payload) return;
const { settings, ...data } = appData.payload;
SettingService.updateSettings(settings);
Data = {
data: { ...Data.data, ...data }
};
};
var handleRequestOpen = async (_app, appData) => {
Logger.debug(`[handleOpen]: Opening ${appData.payload}`);
const encodedUrl = encodeURI(appData.payload);
Logger.info(
`[openUrl]: If your browser doesn't automatically open, try manually clicking the url:
${encodedUrl}
`
);
try {
if (process.platform === "win32") {
exec(`start "" "${encodedUrl}"`);
} else if (process.platform === "darwin") {
exec(`open '${encodedUrl}'`);
} else {
exec(`xdg-open '${encodedUrl}'`);
}
Logger.debug(`URL opening command executed successfully`);
} catch (error) {
Logger.error(`Error opening URL: ${error.message}`);
}
};
var handleRequestLog = (app, appData) => {
Logger.clientLog(appData.request, typeof appData.payload === "object" ? JSON.stringify(appData.payload) : appData.payload);
};
var handleRequestKeyAdd = async (app, appData) => {
Logger.warn("Key data isn't supported");
Logger.debug("Received", appData.payload);
};
var handleRequestKeyRemove = async (app, appData) => {
Logger.warn("Key data isn't supported");
Logger.debug("Received", appData.payload);
};
var handleRequestKeyTrigger = async (app, appData) => {
Logger.warn("Key data isn't supported");
Logger.debug("Received", appData.payload);
};
var handleRequestActionRun = async (app, appData) => {
Logger.warn("Action data isn't supported");
Logger.debug("Received", appData.payload);
};
var handleRequestActionUpdate = async (app, appData) => {
Logger.warn("Action data isn't supported");
Logger.debug("Received", appData.payload);
};
var handleRequestActionRemove = async (app, appData) => {
Logger.warn("Action data isn't supported");
Logger.debug("Received", appData.payload);
};
var handleRequestActionAdd = async (app, appData) => {
Logger.warn("Action data isn't supported");
Logger.debug("Received", appData.payload);
};
var handleRequestGetData = async (app) => {
Logger.info(`[handleAppData]: App is requesting data`);
Logger.debug(`[handleAppData]: Returning Data:`, Data.data);
ServerMessageBus.notify("app:data", { type: "data", payload: Data.data });
};
var handleRequestDelData = async (app, appData) => {
Logger.info(
`[handleAppData]: ${app} is deleting data: ${appData.payload.toString()}`
);
if (!appData.payload || typeof appData.payload !== "string" && !Array.isArray(appData.payload)) {
Logger.info(
`[handleAppData]: Cannot delete data because ${appData.payload.toString()} is not a string or string[]`
);
return;
}
Data.data = Object.fromEntries(
Object.entries(Data.data).filter(([key]) => !appData.payload.includes(key))
);
};
var handleRequestGetConfig = async (app) => {
ServerMessageBus.notify("app:data", { type: "config", payload: {} });
Logger.warn(
`[handleAppData]: ${app} tried accessing "Config" data type which is depreciated and no longer in use!`
);
};
var handleRequestGetSettings = async (app) => {
Logger.info(`[handleAppData]: App is requesting settings`);
const settings = SettingService.getSettings();
Logger.debug(`[handleAppData]: Returning Settings:`, settings);
ServerMessageBus.notify("app:data", {
type: "settings",
payload: settings
});
};
var handleRequestDelSettings = async (app, appData) => {
Logger.info(
`[handleAppData]: ${app} is deleting settings: ${appData.payload.toString()}`
);
if (!appData.payload || typeof appData.payload !== "string" && !Array.isArray(appData.payload)) {
Logger.warn(
`[handleAppData]: Cannot delete settings because ${appData.payload.toString()} is not a string or string[]`
);
return;
}
SettingService.delSettings(Array.isArray(appData.payload) ? appData.payload : [appData.payload]);
};
var handleRequestGetInput = async (app, appData) => {
const templateData = Object.keys(appData.payload).reduce((acc, key) => {
acc[key] = "arbData";
return acc;
}, {});
Logger.info(`[handleAppData]: App is requesting input`);
Logger.debug(`[handleAppData]: Returning Input:`, templateData);
ServerMessageBus.notify("app:data", { type: "input", payload: templateData });
};
var handleConnectionsRequest = async (app) => {
Logger.info(`[handleAppData]: App is requesting connections`);
const sampleClient = {
clientId: "sample-id",
connected: false,
meta: {},
identifiers: {
adb: {
id: "sample-provider",
capabilities: [],
method: ClientConnectionMethod.Unknown,
providerId: "sample-provider",
connectionState: ConnectionState.Established,
active: false
}
},
connectionState: ConnectionState.Disconnected,
timestamp: Date.now(),
currentApp: app
};
Logger.debug(`[handleAppData]: Returning Connections:`, sampleClient);
ServerMessageBus.notify("app:data", {
type: DESKTHING_EVENTS.CLIENT_STATUS,
request: "connections",
payload: [sampleClient]
});
};
var handleGet = {
data: handleRequestGetData,
config: handleRequestGetConfig,
settings: handleRequestGetSettings,
input: handleRequestGetInput,
connections: handleConnectionsRequest
};
var handleSet = {
settings: handleRequestSetSettings,
data: handleRequestSetData,
appData: handleRequestSetAppData,
default: handleRequestMissing,
"settings-init": handleRequestInitSettings
};
var handleDelete = {
settings: handleRequestDelSettings,
data: handleRequestDelData
};
var handleOpen = {
default: handleRequestOpen
};
var handleSendToClient = {
default: async (app, appData) => {
serverService.sendToClient({
app: appData.payload.app || app,
type: appData.payload.type || "",
payload: appData.payload.payload ?? "",
request: appData.payload.request || "",
clientId: appData.payload.clientId || ""
});
}
};
var handleSendToApp = {
default: async (app, appData) => {
Logger.info("Sent data ", appData.payload, " to other a