hooksniff 1.1.1 → 1.3.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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +45 -20
- data/hooksniff.gemspec +1 -1
- data/lib/hooksniff/api/alert.rb +34 -0
- data/lib/hooksniff/api/analytics.rb +21 -0
- data/lib/hooksniff/api/api_key.rb +26 -0
- data/lib/hooksniff/api/application.rb +30 -0
- data/lib/hooksniff/api/audit_log.rb +17 -0
- data/lib/hooksniff/api/authentication.rb +0 -13
- data/lib/hooksniff/api/background_task.rb +21 -0
- data/lib/hooksniff/api/billing.rb +34 -0
- data/lib/hooksniff/api/connector.rb +33 -0
- data/lib/hooksniff/api/custom_domain.rb +26 -0
- data/lib/hooksniff/api/environment.rb +47 -0
- data/lib/hooksniff/api/inbound.rb +30 -0
- data/lib/hooksniff/api/integration.rb +8 -8
- data/lib/hooksniff/api/message_poller.rb +21 -0
- data/lib/hooksniff/api/notification.rb +30 -0
- data/lib/hooksniff/api/operational_webhook.rb +34 -0
- data/lib/hooksniff/api/playground.rb +17 -0
- data/lib/hooksniff/api/portal.rb +33 -0
- data/lib/hooksniff/api/rate_limit.rb +26 -0
- data/lib/hooksniff/api/routing.rb +21 -0
- data/lib/hooksniff/api/schema.rb +25 -0
- data/lib/hooksniff/api/search.rb +13 -0
- data/lib/hooksniff/api/service_token.rb +22 -0
- data/lib/hooksniff/api/sso.rb +26 -0
- data/lib/hooksniff/api/statistics.rb +0 -13
- data/lib/hooksniff/api/stream.rb +28 -9
- data/lib/hooksniff/api/team.rb +42 -0
- data/lib/hooksniff/api/template.rb +21 -0
- data/lib/hooksniff/errors.rb +79 -0
- data/lib/hooksniff/hooksniff_http_client.rb +48 -9
- data/lib/hooksniff/models/aggregate_event_types_out.rb +59 -0
- data/lib/hooksniff/models/message_attempt_recovered_event.rb +53 -0
- data/lib/hooksniff/models/message_attempt_recovered_event_data.rb +70 -0
- data/lib/hooksniff/models/message_in.rb +1 -0
- data/lib/hooksniff/options.rb +31 -0
- data/lib/hooksniff/paginator.rb +45 -0
- data/lib/hooksniff/response_metadata.rb +48 -0
- data/lib/hooksniff/version.rb +1 -1
- data/lib/hooksniff/webhook.rb +62 -0
- data/lib/hooksniff/webhook_event.rb +295 -0
- data/lib/hooksniff.rb +135 -66
- data/test/test_hooksniff.rb +0 -11
- data/test/test_typed_events.rb +260 -0
- metadata +36 -13
- data/lib/hooksniff/background_task.rb +0 -21
- data/lib/hooksniff/connector.rb +0 -33
- data/lib/hooksniff/environment.rb +0 -53
- data/lib/hooksniff/inbound.rb +0 -25
- data/lib/hooksniff/message_poller.rb +0 -32
- data/lib/hooksniff/operational_webhook.rb +0 -12
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HookSniff
|
|
4
|
+
class RateLimit
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list
|
|
10
|
+
@client.execute_request("GET", "/v1/rate-limits")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get(endpoint_id)
|
|
14
|
+
@client.execute_request("GET", "/v1/rate-limits/#{endpoint_id}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def set(endpoint_id, attrs)
|
|
18
|
+
@client.execute_request("POST", "/v1/rate-limits/#{endpoint_id}", body: attrs)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def delete(endpoint_id)
|
|
22
|
+
@client.execute_request("DELETE", "/v1/rate-limits/#{endpoint_id}")
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HookSniff
|
|
4
|
+
class Routing
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def get(endpoint_id)
|
|
10
|
+
@client.execute_request("GET", "/v1/routing/#{endpoint_id}/routing")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def update(endpoint_id, attrs)
|
|
14
|
+
@client.execute_request("PUT", "/v1/routing/#{endpoint_id}/routing", body: attrs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_health(endpoint_id)
|
|
18
|
+
@client.execute_request("GET", "/v1/routing/#{endpoint_id}/health")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HookSniff
|
|
4
|
+
class Schema
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list
|
|
10
|
+
@client.execute_request("GET", "/v1/schemas")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get(id)
|
|
14
|
+
@client.execute_request("GET", "/v1/schemas/#{id}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def register(attrs)
|
|
18
|
+
@client.execute_request("POST", "/v1/schemas", body: attrs)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def validate(id, attrs)
|
|
22
|
+
@client.execute_request("POST", "/v1/schemas/#{id}/validate", body: attrs)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HookSniff
|
|
4
|
+
class ServiceToken
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list
|
|
10
|
+
@client.execute_request("GET", "/v1/service-tokens")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create(attrs)
|
|
14
|
+
@client.execute_request("POST", "/v1/service-tokens", body: attrs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def delete(id)
|
|
18
|
+
@client.execute_request("DELETE", "/v1/service-tokens/#{id}")
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HookSniff
|
|
4
|
+
class Sso
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def get_config
|
|
10
|
+
@client.execute_request("GET", "/v1/sso/config")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def upsert_config(attrs)
|
|
14
|
+
@client.execute_request("POST", "/v1/sso/config", body: attrs)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def delete_config
|
|
18
|
+
@client.execute_request("DELETE", "/v1/sso/config")
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test
|
|
23
|
+
@client.execute_request("POST", "/v1/sso/test")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -20,18 +20,5 @@ module HookSniff
|
|
|
20
20
|
)
|
|
21
21
|
AggregateEventTypesOut.deserialize(res)
|
|
22
22
|
end
|
|
23
|
-
|
|
24
|
-
def app_stats(app_id, options = {})
|
|
25
|
-
options = options.transform_keys(&:to_s)
|
|
26
|
-
res = @client.execute_request(
|
|
27
|
-
"GET",
|
|
28
|
-
"/v1/stats/app/#{app_id}",
|
|
29
|
-
query_params: {
|
|
30
|
-
"since" => options["since"],
|
|
31
|
-
"until" => options["until"]
|
|
32
|
-
}
|
|
33
|
-
)
|
|
34
|
-
res
|
|
35
|
-
end
|
|
36
23
|
end
|
|
37
24
|
end
|
data/lib/hooksniff/api/stream.rb
CHANGED
|
@@ -7,39 +7,58 @@ module HookSniff
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def list_channels
|
|
10
|
-
@client.request(:get, "/
|
|
10
|
+
@client.request(:get, "/v1/stream/channels")
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def get_channel(id)
|
|
14
|
-
@client.request(:get, "/
|
|
14
|
+
@client.request(:get, "/v1/stream/channels/#{id}")
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def create_channel(attrs)
|
|
18
|
-
@client.request(:post, "/
|
|
18
|
+
@client.request(:post, "/v1/stream/channels", attrs)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def update_channel(id, attrs)
|
|
22
|
-
@client.request(:put, "/
|
|
22
|
+
@client.request(:put, "/v1/stream/channels/#{id}", attrs)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def delete_channel(id)
|
|
26
|
-
@client.request(:delete, "/
|
|
26
|
+
@client.request(:delete, "/v1/stream/channels/#{id}")
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def list_messages(id, params = {})
|
|
30
|
-
@client.request(:get, "/
|
|
30
|
+
@client.request(:get, "/v1/stream/channels/#{id}/messages", params)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def list_subscriptions
|
|
34
|
-
@client.request(:get, "/
|
|
34
|
+
@client.request(:get, "/v1/stream/subscriptions")
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def disconnect_subscription(id)
|
|
38
|
-
@client.request(:delete, "/
|
|
38
|
+
@client.request(:delete, "/v1/stream/subscriptions/#{id}")
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def publish(body)
|
|
42
|
-
@client.request(:post, "/
|
|
42
|
+
@client.request(:post, "/v1/stream/publish", body)
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
# Subscribe to real-time events via SSE
|
|
48
|
+
# @param channel_id [String] Channel ID to subscribe to
|
|
49
|
+
# @param block [Block] Callback for each event
|
|
50
|
+
# @return [void]
|
|
51
|
+
def subscribe(channel_id, &block)
|
|
52
|
+
@client.request_stream(:get, "/v1/stream/channels/#{channel_id}/subscribe") do |event|
|
|
53
|
+
block.call(event)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Subscribe to delivery events (legacy SSE endpoint)
|
|
58
|
+
# @param block [Block] Callback for each event
|
|
59
|
+
# @return [void]
|
|
60
|
+
def subscribe_deliveries(&block)
|
|
61
|
+
@client.request_stream(:get, "/v1/stream/deliveries") do |event|
|
|
62
|
+
block.call(event)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HookSniff
|
|
4
|
+
class Team
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list
|
|
10
|
+
@client.execute_request("GET", "/v1/teams")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get(id)
|
|
14
|
+
@client.execute_request("GET", "/v1/teams/#{id}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create(attrs)
|
|
18
|
+
@client.execute_request("POST", "/v1/teams", body: attrs)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def accept_invite(attrs)
|
|
22
|
+
@client.execute_request("POST", "/v1/teams/accept-invite", body: attrs)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def invite(team_id, attrs)
|
|
26
|
+
@client.execute_request("POST", "/v1/teams/#{team_id}/invite", body: attrs)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def list_members(team_id)
|
|
30
|
+
@client.execute_request("GET", "/v1/teams/#{team_id}/members")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def remove_member(team_id, user_id)
|
|
34
|
+
@client.execute_request("DELETE", "/v1/teams/#{team_id}/members/#{user_id}")
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def change_role(team_id, user_id, attrs)
|
|
39
|
+
@client.execute_request("PUT", "/v1/teams/#{team_id}/members/#{user_id}/role", body: attrs)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HookSniff
|
|
4
|
+
class Template
|
|
5
|
+
def initialize(client)
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def list
|
|
10
|
+
@client.execute_request("GET", "/v1/templates")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get(id)
|
|
14
|
+
@client.execute_request("GET", "/v1/templates/#{id}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def apply(id, attrs)
|
|
18
|
+
@client.execute_request("POST", "/v1/templates/#{id}/apply", body: attrs)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/hooksniff/errors.rb
CHANGED
|
@@ -41,6 +41,13 @@ module HookSniff
|
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# 408 Request Timeout
|
|
45
|
+
class RequestTimeoutError < ApiError
|
|
46
|
+
def initialize(response_headers: {}, response_body: nil)
|
|
47
|
+
super(code: 408, response_headers: response_headers, response_body: response_body)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
44
51
|
# 409 Conflict
|
|
45
52
|
class ConflictError < ApiError
|
|
46
53
|
def initialize(response_headers: {}, response_body: nil)
|
|
@@ -48,6 +55,20 @@ module HookSniff
|
|
|
48
55
|
end
|
|
49
56
|
end
|
|
50
57
|
|
|
58
|
+
# 410 Gone
|
|
59
|
+
class GoneError < ApiError
|
|
60
|
+
def initialize(response_headers: {}, response_body: nil)
|
|
61
|
+
super(code: 410, response_headers: response_headers, response_body: response_body)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 413 Payload Too Large
|
|
66
|
+
class PayloadTooLargeError < ApiError
|
|
67
|
+
def initialize(response_headers: {}, response_body: nil)
|
|
68
|
+
super(code: 413, response_headers: response_headers, response_body: response_body)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
51
72
|
# 422 Unprocessable Entity
|
|
52
73
|
class UnprocessableEntityError < ApiError
|
|
53
74
|
attr_reader :validation_errors
|
|
@@ -75,6 +96,13 @@ module HookSniff
|
|
|
75
96
|
end
|
|
76
97
|
end
|
|
77
98
|
|
|
99
|
+
# 501 Not Implemented
|
|
100
|
+
class NotImplementedError < ApiError
|
|
101
|
+
def initialize(response_headers: {}, response_body: nil)
|
|
102
|
+
super(code: 501, response_headers: response_headers, response_body: response_body)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
78
106
|
# 502 Bad Gateway
|
|
79
107
|
class BadGatewayError < ApiError
|
|
80
108
|
def initialize(response_headers: {}, response_body: nil)
|
|
@@ -96,6 +124,45 @@ module HookSniff
|
|
|
96
124
|
end
|
|
97
125
|
end
|
|
98
126
|
|
|
127
|
+
# 507 Insufficient Storage
|
|
128
|
+
class InsufficientStorageError < ApiError
|
|
129
|
+
def initialize(response_headers: {}, response_body: nil)
|
|
130
|
+
super(code: 507, response_headers: response_headers, response_body: response_body)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# 508 Loop Detected
|
|
135
|
+
class LoopDetectedError < ApiError
|
|
136
|
+
def initialize(response_headers: {}, response_body: nil)
|
|
137
|
+
super(code: 508, response_headers: response_headers, response_body: response_body)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Timeout — request exceeded the configured timeout
|
|
142
|
+
class TimeoutError < StandardError
|
|
143
|
+
attr_reader :code
|
|
144
|
+
def initialize(message = "Request timeout")
|
|
145
|
+
@code = 0
|
|
146
|
+
super(message)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Network error — connection failed
|
|
151
|
+
class NetworkError < StandardError
|
|
152
|
+
attr_reader :code
|
|
153
|
+
def initialize(message = "Network error")
|
|
154
|
+
@code = 0
|
|
155
|
+
super(message)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Authentication error — token invalid, expired, or missing
|
|
160
|
+
class AuthenticationError < ApiError
|
|
161
|
+
def initialize(response_headers: {}, response_body: nil)
|
|
162
|
+
super(code: 401, response_headers: response_headers, response_body: response_body)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
99
166
|
# Create the appropriate error from a status code
|
|
100
167
|
def self.create_error_from_status(status_code, response_headers: {}, response_body: nil)
|
|
101
168
|
case status_code
|
|
@@ -107,8 +174,14 @@ module HookSniff
|
|
|
107
174
|
ForbiddenError.new(response_headers: response_headers, response_body: response_body)
|
|
108
175
|
when 404
|
|
109
176
|
NotFoundError.new(response_headers: response_headers, response_body: response_body)
|
|
177
|
+
when 408
|
|
178
|
+
RequestTimeoutError.new(response_headers: response_headers, response_body: response_body)
|
|
110
179
|
when 409
|
|
111
180
|
ConflictError.new(response_headers: response_headers, response_body: response_body)
|
|
181
|
+
when 410
|
|
182
|
+
GoneError.new(response_headers: response_headers, response_body: response_body)
|
|
183
|
+
when 413
|
|
184
|
+
PayloadTooLargeError.new(response_headers: response_headers, response_body: response_body)
|
|
112
185
|
when 422
|
|
113
186
|
UnprocessableEntityError.new(response_headers: response_headers, response_body: response_body)
|
|
114
187
|
when 429
|
|
@@ -116,12 +189,18 @@ module HookSniff
|
|
|
116
189
|
RateLimitError.new(retry_after: retry_after, response_headers: response_headers, response_body: response_body)
|
|
117
190
|
when 500
|
|
118
191
|
InternalServerError.new(response_headers: response_headers, response_body: response_body)
|
|
192
|
+
when 501
|
|
193
|
+
NotImplementedError.new(response_headers: response_headers, response_body: response_body)
|
|
119
194
|
when 502
|
|
120
195
|
BadGatewayError.new(response_headers: response_headers, response_body: response_body)
|
|
121
196
|
when 503
|
|
122
197
|
ServiceUnavailableError.new(response_headers: response_headers, response_body: response_body)
|
|
123
198
|
when 504
|
|
124
199
|
GatewayTimeoutError.new(response_headers: response_headers, response_body: response_body)
|
|
200
|
+
when 507
|
|
201
|
+
InsufficientStorageError.new(response_headers: response_headers, response_body: response_body)
|
|
202
|
+
when 508
|
|
203
|
+
LoopDetectedError.new(response_headers: response_headers, response_body: response_body)
|
|
125
204
|
else
|
|
126
205
|
ApiError.new(code: status_code, response_headers: response_headers, response_body: response_body)
|
|
127
206
|
end
|
|
@@ -47,7 +47,9 @@ module HookSniff
|
|
|
47
47
|
# Create request object
|
|
48
48
|
request = request_class.new(uri.request_uri)
|
|
49
49
|
request["Authorization"] = "Bearer #{@token}"
|
|
50
|
-
|
|
50
|
+
sdk_ua = "hooksniff-libs/#{VERSION}/ruby"
|
|
51
|
+
request["User-Agent"] = sdk_ua
|
|
52
|
+
request["X-HookSniff-SDK"] = sdk_ua
|
|
51
53
|
request["hooksniff-req-id"] = rand(0...(2 ** 64))
|
|
52
54
|
|
|
53
55
|
# Add headers
|
|
@@ -83,26 +85,41 @@ module HookSniff
|
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
private def execute_request_with_retries(request, http)
|
|
86
|
-
res =
|
|
88
|
+
res = nil
|
|
89
|
+
retries = [1, 2, 4] # seconds: 1s, 2s, 4s exponential backoff
|
|
90
|
+
max_retries = retries.length
|
|
91
|
+
|
|
92
|
+
retries.each_with_index do |sleep_duration, index|
|
|
93
|
+
begin
|
|
94
|
+
res = http.request(request)
|
|
95
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Timeout::Error, Errno::ETIMEDOUT, IOError => _e
|
|
96
|
+
# Timeout — retry
|
|
97
|
+
if index < max_retries - 1
|
|
98
|
+
sleep(sleep_duration)
|
|
99
|
+
request["hooksniff-retry-count"] = index + 1
|
|
100
|
+
next
|
|
101
|
+
else
|
|
102
|
+
raise
|
|
103
|
+
end
|
|
104
|
+
end
|
|
87
105
|
|
|
88
|
-
[0.05, 0.1, 0.2].each_with_index do |sleep_duration, index|
|
|
89
106
|
# 429 Rate Limit — respect Retry-After header
|
|
90
107
|
if Integer(res.code) == 429
|
|
91
108
|
retry_after = res["Retry-After"]
|
|
92
109
|
delay = retry_after ? retry_after.to_f : sleep_duration
|
|
93
110
|
sleep(delay)
|
|
94
111
|
request["hooksniff-retry-count"] = index + 1
|
|
95
|
-
res = http.request(request)
|
|
96
112
|
next
|
|
97
113
|
end
|
|
98
114
|
|
|
99
|
-
|
|
100
|
-
|
|
115
|
+
# 5xx Server Error — exponential backoff
|
|
116
|
+
if Integer(res.code) >= 500 && index < max_retries - 1
|
|
117
|
+
sleep(sleep_duration)
|
|
118
|
+
request["hooksniff-retry-count"] = index + 1
|
|
119
|
+
next
|
|
101
120
|
end
|
|
102
121
|
|
|
103
|
-
|
|
104
|
-
request["hooksniff-retry-count"] = index + 1
|
|
105
|
-
res = http.request(request)
|
|
122
|
+
break
|
|
106
123
|
end
|
|
107
124
|
|
|
108
125
|
res
|
|
@@ -126,3 +143,25 @@ module HookSniff
|
|
|
126
143
|
end
|
|
127
144
|
end
|
|
128
145
|
end
|
|
146
|
+
|
|
147
|
+
# Response metadata accessor
|
|
148
|
+
module HookSniff
|
|
149
|
+
class << self
|
|
150
|
+
attr_accessor :last_response
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Debug logging helper
|
|
155
|
+
module HookSniff
|
|
156
|
+
module DebugLogger
|
|
157
|
+
def self.log_request(method, url)
|
|
158
|
+
return unless HookSniff.respond_to?(:debug) && HookSniff.debug
|
|
159
|
+
puts "[HookSniff] → #{method.upcase} #{url}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def self.log_response(status_code, elapsed_ms)
|
|
163
|
+
return unless HookSniff.respond_to?(:debug) && HookSniff.debug
|
|
164
|
+
puts "[HookSniff] ← #{status_code} (#{elapsed_ms}ms)"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# This file is @generated
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module HookSniff
|
|
6
|
+
class AggregateEventTypesOut
|
|
7
|
+
# The QueueBackgroundTask's ID.
|
|
8
|
+
attr_accessor :id
|
|
9
|
+
attr_accessor :status
|
|
10
|
+
attr_accessor :task
|
|
11
|
+
attr_accessor :updated_at
|
|
12
|
+
|
|
13
|
+
ALL_FIELD ||= ["id", "status", "task", "updated_at"].freeze
|
|
14
|
+
private_constant :ALL_FIELD
|
|
15
|
+
|
|
16
|
+
def initialize(attributes = {})
|
|
17
|
+
unless attributes.is_a?(Hash)
|
|
18
|
+
fail(
|
|
19
|
+
ArgumentError,
|
|
20
|
+
"The input argument (attributes) must be a hash in `HookSniff::AggregateEventTypesOut` new method"
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attributes.each do |k, v|
|
|
25
|
+
unless ALL_FIELD.include?(k.to_s)
|
|
26
|
+
fail(ArgumentError, "The field #{k} is not part of HookSniff::AggregateEventTypesOut")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
instance_variable_set("@#{k}", v)
|
|
30
|
+
instance_variable_set("@__#{k}_is_defined", true)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.deserialize(attributes = {})
|
|
35
|
+
attributes = attributes.transform_keys(&:to_s)
|
|
36
|
+
attrs = Hash.new
|
|
37
|
+
attrs["id"] = attributes["id"]
|
|
38
|
+
attrs["status"] = HookSniff::BackgroundTaskStatus.deserialize(attributes["status"])
|
|
39
|
+
attrs["task"] = HookSniff::BackgroundTaskType.deserialize(attributes["task"])
|
|
40
|
+
attrs["updated_at"] = DateTime.rfc3339(attributes["updatedAt"]).to_time
|
|
41
|
+
new(attrs)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def serialize
|
|
45
|
+
out = Hash.new
|
|
46
|
+
out["id"] = HookSniff::serialize_primitive(@id) if @id
|
|
47
|
+
out["status"] = HookSniff::serialize_schema_ref(@status) if @status
|
|
48
|
+
out["task"] = HookSniff::serialize_schema_ref(@task) if @task
|
|
49
|
+
out["updatedAt"] = HookSniff::serialize_primitive(@updated_at) if @updated_at
|
|
50
|
+
out
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Serializes the object to a json string
|
|
54
|
+
# @return String
|
|
55
|
+
def to_json
|
|
56
|
+
JSON.dump(serialize)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# This file is @generated
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module HookSniff
|
|
6
|
+
# Sent on a successful dispatch after an earlier failure op webhook has already been sent.
|
|
7
|
+
class MessageAttemptRecoveredEvent
|
|
8
|
+
attr_accessor :data
|
|
9
|
+
attr_accessor :type
|
|
10
|
+
|
|
11
|
+
ALL_FIELD ||= ["data", "type"].freeze
|
|
12
|
+
private_constant :ALL_FIELD
|
|
13
|
+
|
|
14
|
+
def initialize(attributes = {})
|
|
15
|
+
unless attributes.is_a?(Hash)
|
|
16
|
+
fail(
|
|
17
|
+
ArgumentError,
|
|
18
|
+
"The input argument (attributes) must be a hash in `HookSniff::MessageAttemptRecoveredEvent` new method"
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attributes.each do |k, v|
|
|
23
|
+
unless ALL_FIELD.include?(k.to_s)
|
|
24
|
+
fail(ArgumentError, "The field #{k} is not part of HookSniff::MessageAttemptRecoveredEvent")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
instance_variable_set("@#{k}", v)
|
|
28
|
+
instance_variable_set("@__#{k}_is_defined", true)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.deserialize(attributes = {})
|
|
33
|
+
attributes = attributes.transform_keys(&:to_s)
|
|
34
|
+
attrs = Hash.new
|
|
35
|
+
attrs["data"] = HookSniff::MessageAttemptRecoveredEventData.deserialize(attributes["data"])
|
|
36
|
+
attrs["type"] = attributes["type"]
|
|
37
|
+
new(attrs)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def serialize
|
|
41
|
+
out = Hash.new
|
|
42
|
+
out["data"] = HookSniff::serialize_schema_ref(@data) if @data
|
|
43
|
+
out["type"] = HookSniff::serialize_primitive(@type) if @type
|
|
44
|
+
out
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Serializes the object to a json string
|
|
48
|
+
# @return String
|
|
49
|
+
def to_json
|
|
50
|
+
JSON.dump(serialize)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# This file is @generated
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module HookSniff
|
|
6
|
+
# Sent when a message delivery has failed (all of the retry attempts have been exhausted) as a "message.attempt.exhausted" type or after it's failed four times as a "message.attempt.failing" event.
|
|
7
|
+
class MessageAttemptRecoveredEventData
|
|
8
|
+
# The Application's ID.
|
|
9
|
+
attr_accessor :app_id
|
|
10
|
+
# The Application's UID.
|
|
11
|
+
attr_accessor :app_uid
|
|
12
|
+
# The Endpoint's ID.
|
|
13
|
+
attr_accessor :endpoint_id
|
|
14
|
+
attr_accessor :last_attempt
|
|
15
|
+
# The Message's UID.
|
|
16
|
+
attr_accessor :msg_event_id
|
|
17
|
+
# The Message's ID.
|
|
18
|
+
attr_accessor :msg_id
|
|
19
|
+
|
|
20
|
+
ALL_FIELD ||= ["app_id", "app_uid", "endpoint_id", "last_attempt", "msg_event_id", "msg_id"].freeze
|
|
21
|
+
private_constant :ALL_FIELD
|
|
22
|
+
|
|
23
|
+
def initialize(attributes = {})
|
|
24
|
+
unless attributes.is_a?(Hash)
|
|
25
|
+
fail(
|
|
26
|
+
ArgumentError,
|
|
27
|
+
"The input argument (attributes) must be a hash in `HookSniff::MessageAttemptRecoveredEventData` new method"
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
attributes.each do |k, v|
|
|
32
|
+
unless ALL_FIELD.include?(k.to_s)
|
|
33
|
+
fail(ArgumentError, "The field #{k} is not part of HookSniff::MessageAttemptRecoveredEventData")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
instance_variable_set("@#{k}", v)
|
|
37
|
+
instance_variable_set("@__#{k}_is_defined", true)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.deserialize(attributes = {})
|
|
42
|
+
attributes = attributes.transform_keys(&:to_s)
|
|
43
|
+
attrs = Hash.new
|
|
44
|
+
attrs["app_id"] = attributes["appId"]
|
|
45
|
+
attrs["app_uid"] = attributes["appUid"]
|
|
46
|
+
attrs["endpoint_id"] = attributes["endpointId"]
|
|
47
|
+
attrs["last_attempt"] = HookSniff::MessageAttemptFailedData.deserialize(attributes["lastAttempt"])
|
|
48
|
+
attrs["msg_event_id"] = attributes["msgEventId"]
|
|
49
|
+
attrs["msg_id"] = attributes["msgId"]
|
|
50
|
+
new(attrs)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def serialize
|
|
54
|
+
out = Hash.new
|
|
55
|
+
out["appId"] = HookSniff::serialize_primitive(@app_id) if @app_id
|
|
56
|
+
out["appUid"] = HookSniff::serialize_primitive(@app_uid) if @app_uid
|
|
57
|
+
out["endpointId"] = HookSniff::serialize_primitive(@endpoint_id) if @endpoint_id
|
|
58
|
+
out["lastAttempt"] = HookSniff::serialize_schema_ref(@last_attempt) if @last_attempt
|
|
59
|
+
out["msgEventId"] = HookSniff::serialize_primitive(@msg_event_id) if @msg_event_id
|
|
60
|
+
out["msgId"] = HookSniff::serialize_primitive(@msg_id) if @msg_id
|
|
61
|
+
out
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Serializes the object to a json string
|
|
65
|
+
# @return String
|
|
66
|
+
def to_json
|
|
67
|
+
JSON.dump(serialize)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|