| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| /** |
| * @fileoverview Channel RPCs to Nacl plugin. |
| */ |
| |
| var vom = require('../../../src/vom'); |
| var TaskSequence = require('../../../src/lib/task-sequence'); |
| var channelVdl = |
| require('../../vdl/v.io/x/ref/services/wspr/internal/channel'); |
| var browsprVdl = |
| require('../../vdl/v.io/x/ref/services/wspr/internal/browspr'); |
| var vlog = require('../../../src/lib/vlog'); |
| |
| module.exports = RpcChannel; |
| |
| function uint8ArrayToArrayBuffer(arr) { |
| return arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.length); |
| } |
| |
| function RpcChannel(sendMessage) { |
| if (!(this instanceof RpcChannel)) { |
| return new RpcChannel(sendMessage); |
| } |
| this.sendMessage = sendMessage; |
| this.lastSeq = 0; |
| this.handlers = {}; |
| this.pendingCallbacks = {}; |
| this.decodeQueue = new TaskSequence(); |
| } |
| |
| RpcChannel.prototype.registerRpcHandler = function(type, func) { |
| this.handlers[type] = func; |
| }; |
| |
| RpcChannel.prototype.performRpc = function(type, val, callback) { |
| if (typeof val !== 'object') { |
| throw new Error('val must be of type object, was ' + (typeof val)); |
| } |
| |
| var BrowsprMessage; |
| switch(type) { |
| case 'start': |
| BrowsprMessage = browsprVdl.StartMessage; |
| break; |
| case 'auth:get-accounts': |
| BrowsprMessage = browsprVdl.GetAccountsMessage; |
| break; |
| case 'auth:create-account': |
| BrowsprMessage = browsprVdl.CreateAccountMessage; |
| break; |
| case 'auth:origin-has-account': |
| BrowsprMessage = browsprVdl.OriginHasAccountMessage; |
| break; |
| case 'auth:associate-account': |
| BrowsprMessage = browsprVdl.AssociateAccountMessage; |
| break; |
| case 'create-instance': |
| BrowsprMessage = browsprVdl.CreateInstanceMessage; |
| break; |
| case 'cleanup': |
| BrowsprMessage = browsprVdl.CleanupMessage; |
| break; |
| default: |
| throw new Error('Unknown RPC type', type); |
| } |
| |
| callback = callback || function(){}; |
| var seq = ++this.lastSeq; |
| this.pendingCallbacks[seq] = callback; |
| var request = new channelVdl.Request({ |
| type: type, |
| seq: seq, |
| body: new BrowsprMessage(val) |
| }); |
| this._sendVomEncodedMessage(new channelVdl.Message({ |
| request: request |
| })); |
| }; |
| |
| RpcChannel.prototype._sendVomEncodedMessage = function(msg) { |
| var writer = new vom.ByteArrayMessageWriter(); |
| var enc = new vom.Encoder(writer); |
| enc.encode(msg); |
| var encodedBytes = writer.getBytes(); |
| this._postMessage(uint8ArrayToArrayBuffer(encodedBytes)); |
| }; |
| |
| RpcChannel.prototype._handleRequest = function(req) { |
| var type = req.type; |
| var handler = this.handlers[type]; |
| if (handler === undefined) { |
| throw new Error('Undefined handler for type \'' + type + '\''); |
| } |
| var result; |
| var err; |
| try { |
| result = handler(req.body); |
| } catch (e) { |
| err = e.message; |
| // TODO(bprosnitz) Nil is not handled yet in VOM2. |
| // Remove this when it is implemented. |
| result = 'ResultMessageToBeRemovedWhenVOM2IsComplete'; |
| } |
| var response = new channelVdl.Response({ |
| reqSeq: req.Seq, |
| err: err || '', |
| body: result |
| }); |
| this._sendVomEncodedMessage(new channelVdl.Message({ |
| response: response |
| })); |
| }; |
| |
| RpcChannel.prototype._handleResponse = function(resp) { |
| var seq = resp.reqSeq; |
| var cb = this.pendingCallbacks[seq]; |
| delete this.pendingCallbacks[seq]; |
| if (cb === undefined) { |
| throw new Error('Received response with no matching sequence number '+ |
| JSON.stringify(resp)); |
| } |
| if (resp.err !== '') { |
| return cb(new Error(resp.err)); |
| } |
| // TODO(nlacasse,bpronitz): Is it OK to just call "val" on the body like this? |
| var body = resp.body && resp.body.val; |
| cb(null, body); |
| }; |
| |
| RpcChannel.prototype._handleMessageTask = function(msg) { |
| var msgBytes = new Uint8Array(msg); |
| var reader = new vom.ByteArrayMessageReader(msgBytes); |
| var dec = new vom.Decoder(reader); |
| var channel = this; |
| return dec.decode().then(function(jsMsg) { |
| if (jsMsg._type.name === channelVdl.Message.name) { |
| throw new Error('Message does not have correct Message type: ' + |
| JSON.stringify(jsMsg)); |
| } else if (jsMsg.request) { |
| return channel._handleRequest(jsMsg.request); |
| } else if (jsMsg.response) { |
| return channel._handleResponse(jsMsg.response); |
| } else { |
| var err = new Error('Message has Message type, but no "request" or ' + |
| '"response" fields: ' + JSON.stringify(jsMsg)); |
| vlog.logger.error(err + ': ' + err.stack); |
| } |
| }, function(err) { |
| vlog.logger.error(err + ': ' + err.stack); |
| }); |
| }; |
| RpcChannel.prototype.handleMessage = function(msg) { |
| // We add this to the task queue to make sure that the decode callback for |
| // all the messages are peformed in order. |
| this.decodeQueue.addTask(this._handleMessageTask.bind(this, msg)); |
| }; |
| |
| RpcChannel.prototype._postMessage = function(msg) { |
| this.sendMessage({ |
| type: 'browsprRpc', |
| instanceId: -1, |
| origin: '', |
| body: msg, |
| }); |
| }; |