'use strict'; const { serverTimestamp } = require('./seed'); function readBody(req) { return new Promise((resolve, reject) => { const chunks = []; req.on('data', (chunk) => chunks.push(chunk)); req.on('end', () => resolve(Buffer.concat(chunks))); req.on('error', reject); }); } function sendJson(res, statusCode, body) { const responseBody = JSON.stringify(body); res.writeHead(statusCode, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(responseBody), }); res.end(responseBody); } function createJtlPosServer(store, config = {}) { const { authToken = process.env.AUTH_TOKEN || '9a2e3036ed9c47e389741d9dbb7590e9' } = config; const { certificateFingerprint = 'BC2114CF407A42724BEEF417960F76DCBF9DE879', certificateSerialNumber = '00BFC8BEACDB981B165210EF111CB9D3', serverFingerprint = '39-6D-BD-DE-F3-5C-5A-EA-C2-19-CF-EB-A7-A9-58-2F-20-3F-20-F7-3D-E6-CA-8E-AE-FD-28-30-37-A6-45-AE', mandantId = '1', mandantName = 'eB-Standard', mandantDatabase = 'eazybusiness', } = config; function buildClientStep1() { return { authCode: null, authToken, certificateFingerprint, certificateSerialNumber, mandantId, mandantName: null, mandantDatabase: null, serverFingerprint, name: null, serverTimestamp: serverTimestamp(), }; } function buildClientStep2(authCode) { return { authCode, authToken, certificateFingerprint, certificateSerialNumber, mandantId, mandantName, mandantDatabase, serverFingerprint: null, name: null, serverTimestamp: serverTimestamp(), }; } async function handle(req, res) { const url = new URL(req.url, 'https://localhost'); const route = (method, pathname) => method === req.method && url.pathname === pathname; if (route('GET', '/api/v1/client')) { const authCode = url.searchParams.get('authCode') || ''; const name = url.searchParams.get('name') || 'JTL-POS'; const state = store.getState(); // Step 1 — the first 4 digits of a pre-registered 6-digit pairing code. if (authCode.length <= 4 && authCode.length > 0) { return sendJson(res, 200, buildClientStep1()); } // Step 2 — full 6-digit code must exist in the pending authCodes map. if (authCode.length === 6) { if (state.authCodes[authCode]) { store.dispatch({ type: 'REVOKE_PAIRING_CODE', payload: { code: authCode } }); store.dispatch({ type: 'REGISTER_DEVICE', payload: { token: authToken, name } }); return sendJson(res, 200, buildClientStep2(authCode)); } return sendJson(res, 400, { Message: 'Der Authentifizierungscode ist falsch.' }); } return sendJson(res, 400, { Message: 'Keinen passenden Authentifizierungscode gefunden.' }); } if (route('GET', '/api/v1/init')) { const params = { lastChangedCategory: url.searchParams.get('lastChangedCategory') || '0', lastChangedCustomer: url.searchParams.get('lastChangedCustomer') || '0', lastChangedCustomerGroup: url.searchParams.get('lastChangedCustomerGroup') || '0', lastChangedProduct: url.searchParams.get('lastChangedProduct') || '0', lastChangedConfigurationGroup: url.searchParams.get('lastChangedConfigurationGroup') || '0', lastChangedConfigurationItem: url.searchParams.get('lastChangedConfigurationItem') || '0', lastChangedCompositeProduct: url.searchParams.get('lastChangedCompositeProduct') || '0', lastChangedDeletedEntity: url.searchParams.get('lastChangedDeletedEntity') || '0', }; return sendJson(res, 200, store.getInitCounts(params)); } const entityEndpoints = { '/api/v1/customergroup': { collection: 'customerGroups', param: 'lastChangedCustomerGroup', limit: '200' }, '/api/v1/deletedentity': { collection: 'deletedEntities', param: 'lastChangedDeletedEntity', limit: '200' }, '/api/v1/category': { collection: 'categories', param: 'lastChangedCategory', limit: '20' }, '/api/v1/customer': { collection: 'customers', param: 'lastChangedCustomer', limit: '20' }, '/api/v1/product': { collection: 'products', param: 'lastChangedProduct', limit: '20' }, '/api/v1/productcomposite': { collection: 'productComposites', param: 'lastChangedCompositeProduct', limit: '20' }, }; for (const [pathname, { collection, param, limit: defaultLimit }] of Object.entries(entityEndpoints)) { if (route('GET', pathname)) { const cursor = url.searchParams.get(param) || '0'; const limit = url.searchParams.get('limit') || defaultLimit; const response = store.selectFilteredListFromCollection(collection, cursor, limit); return sendJson(res, 200, response); } } return sendJson(res, 404, { Message: `No HTTP resource was found that matches the request URI '${url}'.`, }); } return async function requestListener(req, res) { const body = await readBody(req); // Attach raw body for optional logging by the wrapper req.rawBody = body; try { await handle(req, res); } catch (err) { sendJson(res, 500, { Message: err.message }); } }; } module.exports = { createJtlPosServer };