UNPKG

@fdm-monster/server

Version:

FDM Monster is a bulk OctoPrint, Klipper, PrusaLink and BambuLab manager to set up, configure and monitor 3D printers. Our aim is to provide neat overview over your farm.

209 lines (208 loc) 6.98 kB
import { uploadFileInputSchema } from "../printer-api.interface.js"; import { ExternalServiceError } from "../../exceptions/runtime.exceptions.js"; import { uploadDoneEvent, uploadFailedEvent, uploadProgressEvent } from "../../constants/event.constants.js"; import { PrusaLinkHttpClientBuilder } from "./utils/prusa-link-http-client.builder.js"; //#region src/services/prusa-link/prusa-link.api.ts const defaultLog = { adapter: "prusa-link" }; /** * Prusa Link OpenAPI spec https://raw.githubusercontent.com/prusa3d/Prusa-Link-Web/master/spec/openapi.yaml * Prusa Link https://github.com/prusa3d/Prusa-Link * Prusa Link Web https://github.com/prusa3d/Prusa-Link-Web/tree/master */ var PrusaLinkApi = class PrusaLinkApi { logger; authHeader = null; constructor(loggerFactory, eventEmitter2, httpClientFactory, settingsStore, printerLogin) { this.eventEmitter2 = eventEmitter2; this.httpClientFactory = httpClientFactory; this.settingsStore = settingsStore; this.printerLogin = printerLogin; this.logger = loggerFactory(PrusaLinkApi.name); this.logger.debug("Constructed api client", this.logMeta()); } get type() { return 2; } set login(login) { this.printerLogin = login; } get client() { return this.createClient(); } async getVersion() { return (await this.client.get("/api/version")).data.server; } async validateConnection() { await this.getVersion(); } async getFiles(recursive = false, startDir = "/usb") { if (recursive) throw new Error("Recursive listing not supported for PrusaLink printers"); const root = (await this.client.get("/api/files")).data.files.find((dir) => dir.path === startDir); if (!root || !root.children) return { dirs: [], files: [] }; const items = root.children.map((child) => ({ path: child.path, size: null, date: null, dir: !child.refs?.download })); return { dirs: items.filter((i) => i.dir), files: items.filter((i) => !i.dir) }; } async getFile(path) { const response = await this.getFileRaw(path); return { path: response.data.name, size: response.data.size, date: null, dir: false }; } async getStatus() { return (await this.client.get("/api/v1/status")).data; } async getPrinterState() { return (await this.client.get("/api/printer")).data; } async getJobState() { return (await this.client.get("/api/job")).data; } connect() { throw new Error("Method not implemented."); } disconnect() { throw new Error("Method not implemented."); } restartServer() { throw new Error("Method not implemented."); } restartHost() { throw new Error("Method not implemented."); } restartPrinterFirmware() { throw new Error("Method not implemented."); } async startPrint(path) { await this.client.post(`/api/v1/files/usb/${path}`); } async pausePrint() { const jobId = await this.getCurrentJobId(); if (!jobId) { this.logger.warn("Job pause command did not complete, job or job id not set"); return; } await this.client.put(`/api/v1/job/${jobId}/pause`); } async resumePrint() { const jobId = await this.getCurrentJobId(); if (!jobId) { this.logger.warn("Job resume command did not complete, job or job id not set"); return; } await this.client.put(`/api/v1/job/${jobId}/resume`); } async cancelPrint() { const jobId = await this.getCurrentJobId(); if (!jobId) { this.logger.warn("Job cancel command did not complete, job or job id not set"); return; } await this.client.delete(`/api/v1/job/${jobId}`); } quickStop() { throw new Error("Method not implemented."); } sendGcode(script) { throw new Error("Method not implemented."); } movePrintHead(amounts) { throw new Error("Method not implemented."); } homeAxes(axes) { throw new Error("Method not implemented."); } async downloadFile(path) { const pathUrl = (await this.getFileRaw(path)).data.refs.download; return await this.client.get(pathUrl, { responseType: "stream" }); } async getFileChunk(path, startBytes, endBytes) { const pathUrl = (await this.getFileRaw(path)).data.refs.download; return await this.createClient((o) => o.withHeaders({ Range: `bytes=${startBytes}-${endBytes}` })).get(pathUrl); } async uploadFile(input) { const validated = uploadFileInputSchema.parse(input); try { const response = await this.createClient((b) => { b.withHeaders({ "Content-Type": "application/octet-stream", "Content-Length": validated.contentLength.toString(), Overwrite: "?1", "Print-After-Upload": validated.startPrint ? "?1" : "?0" }).withTimeout(this.settingsStore.getTimeoutSettings().apiUploadTimeout).withOnUploadProgress((p) => { if (validated.uploadToken) this.eventEmitter2.emit(`${uploadProgressEvent(validated.uploadToken)}`, validated.uploadToken, p); }); }).put(`/api/v1/files/usb/${encodeURIComponent(validated.fileName)}`, validated.stream); if (validated.uploadToken) this.eventEmitter2.emit(`${uploadDoneEvent(validated.uploadToken)}`, validated.uploadToken); return response.data; } catch (e) { if (validated.uploadToken) this.eventEmitter2.emit(`${uploadFailedEvent(validated.uploadToken)}`, validated.uploadToken, e?.message); let data; try { data = JSON.parse(e.response?.body); } catch { data = e.response?.body; } throw new ExternalServiceError({ error: e.message, statusCode: e.response?.statusCode, data, success: false, stack: e.stack }, "Prusa-Link"); } } async deleteFile(path) { await this.client.delete(`/api/v1/files/usb/${path}`); } async deleteFolder(path) { await this.client.delete(`/api/v1/files/usb/${path}`); } getSettings() { throw new Error("Method not implemented."); } getReprintState() { throw new Error("Method not implemented."); } getFileRaw(path) { return this.client.get(`/api/v1/files/usb/${path}`); } async getCurrentJobId() { return (await this.getStatus()).job?.id; } createClient(buildFluentOptions) { const builder = new PrusaLinkHttpClientBuilder(); return this.httpClientFactory.createClientWithBaseUrl(builder, this.printerLogin.printerURL, (b) => { this.logger.debug("Building API client", this.logMeta()); b.withDigestAuth(this.printerLogin.username, this.printerLogin.password, (error) => { this.logger.error("Authentication error occurred", error); }, (error, attemptCount) => { this.logger.log(`Authentication attempt count ${attemptCount} for method ${error.config?.method?.toUpperCase()} path ${error.config?.url}`, this.logMeta()); }, (authHeader) => { this.logger.debug("Authentication successful, saving auth header for later reuse", this.logMeta()); this.authHeader = authHeader; }); if (this.authHeader) b.withAuthHeader(this.authHeader); if (buildFluentOptions && typeof buildFluentOptions === "function") buildFluentOptions(b); }); } logMeta() { return defaultLog; } }; //#endregion export { PrusaLinkApi }; //# sourceMappingURL=prusa-link.api.js.map