JavaScriptから周辺機器を操作する:理論から実践まで

銀行員の仕事は危険で困難です。 統合フロンタルシステムでは、銀行の従業員を支援し、作業を自動化しようとします。 解決する必要のある多くのタスクの1つは、シンクライアントを改良して周辺の銀行機器と連携できるようにすることです。 しかし、これはそれほど単純ではありません。 この記事では、どのように問題を解決しようとしたのか、そしてそれが何につながったのかを説明します。



この記事は、システムのシンクライアントから周辺機器へのアクセスの問題に直面している企業全体のシステムのアーキテクトおよび経験豊富なフロントエンド開発者に役立ちます。







はい、すぐに言いますが、ActiveX、Java Applet、ブラウザプラグインを使用する標準的なアプローチは、セキュリティ、汎用性、および管理性と保守性の難しさの理由で私たちに適していないという事実によって、タスクは複雑になります。



10万人以上のオペレーターが支店で働いている控えめな(中国の基準で)銀行を想像してください。 顧客にサービスを提供する職場があり、さまざまな周辺機器がワークステーションに接続されています。



  1. ネットワーク/ローカルプリンター。

  2. レシートプリンター(Epson-M950またはOlivetti)。

  3. POS端末(Verifone VX820)。

  4. 「タブレット」(デジタル署名付きのタッチメモリ)を読み取るためのデバイス。

  5. さまざまな種類のスキャナー(バーコード、パスポート、または文書のみ)。

  6. ウェブカメラ(潜在的な借り手の写真を撮る)。

  7. 特定の銀行機器-現金自動預け払い機/受取人など



印象的なリストではありませんか? そして、これらすべてを使用して、ブラウザーで実行されている反応アプリケーションから対話する必要があります。



低レベルでのデバイスの操作は、機器メーカーのドライバーまたは内部開発のネイティブライブラリを介して実行されます。 職場でのドライバーおよびその他のソフトウェアのインストールと更新は、一元的に実行されます。 WindowsとInternet Explorer 11または8は職場にあり、LinuxとChrome / Firefoxに切り替える可能性について説明します。 したがって、クロスプラットフォームおよびクロスブラウザの要件。



デバイスの不均一性と障害の問題は多かれ少なかれ解決されていますが、職場からログを取得する機能など、デバイスの動作を監視する必要があります。 周辺機器設定の集中管理も必要です。



セキュリティ要件は、クライアント上で実行されるコードの整合性を制御し、周辺機器へのアクセスを制限すること(ローカルワークステーションからのみアクセスする必要があります)、およびタッチメモリを操作するためのいくつかの特定の要件です。



別の問題は、タブレットと周辺機器の動作です。 考えは、コンピューターをモバイルデバイスに置き換え、銀行業務を含むすべてのオペレーターの機能をそれらに移すことです。 この記事では、タブレットについて詳しくは説明しません。別の記事でこれについて明確に説明します。ここでは、wi-fiを介してタブレット自体を内部銀行ネットワークに接続することに関する質問と、タブレットに直接接続されたデバイスでの作業の問題があることを示します、たとえば、mPOS端末。



どのようにすべてを飼いならそうとしたか、すぐにうまくいかなかった理由



動作条件から、周辺機器との作業スキームに従います:



私たちは問題の解決策を見つけようとし、いくつかのオプションを考え出しました。



HTML5



周辺機器で動作する可能性がありますが、現在の実装はモバイル機器またはオーディオ/ビデオに合わせて調整されており、当社のタスクは「完全に」という言葉には適していません。



Webusb



https://wicg.github.io/webusb/-まず、すべてのデバイスがUSBで接続されているわけではありません。 第二に、現在のところ、Chromeの実験的な機能以外にはWebUSBのサポートはありません。 それも私たちに合わない。



Activex



この方法は古く、実績があります-ラッパーは、ローカルOCXまたはDLLとしてインストールされ、ActiveXObjectを介してアクセスされるドライバーに対して作成されます。



var printer = new ActiveXObject("LocalPrinterComponent.Printer"); printer.print(data + "\r\n");
      
      





