slack-ruby-client 0.14.1 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop_todo.yml +15 -4
  4. data/CHANGELOG.md +7 -0
  5. data/README.md +8 -1
  6. data/Rakefile +1 -1
  7. data/bin/commands.rb +1 -0
  8. data/bin/commands/api.rb +2 -2
  9. data/bin/commands/apps.rb +2 -2
  10. data/bin/commands/apps_permissions.rb +4 -4
  11. data/bin/commands/apps_permissions_resources.rb +2 -2
  12. data/bin/commands/apps_permissions_scopes.rb +2 -2
  13. data/bin/commands/apps_permissions_users.rb +4 -4
  14. data/bin/commands/auth.rb +4 -4
  15. data/bin/commands/bots.rb +2 -2
  16. data/bin/commands/channels.rb +32 -31
  17. data/bin/commands/chat.rb +48 -17
  18. data/bin/commands/chat_scheduledMessages.rb +17 -0
  19. data/bin/commands/conversations.rb +35 -35
  20. data/bin/commands/dialog.rb +2 -2
  21. data/bin/commands/dnd.rb +9 -9
  22. data/bin/commands/emoji.rb +2 -2
  23. data/bin/commands/files.rb +14 -25
  24. data/bin/commands/files_comments.rb +2 -23
  25. data/bin/commands/groups.rb +33 -33
  26. data/bin/commands/im.rb +13 -13
  27. data/bin/commands/migration.rb +2 -2
  28. data/bin/commands/mpim.rb +11 -11
  29. data/bin/commands/oauth.rb +4 -4
  30. data/bin/commands/pins.rb +6 -6
  31. data/bin/commands/reactions.rb +8 -8
  32. data/bin/commands/reminders.rb +10 -10
  33. data/bin/commands/rtm.rb +4 -4
  34. data/bin/commands/search.rb +7 -7
  35. data/bin/commands/stars.rb +6 -6
  36. data/bin/commands/team.rb +8 -8
  37. data/bin/commands/team_profile.rb +2 -2
  38. data/bin/commands/usergroups.rb +11 -11
  39. data/bin/commands/usergroups_users.rb +4 -4
  40. data/bin/commands/users.rb +21 -22
  41. data/bin/commands/users_profile.rb +4 -4
  42. data/lib/slack/events/request.rb +9 -5
  43. data/lib/slack/real_time/client.rb +23 -6
  44. data/lib/slack/real_time/concurrency/async.rb +55 -23
  45. data/lib/slack/real_time/concurrency/celluloid.rb +0 -1
  46. data/lib/slack/real_time/concurrency/eventmachine.rb +0 -5
  47. data/lib/slack/real_time/socket.rb +16 -9
  48. data/lib/slack/version.rb +1 -1
  49. data/lib/slack/web/api/endpoints.rb +2 -0
  50. data/lib/slack/web/api/endpoints/api.rb +1 -1
  51. data/lib/slack/web/api/endpoints/apps.rb +1 -1
  52. data/lib/slack/web/api/endpoints/apps_permissions.rb +2 -2
  53. data/lib/slack/web/api/endpoints/apps_permissions_resources.rb +1 -1
  54. data/lib/slack/web/api/endpoints/apps_permissions_scopes.rb +1 -1
  55. data/lib/slack/web/api/endpoints/apps_permissions_users.rb +2 -2
  56. data/lib/slack/web/api/endpoints/auth.rb +2 -2
  57. data/lib/slack/web/api/endpoints/bots.rb +1 -1
  58. data/lib/slack/web/api/endpoints/channels.rb +18 -15
  59. data/lib/slack/web/api/endpoints/chat.rb +63 -9
  60. data/lib/slack/web/api/endpoints/chat_scheduledMessages.rb +37 -0
  61. data/lib/slack/web/api/endpoints/conversations.rb +17 -17
  62. data/lib/slack/web/api/endpoints/dialog.rb +1 -1
  63. data/lib/slack/web/api/endpoints/dnd.rb +4 -4
  64. data/lib/slack/web/api/endpoints/emoji.rb +1 -1
  65. data/lib/slack/web/api/endpoints/files.rb +7 -18
  66. data/lib/slack/web/api/endpoints/files_comments.rb +1 -34
  67. data/lib/slack/web/api/endpoints/groups.rb +18 -16
  68. data/lib/slack/web/api/endpoints/im.rb +8 -6
  69. data/lib/slack/web/api/endpoints/migration.rb +1 -1
  70. data/lib/slack/web/api/endpoints/mpim.rb +7 -5
  71. data/lib/slack/web/api/endpoints/oauth.rb +2 -2
  72. data/lib/slack/web/api/endpoints/pins.rb +5 -3
  73. data/lib/slack/web/api/endpoints/reactions.rb +6 -4
  74. data/lib/slack/web/api/endpoints/reminders.rb +5 -5
  75. data/lib/slack/web/api/endpoints/rtm.rb +2 -2
  76. data/lib/slack/web/api/endpoints/search.rb +3 -3
  77. data/lib/slack/web/api/endpoints/stars.rb +5 -3
  78. data/lib/slack/web/api/endpoints/team.rb +5 -4
  79. data/lib/slack/web/api/endpoints/team_profile.rb +1 -1
  80. data/lib/slack/web/api/endpoints/usergroups.rb +5 -5
  81. data/lib/slack/web/api/endpoints/usergroups_users.rb +2 -2
  82. data/lib/slack/web/api/endpoints/users.rb +12 -12
  83. data/lib/slack/web/api/endpoints/users_profile.rb +2 -2
  84. data/spec/integration/integration_spec.rb +43 -36
  85. data/spec/slack/events/request_spec.rb +29 -1
  86. data/spec/slack/real_time/concurrency/celluloid_spec.rb +5 -0
  87. data/spec/slack/real_time/concurrency/eventmachine_spec.rb +1 -0
  88. data/spec/slack/web/api/endpoints/chat_scheduledMessages_spec.rb +7 -0
  89. data/spec/slack/web/api/endpoints/files_comments_spec.rb +0 -19
  90. data/spec/spec_helper.rb +5 -0
  91. metadata +6 -2
