// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. define("mojo/public/js/router", [ "mojo/public/js/codec", "mojo/public/js/core", "mojo/public/js/connector", "mojo/public/js/validator", ], function(codec, core, connector, validator) { var Connector = connector.Connector; var MessageReader = codec.MessageReader; var Validator = validator.Validator; function Router(handle, connectorFactory) { if (!core.isHandle(handle)) throw new Error("Router constructor: Not a handle"); if (connectorFactory === undefined) connectorFactory = Connector; this.connector_ = new connectorFactory(handle); this.incomingReceiver_ = null; this.errorHandler_ = null; this.nextRequestID_ = 0; this.completers_ = new Map(); this.payloadValidators_ = []; this.connector_.setIncomingReceiver({ accept: this.handleIncomingMessage_.bind(this), }); this.connector_.setErrorHandler({ onError: this.handleConnectionError_.bind(this), }); } Router.prototype.close = function() { this.completers_.clear(); // Drop any responders. this.connector_.close(); }; Router.prototype.accept = function(message) { this.connector_.accept(message); }; Router.prototype.reject = function(message) { // TODO(mpcomplete): no way to trasmit errors over a Connection. }; Router.prototype.acceptAndExpectResponse = function(message) { // Reserve 0 in case we want it to convey special meaning in the future. var requestID = this.nextRequestID_++; if (requestID == 0) requestID = this.nextRequestID_++; message.setRequestID(requestID); var result = this.connector_.accept(message); if (!result) return Promise.reject(Error("Connection error")); var completer = {}; this.completers_.set(requestID, completer); return new Promise(function(resolve, reject) { completer.resolve = resolve; completer.reject = reject; }); }; Router.prototype.setIncomingReceiver = function(receiver) { this.incomingReceiver_ = receiver; }; Router.prototype.setPayloadValidators = function(payloadValidators) { this.payloadValidators_ = payloadValidators; }; Router.prototype.setErrorHandler = function(handler) { this.errorHandler_ = handler; }; Router.prototype.encounteredError = function() { return this.connector_.encounteredError(); }; Router.prototype.handleIncomingMessage_ = function(message) { var noError = validator.validationError.NONE; var messageValidator = new Validator(message); var err = messageValidator.validateMessageHeader(); for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i) err = this.payloadValidators_[i](messageValidator); if (err == noError) this.handleValidIncomingMessage_(message); else this.handleInvalidIncomingMessage_(message, err); }; Router.prototype.handleValidIncomingMessage_ = function(message) { if (message.expectsResponse()) { if (this.incomingReceiver_) { this.incomingReceiver_.acceptWithResponder(message, this); } else { // If we receive a request expecting a response when the client is not // listening, then we have no choice but to tear down the pipe. this.close(); } } else if (message.isResponse()) { var reader = new MessageReader(message); var requestID = reader.requestID; var completer = this.completers_.get(requestID); this.completers_.delete(requestID); completer.resolve(message); } else { if (this.incomingReceiver_) this.incomingReceiver_.accept(message); } } Router.prototype.handleInvalidIncomingMessage_ = function(message, error) { this.close(); } Router.prototype.handleConnectionError_ = function(result) { this.completers_.forEach(function(value) { value.reject(result); }); if (this.errorHandler_) this.errorHandler_(); this.close(); }; // The TestRouter subclass is only intended to be used in unit tests. // It defeats valid message handling and delgates invalid message handling. function TestRouter(handle, connectorFactory) { Router.call(this, handle, connectorFactory); } TestRouter.prototype = Object.create(Router.prototype); TestRouter.prototype.handleValidIncomingMessage_ = function() { }; TestRouter.prototype.handleInvalidIncomingMessage_ = function(message, error) { this.validationErrorHandler(error); }; var exports = {}; exports.Router = Router; exports.TestRouter = TestRouter; return exports; });