/**
* Enum for TWAIN JS Connection Status.
* @enum {object}
*/
const TWJSConnectionStatus = {
CONNECTING: { value: 0, name: "CONNECTING" },
CONNECTED: { value: 1, name: "CONNECTED" },
CLOSING: { value: 2, name: "CLOSING" },
CLOSED: { value: 3, name: "CLOSED" },
ERROR: { value: 4, name: "ERROR" }
};
// javascript-obfuscator:disable
/**
* Enum for TWDeviceName.
* @enum {string}
*/
const TWDeviceName = {
DEFAULT: "",
SELECT_FROM_UI: "SelectFromUI"
};
/**
* Enum for scan finished status.
* @enum {string}
*/
const TWScanFinishedStatus = {
SUCCESS: "success",
CANCELLED: "cancelled",
ERROR: "error"
};
/**
* Enum for PDF Protection encryption level.
* @enum {Number}
*/
const PdfDocumentSecurityLevel = {
NONE: 0 /** No encryption */,
ENCRYPTED_40BIT: 1 /** 40-bit encryption */,
ENCRYPTED_128BIT: 2 /** 128-bit encryption */
};
const TWSendMessageType = {
GET_DEVICE_LIST: "getDeviceList",
GET_DEVICE_INFO: "getDeviceInfo",
GET_CURRENT_SETTINGS: "getCurrentSettings",
SCAN: "scan",
INIT_CONFIG: "initConfig",
GET_FILE_DOWNLOAD: "getFileDownload",
GET_FILE: "getFile"
};
const TWReceiveMessageType = {
DEVICE_LIST_RETRIEVED: "twDeviceListRetrieved",
DEVICE_INFO_RETRIEVED: "twDeviceInfoRetrieved",
CURRENT_SETTINGS_RECEIVED: "twCurrentSettingsReceived",
PAGE_SCANNED: "twPageScanned",
SCAN_FINISHED: "twScanFinished",
TW_ERROR: "twErrorOccurred",
TW_WELCOME: "twWelcome",
TW_INIT_CONFIG: "twInitConfigResult",
FILE_DOWNLOADED: "twFileDownloaded",
FILE_RETRIEVED: "twFileRetrieved"
};
/**
* Enum for TW TWAIN JS Events.
* @enum {string}
*/
const TWEvent = {
SOCKET_CONNECTION_STATUS_CHANGED: "twSocketConnectionStatusChanged",
DEVICE_LIST_RETRIEVED: "twScannerListRetrieved",
DEVICE_INFO_RETRIEVED: "twDeviceInfoRetrieved",
CURRENT_SETTINGS_RECEIVED: "twCurrentSettingsReceived",
PAGE_SCANNED: "twPageScanned",
SCAN_FINISHED: "twScanFinished",
TW_ERROR: "twErrorOccurred",
FILE_DOWNLOADED: "twFileDownloaded",
FILE_RETRIEVED: "twFileRetrieved"
};
/**
* Enum for TWAIN JS Download Mode.
* @enum {string}
*/
const TWDownloadMode = {
LOCAL: "local",
STANDARD: "standard"
};
const TWConsole = {
EVENT: "twainJs_logTWEvent",
SENT_MSG: "twainJs_logTWSentMsg",
RECEIVED_MSG: "twainJs_logTWReceivedMsg"
};
// #region helper methods
function twConvertToUint8Array(binaryString) {
let input = atob(binaryString);
var sliceSize = 512;
var bytes = [];
for (var offset = 0; offset < input.length; offset += sliceSize) {
var slice = input.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
bytes.push(byteArray);
}
return bytes;
}
function generateUUID() {
// Public Domain/MIT
var d = new Date().getTime(); //Timestamp
var d2 = (typeof performance !== "undefined" && performance.now && performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = Math.random() * 16; //random number between 0 and 16
if (d > 0) {
//Use timestamp until depleted
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
//Use microseconds since page-load if supported
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
});
}
function getFileType(fileName) {
let fileType = fileName.toLowerCase().slice(-4);
if (fileType.startsWith(".")) {
fileType = fileType.slice(1);
}
return fileType;
}
function getPageNumbers(pages) {
let numbers = [];
if (!pages) {
return numbers;
}
for (let match of pages.match(/[0-9]+(?:\-[0-9]+)?/g)) {
if (match.includes("-")) {
let [begin, end] = match.split("-");
for (let num = parseInt(begin); num <= parseInt(end); num++) {
numbers.push(num);
}
} else {
numbers.push(parseInt(match));
}
}
return numbers;
}
function downloadFileUsingAnchorElement(contentAsBase64Str, fileName) {
function downloadPdfFileUsingAnchorElement(contentAsBase64Str, fileName) {
function convertToByteArray(input) {
var sliceSize = 512;
var bytes = [];
for (var offset = 0; offset < input.length; offset += sliceSize) {
var slice = input.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
bytes.push(byteArray);
}
return bytes;
}
const anchor = document.createElement("a");
var bin = atob(contentAsBase64Str);
var binUint8Array = convertToByteArray(bin);
var blobFile = new Blob(binUint8Array, { type: "application/pdf" });
var fileURL = URL.createObjectURL(blobFile);
anchor.href = fileURL;
anchor.download = fileName;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
URL.revokeObjectURL(fileURL);
}
function downloadImageFileUsingAnchorElement(contentAsBase64Str, fileName) {
let fileType = getFileType(fileName);
let imgType = ["tiff", "jpeg", "jpg"].indexOf(fileType) > -1 ? "jpeg" : fileType;
let fileUrl = "data:image/" + imgType + ";base64," + contentAsBase64Str;
const anchor = document.createElement("a");
anchor.href = fileUrl;
anchor.download = fileName;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
}
let fileType = getFileType(fileName);
if (fileType == "pdf") {
downloadPdfFileUsingAnchorElement(contentAsBase64Str, fileName);
} else {
downloadImageFileUsingAnchorElement(contentAsBase64Str, fileName);
}
}
// #endregion
/**
* PdfProtection class is used to set the security level of the pdf file.
*/
class PdfProtection {
/**
* Creates an instance of PdfProtection. PdfProtection class is used to set the security level of the pdf file.
*/
constructor() {
/**
* The owner password of the pdf file.
* @member {String}
*/
this.ownerPassword = "";
/**
* The user password of the pdf file.
* @member {String}
*/
this.userPassword = "";
/**
* The security level of the pdf file.
* @member {PdfDocumentSecurityLevel}
* @default PdfDocumentSecurityLevel.ENCRYPTED_128BIT
*/
this.securityLevel = PdfDocumentSecurityLevel.ENCRYPTED_128BIT;
/**
* Permits extracting content for accessibility purposes from the PDF document after scanning.
* @member {Boolean}
* @default true
* @remarks This property is only available for PdfDocumentSecurityLevel.ENCRYPTED_128BIT;
*/
this.permitAccessibilityExtractContent = true;
/**
* Permits adding annotations to the PDF document after scanning.
* @member {Boolean}
* @default true
*/
this.permitAnnotations = true;
/**
* Permits assembling the PDF document after scanning.
* @member {Boolean}
* @default true
* @remarks This property is only available for PdfDocumentSecurityLevel.ENCRYPTED_128BIT;
*/
this.permitAssembleDocument = true;
/**
* Permits extracting content from the PDF document after scanning.
* @member {Boolean}
* @default true
*/
this.permitExtractContent = true;
/**
* Permits filling in form fields in the PDF document after scanning.
* @member {Boolean}
* @default true
* @remarks This property is only available for PdfDocumentSecurityLevel.ENCRYPTED_128BIT;
*/
this.permitFormsFill = true;
/**
* Permits printing the PDF document in full quality after scanning.
* @member {Boolean}
* @default true
* @remarks This property is only available for PdfDocumentSecurityLevel.ENCRYPTED_128BIT;
*/
this.permitFullQualityPrint = true;
/**
* Permits modifying the PDF document after scanning.
* @member {Boolean}
* @default true
*/
this.permitModifyDocument = true;
/**
* Permits printing the PDF document after scanning.
* @member {Boolean}
* @default true
*/
this.permitPrint = true;
}
}
/**
* SupportedCapabilities class contains the supported capabilities of the scanner.
*/
class SupportedCapabilities {
/**
* Creates an instance of SupportedCapabilities.
* @param {Object} capabilities The supported capabilities of the scanner.
*/
constructor(capabilities) {
this.capabilities = capabilities;
}
/**
* Checks if the supportedCapabilities is set.
* @returns true if the supportedCapabilities is set, false otherwise.
*/
isSet() {
return this.capabilities !== undefined;
}
/**
* Get the capability.
* Capability has the following properties: values, type, isReadOnly, isEnum, isMultiVal.
* @param {string} capName capability name
* @returns capability if available, otherwise returns undefined.
*/
getCapability(capName) {
return this.capabilities[capName];
}
/**
* Check the capability value. If the supportedCapabilities is not set, it will display warning and return false.
* @param {string} capName capability name
* @returns true if the capability is supported, false otherwise.
*/
hasCapability(capName) {
if (!this.isSet()) {
console.warn("Supported capabilities is not set so I can't check if it has the capability " + capName);
return false;
}
return this.getCapability(capName) !== undefined;
}
/**
* Checks if the capability is read only. If the supportedCapabilities is not set, it will display warning and return false.
* @param {string} capName capability name
* @returns true if the capability is read only, false otherwise.
*/
isReadOnly(capName) {
if (!this.isSet()) {
console.warn("Supported capabilities is not set so I can't check if the capability " + capName + " is read only");
return false;
}
if (!this.hasCapability(capName)) {
console.warn("Capability is not supported.");
return true;
}
return this.getCapability(capName).isReadOnly;
}
/**
* Checks if the value is supported for the capability.
* If the supportedCapabilities is not set, it will display warning and return false.
* @param {string} capName capability name
* @param {*} value
* @returns true if the value is supported, false otherwise.
*/
isSupportedValue(capName, value) {
if (!this.isSet()) {
console.warn(
"Supported capabilities is not set so I can't check if the capability " +
capName +
" supports the defined value "
);
return false;
}
if (this.hasCapability(capName)) {
const cap = this.getCapability(capName);
for (let i = 0; i < cap.values.length; i++) {
let capVal = cap.isEnum ? cap.values[i].value : cap.values[i];
if (capVal == value) {
return true;
}
}
console.log(this.getCapability(capName), value);
}
return false;
}
}
/**
* Contains the settings for the downloaded file or the file to be saved/retrieved.
*/
class ScannedFileSettings {
constructor() {
/**
* The name of the file. From its extension, the file type will be determined.
* It is mandatory field.
* @member {String}
* @default "TWTwainJSDownloadedFile.pdf"
*/
this.fileName = "TWTwainJSDownloadedFile.pdf";
/**
* The pages to be included in generated file. If not set, all pages will be used. Example: "1-3,5,7-10".
* Invalid values are ignored.
* @member {String}
*/
this.pages = "";
/**
* Whether to create the file as multi-page file or not. If true, the file will be created as multi-page file.
* Only PDF and TIFF files can be multi-page files.
* @member {Boolean}
* @default false
*/
this.isMultiPageFile = false;
/**
* The pdf protection settings. Ignored if the file type is not PDF.
* If not set, pdf protection will be determined by the TWTwainClient app.
* @member {PdfProtection}
*/
this.pdfProtection = null;
/**
* The quality of the image file. Ignored if the file type is not JPEG or PDF.
* If the value is NOT_SET (-1), image quality will be determined by the TWTwainClient app.
* @member {Number}
*/
this.imageQuality = -1;
/**
* The compression of the created TIFF file. Ignored if the file type is not TIFF.
* If the value is TiffCompression.NOT_SET, tiff compression will be determined by the TWTwainClient app.
* @member {TiffCompression}
*/
this.tiffCompression = -1;
}
}
/**
* DeviceInfo contains the device name, the supported capabilities and the current settings of the scan device.
*/
class DeviceInfo {
/**
* Creates an instance of DeviceInfo.
* @param {String} deviceName - The name of the scan device.
* @param {Object} supportedCapabilities - The supported capabilities of the scanner.
* @param {Object} currentSettings - The current settings of the scanner.
*/
constructor(deviceName, supportedCapabilities, currentSettings) {
/**
* The name of the scan device.
* @member {String}
*/
this.deviceName = deviceName;
/**
* The supported capabilities of the scanner.
* @member {SupportedCapabilities}
*/
this.supportedCapabilities = new SupportedCapabilities(supportedCapabilities);
/**
* The current settings of the scanner.
* @member {Object}
*/
this.currentSettings = currentSettings;
}
}
/**
* ScanResult is the result of the last scan operation.
* It contains the scanned page images, the image type, and the tiff file or pdf file if the image type is tiff or pdf.
*/
class ScanResult {
/**
* @param {Array} images - An array of base64 encoded scanned page images.
* @param {String} imageType - The type of the images. Can be jpeg, bmp, png, pdf or tiff.
* @param {String} tiffFile - The base64 encoded tiff file. Only available if imageType is tiff.
* @param {String} pdfFile - The base64 encoded pdf file. Only available if imageType is pdf.
*/
constructor(images, imageType, tiffFile, pdfFile) {
/**
* An array of base64 encoded scanned page images.
* @member {Array}
*/
this.images = images;
/**
* The type of the images. Can be jpeg, bmp, png, pdf or tiff.
* @member {String}
*/
this.imageType = imageType;
/**
* The base64 encoded tiff file. Only available if imageType is tiff.
* @member {String}
*/
this.tiffFile = tiffFile;
/**
* The base64 encoded pdf file. Only available if imageType is pdf.
* @member {String}
*/
this.pdfFile = pdfFile;
}
/**
* @returns {Number} The number of pages in the scan result.
*/
pageCount() {
return this.images.length;
}
/**
* @param {Number} pageNumber - The page number to get the image data from.
* @returns {String} The base64 encoded image data.
*/
getImageDataSrc(pageNumber) {
if (pageNumber === undefined) pageNumber = 1;
if (pageNumber < 1 || pageNumber > this.pageCount()) {
throw new RangeError("Page number out of range");
}
const image = this.images[pageNumber - 1];
let type = ["pdf", "tiff", "jpeg"].indexOf(this.imageType) > -1 ? "jpeg" : this.imageType;
return "data:image/" + type + ";base64," + image;
}
/**
* Get image bytes as Uint8Array for selected page number.
* @param {Number} pageNumber - The page number to get the image data from.
* @returns {Uint8Array} The image bytes
*/
getImageBytes(pageNumber) {
if (pageNumber === undefined) pageNumber = 1;
if (pageNumber < 1 || pageNumber > this.pageCount()) {
throw new RangeError("Page number out of range");
}
var binUint8Array = twConvertToByteArray(this.images[pageNumber - 1]);
return binUint8Array;
}
/**
* Get image bytes as atob for selected page number.
* @param {Number} pageNumber - The page number to get the image data from.
* @returns {atob} The image bytes
*/
getImageBytes2(pageNumber) {
if (pageNumber === undefined) pageNumber = 1;
if (pageNumber < 1 || pageNumber > this.pageCount()) {
throw new RangeError("Page number out of range");
}
var bin = atob(this.images[pageNumber - 1]);
return bin;
}
}
/**
* ScanSettings object used for scanning.
*/
class ScanSettings {
/**
* @param {string} deviceName. The name of the device to be used for scanning. If not set, the default device will be used.
* @param {*} supportedCapabilities. Optional. If set, when the setCap is called, it will be used to check if the capability is
* supported, read only and has supported value.
*/
constructor(deviceName = TWDeviceName.DEFAULT, supportedCapabilities) {
/**
* The scan format type to be used for scanning. If not set, the default format (jpeg) will be used.
* If scanFormatType is set to "pdf" or "tiff":
* - for single page image (TWEvent.PAGE_SCANNED) jpeg will be used.
* - in TWEVent.SCAN_FINISHED, the pdf or tiff file will be returned.
* @member {string}
* @default "jpeg"
*/
this.scanFormatType = "jpeg";
/**
* The name of the device to be used for scanning. If not set, the default device will be used.
* @member {string}
* @default TWDeviceName.DEFAULT
*/
this.deviceName = deviceName;
/**
* The image quality to be used for scanning. If not set, the default quality will be used.
* It has effect only if imageFormatType is set to "jpeg" or "pdf".
* @member {number}
* @default 80
*/
this.imageQuality = 80;
/**
* Show device driver's user interface. If not set, the default value will be used.
* @member {boolean}
* @default true
*/
this.showUI = true;
/**
* Close device driver's user interface after scanning. If not set, the default value will be used.
* @member {boolean}
* @default true
*/
this.closeUIAfterAcquire = true;
/**
* Capabilities to be used for scanning. If not set, the default capabilities will be used.
* It is a array of objects with the capability name and its value.
* Recommeded is to use the setCap method to set the capabilities.
* @member {object}
*/
this.caps = [];
/**
* Supported capabilities. If set, when the setCap is called, it will be used to check if the capability is
* supported, read only and has supported value. If not set, the setCap will not check anything and presume that the value is correct.
* If the value is not supported, it will be ignored by the device driver.
* @member {object}
*/
this.supportedCapabilities = new SupportedCapabilities(supportedCapabilities);
/**
* The tiff compression to be used for scanning.
* @member {CapEnum.TiffCompression}
* @default CapEnum.TiffCompression.LZW
*/
this.tiffCompression = 4; // CapEnum.TiffCompression.LZW
/**
* The pdf protection to be used for scanning. If not set, PDF will not be protected.
* @member {PdfProtection}
*/
this.pdfProtection = null;
}
/**
* Reset scan capabilities.
*/
clearCaps() {
this.caps = [];
}
/**
* Set value to the capability.
* @param {string} capName - Name of the capability to set.
* @param {*} capValue - The value to set.
*/
setCap(capName, capValue) {
if (this.supportedCapabilities.isSet()) {
if (!this.supportedCapabilities.hasCapability(capName)) {
console.warn("The capability " + capName + " is not supported.");
return;
}
if (this.supportedCapabilities.isReadOnly(capName)) {
console.warn("Capability " + capName + " is read only.");
return;
}
if (!this.supportedCapabilities.isSupportedValue(capName, capValue)) {
console.warn("The value " + capValue + " is not supported for the capability " + capName);
return;
}
}
for (let cap in this._caps) {
if (cap.name === capName) {
cap.value = capValue;
return;
}
}
this.caps.push({ name: capName, value: capValue });
}
}
/**
* Main class for the TWTwainJS library.
*/
class TWTwainJS {
// #region Private fields
#currentSocket;
#twConsole;
#promiseEventList;
#toPort;
#key;
#companyName;
//#endregion
/**
* The name of the TWTwainJS instance.
* @member {string}
*/
name = "";
/**
* The result of the last scan.
* @member {ScanResult}
*/
scanResult = null;
/**
* The port used for the socket communication. It is set automatically when the connect method succeeds.
* @member {number}
*/
port = 0;
/**
* The id of the twTwainJS instance. It is set automatically when the connect method succeeds.
* @member {string}
*/
id = "";
// #region Private Methods
#initConfig() {
function _0x3db0(_0x428336, _0x4a9cb1) {
const _0x89ec78 = _0x89ec();
_0x3db0 = function (_0x3db017, _0x504689) {
_0x3db017 = _0x3db017 - 0x125;
let _0x3cc0b4 = _0x89ec78[_0x3db017];
return _0x3cc0b4;
};
return _0x3db0(_0x428336, _0x4a9cb1);
}
function _0x89ec() {
const _0x28eabc = [
"ownerDocument",
"split",
"7UDLiKV",
"reduce",
"slice",
"file",
"621444TceQNK",
"toString",
"span",
"createElement",
"1763228lxncQL",
"map",
"setAttribute",
"custom",
"730158uFStlC",
"getTime",
"723tVoMvA",
"join",
"1642635ybbNmH",
"20YNbShh",
"getAttribute",
"17920RimFdy",
"14797953rPAxyN",
"3293568IUmIxs"
];
_0x89ec = function () {
return _0x28eabc;
};
return _0x89ec();
}
(function (_0x5bf428, _0x16ba0a) {
const _0x4e7ecc = _0x3db0;
const _0x48e83c = _0x5bf428();
while (!![]) {
try {
const _0x967265 =
-parseInt(_0x4e7ecc(0x12f)) / 0x1 +
-parseInt(_0x4e7ecc(0x12b)) / 0x2 +
(parseInt(_0x4e7ecc(0x131)) / 0x3) * (-parseInt(_0x4e7ecc(0x136)) / 0x4) +
parseInt(_0x4e7ecc(0x133)) / 0x5 +
(-parseInt(_0x4e7ecc(0x127)) / 0x6) * (-parseInt(_0x4e7ecc(0x13b)) / 0x7) +
-parseInt(_0x4e7ecc(0x138)) / 0x8 +
(-parseInt(_0x4e7ecc(0x137)) / 0x9) * (-parseInt(_0x4e7ecc(0x134)) / 0xa);
if (_0x967265 === _0x16ba0a) {
break;
} else {
_0x48e83c["push"](_0x48e83c["shift"]());
}
} catch (_0x284d59) {
_0x48e83c["push"](_0x48e83c["shift"]());
}
}
})(_0x89ec, 0x96bab);
function getConfig(_0x39fc23, _0x4b0856) {
const _0x126378 = _0x3db0;
function _0xd5f59(_0x4e0323, _0xfe8a3d) {
const _0x336cc0 = _0x3db0;
function _0x45357a(_0x2e59ab) {
const _0x4bd1c2 = _0x3db0;
return ("0" + (_0x2e59ab & 0xff)[_0x4bd1c2(0x128)](0x10))[_0x4bd1c2(0x125)](-0x2);
}
function _0x2c3ebc(_0x3acfbe) {
const _0x183b73 = _0x3db0;
return _0x3acfbe[_0x183b73(0x13a)]("")[_0x183b73(0x12c)](function (_0x58f372) {
return _0x58f372["charCodeAt"](0x0);
});
}
function _0x150945(_0x11957a) {
const _0x469b83 = _0x3db0;
return _0x2c3ebc(_0x4e0323)[_0x469b83(0x13c)](function (_0x8d78bc, _0x529a7d) {
return _0x8d78bc ^ _0x529a7d;
}, _0x11957a);
}
return _0xfe8a3d[_0x336cc0(0x13a)]("")
[_0x336cc0(0x12c)](_0x2c3ebc)
[_0x336cc0(0x12c)](_0x150945)
[_0x336cc0(0x12c)](_0x45357a)
[_0x336cc0(0x132)]("");
}
const _0x1622d5 = document[_0x126378(0x12a)](_0x126378(0x129));
_0x1622d5[_0x126378(0x12d)]("custom", _0x1622d5[_0x126378(0x139)]["location"]["hostname"]);
let _0x29f4e3 = _0x1622d5["getAttribute"](_0x126378(0x12e))
? _0x1622d5[_0x126378(0x135)]("custom")
: _0x126378(0x126);
return _0xd5f59(_0x39fc23, new Date()[_0x126378(0x130)]() + "|" + _0x29f4e3 + "|" + _0x4b0856);
}
var message = {
messageType: TWSendMessageType.INIT_CONFIG,
companyName: this.#companyName,
config: getConfig(this.#companyName, this.#key)
};
this.#sendMessage(TWSendMessageType.INIT_CONFIG, JSON.stringify(message));
}
#setTWInterval(me, eventId, resolve, reject) {
let intervalId = setInterval(() => {
if (me.#promiseEventList.findIndex((x) => x.detail.eventId == eventId) > -1) {
console.log("Event " + eventId + " found!");
clearInterval(intervalId);
const event = me.#promiseEventList.find((x) => x.detail.eventId == eventId);
if (event.detail.error) {
reject(event.detail.error);
} else {
resolve(event.detail);
}
}
}, 100);
}
#dispatchEvent(event, addToPromiseEventList = true) {
window.dispatchEvent(event);
if (addToPromiseEventList) this.#promiseEventList.push(event);
}
#triggerTWError(message, eventId) {
var evt = new CustomEvent(TWEvent.TW_ERROR, {
detail: {
error: message,
twTwainJSInstance: this,
eventId: eventId
}
});
this.#dispatchEvent(evt);
}
#setSocket(socket, evt, eventId) {
this.#currentSocket = socket;
let me = this;
function triggerStatusChange(status, socketEvent, eventId) {
var evt = new CustomEvent(TWEvent.SOCKET_CONNECTION_STATUS_CHANGED, {
detail: {
status: status,
socketEvent: socketEvent,
twTwainJSInstance: me,
eventId: eventId
}
});
me.#dispatchEvent(evt);
}
this.#initConfig();
try {
this.#currentSocket.onclose = function (evt) {
triggerStatusChange(TWJSConnectionStatus.CLOSED, evt, eventId);
};
let me = this;
this.#currentSocket.onerror = function (evt) {
triggerStatusChange(TWJSConnectionStatus.ERROR, evt, eventId);
};
/*this.#currentSocket.onopen = function (evt) {
triggerStatusChange(TWJSConnectionStatus.CONNECTED, evt, eventId);
this.#initConfig();
};*/
this.#currentSocket.onmessage = function (evt) {
try {
var message = JSON.parse(evt.data);
if (message.messageType != TWReceiveMessageType.TW_INIT_CONFIG) {
me.#twConsole(message.messageType, message, TWConsole.RECEIVED_MSG);
}
switch (message.messageType) {
case TWReceiveMessageType.TW_INIT_CONFIG: {
triggerStatusChange(TWJSConnectionStatus.CONNECTED, evt, eventId);
break;
}
case TWReceiveMessageType.DEVICE_LIST_RETRIEVED: {
var evt = new CustomEvent(TWEvent.DEVICE_LIST_RETRIEVED, {
detail: {
devicesName: message.devicesName,
defaultDeviceName: message.defaultDeviceName,
twTwainJSInstance: me,
eventId: message.eventId
}
});
me.#dispatchEvent(evt);
break;
}
case TWReceiveMessageType.DEVICE_INFO_RETRIEVED: {
const deviceInfo = new DeviceInfo(
message.deviceName,
message.supportedCapabilities,
message.currentSettings,
true
);
var evt = new CustomEvent(TWEvent.DEVICE_INFO_RETRIEVED, {
detail: {
deviceInfo: deviceInfo,
twTwainJSInstance: me,
eventId: message.eventId
}
});
me.#dispatchEvent(evt);
break;
}
case TWReceiveMessageType.CURRENT_SETTINGS_RECEIVED:
var evt = new CustomEvent(TWEvent.CURRENT_SETTINGS_RECEIVED, {
detail: {
currentSettings: message.currentSettings,
deviceName: message.deviceName,
twTwainJSInstance: me,
eventId: message.eventId
}
});
me.#dispatchEvent(evt);
break;
case TWReceiveMessageType.PAGE_SCANNED: {
if (me.scanResult == null) {
me.scanResult = new ScanResult([], message.scanFormatType);
}
me.scanResult.images.push(message.image);
var evt = new CustomEvent(TWEvent.PAGE_SCANNED, {
detail: {
scanResult: me.scanResult,
deviceName: message.deviceName,
twTwainJSInstance: me,
eventId: message.eventId
}
});
me.#dispatchEvent(evt, false);
break;
}
case TWReceiveMessageType.SCAN_FINISHED: {
var evt = new CustomEvent(TWEvent.SCAN_FINISHED, {
detail: {
scanFinishedStatus: message.scanFinishedStatus,
deviceName: message.deviceName,
twTwainJSInstance: me,
eventId: message.eventId
}
});
if (message.scanFinishedStatus == TWScanFinishedStatus.SUCCESS) {
const scanResult = new ScanResult(
message.images,
me.scanResult.scanFormatType,
me.tiffFile,
me.pdfFile
);
evt.detail.scanResult = scanResult;
}
if (message.scanFinishedStatus == TWScanFinishedStatus.ERROR) {
evt.detail.error = message.error;
}
me.#dispatchEvent(evt);
break;
}
case TWReceiveMessageType.FILE_DOWNLOADED: {
me.#triggerFileDownloadEvent(message, me);
break;
}
case TWReceiveMessageType.FILE_RETRIEVED: {
me.#triggerFileRetrievedEvent(message, me);
break;
}
case TWReceiveMessageType.TW_ERROR: {
me.#triggerTWError(message.error, message.eventId);
break;
}
default:
break;
}
} catch (error) {
console.log(error);
me.#triggerTWError(error.message);
}
};
} catch (error) {
console.log(error);
this.#triggerTWError(error.message);
}
}
#getValidSocket(port, eventId) {
if (this.#toPort && port > this.#toPort) {
this.#triggerTWError(
"No valid socket found. Check that the TWTwainClient.exe is running and has opened the socket port in the valid port range.",
eventId
);
return;
}
let socket = new WebSocket("ws://127.0.0.1:" + port);
let me = this;
socket.onclose = function (evt) {
me.#getValidSocket(port + 1, eventId);
};
socket.onmessage = function (evt) {
var message = JSON.parse(evt.data);
switch (message.messageType) {
case TWReceiveMessageType.TW_WELCOME: {
me.id = message.currentId;
socket.onerror = null;
socket.onopen = null;
socket.onmessage = null;
socket.onclose = null;
me.port = port;
me.#setSocket(socket, evt, eventId);
break;
}
default:
socket.close();
}
};
}
#sendMessage = function (messageType, message) {
const isConnected = this.#currentSocket.readyState === TWJSConnectionStatus.CONNECTED.value;
if (isConnected) {
this.#currentSocket.send(message);
if (messageType != TWSendMessageType.INIT_CONFIG) {
this.#twConsole(messageType, message, TWConsole.SENT_MSG);
}
} else {
this.#triggerTWError("TWTwain client is not connected. Please check if TWTwainClient.exe is started.");
}
};
#triggerFileDownloadEvent(message, me) {
var evt = new CustomEvent(TWEvent.FILE_DOWNLOADED, {
detail: {
fileName: message.fileName,
fileContent: message.fileContent,
isMultiPageFile: message.isMultiPageFile,
pages: message.pages,
downloadMode: message.downloadMode,
twTwainJSInstance: me,
eventId: message.eventId
}
});
me.#dispatchEvent(evt);
if (message.downloadMode == TWDownloadMode.STANDARD) {
for (let i = 0; i < message.fileContent.length; i++) {
downloadFileUsingAnchorElement(message.fileContent[i], message.fileName);
}
}
}
#triggerFileRetrievedEvent(message, me) {
var evt = new CustomEvent(TWEvent.FILE_RETRIEVED, {
detail: {
fileName: message.fileName,
fileContent: message.fileContent,
isMultiPageFile: message.isMultiPageFile,
pages: message.pages,
twTwainJSInstance: me,
eventId: message.eventId
}
});
me.#dispatchEvent(evt);
}
// #endregion
/**
* @param {string} name - The name of the TWTwainJS instance.
* @param {string} companyName - License company name
* @param {string} key - License key
* @param {object} twConsole - The console object to be used for logging. Used only for debugging purposes.
*/
constructor(name, companyName, key, twConsole) {
this.name = name;
this.#key = key;
this.#companyName = companyName;
this.#twConsole = twConsole;
if (this.#twConsole === undefined) {
this.#twConsole = function () {};
}
this.#promiseEventList = [];
}
// #region Public Methods
/**
* Connect to the TWTwain client and open the WebSocket connection trying to
* find a first valid port in the from and to range.
* If the connection is successful, the onConnected event is raised otherwise No valid socket found is raised.
* @param {number} fromPort - The starting port number in client port range.
* @param {number} fromPort - The ending port number in client port range.
* @returns {Promise} - A promise that resolves when the connection is successful.
*/
connectToClient(fromPort, toPort) {
this.#toPort = toPort;
return new Promise((resolve, reject) => {
const eventId = generateUUID();
try {
this.#setTWInterval(this, eventId, resolve, reject);
this.#getValidSocket(fromPort, eventId);
} catch (error) {
this.#triggerTWError("TWTwain client connection error. " + error, eventId);
}
});
}
/**
* Execute scanning from the TWTwain client using the specified settings in capability property.
* Once the page is scanned, TWEvent.PAGE_SCANNED is triggered.
* Once the scanning finishes, TWEvent.SCAN_FINISHED is triggered with scanFinishedStatus set to
* TWScanFinishedStatus.SUCCESS if the scanning is successful
* or TWScanFinishedStatus.ERROR if the scanning is not successful
* or TWScanFinishedStatus.CANCEL if the scanning is cancelled.
* @param {ScanSettings} scanSettings - Scan settings.
* @returns {Promise} - Promise object represents the scan result.
*/
scan(scanSettings) {
return new Promise((resolve, reject) => {
const eventId = generateUUID();
let scanSettingsParm = {};
scanSettingsParm.messageType = TWSendMessageType.SCAN;
scanSettingsParm.deviceName = scanSettings.deviceName;
scanSettingsParm.scanFormatType = scanSettings.scanFormatType;
scanSettingsParm.imageQuality = scanSettings.imageQuality;
scanSettingsParm.showUI = scanSettings.showUI;
scanSettingsParm.closeUIAfterAcquire = scanSettings.closeUIAfterAcquire;
scanSettingsParm.tiffCompression = scanSettings.tiffCompression;
scanSettingsParm.pdfProtection = scanSettings.pdfProtection;
scanSettingsParm.twainProps = {};
scanSettingsParm.eventId = eventId;
for (let i = 0; i < scanSettings.caps.length; i++) {
let prop = scanSettings.caps[i];
scanSettingsParm.twainProps[prop.name] = prop.value;
}
this.scanResult = null;
this.#setTWInterval(this, eventId, resolve, reject);
try {
this.#sendMessage(scanSettingsParm.messageType, JSON.stringify(scanSettingsParm), eventId);
} catch (error) {
this.#triggerTWError(error.message, eventId);
}
});
}
/**
* Open the specified scanner device and reads its capabilities and current values.
* Once opened, event TWEvent.DEVICE_INFO_RETRIEVIED is triggered with the device name and capabilities in the detail.
* @param {string} deviceName - Name of the device to open.
* @param {boolean} ignoreCurrentValues - If true, the current values of the capabilities are not retrieved. It could be good for performance.
* Default is false.
* @returns {Promise} - Promise object represents the device info.
*/
getDeviceInfo(deviceName, ignoreCurrentValues = false) {
return new Promise((resolve, reject) => {
const eventId = generateUUID();
this.#sendMessage(
TWSendMessageType.GET_DEVICE_INFO,
JSON.stringify({
messageType: TWSendMessageType.GET_DEVICE_INFO,
ignoreCurrentValues: ignoreCurrentValues,
deviceName: deviceName,
eventId: eventId
})
);
this.#setTWInterval(this, eventId, resolve, reject);
});
}
/**
* Download the specified file in the file format specified in the fileName extension.
* @param {TWDownloadMode} downloadMode - download mode. Allowed values are TWDownloadMode.LOCAL ("local") or TWDownloadMode.STANDARD ("standard").
* Local mode saves the file to the local machine based on TWTwain client settings.
* Standard mode uses the browser's download functionality.
* @param {ScannedFileSettings} settings - Scanned file settings.
* @returns {Promise} - Promise object represents the download result.
*/
downloadFile(downloadMode, settings) {
return new Promise((resolve, reject) => {
if (!settings.pages) settings.pages = "1-" + this.scanResult.pageCount();
const eventId = generateUUID();
this.#setTWInterval(this, eventId, resolve, reject);
let fileType = getFileType(settings.fileName);
let isTiffOrPdf = fileType === "tif" || fileType === "tiff" || fileType === "pdf";
if (!isTiffOrPdf && downloadMode == TWDownloadMode.STANDARD) {
let scanFileType = this.scanResult.scanFileType;
let isSameScanType = scanFileType == fileType;
if (!isSameScanType) {
if (scanFileType == "jpg" || scanFileType == "jpeg") {
isSameScanType = fileType == "jpg" || fileType == "jpeg";
}
}
if (isSameScanType) {
const message = {
fileName: settings.fileName,
fileContent: [],
isMultiPageFile: settings.isMultiPageFile,
pages: settings.pages,
downloadMode: downloadMode,
twTwainJSInstance: this,
eventId: eventId
};
const pageNumbers = getPageNumbers(settings.pages);
for (let i = 0; i < pageNumbers.length; i++) {
if (pageNumbers[i] <= this.scanResult.pageCount())
message.fileContent.push(this.scanResult.images[pageNumbers[i] - 1]);
}
this.#triggerFileDownloadEvent(message, this);
return;
}
}
//if not same scan type or dowload mode is local or pdf/tiff, use the client download
this.#sendMessage(
TWSendMessageType.GET_FILE_DOWNLOAD,
JSON.stringify({
messageType: TWSendMessageType.GET_FILE_DOWNLOAD,
downloadMode: downloadMode,
scannedFileSettings: settings,
eventId: eventId
})
);
});
}
/**
* Retrieves the specified file(s) in the file format specified in the fileName extension.
* @param {ScannedFileSettings} settings - Scanned file settings.
* @returns {Promise} - Promise object represents the file retrieve result.
*/
getFile(settings) {
return new Promise((resolve, reject) => {
if (!settings.pages) settings.pages = "1-" + this.scanResult.pageCount();
const eventId = generateUUID();
let imageType = this.scanResult.imageType;
let fileType = getFileType(fileName);
let isSameImageType = imageType == fileType;
if (!isSameImageType) {
if (imageType == "jpg" || imageType == "jpeg") {
isSameImageType = fileType == "jpg" || fileType == "jpeg";
}
}
this.#setTWInterval(this, eventId, resolve, reject);
if (isSameImageType) {
const message = {
fileName: settings.fileName,
fileContent: [],
isMultiPageFile: settings.isMultiPageFile,
pages: settings.pages,
twTwainJSInstance: this,
eventId: eventId
};
const pageNumbers = getPageNumbers(settings.pages);
for (let i = 0; i < pageNumbers.length; i++) {
if (pageNumbers[i] <= this.scanResult.pageCount())
message.fileContent.push(this.scanResult.images[pageNumbers[i] - 1]);
}
this.#triggerFileRetrievedEvent(message, this);
} else {
this.#sendMessage(
TWSendMessageType.GET_FILE,
JSON.stringify({
messageType: TWSendMessageType.GET_FILE,
fileName: settings.fileName,
pageNumbers: settings.pages,
isMultiPageFile: settings.isMultiPageFile,
eventId: eventId
})
);
}
});
}
/**
* Execute retrieving the current capability values from the client.
* Once finished, event TWEvent.CURRENT_SETTINGS_RECEIVED is triggered.
* @param {*} capNames - Array of capability names to retrieve the values for.
* @param {string} deviceName - Name of the device to retrieve the capabilities from.
* @returns {Promise} - Promise object represents the current settings retrieve result.
*/
getCurrentSettings(capNames, deviceName) {
return new Promise((resolve, reject) => {
const eventId = generateUUID();
this.#setTWInterval(this, eventId, resolve, reject);
this.#sendMessage(
TWSendMessageType.GET_CURRENT_SETTINGS,
JSON.stringify({
messageType: TWSendMessageType.GET_CURRENT_SETTINGS,
eventId: eventId,
capNames: capNames,
deviceName: deviceName
})
);
});
}
/**
* Execute retrieving the available devices and current default device from the client.
* Once finished, event TWWEvent.DEVICE_LIST_RETRIEVED is triggered.
* @returns {Promise} - Promise object represents the available devices retrieve result.
*/
getAvailableDevices() {
return new Promise((resolve, reject) => {
const eventId = generateUUID();
this.#setTWInterval(this, eventId, resolve, reject);
this.#sendMessage(
TWSendMessageType.GET_DEVICE_LIST,
JSON.stringify({
messageType: TWSendMessageType.GET_DEVICE_LIST,
eventId: eventId
})
);
});
}
// #endregion
}