- PVSM.RU - https://www.pvsm.ru -
Один из клиентов нашего web-проекта захотел использовать для поиска заказов в системе сканер штрихкодов. Но, к сожалению, полностью отказался от идеи работы с ними в режиме имитации клавиатуры — только эмуляция COM-порта.
Вариантов решения было не особенно много:
К счастью, есть способ решения проблемы вторым путём.
Если кто не знает, Chrome Application — это приложения для браузера Chrome, написанные на JavaScript. В этих приложениях доступно API для работы с последовательными портами [1]. Этот вариант практически идеальный для нас.
Основная проблема состоит в том, что хоть у Chrome Application и есть подходящие инструменты, оно не может напрямую работать с открытыми страницами. Тут нам на помощь приходят расширения, которые такую возможность имеют.
Далее я постараюсь подробнее описать как всё это связать вместе, что бы это работало.
socat -d -d pty,raw,echo=0 pty,raw,echo=0
socat[1473] N PTY is /dev/ttys001
socat[1473] N PTY is /dev/ttys002
socat[1473] N starting data transfer loop with FDs [3,3] and [5,5]
cat > /dev/ttys001
вместо /dev/ttys001 указываем тот путь, что вернул socat
И пишем любые сообщения.
cat < /dev/ttys002
/dev/ttys002 — второй путь из socat.
Написав сообщение во втором окне — получим его в третьем, если пришло — можно идти дальше.
В документации [2] достаточно хорошо расписан сам процесс, стоит только обратить внимание на то, что нам необходим доступ к работе с последовательными портами. Для этого в файле manifest.json указываем:
"permissions": [
"serial"
]
Файл background.js содержит код самого приложения:
chrome.app.runtime.onLaunched.addListener(function() {
chrome.serial.connect("/dev/ttys004", {bitrate: 115200}, onConnect);
});
var stringReceived = '';
var onConnect = function(connectionInfo) {
var connectionId = connectionInfo.connectionId;
var onReceiveCallback = function(info) {
if (info.connectionId == connectionId) {
var str = arrayBufferToString(info.data);
if (str.charAt(str.length-1) === 'n') {
stringReceived += str.substring(0, str.length-1);
chrome.runtime.sendMessage('dbmjhdcnjkeeopcmhbooojabanopplnd', {
action: 'scanner', data: {
barcode: stringReceived
}
});
stringReceived = '';
} else {
stringReceived += str;
}
}
};
chrome.serial.onReceive.addListener(onReceiveCallback);
};
function arrayBufferToString (buffer) {
var string = '';
var bytes = new Uint8Array( buffer );
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
string += String.fromCharCode( bytes[ i ] )
}
return string;
}
Разберём его подробнее.
chrome.app.runtime.onLaunched.addListener — добавляет функцию в список, который выполняется при старте приложения.
chrome.serial.connect("/dev/ttys001", {bitrate: 115200}, onConnect) — подключаемся к необходимому нам порту, при установке соединения выполнится функция onConnect.
chrome.serial.onReceive.addListener(onReceiveCallback) — при получении сообщения — вызовется onReceiveCallback
chrome.runtime.sendMessage — функция, которая отправляет сообщение в другое приложение/расширение. Первый аргумент — уникальный ID расширения в которое мы отправляем сообщение — можно увидеть в списке установленных расширений (chrome://extensions/ — парсер ломает ссылку), второй аргумент — сами данные.
Здесь тоже всё несложно и подробно описано в документации [3]
Ключевые настройки из файла манифеста:
"permissions": [
"tabs",
"file:///*"
],
"content_scripts": [
{
"matches": ["file:///*"],
"js": ["action.js"]
}
],
"background": {
"persistent": false,
"scripts": ["js/background.js"]
}
permissions — указывает, что нам необходим доступ к вкладкам, далее указываем к каким (для тестов — указаны все локальные файлы file)
content_scripts — описывает какие дополнительные скрипты запускать на страницах
background — описывает скрипт расширения, который работает в фоне
В background.js содержится код, который отвечает за приём сообщения и отправку его в определённый таб
var onMessage = function(data) {
switch (data.action) {
case 'scanner': {
chrome.tabs.query({url: "file:///*"}, function(tab) {
for (var i = 0; i < tab.length; i++) {
chrome.tabs.sendMessage(tab[i].id, data);
}
});
}
}
};
chrome.runtime.onMessageExternal.addListener(onMessage);
chrome.tabs.query — делает выборку табов по критерию, в нашем случае это url = «file:///*»
Есть 2 способа выполнить js код на странице из расширения
Я выбрал второй вариант. Стоит заметить что любой код, выполняемый во вкладке из расширения, выполняется в специальном окружении. Это значит что он будет иметь полный доступ к DOM элементам, но не будет иметь доступа к любым переменным созданным во вкладке. Подробнее.
Оптимальный способ передать данные из расширения в код вкладки — воспользваться CustomEvent [5]
В файле action.js мы просто получаем сообщение из backgroud.js и создаём событие для document.
chrome.runtime.onMessage.addListener(
function(data) {
var event = new CustomEvent(data.action, {detail: data.data});
document.dispatchEvent(event);
}
);
Осталось самое простое — принять сообщение и сделать с ним желаемые действия, например просто вставить его в input
<html>
<head>
<script type="text/javascript">
document.addEventListener("scanner", function(e) {
document.getElementById('barcode').value = e.detail.barcode;
});
</script>
</head>
<body>
<input id="barcode">
</body>
</html>
В целом я был приятно удивлён тем, что chrome предоставляет API для работы с железом, в том числе не только для чтения, но и для записи.
К сожалению, после того как было сделано практически всё, клиент сообщил, что всё таки переведёт сканеры в режим имитации клавиатуры. Хоть нам в конечном счёте это не пригодилось — надеюсь этот материал будет кому-нибудь полезен.
Если кому интересно могу рассказать про то, как мы создали и поддерживаем несколько одностраничных больших проектов с использованием backbone, как кэшируем всю верстку на стороне клиента.
Автор: Forx
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/72403
Ссылки в тексте:
[1] API для работы с последовательными портами: https://developer.chrome.com/apps/serial
[2] документации: https://developer.chrome.com/apps/first_app
[3] документации: https://developer.chrome.com/extensions/getstarted
[4] chrome.tabs.executeScript: https://developer.chrome.com/extensions/tabs#method-executeScript
[5] CustomEvent: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
[6] Источник: http://habrahabr.ru/post/240775/
Нажмите здесь для печати.