action_webhook 1.1.0 → 1.2.1

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: 2063e93683fe64ce519f241843f17e33da9f29d671b965af97c0f97540a98ecc
4
- data.tar.gz: 1f591bf9cfb727d9ab6e750338b708e4e24df2e782ea3f1a5096277f4311474d
3
+ metadata.gz: f4e6800e63275b752cf3ac373b19b461e87800f794b7c0c547329537a993dc84
4
+ data.tar.gz: b1f879d8133a5b36fed56f01f689149cfb677d2f45af54b1b3c6bc2f8ad1216d
5
5
  SHA512:
6
- metadata.gz: 14ed8242d705d62f573fd884e83bc70479ed4397c4bb3fffd75fd178bdc929fe5d981c395abb99ab7d647c7f1aec3aeaf1685784b3d521e3e62ed2e073ee8c1c
7
- data.tar.gz: 45c7362968926262d79f6dedb16f2391d30a2b64980e3a5d0116558360ee63fe416b54f33a17012b82e5b50d8a465a58d5f91e1b6fa1f05e185959a2ce547230
6
+ metadata.gz: 6a7841d4ca8b3af923e88442b2ec1337598644008a244edfe089afdff178cfcfe9f7dc6b8301a51c03d145cd6501940a4ddf24eefc902f3c729f6c26c72240af
7
+ data.tar.gz: e415ec27166e44d35f05a1d28ace5aee7b7584545bce5521fd66f74370162ac69e8545281ca66bedb6add13f82e9a9048bd15c3b670587b651bdd3b4223cb99f
@@ -1,9 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionWebhook
2
4
  # Base class for defining and delivering webhooks
3
5
  #
4
6
  # Subclass this and define webhook methods (e.g. `created`, `updated`) that
5
7
  # define instance variables and call deliver to send webhooks.
6
8
  #
9
+ # Headers can be provided in two formats:
10
+ # 1. Hash format: { 'Authorization' => 'Bearer token', 'Content-Type' => 'application/json' }
11
+ # 2. Array format: [{ 'key' => 'Authorization', 'value' => 'Bearer token' }, { 'key' => 'Content-Type', 'value' => 'application/json' }]
12
+ #
13
+ # Debug mode can be enabled to log header processing and request details:
14
+ # class MyWebhook < ActionWebhook::Base
15
+ # self.debug_headers = true
16
+ # end
17
+ #
7
18
  # Example:
8
19
  #
9
20
  # class UserWebhook < ActionWebhook::Base
@@ -38,13 +49,30 @@ module ActionWebhook
38
49
  #
39
50
  # class UserWebhook < ActionWebhook::Base
40
51
  # self.deliver_later_queue_name = 'webhooks'
52
+ # self.debug_headers = true # Enable header debugging
41
53
  #
42
54
  # def created(user)
43
55
  # @user = user
44
- # endpoints = WebhookSubscription.where(event: 'user.created').map do |sub|
45
- # { url: sub.url, headers: { 'Authorization' => "Bearer #{sub.token}" } }
56
+ # # Headers can be provided as a hash
57
+ # endpoints_with_hash_headers = WebhookSubscription.where(event: 'user.created').map do |sub|
58
+ # {
59
+ # url: sub.url,
60
+ # headers: { 'Authorization' => "Bearer #{sub.token}", 'X-Custom-Header' => 'value' }
61
+ # }
46
62
  # end
47
- # deliver(endpoints)
63
+ #
64
+ # # Or headers can be provided as an array of key/value objects (useful for database storage)
65
+ # endpoints_with_array_headers = WebhookSubscription.where(event: 'user.created').map do |sub|
66
+ # {
67
+ # url: sub.url,
68
+ # headers: [
69
+ # { 'key' => 'Authorization', 'value' => "Bearer #{sub.token}" },
70
+ # { 'key' => 'X-Custom-Header', 'value' => 'value' }
71
+ # ]
72
+ # }
73
+ # end
74
+ #
75
+ # deliver(endpoints_with_hash_headers)
48
76
  # end
49
77
  # end
50
78
  #
@@ -59,6 +87,9 @@ module ActionWebhook
59
87
  class_attribute :delivery_method, instance_writer: false, default: :deliver_now
60
88
  class_attribute :perform_deliveries, instance_writer: false, default: true
61
89
 
90
+ # Debug configuration
91
+ class_attribute :debug_headers, instance_writer: false, default: false
92
+
62
93
  # Retry configuration
63
94
  class_attribute :max_retries, instance_writer: false, default: 3
64
95
  class_attribute :retry_delay, instance_writer: false, default: 30.seconds
@@ -91,7 +122,11 @@ module ActionWebhook
91
122
  # Handle failed responses
