@zpl-toolchain/print 0.1.2
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.
- package/dist/browser.d.ts +66 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +194 -0
- package/dist/browser.js.map +1 -0
- package/dist/index.d.ts +189 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +647 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +57 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +404 -0
- package/dist/proxy.js.map +1 -0
- package/dist/status.d.ts +14 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +108 -0
- package/dist/status.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +79 -0
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Server } from "node:http";
|
|
2
|
+
import type { ProxyConfig } from "./types.js";
|
|
3
|
+
export type { ProxyConfig } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Create and start an HTTP and WebSocket print proxy server.
|
|
6
|
+
*
|
|
7
|
+
* The proxy accepts JSON POST requests and forwards them to printers over
|
|
8
|
+
* TCP port 9100. This enables browser-based applications to print without
|
|
9
|
+
* requiring the Zebra Browser Print agent.
|
|
10
|
+
*
|
|
11
|
+
* ## Endpoints
|
|
12
|
+
*
|
|
13
|
+
* ### `POST /print`
|
|
14
|
+
* Send ZPL to a printer.
|
|
15
|
+
* ```json
|
|
16
|
+
* { "printer": "192.168.1.100", "zpl": "^XA^FDHello^FS^XZ" }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* ### `POST /status`
|
|
20
|
+
* Query a printer's status.
|
|
21
|
+
* ```json
|
|
22
|
+
* { "printer": "192.168.1.100" }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* ### `GET /health`
|
|
26
|
+
* Returns `{ "ok": true }`.
|
|
27
|
+
*
|
|
28
|
+
* ### WebSocket (`ws://host:port`)
|
|
29
|
+
* Accepts JSON messages with a `type` field (`"print"` or `"status"`).
|
|
30
|
+
* ```json
|
|
31
|
+
* { "type": "print", "printer": "192.168.1.100", "zpl": "^XA^FDHello^FS^XZ" }
|
|
32
|
+
* ```
|
|
33
|
+
* Responses are JSON with an `ok` field:
|
|
34
|
+
* ```json
|
|
35
|
+
* { "ok": true, "success": true, "bytesWritten": 42, "duration": 12 }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { createPrintProxy } from "@zpl-toolchain/print/proxy";
|
|
41
|
+
*
|
|
42
|
+
* const { server, close } = await createPrintProxy({
|
|
43
|
+
* port: 3001,
|
|
44
|
+
* allowedPrinters: ["192.168.1.100", "192.168.1.101"],
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* console.log("Proxy listening on port 3001");
|
|
48
|
+
*
|
|
49
|
+
* // Shut down later
|
|
50
|
+
* await close();
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function createPrintProxy(config?: ProxyConfig): Promise<{
|
|
54
|
+
server: Server;
|
|
55
|
+
close: () => Promise<void>;
|
|
56
|
+
}>;
|
|
57
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAyH9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,GAAE,WAAgB,GACvB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAiSzD"}
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { WebSocketServer } from "ws";
|
|
3
|
+
import { print, tcpQuery } from "./index.js";
|
|
4
|
+
import { parseHostStatus } from "./status.js";
|
|
5
|
+
// ─── Defaults ────────────────────────────────────────────────────────────────
|
|
6
|
+
const DEFAULT_PROXY_PORT = 3001;
|
|
7
|
+
const DEFAULT_HOSTNAME = "127.0.0.1";
|
|
8
|
+
const DEFAULT_MAX_PAYLOAD = 1024 * 1024; // 1 MiB
|
|
9
|
+
const DEFAULT_CORS = "*";
|
|
10
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
11
|
+
function readBody(req, maxSize, timeoutMs = 30_000) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const chunks = [];
|
|
14
|
+
let size = 0;
|
|
15
|
+
let settled = false;
|
|
16
|
+
const fail = (err) => {
|
|
17
|
+
if (settled)
|
|
18
|
+
return;
|
|
19
|
+
settled = true;
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
reject(err);
|
|
22
|
+
};
|
|
23
|
+
const succeed = (value) => {
|
|
24
|
+
if (settled)
|
|
25
|
+
return;
|
|
26
|
+
settled = true;
|
|
27
|
+
clearTimeout(timer);
|
|
28
|
+
resolve(value);
|
|
29
|
+
};
|
|
30
|
+
// Guard against slow-loris: reject if body isn't received within the deadline.
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
req.removeAllListeners("data");
|
|
33
|
+
req.resume();
|
|
34
|
+
fail(new Error("Body read timed out"));
|
|
35
|
+
}, timeoutMs);
|
|
36
|
+
req.on("data", (chunk) => {
|
|
37
|
+
size += chunk.length;
|
|
38
|
+
if (size > maxSize) {
|
|
39
|
+
// Don't destroy the socket — the response still needs to be written
|
|
40
|
+
// (e.g., 413). Just stop collecting data and signal the error.
|
|
41
|
+
req.removeAllListeners("data");
|
|
42
|
+
req.resume(); // drain remaining body data
|
|
43
|
+
fail(new Error(`Payload exceeds maximum size of ${maxSize} bytes`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
chunks.push(chunk);
|
|
47
|
+
});
|
|
48
|
+
req.on("end", () => {
|
|
49
|
+
succeed(Buffer.concat(chunks).toString("utf-8"));
|
|
50
|
+
});
|
|
51
|
+
req.on("error", fail);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function jsonResponse(res, status, body) {
|
|
55
|
+
const payload = JSON.stringify(body);
|
|
56
|
+
res.writeHead(status, {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
59
|
+
});
|
|
60
|
+
res.end(payload);
|
|
61
|
+
}
|
|
62
|
+
function isPrinterAllowed(printer, compiledList) {
|
|
63
|
+
if (compiledList.length === 0)
|
|
64
|
+
return false;
|
|
65
|
+
return compiledList.some(({ pattern, regex }) => {
|
|
66
|
+
if (regex)
|
|
67
|
+
return regex.test(printer);
|
|
68
|
+
return pattern === printer;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function isPortAllowed(port, allowedPorts) {
|
|
72
|
+
return allowedPorts.includes(port);
|
|
73
|
+
}
|
|
74
|
+
function setCorsHeaders(res, cors, origin) {
|
|
75
|
+
if (cors === "*") {
|
|
76
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
77
|
+
}
|
|
78
|
+
else if (Array.isArray(cors)) {
|
|
79
|
+
if (origin && cors.includes(origin)) {
|
|
80
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
81
|
+
res.setHeader("Vary", "Origin");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (typeof cors === "string" && origin && cors === origin) {
|
|
85
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
86
|
+
res.setHeader("Vary", "Origin");
|
|
87
|
+
}
|
|
88
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
89
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
90
|
+
res.setHeader("Access-Control-Max-Age", "86400");
|
|
91
|
+
}
|
|
92
|
+
/** Send JSON to a WebSocket only if the connection is still open. */
|
|
93
|
+
function wsSend(ws, data) {
|
|
94
|
+
if (ws.readyState === 1 /* OPEN */) {
|
|
95
|
+
ws.send(JSON.stringify(data));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ─── Proxy server ────────────────────────────────────────────────────────────
|
|
99
|
+
/**
|
|
100
|
+
* Create and start an HTTP and WebSocket print proxy server.
|
|
101
|
+
*
|
|
102
|
+
* The proxy accepts JSON POST requests and forwards them to printers over
|
|
103
|
+
* TCP port 9100. This enables browser-based applications to print without
|
|
104
|
+
* requiring the Zebra Browser Print agent.
|
|
105
|
+
*
|
|
106
|
+
* ## Endpoints
|
|
107
|
+
*
|
|
108
|
+
* ### `POST /print`
|
|
109
|
+
* Send ZPL to a printer.
|
|
110
|
+
* ```json
|
|
111
|
+
* { "printer": "192.168.1.100", "zpl": "^XA^FDHello^FS^XZ" }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* ### `POST /status`
|
|
115
|
+
* Query a printer's status.
|
|
116
|
+
* ```json
|
|
117
|
+
* { "printer": "192.168.1.100" }
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* ### `GET /health`
|
|
121
|
+
* Returns `{ "ok": true }`.
|
|
122
|
+
*
|
|
123
|
+
* ### WebSocket (`ws://host:port`)
|
|
124
|
+
* Accepts JSON messages with a `type` field (`"print"` or `"status"`).
|
|
125
|
+
* ```json
|
|
126
|
+
* { "type": "print", "printer": "192.168.1.100", "zpl": "^XA^FDHello^FS^XZ" }
|
|
127
|
+
* ```
|
|
128
|
+
* Responses are JSON with an `ok` field:
|
|
129
|
+
* ```json
|
|
130
|
+
* { "ok": true, "success": true, "bytesWritten": 42, "duration": 12 }
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* import { createPrintProxy } from "@zpl-toolchain/print/proxy";
|
|
136
|
+
*
|
|
137
|
+
* const { server, close } = await createPrintProxy({
|
|
138
|
+
* port: 3001,
|
|
139
|
+
* allowedPrinters: ["192.168.1.100", "192.168.1.101"],
|
|
140
|
+
* });
|
|
141
|
+
*
|
|
142
|
+
* console.log("Proxy listening on port 3001");
|
|
143
|
+
*
|
|
144
|
+
* // Shut down later
|
|
145
|
+
* await close();
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export async function createPrintProxy(config = {}) {
|
|
149
|
+
const port = config.port ?? DEFAULT_PROXY_PORT;
|
|
150
|
+
const hostname = config.hostname ?? DEFAULT_HOSTNAME;
|
|
151
|
+
const maxPayload = config.maxPayloadSize ?? DEFAULT_MAX_PAYLOAD;
|
|
152
|
+
const cors = config.cors ?? DEFAULT_CORS;
|
|
153
|
+
const allowed = config.allowedPrinters;
|
|
154
|
+
const allowedPorts = config.allowedPorts ?? [9100];
|
|
155
|
+
const maxConnections = config.maxConnections ?? 50;
|
|
156
|
+
// Pre-compile glob patterns into regexes once at startup.
|
|
157
|
+
const compiledAllowlist = (allowed ?? []).map((pattern) => {
|
|
158
|
+
if (!pattern.includes("*"))
|
|
159
|
+
return { pattern };
|
|
160
|
+
// Bare "*" is a special case meaning "match all" (including dotted IPs/hostnames).
|
|
161
|
+
if (pattern === "*")
|
|
162
|
+
return { pattern, regex: /^.*$/ };
|
|
163
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
164
|
+
const regex = new RegExp("^" + escaped.replace(/\*/g, "[^.]*") + "$");
|
|
165
|
+
return { pattern, regex };
|
|
166
|
+
});
|
|
167
|
+
const server = createServer(async (req, res) => {
|
|
168
|
+
const origin = req.headers.origin;
|
|
169
|
+
setCorsHeaders(res, cors, origin);
|
|
170
|
+
// Handle CORS preflight.
|
|
171
|
+
if (req.method === "OPTIONS") {
|
|
172
|
+
res.writeHead(204);
|
|
173
|
+
res.end();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// ── GET /health ────────────────────────────────────────────────────
|
|
177
|
+
if (req.method === "GET" && req.url === "/health") {
|
|
178
|
+
jsonResponse(res, 200, { ok: true });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Only POST from here on.
|
|
182
|
+
if (req.method !== "POST") {
|
|
183
|
+
jsonResponse(res, 405, { error: "Method not allowed" });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Fail fast on unknown routes before reading body.
|
|
187
|
+
if (req.url !== "/print" && req.url !== "/status") {
|
|
188
|
+
jsonResponse(res, 404, { error: "Not found" });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
const contentType = req.headers["content-type"] ?? "";
|
|
193
|
+
if (!contentType.toLowerCase().includes("application/json")) {
|
|
194
|
+
jsonResponse(res, 415, {
|
|
195
|
+
error: "Content-Type must be application/json",
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const body = await readBody(req, maxPayload);
|
|
200
|
+
let parsed;
|
|
201
|
+
try {
|
|
202
|
+
parsed = JSON.parse(body);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
jsonResponse(res, 400, { error: "Invalid JSON" });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// ── POST /print ──────────────────────────────────────────────────
|
|
209
|
+
if (req.url === "/print") {
|
|
210
|
+
const printer = parsed.printer;
|
|
211
|
+
const zpl = parsed.zpl;
|
|
212
|
+
if (typeof printer !== "string" || !printer) {
|
|
213
|
+
jsonResponse(res, 400, { error: 'Missing required field "printer"' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (typeof zpl !== "string" || !zpl) {
|
|
217
|
+
jsonResponse(res, 400, { error: 'Missing required field "zpl"' });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!isPrinterAllowed(printer, compiledAllowlist)) {
|
|
221
|
+
jsonResponse(res, 403, {
|
|
222
|
+
error: `Printer "${printer}" is not in the allowed list`,
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const reqPort = typeof parsed.port === "number" ? parsed.port : 9100;
|
|
227
|
+
if (!Number.isInteger(reqPort) || reqPort < 1 || reqPort > 65535) {
|
|
228
|
+
jsonResponse(res, 400, { error: "Port must be an integer between 1 and 65535" });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (!isPortAllowed(reqPort, allowedPorts)) {
|
|
232
|
+
jsonResponse(res, 403, { error: `Port ${reqPort} is not in the allowed list` });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const result = await print(zpl, {
|
|
236
|
+
host: printer,
|
|
237
|
+
port: reqPort,
|
|
238
|
+
timeout: typeof parsed.timeout === "number" ? Math.max(1000, Math.min(parsed.timeout, 60000)) : 5000,
|
|
239
|
+
});
|
|
240
|
+
jsonResponse(res, 200, result);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// ── POST /status ─────────────────────────────────────────────────
|
|
244
|
+
if (req.url === "/status") {
|
|
245
|
+
const printer = parsed.printer;
|
|
246
|
+
if (typeof printer !== "string" || !printer) {
|
|
247
|
+
jsonResponse(res, 400, { error: 'Missing required field "printer"' });
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (!isPrinterAllowed(printer, compiledAllowlist)) {
|
|
251
|
+
jsonResponse(res, 403, {
|
|
252
|
+
error: `Printer "${printer}" is not in the allowed list`,
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const printerPort = typeof parsed.port === "number" ? parsed.port : 9100;
|
|
257
|
+
if (!Number.isInteger(printerPort) || printerPort < 1 || printerPort > 65535) {
|
|
258
|
+
jsonResponse(res, 400, { error: "Port must be an integer between 1 and 65535" });
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (!isPortAllowed(printerPort, allowedPorts)) {
|
|
262
|
+
jsonResponse(res, 403, { error: `Port ${printerPort} is not in the allowed list` });
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const timeout = typeof parsed.timeout === "number" ? Math.max(1000, Math.min(parsed.timeout, 60000)) : 5000;
|
|
266
|
+
const raw = await tcpQuery(printer, printerPort, "~HS", timeout);
|
|
267
|
+
const status = parseHostStatus(raw);
|
|
268
|
+
jsonResponse(res, 200, status);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
274
|
+
const isPayloadError = message.includes("Payload exceeds maximum size");
|
|
275
|
+
const isBodyTimeout = message.includes("Body read timed out");
|
|
276
|
+
const status = isPayloadError ? 413 : isBodyTimeout ? 408 : 502;
|
|
277
|
+
// Sanitize: don't leak internal IPs/ports from TCP errors to clients.
|
|
278
|
+
const clientMessage = isPayloadError || isBodyTimeout
|
|
279
|
+
? message
|
|
280
|
+
: "Failed to communicate with printer";
|
|
281
|
+
jsonResponse(res, status, { error: clientMessage });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// ── WebSocket upgrade endpoint ──────────────────────────────────────────
|
|
285
|
+
const wss = new WebSocketServer({
|
|
286
|
+
server,
|
|
287
|
+
maxPayload,
|
|
288
|
+
verifyClient: (info, cb) => {
|
|
289
|
+
if (wss.clients.size >= maxConnections) {
|
|
290
|
+
cb(false, 503, "Too many connections");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const originAllowed = cors === "*" ||
|
|
294
|
+
(Array.isArray(cors) ? cors.includes(info.origin) : cors === info.origin);
|
|
295
|
+
if (!originAllowed) {
|
|
296
|
+
cb(false, 403, "Origin not allowed");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
cb(true);
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
wss.on("connection", (ws) => {
|
|
303
|
+
let alive = true;
|
|
304
|
+
ws.on("pong", () => { alive = true; });
|
|
305
|
+
const heartbeat = setInterval(() => {
|
|
306
|
+
if (!alive) {
|
|
307
|
+
ws.terminate();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
alive = false;
|
|
311
|
+
ws.ping();
|
|
312
|
+
}, 30_000);
|
|
313
|
+
ws.on("close", () => clearInterval(heartbeat));
|
|
314
|
+
ws.on("error", () => {
|
|
315
|
+
// Swallow per-connection errors to prevent process crash.
|
|
316
|
+
// The connection will be cleaned up by the "close" event.
|
|
317
|
+
});
|
|
318
|
+
ws.on("message", (raw) => {
|
|
319
|
+
void (async () => {
|
|
320
|
+
let parsed;
|
|
321
|
+
try {
|
|
322
|
+
const text = typeof raw === "string" ? raw : raw.toString("utf-8");
|
|
323
|
+
parsed = JSON.parse(text);
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
wsSend(ws, { ok: false, error: "Invalid JSON" });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const msgType = parsed.type;
|
|
330
|
+
if (typeof msgType !== "string" || !["print", "status"].includes(msgType)) {
|
|
331
|
+
wsSend(ws, { ok: false, error: 'Missing or invalid "type" field (expected "print" or "status")' });
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const printer = parsed.printer;
|
|
335
|
+
if (typeof printer !== "string" || !printer) {
|
|
336
|
+
wsSend(ws, { ok: false, error: 'Missing required field "printer"' });
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (!isPrinterAllowed(printer, compiledAllowlist)) {
|
|
340
|
+
wsSend(ws, { ok: false, error: `Printer "${printer}" is not in the allowed list` });
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const reqPort = typeof parsed.port === "number" ? parsed.port : 9100;
|
|
344
|
+
if (!Number.isInteger(reqPort) || reqPort < 1 || reqPort > 65535) {
|
|
345
|
+
wsSend(ws, { ok: false, error: "Port must be an integer between 1 and 65535" });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (!isPortAllowed(reqPort, allowedPorts)) {
|
|
349
|
+
wsSend(ws, { ok: false, error: `Port ${reqPort} is not in the allowed list` });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const timeout = typeof parsed.timeout === "number"
|
|
353
|
+
? Math.max(1000, Math.min(parsed.timeout, 60000))
|
|
354
|
+
: 5000;
|
|
355
|
+
try {
|
|
356
|
+
if (msgType === "print") {
|
|
357
|
+
const zpl = parsed.zpl;
|
|
358
|
+
if (typeof zpl !== "string" || !zpl) {
|
|
359
|
+
wsSend(ws, { ok: false, error: 'Missing required field "zpl"' });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const result = await print(zpl, { host: printer, port: reqPort, timeout });
|
|
363
|
+
wsSend(ws, { ok: true, ...result });
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
// status
|
|
367
|
+
const rawResp = await tcpQuery(printer, reqPort, "~HS", timeout);
|
|
368
|
+
const status = parseHostStatus(rawResp);
|
|
369
|
+
wsSend(ws, { ok: true, ...status });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (err) {
|
|
373
|
+
// Sanitize: don't leak internal IPs/ports from TCP errors.
|
|
374
|
+
wsSend(ws, { ok: false, error: "Failed to communicate with printer" });
|
|
375
|
+
}
|
|
376
|
+
})().catch((err) => {
|
|
377
|
+
// Shouldn't happen — inner handler has its own try/catch.
|
|
378
|
+
if (typeof console !== "undefined")
|
|
379
|
+
console.error("[print-proxy] Unhandled error:", err);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
// Cap total connections (HTTP + WebSocket combined) at the OS level.
|
|
384
|
+
server.maxConnections = maxConnections;
|
|
385
|
+
return new Promise((resolve, reject) => {
|
|
386
|
+
server.on("error", reject);
|
|
387
|
+
server.listen(port, hostname, () => {
|
|
388
|
+
server.removeListener("error", reject);
|
|
389
|
+
resolve({
|
|
390
|
+
server,
|
|
391
|
+
close: () => new Promise((res, rej) => {
|
|
392
|
+
// Close all WebSocket connections first
|
|
393
|
+
for (const client of wss.clients) {
|
|
394
|
+
client.terminate();
|
|
395
|
+
}
|
|
396
|
+
wss.close(() => {
|
|
397
|
+
server.close((err) => (err ? rej(err) : res()));
|
|
398
|
+
});
|
|
399
|
+
}),
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/status.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PrinterStatus } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a raw ~HS (Host Status) response string into a structured
|
|
4
|
+
* {@link PrinterStatus} object.
|
|
5
|
+
*
|
|
6
|
+
* The parser is lenient: it returns sensible defaults for fields it cannot
|
|
7
|
+
* extract, so callers never need to worry about partial / malformed responses
|
|
8
|
+
* in the field.
|
|
9
|
+
*
|
|
10
|
+
* @param raw - The full ~HS response as received from the printer socket.
|
|
11
|
+
* @returns A {@link PrinterStatus} with boolean flags and remaining-label count.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseHostStatus(raw: string): PrinterStatus;
|
|
14
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAqChD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CA4E1D"}
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// ─── ~HS Response Parser ─────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// The Zebra ~HS (Host Status) command returns three lines of comma-separated
|
|
4
|
+
// fields framed with STX (\x02) and ETX (\x03).
|
|
5
|
+
//
|
|
6
|
+
// Line 1 (prefix \x02): communication / paper / head flags
|
|
7
|
+
// Line 2 (prefix \x02): function settings
|
|
8
|
+
// Line 3 (prefix \x02): miscellaneous
|
|
9
|
+
//
|
|
10
|
+
// Each line is delimited by CR/LF and wrapped in STX…ETX.
|
|
11
|
+
// Reference: ZPL II Programming Guide, chapter "Host Status Return".
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
const STX = "\x02";
|
|
14
|
+
const ETX = "\x03";
|
|
15
|
+
/**
|
|
16
|
+
* Strip STX / ETX framing characters and split the response into its three
|
|
17
|
+
* comma-separated field arrays.
|
|
18
|
+
*/
|
|
19
|
+
function splitHsLines(raw) {
|
|
20
|
+
// Remove STX/ETX framing, normalise line endings, trim.
|
|
21
|
+
const cleaned = raw
|
|
22
|
+
.replaceAll(STX, "")
|
|
23
|
+
.replaceAll(ETX, "")
|
|
24
|
+
.replace(/\r\n?/g, "\n")
|
|
25
|
+
.trim();
|
|
26
|
+
const lines = cleaned.split("\n").filter((l) => l.trim().length > 0);
|
|
27
|
+
return lines.map((line) => line.split(",").map((f) => f.trim()));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse a raw ~HS (Host Status) response string into a structured
|
|
31
|
+
* {@link PrinterStatus} object.
|
|
32
|
+
*
|
|
33
|
+
* The parser is lenient: it returns sensible defaults for fields it cannot
|
|
34
|
+
* extract, so callers never need to worry about partial / malformed responses
|
|
35
|
+
* in the field.
|
|
36
|
+
*
|
|
37
|
+
* @param raw - The full ~HS response as received from the printer socket.
|
|
38
|
+
* @returns A {@link PrinterStatus} with boolean flags and remaining-label count.
|
|
39
|
+
*/
|
|
40
|
+
export function parseHostStatus(raw) {
|
|
41
|
+
const lines = splitHsLines(raw);
|
|
42
|
+
const line1 = lines[0] ?? [];
|
|
43
|
+
const line2 = lines[1] ?? [];
|
|
44
|
+
const line3 = lines[2] ?? [];
|
|
45
|
+
const flag = (fields, idx) => (fields[idx] ?? "0") !== "0";
|
|
46
|
+
const int = (fields, idx) => {
|
|
47
|
+
const n = parseInt(fields[idx] ?? "", 10);
|
|
48
|
+
return Number.isNaN(n) ? 0 : n;
|
|
49
|
+
};
|
|
50
|
+
// ── Line 1 (12 fields) ────────────────────────────────────────────
|
|
51
|
+
const communicationFlag = int(line1, 0);
|
|
52
|
+
const paperOut = flag(line1, 1);
|
|
53
|
+
const paused = flag(line1, 2);
|
|
54
|
+
const labelLengthDots = int(line1, 3);
|
|
55
|
+
const formatsInBuffer = int(line1, 4);
|
|
56
|
+
const bufferFull = flag(line1, 5);
|
|
57
|
+
const commDiagMode = flag(line1, 6);
|
|
58
|
+
const partialFormat = flag(line1, 7);
|
|
59
|
+
const reserved1 = int(line1, 8);
|
|
60
|
+
const corruptRam = flag(line1, 9);
|
|
61
|
+
const underTemperature = flag(line1, 10);
|
|
62
|
+
const overTemperature = flag(line1, 11);
|
|
63
|
+
// ── Line 2 (10 fields) ────────────────────────────────────────────
|
|
64
|
+
const functionSettings = int(line2, 0);
|
|
65
|
+
const headOpen = flag(line2, 1);
|
|
66
|
+
const ribbonOut = flag(line2, 2);
|
|
67
|
+
const thermalTransferMode = flag(line2, 3);
|
|
68
|
+
const printMode = int(line2, 4);
|
|
69
|
+
const printWidthMode = int(line2, 5);
|
|
70
|
+
const labelWaiting = flag(line2, 6);
|
|
71
|
+
const labelsRemaining = int(line2, 7);
|
|
72
|
+
const formatWhilePrinting = int(line2, 8);
|
|
73
|
+
const graphicsStoredInMemory = int(line2, 9);
|
|
74
|
+
// ── Line 3 (2 fields) ─────────────────────────────────────────────
|
|
75
|
+
const password = int(line3, 0);
|
|
76
|
+
const staticRamInstalled = flag(line3, 1);
|
|
77
|
+
// "ready" = not paused, head closed, media loaded.
|
|
78
|
+
const ready = !paused && !headOpen && !paperOut;
|
|
79
|
+
return {
|
|
80
|
+
ready,
|
|
81
|
+
communicationFlag,
|
|
82
|
+
paperOut,
|
|
83
|
+
paused,
|
|
84
|
+
labelLengthDots,
|
|
85
|
+
formatsInBuffer,
|
|
86
|
+
bufferFull,
|
|
87
|
+
commDiagMode,
|
|
88
|
+
partialFormat,
|
|
89
|
+
reserved1,
|
|
90
|
+
corruptRam,
|
|
91
|
+
underTemperature,
|
|
92
|
+
overTemperature,
|
|
93
|
+
functionSettings,
|
|
94
|
+
headOpen,
|
|
95
|
+
ribbonOut,
|
|
96
|
+
thermalTransferMode,
|
|
97
|
+
printMode,
|
|
98
|
+
printWidthMode,
|
|
99
|
+
labelWaiting,
|
|
100
|
+
labelsRemaining,
|
|
101
|
+
formatWhilePrinting,
|
|
102
|
+
graphicsStoredInMemory,
|
|
103
|
+
password,
|
|
104
|
+
staticRamInstalled,
|
|
105
|
+
raw,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
|