@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.
@@ -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
+ }