You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
3.7 KiB
133 lines
3.7 KiB
'use strict'; |
|
|
|
var Stream = require('stream').Stream, |
|
util = require('util'), |
|
driver = require('websocket-driver'), |
|
Headers = require('websocket-driver/lib/websocket/driver/headers'), |
|
API = require('./websocket/api'), |
|
EventTarget = require('./websocket/api/event_target'), |
|
Event = require('./websocket/api/event'); |
|
|
|
var EventSource = function(request, response, options) { |
|
this.writable = true; |
|
options = options || {}; |
|
|
|
this._stream = response.socket; |
|
this._ping = options.ping || this.DEFAULT_PING; |
|
this._retry = options.retry || this.DEFAULT_RETRY; |
|
|
|
var scheme = driver.isSecureRequest(request) ? 'https:' : 'http:'; |
|
this.url = scheme + '//' + request.headers.host + request.url; |
|
this.lastEventId = request.headers['last-event-id'] || ''; |
|
this.readyState = API.CONNECTING; |
|
|
|
var headers = new Headers(), |
|
self = this; |
|
|
|
if (options.headers) { |
|
for (var key in options.headers) headers.set(key, options.headers[key]); |
|
} |
|
|
|
if (!this._stream || !this._stream.writable) return; |
|
process.nextTick(function() { self._open() }); |
|
|
|
this._stream.setTimeout(0); |
|
this._stream.setNoDelay(true); |
|
|
|
var handshake = 'HTTP/1.1 200 OK\r\n' + |
|
'Content-Type: text/event-stream\r\n' + |
|
'Cache-Control: no-cache, no-store\r\n' + |
|
'Connection: close\r\n' + |
|
headers.toString() + |
|
'\r\n' + |
|
'retry: ' + Math.floor(this._retry * 1000) + '\r\n\r\n'; |
|
|
|
this._write(handshake); |
|
|
|
this._stream.on('drain', function() { self.emit('drain') }); |
|
|
|
if (this._ping) |
|
this._pingTimer = setInterval(function() { self.ping() }, this._ping * 1000); |
|
|
|
['error', 'end'].forEach(function(event) { |
|
self._stream.on(event, function() { self.close() }); |
|
}); |
|
}; |
|
util.inherits(EventSource, Stream); |
|
|
|
EventSource.isEventSource = function(request) { |
|
if (request.method !== 'GET') return false; |
|
var accept = (request.headers.accept || '').split(/\s*,\s*/); |
|
return accept.indexOf('text/event-stream') >= 0; |
|
}; |
|
|
|
var instance = { |
|
DEFAULT_PING: 10, |
|
DEFAULT_RETRY: 5, |
|
|
|
_write: function(chunk) { |
|
if (!this.writable) return false; |
|
try { |
|
return this._stream.write(chunk, 'utf8'); |
|
} catch (e) { |
|
return false; |
|
} |
|
}, |
|
|
|
_open: function() { |
|
if (this.readyState !== API.CONNECTING) return; |
|
|
|
this.readyState = API.OPEN; |
|
|
|
var event = new Event('open'); |
|
event.initEvent('open', false, false); |
|
this.dispatchEvent(event); |
|
}, |
|
|
|
write: function(message) { |
|
return this.send(message); |
|
}, |
|
|
|
end: function(message) { |
|
if (message !== undefined) this.write(message); |
|
this.close(); |
|
}, |
|
|
|
send: function(message, options) { |
|
if (this.readyState > API.OPEN) return false; |
|
|
|
message = String(message).replace(/(\r\n|\r|\n)/g, '$1data: '); |
|
options = options || {}; |
|
|
|
var frame = ''; |
|
if (options.event) frame += 'event: ' + options.event + '\r\n'; |
|
if (options.id) frame += 'id: ' + options.id + '\r\n'; |
|
frame += 'data: ' + message + '\r\n\r\n'; |
|
|
|
return this._write(frame); |
|
}, |
|
|
|
ping: function() { |
|
return this._write(':\r\n\r\n'); |
|
}, |
|
|
|
close: function() { |
|
if (this.readyState > API.OPEN) return false; |
|
|
|
this.readyState = API.CLOSED; |
|
this.writable = false; |
|
if (this._pingTimer) clearInterval(this._pingTimer); |
|
if (this._stream) this._stream.end(); |
|
|
|
var event = new Event('close'); |
|
event.initEvent('close', false, false); |
|
this.dispatchEvent(event); |
|
|
|
return true; |
|
} |
|
}; |
|
|
|
for (var method in instance) EventSource.prototype[method] = instance[method]; |
|
for (var key in EventTarget) EventSource.prototype[key] = EventTarget[key]; |
|
|
|
module.exports = EventSource;
|
|
|