eventsource.js 3.7 KB

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