faye
Version:
Simple pub/sub messaging for the web
218 lines (164 loc) • 6.12 kB
JavaScript
'use strict';
var Class = require('../util/class'),
Cookie = require('../util/cookies').Cookie,
Promise = require('../util/promise'),
array = require('../util/array'),
assign = require('../util/assign'),
Logging = require('../mixins/logging'),
Timeouts = require('../mixins/timeouts'),
Channel = require('../protocol/channel');
var Transport = assign(Class({ className: 'Transport',
DEFAULT_PORTS: { 'http:': 80, 'https:': 443, 'ws:': 80, 'wss:': 443 },
MAX_DELAY: 0,
batching: true,
initialize: function(dispatcher, endpoint) {
this._dispatcher = dispatcher;
this.endpoint = endpoint;
this._outbox = [];
this._proxy = assign({}, this._dispatcher.proxy);
if (!this._proxy.origin)
this._proxy.origin = this._findProxy();
},
close: function() {},
encode: function(messages) {
return '';
},
sendMessage: function(message) {
this.debug('Client ? sending message to ?: ?',
this._dispatcher.clientId, this.endpoint.href, message);
if (!this.batching) return Promise.resolve(this.request([message]));
this._outbox.push(message);
this._flushLargeBatch();
if (message.channel === Channel.HANDSHAKE)
return this._publish(0.01);
if (message.channel === Channel.CONNECT)
this._connectMessage = message;
return this._publish(this.MAX_DELAY);
},
_makePromise: function() {
var self = this;
this._requestPromise = this._requestPromise || new Promise(function(resolve) {
self._resolvePromise = resolve;
});
},
_publish: function(delay) {
this._makePromise();
this.addTimeout('publish', delay, function() {
this._flush();
delete this._requestPromise;
}, this);
return this._requestPromise;
},
_flush: function() {
this.removeTimeout('publish');
if (this._outbox.length > 1 && this._connectMessage)
this._connectMessage.advice = { timeout: 0 };
this._resolvePromise(this.request(this._outbox));
this._connectMessage = null;
this._outbox = [];
},
_flushLargeBatch: function() {
var string = this.encode(this._outbox);
if (string.length < this._dispatcher.maxRequestSize) return;
var last = this._outbox.pop();
this._makePromise();
this._flush();
if (last) this._outbox.push(last);
},
_receive: function(replies) {
if (!replies) return;
replies = [].concat(replies);
this.debug('Client ? received from ? via ?: ?',
this._dispatcher.clientId, this.endpoint.href, this.connectionType, replies);
for (var i = 0, n = replies.length; i < n; i++)
this._dispatcher.handleResponse(replies[i]);
},
_handleError: function(messages, immediate) {
messages = [].concat(messages);
this.debug('Client ? failed to send to ? via ?: ?',
this._dispatcher.clientId, this.endpoint.href, this.connectionType, messages);
for (var i = 0, n = messages.length; i < n; i++)
this._dispatcher.handleError(messages[i]);
},
_getCookies: function() {
var cookies = this._dispatcher.cookies,
url = this.endpoint.href;
if (!cookies) return '';
return array.map(cookies.getCookiesSync(url), function(cookie) {
return cookie.cookieString();
}).join('; ');
},
_storeCookies: function(setCookie) {
var cookies = this._dispatcher.cookies,
url = this.endpoint.href,
cookie;
if (!setCookie || !cookies) return;
setCookie = [].concat(setCookie);
for (var i = 0, n = setCookie.length; i < n; i++) {
cookie = Cookie.parse(setCookie[i]);
cookies.setCookieSync(cookie, url);
}
},
_findProxy: function() {
if (typeof process === 'undefined') return undefined;
var protocol = this.endpoint.protocol;
if (!protocol) return undefined;
var name = protocol.replace(/:$/, '').toLowerCase() + '_proxy',
upcase = name.toUpperCase(),
env = process.env,
keys, proxy;
if (name === 'http_proxy' && env.REQUEST_METHOD) {
keys = Object.keys(env).filter(function(k) { return /^http_proxy$/i.test(k) });
if (keys.length === 1) {
if (keys[0] === name && env[upcase] === undefined)
proxy = env[name];
} else if (keys.length > 1) {
proxy = env[name];
}
proxy = proxy || env['CGI_' + upcase];
} else {
proxy = env[name] || env[upcase];
if (proxy && !env[name])
console.warn('The environment variable ' + upcase +
' is discouraged. Use ' + name + '.');
}
return proxy;
}
}), {
get: function(dispatcher, allowed, disabled, callback, context) {
var endpoint = dispatcher.endpoint;
array.asyncEach(this._transports, function(pair, resume) {
var connType = pair[0], klass = pair[1],
connEndpoint = dispatcher.endpointFor(connType);
if (array.indexOf(disabled, connType) >= 0)
return resume();
if (array.indexOf(allowed, connType) < 0) {
klass.isUsable(dispatcher, connEndpoint, function() {});
return resume();
}
klass.isUsable(dispatcher, connEndpoint, function(isUsable) {
if (!isUsable) return resume();
var transport = klass.hasOwnProperty('create') ? klass.create(dispatcher, connEndpoint) : new klass(dispatcher, connEndpoint);
callback.call(context, transport);
});
}, function() {
throw new Error('Could not find a usable connection type for ' + endpoint.href);
});
},
register: function(type, klass) {
this._transports.push([type, klass]);
klass.prototype.connectionType = type;
},
getConnectionTypes: function() {
return array.map(this._transports, function(t) { return t[0] });
},
disable: function(feature) {
if (feature !== 'autodisconnect') return;
for (var i = 0; i < this._transports.length; i++)
this._transports[i][1]._unloaded = false;
},
_transports: []
});
assign(Transport.prototype, Logging);
assign(Transport.prototype, Timeouts);
module.exports = Transport;