build/faye.js in faye-0.3.1 vs build/faye.js in faye-0.3.2

- old
+ new

@@ -10,11 +10,11 @@ } return dest; }; Faye.extend(Faye, { - VERSION: '0.3.1', + VERSION: '0.3.2', BAYEUX_VERSION: '1.0', ID_LENGTH: 128, JSONP_CALLBACK: 'jsonpcallback', CONNECTION_TYPES: ["long-polling", "callback-polling"], @@ -141,10 +141,39 @@ this.each(actual, function(key, value) { result = result && (expected[key] === value); }); return result; } + }, + + // http://assanka.net/content/tech/2009/09/02/json2-js-vs-prototype/ + toJSON: function(object) { + if (this.stringify) + return this.stringify(object, function(key, value) { + return (this[key] instanceof Array) + ? this[key] + : value; + }); + + return JSON.stringify(object); + }, + + timestamp: function() { + var date = new Date(), + year = date.getFullYear(), + month = date.getMonth() + 1, + day = date.getDate(), + hour = date.getHours(), + minute = date.getMinutes(), + second = date.getSeconds(); + + var pad = function(n) { + return n < 10 ? '0' + n : String(n); + }; + + return pad(year) + '-' + pad(month) + '-' + pad(day) + ' ' + + pad(hour) + ':' + pad(minute) + ':' + pad(second); } }); Faye.Class = function(parent, methods) { @@ -231,10 +260,76 @@ }); } }; +Faye.Logging = { + LOG_LEVELS: { + error: 3, + warn: 2, + info: 1, + debug: 0 + }, + + logLevel: 'error', + + log: function(message, level) { + if (!Faye.logger) return; + + var levels = Faye.Logging.LOG_LEVELS; + if (levels[Faye.Logging.logLevel] > levels[level]) return; + + var banner = '[' + level.toUpperCase() + '] [Faye', + klass = null; + + for (var key in Faye) { + if (klass) continue; + if (typeof Faye[key] !== 'function') continue; + if (this instanceof Faye[key]) klass = key; + } + if (klass) banner += '.' + klass; + banner += '] '; + + Faye.logger(Faye.timestamp() + ' ' + banner + message); + }, + + error: function(message) { + this.log(message, 'error'); + }, + warn: function(message) { + this.log(message, 'warn'); + }, + info: function(message) { + this.log(message, 'info'); + }, + debug: function(message) { + this.log(message, 'debug'); + } +}; + + +Faye.Timeouts = { + addTimeout: function(name, delay, callback, scope) { + this._timeouts = this._timeouts || {}; + if (this._timeouts.hasOwnProperty(name)) return; + var self = this; + this._timeouts[name] = setTimeout(function() { + delete self._timeouts[name]; + callback.call(scope); + }, 1000 * delay); + }, + + removeTimeout: function(name) { + this._timeouts = this._timeouts || {}; + var timeout = this._timeouts[name]; + if (!timeout) return; + clearTimeout(timeout); + delete this._timeouts[name]; + } +}; + + Faye.Channel = Faye.Class({ initialize: function(name) { this.__id = this.name = name; }, @@ -298,10 +393,14 @@ subtree.each(path, block, context); }); if (this._value !== undefined) block.call(context, prefix, this._value); }, + getKeys: function() { + return this.map(function(key, value) { return '/' + key.join('/') }); + }, + map: function(block, context) { var result = []; this.each([], function(path, value) { result.push(block.call(context, path, value)); }); @@ -387,32 +486,47 @@ }); Faye.Transport = Faye.extend(Faye.Class({ initialize: function(client, endpoint) { + this.debug('Created new transport for ' + endpoint); this._client = client; this._endpoint = endpoint; }, send: function(message, callback, scope) { if (!(message instanceof Array) && !message.id) message.id = this._client._namespace.generate(); + this.debug('Client ' + this._client._clientId + + ' sending message to ' + this._endpoint + ': ' + + Faye.toJSON(message)); + this.request(message, function(responses) { + this.debug('Client ' + this._client._clientId + + ' received from ' + this._endpoint + ': ' + + Faye.toJSON(responses)); + if (!callback) return; + + var messages = [], deliverable = true; Faye.each([].concat(responses), function(response) { + + if (response.id === message.id) { + if (callback.call(scope, response) === false) + deliverable = false; + } - if (response.id === message.id) - callback.call(scope, response); - if (response.advice) this._client.handleAdvice(response.advice); if (response.data && response.channel) - this._client.sendToSubscribers(response); + messages.push(response); }, this); + + if (deliverable) this._client.deliverMessages(messages); }, this); } }), { get: function(client, connectionTypes) { var endpoint = client._endpoint; @@ -442,27 +556,36 @@ Faye.each(this._transports, function(key, type) { list.push(key) }); return list; } }); +Faye.extend(Faye.Transport.prototype, Faye.Logging); + Faye.Client = Faye.Class({ - UNCONNECTED: 1, - CONNECTING: 2, - CONNECTED: 3, - DISCONNECTED: 4, + UNCONNECTED: 1, + CONNECTING: 2, + CONNECTED: 3, + DISCONNECTED: 4, - HANDSHAKE: 'handshake', - RETRY: 'retry', - NONE: 'none', + HANDSHAKE: 'handshake', + RETRY: 'retry', + NONE: 'none', - DEFAULT_ENDPOINT: '/bayeux', - MAX_DELAY: 0.1, - INTERVAL: 1000.0, + CONNECTION_TIMEOUT: 60.0, - initialize: function(endpoint) { + DEFAULT_ENDPOINT: '/bayeux', + MAX_DELAY: 0.1, + INTERVAL: 1000.0, + + initialize: function(endpoint, options) { + this.info('New client created for ' + endpoint); + this._endpoint = endpoint || this.DEFAULT_ENDPOINT; + this._options = options || {}; + this._timeout = this._options.timeout || this.CONNECTION_TIMEOUT; + this._transport = Faye.Transport.get(this); this._state = this.UNCONNECTED; this._namespace = new Faye.Namespace(); this._outbox = []; this._channels = new Faye.Channel.Tree(); @@ -498,26 +621,30 @@ if (this._state !== this.UNCONNECTED) return; this._state = this.CONNECTING; var self = this; + this.info('Initiating handshake with ' + this._endpoint); + this._transport.send({ channel: Faye.Channel.HANDSHAKE, version: Faye.BAYEUX_VERSION, supportedConnectionTypes: Faye.Transport.supportedConnectionTypes() }, function(response) { if (!response.successful) { + this.info('Handshake unsuccessful'); setTimeout(function() { self.handshake(callback, scope) }, this._advice.interval); return this._state = this.UNCONNECTED; } this._state = this.CONNECTED; this._clientId = response.clientId; this._transport = Faye.Transport.get(this, response.supportedConnectionTypes); + this.info('Handshake successful: ' + this._clientId); if (callback) callback.call(scope); }, this); }, // Request Response @@ -531,36 +658,45 @@ // * timestamp connect: function(callback, scope) { if (this._advice.reconnect === this.NONE) return; if (this._state === this.DISCONNECTED) return; - if (this._advice.reconnect === this.HANDSHAKE || this._state === this.UNCONNECTED) + if (this._advice.reconnect === this.HANDSHAKE || this._state === this.UNCONNECTED) { + this._beginReconnectTimeout(); return this.handshake(function() { this.connect(callback, scope) }, this); + } if (this._state === this.CONNECTING) return this.callback(callback, scope); if (this._state !== this.CONNECTED) return; + this.info('Calling deferred actions for ' + this._clientId); this.setDeferredStatus('succeeded'); this.setDeferredStatus('deferred'); if (callback) callback.call(scope); if (this._connectionId) return; this._connectionId = this._namespace.generate(); var self = this; + this.info('Initiating connection for ' + this._clientId); this._transport.send({ channel: Faye.Channel.CONNECT, clientId: this._clientId, connectionType: this._transport.connectionType, id: this._connectionId - }, function(response) { + }, this._verifyClientId(function(response) { delete this._connectionId; + this.removeTimeout('reconnect'); + + this.info('Closed connection for ' + this._clientId); setTimeout(function() { self.connect() }, this._advice.interval); - }, this); + })); + + this._beginReconnectTimeout(); }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful @@ -570,15 +706,18 @@ // * id disconnect: function() { if (this._state !== this.CONNECTED) return; this._state = this.DISCONNECTED; + this.info('Disconnecting ' + this._clientId); + this._transport.send({ channel: Faye.Channel.DISCONNECT, clientId: this._clientId }); + this.info('Clearing channel listeners for ' + this._clientId); this._channels = new Faye.Channel.Tree(); }, // Request Response // MUST include: * channel MUST include: * channel @@ -594,23 +733,29 @@ this.connect(function() { channels = [].concat(channels); this._validateChannels(channels); + this.info('Client ' + this._clientId + ' attempting to subscribe to [' + + channels.join(', ') + ']'); + this._transport.send({ channel: Faye.Channel.SUBSCRIBE, clientId: this._clientId, subscription: channels - }, function(response) { - if (!response.successful) return; + }, this._verifyClientId(function(response) { + if (!response.successful || !callback) return; + this.info('Subscription acknowledged for ' + this._clientId + ' to [' + + channels.join(', ') + ']'); + channels = [].concat(response.subscription); Faye.each(channels, function(channel) { this._channels.set(channel, [callback, scope]); }, this); - }, this); + })); }, this); }, // Request Response @@ -627,23 +772,29 @@ this.connect(function() { channels = [].concat(channels); this._validateChannels(channels); + this.info('Client ' + this._clientId + ' attempting to unsubscribe from [' + + channels.join(', ') + ']'); + this._transport.send({ channel: Faye.Channel.UNSUBSCRIBE, clientId: this._clientId, subscription: channels - }, function(response) { + }, this._verifyClientId(function(response) { if (!response.successful) return; + this.info('Unsubscription acknowledged for ' + this._clientId + ' from [' + + channels.join(', ') + ']'); + channels = [].concat(response.subscription); Faye.each(channels, function(channel) { this._channels.set(channel, null); }, this); - }, this); + })); }, this); }, // Request Response @@ -655,40 +806,55 @@ publish: function(channel, data) { this.connect(function() { this._validateChannels([channel]); + this.info('Client ' + this._clientId + ' queueing published message to ' + + channel + ': ' + Faye.toJSON(data)); + this._enqueue({ channel: channel, data: data, clientId: this._clientId }); - if (this._timeout) return; - var self = this; + this.addTimeout('publish', this.MAX_DELAY, this._flush, this); - this._timeout = setTimeout(function() { - delete self._timeout; - self._flush(); - }, this.MAX_DELAY * 1000); - }, this); }, handleAdvice: function(advice) { Faye.extend(this._advice, advice); if (this._advice.reconnect === this.HANDSHAKE) this._clientId = null; }, - sendToSubscribers: function(message) { - var channels = this._channels.glob(message.channel); - Faye.each(channels, function(callback) { - if (!callback) return; - callback[0].call(callback[1], message.data); - }); + deliverMessages: function(messages) { + Faye.each(messages, function(message) { + this.info('Client ' + this._clientId + ' calling listeners for ' + + message.channel + ' with ' + Faye.toJSON(message.data)); + + var channels = this._channels.glob(message.channel); + Faye.each(channels, function(callback) { + if (!callback) return; + callback[0].call(callback[1], message.data); + }); + }, this); }, + _beginReconnectTimeout: function() { + this.addTimeout('reconnect', this._timeout, function() { + delete this._connectionId; + delete this._clientId; + this._state = this.UNCONNECTED; + + this.info('Server took >' + this._timeout + 's to reply to connection for ' + + this._clientId + ': attempting to reconnect'); + + this.subscribe(this._channels.getKeys()); + }, this); + }, + _enqueue: function(message) { this._outbox.push(message); }, _flush: function() { @@ -701,14 +867,25 @@ if (!Faye.Channel.isValid(channel)) throw '"' + channel + '" is not a valid channel name'; if (!Faye.Channel.isSubscribable(channel)) throw 'Clients may not subscribe to channel "' + channel + '"'; }); + }, + + _verifyClientId: function(callback) { + var self = this; + return function(response) { + if (response.clientId !== self._clientId) return false; + callback.call(self, response); + return true; + }; } }); Faye.extend(Faye.Client.prototype, Faye.Deferrable); +Faye.extend(Faye.Client.prototype, Faye.Timeouts); +Faye.extend(Faye.Client.prototype, Faye.Logging); Faye.Set = Faye.Class({ initialize: function() { this._index = {}; @@ -755,10 +932,11 @@ }); Faye.Server = Faye.Class({ initialize: function(options) { + this.info('New server created'); this._options = options || {}; this._channels = new Faye.Channel.Tree(); this._clients = {}; this._namespace = new Faye.Namespace(); }, @@ -768,19 +946,22 @@ Faye.each(this._clients, function(key, value) { ids.push(key) }); return ids; }, process: function(messages, local, callback) { + this.debug('Processing messages from ' + (local ? 'LOCAL' : 'REMOTE') + ' client'); + messages = [].concat(messages); var processed = 0, responses = []; Faye.each(messages, function(message) { this._handle(message, local, function(reply) { responses = responses.concat(reply); processed += 1; - if (processed === messages.length) callback(responses); - }); + if (processed < messages.length) return; + callback(responses); + }, this); }, this); }, flushConnection: function(messages) { messages = [].concat(messages); @@ -801,57 +982,68 @@ client.disconnect(); client.stopObserving('staleClient', this._destroyClient, this); delete this._clients[client.id]; }, - _handle: function(message, local, callback) { - var clientId = message.clientId, - channel = message.channel, + _handle: function(message, local, callback, scope) { + var channel = message.channel, response; message.__id = Faye.random(); - Faye.each(this._channels.glob(channel), function(c) { c.push(message) }); + Faye.each(this._channels.glob(channel), function(c) { + c.push(message); + this.info('Publishing message ' + Faye.toJSON(message.data) + + ' from client ' + clientId + ' to ' + c.name); + }, this); if (Faye.Channel.isMeta(channel)) { response = this[Faye.Channel.parse(channel)[1]](message, local); - clientId = clientId || response.clientId; + var clientId = response.clientId; response.advice = response.advice || {}; Faye.extend(response.advice, { reconnect: this._clients.hasOwnProperty(clientId) ? 'retry' : 'handshake', - interval: Faye.Connection.INTERVAL * 1000 + interval: Math.floor(Faye.Connection.prototype.INTERVAL * 1000) }, false); - response.id = message.id; - if (response.channel !== Faye.Channel.CONNECT || response.successful !== true) - return callback(response); + return callback.call(scope, response); + this.info('Accepting connection from ' + response.clientId); return this._connection(response.clientId).connect(function(events) { + this.info('Sending event messages to ' + response.clientId); + this.debug('Events for ' + response.clientId + ': ' + Faye.toJSON(events)); Faye.each(events, function(e) { delete e.__id }); - callback([response].concat(events)); - }); + callback.call(scope, [response].concat(events)); + }, this); } if (!message.clientId || Faye.Channel.isService(channel)) return callback([]); - callback( { channel: channel, - successful: true, - id: message.id } ); + response = this._makeResponse(message); + response.successful = true; + callback(response); }, + _makeResponse: function(message) { + var response = {}; + Faye.each(['id', 'clientId', 'channel'], function(field) { + if (message[field]) response[field] = message[field]; + }); + return response; + }, + // MUST contain * version // * supportedConnectionTypes // MAY contain * minimumVersion // * ext // * id handshake: function(message, local) { - var response = { channel: Faye.Channel.HANDSHAKE, - version: Faye.BAYEUX_VERSION, - id: message.id }; + var response = this._makeResponse(message); + response.version = Faye.BAYEUX_VERSION; if (!message.version) response.error = Faye.Error.parameterMissing('version'); var clientConns = message.supportedConnectionTypes, @@ -874,66 +1066,66 @@ response.successful = !response.error; if (!response.successful) return response; var clientId = this._namespace.generate(); response.clientId = this._connection(clientId).id; + this.info('Accepting handshake from client ' + response.clientId); return response; }, // MUST contain * clientId // * connectionType // MAY contain * ext // * id connect: function(message, local) { - var response = { channel: Faye.Channel.CONNECT, - id: message.id }; + var response = this._makeResponse(message); var clientId = message.clientId, client = clientId ? this._clients[clientId] : null, connectionType = message.connectionType; if (!client) response.error = Faye.Error.clientUnknown(clientId); if (!clientId) response.error = Faye.Error.parameterMissing('clientId'); if (!connectionType) response.error = Faye.Error.parameterMissing('connectionType'); response.successful = !response.error; + if (!response.successful) delete response.clientId; if (!response.successful) return response; response.clientId = client.id; return response; }, // MUST contain * clientId // MAY contain * ext // * id disconnect: function(message, local) { - var response = { channel: Faye.Channel.DISCONNECT, - id: message.id }; + var response = this._makeResponse(message); var clientId = message.clientId, client = clientId ? this._clients[clientId] : null; if (!client) response.error = Faye.Error.clientUnknown(clientId); if (!clientId) response.error = Faye.Error.parameterMissing('clientId'); response.successful = !response.error; + if (!response.successful) delete response.clientId; if (!response.successful) return response; this._destroyClient(client); + this.info('Disconnected client: ' + clientId); response.clientId = clientId; return response; }, // MUST contain * clientId // * subscription // MAY contain * ext // * id subscribe: function(message, local) { - var response = { channel: Faye.Channel.SUBSCRIBE, - clientId: message.clientId, - id: message.id }; + var response = this._makeResponse(message); var clientId = message.clientId, client = clientId ? this._clients[clientId] : null, subscription = message.subscription; @@ -950,10 +1142,12 @@ if (!local && !Faye.Channel.isSubscribable(channel)) response.error = Faye.Error.channelForbidden(channel); if (!Faye.Channel.isValid(channel)) response.error = Faye.Error.channelInvalid(channel); if (response.error) return; channel = this._channels.findOrCreate(channel); + + this.info('Subscribing client ' + clientId + ' to ' + channel.name); client.subscribe(channel); }, this); response.successful = !response.error; return response; @@ -962,13 +1156,11 @@ // MUST contain * clientId // * subscription // MAY contain * ext // * id unsubscribe: function(message, local) { - var response = { channel: Faye.Channel.UNSUBSCRIBE, - clientId: message.clientId, - id: message.id }; + var response = this._makeResponse(message); var clientId = message.clientId, client = clientId ? this._clients[clientId] : null, subscription = message.subscription; @@ -983,35 +1175,38 @@ if (!Faye.Channel.isValid(channel)) return response.error = Faye.Error.channelInvalid(channel); channel = this._channels.get(channel); - if (channel) client.unsubscribe(channel); + if (!channel) return; + + this.info('Unsubscribing client ' + clientId + ' from ' + channel.name); + client.unsubscribe(channel); }, this); response.successful = !response.error; return response; } }); +Faye.extend(Faye.Server.prototype, Faye.Logging); + Faye.Connection = Faye.Class({ MAX_DELAY: 0.1, INTERVAL: 1.0, TIMEOUT: 60.0, initialize: function(id, options) { this.id = id; this._options = options; + this._timeout = this._options.timeout || this.TIMEOUT; this._channels = new Faye.Set(); this._inbox = new Faye.Set(); + this._connected = false }, - getTimeout: function() { - return this._options.timeout || this.TIMEOUT; - }, - _onMessage: function(event) { this._inbox.add(event); this._beginDeliveryTimeout(); }, @@ -1025,21 +1220,17 @@ if (!this._channels.member(channel)) return; this._channels.remove(channel); channel.stopObserving('message', this._onMessage, this); }, - connect: function(callback) { - this.callback(callback); + connect: function(callback, scope) { + this.callback(callback, scope); if (this._connected) return; this._connected = true; + this.removeTimeout('deletion'); - if (this._deletionTimeout) { - clearTimeout(this._deletionTimeout); - delete this._deletionTimeout; - } - this._beginDeliveryTimeout(); this._beginConnectionTimeout(); }, flush: function() { @@ -1057,54 +1248,33 @@ this.unsubscribe('all'); this.flush(); }, _beginDeliveryTimeout: function() { - if (this._deliveryTimeout || !this._connected || this._inbox.isEmpty()) - return; - - var self = this; - this._deliveryTimeout = setTimeout(function () { self.flush() }, - this.MAX_DELAY * 1000); + if (!this._connected || this._inbox.isEmpty()) return; + this.addTimeout('delivery', this.MAX_DELAY, this.flush, this); }, _beginConnectionTimeout: function() { - if (this._connectionTimeout || !this._connected) - return; - - var self = this; - this._connectionTimeout = setTimeout(function() { self.flush() }, - this.getTimeout() * 1000); + if (!this._connected) return; + this.addTimeout('connection', this._timeout, this.flush, this); }, _releaseConnection: function() { - if (this._connectionTimeout) { - clearTimeout(this._connectionTimeout); - delete this._connectionTimeout; - } - - if (this._deliveryTimeout) { - clearTimeout(this._deliveryTimeout); - delete this._deliveryTimeout; - } - + this.removeTimeout('connection'); + this.removeTimeout('delivery'); this._connected = false; - this._scheduleForDeletion(); - }, - - _scheduleForDeletion: function() { - if (this._deletionTimeout) return; - var self = this; - this._deletionTimeout = setTimeout(function() { - self.fire('staleClient', self); - }, 10 * 1000 * this.INTERVAL); + this.addTimeout('deletion', 10 * this.INTERVAL, function() { + this.fire('staleClient', this); + }, this); } }); Faye.extend(Faye.Connection.prototype, Faye.Deferrable); Faye.extend(Faye.Connection.prototype, Faye.Observable); +Faye.extend(Faye.Connection.prototype, Faye.Timeouts); Faye.Error = Faye.Class({ initialize: function(code, args, message) { this.code = code; @@ -1168,77 +1338,42 @@ return new this(500, arguments, "Internal server error").toString(); }; -Faye.NodeHttpTransport = Faye.Class(Faye.Transport, { - request: function(message, callback, scope) { - var params = {message: JSON.stringify(message)}, - request = this.createRequest(); - - request.write(querystring.stringify(params)); - - request.addListener('response', function(response) { - if (!callback) return; - response.addListener('data', function(chunk) { - callback.call(scope, JSON.parse(chunk)); - }); - }); - request.close(); - }, - - createRequest: function() { - var uri = url.parse(this._endpoint), - client = http.createClient(uri.port, uri.hostname); - - return client.request('POST', uri.pathname, { - 'Content-Type': 'application/x-www-form-urlencoded' - }); - } -}); - -Faye.NodeHttpTransport.isUsable = function(endpoint) { - return typeof endpoint === 'string'; -}; - -Faye.Transport.register('long-polling', Faye.NodeHttpTransport); - -Faye.NodeLocalTransport = Faye.Class(Faye.Transport, { - request: function(message, callback, scope) { - this._endpoint.process(message, true, function(response) { - callback.call(scope, response); - }); - } -}); - -Faye.NodeLocalTransport.isUsable = function(endpoint) { - return endpoint instanceof Faye.Server; -}; - -Faye.Transport.register('in-process', Faye.NodeLocalTransport); - - var path = require('path'), fs = require('fs'), sys = require('sys'), url = require('url'), http = require('http'), querystring = require('querystring'); +Faye.logger = function(message) { + sys.puts(message); +}; + +Faye.withDataFor = function(transport, callback, scope) { + var data = ''; + transport.addListener('data', function(chunk) { data += chunk }); + transport.addListener('end', function() { + callback.call(scope, data); + }); +}; + Faye.NodeAdapter = Faye.Class({ DEFAULT_ENDPOINT: '/bayeux', SCRIPT_PATH: path.dirname(__filename) + '/faye-client-min.js', - TYPE_JSON: {'Content-Type': 'text/json'}, + TYPE_JSON: {'Content-Type': 'application/json'}, TYPE_SCRIPT: {'Content-Type': 'text/javascript'}, TYPE_TEXT: {'Content-Type': 'text/plain'}, initialize: function(options) { - this._options = options || {}; - this._endpoint = this._options.mount || this.DEFAULT_ENDPOINT; - this._script = this._endpoint + '.js'; - this._server = new Faye.Server(this._options); + this._options = options || {}; + this._endpoint = this._options.mount || this.DEFAULT_ENDPOINT; + this._endpointRe = new RegExp('^' + this._endpoint + '(/[^/]+)*(\\.js)?$'); + this._server = new Faye.Server(this._options); }, getClient: function() { return this._client = this._client || new Faye.Client(this._server); }, @@ -1250,39 +1385,34 @@ }).listen(Number(port)); }, call: function(request, response) { var requestUrl = url.parse(request.url, true), - self = this; + self = this, data; - switch (requestUrl.pathname) { + if (!this._endpointRe.test(requestUrl.pathname)) + return false; + + if (/\.js$/.test(requestUrl.pathname)) { + fs.readFile(this.SCRIPT_PATH, function(err, content) { + response.sendHeader(200, self.TYPE_SCRIPT); + response.write(content); + response.close(); + }); - case this._endpoint: - var isGet = (request.method === 'GET'); - - if (isGet) - this._callWithParams(request, response, requestUrl.query); - - else - request.addListener('data', function(chunk) { - self._callWithParams(request, response, querystring.parse(chunk)); - }); - - return true; - break; + } else { + var isGet = (request.method === 'GET'); - case this._script: - fs.readFile(this.SCRIPT_PATH, function(err, content) { - response.sendHeader(200, self.TYPE_SCRIPT); - response.write(content); - response.close(); - }); - return true; - break; + if (isGet) + this._callWithParams(request, response, requestUrl.query); - default: return false; + else + Faye.withDataFor(request, function(data) { + self._callWithParams(request, response, {message: data}); + }); } + return true; }, _callWithParams: function(request, response, params) { try { var message = JSON.parse(params.message), @@ -1306,6 +1436,57 @@ } } }); exports.NodeAdapter = Faye.NodeAdapter; -exports.Client = Faye.Client; +exports.Client = Faye.Client; + + +Faye.NodeHttpTransport = Faye.Class(Faye.Transport, { + request: function(message, callback, scope) { + var request = this.createRequestForMessage(message); + + request.addListener('response', function(response) { + if (!callback) return; + Faye.withDataFor(response, function(data) { + callback.call(scope, JSON.parse(data)); + }); + }); + request.close(); + }, + + createRequestForMessage: function(message) { + var content = JSON.stringify(message), + uri = url.parse(this._endpoint), + client = http.createClient(uri.port, uri.hostname); + + if (parseInt(uri.port) === 443) client.setSecure('X509_PEM'); + + var request = client.request('POST', uri.pathname, { + 'Content-Type': 'application/json', + 'host': uri.hostname, + 'Content-Length': content.length + }); + request.write(content); + return request; + } +}); + +Faye.NodeHttpTransport.isUsable = function(endpoint) { + return typeof endpoint === 'string'; +}; + +Faye.Transport.register('long-polling', Faye.NodeHttpTransport); + +Faye.NodeLocalTransport = Faye.Class(Faye.Transport, { + request: function(message, callback, scope) { + this._endpoint.process(message, true, function(response) { + callback.call(scope, response); + }); + } +}); + +Faye.NodeLocalTransport.isUsable = function(endpoint) { + return endpoint instanceof Faye.Server; +}; + +Faye.Transport.register('in-process', Faye.NodeLocalTransport); \ No newline at end of file