UNPKG

@emartech/faye-redis-sharded

Version:

Redis backend engine for Faye with support for sharding

408 lines (327 loc) 14.3 kB
Faye.Client = Faye.Class({ UNCONNECTED: 1, CONNECTING: 2, CONNECTED: 3, DISCONNECTED: 4, HANDSHAKE: 'handshake', RETRY: 'retry', NONE: 'none', CONNECTION_TIMEOUT: 60, DEFAULT_RETRY: 5, MAX_REQUEST_SIZE: 2048, DEFAULT_ENDPOINT: '/bayeux', INTERVAL: 0, initialize: function(endpoint, options) { this.info('New client created for ?', endpoint); this._options = options || {}; this.endpoint = Faye.URI.parse(endpoint || this.DEFAULT_ENDPOINT); this.endpoints = this._options.endpoints || {}; this.transports = {}; this.cookies = Faye.Cookies && new Faye.Cookies.CookieJar(); this.headers = {}; this.ca = this._options.ca; this._disabled = []; this._retry = this._options.retry || this.DEFAULT_RETRY; for (var key in this.endpoints) this.endpoints[key] = Faye.URI.parse(this.endpoints[key]); this.maxRequestSize = this.MAX_REQUEST_SIZE; this._state = this.UNCONNECTED; this._channels = new Faye.Channel.Set(); this._messageId = 0; this._responseCallbacks = {}; this._advice = { reconnect: this.RETRY, interval: 1000 * (this._options.interval || this.INTERVAL), timeout: 1000 * (this._options.timeout || this.CONNECTION_TIMEOUT) }; if (Faye.Event && Faye.ENV.onbeforeunload !== undefined) Faye.Event.on(Faye.ENV, 'beforeunload', function() { if (Faye.indexOf(this._disabled, 'autodisconnect') < 0) this.disconnect(); }, this); }, disable: function(feature) { this._disabled.push(feature); }, setHeader: function(name, value) { this.headers[name] = value; }, // Request // MUST include: * channel // * version // * supportedConnectionTypes // MAY include: * minimumVersion // * ext // * id // // Success Response Failed Response // MUST include: * channel MUST include: * channel // * version * successful // * supportedConnectionTypes * error // * clientId MAY include: * supportedConnectionTypes // * successful * advice // MAY include: * minimumVersion * version // * advice * minimumVersion // * ext * ext // * id * id // * authSuccessful handshake: function(callback, context) { if (this._advice.reconnect === this.NONE) return; if (this._state !== this.UNCONNECTED) return; this._state = this.CONNECTING; var self = this; this.info('Initiating handshake with ?', Faye.URI.stringify(this.endpoint)); this._selectTransport(Faye.MANDATORY_CONNECTION_TYPES); this._send({ channel: Faye.Channel.HANDSHAKE, version: Faye.BAYEUX_VERSION, supportedConnectionTypes: [this._transport.connectionType] }, function(response) { if (response.successful) { this._state = this.CONNECTED; this._clientId = response.clientId; this._selectTransport(response.supportedConnectionTypes); this.info('Handshake successful: ?', this._clientId); this.subscribe(this._channels.getKeys(), true); if (callback) Faye.Promise.defer(function() { callback.call(context) }); } else { this.info('Handshake unsuccessful'); Faye.ENV.setTimeout(function() { self.handshake(callback, context) }, this._retry * 1000); this._state = this.UNCONNECTED; } }, this); }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful // * connectionType * clientId // MAY include: * ext MAY include: * error // * id * advice // * ext // * id // * timestamp connect: function(callback, context) { if (this._advice.reconnect === this.NONE) return; if (this._state === this.DISCONNECTED) return; if (this._state === this.UNCONNECTED) return this.handshake(function() { this.connect(callback, context) }, this); this.callback(callback, context); if (this._state !== this.CONNECTED) return; this.info('Calling deferred actions for ?', this._clientId); this.setDeferredStatus('succeeded'); this.setDeferredStatus('unknown'); if (this._connectRequest) return; this._connectRequest = true; this.info('Initiating connection for ?', this._clientId); this._send({ channel: Faye.Channel.CONNECT, clientId: this._clientId, connectionType: this._transport.connectionType }, this._cycleConnection, this); }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful // MAY include: * ext * clientId // * id MAY include: * error // * ext // * id disconnect: function() { if (this._state !== this.CONNECTED) return; this._state = this.DISCONNECTED; this.info('Disconnecting ?', this._clientId); this._send({ channel: Faye.Channel.DISCONNECT, clientId: this._clientId }, function(response) { if (!response.successful) return; this._transport.close(); delete this._transport; }, this); this.info('Clearing channel listeners for ?', this._clientId); this._channels = new Faye.Channel.Set(); }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful // * subscription * clientId // MAY include: * ext * subscription // * id MAY include: * error // * advice // * ext // * id // * timestamp subscribe: function(channel, callback, context) { if (channel instanceof Array) return Faye.map(channel, function(c) { return this.subscribe(c, callback, context); }, this); var subscription = new Faye.Subscription(this, channel, callback, context), force = (callback === true), hasSubscribe = this._channels.hasSubscription(channel); if (hasSubscribe && !force) { this._channels.subscribe([channel], callback, context); subscription.setDeferredStatus('succeeded'); return subscription; } this.connect(function() { this.info('Client ? attempting to subscribe to ?', this._clientId, channel); if (!force) this._channels.subscribe([channel], callback, context); this._send({ channel: Faye.Channel.SUBSCRIBE, clientId: this._clientId, subscription: channel }, function(response) { if (!response.successful) { subscription.setDeferredStatus('failed', Faye.Error.parse(response.error)); return this._channels.unsubscribe(channel, callback, context); } var channels = [].concat(response.subscription); this.info('Subscription acknowledged for ? to ?', this._clientId, channels); subscription.setDeferredStatus('succeeded'); }, this); }, this); return subscription; }, // Request Response // MUST include: * channel MUST include: * channel // * clientId * successful // * subscription * clientId // MAY include: * ext * subscription // * id MAY include: * error // * advice // * ext // * id // * timestamp unsubscribe: function(channel, callback, context) { if (channel instanceof Array) return Faye.map(channel, function(c) { return this.unsubscribe(c, callback, context); }, this); var dead = this._channels.unsubscribe(channel, callback, context); if (!dead) return; this.connect(function() { this.info('Client ? attempting to unsubscribe from ?', this._clientId, channel); this._send({ channel: Faye.Channel.UNSUBSCRIBE, clientId: this._clientId, subscription: channel }, function(response) { if (!response.successful) return; var channels = [].concat(response.subscription); this.info('Unsubscription acknowledged for ? from ?', this._clientId, channels); }, this); }, this); }, // Request Response // MUST include: * channel MUST include: * channel // * data * successful // MAY include: * clientId MAY include: * id // * id * error // * ext * ext publish: function(channel, data) { var publication = new Faye.Publication(); this.connect(function() { this.info('Client ? queueing published message to ?: ?', this._clientId, channel, data); this._send({ channel: channel, data: data, clientId: this._clientId }, function(response) { if (response.successful) publication.setDeferredStatus('succeeded'); else publication.setDeferredStatus('failed', Faye.Error.parse(response.error)); }, this); }, this); return publication; }, receiveMessage: function(message) { var id = message.id, callback; if (message.successful !== undefined) { callback = this._responseCallbacks[id]; delete this._responseCallbacks[id]; } this.pipeThroughExtensions('incoming', message, null, function(message) { if (!message) return; if (message.advice) this._handleAdvice(message.advice); this._deliverMessage(message); if (callback) callback[0].call(callback[1], message); }, this); if (this._transportUp === true) return; this._transportUp = true; this.trigger('transport:up'); }, messageError: function(messages, immediate) { var retry = this._retry, self = this, id, message; for (var i = 0, n = messages.length; i < n; i++) { message = messages[i]; id = message.id; if (immediate) this._transportSend(message); else Faye.ENV.setTimeout(function() { self._transportSend(message) }, retry * 1000); } if (immediate || this._transportUp === false) return; this._transportUp = false; this.trigger('transport:down'); }, _selectTransport: function(transportTypes) { Faye.Transport.get(this, transportTypes, this._disabled, function(transport) { this.debug('Selected ? transport for ?', transport.connectionType, Faye.URI.stringify(transport.endpoint)); if (transport === this._transport) return; if (this._transport) this._transport.close(); this._transport = transport; }, this); }, _send: function(message, callback, context) { if (!this._transport) return; message.id = message.id || this._generateMessageId(); this.pipeThroughExtensions('outgoing', message, null, function(message) { if (!message) return; if (callback) this._responseCallbacks[message.id] = [callback, context]; this._transportSend(message); }, this); }, _transportSend: function(message) { if (!this._transport) return; var timeout = 1.2 * (this._advice.timeout || this._retry * 1000), envelope = new Faye.Envelope(message, timeout); envelope.errback(function(immediate) { this.messageError([message], immediate); }, this); this._transport.send(envelope); }, _generateMessageId: function() { this._messageId += 1; if (this._messageId >= Math.pow(2,32)) this._messageId = 0; return this._messageId.toString(36); }, _handleAdvice: function(advice) { Faye.extend(this._advice, advice); if (this._advice.reconnect === this.HANDSHAKE && this._state !== this.DISCONNECTED) { this._state = this.UNCONNECTED; this._clientId = null; this._cycleConnection(); } }, _deliverMessage: function(message) { if (!message.channel || message.data === undefined) return; this.info('Client ? calling listeners for ? with ?', this._clientId, message.channel, message.data); this._channels.distributeMessage(message); }, _cycleConnection: function() { if (this._connectRequest) { this._connectRequest = null; this.info('Closed connection for ?', this._clientId); } var self = this; Faye.ENV.setTimeout(function() { self.connect() }, this._advice.interval); } }); Faye.extend(Faye.Client.prototype, Faye.Deferrable); Faye.extend(Faye.Client.prototype, Faye.Publisher); Faye.extend(Faye.Client.prototype, Faye.Logging); Faye.extend(Faye.Client.prototype, Faye.Extensible);