Skip to content

Latest commit

 

History

History
2852 lines (2574 loc) · 138 KB

File metadata and controls

2852 lines (2574 loc) · 138 KB
 
1
'use strict';
2
3
/**
Oct 26, 2023
Oct 26, 2023
4
* @version 2.2.4-SNAPSHOT
5
* @overview QZ Tray Connector
Dec 7, 2023
Dec 7, 2023
6
* @license LGPL-2.1-only
7
* <p/>
8
* Connects a web client to the QZ Tray software.
9
* Enables printing and device communication from javascript.
10
*/
11
var qz = (function() {
12
13
///// POLYFILLS /////
14
15
if (!Array.isArray) {
16
Array.isArray = function(arg) {
17
return Object.prototype.toString.call(arg) === '[object Array]';
18
};
19
}
20
Jun 9, 2020
Jun 9, 2020
21
if (!Number.isInteger) {
22
Number.isInteger = function(value) {
23
return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
24
};
25
}
26
27
///// PRIVATE METHODS /////
28
29
var _qz = {
Oct 26, 2023
Oct 26, 2023
30
VERSION: "2.2.4-SNAPSHOT", //must match @version above
31
DEBUG: false,
32
33
log: {
34
/** Debugging messages */
35
trace: function() { if (_qz.DEBUG) { console.log.apply(console, arguments); } },
36
/** General messages */
37
info: function() { console.info.apply(console, arguments); },
Dec 20, 2019
Dec 20, 2019
38
/** General warnings */
39
warn: function() { console.warn.apply(console, arguments); },
40
/** Debugging errors */
Dec 20, 2019
Dec 20, 2019
41
allay: function() { if (_qz.DEBUG) { console.warn.apply(console, arguments); } },
42
/** General errors */
43
error: function() { console.error.apply(console, arguments); }
44
},
45
46
May 14, 2016
May 14, 2016
47
//stream types
48
streams: {
Mar 16, 2021
Mar 16, 2021
49
serial: 'SERIAL', usb: 'USB', hid: 'HID', printer: 'PRINTER', file: 'FILE', socket: 'SOCKET'
50
},
51
52
53
websocket: {
54
/** The actual websocket object managing the connection. */
55
connection: null,
Oct 5, 2023
Oct 5, 2023
56
/** Track if a connection attempt is being cancelled. */
57
shutdown: false,
58
59
/** Default parameters used on new connections. Override values using options parameter on {@link qz.websocket.connect}. */
60
connectConfig: {
Aug 16, 2016
Aug 16, 2016
61
host: ["localhost", "localhost.qz.io"], //hosts QZ Tray can be running on
62
hostIndex: 0, //internal var - index on host array
63
usingSecure: true, //boolean use of secure protocol
64
protocol: {
Aug 16, 2016
Aug 16, 2016
65
secure: "wss://", //secure websocket
66
insecure: "ws://" //insecure websocket
67
},
68
port: {
69
secure: [8181, 8282, 8383, 8484], //list of secure ports QZ Tray could be listening on
70
insecure: [8182, 8283, 8384, 8485], //list of insecure ports QZ Tray could be listening on
Aug 16, 2016
Aug 16, 2016
71
portIndex: 0 //internal var - index on active port array
72
},
73
keepAlive: 60, //time between pings to keep connection alive, in seconds
74
retries: 0, //number of times to reconnect before failing
75
delay: 0 //seconds before firing a connection
76
},
77
78
setup: {
79
/** Loop through possible ports to open connection, sets web socket calls that will settle the promise. */
80
findConnection: function(config, resolve, reject) {
Oct 5, 2023
Oct 5, 2023
81
if (_qz.websocket.shutdown) {
82
reject(new Error("Connection attempt cancelled by user"));
83
return;
84
}
85
Jun 29, 2017
Jun 29, 2017
86
//force flag if missing ports
87
if (!config.port.secure.length) {
88
if (!config.port.insecure.length) {
89
reject(new Error("No ports have been specified to connect over"));
90
return;
91
} else if (config.usingSecure) {
92
_qz.log.error("No secure ports specified - forcing insecure connection");
93
config.usingSecure = false;
94
}
95
} else if (!config.port.insecure.length && !config.usingSecure) {
96
_qz.log.trace("No insecure ports specified - forcing secure connection");
97
config.usingSecure = true;
98
}
99
Aug 17, 2016
Aug 17, 2016
100
var deeper = function() {
Oct 5, 2023
Oct 5, 2023
101
if (_qz.websocket.shutdown) {
102
//connection attempt was cancelled, bail out
103
reject(new Error("Connection attempt cancelled by user"));
104
return;
105
}
106
Aug 17, 2016
Aug 17, 2016
107
config.port.portIndex++;
108
109
if ((config.usingSecure && config.port.portIndex >= config.port.secure.length)
110
|| (!config.usingSecure && config.port.portIndex >= config.port.insecure.length)) {
111
if (config.hostIndex >= config.host.length - 1) {
112
//give up, all hope is lost
113
reject(new Error("Unable to establish connection with QZ"));
114
return;
115
} else {
116
config.hostIndex++;
117
config.port.portIndex = 0;
118
}
119
}
120
121
// recursive call until connection established or all ports are exhausted
122
_qz.websocket.setup.findConnection(config, resolve, reject);
123
};
124
125
var address;
126
if (config.usingSecure) {
Aug 16, 2016
Aug 16, 2016
127
address = config.protocol.secure + config.host[config.hostIndex] + ":" + config.port.secure[config.port.portIndex];
128
} else {
Aug 16, 2016
Aug 16, 2016
129
address = config.protocol.insecure + config.host[config.hostIndex] + ":" + config.port.insecure[config.port.portIndex];
130
}
131
132
try {
133
_qz.log.trace("Attempting connection", address);
134
_qz.websocket.connection = new _qz.tools.ws(address);
135
}
136
catch(err) {
137
_qz.log.error(err);
Aug 17, 2016
Aug 17, 2016
138
deeper();
139
return;
140
}
141
142
if (_qz.websocket.connection != null) {
143
_qz.websocket.connection.established = false;
144
145
//called on successful connection to qz, begins setup of websocket calls and resolves connect promise after certificate is sent
146
_qz.websocket.connection.onopen = function(evt) {
Jun 27, 2018
Jun 27, 2018
147
if (!_qz.websocket.connection.established) {
148
_qz.log.trace(evt);
149
_qz.log.info("Established connection with QZ Tray on " + address);
150
Jun 27, 2018
Jun 27, 2018
151
_qz.websocket.setup.openConnection({ resolve: resolve, reject: reject });
152
Jun 27, 2018
Jun 27, 2018
153
if (config.keepAlive > 0) {
154
var interval = setInterval(function() {
Feb 1, 2023
Feb 1, 2023
155
if (!_qz.tools.isActive() || _qz.websocket.connection.interval !== interval) {
Jun 27, 2018
Jun 27, 2018
156
clearInterval(interval);
157
return;
158
}
159
Jun 27, 2018
Jun 27, 2018
160
_qz.websocket.connection.send("ping");
161
}, config.keepAlive * 1000);
Feb 1, 2023
Feb 1, 2023
162
163
_qz.websocket.connection.interval = interval;
Jun 27, 2018
Jun 27, 2018
164
}
165
}
166
};
167
168
//called during websocket close during setup
169
_qz.websocket.connection.onclose = function() {
170
// Safari compatibility fix to raise error event
Jun 27, 2018
Jun 27, 2018
171
if (_qz.websocket.connection && typeof navigator !== 'undefined' && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) {
172
_qz.websocket.connection.onerror();
173
}
174
};
175
176
//called for errors during setup (such as invalid ports), reject connect promise only if all ports have been tried
177
_qz.websocket.connection.onerror = function(evt) {
178
_qz.log.trace(evt);
Dec 12, 2017
Dec 12, 2017
179
180
_qz.websocket.connection = null;
181
Aug 17, 2016
Aug 17, 2016
182
deeper();
183
};
184
} else {
Aug 17, 2016
Aug 17, 2016
185
reject(new Error("Unable to create a websocket connection"));
186
}
187
},
188
189
/** Finish setting calls on successful connection, sets web socket calls that won't settle the promise. */
190
openConnection: function(openPromise) {
191
_qz.websocket.connection.established = true;
192
193
//called when an open connection is closed
194
_qz.websocket.connection.onclose = function(evt) {
195
_qz.log.trace(evt);
196
197
_qz.websocket.connection = null;
Mar 16, 2021
Mar 16, 2021
198
_qz.websocket.callClose(evt);
199
_qz.log.info("Closed connection with QZ Tray");
Mar 21, 2016
Mar 21, 2016
200
201
for(var uid in _qz.websocket.pendingCalls) {
202
if (_qz.websocket.pendingCalls.hasOwnProperty(uid)) {
203
_qz.websocket.pendingCalls[uid].reject(new Error("Connection closed before response received"));
204
}
205
}
Mar 16, 2021
Mar 16, 2021
206
207
//if this is set, then an explicit close call was made
208
if (this.promise != undefined) {
209
this.promise.resolve();
210
}
211
};
212
213
//called for any errors with an open connection
214
_qz.websocket.connection.onerror = function(evt) {
215
_qz.websocket.callError(evt);
216
};
217
218
//send JSON objects to qz
219
_qz.websocket.connection.sendData = function(obj) {
220
_qz.log.trace("Preparing object for websocket", obj);
221
222
if (obj.timestamp == undefined) {
223
obj.timestamp = Date.now();
Apr 30, 2018
Apr 30, 2018
224
if (typeof obj.timestamp !== 'number') {
225
obj.timestamp = new Date().getTime();
226
}
227
}
228
if (obj.promise != undefined) {
229
obj.uid = _qz.websocket.setup.newUID();
230
_qz.websocket.pendingCalls[obj.uid] = obj.promise;
231
}
232
Mar 31, 2018
Mar 31, 2018
233
// track requesting monitor
234
obj.position = {
Sep 30, 2022
Sep 30, 2022
235
x: typeof screen !== 'undefined' ? ((screen.availWidth || screen.width) / 2) + (screen.left || screen.availLeft || 0) : 0,
236
y: typeof screen !== 'undefined' ? ((screen.availHeight || screen.height) / 2) + (screen.top || screen.availTop || 0) : 0
Mar 31, 2018
Mar 31, 2018
237
};
238
239
try {
Sep 23, 2022
Sep 23, 2022
240
if (obj.call != undefined && obj.signature == undefined && _qz.security.needsSigned(obj.call)) {
241
var signObj = {
242
call: obj.call,
243
params: obj.params,
244
timestamp: obj.timestamp
245
};
246
Nov 15, 2017
Nov 15, 2017
247
//make a hashing promise if not already one
248
var hashing = _qz.tools.hash(_qz.tools.stringify(signObj));
249
if (!hashing.then) {
250
hashing = _qz.tools.promise(function(resolve) {
251
resolve(hashing);
252
});
253
}
254
255
hashing.then(function(hashed) {
256
return _qz.security.callSign(hashed);
257
}).then(function(signature) {
258
_qz.log.trace("Signature for call", signature);
Sep 23, 2022
Sep 23, 2022
259
obj.signature = signature || "";
260
obj.signAlgorithm = _qz.security.signAlgorithm;
Nov 27, 2019
Nov 27, 2019
261
262
_qz.signContent = undefined;
263
_qz.websocket.connection.send(_qz.tools.stringify(obj));
264
});
265
} else {
266
_qz.log.trace("Signature for call", obj.signature);
267
268
//called for pre-signed content and (unsigned) setup calls
269
_qz.websocket.connection.send(_qz.tools.stringify(obj));
270
}
271
}
272
catch(err) {
273
_qz.log.error(err);
274
275
if (obj.promise != undefined) {
276
obj.promise.reject(err);
277
delete _qz.websocket.pendingCalls[obj.uid];
278
}
279
}
280
};
281
282
//receive message from qz
283
_qz.websocket.connection.onmessage = function(evt) {
284
var returned = JSON.parse(evt.data);
285
286
if (returned.uid == null) {
287
if (returned.type == null) {
288
//incorrect response format, likely connected to incompatible qz version
289
_qz.websocket.connection.close(4003, "Connected to incompatible QZ Tray version");
290
291
} else {
May 14, 2016
May 14, 2016
292
//streams (callbacks only, no promises)
293
switch(returned.type) {
294
case _qz.streams.serial:
295
if (!returned.event) {
296
returned.event = JSON.stringify({ portName: returned.key, output: returned.data });
297
}
298
299
_qz.serial.callSerial(JSON.parse(returned.event));
300
break;
Mar 16, 2021
Mar 16, 2021
301
case _qz.streams.socket:
302
_qz.socket.callSocket(JSON.parse(returned.event));
303
break;
May 14, 2016
May 14, 2016
304
case _qz.streams.usb:
305
if (!returned.event) {
306
returned.event = JSON.stringify({ vendorId: returned.key[0], productId: returned.key[1], output: returned.data });
307
}
308
309
_qz.usb.callUsb(JSON.parse(returned.event));
310
break;
Jun 29, 2016
Jun 29, 2016
311
case _qz.streams.hid:
312
_qz.hid.callHid(JSON.parse(returned.event));
313
break;
Jun 15, 2019
Jun 15, 2019
314
case _qz.streams.printer:
315
_qz.printers.callPrinter(JSON.parse(returned.event));
316
break;
Jun 15, 2019
Jun 15, 2019
317
case _qz.streams.file:
318
_qz.file.callFile(JSON.parse(returned.event));
319
break;
May 14, 2016
May 14, 2016
320
default:
Dec 20, 2019
Dec 20, 2019
321
_qz.log.allay("Cannot determine stream type for callback", returned);
May 14, 2016
May 14, 2016
322
break;
323
}
324
}
325
326
return;
327
}
328
329
_qz.log.trace("Received response from websocket", returned);
330
331
var promise = _qz.websocket.pendingCalls[returned.uid];
332
if (promise == undefined) {
Dec 20, 2019
Dec 20, 2019
333
_qz.log.allay('No promise found for returned response');
334
} else {
335
if (returned.error != undefined) {
336
promise.reject(new Error(returned.error));
337
} else {
338
promise.resolve(returned.result);
339
}
340
}
341
342
delete _qz.websocket.pendingCalls[returned.uid];
343
};
344
345
346
//send up the certificate before making any calls
347
//also gives the user a chance to deny the connection
348
function sendCert(cert) {
349
if (cert === undefined) { cert = null; }
350
Jul 1, 2020
Jul 1, 2020
351
//websocket setup, query what version is connected
352
qz.api.getVersion().then(function(version) {
353
_qz.websocket.connection.version = version;
354
_qz.websocket.connection.semver = version.toLowerCase().replace(/-rc\./g, "-rc").split(/[\\+\\.-]/g);
355
for(var i = 0; i < _qz.websocket.connection.semver.length; i++) {
356
try {
357
if (i == 3 && _qz.websocket.connection.semver[i].toLowerCase().indexOf("rc") == 0) {
358
// Handle "rc1" pre-release by negating build info
359
_qz.websocket.connection.semver[i] = -(_qz.websocket.connection.semver[i].replace(/\D/g, ""));
360
continue;
361
}
362
_qz.websocket.connection.semver[i] = parseInt(_qz.websocket.connection.semver[i]);
363
}
364
catch(ignore) {}
Jun 15, 2019
Jun 15, 2019
365
Jul 1, 2020
Jul 1, 2020
366
if (_qz.websocket.connection.semver.length < 4) {
367
_qz.websocket.connection.semver[3] = 0;
Jun 9, 2020
Jun 9, 2020
368
}
369
}
Nov 27, 2019
Nov 27, 2019
370
Jul 1, 2020
Jul 1, 2020
371
//algorithm can be declared before a connection, check for incompatibilities now that we have one
372
_qz.compatible.algorithm(true);
373
}).then(function() {
374
_qz.websocket.connection.sendData({ certificate: cert, promise: openPromise });
375
});
376
}
377
Jul 15, 2021
Jul 15, 2021
378
_qz.security.callCert().then(sendCert).catch(function(error) {
379
_qz.log.warn("Failed to get certificate:", error);
Sep 4, 2023
Sep 4, 2023
380
381
if (_qz.security.rejectOnCertFailure) {
382
openPromise.reject(error);
383
} else {
384
sendCert(null);
385
}
Jul 15, 2021
Jul 15, 2021
386
});
387
},
388
389
/** Generate unique ID used to map a response to a call. */
390
newUID: function() {
391
var len = 6;
392
return (new Array(len + 1).join("0") + (Math.random() * Math.pow(36, len) << 0).toString(36)).slice(-len)
393
}
394
},
395
Apr 6, 2016
Apr 6, 2016
396
dataPromise: function(callName, params, signature, signingTimestamp) {
397
return _qz.tools.promise(function(resolve, reject) {
398
var msg = {
399
call: callName,
400
promise: { resolve: resolve, reject: reject },
401
params: params,
402
signature: signature,
403
timestamp: signingTimestamp
404
};
405
406
_qz.websocket.connection.sendData(msg);
407
});
408
},
409
410
/** Library of promises awaiting a response, uid -> promise */
411
pendingCalls: {},
412
413
/** List of functions to call on error from the websocket. */
414
errorCallbacks: [],
415
/** Calls all functions registered to listen for errors. */
416
callError: function(evt) {
417
if (Array.isArray(_qz.websocket.errorCallbacks)) {
418
for(var i = 0; i < _qz.websocket.errorCallbacks.length; i++) {
419
_qz.websocket.errorCallbacks[i](evt);
420
}
421
} else {
422
_qz.websocket.errorCallbacks(evt);
423
}
424
},
425
426
/** List of function to call on closing from the websocket. */
427
closedCallbacks: [],
428
/** Calls all functions registered to listen for closing. */
429
callClose: function(evt) {
430
if (Array.isArray(_qz.websocket.closedCallbacks)) {
431
for(var i = 0; i < _qz.websocket.closedCallbacks.length; i++) {
432
_qz.websocket.closedCallbacks[i](evt);
433
}
434
} else {
435
_qz.websocket.closedCallbacks(evt);
436
}
437
}
438
},
439
440
441
printing: {
442
/** Default options used for new printer configs. Can be overridden using {@link qz.configs.setDefaults}. */
443
defaultConfig: {
444
//value purposes are explained in the qz.configs.setDefaults docs
445
Jan 31, 2020
Jan 31, 2020
446
bounds: null,
447
colorType: 'color',
448
copies: 1,
449
density: 0,
450
duplex: false,
Jun 29, 2017
Jun 29, 2017
451
fallbackDensity: null,
452
interpolation: 'bicubic',
Mar 21, 2016
Mar 21, 2016
453
jobName: null,
Mar 30, 2018
Mar 30, 2018
454
legacy: false,
455
margins: 0,
456
orientation: null,
457
paperThickness: null,
458
printerTray: null,
Jun 15, 2019
Jun 15, 2019
459
rasterize: false,
460
rotation: 0,
461
scaleContent: true,
462
size: null,
463
units: 'in',
464
Oct 5, 2021
Oct 5, 2021
465
forceRaw: false,
466
encoding: null,
Nov 13, 2020
Nov 13, 2020
467
spool: null
468
}
469
},
470
471
472
serial: {
473
/** List of functions called when receiving data from serial connection. */
474
serialCallbacks: [],
475
/** Calls all functions registered to listen for serial events. */
May 14, 2016
May 14, 2016
476
callSerial: function(streamEvent) {
477
if (Array.isArray(_qz.serial.serialCallbacks)) {
478
for(var i = 0; i < _qz.serial.serialCallbacks.length; i++) {
May 14, 2016
May 14, 2016
479
_qz.serial.serialCallbacks[i](streamEvent);
480
}
481
} else {
May 14, 2016
May 14, 2016
482
_qz.serial.serialCallbacks(streamEvent);
483
}
484
}
485
},
486
487
Mar 16, 2021
Mar 16, 2021
488
socket: {
489
/** List of functions called when receiving data from network socket connection. */
490
socketCallbacks: [],
491
/** Calls all functions registered to listen for network socket events. */
492
callSocket: function(socketEvent) {
493
if (Array.isArray(_qz.socket.socketCallbacks)) {
494
for(var i = 0; i < _qz.socket.socketCallbacks.length; i++) {
495
_qz.socket.socketCallbacks[i](socketEvent);
496
}
497
} else {
498
_qz.socket.socketCallbacks(socketEvent);
499
}
500
}
501
},
502
503
504
usb: {
505
/** List of functions called when receiving data from usb connection. */
506
usbCallbacks: [],
May 14, 2016
May 14, 2016
507
/** Calls all functions registered to listen for usb events. */
508
callUsb: function(streamEvent) {
509
if (Array.isArray(_qz.usb.usbCallbacks)) {
510
for(var i = 0; i < _qz.usb.usbCallbacks.length; i++) {
May 14, 2016
May 14, 2016
511
_qz.usb.usbCallbacks[i](streamEvent);
512
}
513
} else {
May 14, 2016
May 14, 2016
514
_qz.usb.usbCallbacks(streamEvent);
515
}
516
}
517
},
518
519
Jun 29, 2016
Jun 29, 2016
520
hid: {
521
/** List of functions called when receiving data from hid connection. */
522
hidCallbacks: [],
523
/** Calls all functions registered to listen for hid events. */
524
callHid: function(streamEvent) {
525
if (Array.isArray(_qz.hid.hidCallbacks)) {
526
for(var i = 0; i < _qz.hid.hidCallbacks.length; i++) {
527
_qz.hid.hidCallbacks[i](streamEvent);
528
}
529
} else {
530
_qz.hid.hidCallbacks(streamEvent);
531
}
532
}
533
},
534
535
Jun 15, 2019
Jun 15, 2019
536
printers: {
537
/** List of functions called when receiving data from printer connection. */
538
printerCallbacks: [],
539
/** Calls all functions registered to listen for printer events. */
540
callPrinter: function(streamEvent) {
541
if (Array.isArray(_qz.printers.printerCallbacks)) {
542
for(var i = 0; i < _qz.printers.printerCallbacks.length; i++) {
543
_qz.printers.printerCallbacks[i](streamEvent);
544
}
545
} else {
546
_qz.printers.printerCallbacks(streamEvent);
547
}
548
}
549
},
550
551
Jun 15, 2019
Jun 15, 2019
552
file: {
553
/** List of functions called when receiving info regarding file changes. */
554
fileCallbacks: [],
555
/** Calls all functions registered to listen for file events. */
556
callFile: function(streamEvent) {
557
if (Array.isArray(_qz.file.fileCallbacks)) {
558
for(var i = 0; i < _qz.file.fileCallbacks.length; i++) {
559
_qz.file.fileCallbacks[i](streamEvent);
560
}
561
} else {
562
_qz.file.fileCallbacks(streamEvent);
563
}
564
}
565
},
566
567
568
security: {
569
/** Function used to resolve promise when acquiring site's public certificate. */
Feb 20, 2020
Feb 20, 2020
570
certHandler: function(resolve, reject) { reject(); },
571
/** Called to create new promise (using {@link _qz.security.certHandler}) for certificate retrieval. */
572
callCert: function() {
Jan 28, 2022
Jan 28, 2022
573
if (typeof _qz.security.certHandler.then === 'function') {
Feb 20, 2020
Feb 20, 2020
574
//already a promise
Jan 28, 2022
Jan 28, 2022
575
return _qz.security.certHandler;
576
} else if (_qz.security.certHandler.constructor.name === "AsyncFunction") {
577
//already callable as a promise
Feb 20, 2020
Feb 20, 2020
578
return _qz.security.certHandler();
579
} else {
580
//turn into a promise
581
return _qz.tools.promise(_qz.security.certHandler);
582
}
583
},
584
585
/** Function used to create promise resolver when requiring signed calls. */
Feb 20, 2020
Feb 20, 2020
586
signatureFactory: function() { return function(resolve) { resolve(); } },
587
/** Called to create new promise (using {@link _qz.security.signatureFactory}) for signed calls. */
588
callSign: function(toSign) {
Jan 28, 2022
Jan 28, 2022
589
if (_qz.security.signatureFactory.constructor.name === "AsyncFunction") {
590
//use directly
Feb 20, 2020
Feb 20, 2020
591
return _qz.security.signatureFactory(toSign);
592
} else {
Jan 28, 2022
Jan 28, 2022
593
//use in a promise
Feb 20, 2020
Feb 20, 2020
594
return _qz.tools.promise(_qz.security.signatureFactory(toSign));
595
}
Nov 27, 2019
Nov 27, 2019
596
},
597
598
/** Signing algorithm used on signatures */
Sep 23, 2022
Sep 23, 2022
599
signAlgorithm: "SHA1",
600
Sep 4, 2023
Sep 4, 2023
601
rejectOnCertFailure: false,
602
Sep 23, 2022
Sep 23, 2022
603
needsSigned: function(callName) {
604
const undialoged = [
605
"printers.getStatus",
606
"printers.stopListening",
607
"usb.isClaimed",
608
"usb.closeStream",
609
"usb.releaseDevice",
610
"hid.stopListening",
611
"hid.isClaimed",
612
"hid.closeStream",
613
"hid.releaseDevice",
614
"file.stopListening",
615
"getVersion"
616
];
617
618
return callName != null && undialoged.indexOf(callName) === -1;
619
}
620
},
621
622
623
tools: {
624
/** Create a new promise */
625
promise: function(resolver) {
Apr 27, 2020
Apr 27, 2020
626
//prefer global object for historical purposes
Jul 1, 2020
Jul 1, 2020
627
if (typeof RSVP !== 'undefined') {
Apr 27, 2020
Apr 27, 2020
628
return new RSVP.Promise(resolver);
Jul 1, 2020
Jul 1, 2020
629
} else if (typeof Promise !== 'undefined') {
Apr 27, 2020
Apr 27, 2020
630
return new Promise(resolver);
631
} else {
632
_qz.log.error("Promise/A+ support is required. See qz.api.setPromiseType(...)");
633
}
634
},
635
Apr 6, 2022
Apr 6, 2022
636
/** Stub for rejecting with an Error from withing a Promise */
637
reject: function(error) {
638
return _qz.tools.promise(function(resolve, reject) {
639
reject(error);
640
});
641
},
642
643
stringify: function(object) {
644
//old versions of prototype affect stringify
645
var pjson = Array.prototype.toJSON;
646
delete Array.prototype.toJSON;
647
Sep 23, 2022
Sep 23, 2022
648
function skipKeys(key, value) {
649
if (key === "promise") {
650
return undefined;
651
}
652
653
return value;
654
}
655
656
var result = JSON.stringify(object, skipKeys);
657
Oct 18, 2016
Oct 18, 2016
658
if (pjson) {
659
Array.prototype.toJSON = pjson;
660
}
661
662
return result;
663
},
664
Jun 29, 2017
Jun 29, 2017
665
hash: function(data) {
Apr 27, 2020
Apr 27, 2020
666
//prefer global object for historical purposes
Jul 1, 2020
Jul 1, 2020
667
if (typeof Sha256 !== 'undefined') {
Apr 27, 2020
Apr 27, 2020
668
return Sha256.hash(data);
669
} else {
670
return _qz.SHA.hash(data);
671
}
Jun 29, 2017
Jun 29, 2017
672
},
673
674
ws: typeof WebSocket !== 'undefined' ? WebSocket : null,
675
676
absolute: function(loc) {
Jun 29, 2017
Jun 29, 2017
677
if (typeof window !== 'undefined' && typeof document.createElement === 'function') {
678
var a = document.createElement("a");
679
a.href = loc;
680
return a.href;
Jul 1, 2020
Jul 1, 2020
681
} else if (typeof exports === 'object') {
Dec 20, 2019
Dec 20, 2019
682
//node.js
683
require('path').resolve(loc);
684
}
685
return loc;
686
},
687
Jun 15, 2019
Jun 15, 2019
688
relative: function(data) {
689
for(var i = 0; i < data.length; i++) {
690
if (data[i].constructor === Object) {
691
var absolute = false;
692
Apr 20, 2023
Apr 20, 2023
693
if (data[i].data && data[i].data.search && data[i].data.search(/data:image\/\w+;base64,/) === 0) {
Jun 15, 2019
Jun 15, 2019
694
//upgrade from old base64 behavior
695
data[i].flavor = "base64";
Jun 15, 2019
Jun 15, 2019
696
data[i].data = data[i].data.replace(/^data:image\/\w+;base64,/, "");
Jun 15, 2019
Jun 15, 2019
697
} else if (data[i].flavor) {
Jun 15, 2019
Jun 15, 2019
698
//if flavor is known, we can directly check for absolute flavor types
Jun 15, 2019
Jun 15, 2019
699
if (["FILE", "XML"].indexOf(data[i].flavor.toUpperCase()) > -1) {
Jun 15, 2019
Jun 15, 2019
700
absolute = true;
701
}
Jun 15, 2019
Jun 15, 2019
702
} else if (data[i].format && ["HTML", "IMAGE", "PDF", "FILE", "XML"].indexOf(data[i].format.toUpperCase()) > -1) {
Jun 15, 2019
Jun 15, 2019
703
//if flavor is not known, all valid pixel formats default to file flavor
Jun 15, 2019
Jun 15, 2019
704
//previous v2.0 data also used format as what is now flavor, so we check for those values here too
Jun 15, 2019
Jun 15, 2019
705
absolute = true;
Jul 2, 2019
Jul 2, 2019
706
} else if (data[i].type && ((["PIXEL", "IMAGE", "PDF"].indexOf(data[i].type.toUpperCase()) > -1 && !data[i].format)
Jun 15, 2019
Jun 15, 2019
707
|| (["HTML", "PDF"].indexOf(data[i].type.toUpperCase()) > -1 && (!data[i].format || data[i].format.toUpperCase() === "FILE")))) {
Jun 15, 2019
Jun 15, 2019
708
//if all we know is pixel type, then it is image's file flavor
Jun 15, 2019
Jun 15, 2019
709
//previous v2.0 data also used type as what is now format, so we check for those value here too
Jun 15, 2019
Jun 15, 2019
710
absolute = true;
711
}
712
713
if (absolute) {
714
//change relative links to absolute
715
data[i].data = _qz.tools.absolute(data[i].data);
716
}
717
if (data[i].options && typeof data[i].options.overlay === 'string') {
718
data[i].options.overlay = _qz.tools.absolute(data[i].options.overlay);
719
}
720
}
721
}
722
},
723
724
/** Performs deep copy to target from remaining params */
725
extend: function(target) {
726
//special case when reassigning properties as objects in a deep copy
727
if (typeof target !== 'object') {
728
target = {};
729
}
730
731
for(var i = 1; i < arguments.length; i++) {
732
var source = arguments[i];
733
if (!source) { continue; }
734
735
for(var key in source) {
736
if (source.hasOwnProperty(key)) {
737
if (target === source[key]) { continue; }
738
739
if (source[key] && source[key].constructor && source[key].constructor === Object) {
740
var clone;
741
if (Array.isArray(source[key])) {
742
clone = target[key] || [];
743
} else {
744
clone = target[key] || {};
745
}
746
747
target[key] = _qz.tools.extend(clone, source[key]);
748
} else if (source[key] !== undefined) {
749
target[key] = source[key];
750
}
751
}
752
}
753
}
754
755
return target;
Jun 15, 2019
Jun 15, 2019
756
},
757
Jun 9, 2020
Jun 9, 2020
758
versionCompare: function(major, minor, patch, build) {
Jun 10, 2020
Jun 10, 2020
759
if (_qz.tools.assertActive()) {
760
var semver = _qz.websocket.connection.semver;
761
if (semver[0] != major) {
762
return semver[0] - major;
763
}
764
if (minor != undefined && semver[1] != minor) {
765
return semver[1] - minor;
766
}
767
if (patch != undefined && semver[2] != patch) {
768
return semver[2] - patch;
769
}
770
if (build != undefined && semver.length > 3 && semver[3] != build) {
771
return Number.isInteger(semver[3]) && Number.isInteger(build) ? semver[3] - build : semver[3].toString().localeCompare(build.toString());
772
}
773
return 0;
Jun 9, 2020
Jun 9, 2020
774
}
775
},
776
777
isVersion: function(major, minor, patch, build) {
778
return _qz.tools.versionCompare(major, minor, patch, build) == 0;
Jun 10, 2020
Jun 10, 2020
779
},
780
781
isActive: function() {
Oct 5, 2023
Oct 5, 2023
782
return !_qz.websocket.shutdown && _qz.websocket.connection != null
783
&& (_qz.websocket.connection.readyState === _qz.tools.ws.OPEN
784
|| _qz.websocket.connection.readyState === _qz.tools.ws.CONNECTING);
Jun 10, 2020
Jun 10, 2020
785
},
786
787
assertActive: function() {
788
if (_qz.tools.isActive()) {
789
return true;
790
}
791
// Promise won't reject on throw; yet better than 'undefined'
792
throw new Error("A connection to QZ has not been established yet");
Apr 20, 2023
Apr 20, 2023
793
},
794
795
uint8ArrayToHex: function(uint8) {
796
return Array.from(uint8)
797
.map(function(i) { return i.toString(16).padStart(2, '0'); })
798
.join('');
799
},
800
801
uint8ArrayToBase64: function(uint8) {
802
/**
803
* Adapted from Egor Nepomnyaschih's code under MIT Licence (C) 2020
804
* see https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
805
*/
806
var map = [
807
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
808
"V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
809
"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
810
];
811
812
var result = '', i, l = uint8.length;
813
for (i = 2; i < l; i += 3) {
814
result += map[uint8[i - 2] >> 2];
815
result += map[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)];
816
result += map[((uint8[i - 1] & 0x0F) << 2) | (uint8[i] >> 6)];
817
result += map[uint8[i] & 0x3F];
818
}
819
if (i === l + 1) { // 1 octet yet to write
820
result += map[uint8[i - 2] >> 2];
821
result += map[(uint8[i - 2] & 0x03) << 4];
822
result += "==";
823
}
824
if (i === l) { // 2 octets yet to write
825
result += map[uint8[i - 2] >> 2];
826
result += map[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)];
827
result += map[(uint8[i - 1] & 0x0F) << 2];
828
result += "=";
829
}
830
return result;
831
},
Dec 20, 2019
Dec 20, 2019
832
},
833
834
compatible: {
835
/** Converts message format to a previous version's */
836
data: function(printData) {
Apr 20, 2023
Apr 20, 2023
837
// special handling for Uint8Array
Jul 6, 2023
Jul 6, 2023
838
for(var i = 0; i < printData.length; i++) {
839
if (printData[i].constructor === Object && printData[i].data instanceof Uint8Array) {
840
if (printData[i].flavor) {
841
var flavor = printData[i].flavor.toString().toUpperCase();
842
switch(flavor) {
843
case 'BASE64':
844
printData[i].data = _qz.tools.uint8ArrayToBase64(printData[i].data);
845
break;
846
case 'HEX':
847
printData[i].data = _qz.tools.uint8ArrayToHex(printData[i].data);
848
break;
849
default:
850
throw new Error("Uint8Array conversion to '" + flavor + "' is not supported.");
851
}
Apr 20, 2023
Apr 20, 2023
852
}
853
}
Apr 18, 2024
Apr 18, 2024
854
}
855
856
if(_qz.tools.versionCompare(2, 2, 4) < 0) {
857
for(var i = 0; i < printData.length; i++) {
858
if (printData[i].constructor === Object) {
859
// dotDensity: "double-legacy|single-legacy" since 2.2.4. Fallback to "double|single"
860
if (printData[i].options && typeof printData[i].options.dotDensity === 'string') {
861
printData[i].options.dotDensity = printData[i].options.dotDensity.toLowerCase().replace("-legacy", "");
862
}
863
}
864
}
Apr 20, 2023
Apr 20, 2023
865
}
866
Dec 20, 2019
Dec 20, 2019
867
if (_qz.tools.isVersion(2, 0)) {
Jun 15, 2019
Jun 15, 2019
868
/*
869
2.0.x conversion
870
-----
871
type=pixel -> use format as 2.0 type (unless 'command' format, which forces 2.0 'raw' type)
872
type=raw -> 2.0 type has to be 'raw'
873
if format is 'image' -> force 2.0 'image' format, ignore everything else (unsupported in 2.0)
874
875
flavor translates straight to 2.0 format (unless forced to 'raw'/'image')
876
*/
877
_qz.log.trace("Converting print data to v2.0 for " + _qz.websocket.connection.version);
878
for(var i = 0; i < printData.length; i++) {
879
if (printData[i].constructor === Object) {
Feb 7, 2020
Feb 7, 2020
880
if (printData[i].type && printData[i].type.toUpperCase() === "RAW" && printData[i].format && printData[i].format.toUpperCase() === "IMAGE") {
881
if (printData[i].flavor && printData[i].flavor.toUpperCase() === "BASE64") {
Jun 15, 2019
Jun 15, 2019
882
//special case for raw base64 images
883
printData[i].data = "data:image/compat;base64," + printData[i].data;
884
}
885
printData[i].flavor = "IMAGE"; //forces 'image' format when shifting for conversion
886
}
Feb 7, 2020
Feb 7, 2020
887
if ((printData[i].type && printData[i].type.toUpperCase() === "RAW") || (printData[i].format && printData[i].format.toUpperCase() === "COMMAND")) {
Jun 15, 2019
Jun 15, 2019
888
printData[i].format = "RAW"; //forces 'raw' type when shifting for conversion
889
}
890
891
printData[i].type = printData[i].format;
892
printData[i].format = printData[i].flavor;
893
delete printData[i].flavor;
894
}
895
}
896
}
Dec 20, 2019
Dec 20, 2019
897
},
898
Feb 26, 2020
Feb 26, 2020
899
/* Converts config defaults to match previous version */
900
config: function(config, dirty) {
901
if (_qz.tools.isVersion(2, 0)) {
902
if (!dirty.rasterize) {
903
config.rasterize = true;
904
}
905
}
Jan 7, 2022
Jan 7, 2022
906
if(_qz.tools.versionCompare(2, 2) < 0) {
907
if(config.forceRaw !== 'undefined') {
Oct 5, 2021
Oct 5, 2021
908
config.altPrinting = config.forceRaw;
909
delete config.forceRaw;
910
}
911
}
Nov 13, 2020
Nov 13, 2020
912
if(_qz.tools.versionCompare(2, 1, 2, 11) < 0) {
Nov 13, 2020
Nov 13, 2020
913
if(config.spool) {
914
if(config.spool.size) {
915
config.perSpool = config.spool.size;
916
delete config.spool.size;
917
}
918
if(config.spool.end) {
919
config.endOfDoc = config.spool.end;
920
delete config.spool.end;
921
}
922
delete config.spool;
Nov 13, 2020
Nov 13, 2020
923
}
924
}
Feb 26, 2020
Feb 26, 2020
925
return config;
926
},
927
Dec 20, 2019
Dec 20, 2019
928
/** Compat wrapper with previous version **/
929
networking: function(hostname, port, signature, signingTimestamp, mappingCallback) {
930
// Use 2.0
931
if (_qz.tools.isVersion(2, 0)) {
932
return _qz.tools.promise(function(resolve, reject) {
933
_qz.websocket.dataPromise('websocket.getNetworkInfo', {
934
hostname: hostname,
935
port: port
936
}, signature, signingTimestamp).then(function(data) {
937
if (typeof mappingCallback !== 'undefined') {
938
resolve(mappingCallback(data));
939
} else {
940
resolve(data);
941
}
942
}, reject);
943
});
944
}
945
// Wrap 2.1
946
return _qz.tools.promise(function(resolve, reject) {
947
_qz.websocket.dataPromise('networking.device', {
948
hostname: hostname,
949
port: port
950
}, signature, signingTimestamp).then(function(data) {
951
resolve({ ipAddress: data.ip, macAddress: data.mac });
952
}, reject);
953
});
954
},
955
956
/** Check if QZ version supports chosen algorithm */
957
algorithm: function(quiet) {
958
//if not connected yet we will assume compatibility exists for the time being
Jun 10, 2020
Jun 10, 2020
959
if (_qz.tools.isActive()) {
Dec 20, 2019
Dec 20, 2019
960
if (_qz.tools.isVersion(2, 0)) {
Jul 1, 2020
Jul 1, 2020
961
if (!quiet) {
Dec 20, 2019
Dec 20, 2019
962
_qz.log.warn("Connected to an older version of QZ, alternate signature algorithms are not supported");
963
}
964
return false;
965
}
966
}
967
968
return true;
969
}
Apr 27, 2020
Apr 27, 2020
970
},
971
972
/**
973
* Adapted from Chris Veness's code under MIT Licence (C) 2002
974
* see http://www.movable-type.co.uk/scripts/sha256.html
975
*/
976
SHA: {
977
//@formatter:off - keep this block compact
978
hash: function(msg) {
979
// add trailing '1' bit (+ 0's padding) to string [§5.1.1]
Jan 19, 2023
Jan 19, 2023
980
msg = _qz.SHA._utf8Encode(msg) + String.fromCharCode(0x80);
Apr 27, 2020
Apr 27, 2020
981
982
// constants [§4.2.2]
983
var K = [
984
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
985
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
986
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
987
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
988
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
989
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
990
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
991
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
992
];
993
// initial hash value [§5.3.1]
994
var H = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ];
995
996
// convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
997
var l = msg.length / 4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
998
var N = Math.ceil(l / 16); // number of 16-integer-blocks required to hold 'l' ints
999
var M = new Array(N);
1000