8889841ceventsource.js000066600000007300150443210300007440 0ustar00'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; websocket/api/event_target.js000066600000001177150443210300012332 0ustar00'use strict'; var Event = require('./event'); var EventTarget = { onopen: null, onmessage: null, onerror: null, onclose: null, addEventListener: function(eventType, listener, useCapture) { this.on(eventType, listener); }, removeEventListener: function(eventType, listener, useCapture) { this.removeListener(eventType, listener); }, dispatchEvent: function(event) { event.target = event.currentTarget = this; event.eventPhase = Event.AT_TARGET; if (this['on' + event.type]) this['on' + event.type](event); this.emit(event.type, event); } }; module.exports = EventTarget; websocket/api/event.js000066600000001010150443210300010746 0ustar00'use strict'; var Event = function(eventType, options) { this.type = eventType; for (var key in options) this[key] = options[key]; }; Event.prototype.initEvent = function(eventType, canBubble, cancelable) { this.type = eventType; this.bubbles = canBubble; this.cancelable = cancelable; }; Event.prototype.stopPropagation = function() {}; Event.prototype.preventDefault = function() {}; Event.CAPTURING_PHASE = 1; Event.AT_TARGET = 2; Event.BUBBLING_PHASE = 3; module.exports = Event; websocket/api.js000066600000012451150443210300007640 0ustar00'use strict'; var Stream = require('stream').Stream, util = require('util'), driver = require('websocket-driver'), EventTarget = require('./api/event_target'), Event = require('./api/event'); var API = function(options) { options = options || {}; driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']); this.readable = this.writable = true; var headers = options.headers; if (headers) { for (var name in headers) this._driver.setHeader(name, headers[name]); } var extensions = options.extensions; if (extensions) { [].concat(extensions).forEach(this._driver.addExtension, this._driver); } this._ping = options.ping; this._pingId = 0; this.readyState = API.CONNECTING; this.bufferedAmount = 0; this.protocol = ''; this.url = this._driver.url; this.version = this._driver.version; var self = this; this._driver.on('open', function(e) { self._open() }); this._driver.on('message', function(e) { self._receiveMessage(e.data) }); this._driver.on('close', function(e) { self._beginClose(e.reason, e.code) }); this._driver.on('error', function(error) { self._emitError(error.message); }); this.on('error', function() {}); this._driver.messages.on('drain', function() { self.emit('drain'); }); if (this._ping) this._pingTimer = setInterval(function() { self._pingId += 1; self.ping(self._pingId.toString()); }, this._ping * 1000); this._configureStream(); if (!this._proxy) { this._stream.pipe(this._driver.io); this._driver.io.pipe(this._stream); } }; util.inherits(API, Stream); API.CONNECTING = 0; API.OPEN = 1; API.CLOSING = 2; API.CLOSED = 3; API.CLOSE_TIMEOUT = 30000; var instance = { write: function(data) { return this.send(data); }, end: function(data) { if (data !== undefined) this.send(data); this.close(); }, pause: function() { return this._driver.messages.pause(); }, resume: function() { return this._driver.messages.resume(); }, send: function(data) { if (this.readyState > API.OPEN) return false; if (!(data instanceof Buffer)) data = String(data); return this._driver.messages.write(data); }, ping: function(message, callback) { if (this.readyState > API.OPEN) return false; return this._driver.ping(message, callback); }, close: function(code, reason) { if (code === undefined) code = 1000; if (reason === undefined) reason = ''; if (code !== 1000 && (code < 3000 || code > 4999)) throw new Error("Failed to execute 'close' on WebSocket: " + "The code must be either 1000, or between 3000 and 4999. " + code + " is neither."); if (this.readyState < API.CLOSING) { var self = this; this._closeTimer = setTimeout(function() { self._beginClose('', 1006); }, API.CLOSE_TIMEOUT); } if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING; this._driver.close(reason, code); }, _configureStream: function() { var self = this; this._stream.setTimeout(0); this._stream.setNoDelay(true); ['close', 'end'].forEach(function(event) { this._stream.on(event, function() { self._finalizeClose() }); }, this); this._stream.on('error', function(error) { self._emitError('Network error: ' + self.url + ': ' + error.message); self._finalizeClose(); }); }, _open: function() { if (this.readyState !== API.CONNECTING) return; this.readyState = API.OPEN; this.protocol = this._driver.protocol || ''; var event = new Event('open'); event.initEvent('open', false, false); this.dispatchEvent(event); }, _receiveMessage: function(data) { if (this.readyState > API.OPEN) return false; if (this.readable) this.emit('data', data); var event = new Event('message', { data: data }); event.initEvent('message', false, false); this.dispatchEvent(event); }, _emitError: function(message) { if (this.readyState >= API.CLOSING) return; var event = new Event('error', { message: message }); event.initEvent('error', false, false); this.dispatchEvent(event); }, _beginClose: function(reason, code) { if (this.readyState === API.CLOSED) return; this.readyState = API.CLOSING; this._closeParams = [reason, code]; if (this._stream) { this._stream.destroy(); if (!this._stream.readable) this._finalizeClose(); } }, _finalizeClose: function() { if (this.readyState === API.CLOSED) return; this.readyState = API.CLOSED; if (this._closeTimer) clearTimeout(this._closeTimer); if (this._pingTimer) clearInterval(this._pingTimer); if (this._stream) this._stream.end(); if (this.readable) this.emit('end'); this.readable = this.writable = false; var reason = this._closeParams ? this._closeParams[0] : '', code = this._closeParams ? this._closeParams[1] : 1006; var event = new Event('close', { code: code, reason: reason }); event.initEvent('close', false, false); this.dispatchEvent(event); } }; for (var method in instance) API.prototype[method] = instance[method]; for (var key in EventTarget) API.prototype[key] = EventTarget[key]; module.exports = API; websocket/client.js000066600000005263150443210300010350 0ustar00'use strict'; var util = require('util'), net = require('net'), tls = require('tls'), url = require('url'), driver = require('websocket-driver'), API = require('./api'), Event = require('./api/event'); var DEFAULT_PORTS = { 'http:': 80, 'https:': 443, 'ws:':80, 'wss:': 443 }, SECURE_PROTOCOLS = ['https:', 'wss:']; var Client = function(_url, protocols, options) { options = options || {}; this.url = _url; this._driver = driver.client(this.url, { maxLength: options.maxLength, protocols: protocols }); ['open', 'error'].forEach(function(event) { this._driver.on(event, function() { self.headers = self._driver.headers; self.statusCode = self._driver.statusCode; }); }, this); var proxy = options.proxy || {}, endpoint = url.parse(proxy.origin || this.url), port = endpoint.port || DEFAULT_PORTS[endpoint.protocol], secure = SECURE_PROTOCOLS.indexOf(endpoint.protocol) >= 0, onConnect = function() { self._onConnect() }, netOptions = options.net || {}, originTLS = options.tls || {}, socketTLS = proxy.origin ? (proxy.tls || {}) : originTLS, self = this; netOptions.host = socketTLS.host = endpoint.hostname; netOptions.port = socketTLS.port = port; originTLS.ca = originTLS.ca || options.ca; socketTLS.servername = socketTLS.servername || endpoint.hostname; this._stream = secure ? tls.connect(socketTLS, onConnect) : net.connect(netOptions, onConnect); if (proxy.origin) this._configureProxy(proxy, originTLS); API.call(this, options); }; util.inherits(Client, API); Client.prototype._onConnect = function() { var worker = this._proxy || this._driver; worker.start(); }; Client.prototype._configureProxy = function(proxy, originTLS) { var uri = url.parse(this.url), secure = SECURE_PROTOCOLS.indexOf(uri.protocol) >= 0, self = this, name; this._proxy = this._driver.proxy(proxy.origin); if (proxy.headers) { for (name in proxy.headers) this._proxy.setHeader(name, proxy.headers[name]); } this._proxy.pipe(this._stream, { end: false }); this._stream.pipe(this._proxy); this._proxy.on('connect', function() { if (secure) { var options = { socket: self._stream, servername: uri.hostname }; for (name in originTLS) options[name] = originTLS[name]; self._stream = tls.connect(options); self._configureStream(); } self._driver.io.pipe(self._stream); self._stream.pipe(self._driver.io); self._driver.start(); }); this._proxy.on('error', function(error) { self._driver.emit('error', error); }); }; module.exports = Client; websocket.js000066600000002445150443210300007071 0ustar00// API references: // // * https://html.spec.whatwg.org/multipage/comms.html#network // * https://dom.spec.whatwg.org/#interface-eventtarget // * https://dom.spec.whatwg.org/#interface-event 'use strict'; var util = require('util'), driver = require('websocket-driver'), API = require('./websocket/api'); var WebSocket = function(request, socket, body, protocols, options) { options = options || {}; this._stream = socket; this._driver = driver.http(request, { maxLength: options.maxLength, protocols: protocols }); var self = this; if (!this._stream || !this._stream.writable) return; if (!this._stream.readable) return this._stream.end(); var catchup = function() { self._stream.removeListener('data', catchup) }; this._stream.on('data', catchup); API.call(this, options); process.nextTick(function() { self._driver.start(); self._driver.io.write(body); }); }; util.inherits(WebSocket, API); WebSocket.isWebSocket = function(request) { return driver.isWebSocket(request); }; WebSocket.validateOptions = function(options, validKeys) { driver.validateOptions(options, validKeys); }; WebSocket.WebSocket = WebSocket; WebSocket.Client = require('./websocket/client'); WebSocket.EventSource = require('./eventsource'); module.exports = WebSocket;