diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8dcf55fa..98935ead 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2018-08-20 08:24:54 -0400 using RuboCop version 0.58.2. +# on 2018-08-27 13:29:57 +0200 using RuboCop version 0.58.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new diff --git a/.travis.yml b/.travis.yml index 8615f1af..618fb5bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,6 +18,8 @@ matrix: env: CONCURRENCY=celluloid-io - rvm: 2.4.1 env: CONCURRENCY=faye-websocket + - rvm: 2.5 + env: CONCURRENCY=async-websocket allow_failures: - rvm: ruby-head - rvm: jruby-head diff --git a/CHANGELOG.md b/CHANGELOG.md index 40190c47..5e4cdebc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### 0.12.1 (Next) +* [#219](https://github.com/slack-ruby/slack-ruby-client/pull/219): Added support for `async-websocket` - [@dblock](https://github.com/dblock), [@ioquatix](https://github.com/ioquatix). * Your contribution here. ### 0.12.0 (8/20/2018) diff --git a/README.md b/README.md index cce4132c..49184f8b 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,10 @@ Add to Gemfile. gem 'slack-ruby-client' ``` -If you're going to be using the RealTime client, add either `eventmachine` and `faye-websocket` or `celluloid-io`. See below for more information about concurrency. +If you're going to be using the RealTime client, add either `async-websocket`, `eventmachine` and `faye-websocket` or `celluloid-io`. See below for more information about concurrency. We recommend you use `async-websocket`. ``` -gem 'eventmachine' -gem 'faye-websocket' +gem 'async-websocket' ``` Run `bundle install`. @@ -374,11 +373,11 @@ See [#134](https://github.com/slack-ruby/slack-ruby-client/issues/134) for a dis #### Concurrency -`Slack::RealTime::Client` needs help from a concurrency library and supports [Faye::WebSocket](https://github.com/faye/faye-websocket-ruby) with [Eventmachine](https://github.com/eventmachine/eventmachine) and [Celluloid](https://github.com/celluloid/celluloid). It will auto-detect one or the other depending on the gems in your Gemfile, but you can also set concurrency explicitly. +`Slack::RealTime::Client` needs help from a concurrency library and supports [Async](https://github.com/socketry/async), [Faye::WebSocket](https://github.com/faye/faye-websocket-ruby) with [Eventmachine](https://github.com/eventmachine/eventmachine) and [Celluloid](https://github.com/celluloid/celluloid). It will auto-detect one or the other depending on the gems in your Gemfile, but you can also set concurrency explicitly. ```ruby Slack::RealTime.configure do |config| - config.concurrency = Slack::RealTime::Concurrency::Eventmachine + config.concurrency = Slack::RealTime::Concurrency::Async end ``` @@ -390,6 +389,16 @@ client = Slack::RealTime::Client.new client.start_async ``` +##### Async + +This is the recommended library. Add `async-websocket` to your Gemfile. + +``` +gem 'async-websocket' +``` + +See a fully working example in [examples/hi_real_time_async_async](examples/hi_real_time_async_async/hi.rb). + ##### Faye::Websocket with Eventmachine Add the following to your Gemfile. diff --git a/UPGRADING.md b/UPGRADING.md index 8fa79dbe..7b73e8fd 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,12 @@ Upgrading Slack-Ruby-Client =========================== +### Upgrading to >= 0.12.2 + +#### Recommended Async Library + +The RealTime client now supports [async-websocket](https://github.com/socketry/async-websocket), which is now the recommended library following numerous disconnect issues in [#208](https://github.com/slack-ruby/slack-ruby-client/issues/208). + ### Upgrading to >= 0.9.0 #### Changes in How the RTM Client Connects diff --git a/examples/hi_real_time_async_async/Gemfile b/examples/hi_real_time_async_async/Gemfile new file mode 100644 index 00000000..4be70966 --- /dev/null +++ b/examples/hi_real_time_async_async/Gemfile @@ -0,0 +1,6 @@ +source 'http://rubygems.org' + +gem 'slack-ruby-client', path: '../..' + +gem 'async-websocket' +gem 'foreman' diff --git a/examples/hi_real_time_async_async/Procfile b/examples/hi_real_time_async_async/Procfile new file mode 100644 index 00000000..8f831a3a --- /dev/null +++ b/examples/hi_real_time_async_async/Procfile @@ -0,0 +1,2 @@ +console: bundle exec ruby hi.rb + diff --git a/examples/hi_real_time_async_async/hi.rb b/examples/hi_real_time_async_async/hi.rb new file mode 100644 index 00000000..afa2a8ef --- /dev/null +++ b/examples/hi_real_time_async_async/hi.rb @@ -0,0 +1,37 @@ +require 'slack-ruby-client' +require 'async' + +raise 'Missing ENV[SLACK_API_TOKENS]!' unless ENV.key?('SLACK_API_TOKENS') + +$stdout.sync = true +logger = Logger.new($stdout) +logger.level = Logger::DEBUG + +threads = [] + +ENV['SLACK_API_TOKENS'].split.each do |token| + logger.info "Starting #{token[0..12]} ..." + + client = Slack::RealTime::Client.new(token: token) + + client.on :hello do + logger.info "Successfully connected, welcome '#{client.self.name}' to the '#{client.team.name}' team at https://#{client.team.domain}.slack.com." + end + + client.on :message do |data| + logger.info data + + client.typing channel: data.channel + + case data.text + when /hi/ then + client.message channel: data.channel, text: "Hi <@#{data.user}>!" + else + client.message channel: data.channel, text: "Sorry <@#{data.user}>, what?" + end + end + + threads << client.start_async +end + +threads.each(&:join) diff --git a/lib/slack/real_time/concurrency.rb b/lib/slack/real_time/concurrency.rb index 6fe1026e..42852977 100644 --- a/lib/slack/real_time/concurrency.rb +++ b/lib/slack/real_time/concurrency.rb @@ -1,6 +1,7 @@ module Slack module RealTime module Concurrency + autoload :Async, 'slack/real_time/concurrency/async' autoload :Eventmachine, 'slack/real_time/concurrency/eventmachine' autoload :Celluloid, 'slack/real_time/concurrency/celluloid' end diff --git a/lib/slack/real_time/concurrency/async.rb b/lib/slack/real_time/concurrency/async.rb new file mode 100644 index 00000000..94649002 --- /dev/null +++ b/lib/slack/real_time/concurrency/async.rb @@ -0,0 +1,67 @@ +require 'async/websocket' + +module Slack + module RealTime + module Concurrency + module Async + class Client < ::Async::WebSocket::Client + extend ::Forwardable + def_delegators :@driver, :on, :text, :binary, :emit + end + + class Socket < Slack::RealTime::Socket + attr_reader :client + + def start_async(client) + Thread.new do + ::Async::Reactor.run do + client.run_loop + end + end + end + + def connect! + super + run_loop + end + + def close + @closing = true + @driver.close if @driver + super + end + + def run_loop + @closing = false + while @driver && @driver.next_event + # $stderr.puts event.inspect + end + end + + protected + + def build_ssl_context + OpenSSL::SSL::SSLContext.new(:TLSv1_2_client).tap do |ctx| + ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER) + end + end + + def build_endpoint + endpoint = ::Async::IO::Endpoint.tcp(addr, port) + endpoint = ::Async::IO::SSLEndpoint.new(endpoint, ssl_context: build_ssl_context) if secure? + endpoint + end + + def connect_socket + build_endpoint.connect + end + + def connect + @socket = connect_socket + @driver = Client.new(@socket, url) + end + end + end + end + end +end diff --git a/lib/slack/real_time/config.rb b/lib/slack/real_time/config.rb index 5f753e10..3e2d864c 100644 --- a/lib/slack/real_time/config.rb +++ b/lib/slack/real_time/config.rb @@ -36,7 +36,7 @@ def concurrency private def detect_concurrency - %i[Eventmachine Celluloid].each do |concurrency| + %i[Async Eventmachine Celluloid].each do |concurrency| begin return Slack::RealTime::Concurrency.const_get(concurrency) rescue LoadError, NameError @@ -44,7 +44,7 @@ def detect_concurrency end end - raise NoConcurrencyError, 'Missing concurrency. Add faye-websocket or celluloid-io to your Gemfile.' + raise NoConcurrencyError, 'Missing concurrency. Add async-websocket, faye-websocket or celluloid-io to your Gemfile.' end end diff --git a/spec/integration/integration_spec.rb b/spec/integration/integration_spec.rb index ae25537a..9be48158 100644 --- a/spec/integration/integration_spec.rb +++ b/spec/integration/integration_spec.rb @@ -74,6 +74,7 @@ def stop_server after do wait_for_server + connection.join end context 'client connected' do