From b5e0e58fe17b9cf5f53688ee1fac74772e565da1 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Tue, 23 Jun 2015 18:16:31 -0400 Subject: Require Cable.Channel constructors to define their channel name Function.name is not widely supported, and a function's name can be mangled by a minifier making it an unreliable property to infer the channel name from --- lib/assets/javascripts/channel.js.coffee | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/channel.js.coffee b/lib/assets/javascripts/channel.js.coffee index 2f07affb19..c972334140 100644 --- a/lib/assets/javascripts/channel.js.coffee +++ b/lib/assets/javascripts/channel.js.coffee @@ -1,9 +1,12 @@ class @Cable.Channel constructor: (params = {}) -> - @channelName ?= "#{@underscore(@constructor.name)}_channel" + {channelName} = @constructor - params['channel'] = @channelName - @channelIdentifier = JSON.stringify params + if channelName? + params['channel'] = channelName + @channelIdentifier = JSON.stringify params + else + throw new Error "This channel's constructor is missing the required 'channelName' property" cable.subscribe(@channelIdentifier, { onConnect: @connected @@ -28,7 +31,3 @@ class @Cable.Channel send: (data) -> cable.sendData @channelIdentifier, JSON.stringify data - - - underscore: (value) -> - value.replace(/[A-Z]/g, (match) => "_#{match.toLowerCase()}").substr(1) \ No newline at end of file -- cgit v1.2.3 From 268ee5208ce513eb0b74e2354259e7991d1633c9 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Wed, 24 Jun 2015 14:26:26 -0400 Subject: Create JavaScript channels identified by their Ruby class name --- lib/assets/javascripts/cable.js.coffee | 5 ++++ lib/assets/javascripts/channel.js.coffee | 43 +++++++++++++------------------- 2 files changed, 23 insertions(+), 25 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable.js.coffee b/lib/assets/javascripts/cable.js.coffee index 7c033d3b08..5d5c3a0a53 100644 --- a/lib/assets/javascripts/cable.js.coffee +++ b/lib/assets/javascripts/cable.js.coffee @@ -23,6 +23,11 @@ class @Cable connection.onerror = @reconnect connection + createChannel: (channelName, mixin) -> + channel = channelName + params = if typeof channel is "object" then channel else {channel} + new Cable.Channel this, params, mixin + isConnected: => @connection?.readyState is 1 diff --git a/lib/assets/javascripts/channel.js.coffee b/lib/assets/javascripts/channel.js.coffee index c972334140..8bca24bd0e 100644 --- a/lib/assets/javascripts/channel.js.coffee +++ b/lib/assets/javascripts/channel.js.coffee @@ -1,33 +1,26 @@ class @Cable.Channel - constructor: (params = {}) -> - {channelName} = @constructor + constructor: (@cable, params = {}, mixin) -> + @identifier = JSON.stringify(params) + extend(this, mixin) - if channelName? - params['channel'] = channelName - @channelIdentifier = JSON.stringify params - else - throw new Error "This channel's constructor is missing the required 'channelName' property" - - cable.subscribe(@channelIdentifier, { - onConnect: @connected - onDisconnect: @disconnected - onReceiveData: @received - }) - - - connected: => - # Override in the subclass - - disconnected: => - # Override in the subclass - - received: (data) => - # Override in the subclass + @cable.subscribe @identifier, + onConnect: => @connected?() + onDisconnect: => @disconnected?() + onReceiveData: (data) => @receive?(data) # Perform a channel action with the optional data passed as an attribute perform: (action, data = {}) -> data.action = action - cable.sendData @channelIdentifier, JSON.stringify data + @cable.sendData(@identifier, JSON.stringify(data)) send: (data) -> - cable.sendData @channelIdentifier, JSON.stringify data + @cable.sendData(@identifier, JSON.stringify(data)) + + close: -> + @cable.unsubscribe(@identifier) + + extend = (object, properties) -> + if properties? + for key, value of properties + object[key] = value + object -- cgit v1.2.3 From 0f761c0d51b8ccfd0d33562194cc5ef92199dc18 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Wed, 24 Jun 2015 18:22:16 -0400 Subject: Update API to camel cased equivalent of WebSocket's API --- lib/assets/javascripts/cable.js.coffee | 47 ++++++++++++++++++-------------- lib/assets/javascripts/channel.js.coffee | 19 ++++++------- 2 files changed, 35 insertions(+), 31 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable.js.coffee b/lib/assets/javascripts/cable.js.coffee index 5d5c3a0a53..9fc269f994 100644 --- a/lib/assets/javascripts/cable.js.coffee +++ b/lib/assets/javascripts/cable.js.coffee @@ -16,11 +16,10 @@ class @Cable createConnection: -> connection = new WebSocket(@cableUrl) - connection.onmessage = @receiveData - connection.onopen = @connected - connection.onclose = @reconnect - - connection.onerror = @reconnect + connection.onmessage = @onMessage + connection.onopen = @onConnect + connection.onclose = @onClose + connection.onerror = @onError connection createChannel: (channelName, mixin) -> @@ -31,31 +30,40 @@ class @Cable isConnected: => @connection?.readyState is 1 - sendData: (identifier, data) => + sendMessage: (identifier, data) => if @isConnected() @connection.send JSON.stringify { command: 'message', identifier: identifier, data: data } - receiveData: (message) => + onMessage: (message) => data = JSON.parse message.data if data.identifier is '_ping' @pingReceived(data.message) else - @subscribers[data.identifier]?.onReceiveData(data.message) + @subscribers[data.identifier]?.onMessage?(data.message) - connected: => + onConnect: => @startWaitingForPing() @resetConnectionAttemptsCount() - for identifier, callbacks of @subscribers + for identifier, subscriber of @subscribers @subscribeOnServer(identifier) - callbacks['onConnect']?() + subscriber.onConnect?() - reconnect: => - @removeExistingConnection() + onClose: => + @reconnect() + + onError: => + @reconnect() + disconnect: -> + @removeExistingConnection() @resetPingTime() - @disconnected() + for identifier, subscriber of @subscribers + subscriber.onDisconnect?() + + reconnect: -> + @disconnect() setTimeout => @incrementConnectionAttemptsCount() @@ -95,21 +103,18 @@ class @Cable resetPingTime: => @lastPingTime = null - disconnected: => - callbacks['onDisconnect']?() for identifier, callbacks of @subscribers - giveUp: => # Show an error message - subscribe: (identifier, callbacks) => - @subscribers[identifier] = callbacks + subscribe: (identifier, subscriber) => + @subscribers[identifier] = subscriber if @isConnected() @subscribeOnServer(identifier) - @subscribers[identifier]['onConnect']?() + subscriber.onConnect?() unsubscribe: (identifier) => - @unsubscribeOnServer(identifier, 'unsubscribe') + @unsubscribeOnServer(identifier) delete @subscribers[identifier] subscribeOnServer: (identifier) => diff --git a/lib/assets/javascripts/channel.js.coffee b/lib/assets/javascripts/channel.js.coffee index 8bca24bd0e..5196d5e03f 100644 --- a/lib/assets/javascripts/channel.js.coffee +++ b/lib/assets/javascripts/channel.js.coffee @@ -2,21 +2,20 @@ class @Cable.Channel constructor: (@cable, params = {}, mixin) -> @identifier = JSON.stringify(params) extend(this, mixin) - - @cable.subscribe @identifier, - onConnect: => @connected?() - onDisconnect: => @disconnected?() - onReceiveData: (data) => @receive?(data) + @subscribe(@identifier, this) # Perform a channel action with the optional data passed as an attribute - perform: (action, data = {}) -> + sendAction: (action, data = {}) -> data.action = action - @cable.sendData(@identifier, JSON.stringify(data)) + @sendMessage(data) + + sendMessage: (data) -> + @cable.sendMessage(@identifier, JSON.stringify(data)) - send: (data) -> - @cable.sendData(@identifier, JSON.stringify(data)) + subscribe: -> + @cable.subscribe(@identifier, this) - close: -> + unsubscribe: -> @cable.unsubscribe(@identifier) extend = (object, properties) -> -- cgit v1.2.3 From c7f00661bf0cc54a73ccdb9d27fa10b0fd806e43 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 10:21:53 -0400 Subject: Move connection and subscriber code into their own classes --- lib/assets/javascripts/cable.js.coffee | 134 ++------------------- lib/assets/javascripts/cable/channel.js.coffee | 22 ++++ lib/assets/javascripts/cable/connection.js.coffee | 102 ++++++++++++++++ .../javascripts/cable/subscriber_manager.js.coffee | 26 ++++ lib/assets/javascripts/channel.js.coffee | 25 ---- 5 files changed, 160 insertions(+), 149 deletions(-) create mode 100644 lib/assets/javascripts/cable/channel.js.coffee create mode 100644 lib/assets/javascripts/cable/connection.js.coffee create mode 100644 lib/assets/javascripts/cable/subscriber_manager.js.coffee delete mode 100644 lib/assets/javascripts/channel.js.coffee (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable.js.coffee b/lib/assets/javascripts/cable.js.coffee index 9fc269f994..4c93f8f062 100644 --- a/lib/assets/javascripts/cable.js.coffee +++ b/lib/assets/javascripts/cable.js.coffee @@ -1,134 +1,20 @@ #= require_self -#= require_tree . +#= require cable/subscriber_manager +#= require cable/connection +#= require cable/channel class @Cable - MAX_CONNECTION_INTERVAL: 5 * 1000 - PING_STALE_INTERVAL: 8 - - constructor: (@cableUrl) -> - @subscribers = {} - @resetPingTime() - @resetConnectionAttemptsCount() - @connect() - - connect: -> - @connection = @createConnection() - - createConnection: -> - connection = new WebSocket(@cableUrl) - connection.onmessage = @onMessage - connection.onopen = @onConnect - connection.onclose = @onClose - connection.onerror = @onError - connection + constructor: (@url) -> + @subscribers = new Cable.SubscriberManager this + @connection = new Cable.Connection this createChannel: (channelName, mixin) -> channel = channelName params = if typeof channel is "object" then channel else {channel} new Cable.Channel this, params, mixin - isConnected: => - @connection?.readyState is 1 - - sendMessage: (identifier, data) => - if @isConnected() - @connection.send JSON.stringify { command: 'message', identifier: identifier, data: data } - - onMessage: (message) => - data = JSON.parse message.data - - if data.identifier is '_ping' - @pingReceived(data.message) - else - @subscribers[data.identifier]?.onMessage?(data.message) - - onConnect: => - @startWaitingForPing() - @resetConnectionAttemptsCount() - - for identifier, subscriber of @subscribers - @subscribeOnServer(identifier) - subscriber.onConnect?() - - onClose: => - @reconnect() - - onError: => - @reconnect() - - disconnect: -> - @removeExistingConnection() - @resetPingTime() - for identifier, subscriber of @subscribers - subscriber.onDisconnect?() - - reconnect: -> - @disconnect() - - setTimeout => - @incrementConnectionAttemptsCount() - @connect() - , @generateReconnectInterval() - - removeExistingConnection: => - if @connection? - @clearPingWaitTimeout() - - @connection.onclose = -> # no-op - @connection.onerror = -> # no-op - @connection.close() - @connection = null - - resetConnectionAttemptsCount: => - @connectionAttempts = 1 - - incrementConnectionAttemptsCount: => - @connectionAttempts += 1 - - generateReconnectInterval: () -> - interval = (Math.pow(2, @connectionAttempts) - 1) * 1000 - if interval > @MAX_CONNECTION_INTERVAL then @MAX_CONNECTION_INTERVAL else interval - - startWaitingForPing: => - @clearPingWaitTimeout() - - @waitForPingTimeout = setTimeout => - console.log "Ping took too long to arrive. Reconnecting.." - @reconnect() - , @PING_STALE_INTERVAL * 1000 - - clearPingWaitTimeout: => - clearTimeout(@waitForPingTimeout) - - resetPingTime: => - @lastPingTime = null - - giveUp: => - # Show an error message - - subscribe: (identifier, subscriber) => - @subscribers[identifier] = subscriber - - if @isConnected() - @subscribeOnServer(identifier) - subscriber.onConnect?() - - unsubscribe: (identifier) => - @unsubscribeOnServer(identifier) - delete @subscribers[identifier] - - subscribeOnServer: (identifier) => - if @isConnected() - @connection.send JSON.stringify { command: 'subscribe', identifier: identifier } - - unsubscribeOnServer: (identifier) => - if @isConnected() - @connection.send JSON.stringify { command: 'unsubscribe', identifier: identifier } + sendMessage: (identifier, data) -> + @sendCommand(identifier, "message", data) - pingReceived: (timestamp) => - if @lastPingTime? and (timestamp - @lastPingTime) > @PING_STALE_INTERVAL - console.log "Websocket connection is stale. Reconnecting.." - @reconnect() - else - @startWaitingForPing() - @lastPingTime = timestamp + sendCommand: (identifier, command, data) -> + @connection.send({identifier, command, data}) diff --git a/lib/assets/javascripts/cable/channel.js.coffee b/lib/assets/javascripts/cable/channel.js.coffee new file mode 100644 index 0000000000..645a44e140 --- /dev/null +++ b/lib/assets/javascripts/cable/channel.js.coffee @@ -0,0 +1,22 @@ +class Cable.Channel + constructor: (@cable, params = {}, mixin) -> + @identifier = JSON.stringify(params) + extend(this, mixin) + @cable.subscribers.add(@identifier, this) + + # Perform a channel action with the optional data passed as an attribute + sendAction: (action, data = {}) -> + data.action = action + @sendMessage(data) + + sendMessage: (data) -> + @cable.sendMessage(@identifier, JSON.stringify(data)) + + unsubscribe: -> + @cable.subscribers.remove(@identifier) + + extend = (object, properties) -> + if properties? + for key, value of properties + object[key] = value + object diff --git a/lib/assets/javascripts/cable/connection.js.coffee b/lib/assets/javascripts/cable/connection.js.coffee new file mode 100644 index 0000000000..a318925b97 --- /dev/null +++ b/lib/assets/javascripts/cable/connection.js.coffee @@ -0,0 +1,102 @@ +class Cable.Connection + MAX_CONNECTION_INTERVAL: 5 * 1000 + PING_STALE_INTERVAL: 8 + + constructor: (@cable) -> + @resetPingTime() + @resetConnectionAttemptsCount() + @connect() + + send: (data) -> + if @isConnected() + @websocket.send(JSON.stringify(data)) + true + else + false + + connect: -> + @websocket = @createWebSocket() + + createWebSocket: -> + ws = new WebSocket(@cable.url) + ws.onmessage = @onMessage + ws.onopen = @onConnect + ws.onclose = @onClose + ws.onerror = @onError + ws + + onMessage: (message) => + data = JSON.parse message.data + + if data.identifier is '_ping' + @pingReceived(data.message) + else + @cable.subscribers.notify(data.identifier, "onMessage", data.message) + + onConnect: => + @startWaitingForPing() + @resetConnectionAttemptsCount() + @cable.subscribers.reload() + + onClose: => + @reconnect() + + onError: => + @reconnect() + + isConnected: -> + @websocket?.readyState is 1 + + disconnect: -> + @removeExistingConnection() + @resetPingTime() + @cable.subscribers.notifyAll("onDisconnect") + + reconnect: -> + @disconnect() + + setTimeout => + @incrementConnectionAttemptsCount() + @connect() + , @generateReconnectInterval() + + removeExistingConnection: -> + if @websocket? + @clearPingWaitTimeout() + + @websocket.onclose = -> # no-op + @websocket.onerror = -> # no-op + @websocket.close() + @websocket = null + + resetConnectionAttemptsCount: -> + @connectionAttempts = 1 + + incrementConnectionAttemptsCount: -> + @connectionAttempts += 1 + + generateReconnectInterval: () -> + interval = (Math.pow(2, @connectionAttempts) - 1) * 1000 + if interval > @MAX_CONNECTION_INTERVAL then @MAX_CONNECTION_INTERVAL else interval + + startWaitingForPing: -> + @clearPingWaitTimeout() + + @waitForPingTimeout = setTimeout => + console.log "Ping took too long to arrive. Reconnecting.." + @reconnect() + , @PING_STALE_INTERVAL * 1000 + + clearPingWaitTimeout: -> + clearTimeout(@waitForPingTimeout) + + resetPingTime: -> + @lastPingTime = null + + pingReceived: (timestamp) -> + if @lastPingTime? and (timestamp - @lastPingTime) > @PING_STALE_INTERVAL + console.log "Websocket connection is stale. Reconnecting.." + @reconnect() + else + @startWaitingForPing() + @lastPingTime = timestamp diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee new file mode 100644 index 0000000000..4f46efe817 --- /dev/null +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -0,0 +1,26 @@ +class Cable.SubscriberManager + constructor: (@cable) -> + @subscribers = {} + + add: (identifier, subscriber) -> + @subscribers[identifier] = subscriber + if @cable.sendCommand(identifier, "subscribe") + @notify(identifier, "onConnect") + + reload: -> + for identifier in Object.keys(@subscribers) + if @cable.sendCommand(identifier, "subscribe") + @notify(identifier, "onConnect") + + remove: (identifier) -> + if subscriber = @subscribers[identifier] + @cable.sendCommand(identifier, "unsubscribe") + delete @subscribers[identifier] + + notifyAll: (event, args...) -> + for identifier in Object.keys(@subscribers) + @notify(identifier, event, args...) + + notify: (identifier, event, args...) -> + if subscriber = @subscribers[identifier] + subscriber[event]?(args...) diff --git a/lib/assets/javascripts/channel.js.coffee b/lib/assets/javascripts/channel.js.coffee deleted file mode 100644 index 5196d5e03f..0000000000 --- a/lib/assets/javascripts/channel.js.coffee +++ /dev/null @@ -1,25 +0,0 @@ -class @Cable.Channel - constructor: (@cable, params = {}, mixin) -> - @identifier = JSON.stringify(params) - extend(this, mixin) - @subscribe(@identifier, this) - - # Perform a channel action with the optional data passed as an attribute - sendAction: (action, data = {}) -> - data.action = action - @sendMessage(data) - - sendMessage: (data) -> - @cable.sendMessage(@identifier, JSON.stringify(data)) - - subscribe: -> - @cable.subscribe(@identifier, this) - - unsubscribe: -> - @cable.unsubscribe(@identifier) - - extend = (object, properties) -> - if properties? - for key, value of properties - object[key] = value - object -- cgit v1.2.3 From 53d0b22aeeba1a585abe47b4a9417462b812d1a4 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 10:32:36 -0400 Subject: Switch back to original API and callback naming --- lib/assets/javascripts/cable/channel.js.coffee | 6 +++--- lib/assets/javascripts/cable/connection.js.coffee | 4 ++-- lib/assets/javascripts/cable/subscriber_manager.js.coffee | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable/channel.js.coffee b/lib/assets/javascripts/cable/channel.js.coffee index 645a44e140..bbdc9c5589 100644 --- a/lib/assets/javascripts/cable/channel.js.coffee +++ b/lib/assets/javascripts/cable/channel.js.coffee @@ -5,11 +5,11 @@ class Cable.Channel @cable.subscribers.add(@identifier, this) # Perform a channel action with the optional data passed as an attribute - sendAction: (action, data = {}) -> + perform: (action, data = {}) -> data.action = action - @sendMessage(data) + @send(data) - sendMessage: (data) -> + send: (data) -> @cable.sendMessage(@identifier, JSON.stringify(data)) unsubscribe: -> diff --git a/lib/assets/javascripts/cable/connection.js.coffee b/lib/assets/javascripts/cable/connection.js.coffee index a318925b97..e987c227c6 100644 --- a/lib/assets/javascripts/cable/connection.js.coffee +++ b/lib/assets/javascripts/cable/connection.js.coffee @@ -31,7 +31,7 @@ class Cable.Connection if data.identifier is '_ping' @pingReceived(data.message) else - @cable.subscribers.notify(data.identifier, "onMessage", data.message) + @cable.subscribers.notify(data.identifier, "received", data.message) onConnect: => @startWaitingForPing() @@ -50,7 +50,7 @@ class Cable.Connection disconnect: -> @removeExistingConnection() @resetPingTime() - @cable.subscribers.notifyAll("onDisconnect") + @cable.subscribers.notifyAll("disconnected") reconnect: -> @disconnect() diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index 4f46efe817..98223d076c 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -5,12 +5,12 @@ class Cable.SubscriberManager add: (identifier, subscriber) -> @subscribers[identifier] = subscriber if @cable.sendCommand(identifier, "subscribe") - @notify(identifier, "onConnect") + @notify(identifier, "connected") reload: -> for identifier in Object.keys(@subscribers) if @cable.sendCommand(identifier, "subscribe") - @notify(identifier, "onConnect") + @notify(identifier, "connected") remove: (identifier) -> if subscriber = @subscribers[identifier] -- cgit v1.2.3 From d9d7371c568fe99ef460202ebe7217bfed050e88 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 11:36:40 -0400 Subject: Assume subscribers have an identifier --- lib/assets/javascripts/cable.js.coffee | 7 ++--- lib/assets/javascripts/cable/channel.js.coffee | 6 ++-- .../javascripts/cable/subscriber_manager.js.coffee | 35 +++++++++++++--------- 3 files changed, 26 insertions(+), 22 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable.js.coffee b/lib/assets/javascripts/cable.js.coffee index 4c93f8f062..86e08e15c0 100644 --- a/lib/assets/javascripts/cable.js.coffee +++ b/lib/assets/javascripts/cable.js.coffee @@ -13,8 +13,5 @@ class @Cable params = if typeof channel is "object" then channel else {channel} new Cable.Channel this, params, mixin - sendMessage: (identifier, data) -> - @sendCommand(identifier, "message", data) - - sendCommand: (identifier, command, data) -> - @connection.send({identifier, command, data}) + send: (data) -> + @connection.send(data) diff --git a/lib/assets/javascripts/cable/channel.js.coffee b/lib/assets/javascripts/cable/channel.js.coffee index bbdc9c5589..9168a76d3c 100644 --- a/lib/assets/javascripts/cable/channel.js.coffee +++ b/lib/assets/javascripts/cable/channel.js.coffee @@ -2,7 +2,7 @@ class Cable.Channel constructor: (@cable, params = {}, mixin) -> @identifier = JSON.stringify(params) extend(this, mixin) - @cable.subscribers.add(@identifier, this) + @cable.subscribers.add(this) # Perform a channel action with the optional data passed as an attribute perform: (action, data = {}) -> @@ -10,10 +10,10 @@ class Cable.Channel @send(data) send: (data) -> - @cable.sendMessage(@identifier, JSON.stringify(data)) + @cable.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) unsubscribe: -> - @cable.subscribers.remove(@identifier) + @cable.subscribers.remove(this) extend = (object, properties) -> if properties? diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index 98223d076c..e2e6c9e228 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -2,25 +2,32 @@ class Cable.SubscriberManager constructor: (@cable) -> @subscribers = {} - add: (identifier, subscriber) -> + add: (subscriber) -> + {identifier} = subscriber @subscribers[identifier] = subscriber - if @cable.sendCommand(identifier, "subscribe") - @notify(identifier, "connected") + if @sendCommand("subscribe", identifier) + @notify(subscriber, "connected") reload: -> - for identifier in Object.keys(@subscribers) - if @cable.sendCommand(identifier, "subscribe") - @notify(identifier, "connected") + for identifier, subscriber of @subscribers + if @sendCommand("subscribe", identifier) + @notify(subscriber, "connected") - remove: (identifier) -> - if subscriber = @subscribers[identifier] - @cable.sendCommand(identifier, "unsubscribe") - delete @subscribers[identifier] + remove: (subscriber) -> + {identifier} = subscriber + @sendCommand("unsubscribe", identifier) + delete @subscribers[identifier] notifyAll: (event, args...) -> - for identifier in Object.keys(@subscribers) - @notify(identifier, event, args...) + for identifier, subscriber of @subscribers + @notify(subscriber, event, args...) - notify: (identifier, event, args...) -> - if subscriber = @subscribers[identifier] + notify: (subscriber, event, args...) -> + if typeof subscriber is "string" + subscriber = @subscribers[subscriber] + + if subscriber subscriber[event]?(args...) + + sendCommand: (command, identifier) -> + @cable.send({command, identifier}) -- cgit v1.2.3 From c846f43d46908750020710b4e9437f9395fb9594 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 13:52:47 -0400 Subject: Extract connection monitoring and rewrite as a subscriber --- lib/assets/javascripts/cable.js.coffee | 2 + lib/assets/javascripts/cable/connection.js.coffee | 94 +++++----------------- .../javascripts/cable/connection_monitor.js.coffee | 41 ++++++++++ .../javascripts/cable/subscriber_manager.js.coffee | 1 + 4 files changed, 66 insertions(+), 72 deletions(-) create mode 100644 lib/assets/javascripts/cable/connection_monitor.js.coffee (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable.js.coffee b/lib/assets/javascripts/cable.js.coffee index 86e08e15c0..0f1d7c8773 100644 --- a/lib/assets/javascripts/cable.js.coffee +++ b/lib/assets/javascripts/cable.js.coffee @@ -4,6 +4,8 @@ #= require cable/channel class @Cable + @PING_IDENTIFIER: "_ping" + constructor: (@url) -> @subscribers = new Cable.SubscriberManager this @connection = new Cable.Connection this diff --git a/lib/assets/javascripts/cable/connection.js.coffee b/lib/assets/javascripts/cable/connection.js.coffee index e987c227c6..096dd519f7 100644 --- a/lib/assets/javascripts/cable/connection.js.coffee +++ b/lib/assets/javascripts/cable/connection.js.coffee @@ -1,10 +1,8 @@ -class Cable.Connection - MAX_CONNECTION_INTERVAL: 5 * 1000 - PING_STALE_INTERVAL: 8 +#= require cable/connection_monitor +class Cable.Connection constructor: (@cable) -> - @resetPingTime() - @resetConnectionAttemptsCount() + new Cable.ConnectionMonitor @cable @connect() send: (data) -> @@ -15,88 +13,40 @@ class Cable.Connection false connect: -> - @websocket = @createWebSocket() + @removeWebsocket() + @createWebSocket() createWebSocket: -> - ws = new WebSocket(@cable.url) - ws.onmessage = @onMessage - ws.onopen = @onConnect - ws.onclose = @onClose - ws.onerror = @onError - ws + @websocket = new WebSocket(@cable.url) + @websocket.onmessage = @onMessage + @websocket.onopen = @onConnect + @websocket.onclose = @onClose + @websocket.onerror = @onError + @websocket + + removeWebsocket: -> + if @websocket? + @websocket.onclose = -> # no-op + @websocket.onerror = -> # no-op + @websocket.close() + @websocket = null onMessage: (message) => data = JSON.parse message.data - - if data.identifier is '_ping' - @pingReceived(data.message) - else - @cable.subscribers.notify(data.identifier, "received", data.message) + @cable.subscribers.notify(data.identifier, "received", data.message) onConnect: => - @startWaitingForPing() - @resetConnectionAttemptsCount() @cable.subscribers.reload() onClose: => - @reconnect() + @disconnect() onError: => - @reconnect() + @disconnect() isConnected: -> @websocket?.readyState is 1 disconnect: -> - @removeExistingConnection() - @resetPingTime() @cable.subscribers.notifyAll("disconnected") - - reconnect: -> - @disconnect() - - setTimeout => - @incrementConnectionAttemptsCount() - @connect() - , @generateReconnectInterval() - - removeExistingConnection: -> - if @websocket? - @clearPingWaitTimeout() - - @websocket.onclose = -> # no-op - @websocket.onerror = -> # no-op - @websocket.close() - @websocket = null - - resetConnectionAttemptsCount: -> - @connectionAttempts = 1 - - incrementConnectionAttemptsCount: -> - @connectionAttempts += 1 - - generateReconnectInterval: () -> - interval = (Math.pow(2, @connectionAttempts) - 1) * 1000 - if interval > @MAX_CONNECTION_INTERVAL then @MAX_CONNECTION_INTERVAL else interval - - startWaitingForPing: -> - @clearPingWaitTimeout() - - @waitForPingTimeout = setTimeout => - console.log "Ping took too long to arrive. Reconnecting.." - @reconnect() - , @PING_STALE_INTERVAL * 1000 - - clearPingWaitTimeout: -> - clearTimeout(@waitForPingTimeout) - - resetPingTime: -> - @lastPingTime = null - - pingReceived: (timestamp) -> - if @lastPingTime? and (timestamp - @lastPingTime) > @PING_STALE_INTERVAL - console.log "Websocket connection is stale. Reconnecting.." - @reconnect() - else - @startWaitingForPing() - @lastPingTime = timestamp + @removeWebsocket() diff --git a/lib/assets/javascripts/cable/connection_monitor.js.coffee b/lib/assets/javascripts/cable/connection_monitor.js.coffee new file mode 100644 index 0000000000..078e1d3b26 --- /dev/null +++ b/lib/assets/javascripts/cable/connection_monitor.js.coffee @@ -0,0 +1,41 @@ +class Cable.ConnectionMonitor + MAX_CONNECTION_INTERVAL: 5 * 1000 + PING_STALE_INTERVAL: 8 * 1000 + + identifier: Cable.PING_IDENTIFIER + + constructor: (@cable) -> + @reset() + @cable.subscribers.add(this) + @pollConnection() + + connected: -> + @reset() + @pingedAt = now() + + received: -> + @pingedAt = now() + + reset: -> + @connectionAttempts = 1 + + pollConnection: -> + setTimeout => + @reconnect() if @connectionIsStale() + @pollConnection() + , @getPollTimeout() + + getPollTimeout: -> + interval = (Math.pow(2, @connectionAttempts) - 1) * 1000 + if interval > @MAX_CONNECTION_INTERVAL then @MAX_CONNECTION_INTERVAL else interval + + connectionIsStale: -> + @pingedAt? and (now() - @pingedAt) > @PING_STALE_INTERVAL + + reconnect: -> + console.log "Ping took too long to arrive. Reconnecting.." + @connectionAttempts += 1 + @cable.connection.connect() + + now = -> + new Date().getTime() diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index e2e6c9e228..d76832a802 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -30,4 +30,5 @@ class Cable.SubscriberManager subscriber[event]?(args...) sendCommand: (command, identifier) -> + return true if identifier is Cable.PING_IDENTIFIER @cable.send({command, identifier}) -- cgit v1.2.3 From 288d289b72b2a558e0c7cc9151692eb18a2def0d Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 14:07:14 -0400 Subject: Add "initialized" notification --- lib/assets/javascripts/cable/subscriber_manager.js.coffee | 1 + 1 file changed, 1 insertion(+) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index d76832a802..da9a03c3b6 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -5,6 +5,7 @@ class Cable.SubscriberManager add: (subscriber) -> {identifier} = subscriber @subscribers[identifier] = subscriber + @notify(subscriber, "initialized") if @sendCommand("subscribe", identifier) @notify(subscriber, "connected") -- cgit v1.2.3 From 3509a1226c3b35aa018befd5f20327845c8c1ee9 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 15:22:39 -0400 Subject: event -> callbackName --- lib/assets/javascripts/cable/subscriber_manager.js.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index da9a03c3b6..ca79f1146e 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -19,16 +19,16 @@ class Cable.SubscriberManager @sendCommand("unsubscribe", identifier) delete @subscribers[identifier] - notifyAll: (event, args...) -> + notifyAll: (callbackName, args...) -> for identifier, subscriber of @subscribers - @notify(subscriber, event, args...) + @notify(subscriber, callbackName, args...) - notify: (subscriber, event, args...) -> + notify: (subscriber, callbackName, args...) -> if typeof subscriber is "string" subscriber = @subscribers[subscriber] if subscriber - subscriber[event]?(args...) + subscriber[callbackName]?(args...) sendCommand: (command, identifier) -> return true if identifier is Cable.PING_IDENTIFIER -- cgit v1.2.3 From 0e5c9e741ac2da96f6e1b0f2dd1de03f50a19b90 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 16:08:44 -0400 Subject: Manage an array of subscribers since there may be more than one subscription to a channel --- .../javascripts/cable/subscriber_manager.js.coffee | 27 +++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index ca79f1146e..9ff727aa0c 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -1,35 +1,36 @@ class Cable.SubscriberManager constructor: (@cable) -> - @subscribers = {} + @subscribers = [] add: (subscriber) -> - {identifier} = subscriber - @subscribers[identifier] = subscriber + @subscribers.push(subscriber) @notify(subscriber, "initialized") - if @sendCommand("subscribe", identifier) + if @sendCommand(subscriber, "subscribe") @notify(subscriber, "connected") reload: -> - for identifier, subscriber of @subscribers - if @sendCommand("subscribe", identifier) + for subscriber in @subscribers + if @sendCommand(subscriber, "subscribe") @notify(subscriber, "connected") remove: (subscriber) -> - {identifier} = subscriber - @sendCommand("unsubscribe", identifier) - delete @subscribers[identifier] + @sendCommand(subscriber, "unsubscribe") + @subscibers = (s for s in @subscribers when s isnt subscriber) notifyAll: (callbackName, args...) -> - for identifier, subscriber of @subscribers + for subscriber in @subscribers @notify(subscriber, callbackName, args...) notify: (subscriber, callbackName, args...) -> if typeof subscriber is "string" - subscriber = @subscribers[subscriber] + subscribers = (s for s in @subscribers when s.identifier is subscriber) + else + subscribers = [subscriber] - if subscriber + for subscriber in subscribers subscriber[callbackName]?(args...) - sendCommand: (command, identifier) -> + sendCommand: (subscriber, command) -> + {identifier} = subscriber return true if identifier is Cable.PING_IDENTIFIER @cable.send({command, identifier}) -- cgit v1.2.3 From fa0281aeb122058fa7d353fcd74e08a6702a9061 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 16:12:26 -0400 Subject: Cable.Channel -> Cable.Subscription --- lib/assets/javascripts/cable.js.coffee | 6 +++--- lib/assets/javascripts/cable/channel.js.coffee | 22 ---------------------- .../javascripts/cable/subscription.js.coffee | 22 ++++++++++++++++++++++ 3 files changed, 25 insertions(+), 25 deletions(-) delete mode 100644 lib/assets/javascripts/cable/channel.js.coffee create mode 100644 lib/assets/javascripts/cable/subscription.js.coffee (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable.js.coffee b/lib/assets/javascripts/cable.js.coffee index 0f1d7c8773..fad5aa05d3 100644 --- a/lib/assets/javascripts/cable.js.coffee +++ b/lib/assets/javascripts/cable.js.coffee @@ -1,7 +1,7 @@ #= require_self #= require cable/subscriber_manager #= require cable/connection -#= require cable/channel +#= require cable/subscription class @Cable @PING_IDENTIFIER: "_ping" @@ -10,10 +10,10 @@ class @Cable @subscribers = new Cable.SubscriberManager this @connection = new Cable.Connection this - createChannel: (channelName, mixin) -> + createSubscription: (channelName, mixin) -> channel = channelName params = if typeof channel is "object" then channel else {channel} - new Cable.Channel this, params, mixin + new Cable.Subscription this, params, mixin send: (data) -> @connection.send(data) diff --git a/lib/assets/javascripts/cable/channel.js.coffee b/lib/assets/javascripts/cable/channel.js.coffee deleted file mode 100644 index 9168a76d3c..0000000000 --- a/lib/assets/javascripts/cable/channel.js.coffee +++ /dev/null @@ -1,22 +0,0 @@ -class Cable.Channel - constructor: (@cable, params = {}, mixin) -> - @identifier = JSON.stringify(params) - extend(this, mixin) - @cable.subscribers.add(this) - - # Perform a channel action with the optional data passed as an attribute - perform: (action, data = {}) -> - data.action = action - @send(data) - - send: (data) -> - @cable.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) - - unsubscribe: -> - @cable.subscribers.remove(this) - - extend = (object, properties) -> - if properties? - for key, value of properties - object[key] = value - object diff --git a/lib/assets/javascripts/cable/subscription.js.coffee b/lib/assets/javascripts/cable/subscription.js.coffee new file mode 100644 index 0000000000..8057ff7790 --- /dev/null +++ b/lib/assets/javascripts/cable/subscription.js.coffee @@ -0,0 +1,22 @@ +class Cable.Subscription + constructor: (@cable, params = {}, mixin) -> + @identifier = JSON.stringify(params) + extend(this, mixin) + @cable.subscribers.add(this) + + # Perform a channel action with the optional data passed as an attribute + perform: (action, data = {}) -> + data.action = action + @send(data) + + send: (data) -> + @cable.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) + + unsubscribe: -> + @cable.subscribers.remove(this) + + extend = (object, properties) -> + if properties? + for key, value of properties + object[key] = value + object -- cgit v1.2.3 From 84ed13970003e9ec2f21c35df111370492519cdc Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 16:24:58 -0400 Subject: Cable.Consumer --- lib/assets/javascripts/cable.js.coffee | 21 +++++---------------- lib/assets/javascripts/cable/connection.js.coffee | 12 ++++++------ .../javascripts/cable/connection_monitor.js.coffee | 6 +++--- lib/assets/javascripts/cable/consumer.js.coffee | 16 ++++++++++++++++ .../javascripts/cable/subscriber_manager.js.coffee | 4 ++-- lib/assets/javascripts/cable/subscription.js.coffee | 8 ++++---- 6 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 lib/assets/javascripts/cable/consumer.js.coffee (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable.js.coffee b/lib/assets/javascripts/cable.js.coffee index fad5aa05d3..0bd1757505 100644 --- a/lib/assets/javascripts/cable.js.coffee +++ b/lib/assets/javascripts/cable.js.coffee @@ -1,19 +1,8 @@ #= require_self -#= require cable/subscriber_manager -#= require cable/connection -#= require cable/subscription +#= require cable/consumer -class @Cable - @PING_IDENTIFIER: "_ping" +@Cable = + PING_IDENTIFIER: "_ping" - constructor: (@url) -> - @subscribers = new Cable.SubscriberManager this - @connection = new Cable.Connection this - - createSubscription: (channelName, mixin) -> - channel = channelName - params = if typeof channel is "object" then channel else {channel} - new Cable.Subscription this, params, mixin - - send: (data) -> - @connection.send(data) + createConsumer: (url) -> + new Cable.Consumer url diff --git a/lib/assets/javascripts/cable/connection.js.coffee b/lib/assets/javascripts/cable/connection.js.coffee index 096dd519f7..6ee28fcb75 100644 --- a/lib/assets/javascripts/cable/connection.js.coffee +++ b/lib/assets/javascripts/cable/connection.js.coffee @@ -1,8 +1,8 @@ #= require cable/connection_monitor class Cable.Connection - constructor: (@cable) -> - new Cable.ConnectionMonitor @cable + constructor: (@consumer) -> + new Cable.ConnectionMonitor @consumer @connect() send: (data) -> @@ -17,7 +17,7 @@ class Cable.Connection @createWebSocket() createWebSocket: -> - @websocket = new WebSocket(@cable.url) + @websocket = new WebSocket(@consumer.url) @websocket.onmessage = @onMessage @websocket.onopen = @onConnect @websocket.onclose = @onClose @@ -33,10 +33,10 @@ class Cable.Connection onMessage: (message) => data = JSON.parse message.data - @cable.subscribers.notify(data.identifier, "received", data.message) + @consumer.subscribers.notify(data.identifier, "received", data.message) onConnect: => - @cable.subscribers.reload() + @consumer.subscribers.reload() onClose: => @disconnect() @@ -48,5 +48,5 @@ class Cable.Connection @websocket?.readyState is 1 disconnect: -> - @cable.subscribers.notifyAll("disconnected") + @consumer.subscribers.notifyAll("disconnected") @removeWebsocket() diff --git a/lib/assets/javascripts/cable/connection_monitor.js.coffee b/lib/assets/javascripts/cable/connection_monitor.js.coffee index 078e1d3b26..cf36b2a457 100644 --- a/lib/assets/javascripts/cable/connection_monitor.js.coffee +++ b/lib/assets/javascripts/cable/connection_monitor.js.coffee @@ -4,9 +4,9 @@ class Cable.ConnectionMonitor identifier: Cable.PING_IDENTIFIER - constructor: (@cable) -> + constructor: (@consumer) -> @reset() - @cable.subscribers.add(this) + @consumer.subscribers.add(this) @pollConnection() connected: -> @@ -35,7 +35,7 @@ class Cable.ConnectionMonitor reconnect: -> console.log "Ping took too long to arrive. Reconnecting.." @connectionAttempts += 1 - @cable.connection.connect() + @consumer.connection.connect() now = -> new Date().getTime() diff --git a/lib/assets/javascripts/cable/consumer.js.coffee b/lib/assets/javascripts/cable/consumer.js.coffee new file mode 100644 index 0000000000..a9abd6256a --- /dev/null +++ b/lib/assets/javascripts/cable/consumer.js.coffee @@ -0,0 +1,16 @@ +#= require cable/connection +#= require cable/subscription +#= require cable/subscriber_manager + +class Cable.Consumer + constructor: (@url) -> + @subscribers = new Cable.SubscriberManager this + @connection = new Cable.Connection this + + createSubscription: (channelName, mixin) -> + channel = channelName + params = if typeof channel is "object" then channel else {channel} + new Cable.Subscription this, params, mixin + + send: (data) -> + @connection.send(data) diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index 9ff727aa0c..0893d217ac 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -1,5 +1,5 @@ class Cable.SubscriberManager - constructor: (@cable) -> + constructor: (@consumer) -> @subscribers = [] add: (subscriber) -> @@ -33,4 +33,4 @@ class Cable.SubscriberManager sendCommand: (subscriber, command) -> {identifier} = subscriber return true if identifier is Cable.PING_IDENTIFIER - @cable.send({command, identifier}) + @consumer.send({command, identifier}) diff --git a/lib/assets/javascripts/cable/subscription.js.coffee b/lib/assets/javascripts/cable/subscription.js.coffee index 8057ff7790..74cc35a7a7 100644 --- a/lib/assets/javascripts/cable/subscription.js.coffee +++ b/lib/assets/javascripts/cable/subscription.js.coffee @@ -1,8 +1,8 @@ class Cable.Subscription - constructor: (@cable, params = {}, mixin) -> + constructor: (@consumer, params = {}, mixin) -> @identifier = JSON.stringify(params) extend(this, mixin) - @cable.subscribers.add(this) + @consumer.subscribers.add(this) # Perform a channel action with the optional data passed as an attribute perform: (action, data = {}) -> @@ -10,10 +10,10 @@ class Cable.Subscription @send(data) send: (data) -> - @cable.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) + @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) unsubscribe: -> - @cable.subscribers.remove(this) + @consumer.subscribers.remove(this) extend = (object, properties) -> if properties? -- cgit v1.2.3 From 315c0fbf1942429c1bcb4f862dd18fb7a585231c Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Thu, 25 Jun 2015 18:46:33 -0400 Subject: Fix misspelled variable --- lib/assets/javascripts/cable/subscriber_manager.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index 0893d217ac..922c74808c 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -15,7 +15,7 @@ class Cable.SubscriberManager remove: (subscriber) -> @sendCommand(subscriber, "unsubscribe") - @subscibers = (s for s in @subscribers when s isnt subscriber) + @subscribers = (s for s in @subscribers when s isnt subscriber) notifyAll: (callbackName, args...) -> for subscriber in @subscribers -- cgit v1.2.3 From 5541b8fcaf9ec40e6f16c50cb45030838a3e3450 Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Fri, 26 Jun 2015 10:24:29 -0400 Subject: Update connection API with #open, #close, #reopen --- lib/assets/javascripts/cable/connection.js.coffee | 39 +++++++++++----------- .../javascripts/cable/connection_monitor.js.coffee | 2 +- 2 files changed, 21 insertions(+), 20 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable/connection.js.coffee b/lib/assets/javascripts/cable/connection.js.coffee index 6ee28fcb75..cd9539a6aa 100644 --- a/lib/assets/javascripts/cable/connection.js.coffee +++ b/lib/assets/javascripts/cable/connection.js.coffee @@ -3,39 +3,41 @@ class Cable.Connection constructor: (@consumer) -> new Cable.ConnectionMonitor @consumer - @connect() + @open() send: (data) -> - if @isConnected() + if @isOpen() @websocket.send(JSON.stringify(data)) true else false - connect: -> - @removeWebsocket() - @createWebSocket() - - createWebSocket: -> + open: -> @websocket = new WebSocket(@consumer.url) @websocket.onmessage = @onMessage - @websocket.onopen = @onConnect + @websocket.onopen = @onOpen @websocket.onclose = @onClose @websocket.onerror = @onError @websocket - removeWebsocket: -> - if @websocket? - @websocket.onclose = -> # no-op - @websocket.onerror = -> # no-op - @websocket.close() - @websocket = null + close: -> + @websocket.close() unless @isClosed() + + reopen: -> + @close() + @open() + + isOpen: -> + @websocket.readyState is WebSocket.OPEN + + isClosed: -> + @websocket.readyState in [ WebSocket.CLOSED, WebSocket.CLOSING ] onMessage: (message) => data = JSON.parse message.data @consumer.subscribers.notify(data.identifier, "received", data.message) - onConnect: => + onOpen: => @consumer.subscribers.reload() onClose: => @@ -43,10 +45,9 @@ class Cable.Connection onError: => @disconnect() - - isConnected: -> - @websocket?.readyState is 1 + @websocket.onclose = -> # no-op + @websocket.onerror = -> # no-op + try @close() disconnect: -> @consumer.subscribers.notifyAll("disconnected") - @removeWebsocket() diff --git a/lib/assets/javascripts/cable/connection_monitor.js.coffee b/lib/assets/javascripts/cable/connection_monitor.js.coffee index cf36b2a457..bb4ee8f7f6 100644 --- a/lib/assets/javascripts/cable/connection_monitor.js.coffee +++ b/lib/assets/javascripts/cable/connection_monitor.js.coffee @@ -35,7 +35,7 @@ class Cable.ConnectionMonitor reconnect: -> console.log "Ping took too long to arrive. Reconnecting.." @connectionAttempts += 1 - @consumer.connection.connect() + @consumer.connection.reopen() now = -> new Date().getTime() -- cgit v1.2.3 From d2c613cd8f422b9cf2dd8a765681066d6045036a Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Sat, 27 Jun 2015 13:13:44 -0400 Subject: Wait for connection to close before reopening it --- lib/assets/javascripts/cable/connection.js.coffee | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable/connection.js.coffee b/lib/assets/javascripts/cable/connection.js.coffee index cd9539a6aa..98af9ad8ab 100644 --- a/lib/assets/javascripts/cable/connection.js.coffee +++ b/lib/assets/javascripts/cable/connection.js.coffee @@ -12,20 +12,23 @@ class Cable.Connection else false - open: -> + open: => @websocket = new WebSocket(@consumer.url) @websocket.onmessage = @onMessage @websocket.onopen = @onOpen @websocket.onclose = @onClose @websocket.onerror = @onError - @websocket close: -> @websocket.close() unless @isClosed() reopen: -> - @close() - @open() + if @isClosed() + @open() + else + @websocket.onclose = @open + @websocket.onerror = @open + @websocket.close() isOpen: -> @websocket.readyState is WebSocket.OPEN -- cgit v1.2.3 From 336d12f97aba485809005f27d4952705e312251c Mon Sep 17 00:00:00 2001 From: Javan Makhmali Date: Sat, 27 Jun 2015 16:17:00 -0400 Subject: Rework connection monitor --- lib/assets/javascripts/cable/connection.js.coffee | 3 -- .../javascripts/cable/connection_monitor.js.coffee | 62 +++++++++++++++------- lib/assets/javascripts/cable/consumer.js.coffee | 2 + .../javascripts/cable/subscriber_manager.js.coffee | 6 ++- 4 files changed, 49 insertions(+), 24 deletions(-) (limited to 'lib/assets/javascripts') diff --git a/lib/assets/javascripts/cable/connection.js.coffee b/lib/assets/javascripts/cable/connection.js.coffee index 98af9ad8ab..4f7d2abada 100644 --- a/lib/assets/javascripts/cable/connection.js.coffee +++ b/lib/assets/javascripts/cable/connection.js.coffee @@ -1,8 +1,5 @@ -#= require cable/connection_monitor - class Cable.Connection constructor: (@consumer) -> - new Cable.ConnectionMonitor @consumer @open() send: (data) -> diff --git a/lib/assets/javascripts/cable/connection_monitor.js.coffee b/lib/assets/javascripts/cable/connection_monitor.js.coffee index bb4ee8f7f6..fc5093c5eb 100644 --- a/lib/assets/javascripts/cable/connection_monitor.js.coffee +++ b/lib/assets/javascripts/cable/connection_monitor.js.coffee @@ -1,13 +1,17 @@ class Cable.ConnectionMonitor - MAX_CONNECTION_INTERVAL: 5 * 1000 - PING_STALE_INTERVAL: 8 * 1000 - identifier: Cable.PING_IDENTIFIER + pollInterval: + min: 2 + max: 30 + + staleThreshold: + startedAt: 4 + pingedAt: 8 + constructor: (@consumer) -> - @reset() @consumer.subscribers.add(this) - @pollConnection() + @start() connected: -> @reset() @@ -17,25 +21,45 @@ class Cable.ConnectionMonitor @pingedAt = now() reset: -> - @connectionAttempts = 1 + @reconnectAttempts = 0 + + start: -> + @reset() + delete @stoppedAt + @startedAt = now() + @poll() - pollConnection: -> + stop: -> + @stoppedAt = now() + + poll: -> setTimeout => - @reconnect() if @connectionIsStale() - @pollConnection() - , @getPollTimeout() + unless @stoppedAt + @reconnectIfStale() + @poll() + , @getInterval() - getPollTimeout: -> - interval = (Math.pow(2, @connectionAttempts) - 1) * 1000 - if interval > @MAX_CONNECTION_INTERVAL then @MAX_CONNECTION_INTERVAL else interval + getInterval: -> + {min, max} = @pollInterval + interval = 4 * Math.log(@reconnectAttempts + 1) + clamp(interval, min, max) * 1000 - connectionIsStale: -> - @pingedAt? and (now() - @pingedAt) > @PING_STALE_INTERVAL + reconnectIfStale: -> + if @connectionIsStale() + @reconnectAttempts += 1 + @consumer.connection.reopen() - reconnect: -> - console.log "Ping took too long to arrive. Reconnecting.." - @connectionAttempts += 1 - @consumer.connection.reopen() + connectionIsStale: -> + if @pingedAt + secondsSince(@pingedAt) > @staleThreshold.pingedAt + else + secondsSince(@startedAt) > @staleThreshold.startedAt now = -> new Date().getTime() + + secondsSince = (time) -> + (now() - time) / 1000 + + clamp = (number, min, max) -> + Math.max(min, Math.min(max, number)) diff --git a/lib/assets/javascripts/cable/consumer.js.coffee b/lib/assets/javascripts/cable/consumer.js.coffee index a9abd6256a..b9c08807f2 100644 --- a/lib/assets/javascripts/cable/consumer.js.coffee +++ b/lib/assets/javascripts/cable/consumer.js.coffee @@ -1,4 +1,5 @@ #= require cable/connection +#= require cable/connection_monitor #= require cable/subscription #= require cable/subscriber_manager @@ -6,6 +7,7 @@ class Cable.Consumer constructor: (@url) -> @subscribers = new Cable.SubscriberManager this @connection = new Cable.Connection this + @connectionMonitor = new Cable.ConnectionMonitor this createSubscription: (channelName, mixin) -> channel = channelName diff --git a/lib/assets/javascripts/cable/subscriber_manager.js.coffee b/lib/assets/javascripts/cable/subscriber_manager.js.coffee index 922c74808c..0b6a16590c 100644 --- a/lib/assets/javascripts/cable/subscriber_manager.js.coffee +++ b/lib/assets/javascripts/cable/subscriber_manager.js.coffee @@ -32,5 +32,7 @@ class Cable.SubscriberManager sendCommand: (subscriber, command) -> {identifier} = subscriber - return true if identifier is Cable.PING_IDENTIFIER - @consumer.send({command, identifier}) + if identifier is Cable.PING_IDENTIFIER + @consumer.connection.isOpen() + else + @consumer.send({command, identifier}) -- cgit v1.2.3