ただし、IEおよびWindowsでのみ機能します。 ActiveXテクノロジーをポータブルにする試みにもかかわらず、Microsoftはプラグインテクノロジーを支持してActiveXの開発を放棄しました。



ブラウザプラグイン



これも私たちには適さず、ターゲットにされておらず、ブラウザにバインドされ、普遍性を制限します。



ネイティブJavaコンポーネント



Localhostは、JavaScriptからアクセス可能なサービスを公開します。 彼らはこれを拒否することも決めました 実装には時間がかかります。Webサーバーを使用するか、独自に作成する必要があります。



アプレット



ターゲットではありません。ローカルデバイスのアクセス権に問題があります。



そして何をすべきか?



その結果、周辺機器を操作する次のアーキテクチャに決着しました。







JavaScriptのクライアントアプリケーションは、周辺APIモジュールを介してローカルサービスにアクセスします。これは、 socket.ioのクライアント部分のバインディングです。



ワークステーションにnode.jsがインストールされ、OSの起動時にサービスアカウントで起動されます。 Node.jsはブートストラップモジュールを実行します。これは、サーバーからローカルファイルシステムに周辺機器を操作するためのnpmモジュールをロードする役割を果たします。 クライアントコードはイベントを生成します。属性とコードは、デバイスで動作するモジュールのバージョンとバージョン、呼び出されたメソッドとそのパラメーターです。



 <html> <head> <title>Test hello.socket.io</title> <script src="socket.io.min.js"></script> <script> var socket = io.connect('http://localhost:8055'); socket.on('eSACExec', function (data) { var newLi = document.createElement('li'); newLi.style.color = "green"; newLi.innerHTML = data.data + ' from ' + data.from; document.getElementById("list").appendChild(newLi); console.log(data.data); }); socket.on('eSACOnError', function (data) { var newLi = document.createElement('li'); newLi.style.color = "red"; newLi.innerHTML = data.message; document.getElementById("list").appendChild(newLi); console.log(data.error); }); function sendHello() { socket.emit('eSASExec', {module: 'api.system.win.window', version:'0.0.1', repoURL: 'git+https://github.com/Gromatchikov/', method: 'sayHello', params: {hello: 'Server API'}}); } </script> </head> <body> <ol id="list"> </ol> <button onclick="sendHello()">send</button> </body> </html>
      
      





ブートストラップは、プラットフォームサービスの操作も担当します(管理者の要求に応じて職場からログをアップロードするなど)。



各周辺機器には、node.jsで実行される、それを操作するための独自のモジュールがあります。 ブートストラップは、モジュールメソッドの呼び出しをプロキシします。



 var http = require('http'); var sio = require('socket.io'); var bootstrapSA = require('./bootstrap.js'); var modules = new Object(); function SAServer() { if (!(this instanceof SAServer)) { return new SAServer(); } this.app = http.createServer(); this.sioApp = sio.listen(this.app); this.sioApp.sockets.on('connection', function (client) { client.on('eSASExec', function (data) { try { console.info('SAserver event eSASExec:'); console.info(data); bootstrapSA.demandModuleProperty(modules, data.repoURL, data.module, data.version, function (module) { var fullModuleName = bootstrapSA.getFullName(data.module, data.version) var api = modules[fullModuleName]; if (api != undefined) { var result = api[data.method](data.params); console.info('Result eSASExec', result); client.emit('eSACExec', {data: result || '', from: fullModuleName}); } else { console.error('[', 'Error ', fullModuleName, '] is ', undefined); client.emit('eSACOnError', { message: 'Error define api module' + fullModuleName}); } }); } catch (_error) { console.error('[', 'Error eSASExec', ( _error.code ? _error.code : ''), ']', _error); client.emit('eSACOnError', { message: 'Error :(', error: _error.stack || ''}); } }); client.on('disconnect', () => { console.log('User disconnected'); }); client.on('eSASStop', () => { process.exit(0); }); console.info('User connected'); }); console.info('System API server loaded'); } //  SAServer.prototype.start = function startSAServer(port) { //   this.currentPort = port; if (this.currentPort == undefined) { this.currentPort = process.env.npm_package_config_port; } this.app.listen(this.currentPort); console.log('System API server running at http://127.0.0.1:'+ this.currentPort); bootstrapSA.inspect('parent object', modules); }; module.exports = SAServer;
      
      





