chat_sdk-slack 0.9.0 → 1.0.0

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chat_sdk/slack/adapter.rb +124 -25
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c350492bf0bf3f0398f8146794cfd21c57218ac69c0eaa2fca06123e1ebd91cd
4
- data.tar.gz: a538ab5f51da90868d3fd4b018ba33d98b97d703d952844909cdc614e5a08e10
3
+ metadata.gz: 17c05a1b3391a1d2f8a4a248fc2ee2e4048b5fbdf608cfd10becba0239f76cde
4
+ data.tar.gz: a5be2b1d3ac3738c28d92e53bc3209b4d565ceb3f08635e469c65e606e43cdc7
5
5
  SHA512:
6
- metadata.gz: e5b9e7c785c898a7826562ce527b3a9dc5753adf42459f6248e3e0f363e5ec5478af2a959f8b2d30978ff8e7234c63838d8ee96a68172ac20c647f512073d61a
7
- data.tar.gz: 0b78d5335690d409deedc6a69f45a57093ab559a35a0e20f1ee4518b883afc924f5919a4b2f46e1b50dc12fd443ae7eb9547c3c268daff1928c6db37244f1902
6
+ metadata.gz: f405de4643b259ed5477d52f2f6b733b4af2fb12eeb184a019ac9ac98d0aa982a8a156752f48aae95989e3ff6a1d5bf236255056e2e75c0d2ea58f7f886a1320
7
+ data.tar.gz: 99a0b0a0e7b9d85f17b203c483252dadd24a82269cce6b8776fe09153bc99215372c6275975dbef80c810a46aba34752efc85946546107cb21927edd11350d4d
@@ -8,22 +8,92 @@ module ChatSDK
8
8
  :streaming_edit, :threads, :direct_messages, :message_history,
9
9
  :scheduled_messages
10
10
 
11
- attr_reader :client
12
-
13
- def initialize(bot_token: nil, signing_secret: nil)
11
+ def initialize(bot_token: nil, signing_secret: nil,
12
+ client_id: nil, client_secret: nil)
14
13
  @bot_token = bot_token || ENV["SLACK_BOT_TOKEN"]
15
14
  @signing_secret = signing_secret || ENV["SLACK_SIGNING_SECRET"]
15
+ @client_id = client_id || ENV["SLACK_CLIENT_ID"]
16
+ @client_secret = client_secret || ENV["SLACK_CLIENT_SECRET"]
16
17
 
17
- raise ChatSDK::ConfigurationError, "Slack bot_token required" unless @bot_token
18
+ unless @bot_token || @client_id
19
+ raise ChatSDK::ConfigurationError, "Slack bot_token or client_id required"
20
+ end
18
21
  raise ChatSDK::ConfigurationError, "Slack signing_secret required" unless @signing_secret
19
22
 
20
- ::Slack.configure do |config|
21
- config.token = @bot_token
23
+ if @bot_token
24
+ ::Slack.configure do |config|
25
+ config.token = @bot_token
26
+ end
27
+ @client = ::Slack::Web::Client.new(token: @bot_token)
22
28
  end
23
29
 
24
- @client = ::Slack::Web::Client.new(token: @bot_token)
25
30
  @renderer = BlockKitRenderer.new
26
31
  @modal_renderer = ModalRenderer.new
32
+ @team_clients = {}
33
+ end
34
+
35
+ # Returns the per-request client (multi-workspace) or the static client (single-workspace).
36
+ def client
37
+ ::Thread.current[:chat_sdk_slack_client] || @client
38
+ end
39
+
40
+ # Inject state store after initialization (e.g., from Chat instance).
41
+ def set_state(state)
42
+ @state = state
43
+ end
44
+
45
+ # --- Multi-workspace installation management ---
46
+
47
+ def set_installation(team_id, bot_token:, bot_user_id: nil, team_name: nil)
48
+ raise ChatSDK::ConfigurationError, "Multi-workspace mode requires state" unless @state
49
+
50
+ @team_clients.delete(team_id)
51
+ @state.set(installation_key(team_id), {
52
+ "bot_token" => bot_token,
53
+ "bot_user_id" => bot_user_id,
54
+ "team_name" => team_name
55
+ })
56
+ end
57
+
58
+ def get_installation(team_id)
59
+ return nil unless @state
60
+
61
+ @state.get(installation_key(team_id))
62
+ end
63
+
64
+ def delete_installation(team_id)
65
+ @team_clients.delete(team_id)
66
+ return unless @state
67
+
68
+ @state.delete(installation_key(team_id))
69
+ end
70
+
71
+ def handle_oauth_callback(code:, redirect_uri: nil)
72
+ raise ChatSDK::ConfigurationError, "client_id required for OAuth" unless @client_id
73
+
74
+ temp_client = ::Slack::Web::Client.new
75
+ params = {
76
+ client_id: @client_id,
77
+ client_secret: @client_secret,
78
+ code: code
79
+ }
80
+ params[:redirect_uri] = redirect_uri if redirect_uri
81
+
82
+ result = temp_client.oauth_v2_access(**params)
83
+
84
+ team_id = result["team"]["id"]
85
+ installation = {
86
+ "bot_token" => result["access_token"],
87
+ "bot_user_id" => result["bot_user_id"],
88
+ "team_name" => result["team"]["name"]
89
+ }
90
+
91
+ set_installation(team_id,
92
+ bot_token: installation["bot_token"],
93
+ bot_user_id: installation["bot_user_id"],
94
+ team_name: installation["team_name"])
95
+
96
+ {team_id: team_id, installation: installation}
27
97
  end
