@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
JavaScript
"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