stream-chat-ruby 3.25.0 → 3.26.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2235f7488f397e1fe12aec380882477dbec095ab982cccc1fd2e5bd59dc82499
4
- data.tar.gz: f3755f2162a2c1fe210485c09b32d78aa167b152b9af29b44c34ce25f5c109ac
3
+ metadata.gz: '019bf5c538cd43fe7a5faef6b980bf00818756ec5cc470d0e61f8e5d610bbf4e'
4
+ data.tar.gz: 389642e627468500058a1aa007dae4a9b2bda9bcda2e73e5fd30218730e74ce3
5
5
  SHA512:
6
- metadata.gz: 216d4b854c56b4ef619ce3ecaf03ec8a88fb0304b53a60da2868cc38316b40ada6cffe2aafdb41b1580de7b90b83c6f32cff04734eac7e965db789b02a6d2c2b
7
- data.tar.gz: 9d5bbf7426bd8dde38de6ea03515f8a6afe9b07313287717c1bade1156808ccced56524bc97af6ca02afef18b975036950dcf6e6e1e27fc45dac3c9021dbd6fe
6
+ metadata.gz: c2cbc7e4c1f481fefea8d06ee3a7e4e8c3c0c8e26a068b0754574588c0d65ce262886ffe3ebcee86196085df7d520ad4b1f6234c8b87351ba57d61e8d29132e2
7
+ data.tar.gz: 92abc3b3c173377ac3d5bc19957221f055fdfb15eabc38d8a64ef302be883b3441db8bbf22da87b937dbbb88f726344430bc5e6a8b2376f9a0f1c90734ff7d0a
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --profile 25
data/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [3.26.2](https://github.com/GetStream/stream-chat-ruby/compare/v3.26.1...v3.26.2) (2026-06-30)
6
+
7
+
8
+ ### Features
9
+
10
+ * **webhooks:** verify_and_parse_* API for compressed payloads (CHA-3071) ([#211](https://github.com/GetStream/stream-chat-ruby/issues/211)) ([dd76c8f](https://github.com/GetStream/stream-chat-ruby/commit/dd76c8f39d0fae98c3d2c331635e9369b40e3696))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * guard StreamAPIException against empty and non-integer error bodies ([#217](https://github.com/GetStream/stream-chat-ruby/issues/217)) ([415f6c0](https://github.com/GetStream/stream-chat-ruby/commit/415f6c0bd7b51b87631c5a1e8f821e50a5def2ec))
16
+
17
+
18
+ ### Other
19
+
20
+ * before-message-send hook attempt timeout (CHA-3267) ([#216](https://github.com/GetStream/stream-chat-ruby/issues/216)) ([e721776](https://github.com/GetStream/stream-chat-ruby/commit/e721776376c064b434485bd4cb4188762f9f3c2b)), closes [GetStream/chat#13541](https://github.com/GetStream/chat/issues/13541)
21
+ * **spec:** clean fellowship sediment + retry commands eventually ([#212](https://github.com/GetStream/stream-chat-ruby/issues/212)) ([f3c8f5b](https://github.com/GetStream/stream-chat-ruby/commit/f3c8f5be841f95c2e0e399d3d180d7565d80fd6a))
22
+
23
+ ### [3.26.1](https://github.com/GetStream/stream-chat-ruby/compare/v3.25.0...v3.26.1) (2026-04-29)
24
+
25
+
26
+ ### Features
27
+
28
+ * add delete_reactions support for ban user ([#206](https://github.com/GetStream/stream-chat-ruby/issues/206)) ([c876050](https://github.com/GetStream/stream-chat-ruby/commit/c876050936b3f4727002707e94db872c91a6114c))
29
+
30
+
31
+ ### Other
32
+
33
+ * relax jwt dependency to allow v3 ([#208](https://github.com/GetStream/stream-chat-ruby/issues/208)) ([8305806](https://github.com/GetStream/stream-chat-ruby/commit/8305806127859540bbcd936ccda19ac9c13b9897)), closes [#12716](https://github.com/GetStream/stream-chat-ruby/issues/12716)
34
+
5
35
  ## [3.25.0](https://github.com/GetStream/stream-chat-ruby/compare/v3.24.0...v3.25.0) (2026-03-05)
6
36
 
7
37
 
data/README.md CHANGED
@@ -1,3 +1,9 @@
1
+ > **Note:** [`getstream-ruby`](https://github.com/GetStream/getstream-ruby) is the new full-product SDK for Stream, covering Chat, Video, Moderation, and Feeds with long-term support.
2
+ >
3
+ > **`stream-chat-ruby` is now in maintenance mode.** It will continue to receive critical fixes and requested features, so it is safe to keep using. However, we encourage existing users to migrate when convenient and strongly recommend that new projects start with `getstream-ruby`.
4
+ >
5
+ > Check out the [Migration Guide](https://github.com/GetStream/getstream-ruby/blob/570aaf9b73b27fe01a4abbc1f7c6b639595c304e/docs/migration-from-stream-chat-ruby/README.md) for side-by-side examples covering common use cases.
6
+
1
7
  # Official Ruby SDK for [Stream Chat](https://getstream.io/chat/)
2
8
 
3
9
  [![build](https://github.com/GetStream/stream-chat-ruby/workflows/build/badge.svg)](https://github.com/GetStream/stream-chat-ruby/actions) [![Gem Version](https://badge.fury.io/rb/stream-chat-ruby.svg)](http://badge.fury.io/rb/stream-chat-ruby)
@@ -1,7 +1,11 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'base64'
4
5
  require 'open-uri'
6
+ require 'openssl'
7
+ require 'stringio'
8
+ require 'zlib'
5
9
  require 'faraday'
6
10
  require 'faraday/multipart'
7
11
  require 'faraday/net_http_persistent'
@@ -18,9 +22,10 @@ require 'stream-chat/util'
18
22
  require 'stream-chat/types'
19
23
  require 'stream-chat/moderation'
20
24
  require 'stream-chat/channel_batch_updater'
25
+ require 'stream-chat/webhook'
21
26
 
22
27
  module StreamChat
23
- DEFAULT_BLOCKLIST = 'profanity_en_2020_v1'
28
+ DEFAULT_BLOCKLIST = 'profanity'
24
29
  SOFT_DELETE = 'soft'
25
30
  HARD_DELETE = 'hard'
26
31
 
@@ -698,11 +703,57 @@ module StreamChat
698
703
  get('rate_limits', params: params)
699
704
  end
700
705
 
701
- # Verify the signature added to a webhook event.
706
+ # Verify the signature on a webhook event using the client's API secret.
707
+ #
708
+ # Backward-compatible boolean helper. New integrations should call
709
+ # {#verify_and_parse_webhook} (or the SQS / SNS variants), which also handle
710
+ # gzip payload compression and return the parsed event.
702
711
  sig { params(request_body: String, x_signature: String).returns(T::Boolean) }
703
712
  def verify_webhook(request_body, x_signature)
704
- signature = OpenSSL::HMAC.hexdigest('SHA256', @api_secret, request_body)
705
- signature == x_signature
713
+ StreamChat::Webhook.verify_signature(request_body, x_signature, @api_secret)
714
+ end
715
+
716
+ # Verify and parse an HTTP webhook event.
717
+ #
718
+ # Decompresses `body` when gzipped (detected from the body bytes), verifies
719
+ # the `X-Signature` header against the client's API secret, and returns the
720
+ # parsed event as a `Hash`. Typed event classes are planned for a future
721
+ # release.
722
+ #
723
+ # @param body [String, Array<Integer>] Raw HTTP request body bytes Stream
724
+ # signed.
725
+ # @param signature [String] Value of the `X-Signature` header.
726
+ # @return [Hash] The parsed event.
727
+ # @raise [InvalidWebhookError] When the signature does not match or the
728
+ # gzip envelope is malformed.
729
+ sig do
730
+ params(
731
+ body: T.any(String, T::Array[Integer]),
732
+ signature: String
733
+ ).returns(T::Hash[String, T.untyped])
734
+ end
735
+ def verify_and_parse_webhook(body, signature)
736
+ StreamChat::Webhook.verify_and_parse_webhook(body, signature, @api_secret)
737
+ end
738
+
739
+ # Parse an SQS-delivered webhook event (decode only; Stream does not HMAC-sign SQS bodies).
740
+ #
741
+ # @param message_body [String] SQS message `Body` string.
742
+ # @return [Hash] The parsed event.
743
+ # @raise [InvalidWebhookError] on malformed base64 or gzip envelope
744
+ sig { params(message_body: String).returns(T::Hash[String, T.untyped]) }
745
+ def parse_sqs(message_body)
746
+ StreamChat::Webhook.parse_sqs(message_body)
747
+ end
748
+
749
+ # Parse an SNS-delivered webhook event (unwraps envelope JSON when needed).
750
+ #
751
+ # @param message [String] Raw SNS POST body or pre-extracted Message string.
752
+ # @return [Hash] The parsed event.
753
+ # @raise [InvalidWebhookError] on decode failures
754
+ sig { params(message: String).returns(T::Hash[String, T.untyped]) }
755
+ def parse_sns(message)
756
+ StreamChat::Webhook.parse_sns(message)
706
757
  end
707
758
 
708
759
  # Allows you to send custom events to a connected user.
@@ -739,7 +790,7 @@ module StreamChat
739
790
  # Returns all blocklists.
740
791
  #
741
792
  # A Block List is a list of words that you can use to moderate chat messages. Stream Chat
742
- # comes with a built-in Block List called profanity_en_2020_v1 which contains over a thousand
793
+ # comes with a built-in Block List called profanity which contains over a thousand
743
794
  # of the most common profane words.
744
795
  # You can manage your own block lists via the Stream dashboard or APIs to a manage
745
796
  # blocklists and configure your channel types to use them.
@@ -751,7 +802,7 @@ module StreamChat
751
802
  # Returns a blocklist.
752
803
  #
753
804
  # A Block List is a list of words that you can use to moderate chat messages. Stream Chat
754
- # comes with a built-in Block List called profanity_en_2020_v1 which contains over a thousand
805
+ # comes with a built-in Block List called profanity which contains over a thousand
755
806
  # of the most common profane words.
756
807
  # You can manage your own block lists via the Stream dashboard or APIs to a manage
757
808
  # blocklists and configure your channel types to use them.
@@ -763,7 +814,7 @@ module StreamChat
763
814
  # Creates a blocklist.
764
815
  #
765
816
  # A Block List is a list of words that you can use to moderate chat messages. Stream Chat
766
- # comes with a built-in Block List called profanity_en_2020_v1 which contains over a thousand
817
+ # comes with a built-in Block List called profanity which contains over a thousand
767
818
  # of the most common profane words.
768
819
  # You can manage your own block lists via the Stream dashboard or APIs to a manage
769
820
  # blocklists and configure your channel types to use them.
@@ -775,7 +826,7 @@ module StreamChat
775
826
  # Updates a blocklist.
776
827
  #
777
828
  # A Block List is a list of words that you can use to moderate chat messages. Stream Chat
778
- # comes with a built-in Block List called profanity_en_2020_v1 which contains over a thousand
829
+ # comes with a built-in Block List called profanity which contains over a thousand
779
830
  # of the most common profane words.
780
831
  # You can manage your own block lists via the Stream dashboard or APIs to a manage
781
832
  # blocklists and configure your channel types to use them.
@@ -787,7 +838,7 @@ module StreamChat
787
838
  # Deletes a blocklist.
788
839
  #
789
840
  # A Block List is a list of words that you can use to moderate chat messages. Stream Chat
790
- # comes with a built-in Block List called profanity_en_2020_v1 which contains over a thousand
841
+ # comes with a built-in Block List called profanity which contains over a thousand
791
842
  # of the most common profane words.
792
843
  # You can manage your own block lists via the Stream dashboard or APIs to a manage
793
844
  # blocklists and configure your channel types to use them.
@@ -21,14 +21,28 @@ module StreamChat
21
21
  def initialize(response)
22
22
  super()
23
23
  @response = response
24
+
25
+ # Seed defaults first so the typed readers never return nil. A 5xx can
26
+ # arrive with an empty or non-JSON body (e.g. a load balancer emitting a
27
+ # bare 503), and an error envelope may omit "code"/"message" or send a
28
+ # non-integer "code". Without these defaults, reading error_code on such
29
+ # an exception raised a Sorbet TypeError that masked the real HTTP error.
30
+ @json_response = T.let(false, T::Boolean)
31
+ @error_code = T.let(-1, Integer)
32
+ @error_message = T.let('unknown', String)
33
+
24
34
  begin
25
35
  parsed_response = JSON.parse(response.body)
26
- @json_response = T.let(true, T::Boolean)
27
- @error_code = T.let(parsed_response.fetch('code', 'unknown'), Integer)
28
- @error_message = T.let(parsed_response.fetch('message', 'unknown'), String)
29
36
  rescue JSON::ParserError
30
- @json_response = false
37
+ return
31
38
  end
39
+ return unless parsed_response.is_a?(Hash)
40
+
41
+ @json_response = true
42
+ code = parsed_response['code']
43
+ @error_code = code if code.is_a?(Integer)
44
+ msg = parsed_response['message']
45
+ @error_message = msg if msg.is_a?(String)
32
46
  end
33
47
 
34
48
  sig { returns(String) }
@@ -47,4 +61,15 @@ module StreamChat
47
61
  end
48
62
 
49
63
  class StreamChannelException < StandardError; end
64
+
65
+ # Raised by webhook verify/parse helpers when the HMAC does not match or a
66
+ # gzip/base64/JSON envelope cannot be decoded. The message identifies which
67
+ # failure mode fired; the class-level constants below are the canonical
68
+ # strings for callers that prefer exact-match filtering over substring.
69
+ class InvalidWebhookError < StandardError
70
+ SIGNATURE_MISMATCH = 'signature mismatch'
71
+ INVALID_BASE64 = 'invalid base64 encoding'
72
+ GZIP_FAILED = 'gzip decompression failed'
73
+ INVALID_JSON = 'invalid JSON payload'
74
+ end
50
75
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module StreamChat
5
- VERSION = '3.25.0'
5
+ VERSION = '3.26.2'
6
6
  end
@@ -0,0 +1,206 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'base64'
5
+ require 'json'
6
+ require 'openssl'
7
+ require 'sorbet-runtime'
8
+ require 'stringio'
9
+ require 'zlib'
10
+
11
+ require 'stream-chat/errors'
12
+
13
+ module StreamChat
14
+ # Stateless helpers implementing the cross-SDK webhook contract documented at
15
+ # https://getstream.io/chat/docs/node/webhooks_overview/.
16
+ #
17
+ # The composite functions (`verify_and_parse_webhook`, `parse_sqs`,
18
+ # `parse_sns`) are the recommended entry points. The primitives
19
+ # they compose (`gunzip_payload`, `decode_sqs_payload`, `decode_sns_payload`,
20
+ # `verify_signature`, `parse_event`) are exposed so callers can build custom
21
+ # flows or run individual steps in isolation.
22
+ #
23
+ # The Ruby SDK currently returns the parsed JSON as a `Hash`; typed event
24
+ # classes will land in a future release.
25
+ module Webhook # rubocop:disable Metrics/ModuleLength
26
+ extend T::Sig
27
+
28
+ GZIP_MAGIC = T.let("\x1f\x8b".b.freeze, String)
29
+
30
+ # Coerces the webhook body into a binary `String` regardless of whether
31
+ # the caller hands us a `String` (HTTP `request.raw_post`) or an array of
32
+ # bytes (which is what some Ruby SQS clients yield when the message body
33
+ # is binary safe).
34
+ sig { params(body: T.any(String, T::Array[Integer])).returns(String) }
35
+ def self.normalize_body(body)
36
+ raw =
37
+ if body.is_a?(Array)
38
+ body.pack('C*')
39
+ else
40
+ String.new(body)
41
+ end
42
+ raw.force_encoding(Encoding::ASCII_8BIT)
43
+ end
44
+
45
+ # Returns `body` unchanged unless it starts with the gzip magic
46
+ # (`1f 8b`, per RFC 1952), in which case the gzip stream is inflated and
47
+ # the decompressed bytes are returned.
48
+ #
49
+ # Magic-byte detection (rather than relying on a header) keeps the same
50
+ # handler correct when middleware - Rack, Rails - auto-decompresses the
51
+ # request before your code sees it.
52
+ sig { params(body: T.any(String, T::Array[Integer])).returns(String) }
53
+ def self.gunzip_payload(body)
54
+ raw = normalize_body(body)
55
+ return raw unless raw.start_with?(GZIP_MAGIC)
56
+
57
+ begin
58
+ Zlib::GzipReader.new(StringIO.new(raw)).read.force_encoding(Encoding::ASCII_8BIT)
59
+ rescue Zlib::Error
60
+ raise InvalidWebhookError, InvalidWebhookError::GZIP_FAILED
61
+ end
62
+ end
63
+
64
+ # Reverses the SQS firehose envelope: the message `Body` is base64-decoded
65
+ # and, when the result begins with the gzip magic, gzip-decompressed. The
66
+ # same call works whether or not Stream is currently compressing payloads.
67
+ sig { params(body: String).returns(String) }
68
+ def self.decode_sqs_payload(body)
69
+ decoded =
70
+ begin
71
+ Base64.strict_decode64(body)
72
+ rescue ArgumentError
73
+ raise InvalidWebhookError, InvalidWebhookError::INVALID_BASE64
74
+ end
75
+ gunzip_payload(decoded)
76
+ end
77
+
78
+ # Reverses an SNS HTTP notification envelope. When `notification_body` is a
79
+ # JSON envelope (`{"Type":"Notification","Message":"..."}`), the inner
80
+ # `Message` field is extracted and run through the SQS pipeline
81
+ # (base64-decode, then gzip-if-magic). When the input is not a JSON
82
+ # envelope it is treated as the already-extracted `Message` string, so
83
+ # call sites that pre-unwrap continue to work.
84
+ sig { params(notification_body: String).returns(String) }
85
+ def self.decode_sns_payload(notification_body)
86
+ inner = extract_sns_message(notification_body)
87
+ decode_sqs_payload(inner.nil? ? notification_body : inner)
88
+ end
89
+
90
+ sig { params(notification_body: String).returns(T.nilable(String)) }
91
+ def self.extract_sns_message(notification_body)
92
+ trimmed = notification_body.to_s.lstrip
93
+ return nil unless trimmed.start_with?('{')
94
+
95
+ begin
96
+ parsed = JSON.parse(trimmed)
97
+ rescue JSON::ParserError
98
+ return nil
99
+ end
100
+ return nil unless parsed.is_a?(Hash)
101
+
102
+ message = parsed['Message']
103
+ message.is_a?(String) ? message : nil
104
+ end
105
+ private_class_method :extract_sns_message
106
+
107
+ # Constant-time HMAC-SHA256 verification of `signature` against the digest
108
+ # of `body` keyed by `secret`.
109
+ #
110
+ # The signature is always computed over the **uncompressed** JSON bytes,
111
+ # so callers that decoded a gzipped or base64-wrapped payload must pass
112
+ # the inflated bytes here.
113
+ sig do
114
+ params(
115
+ body: T.any(String, T::Array[Integer]),
116
+ signature: String,
117
+ secret: String
118
+ ).returns(T::Boolean)
119
+ end
120
+ def self.verify_signature(body, signature, secret)
121
+ raw = normalize_body(body)
122
+ expected = OpenSSL::HMAC.hexdigest('SHA256', secret, raw)
123
+ constant_time_equal?(expected, signature)
124
+ end
125
+
126
+ # Parse a JSON-encoded webhook event into a `Hash`.
127
+ #
128
+ # The Ruby SDK currently returns the parsed JSON as a `Hash`; typed event
129
+ # classes will land in a future release. The function name matches the
130
+ # documented primitive so callers can swap in a typed parser later without
131
+ # changing call sites.
132
+ sig { params(payload: String).returns(T::Hash[String, T.untyped]) }
133
+ def self.parse_event(payload)
134
+ result =
135
+ begin
136
+ JSON.parse(payload)
137
+ rescue JSON::ParserError
138
+ raise InvalidWebhookError, InvalidWebhookError::INVALID_JSON
139
+ end
140
+ raise InvalidWebhookError, InvalidWebhookError::INVALID_JSON unless result.is_a?(Hash)
141
+
142
+ result
143
+ end
144
+
145
+ sig do
146
+ params(
147
+ payload: String,
148
+ signature: String,
149
+ secret: String
150
+ ).returns(T::Hash[String, T.untyped])
151
+ end
152
+ def self.verify_and_parse_internal(payload, signature, secret)
153
+ raise InvalidWebhookError, InvalidWebhookError::SIGNATURE_MISMATCH unless verify_signature(payload, signature, secret)
154
+
155
+ parse_event(payload)
156
+ end
157
+ private_class_method :verify_and_parse_internal
158
+
159
+ # Decompress `body` when gzipped, verify the HMAC `signature`, and return
160
+ # the parsed event.
161
+ sig do
162
+ params(
163
+ body: T.any(String, T::Array[Integer]),
164
+ signature: String,
165
+ secret: String
166
+ ).returns(T::Hash[String, T.untyped])
167
+ end
168
+ def self.verify_and_parse_webhook(body, signature, secret)
169
+ verify_and_parse_internal(gunzip_payload(body), signature, secret)
170
+ end
171
+
172
+ # Decode the SQS `Body` (base64, then gzip-if-magic) and return the parsed
173
+ # event JSON. Stream does not ship an application-level signature on SQS payloads.
174
+ sig { params(message_body: String).returns(T::Hash[String, T.untyped]) }
175
+ def self.parse_sqs(message_body)
176
+ parse_event(decode_sqs_payload(message_body))
177
+ end
178
+
179
+ # Decode SNS (unwrap envelope when needed) and return the parsed event JSON.
180
+ sig { params(message: String).returns(T::Hash[String, T.untyped]) }
181
+ def self.parse_sns(message)
182
+ parse_event(decode_sns_payload(message))
183
+ end
184
+
185
+ # Timing-safe equality check used to compare the locally computed HMAC
186
+ # with the `X-Signature` header. Prefers the OpenSSL primitive when the
187
+ # Ruby build exposes it, otherwise falls back to a manual byte XOR loop
188
+ # that does not short-circuit on the first mismatch.
189
+ sig { params(left: String, right: String).returns(T::Boolean) }
190
+ def self.constant_time_equal?(left, right)
191
+ a = left.b
192
+ b = right.b
193
+ return false unless a.bytesize == b.bytesize
194
+
195
+ if OpenSSL.respond_to?(:fixed_length_secure_compare)
196
+ OpenSSL.fixed_length_secure_compare(a, b)
197
+ else
198
+ a_bytes = a.bytes
199
+ b_bytes = b.bytes
200
+ diff = 0
201
+ a_bytes.each_with_index { |byte, i| diff |= byte ^ b_bytes[i] }
202
+ diff.zero?
203
+ end
204
+ end
205
+ end
206
+ end
data/stream-chat.gemspec CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |gem|
14
14
  gem.homepage = 'http://github.com/GetStream/stream-chat-ruby'
15
15
  gem.authors = ['getstream.io']
16
16
  gem.files = Dir.chdir(File.expand_path(__dir__)) do
17
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|sorbet|spec|\.github|scripts|assets)/}) }
17
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|sorbet|spec|\.github|scripts|assets|docs)/}) }
18
18
  end
19
19
  gem.required_ruby_version = '>=3.0.0'
20
20
  gem.metadata = {
@@ -29,7 +29,7 @@ Gem::Specification.new do |gem|
29
29
  gem.add_dependency 'faraday', '~> 2.12'
30
30
  gem.add_dependency 'faraday-multipart', '~> 1.1'
31
31
  gem.add_dependency 'faraday-net_http_persistent', '~> 2.3'
32
- gem.add_dependency 'jwt', '~> 2.10'
32
+ gem.add_dependency 'jwt', '>= 2.10', '< 4'
33
33
  gem.add_dependency 'net-http-persistent', '~> 4.0'
34
34
  gem.add_dependency 'sorbet-runtime', '>= 0.5.11820', '< 1'
35
35
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stream-chat-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.25.0
4
+ version: 3.26.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - getstream.io
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-05 00:00:00.000000000 Z
11
+ date: 2026-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -56,16 +56,22 @@ dependencies:
56
56
  name: jwt
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '2.10'
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: '4'
62
65
  type: :runtime
63
66
  prerelease: false
64
67
  version_requirements: !ruby/object:Gem::Requirement
65
68
  requirements:
66
- - - "~>"
69
+ - - ">="
67
70
  - !ruby/object:Gem::Version
68
71
  version: '2.10'
72
+ - - "<"
73
+ - !ruby/object:Gem::Version
74
+ version: '4'
69
75
  - !ruby/object:Gem::Dependency
70
76
  name: net-http-persistent
71
77
  requirement: !ruby/object:Gem::Requirement
@@ -107,6 +113,7 @@ extensions: []
107
113
  extra_rdoc_files: []
108
114
  files:
109
115
  - ".gitignore"
116
+ - ".rspec"
110
117
  - ".rubocop.yml"
111
118
  - ".versionrc.js"
112
119
  - CHANGELOG.md
@@ -130,6 +137,7 @@ files:
130
137
  - lib/stream-chat/types.rb
131
138
  - lib/stream-chat/util.rb
132
139
  - lib/stream-chat/version.rb
140
+ - lib/stream-chat/webhook.rb
133
141
  - stream-chat.gemspec
134
142
  homepage: http://github.com/GetStream/stream-chat-ruby
135
143
  licenses: []