printservant/index.js

173 lines
4.7 KiB
JavaScript

require('console-stamp')(console)
const ipp = require('ipp')
const express = require('express')
const bodyParser = require('body-parser')
// --- Config ---
const PORT = Number(process.env.PRINTSERVANT_PORT) || 3199
const CONFIG = JSON.parse(process.env.PRINTSERVANT_CONFIG || 'null')
const SECRET = process.env.PRINTSERVANT_SECRET || ''
if (!(PORT && CONFIG)) {
throw new Error("PRINTSERVANT_PORT and PRINTSERVANT_CONFIG environment variables must be set")
}
const printers = []
for (const printerConfig of CONFIG.printers) {
const { name, aliases = [], ipp_url } = printerConfig
if (!(name && ipp_url)) {
throw new Error("Printer config must have a name and url")
}
if (ipp_url.includes('$SECRET') && !SECRET) {
throw new Error(`Printer '${name}' config has a secret in the url but no PRINTSERVANT_CONFIG was provided`)
}
const url = ipp_url.replace('$SECRET', SECRET)
const ippPrinter = ipp.Printer(url)
printers.push({ name, aliases, ippPrinter })
}
const printerMap = new Map()
printers.forEach(printer => {
printerMap.set(printer.name.toLowerCase(), printer)
printer.aliases.forEach(alias => {
printerMap.set(alias.toLowerCase(), printer)
})
})
function getPrinter(name) {
return printerMap.get(name.toLowerCase())
}
// --- Debug API ---
const app = express()
app.get('/', (req, res) => {
res.send(`<pre>Hello world! API routes available:
- GET /
- GET /health
- GET /printers
- Lists all printers (names and aliases)
- GET /printers/:name/attributes
- Prints IPP attributes
- GET /printers/:name/jobs
- Prints IPP jobs
- POST /print?printer=:name (body: application/pdf)
- Prints a PDF
- :name - printer name or alias (case insensitive)
- Response: 200 OK if sent to printer successfully (not necessarily printed), 4xx/5xx otherwise
</pre>`)
})
app.get('/health', (req, res) => {
res.send(`Printservant is healthy (probably)`)
})
app.get('/printers', (req, res) => {
res.send(printers.map(({ name, aliases }) => ({ name, aliases })))
})
function executeSimpleCommand(printerName, command, res) {
const printer = getPrinter(printerName)
if (!printer) {
console.log(`Printer '${printerName}' not found`)
res.status(404).send(`Printer '${printerName}' not found`)
return
}
printer.ippPrinter.execute(command, null, function (error, result) {
if (error || result.statusCode !== 'successful-ok') {
console.error(error || result);
res.status(500)
}
res.send({ result, error })
});
}
app.get('/printers/:name/attributes', (req, res) => {
const { name } = req.params
console.log(`Getting attributes for printer: '${name}'`)
executeSimpleCommand(name, "Get-Printer-Attributes", res)
})
app.get('/printers/:name/jobs', (req, res) => {
const { name } = req.params
console.log(`Getting jobs for printer: '${name}'`)
executeSimpleCommand(name, "Get-Jobs", res)
})
// --- Print API ---
app.post('/print', bodyParser.raw({ type: '*/*' }), (req, res) => {
const { body, query } = req
console.log("Received print job: ", { body, query })
// validate query params
const { printer: printerName, ...otherParams } = query
if (!printerName) {
res.status(400).send("No printer specified, pass ?printer=:name in the query params")
return
} else if (Object.keys(otherParams).length) {
res.status(400).send("Unknown query params, only ?printer=:name is supported")
return
}
const printer = getPrinter(printerName)
if (!printer) {
console.log(`Printer '${name}' not found`)
res.status(404).send(`Printer '${name}' not found`)
return
}
// validate body
if (!body || !body.length) {
res.status(400).send("No data received, please send a PDF")
return
} else if (body.subarray(0, 4).toString() !== "%PDF") {
res.status(415).send("Data does not look like a PDF, please send a PDF")
return
} else if (body.length > 10_000_000) {
res.status(413).send("Data too large (limit: 10MB), please send a smaller PDF")
return
}
// send print job
const msg = {
"operation-attributes-tag": {
"document-format": "application/pdf",
},
"job-attributes-tag": {
// FIXME: This should work, but does not
// "copies": 2,
},
data: body
};
console.log("Sending print job: ", msg)
printer.ippPrinter.execute("Print-Job", msg, function(error, result){
console.log({ result, error });
if (error) {
res.status(500)
} else if (result.statusCode !== 'successful-ok') {
res.status(400) // not sure, depends lol
}
res.send({ result, error })
});
})
// --- Let's goooooooo ---
app.listen(PORT, () => {
console.log(`printservant listening on port ${PORT}`)
})