UNPKG

faye

Version:

Simple pub/sub messaging for the web

162 lines (127 loc) 4.58 kB
'use strict'; var Class = require('../util/class'), Promise = require('../util/promise'), Set = require('../util/set'), URI = require('../util/uri'), browser = require('../util/browser'), copyObject = require('../util/copy_object'), assign = require('../util/assign'), toJSON = require('../util/to_json'), ws = require('../util/websocket'), Deferrable = require('../mixins/deferrable'), Transport = require('./transport'); var WebSocket = assign(Class(Transport, { UNCONNECTED: 1, CONNECTING: 2, CONNECTED: 3, batching: false, isUsable: function(callback, context) { this.callback(function() { callback.call(context, true) }); this.errback(function() { callback.call(context, false) }); this.connect(); }, request: function(messages) { this._pending = this._pending || new Set(); for (var i = 0, n = messages.length; i < n; i++) this._pending.add(messages[i]); var self = this; var promise = new Promise(function(resolve, reject) { self.callback(function(socket) { if (!socket || socket.readyState !== 1) return; socket.send(toJSON(messages)); resolve(socket); }); self.connect(); }); return { abort: function() { promise.then(function(ws) { ws.close() }) } }; }, connect: function() { if (WebSocket._unloaded) return; this._state = this._state || this.UNCONNECTED; if (this._state !== this.UNCONNECTED) return; this._state = this.CONNECTING; var socket = this._createSocket(); if (!socket) return this.setDeferredStatus('failed'); var self = this; socket.onopen = function() { if (socket.headers) self._storeCookies(socket.headers['set-cookie']); self._socket = socket; self._state = self.CONNECTED; self._everConnected = true; self.setDeferredStatus('succeeded', socket); }; var closed = false; socket.onclose = socket.onerror = function() { if (closed) return; closed = true; var wasConnected = (self._state === self.CONNECTED); socket.onopen = socket.onclose = socket.onerror = socket.onmessage = null; delete self._socket; self._state = self.UNCONNECTED; var pending = self._pending ? self._pending.toArray() : []; delete self._pending; if (wasConnected || self._everConnected) { self.setDeferredStatus('unknown'); self._handleError(pending, wasConnected); } else { self.setDeferredStatus('failed'); } }; socket.onmessage = function(event) { var replies; try { replies = JSON.parse(event.data) } catch (error) {} if (!replies) return; replies = [].concat(replies); for (var i = 0, n = replies.length; i < n; i++) { if (replies[i].successful === undefined) continue; self._pending.remove(replies[i]); } self._receive(replies); }; }, close: function() { if (!this._socket) return; this._socket.close(); }, _createSocket: function() { var url = WebSocket.getSocketUrl(this.endpoint), headers = this._dispatcher.headers, extensions = this._dispatcher.wsExtensions, cookie = this._getCookies(), tls = this._dispatcher.tls, options = { extensions: extensions, headers: headers, proxy: this._proxy, tls: tls }; if (cookie !== '') options.headers['Cookie'] = cookie; try { return ws.create(url, [], options); } catch (e) { // catch CSP error to allow transport to fallback to next connType } } }), { PROTOCOLS: { 'http:': 'ws:', 'https:': 'wss:' }, create: function(dispatcher, endpoint) { var sockets = dispatcher.transports.websocket = dispatcher.transports.websocket || {}; sockets[endpoint.href] = sockets[endpoint.href] || new this(dispatcher, endpoint); return sockets[endpoint.href]; }, getSocketUrl: function(endpoint) { endpoint = copyObject(endpoint); endpoint.protocol = this.PROTOCOLS[endpoint.protocol]; return URI.stringify(endpoint); }, isUsable: function(dispatcher, endpoint, callback, context) { this.create(dispatcher, endpoint).isUsable(callback, context); } }); assign(WebSocket.prototype, Deferrable); if (browser.Event && global.onbeforeunload !== undefined) { browser.Event.on(global, 'beforeunload', function() { if (WebSocket._unloaded === undefined) WebSocket._unloaded = true; }); } module.exports = WebSocket;