retail-print-kit 0.0.0-stage → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +209 -2
- package/bin/retail-print-kit.js +318 -0
- package/dist/browser/retail-print-kit.js +26 -0
- package/dist/browser/retail-print-kit.js.map +7 -0
- package/dist/browser/retail-print-kit.mjs +25 -0
- package/dist/browser.d.ts +15 -0
- package/dist/browser.js +35 -0
- package/dist/core/barcode1d.d.ts +12 -0
- package/dist/core/barcode1d.js +192 -0
- package/dist/core/bitmap.d.ts +31 -0
- package/dist/core/bitmap.js +126 -0
- package/dist/core/builder.d.ts +58 -0
- package/dist/core/builder.js +177 -0
- package/dist/core/encode.d.ts +8 -0
- package/dist/core/encode.js +17 -0
- package/dist/core/escpos-encoder.d.ts +4 -0
- package/dist/core/escpos-encoder.js +150 -0
- package/dist/core/identify.d.ts +22 -0
- package/dist/core/identify.js +71 -0
- package/dist/core/image.d.ts +13 -0
- package/dist/core/image.js +32 -0
- package/dist/core/ops.d.ts +78 -0
- package/dist/core/ops.js +2 -0
- package/dist/core/printer.d.ts +35 -0
- package/dist/core/printer.js +96 -0
- package/dist/core/rasterizer.d.ts +29 -0
- package/dist/core/rasterizer.js +199 -0
- package/dist/core/star-graphic-encoder.d.ts +12 -0
- package/dist/core/star-graphic-encoder.js +48 -0
- package/dist/core/star-line-encoder.d.ts +8 -0
- package/dist/core/star-line-encoder.js +119 -0
- package/dist/core/status.d.ts +22 -0
- package/dist/core/status.js +52 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +49 -0
- package/dist/label/builder.d.ts +39 -0
- package/dist/label/builder.js +37 -0
- package/dist/label/ezpl.d.ts +6 -0
- package/dist/label/ezpl.js +78 -0
- package/dist/label/index.d.ts +12 -0
- package/dist/label/index.js +42 -0
- package/dist/label/svg.d.ts +3 -0
- package/dist/label/svg.js +46 -0
- package/dist/label/template.d.ts +61 -0
- package/dist/label/template.js +33 -0
- package/dist/label/tspl.d.ts +3 -0
- package/dist/label/tspl.js +83 -0
- package/dist/label/types.d.ts +69 -0
- package/dist/label/types.js +10 -0
- package/dist/label/zpl.d.ts +3 -0
- package/dist/label/zpl.js +57 -0
- package/dist/preview/html.d.ts +14 -0
- package/dist/preview/html.js +132 -0
- package/dist/preview/index.d.ts +8 -0
- package/dist/preview/index.js +14 -0
- package/dist/preview/png.d.ts +3 -0
- package/dist/preview/png.js +89 -0
- package/dist/profiles/epson.d.ts +10 -0
- package/dist/profiles/epson.js +54 -0
- package/dist/profiles/index.d.ts +7 -0
- package/dist/profiles/index.js +36 -0
- package/dist/profiles/star.d.ts +7 -0
- package/dist/profiles/star.js +39 -0
- package/dist/profiles/types.d.ts +41 -0
- package/dist/profiles/types.js +2 -0
- package/dist/template/expr.d.ts +16 -0
- package/dist/template/expr.js +85 -0
- package/dist/template/index.d.ts +4 -0
- package/dist/template/index.js +27 -0
- package/dist/template/render.d.ts +8 -0
- package/dist/template/render.js +119 -0
- package/dist/template/types.d.ts +81 -0
- package/dist/template/types.js +2 -0
- package/dist/transports/discover.d.ts +21 -0
- package/dist/transports/discover.js +73 -0
- package/dist/transports/net.d.ts +20 -0
- package/dist/transports/net.js +72 -0
- package/dist/transports/serial.d.ts +20 -0
- package/dist/transports/serial.js +71 -0
- package/dist/transports/types.d.ts +16 -0
- package/dist/transports/types.js +41 -0
- package/dist/transports/usb.d.ts +30 -0
- package/dist/transports/usb.js +128 -0
- package/dist/util/base64.d.ts +2 -0
- package/dist/util/base64.js +38 -0
- package/package.json +133 -4
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.render = render;
|
|
4
|
+
exports.renderBlocks = renderBlocks;
|
|
5
|
+
const builder_1 = require("../core/builder");
|
|
6
|
+
const expr_1 = require("./expr");
|
|
7
|
+
const bitmap_1 = require("../core/bitmap");
|
|
8
|
+
const ALIGN = { left: 'lt', center: 'ct', right: 'rt' };
|
|
9
|
+
/** Renders a template with data into ops for the given profile. */
|
|
10
|
+
function render(template, data, profile) {
|
|
11
|
+
if (template.version !== 1)
|
|
12
|
+
throw new Error(`retail-print-kit: unsupported template version ${String(template.version)}`);
|
|
13
|
+
const b = new builder_1.OpBuilder(profile);
|
|
14
|
+
renderBlocks(template.blocks, { root: data, current: data }, b);
|
|
15
|
+
return b.operations;
|
|
16
|
+
}
|
|
17
|
+
function renderBlocks(blocks, scope, b) {
|
|
18
|
+
for (const block of blocks)
|
|
19
|
+
renderBlock(block, scope, b);
|
|
20
|
+
}
|
|
21
|
+
function renderBlock(block, scope, b) {
|
|
22
|
+
switch (block.type) {
|
|
23
|
+
case 'text': {
|
|
24
|
+
if (block.align)
|
|
25
|
+
b.align(ALIGN[block.align]);
|
|
26
|
+
if (block.font)
|
|
27
|
+
b.setFont(block.font);
|
|
28
|
+
if (block.size)
|
|
29
|
+
b.size(block.size[0], block.size[1]);
|
|
30
|
+
if (block.style)
|
|
31
|
+
b.style(block.style);
|
|
32
|
+
b.text((0, expr_1.interpolate)(block.value, scope));
|
|
33
|
+
if (block.style)
|
|
34
|
+
b.style('normal');
|
|
35
|
+
if (block.size)
|
|
36
|
+
b.size(1, 1);
|
|
37
|
+
if (block.font)
|
|
38
|
+
b.setFont('a');
|
|
39
|
+
if (block.align && block.align !== 'left')
|
|
40
|
+
b.align('lt');
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'line':
|
|
44
|
+
b.drawLine(block.char ?? '-');
|
|
45
|
+
break;
|
|
46
|
+
case 'row':
|
|
47
|
+
b.tableCustom(block.cells.map((c) => ({ text: (0, expr_1.interpolate)(c.value, scope), width: c.width, align: c.align ?? 'LEFT' })));
|
|
48
|
+
break;
|
|
49
|
+
case 'table': {
|
|
50
|
+
const rows = (0, expr_1.evaluate)(block.rows, scope);
|
|
51
|
+
if (block.header !== false && block.columns.some((c) => c.header)) {
|
|
52
|
+
b.tableCustom(block.columns.map((c) => ({ text: c.header ?? '', width: c.width, align: c.align ?? 'LEFT' })));
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(rows))
|
|
55
|
+
break;
|
|
56
|
+
rows.forEach((row, index) => {
|
|
57
|
+
const rowScope = { root: scope.root, current: row, index };
|
|
58
|
+
const cells = block.columns.map((c) => ({ text: (0, expr_1.interpolate)(c.value, rowScope), width: c.width, align: c.align ?? 'LEFT' }));
|
|
59
|
+
b.tableCustom(cells);
|
|
60
|
+
});
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'feed':
|
|
64
|
+
b.feed(block.lines ?? 1);
|
|
65
|
+
break;
|
|
66
|
+
case 'newline':
|
|
67
|
+
b.newLine(block.count ?? 1);
|
|
68
|
+
break;
|
|
69
|
+
case 'cut':
|
|
70
|
+
b.cut(block.partial ?? false, block.feed ?? 3);
|
|
71
|
+
break;
|
|
72
|
+
case 'drawer':
|
|
73
|
+
b.cashdraw(block.pin ?? 2);
|
|
74
|
+
break;
|
|
75
|
+
case 'qr': {
|
|
76
|
+
if (block.align)
|
|
77
|
+
b.align(ALIGN[block.align]);
|
|
78
|
+
b.qr((0, expr_1.interpolate)(block.value, scope), { size: block.size });
|
|
79
|
+
if (block.align && block.align !== 'left')
|
|
80
|
+
b.align('lt');
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'barcode': {
|
|
84
|
+
if (block.align)
|
|
85
|
+
b.align(ALIGN[block.align]);
|
|
86
|
+
b.barcode((0, expr_1.interpolate)(block.value, scope), block.format ?? 'CODE128', {
|
|
87
|
+
height: block.height,
|
|
88
|
+
width: block.width,
|
|
89
|
+
position: block.text === false ? 'none' : 'below',
|
|
90
|
+
});
|
|
91
|
+
if (block.align && block.align !== 'left')
|
|
92
|
+
b.align('lt');
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'image': {
|
|
96
|
+
if (block.align)
|
|
97
|
+
b.align(ALIGN[block.align]);
|
|
98
|
+
b.image((0, bitmap_1.deserializeBitmap)(block));
|
|
99
|
+
if (block.align && block.align !== 'left')
|
|
100
|
+
b.align('lt');
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 'each': {
|
|
104
|
+
const items = (0, expr_1.evaluate)(block.items, scope);
|
|
105
|
+
if (!Array.isArray(items))
|
|
106
|
+
break;
|
|
107
|
+
items.forEach((item, index) => renderBlocks(block.blocks, { root: scope.root, current: item, index }, b));
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case 'if':
|
|
111
|
+
if ((0, expr_1.truthy)(block.when, scope))
|
|
112
|
+
renderBlocks(block.blocks, scope, b);
|
|
113
|
+
else if (block.else)
|
|
114
|
+
renderBlocks(block.else, scope, b);
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`retail-print-kit: unknown template block "${block.type}"`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { BarcodeType } from '../core/ops';
|
|
2
|
+
import { EscposStyle } from '../core/builder';
|
|
3
|
+
export type TAlign = 'left' | 'center' | 'right';
|
|
4
|
+
export type TCellAlign = 'LEFT' | 'CENTER' | 'RIGHT';
|
|
5
|
+
/** A column of a `table` block. `value` is a template string evaluated per row. */
|
|
6
|
+
export interface TableColumn {
|
|
7
|
+
value: string;
|
|
8
|
+
header?: string;
|
|
9
|
+
width?: number;
|
|
10
|
+
align?: TCellAlign;
|
|
11
|
+
}
|
|
12
|
+
export type Block = {
|
|
13
|
+
type: 'text';
|
|
14
|
+
value: string;
|
|
15
|
+
align?: TAlign;
|
|
16
|
+
size?: [number, number];
|
|
17
|
+
style?: EscposStyle;
|
|
18
|
+
font?: 'a' | 'b';
|
|
19
|
+
} | {
|
|
20
|
+
type: 'line';
|
|
21
|
+
char?: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'row';
|
|
24
|
+
cells: Array<{
|
|
25
|
+
value: string;
|
|
26
|
+
width?: number;
|
|
27
|
+
align?: TCellAlign;
|
|
28
|
+
}>;
|
|
29
|
+
} | {
|
|
30
|
+
type: 'table';
|
|
31
|
+
rows: string;
|
|
32
|
+
columns: TableColumn[];
|
|
33
|
+
header?: boolean;
|
|
34
|
+
} | {
|
|
35
|
+
type: 'feed';
|
|
36
|
+
lines?: number;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'newline';
|
|
39
|
+
count?: number;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'cut';
|
|
42
|
+
partial?: boolean;
|
|
43
|
+
feed?: number;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'drawer';
|
|
46
|
+
pin?: 2 | 5;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'qr';
|
|
49
|
+
value: string;
|
|
50
|
+
size?: number;
|
|
51
|
+
align?: TAlign;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'barcode';
|
|
54
|
+
value: string;
|
|
55
|
+
format?: BarcodeType;
|
|
56
|
+
height?: number;
|
|
57
|
+
width?: number;
|
|
58
|
+
align?: TAlign;
|
|
59
|
+
text?: boolean;
|
|
60
|
+
} | {
|
|
61
|
+
type: 'image';
|
|
62
|
+
width: number;
|
|
63
|
+
height: number;
|
|
64
|
+
data: string;
|
|
65
|
+
align?: TAlign;
|
|
66
|
+
} | {
|
|
67
|
+
type: 'each';
|
|
68
|
+
items: string;
|
|
69
|
+
blocks: Block[];
|
|
70
|
+
} | {
|
|
71
|
+
type: 'if';
|
|
72
|
+
when: string;
|
|
73
|
+
blocks: Block[];
|
|
74
|
+
else?: Block[];
|
|
75
|
+
};
|
|
76
|
+
export interface Template {
|
|
77
|
+
name: string;
|
|
78
|
+
version: 1;
|
|
79
|
+
kind?: 'receipt';
|
|
80
|
+
blocks: Block[];
|
|
81
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Identity } from '../core/identify';
|
|
2
|
+
export interface DiscoverOptions {
|
|
3
|
+
/** TCP port to probe. Default 9100 (raw printing). */
|
|
4
|
+
port?: number;
|
|
5
|
+
/** Per-host connect timeout in milliseconds. Default 400. */
|
|
6
|
+
timeout?: number;
|
|
7
|
+
/** Simultaneous probes. Default 64. */
|
|
8
|
+
concurrency?: number;
|
|
9
|
+
/** Subnets as "192.168.1" prefixes; defaults to every private IPv4 /24 this machine is on. */
|
|
10
|
+
subnets?: string[];
|
|
11
|
+
/** Ask each printer found for its maker and model. Default false. */
|
|
12
|
+
identify?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface DiscoveredPrinter {
|
|
15
|
+
host: string;
|
|
16
|
+
port: number;
|
|
17
|
+
identity?: Identity;
|
|
18
|
+
}
|
|
19
|
+
export declare function localSubnets(): string[];
|
|
20
|
+
/** Sweeps the local /24 networks for hosts that accept raw printing connections. */
|
|
21
|
+
export declare function discoverNetworkPrinters(options?: DiscoverOptions): Promise<DiscoveredPrinter[]>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.localSubnets = localSubnets;
|
|
7
|
+
exports.discoverNetworkPrinters = discoverNetworkPrinters;
|
|
8
|
+
const net_1 = __importDefault(require("net"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const net_2 = require("./net");
|
|
11
|
+
const identify_1 = require("../core/identify");
|
|
12
|
+
function isPrivate(ip) {
|
|
13
|
+
return /^10\./.test(ip) || /^192\.168\./.test(ip) || /^172\.(1[6-9]|2\d|3[01])\./.test(ip);
|
|
14
|
+
}
|
|
15
|
+
function localSubnets() {
|
|
16
|
+
const out = new Set();
|
|
17
|
+
for (const list of Object.values(os_1.default.networkInterfaces())) {
|
|
18
|
+
for (const a of list ?? []) {
|
|
19
|
+
if (a.family === 'IPv4' && !a.internal && isPrivate(a.address))
|
|
20
|
+
out.add(a.address.split('.').slice(0, 3).join('.'));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return [...out];
|
|
24
|
+
}
|
|
25
|
+
function probe(host, port, timeout) {
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const s = new net_1.default.Socket();
|
|
28
|
+
const done = (ok) => {
|
|
29
|
+
s.destroy();
|
|
30
|
+
resolve(ok);
|
|
31
|
+
};
|
|
32
|
+
s.setTimeout(timeout, () => done(false));
|
|
33
|
+
s.once('error', () => done(false));
|
|
34
|
+
s.connect(port, host, () => done(true));
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Sweeps the local /24 networks for hosts that accept raw printing connections. */
|
|
38
|
+
async function discoverNetworkPrinters(options = {}) {
|
|
39
|
+
const port = options.port ?? 9100;
|
|
40
|
+
const timeout = options.timeout ?? 400;
|
|
41
|
+
const subnets = options.subnets ?? localSubnets();
|
|
42
|
+
const hosts = [];
|
|
43
|
+
for (const s of subnets)
|
|
44
|
+
for (let i = 1; i < 255; i++)
|
|
45
|
+
hosts.push(`${s}.${i}`);
|
|
46
|
+
const found = [];
|
|
47
|
+
let next = 0;
|
|
48
|
+
const worker = async () => {
|
|
49
|
+
while (next < hosts.length) {
|
|
50
|
+
const host = hosts[next++];
|
|
51
|
+
if (await probe(host, port, timeout))
|
|
52
|
+
found.push({ host, port });
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
await Promise.all(Array.from({ length: Math.min(options.concurrency ?? 64, hosts.length) }, worker));
|
|
56
|
+
found.sort((a, b) => a.host.localeCompare(b.host, undefined, { numeric: true }));
|
|
57
|
+
if (options.identify) {
|
|
58
|
+
for (const p of found) {
|
|
59
|
+
const t = new net_2.NetTransport(p.host, { port: p.port, timeout: 2000 });
|
|
60
|
+
try {
|
|
61
|
+
await t.open();
|
|
62
|
+
p.identity = await (0, identify_1.identify)(t, 800);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* leave unidentified */
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await t.close().catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return found;
|
|
73
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Transport } from './types';
|
|
2
|
+
export interface NetOptions {
|
|
3
|
+
port?: number;
|
|
4
|
+
/** Connect and write timeout in milliseconds. Default 5000. */
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
/** Raw TCP ("JetDirect", port 9100). Pure Node, works with every network receipt printer. */
|
|
8
|
+
export declare class NetTransport implements Transport {
|
|
9
|
+
readonly host: string;
|
|
10
|
+
readonly name = "net";
|
|
11
|
+
private socket;
|
|
12
|
+
private readonly inbox;
|
|
13
|
+
private readonly port;
|
|
14
|
+
private readonly timeout;
|
|
15
|
+
constructor(host: string, options?: NetOptions);
|
|
16
|
+
open(): Promise<void>;
|
|
17
|
+
write(bytes: Buffer): Promise<void>;
|
|
18
|
+
read(timeoutMs: number): Promise<Buffer>;
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.NetTransport = void 0;
|
|
7
|
+
const net_1 = __importDefault(require("net"));
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
/** Raw TCP ("JetDirect", port 9100). Pure Node, works with every network receipt printer. */
|
|
10
|
+
class NetTransport {
|
|
11
|
+
constructor(host, options = {}) {
|
|
12
|
+
this.host = host;
|
|
13
|
+
this.name = 'net';
|
|
14
|
+
this.socket = null;
|
|
15
|
+
this.inbox = new types_1.ReadQueue();
|
|
16
|
+
this.port = options.port ?? 9100;
|
|
17
|
+
this.timeout = options.timeout ?? 5000;
|
|
18
|
+
}
|
|
19
|
+
open() {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const socket = new net_1.default.Socket();
|
|
22
|
+
const fail = (err) => {
|
|
23
|
+
socket.destroy();
|
|
24
|
+
this.socket = null;
|
|
25
|
+
reject(err);
|
|
26
|
+
};
|
|
27
|
+
socket.setTimeout(this.timeout, () => fail(new Error(`retail-print-kit: timeout connecting to ${this.host}:${this.port}`)));
|
|
28
|
+
socket.once('error', fail);
|
|
29
|
+
socket.connect(this.port, this.host, () => {
|
|
30
|
+
socket.removeListener('error', fail);
|
|
31
|
+
socket.setTimeout(0);
|
|
32
|
+
socket.on('data', (d) => this.inbox.push(Buffer.from(d)));
|
|
33
|
+
socket.on('error', () => this.inbox.clear());
|
|
34
|
+
this.socket = socket;
|
|
35
|
+
resolve();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
write(bytes) {
|
|
40
|
+
const socket = this.socket;
|
|
41
|
+
if (!socket)
|
|
42
|
+
return Promise.reject(new Error('retail-print-kit: net transport is not open'));
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const timer = setTimeout(() => reject(new Error(`retail-print-kit: timeout writing to ${this.host}:${this.port}`)), this.timeout);
|
|
45
|
+
socket.write(bytes, (err) => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
if (err)
|
|
48
|
+
reject(err);
|
|
49
|
+
else
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
read(timeoutMs) {
|
|
55
|
+
if (!this.socket)
|
|
56
|
+
return Promise.reject(new Error('retail-print-kit: net transport is not open'));
|
|
57
|
+
return this.inbox.next(timeoutMs, `${this.host}:${this.port}`);
|
|
58
|
+
}
|
|
59
|
+
close() {
|
|
60
|
+
const socket = this.socket;
|
|
61
|
+
this.socket = null;
|
|
62
|
+
this.inbox.clear();
|
|
63
|
+
if (!socket)
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
socket.once('close', () => resolve());
|
|
67
|
+
socket.end();
|
|
68
|
+
setTimeout(() => socket.destroy(), this.timeout).unref();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.NetTransport = NetTransport;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Transport } from './types';
|
|
2
|
+
export interface SerialOptions {
|
|
3
|
+
baudRate?: number;
|
|
4
|
+
/** Write timeout in milliseconds. Default 5000. */
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
7
|
+
/** RS-232 / USB-serial printer. Needs the optional `serialport` peer dependency. */
|
|
8
|
+
export declare class SerialTransport implements Transport {
|
|
9
|
+
readonly path: string;
|
|
10
|
+
private readonly options;
|
|
11
|
+
readonly name = "serial";
|
|
12
|
+
private port;
|
|
13
|
+
private readonly inbox;
|
|
14
|
+
constructor(path: string, options?: SerialOptions);
|
|
15
|
+
static list(): Promise<string[]>;
|
|
16
|
+
open(): Promise<void>;
|
|
17
|
+
write(bytes: Buffer): Promise<void>;
|
|
18
|
+
read(timeoutMs: number): Promise<Buffer>;
|
|
19
|
+
close(): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SerialTransport = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
function loadSerial() {
|
|
6
|
+
try {
|
|
7
|
+
return require('serialport').SerialPort;
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
throw new Error('retail-print-kit: the "serialport" package is not installed; run `npm install serialport` to print over serial');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** RS-232 / USB-serial printer. Needs the optional `serialport` peer dependency. */
|
|
14
|
+
class SerialTransport {
|
|
15
|
+
constructor(path, options = {}) {
|
|
16
|
+
this.path = path;
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.name = 'serial';
|
|
19
|
+
this.port = null;
|
|
20
|
+
this.inbox = new types_1.ReadQueue();
|
|
21
|
+
}
|
|
22
|
+
static async list() {
|
|
23
|
+
const SerialPort = loadSerial();
|
|
24
|
+
const ports = await SerialPort.list();
|
|
25
|
+
return ports.map((p) => p.path);
|
|
26
|
+
}
|
|
27
|
+
open() {
|
|
28
|
+
const SerialPort = loadSerial();
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const port = new SerialPort({ path: this.path, baudRate: this.options.baudRate ?? 9600 }, (err) => {
|
|
31
|
+
if (err)
|
|
32
|
+
return reject(err);
|
|
33
|
+
port.on('data', (d) => this.inbox.push(Buffer.from(d)));
|
|
34
|
+
this.port = port;
|
|
35
|
+
resolve();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
write(bytes) {
|
|
40
|
+
const port = this.port;
|
|
41
|
+
if (!port)
|
|
42
|
+
return Promise.reject(new Error('retail-print-kit: serial transport is not open'));
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const timer = setTimeout(() => reject(new Error(`retail-print-kit: timeout writing to ${this.path}`)), this.options.timeout ?? 5000);
|
|
45
|
+
port.write(bytes, (err) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
return reject(err);
|
|
49
|
+
}
|
|
50
|
+
port.drain((e) => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
e ? reject(e) : resolve();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
read(timeoutMs) {
|
|
58
|
+
if (!this.port)
|
|
59
|
+
return Promise.reject(new Error('retail-print-kit: serial transport is not open'));
|
|
60
|
+
return this.inbox.next(timeoutMs, this.path);
|
|
61
|
+
}
|
|
62
|
+
close() {
|
|
63
|
+
const port = this.port;
|
|
64
|
+
this.port = null;
|
|
65
|
+
this.inbox.clear();
|
|
66
|
+
if (!port || !port.isOpen)
|
|
67
|
+
return Promise.resolve();
|
|
68
|
+
return new Promise((resolve, reject) => port.close((err) => (err ? reject(err) : resolve())));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.SerialTransport = SerialTransport;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface Transport {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
open(): Promise<void>;
|
|
4
|
+
write(bytes: Buffer): Promise<void>;
|
|
5
|
+
/** Resolves with the next bytes the printer sends, or rejects after `timeoutMs`. Optional. */
|
|
6
|
+
read?(timeoutMs: number): Promise<Buffer>;
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/** Shared read queue for transports that receive data as events. */
|
|
10
|
+
export declare class ReadQueue {
|
|
11
|
+
private chunks;
|
|
12
|
+
private waiters;
|
|
13
|
+
push(chunk: Buffer): void;
|
|
14
|
+
next(timeoutMs: number, what: string): Promise<Buffer>;
|
|
15
|
+
clear(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReadQueue = void 0;
|
|
4
|
+
/** Shared read queue for transports that receive data as events. */
|
|
5
|
+
class ReadQueue {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.chunks = [];
|
|
8
|
+
this.waiters = [];
|
|
9
|
+
}
|
|
10
|
+
push(chunk) {
|
|
11
|
+
const w = this.waiters.shift();
|
|
12
|
+
if (w) {
|
|
13
|
+
clearTimeout(w.timer);
|
|
14
|
+
w.resolve(chunk);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
this.chunks.push(chunk);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
next(timeoutMs, what) {
|
|
21
|
+
const ready = this.chunks.shift();
|
|
22
|
+
if (ready)
|
|
23
|
+
return Promise.resolve(ready);
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const timer = setTimeout(() => {
|
|
26
|
+
this.waiters = this.waiters.filter((x) => x.timer !== timer);
|
|
27
|
+
reject(new Error(`retail-print-kit: timeout waiting for ${what}`));
|
|
28
|
+
}, timeoutMs);
|
|
29
|
+
this.waiters.push({ resolve, reject, timer });
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
clear() {
|
|
33
|
+
this.chunks = [];
|
|
34
|
+
for (const w of this.waiters) {
|
|
35
|
+
clearTimeout(w.timer);
|
|
36
|
+
w.reject(new Error('retail-print-kit: transport closed'));
|
|
37
|
+
}
|
|
38
|
+
this.waiters = [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.ReadQueue = ReadQueue;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Transport } from './types';
|
|
2
|
+
export interface UsbOptions {
|
|
3
|
+
vendorId?: number;
|
|
4
|
+
productId?: number;
|
|
5
|
+
/** Write timeout in milliseconds. Default 5000. */
|
|
6
|
+
timeout?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* USB printer-class device. Needs the optional `usb` peer dependency. The detach listener is
|
|
10
|
+
* attached on open and removed on close, so nothing leaks across printer instances.
|
|
11
|
+
*/
|
|
12
|
+
export declare class UsbTransport implements Transport {
|
|
13
|
+
private readonly options;
|
|
14
|
+
readonly name = "usb";
|
|
15
|
+
private usb;
|
|
16
|
+
private device;
|
|
17
|
+
private endpoint;
|
|
18
|
+
private inEndpoint;
|
|
19
|
+
private iface;
|
|
20
|
+
private onDetach;
|
|
21
|
+
constructor(options?: UsbOptions);
|
|
22
|
+
static list(): Array<{
|
|
23
|
+
vendorId: number;
|
|
24
|
+
productId: number;
|
|
25
|
+
}>;
|
|
26
|
+
open(): Promise<void>;
|
|
27
|
+
write(bytes: Buffer): Promise<void>;
|
|
28
|
+
read(timeoutMs: number): Promise<Buffer>;
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
}
|