getstream-ruby 6.1.1 → 7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f8a1dd8b4379c6af81682b352877f55c651f081265e87cc5ec08a477b19c91d
4
- data.tar.gz: 7b46eb14dac0d23a0f76223244125ec4ff54ee2130f5b554fae68e363ac7ac00
3
+ metadata.gz: 725fbf9f73fef4a9c33f241c9911d226222041e245084e2dbca69cbc0418eaef
4
+ data.tar.gz: 637cc6dd6bc256959b15ae6a98e62f1131f3bfdf6183e8c3f64acd6e6cfc33d2
5
5
  SHA512:
6
- metadata.gz: 4b88721605b6c6cc61769dee8c0940ad9b351676a7ed77c0ac9799f898dc3c297534dff333710617ebd4de527387b3273b66a31c7b49808d18f08f908b91d172
7
- data.tar.gz: 58ccb9e4f16e22cce7dfff31c915f9c2c4c9be2a3f5a3e14ad6c31182f1ebb4da3cdecf63321ca19920abe15be0ce1a669e248ceaa80aab99c4a3413150669ea
6
+ metadata.gz: 21e076ab8023229dbdda7feff3405716929d315aa96c0d0478df09c3f7b27db8a6d24e9ea399782e16cc52f355807e33adcebe558e2c040acfcb66875b8d9ed8
7
+ data.tar.gz: 96f2ce649e6c7f40e1651696f39c7e3612ea822e93351c3a86f0ce352b854f7a4ac12e7216f95df9933754da6a107dcb2ac6aa85892c9bc453f437c5cbf252a3
data/README.md CHANGED
@@ -350,18 +350,20 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/getstr
350
350
 
351
351
  ## Release Process
352
352
 
353
- Releases use two paths:
353
+ Releases use two paths, both handled by `.github/workflows/release.yml`:
354
354
 
355
- - Default: automatic release when a PR is merged to `main`/`master`.
356
- - Fallback: manual release using `.github/workflows/manual-release.yml` (admin use only).
355
+ - **Default**: automatic release when a PR is merged to `main`/`master`. The PR title (and body) drives the semver bump.
356
+ - **Fallback**: manual release via the `Release` workflow's `workflow_dispatch` (admin use). Select a `version_bump` (`patch`/`minor`/`major`). `use_current_version=true` skips the bump and publishes whatever is already in `lib/getstream_ruby/version.rb`.
357
357
 
358
- Automatic semver bump rules are based on merged PR title/body:
358
+ Automatic semver bump rules:
359
359
 
360
360
  - `feat:` -> minor
361
361
  - `fix:` (or `bug:`) -> patch
362
- - `feat!:` or `BREAKING CHANGE` in PR body -> major
362
+ - `feat!:`, `<type>(scope)!:`, or `BREAKING CHANGE` in the PR body/title -> major
363
363
 
364
- PRs with other prefixes do not trigger a release.
364
+ PRs with any other prefix do not trigger a release.
365
+
366
+ The release pipeline runs lint (`make format-check && make lint && make security`), the unit suite (`make test`), and all three integration suites (chat, feed, video) on the merged commit before publishing to RubyGems. Each step is idempotent; a failed run can be re-dispatched from the Actions UI.
365
367
 
366
368
  ## License
367
369
 
@@ -13,6 +13,7 @@ require_relative 'generated/chat_client'
13
13
  require_relative 'generated/video_client'
14
14
  require_relative 'extensions/moderation_extensions'
15
15
  require_relative 'generated/feed'
16
+ require_relative 'generated/webhook'
16
17
  require_relative 'stream_response'
17
18
 
18
19
  module GetStreamRuby
@@ -76,6 +77,52 @@ module GetStreamRuby
76
77
  GetStream::Generated::Feed.new(self, feed_group_id, feed_id)
77
78
  end
78
79
 
80
+ # Verify a webhook signature using this client's API secret (CHA-2961).
81
+ #
82
+ # Convenience wrapper around StreamChat::Webhook.verify_signature that
83
+ # supplies the secret automatically. The module-level method is still
84
+ # available for callers that need to verify with an arbitrary secret.
85
+ #
86
+ # @param body [String] The raw request body (already-decompressed)
87
+ # @param signature [String] The signature from the X-Signature header
88
+ # @return [Boolean] true if the signature is valid, false otherwise
89
+ def verify_signature(body, signature)
90
+ StreamChat::Webhook.verify_signature(body, signature, @configuration.api_secret)
91
+ end
92
+
93
+ # Verify and parse a webhook payload in one call, using this client's API
94
+ # secret (CHA-2961).
95
+ #
96
+ # Handles gzip-compressed bodies transparently. Raises
97
+ # StreamChat::Webhook::InvalidWebhookError on signature mismatch or parse
98
+ # failures; distinguish failure modes via the message substring.
99
+ #
100
+ # @param body [String] raw request body (possibly gzip-compressed)
101
+ # @param signature [String] X-Signature header value
102
+ # @return [Object] the typed event class instance or
103
+ # StreamChat::Webhook::UnknownEvent
104
+ # @raise [StreamChat::Webhook::InvalidWebhookError]
105
+ def verify_and_parse_webhook(body, signature)
106
+ StreamChat::Webhook.verify_and_parse_webhook(body, signature, @configuration.api_secret)
107
+ end
108
+
109
+ # Decode + parse a Stream-delivered SQS message body.
110
+ #
111
+ # Convenience wrapper around StreamChat::Webhook.parse_sqs. No signature is
112
+ # required; SQS deliveries are authenticated via AWS IAM.
113
+ def parse_sqs(message_body)
114
+ StreamChat::Webhook.parse_sqs(message_body)
115
+ end
116
+
117
+ # Decode + parse a Stream-delivered SNS notification body.
118
+ #
119
+ # Accepts either the raw SNS HTTP envelope JSON or the pre-extracted Message
120
+ # string. Convenience wrapper around StreamChat::Webhook.parse_sns. No signature
121
+ # is required; SNS deliveries are authenticated via AWS IAM.
122
+ def parse_sns(notification_body)
123
+ StreamChat::Webhook.parse_sns(notification_body)
124
+ end
125
+
79
126
  # @param path [String] The API path
80
127
  # @param body [Hash] The request body
81
128
  # @return [GetStreamRuby::StreamResponse] The API response
@@ -981,6 +981,32 @@ module GetStream
981
981
  )
982
982
  end
983
983
 
984
+ # Searches mentionable roles (user-assignable + channel-assignable, built-in and custom) by name prefix for autocomplete
985
+ #
986
+ # @param query [String]
987
+ # @param limit [Integer]
988
+ # @param name_gt [String]
989
+ # @param role_type [String]
990
+ # @param include_global_roles [Boolean]
991
+ # @return [Models::SearchRolesResponse]
992
+ def search_roles(query, limit = nil, name_gt = nil, role_type = nil, include_global_roles = nil)
993
+ path = '/api/v2/roles/search'
994
+ # Build query parameters
995
+ query_params = {}
996
+ query_params['query'] = query unless query.nil?
997
+ query_params['limit'] = limit unless limit.nil?
998
+ query_params['name_gt'] = name_gt unless name_gt.nil?
999
+ query_params['role_type'] = role_type unless role_type.nil?
1000
+ query_params['include_global_roles'] = include_global_roles unless include_global_roles.nil?
1001
+
1002
+ # Make the API request
1003
+ @client.make_request(
1004
+ :get,
1005
+ path,
1006
+ query_params: query_params
1007
+ )
1008
+ end
1009
+
984
1010
  # Deletes custom role
