@fdm-monster/server
Version:
FDM Monster is a bulk OctoPrint manager to set up, configure and monitor 3D printers. Our aim is to provide extremely optimized websocket performance and reliability.
350 lines (349 loc) • 14.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "MoonrakerWebsocketAdapter", {
enumerable: true,
get: function() {
return MoonrakerWebsocketAdapter;
}
});
const _serverconstants = require("../../server.constants");
const _urlutils = require("../../utils/url.utils");
const _octoprintwebsocketadapter = require("../octoprint/octoprint-websocket.adapter");
const _moonrakerconstants = require("./constants/moonraker.constants");
const _socketstatetype = require("../../shared/dtos/socket-state.type");
const _apistatetype = require("../../shared/dtos/api-state.type");
const _prettyprintutils = require("../../utils/pretty-print.utils");
const _lodash = /*#__PURE__*/ _interop_require_default(require("lodash"));
const _printerapiinterface = require("../printer-api.interface");
const _websocketrpcextendedadapter = require("../../shared/websocket-rpc-extended.adapter");
const _normalizeurl = require("../../utils/normalize-url");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
class MoonrakerWebsocketAdapter extends _websocketrpcextendedadapter.WebsocketRpcExtendedAdapter {
moonrakerClient;
eventEmitter2;
configService;
serverVersion;
printerType;
socketState;
lastMessageReceivedTimestamp;
stateUpdated;
stateUpdateTimestamp;
apiStateUpdated;
apiStateUpdateTimestamp;
apiState;
login;
printerId;
refreshPrinterObjectsInterval;
printerObjects;
socketURL;
constructor(loggerFactory, moonrakerClient, eventEmitter2, configService, serverVersion){
super(loggerFactory), this.moonrakerClient = moonrakerClient, this.eventEmitter2 = eventEmitter2, this.configService = configService, this.serverVersion = serverVersion, this.printerType = 1, this.socketState = _socketstatetype.SOCKET_STATE.unopened, this.lastMessageReceivedTimestamp = null, this.stateUpdated = false, this.stateUpdateTimestamp = null, this.apiStateUpdated = false, this.apiStateUpdateTimestamp = null, this.apiState = _apistatetype.API_STATE.unset, this.printerObjects = {
eventtime: null,
status: null
};
this.logger = loggerFactory(MoonrakerWebsocketAdapter.name);
}
get _debugMode() {
return this.configService.get(_serverconstants.AppConstants.debugSocketStatesKey, _serverconstants.AppConstants.defaultDebugSocketStates) === "true";
}
get subscriptionObjects() {
return {
pause_resume: [],
idle_timeout: [],
print_stats: [],
heaters: [],
heater_bed: [],
extruder: [],
display_status: [],
webhooks: [],
virtual_sdcard: [],
gcode_move: [],
stepper_enable: [],
fan: [],
motion_report: [],
system_stats: []
};
}
needsReopen() {
return false;
}
needsSetup() {
return this.socketState === _socketstatetype.SOCKET_STATE.unopened;
}
needsReauth() {
return false;
}
async reauthSession() {}
registerCredentials(socketLogin) {
const { printerId, loginDto } = socketLogin;
this.printerId = printerId;
this.login = loginDto;
const httpUrlString = (0, _normalizeurl.normalizeUrl)(this.login.printerURL);
const httpUrl = new URL(httpUrlString);
const httpUrlPath = httpUrl.pathname;
const wsUrl = (0, _urlutils.httpToWsUrl)(httpUrlString);
wsUrl.pathname = (httpUrlPath ?? "/") + "websocket";
this.socketURL = wsUrl;
}
open() {
if (this.socket) {
throw new Error(`Socket already exists by printerId, ignoring open request`);
}
super.open(this.socketURL);
}
close() {
clearInterval(this.refreshPrinterObjectsInterval);
super.close();
}
async setupSocketSession() {
this.resetSocketState();
await this.moonrakerClient.getApiVersion(this.login).catch((e)=>{
this.setSocketState("aborted");
this.logger.error(`Printer (${this.printerId}) network or transport error, marking it as unreachable; ${e}`);
this.setApiState("noResponse");
throw e;
});
this.setApiState(_apistatetype.API_STATE.responding);
await this.updateCurrentStateSafely();
if (this.refreshPrinterObjectsInterval) {
clearInterval(this.refreshPrinterObjectsInterval);
}
this.refreshPrinterObjectsInterval = setInterval(async ()=>{
await this.updateCurrentStateSafely();
}, 15000);
}
emitEventSync(event, payload) {
if (!this.eventEmittingAllowed) {
return;
}
this.eventEmitter2.emit((0, _moonrakerconstants.moonrakerEvent)(event), {
event,
payload,
printerId: this.printerId
});
}
resetSocketState() {
this.setSocketState("unopened");
this.setApiState("unset");
}
isClosedOrAborted() {
return this.socketState === _socketstatetype.SOCKET_STATE.closed || this.socketState === _socketstatetype.SOCKET_STATE.aborted;
}
async afterOpened(_) {
this.setSocketState(_socketstatetype.SOCKET_STATE.opened);
const response = await this.sendRequest({
jsonrpc: "2.0",
method: "server.connection.identify",
params: {
client_name: "FDM Monster",
version: this.serverVersion,
type: "other",
url: _serverconstants.AppConstants.githubUrl
},
id: 1
});
try {
const query = this.subscriptionObjects;
const result = await this.moonrakerClient.postSubscribePrinterObjects(this.login, response.result.connection_id, query);
this.printerObjects = result.data.result;
await this.emitCurrentEvent(this.printerObjects);
} catch (e) {
const ae = e;
if (ae.isAxiosError) {
if (ae.response?.status === 503) {
this.logger.warn(`Klipper host issue ${(0, _prettyprintutils.PP)(ae.response.data?.error?.message)}`);
} else if (ae.response?.status === 404) {
this.logger.error("Error while afterOpened (404) - usually this means Moonraker is still starting");
}
this.setApiState(_apistatetype.API_STATE.noResponse);
return;
}
this.logger.error("Unknown error while afterOpened");
this.setApiState(_apistatetype.API_STATE.noResponse);
}
}
async onEventMessage(event) {
this.lastMessageReceivedTimestamp = Date.now();
if (this.socketState !== _socketstatetype.SOCKET_STATE.authenticated) {
this.setSocketState("authenticated");
}
const eventName = event.method;
if (this._debugMode) {
this.logger.log(`RX Msg ${eventName} ${JSON.stringify(event.params)?.substring(0, 80)}...`);
}
const payload = event.params?.length ? event.params[0] : undefined;
if (eventName === "notify_service_state_changed") {
if (!event.params) {
this.logger.error("Received 'notify_service_state_changed' but service indicators params were undefined");
return;
}
const serviceChanged = event.params[0];
if (serviceChanged.klipper?.active_state || serviceChanged.klipper_mcu?.active_state || serviceChanged.moonraker?.active_state) {
this.logger.log("Received notify_service_state_changed, reloading Moonraker printer objects");
await this.setupSocketSession();
}
return;
}
if (eventName === "notify_klippy_ready") {
this.logger.log("Received notify_klippy_ready, reloading Moonraker printer objects");
return await this.setupSocketSession();
}
if (eventName === "notify_klippy_disconnected") {
this.logger.log("Received notify_klippy_disconnected, reloading Moonraker printer objects");
return await this.setupSocketSession();
}
if (eventName === "notify_klippy_shutdown") {
this.logger.log("Received notify_klippy_shutdown, reloading Moonraker printer objects");
return await this.setupSocketSession();
}
if (eventName === "notify_status_update") {
if (!event.params) {
this.logger.error("Received 'notify_status_update' but service indicators params were undefined");
return;
}
const [data, eventtime] = event.params;
const subState = Object.keys(data)[0];
if (Object.keys(this.printerObjects.status).includes(subState)) {
this.printerObjects.status = _lodash.default.merge(this.printerObjects.status, data);
this.printerObjects.eventtime = eventtime;
await this.emitCurrentEvent(this.printerObjects);
} else {
this.logger.warn(`Substate ${subState} unknown`);
}
return;
}
await this.emitEvent(eventName, payload);
}
async afterClosed(event) {
this.setSocketState("closed");
delete this.socket;
await this.emitEvent(_octoprintwebsocketadapter.WsMessage.WS_CLOSED, "connection closed");
}
async onError(error) {
this.setSocketState("error");
await this.emitEvent(_octoprintwebsocketadapter.WsMessage.WS_ERROR, error?.length ? error : "connection error");
}
async updateCurrentStateSafely() {
try {
const query = this.subscriptionObjects;
const objects = await this.moonrakerClient.getPrinterObjectsQuery(this.login, query);
this.printerObjects = objects.data.result;
this.setApiState(_apistatetype.API_STATE.responding);
return await this.emitCurrentEvent(this.printerObjects);
} catch (e) {
const castError = e;
if (castError.isAxiosError) {
if (castError?.response?.status == 503) {
this.printerObjects.status = null;
this.printerObjects.eventtime = Date.now();
return await this.emitCurrentEvent(this.printerObjects);
}
this.logger.error("Could not update Moonraker printer objects due to a request error");
this.setApiState(_apistatetype.API_STATE.noResponse);
return;
}
this.logger.error(`Could not update Moonraker current due to an unknown error`);
this.setApiState(_apistatetype.API_STATE.noResponse);
}
}
async emitCurrentEvent(printerObject) {
const originalKlipperObjects = printerObject.status;
const flags = {
operational: false,
printing: false,
cancelling: false,
pausing: false,
paused: false,
resuming: false,
finishing: false,
closedOrError: false,
error: false,
ready: false,
sdReady: false
};
let filename = "";
let printTime = null;
let stateText = "Unset";
let error = "";
let completion = null;
if (originalKlipperObjects != null) {
stateText = originalKlipperObjects.display_status?.message;
if (originalKlipperObjects.print_stats?.state?.length) {
const systemState = originalKlipperObjects.webhooks;
const printState = originalKlipperObjects.print_stats.state;
const idleState = originalKlipperObjects.idle_timeout?.state;
filename = originalKlipperObjects.print_stats.filename;
printTime = originalKlipperObjects.print_stats.print_duration;
flags.operational = systemState.state === "ready";
if (flags.operational) {
flags.printing = printState === "printing";
flags.paused = printState === "paused";
flags.ready = printState === "standby" && idleState !== "Printing";
flags.sdReady = true;
} else {
flags.error = true;
stateText = "Klipper reports: " + (systemState.state ?? "unknown")?.toUpperCase();
}
}
completion = (originalKlipperObjects.display_status?.progress ?? 0) * 100.0;
}
const currentMessage = {
progress: {
printTime,
completion
},
state: {
text: stateText,
error,
flags
},
job: {
file: {
name: filename,
path: filename
}
}
};
await this.emitEvent("notify_status_update", originalKlipperObjects);
await this.emitEvent("current", currentMessage);
}
async emitEvent(event, payload) {
if (!this.eventEmittingAllowed) {
return;
}
await this.eventEmitter2.emitAsync((0, _moonrakerconstants.moonrakerEvent)(event), {
event,
payload,
printerId: this.printerId,
printerType: _printerapiinterface.MoonrakerType
});
}
setSocketState(state) {
this.socketState = state;
this.stateUpdated = true;
this.stateUpdateTimestamp = Date.now();
if (this._debugMode) {
this.logger.log(`${this.printerId} Socket state updated to: ` + state);
}
this.emitEventSync(_octoprintwebsocketadapter.WsMessage.WS_STATE_UPDATED, state);
}
setApiState(state) {
if (state === _apistatetype.API_STATE.globalKey) {
throw new Error("GlobalKey is an invalid WS state for Moonraker");
}
this.apiState = state;
this.apiStateUpdated = true;
this.apiStateUpdateTimestamp = Date.now();
if (this._debugMode) {
this.logger.log(`${this.printerId} API state updated to: ` + state);
}
this.emitEventSync(_octoprintwebsocketadapter.WsMessage.API_STATE_UPDATED, state);
}
}
//# sourceMappingURL=moonraker-websocket.adapter.js.map