halley
Version:
A bayeux client for modern browsers and node. Forked from Faye
198 lines (160 loc) • 5.29 kB
JavaScript
'use strict';
var debug = require('debug')('halley:pool');
var Promise = require('bluebird');
var cancelBarrier = require('../util/promise-util').cancelBarrier;
var LazySingleton = require('../util/promise-util').LazySingleton;
function TransportPool(dispatcher, endpoint, advice, disabled, registered) {
this._dispatcher = dispatcher;
this._endpoint = endpoint;
this._advice = advice;
this._transports = {};
this._disabled = disabled;
this._registered = registered;
this._current = new LazySingleton(this._reselect.bind(this));
this._registeredHash = registered.reduce(function(memo, transport) {
var type = transport[0];
var Klass = transport[1];
memo[type] = Klass;
return memo;
}, {});
this._allowed = null;
this.setAllowed(null)
.catch(function(err) {
debug('Unable to preconnect to any available transports: err=%s', err);
})
.done();
}
TransportPool.prototype = {
/** Returns a promise to transport */
get: function() {
var current = this._current.get();
return cancelBarrier(current);
},
current: function() {
var c = this._current.peek();
if (c && c.isFulfilled()) {
return c.value();
}
},
/**
* Set the allowed transport types that `.get` will return
*/
setAllowed: function(allowedTypes, cleanup) {
// Maintain the order from this._allowed
this._allowed = this._registered
.map(function(transport) {
return transport[0];
})
.filter(function(type) {
return !allowedTypes || allowedTypes.indexOf(type) >= 0;
});
if (cleanup) {
// Remove transports that we won't use
Object.keys(this._transports).forEach(function(type) {
if (this._allowed.indexOf(type) >= 0) return;
var transport = this._transports[type];
delete this._transports[type];
if (transport.isFulfilled()) {
transport.value().close();
} else {
transport.cancel();
}
}, this);
}
return this.reevaluate();
},
reevaluate: function() {
this._current.clear();
var current = this._current.get();
return cancelBarrier(current);
},
_reselect: function() {
var allowed = this._allowed;
debug('_reselect: %j', allowed);
// Load the transport
var connectionPromises = allowed
.filter(function(type) {
var Klass = this._registeredHash[type];
if (this._disabled && this._disabled.indexOf(type) >= 0) return false;
return Klass.isUsable(this._endpoint);
}, this)
.map(function(type) {
var Klass = this._registeredHash[type];
var current = this._transports[type];
if (current) {
if(!current.isRejected() && !current.isCancelled()) {
return current;
}
// Should we cancel the current?
}
var instance = new Klass(this._dispatcher, this._endpoint, this._advice);
var promise;
// If the promise has a connect method, we call it
if (instance.connect) {
promise = instance.connect().return(instance);
// If the transport isn't connected yet, we should try again when it does
if (promise.isPending()) {
promise.bind(this).tap(function(transport) {
debug('_reselect: %j is now connected so we are going to reevaulate', transport.connectionType);
this.reevaluate();
});
}
} else {
promise = Promise.resolve(instance);
}
this._transports[type] = promise;
return promise;
}, this);
if (!connectionPromises.length) {
return Promise.reject(new Error('No suitable transports available'));
}
// Return the first usable transport
return Promise.any(connectionPromises)
.then(function(transport) {
debug('Selected transport %s', transport.connectionType);
return transport;
})
.catch(Promise.AggregateError, function(err) {
/* Fail with the first problem */
throw err[0];
});
},
close: function() {
debug('_close');
var transports = this._transports;
this._transports = {};
var current = this._current.value;
if (current) {
this._current.clear();
if (current.isPending()) {
current.cancel();
}
}
Object.keys(transports).forEach(function(type) {
var transportPromise = transports[type];
if (transportPromise.isFulfilled()) {
transportPromise.value().close();
} else {
transportPromise.cancel();
}
});
},
/**
* Called on transport close
*/
down: function(transport) {
var connectionType = transport.connectionType;
var transportPromise = this._transports[connectionType];
if (!transportPromise) return;
if (transportPromise.isFulfilled()) {
var existingTransport = transportPromise.value();
if (existingTransport !== transport) return;
// Don't call transport.close as this
// will be called from the close
delete this._transports[connectionType];
// Next time someone does a `.get` we will attempt to reselect
this._current.clear();
}
}
};
module.exports = TransportPool;