hookd-client 1.2.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/README.md +38 -3
- data/lib/hookd/client.rb +44 -12
- data/lib/hookd/hook.rb +10 -4
- data/lib/hookd/hook_activity.rb +30 -0
- data/lib/hookd/version.rb +1 -1
- data/lib/hookd.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eeded85d2f08978163e752d64342ca5e52fd1cf1fcaade7da0305caeb53e03b5
|
|
4
|
+
data.tar.gz: 966b9eaf4f9d25549f0473471e446628af82c8ea160e5ebbefef518aa1a6fa08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0224226f06a4996e6034caabb6182f31d9a7848262a44bd8ed0571fe898a7706f4b95c46bdb0e5617c0bc04f948fc5dc8e5bffbb46cd20de04d9bdcacbe0b2fe
|
|
7
|
+
data.tar.gz: 88e85eab06e2d351b6231f2769104b6d6a3320ae1cb2ccf68f432dad732a689ee5f50386ca69199534a76fb09425ddfd70f7d711b03b8e4d836e2b25415784ad
|
data/README.md
CHANGED
|
@@ -72,9 +72,6 @@ hook_ids = hooks.map(&:id)
|
|
|
72
72
|
puts "Created hooks: #{hook_ids.join(', ')}"
|
|
73
73
|
|
|
74
74
|
# Simulate some interactions...
|
|
75
|
-
# (make DNS queries, HTTP requests, etc.)
|
|
76
|
-
|
|
77
|
-
# Simulate some interactions
|
|
78
75
|
puts "\nSimulating HTTP requests..."
|
|
79
76
|
hooks.each do |hook|
|
|
80
77
|
Typhoeus.get(hook.http)
|
|
@@ -133,8 +130,20 @@ hooks = client.register(count: 5)
|
|
|
133
130
|
# => [#<Hookd::Hook id="abc123" ...>, #<Hookd::Hook id="def456" ...>, ...]
|
|
134
131
|
```
|
|
135
132
|
|
|
133
|
+
**Long-lived hook (survives restarts, for stored-XSS style detection):**
|
|
134
|
+
```ruby
|
|
135
|
+
hook = client.register(ttl: '7d', metadata: { target: 'acme', field: 'profile.bio' })
|
|
136
|
+
# => #<Hookd::Hook id="abc123" ...>
|
|
137
|
+
hook.expires_at # => "2025-10-08T10:30:00Z"
|
|
138
|
+
hook.metadata # => {"target"=>"acme", "field"=>"profile.bio"}
|
|
139
|
+
```
|
|
140
|
+
|
|
136
141
|
Parameters:
|
|
137
142
|
- `count` (Integer, optional) - Number of hooks to create (default: 1)
|
|
143
|
+
- `ttl` (String, optional) - Lifetime as a Go duration (`"168h"`) or day count
|
|
144
|
+
(`"7d"`); a value above the server's ephemeral `hook_ttl` registers a durable
|
|
145
|
+
long-lived hook. Omit for an ephemeral hook.
|
|
146
|
+
- `metadata` (Hash, optional) - Stored with the hook and echoed back on poll
|
|
138
147
|
|
|
139
148
|
Returns:
|
|
140
149
|
- `Hookd::Hook` object when `count` is 1 or not specified
|
|
@@ -146,6 +155,21 @@ Raises:
|
|
|
146
155
|
- `Hookd::ServerError` - Server error (5xx)
|
|
147
156
|
- `Hookd::ConnectionError` - Connection failed
|
|
148
157
|
|
|
158
|
+
##### `#activity`
|
|
159
|
+
|
|
160
|
+
List the long-lived hooks that currently have pending interactions, so you can
|
|
161
|
+
discover which fired without polling each one; drain the details with `#poll`.
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
client.activity.each do |a|
|
|
165
|
+
puts "#{a.hook.id} fired #{a.pending_count} time(s), meta=#{a.hook.metadata}"
|
|
166
|
+
client.poll(a.hook.id)
|
|
167
|
+
end
|
|
168
|
+
# => [#<Hookd::HookActivity hook=abc123 pending=3>, ...]
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Returns: Array of `Hookd::HookActivity` (empty when none fired or long-lived is disabled)
|
|
172
|
+
|
|
149
173
|
##### `#poll(hook_id)`
|
|
150
174
|
|
|
151
175
|
Poll for interactions captured by a single hook.
|
|
@@ -236,6 +260,17 @@ Attributes:
|
|
|
236
260
|
- `http` (String) - HTTP endpoint
|
|
237
261
|
- `https` (String) - HTTPS endpoint
|
|
238
262
|
- `created_at` (String) - Creation timestamp
|
|
263
|
+
- `expires_at` (String, nil) - Expiry timestamp (long-lived hooks)
|
|
264
|
+
- `metadata` (Hash, nil) - Metadata attached at registration
|
|
265
|
+
|
|
266
|
+
#### `Hookd::HookActivity`
|
|
267
|
+
|
|
268
|
+
Represents a long-lived hook that has pending interactions (returned by `#activity`).
|
|
269
|
+
|
|
270
|
+
Attributes:
|
|
271
|
+
- `hook` (`Hookd::Hook`) - The long-lived hook that fired
|
|
272
|
+
- `pending_count` (Integer) - Number of interactions awaiting poll
|
|
273
|
+
- `last_interaction_at` (String) - Timestamp of the most recent interaction
|
|
239
274
|
|
|
240
275
|
#### `Hookd::Interaction`
|
|
241
276
|
|
data/lib/hookd/client.rb
CHANGED
|
@@ -22,25 +22,20 @@ module Hookd
|
|
|
22
22
|
|
|
23
23
|
# Register one or more hooks
|
|
24
24
|
# @param count [Integer, nil] number of hooks to register (default: 1)
|
|
25
|
+
# @param ttl [String, nil] lifetime as a Go duration ("168h") or day count
|
|
26
|
+
# ("7d"); a value above the server's ephemeral hook_ttl registers a durable
|
|
27
|
+
# long-lived hook. Omit for an ephemeral hook.
|
|
28
|
+
# @param metadata [Hash, nil] arbitrary data stored with the hook and echoed
|
|
29
|
+
# back on poll
|
|
25
30
|
# @return [Hookd::Hook, Array<Hookd::Hook>] single hook or array of hooks
|
|
26
31
|
# @raise [Hookd::AuthenticationError] if authentication fails
|
|
27
32
|
# @raise [Hookd::ServerError] if server returns 5xx
|
|
28
33
|
# @raise [Hookd::ConnectionError] if connection fails
|
|
29
34
|
# @raise [ArgumentError] if count is invalid
|
|
30
|
-
def register(count: nil)
|
|
31
|
-
body = count.nil? ? nil : { count: count }
|
|
32
|
-
|
|
35
|
+
def register(count: nil, ttl: nil, metadata: nil)
|
|
33
36
|
raise ArgumentError, 'count must be a positive integer' if count && (!count.is_a?(Integer) || count < 1)
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Single hook response (backward compatible)
|
|
38
|
-
return Hook.from_hash(response) if response.key?('id')
|
|
39
|
-
|
|
40
|
-
# Multiple hooks response
|
|
41
|
-
return [] if response['hooks'].nil? || response['hooks'].empty?
|
|
42
|
-
|
|
43
|
-
response['hooks'].map { |h| Hook.from_hash(h) }
|
|
38
|
+
parse_register_response(post('/register', register_body(count, ttl, metadata)))
|
|
44
39
|
end
|
|
45
40
|
|
|
46
41
|
# Poll for interactions on a hook
|
|
@@ -92,8 +87,45 @@ module Hookd
|
|
|
92
87
|
get('/metrics')
|
|
93
88
|
end
|
|
94
89
|
|
|
90
|
+
# List long-lived hooks that currently have pending interactions, so you can
|
|
91
|
+
# discover which of your long-lived hooks fired without polling each one.
|
|
92
|
+
# Drain the details with #poll. Returns an empty array when none have fired
|
|
93
|
+
# (or the server has long-lived hooks disabled).
|
|
94
|
+
# @return [Array<Hookd::HookActivity>]
|
|
95
|
+
# @raise [Hookd::AuthenticationError] if authentication fails
|
|
96
|
+
# @raise [Hookd::ServerError] if server returns 5xx
|
|
97
|
+
# @raise [Hookd::ConnectionError] if connection fails
|
|
98
|
+
def activity
|
|
99
|
+
response = get('/activity')
|
|
100
|
+
|
|
101
|
+
hooks = response['hooks']
|
|
102
|
+
return [] if hooks.nil? || hooks.empty? || !hooks.is_a?(Array)
|
|
103
|
+
|
|
104
|
+
hooks.map { |h| HookActivity.from_hash(h) }
|
|
105
|
+
rescue NoMethodError => e
|
|
106
|
+
raise Error, "Invalid response format: #{e.message}"
|
|
107
|
+
end
|
|
108
|
+
|
|
95
109
|
private
|
|
96
110
|
|
|
111
|
+
def register_body(count, ttl, metadata)
|
|
112
|
+
body = {}
|
|
113
|
+
body[:count] = count unless count.nil?
|
|
114
|
+
body[:ttl] = ttl unless ttl.nil?
|
|
115
|
+
body[:metadata] = metadata unless metadata.nil?
|
|
116
|
+
body.empty? ? nil : body
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def parse_register_response(response)
|
|
120
|
+
# Single hook response (backward compatible)
|
|
121
|
+
return Hook.from_hash(response) if response.key?('id')
|
|
122
|
+
|
|
123
|
+
# Multiple hooks response
|
|
124
|
+
return [] if response['hooks'].nil? || response['hooks'].empty?
|
|
125
|
+
|
|
126
|
+
response['hooks'].map { |h| Hook.from_hash(h) }
|
|
127
|
+
end
|
|
128
|
+
|
|
97
129
|
def get(path)
|
|
98
130
|
url = "#{@server}#{path}"
|
|
99
131
|
response = @http.get(url)
|
data/lib/hookd/hook.rb
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Hookd
|
|
4
|
-
# Represents a registered hook with DNS and HTTP endpoints
|
|
4
|
+
# Represents a registered hook with DNS and HTTP endpoints. expires_at and
|
|
5
|
+
# metadata are populated for long-lived hooks (registered with a ttl) and nil
|
|
6
|
+
# otherwise.
|
|
5
7
|
class Hook
|
|
6
|
-
attr_reader :id, :dns, :http, :https, :created_at
|
|
8
|
+
attr_reader :id, :dns, :http, :https, :created_at, :expires_at, :metadata
|
|
7
9
|
|
|
8
|
-
def initialize(id:, dns:, http:, https:, created_at:)
|
|
10
|
+
def initialize(id:, dns:, http:, https:, created_at:, expires_at: nil, metadata: nil)
|
|
9
11
|
@id = id
|
|
10
12
|
@dns = dns
|
|
11
13
|
@http = http
|
|
12
14
|
@https = https
|
|
13
15
|
@created_at = created_at
|
|
16
|
+
@expires_at = expires_at
|
|
17
|
+
@metadata = metadata
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
# Create a Hook from API response hash
|
|
@@ -22,7 +26,9 @@ module Hookd
|
|
|
22
26
|
dns: hash['dns'],
|
|
23
27
|
http: hash['http'],
|
|
24
28
|
https: hash['https'],
|
|
25
|
-
created_at: hash['created_at']
|
|
29
|
+
created_at: hash['created_at'],
|
|
30
|
+
expires_at: hash['expires_at'],
|
|
31
|
+
metadata: hash['metadata']
|
|
26
32
|
)
|
|
27
33
|
end
|
|
28
34
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hookd
|
|
4
|
+
# Summarises a long-lived hook that currently has pending interactions,
|
|
5
|
+
# returned by Client#activity.
|
|
6
|
+
class HookActivity
|
|
7
|
+
attr_reader :hook, :pending_count, :last_interaction_at
|
|
8
|
+
|
|
9
|
+
def initialize(hook:, pending_count:, last_interaction_at:)
|
|
10
|
+
@hook = hook
|
|
11
|
+
@pending_count = pending_count
|
|
12
|
+
@last_interaction_at = last_interaction_at
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Create a HookActivity from an API response hash
|
|
16
|
+
def self.from_hash(hash)
|
|
17
|
+
raise ArgumentError, "Invalid hash: expected Hash, got #{hash.class}" unless hash.is_a?(Hash)
|
|
18
|
+
|
|
19
|
+
new(
|
|
20
|
+
hook: Hook.from_hash(hash['hook']),
|
|
21
|
+
pending_count: hash['pending_count'],
|
|
22
|
+
last_interaction_at: hash['last_interaction_at']
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_s
|
|
27
|
+
"#<Hookd::HookActivity hook=#{hook.id} pending=#{pending_count}>"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/hookd/version.rb
CHANGED
data/lib/hookd.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hookd-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua MARTINELLE
|
|
@@ -36,6 +36,7 @@ files:
|
|
|
36
36
|
- lib/hookd/client.rb
|
|
37
37
|
- lib/hookd/error.rb
|
|
38
38
|
- lib/hookd/hook.rb
|
|
39
|
+
- lib/hookd/hook_activity.rb
|
|
39
40
|
- lib/hookd/interaction.rb
|
|
40
41
|
- lib/hookd/version.rb
|
|
41
42
|
homepage: https://github.com/JoshuaMart/Hookd
|