distributed-press-api-client 0.3.1 → 0.4.0rc3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/distributed_press/v1/schemas/bittorrent_protocol.rb +1 -1
- data/lib/distributed_press/v1/social/allowlist.rb +18 -0
- data/lib/distributed_press/v1/social/blocklist.rb +75 -0
- data/lib/distributed_press/v1/social/client.rb +220 -86
- data/lib/distributed_press/v1/social/dereferencer.rb +89 -0
- data/lib/distributed_press/v1/social/dereferencer.rb.orig +132 -0
- data/lib/distributed_press/v1/social/hook.rb +100 -0
- data/lib/distributed_press/v1/social/inbox.rb +66 -0
- data/lib/distributed_press/v1/social/outbox.rb +50 -0
- data/lib/distributed_press/v1/social/reference.rb +39 -0
- data/lib/distributed_press/v1/social/referenced_object.rb +89 -0
- data/lib/distributed_press/v1/social/schemas/webhook.rb +24 -0
- data/lib/distributed_press/v1/social/signed_headers.rb +106 -0
- data/lib/distributed_press/version.rb +1 -1
- data/lib/dry/schema/processor_decorator.rb +25 -0
- data/lib/dry/schema/result_decorator.rb +35 -0
- metadata +45 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee95dc9deb3cb19084a66ee33ee4970906619c6489ce0bf29b0aa9f90ae99d93
|
4
|
+
data.tar.gz: abcb6a90f1e16ad51aac21f15b79067c141f4cdc45edfbddc69704dd235adac7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d3e98400b29ee2258a29beb1c47fab70a07d1cbdd43a96df9842a507d2ee3f3aaf9d3eeef1dee381967363227fb8af68b0b9f080861feac67595b1a081c43b0
|
7
|
+
data.tar.gz: 55291cec6e026520a3e9f111fd80e55b4762aa551f1dcae12015951a01d9d6dd55bbda07394f5f36566e3500abed7673e2a71c90feaf2f4092ef838361d3c8e8
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'blocklist'
|
4
|
+
|
5
|
+
class DistributedPress
|
6
|
+
module V1
|
7
|
+
module Social
|
8
|
+
# Manages Social Inbox allowlist
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# DistributedPress::V1::Social::Allowlist.new(client: client)
|
12
|
+
# DistributedPress::V1::Social::Allowlist.new(client: client, actor: '@sutty@sutty.nl')
|
13
|
+
class Allowlist < Blocklist
|
14
|
+
ENDPOINT = '/allowlist'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'client'
|
4
|
+
|
5
|
+
class DistributedPress
|
6
|
+
module V1
|
7
|
+
module Social
|
8
|
+
# Manages Social Inbox blocklist
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# DistributedPress::V1::Social::Blocklist.new(client: client)
|
12
|
+
# DistributedPress::V1::Social::Blocklist.new(client: client, actor: '@sutty@sutty.nl')
|
13
|
+
class Blocklist
|
14
|
+
ACCEPT = %w[text/plain].freeze
|
15
|
+
CONTENT_TYPE = 'text/plain'
|
16
|
+
ENDPOINT = '/blocklist'
|
17
|
+
|
18
|
+
# @return [DistributedPress::V1::Social::Client]
|
19
|
+
attr_reader :client
|
20
|
+
|
21
|
+
# @return [String,nil]
|
22
|
+
attr_reader :actor
|
23
|
+
|
24
|
+
# @param :client [DistributedPress::V1::Social::Client]
|
25
|
+
# @param :actor [String]
|
26
|
+
def initialize(client:, actor: nil)
|
27
|
+
@client = client
|
28
|
+
@actor = actor
|
29
|
+
|
30
|
+
@serializer = proc do |body|
|
31
|
+
body.join("\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param :body [String,nil]
|
35
|
+
# @return [Array<String>]
|
36
|
+
@parser =
|
37
|
+
proc do |body, _|
|
38
|
+
next [] if body.nil?
|
39
|
+
|
40
|
+
body.split("\n").map(&:strip).reject(&:empty?)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Obtains the current blocklist
|
45
|
+
#
|
46
|
+
# @return [Array<String>]
|
47
|
+
def get
|
48
|
+
client.get(endpoint: endpoint, parser: @parser, content_type: CONTENT_TYPE, accept: ACCEPT)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sends an array of instances and accounts to be blocked
|
52
|
+
#
|
53
|
+
# @param :list [Array<String>]
|
54
|
+
# @return [HTTParty::Response]
|
55
|
+
def post(list:)
|
56
|
+
client.post(endpoint: endpoint, body: list, serializer: @serializer, parser: @parser,
|
57
|
+
content_type: CONTENT_TYPE, accept: ACCEPT)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Removes instances and accounts from the blocklist
|
61
|
+
#
|
62
|
+
# @param :list [Array<String>]
|
63
|
+
# @return [HTTParty::Response]
|
64
|
+
def delete(list:)
|
65
|
+
client.delete(endpoint: endpoint, body: list, serializer: @serializer, parser: @parser,
|
66
|
+
content_type: CONTENT_TYPE, accept: ACCEPT)
|
67
|
+
end
|
68
|
+
|
69
|
+
def endpoint
|
70
|
+
@endpoint ||= "/v1/#{actor}#{self.class::ENDPOINT}".squeeze('/')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'httparty'
|
4
|
+
require 'httparty/cache'
|
4
5
|
require 'openssl'
|
5
6
|
require 'time'
|
6
7
|
require 'digest/sha2'
|
7
8
|
require 'uri'
|
9
|
+
require_relative '../../version'
|
10
|
+
require_relative 'signed_headers'
|
8
11
|
|
9
12
|
class DistributedPress
|
10
13
|
module V1
|
@@ -17,22 +20,33 @@ class DistributedPress
|
|
17
20
|
# @see {https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb}
|
18
21
|
class Client
|
19
22
|
include ::HTTParty
|
23
|
+
include ::HTTParty::Cache
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
class Error < StandardError; end
|
26
|
+
class ContentTooLargeError < Error; end
|
27
|
+
class BrotliUnsupportedError < Error; end
|
28
|
+
|
29
|
+
# Always cache
|
30
|
+
caching true
|
31
|
+
|
32
|
+
# Content types sent and accepted
|
33
|
+
ACCEPT = %w[application/activity+json application/ld+json application/json].freeze
|
34
|
+
CONTENT_TYPE = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
35
|
+
|
36
|
+
ACCEPT.each do |accept|
|
37
|
+
HTTParty::Parser::SupportedFormats[accept] = :json
|
38
|
+
end
|
31
39
|
|
32
40
|
# API URL
|
41
|
+
#
|
33
42
|
# @return [String]
|
34
43
|
attr_reader :url
|
35
44
|
|
45
|
+
# API URI
|
46
|
+
#
|
47
|
+
# @return [URI]
|
48
|
+
attr_reader :uri
|
49
|
+
|
36
50
|
# RSA key size
|
37
51
|
#
|
38
52
|
# @return [Integer]
|
@@ -43,36 +57,57 @@ class DistributedPress
|
|
43
57
|
# @return [String]
|
44
58
|
attr_reader :public_key_url
|
45
59
|
|
60
|
+
# Logger
|
61
|
+
#
|
62
|
+
# @return [Logger]
|
63
|
+
attr_reader :logger
|
64
|
+
|
46
65
|
# @param :url [String] Social Distributed Press URL
|
47
66
|
# @param :public_key [String] URL where public key is available
|
48
67
|
# @param :private_key_pem [String] RSA Private key, PEM encoded
|
49
68
|
# @param :key_size [Integer]
|
50
69
|
# @param :logger [Logger]
|
51
|
-
|
52
|
-
|
53
|
-
|
70
|
+
# @param :cache_store [HTTParty::Cache::Store::Abstract,Symbol]
|
71
|
+
def initialize(public_key_url:, url: 'https://social.distributed.press', private_key_pem: nil, key_size: 2048,
|
72
|
+
logger: nil, cache_store: :memory)
|
73
|
+
self.class.default_options[:logger] = @logger = logger
|
54
74
|
self.class.default_options[:log_level] = :debug
|
75
|
+
self.class.cache_store cache_store
|
55
76
|
|
77
|
+
@url = HTTParty.normalize_base_uri(url)
|
56
78
|
@public_key_url = public_key_url
|
57
79
|
@key_size = key_size
|
58
80
|
@private_key_pem = private_key_pem
|
59
|
-
|
60
|
-
|
61
|
-
|
81
|
+
@uri = URI.parse(url)
|
82
|
+
@serializer ||= proc do |body|
|
83
|
+
body.to_json
|
62
84
|
end
|
63
85
|
end
|
64
86
|
|
87
|
+
def _dump(_)
|
88
|
+
Marshal.dump({
|
89
|
+
public_key_url: public_key_url,
|
90
|
+
url: url,
|
91
|
+
private_key_pem: @private_key_pem,
|
92
|
+
key_size: key_size,
|
93
|
+
cache_store: self.class.cache_store
|
94
|
+
})
|
95
|
+
end
|
96
|
+
|
97
|
+
def self._load(hash)
|
98
|
+
new(**Marshal.load(hash))
|
99
|
+
end
|
100
|
+
|
65
101
|
# GET request
|
66
102
|
#
|
67
103
|
# @param endpoint [String]
|
68
|
-
# @
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
self.class.get(endpoint, headers: headers)
|
104
|
+
# @param parser [HTTParty::Parser,Proc]
|
105
|
+
# @param content_type [String]
|
106
|
+
# @param accept [Array<String>]
|
107
|
+
# @return [HTTParty::Response]
|
108
|
+
def get(endpoint:, parser: HTTParty::Parser, content_type: CONTENT_TYPE, accept: ACCEPT)
|
109
|
+
perform_signed_request method: Net::HTTP::Get, endpoint: endpoint, parser: parser, content_type: content_type,
|
110
|
+
accept: accept
|
76
111
|
end
|
77
112
|
|
78
113
|
# POST request
|
@@ -80,16 +115,45 @@ class DistributedPress
|
|
80
115
|
# @todo Use DRY-schemas
|
81
116
|
# @param endpoint [String]
|
82
117
|
# @param body [Hash]
|
83
|
-
# @
|
84
|
-
|
85
|
-
|
86
|
-
|
118
|
+
# @param serializer [Proc]
|
119
|
+
# @param parser [HTTParty::Parser,Proc]
|
120
|
+
# @param content_type [String]
|
121
|
+
# @param accept [Array<String>]
|
122
|
+
# @return [HTTParty::Response]
|
123
|
+
def post(endpoint:, body: nil, serializer: @serializer, parser: HTTParty::Parser, content_type: CONTENT_TYPE,
|
124
|
+
accept: ACCEPT)
|
125
|
+
perform_signed_request method: Net::HTTP::Post, endpoint: endpoint, body: body, serializer: serializer,
|
126
|
+
parser: parser, content_type: content_type, accept: accept
|
127
|
+
end
|
87
128
|
|
88
|
-
|
89
|
-
|
90
|
-
|
129
|
+
# PUT request
|
130
|
+
#
|
131
|
+
# @param endpoint [String]
|
132
|
+
# @param body [Hash]
|
133
|
+
# @param serializer [Proc]
|
134
|
+
# @param parser [HTTParty::Parser,Proc]
|
135
|
+
# @param content_type [String]
|
136
|
+
# @param accept [Array<String>]
|
137
|
+
# @return [HTTParty::Response]
|
138
|
+
def put(endpoint:, body: nil, serializer: @serializer, parser: HTTParty::Parser, content_type: CONTENT_TYPE,
|
139
|
+
accept: ACCEPT)
|
140
|
+
perform_signed_request method: Net::HTTP::Put, endpoint: endpoint, body: body, serializer: serializer,
|
141
|
+
parser: parser, content_type: content_type, accept: accept
|
142
|
+
end
|
91
143
|
|
92
|
-
|
144
|
+
# DELETE request with optional body
|
145
|
+
#
|
146
|
+
# @param endpoint [String]
|
147
|
+
# @param body [Hash]
|
148
|
+
# @param serializer [Proc]
|
149
|
+
# @param parser [HTTParty::Parser,Proc]
|
150
|
+
# @param content_type [String]
|
151
|
+
# @param accept [Array<String>]
|
152
|
+
# @return [HTTParty::Response]
|
153
|
+
def delete(endpoint:, body: nil, serializer: @serializer, parser: HTTParty::Parser, content_type: CONTENT_TYPE,
|
154
|
+
accept: ACCEPT)
|
155
|
+
perform_signed_request method: Net::HTTP::Delete, endpoint: endpoint, body: body, serializer: serializer,
|
156
|
+
parser: parser, content_type: content_type, accept: accept
|
93
157
|
end
|
94
158
|
|
95
159
|
# Loads or generates a private key
|
@@ -115,84 +179,154 @@ class DistributedPress
|
|
115
179
|
|
116
180
|
private
|
117
181
|
|
118
|
-
|
119
|
-
|
182
|
+
# Perform request
|
183
|
+
#
|
184
|
+
# @param method [Net::HTTP::Get,Net::HTTP::Post,Net::HTTP::Put,Net::HTTP::Delete]
|
185
|
+
# @param endpoint [String]
|
186
|
+
# @param body [Hash]
|
187
|
+
# @param serializer [Proc]
|
188
|
+
# @param parser [HTTParty::Parser,Proc]
|
189
|
+
# @param content_type [String]
|
190
|
+
# @param accept [Array<String>]
|
191
|
+
# @return [HTTParty::Response]
|
192
|
+
def perform_signed_request(method:, endpoint:, body: nil, serializer: @default_serializer, parser: HTTParty::Parser,
|
193
|
+
content_type: CONTENT_TYPE, accept: ACCEPT)
|
194
|
+
headers = default_headers(content_type: content_type, accept: accept)
|
195
|
+
|
196
|
+
unless body.nil?
|
197
|
+
body = serializer.call(body)
|
198
|
+
checksum_body!(body, headers)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Mimics HTTParty.perform_request, but processing the header
|
202
|
+
# after the request is instantiated. No need to process
|
203
|
+
# cookies for now. Uses the public key URL as a caching key so
|
204
|
+
# we revalidate access on shared caches.
|
205
|
+
options = { body: body, headers: headers, base_uri: url, parser: parser, stream_body: true, cache_key: public_key_url }
|
206
|
+
options = HTTParty::ModuleInheritableAttributes.hash_deep_dup(self.class.default_options).merge(options)
|
207
|
+
response_body = ''.dup
|
208
|
+
|
209
|
+
HTTParty::Request.new(method, endpoint, options).tap do |request|
|
210
|
+
headers.request = request
|
211
|
+
end.perform do |fragment|
|
212
|
+
fragment_to_body!(fragment, response_body)
|
213
|
+
end.tap do |response|
|
214
|
+
next if response_body.empty?
|
215
|
+
|
216
|
+
fix_response!(response, response_body)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Default headers
|
221
|
+
#
|
222
|
+
# @param :content_type [String]
|
223
|
+
# @param :accept [Array<String>]
|
224
|
+
# @return [SignedHeaders]
|
225
|
+
def default_headers(content_type: CONTENT_TYPE, accept: ACCEPT)
|
226
|
+
SignedHeaders[
|
120
227
|
'User-Agent' => "DistributedPress/#{DistributedPress::VERSION}",
|
121
|
-
'Content-Type' =>
|
122
|
-
'
|
123
|
-
|
228
|
+
'Content-Type' => content_type,
|
229
|
+
'Accept' => accept.join(', '),
|
230
|
+
'Host' => host,
|
231
|
+
'Accept-Encoding' => "#{'br;q=1.0,' if brotli?}gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
|
232
|
+
'Date' => Time.now.utc.httpdate
|
233
|
+
].tap do |headers|
|
234
|
+
headers.private_key = private_key
|
235
|
+
headers.public_key_url = public_key_url
|
236
|
+
end
|
124
237
|
end
|
125
238
|
|
126
|
-
#
|
239
|
+
# Generate a checksum for the request body
|
127
240
|
#
|
128
|
-
# @
|
241
|
+
# @param :body [String]
|
129
242
|
# @param :headers [Hash]
|
130
|
-
def
|
131
|
-
headers['
|
132
|
-
'keyId' => public_key_url,
|
133
|
-
'algorithm' => ALGORITHM,
|
134
|
-
'headers' => signable_headers(headers),
|
135
|
-
'signature' => signed_headers(headers)
|
136
|
-
}.map do |key, value|
|
137
|
-
"#{key}=\"#{value}\""
|
138
|
-
end.join(',')
|
139
|
-
|
140
|
-
headers.delete REQUEST_TARGET
|
243
|
+
def checksum_body!(body, headers)
|
244
|
+
headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest body}"
|
141
245
|
|
142
246
|
nil
|
143
247
|
end
|
144
248
|
|
145
|
-
#
|
146
|
-
#
|
249
|
+
# If the endpoint is on a subdirectory, we need the absolute
|
250
|
+
# path for signing
|
147
251
|
#
|
252
|
+
# @param :endpoint [String]
|
148
253
|
# @return [String]
|
149
|
-
def
|
150
|
-
|
254
|
+
def absolute_endpoint(endpoint)
|
255
|
+
"#{uri.path}/#{endpoint}".squeeze('/')
|
151
256
|
end
|
152
257
|
|
153
|
-
#
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
258
|
+
# Brotli available
|
259
|
+
def brotli?
|
260
|
+
begin
|
261
|
+
require 'brs'
|
262
|
+
rescue LoadError => e
|
263
|
+
if logger
|
264
|
+
logger.warn e.message
|
265
|
+
else
|
266
|
+
puts e
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
defined? ::BRS
|
164
271
|
end
|
165
272
|
|
166
|
-
#
|
273
|
+
# Inflates a Brotli compressed fragment. A fragment could be
|
274
|
+
# small enough to contain a huge inflated payload. In our
|
275
|
+
# tests, 2GB of zeroes were compressed to a 1.6KB fragment.
|
167
276
|
#
|
168
|
-
# @
|
169
|
-
# @
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
277
|
+
# @todo Maybe each_char is too slow?
|
278
|
+
# @param fragment [HTTParty::ResponseFragment]
|
279
|
+
# @param append_to [String]
|
280
|
+
def inflate_fragment!(fragment, append_to)
|
281
|
+
fragment_io = StringIO.new(fragment)
|
282
|
+
|
283
|
+
BRS::Stream::Reader.new(fragment_io, source_buffer_length: 256,
|
284
|
+
destination_buffer_length: 1024).each_char do |char|
|
285
|
+
append_to << char
|
286
|
+
|
287
|
+
raise ContentTooLargeError if append_to.bytesize > 1_000_000
|
288
|
+
end
|
174
289
|
end
|
175
290
|
|
176
|
-
#
|
291
|
+
# Collect fragments into body by checking the bytesize and
|
292
|
+
# failing if it gets too big.
|
177
293
|
#
|
178
|
-
# @param :
|
179
|
-
# @param :
|
180
|
-
def
|
181
|
-
|
294
|
+
# @param :fragment [HTTParty::ResponseFragment]
|
295
|
+
# @param :append_to [String]
|
296
|
+
def fragment_to_body!(fragment, append_to)
|
297
|
+
case fragment.http_response['content-encoding']
|
298
|
+
when 'br'
|
299
|
+
# Raise an error rather than returning an empty body
|
300
|
+
unless brotli?
|
301
|
+
raise BrotliUnsupportedError,
|
302
|
+
'Server sent brotli-encoded response but ruby-brs gem is missing'
|
303
|
+
end
|
182
304
|
|
183
|
-
|
305
|
+
inflate_fragment!(fragment, append_to)
|
306
|
+
else
|
307
|
+
append_to << fragment
|
308
|
+
end
|
309
|
+
|
310
|
+
raise ContentTooLargeError if append_to.bytesize > 1_000_000
|
311
|
+
rescue ContentTooLargeError
|
312
|
+
raise ContentTooLargeError, "Content too large #{append_to.bytesize} bytes!"
|
184
313
|
end
|
185
314
|
|
186
|
-
#
|
315
|
+
# Re-adds a streamed body to the response and caches it again so
|
316
|
+
# it can carry the body
|
187
317
|
#
|
188
|
-
# @param
|
189
|
-
# @param
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
318
|
+
# @param response [HTTParty::Response]
|
319
|
+
# @param body [String]
|
320
|
+
def fix_response!(response, body)
|
321
|
+
request = response.request
|
322
|
+
parser = request.options[:parser]
|
323
|
+
format = request.format || :plain
|
194
324
|
|
195
|
-
|
325
|
+
response.instance_variable_set(:@body, body)
|
326
|
+
response.instance_variable_set(:@parsed_block, -> { parser.call(body, format) })
|
327
|
+
response.instance_variable_set(:@parsed_response, nil)
|
328
|
+
|
329
|
+
self.class.cache_store.set(response.cache_key, response)
|
196
330
|
end
|
197
331
|
end
|
198
332
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'addressable'
|
4
|
+
require_relative 'reference'
|
5
|
+
require_relative 'referenced_object'
|
6
|
+
|
7
|
+
class DistributedPress
|
8
|
+
module V1
|
9
|
+
module Social
|
10
|
+
# Fetches ActivityStreams from different instances by
|
11
|
+
# instantiating clients.
|
12
|
+
class Dereferencer
|
13
|
+
# @return [DistributedPress::V1::Social::Client]
|
14
|
+
attr_reader :client
|
15
|
+
|
16
|
+
# We only need the client
|
17
|
+
def _dump(_)
|
18
|
+
Marshal.dump(client)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self._load(client)
|
22
|
+
new(client: Marshal.load(client))
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param :client [DistributedPress::V1::Social::Client]
|
26
|
+
def initialize(client:)
|
27
|
+
@client = client
|
28
|
+
@parser =
|
29
|
+
proc do |body, format|
|
30
|
+
next HTTParty::Parser.call(body, format || :plain) unless body&.starts_with? '{'
|
31
|
+
|
32
|
+
ReferencedObject.new(object: HTTParty::Parser.call(body, :json), dereferencer: self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Fetch a URI
|
37
|
+
#
|
38
|
+
# @param :uri [String, Addressable::URI]
|
39
|
+
# @return [HTTParty::Response]
|
40
|
+
def get(uri:)
|
41
|
+
uri = uris(uri)
|
42
|
+
|
43
|
+
clients(uri).get(endpoint: uri.path, parser: @parser)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Gets a client for a URI
|
47
|
+
#
|
48
|
+
# @param :uri [Addressable::URI]
|
49
|
+
# @return [DistributedPress::V1::Social::Client]
|
50
|
+
def clients(uri)
|
51
|
+
@@clients ||= {}
|
52
|
+
@@clients[uri.origin] ||=
|
53
|
+
client.class.new(
|
54
|
+
url: uri.origin,
|
55
|
+
public_key_url: client.public_key_url,
|
56
|
+
private_key_pem: client.private_key.to_s,
|
57
|
+
logger: client.logger,
|
58
|
+
cache_store: client.class.cache_store
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Gets a reference for a URI and indexes it by the complete
|
63
|
+
# and normalized URI
|
64
|
+
#
|
65
|
+
# @param :uri [String, Addressable::URI]
|
66
|
+
# @return [DistributedPress::V1::Social::Reference]
|
67
|
+
def references(uri)
|
68
|
+
@@references ||= {}
|
69
|
+
@@references[uri.to_s] ||= Reference.new(uri: uri.to_s, dereferencer: self)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Make sure we're getting a normalized Addressable::URI
|
73
|
+
#
|
74
|
+
# @param :uri [String, Addressable::URI]
|
75
|
+
# @return [Addressable::URI]
|
76
|
+
def uris(uri)
|
77
|
+
@@uris ||= {}
|
78
|
+
@@uris[uri.to_s] ||=
|
79
|
+
(if uri.is_a? Addressable::URI
|
80
|
+
uri
|
81
|
+
else
|
82
|
+
Addressable::URI.parse(uri)
|
83
|
+
end).normalize
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|