92
123
  if failed_responses.any? && @attempts < self.class.max_retries
93
124
  # Extract failed webhook details for retry
94
- failed_webhook_details = failed_responses.map { |r| @webhook_details.find { |detail| detail[:url] == r[:url] } }.compact
125
+ failed_webhook_details = failed_responses.map do |r|
126
+ @webhook_details.find do |detail|
127
+ detail[:url] == r[:url]
128
+ end
129
+ end.compact
95
130
  retry_with_backoff(failed_webhook_details)
96
131
  elsif failed_responses.any?
97
132
  # All retries exhausted for failed URLs
@@ -234,14 +269,73 @@ module ActionWebhook
234
269
  assigns
235
270
  end
236
271
 
272
+ # Builds HTTP headers for webhook requests
273
+ #
274
+ # Supports two input formats:
275
+ # 1. Hash format: { 'Authorization' => 'Bearer token', 'Content-Type' => 'application/json' }
276
+ # 2. Array format: [{ 'key' => 'Authorization', 'value' => 'Bearer token' }, { 'key' => 'Content-Type', 'value' => 'application/json' }]
277
+ #
278
+ # The array format is useful when storing headers in databases where you need
279
+ # structured data with separate key and value fields.
280
+ #
281
+ # @param detail_headers [Hash, Array, nil] Headers in hash or array format
282
+ # @return [Hash] Formatted headers hash ready for HTTP request
237
283
  def build_headers(detail_headers)
238
- headers = default_headers.merge(detail_headers)
284
+ # Handle both hash format and array format with key/value objects
285
+ processed_headers = case detail_headers
286
+ when Array
287
+ # Transform array of header hashes [{'key': 'value'}] into a single hash
288
+ detail_headers.each_with_object({}) do |header_item, acc|
289
+ next unless header_item.is_a?(Hash)
290
+
291
+ # Handle string keys
292
+ if header_item.key?("key") && header_item.key?("value")
293
+ key = header_item["key"]
294
+ value = header_item["value"]
295
+ acc[key.to_s] = value.to_s if key && value
296
+ # Handle symbol keys
297
+ elsif header_item.key?(:key) && header_item.key?(:value)
298
+ key = header_item[:key]
299
+ value = header_item[:value]
300
+ acc[key.to_s] = value.to_s if key && value
301
+ else
302
+ # Log warning for malformed header items
303
+ logger&.warn("Skipping malformed header item: #{header_item.inspect}")
304
+ end
305
+ end
306
+ when Hash
307
+ # Ensure all keys and values are strings for consistency
308
+ detail_headers.transform_keys(&:to_s).transform_values(&:to_s)
309
+ when NilClass
310
+ {}
311
+ else
312
+ logger&.warn("Unknown header format: #{detail_headers.class}. Expected Hash or Array.")
313
+ {}
314
+ end
315
+
316
+ headers = default_headers.merge(processed_headers)
239
317
  headers["Content-Type"] = "application/json" unless headers.key?("Content-Type")
240
318
  headers["X-Webhook-Attempt"] = @attempts.to_s if @attempts.positive?
319
+
320
+ # Debug headers if enabled
321
+ if self.class.debug_headers
322
+ logger&.info("ActionWebhook Headers Debug:")
323
+ logger&.info(" Default headers: #{default_headers}")
324
+ logger&.info(" Processed headers: #{processed_headers}")
325
+ logger&.info(" Final headers: #{headers}")
326
+ end
327
+
241
328
  headers
242
329
  end
243
330
 
244
331
  def send_webhook_request(url, payload, headers)
332
+ if self.class.debug_headers
333
+ logger&.info("ActionWebhook Request Debug:")
334
+ logger&.info(" URL: #{url}")
335
+ logger&.info(" Headers: #{headers}")
336
+ logger&.info(" Payload size: #{payload.to_json.bytesize} bytes")
337
+ end
338
+
245
339
  HTTParty.post(url, body: payload.to_json, headers: headers, timeout: 10)
246
340
  end
247
341
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionWebhook
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.1"
5
5
  end
metadata CHANGED
@@ -1,28 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_webhook
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vinay Uttam Vemparala
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-06 00:00:00.000000000 Z
10
+ date: 2025-07-31 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: httparty
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: 0.18.1
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: 0.18.1
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: activejob
28
14
  requirement: !ruby/object:Gem::Requirement
@@ -57,6 +43,20 @@ dependencies:
57
43
  - - "~>"
58
44
  - !ruby/object:Gem::Version
59
45
  version: '1.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: httparty
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: 0.18.1
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 0.18.1
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: yard
62
62
  requirement: !ruby/object:Gem::Requirement