@alizadefaig/pos-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ class Database {
2
+ constructor() {
3
+ }
4
+ }
5
+
6
+ module.exports = Database;
@@ -0,0 +1,21 @@
1
+ const {io} = require("socket.io-client");
2
+ const serverSocket = require('../socket/server')();
3
+ let clientSocket = null;
4
+ module.exports = (target, companyId) => {
5
+ if (!clientSocket && companyId) {
6
+ clientSocket = io(target, {
7
+ query: {
8
+ company_id: companyId
9
+ }
10
+ });
11
+ }
12
+
13
+ if (clientSocket) {
14
+ clientSocket.onAny((companyId, eventName, ...args) => {
15
+ serverSocket.to(companyId).emit(eventName, ...args);
16
+ })
17
+ }
18
+
19
+
20
+ return clientSocket;
21
+ }
@@ -0,0 +1,17 @@
1
+ const {Server} = require("socket.io");
2
+ let serverSocket = null;
3
+ module.exports = (server = null) => {
4
+ if (server && !serverSocket) {
5
+ serverSocket = new Server(server, {
6
+ cors: {
7
+ origin: '*'
8
+ }
9
+ })
10
+
11
+ serverSocket.on('connection', (socket) => {
12
+ socket.join(socket.handshake.query.company_id);
13
+ });
14
+ }
15
+
16
+ return serverSocket;
17
+ }
package/config.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "isServer": true,
3
+ "apiUrl": "https://admin.cafepos.az",
4
+ "debug": false,
5
+ "port": 3005
6
+ }
package/lib/axios.js ADDED
@@ -0,0 +1,8 @@
1
+ const axios = require("axios");
2
+ const {appConfig} = require("../server");
3
+ const instance = axios.create({
4
+ baseURL: appConfig.apiUrl
5
+ })
6
+
7
+
8
+ module.exports = instance
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@alizadefaig/pos-server",
3
+ "version": "1.0.0",
4
+ "description": "Server in express js with Proxy to Main server if need and will work with Electron JS",
5
+ "homepage": "https://gitlab.com/faigalizade/pos-server#readme",
6
+ "bugs": {
7
+ "url": "https://gitlab.com/faigalizade/pos-server/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+ssh://git@gitlab.com/faigalizade/pos-server.git"
12
+ },
13
+ "license": "ISC",
14
+ "author": "faigalizade@newave.az",
15
+ "type": "commonjs",
16
+ "main": "server.js",
17
+ "directories": {
18
+ "lib": "lib"
19
+ },
20
+ "scripts": {
21
+ "test": "echo \"Error: no test specified\" && exit 1",
22
+ "start": "node server.js"
23
+ },
24
+ "dependencies": {
25
+ "axios": "^1.8.4",
26
+ "cors": "^2.8.5",
27
+ "express": "^5.1.0",
28
+ "form-data": "^4.0.2",
29
+ "multer": "^1.4.5-lts.2",
30
+ "sequelize": "^6.37.7",
31
+ "socket.io": "^4.8.1",
32
+ "socket.io-client": "^4.8.1",
33
+ "cafetap-escpos-usb": "gitlab:faigalizade/escpos-usb",
34
+ "escpos": "^3.0.0-alpha.6",
35
+ "escpos-network": "^3.0.0-alpha.5"
36
+ }
37
+ }
@@ -0,0 +1,143 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const escpos = require('escpos');
4
+ const {getIpAddress, ipRange, getPrinterDevice} = require("../../../utils");
5
+ const net = require('net');
6
+ escpos.USB = require('cafetap-escpos-usb');
7
+ escpos.Network = require('escpos-network');
8
+ const axios = require('axios');
9
+ const appConfig = require("../../../config.json");
10
+
11
+ let PROXY_TARGET = appConfig.apiUrl || 'http://localhost:8000';
12
+ const getUsbPrinters = async () => {
13
+ let result = [];
14
+ const usbDevices = escpos.USB.findPrinter();
15
+ for (const usbDevice of usbDevices) {
16
+ usbDevice.open((error) => {
17
+ console.log(error)
18
+ });
19
+ result.push({
20
+ type: 'USB',
21
+ name: await getStringDescriptor(usbDevice, usbDevice.deviceDescriptor.iProduct),
22
+ vendor_id: usbDevice.deviceDescriptor.idVendor,
23
+ product_id: usbDevice.deviceDescriptor.idProduct
24
+ });
25
+ usbDevice.close();
26
+ }
27
+ result.push({
28
+ type: 'USB',
29
+ name: 'Text',
30
+ vendor_id: 123,
31
+ product_id: 345
32
+ });
33
+
34
+ return result;
35
+ }
36
+
37
+ const getStringDescriptor = async (device, index) => {
38
+ return new Promise((resolve, reject) => {
39
+ device.getStringDescriptor(index, (err, data) => {
40
+ if (err) {
41
+ reject(err);
42
+ } else {
43
+ resolve(data);
44
+ }
45
+ });
46
+ });
47
+ }
48
+
49
+ const getNetworkDevices = async () => {
50
+ let networkDevices = [];
51
+ const connectionTimeout = 1000;
52
+ const ip = await getIpAddress();
53
+ if (!ip) {
54
+ return [];
55
+ }
56
+ const ips = await ipRange(ip);
57
+ if (ips.length) {
58
+ await Promise.all(
59
+ ips.map(async (ip) => {
60
+ const socket = new net.Socket();
61
+ let connected = false;
62
+
63
+ try {
64
+ await new Promise((resolve) => {
65
+ socket.on('connect', async () => {
66
+ connected = true;
67
+ networkDevices.push({
68
+ type: 'LAN',
69
+ name: 'ESC/POS',
70
+ ip_address: ip
71
+ });
72
+ socket.destroy();
73
+ resolve();
74
+ });
75
+
76
+ socket.on('error', (e) => {
77
+ socket.destroy();
78
+ resolve();
79
+ });
80
+
81
+ socket.connect(9100, ip);
82
+
83
+ setTimeout(() => {
84
+ if (!connected) {
85
+ socket.destroy();
86
+ resolve();
87
+ }
88
+ }, connectionTimeout);
89
+ });
90
+ } catch (error) {
91
+ console.error('Socket connection error:', error);
92
+ }
93
+ })
94
+ );
95
+ }
96
+ return networkDevices;
97
+ }
98
+
99
+ router.get('/printers.get', async (req, res) => {
100
+ let usbPrinters = await getUsbPrinters();
101
+ let networkPrinters = await getNetworkDevices();
102
+ axios.get(PROXY_TARGET + '/api/printers.get', {
103
+ headers: {...req.headers, host: new URL(PROXY_TARGET).host},
104
+ }).then(data => {
105
+ console.log(data.data);
106
+ })
107
+
108
+
109
+ res.json([...usbPrinters, ...networkPrinters]);
110
+ });
111
+
112
+ router.post('/printers.print', (req, res) => {
113
+ const device = getPrinterDevice(req.body.printer)
114
+ if (!device) {
115
+ res.send('bad').status(404);
116
+ }
117
+ const image = req.body.content;
118
+ const printer = new escpos.Printer(device, {});
119
+ const imageBuffer = Buffer.from(image, 'base64');
120
+
121
+ device.open((err) => {
122
+ if (err) {
123
+ console.error('Ошибка подключения к принтеру:', err);
124
+ res.send(err).status(404);
125
+ return;
126
+ }
127
+
128
+ printer.image(imageBuffer, 's8');
129
+ if (req.body.bell) {
130
+ printer.beep(2, 3);
131
+ }
132
+ if (req.body.bell) {
133
+ printer.cashdraw(2);
134
+ }
135
+
136
+ printer.cut()
137
+ .close();
138
+
139
+ res.send('ok');
140
+ });
141
+ })
142
+
143
+ module.exports = router;
@@ -0,0 +1,19 @@
1
+ const express = require('express');
2
+ const serverSocket = require("../../app/socket/server")();
3
+ const router = express.Router();
4
+
5
+
6
+ router.all('/socket', (req, res) => {
7
+ const companyId = req.body?.company_id || req.query?.company_id;
8
+ const eventName = req.body?.event || req.query?.event;
9
+ const data = req.body?.data || req.query?.data;
10
+ if (companyId || eventName || data) {
11
+ return res.send('ok');
12
+ }
13
+ serverSocket
14
+ .to(companyId)
15
+ .emit(eventName, data);
16
+ res.send('ok');
17
+ });
18
+
19
+ module.exports = router;
package/server.js ADDED
@@ -0,0 +1,111 @@
1
+ const express = require('express')
2
+ const app = express();
3
+ const http = require('http');
4
+ const cors = require('cors')
5
+ const server = http.createServer(app);
6
+ const multer = require('multer');
7
+ const {join} = require("node:path");
8
+ const upload = multer();
9
+ const axios = require("axios");
10
+ const {loadRoutes, streamToString} = require("./utils");
11
+ const appConfig = require("./config.json");
12
+ const FormData = require("form-data");
13
+ app.use(express.json());
14
+ app.use(express.urlencoded({extended: true})); // Для обработки form-urlencoded
15
+ app.use(upload.any()); // Позволяет обрабатывать файлы
16
+ app.use(cors());
17
+ const runExpress = (conf = null) => {
18
+ const appConfig = conf || require('./config.json');
19
+ let PROXY_TARGET = appConfig.apiUrl;
20
+ const port = appConfig.port;
21
+ const isServer = appConfig.isServer;
22
+ const serverSocket = require('./app/socket/server')(server);
23
+ let clientSocket = null;
24
+ app.get('/up', (req, res) => {
25
+ res.send('ok');
26
+ });
27
+
28
+
29
+ app.get('/init', (req, res) => {
30
+ if (!appConfig.isServer && clientSocket && req.header('company-id')) {
31
+ clientSocket = require('./app/socket/client')(PROXY_TARGET, req.header('company-id'));
32
+ }
33
+ res.send('ok');
34
+ });
35
+
36
+
37
+ loadRoutes(app, join(__dirname, ('routes/' + (appConfig.isServer ? 'server' : 'desktop'))));
38
+ loadRoutes(app, join(__dirname, ('routes/common')));
39
+ app.use(async (req, res, next) => {
40
+ try {
41
+ const targetUrl = PROXY_TARGET + req.originalUrl;
42
+ const config = {
43
+ method: req.method,
44
+ url: targetUrl,
45
+ headers: {...req.headers, host: new URL(PROXY_TARGET).host},
46
+ responseType: 'stream'
47
+ };
48
+
49
+ if (req.body && Object.keys(req.body).length > 0) {
50
+ config.data = req.body;
51
+ }
52
+
53
+ if (req.files && req.files.length > 0) {
54
+ const FormData = require('form-data');
55
+ const form = new FormData();
56
+ req.files.forEach(file => {
57
+ form.append(file.fieldname, file.buffer, file.originalname);
58
+ });
59
+
60
+ Object.entries(req.body).forEach(([key, value]) => {
61
+ form.append(key, value);
62
+ });
63
+
64
+ config.data = form;
65
+ config.headers = {...config.headers, ...form.getHeaders(), 'Accept': 'application/json'};
66
+ }
67
+
68
+ const response = await axios(config);
69
+
70
+
71
+ res.status(response.status);
72
+ // Object.entries(response.headers).forEach(([key, value]) => {
73
+ // res.setHeader(key, value);
74
+ // });
75
+
76
+ const responseData = await streamToString(response.data);
77
+
78
+
79
+ if (typeof responseData === 'string' || Buffer.isBuffer(responseData)) {
80
+ res.send(responseData);
81
+ } else {
82
+ res.json(responseData);
83
+ }
84
+
85
+ } catch (error) {
86
+ if (error.response) {
87
+ // Если сервер вернул ответ с ошибкой
88
+ res.status(error.response.status);
89
+
90
+
91
+ if (error.response.data && typeof error.response.data === 'object') {
92
+ const details = await streamToString(error.response.data)
93
+ res.json({error: 'Ошибка от проксируемого сервера', details});
94
+ } else {
95
+ res.send(error.response.data);
96
+ }
97
+ } else {
98
+ // Внутренняя ошибка сервера (например, таймаут или потеря связи)
99
+ res.status(500).json({error: 'Внутренняя ошибка прокси', details: error.message});
100
+ }
101
+
102
+
103
+ console.error('Ошибка при проксировании запроса:', error.message);
104
+ }
105
+ });
106
+ server.listen(port, () => {
107
+ console.log('Server started on: ' + port);
108
+ })
109
+ }
110
+
111
+ module.exports = runExpress
package/utils.js ADDED
@@ -0,0 +1,100 @@
1
+ const {readdirSync, existsSync} = require("node:fs");
2
+ const {extname, join} = require("node:path");
3
+ const {networkInterfaces} = require("os");
4
+
5
+ function loadRoutes(app, routesDir, prefix = '') {
6
+ if (!existsSync(routesDir)) {
7
+ return;
8
+ }
9
+ // Читаем содержимое директории
10
+ const items = readdirSync(routesDir, {withFileTypes: true});
11
+
12
+ for (const item of items) {
13
+ const fullPath = join(routesDir, item.name);
14
+
15
+ if (item.isDirectory()) {
16
+ // Для папок - рекурсивно загружаем содержимое с новым префиксом
17
+ const newPrefix = join(prefix, item.name);
18
+ loadRoutes(app, fullPath, newPrefix);
19
+ } else if (item.isFile() && extname(item.name) === '.js') {
20
+ // Для JS файлов - загружаем роутер
21
+ const routePrefix = prefix;
22
+ const router = require(fullPath);
23
+
24
+ // Регистрируем роутер с префиксом
25
+ app.use(`/${routePrefix}/`, router);
26
+
27
+ console.log(`Route loaded ${prefix}: ${routePrefix} -> ${fullPath}`);
28
+ }
29
+ }
30
+ }
31
+
32
+ async function getIpAddress() {
33
+ const interfaces = networkInterfaces();
34
+ for (const interfaceName in interfaces) {
35
+ const addresses = interfaces[interfaceName];
36
+ for (const addressInfo of addresses) {
37
+ if (addressInfo.family === 'IPv4' && !addressInfo.internal) {
38
+ return addressInfo.address;
39
+ }
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+
45
+ async function ipRange(ip) {
46
+ if (!ip) {
47
+ return [];
48
+ }
49
+ const parts = ip.split('.');
50
+ const networkAddress = parts.slice(0, 3).join('.') + '.';
51
+ const start = 0;
52
+ const end = 256;
53
+ const ips = [];
54
+
55
+ for (let i = start; i < end; i++) {
56
+ ips.push(networkAddress + i);
57
+ }
58
+
59
+ return ips;
60
+ }
61
+
62
+ function getPrinterDevice(printer) {
63
+ const escpos = require('escpos');
64
+ escpos.USB = require('cafetap-escpos-usb');
65
+ escpos.Network = require('escpos-network');
66
+ let device = null;
67
+ try {
68
+ if (printer.type === 'USB') {
69
+ device = new escpos.USB(printer.vendor_id, printer.product_id);
70
+ } else {
71
+ device = new escpos.Network(printer.ip_address);
72
+ }
73
+ } catch (e) {
74
+ console.log(e);
75
+ }
76
+
77
+ return device;
78
+ }
79
+
80
+ const streamToString = async (stream) => {
81
+ const chunks = [];
82
+ for await (const chunk of stream) {
83
+ chunks.push(chunk);
84
+ }
85
+ const buffer = Buffer.concat(chunks);
86
+
87
+ try {
88
+ return JSON.parse(buffer.toString()); // Если это JSON
89
+ } catch (e) {
90
+ return buffer.toString(); // Обычный текст или бинарные данные
91
+ }
92
+ };
93
+
94
+ module.exports = {
95
+ loadRoutes,
96
+ getIpAddress,
97
+ ipRange,
98
+ getPrinterDevice,
99
+ streamToString
100
+ }