├── .gitignore ├── .babelrc ├── index.js ├── src └── actioncable │ ├── Logger.js │ ├── Cable.js │ └── cable │ ├── Consumer.js │ ├── Subscription.js │ ├── ConnectionMonitor.js │ ├── Subscriptions.js │ └── Connection.js ├── dist └── actioncable │ ├── Logger.js │ ├── Cable.js │ └── cable │ ├── Subscription.js │ ├── Connection.js │ ├── ConnectionMonitor.js │ ├── Consumer.js │ └── Subscriptions.js ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { "presets": ["es2015"] } 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/actioncable/Cable'); 2 | -------------------------------------------------------------------------------- /src/actioncable/Logger.js: -------------------------------------------------------------------------------- 1 | let Debugging = null; 2 | 3 | export default { 4 | startDebugging: () => { 5 | Debugging = true; 6 | }, 7 | stopDebugging: () => { 8 | Debugging = null; 9 | }, 10 | log: (...messages) => { 11 | if (Debugging) { 12 | messages.push(Date.now()); 13 | return console.log('[ActionCable]', ...messages); // eslint-disable-line no-console 14 | } 15 | return true; 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /dist/actioncable/Logger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var Debugging = null; 7 | 8 | exports.default = { 9 | startDebugging: function startDebugging() { 10 | return Debugging = true; 11 | }, 12 | stopDebugging: function stopDebugging() { 13 | return Debugging = null; 14 | }, 15 | log: function log() { 16 | for (var _len = arguments.length, messages = Array(_len), _key = 0; _key < _len; _key++) { 17 | messages[_key] = arguments[_key]; 18 | } 19 | 20 | if (Debugging) { 21 | var _console; 22 | 23 | messages.push(Date.now()); 24 | return (_console = console).log.apply(_console, ["[ActionCable]"].concat(messages)); 25 | } 26 | } 27 | }; -------------------------------------------------------------------------------- /src/actioncable/Cable.js: -------------------------------------------------------------------------------- 1 | import Consumer from './Cable/Consumer'; 2 | 3 | const CreateWebSocketURL = (url) => { 4 | if (url && !/^wss?:/i.test(url)) { 5 | const a = document.createElement('a'); 6 | a.href = url; 7 | // Fix populating Location properties in IE. Otherwise, protocol will be blank. 8 | a.href = a.href; 9 | a.protocol = a.protocol.replace('http', 'ws'); 10 | return a.href; 11 | } 12 | return url; 13 | }; 14 | 15 | export default { 16 | createConsumer: (url, options) => ( 17 | new Consumer(CreateWebSocketURL(url), options) 18 | ), 19 | endConsumer: (consumer) => { 20 | consumer.connection.close(); 21 | consumer.connection.disconnect(); 22 | consumer.connectionMonitor.stop(); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "es6-actioncable", 3 | "version": "0.5.6", 4 | "description": "Port of rails actioncable coffeescript to es6", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "babel src -d dist" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/eacaps/es6-actioncable.git" 12 | }, 13 | "keywords": [ 14 | "actioncable", 15 | "rails", 16 | "websocket" 17 | ], 18 | "author": "Eric Capito (https://github.com/eacaps)", 19 | "license": "Unlicense", 20 | "bugs": { 21 | "url": "https://github.com/eacaps/es6-actioncable/issues" 22 | }, 23 | "homepage": "https://github.com/eacaps/es6-actioncable", 24 | "devDependencies": {}, 25 | "dependencies": { 26 | "babel-cli": "^6.8.0", 27 | "babel-preset-es2015": "^6.6.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dist/actioncable/Cable.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _Consumer = require("./cable/Consumer"); 8 | 9 | var _Consumer2 = _interopRequireDefault(_Consumer); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | var CreateWebSocketURL = function CreateWebSocketURL(url) { 14 | if (url && !/^wss?:/i.test(url)) { 15 | var a = document.createElement("a"); 16 | a.href = url; 17 | // Fix populating Location properties in IE. Otherwise, protocol will be blank. 18 | a.href = a.href; 19 | a.protocol = a.protocol.replace("http", "ws"); 20 | return a.href; 21 | } else { 22 | return url; 23 | } 24 | }; 25 | 26 | exports.default = { 27 | createConsumer: function createConsumer(url, options) { 28 | return new _Consumer2.default(CreateWebSocketURL(url), options); 29 | }, 30 | endConsumer: function endConsumer(consumer) { 31 | consumer.connection.close(); 32 | consumer.connection.disconnect(); 33 | consumer.connectionMonitor.stop(); 34 | } 35 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /src/actioncable/cable/Consumer.js: -------------------------------------------------------------------------------- 1 | /* 2 | # The Cable.Consumer establishes the connection to a server-side Ruby 3 | Connection object. Once established, 4 | # the Cable.ConnectionMonitor will ensure that its properly maintained through 5 | heartbeats and checking for stale updates. 6 | # The Consumer instance is also the gateway to establishing subscriptions to 7 | desired channels through the #createSubscription 8 | # method. 9 | # 10 | # The following example shows how this can be setup: 11 | # 12 | # @App = {} 13 | # App.cable = Cable.createConsumer "ws://example.com/accounts/1" 14 | # App.appearance = App.cable.subscriptions.create "AppearanceChannel" 15 | # 16 | # For more details on how you'd configure an actual channel subscription, see Cable.Subscription. 17 | */ 18 | import Subscriptions from './Subscriptions'; 19 | import Connection from './Connection'; 20 | import ConnectionMonitor from './ConnectionMonitor'; 21 | 22 | class Consumer { 23 | constructor(url, options) { 24 | this.options = options || {}; 25 | this.url = url; 26 | 27 | this.subscriptions = new Subscriptions(this); 28 | this.connection = new Connection(this); 29 | this.connectionMonitor = new ConnectionMonitor(this); 30 | } 31 | send(data) { 32 | return this.connection.send(data); 33 | } 34 | toJSON() { 35 | return { 36 | url: this.url, 37 | subscriptions: this.subscriptions, 38 | connection: this.connection, 39 | connectionMonitor: this.connectionMonitor, 40 | }; 41 | } 42 | } 43 | 44 | export default Consumer; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # es6-actioncable 2 | This module is a port of the rails/actioncable coffeescript code to ES6 and nodized it. For more info on actioncable, check out their github page - https://github.com/rails/actioncable. 3 | 4 | ## Usage 5 | Here is a sample of what I have in my application. 6 | 7 | Websocket.js 8 | 9 | import Cable from 'es6-actioncable'; 10 | 11 | class Websocket { 12 | constructor() { 13 | } 14 | 15 | connect() { 16 | console.log('connecting websocket'); 17 | this.consumer = Cable.createConsumer(WEBSOCKET_URL); 18 | } 19 | 20 | getConsumer() { 21 | if(!this.consumer) { 22 | this.connect(); 23 | } 24 | return this.consumer; 25 | } 26 | 27 | closeConnection() { 28 | if(this.consumer) { 29 | Cable.endConsumer(this.consumer); 30 | } 31 | delete this.consumer; 32 | } 33 | } 34 | 35 | MyChannel.js 36 | 37 | import WebSocket from './websocket'; 38 | 39 | class MyChannel { 40 | constructor() { 41 | } 42 | subscribe() { 43 | this.subscription = WebSocket.getConsumer().subscriptions.create("MyChannel", { 44 | connected: function () { 45 | console.log('connected to mychannel'); 46 | }, 47 | received: function (data) { 48 | //do stuff with data 49 | } 50 | }); 51 | } 52 | unsubscribe() { 53 | if(this.subscription) 54 | this.subscription.unsubscribe(); 55 | } 56 | } 57 | 58 | Actioncable is good stuff, even if it is in Ruby. 59 | 60 | ## Connecting from Node.js 61 | 62 | `es6-actioncable` will work under Node.js, however you will need to bear the following in mind: 63 | 64 | * You will need to supply your own websocket library, 2 out of 2 developers recommend: https://www.npmjs.com/package/websocket. 65 | * Your ActionCable Rails server must be bound to a specific IP or `0.0.0.0`, but not localhost. This can be done as follows `rails server -b 0.0.0.0`. See https://twitter.com/mattheworiordan/status/713350750483693568 for an explanation of the issue. 66 | * You will need to pass the origin to the WebSocket library as Rails will by default reject requests with an invalid origin. See example below: 67 | 68 | ```javascript 69 | const consumer = Cable.createConsumer('ws://0.0.0.0:3000/cable', { createWebsocket: (options) => { 70 | var w3cwebsocket = require('websocket').w3cwebsocket; 71 | let webSocket = new w3cwebsocket( 72 | 'ws://0.0.0.0:3000/cable', 73 | options.protocols, 74 | 'http://0.0.0.0:3000', 75 | options.headers, 76 | options.extraRequestOptions 77 | ); 78 | return webSocket; 79 | } }); 80 | ``` 81 | -------------------------------------------------------------------------------- /src/actioncable/cable/Subscription.js: -------------------------------------------------------------------------------- 1 | /* 2 | # A new subscription is created through the Cable.Subscriptions instance available 3 | on the consumer. 4 | # It provides a number of callbacks and a method for calling remote procedure calls 5 | on the corresponding 6 | # Channel instance on the server side. 7 | # 8 | # An example demonstrates the basic functionality: 9 | # 10 | # App.appearance = App.cable.subscriptions.create "AppearanceChannel", 11 | # connected: -> 12 | # # Called once the subscription has been successfully completed 13 | # 14 | # appear: -> 15 | # @perform 'appear', appearing_on: @appearingOn() 16 | # 17 | # away: -> 18 | # @perform 'away' 19 | # 20 | # appearingOn: -> 21 | # $('main').data 'appearing-on' 22 | # 23 | # The methods #appear and #away forward their intent to the remote AppearanceChannel 24 | instance on the server 25 | # by calling the `@perform` method with the first parameter being the action 26 | (which maps to AppearanceChannel#appear/away). 27 | # The second parameter is a hash that'll get JSON encoded and made available on 28 | the server in the data parameter. 29 | # 30 | # This is how the server component would look: 31 | # 32 | # class AppearanceChannel < ApplicationCable::Channel 33 | # def subscribed 34 | # current_user.appear 35 | # end 36 | # 37 | # def unsubscribed 38 | # current_user.disappear 39 | # end 40 | # 41 | # def appear(data) 42 | # current_user.appear on: data['appearing_on'] 43 | # end 44 | # 45 | # def away 46 | # current_user.away 47 | # end 48 | # end 49 | # 50 | # The "AppearanceChannel" name is automatically mapped between the client-side 51 | subscription creation and the server-side Ruby class name. 52 | # The AppearanceChannel#appear/away public methods are exposed automatically to 53 | client-side invocation through the @perform method. 54 | */ 55 | 56 | const extend = (object, properties) => { 57 | let key; 58 | let value; 59 | if (properties != null) { 60 | /* eslint-disable */ 61 | for (key in properties) { 62 | if ({}.hasOwnProperty.call(properties, key)) { 63 | value = properties[key]; 64 | object[key] = value; 65 | } 66 | } 67 | /* eslint-enable */ 68 | } 69 | return object; 70 | }; 71 | 72 | class Subscription { 73 | constructor(subscriptions, params, mixin) { 74 | this.subscriptions = subscriptions; 75 | this.identifier = JSON.stringify(params || {}); 76 | extend(this, mixin); 77 | this.consumer = this.subscriptions.consumer; 78 | this.subscriptions.add(this); 79 | } 80 | 81 | perform(action, data) { 82 | let formattedData = Object.assign({}, data); 83 | if (formattedData === null) { 84 | formattedData = {}; 85 | } 86 | formattedData.action = action; 87 | return this.send(formattedData); 88 | } 89 | 90 | send(data) { 91 | return this.consumer.send({ 92 | command: 'message', 93 | identifier: this.identifier, 94 | data: JSON.stringify(data), 95 | }); 96 | } 97 | 98 | unsubscribe() { 99 | return this.subscriptions.remove(this); 100 | } 101 | } 102 | 103 | export default Subscription; 104 | -------------------------------------------------------------------------------- /dist/actioncable/cable/Subscription.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 10 | 11 | /* 12 | # A new subscription is created through the Cable.Subscriptions instance available on the consumer. 13 | # It provides a number of callbacks and a method for calling remote procedure calls on the corresponding 14 | # Channel instance on the server side. 15 | # 16 | # An example demonstrates the basic functionality: 17 | # 18 | # App.appearance = App.cable.subscriptions.create "AppearanceChannel", 19 | # connected: -> 20 | # # Called once the subscription has been successfully completed 21 | # 22 | # appear: -> 23 | # @perform 'appear', appearing_on: @appearingOn() 24 | # 25 | # away: -> 26 | # @perform 'away' 27 | # 28 | # appearingOn: -> 29 | # $('main').data 'appearing-on' 30 | # 31 | # The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server 32 | # by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). 33 | # The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. 34 | # 35 | # This is how the server component would look: 36 | # 37 | # class AppearanceChannel < ApplicationCable::Channel 38 | # def subscribed 39 | # current_user.appear 40 | # end 41 | # 42 | # def unsubscribed 43 | # current_user.disappear 44 | # end 45 | # 46 | # def appear(data) 47 | # current_user.appear on: data['appearing_on'] 48 | # end 49 | # 50 | # def away 51 | # current_user.away 52 | # end 53 | # end 54 | # 55 | # The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. 56 | # The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. 57 | */ 58 | 59 | var extend = function extend(object, properties) { 60 | var key, value; 61 | if (properties != null) { 62 | for (key in properties) { 63 | value = properties[key]; 64 | object[key] = value; 65 | } 66 | } 67 | return object; 68 | }; 69 | 70 | var Subscription = function () { 71 | function Subscription(subscriptions, params, mixin) { 72 | _classCallCheck(this, Subscription); 73 | 74 | this.subscriptions = subscriptions; 75 | if (params == null) { 76 | params = {}; 77 | } 78 | this.identifier = JSON.stringify(params); 79 | extend(this, mixin); 80 | this.consumer = this.subscriptions.consumer; 81 | this.subscriptions.add(this); 82 | } 83 | 84 | _createClass(Subscription, [{ 85 | key: "perform", 86 | value: function perform(action, data) { 87 | if (data == null) { 88 | data = {}; 89 | } 90 | data.action = action; 91 | return this.send(data); 92 | } 93 | }, { 94 | key: "send", 95 | value: function send(data) { 96 | return this.consumer.send({ 97 | command: "message", 98 | identifier: this.identifier, 99 | data: JSON.stringify(data) 100 | }); 101 | } 102 | }, { 103 | key: "unsubscribe", 104 | value: function unsubscribe() { 105 | return this.subscriptions.remove(this); 106 | } 107 | }]); 108 | 109 | return Subscription; 110 | }(); 111 | 112 | exports.default = Subscription; -------------------------------------------------------------------------------- /src/actioncable/cable/ConnectionMonitor.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Responsible for ensuring the cable connection is in good health by validating 3 | the heartbeat pings sent from the server, and attempting 4 | # revival reconnections if things go astray. Internal class, not intended for 5 | direct user manipulation. 6 | */ 7 | import ActionCable from '../Logger'; 8 | 9 | const now = () => ( 10 | new Date().getTime() 11 | ); 12 | 13 | const secondsSince = time => ( 14 | (now() - time) / 1000 15 | ); 16 | 17 | const clamp = (number, min, max) => ( 18 | Math.max(min, Math.min(max, number)) 19 | ); 20 | 21 | class ConnectionMonitor { 22 | constructor(consumer) { 23 | this.pollInterval = { 24 | min: 3, 25 | max: 30, 26 | }; 27 | this.staleThreshold = 6; 28 | this.consumer = consumer; 29 | this.visibilityDidChange = this.visibilityDidChange.bind(this); 30 | this.start(); 31 | } 32 | 33 | connected() { 34 | this.reset(); 35 | this.pingedAt = now(); 36 | delete this.disconnectedAt; 37 | return ActionCable.log('ConnectionMonitor connected'); 38 | } 39 | 40 | disconnected() { 41 | this.disconnectedAt = now(); 42 | return ActionCable.log('ConnectionMonitor disconnected'); 43 | } 44 | 45 | ping() { 46 | this.pingedAt = now(); 47 | return this.pinedAt; 48 | } 49 | 50 | reset() { 51 | this.reconnectAttempts = 0; 52 | return this.consumer.connection.isOpen(); 53 | } 54 | 55 | start() { 56 | this.reset(); 57 | delete this.stoppedAt; 58 | this.startedAt = now(); 59 | this.poll(); 60 | if (typeof document !== 'undefined') { 61 | document.addEventListener('visibilitychange', this.visibilityDidChange); 62 | } 63 | 64 | return ActionCable.log( 65 | `ConnectionMonitor started, pollInterval is ${this.getInterval()}ms`); 66 | } 67 | 68 | stop() { 69 | this.stoppedAt = now(); 70 | if (typeof document !== 'undefined') { 71 | document.removeEventListener('visibilitychange', 72 | this.visibilityDidChange); 73 | } 74 | 75 | return ActionCable.log('ConnectionMonitor stopped'); 76 | } 77 | 78 | poll() { 79 | return setTimeout((that => ( 80 | () => { 81 | if (!that.stoppedAt) { 82 | that.reconnectIfStale(); 83 | return that.poll(); 84 | } 85 | return true; 86 | } 87 | ))(this), this.getInterval()); 88 | } 89 | 90 | getInterval() { 91 | const ref = this.pollInterval; 92 | const min = ref.min; 93 | const max = ref.max; 94 | const interval = 5 * Math.log(this.reconnectAttempts + 1); 95 | return clamp(interval, min, max) * 1000; 96 | } 97 | 98 | reconnectIfStale() { 99 | if (this.connectionIsStale()) { 100 | ActionCable.log( 101 | `ConnectionMonitor detected stale connection, reconnectAttempts = ${this.reconnectAttempts}`); 102 | this.reconnectAttempts += 1; 103 | if (this.disconnectedRecently()) { 104 | return ActionCable.log( 105 | `ConnectionMonitor skipping reopen because recently disconnected at ${this.disconnectedAt}`); 106 | } 107 | ActionCable.log('ConnectionMonitor reopening'); 108 | return this.consumer.connection.reopen(); 109 | } 110 | return true; 111 | } 112 | 113 | connectionIsStale() { 114 | const pingedAt = this.pingedAt !== null ? this.pingedAt : this.startedAt; 115 | return pingedAt > this.stateThreshold; 116 | } 117 | 118 | disconnectedRecently() { 119 | return this.disconnectedAt && secondsSince(this.disconnectedAt) < 120 | this.staleThreshold; 121 | } 122 | 123 | visibilityDidChange() { 124 | if (document.visibilityState === 'visible') { 125 | return setTimeout((that => ( 126 | () => { 127 | if (that.connectionIsStale() || 128 | !that.consumer.connection.isOpen()) { 129 | ActionCable.log( 130 | `ConnectionMonitor reopening stale connection after visibilitychange to ${document.visibilityState}`); 131 | return that.consumer.connection.reopen(); 132 | } 133 | return true; 134 | } 135 | ))(this), 200); 136 | } 137 | return true; 138 | } 139 | 140 | toJSON() { 141 | const interval = this.getInterval(); 142 | const connectionIsStale = this.connectionIsStale(); 143 | return { 144 | startedAt: this.startedAt, 145 | stoppedAt: this.stoppedAt, 146 | pingedAt: this.pingedAt, 147 | reconnectAttempts: this.reconnectAttempts, 148 | connectionIsStale, 149 | interval, 150 | }; 151 | } 152 | } 153 | 154 | export default ConnectionMonitor; 155 | -------------------------------------------------------------------------------- /src/actioncable/cable/Subscriptions.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Collection class for creating (and internally managing) channel subscriptions. 3 | The only method intended to be triggered by the user 4 | # us Cable.Subscriptions#create, and it should be called through the consumer like so: 5 | # 6 | # @App = {} 7 | # App.cable = Cable.createConsumer "ws://example.com/accounts/1" 8 | # App.appearance = App.cable.subscriptions.create "AppearanceChannel" 9 | # 10 | # For more details on how you'd configure an actual channel subscription, see Cable.Subscription. 11 | */ 12 | import Subscription from './Subscription'; 13 | 14 | const slice = [].slice; 15 | 16 | class Subscriptions { 17 | constructor(consumer) { 18 | this.consumer = consumer; 19 | this.subscriptions = []; 20 | } 21 | 22 | create(channelName, mixin) { 23 | const channel = channelName; 24 | const params = typeof channel === 'object' ? channel : { 25 | channel, 26 | }; 27 | return new Subscription(this, params, mixin); 28 | } 29 | 30 | add(subscription) { 31 | this.subscriptions.push(subscription); 32 | this.notify(subscription, 'initialized'); 33 | return this.sendCommand(subscription, 'subscribe'); 34 | } 35 | 36 | remove(subscription) { 37 | this.forget(subscription); 38 | if (!this.findAll(subscription.identifier).length) { 39 | return this.sendCommand(subscription, 'unsubscribe'); 40 | } 41 | return true; 42 | } 43 | 44 | reject(identifier) { 45 | let i = 0; 46 | let len; 47 | let subscription; 48 | const ref = this.findAll(identifier); 49 | const results = []; 50 | for (len = ref.length; i < len; i += 1) { 51 | subscription = ref[i]; 52 | this.forget(subscription); 53 | results.push(this.notify(subscription, 'rejected')); 54 | } 55 | return results; 56 | } 57 | 58 | forget(subscription) { 59 | let s; 60 | this.subscriptions = (() => { 61 | let i; 62 | let len; 63 | const ref = this.subscriptions; 64 | const results = []; 65 | for (i = 0, len = ref.length; i < len; i += 1) { 66 | s = ref[i]; 67 | if (s !== subscription) { 68 | results.push(s); 69 | } 70 | } 71 | return results; 72 | }).call(this); 73 | } 74 | 75 | reload() { 76 | let i; 77 | let len; 78 | let subscription; 79 | const ref = this.subscriptions; 80 | const results = []; 81 | for (i = 0, len = ref.length; i < len; i += 1) { 82 | subscription = ref[i]; 83 | results.push(this.sendCommand(subscription, 'subscribe')); 84 | } 85 | return results; 86 | } 87 | 88 | findAll(identifier) { 89 | let i; 90 | let len; 91 | let s; 92 | const ref = this.subscriptions; 93 | const results = []; 94 | for (i = 0, len = ref.length; i < len; i += 1) { 95 | s = ref[i]; 96 | if (s.identifier === identifier) { 97 | results.push(s); 98 | } 99 | } 100 | return results; 101 | } 102 | 103 | notifyAll(...args) { 104 | let i; 105 | let len; 106 | let subscription; 107 | const callbackName = args[0]; 108 | const formattedArgs = args.length <= 2 ? 109 | slice.call(args, 1) : 110 | []; 111 | const ref = this.subscriptions; 112 | const results = []; 113 | for (i = 0, len = ref.length; i < len; i += 1) { 114 | subscription = ref[i]; 115 | results.push( 116 | this.notify(...[subscription, callbackName].concat(slice.call(formattedArgs)))); 117 | } 118 | return results; 119 | } 120 | 121 | notify(...args) { 122 | let subscription = args[0]; 123 | const callbackName = args[1]; 124 | let i; 125 | let len; 126 | 127 | let subscriptions; 128 | const formattedArgs = args.length <= 3 129 | ? slice.call(args, 2) : []; 130 | if (typeof subscription === 'string') { 131 | subscriptions = this.findAll(subscription); 132 | } else { 133 | subscriptions = [subscription]; 134 | } 135 | const results = []; 136 | for (i = 0, len = subscriptions.length; i < len; i += 1) { 137 | subscription = subscriptions[i]; 138 | results.push(typeof subscription[callbackName] === 'function' ? 139 | subscription[callbackName](...formattedArgs) : 140 | 0); 141 | } 142 | return results; 143 | } 144 | 145 | sendCommand(subscription, command) { 146 | const identifier = subscription.identifier; 147 | return this.consumer.send({ 148 | command, 149 | identifier, 150 | }); 151 | } 152 | 153 | toJSON() { 154 | let i; 155 | let len; 156 | let subscription; 157 | const ref = this.subscriptions; 158 | const results = []; 159 | for (i = 0, len = ref.length; i < len; i += 1) { 160 | subscription = ref[i]; 161 | results.push(subscription.identifier); 162 | } 163 | return results; 164 | } 165 | 166 | } 167 | 168 | export default Subscriptions; 169 | -------------------------------------------------------------------------------- /src/actioncable/cable/Connection.js: -------------------------------------------------------------------------------- 1 | // # Encapsulate the cable connection held by the consumer. 2 | // This is an internal class not intended for direct user manipulation. 3 | import ActionCable from '../Logger'; 4 | 5 | const slice = [].slice; 6 | const indexOf = [].indexOf; 7 | 8 | const MessageTypes = { 9 | welcome: 'welcome', 10 | ping: 'ping', 11 | confirmation: 'confirm_subscription', 12 | rejection: 'reject_subscription', 13 | }; 14 | 15 | class Connection { 16 | constructor(consumer) { 17 | this.reopenDelay = 500; 18 | this.consumer = consumer; 19 | const that = this; 20 | this.events = { 21 | message: (event) => { 22 | const ref = JSON.parse(event.data); 23 | const identifier = ref.identifier; 24 | const message = ref.message; 25 | const type = ref.type; 26 | switch (type) { 27 | case MessageTypes.welcome: 28 | return that.consumer.connectionMonitor.connected(); 29 | case MessageTypes.ping: 30 | return that.consumer.connectionMonitor.ping(); 31 | case MessageTypes.confirmation: 32 | return that.consumer.subscriptions.notify(identifier, 'connected'); 33 | case MessageTypes.rejection: 34 | return that.consumer.subscriptions.reject(identifier); 35 | default: 36 | if (identifier === MessageTypes.ping) { 37 | return that.consumer.connectionMonitor.ping(); 38 | } 39 | return that.consumer.subscriptions.notify(identifier, 'received', message); 40 | } 41 | }, 42 | open: () => { 43 | ActionCable.log('WebSocket onopen event'); 44 | that.disconnected = false; 45 | return that.consumer.subscriptions.reload(); 46 | }, 47 | close: () => { 48 | ActionCable.log('WebSocket onclose event'); 49 | return that.disconnect(); 50 | }, 51 | error: () => { 52 | ActionCable.log('WebSocket onerror event'); 53 | return that.disconnect(); 54 | }, 55 | }; 56 | this.open(); 57 | } 58 | 59 | send(data) { 60 | if (this.isOpen()) { 61 | this.webSocket.send(JSON.stringify(data)); 62 | return true; 63 | } 64 | return false; 65 | } 66 | 67 | open() { 68 | if (this.isAlive()) { 69 | ActionCable.log(`Attemped to open WebSocket, but existing socket is ${this.getState()}`); 70 | throw new Error('Existing connection must be closed before opening'); 71 | } else { 72 | ActionCable.log(`Opening WebSocket, current state is ${this.getState()}`); 73 | if (this.webSocket != null) { 74 | this.uninstallEventHandlers(); 75 | } 76 | // allow people to pass in their own method to create websockets 77 | if (this.consumer.options.createWebsocket) { 78 | this.webSocket = this.consumer.options.createWebsocket(this.consumer.options); 79 | } else { 80 | this.webSocket = new WebSocket(this.consumer.url); 81 | } 82 | this.installEventHandlers(); 83 | return true; 84 | } 85 | } 86 | 87 | close() { 88 | return this.webSocket != null ? this.webSocket.close() : true; 89 | } 90 | 91 | reopen() { 92 | ActionCable.log(`Reopening WebSocket, current state is ${this.getState()}`); 93 | if (this.isAlive()) { 94 | try { 95 | return this.close(); 96 | } catch (error) { 97 | return ActionCable.log('Failed to reopen WebSocket', error); 98 | } finally { 99 | ActionCable.log(`Reopening WebSocket in ${this.reopenDelay}ms`); 100 | setTimeout(this.open.bind(this), this.reopenDelay); 101 | } 102 | } else { 103 | return this.open(); 104 | } 105 | } 106 | 107 | isOpen() { 108 | return this.isState('open'); 109 | } 110 | 111 | isAlive() { 112 | return (this.webSocket != null) && !this.isState('closing', 'closed'); 113 | } 114 | 115 | isState(...args) { 116 | const states = args.length <= 1 ? slice.call(args, 0) : []; 117 | const ref = this.getState(); 118 | return indexOf.call(states, ref) >= 0; 119 | } 120 | 121 | getState() { 122 | const states = ['connecting', 'open', 'closing', 'closed']; 123 | if (this.webSocket) { 124 | return states[this.webSocket.readyState]; 125 | } 126 | return true; 127 | } 128 | 129 | installEventHandlers() { 130 | let eventName; 131 | let handler; 132 | /* eslint-disable */ 133 | for (eventName in this.events) { 134 | handler = this.events[eventName].bind(this); 135 | this.webSocket[`on${eventName}`] = handler; 136 | } 137 | /* eslint-enable */ 138 | } 139 | 140 | uninstallEventHandlers() { 141 | let eventName; 142 | /* eslint-disable */ 143 | for (eventName in this.events) { 144 | this.webSocket[`on${eventName}`] = () => {}; 145 | } 146 | /* eslint-enable */ 147 | } 148 | 149 | disconnect() { 150 | if (this.disconnected) { 151 | return; 152 | } 153 | this.disconnected = true; 154 | this.consumer.connectionMonitor.disconnected(); 155 | this.consumer.subscriptions.notifyAll('disconnected'); 156 | } 157 | 158 | toJSON() { 159 | return { 160 | state: this.getState(), 161 | }; 162 | } 163 | } 164 | 165 | export default Connection; 166 | -------------------------------------------------------------------------------- /dist/actioncable/cable/Connection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); //# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. 8 | 9 | 10 | var _Logger = require('../Logger'); 11 | 12 | var _Logger2 = _interopRequireDefault(_Logger); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 17 | 18 | var slice = [].slice; 19 | var indexOf = [].indexOf; 20 | 21 | var MessageTypes = { 22 | welcome: 'welcome', 23 | ping: 'ping', 24 | confirmation: 'confirm_subscription', 25 | rejection: 'reject_subscription' 26 | }; 27 | 28 | var Connection = function () { 29 | function Connection(consumer) { 30 | _classCallCheck(this, Connection); 31 | 32 | this.reopenDelay = 500; 33 | this.consumer = consumer; 34 | var _this = this; 35 | this.events = { 36 | message: function message(event) { 37 | var identifier, message, ref, type; 38 | ref = JSON.parse(event.data), identifier = ref.identifier, message = ref.message, type = ref.type; 39 | switch (type) { 40 | case MessageTypes.welcome: 41 | return _this.consumer.connectionMonitor.connected(); 42 | case MessageTypes.ping: 43 | return _this.consumer.connectionMonitor.ping(); 44 | case MessageTypes.confirmation: 45 | return _this.consumer.subscriptions.notify(identifier, "connected"); 46 | case MessageTypes.rejection: 47 | return _this.consumer.subscriptions.reject(identifier); 48 | default: 49 | if (identifier === MessageTypes.ping) { 50 | return _this.consumer.connectionMonitor.ping(); 51 | } 52 | return _this.consumer.subscriptions.notify(identifier, "received", message); 53 | } 54 | }, 55 | open: function open() { 56 | _Logger2.default.log("WebSocket onopen event"); 57 | _this.disconnected = false; 58 | return _this.consumer.subscriptions.reload(); 59 | }, 60 | close: function close() { 61 | _Logger2.default.log("WebSocket onclose event"); 62 | return _this.disconnect(); 63 | }, 64 | error: function error() { 65 | _Logger2.default.log("WebSocket onerror event"); 66 | return _this.disconnect(); 67 | } 68 | }; 69 | this.open(); 70 | } 71 | 72 | _createClass(Connection, [{ 73 | key: 'send', 74 | value: function send(data) { 75 | if (this.isOpen()) { 76 | this.webSocket.send(JSON.stringify(data)); 77 | return true; 78 | } else { 79 | return false; 80 | } 81 | } 82 | }, { 83 | key: 'open', 84 | value: function open() { 85 | if (this.isAlive()) { 86 | _Logger2.default.log("Attemped to open WebSocket, but existing socket is " + this.getState()); 87 | throw new Error("Existing connection must be closed before opening"); 88 | } else { 89 | _Logger2.default.log("Opening WebSocket, current state is " + this.getState()); 90 | if (this.webSocket != null) { 91 | this.uninstallEventHandlers(); 92 | } 93 | //allow people to pass in their own method to create websockets 94 | if (this.consumer.options.createWebsocket) { 95 | this.webSocket = this.consumer.options.createWebsocket(this.consumer.options); 96 | } else { 97 | this.webSocket = new WebSocket(this.consumer.url); 98 | } 99 | this.installEventHandlers(); 100 | return true; 101 | } 102 | } 103 | }, { 104 | key: 'close', 105 | value: function close() { 106 | var ref; 107 | return (ref = this.webSocket) != null ? ref.close() : void 0; 108 | } 109 | }, { 110 | key: 'reopen', 111 | value: function reopen() { 112 | _Logger2.default.log("Reopening WebSocket, current state is " + this.getState()); 113 | if (this.isAlive()) { 114 | try { 115 | return this.close(); 116 | } catch (error) { 117 | return _Logger2.default.log("Failed to reopen WebSocket", error); 118 | } finally { 119 | _Logger2.default.log("Reopening WebSocket in " + this.reopenDelay + "ms"); 120 | setTimeout(this.open.bind(this), this.reopenDelay); 121 | } 122 | } else { 123 | return this.open(); 124 | } 125 | } 126 | }, { 127 | key: 'isOpen', 128 | value: function isOpen() { 129 | return this.isState("open"); 130 | } 131 | }, { 132 | key: 'isAlive', 133 | value: function isAlive() { 134 | return this.webSocket != null && !this.isState("closing", "closed"); 135 | } 136 | }, { 137 | key: 'isState', 138 | value: function isState() { 139 | var ref, states; 140 | states = 1 <= arguments.length ? slice.call(arguments, 0) : []; 141 | return ref = this.getState(), indexOf.call(states, ref) >= 0; 142 | } 143 | }, { 144 | key: 'getState', 145 | value: function getState() { 146 | var ref, state, value; 147 | var states = ['connecting', 'open', 'closing', 'closed']; 148 | if (this.webSocket) { 149 | return states[this.webSocket.readyState]; 150 | } 151 | } 152 | }, { 153 | key: 'installEventHandlers', 154 | value: function installEventHandlers() { 155 | var eventName, handler; 156 | for (eventName in this.events) { 157 | handler = this.events[eventName].bind(this); 158 | this.webSocket["on" + eventName] = handler; 159 | } 160 | } 161 | }, { 162 | key: 'uninstallEventHandlers', 163 | value: function uninstallEventHandlers() { 164 | var eventName; 165 | for (eventName in this.events) { 166 | this.webSocket["on" + eventName] = function () {}; 167 | } 168 | } 169 | }, { 170 | key: 'disconnect', 171 | value: function disconnect() { 172 | if (this.disconnected) { 173 | return; 174 | } 175 | this.disconnected = true; 176 | this.consumer.connectionMonitor.disconnected(); 177 | return this.consumer.subscriptions.notifyAll("disconnected"); 178 | } 179 | }, { 180 | key: 'toJSON', 181 | value: function toJSON() { 182 | return { 183 | state: this.getState() 184 | }; 185 | } 186 | }]); 187 | 188 | return Connection; 189 | }(); 190 | 191 | exports.default = Connection; -------------------------------------------------------------------------------- /dist/actioncable/cable/ConnectionMonitor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* 8 | # Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting 9 | # revival reconnections if things go astray. Internal class, not intended for direct user manipulation. 10 | */ 11 | 12 | 13 | var _Logger = require("../Logger"); 14 | 15 | var _Logger2 = _interopRequireDefault(_Logger); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | var now = function now() { 22 | return new Date().getTime(); 23 | }; 24 | 25 | var secondsSince = function secondsSince(time) { 26 | return (now() - time) / 1000; 27 | }; 28 | 29 | var clamp = function clamp(number, min, max) { 30 | return Math.max(min, Math.min(max, number)); 31 | }; 32 | 33 | var ConnectionMonitor = function () { 34 | function ConnectionMonitor(consumer) { 35 | _classCallCheck(this, ConnectionMonitor); 36 | 37 | this.pollInterval = { 38 | min: 3, 39 | max: 30 40 | }; 41 | this.staleThreshold = 6; 42 | this.consumer = consumer; 43 | this.visibilityDidChange = this._visibilityDidChange.bind(this); 44 | this.start(); 45 | } 46 | 47 | _createClass(ConnectionMonitor, [{ 48 | key: "connected", 49 | value: function connected() { 50 | this.reset(); 51 | this.pingedAt = now(); 52 | delete this.disconnectedAt; 53 | return _Logger2.default.log("ConnectionMonitor connected"); 54 | } 55 | }, { 56 | key: "disconnected", 57 | value: function disconnected() { 58 | this.disconnectedAt = now(); 59 | return _Logger2.default.log("ConnectionMonitor disconnected"); 60 | } 61 | }, { 62 | key: "ping", 63 | value: function ping() { 64 | return this.pingedAt = now(); 65 | } 66 | }, { 67 | key: "reset", 68 | value: function reset() { 69 | this.reconnectAttempts = 0; 70 | return this.consumer.connection.isOpen(); 71 | } 72 | }, { 73 | key: "start", 74 | value: function start() { 75 | this.reset(); 76 | delete this.stoppedAt; 77 | this.startedAt = now(); 78 | this.poll(); 79 | if (typeof document !== 'undefined') { 80 | document.addEventListener("visibilitychange", this.visibilityDidChange); 81 | }; 82 | return _Logger2.default.log("ConnectionMonitor started, pollInterval is " + this.getInterval() + "ms"); 83 | } 84 | }, { 85 | key: "stop", 86 | value: function stop() { 87 | this.stoppedAt = now(); 88 | if (typeof document !== 'undefined') { 89 | document.removeEventListener("visibilitychange", this.visibilityDidChange); 90 | }; 91 | return _Logger2.default.log("ConnectionMonitor stopped"); 92 | } 93 | }, { 94 | key: "poll", 95 | value: function poll() { 96 | return setTimeout(function (_this) { 97 | return function () { 98 | if (!_this.stoppedAt) { 99 | _this.reconnectIfStale(); 100 | return _this.poll(); 101 | } 102 | }; 103 | }(this), this.getInterval()); 104 | } 105 | }, { 106 | key: "getInterval", 107 | value: function getInterval() { 108 | var interval, max, min, ref; 109 | ref = this.pollInterval, min = ref.min, max = ref.max; 110 | interval = 5 * Math.log(this.reconnectAttempts + 1); 111 | return clamp(interval, min, max) * 1000; 112 | } 113 | }, { 114 | key: "reconnectIfStale", 115 | value: function reconnectIfStale() { 116 | if (this.connectionIsStale()) { 117 | _Logger2.default.log("ConnectionMonitor detected stale connection, reconnectAttempts = " + this.reconnectAttempts); 118 | this.reconnectAttempts++; 119 | if (this.disconnectedRecently()) { 120 | return _Logger2.default.log("ConnectionMonitor skipping reopen because recently disconnected at " + this.disconnectedAt); 121 | } else { 122 | _Logger2.default.log("ConnectionMonitor reopening"); 123 | return this.consumer.connection.reopen(); 124 | } 125 | } 126 | } 127 | }, { 128 | key: "connectionIsStale", 129 | value: function connectionIsStale() { 130 | var ref; 131 | return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.staleThreshold; 132 | } 133 | }, { 134 | key: "disconnectedRecently", 135 | value: function disconnectedRecently() { 136 | return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.staleThreshold; 137 | } 138 | }, { 139 | key: "_visibilityDidChange", 140 | value: function _visibilityDidChange() { 141 | if (document.visibilityState === "visible") { 142 | return setTimeout(function (_this) { 143 | return function () { 144 | if (_this.connectionIsStale() || !_this.consumer.connection.isOpen()) { 145 | _Logger2.default.log("ConnectionMonitor reopening stale connection after visibilitychange to " + document.visibilityState); 146 | return _this.consumer.connection.reopen(); 147 | } 148 | }; 149 | }(this), 200); 150 | } 151 | } 152 | }, { 153 | key: "toJSON", 154 | value: function toJSON() { 155 | var connectionIsStale, interval; 156 | interval = this.getInterval(); 157 | connectionIsStale = this.connectionIsStale(); 158 | return { 159 | startedAt: this.startedAt, 160 | stoppedAt: this.stoppedAt, 161 | pingedAt: this.pingedAt, 162 | reconnectAttempts: this.reconnectAttempts, 163 | connectionIsStale: connectionIsStale, 164 | interval: interval 165 | }; 166 | } 167 | }]); 168 | 169 | return ConnectionMonitor; 170 | }(); 171 | 172 | exports.default = ConnectionMonitor; -------------------------------------------------------------------------------- /dist/actioncable/cable/Consumer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* 8 | # The Cable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, 9 | # the Cable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. 10 | # The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription 11 | # method. 12 | # 13 | # The following example shows how this can be setup: 14 | # 15 | # @App = {} 16 | # App.cable = Cable.createConsumer "ws://example.com/accounts/1" 17 | # App.appearance = App.cable.subscriptions.create "AppearanceChannel" 18 | # 19 | # For more details on how you'd configure an actual channel subscription, see Cable.Subscription. 20 | */ 21 | 22 | 23 | var _Subscription = require('./Subscription'); 24 | 25 | var _Subscription2 = _interopRequireDefault(_Subscription); 26 | 27 | var _Subscriptions = require('./Subscriptions'); 28 | 29 | var _Subscriptions2 = _interopRequireDefault(_Subscriptions); 30 | 31 | var _Connection = require('./Connection'); 32 | 33 | var _Connection2 = _interopRequireDefault(_Connection); 34 | 35 | var _ConnectionMonitor = require('./ConnectionMonitor'); 36 | 37 | var _ConnectionMonitor2 = _interopRequireDefault(_ConnectionMonitor); 38 | 39 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 40 | 41 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 42 | 43 | var Consumer = function () { 44 | function Consumer(url, options) { 45 | _classCallCheck(this, Consumer); 46 | 47 | this.options = options ? options : {}; 48 | this.url = url; 49 | 50 | this.subscriptions = new _Subscriptions2.default(this); 51 | this.connection = new _Connection2.default(this); 52 | this.connectionMonitor = new _ConnectionMonitor2.default(this); 53 | } 54 | 55 | _createClass(Consumer, [{ 56 | key: 'send', 57 | value: function send(data) { 58 | return this.connection.send(data); 59 | } 60 | }, { 61 | key: 'toJSON', 62 | value: function toJSON() { 63 | return { 64 | url: this.url, 65 | subscriptions: this.subscriptions, 66 | connection: this.connection, 67 | connectionMonitor: this.connectionMonitor 68 | }; 69 | } 70 | }]); 71 | 72 | return Consumer; 73 | }(); 74 | 75 | exports.default = Consumer; -------------------------------------------------------------------------------- /dist/actioncable/cable/Subscriptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* 10 | # Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user 11 | # us Cable.Subscriptions#create, and it should be called through the consumer like so: 12 | # 13 | # @App = {} 14 | # App.cable = Cable.createConsumer "ws://example.com/accounts/1" 15 | # App.appearance = App.cable.subscriptions.create "AppearanceChannel" 16 | # 17 | # For more details on how you'd configure an actual channel subscription, see Cable.Subscription. 18 | */ 19 | 20 | 21 | var _Subscription = require('./Subscription'); 22 | 23 | var _Subscription2 = _interopRequireDefault(_Subscription); 24 | 25 | var _Cable = require('../Cable'); 26 | 27 | var _Cable2 = _interopRequireDefault(_Cable); 28 | 29 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 30 | 31 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 32 | 33 | var slice = [].slice; 34 | 35 | var Subscriptions = function () { 36 | function Subscriptions(consumer) { 37 | _classCallCheck(this, Subscriptions); 38 | 39 | this.consumer = consumer; 40 | this.subscriptions = []; 41 | } 42 | 43 | _createClass(Subscriptions, [{ 44 | key: 'create', 45 | value: function create(channelName, mixin) { 46 | var channel, params; 47 | channel = channelName; 48 | params = (typeof channel === 'undefined' ? 'undefined' : _typeof(channel)) === "object" ? channel : { 49 | channel: channel 50 | }; 51 | return new _Subscription2.default(this, params, mixin); 52 | } 53 | }, { 54 | key: 'add', 55 | value: function add(subscription) { 56 | this.subscriptions.push(subscription); 57 | this.notify(subscription, "initialized"); 58 | return this.sendCommand(subscription, "subscribe"); 59 | } 60 | }, { 61 | key: 'remove', 62 | value: function remove(subscription) { 63 | this.forget(subscription); 64 | if (!this.findAll(subscription.identifier).length) { 65 | return this.sendCommand(subscription, "unsubscribe"); 66 | } 67 | } 68 | }, { 69 | key: 'reject', 70 | value: function reject(identifier) { 71 | var i, len, ref, results, subscription; 72 | ref = this.findAll(identifier); 73 | results = []; 74 | for (i = 0, len = ref.length; i < len; i++) { 75 | subscription = ref[i]; 76 | this.forget(subscription); 77 | results.push(this.notify(subscription, "rejected")); 78 | } 79 | return results; 80 | } 81 | }, { 82 | key: 'forget', 83 | value: function forget(subscription) { 84 | var s; 85 | return this.subscriptions = function () { 86 | var i, len, ref, results; 87 | ref = this.subscriptions; 88 | results = []; 89 | for (i = 0, len = ref.length; i < len; i++) { 90 | s = ref[i]; 91 | if (s !== subscription) { 92 | results.push(s); 93 | } 94 | } 95 | return results; 96 | }.call(this); 97 | } 98 | }, { 99 | key: 'reload', 100 | value: function reload() { 101 | var i, len, ref, results, subscription; 102 | ref = this.subscriptions; 103 | results = []; 104 | for (i = 0, len = ref.length; i < len; i++) { 105 | subscription = ref[i]; 106 | results.push(this.sendCommand(subscription, "subscribe")); 107 | } 108 | return results; 109 | } 110 | }, { 111 | key: 'findAll', 112 | value: function findAll(identifier) { 113 | var i, len, ref, results, s; 114 | ref = this.subscriptions; 115 | results = []; 116 | for (i = 0, len = ref.length; i < len; i++) { 117 | s = ref[i]; 118 | if (s.identifier === identifier) { 119 | results.push(s); 120 | } 121 | } 122 | return results; 123 | } 124 | }, { 125 | key: 'notifyAll', 126 | value: function notifyAll() { 127 | var args, callbackName, i, len, ref, results, subscription; 128 | callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; 129 | ref = this.subscriptions; 130 | results = []; 131 | for (i = 0, len = ref.length; i < len; i++) { 132 | subscription = ref[i]; 133 | results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args)))); 134 | } 135 | return results; 136 | } 137 | }, { 138 | key: 'notify', 139 | value: function notify() { 140 | var args, callbackName, i, len, results, subscription, subscriptions; 141 | subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; 142 | if (typeof subscription === "string") { 143 | subscriptions = this.findAll(subscription); 144 | } else { 145 | subscriptions = [subscription]; 146 | } 147 | results = []; 148 | for (i = 0, len = subscriptions.length; i < len; i++) { 149 | subscription = subscriptions[i]; 150 | results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0); 151 | } 152 | return results; 153 | } 154 | }, { 155 | key: 'sendCommand', 156 | value: function sendCommand(subscription, command) { 157 | var identifier; 158 | identifier = subscription.identifier; 159 | return this.consumer.send({ 160 | command: command, 161 | identifier: identifier 162 | }); 163 | } 164 | }, { 165 | key: 'toJSON', 166 | value: function toJSON() { 167 | var i, len, ref, results, subscription; 168 | ref = this.subscriptions; 169 | results = []; 170 | for (i = 0, len = ref.length; i < len; i++) { 171 | subscription = ref[i]; 172 | results.push(subscription.identifier); 173 | } 174 | return results; 175 | } 176 | }]); 177 | 178 | return Subscriptions; 179 | }(); 180 | 181 | exports.default = Subscriptions; --------------------------------------------------------------------------------