From eb8c713c987480e7a0362ae3de617ba0c0f27d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 2 Sep 2015 02:57:38 -0300 Subject: .js.coffee -> .coffee It was initially required, but support for the shorthand has been supported since sprockets 2.1. Eventually 4.x will only support the shorthand version. Just want to get new people using the prefer stuff ASAP. --- lib/assets/javascripts/cable.coffee | 8 ++ lib/assets/javascripts/cable.js.coffee | 8 -- lib/assets/javascripts/cable/connection.coffee | 65 ++++++++++++++++ lib/assets/javascripts/cable/connection.js.coffee | 65 ---------------- .../javascripts/cable/connection_monitor.coffee | 87 ++++++++++++++++++++++ .../javascripts/cable/connection_monitor.js.coffee | 87 ---------------------- lib/assets/javascripts/cable/consumer.coffee | 31 ++++++++ lib/assets/javascripts/cable/consumer.js.coffee | 31 -------- lib/assets/javascripts/cable/subscription.coffee | 68 +++++++++++++++++ .../javascripts/cable/subscription.js.coffee | 68 ----------------- lib/assets/javascripts/cable/subscriptions.coffee | 71 ++++++++++++++++++ .../javascripts/cable/subscriptions.js.coffee | 71 ------------------ 12 files changed, 330 insertions(+), 330 deletions(-) create mode 100644 lib/assets/javascripts/cable.coffee delete mode 100644 lib/assets/javascripts/cable.js.coffee create mode 100644 lib/assets/javascripts/cable/connection.coffee delete mode 100644 lib/assets/javascripts/cable/connection.js.coffee create mode 100644 lib/assets/javascripts/cable/connection_monitor.coffee delete mode 100644 lib/assets/javascripts/cable/connection_monitor.js.coffee create mode 100644 lib/assets/javascripts/cable/consumer.coffee delete mode 100644 lib/assets/javascripts/cable/consumer.js.coffee create mode 100644 lib/assets/javascripts/cable/subscription.coffee delete mode 100644 lib/assets/javascripts/cable/subscription.js.coffee create mode 100644 lib/assets/javascripts/cable/subscriptions.coffee delete mode 100644 lib/assets/javascripts/cable/subscriptions.js.coffee diff --git a/lib/assets/javascripts/cable.coffee b/lib/assets/javascripts/cable.coffee new file mode 100644 index 0000000000..0bd1757505 --- /dev/null +++ b/lib/assets/javascripts/cable.coffee @@ -0,0 +1,8 @@ +#= require_self +#= require cable/consumer + +@Cable = + PING_IDENTIFIER: "_ping" + + createConsumer: (url) -> + new Cable.Consumer url diff --git a/lib/assets/javascripts/cable.js.coffee b/lib/assets/javascripts/cable.js.coffee deleted file mode 100644 index 0bd1757505..0000000000 --- a/lib/assets/javascripts/cable.js.coffee +++ /dev/null @@ -1,8 +0,0 @@ -#= require_self -#= require cable/consumer - -@Cable = - PING_IDENTIFIER: "_ping" - - createConsumer: (url) -> - new Cable.Consumer url diff --git a/lib/assets/javascripts/cable/connection.coffee b/lib/assets/javascripts/cable/connection.coffee new file mode 100644 index 0000000000..2259ddcedd --- /dev/null +++ b/lib/assets/javascripts/cable/connection.coffee @@ -0,0 +1,65 @@ +# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. +class Cable.Connection + constructor: (@consumer) -> + @open() + + send: (data) -> + if @isOpen() + @webSocket.send(JSON.stringify(data)) + true + else + false + + open: -> + if @isOpen() + throw new Error("Must close existing connection before opening") + else + @webSocket = new WebSocket(@consumer.url) + @installEventHandlers() + + close: -> + @webSocket?.close() + + reopen: -> + @close() + @open() + + isOpen: -> + @isState("open") + + # Private + + isState: (states...) -> + @getState() in states + + getState: -> + return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState + null + + installEventHandlers: -> + for eventName of @events + handler = @events[eventName].bind(this) + @webSocket["on#{eventName}"] = handler + + events: + message: (event) -> + {identifier, message} = JSON.parse(event.data) + @consumer.subscriptions.notify(identifier, "received", message) + + open: -> + @disconnected = false + @consumer.subscriptions.reload() + + close: -> + @disconnect() + + error: -> + @disconnect() + + disconnect: -> + return if @disconnected + @disconnected = true + @consumer.subscriptions.notifyAll("disconnected") + + toJSON: -> + state: @getState() diff --git a/lib/assets/javascripts/cable/connection.js.coffee b/lib/assets/javascripts/cable/connection.js.coffee deleted file mode 100644 index 2259ddcedd..0000000000 --- a/lib/assets/javascripts/cable/connection.js.coffee +++ /dev/null @@ -1,65 +0,0 @@ -# Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. -class Cable.Connection - constructor: (@consumer) -> - @open() - - send: (data) -> - if @isOpen() - @webSocket.send(JSON.stringify(data)) - true - else - false - - open: -> - if @isOpen() - throw new Error("Must close existing connection before opening") - else - @webSocket = new WebSocket(@consumer.url) - @installEventHandlers() - - close: -> - @webSocket?.close() - - reopen: -> - @close() - @open() - - isOpen: -> - @isState("open") - - # Private - - isState: (states...) -> - @getState() in states - - getState: -> - return state.toLowerCase() for state, value of WebSocket when value is @webSocket?.readyState - null - - installEventHandlers: -> - for eventName of @events - handler = @events[eventName].bind(this) - @webSocket["on#{eventName}"] = handler - - events: - message: (event) -> - {identifier, message} = JSON.parse(event.data) - @consumer.subscriptions.notify(identifier, "received", message) - - open: -> - @disconnected = false - @consumer.subscriptions.reload() - - close: -> - @disconnect() - - error: -> - @disconnect() - - disconnect: -> - return if @disconnected - @disconnected = true - @consumer.subscriptions.notifyAll("disconnected") - - toJSON: -> - state: @getState() diff --git a/lib/assets/javascripts/cable/connection_monitor.coffee b/lib/assets/javascripts/cable/connection_monitor.coffee new file mode 100644 index 0000000000..30ce11957c --- /dev/null +++ b/lib/assets/javascripts/cable/connection_monitor.coffee @@ -0,0 +1,87 @@ +# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting +# revival reconnections if things go astray. Internal class, not intended for direct user manipulation. +class Cable.ConnectionMonitor + identifier: Cable.PING_IDENTIFIER + + pollInterval: + min: 2 + max: 30 + + staleThreshold: + startedAt: 4 + pingedAt: 8 + + constructor: (@consumer) -> + @consumer.subscriptions.add(this) + @start() + + connected: -> + @reset() + @pingedAt = now() + + disconnected: -> + if @reconnectAttempts++ is 0 + setTimeout => + @consumer.connection.open() unless @consumer.connection.isOpen() + , 200 + + received: -> + @pingedAt = now() + + reset: -> + @reconnectAttempts = 0 + + start: -> + @reset() + delete @stoppedAt + @startedAt = now() + @poll() + document.addEventListener("visibilitychange", @visibilityDidChange) + + stop: -> + @stoppedAt = now() + document.removeEventListener("visibilitychange", @visibilityDidChange) + + poll: -> + setTimeout => + unless @stoppedAt + @reconnectIfStale() + @poll() + , @getInterval() + + getInterval: -> + {min, max} = @pollInterval + interval = 4 * Math.log(@reconnectAttempts + 1) + clamp(interval, min, max) * 1000 + + reconnectIfStale: -> + if @connectionIsStale() + @reconnectAttempts++ + @consumer.connection.reopen() + + connectionIsStale: -> + if @pingedAt + secondsSince(@pingedAt) > @staleThreshold.pingedAt + else + secondsSince(@startedAt) > @staleThreshold.startedAt + + visibilityDidChange: => + if document.visibilityState is "visible" + setTimeout => + if @connectionIsStale() or not @consumer.connection.isOpen() + @consumer.connection.reopen() + , 200 + + toJSON: -> + interval = @getInterval() + connectionIsStale = @connectionIsStale() + {@startedAt, @stoppedAt, @pingedAt, @reconnectAttempts, connectionIsStale, interval} + + 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/connection_monitor.js.coffee b/lib/assets/javascripts/cable/connection_monitor.js.coffee deleted file mode 100644 index 30ce11957c..0000000000 --- a/lib/assets/javascripts/cable/connection_monitor.js.coffee +++ /dev/null @@ -1,87 +0,0 @@ -# Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting -# revival reconnections if things go astray. Internal class, not intended for direct user manipulation. -class Cable.ConnectionMonitor - identifier: Cable.PING_IDENTIFIER - - pollInterval: - min: 2 - max: 30 - - staleThreshold: - startedAt: 4 - pingedAt: 8 - - constructor: (@consumer) -> - @consumer.subscriptions.add(this) - @start() - - connected: -> - @reset() - @pingedAt = now() - - disconnected: -> - if @reconnectAttempts++ is 0 - setTimeout => - @consumer.connection.open() unless @consumer.connection.isOpen() - , 200 - - received: -> - @pingedAt = now() - - reset: -> - @reconnectAttempts = 0 - - start: -> - @reset() - delete @stoppedAt - @startedAt = now() - @poll() - document.addEventListener("visibilitychange", @visibilityDidChange) - - stop: -> - @stoppedAt = now() - document.removeEventListener("visibilitychange", @visibilityDidChange) - - poll: -> - setTimeout => - unless @stoppedAt - @reconnectIfStale() - @poll() - , @getInterval() - - getInterval: -> - {min, max} = @pollInterval - interval = 4 * Math.log(@reconnectAttempts + 1) - clamp(interval, min, max) * 1000 - - reconnectIfStale: -> - if @connectionIsStale() - @reconnectAttempts++ - @consumer.connection.reopen() - - connectionIsStale: -> - if @pingedAt - secondsSince(@pingedAt) > @staleThreshold.pingedAt - else - secondsSince(@startedAt) > @staleThreshold.startedAt - - visibilityDidChange: => - if document.visibilityState is "visible" - setTimeout => - if @connectionIsStale() or not @consumer.connection.isOpen() - @consumer.connection.reopen() - , 200 - - toJSON: -> - interval = @getInterval() - connectionIsStale = @connectionIsStale() - {@startedAt, @stoppedAt, @pingedAt, @reconnectAttempts, connectionIsStale, interval} - - 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.coffee b/lib/assets/javascripts/cable/consumer.coffee new file mode 100644 index 0000000000..05a7398e79 --- /dev/null +++ b/lib/assets/javascripts/cable/consumer.coffee @@ -0,0 +1,31 @@ +#= require cable/connection +#= require cable/connection_monitor +#= require cable/subscriptions +#= require cable/subscription + +# The Cable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, +# the Cable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. +# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription +# method. +# +# The following example shows how this can be setup: +# +# @App = {} +# App.cable = Cable.createConsumer "ws://example.com/accounts/1" +# App.appearance = App.cable.subscriptions.create "AppearanceChannel" +# +# For more details on how you'd configure an actual channel subscription, see Cable.Subscription. +class Cable.Consumer + constructor: (@url) -> + @subscriptions = new Cable.Subscriptions this + @connection = new Cable.Connection this + @connectionMonitor = new Cable.ConnectionMonitor this + + send: (data) -> + @connection.send(data) + + inspect: -> + JSON.stringify(this, null, 2) + + toJSON: -> + {@url, @subscriptions, @connection, @connectionMonitor} diff --git a/lib/assets/javascripts/cable/consumer.js.coffee b/lib/assets/javascripts/cable/consumer.js.coffee deleted file mode 100644 index 05a7398e79..0000000000 --- a/lib/assets/javascripts/cable/consumer.js.coffee +++ /dev/null @@ -1,31 +0,0 @@ -#= require cable/connection -#= require cable/connection_monitor -#= require cable/subscriptions -#= require cable/subscription - -# The Cable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, -# the Cable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. -# The Consumer instance is also the gateway to establishing subscriptions to desired channels through the #createSubscription -# method. -# -# The following example shows how this can be setup: -# -# @App = {} -# App.cable = Cable.createConsumer "ws://example.com/accounts/1" -# App.appearance = App.cable.subscriptions.create "AppearanceChannel" -# -# For more details on how you'd configure an actual channel subscription, see Cable.Subscription. -class Cable.Consumer - constructor: (@url) -> - @subscriptions = new Cable.Subscriptions this - @connection = new Cable.Connection this - @connectionMonitor = new Cable.ConnectionMonitor this - - send: (data) -> - @connection.send(data) - - inspect: -> - JSON.stringify(this, null, 2) - - toJSON: -> - {@url, @subscriptions, @connection, @connectionMonitor} diff --git a/lib/assets/javascripts/cable/subscription.coffee b/lib/assets/javascripts/cable/subscription.coffee new file mode 100644 index 0000000000..5b024d4e15 --- /dev/null +++ b/lib/assets/javascripts/cable/subscription.coffee @@ -0,0 +1,68 @@ +# A new subscription is created through the Cable.Subscriptions instance available on the consumer. +# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding +# Channel instance on the server side. +# +# An example demonstrates the basic functionality: +# +# App.appearance = App.cable.subscriptions.create "AppearanceChannel", +# connected: -> +# # Called once the subscription has been successfully completed +# +# appear: -> +# @perform 'appear', appearing_on: @appearingOn() +# +# away: -> +# @perform 'away' +# +# appearingOn: -> +# $('main').data 'appearing-on' +# +# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server +# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). +# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. +# +# This is how the server component would look: +# +# class AppearanceChannel < ApplicationCable::Channel +# def subscribed +# current_user.appear +# end +# +# def unsubscribed +# current_user.disappear +# end +# +# def appear(data) +# current_user.appear on: data['appearing_on'] +# end +# +# def away +# current_user.away +# end +# end +# +# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. +# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. +class Cable.Subscription + constructor: (@subscriptions, params = {}, mixin) -> + @identifier = JSON.stringify(params) + extend(this, mixin) + @subscriptions.add(this) + @consumer = @subscriptions.consumer + + # Perform a channel action with the optional data passed as an attribute + perform: (action, data = {}) -> + data.action = action + @send(data) + + send: (data) -> + @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) + + unsubscribe: -> + @subscriptions.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 deleted file mode 100644 index 5b024d4e15..0000000000 --- a/lib/assets/javascripts/cable/subscription.js.coffee +++ /dev/null @@ -1,68 +0,0 @@ -# A new subscription is created through the Cable.Subscriptions instance available on the consumer. -# It provides a number of callbacks and a method for calling remote procedure calls on the corresponding -# Channel instance on the server side. -# -# An example demonstrates the basic functionality: -# -# App.appearance = App.cable.subscriptions.create "AppearanceChannel", -# connected: -> -# # Called once the subscription has been successfully completed -# -# appear: -> -# @perform 'appear', appearing_on: @appearingOn() -# -# away: -> -# @perform 'away' -# -# appearingOn: -> -# $('main').data 'appearing-on' -# -# The methods #appear and #away forward their intent to the remote AppearanceChannel instance on the server -# by calling the `@perform` method with the first parameter being the action (which maps to AppearanceChannel#appear/away). -# The second parameter is a hash that'll get JSON encoded and made available on the server in the data parameter. -# -# This is how the server component would look: -# -# class AppearanceChannel < ApplicationCable::Channel -# def subscribed -# current_user.appear -# end -# -# def unsubscribed -# current_user.disappear -# end -# -# def appear(data) -# current_user.appear on: data['appearing_on'] -# end -# -# def away -# current_user.away -# end -# end -# -# The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name. -# The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the @perform method. -class Cable.Subscription - constructor: (@subscriptions, params = {}, mixin) -> - @identifier = JSON.stringify(params) - extend(this, mixin) - @subscriptions.add(this) - @consumer = @subscriptions.consumer - - # Perform a channel action with the optional data passed as an attribute - perform: (action, data = {}) -> - data.action = action - @send(data) - - send: (data) -> - @consumer.send(command: "message", identifier: @identifier, data: JSON.stringify(data)) - - unsubscribe: -> - @subscriptions.remove(this) - - extend = (object, properties) -> - if properties? - for key, value of properties - object[key] = value - object diff --git a/lib/assets/javascripts/cable/subscriptions.coffee b/lib/assets/javascripts/cable/subscriptions.coffee new file mode 100644 index 0000000000..eeaa697081 --- /dev/null +++ b/lib/assets/javascripts/cable/subscriptions.coffee @@ -0,0 +1,71 @@ +# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user +# us Cable.Subscriptions#create, and it should be called through the consumer like so: +# +# @App = {} +# App.cable = Cable.createConsumer "ws://example.com/accounts/1" +# App.appearance = App.cable.subscriptions.create "AppearanceChannel" +# +# For more details on how you'd configure an actual channel subscription, see Cable.Subscription. +class Cable.Subscriptions + constructor: (@consumer) -> + @subscriptions = [] + @history = [] + + create: (channelName, mixin) -> + channel = channelName + params = if typeof channel is "object" then channel else {channel} + new Cable.Subscription this, params, mixin + + # Private + + add: (subscription) -> + @subscriptions.push(subscription) + @notify(subscription, "initialized") + if @sendCommand(subscription, "subscribe") + @notify(subscription, "connected") + + reload: -> + for subscription in @subscriptions + if @sendCommand(subscription, "subscribe") + @notify(subscription, "connected") + + remove: (subscription) -> + @subscriptions = (s for s in @subscriptions when s isnt subscription) + unless @findAll(subscription.identifier).length + @sendCommand(subscription, "unsubscribe") + + findAll: (identifier) -> + s for s in @subscriptions when s.identifier is identifier + + notifyAll: (callbackName, args...) -> + for subscription in @subscriptions + @notify(subscription, callbackName, args...) + + notify: (subscription, callbackName, args...) -> + if typeof subscription is "string" + subscriptions = @findAll(subscription) + else + subscriptions = [subscription] + + for subscription in subscriptions + subscription[callbackName]?(args...) + + if callbackName in ["initialized", "connected", "disconnected"] + {identifier} = subscription + @record(notification: {identifier, callbackName, args}) + + sendCommand: (subscription, command) -> + {identifier} = subscription + if identifier is Cable.PING_IDENTIFIER + @consumer.connection.isOpen() + else + @consumer.send({command, identifier}) + + record: (data) -> + data.time = new Date() + @history = @history.slice(-19) + @history.push(data) + + toJSON: -> + history: @history + identifiers: (subscription.identifier for subscription in @subscriptions) diff --git a/lib/assets/javascripts/cable/subscriptions.js.coffee b/lib/assets/javascripts/cable/subscriptions.js.coffee deleted file mode 100644 index eeaa697081..0000000000 --- a/lib/assets/javascripts/cable/subscriptions.js.coffee +++ /dev/null @@ -1,71 +0,0 @@ -# Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user -# us Cable.Subscriptions#create, and it should be called through the consumer like so: -# -# @App = {} -# App.cable = Cable.createConsumer "ws://example.com/accounts/1" -# App.appearance = App.cable.subscriptions.create "AppearanceChannel" -# -# For more details on how you'd configure an actual channel subscription, see Cable.Subscription. -class Cable.Subscriptions - constructor: (@consumer) -> - @subscriptions = [] - @history = [] - - create: (channelName, mixin) -> - channel = channelName - params = if typeof channel is "object" then channel else {channel} - new Cable.Subscription this, params, mixin - - # Private - - add: (subscription) -> - @subscriptions.push(subscription) - @notify(subscription, "initialized") - if @sendCommand(subscription, "subscribe") - @notify(subscription, "connected") - - reload: -> - for subscription in @subscriptions - if @sendCommand(subscription, "subscribe") - @notify(subscription, "connected") - - remove: (subscription) -> - @subscriptions = (s for s in @subscriptions when s isnt subscription) - unless @findAll(subscription.identifier).length - @sendCommand(subscription, "unsubscribe") - - findAll: (identifier) -> - s for s in @subscriptions when s.identifier is identifier - - notifyAll: (callbackName, args...) -> - for subscription in @subscriptions - @notify(subscription, callbackName, args...) - - notify: (subscription, callbackName, args...) -> - if typeof subscription is "string" - subscriptions = @findAll(subscription) - else - subscriptions = [subscription] - - for subscription in subscriptions - subscription[callbackName]?(args...) - - if callbackName in ["initialized", "connected", "disconnected"] - {identifier} = subscription - @record(notification: {identifier, callbackName, args}) - - sendCommand: (subscription, command) -> - {identifier} = subscription - if identifier is Cable.PING_IDENTIFIER - @consumer.connection.isOpen() - else - @consumer.send({command, identifier}) - - record: (data) -> - data.time = new Date() - @history = @history.slice(-19) - @history.push(data) - - toJSON: -> - history: @history - identifiers: (subscription.identifier for subscription in @subscriptions) -- cgit v1.2.3