UNPKG

@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.

321 lines (320 loc) 13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function _export(target, all) { for(var name in all)Object.defineProperty(target, name, { enumerable: true, get: all[name] }); } _export(exports, { OctoPrintMessage: function() { return OctoPrintMessage; }, OctoprintWebsocketAdapter: function() { return OctoprintWebsocketAdapter; }, WsMessage: function() { return WsMessage; }, octoPrintEvent: function() { return octoPrintEvent; }, octoPrintWebsocketEvent: function() { return octoPrintWebsocketEvent; } }); const _httpstatuscodesconstants = require("../../constants/http-status-codes.constants"); const _serverconstants = require("../../server.constants"); const _runtimeexceptions = require("../../exceptions/runtime.exceptions"); const _urlutils = require("../../utils/url.utils"); const _normalizeurl = require("../../utils/normalize-url"); const _websocketadapter = require("../../shared/websocket.adapter"); const _socketstatetype = require("../../shared/dtos/socket-state.type"); const _apistatetype = require("../../shared/dtos/api-state.type"); const _printerapiinterface = require("../printer-api.interface"); const WsMessage = { WS_OPENED: "WS_OPENED", WS_CLOSED: "WS_CLOSED", WS_ERROR: "WS_ERROR", API_STATE_UPDATED: "API_STATE_UPDATED", WS_STATE_UPDATED: "WS_STATE_UPDATED" }; const OctoPrintMessage = { connected: "connected", reauthRequired: "reauthRequired", current: "current", history: "history", event: "event", plugin: "plugin", timelapse: "timelapse", slicingProgress: "slicingProgress" }; const octoPrintWebsocketEvent = (printerId)=>`octoprint.${printerId}`; const octoPrintEvent = (event)=>`octoprint.${event}`; class OctoprintWebsocketAdapter extends _websocketadapter.WebsocketAdapter { octoprintClient; eventEmitter2; configService; printerType; printerId; stateUpdated; stateUpdateTimestamp; socketState; apiStateUpdated; apiStateUpdateTimestamp; apiState; lastMessageReceivedTimestamp; reauthRequired; reauthRequiredTimestamp; login; socketURL; sessionDto; username; refreshPrinterCurrentInterval; constructor(loggerFactory, octoprintClient, eventEmitter2, configService){ super(loggerFactory), this.octoprintClient = octoprintClient, this.eventEmitter2 = eventEmitter2, this.configService = configService, this.printerType = 0, this.stateUpdated = false, this.stateUpdateTimestamp = null, this.socketState = _socketstatetype.SOCKET_STATE.unopened, this.apiStateUpdated = false, this.apiStateUpdateTimestamp = null, this.apiState = _apistatetype.API_STATE.unset, this.lastMessageReceivedTimestamp = null, this.reauthRequired = false, this.reauthRequiredTimestamp = null; this.logger = loggerFactory(OctoprintWebsocketAdapter.name); } get _debugMode() { return this.configService.get(_serverconstants.AppConstants.debugSocketStatesKey, _serverconstants.AppConstants.defaultDebugSocketStates) === "true"; } needsReopen() { const isApiOnline = this.apiState === _apistatetype.API_STATE.responding; return isApiOnline && (this.socketState === _socketstatetype.SOCKET_STATE.closed || this.socketState === _socketstatetype.SOCKET_STATE.error); } needsSetup() { return this.socketState === _socketstatetype.SOCKET_STATE.unopened; } needsReauth() { return this.reauthRequired; } isClosedOrAborted() { return this.socketState === _socketstatetype.SOCKET_STATE.closed || this.socketState === _socketstatetype.SOCKET_STATE.aborted; } 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 ?? "/") + "sockjs/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.refreshPrinterCurrentInterval); super.close(); } async sendThrottle(throttle = _serverconstants.AppConstants.defaultSocketThrottleRate) { return await this.sendMessage(JSON.stringify({ throttle })); } async reauthSession() { this.logger.log("Sending reauthSession"); await this.setupSocketSession(); await this.sendAuth(); this.resetReauthRequired(); } async setupSocketSession() { this.resetSocketState(); this.sessionDto = await this.octoprintClient.login(this.login).then((d)=>{ const r = d.data; if (r.name === "_api") { this.setApiState("globalKey"); this.setSocketState("aborted"); throw new _runtimeexceptions.ExternalServiceError("Global API Key detected, aborting socket connection", "OctoPrint"); } else if (r.needs?.group[0] === "guests") { this.logger.warn("Detected group guests in OctoPrint login response, marking as unauthorized"); this.setApiState("authFail"); this.setSocketState("aborted"); throw new _runtimeexceptions.ExternalServiceError("Guest group detected, authentication failed, aborting socket connection", "OctoPrint"); } this.setApiState("responding"); this.setSocketState("opening"); return r; }).catch((e)=>{ this.setSocketState("aborted"); if (e instanceof _runtimeexceptions.ExternalServiceError) { this.logger.warn(`Printer authorization error, apiState: ${this.apiState}`); throw e; } else { if (e?.response?.status === 403) { this.setApiState("authFail"); this.setSocketState("aborted"); throw new _runtimeexceptions.ExternalServiceError(e, "OctoPrint"); } this.logger.error(`Printer (${this.printerId}) network or transport error, marking it as unreachable; ${e}`); this.setApiState("noResponse"); } throw e; }); this.username = await this.octoprintClient.getAdminUserOrDefault(this.login).catch((e)=>{ const status = e.response?.status; this.setApiState("authFail"); this.setSocketState("aborted"); if (status && [ _httpstatuscodesconstants.HttpStatusCode.BAD_GATEWAY, _httpstatuscodesconstants.HttpStatusCode.NOT_IMPLEMENTED, _httpstatuscodesconstants.HttpStatusCode.SERVICE_UNAVAILABLE, _httpstatuscodesconstants.HttpStatusCode.GATEWAY_TIMEOUT ].includes(status)) { this.logger.error(`Detected a 501-504 error (${status}) probably OctoPrint has crashed or is restarting`); } throw e; }); await this.updateCurrentStateSafely(); this.logger.log(`Setting up printer current interval loop with 10 seconds interval`); if (this.refreshPrinterCurrentInterval) { clearInterval(this.refreshPrinterCurrentInterval); } this.refreshPrinterCurrentInterval = setInterval(async ()=>{ await this.updateCurrentStateSafely(); }, 10000); } setReauthRequired() { this.reauthRequired = true; this.reauthRequiredTimestamp = Date.now(); } resetReauthRequired() { this.reauthRequired = false; this.reauthRequiredTimestamp = null; } resetSocketState() { this.setSocketState("unopened"); this.setApiState("unset"); } emitEventSync(event, payload) { if (!this.eventEmittingAllowed) { return; } this.eventEmitter2.emit(octoPrintEvent(event), { event, payload, printerId: this.printerId, printerType: _printerapiinterface.OctoprintType }); } async afterOpened(_) { this.setSocketState("opened"); await this.sendAuth(); await this.sendThrottle(_serverconstants.AppConstants.defaultSocketThrottleRate); } async onMessage(message) { this.lastMessageReceivedTimestamp = Date.now(); if (this.socketState !== _socketstatetype.SOCKET_STATE.authenticated) { this.setSocketState("authenticated"); } const data = JSON.parse(message); const eventName = Object.keys(data)[0]; const payload = data[eventName]; if (this._debugMode) { this.logger.log(`RX Msg ${eventName} ${message.substring(0, 140)}...`); } if (eventName === OctoPrintMessage.reauthRequired) { this.logger.log("Received 'reauthRequired', acting on it"); this.setReauthRequired(); } await this.emitEvent(eventName, payload); } async afterClosed(event) { this.setSocketState("closed"); delete this.socket; await this.emitEvent(WsMessage.WS_CLOSED, "connection closed"); } async onError(error) { this.setSocketState("error"); await this.emitEvent(WsMessage.WS_ERROR, error?.length ? error : "connection error"); } async updateCurrentStateSafely() { try { const current = await this.octoprintClient.getPrinterCurrent(this.login, true); const isOperational = current.data?.state?.flags?.operational; let job = {}; if (isOperational) { const jobResponse = await this.octoprintClient.getJob(this.login); job = jobResponse.data; } this.setApiState(_apistatetype.API_STATE.responding); return await this.emitEvent("current", { ...current.data, progress: job?.progress, job: job?.job }); } catch (e) { if (e.isAxiosError) { const castError = e; if (castError?.response?.status == 409) { this.logger.error(`Printer current interval loop error`); await this.emitEvent("current", { state: { flags: { operational: false, error: false }, text: "USB disconnected", error: castError?.response.data.error } }); return; } this.logger.error(`Could not update Octoprint current due to a request error`); this.setApiState(_apistatetype.API_STATE.noResponse); return; } this.logger.error(`Could not update Octoprint current due to an unknown error`); this.setApiState(_apistatetype.API_STATE.noResponse); } } async emitEvent(event, payload) { if (!this.eventEmittingAllowed) { return; } await this.eventEmitter2.emitAsync(octoPrintEvent(event), { event, payload, printerId: this.printerId, printerType: 0 }); } async sendAuth() { if (!this.sessionDto?.session?.length) { throw new Error("Cant send auth, session is unset."); } this.setSocketState(_socketstatetype.SOCKET_STATE.authenticating); await this.sendMessage(JSON.stringify({ auth: `${this.username}:${this.sessionDto.session}` })); } 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(WsMessage.WS_STATE_UPDATED, state); } setApiState(state) { if (state === _apistatetype.API_STATE.globalKey) { this.logger.warn("Global API Key WS State detected"); } 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(WsMessage.API_STATE_UPDATED, state); } } //# sourceMappingURL=octoprint-websocket.adapter.js.map