@gohcltech/edge-print-client 0.2.0-dev.6
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/README.md +301 -0
- package/dist/edge-print.d.ts +367 -0
- package/dist/edge-print.js +259 -0
- package/package.json +22 -0
- package/src/edge-print.ts +484 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module edge-print
|
|
3
|
+
*
|
|
4
|
+
* Browser-side WebSocket client for the Edge Printing agent.
|
|
5
|
+
* Drop-in replacement for qz-tray.js with a simpler token-based auth model.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import ep from '@gohcltech/edge-print-client'
|
|
10
|
+
*
|
|
11
|
+
* await ep.connect({ token: 'your-api-token' })
|
|
12
|
+
* const printers = await ep.printers()
|
|
13
|
+
* const jobId = await ep.print(
|
|
14
|
+
* { printer: 'Office Laser' },
|
|
15
|
+
* [{ type: 'pixel', format: 'pdf', flavor: 'base64', data: pdfBase64 }],
|
|
16
|
+
* )
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* WebSocket client for the Edge Printing agent.
|
|
21
|
+
*
|
|
22
|
+
* Each instance manages a single persistent connection. For most applications
|
|
23
|
+
* the exported {@link ep} singleton is sufficient; create additional instances
|
|
24
|
+
* only when you need concurrent connections to different agents.
|
|
25
|
+
*
|
|
26
|
+
* ### Lifecycle
|
|
27
|
+
* ```
|
|
28
|
+
* connect() → printers() / print() / … → disconnect()
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { EdgePrintClient } from '@gohcltech/edge-print-client'
|
|
34
|
+
*
|
|
35
|
+
* const client = new EdgePrintClient()
|
|
36
|
+
* await client.connect({ token: 'abc123' })
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class EdgePrintClient {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.ws = null;
|
|
42
|
+
this.pending = new Map();
|
|
43
|
+
this.authenticated = false;
|
|
44
|
+
this.closeListeners = [];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Open a WebSocket connection to the Edge Printing agent and authenticate.
|
|
48
|
+
*
|
|
49
|
+
* On failure the client retries up to `options.retries` times (default 3),
|
|
50
|
+
* waiting `options.retryDelay` ms (default 1 000) between attempts. If all
|
|
51
|
+
* attempts fail the last error is re-thrown.
|
|
52
|
+
*
|
|
53
|
+
* @throws {Error} If the agent is unreachable or the token is rejected after
|
|
54
|
+
* all retries are exhausted.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* await ep.connect({
|
|
59
|
+
* host: '127.0.0.1',
|
|
60
|
+
* port: 8181,
|
|
61
|
+
* token: 'abc123',
|
|
62
|
+
* retries: 5,
|
|
63
|
+
* retryDelay: 2000,
|
|
64
|
+
* })
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
async connect(options) {
|
|
68
|
+
const { host = '127.0.0.1', port = 8181, token, retries = 3, retryDelay = 1000, } = options;
|
|
69
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
70
|
+
try {
|
|
71
|
+
await this.openSocket(`wss://${host}:${port}`);
|
|
72
|
+
await this.request('auth', { token });
|
|
73
|
+
this.authenticated = true;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (attempt === retries)
|
|
78
|
+
throw err;
|
|
79
|
+
await sleep(retryDelay);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Return all printers available on the agent machine.
|
|
85
|
+
*
|
|
86
|
+
* @throws {Error} If not connected.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const printers = await ep.printers()
|
|
91
|
+
* const colorPrinters = printers.filter(p => p.color)
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
async printers() {
|
|
95
|
+
const resp = await this.request('get_printers', {});
|
|
96
|
+
return resp.printers;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Return the name of the OS default printer.
|
|
100
|
+
*
|
|
101
|
+
* Cheaper than calling {@link printers} when you only need the default name
|
|
102
|
+
* and no other printer metadata.
|
|
103
|
+
*
|
|
104
|
+
* @throws {Error} If not connected.
|
|
105
|
+
*/
|
|
106
|
+
async defaultPrinter() {
|
|
107
|
+
const resp = await this.request('get_default_printer', {});
|
|
108
|
+
return resp.name;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Submit a print job to the agent.
|
|
112
|
+
*
|
|
113
|
+
* @param config - Printer selection and job settings.
|
|
114
|
+
* @param data - One or more content items to print (pages, labels, …).
|
|
115
|
+
* @returns The job ID assigned by the agent.
|
|
116
|
+
*
|
|
117
|
+
* @throws {Error} If not connected, or if the agent rejects the job.
|
|
118
|
+
*
|
|
119
|
+
* @example Print a PDF
|
|
120
|
+
* ```ts
|
|
121
|
+
* const jobId = await ep.print(
|
|
122
|
+
* { printer: 'Office Laser', copies: 2, duplex: 'long-edge' },
|
|
123
|
+
* [{ type: 'pixel', format: 'pdf', flavor: 'base64', data: pdfBase64 }],
|
|
124
|
+
* )
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @example Print a ZPL label
|
|
128
|
+
* ```ts
|
|
129
|
+
* await ep.print(
|
|
130
|
+
* { printer: 'Zebra ZT410' },
|
|
131
|
+
* [{ type: 'raw', format: 'command', flavor: 'plain', data: zplString }],
|
|
132
|
+
* )
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
async print(config, data) {
|
|
136
|
+
const resp = await this.request('print', { config, data });
|
|
137
|
+
return resp.job_id;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Close the WebSocket connection and reset client state.
|
|
141
|
+
*
|
|
142
|
+
* Any in-flight requests are rejected. Safe to call when already
|
|
143
|
+
* disconnected.
|
|
144
|
+
*/
|
|
145
|
+
disconnect() {
|
|
146
|
+
this.ws?.close();
|
|
147
|
+
this.ws = null;
|
|
148
|
+
this.authenticated = false;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* `true` when the WebSocket is open and the session is authenticated.
|
|
152
|
+
*
|
|
153
|
+
* Use this to guard print calls in components that may render before the
|
|
154
|
+
* connection is established.
|
|
155
|
+
*/
|
|
156
|
+
isConnected() {
|
|
157
|
+
return this.ws?.readyState === WebSocket.OPEN && this.authenticated;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Register a callback invoked whenever the connection closes — whether from
|
|
161
|
+
* a network drop, an agent restart, or an explicit {@link disconnect} call.
|
|
162
|
+
*
|
|
163
|
+
* Multiple listeners can be registered; all are called in registration order.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* ep.onClose(() => {
|
|
168
|
+
* console.warn('Lost connection to Edge Printing agent — reconnecting…')
|
|
169
|
+
* reconnect()
|
|
170
|
+
* })
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
onClose(fn) {
|
|
174
|
+
this.closeListeners.push(fn);
|
|
175
|
+
}
|
|
176
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
177
|
+
openSocket(url) {
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const ws = new WebSocket(url);
|
|
180
|
+
ws.onopen = () => { this.ws = ws; resolve(); };
|
|
181
|
+
ws.onerror = () => reject(new Error(`Cannot reach Edge Printing agent at ${url}`));
|
|
182
|
+
ws.onmessage = (ev) => this.handleMessage(String(ev.data));
|
|
183
|
+
ws.onclose = () => {
|
|
184
|
+
this.ws = null;
|
|
185
|
+
this.authenticated = false;
|
|
186
|
+
this.rejectPending(new Error('Connection closed'));
|
|
187
|
+
this.closeListeners.forEach(fn => fn());
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
handleMessage(raw) {
|
|
192
|
+
let msg;
|
|
193
|
+
try {
|
|
194
|
+
msg = JSON.parse(raw);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const id = msg['id'];
|
|
200
|
+
if (!id || !this.pending.has(id))
|
|
201
|
+
return;
|
|
202
|
+
const { resolve, reject } = this.pending.get(id);
|
|
203
|
+
this.pending.delete(id);
|
|
204
|
+
if (msg['type'] === 'error') {
|
|
205
|
+
reject(new Error(msg['message'] ?? 'Unknown error'));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
resolve(msg);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
request(type, payload) {
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
214
|
+
reject(new Error('Not connected to Edge Printing agent'));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const id = crypto.randomUUID();
|
|
218
|
+
this.pending.set(id, { resolve, reject });
|
|
219
|
+
const timeout = setTimeout(() => {
|
|
220
|
+
if (this.pending.has(id)) {
|
|
221
|
+
this.pending.delete(id);
|
|
222
|
+
reject(new Error(`Request "${type}" timed out`));
|
|
223
|
+
}
|
|
224
|
+
}, 30000);
|
|
225
|
+
this.ws.send(JSON.stringify({ type, id, ...payload }));
|
|
226
|
+
// Wrap resolve/reject to also clear the timeout.
|
|
227
|
+
const original = this.pending.get(id);
|
|
228
|
+
this.pending.set(id, {
|
|
229
|
+
resolve: (v) => { clearTimeout(timeout); original.resolve(v); },
|
|
230
|
+
reject: (e) => { clearTimeout(timeout); original.reject(e); },
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
rejectPending(err) {
|
|
235
|
+
for (const { reject } of this.pending.values())
|
|
236
|
+
reject(err);
|
|
237
|
+
this.pending.clear();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function sleep(ms) {
|
|
241
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Shared singleton `EdgePrintClient` instance.
|
|
245
|
+
*
|
|
246
|
+
* Suitable for most single-page applications. Call {@link EdgePrintClient.connect}
|
|
247
|
+
* once at app startup, then use `ep` from any module without passing the
|
|
248
|
+
* client around.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* import ep from '@gohcltech/edge-print-client'
|
|
253
|
+
*
|
|
254
|
+
* await ep.connect({ token: 'abc123' })
|
|
255
|
+
* await ep.print({ printer: 'Office Laser' }, [pdfData])
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
export const ep = new EdgePrintClient();
|
|
259
|
+
export default ep;
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gohcltech/edge-print-client",
|
|
3
|
+
"version": "0.2.0-dev.6",
|
|
4
|
+
"description": "Browser client for the Edge Printing WebSocket agent",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/edge-print.cjs",
|
|
7
|
+
"module": "./dist/edge-print.js",
|
|
8
|
+
"types": "./dist/edge-print.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/edge-print.js",
|
|
12
|
+
"require": "./dist/edge-print.cjs",
|
|
13
|
+
"types": "./dist/edge-print.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "^5.5.0"
|
|
21
|
+
}
|
|
22
|
+
}
|