@@ -2,8 +2,8 @@
2
2
 
3
3
  desc 'UsergroupsUsers methods.'
4
4
  command 'usergroups_users' do |g|
5
- g.desc 'List all users in a User Group'
6
- g.long_desc %( List all users in a User Group )
5
+ g.desc 'This method returns a list of all users within a User Group.'
6
+ g.long_desc %( This method returns a list of all users within a User Group. )
7
7
  g.command 'list' do |c|
8
8
  c.flag 'usergroup', desc: 'The encoded ID of the User Group to update.'
9
9
  c.flag 'include_disabled', desc: 'Allow results that involve disabled User Groups.'
@@ -12,8 +12,8 @@ command 'usergroups_users' do |g|
12
12
  end
13
13
  end
14
14
 
15
- g.desc 'Update the list of users for a User Group'
16
- g.long_desc %( Update the list of users for a User Group )
15
+ g.desc 'This method updates the list of users that belong to a User Group. This method replaces all users in a User Group with the list of users provided in the users parameter.'
16
+ g.long_desc %( This method updates the list of users that belong to a User Group. This method replaces all users in a User Group with the list of users provided in the users parameter. )
17
17
  g.command 'update' do |c|
18
18
  c.flag 'usergroup', desc: 'The encoded ID of the User Group to update.'
19
19
  c.flag 'users', desc: 'A comma separated string of encoded user IDs that represent the entire list of users for the User Group.'
@@ -1,9 +1,9 @@
1
1
  # This file was auto-generated by lib/tasks/web.rake
2
2
 
3
- desc 'Get info on members of your Slack team.'
3
+ desc 'Users methods.'
4
4
  command 'users' do |g|