28
98
 
29
99
  def name
@@ -74,6 +144,14 @@ module ChatSDK
74
144
  parsed = parse_body(body, rack_request.content_type)
75
145
  return [] unless parsed
76
146
 
147
+ # Multi-workspace: resolve per-team client from payload
148
+ # Clear any stale client from a previous request on this thread (Puma thread pool safety)
149
+ if @client_id
150
+ ::Thread.current[:chat_sdk_slack_client] = nil
151
+ team_id = parsed["team_id"] || parsed.dig("team", "id")
152
+ resolve_team_client(team_id) if team_id
153
+ end
154
+
77
155
  EventParser.parse(parsed)
78
156
  end
79
157
 
@@ -85,7 +163,7 @@ module ChatSDK
85
163
 
86
164
  apply_message_params(params, msg)
87
165
 
88
- result = @client.chat_postMessage(**params)
166
+ result = client.chat_postMessage(**params)
89
167
 
90
168
  ChatSDK::Message.new(
91
169
  id: result["ts"],
@@ -104,11 +182,11 @@ module ChatSDK
104
182
 
105
183
  apply_message_params(params, msg)
106
184
 
107
- @client.chat_update(**params)
185
+ client.chat_update(**params)
108
186
  end
109
187
 
110
188
  def delete_message(channel_id:, message_id:)
111
- @client.chat_delete(channel: channel_id, ts: message_id)
189
+ client.chat_delete(channel: channel_id, ts: message_id)
112
190
  end
113
191
 
114
192
  def post_ephemeral(channel_id:, user_id:, message:, thread_id: nil)
@@ -118,26 +196,26 @@ module ChatSDK
118
196
 
119
197
  apply_message_params(params, msg)
120
198
 
121
- @client.chat_postEphemeral(**params)
199
+ client.chat_postEphemeral(**params)
122
200
  end
123
201
 
124
202
  def upload_file(channel_id:, io:, filename:, thread_id: nil, comment: nil)
125
203
  params = {channels: channel_id, file: Faraday::Multipart::FilePart.new(io, nil, filename)}
126
204
  params[:thread_ts] = thread_id if thread_id
127
205
  params[:initial_comment] = comment if comment
128
- @client.files_upload(**params)
206
+ client.files_upload(**params)
129
207
  end
130
208
 
131
209
  def add_reaction(channel_id:, message_id:, emoji:)
132
- @client.reactions_add(channel: channel_id, timestamp: message_id, name: emoji)
210
+ client.reactions_add(channel: channel_id, timestamp: message_id, name: emoji)
133
211
  end
134
212
 
135
213
  def remove_reaction(channel_id:, message_id:, emoji:)
136
- @client.reactions_remove(channel: channel_id, timestamp: message_id, name: emoji)
214
+ client.reactions_remove(channel: channel_id, timestamp: message_id, name: emoji)
137
215
  end
138
216
 
139
217
  def get_user(user_id)
140
- result = @client.users_info(user: user_id)
218
+ result = client.users_info(user: user_id)
141
219
  return nil unless result&.dig("user", "id")
142
220
 
143
221
  ChatSDK::Author.new(
@@ -150,7 +228,7 @@ module ChatSDK
150
228
  end
151
229
 
152
230
  def open_dm(user_id)
153
- result = @client.conversations_open(users: user_id)
231
+ result = client.conversations_open(users: user_id)
154
232
  result["channel"]["id"]
155
233
  end
156
234
 
@@ -161,7 +239,7 @@ module ChatSDK
161
239
  params = {channel: channel_id, text: text, post_at: post_at.to_i}
162
240
  params[:thread_ts] = thread_id if thread_id
163
241
 
164
- result = @client.chat_scheduleMessage(**params)
242
+ result = client.chat_scheduleMessage(**params)
165
243
 
166
244
  ChatSDK::Message.new(
167
245
  id: result["scheduled_message_id"],
@@ -176,9 +254,9 @@ module ChatSDK
176
254
 
177
255
  def fetch_messages(channel_id:, thread_id: nil, cursor: nil, limit: 50)
178
256
  result = if thread_id
179
- @client.conversations_replies(channel: channel_id, ts: thread_id, cursor: cursor, limit: limit)
257
+ client.conversations_replies(channel: channel_id, ts: thread_id, cursor: cursor, limit: limit)
180
258
  else
181
- @client.conversations_history(channel: channel_id, cursor: cursor, limit: limit)
259
+ client.conversations_history(channel: channel_id, cursor: cursor, limit: limit)
182
260
  end
183
261
  messages = result["messages"].map { |m| parse_slack_message(m, channel_id) }
184
262
  [messages, result["response_metadata"]&.dig("next_cursor")]
@@ -186,15 +264,15 @@ module ChatSDK
186
264
 
187
265
  def open_modal(trigger_id:, modal:)
188
266
  view = @modal_renderer.render(modal)
189
- @client.views_open(trigger_id: trigger_id, view: view)
267
+ client.views_open(trigger_id: trigger_id, view: view)
190
268
  end
191
269
 
192
270
  def publish_home_view(user_id:, view:)
193
- @client.views_publish(user_id: user_id, view: view)
271
+ client.views_publish(user_id: user_id, view: view)
194
272
  end
195
273
 
196
274
  def set_suggested_prompts(channel_id:, thread_id:, prompts:)
197
- @client.assistant_threads_setSuggestedPrompts(
275
+ client.assistant_threads_setSuggestedPrompts(
198
276
  channel_id: channel_id,
199
277
  thread_ts: thread_id,
200
278
  prompts: prompts
@@ -202,7 +280,7 @@ module ChatSDK
202
280
  end
203
281
 
204
282
  def set_assistant_status(channel_id:, thread_id:, status:)
205
- @client.assistant_threads_setStatus(
283
+ client.assistant_threads_setStatus(
206
284
  channel_id: channel_id,
207
285
  thread_ts: thread_id,
208
286
  status: status
@@ -210,7 +288,7 @@ module ChatSDK
210
288
  end
211
289
 
212
290
  def set_assistant_title(channel_id:, thread_id:, title:)
213
- @client.assistant_threads_setTitle(
291
+ client.assistant_threads_setTitle(
214
292
  channel_id: channel_id,
215
293
  thread_ts: thread_id,
216
294
  title: title
@@ -234,7 +312,7 @@ module ChatSDK
234
312
  app_token ||= ENV["SLACK_APP_TOKEN"]
235
313
  raise ChatSDK::ConfigurationError, "Slack app_token required for socket mode" unless app_token
236
314
 
237
- socket = SocketMode.new(app_token: app_token, bot_client: @client)
315
+ socket = SocketMode.new(app_token: app_token, bot_client: client)
238
316
  socket.start(&block)
239
317
  end
240
318
 
@@ -252,6 +330,27 @@ module ChatSDK
252
330
 
253
331
  private
254
332
 
333
+ def resolve_team_client(team_id)
334
+ return unless @client_id && @state
335
+
336
+ cached = @team_clients[team_id]
337
+ if cached
338
+ ::Thread.current[:chat_sdk_slack_client] = cached
339
+ return
340
+ end
341
+
342
+ installation = get_installation(team_id)
343
+ return unless installation
344
+
345
+ new_client = ::Slack::Web::Client.new(token: installation["bot_token"])
346
+ @team_clients[team_id] = new_client
347
+ ::Thread.current[:chat_sdk_slack_client] = new_client
348
+ end
349
+
350
+ def installation_key(team_id)
351
+ "slack:installation:#{team_id}"
352
+ end
353
+
255
354
  def apply_message_params(params, msg)
256
355
  if msg.card?
257
356
  params[:blocks] = @renderer.render(msg.card)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chat_sdk-slack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Quentin Rousseau
@@ -13,14 +13,14 @@ dependencies:
13
13
  name: chat_sdk
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: '0.1'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - "~>"
23
+ - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.1'
26
26
  - !ruby/object:Gem::Dependency