postproxy-sdk 1.0.0 → 1.2.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 +101 -0
- data/lib/postproxy/client.rb +6 -0
- data/lib/postproxy/constants.rb +3 -1
- data/lib/postproxy/resources/posts.rb +36 -3
- data/lib/postproxy/resources/webhooks.rb +59 -0
- data/lib/postproxy/types.rb +123 -2
- data/lib/postproxy/version.rb +1 -1
- data/lib/postproxy/webhook_signature.rb +28 -0
- data/lib/postproxy.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 665c9b0d7745b0da8ae0bd7864047997816bfa532725ee338588b94f1e6e9724
|
|
4
|
+
data.tar.gz: d9ebae2aa90d310886561d4ec4b2da268a2dcd6d341b59343c1661c3429b656c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32384b1c8ca96c498094d59530fd2617a542a911342ef969c93243f31da53afeb06a5d9de458413bfd0ef584c09c53a207743d0a217d49fb1dc097972e2e697e
|
|
7
|
+
data.tar.gz: 15c9e8c42a588f99fe9ef0eb93536c7206f3a90b16d75cd1648bb1497ec5fb3b9e00fae71de3abad3b2a4c2ca442c88aea54ba129b58f6d4dc283d8c9a07de0d
|
data/README.md
CHANGED
|
@@ -92,10 +92,111 @@ post = client.posts.create(
|
|
|
92
92
|
scheduled_at: (Time.now + 3600).iso8601
|
|
93
93
|
)
|
|
94
94
|
|
|
95
|
+
# Create a thread post
|
|
96
|
+
post = client.posts.create(
|
|
97
|
+
"Thread starts here",
|
|
98
|
+
profiles: ["prof-1"],
|
|
99
|
+
thread: [
|
|
100
|
+
{ body: "Second post in the thread" },
|
|
101
|
+
{ body: "Third with media", media: ["https://example.com/img.jpg"] },
|
|
102
|
+
]
|
|
103
|
+
)
|
|
104
|
+
post.thread.each { |child| puts "#{child.id}: #{child.body}" }
|
|
105
|
+
|
|
95
106
|
# Delete a post
|
|
96
107
|
client.posts.delete("post-id")
|
|
97
108
|
```
|
|
98
109
|
|
|
110
|
+
## Post Stats
|
|
111
|
+
|
|
112
|
+
Retrieve stats snapshots for posts over time. Supports filtering by profiles/networks and timespan.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# Get stats for one or more posts
|
|
116
|
+
stats = client.posts.stats(["post-id-1", "post-id-2"])
|
|
117
|
+
stats.data.each do |post_id, post_stats|
|
|
118
|
+
post_stats.platforms.each do |platform|
|
|
119
|
+
puts "#{post_id} on #{platform.platform} (#{platform.profile_id}):"
|
|
120
|
+
platform.records.each do |record|
|
|
121
|
+
puts " #{record.recorded_at}: #{record.stats}"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Filter by profiles or networks
|
|
127
|
+
stats = client.posts.stats(["post-id"], profiles: ["instagram", "twitter"])
|
|
128
|
+
|
|
129
|
+
# Filter by profile hashids
|
|
130
|
+
stats = client.posts.stats(["post-id"], profiles: ["prof-abc", "prof-def"])
|
|
131
|
+
|
|
132
|
+
# Filter by time range
|
|
133
|
+
stats = client.posts.stats(
|
|
134
|
+
["post-id"],
|
|
135
|
+
from: "2026-02-01T00:00:00Z",
|
|
136
|
+
to: "2026-02-24T00:00:00Z"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# Using Time objects
|
|
140
|
+
stats = client.posts.stats(
|
|
141
|
+
["post-id"],
|
|
142
|
+
from: Time.now - 86400 * 7,
|
|
143
|
+
to: Time.now
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Stats vary by platform:
|
|
148
|
+
|
|
149
|
+
| Platform | Fields |
|
|
150
|
+
|----------|--------|
|
|
151
|
+
| Instagram | `impressions`, `likes`, `comments`, `saved`, `profile_visits`, `follows` |
|
|
152
|
+
| Facebook | `impressions`, `clicks`, `likes` |
|
|
153
|
+
| Threads | `impressions`, `likes`, `replies`, `reposts`, `quotes`, `shares` |
|
|
154
|
+
| Twitter | `impressions`, `likes`, `retweets`, `comments`, `quotes`, `saved` |
|
|
155
|
+
| YouTube | `impressions`, `likes`, `comments`, `saved` |
|
|
156
|
+
| LinkedIn | `impressions` |
|
|
157
|
+
| TikTok | `impressions`, `likes`, `comments`, `shares` |
|
|
158
|
+
| Pinterest | `impressions`, `likes`, `comments`, `saved`, `outbound_clicks` |
|
|
159
|
+
|
|
160
|
+
## Webhooks
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
# List webhooks
|
|
164
|
+
webhooks = client.webhooks.list.data
|
|
165
|
+
|
|
166
|
+
# Get a webhook
|
|
167
|
+
webhook = client.webhooks.get("wh-id")
|
|
168
|
+
|
|
169
|
+
# Create a webhook
|
|
170
|
+
webhook = client.webhooks.create(
|
|
171
|
+
"https://example.com/webhook",
|
|
172
|
+
events: ["post.published", "post.failed"],
|
|
173
|
+
description: "My webhook"
|
|
174
|
+
)
|
|
175
|
+
puts webhook.id, webhook.secret
|
|
176
|
+
|
|
177
|
+
# Update a webhook
|
|
178
|
+
webhook = client.webhooks.update("wh-id", events: ["post.published"], enabled: false)
|
|
179
|
+
|
|
180
|
+
# Delete a webhook
|
|
181
|
+
client.webhooks.delete("wh-id")
|
|
182
|
+
|
|
183
|
+
# List deliveries
|
|
184
|
+
deliveries = client.webhooks.deliveries("wh-id", page: 1, per_page: 10)
|
|
185
|
+
deliveries.data.each { |d| puts "#{d.event_type}: #{d.success}" }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Signature verification
|
|
189
|
+
|
|
190
|
+
Verify incoming webhook signatures using HMAC-SHA256:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
PostProxy::WebhookSignature.verify(
|
|
194
|
+
payload: request.body.read,
|
|
195
|
+
signature_header: request.headers["X-PostProxy-Signature"],
|
|
196
|
+
secret: "whsec_..."
|
|
197
|
+
)
|
|
198
|
+
```
|
|
199
|
+
|
|
99
200
|
## Profiles
|
|
100
201
|
|
|
101
202
|
```ruby
|
data/lib/postproxy/client.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require_relative "resources/posts"
|
|
5
5
|
require_relative "resources/profiles"
|
|
6
6
|
require_relative "resources/profile_groups"
|
|
7
|
+
require_relative "resources/webhooks"
|
|
7
8
|
|
|
8
9
|
module PostProxy
|
|
9
10
|
class Client
|
|
@@ -17,6 +18,7 @@ module PostProxy
|
|
|
17
18
|
@posts = nil
|
|
18
19
|
@profiles = nil
|
|
19
20
|
@profile_groups = nil
|
|
21
|
+
@webhooks = nil
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def posts
|
|
@@ -31,6 +33,10 @@ module PostProxy
|
|
|
31
33
|
@profile_groups ||= Resources::ProfileGroups.new(self)
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
def webhooks
|
|
37
|
+
@webhooks ||= Resources::Webhooks.new(self)
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
def request(method, path, params: nil, json: nil, data: nil, files: nil, profile_group_id: nil)
|
|
35
41
|
url = "/api#{path}"
|
|
36
42
|
|
data/lib/postproxy/constants.rb
CHANGED
|
@@ -7,7 +7,9 @@ module PostProxy
|
|
|
7
7
|
|
|
8
8
|
PROFILE_STATUSES = %w[active expired inactive].freeze
|
|
9
9
|
|
|
10
|
-
POST_STATUSES = %w[pending draft processing processed scheduled].freeze
|
|
10
|
+
POST_STATUSES = %w[pending draft processing processed scheduled media_processing_failed].freeze
|
|
11
|
+
|
|
12
|
+
MEDIA_STATUSES = %w[pending processed failed].freeze
|
|
11
13
|
|
|
12
14
|
PLATFORM_POST_STATUSES = %w[pending processing published failed deleted].freeze
|
|
13
15
|
|
|
@@ -29,8 +29,11 @@ module PostProxy
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def create(body, profiles:, media: nil, media_files: nil, platforms: nil,
|
|
32
|
-
scheduled_at: nil, draft: nil, profile_group_id: nil)
|
|
33
|
-
|
|
32
|
+
thread: nil, scheduled_at: nil, draft: nil, profile_group_id: nil)
|
|
33
|
+
has_files = media_files && !media_files.empty?
|
|
34
|
+
has_thread_files = thread&.any? { |t| t[:media_files]&.any? }
|
|
35
|
+
|
|
36
|
+
if has_files || has_thread_files
|
|
34
37
|
form_data = { "post[body]" => body }
|
|
35
38
|
form_data["post[scheduled_at]"] = format_time(scheduled_at) if scheduled_at
|
|
36
39
|
form_data["post[draft]"] = draft.to_s if !draft.nil?
|
|
@@ -54,7 +57,7 @@ module PostProxy
|
|
|
54
57
|
end
|
|
55
58
|
end
|
|
56
59
|
|
|
57
|
-
media_files
|
|
60
|
+
media_files&.each do |path|
|
|
58
61
|
path = path.to_s
|
|
59
62
|
filename = File.basename(path)
|
|
60
63
|
content_type = mime_type_for(filename)
|
|
@@ -62,6 +65,22 @@ module PostProxy
|
|
|
62
65
|
files << ["media[]", filename, io, content_type]
|
|
63
66
|
end
|
|
64
67
|
|
|
68
|
+
thread&.each_with_index do |t, i|
|
|
69
|
+
form_data["thread[#{i}][body]"] = t[:body] if t[:body]
|
|
70
|
+
|
|
71
|
+
t[:media]&.each do |m|
|
|
72
|
+
files << ["thread[#{i}][media][]", nil, m, "text/plain"]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
t[:media_files]&.each do |path|
|
|
76
|
+
path = path.to_s
|
|
77
|
+
filename = File.basename(path)
|
|
78
|
+
content_type = mime_type_for(filename)
|
|
79
|
+
io = File.open(path, "rb")
|
|
80
|
+
files << ["thread[#{i}][media][]", filename, io, content_type]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
65
84
|
result = @client.request(:post, "/posts",
|
|
66
85
|
data: form_data,
|
|
67
86
|
files: files,
|
|
@@ -75,6 +94,7 @@ module PostProxy
|
|
|
75
94
|
json_body = { post: post_payload, profiles: profiles }
|
|
76
95
|
json_body[:platforms] = platforms.is_a?(PlatformParams) ? platforms.to_h : platforms if platforms
|
|
77
96
|
json_body[:media] = media if media
|
|
97
|
+
json_body[:thread] = thread if thread
|
|
78
98
|
|
|
79
99
|
result = @client.request(:post, "/posts", json: json_body, profile_group_id: profile_group_id)
|
|
80
100
|
end
|
|
@@ -87,6 +107,19 @@ module PostProxy
|
|
|
87
107
|
Post.new(**result)
|
|
88
108
|
end
|
|
89
109
|
|
|
110
|
+
def stats(post_ids, profiles: nil, from: nil, to: nil)
|
|
111
|
+
params = { post_ids: post_ids.is_a?(Array) ? post_ids.join(",") : post_ids }
|
|
112
|
+
params[:profiles] = profiles.is_a?(Array) ? profiles.join(",") : profiles if profiles
|
|
113
|
+
params[:from] = format_time(from) if from
|
|
114
|
+
params[:to] = format_time(to) if to
|
|
115
|
+
|
|
116
|
+
result = @client.request(:get, "/posts/stats", params: params)
|
|
117
|
+
posts = (result[:data] || {}).each_with_object({}) do |(post_id, post_data), hash|
|
|
118
|
+
hash[post_id.to_s] = PostStats.new(**post_data.transform_keys(&:to_sym))
|
|
119
|
+
end
|
|
120
|
+
StatsResponse.new(data: posts)
|
|
121
|
+
end
|
|
122
|
+
|
|
90
123
|
def delete(id, profile_group_id: nil)
|
|
91
124
|
result = @client.request(:delete, "/posts/#{id}", profile_group_id: profile_group_id)
|
|
92
125
|
DeleteResponse.new(**result)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module PostProxy
|
|
2
|
+
module Resources
|
|
3
|
+
class Webhooks
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list
|
|
9
|
+
result = @client.request(:get, "/webhooks")
|
|
10
|
+
webhooks = (result[:data] || []).map { |w| Webhook.new(**w) }
|
|
11
|
+
ListResponse.new(data: webhooks)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get(id)
|
|
15
|
+
result = @client.request(:get, "/webhooks/#{id}")
|
|
16
|
+
Webhook.new(**result)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create(url, events:, description: nil)
|
|
20
|
+
json_body = { url: url, events: events }
|
|
21
|
+
json_body[:description] = description if description
|
|
22
|
+
|
|
23
|
+
result = @client.request(:post, "/webhooks", json: json_body)
|
|
24
|
+
Webhook.new(**result)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def update(id, url: nil, events: nil, enabled: nil, description: nil)
|
|
28
|
+
json_body = {}
|
|
29
|
+
json_body[:url] = url unless url.nil?
|
|
30
|
+
json_body[:events] = events unless events.nil?
|
|
31
|
+
json_body[:enabled] = enabled unless enabled.nil?
|
|
32
|
+
json_body[:description] = description unless description.nil?
|
|
33
|
+
|
|
34
|
+
result = @client.request(:patch, "/webhooks/#{id}", json: json_body)
|
|
35
|
+
Webhook.new(**result)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def delete(id)
|
|
39
|
+
result = @client.request(:delete, "/webhooks/#{id}")
|
|
40
|
+
DeleteResponse.new(**result)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def deliveries(id, page: nil, per_page: nil)
|
|
44
|
+
params = {}
|
|
45
|
+
params[:page] = page if page
|
|
46
|
+
params[:per_page] = per_page if per_page
|
|
47
|
+
|
|
48
|
+
result = @client.request(:get, "/webhooks/#{id}/deliveries", params: params)
|
|
49
|
+
deliveries = (result[:data] || []).map { |d| WebhookDelivery.new(**d) }
|
|
50
|
+
PaginatedResponse.new(
|
|
51
|
+
data: deliveries,
|
|
52
|
+
total: result[:total],
|
|
53
|
+
page: result[:page],
|
|
54
|
+
per_page: result[:per_page]
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/postproxy/types.rb
CHANGED
|
@@ -84,18 +84,89 @@ module PostProxy
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
class Media < Model
|
|
88
|
+
attr_accessor :id, :status, :error_message, :content_type, :source_url, :url
|
|
89
|
+
|
|
90
|
+
def initialize(**attrs)
|
|
91
|
+
@error_message = nil
|
|
92
|
+
@source_url = nil
|
|
93
|
+
@url = nil
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class ThreadChild < Model
|
|
99
|
+
attr_accessor :id, :body, :media
|
|
100
|
+
|
|
101
|
+
def initialize(**attrs)
|
|
102
|
+
@media = []
|
|
103
|
+
super
|
|
104
|
+
@media = (@media || []).map do |m|
|
|
105
|
+
m.is_a?(Media) ? m : Media.new(**m.transform_keys(&:to_sym))
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
87
110
|
class Post < Model
|
|
88
|
-
attr_accessor :id, :body, :status, :scheduled_at, :created_at, :platforms
|
|
111
|
+
attr_accessor :id, :body, :status, :scheduled_at, :created_at, :media, :platforms, :thread
|
|
89
112
|
|
|
90
113
|
def initialize(**attrs)
|
|
91
114
|
@scheduled_at = nil
|
|
115
|
+
@media = []
|
|
92
116
|
@platforms = []
|
|
117
|
+
@thread = []
|
|
93
118
|
super
|
|
94
119
|
@scheduled_at = parse_time(@scheduled_at)
|
|
95
120
|
@created_at = parse_time(@created_at)
|
|
121
|
+
@media = (@media || []).map do |m|
|
|
122
|
+
m.is_a?(Media) ? m : Media.new(**m.transform_keys(&:to_sym))
|
|
123
|
+
end
|
|
96
124
|
@platforms = (@platforms || []).map do |p|
|
|
97
125
|
p.is_a?(PlatformResult) ? p : PlatformResult.new(**p.transform_keys(&:to_sym))
|
|
98
126
|
end
|
|
127
|
+
@thread = (@thread || []).map do |t|
|
|
128
|
+
t.is_a?(ThreadChild) ? t : ThreadChild.new(**t.transform_keys(&:to_sym))
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def parse_time(value)
|
|
135
|
+
return nil if value.nil?
|
|
136
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
class Webhook < Model
|
|
141
|
+
attr_accessor :id, :url, :events, :enabled, :description, :secret,
|
|
142
|
+
:created_at, :updated_at
|
|
143
|
+
|
|
144
|
+
def initialize(**attrs)
|
|
145
|
+
@events = []
|
|
146
|
+
@description = nil
|
|
147
|
+
@secret = nil
|
|
148
|
+
super
|
|
149
|
+
@created_at = parse_time(@created_at)
|
|
150
|
+
@updated_at = parse_time(@updated_at)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def parse_time(value)
|
|
156
|
+
return nil if value.nil?
|
|
157
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
class WebhookDelivery < Model
|
|
162
|
+
attr_accessor :id, :event_id, :event_type, :response_status,
|
|
163
|
+
:attempt_number, :success, :attempted_at, :created_at
|
|
164
|
+
|
|
165
|
+
def initialize(**attrs)
|
|
166
|
+
@response_status = nil
|
|
167
|
+
super
|
|
168
|
+
@attempted_at = parse_time(@attempted_at)
|
|
169
|
+
@created_at = parse_time(@created_at)
|
|
99
170
|
end
|
|
100
171
|
|
|
101
172
|
private
|
|
@@ -110,6 +181,56 @@ module PostProxy
|
|
|
110
181
|
attr_accessor :id, :name
|
|
111
182
|
end
|
|
112
183
|
|
|
184
|
+
class StatsRecord < Model
|
|
185
|
+
attr_accessor :stats, :recorded_at
|
|
186
|
+
|
|
187
|
+
def initialize(**attrs)
|
|
188
|
+
@stats = {}
|
|
189
|
+
@recorded_at = nil
|
|
190
|
+
super
|
|
191
|
+
@recorded_at = parse_time(@recorded_at)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
def parse_time(value)
|
|
197
|
+
return nil if value.nil?
|
|
198
|
+
value.is_a?(Time) ? value : Time.parse(value.to_s)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class PlatformStats < Model
|
|
203
|
+
attr_accessor :profile_id, :platform, :records
|
|
204
|
+
|
|
205
|
+
def initialize(**attrs)
|
|
206
|
+
@records = []
|
|
207
|
+
super
|
|
208
|
+
@records = (@records || []).map do |r|
|
|
209
|
+
r.is_a?(StatsRecord) ? r : StatsRecord.new(**r.transform_keys(&:to_sym))
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
class PostStats < Model
|
|
215
|
+
attr_accessor :platforms
|
|
216
|
+
|
|
217
|
+
def initialize(**attrs)
|
|
218
|
+
@platforms = []
|
|
219
|
+
super
|
|
220
|
+
@platforms = (@platforms || []).map do |p|
|
|
221
|
+
p.is_a?(PlatformStats) ? p : PlatformStats.new(**p.transform_keys(&:to_sym))
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class StatsResponse
|
|
227
|
+
attr_reader :data
|
|
228
|
+
|
|
229
|
+
def initialize(data:)
|
|
230
|
+
@data = data
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
113
234
|
class ListResponse
|
|
114
235
|
attr_reader :data
|
|
115
236
|
|
|
@@ -163,7 +284,7 @@ module PostProxy
|
|
|
163
284
|
end
|
|
164
285
|
|
|
165
286
|
class YouTubeParams < Model
|
|
166
|
-
attr_accessor :format, :title, :privacy_status, :cover_url
|
|
287
|
+
attr_accessor :format, :title, :privacy_status, :cover_url, :made_for_kids
|
|
167
288
|
end
|
|
168
289
|
|
|
169
290
|
class PinterestParams < Model
|
data/lib/postproxy/version.rb
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
|
|
3
|
+
module PostProxy
|
|
4
|
+
module WebhookSignature
|
|
5
|
+
def self.verify(payload, signature_header, secret)
|
|
6
|
+
parts = signature_header.split(",").map { |p| p.split("=", 2) }.to_h
|
|
7
|
+
timestamp = parts["t"]
|
|
8
|
+
expected = parts["v1"]
|
|
9
|
+
|
|
10
|
+
return false if timestamp.nil? || expected.nil?
|
|
11
|
+
|
|
12
|
+
signed_payload = "#{timestamp}.#{payload}"
|
|
13
|
+
computed = OpenSSL::HMAC.hexdigest("SHA256", secret, signed_payload)
|
|
14
|
+
|
|
15
|
+
secure_compare(computed, expected)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private_class_method def self.secure_compare(a, b)
|
|
19
|
+
return false unless a.bytesize == b.bytesize
|
|
20
|
+
|
|
21
|
+
l = a.unpack("C*")
|
|
22
|
+
r = b.unpack("C*")
|
|
23
|
+
result = 0
|
|
24
|
+
l.zip(r) { |x, y| result |= x ^ y }
|
|
25
|
+
result.zero?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/postproxy.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: postproxy-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- PostProxy
|
|
@@ -81,8 +81,10 @@ files:
|
|
|
81
81
|
- lib/postproxy/resources/posts.rb
|
|
82
82
|
- lib/postproxy/resources/profile_groups.rb
|
|
83
83
|
- lib/postproxy/resources/profiles.rb
|
|
84
|
+
- lib/postproxy/resources/webhooks.rb
|
|
84
85
|
- lib/postproxy/types.rb
|
|
85
86
|
- lib/postproxy/version.rb
|
|
87
|
+
- lib/postproxy/webhook_signature.rb
|
|
86
88
|
homepage: https://postproxy.dev
|
|
87
89
|
licenses:
|
|
88
90
|
- MIT
|