5
- g.desc 'List conversations the calling user may access.'
6
- g.long_desc %( List conversations the calling user may access. )
5
+ g.desc "As part of the Conversations API, this method's required scopes depend on the type of channel-like object you're working with. For classic Slack apps, a corresponding channels: scope is required when working with public channels, groups: for private channels, also the same rules are applied for im: and mpim:. For workspace apps, a conversations: scope is all that's needed."
6
+ g.long_desc %( As part of the Conversations API, this method's required scopes depend on the type of channel-like object you're working with. For classic Slack apps, a corresponding channels: scope is required when working with public channels, groups: for private channels, also the same rules are applied for im: and mpim:. For workspace apps, a conversations: scope is all that's needed. )
7
7
  g.command 'conversations' do |c|
8
8
  c.flag 'cursor', desc: "Paginate through collections of data by setting the cursor parameter to a next_cursor attribute returned by a previous request's response_metadata. Default value fetches the first 'page' of the collection. See pagination for more detail."
9
9
  c.flag 'exclude_archived', desc: 'Set to true to exclude archived channels from the list.'
@@ -15,16 +15,16 @@ command 'users' do |g|
15
15
  end
16
16
  end
17
17
 
18
- g.desc 'Delete the user profile photo'
19
- g.long_desc %( Delete the user profile photo )
18
+ g.desc 'This method allows the user to delete their profile image. It will clear whatever image is currently set.'
19
+ g.long_desc %( This method allows the user to delete their profile image. It will clear whatever image is currently set. )
20
20
  g.command 'deletePhoto' do |c|
21
21
  c.action do |_global_options, options, _args|
22
22
  puts JSON.dump($client.users_deletePhoto(options))
23
23
  end
24
24
  end
25
25
 
26
- g.desc 'Gets user presence information.'
27
- g.long_desc %( Gets user presence information. )
26
+ g.desc "This method lets you find out information about a user's presence."
27
+ g.long_desc %( This method lets you find out information about a user's presence. Consult the presence documentation for more details. )
28
28
  g.command 'getPresence' do |c|
29
29
  c.flag 'user', desc: 'User to get presence info on. Defaults to the authed user.'
30
30
  c.action do |_global_options, options, _args|
@@ -41,16 +41,16 @@ command 'users' do |g|
41
41
  end
42
42
  end
43
43
 
44
- g.desc "Get a user's identity."
45
- g.long_desc %( Get a user's identity. )
44
+ g.desc "After your Slack app is awarded an identity token through Sign in with Slack, use this method to retrieve a user's identity."
45
+ g.long_desc %( After your Slack app is awarded an identity token through Sign in with Slack, use this method to retrieve a user's identity. )
46
46
  g.command 'identity' do |c|
47
47
  c.action do |_global_options, options, _args|
48
48
  puts JSON.dump($client.users_identity(options))
49
49
  end
50
50
  end
51
51
 
52
- g.desc 'Gets information about a user.'
53
- g.long_desc %( Gets information about a user. )
52
+ g.desc 'This method returns information about a member of a workspace.'
53
+ g.long_desc %( This method returns information about a member of a workspace. )
54
54
  g.command 'info' do |c|
55
55
  c.flag 'user', desc: 'User to get info on.'
56
56
  c.flag 'include_locale', desc: 'Set this to true to receive the locale for this user. Defaults to false.'
@@ -59,20 +59,19 @@ command 'users' do |g|
59
59
  end
60
60
  end
61
61
 
62
- g.desc 'Lists all users in a Slack team.'
63
- g.long_desc %( Lists all users in a Slack team. )
62
+ g.desc 'This method returns a list of all users in the workspace. This includes deleted/deactivated users.'
63
+ g.long_desc %( This method returns a list of all users in the workspace. This includes deleted/deactivated users. )
64
64
  g.command 'list' do |c|
65
65
  c.flag 'cursor', desc: "Paginate through collections of data by setting the cursor parameter to a next_cursor attribute returned by a previous request's response_metadata. Default value fetches the first 'page' of the collection. See pagination for more detail."
66
66
  c.flag 'include_locale', desc: 'Set this to true to receive the locale for users. Defaults to false.'
67
67
  c.flag 'limit', desc: "The maximum number of items to return. Fewer than the requested number of items may be returned, even if the end of the users list hasn't been reached."
68
- c.flag 'presence', desc: 'Deprecated. Whether to include presence data in the output. Defaults to false. Setting this to true reduces performance, especially with large teams.'
69
68
  c.action do |_global_options, options, _args|
70
69
  puts JSON.dump($client.users_list(options))
71
70
  end
72
71
  end
73
72
 
74
- g.desc 'Find a user with an email address.'
75
- g.long_desc %( Find a user with an email address. )
73
+ g.desc 'Retrieve a single user by looking them up by their registered email address. Requires users:read.email.'
74
+ g.long_desc %( Retrieve a single user by looking them up by their registered email address. Requires users:read.email. )
76
75
  g.command 'lookupByEmail' do |c|
77
76
  c.flag 'email', desc: 'An email address belonging to a user in the workspace.'
78
77
  c.action do |_global_options, options, _args|
@@ -89,16 +88,16 @@ command 'users' do |g|
89
88
  end
90
89
  end
91
90
 
92
- g.desc 'Marked a user as active. Deprecated and non-functional.'
93
- g.long_desc %( Marked a user as active. Deprecated and non-functional. )
91
+ g.desc 'This method is no longer functional and the behavior it controlled is no longer offered. The method will no longer exist beginning May 8, 2018.'
92
+ g.long_desc %( This method is no longer functional and the behavior it controlled is no longer offered. The method will no longer exist beginning May 8, 2018. )
94
93
  g.command 'setActive' do |c|
95
94
  c.action do |_global_options, options, _args|
96
95
  puts JSON.dump($client.users_setActive(options))
97
96
  end
98
97
  end
99
98
 
100
- g.desc 'Set the user profile photo'
101
- g.long_desc %( Set the user profile photo )
99
+ g.desc 'This method allows the user to set their profile image. The caller can pass image data via image.'
100
+ g.long_desc %( This method allows the user to set their profile image. The caller can pass image data via image. )
102
101
  g.command 'setPhoto' do |c|
103
102
  c.flag 'image', desc: 'File contents via multipart/form-data.'
104
103
  c.flag 'crop_w', desc: 'Width/height of crop box (always square).'
@@ -109,8 +108,8 @@ command 'users' do |g|
109
108
  end
110
109
  end
111
110
 
112
- g.desc 'Manually sets user presence.'
113
- g.long_desc %( Manually sets user presence. )
111
+ g.desc "This method lets you set the calling user's manual presence."
112
+ g.long_desc %( This method lets you set the calling user's manual presence. Consult the presence documentation for more details. )
114
113
  g.command 'setPresence' do |c|
115
114
  c.flag 'presence', desc: 'Either auto or away.'
116
115
  c.action do |_global_options, options, _args|
@@ -2,8 +2,8 @@
2
2
 
3
3
  desc 'UsersProfile methods.'
4
4
  command 'users_profile' do |g|
5
- g.desc "Retrieves a user's profile information."
6
- g.long_desc %( Retrieves a user's profile information. )
5
+ g.desc "Use this method to retrieve a user's profile information."
6
+ g.long_desc %( Use this method to retrieve a user's profile information. )
7
7
  g.command 'get' do |c|
8
8
  c.flag 'include_labels', desc: 'Include labels for each ID in custom profile fields.'
9
9
  c.flag 'user', desc: 'User to retrieve profile info for.'
@@ -12,8 +12,8 @@ command 'users_profile' do |g|
12
12
  end
13
13
  end
14
14
 
15
- g.desc 'Set the profile information for a user.'
16
- g.long_desc %( Set the profile information for a user. )
15
+ g.desc "Use this method to set a user's profile information, including name, email, current status, and other attributes."
16
+ g.long_desc %( Use this method to set a user's profile information, including name, email, current status, and other attributes. )
17
17
  g.command 'set' do |c|
18
18
  c.flag 'name', desc: 'Name of a single key to set. Usable only if profile is not passed.'
19
19
  c.flag 'profile', desc: 'Collection of key:value pairs presented as a URL-encoded JSON hash. At most 50 fields may be set. Each field name is limited to 255 characters.'
@@ -5,10 +5,14 @@ module Slack
5
5
  class TimestampExpired < StandardError; end
6
6
  class InvalidSignature < StandardError; end
7
7
 
8
- attr_reader :http_request
8
+ attr_reader :http_request,
9
+ :signing_secret,
10
+ :signature_expires_in
9
11
 
10
- def initialize(http_request)
12
+ def initialize(http_request, options = {})
11
13
  @http_request = http_request
14
+ @signing_secret = options[:signing_secret] || Slack::Events.config.signing_secret
15
+ @signature_expires_in = options[:signature_expires_in] || Slack::Events.config.signature_expires_in
12
16
  end
13
17
 
14
18
  # Request timestamp.
@@ -34,16 +38,16 @@ module Slack
34
38
 
35
39
  # Returns true if the signature coming from Slack has expired.
36
40
  def expired?
37
- timestamp.nil? || (Time.now.to_i - timestamp.to_i).abs > Slack::Events.config.signature_expires_in
41
+ timestamp.nil? || (Time.now.to_i - timestamp.to_i).abs > signature_expires_in
38
42
  end
39
43
 
40
44
  # Returns true if the signature coming from Slack is valid.
41
45
  def valid?
42
- raise MissingSigningSecret unless Slack::Events.config.signing_secret
46
+ raise MissingSigningSecret unless signing_secret
43
47
 
44
48
  digest = OpenSSL::Digest::SHA256.new
45
49
  signature_basestring = [version, timestamp, body].join(':')
46
- hex_hash = OpenSSL::HMAC.hexdigest(digest, Slack::Events.config.signing_secret, signature_basestring)
50
+ hex_hash = OpenSSL::HMAC.hexdigest(digest, signing_secret, signature_basestring)
47
51
  computed_signature = [version, hex_hash].join('=')
48
52
  computed_signature == signature
49
53
  end
@@ -81,8 +81,6 @@ module Slack
81
81
 
82
82
  def run_loop
83
83
  @socket.connect! do |driver|
84
- @callback.call(driver) if @callback
85
-
86
84
  driver.on :open do |event|
87
85
  logger.debug("#{self.class}##{__method__}") { event.class.name }
88
86
  open(event)
@@ -100,16 +98,35 @@ module Slack
100
98
  close(event)
101
99
  callback(event, :closed)
102
100
  end
101
+
102
+ # This must be called last to ensure any events are registered before invoking user code.
103
+ @callback.call(driver) if @callback
103
104
  end
104
105
  end
105
106
 
106
- def run_ping!
107
+ # Ensure the server is running, and ping the remote server if no other messages were sent.
108
+ def keep_alive?
109
+ # We can't ping the remote server if we aren't connected.
110
+ return false if @socket.nil? || !@socket.connected?
111
+
107
112
  time_since_last_message = @socket.time_since_last_message
108
- return if time_since_last_message < websocket_ping
109
- raise Slack::RealTime::Client::ClientNotStartedError if !@socket.connected? || time_since_last_message > (websocket_ping * 2)
110
113
 
114
+ # If the server responded within the specified time, we are okay:
115
+ return true if time_since_last_message < websocket_ping
116
+
117
+ # If the server has not responded for a while:
118
+ return false if time_since_last_message > (websocket_ping * 2)
119
+
120
+ # Kick off the next ping message:
111
121
  ping
112
- rescue Slack::RealTime::Client::ClientNotStartedError
122
+
123
+ true
124
+ end
125
+
126
+ # Check if the remote server is responsive, and if not, restart the connection.
127
+ def run_ping!
128
+ return if keep_alive?
129
+
113
130
  restart_async
114
131
  end
115
132
 
@@ -1,14 +1,11 @@
1
1
  require 'async/websocket'
2
+ require 'async/notification'
2
3
  require 'async/clock'
3
4
 
4
5
  module Slack
5
6
  module RealTime
6
7
  module Concurrency
7
8
  module Async
8
- class Reactor < ::Async::Reactor
9
- def_delegators :@timers, :cancel
10
- end
11
-
12
9
  class Client < ::Async::WebSocket::Client
13
10
  extend ::Forwardable
14
11
  def_delegators :@driver, :on, :text, :binary, :emit
@@ -17,35 +14,57 @@ module Slack
17
14
  class Socket < Slack::RealTime::Socket
18
15
  attr_reader :client
19
16
 
17
+ def start_sync(client)
18
+ start_reactor(client).wait
19
+ end
20
+
20
21
  def start_async(client)
21
- @reactor = Reactor.new
22
22
  Thread.new do
23
+ start_reactor(client)
24
+ end
25
+ end
26
+
27
+ def start_reactor(client)
28
+ Async do |task|
29
+ @restart = ::Async::Notification.new
30
+
23
31
  if client.run_ping?
24
- @reactor.every(client.websocket_ping) do
25
- client.run_ping!
32
+ @ping_task = task.async do |subtask|
33
+ subtask.annotate 'client keep-alive'
34
+
35
+ # The timer task will naturally exit after the driver is set to nil.
36
+ while @restart
37
+ subtask.sleep client.websocket_ping
38
+ client.run_ping! if @restart
39
+ end
26
40
  end
27
41
  end
28
- @reactor.run do |task|
29
- task.async do
30
- client.run_loop
42
+
43
+ while @restart
44
+ @client_task.stop if @client_task
45
+
46
+ @client_task = task.async do |subtask|
47
+ begin
48
+ subtask.annotate 'client run-loop'
49
+ client.run_loop
50
+ rescue ::Async::Wrapper::Cancelled => e
51
+ # Will get restarted by ping worker.
52
+ client.logger.warn(subtask.to_s) { e.message }
53
+ end
31
54
  end
55
+
56
+ @restart.wait
32
57
  end
58
+
59
+ @ping_task.stop if @ping_task
33
60
  end
34
61
  end
35
62
 
36
- def restart_async(client, new_url)
63
+ def restart_async(_client, new_url)
37
64
  @url = new_url
38
65
  @last_message_at = current_time
39
- return unless @reactor
40
66
 
41
- @reactor.async do
42
- client.run_loop
43
- end
44
- end
45
-
46
- def disconnect!
47
- super
48
- @reactor.cancel
67
+ @restart.signal if @restart
49
68
  end
50
69
 
51
70
  def current_time
@@ -57,14 +76,27 @@ module Slack
57
76
  run_loop
58
77
  end
59
78
 
79
+ # Kill the restart/ping loop.
80
+ def disconnect!
81
+ super
82
+ ensure
83
+ if restart = @restart
84
+ @restart = nil
85
+ restart.signal
86
+ end
87
+ end
88
+
89
+ # Close the socket.
60
90
  def close
61
- @closing = true
62
- @driver.close if @driver
63
91
  super
92
+ ensure
93
+ if @socket
94
+ @socket.close
95
+ @socket = nil
96
+ end
64
97
  end
65
98
 
66
99
  def run_loop
67
- @closing = false
68
100
  while @driver && @driver.next_event
69
101
  # $stderr.puts event.inspect
70
102
  end
@@ -46,7 +46,6 @@ module Slack
46
46
 
47
47
  def close
48
48
  @closing = true
49
- driver.close if driver
50
49
  super
51
50
  end
52
51
 
@@ -56,11 +56,6 @@ module Slack
56
56
  @thread = nil
57
57
  end
58
58
 
59
- def close
60
- driver.close if driver
61
- super
62
- end
63
-
64
59
  def send_data(message)
65
60
  logger.debug("#{self.class}##{__method__}") { message }
66
61
  driver.send(message)
@@ -18,9 +18,9 @@ module Slack
18
18
  def send_data(message)
19
19
  logger.debug("#{self.class}##{__method__}") { message }
20
20
  case message
21
- when Numeric then driver.text(message.to_s)
22
- when String then driver.text(message)
23
- when Array then driver.binary(message)
21
+ when Numeric then @driver.text(message.to_s)
22
+ when String then @driver.text(message)
23
+ when Array then @driver.binary(message)
24
24
  else false
25
25
  end
26
26
  end
@@ -29,21 +29,24 @@ module Slack
29
29
  return if connected?
30
30
 
31
31
  connect
32
- logger.debug("#{self.class}##{__method__}") { driver.class }
32
+ logger.debug("#{self.class}##{__method__}") { @driver.class }
33
33
 
34
- driver.on :message do
34
+ @driver.on :message do
35
35
  @last_message_at = current_time
36
36
  end
37
37
 
38
- yield driver if block_given?
38
+ yield @driver if block_given?
39
39
  end
40
40
 
41
+ # Gracefully shut down the connection.
41
42
  def disconnect!
42
- driver.close
43
+ @driver.close
44
+ ensure
45
+ close
43
46
  end
44
47
 
45
48
  def connected?
46
- !driver.nil?
49
+ !@driver.nil?
47
50
  end
48
51
 
49
52
  def start_sync(client)
@@ -73,7 +76,11 @@ module Slack
73
76
  end
74
77
 
75
78
  def close
76
- @driver = nil
79
+ # When you call `driver.emit(:close)`, it will typically end up calling `client.close` which will call `@socket.close` and end up back here. In order to break this infinite recursion, we check and set `@driver = nil` before invoking `client.close`.
80
+ if driver = @driver
81
+ @driver = nil
82
+ driver.emit(:close)
83
+ end
77
84
  end
78
85
 
79
86
  protected
@@ -1,3 +1,3 @@
1
1
  module Slack
2
- VERSION = '0.14.1'.freeze
2
+ VERSION = '0.14.2'.freeze
3
3
  end
@@ -10,6 +10,7 @@ require_relative 'endpoints/auth'
10
10
  require_relative 'endpoints/bots'
11
11
  require_relative 'endpoints/channels'
12
12
  require_relative 'endpoints/chat'
13
+ require_relative 'endpoints/chat_scheduledMessages'
13
14
  require_relative 'endpoints/conversations'
14
15
  require_relative 'endpoints/dialog'
15
16
  require_relative 'endpoints/dnd'
@@ -54,6 +55,7 @@ module Slack
54
55
  include Bots
55
56
  include Channels
56
57
  include Chat
58
+ include ChatScheduledmessages
57
59
  include Conversations
58
60
  include Dialog
59
61
  include Dnd