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