action_webhook 0.1.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 +7 -0
- data/lib/action_webhook/base.rb +431 -0
- data/lib/action_webhook/delivery_job.rb +30 -0
- data/lib/action_webhook/version.rb +5 -0
- data/lib/action_webhook.rb +10 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2cd12e0bb7797fc35e1f987a3af410dd2a4389477d8170f3ab3865aa333d3ecf
|
4
|
+
data.tar.gz: 2b5e65c5f463f643dd9081d7dc9d899d289211da93d4ccd5708797d193bb4066
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 382b8c38b35ea02f8a0dc1ba62ff4df47acf14e30b70188fe0a5ebc0a1558bb1d25e75715de907f1eaa1e8ac8ac29825575fb7db5081a30202f5314bad4831bf
|
7
|
+
data.tar.gz: 80d3a48160158dfe614404a146280dcadd22040b89d18d0f3d0461f0bf04db3819aacf5985e33bee0dfc8e257836ff74dc46c5774a9c0b5df72da1cb09eb6a69
|
@@ -0,0 +1,431 @@
|
|
1
|
+
module ActionWebhook
|
2
|
+
# Base class for defining and delivering webhooks
|
3
|
+
#
|
4
|
+
# Subclass this and define webhook methods (e.g. `created`, `updated`) that
|
5
|
+
# define instance variables and call deliver to send webhooks.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# class UserWebhook < ActionWebhook::Base
|
10
|
+
# def created(user)
|
11
|
+
# @user = user
|
12
|
+
# # Get webhook endpoints from your database or config
|
13
|
+
# endpoints = WebhookSubscription.where(event: 'user.created').map do |sub|
|
14
|
+
# { url: sub.url, headers: { 'Authorization' => "Bearer #{sub.token}" } }
|
15
|
+
# end
|
16
|
+
# deliver(endpoints)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Then in your controller or model:
|
21
|
+
#
|
22
|
+
# # Send immediately
|
23
|
+
# UserWebhook.created(user).deliver_now
|
24
|
+
#
|
25
|
+
# # Send in background
|
26
|
+
# UserWebhook.created(user).deliver_later
|
27
|
+
#
|
28
|
+
class Base
|
29
|
+
# Add these lines near the top of your class
|
30
|
+
include GlobalID::Identification if defined?(GlobalID)
|
31
|
+
include ActiveJob::SerializationAdapter::ObjectSerializer if defined?(ActiveJob::SerializationAdapter)
|
32
|
+
|
33
|
+
# Delivery configuration
|
34
|
+
class_attribute :delivery_job, instance_writer: false, default: -> { "ActionWebhook::DeliveryJob".constantize }
|
35
|
+
class_attribute :deliver_later_queue_name, instance_writer: false
|
36
|
+
class_attribute :default_headers, instance_writer: false, default: {}
|
37
|
+
class_attribute :delivery_method, instance_writer: false, default: :deliver_now
|
38
|
+
class_attribute :perform_deliveries, instance_writer: false, default: true
|
39
|
+
|
40
|
+
# Retry configuration
|
41
|
+
class_attribute :max_retries, instance_writer: false, default: 3
|
42
|
+
class_attribute :retry_delay, instance_writer: false, default: 30.seconds
|
43
|
+
class_attribute :retry_backoff, instance_writer: false, default: :exponential # :linear or :exponential
|
44
|
+
class_attribute :retry_jitter, instance_writer: false, default: 5.seconds
|
45
|
+
|
46
|
+
# Callbacks
|
47
|
+
class_attribute :after_deliver_callback, instance_writer: false
|
48
|
+
class_attribute :after_retries_exhausted_callback, instance_writer: false
|
49
|
+
|
50
|
+
# The webhook action that will be performed
|
51
|
+
attr_accessor :action_name
|
52
|
+
|
53
|
+
# The webhook details (URLs and headers) that will be used
|
54
|
+
attr_accessor :webhook_details
|
55
|
+
|
56
|
+
# Stores the execution params for later delivery
|
57
|
+
attr_accessor :params
|
58
|
+
|
59
|
+
# Current attempt number for retries
|
60
|
+
attr_accessor :attempts
|
61
|
+
|
62
|
+
# Creates a new webhook and initializes its settings
|
63
|
+
def initialize
|
64
|
+
@_webhook_message = {}
|
65
|
+
@_webhook_defaults = {}
|
66
|
+
@attempts = 0
|
67
|
+
end
|
68
|
+
|
69
|
+
# Synchronously delivers the webhook
|
70
|
+
def deliver_now
|
71
|
+
@attempts += 1
|
72
|
+
response = process_webhook
|
73
|
+
|
74
|
+
# Call success callback if defined
|
75
|
+
if response.all? { |r| r[:success] }
|
76
|
+
invoke_callback(self.class.after_deliver_callback, response)
|
77
|
+
elsif @attempts < self.class.max_retries
|
78
|
+
# Schedule a retry with backoff
|
79
|
+
retry_with_backoff
|
80
|
+
else
|
81
|
+
# We've exhausted all retries
|
82
|
+
invoke_callback(self.class.after_retries_exhausted_callback, response)
|
83
|
+
end
|
84
|
+
|
85
|
+
response
|
86
|
+
end
|
87
|
+
|
88
|
+
# Helper method to invoke a callback that might be a symbol or a proc
|
89
|
+
def invoke_callback(callback, response)
|
90
|
+
return unless callback
|
91
|
+
|
92
|
+
case callback
|
93
|
+
when Symbol
|
94
|
+
send(callback, response)
|
95
|
+
when Proc
|
96
|
+
callback.call(self, response)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Enqueues the webhook delivery via ActiveJob
|
101
|
+
def deliver_later(options = {})
|
102
|
+
enqueue_delivery(:deliver_now, options)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Prepares the webhook for delivery using the current method as template name
|
106
|
+
# and instance variables as template data
|
107
|
+
#
|
108
|
+
# @param webhook_details [Array<Hash>] Array of hashes with :url and :headers keys
|
109
|
+
# @param params [Hash] Optional parameters to store for later delivery
|
110
|
+
# @return [DeliveryMessenger] A messenger object for further delivery options
|
111
|
+
def deliver(webhook_details, params = {})
|
112
|
+
# Determine action name from the caller
|
113
|
+
@action_name = caller_locations(1, 1)[0].label.to_sym
|
114
|
+
@webhook_details = webhook_details
|
115
|
+
@params = params
|
116
|
+
|
117
|
+
# Return self for chaining with delivery methods
|
118
|
+
DeliveryMessenger.new(self)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Renders a template based on the action name
|
122
|
+
#
|
123
|
+
# @param variables [Hash] Optional variables to add to template context
|
124
|
+
# @return [Hash] The JSON payload
|
125
|
+
def build_payload(variables = {})
|
126
|
+
# Combine instance variables and passed variables
|
127
|
+
assigns = {}
|
128
|
+
|
129
|
+
# Extract instance variables
|
130
|
+
instance_variables.each do |ivar|
|
131
|
+
# Skip internal instance variables
|
132
|
+
next if ivar.to_s.start_with?("@_")
|
133
|
+
next if [:@action_name, :@webhook_details, :@params, :@attempts].include?(ivar)
|
134
|
+
|
135
|
+
# Add to assigns with symbol key (without @)
|
136
|
+
assigns[ivar.to_s[1..-1].to_sym] = instance_variable_get(ivar)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Add passed variables
|
140
|
+
assigns.merge!(variables)
|
141
|
+
|
142
|
+
# Render the template
|
143
|
+
generate_json_from_template(@action_name, assigns)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Posts payload(s) to the given webhook endpoints
|
147
|
+
#
|
148
|
+
# @param webhook_details [Array<Hash>] An array of hashes containing `url` and `headers`
|
149
|
+
# @param payloads [Array<Hash>] One payload for each webhook
|
150
|
+
# @return [Array<Hash>] Array of response objects with status and body
|
151
|
+
def post_webhook(webhook_details, payloads)
|
152
|
+
responses = []
|
153
|
+
|
154
|
+
webhook_details.each_with_index do |detail, idx|
|
155
|
+
begin
|
156
|
+
# Ensure headers exists
|
157
|
+
detail[:headers] ||= {}
|
158
|
+
|
159
|
+
# Merge default headers
|
160
|
+
headers = default_headers.merge(detail[:headers])
|
161
|
+
|
162
|
+
# Add content type if not present
|
163
|
+
headers["Content-Type"] = "application/json" unless headers.key?("Content-Type")
|
164
|
+
|
165
|
+
# Add attempt tracking in headers
|
166
|
+
headers["X-Webhook-Attempt"] = @attempts.to_s if @attempts > 0
|
167
|
+
|
168
|
+
response = HTTParty.post(
|
169
|
+
detail[:url],
|
170
|
+
body: payloads[idx].to_json,
|
171
|
+
headers: headers,
|
172
|
+
timeout: 10 # Add reasonable timeout
|
173
|
+
)
|
174
|
+
|
175
|
+
responses << {
|
176
|
+
success: response.success?,
|
177
|
+
status: response.code,
|
178
|
+
body: response.body,
|
179
|
+
url: detail[:url],
|
180
|
+
attempt: @attempts
|
181
|
+
}
|
182
|
+
rescue => e
|
183
|
+
responses << {
|
184
|
+
success: false,
|
185
|
+
error: e.message,
|
186
|
+
url: detail[:url],
|
187
|
+
attempt: @attempts
|
188
|
+
}
|
189
|
+
logger.error("Webhook delivery failed: #{e.message} for URL: #{detail[:url]} (Attempt #{@attempts})")
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
responses
|
194
|
+
end
|
195
|
+
|
196
|
+
# Renders a JSON payload from a `.json.erb` template
|
197
|
+
#
|
198
|
+
# @param event_name [Symbol] the name of the webhook method (e.g. `:created`)
|
199
|
+
# @param assigns [Hash] local variables to pass into the template
|
200
|
+
# @return [Hash] the parsed JSON payload
|
201
|
+
def generate_json_from_template(event_name, assigns = {})
|
202
|
+
webhook_class_name = self.class.name.underscore
|
203
|
+
|
204
|
+
# Possible template locations
|
205
|
+
possible_paths = [
|
206
|
+
# Main app templates
|
207
|
+
File.join(Rails.root.to_s, "app/webhooks/#{webhook_class_name}/#{event_name}.json.erb"),
|
208
|
+
|
209
|
+
# Engine templates
|
210
|
+
engine_root && File.join(engine_root, "app/webhooks/#{webhook_class_name}/#{event_name}.json.erb"),
|
211
|
+
|
212
|
+
# Namespaced templates in engine
|
213
|
+
engine_root && self.class.module_parent != Object &&
|
214
|
+
File.join(engine_root, "app/webhooks/#{self.class.module_parent.name.underscore}/#{webhook_class_name.split('/').last}/#{event_name}.json.erb")
|
215
|
+
].compact
|
216
|
+
|
217
|
+
# Find the first template that exists
|
218
|
+
template_path = possible_paths.find { |path| File.exist?(path) }
|
219
|
+
|
220
|
+
unless template_path
|
221
|
+
raise ArgumentError, "Template not found for #{event_name} in paths:\n#{possible_paths.join("\n")}"
|
222
|
+
end
|
223
|
+
|
224
|
+
template = ERB.new(File.read(template_path))
|
225
|
+
json = template.result_with_hash(assigns)
|
226
|
+
JSON.parse(json)
|
227
|
+
rescue JSON::ParserError => e
|
228
|
+
raise "Invalid JSON in template #{event_name}: #{e.message}"
|
229
|
+
end
|
230
|
+
|
231
|
+
# Process the webhook to generate and send the payload
|
232
|
+
def process_webhook
|
233
|
+
# Render the message
|
234
|
+
payloads = [build_payload]
|
235
|
+
|
236
|
+
# Post the webhook
|
237
|
+
post_webhook(webhook_details, payloads)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Schedule a retry with appropriate backoff delay
|
241
|
+
# Modify the retry_with_backoff method:
|
242
|
+
def retry_with_backoff
|
243
|
+
delay = calculate_backoff_delay
|
244
|
+
|
245
|
+
logger.info("Scheduling webhook retry #{@attempts + 1}/#{self.class.max_retries} in #{delay} seconds")
|
246
|
+
|
247
|
+
# Get the actual job class by evaluating the proc
|
248
|
+
job_class = self.class.delivery_job.is_a?(Proc) ? self.class.delivery_job.call : self.class.delivery_job
|
249
|
+
|
250
|
+
# Serialize the webhook and pass the serialized data instead of the object
|
251
|
+
serialized_webhook = serialize
|
252
|
+
|
253
|
+
# Re-enqueue with the calculated delay
|
254
|
+
if deliver_later_queue_name
|
255
|
+
job_class.set(queue: deliver_later_queue_name, wait: delay).perform_later('deliver_now', serialized_webhook)
|
256
|
+
else
|
257
|
+
job_class.set(wait: delay).perform_later('deliver_now', serialized_webhook)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Calculate delay based on retry strategy
|
262
|
+
def calculate_backoff_delay
|
263
|
+
base_delay = self.class.retry_delay
|
264
|
+
|
265
|
+
case self.class.retry_backoff
|
266
|
+
when :exponential
|
267
|
+
# 30s, 60s, 120s, etc.
|
268
|
+
delay = base_delay * (2 ** (@attempts - 1))
|
269
|
+
when :linear
|
270
|
+
# 30s, 60s, 90s, etc.
|
271
|
+
delay = base_delay * @attempts
|
272
|
+
else
|
273
|
+
delay = base_delay
|
274
|
+
end
|
275
|
+
|
276
|
+
# Add jitter to prevent thundering herd problem
|
277
|
+
jitter = rand(self.class.retry_jitter)
|
278
|
+
delay + jitter
|
279
|
+
end
|
280
|
+
|
281
|
+
# For ActiveJob serialization
|
282
|
+
def serialize
|
283
|
+
{
|
284
|
+
'action_name' => @action_name.to_s,
|
285
|
+
'webhook_details' => @webhook_details,
|
286
|
+
'params' => @params,
|
287
|
+
'attempts' => @attempts,
|
288
|
+
'instance_variables' => collect_instance_variables,
|
289
|
+
'webhook_class' => self.class.name
|
290
|
+
}
|
291
|
+
end
|
292
|
+
|
293
|
+
# Restores state from serialized data
|
294
|
+
def deserialize(data)
|
295
|
+
@action_name = data['action_name'].to_sym
|
296
|
+
@webhook_details = data['webhook_details']
|
297
|
+
@params = data['params']
|
298
|
+
@attempts = data['attempts'] || 0
|
299
|
+
|
300
|
+
# Restore instance variables
|
301
|
+
data['instance_variables'].each do |name, value|
|
302
|
+
instance_variable_set("@#{name}", value)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
private
|
307
|
+
|
308
|
+
# Collects all non-system instance variables for serialization
|
309
|
+
def collect_instance_variables
|
310
|
+
result = {}
|
311
|
+
instance_variables.each do |ivar|
|
312
|
+
# Skip internal instance variables
|
313
|
+
next if ivar.to_s.start_with?("@_")
|
314
|
+
next if [:@action_name, :@webhook_details, :@params, :@attempts].include?(ivar)
|
315
|
+
|
316
|
+
# Add to result without @
|
317
|
+
result[ivar.to_s[1..-1]] = instance_variable_get(ivar)
|
318
|
+
end
|
319
|
+
result
|
320
|
+
end
|
321
|
+
|
322
|
+
# Find the engine root path
|
323
|
+
def engine_root
|
324
|
+
return nil unless defined?(Rails::Engine)
|
325
|
+
|
326
|
+
mod = self.class.module_parent
|
327
|
+
while mod != Object
|
328
|
+
constants = mod.constants.select { |c|
|
329
|
+
const = mod.const_get(c)
|
330
|
+
const.is_a?(Class) && const < Rails::Engine
|
331
|
+
}
|
332
|
+
|
333
|
+
return mod.const_get(constants.first).root.to_s if constants.any?
|
334
|
+
mod = mod.module_parent
|
335
|
+
end
|
336
|
+
|
337
|
+
nil
|
338
|
+
end
|
339
|
+
|
340
|
+
# Similarly update enqueue_delivery:
|
341
|
+
def enqueue_delivery(delivery_method, options = {})
|
342
|
+
options = options.dup
|
343
|
+
queue = options.delete(:queue) || self.class.deliver_later_queue_name
|
344
|
+
|
345
|
+
args = [delivery_method.to_s]
|
346
|
+
|
347
|
+
# Get the actual job class by evaluating the proc
|
348
|
+
job_class = self.class.delivery_job.is_a?(Proc) ? self.class.delivery_job.call : self.class.delivery_job
|
349
|
+
|
350
|
+
# Serialize the webhook
|
351
|
+
serialized_webhook = serialize
|
352
|
+
|
353
|
+
# Use the delivery job to perform the delivery with serialized data
|
354
|
+
if queue
|
355
|
+
job_class.set(queue: queue).perform_later(*args, serialized_webhook)
|
356
|
+
else
|
357
|
+
job_class.set(wait: options[:wait]).perform_later(*args, serialized_webhook) if options[:wait]
|
358
|
+
job_class.perform_later(*args, serialized_webhook) unless options[:wait]
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# Logger for webhook errors
|
363
|
+
def logger
|
364
|
+
Rails.logger
|
365
|
+
end
|
366
|
+
|
367
|
+
# Class methods
|
368
|
+
class << self
|
369
|
+
# Handle method calls on the class
|
370
|
+
def method_missing(method_name, *args, &block)
|
371
|
+
if public_instance_methods(false).include?(method_name)
|
372
|
+
# Create a new instance
|
373
|
+
webhook = new
|
374
|
+
|
375
|
+
# Call the instance method
|
376
|
+
webhook.send(method_name, *args, &block)
|
377
|
+
else
|
378
|
+
super
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def respond_to_missing?(method_name, include_private = false)
|
383
|
+
public_instance_methods(false).include?(method_name) || super
|
384
|
+
end
|
385
|
+
|
386
|
+
# Test delivery collection
|
387
|
+
def deliveries
|
388
|
+
@deliveries ||= []
|
389
|
+
end
|
390
|
+
|
391
|
+
# Reset the test delivery collection
|
392
|
+
def clear_deliveries
|
393
|
+
@deliveries = []
|
394
|
+
end
|
395
|
+
|
396
|
+
# Register callback for successful delivery
|
397
|
+
def after_deliver(method_name = nil, &block)
|
398
|
+
self.after_deliver_callback = block_given? ? block : method_name
|
399
|
+
end
|
400
|
+
|
401
|
+
# Register callback for when retries are exhausted
|
402
|
+
def after_retries_exhausted(method_name = nil, &block)
|
403
|
+
self.after_retries_exhausted_callback = block_given? ? block : method_name
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Delivery messenger for ActionMailer-like API
|
409
|
+
class DeliveryMessenger
|
410
|
+
def initialize(webhook)
|
411
|
+
@webhook = webhook
|
412
|
+
end
|
413
|
+
|
414
|
+
def deliver_now
|
415
|
+
if @webhook.respond_to?(:perform_deliveries) && !@webhook.perform_deliveries
|
416
|
+
# Skip delivery
|
417
|
+
nil
|
418
|
+
elsif @webhook.class.delivery_method == :test
|
419
|
+
# Test delivery
|
420
|
+
ActionWebhook::Base.deliveries << @webhook
|
421
|
+
else
|
422
|
+
# Normal delivery
|
423
|
+
@webhook.deliver_now
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def deliver_later(options = {})
|
428
|
+
@webhook.deliver_later(options)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ActionWebhook
|
2
|
+
# Job responsible for delivering webhooks in the background
|
3
|
+
class DeliveryJob < ActiveJob::Base
|
4
|
+
queue_as { ActionWebhook::Base.deliver_later_queue_name || :webhooks }
|
5
|
+
|
6
|
+
# Performs the webhook delivery with the specified delivery method
|
7
|
+
#
|
8
|
+
# @param delivery_method [String] The delivery method to call (e.g., "deliver_now")
|
9
|
+
# @param serialized_webhook [Hash] The serialized webhook data
|
10
|
+
def perform(delivery_method, serialized_webhook)
|
11
|
+
# Reconstruct the webhook from serialized data
|
12
|
+
webhook_class = serialized_webhook['webhook_class'].constantize
|
13
|
+
webhook = webhook_class.new
|
14
|
+
webhook.deserialize(serialized_webhook)
|
15
|
+
|
16
|
+
# Invoke the specified delivery method
|
17
|
+
webhook.send(delivery_method)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Handles serialization failures by logging errors
|
21
|
+
rescue_from StandardError do |exception|
|
22
|
+
# Log the error
|
23
|
+
Rails.logger.error("ActionWebhook delivery failed: #{exception.message}")
|
24
|
+
Rails.logger.error(exception.backtrace.join("\n"))
|
25
|
+
|
26
|
+
# Re-raise the exception if in development or test
|
27
|
+
raise exception if Rails.env.development? || Rails.env.test?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_webhook
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vinay Uttam Vemparala
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-04-25 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rails
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '7'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '7'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: httparty
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.18.1
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.18.1
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: yard
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
description: A Rails library for triggering webhooks. Inspired by ActionMailer from
|
55
|
+
Rails
|
56
|
+
email:
|
57
|
+
- 15381417+vinayuttam@users.noreply.github.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- lib/action_webhook.rb
|
63
|
+
- lib/action_webhook/base.rb
|
64
|
+
- lib/action_webhook/delivery_job.rb
|
65
|
+
- lib/action_webhook/version.rb
|
66
|
+
homepage: https://github.com/vinayuttam/action_webhook.git
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata:
|
70
|
+
homepage_uri: https://github.com/vinayuttam/action_webhook.git
|
71
|
+
source_code_uri: https://github.com/vinayuttam/action_webhook.git
|
72
|
+
changelog_uri: https://github.com/vinayuttam/action_webhook.git/blob/main/CHANGELOG.md
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 3.1.0
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubygems_version: 3.6.2
|
88
|
+
specification_version: 4
|
89
|
+
summary: A gem for triggering webhooks similar to trigger emails on Rails
|
90
|
+
test_files: []
|