diff options
Diffstat (limited to 'actioncable')
22 files changed, 167 insertions, 55 deletions
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 7f3177b64e..9f312f8806 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,52 @@ +## Rails 6.0.0.beta3 (March 11, 2019) ## + +* No changes. + + +## Rails 6.0.0.beta2 (February 25, 2019) ## + +* PostgreSQL subscription adapters now support `channel_prefix` option in cable.yml + + Avoids channel name collisions when multiple apps use the same database for Action Cable. + + *Vladimir Dementyev* + +* Allow passing custom configuration to `ActionCable::Server::Base`. + + You can now create a standalone Action Cable server with a custom configuration + (e.g. to run it in isolation from the default one): + + ```ruby + config = ActionCable::Server::Configuration.new + config.cable = { adapter: "redis", channel_prefix: "custom_" } + + CUSTOM_CABLE = ActionCable::Server::Base.new(config: config) + ``` + + Then you can mount it in the `routes.rb` file: + + ```ruby + Rails.application.routes.draw do + mount CUSTOM_CABLE => "/custom_cable" + # ... + end + ``` + + *Vladimir Dementyev* + +* Add `:action_cable_connection` and `:action_cable_channel` load hooks. + + You can use them to extend `ActionCable::Connection::Base` and `ActionCable::Channel::Base` + functionality: + + ```ruby + ActiveSupport.on_load(:action_cable_channel) do + # do something in the context of ActionCable::Channel::Base + end + ``` + + *Vladimir Dementyev* + * Add `Channel::Base#broadcast_to`. You can now call `broadcast_to` within a channel action, which equals to @@ -19,6 +68,12 @@ ## Rails 6.0.0.beta1 (January 18, 2019) ## +* [Rename npm package](https://github.com/rails/rails/pull/34905) from + [`actioncable`](https://www.npmjs.com/package/actioncable) to + [`@rails/actioncable`](https://www.npmjs.com/package/@rails/actioncable). + + *Javan Makhmali* + * Merge [`action-cable-testing`](https://github.com/palkan/action-cable-testing) to Rails. *Vladimir Dementyev* diff --git a/actioncable/README.md b/actioncable/README.md index 60c879e1f4..38eb251f42 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -13,7 +13,7 @@ You can read more about Action Cable in the [Action Cable Overview](https://edge API documentation is at: -* http://api.rubyonrails.org +* https://api.rubyonrails.org Bug reports for the Ruby on Rails project can be filed here: diff --git a/actioncable/Rakefile b/actioncable/Rakefile index 35de50f05a..121037844d 100644 --- a/actioncable/Rakefile +++ b/actioncable/Rakefile @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "base64" require "rake/testtask" require "pathname" require "open3" @@ -25,7 +26,8 @@ namespace :test do end task :integration do - system("yarn test") || raise("Failures") + system(Hash[*Base64.decode64(ENV.fetch("ENCODED", "")).split(/[ =]/)], "yarn", "test") + exit($?.exitstatus) unless $?.success? end end diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index 29836f012f..7aefb67c51 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.author = ["Pratik Naik", "David Heinemeier Hansson"] s.email = ["pratiknaik@gmail.com", "david@loudthinking.com"] - s.homepage = "http://rubyonrails.org" + s.homepage = "https://rubyonrails.org" s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*", "app/assets/javascripts/action_cable.js"] s.require_path = "lib" diff --git a/actioncable/app/assets/javascripts/action_cable.js b/actioncable/app/assets/javascripts/action_cable.js index 280adbfa83..8349361405 100644 --- a/actioncable/app/assets/javascripts/action_cable.js +++ b/actioncable/app/assets/javascripts/action_cable.js @@ -28,6 +28,22 @@ throw new TypeError("Cannot call a class as a function"); } }; + 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; + }; + }(); var now = function now() { return new Date().getTime(); }; @@ -432,7 +448,7 @@ var Consumer = function() { function Consumer(url) { classCallCheck(this, Consumer); - this.url = url; + this._url = url; this.subscriptions = new Subscriptions(this); this.connection = new Connection(this); } @@ -452,19 +468,18 @@ return this.connection.open(); } }; + createClass(Consumer, [ { + key: "url", + get: function get$$1() { + return createWebSocketURL(this._url); + } + } ]); return Consumer; }(); - function createConsumer() { - var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getConfig("url") || INTERNAL.default_mount_path; - return new Consumer(createWebSocketURL(url)); - } - function getConfig(name) { - var element = document.head.querySelector("meta[name='action-cable-" + name + "']"); - if (element) { - return element.getAttribute("content"); - } - } function createWebSocketURL(url) { + if (typeof url === "function") { + url = url(); + } if (url && !/^wss?:/i.test(url)) { var a = document.createElement("a"); a.href = url; @@ -475,6 +490,16 @@ return url; } } + function createConsumer() { + var url = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getConfig("url") || INTERNAL.default_mount_path; + return new Consumer(url); + } + function getConfig(name) { + var element = document.head.querySelector("meta[name='action-cable-" + name + "']"); + if (element) { + return element.getAttribute("content"); + } + } exports.Connection = Connection; exports.ConnectionMonitor = ConnectionMonitor; exports.Consumer = Consumer; @@ -482,10 +507,10 @@ exports.Subscription = Subscription; exports.Subscriptions = Subscriptions; exports.adapters = adapters; + exports.createWebSocketURL = createWebSocketURL; exports.logger = logger; exports.createConsumer = createConsumer; exports.getConfig = getConfig; - exports.createWebSocketURL = createWebSocketURL; Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/actioncable/app/javascript/action_cable/consumer.js b/actioncable/app/javascript/action_cable/consumer.js index e8440f39f5..e2e0dea8b5 100644 --- a/actioncable/app/javascript/action_cable/consumer.js +++ b/actioncable/app/javascript/action_cable/consumer.js @@ -29,11 +29,15 @@ import Subscriptions from "./subscriptions" export default class Consumer { constructor(url) { - this.url = url + this._url = url this.subscriptions = new Subscriptions(this) this.connection = new Connection(this) } + get url() { + return createWebSocketURL(this._url) + } + send(data) { return this.connection.send(data) } @@ -52,3 +56,20 @@ export default class Consumer { } } } + +export function createWebSocketURL(url) { + if (typeof url === "function") { + url = url() + } + + if (url && !/^wss?:/i.test(url)) { + const a = document.createElement("a") + a.href = url + // Fix populating Location properties in IE. Otherwise, protocol will be blank. + a.href = a.href + a.protocol = a.protocol.replace("http", "ws") + return a.href + } else { + return url + } +} diff --git a/actioncable/app/javascript/action_cable/index.js b/actioncable/app/javascript/action_cable/index.js index 659418396f..848b5631d6 100644 --- a/actioncable/app/javascript/action_cable/index.js +++ b/actioncable/app/javascript/action_cable/index.js @@ -1,6 +1,6 @@ import Connection from "./connection" import ConnectionMonitor from "./connection_monitor" -import Consumer from "./consumer" +import Consumer, { createWebSocketURL } from "./consumer" import INTERNAL from "./internal" import Subscription from "./subscription" import Subscriptions from "./subscriptions" @@ -15,11 +15,12 @@ export { Subscription, Subscriptions, adapters, + createWebSocketURL, logger, } export function createConsumer(url = getConfig("url") || INTERNAL.default_mount_path) { - return new Consumer(createWebSocketURL(url)) + return new Consumer(url) } export function getConfig(name) { @@ -28,16 +29,3 @@ export function getConfig(name) { return element.getAttribute("content") } } - -export function createWebSocketURL(url) { - if (url && !/^wss?:/i.test(url)) { - const a = document.createElement("a") - a.href = url - // Fix populating Location properties in IE. Otherwise, protocol will be blank. - a.href = a.href - a.protocol = a.protocol.replace("http", "ws") - return a.href - } else { - return url - } -} diff --git a/actioncable/karma.conf.js b/actioncable/karma.conf.js index 845b38d74f..83e9c98af1 100644 --- a/actioncable/karma.conf.js +++ b/actioncable/karma.conf.js @@ -52,9 +52,9 @@ if (process.env.CI) { } function buildId() { - const { TRAVIS_BUILD_NUMBER, TRAVIS_BUILD_ID } = process.env - return TRAVIS_BUILD_NUMBER && TRAVIS_BUILD_ID - ? `TRAVIS #${TRAVIS_BUILD_NUMBER} (${TRAVIS_BUILD_ID})` + const { BUILDKITE_JOB_ID } = process.env + return BUILDKITE_JOB_ID + ? `Buildkite ${BUILDKITE_JOB_ID}` : "" } } diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb index ad0d3685cd..af061c843a 100644 --- a/actioncable/lib/action_cable/channel/base.rb +++ b/actioncable/lib/action_cable/channel/base.rb @@ -307,3 +307,5 @@ module ActionCable end end end + +ActiveSupport.run_load_hooks(:action_cable_channel, ActionCable::Channel::Base) diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index 0044afad98..c469f7066c 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -260,3 +260,5 @@ module ActionCable end end end + +ActiveSupport.run_load_hooks(:action_cable_connection, ActionCable::Connection::Base) diff --git a/actioncable/lib/action_cable/connection/test_case.rb b/actioncable/lib/action_cable/connection/test_case.rb index 8d25a55c8a..f1673fea08 100644 --- a/actioncable/lib/action_cable/connection/test_case.rb +++ b/actioncable/lib/action_cable/connection/test_case.rb @@ -176,7 +176,7 @@ module ActionCable # # Accepts request path as the first argument and the following request options: # - # - params – url parameters (Hash) + # - params – URL parameters (Hash) # - headers – request headers (Hash) # - session – session data (Hash) # - env – additional Rack env configuration (Hash) diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb index 5082827417..2be81736c6 100644 --- a/actioncable/lib/action_cable/gem_version.rb +++ b/actioncable/lib/action_cable/gem_version.rb @@ -10,7 +10,7 @@ module ActionCable MAJOR = 6 MINOR = 0 TINY = 0 - PRE = "beta1" + PRE = "beta3" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb index 2b9e1cba3b..98b3743175 100644 --- a/actioncable/lib/action_cable/server/base.rb +++ b/actioncable/lib/action_cable/server/base.rb @@ -12,14 +12,17 @@ module ActionCable include ActionCable::Server::Broadcasting include ActionCable::Server::Connections - cattr_accessor :config, instance_accessor: true, default: ActionCable::Server::Configuration.new + cattr_accessor :config, instance_accessor: false, default: ActionCable::Server::Configuration.new + + attr_reader :config def self.logger; config.logger; end delegate :logger, to: :config attr_reader :mutex - def initialize + def initialize(config: self.class.config) + @config = config @mutex = Monitor.new @remote_connections = @event_loop = @worker_pool = @pubsub = nil end diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb index 50ec438c3a..1d60bed4af 100644 --- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb +++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb @@ -8,6 +8,8 @@ require "digest/sha1" module ActionCable module SubscriptionAdapter class PostgreSQL < Base # :nodoc: + prepend ChannelPrefix + def initialize(*) super @listener = nil diff --git a/actioncable/lib/rails/generators/channel/templates/javascript/channel.js.tt b/actioncable/lib/rails/generators/channel/templates/javascript/channel.js.tt index 33baaa5a22..ddf6b2d79b 100644 --- a/actioncable/lib/rails/generators/channel/templates/javascript/channel.js.tt +++ b/actioncable/lib/rails/generators/channel/templates/javascript/channel.js.tt @@ -1,15 +1,15 @@ import consumer from "./consumer" consumer.subscriptions.create("<%= class_name %>Channel", { - connected: function() { + connected() { // Called when the subscription is ready for use on the server }, - disconnected: function() { + disconnected() { // Called when the subscription has been terminated by the server }, - received: function(data) { + received(data) { // Called when there's incoming data on the websocket for this channel }<%= actions.any? ? ",\n" : '' %> <% actions.each do |action| -%> diff --git a/actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt b/actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt index eec7e54b8a..0eceb59b18 100644 --- a/actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt +++ b/actioncable/lib/rails/generators/channel/templates/javascript/consumer.js.tt @@ -1,6 +1,6 @@ // Action Cable provides the framework to deal with WebSockets in Rails. // You can generate new channels where WebSocket features live using the `rails generate channel` command. -import ActionCable from "@rails/actioncable" +import { createConsumer } from "@rails/actioncable" -export default ActionCable.createConsumer() +export default createConsumer() diff --git a/actioncable/package.json b/actioncable/package.json index 801fcc0c22..4451afd17f 100644 --- a/actioncable/package.json +++ b/actioncable/package.json @@ -1,6 +1,6 @@ { "name": "@rails/actioncable", - "version": "6.0.0-beta1", + "version": "6.0.0-beta3", "description": "WebSocket framework for Ruby on Rails.", "main": "app/assets/javascripts/action_cable.js", "files": [ diff --git a/actioncable/test/javascript/src/unit/action_cable_test.js b/actioncable/test/javascript/src/unit/action_cable_test.js index 83426fa32e..c46f9878d2 100644 --- a/actioncable/test/javascript/src/unit/action_cable_test.js +++ b/actioncable/test/javascript/src/unit/action_cable_test.js @@ -41,5 +41,17 @@ module("ActionCable", () => { assert.equal(consumer.url, testURL) }) + + test("dynamically computes URL from function", assert => { + let dynamicURL = testURL + const generateURL = () => { + return dynamicURL + } + const consumer = ActionCable.createConsumer(generateURL) + assert.equal(consumer.url, testURL) + + dynamicURL = `${testURL}foo` + assert.equal(consumer.url, `${testURL}foo`) + }) }) }) diff --git a/actioncable/test/subscription_adapter/channel_prefix.rb b/actioncable/test/subscription_adapter/channel_prefix.rb index 3071facd9d..475e6cfd3a 100644 --- a/actioncable/test/subscription_adapter/channel_prefix.rb +++ b/actioncable/test/subscription_adapter/channel_prefix.rb @@ -2,17 +2,9 @@ require "test_helper" -class ActionCable::Server::WithIndependentConfig < ActionCable::Server::Base - # ActionCable::Server::Base defines config as a class variable. - # Need config to be an instance variable here as we're testing 2 separate configs - def config - @config ||= ActionCable::Server::Configuration.new - end -end - module ChannelPrefixTest def test_channel_prefix - server2 = ActionCable::Server::WithIndependentConfig.new + server2 = ActionCable::Server::Base.new(config: ActionCable::Server::Configuration.new) server2.config.cable = alt_cable_config server2.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } diff --git a/actioncable/test/subscription_adapter/postgresql_test.rb b/actioncable/test/subscription_adapter/postgresql_test.rb index 5fb26a8896..4348eb1b1e 100644 --- a/actioncable/test/subscription_adapter/postgresql_test.rb +++ b/actioncable/test/subscription_adapter/postgresql_test.rb @@ -2,11 +2,13 @@ require "test_helper" require_relative "common" +require_relative "channel_prefix" require "active_record" class PostgresqlAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest + include ChannelPrefixTest def setup database_config = { "adapter" => "postgresql", "database" => "activerecord_unittest" } diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index ac2d8ef724..35840a4036 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -11,7 +11,11 @@ class RedisAdapterTest < ActionCable::TestCase include ChannelPrefixTest def cable_config - { adapter: "redis", driver: "ruby" } + { adapter: "redis", driver: "ruby" }.tap do |x| + if host = URI(ENV["REDIS_URL"] || "").hostname + x[:host] = host + end + end end end @@ -25,7 +29,7 @@ class RedisAdapterTest::AlternateConfiguration < RedisAdapterTest def cable_config alt_cable_config = super.dup alt_cable_config.delete(:url) - alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12) + alt_cable_config.merge(host: URI(ENV["REDIS_URL"] || "").hostname || "127.0.0.1", port: 6379, db: 12) end end diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb index c924f1e475..033f034b0c 100644 --- a/actioncable/test/test_helper.rb +++ b/actioncable/test/test_helper.rb @@ -41,3 +41,5 @@ class ActionCable::TestCase < ActiveSupport::TestCase end end end + +require_relative "../../tools/test_common" |