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 +4 -4
- data/lib/action_webhook/base.rb +99 -5
- data/lib/action_webhook/version.rb +1 -1
- metadata +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f4e6800e63275b752cf3ac373b19b461e87800f794b7c0c547329537a993dc84
|
4
|
+
data.tar.gz: b1f879d8133a5b36fed56f01f689149cfb677d2f45af54b1b3c6bc2f8ad1216d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a7841d4ca8b3af923e88442b2ec1337598644008a244edfe089afdff178cfcfe9f7dc6b8301a51c03d145cd6501940a4ddf24eefc902f3c729f6c26c72240af
|
7
|
+
data.tar.gz: e415ec27166e44d35f05a1d28ace5aee7b7584545bce5521fd66f74370162ac69e8545281ca66bedb6add13f82e9a9048bd15c3b670587b651bdd3b4223cb99f
|
data/lib/action_webhook/base.rb
CHANGED
@@ -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
|
-
#
|
45
|
-
#
|
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
|
-
#
|
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
|
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
|
-
|
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
|
|
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
|
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-
|
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
|