このモジュールは、npm node.jsの「ffi」モジュールを介して、オペレーティングシステムまたはドライバーの低レベルAPIで動作します。



 //    JavaScript (UTF-8)  UTF-16 function TEXT(text){ return new Buffer(text, 'ucs2').toString('binary'); } var FFI = require('ffi'); //   user32.dll var user32 = new FFI.Library('user32', { 'MessageBoxW': [ 'int32', [ 'int32', 'string', 'string', 'int32' ] ] }); function WindowSA() { if (!(this instanceof WindowSA)) { return new WindowSA(); } console.log('Window system API module loaded'); } WindowSA.prototype.sayHello = function sayHello(params) { //   var OK_or_Cancel = user32.MessageBoxW(0, TEXT(', "' + params.hello + '"!'), TEXT(' '), 1); }; module.exports = WindowSA;
      
      





クライアントアプリケーションがブートストラップを呼び出してモジュールとバージョンを渡すと、ブートストラップはローカルストレージをチェックします。 必要なモジュールがローカルストレージにない場合、サーバーからポンプで送られます。 したがって、ドライバーとブートストラップ付きのnode.jsのみが中央にインストールされ、デバイスを操作するためのnpmモジュールがランタイムでダウンロードされます。 ただし、この機能は産業用構成で使用される可能性は低いため、従業員のワークステーションへのデバイスドライバーのリモートインストール中に、対応するnpm周辺APIモジュール(JSバンドル)もインストールされると想定しています。



 var loadModule = function (obj, repoURL, name, version, callback) { var mod; try { //todo mod = require(fullModuleName);    major.minor   fix console.log('System API module "%s" require...', getFullName(name, version)); mod = new require(name)(); return mod; } catch (err){ errFlag = true; console.error('Error require', err.code, err); if (err.code == 'MODULE_NOT_FOUND') { installModule(obj, repoURL, name, version, callback); } } } //   ,      //todo       name@version (fullModuleName) var demandModuleProperty = function (obj, repoURL, name, version, callback) { var fullModuleName = getFullName(name, version); var errFlag = false; if (!obj.hasOwnProperty(fullModuleName)) { console.log('System API module "%s" defining', fullModuleName); var mod = loadModule(obj, repoURL, name, version, callback); if (mod == undefined){ return; } Object.defineProperty(obj, fullModuleName, { configurable: true, enumerable: true, get: function () { Object.defineProperty(obj, fullModuleName, { configurable: false, enumerable: true, value: mod }); console.log('System API module "%s" defined', fullModuleName); inspect('parent object', obj); inspect(fullModuleName, obj[fullModuleName]); } }); } if (!errFlag) { console.log('Callback for "%s"', fullModuleName); callback(obj[fullModuleName]); } }; //    //todo       name@version,   major.minor   fix var installModule = function(obj, repoURL, name, version, callback){ console.log('Installing module %s version %s', name, version); var fullURL = getFullURL(repoURL, name, version); npm.load({progress: true, '--save-optional': true, '--force': true, '--ignore-scripts': true},function(err) { // handle errors // install module npm.commands.install([fullURL], function(er, data) { // log errors or data if (!er){ console.info('System API module "%s" installed', name); //    demandModuleProperty(obj, repoURL, name, version, callback); } else { console.error('Error NPM Install', er.code, er); } }); npm.on('log', function(message) { // log installation progress console.log('NPM logs:' + message); }); }); };
      
      





このソリューションはまだ実装されていません。現在取り組んでおり、結果については別の記事で確実にお知らせします。 それまでの間、選択したアプローチについてどう思われますか? どんな落とし穴が待っていますか? コメントの議論に参加することを皆に勧めます。



All Articles