985
1011
  #
986
1012
  # @param name [String]
@@ -17,11 +17,13 @@ module GetStream
17
17
  # Delete a single feed by its ID
18
18
  #
19
19
  # @param hard_delete [Boolean]
20
+ # @param purge_user_activities [Boolean]
20
21
  # @return [Models::DeleteFeedResponse]
21
- def delete_feed(hard_delete = nil)
22
+ def delete_feed(hard_delete = nil, purge_user_activities = nil)
22
23
  # Build query parameters
23
24
  query_params = {}
24
25
  query_params['hard_delete'] = hard_delete unless hard_delete.nil?
26
+ query_params['purge_user_activities'] = purge_user_activities unless purge_user_activities.nil?
25
27
 
26
28
  # Delegate to the FeedsClient
27
29
  @client.feeds.delete_feed(@feed_group_id, @feed_id, query_params)
@@ -992,8 +992,9 @@ module GetStream
992
992
  # @param feed_group_id [String]
993
993
  # @param feed_id [String]
994
994
  # @param hard_delete [Boolean]
995
+ # @param purge_user_activities [Boolean]
995
996
  # @return [Models::DeleteFeedResponse]
996
- def delete_feed(feed_group_id, feed_id, hard_delete = nil)
997
+ def delete_feed(feed_group_id, feed_id, hard_delete = nil, purge_user_activities = nil)
997
998
  path = '/api/v2/feeds/feed_groups/{feed_group_id}/feeds/{feed_id}'
998
999
  # Replace path parameters
999
1000
  path = path.gsub('{feed_group_id}', feed_group_id.to_s)
@@ -1001,6 +1002,7 @@ module GetStream
1001
1002
  # Build query parameters
1002
1003
  query_params = {}
1003
1004
  query_params['hard_delete'] = hard_delete unless hard_delete.nil?
1005
+ query_params['purge_user_activities'] = purge_user_activities unless purge_user_activities.nil?
1004
1006
 
1005
1007
  # Make the API request
1006
1008
  @client.make_request(
@@ -43,7 +43,7 @@ module GetStream
43
43
  @started_at = attributes[:started_at] || attributes['started_at']
44
44
  @task_id = attributes[:task_id] || attributes['task_id']
45
45
  @custom = attributes[:custom] || attributes['custom']
46
- @type = attributes[:type] || attributes['type'] || "export.users.error"
46
+ @type = attributes[:type] || attributes['type'] || "export.bulk_image_moderation.error"
47
47
  @received_at = attributes[:received_at] || attributes['received_at'] || nil
48
48
  end
49
49
 
@@ -15,19 +15,24 @@ module GetStream
15
15
  # @!attribute hard_delete
16
16
  # @return [Boolean] Whether to permanently delete the feeds instead of soft delete
17
17
  attr_accessor :hard_delete
18
+ # @!attribute purge_user_activities
19
+ # @return [Boolean] When hard-deleting, also fully delete activities authored by each feed's owner from every other feed those activities were fanned out to. Default false preserves existing fan-out. Requires 'hard_delete' to be true; the request is rejected otherwise. Feeds with no recorded owner (created_by_id is empty) are silently skipped for the purge step — owner-matching against an empty string is a safety guard, not a wildcard.
20
+ attr_accessor :purge_user_activities
18
21
 
19
22
  # Initialize with attributes
20
23
  def initialize(attributes = {})
21
24
  super(attributes)
22
25
  @feeds = attributes[:feeds] || attributes['feeds']
23
26
  @hard_delete = attributes[:hard_delete] || attributes['hard_delete'] || nil
27
+ @purge_user_activities = attributes[:purge_user_activities] || attributes['purge_user_activities'] || nil
24
28
  end
25
29
 
26
30
  # Override field mappings for JSON serialization
27
31
  def self.json_field_mappings
28
32
  {
29
33
  feeds: 'feeds',
30
- hard_delete: 'hard_delete'
34
+ hard_delete: 'hard_delete',
35
+ purge_user_activities: 'purge_user_activities'
31
36
  }
32
37
  end
33
38
  end
@@ -21,6 +21,9 @@ module GetStream
21
21
  # @!attribute content_type
22
22
  # @return [String] Type of content: 'text' (default), 'message', or 'username'. Stored as-sent; only 'username' routes to the username moderation API.
23
23
  attr_accessor :content_type
24
+ # @!attribute dry_run
25
+ # @return [Boolean] When true, run moderation and return labels without persisting the result. Useful for one-off checks (e.g. UI testers) that should not be recorded in the stored history.
26
+ attr_accessor :dry_run
24
27
  # @!attribute policy
25
28
  # @return [String] Optional moderation policy key (max 128 chars)
26
29
  attr_accessor :policy
@@ -35,6 +38,7 @@ module GetStream
35
38
  @category = attributes[:category] || attributes['category'] || nil
36
39
  @content_id = attributes[:content_id] || attributes['content_id'] || nil
37
40
  @content_type = attributes[:content_type] || attributes['content_type'] || nil
41
+ @dry_run = attributes[:dry_run] || attributes['dry_run'] || nil
38
42
  @policy = attributes[:policy] || attributes['policy'] || nil
39
43
  @user_id = attributes[:user_id] || attributes['user_id'] || nil
40
44
  end
@@ -46,6 +50,7 @@ module GetStream
46
50
  category: 'category',
47
51
  content_id: 'content_id',
48
52
  content_type: 'content_type',
53
+ dry_run: 'dry_run',
49
54
  policy: 'policy',
50
55
  user_id: 'user_id'
51
56
  }
@@ -21,12 +21,18 @@ module GetStream
21
21
  # @!attribute prev
22
22
  # @return [String]
23
23
  attr_accessor :prev
24
+ # @!attribute user_id
25
+ # @return [String]
26
+ attr_accessor :user_id
24
27
  # @!attribute sort
25
28
  # @return [Array<SortParamRequest>] Sorting parameters for the query
26
29
  attr_accessor :sort
27
30
  # @!attribute filter
28
31
  # @return [Object] Filters to apply to the query
29
32
  attr_accessor :filter
33
+ # @!attribute user
34
+ # @return [UserRequest]
35
+ attr_accessor :user
30
36
 
31
37
  # Initialize with attributes
32
38
  def initialize(attributes = {})
@@ -35,8 +41,10 @@ module GetStream
35
41
  @limit = attributes[:limit] || attributes['limit'] || nil
36
42
  @next = attributes[:next] || attributes['next'] || nil
37
43
  @prev = attributes[:prev] || attributes['prev'] || nil
44
+ @user_id = attributes[:user_id] || attributes['user_id'] || nil
38
45
  @sort = attributes[:sort] || attributes['sort'] || nil
39
46
  @filter = attributes[:filter] || attributes['filter'] || nil
47
+ @user = attributes[:user] || attributes['user'] || nil
40
48
  end
41
49
 
42
50
  # Override field mappings for JSON serialization
@@ -46,8 +54,10 @@ module GetStream
46
54
  limit: 'limit',
47
55
  next: 'next',
48
56
  prev: 'prev',
