blob: ed2264bc2ad60c52d926688a30361bca25e00f71 [file] [log] [blame]
// 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.ByteMessageWriter();
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,
});
};