// jsonrpc.js - Implementation of JSON-RPC 2.0 protocol for Moonraker communication import { loadConfig } from "../config.js"; import log4js from "log4js"; // Load configuration const config = loadConfig(); const logger = log4js.getLogger("JSON RPC"); logger.level = config.logLevel; export class JsonRPC { constructor() { this.idCounter = 0; this.methods = {}; this.pendingRequests = {}; } // Generate a unique ID for RPC requests generateId() { return this.idCounter++; } // Register a method to handle incoming notifications/responses registerMethod(methodName, callback) { this.methods[methodName] = callback; } // Process incoming messages processMessage(message) { if (message.method && this.methods[message.method]) { // Handle method call or notification this.methods[message.method](message.params); logger.trace(`JSON-RPC notification: ${message.method}`); } else if (message.id !== undefined) { // Handle response to a previous request const rpcPromise = this.pendingRequests[message.id]; if (rpcPromise) { if (message.error) { logger.error(`Error in JSON-RPC response: ${message.error}`); rpcPromise.reject(message.error); } else { logger.debug(`JSON-RPC response: OK`); logger.trace("Result:", message.result); rpcPromise.resolve(message.result); } delete this.pendingRequests[message.id]; } } // If it's a notification without a registered method, ignore it } // Call a method without parameters callMethod(method) { return this.callMethodWithKwargs(method, {}); } // Call a method with parameters callMethodWithKwargs(method, params) { logger.debug(`Calling method: ${method}`); logger.trace("Params:", params); const id = this.generateId(); const request = { jsonrpc: "2.0", method: method, params: params, id: id, }; return new Promise((resolve, reject) => { this.pendingRequests[id] = { resolve, reject }; // The actual sending of the message is done by the WebSocket connection // This just prepares the message and returns a promise if (this.socket) { this.socket.send(JSON.stringify(request)); } else { // If socket is not directly attached to this instance, the caller // is responsible for sending the serialized request this.lastRequest = JSON.stringify(request); } }); } // For external socket handling getLastRequest() { const req = this.lastRequest; this.lastRequest = null; return req; } // Associate a WebSocket with this RPC instance for direct communication setSocket(socket) { this.socket = socket; } }