Files
jtlPosSync/server.js
2026-07-06 01:31:11 +02:00

125 lines
3.5 KiB
JavaScript

import fs from 'node:fs';
import https from 'node:https';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import 'dotenv/config';
import { connectDb, closeDb } from './src/db.js';
import { createJtlPosServer } from './src/jtl-server.js';
import { createPairingStore } from './src/pairing.js';
import { closeOrderLog } from './src/order-log.js';
import { closeRequestLog, logRequest } from './src/request-log.js';
import { logger } from './src/logger.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PORT = Number(process.env.PORT) || 4443;
const AUTH_TOKEN = process.env.AUTH_TOKEN || 'df40ad2067954646abb0499548a52241';
const PAIRING_CODE = process.env.PAIRING_CODE || '307018';
const certsDir = path.join(__dirname, 'certs');
const keyPath = path.join(certsDir, 'key.pem');
const certPath = path.join(certsDir, 'cert.pem');
if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) {
logger.error('Missing TLS certificate. Run: npm run cert');
process.exit(1);
}
const CONSOLE_URL_MAX_LENGTH = 100;
function truncateUrl(url) {
if (url.length <= CONSOLE_URL_MAX_LENGTH) {
return url;
}
return `${url.slice(0, CONSOLE_URL_MAX_LENGTH)}...`;
}
function formatBody(buffer) {
if (!buffer.length) {
return '(empty)';
}
const text = buffer.toString('utf8');
if (/^[\x09\x0A\x0D\x20-\x7E\u0080-\uFFFF]*$/.test(text)) {
return text;
}
return `[binary ${buffer.length} bytes]`;
}
const pairingStore = createPairingStore();
pairingStore.setPairingCode(PAIRING_CODE, 'JTL-POS');
pairingStore.registerDevice(AUTH_TOKEN, 'JTL-POS');
const jtlHandler = createJtlPosServer(pairingStore, { authToken: AUTH_TOKEN });
const loggedJtlHandler = async (req, res) => {
const started = Date.now();
let responseBuffer = Buffer.alloc(0);
const originalEnd = res.end.bind(res);
res.end = (chunk, ...args) => {
if (chunk) {
responseBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
}
return originalEnd(chunk, ...args);
};
await jtlHandler(req, res);
const responseBody = formatBody(responseBuffer);
const durationMs = Date.now() - started;
logger.info(`${req.socket.remoteAddress} ${req.method} ${truncateUrl(req.url)} ${res.statusCode} ${durationMs}ms`);
logRequest({
remoteAddress: req.socket.remoteAddress,
method: req.method,
url: req.url,
statusCode: res.statusCode,
durationMs,
response: responseBody,
});
};
const httpsServer = https.createServer(
{
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certPath),
},
loggedJtlHandler
);
async function start() {
try {
await connectDb();
logger.success(`MSSQL connected: ${process.env.MSSQL_SERVER}/${process.env.MSSQL_DATABASE}`);
} catch (err) {
logger.warn(`MSSQL connection skipped: ${err.message}`);
logger.warn('POS handshake will still work; sync from database is not available yet.');
}
httpsServer.listen(PORT, '0.0.0.0', () => {
logger.success(`HTTPS POS server listening on https://0.0.0.0:${PORT}`);
logger.info(`Certificate: ${certPath}`);
logger.info(`Pairing code: ${PAIRING_CODE}`);
logger.info(`Auth token: ${AUTH_TOKEN}`);
logger.info('Import cert.pem into JTL POS trust store if required.');
});
}
async function shutdown() {
logger.info('Shutting down...');
httpsServer.close();
await closeDb();
closeRequestLog();
closeOrderLog();
}
process.on('SIGINT', () => {
shutdown().finally(() => process.exit(0));
});
process.on('SIGTERM', () => {
shutdown().finally(() => process.exit(0));
});
start();