[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | var Stream = require('stream').Stream,
|
---|
| 4 | util = require('util'),
|
---|
| 5 | driver = require('websocket-driver'),
|
---|
| 6 | Headers = require('websocket-driver/lib/websocket/driver/headers'),
|
---|
| 7 | API = require('./websocket/api'),
|
---|
| 8 | EventTarget = require('./websocket/api/event_target'),
|
---|
| 9 | Event = require('./websocket/api/event');
|
---|
| 10 |
|
---|
| 11 | var EventSource = function(request, response, options) {
|
---|
| 12 | this.writable = true;
|
---|
| 13 | options = options || {};
|
---|
| 14 |
|
---|
| 15 | this._stream = response.socket;
|
---|
| 16 | this._ping = options.ping || this.DEFAULT_PING;
|
---|
| 17 | this._retry = options.retry || this.DEFAULT_RETRY;
|
---|
| 18 |
|
---|
| 19 | var scheme = driver.isSecureRequest(request) ? 'https:' : 'http:';
|
---|
| 20 | this.url = scheme + '//' + request.headers.host + request.url;
|
---|
| 21 | this.lastEventId = request.headers['last-event-id'] || '';
|
---|
| 22 | this.readyState = API.CONNECTING;
|
---|
| 23 |
|
---|
| 24 | var headers = new Headers(),
|
---|
| 25 | self = this;
|
---|
| 26 |
|
---|
| 27 | if (options.headers) {
|
---|
| 28 | for (var key in options.headers) headers.set(key, options.headers[key]);
|
---|
| 29 | }
|
---|
| 30 |
|
---|
| 31 | if (!this._stream || !this._stream.writable) return;
|
---|
| 32 | process.nextTick(function() { self._open() });
|
---|
| 33 |
|
---|
| 34 | this._stream.setTimeout(0);
|
---|
| 35 | this._stream.setNoDelay(true);
|
---|
| 36 |
|
---|
| 37 | var handshake = 'HTTP/1.1 200 OK\r\n' +
|
---|
| 38 | 'Content-Type: text/event-stream\r\n' +
|
---|
| 39 | 'Cache-Control: no-cache, no-store\r\n' +
|
---|
| 40 | 'Connection: close\r\n' +
|
---|
| 41 | headers.toString() +
|
---|
| 42 | '\r\n' +
|
---|
| 43 | 'retry: ' + Math.floor(this._retry * 1000) + '\r\n\r\n';
|
---|
| 44 |
|
---|
| 45 | this._write(handshake);
|
---|
| 46 |
|
---|
| 47 | this._stream.on('drain', function() { self.emit('drain') });
|
---|
| 48 |
|
---|
| 49 | if (this._ping)
|
---|
| 50 | this._pingTimer = setInterval(function() { self.ping() }, this._ping * 1000);
|
---|
| 51 |
|
---|
| 52 | ['error', 'end'].forEach(function(event) {
|
---|
| 53 | self._stream.on(event, function() { self.close() });
|
---|
| 54 | });
|
---|
| 55 | };
|
---|
| 56 | util.inherits(EventSource, Stream);
|
---|
| 57 |
|
---|
| 58 | EventSource.isEventSource = function(request) {
|
---|
| 59 | if (request.method !== 'GET') return false;
|
---|
| 60 | var accept = (request.headers.accept || '').split(/\s*,\s*/);
|
---|
| 61 | return accept.indexOf('text/event-stream') >= 0;
|
---|
| 62 | };
|
---|
| 63 |
|
---|
| 64 | var instance = {
|
---|
| 65 | DEFAULT_PING: 10,
|
---|
| 66 | DEFAULT_RETRY: 5,
|
---|
| 67 |
|
---|
| 68 | _write: function(chunk) {
|
---|
| 69 | if (!this.writable) return false;
|
---|
| 70 | try {
|
---|
| 71 | return this._stream.write(chunk, 'utf8');
|
---|
| 72 | } catch (e) {
|
---|
| 73 | return false;
|
---|
| 74 | }
|
---|
| 75 | },
|
---|
| 76 |
|
---|
| 77 | _open: function() {
|
---|
| 78 | if (this.readyState !== API.CONNECTING) return;
|
---|
| 79 |
|
---|
| 80 | this.readyState = API.OPEN;
|
---|
| 81 |
|
---|
| 82 | var event = new Event('open');
|
---|
| 83 | event.initEvent('open', false, false);
|
---|
| 84 | this.dispatchEvent(event);
|
---|
| 85 | },
|
---|
| 86 |
|
---|
| 87 | write: function(message) {
|
---|
| 88 | return this.send(message);
|
---|
| 89 | },
|
---|
| 90 |
|
---|
| 91 | end: function(message) {
|
---|
| 92 | if (message !== undefined) this.write(message);
|
---|
| 93 | this.close();
|
---|
| 94 | },
|
---|
| 95 |
|
---|
| 96 | send: function(message, options) {
|
---|
| 97 | if (this.readyState > API.OPEN) return false;
|
---|
| 98 |
|
---|
| 99 | message = String(message).replace(/(\r\n|\r|\n)/g, '$1data: ');
|
---|
| 100 | options = options || {};
|
---|
| 101 |
|
---|
| 102 | var frame = '';
|
---|
| 103 | if (options.event) frame += 'event: ' + options.event + '\r\n';
|
---|
| 104 | if (options.id) frame += 'id: ' + options.id + '\r\n';
|
---|
| 105 | frame += 'data: ' + message + '\r\n\r\n';
|
---|
| 106 |
|
---|
| 107 | return this._write(frame);
|
---|
| 108 | },
|
---|
| 109 |
|
---|
| 110 | ping: function() {
|
---|
| 111 | return this._write(':\r\n\r\n');
|
---|
| 112 | },
|
---|
| 113 |
|
---|
| 114 | close: function() {
|
---|
| 115 | if (this.readyState > API.OPEN) return false;
|
---|
| 116 |
|
---|
| 117 | this.readyState = API.CLOSED;
|
---|
| 118 | this.writable = false;
|
---|
| 119 | if (this._pingTimer) clearInterval(this._pingTimer);
|
---|
| 120 | if (this._stream) this._stream.end();
|
---|
| 121 |
|
---|
| 122 | var event = new Event('close');
|
---|
| 123 | event.initEvent('close', false, false);
|
---|
| 124 | this.dispatchEvent(event);
|
---|
| 125 |
|
---|
| 126 | return true;
|
---|
| 127 | }
|
---|
| 128 | };
|
---|
| 129 |
|
---|
| 130 | for (var method in instance) EventSource.prototype[method] = instance[method];
|
---|
| 131 | for (var key in EventTarget) EventSource.prototype[key] = EventTarget[key];
|
---|
| 132 |
|
---|
| 133 | module.exports = EventSource;
|
---|