UNPKG

qz-tray

Version:

Connects a web client to the QZ Tray software. Enables printing and device communication from javascript.

1,040 lines (907 loc) 142 kB
'use strict'; /** * @version 2.2.5 * @overview QZ Tray Connector * @license LGPL-2.1-only * <p/> * Connects a web client to the QZ Tray software. * Enables printing and device communication from javascript. */ var qz = (function() { ///// POLYFILLS ///// if (!Array.isArray) { Array.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; } if (!Number.isInteger) { Number.isInteger = function(value) { return typeof value === 'number' && isFinite(value) && Math.floor(value) === value; }; } ///// PRIVATE METHODS ///// var _qz = { VERSION: "2.2.5", //must match @version above DEBUG: false, log: { /** Debugging messages */ trace: function() { if (_qz.DEBUG) { console.log.apply(console, arguments); } }, /** General messages */ info: function() { console.info.apply(console, arguments); }, /** General warnings */ warn: function() { console.warn.apply(console, arguments); }, /** Debugging errors */ allay: function() { if (_qz.DEBUG) { console.warn.apply(console, arguments); } }, /** General errors */ error: function() { console.error.apply(console, arguments); } }, //stream types streams: { serial: 'SERIAL', usb: 'USB', hid: 'HID', printer: 'PRINTER', file: 'FILE', socket: 'SOCKET' }, websocket: { /** The actual websocket object managing the connection. */ connection: null, /** Track if a connection attempt is being cancelled. */ shutdown: false, /** Default parameters used on new connections. Override values using options parameter on {@link qz.websocket.connect}. */ connectConfig: { host: ["localhost", "localhost.qz.io"], //hosts QZ Tray can be running on hostIndex: 0, //internal var - index on host array usingSecure: true, //boolean use of secure protocol protocol: { secure: "wss://", //secure websocket insecure: "ws://" //insecure websocket }, port: { secure: [8181, 8282, 8383, 8484], //list of secure ports QZ Tray could be listening on insecure: [8182, 8283, 8384, 8485], //list of insecure ports QZ Tray could be listening on portIndex: 0 //internal var - index on active port array }, keepAlive: 60, //time between pings to keep connection alive, in seconds retries: 0, //number of times to reconnect before failing delay: 0 //seconds before firing a connection }, setup: { /** Loop through possible ports to open connection, sets web socket calls that will settle the promise. */ findConnection: function(config, resolve, reject) { if (_qz.websocket.shutdown) { reject(new Error("Connection attempt cancelled by user")); return; } //force flag if missing ports if (!config.port.secure.length) { if (!config.port.insecure.length) { reject(new Error("No ports have been specified to connect over")); return; } else if (config.usingSecure) { _qz.log.error("No secure ports specified - forcing insecure connection"); config.usingSecure = false; } } else if (!config.port.insecure.length && !config.usingSecure) { _qz.log.trace("No insecure ports specified - forcing secure connection"); config.usingSecure = true; } var deeper = function() { if (_qz.websocket.shutdown) { //connection attempt was cancelled, bail out reject(new Error("Connection attempt cancelled by user")); return; } config.port.portIndex++; if ((config.usingSecure && config.port.portIndex >= config.port.secure.length) || (!config.usingSecure && config.port.portIndex >= config.port.insecure.length)) { if (config.hostIndex >= config.host.length - 1) { //give up, all hope is lost reject(new Error("Unable to establish connection with QZ")); return; } else { config.hostIndex++; config.port.portIndex = 0; } } // recursive call until connection established or all ports are exhausted _qz.websocket.setup.findConnection(config, resolve, reject); }; var address; if (config.usingSecure) { address = config.protocol.secure + config.host[config.hostIndex] + ":" + config.port.secure[config.port.portIndex]; } else { address = config.protocol.insecure + config.host[config.hostIndex] + ":" + config.port.insecure[config.port.portIndex]; } try { _qz.log.trace("Attempting connection", address); _qz.websocket.connection = new _qz.tools.ws(address); } catch(err) { _qz.log.error(err); deeper(); return; } if (_qz.websocket.connection != null) { _qz.websocket.connection.established = false; //called on successful connection to qz, begins setup of websocket calls and resolves connect promise after certificate is sent _qz.websocket.connection.onopen = function(evt) { if (!_qz.websocket.connection.established) { _qz.log.trace(evt); _qz.log.info("Established connection with QZ Tray on " + address); _qz.websocket.setup.openConnection({ resolve: resolve, reject: reject }); if (config.keepAlive > 0) { var interval = setInterval(function() { if (!_qz.tools.isActive() || _qz.websocket.connection.interval !== interval) { clearInterval(interval); return; } _qz.websocket.connection.send("ping"); }, config.keepAlive * 1000); _qz.websocket.connection.interval = interval; } } }; //called during websocket close during setup _qz.websocket.connection.onclose = function() { // Safari compatibility fix to raise error event if (_qz.websocket.connection && typeof navigator !== 'undefined' && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { _qz.websocket.connection.onerror(); } }; //called for errors during setup (such as invalid ports), reject connect promise only if all ports have been tried _qz.websocket.connection.onerror = function(evt) { _qz.log.trace(evt); _qz.websocket.connection = null; deeper(); }; } else { reject(new Error("Unable to create a websocket connection")); } }, /** Finish setting calls on successful connection, sets web socket calls that won't settle the promise. */ openConnection: function(openPromise) { _qz.websocket.connection.established = true; //called when an open connection is closed _qz.websocket.connection.onclose = function(evt) { _qz.log.trace(evt); _qz.websocket.connection = null; _qz.websocket.callClose(evt); _qz.log.info("Closed connection with QZ Tray"); for(var uid in _qz.websocket.pendingCalls) { if (_qz.websocket.pendingCalls.hasOwnProperty(uid)) { _qz.websocket.pendingCalls[uid].reject(new Error("Connection closed before response received")); } } //if this is set, then an explicit close call was made if (this.promise != undefined) { this.promise.resolve(); } }; //called for any errors with an open connection _qz.websocket.connection.onerror = function(evt) { _qz.websocket.callError(evt); }; //send JSON objects to qz _qz.websocket.connection.sendData = function(obj) { _qz.log.trace("Preparing object for websocket", obj); if (obj.timestamp == undefined) { obj.timestamp = Date.now(); if (typeof obj.timestamp !== 'number') { obj.timestamp = new Date().getTime(); } } if (obj.promise != undefined) { obj.uid = _qz.websocket.setup.newUID(); _qz.websocket.pendingCalls[obj.uid] = obj.promise; } // track requesting monitor obj.position = { x: typeof screen !== 'undefined' ? ((screen.availWidth || screen.width) / 2) + (screen.left || screen.availLeft || 0) : 0, y: typeof screen !== 'undefined' ? ((screen.availHeight || screen.height) / 2) + (screen.top || screen.availTop || 0) : 0 }; try { if (obj.call != undefined && obj.signature == undefined && _qz.security.needsSigned(obj.call)) { var signObj = { call: obj.call, params: obj.params, timestamp: obj.timestamp }; //make a hashing promise if not already one var hashing = _qz.tools.hash(_qz.tools.stringify(signObj)); if (!hashing.then) { hashing = _qz.tools.promise(function(resolve) { resolve(hashing); }); } hashing.then(function(hashed) { return _qz.security.callSign(hashed); }).then(function(signature) { _qz.log.trace("Signature for call", signature); obj.signature = signature || ""; obj.signAlgorithm = _qz.security.signAlgorithm; _qz.signContent = undefined; _qz.websocket.connection.send(_qz.tools.stringify(obj)); }).catch(function(err) { _qz.log.error("Signing failed", err); if (obj.promise != undefined) { obj.promise.reject(new Error("Failed to sign request")); delete _qz.websocket.pendingCalls[obj.uid]; } }); } else { _qz.log.trace("Signature for call", obj.signature); //called for pre-signed content and (unsigned) setup calls _qz.websocket.connection.send(_qz.tools.stringify(obj)); } } catch(err) { _qz.log.error(err); if (obj.promise != undefined) { obj.promise.reject(err); delete _qz.websocket.pendingCalls[obj.uid]; } } }; //receive message from qz _qz.websocket.connection.onmessage = function(evt) { var returned = JSON.parse(evt.data); if (returned.uid == null) { if (returned.type == null) { //incorrect response format, likely connected to incompatible qz version _qz.websocket.connection.close(4003, "Connected to incompatible QZ Tray version"); } else { //streams (callbacks only, no promises) switch(returned.type) { case _qz.streams.serial: if (!returned.event) { returned.event = JSON.stringify({ portName: returned.key, output: returned.data }); } _qz.serial.callSerial(JSON.parse(returned.event)); break; case _qz.streams.socket: _qz.socket.callSocket(JSON.parse(returned.event)); break; case _qz.streams.usb: if (!returned.event) { returned.event = JSON.stringify({ vendorId: returned.key[0], productId: returned.key[1], output: returned.data }); } _qz.usb.callUsb(JSON.parse(returned.event)); break; case _qz.streams.hid: _qz.hid.callHid(JSON.parse(returned.event)); break; case _qz.streams.printer: _qz.printers.callPrinter(JSON.parse(returned.event)); break; case _qz.streams.file: _qz.file.callFile(JSON.parse(returned.event)); break; default: _qz.log.allay("Cannot determine stream type for callback", returned); break; } } return; } _qz.log.trace("Received response from websocket", returned); var promise = _qz.websocket.pendingCalls[returned.uid]; if (promise == undefined) { _qz.log.allay('No promise found for returned response'); } else { if (returned.error != undefined) { promise.reject(new Error(returned.error)); } else { promise.resolve(returned.result); } } delete _qz.websocket.pendingCalls[returned.uid]; }; //send up the certificate before making any calls //also gives the user a chance to deny the connection function sendCert(cert) { if (cert === undefined) { cert = null; } //websocket setup, query what version is connected qz.api.getVersion().then(function(version) { _qz.websocket.connection.version = version; _qz.websocket.connection.semver = version.toLowerCase().replace(/-rc\./g, "-rc").split(/[\\+\\.-]/g); for(var i = 0; i < _qz.websocket.connection.semver.length; i++) { try { if (i == 3 && _qz.websocket.connection.semver[i].toLowerCase().indexOf("rc") == 0) { // Handle "rc1" pre-release by negating build info _qz.websocket.connection.semver[i] = -(_qz.websocket.connection.semver[i].replace(/\D/g, "")); continue; } _qz.websocket.connection.semver[i] = parseInt(_qz.websocket.connection.semver[i]); } catch(ignore) {} if (_qz.websocket.connection.semver.length < 4) { _qz.websocket.connection.semver[3] = 0; } } //algorithm can be declared before a connection, check for incompatibilities now that we have one _qz.compatible.algorithm(true); }).then(function() { _qz.websocket.connection.sendData({ certificate: cert, promise: openPromise }); }); } _qz.security.callCert().then(sendCert).catch(function(error) { _qz.log.warn("Failed to get certificate:", error); if (_qz.security.rejectOnCertFailure) { openPromise.reject(error); } else { sendCert(null); } }); }, /** Generate unique ID used to map a response to a call. */ newUID: function() { var len = 6; return (new Array(len + 1).join("0") + (Math.random() * Math.pow(36, len) << 0).toString(36)).slice(-len) } }, dataPromise: function(callName, params, signature, signingTimestamp) { return _qz.tools.promise(function(resolve, reject) { var msg = { call: callName, promise: { resolve: resolve, reject: reject }, params: params, signature: signature, timestamp: signingTimestamp }; _qz.websocket.connection.sendData(msg); }); }, /** Library of promises awaiting a response, uid -> promise */ pendingCalls: {}, /** List of functions to call on error from the websocket. */ errorCallbacks: [], /** Calls all functions registered to listen for errors. */ callError: function(evt) { if (Array.isArray(_qz.websocket.errorCallbacks)) { for(var i = 0; i < _qz.websocket.errorCallbacks.length; i++) { _qz.websocket.errorCallbacks[i](evt); } } else { _qz.websocket.errorCallbacks(evt); } }, /** List of function to call on closing from the websocket. */ closedCallbacks: [], /** Calls all functions registered to listen for closing. */ callClose: function(evt) { if (Array.isArray(_qz.websocket.closedCallbacks)) { for(var i = 0; i < _qz.websocket.closedCallbacks.length; i++) { _qz.websocket.closedCallbacks[i](evt); } } else { _qz.websocket.closedCallbacks(evt); } } }, printing: { /** Default options used for new printer configs. Can be overridden using {@link qz.configs.setDefaults}. */ defaultConfig: { //value purposes are explained in the qz.configs.setDefaults docs bounds: null, colorType: 'color', copies: 1, density: 0, duplex: false, fallbackDensity: null, interpolation: 'bicubic', jobName: null, legacy: false, margins: 0, orientation: null, paperThickness: null, printerTray: null, rasterize: false, rotation: 0, scaleContent: true, size: null, units: 'in', forceRaw: false, encoding: null, spool: null } }, serial: { /** List of functions called when receiving data from serial connection. */ serialCallbacks: [], /** Calls all functions registered to listen for serial events. */ callSerial: function(streamEvent) { if (Array.isArray(_qz.serial.serialCallbacks)) { for(var i = 0; i < _qz.serial.serialCallbacks.length; i++) { _qz.serial.serialCallbacks[i](streamEvent); } } else { _qz.serial.serialCallbacks(streamEvent); } } }, socket: { /** List of functions called when receiving data from network socket connection. */ socketCallbacks: [], /** Calls all functions registered to listen for network socket events. */ callSocket: function(socketEvent) { if (Array.isArray(_qz.socket.socketCallbacks)) { for(var i = 0; i < _qz.socket.socketCallbacks.length; i++) { _qz.socket.socketCallbacks[i](socketEvent); } } else { _qz.socket.socketCallbacks(socketEvent); } } }, usb: { /** List of functions called when receiving data from usb connection. */ usbCallbacks: [], /** Calls all functions registered to listen for usb events. */ callUsb: function(streamEvent) { if (Array.isArray(_qz.usb.usbCallbacks)) { for(var i = 0; i < _qz.usb.usbCallbacks.length; i++) { _qz.usb.usbCallbacks[i](streamEvent); } } else { _qz.usb.usbCallbacks(streamEvent); } } }, hid: { /** List of functions called when receiving data from hid connection. */ hidCallbacks: [], /** Calls all functions registered to listen for hid events. */ callHid: function(streamEvent) { if (Array.isArray(_qz.hid.hidCallbacks)) { for(var i = 0; i < _qz.hid.hidCallbacks.length; i++) { _qz.hid.hidCallbacks[i](streamEvent); } } else { _qz.hid.hidCallbacks(streamEvent); } } }, printers: { /** List of functions called when receiving data from printer connection. */ printerCallbacks: [], /** Calls all functions registered to listen for printer events. */ callPrinter: function(streamEvent) { if (Array.isArray(_qz.printers.printerCallbacks)) { for(var i = 0; i < _qz.printers.printerCallbacks.length; i++) { _qz.printers.printerCallbacks[i](streamEvent); } } else { _qz.printers.printerCallbacks(streamEvent); } } }, file: { /** List of functions called when receiving info regarding file changes. */ fileCallbacks: [], /** Calls all functions registered to listen for file events. */ callFile: function(streamEvent) { if (Array.isArray(_qz.file.fileCallbacks)) { for(var i = 0; i < _qz.file.fileCallbacks.length; i++) { _qz.file.fileCallbacks[i](streamEvent); } } else { _qz.file.fileCallbacks(streamEvent); } } }, security: { /** Function used to resolve promise when acquiring site's public certificate. */ certHandler: function(resolve, reject) { reject(); }, /** Called to create new promise (using {@link _qz.security.certHandler}) for certificate retrieval. */ callCert: function() { if (typeof _qz.security.certHandler.then === 'function') { //already a promise return _qz.security.certHandler; } else if (_qz.security.certHandler.constructor.name === "AsyncFunction") { //already callable as a promise return _qz.security.certHandler(); } else { //turn into a promise return _qz.tools.promise(_qz.security.certHandler); } }, /** Function used to create promise resolver when requiring signed calls. */ signatureFactory: function() { return function(resolve) { resolve(); } }, /** Called to create new promise (using {@link _qz.security.signatureFactory}) for signed calls. */ callSign: function(toSign) { if (_qz.security.signatureFactory.constructor.name === "AsyncFunction") { //use directly return _qz.security.signatureFactory(toSign); } else { //use in a promise return _qz.tools.promise(_qz.security.signatureFactory(toSign)); } }, /** Signing algorithm used on signatures */ signAlgorithm: "SHA1", rejectOnCertFailure: false, needsSigned: function(callName) { const undialoged = [ "printers.getStatus", "printers.stopListening", "usb.isClaimed", "usb.closeStream", "usb.releaseDevice", "hid.stopListening", "hid.isClaimed", "hid.closeStream", "hid.releaseDevice", "file.stopListening", "getVersion" ]; return callName != null && undialoged.indexOf(callName) === -1; } }, tools: { /** Create a new promise */ promise: function(resolver) { //prefer global object for historical purposes if (typeof RSVP !== 'undefined') { return new RSVP.Promise(resolver); } else if (typeof Promise !== 'undefined') { return new Promise(resolver); } else { _qz.log.error("Promise/A+ support is required. See qz.api.setPromiseType(...)"); } }, /** Stub for rejecting with an Error from withing a Promise */ reject: function(error) { return _qz.tools.promise(function(resolve, reject) { reject(error); }); }, stringify: function(object) { //old versions of prototype affect stringify var pjson = Array.prototype.toJSON; delete Array.prototype.toJSON; function skipKeys(key, value) { if (key === "promise") { return undefined; } return value; } var result = JSON.stringify(object, skipKeys); if (pjson) { Array.prototype.toJSON = pjson; } return result; }, hash: function(data) { //prefer global object for historical purposes if (typeof Sha256 !== 'undefined') { return Sha256.hash(data); } else { return _qz.SHA.hash(data); } }, ws: typeof WebSocket !== 'undefined' ? WebSocket : null, absolute: function(loc) { if (typeof window !== 'undefined' && typeof document.createElement === 'function') { var a = document.createElement("a"); a.href = loc; return a.href; } else if (typeof exports === 'object') { //node.js require('path').resolve(loc); } return loc; }, relative: function(data) { for(var i = 0; i < data.length; i++) { if (data[i].constructor === Object) { var absolute = false; if (data[i].data && data[i].data.search && data[i].data.search(/data:image\/\w+;base64,/) === 0) { //upgrade from old base64 behavior data[i].flavor = "base64"; data[i].data = data[i].data.replace(/^data:image\/\w+;base64,/, ""); } else if (data[i].flavor) { //if flavor is known, we can directly check for absolute flavor types if (["FILE", "XML"].indexOf(data[i].flavor.toUpperCase()) > -1) { absolute = true; } } else if (data[i].format && ["HTML", "IMAGE", "PDF", "FILE", "XML"].indexOf(data[i].format.toUpperCase()) > -1) { //if flavor is not known, all valid pixel formats default to file flavor //previous v2.0 data also used format as what is now flavor, so we check for those values here too absolute = true; } else if (data[i].type && ((["PIXEL", "IMAGE", "PDF"].indexOf(data[i].type.toUpperCase()) > -1 && !data[i].format) || (["HTML", "PDF"].indexOf(data[i].type.toUpperCase()) > -1 && (!data[i].format || data[i].format.toUpperCase() === "FILE")))) { //if all we know is pixel type, then it is image's file flavor //previous v2.0 data also used type as what is now format, so we check for those value here too absolute = true; } if (absolute) { //change relative links to absolute data[i].data = _qz.tools.absolute(data[i].data); } if (data[i].options && typeof data[i].options.overlay === 'string') { data[i].options.overlay = _qz.tools.absolute(data[i].options.overlay); } } } }, /** Performs deep copy to target from remaining params */ extend: function(target) { //special case when reassigning properties as objects in a deep copy if (typeof target !== 'object') { target = {}; } for(var i = 1; i < arguments.length; i++) { var source = arguments[i]; if (!source) { continue; } for(var key in source) { if (source.hasOwnProperty(key)) { if (target === source[key]) { continue; } if (source[key] && source[key].constructor && source[key].constructor === Object) { var clone; if (Array.isArray(source[key])) { clone = target[key] || []; } else { clone = target[key] || {}; } target[key] = _qz.tools.extend(clone, source[key]); } else if (source[key] !== undefined) { target[key] = source[key]; } } } } return target; }, versionCompare: function(major, minor, patch, build) { if (_qz.tools.assertActive()) { var semver = _qz.websocket.connection.semver; if (semver[0] != major) { return semver[0] - major; } if (minor != undefined && semver[1] != minor) { return semver[1] - minor; } if (patch != undefined && semver[2] != patch) { return semver[2] - patch; } if (build != undefined && semver.length > 3 && semver[3] != build) { return Number.isInteger(semver[3]) && Number.isInteger(build) ? semver[3] - build : semver[3].toString().localeCompare(build.toString()); } return 0; } }, isVersion: function(major, minor, patch, build) { return _qz.tools.versionCompare(major, minor, patch, build) == 0; }, isActive: function() { return !_qz.websocket.shutdown && _qz.websocket.connection != null && (_qz.websocket.connection.readyState === _qz.tools.ws.OPEN || _qz.websocket.connection.readyState === _qz.tools.ws.CONNECTING); }, assertActive: function() { if (_qz.tools.isActive()) { return true; } // Promise won't reject on throw; yet better than 'undefined' throw new Error("A connection to QZ has not been established yet"); }, uint8ArrayToHex: function(uint8) { return Array.from(uint8) .map(function(i) { return i.toString(16).padStart(2, '0'); }) .join(''); }, uint8ArrayToBase64: function(uint8) { /** * Adapted from Egor Nepomnyaschih's code under MIT Licence (C) 2020 * see https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727 */ var map = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/" ]; var result = '', i, l = uint8.length; for (i = 2; i < l; i += 3) { result += map[uint8[i - 2] >> 2]; result += map[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; result += map[((uint8[i - 1] & 0x0F) << 2) | (uint8[i] >> 6)]; result += map[uint8[i] & 0x3F]; } if (i === l + 1) { // 1 octet yet to write result += map[uint8[i - 2] >> 2]; result += map[(uint8[i - 2] & 0x03) << 4]; result += "=="; } if (i === l) { // 2 octets yet to write result += map[uint8[i - 2] >> 2]; result += map[((uint8[i - 2] & 0x03) << 4) | (uint8[i - 1] >> 4)]; result += map[(uint8[i - 1] & 0x0F) << 2]; result += "="; } return result; }, }, compatible: { /** Converts message format to a previous version's */ data: function(printData) { // special handling for Uint8Array for(var i = 0; i < printData.length; i++) { if (printData[i].constructor === Object && printData[i].data instanceof Uint8Array) { if (printData[i].flavor) { var flavor = printData[i].flavor.toString().toUpperCase(); switch(flavor) { case 'BASE64': printData[i].data = _qz.tools.uint8ArrayToBase64(printData[i].data); break; case 'HEX': printData[i].data = _qz.tools.uint8ArrayToHex(printData[i].data); break; default: throw new Error("Uint8Array conversion to '" + flavor + "' is not supported."); } } } } if(_qz.tools.versionCompare(2, 2, 4) < 0) { for(var i = 0; i < printData.length; i++) { if (printData[i].constructor === Object) { // dotDensity: "double-legacy|single-legacy" since 2.2.4. Fallback to "double|single" if (printData[i].options && typeof printData[i].options.dotDensity === 'string') { printData[i].options.dotDensity = printData[i].options.dotDensity.toLowerCase().replace("-legacy", ""); } } } } if (_qz.tools.isVersion(2, 0)) { /* 2.0.x conversion ----- type=pixel -> use format as 2.0 type (unless 'command' format, which forces 2.0 'raw' type) type=raw -> 2.0 type has to be 'raw' if format is 'image' -> force 2.0 'image' format, ignore everything else (unsupported in 2.0) flavor translates straight to 2.0 format (unless forced to 'raw'/'image') */ _qz.log.trace("Converting print data to v2.0 for " + _qz.websocket.connection.version); for(var i = 0; i < printData.length; i++) { if (printData[i].constructor === Object) { if (printData[i].type && printData[i].type.toUpperCase() === "RAW" && printData[i].format && printData[i].format.toUpperCase() === "IMAGE") { if (printData[i].flavor && printData[i].flavor.toUpperCase() === "BASE64") { //special case for raw base64 images printData[i].data = "data:image/compat;base64," + printData[i].data; } printData[i].flavor = "IMAGE"; //forces 'image' format when shifting for conversion } if ((printData[i].type && printData[i].type.toUpperCase() === "RAW") || (printData[i].format && printData[i].format.toUpperCase() === "COMMAND")) { printData[i].format = "RAW"; //forces 'raw' type when shifting for conversion } printData[i].type = printData[i].format; printData[i].format = printData[i].flavor; delete printData[i].flavor; } } } }, /* Converts config defaults to match previous version */ config: function(config, dirty) { if (_qz.tools.isVersion(2, 0)) { if (!dirty.rasterize) { config.rasterize = true; } } if(_qz.tools.versionCompare(2, 2) < 0) { if(config.forceRaw !== 'undefined') { config.altPrinting = config.forceRaw; delete config.forceRaw; } } if(_qz.tools.versionCompare(2, 1, 2, 11) < 0) { if(config.spool) { if(config.spool.size) { config.perSpool = config.spool.size; delete config.spool.size; } if(config.spool.end) { config.endOfDoc = config.spool.end; delete config.spool.end; } delete config.spool; } } return config; }, /** Compat wrapper with previous version **/ networking: function(hostname, port, signature, signingTimestamp, mappingCallback) { // Use 2.0 if (_qz.tools.isVersion(2, 0)) { return _qz.tools.promise(function(resolve, reject) { _qz.websocket.dataPromise('websocket.getNetworkInfo', { hostname: hostname, port: port }, signature, signingTimestamp).then(function(data) { if (typeof mappingCallback !== 'undefined') { resolve(mappingCallback(data)); } else { resolve(data); } }, reject); }); } // Wrap 2.1 return _qz.tools.promise(function(resolve, reject) { _qz.websocket.dataPromise('networking.device', { hostname: hostname, port: port }, signature, signingTimestamp).then(function(data) { resolve({ ipAddress: data.ip, macAddress: data.mac }); }, reject); }); }, /** Check if QZ version supports chosen algorithm */ algorithm: function(quiet) { //if not connected yet we will assume compatibility exists for the time being //check semver to guard race condition for pending connections if (_qz.tools.isActive() && _qz.websocket.connection.semver) { if (_qz.tools.isVersion(2, 0)) { if (!quiet) { _qz.log.warn("Connected to an older version of QZ, alternate signature algorithms are not supported"); } return false; } } return true; } }, /** * Adapted from Chris Veness's code under MIT Licence (C) 2002 * see http://www.movable-type.co.uk/scripts/sha256.html */ SHA: { //@formatter:off - keep this block compact hash: function(msg) { // add trailing '1' bit (+ 0's padding) to string [§5.1.1] msg = _qz.SHA._utf8Encode(msg) + String.fromCharCode(0x80); // constants [§4.2.2] var K = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ]; // initial hash value [§5.3.1] var H = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1] var l = msg.length / 4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length var N = Math.ceil(l / 16); // number of 16-integer-blocks required to hold 'l' ints var M = new Array(N); for(var i = 0; i < N; i++) { M[i] = new Array(16); for(var j = 0; j < 16; j++) { // encode 4 chars per integer, big-endian encoding M[i][j] = (msg.charCodeAt(i * 64 + j * 4) << 24) | (msg.charCodeAt(i * 64 + j * 4 + 1) << 16) | (msg.charCodeAt(i * 64 + j * 4 + 2) << 8) | (msg.charCodeAt(i * 64 + j * 4 + 3)); } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0 } // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1] // note: most significant word would be (len-1)*8 >>> 32, but since JS converts // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators M[N-1][14] = ((msg.length - 1) * 8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]); M[N-1][15] = ((msg.length - 1) * 8) & 0xffffffff; // HASH COMPUTATION [§6.1.2] var W = new Array(64); var a, b, c, d, e, f, g, h; for(var i = 0; i < N; i++) { // 1 - prepare message schedule 'W' for(var t = 0; t < 16; t++) { W[t] = M[i][t]; } for(var t = 16; t < 64; t++) { W[t] = (_qz.SHA._dev1(W[t-2]) + W[t-7] + _qz.SHA._dev0(W[t-15]) + W[t-16]) & 0xffffffff; } // 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value a = H[0]; b = H[1]; c = H[2]; d = H[3]; e = H[4]; f = H[5]; g = H[6]; h = H[7]; // 3 - main loop (note 'addition modulo 2^32') for(var t = 0; t < 64; t++) { var T1 = h + _qz.SHA._sig1(e) + _qz.SHA._ch(e, f, g) + K[t] + W[t]; var T2 = _qz.SHA._sig0(a) + _qz.SHA._maj(a, b, c); h = g; g = f; f = e; e = (d + T1) & 0xffffffff; d = c; c = b; b = a; a = (T1 + T2) & 0xffffffff; } // 4 - compute the new intermediate hash value (note 'addition modulo 2^32') H[0] = (H[0]+a) & 0xffffffff; H[1] = (H[1]+b) & 0xffffffff; H[2] = (H[2]+c) & 0xffffffff; H[3] = (H[3]+d) & 0xffffffff; H[4] = (H[4]+e)