Module: StreamChat::Webhook
- Extended by:
- T::Sig
- Defined in:
- lib/stream-chat/webhook.rb
Overview
Stateless helpers implementing the cross-SDK webhook contract documented at https://getstream.io/chat/docs/node/webhooks_overview/.
The composite functions (verify_and_parse_webhook, parse_sqs,
parse_sns) are the recommended entry points. The primitives
they compose (gunzip_payload, decode_sqs_payload, decode_sns_payload,
verify_signature, parse_event) are exposed so callers can build custom
flows or run individual steps in isolation.
The Ruby SDK currently returns the parsed JSON as a Hash; typed event
classes will land in a future release.
Constant Summary collapse
- GZIP_MAGIC =
T.let("\x1f\x8b".b.freeze, String)
Class Method Summary collapse
- .constant_time_equal?(left, right) ⇒ Boolean
- .decode_sns_payload(notification_body) ⇒ Object
- .decode_sqs_payload(body) ⇒ Object
- .gunzip_payload(body) ⇒ Object
- .normalize_body(body) ⇒ Object
- .parse_event(payload) ⇒ Object
- .parse_sns(message) ⇒ Object
- .parse_sqs(message_body) ⇒ Object
- .verify_and_parse_webhook(body, signature, secret) ⇒ Object
- .verify_signature(body, signature, secret) ⇒ Object
Class Method Details
.constant_time_equal?(left, right) ⇒ Boolean
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/stream-chat/webhook.rb', line 190 def self.constant_time_equal?(left, right) a = left.b b = right.b return false unless a.bytesize == b.bytesize if OpenSSL.respond_to?(:fixed_length_secure_compare) OpenSSL.fixed_length_secure_compare(a, b) else a_bytes = a.bytes b_bytes = b.bytes diff = 0 a_bytes.each_with_index { |byte, i| diff |= byte ^ b_bytes[i] } diff.zero? end end |
.decode_sns_payload(notification_body) ⇒ Object
85 86 87 88 |
# File 'lib/stream-chat/webhook.rb', line 85 def self.decode_sns_payload(notification_body) inner = (notification_body) decode_sqs_payload(inner.nil? ? notification_body : inner) end |
.decode_sqs_payload(body) ⇒ Object
68 69 70 71 72 73 74 75 76 |
# File 'lib/stream-chat/webhook.rb', line 68 def self.decode_sqs_payload(body) decoded = begin Base64.strict_decode64(body) rescue ArgumentError raise InvalidWebhookError, InvalidWebhookError::INVALID_BASE64 end gunzip_payload(decoded) end |
.gunzip_payload(body) ⇒ Object
53 54 55 56 57 58 59 60 61 62 |
# File 'lib/stream-chat/webhook.rb', line 53 def self.gunzip_payload(body) raw = normalize_body(body) return raw unless raw.start_with?(GZIP_MAGIC) begin Zlib::GzipReader.new(StringIO.new(raw)).read.force_encoding(Encoding::ASCII_8BIT) rescue Zlib::Error raise InvalidWebhookError, InvalidWebhookError::GZIP_FAILED end end |
.normalize_body(body) ⇒ Object
35 36 37 38 39 40 41 42 43 |
# File 'lib/stream-chat/webhook.rb', line 35 def self.normalize_body(body) raw = if body.is_a?(Array) body.pack('C*') else String.new(body) end raw.force_encoding(Encoding::ASCII_8BIT) end |
.parse_event(payload) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/stream-chat/webhook.rb', line 133 def self.parse_event(payload) result = begin JSON.parse(payload) rescue JSON::ParserError raise InvalidWebhookError, InvalidWebhookError::INVALID_JSON end raise InvalidWebhookError, InvalidWebhookError::INVALID_JSON unless result.is_a?(Hash) result end |
.parse_sns(message) ⇒ Object
181 182 183 |
# File 'lib/stream-chat/webhook.rb', line 181 def self.parse_sns() parse_event(decode_sns_payload()) end |
.parse_sqs(message_body) ⇒ Object
175 176 177 |
# File 'lib/stream-chat/webhook.rb', line 175 def self.parse_sqs() parse_event(decode_sqs_payload()) end |
.verify_and_parse_webhook(body, signature, secret) ⇒ Object
168 169 170 |
# File 'lib/stream-chat/webhook.rb', line 168 def self.verify_and_parse_webhook(body, signature, secret) verify_and_parse_internal(gunzip_payload(body), signature, secret) end |
.verify_signature(body, signature, secret) ⇒ Object
120 121 122 123 124 |
# File 'lib/stream-chat/webhook.rb', line 120 def self.verify_signature(body, signature, secret) raw = normalize_body(body) expected = OpenSSL::HMAC.hexdigest('SHA256', secret, raw) constant_time_equal?(expected, signature) end |