/** * 送信側の例: * * Ext.require('Ext.device.Tunnel', function(Tunnel) { * var broadcast = Tunnel.broadcast('time'), * connectToFirstReceiver = broadcast.then(function(receivers){ * return Tunnel.connect(receivers[0].id); * }), * send = connectToFirstReceiver.then(function(connection) { * // 'true' as second argument to bring the receiver app to the foreground * // otherwise, it will simply run in the background * return connection.send('You are summoned!', true); * }); * * send.then(function(reply){ * console.log(reply); * }); * }); * * 受信側の例: * * Ext.require('Ext.device.Tunnel', function(Tunnel) { * Tunnel.onConnect(function(appId) { * console.log('Got connection from ' + appId); * * // Accept all * return true; * }); * * Tunnel.onMessage(function(appId, message) { * console.log('Got message from ' + appId + ' ' + message); * * return 'Yeah I got it'; * }); * }); * * 同期メッセージのハンドリング: * * Tunnel.onMessage(function(appId, message) { * var promise = new Ext.Promise(); * * console.log('Got message from ' + appId + ' ' + message); * * // Do whatever needed asynchronously before return the result (fulfilling the promise) * setTimeout(function(){ * promise.fulfill('Yeah I got it'); * }, 3000); * * return promise; * }); */ Ext.define('Ext.device.tunnel.Abstract', { requires: ['Ext.Promise'], messageId: 0, constructor: function() { this.pendingReceivePromises = {}; this.connections = {}; this.connectQueue = []; this.messageQueue = []; }, /** * メッセージを流して(目的)応答できる受信側を探します。 * @param {String} message * * @returns {Ext.Promise} 達成された場合にオブジェクトの配列を提供するという保証。各オブジェクトには受信側('id'を含む)、'name'、'icon'キーの情報が格納されています。 */ broadcast: function(message) { return Ext.Promise.from([]); }, /** * 指定されたIDを使用して別のアプリケーションへの接続を作成します。 * @param {String} receiverId 接続先のアプリケーションのID。このIDを#broadcastから取得。 * @returns {Ext.Promise} */ connect: function(receiverId) { var connections = this.connections, connection = connections[receiverId]; if (connection) { return Ext.Promise.from(connection); } else { return this.send(receiverId, '__CONNECT__').then(function() { connections[receiverId] = connection = new Ext.device.tunnel.Connection(receiverId); return connection; }); } }, /** * メッセージを送信。 * @param {String} receiverId 接続先のアプリケーションのID。このIDを#broadcastから取得。 * @param {*} message 送信するメッセージ。JSON対応可能なものであればオブジェクトでも構いません。 * @param {Boolean} [foreground] 受信側アプリケーションをフォアグラウンドにするかどうか。 * @returns {Ext.Promise} */ send: function(receiverId, message, foreground) { var messageId = this.messageId++, receivePromise = new Ext.Promise(), sendPromise = this.doSend(receiverId, messageId, message, foreground), pendingReceivePromises = this.pendingReceivePromises; pendingReceivePromises[messageId] = receivePromise; sendPromise.error(function(reason) { delete pendingReceivePromises[messageId]; receivePromise.reject(reason); }); return receivePromise; }, /** * 新しい接続を処理するコールバックを指定。返されたブール値によって接続の受け入れ可否を判断します。 * @param {Function} callback */ onConnect: function(callback) { var queue = this.connectQueue.slice(0), i, ln, args; this.connectQueue.length = 0; if (callback) { this.connectCallback = callback; for (i = 0, ln = queue.length; i < ln; i++) { args = queue[i]; this.onReceived.apply(this, args); } } }, /** * 受信メッセージを処理するコールバックを指定。戻り値が送信側に返されます。処理を非同期で行うことが必要な場合はExt.Promiseのインスタンスが返されます。 * @param {Function} callback */ onMessage: function(callback) { var queue = this.messageQueue.slice(0), i, ln, args; this.messageQueue.length = 0; if (callback) { this.messageCallback = callback; for (i = 0, ln = queue.length; i < ln; i++) { args = queue[i]; this.onReceived.apply(this, args); } } }, /** * @private */ onAppConnect: function() { return this.connectCallback.apply(this, arguments); }, /** * @private */ onAppMessage: function(appId, message) { var connection = this.connections[appId], response; if (connection) { response = connection.receive(message); } if (typeof response == 'undefined') { response = this.messageCallback.apply(this, arguments); } return response; }, /** * @private */ onReceived: function(data) { var appId = data.appId, message = data.message, messageId = data.id, foreground = data.foreground, pendingReceivePromises = this.pendingReceivePromises, pendingPromise = pendingReceivePromises[messageId], connectCallback = this.connectCallback, messageCallback = this.messageCallback, response; delete pendingReceivePromises[messageId]; // A response if (pendingPromise) { if (message.error) { pendingPromise.reject(message.error); } else { pendingPromise.fulfill(message.success); } } // A request else { try { if (message === '__CONNECT__') { if (!connectCallback) { this.connectQueue.push(arguments); return; } else { response = this.onAppConnect(appId); } } else { if (!messageCallback) { this.messageQueue.push(arguments); return; } else { response = this.onAppMessage(appId, message); } } if (response instanceof Ext.Promise) { response.then(this, function(result) { this.doSend(appId, messageId, { success: result }, foreground); }, function(reason) { this.doSend(appId, messageId, { error: reason }, foreground); }); } else { this.doSend(appId, messageId, { success: response }, foreground); } } catch (e) { this.doSend(appId, messageId, { error: e }, foreground); } } }, /** * @private */ doSend: Ext.emptyFn });