Home | History | Annotate | Download | only in js
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 define("mojo/public/js/router", [
      6   "mojo/public/js/codec",
      7   "mojo/public/js/core",
      8   "mojo/public/js/connector",
      9   "mojo/public/js/validator",
     10 ], function(codec, core, connector, validator) {
     11 
     12   var Connector = connector.Connector;
     13   var MessageReader = codec.MessageReader;
     14   var Validator = validator.Validator;
     15 
     16   function Router(handle, connectorFactory) {
     17     if (!core.isHandle(handle))
     18       throw new Error("Router constructor: Not a handle");
     19     if (connectorFactory === undefined)
     20       connectorFactory = Connector;
     21     this.connector_ = new connectorFactory(handle);
     22     this.incomingReceiver_ = null;
     23     this.errorHandler_ = null;
     24     this.nextRequestID_ = 0;
     25     this.completers_ = new Map();
     26     this.payloadValidators_ = [];
     27 
     28     this.connector_.setIncomingReceiver({
     29         accept: this.handleIncomingMessage_.bind(this),
     30     });
     31     this.connector_.setErrorHandler({
     32         onError: this.handleConnectionError_.bind(this),
     33     });
     34   }
     35 
     36   Router.prototype.close = function() {
     37     this.completers_.clear();  // Drop any responders.
     38     this.connector_.close();
     39   };
     40 
     41   Router.prototype.accept = function(message) {
     42     this.connector_.accept(message);
     43   };
     44 
     45   Router.prototype.reject = function(message) {
     46     // TODO(mpcomplete): no way to trasmit errors over a Connection.
     47   };
     48 
     49   Router.prototype.acceptAndExpectResponse = function(message) {
     50     // Reserve 0 in case we want it to convey special meaning in the future.
     51     var requestID = this.nextRequestID_++;
     52     if (requestID == 0)
     53       requestID = this.nextRequestID_++;
     54 
     55     message.setRequestID(requestID);
     56     var result = this.connector_.accept(message);
     57     if (!result)
     58       return Promise.reject(Error("Connection error"));
     59 
     60     var completer = {};
     61     this.completers_.set(requestID, completer);
     62     return new Promise(function(resolve, reject) {
     63       completer.resolve = resolve;
     64       completer.reject = reject;
     65     });
     66   };
     67 
     68   Router.prototype.setIncomingReceiver = function(receiver) {
     69     this.incomingReceiver_ = receiver;
     70   };
     71 
     72   Router.prototype.setPayloadValidators = function(payloadValidators) {
     73     this.payloadValidators_ = payloadValidators;
     74   };
     75 
     76   Router.prototype.setErrorHandler = function(handler) {
     77     this.errorHandler_ = handler;
     78   };
     79 
     80   Router.prototype.encounteredError = function() {
     81     return this.connector_.encounteredError();
     82   };
     83 
     84   Router.prototype.handleIncomingMessage_ = function(message) {
     85     var noError = validator.validationError.NONE;
     86     var messageValidator = new Validator(message);
     87     var err = messageValidator.validateMessageHeader();
     88     for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
     89       err = this.payloadValidators_[i](messageValidator);
     90 
     91     if (err == noError)
     92       this.handleValidIncomingMessage_(message);
     93     else
     94       this.handleInvalidIncomingMessage_(message, err);
     95   };
     96 
     97   Router.prototype.handleValidIncomingMessage_ = function(message) {
     98     if (message.expectsResponse()) {
     99       if (this.incomingReceiver_) {
    100         this.incomingReceiver_.acceptWithResponder(message, this);
    101       } else {
    102         // If we receive a request expecting a response when the client is not
    103         // listening, then we have no choice but to tear down the pipe.
    104         this.close();
    105       }
    106     } else if (message.isResponse()) {
    107       var reader = new MessageReader(message);
    108       var requestID = reader.requestID;
    109       var completer = this.completers_.get(requestID);
    110       this.completers_.delete(requestID);
    111       completer.resolve(message);
    112     } else {
    113       if (this.incomingReceiver_)
    114         this.incomingReceiver_.accept(message);
    115     }
    116   }
    117 
    118   Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
    119     this.close();
    120   }
    121 
    122   Router.prototype.handleConnectionError_ = function(result) {
    123     this.completers_.forEach(function(value) {
    124       value.reject(result);
    125     });
    126     if (this.errorHandler_)
    127       this.errorHandler_();
    128     this.close();
    129   };
    130 
    131   // The TestRouter subclass is only intended to be used in unit tests.
    132   // It defeats valid message handling and delgates invalid message handling.
    133 
    134   function TestRouter(handle, connectorFactory) {
    135     Router.call(this, handle, connectorFactory);
    136   }
    137 
    138   TestRouter.prototype = Object.create(Router.prototype);
    139 
    140   TestRouter.prototype.handleValidIncomingMessage_ = function() {
    141   };
    142 
    143   TestRouter.prototype.handleInvalidIncomingMessage_ =
    144       function(message, error) {
    145         this.validationErrorHandler(error);
    146       };
    147 
    148   var exports = {};
    149   exports.Router = Router;
    150   exports.TestRouter = TestRouter;
    151   return exports;
    152 });
    153