57
+ user_id: 'user_id',
49
58
  sort: 'sort',
50
- filter: 'filter'
59
+ filter: 'filter',
60
+ user: 'user'
51
61
  }
52
62
  end
53
63
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT.
4
+
5
+ module GetStream
6
+ module Generated
7
+ module Models
8
+ #
9
+ class SearchRolesResponse < GetStream::BaseModel
10
+
11
+ # Model attributes
12
+ # @!attribute duration
13
+ # @return [String]
14
+ attr_accessor :duration
15
+ # @!attribute roles
16
+ # @return [Array<Role>] Matching roles, sorted ascending by name
17
+ attr_accessor :roles
18
+
19
+ # Initialize with attributes
20
+ def initialize(attributes = {})
21
+ super(attributes)
22
+ @duration = attributes[:duration] || attributes['duration']
23
+ @roles = attributes[:roles] || attributes['roles']
24
+ end
25
+
26
+ # Override field mappings for JSON serialization
27
+ def self.json_field_mappings
28
+ {
29
+ duration: 'duration',
30
+ roles: 'roles'
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,7 +1,10 @@
1
1
  # Code generated by GetStream internal OpenAPI code generator. DO NOT EDIT.
2
2
 
3
- require 'openssl'
3
+ require 'base64'
4
4
  require 'json'
5
+ require 'openssl'
6
+ require 'time'
7
+ require 'zlib'
5
8
  require_relative 'models/activity_added_event'
6
9
  require_relative 'models/activity_deleted_event'
7
10
  require_relative 'models/activity_feedback_event'
@@ -169,6 +172,47 @@ require_relative 'models/user_updated_event'
169
172
 
170
173
  module StreamChat
171
174
  module Webhook
175
+ # Raised for every webhook handling failure: signature mismatch, invalid
176
+ # JSON, missing/non-string +type+ field, gzip-prefixed body that fails to
177
+ # decompress, invalid base64 in a queue body, or a malformed SNS envelope.
178
+ #
179
+ # Rescue this single class for any webhook problem; filter on the message
180
+ # text or against the failure-mode constants below to differentiate.
181
+ class InvalidWebhookError < StandardError
182
+ SIGNATURE_MISMATCH = 'signature mismatch'
183
+ INVALID_BASE64 = 'invalid base64 encoding'
184
+ GZIP_FAILED = 'gzip decompression failed'
185
+ INVALID_JSON = 'invalid JSON payload'
186
+ end
187
+
188
+ # Returned by parse_event when the type discriminator is well-formed but
189
+ # unknown to this SDK version.
190
+ #
191
+ # Stable forward-compat surface: backend may add new event types before this
192
+ # SDK is regenerated. Switch on the returned object's class and include
193
+ # UnknownEvent as a fallback case.
194
+ #
195
+ # @!attribute [r] type
196
+ # @return [String] the unrecognized discriminator value
197
+ # @!attribute [r] created_at
198
+ # @return [Time, nil] parsed timestamp from the envelope, or nil if missing/unparseable
199
+ # @!attribute [r] raw
200
+ # @return [Hash] the full parsed JSON hash for inspection
201
+ class UnknownEvent
202
+ attr_reader :type, :created_at, :raw
203
+
204
+ def initialize(type:, created_at: nil, raw: {})
205
+ @type = type
206
+ @created_at = created_at
207
+ @raw = raw
208
+ end
209
+ end
210
+
211
+ # gzip magic prefix (RFC 1952 §2.3.1). JSON cannot start with these bytes,
212
+ # so this gives unambiguous detection for Stream's always-JSON payloads.
213
+ GZIP_MAGIC = "\x1F\x8B".b.freeze
214
+ private_constant :GZIP_MAGIC
215
+
172
216
  # Webhook event type constants
173
217
  EVENT_TYPE_WILDCARD = '*'
174
218
  EVENT_TYPE_APPEAL_ACCEPTED = 'appeal.accepted'
@@ -349,6 +393,9 @@ module StreamChat
349
393
 
350
394
  # Deserialize a raw webhook payload into a typed event object.
351
395
  #
396
+ # Throws on unknown event types. For forward-compatible parsing that returns
397
+ # an {UnknownEvent} on unrecognized discriminators, use {parse_event}.
398
+ #
352
399
  # @param raw_event [String, Hash] The raw webhook payload
353
400
  # @return [Object] A typed event object corresponding to the event type
354
401
  # @raise [ArgumentError] if the event type is unknown or deserialization fails
@@ -383,339 +430,339 @@ module StreamChat
383
430
  private_class_method def self.event_class_for_type(event_type)
384
431
  case event_type
385
432
  when '*'
386
- StreamChat::CustomEvent
433
+ GetStream::Generated::Models::CustomEvent
387
434
  when 'appeal.accepted'
388
- StreamChat::AppealAcceptedEvent
435
+ GetStream::Generated::Models::AppealAcceptedEvent
389
436
  when 'appeal.created'
390
- StreamChat::AppealCreatedEvent
437
+ GetStream::Generated::Models::AppealCreatedEvent
391
438
  when 'appeal.rejected'
392
- StreamChat::AppealRejectedEvent
439
+ GetStream::Generated::Models::AppealRejectedEvent
393
440
  when 'call.accepted'
394
- StreamChat::CallAcceptedEvent
441
+ GetStream::Generated::Models::CallAcceptedEvent
395
442
  when 'call.blocked_user'
396
- StreamChat::BlockedUserEvent
443
+ GetStream::Generated::Models::BlockedUserEvent
397
444
  when 'call.closed_caption'
398
- StreamChat::ClosedCaptionEvent
445
+ GetStream::Generated::Models::ClosedCaptionEvent
399
446
  when 'call.closed_captions_failed'
400
- StreamChat::CallClosedCaptionsFailedEvent
447
+ GetStream::Generated::Models::CallClosedCaptionsFailedEvent
401
448
  when 'call.closed_captions_started'
402
- StreamChat::CallClosedCaptionsStartedEvent
449
+ GetStream::Generated::Models::CallClosedCaptionsStartedEvent
403
450
  when 'call.closed_captions_stopped'
404
- StreamChat::CallClosedCaptionsStoppedEvent
451
+ GetStream::Generated::Models::CallClosedCaptionsStoppedEvent
405
452
  when 'call.created'
406
- StreamChat::CallCreatedEvent
453
+ GetStream::Generated::Models::CallCreatedEvent
407
454
  when 'call.deleted'
408
- StreamChat::CallDeletedEvent
455
+ GetStream::Generated::Models::CallDeletedEvent
409
456
  when 'call.dtmf'
410
- StreamChat::CallDTMFEvent
457
+ GetStream::Generated::Models::CallDTMFEvent
411
458
  when 'call.ended'
412
- StreamChat::CallEndedEvent
459
+ GetStream::Generated::Models::CallEndedEvent
413
460
  when 'call.frame_recording_failed'
414
- StreamChat::CallFrameRecordingFailedEvent
461
+ GetStream::Generated::Models::CallFrameRecordingFailedEvent
415
462
  when 'call.frame_recording_ready'
416
- StreamChat::CallFrameRecordingFrameReadyEvent
463
+ GetStream::Generated::Models::CallFrameRecordingFrameReadyEvent
417
464
  when 'call.frame_recording_started'
418
- StreamChat::CallFrameRecordingStartedEvent
465
+ GetStream::Generated::Models::CallFrameRecordingStartedEvent
419
466
  when 'call.frame_recording_stopped'
420
- StreamChat::CallFrameRecordingStoppedEvent
467
+ GetStream::Generated::Models::CallFrameRecordingStoppedEvent
421
468
  when 'call.hls_broadcasting_failed'
422
- StreamChat::CallHLSBroadcastingFailedEvent
469
+ GetStream::Generated::Models::CallHLSBroadcastingFailedEvent
423
470
  when 'call.hls_broadcasting_started'
424
- StreamChat::CallHLSBroadcastingStartedEvent
471
+ GetStream::Generated::Models::CallHLSBroadcastingStartedEvent
425
472
  when 'call.hls_broadcasting_stopped'
426
- StreamChat::CallHLSBroadcastingStoppedEvent
473
+ GetStream::Generated::Models::CallHLSBroadcastingStoppedEvent
427
474
  when 'call.kicked_user'
428
- StreamChat::KickedUserEvent
475
+ GetStream::Generated::Models::KickedUserEvent
429
476
  when 'call.live_started'
430
- StreamChat::CallLiveStartedEvent
477
+ GetStream::Generated::Models::CallLiveStartedEvent
431
478
  when 'call.member_added'
432
- StreamChat::CallMemberAddedEvent
479
+ GetStream::Generated::Models::CallMemberAddedEvent
433
480
  when 'call.member_removed'
434
- StreamChat::CallMemberRemovedEvent
481
+ GetStream::Generated::Models::CallMemberRemovedEvent
435
482
  when 'call.member_updated'
436
- StreamChat::CallMemberUpdatedEvent
483
+ GetStream::Generated::Models::CallMemberUpdatedEvent
437
484
  when 'call.member_updated_permission'
438
- StreamChat::CallMemberUpdatedPermissionEvent
485
+ GetStream::Generated::Models::CallMemberUpdatedPermissionEvent
439
486
  when 'call.missed'
440
- StreamChat::CallMissedEvent
487
+ GetStream::Generated::Models::CallMissedEvent
441
488
  when 'call.moderation_blur'
442
- StreamChat::CallModerationBlurEvent
489
+ GetStream::Generated::Models::CallModerationBlurEvent
443
490
  when 'call.moderation_warning'
444
- StreamChat::CallModerationWarningEvent
491
+ GetStream::Generated::Models::CallModerationWarningEvent
445
492
  when 'call.notification'
446
- StreamChat::CallNotificationEvent
493
+ GetStream::Generated::Models::CallNotificationEvent
447
494
  when 'call.permission_request'
448
- StreamChat::PermissionRequestEvent
495
+ GetStream::Generated::Models::PermissionRequestEvent
449
496
  when 'call.permissions_updated'
450
- StreamChat::UpdatedCallPermissionsEvent
497
+ GetStream::Generated::Models::UpdatedCallPermissionsEvent
451
498
  when 'call.reaction_new'
452
- StreamChat::CallReactionEvent
499
+ GetStream::Generated::Models::CallReactionEvent
453
500
  when 'call.recording_failed'
454
- StreamChat::CallRecordingFailedEvent
501
+ GetStream::Generated::Models::CallRecordingFailedEvent
455
502
  when 'call.recording_ready'
456
- StreamChat::CallRecordingReadyEvent
503
+ GetStream::Generated::Models::CallRecordingReadyEvent
457
504
  when 'call.recording_started'
458
- StreamChat::CallRecordingStartedEvent
505
+ GetStream::Generated::Models::CallRecordingStartedEvent
459
506
  when 'call.recording_stopped'
460
- StreamChat::CallRecordingStoppedEvent
507
+ GetStream::Generated::Models::CallRecordingStoppedEvent
461
508
  when 'call.rejected'
462
- StreamChat::CallRejectedEvent
509
+ GetStream::Generated::Models::CallRejectedEvent
463
510
  when 'call.ring'
464
- StreamChat::CallRingEvent
511
+ GetStream::Generated::Models::CallRingEvent
465
512
  when 'call.rtmp_broadcast_failed'
466
- StreamChat::CallRtmpBroadcastFailedEvent
513
+ GetStream::Generated::Models::CallRtmpBroadcastFailedEvent
467
514
  when 'call.rtmp_broadcast_started'
468
- StreamChat::CallRtmpBroadcastStartedEvent
515
+ GetStream::Generated::Models::CallRtmpBroadcastStartedEvent
469
516
  when 'call.rtmp_broadcast_stopped'
470
- StreamChat::CallRtmpBroadcastStoppedEvent
517
+ GetStream::Generated::Models::CallRtmpBroadcastStoppedEvent
471
518
  when 'call.session_ended'
472
- StreamChat::CallSessionEndedEvent
519
+ GetStream::Generated::Models::CallSessionEndedEvent
473
520
  when 'call.session_participant_count_updated'
474
- StreamChat::CallSessionParticipantCountsUpdatedEvent
521
+ GetStream::Generated::Models::CallSessionParticipantCountsUpdatedEvent
475
522
  when 'call.session_participant_joined'
476
- StreamChat::CallSessionParticipantJoinedEvent
523
+ GetStream::Generated::Models::CallSessionParticipantJoinedEvent
477
524
  when 'call.session_participant_left'
478
- StreamChat::CallSessionParticipantLeftEvent
525
+ GetStream::Generated::Models::CallSessionParticipantLeftEvent
479
526
  when 'call.session_started'
480
- StreamChat::CallSessionStartedEvent
527
+ GetStream::Generated::Models::CallSessionStartedEvent
481
528
  when 'call.stats_report_ready'
482
- StreamChat::CallStatsReportReadyEvent
529
+ GetStream::Generated::Models::CallStatsReportReadyEvent
483
530
  when 'call.transcription_failed'
484
- StreamChat::CallTranscriptionFailedEvent
531
+ GetStream::Generated::Models::CallTranscriptionFailedEvent
485
532
  when 'call.transcription_ready'
486
- StreamChat::CallTranscriptionReadyEvent
533
+ GetStream::Generated::Models::CallTranscriptionReadyEvent
487
534
  when 'call.transcription_started'
488
- StreamChat::CallTranscriptionStartedEvent
535
+ GetStream::Generated::Models::CallTranscriptionStartedEvent
489
536
  when 'call.transcription_stopped'
490
- StreamChat::CallTranscriptionStoppedEvent
537
+ GetStream::Generated::Models::CallTranscriptionStoppedEvent
491
538
  when 'call.unblocked_user'
492
- StreamChat::UnblockedUserEvent
539
+ GetStream::Generated::Models::UnblockedUserEvent
493
540
  when 'call.updated'
494
- StreamChat::CallUpdatedEvent
541
+ GetStream::Generated::Models::CallUpdatedEvent
495
542
  when 'call.user_feedback_submitted'
496
- StreamChat::CallUserFeedbackSubmittedEvent
543
+ GetStream::Generated::Models::CallUserFeedbackSubmittedEvent
497
544
  when 'call.user_muted'
498
- StreamChat::CallUserMutedEvent
545
+ GetStream::Generated::Models::CallUserMutedEvent
499
546
  when 'campaign.completed'
500
- StreamChat::CampaignCompletedEvent
547
+ GetStream::Generated::Models::CampaignCompletedEvent
501
548
  when 'campaign.started'
502
- StreamChat::CampaignStartedEvent
549
+ GetStream::Generated::Models::CampaignStartedEvent
503
550
  when 'channel.created'
504
- StreamChat::ChannelCreatedEvent
551
+ GetStream::Generated::Models::ChannelCreatedEvent
505
552
  when 'channel.deleted'
506
- StreamChat::ChannelDeletedEvent
553
+ GetStream::Generated::Models::ChannelDeletedEvent
507
554
  when 'channel.frozen'
508
- StreamChat::ChannelFrozenEvent
555
+ GetStream::Generated::Models::ChannelFrozenEvent
509
556
  when 'channel.hidden'
510
- StreamChat::ChannelHiddenEvent
557
+ GetStream::Generated::Models::ChannelHiddenEvent
511
558
  when 'channel.max_streak_changed'
512
- StreamChat::MaxStreakChangedEvent
559
+ GetStream::Generated::Models::MaxStreakChangedEvent
513
560
  when 'channel.muted'
514
- StreamChat::ChannelMutedEvent
561
+ GetStream::Generated::Models::ChannelMutedEvent
515
562
  when 'channel.truncated'
516
- StreamChat::ChannelTruncatedEvent
563
+ GetStream::Generated::Models::ChannelTruncatedEvent
517
564
  when 'channel.unfrozen'
518
- StreamChat::ChannelUnFrozenEvent
565
+ GetStream::Generated::Models::ChannelUnFrozenEvent
519
566
  when 'channel.unmuted'
520
- StreamChat::ChannelUnmutedEvent
567
+ GetStream::Generated::Models::ChannelUnmutedEvent
521
568
  when 'channel.updated'
522
- StreamChat::ChannelUpdatedEvent
569
+ GetStream::Generated::Models::ChannelUpdatedEvent
523
570
  when 'channel.visible'
524
- StreamChat::ChannelVisibleEvent
571
+ GetStream::Generated::Models::ChannelVisibleEvent
525
572
  when 'channel_batch_update.completed'
526
- StreamChat::ChannelBatchCompletedEvent
573
+ GetStream::Generated::Models::ChannelBatchCompletedEvent
527
574
  when 'channel_batch_update.started'
528
- StreamChat::ChannelBatchStartedEvent
575
+ GetStream::Generated::Models::ChannelBatchStartedEvent
529
576
  when 'custom'
530
- StreamChat::CustomVideoEvent
577
+ GetStream::Generated::Models::CustomVideoEvent
531
578
  when 'export.bulk_image_moderation.error'
532
- StreamChat::AsyncExportErrorEvent
579
+ GetStream::Generated::Models::AsyncExportErrorEvent
533
580
  when 'export.bulk_image_moderation.success'
534
- StreamChat::AsyncBulkImageModerationEvent
581
+ GetStream::Generated::Models::AsyncBulkImageModerationEvent
535
582
  when 'export.channels.error'
536
- StreamChat::AsyncExportErrorEvent
583
+ GetStream::Generated::Models::AsyncExportErrorEvent
537
584
  when 'export.channels.success'
538
- StreamChat::AsyncExportChannelsEvent
585
+ GetStream::Generated::Models::AsyncExportChannelsEvent
539
586
  when 'export.moderation_logs.error'
540
- StreamChat::AsyncExportErrorEvent
587
+ GetStream::Generated::Models::AsyncExportErrorEvent
541
588
  when 'export.moderation_logs.success'
542
- StreamChat::AsyncExportModerationLogsEvent
589
+ GetStream::Generated::Models::AsyncExportModerationLogsEvent
543
590
  when 'export.users.error'
544
- StreamChat::AsyncExportErrorEvent
591
+ GetStream::Generated::Models::AsyncExportErrorEvent
545
592
  when 'export.users.success'
546
- StreamChat::AsyncExportUsersEvent
593
+ GetStream::Generated::Models::AsyncExportUsersEvent
547
594
  when 'feeds.activity.added'
548
- StreamChat::ActivityAddedEvent
595
+ GetStream::Generated::Models::ActivityAddedEvent
549
596
  when 'feeds.activity.deleted'
550
- StreamChat::ActivityDeletedEvent
597
+ GetStream::Generated::Models::ActivityDeletedEvent
551
598
  when 'feeds.activity.feedback'
552
- StreamChat::ActivityFeedbackEvent
599
+ GetStream::Generated::Models::ActivityFeedbackEvent
553
600
  when 'feeds.activity.marked'
554
- StreamChat::ActivityMarkEvent
601
+ GetStream::Generated::Models::ActivityMarkEvent
555
602
  when 'feeds.activity.pinned'
556
- StreamChat::ActivityPinnedEvent
603
+ GetStream::Generated::Models::ActivityPinnedEvent
557
604
  when 'feeds.activity.reaction.added'
558
- StreamChat::ActivityReactionAddedEvent
605
+ GetStream::Generated::Models::ActivityReactionAddedEvent
559
606
  when 'feeds.activity.reaction.deleted'
560
- StreamChat::ActivityReactionDeletedEvent
607
+ GetStream::Generated::Models::ActivityReactionDeletedEvent
561
608
  when 'feeds.activity.reaction.updated'
562
- StreamChat::ActivityReactionUpdatedEvent
609
+ GetStream::Generated::Models::ActivityReactionUpdatedEvent
563
610
  when 'feeds.activity.removed_from_feed'
564
- StreamChat::ActivityRemovedFromFeedEvent
611
+ GetStream::Generated::Models::ActivityRemovedFromFeedEvent
565
612
  when 'feeds.activity.restored'
566
- StreamChat::ActivityRestoredEvent
613
+ GetStream::Generated::Models::ActivityRestoredEvent
567
614
  when 'feeds.activity.unpinned'
568
- StreamChat::ActivityUnpinnedEvent
615
+ GetStream::Generated::Models::ActivityUnpinnedEvent
569
616
  when 'feeds.activity.updated'
570
- StreamChat::ActivityUpdatedEvent
617
+ GetStream::Generated::Models::ActivityUpdatedEvent
571
618
  when 'feeds.bookmark.added'
572
- StreamChat::BookmarkAddedEvent
619
+ GetStream::Generated::Models::BookmarkAddedEvent
573
620
  when 'feeds.bookmark.deleted'
574
- StreamChat::BookmarkDeletedEvent
621
+ GetStream::Generated::Models::BookmarkDeletedEvent
575
622
  when 'feeds.bookmark.updated'
576
- StreamChat::BookmarkUpdatedEvent
623
+ GetStream::Generated::Models::BookmarkUpdatedEvent
577
624
  when 'feeds.bookmark_folder.deleted'
578
- StreamChat::BookmarkFolderDeletedEvent
625
+ GetStream::Generated::Models::BookmarkFolderDeletedEvent
579
626
  when 'feeds.bookmark_folder.updated'
580
- StreamChat::BookmarkFolderUpdatedEvent
627
+ GetStream::Generated::Models::BookmarkFolderUpdatedEvent
581
628
  when 'feeds.comment.added'
582
- StreamChat::CommentAddedEvent
629
+ GetStream::Generated::Models::CommentAddedEvent
583
630
  when 'feeds.comment.deleted'
584
- StreamChat::CommentDeletedEvent
631
+ GetStream::Generated::Models::CommentDeletedEvent
585
632
  when 'feeds.comment.reaction.added'
586
- StreamChat::CommentReactionAddedEvent
633
+ GetStream::Generated::Models::CommentReactionAddedEvent
587
634
  when 'feeds.comment.reaction.deleted'
588
- StreamChat::CommentReactionDeletedEvent
635
+ GetStream::Generated::Models::CommentReactionDeletedEvent
589
636
  when 'feeds.comment.reaction.updated'
590
- StreamChat::CommentReactionUpdatedEvent
637
+ GetStream::Generated::Models::CommentReactionUpdatedEvent
591
638
  when 'feeds.comment.restored'
592
- StreamChat::CommentRestoredEvent
639
+ GetStream::Generated::Models::CommentRestoredEvent
593
640
  when 'feeds.comment.updated'
594
- StreamChat::CommentUpdatedEvent
641
+ GetStream::Generated::Models::CommentUpdatedEvent
595
642
  when 'feeds.feed.created'
596
- StreamChat::FeedCreatedEvent
643
+ GetStream::Generated::Models::FeedCreatedEvent
597
644
  when 'feeds.feed.deleted'
598
- StreamChat::FeedDeletedEvent
645
+ GetStream::Generated::Models::FeedDeletedEvent
599
646
  when 'feeds.feed.updated'
600
- StreamChat::FeedUpdatedEvent
647
+ GetStream::Generated::Models::FeedUpdatedEvent
601
648
  when 'feeds.feed_group.changed'
602
- StreamChat::FeedGroupChangedEvent
649
+ GetStream::Generated::Models::FeedGroupChangedEvent
603
650
  when 'feeds.feed_group.deleted'
604
- StreamChat::FeedGroupDeletedEvent
651
+ GetStream::Generated::Models::FeedGroupDeletedEvent
605
652
  when 'feeds.feed_group.restored'
606
- StreamChat::FeedGroupRestoredEvent
653
+ GetStream::Generated::Models::FeedGroupRestoredEvent
607
654
  when 'feeds.feed_member.added'
608
- StreamChat::FeedMemberAddedEvent
655
+ GetStream::Generated::Models::FeedMemberAddedEvent
609
656
  when 'feeds.feed_member.removed'
610
- StreamChat::FeedMemberRemovedEvent
657
+ GetStream::Generated::Models::FeedMemberRemovedEvent
611
658
  when 'feeds.feed_member.updated'
612
- StreamChat::FeedMemberUpdatedEvent
659
+ GetStream::Generated::Models::FeedMemberUpdatedEvent
613
660
  when 'feeds.follow.created'
614
- StreamChat::FollowCreatedEvent
661
+ GetStream::Generated::Models::FollowCreatedEvent
615
662
  when 'feeds.follow.deleted'
616
- StreamChat::FollowDeletedEvent
663
+ GetStream::Generated::Models::FollowDeletedEvent
617
664
  when 'feeds.follow.updated'
618
- StreamChat::FollowUpdatedEvent
665
+ GetStream::Generated::Models::FollowUpdatedEvent
619
666
  when 'feeds.notification_feed.updated'
620
- StreamChat::NotificationFeedUpdatedEvent
667
+ GetStream::Generated::Models::NotificationFeedUpdatedEvent
621
668
  when 'feeds.stories_feed.updated'
622
- StreamChat::StoriesFeedUpdatedEvent
669
+ GetStream::Generated::Models::StoriesFeedUpdatedEvent
623
670
  when 'flag.updated'
624
- StreamChat::FlagUpdatedEvent
671
+ GetStream::Generated::Models::FlagUpdatedEvent
625
672
  when 'ingress.error'
626
- StreamChat::IngressErrorEvent
673
+ GetStream::Generated::Models::IngressErrorEvent
627
674
  when 'ingress.started'
628
- StreamChat::IngressStartedEvent
675
+ GetStream::Generated::Models::IngressStartedEvent
629
676
  when 'ingress.stopped'
630
- StreamChat::IngressStoppedEvent
677
+ GetStream::Generated::Models::IngressStoppedEvent
631
678
  when 'member.added'
632
- StreamChat::MemberAddedEvent
679
+ GetStream::Generated::Models::MemberAddedEvent
633
680
  when 'member.removed'
634
- StreamChat::MemberRemovedEvent
681
+ GetStream::Generated::Models::MemberRemovedEvent
635
682
  when 'member.updated'
636
- StreamChat::MemberUpdatedEvent
683
+ GetStream::Generated::Models::MemberUpdatedEvent
637
684
  when 'message.deleted'
638
- StreamChat::MessageDeletedEvent
685
+ GetStream::Generated::Models::MessageDeletedEvent
639
686
  when 'message.flagged'
640
- StreamChat::MessageFlaggedEvent
687
+ GetStream::Generated::Models::MessageFlaggedEvent
641
688
  when 'message.new'
642
- StreamChat::MessageNewEvent
689
+ GetStream::Generated::Models::MessageNewEvent
643
690
  when 'message.pending'
644
- StreamChat::PendingMessageEvent
691
+ GetStream::Generated::Models::PendingMessageEvent
645
692
  when 'message.read'
646
- StreamChat::MessageReadEvent
693
+ GetStream::Generated::Models::MessageReadEvent
647
694
  when 'message.unblocked'
648
- StreamChat::MessageUnblockedEvent
695
+ GetStream::Generated::Models::MessageUnblockedEvent
649
696
  when 'message.undeleted'
650
- StreamChat::MessageUndeletedEvent
697
+ GetStream::Generated::Models::MessageUndeletedEvent
651
698
  when 'message.updated'
652
- StreamChat::MessageUpdatedEvent
699
+ GetStream::Generated::Models::MessageUpdatedEvent
653
700
  when 'moderation.custom_action'
654
- StreamChat::ModerationCustomActionEvent
701
+ GetStream::Generated::Models::ModerationCustomActionEvent
655
702
  when 'moderation.flagged'
656
- StreamChat::ModerationFlaggedEvent
703
+ GetStream::Generated::Models::ModerationFlaggedEvent
657
704
  when 'moderation.mark_reviewed'
658
- StreamChat::ModerationMarkReviewedEvent
705
+ GetStream::Generated::Models::ModerationMarkReviewedEvent
659
706
  when 'moderation_check.completed'
660
- StreamChat::ModerationCheckCompletedEvent
707
+ GetStream::Generated::Models::ModerationCheckCompletedEvent
661
708
  when 'moderation_rule.triggered'
662
- StreamChat::ModerationRulesTriggeredEvent
709
+ GetStream::Generated::Models::ModerationRulesTriggeredEvent
663
710
  when 'notification.mark_unread'
664
- StreamChat::NotificationMarkUnreadEvent
711
+ GetStream::Generated::Models::NotificationMarkUnreadEvent
665
712
  when 'notification.reminder_due'
666
- StreamChat::ReminderNotificationEvent
713
+ GetStream::Generated::Models::ReminderNotificationEvent
667
714
  when 'notification.thread_message_new'
668
- StreamChat::NotificationThreadMessageNewEvent
715
+ GetStream::Generated::Models::NotificationThreadMessageNewEvent
669
716
  when 'reaction.deleted'
670
- StreamChat::ReactionDeletedEvent
717
+ GetStream::Generated::Models::ReactionDeletedEvent
671
718
  when 'reaction.new'
672
- StreamChat::ReactionNewEvent
719
+ GetStream::Generated::Models::ReactionNewEvent
673
720
  when 'reaction.updated'
674
- StreamChat::ReactionUpdatedEvent
721
+ GetStream::Generated::Models::ReactionUpdatedEvent
675
722
  when 'reminder.created'
676
- StreamChat::ReminderCreatedEvent
723
+ GetStream::Generated::Models::ReminderCreatedEvent
677
724
  when 'reminder.deleted'
678
- StreamChat::ReminderDeletedEvent
725
+ GetStream::Generated::Models::ReminderDeletedEvent
679
726
  when 'reminder.updated'
680
- StreamChat::ReminderUpdatedEvent
727
+ GetStream::Generated::Models::ReminderUpdatedEvent
681
728
  when 'review_queue_item.new'
682
- StreamChat::ReviewQueueItemNewEvent
729
+ GetStream::Generated::Models::ReviewQueueItemNewEvent
683
730
  when 'review_queue_item.updated'
684
- StreamChat::ReviewQueueItemUpdatedEvent
731
+ GetStream::Generated::Models::ReviewQueueItemUpdatedEvent
685
732
  when 'thread.updated'
686
- StreamChat::ThreadUpdatedEvent
733
+ GetStream::Generated::Models::ThreadUpdatedEvent
687
734
  when 'user.banned'
688
- StreamChat::UserBannedEvent
735
+ GetStream::Generated::Models::UserBannedEvent
689
736
  when 'user.deactivated'
690
- StreamChat::UserDeactivatedEvent
737
+ GetStream::Generated::Models::UserDeactivatedEvent
691
738
  when 'user.deleted'
692
- StreamChat::UserDeletedEvent
739
+ GetStream::Generated::Models::UserDeletedEvent
693
740
  when 'user.flagged'
694
- StreamChat::UserFlaggedEvent
741
+ GetStream::Generated::Models::UserFlaggedEvent
695
742
  when 'user.messages.deleted'
696
- StreamChat::UserMessagesDeletedEvent
743
+ GetStream::Generated::Models::UserMessagesDeletedEvent
697
744
  when 'user.muted'
698
- StreamChat::UserMutedEvent
745
+ GetStream::Generated::Models::UserMutedEvent
699
746
  when 'user.reactivated'
700
- StreamChat::UserReactivatedEvent
747
+ GetStream::Generated::Models::UserReactivatedEvent
701
748
  when 'user.unbanned'
702
- StreamChat::UserUnbannedEvent
749
+ GetStream::Generated::Models::UserUnbannedEvent
703
750
  when 'user.unmuted'
704
- StreamChat::UserUnmutedEvent
751
+ GetStream::Generated::Models::UserUnmutedEvent
705
752
  when 'user.unread_message_reminder'
706
- StreamChat::UserUnreadReminderEvent
753
+ GetStream::Generated::Models::UserUnreadReminderEvent
707
754
  when 'user.updated'
708
- StreamChat::UserUpdatedEvent
755
+ GetStream::Generated::Models::UserUpdatedEvent
709
756
  when 'user_group.created'
710
- StreamChat::UserGroupCreatedEvent
757
+ GetStream::Generated::Models::UserGroupCreatedEvent
711
758
  when 'user_group.deleted'
712
- StreamChat::UserGroupDeletedEvent
759
+ GetStream::Generated::Models::UserGroupDeletedEvent
713
760
  when 'user_group.member_added'
714
- StreamChat::UserGroupMemberAddedEvent
761
+ GetStream::Generated::Models::UserGroupMemberAddedEvent
715
762
  when 'user_group.member_removed'
716
- StreamChat::UserGroupMemberRemovedEvent
763
+ GetStream::Generated::Models::UserGroupMemberRemovedEvent
717
764
  when 'user_group.updated'
718
- StreamChat::UserGroupUpdatedEvent
765
+ GetStream::Generated::Models::UserGroupUpdatedEvent
719
766
  else
720
767
  nil
721
768
  end
@@ -725,17 +772,201 @@ module StreamChat
725
772
 
726
773
  # Verify the HMAC-SHA256 signature of a webhook payload.
727
774
  #
728
- # @param body [String] The raw request body
775
+ # @param body [String] The raw request body (already-decompressed)
729
776
  # @param signature [String] The signature from the X-Signature header
730
777
  # @param secret [String] Your webhook secret (found in the Stream Dashboard)
731
778
  # @return [Boolean] true if the signature is valid, false otherwise
732
779
  def self.verify_signature(body, signature, secret)
733
780
  return false if signature.nil? || signature.bytesize != 64
734
-
781
+
735
782
  expected = OpenSSL::HMAC.hexdigest('SHA256', secret, body)
736
783
  OpenSSL.fixed_length_secure_compare(signature, expected)
737
784
  rescue ArgumentError
738
785
  false
739
786
  end
787
+
788
+ # Decompress the body if it is gzip-prefixed (first two bytes 0x1F 0x8B),
789
+ # else return the body bytes unchanged.
790
+ #
791
+ # Magic-byte detection is reliable for Stream payloads because Stream webhook
792
+ # bodies are always JSON, and JSON cannot start with 0x1F.
793
+ #
794
+ # @param body [String] raw body (binary-safe)
795
+ # @return [String] decompressed body, or the original body if not gzip-prefixed
796
+ # @raise [InvalidWebhookError] if body has the gzip magic prefix but
797
+ # isn't a valid gzip stream
798
+ def self.gunzip_payload(body)
799
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: body must be a String" unless body.is_a?(String)
800
+
801
+ bytes = body.b
802
+ return bytes if bytes.bytesize < 2 || bytes.byteslice(0, 2) != GZIP_MAGIC
803
+
804
+ Zlib.gunzip(bytes)
805
+ rescue Zlib::Error => e
806
+ raise InvalidWebhookError, "#{InvalidWebhookError::GZIP_FAILED}: #{e.message}"
807
+ end
808
+
809
+ # Decode an SQS Message Body: try base64 first, fall back to raw bytes if
810
+ # base64 fails, then gunzip if gzip-prefixed.
811
+ #
812
+ # Wire format (per CHA-3071): SQS bodies are raw JSON when
813
+ # enable_hook_payload_compression is off (today's default for all existing
814
+ # apps), and base64(gzip(json)) when it's on. This helper handles both:
815
+ # raw JSON starts with '{' which is not valid base64, so the base64 decode
816
+ # fails and we fall through to raw bytes, then {gunzip_payload}'s magic-byte
817
+ # detection decides whether to decompress.
818
+ #
819
+ # {parse_sqs} sits on top of this and works transparently for both wire
820
+ # formats — no caller code change, no flag, no header.
821
+ #
822
+ # @param message_body [String]
823
+ # @return [String]
824
+ # @raise [InvalidWebhookError] only if gzip decompression fails (input had gzip magic prefix)
825
+ def self.decode_sqs_payload(message_body)
826
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: message_body must be a String" unless message_body.is_a?(String)
827
+
828
+ decoded =
829
+ begin
830
+ Base64.strict_decode64(message_body)
831
+ rescue ArgumentError
832
+ # Not base64 — treat input as raw bytes (uncompressed wire format).
833
+ message_body.dup.force_encoding(Encoding::ASCII_8BIT)
834
+ end
835
+ gunzip_payload(decoded)
836
+ end
837
+
838
+ # Return the inner +Message+ field when +body+ is a standard SNS
839
+ # notification envelope JSON; otherwise return +body+ unchanged so a
840
+ # pre-extracted Message string flows through.
841
+ #
842
+ # Heuristic: try to JSON-parse the input. If it yields a Hash with a
843
+ # String +Message+ field, that's the envelope shape — return the Message.
844
+ # Otherwise the input is presumed to BE the pre-extracted Message
845
+ # (base64-encoded bytes are not valid JSON, so this falls through cleanly).
846
+ def self.unwrap_sns_notification_body(body)
847
+ env = JSON.parse(body)
848
+ return env['Message'] if env.is_a?(Hash) && env['Message'].is_a?(String)
849
+
850
+ body
851
+ rescue JSON::ParserError
852
+ body
853
+ end
854
+ private_class_method :unwrap_sns_notification_body
855
+
856
+ # Decode an SNS notification body. Accepts either:
857
+ # * a full SNS HTTP notification envelope JSON
858
+ # ({"Type":"Notification","Message":"<base64>",...}), or
859
+ # * a pre-extracted Message string (forwarded-through-SQS path).
860
+ # The inner payload is then base64-decoded and gunzipped via
861
+ # {decode_sqs_payload}.
862
+ #
863
+ # @param notification_body [String]
864
+ # @return [String]
865
+ # @raise [InvalidWebhookError]
866
+ def self.decode_sns_payload(notification_body)
867
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: notification_body must be a String" unless notification_body.is_a?(String)
868
+
869
+ decode_sqs_payload(unwrap_sns_notification_body(notification_body))
870
+ end
871
+
872
+ # Parse a webhook payload and return the typed event for known discriminators
873
+ # or {UnknownEvent} for well-formed-but-unknown ones.
874
+ #
875
+ # Distinct from {parse_webhook_event}: parse_event returns an UnknownEvent on
876
+ # unrecognized discriminators (forward-compat); parse_webhook_event throws.
877
+ #
878
+ # @param payload [String]
879
+ # @return [Object] the typed event class instance or {UnknownEvent}
880
+ # @raise [InvalidWebhookError] for invalid JSON, missing/non-string type field,
881
+ # or known-type deserialization failure
882
+ def self.parse_event(payload)
883
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: payload must be a String" unless payload.is_a?(String)
884
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: payload must not be empty" if payload.empty?
885
+
886
+ data = JSON.parse(payload)
887
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: webhook payload must be a JSON object" unless data.is_a?(Hash)
888
+
889
+ event_type = data['type']
890
+ unless event_type.is_a?(String) && !event_type.empty?
891
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: webhook payload missing 'type' string field"
892
+ end
893
+
894
+ event_class = event_class_for_type(event_type)
895
+ return build_unknown_event(event_type, data) if event_class.nil?
896
+
897
+ begin
898
+ event_class.new(data)
899
+ rescue StandardError => e
900
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: failed to deserialize event: #{e.message}"
901
+ end
902
+ rescue JSON::ParserError => e
903
+ raise InvalidWebhookError, "#{InvalidWebhookError::INVALID_JSON}: failed to parse webhook payload: #{e.message}"
904
+ end
905
+
906
+ private_class_method def self.build_unknown_event(event_type, data)
907
+ created_raw = data['created_at']
908
+ created_at = nil
909
+ if created_raw.is_a?(String)
910
+ begin
911
+ created_at = Time.iso8601(created_raw)
912
+ rescue ArgumentError
913
+ created_at = nil
914
+ end
915
+ end
916
+ UnknownEvent.new(type: event_type, created_at: created_at, raw: data)
917
+ end
918
+
919
+ public
920
+
921
+ # HTTP composite: gunzip (if gzip-prefixed) -> verify HMAC-SHA256 -> parse.
922
+ #
923
+ # The signature header is X-Signature. The signature is HMAC-SHA256 of the
924
+ # *uncompressed* JSON body, hex-encoded. Magic-byte detection means callers
925
+ # can pass either the raw HTTP body or already-decompressed bytes; both work.
926
+ #
927
+ # @param body [String] raw request body (possibly gzip-compressed)
928
+ # @param signature [String] X-Signature header value
929
+ # @param secret [String] webhook secret
930
+ # @return [Object] the typed event class instance or {UnknownEvent}
931
+ # @raise [InvalidWebhookError] for every failure mode: signature mismatch,
932
+ # gunzip failure, JSON parse, type dispatch, or event deserialization
933
+ # failure. Filter on the message text (or the failure-mode constants
934
+ # on InvalidWebhookError) to differentiate.
935
+ def self.verify_and_parse_webhook(body, signature, secret)
936
+ payload = gunzip_payload(body)
937
+ raise InvalidWebhookError, InvalidWebhookError::SIGNATURE_MISMATCH unless verify_signature(payload, signature, secret)
938
+
939
+ parse_event(payload)
940
+ end
941
+
942
+ # SQS composite: base64-decode -> gunzip (if gzip-prefixed) -> parse.
943
+ #
944
+ # The backend emits no signature attribute on SQS messages today; this helper
945
+ # therefore performs no signature verification. If a signed variant is added
946
+ # later, it will be a separate function rather than retrofitting this signature.
947
+ #
948
+ # @param message_body [String]
949
+ # @return [Object] the typed event class instance or {UnknownEvent}
950
+ # @raise [InvalidWebhookError]
951
+ def self.parse_sqs(message_body)
952
+ parse_event(decode_sqs_payload(message_body))
953
+ end
954
+
955
+ # SNS composite: parse SNS envelope -> base64-decode -> gunzip -> parse.
956
+ # Same no-signature posture as {parse_sqs}.
957
+ #
958
+ # @param notification_body [String]
959
+ # @return [Object] the typed event class instance or {UnknownEvent}
960
+ # @raise [InvalidWebhookError]
961
+ def self.parse_sns(notification_body)
962
+ parse_event(decode_sns_payload(notification_body))
963
+ end
740
964
  end
741
965
  end
966
+
967
+ # Spec §7 alias: canonical name +Stream::Webhook+ aliases the existing
968
+ # +StreamChat::Webhook+ for one minor-version cycle. New callers should use
969
+ # the canonical name; existing callers continue to work unchanged.
970
+ module Stream
971
+ Webhook = StreamChat::Webhook
972
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GetStreamRuby
4
4
 
5
- VERSION = '6.1.1'
5
+ VERSION = '7.0.0'
6
6
 
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: getstream-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.1
4
+ version: 7.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GetStream
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-11 00:00:00.000000000 Z
11
+ date: 2026-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -1112,6 +1112,7 @@ files:
1112
1112
  - lib/getstream_ruby/generated/models/search_response.rb
1113
1113
  - lib/getstream_ruby/generated/models/search_result.rb
1114
1114
  - lib/getstream_ruby/generated/models/search_result_message.rb
1115
+ - lib/getstream_ruby/generated/models/search_roles_response.rb
1115
1116
  - lib/getstream_ruby/generated/models/search_user_groups_response.rb
1116
1117
  - lib/getstream_ruby/generated/models/search_warning.rb
1117
1118
  - lib/getstream_ruby/generated/models/segment.rb