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 5define("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