didkit 0.2.3 → 0.3.1
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/CHANGELOG.md +31 -0
- data/LICENSE.txt +1 -1
- data/README.md +58 -34
- data/lib/didkit/did.rb +41 -19
- data/lib/didkit/document.rb +20 -14
- data/lib/didkit/plc_importer.rb +22 -6
- data/lib/didkit/plc_operation.rb +9 -2
- data/lib/didkit/requests.rb +76 -18
- data/lib/didkit/resolver.rb +14 -14
- data/lib/didkit/services.rb +13 -0
- data/lib/didkit/version.rb +1 -1
- metadata +7 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b81757b37a0aa45a3ccefe6e8ebac777a283a6f8d3b827308f1f029b30abe0c8
|
|
4
|
+
data.tar.gz: beef9f4a9cc71c9edc46fce5e95df2b1c6a442c223826054eafed721d744b8ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 24c046d3566c3816c700e60936f9313b837df8403dc0435e67c317eabaf4e554c29357573a825d72a4795185d942c67c0fc408457a2f3c72e8569c1fc9c718ab
|
|
7
|
+
data.tar.gz: d0e4bb8224a6a0c5b6d62edfee27869fc7227229d3d95f482beb2c917ac0ea99c45fed7e8993299c8a2b69ef0a9d5f58e1d610e108f0f15aac50ffebb7599dfe
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
## [0.3.1] - 2025-12-19
|
|
2
|
+
|
|
3
|
+
- allow passing a DID string or object to `#resolve_handle` and just return that DID – so you can have a script that accepts either a handle or a DID, and passes the input to `DID.resolve_handle` without checking which one it is
|
|
4
|
+
- allow passing another DID object to `DID.new` and return a copy of that DID
|
|
5
|
+
- parse `seq` field in `PLCOperation` if included and expose it as a property
|
|
6
|
+
- fixed some errors on Rubies older than 3.2 due to missing `filter_map` and `URI#origin`
|
|
7
|
+
- `PLCOperation` verifies if the argument is a `Hash`
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2025-12-15
|
|
10
|
+
|
|
11
|
+
Breaking changes:
|
|
12
|
+
|
|
13
|
+
* removed `DID#is_known_by_relay?` – it doesn't work anymore, since relays are now non-archival and they expose almost no XRPC routes
|
|
14
|
+
* renamed a few handle-related methods:
|
|
15
|
+
- `get_validated_handle` -> `get_verified_handle`
|
|
16
|
+
- `pick_valid_handle` -> `first_verified_handle`
|
|
17
|
+
|
|
18
|
+
Also:
|
|
19
|
+
|
|
20
|
+
- added `DID#account_status` method, which checks `getRepoStatus` endpoint to tell if an account is active, deactivated, taken down etc.
|
|
21
|
+
- added `DID#account_active?` helper (`account_status == :active`)
|
|
22
|
+
- `DID#account_exists?` now calls `getRepoStatus` (via `account_status`, checking if it's not nil) instead of `getLatestCommit`
|
|
23
|
+
- added `DID#document` which keeps a memoized copy of the document
|
|
24
|
+
- added `pds_host` & `labeler_host` methods to `PLCOperation` and `Document`, which return the PDS/labeller address without the `https://`
|
|
25
|
+
- added `labeller_endpoint` & `labeller_host` aliases for the double-L enjoyers :]
|
|
26
|
+
- added `PLCOperation#cid`
|
|
27
|
+
- `PLCImporter` now removes duplicate operations at the edge of pages returned from the `/export` API
|
|
28
|
+
- rewritten some networking code – all classes now use `Net::HTTP` with consistent options instead of `open-uri`
|
|
29
|
+
|
|
30
|
+
Note: `PLCImporter` will be rewritten soon to add support for updated [plc.directory](https://plc.directory) APIs, so be prepared for some breaking changes there in v. 0.4.
|
|
31
|
+
|
|
1
32
|
## [0.2.3] - 2024-07-02
|
|
2
33
|
|
|
3
34
|
- added a `DID#get_audit_log` method that fetches the PLC audit log for a DID
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
A small Ruby gem for handling Distributed Identifiers (DIDs) in Bluesky / AT Protocol.
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
|
-
> ATProto Ruby
|
|
6
|
+
> Part of ATProto Ruby SDK: [ruby.sdk.blue](https://ruby.sdk.blue)
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
## What does it do
|
|
@@ -13,75 +13,99 @@ Accounts on Bluesky use identifiers like [did:plc:oio4hkxaop4ao4wz2pp3f4cr](http
|
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
|
+
From the command line:
|
|
17
|
+
|
|
16
18
|
gem install didkit
|
|
17
19
|
|
|
20
|
+
Or, add this to your `Gemfile`:
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
gem 'didkit', '~> 0.3'
|
|
20
23
|
|
|
21
|
-
Use the `DIDKit::Resolver` class to look up DIDs and handles.
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
The simplest way to use the gem is through the `DIDKit::DID` class, aliased as just `DID`:
|
|
24
28
|
|
|
25
29
|
```rb
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
did = DID.resolve_handle('jay.bsky.team')
|
|
31
|
+
# => #<DIDKit::DID:0x0... @did="did:plc:oky5czdrnfjpqslsw2a5iclo",
|
|
32
|
+
# @resolved_by=:dns, @type=:plc>
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
This returns
|
|
35
|
+
This returns a `DID` object, which tells you:
|
|
32
36
|
|
|
33
37
|
- the DID as a string (`#to_s` or `#did`)
|
|
34
38
|
- the DID type (`#type`, `:plc` or `:web`)
|
|
35
39
|
- if the handle was resolved via a DNS entry or a `.well-known` file (`#resolved_by`, `:dns` or `:http`)
|
|
36
40
|
|
|
37
|
-
To go in the other direction – to find an assigned and verified handle given a DID –
|
|
41
|
+
To go in the other direction – to find an assigned and verified handle given a DID – create a `DID` from a DID string and call `get_verified_handle`:
|
|
38
42
|
|
|
39
43
|
```rb
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
DID.new('did:plc:ewvi7nxzyoun6zhxrhs64oiz').get_verified_handle
|
|
45
|
+
# => "atproto.com"
|
|
42
46
|
```
|
|
43
47
|
|
|
44
|
-
You can also load the DID document using `
|
|
48
|
+
You can also load the DID JSON document using `#document`, which returns a `DIDKit::Document` (`DID` caches the document, so don't worry about calling this method multiple times):
|
|
45
49
|
|
|
46
50
|
```rb
|
|
47
|
-
|
|
48
|
-
# => #<DIDKit::Document:0x0000000105d751f8 @did=#<DIDKit::DID:...>, @json={...}>
|
|
51
|
+
did = DID.new('did:plc:ragtjsm2j2vknwkz3zp4oxrd')
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
did.document.handles
|
|
54
|
+
# => ["pfrazee.com"]
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
did.document.pds_host
|
|
57
|
+
# => "morel.us-east.host.bsky.network"
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
|
|
61
|
+
### Checking account status
|
|
62
|
+
|
|
63
|
+
`DIDKit::DID` also includes a few methods for checking the status of a given account (repo), which call the `com.atproto.sync.getRepoStatus` endpoint on the account's assigned PDS:
|
|
58
64
|
|
|
59
65
|
```rb
|
|
60
|
-
did = DID.
|
|
61
|
-
|
|
66
|
+
did = DID.new('did:plc:ch7azdejgddtlijyzurfdihn')
|
|
67
|
+
did.account_status
|
|
68
|
+
# => :takendown
|
|
69
|
+
did.account_active?
|
|
70
|
+
# => false
|
|
71
|
+
did.account_exists?
|
|
72
|
+
# => true
|
|
73
|
+
|
|
74
|
+
did = DID.new('did:plc:44ybard66vv44zksje25o7dz')
|
|
75
|
+
did.account_status
|
|
76
|
+
# => :active
|
|
77
|
+
did.account_active?
|
|
78
|
+
# => true
|
|
79
|
+
```
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
# => "did:plc:oky5czdrnfjpqslsw2a5iclo"
|
|
81
|
+
### Configuration
|
|
65
82
|
|
|
66
|
-
|
|
67
|
-
# => #<DIDKit::Document:0x00000001066d4898 @did=#<DIDKit::DID:...>, @json={...}>
|
|
83
|
+
You can customize some things about the DID/handle lookups by using the `DIDKit::Resolver` class, which the methods in `DID` use behind the scenes.
|
|
68
84
|
|
|
69
|
-
|
|
70
|
-
# => "jay.bsky.team"
|
|
71
|
-
```
|
|
85
|
+
Currently available options include:
|
|
72
86
|
|
|
87
|
+
- `:nameserver` - override the nameserver used for DNS lookups, e.g. to use Google's or CloudFlare's DNS
|
|
88
|
+
- `:timeout` - change the connection/response timeout for HTTP requests (default: 15 s)
|
|
89
|
+
- `:max_redirects` - change allowed maximum number of redirects (default: 5)
|
|
73
90
|
|
|
74
|
-
|
|
91
|
+
Example:
|
|
92
|
+
|
|
93
|
+
```rb
|
|
94
|
+
resolver = DIDKit::Resolver.new(nameserver: '8.8.8.8', timeout: 30)
|
|
75
95
|
|
|
76
|
-
|
|
96
|
+
did = resolver.resolve_handle('nytimes.com')
|
|
97
|
+
# => #<DIDKit::DID:0x0... @did="did:plc:eclio37ymobqex2ncko63h4r",
|
|
98
|
+
# @resolved_by=:dns, @type=:plc>
|
|
77
99
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```
|
|
100
|
+
resolver.resolve_did(did)
|
|
101
|
+
# => #<DIDKit::Document:0x0... @did=#<DIDKit::DID:...>, @json={...}>
|
|
81
102
|
|
|
103
|
+
resolver.get_verified_handle(did)
|
|
104
|
+
# => 'nytimes.com'
|
|
105
|
+
```
|
|
82
106
|
|
|
83
107
|
## Credits
|
|
84
108
|
|
|
85
|
-
Copyright ©
|
|
109
|
+
Copyright © 2025 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr)).
|
|
86
110
|
|
|
87
111
|
The code is available under the terms of the [zlib license](https://choosealicense.com/licenses/zlib/) (permissive, similar to MIT).
|
data/lib/didkit/did.rb
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
1
4
|
require_relative 'errors'
|
|
2
5
|
require_relative 'requests'
|
|
3
6
|
require_relative 'resolver'
|
|
4
7
|
|
|
5
8
|
module DIDKit
|
|
6
9
|
class DID
|
|
10
|
+
GENERIC_REGEXP = /\Adid\:\w+\:.+\z/
|
|
11
|
+
|
|
7
12
|
include Requests
|
|
8
13
|
|
|
9
14
|
def self.resolve_handle(handle)
|
|
@@ -13,9 +18,13 @@ module DIDKit
|
|
|
13
18
|
attr_reader :type, :did, :resolved_by
|
|
14
19
|
|
|
15
20
|
def initialize(did, resolved_by = nil)
|
|
16
|
-
if did
|
|
21
|
+
if did.is_a?(DID)
|
|
22
|
+
did = did.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if did =~ GENERIC_REGEXP
|
|
17
26
|
@did = did
|
|
18
|
-
@type =
|
|
27
|
+
@type = did.split(':')[1].to_sym
|
|
19
28
|
else
|
|
20
29
|
raise DIDError.new("Invalid DID format")
|
|
21
30
|
end
|
|
@@ -29,12 +38,16 @@ module DIDKit
|
|
|
29
38
|
|
|
30
39
|
alias to_s did
|
|
31
40
|
|
|
41
|
+
def document
|
|
42
|
+
@document ||= get_document
|
|
43
|
+
end
|
|
44
|
+
|
|
32
45
|
def get_document
|
|
33
46
|
Resolver.new.resolve_did(self)
|
|
34
47
|
end
|
|
35
48
|
|
|
36
|
-
def
|
|
37
|
-
Resolver.new.
|
|
49
|
+
def get_verified_handle
|
|
50
|
+
Resolver.new.get_verified_handle(document)
|
|
38
51
|
end
|
|
39
52
|
|
|
40
53
|
def get_audit_log
|
|
@@ -49,32 +62,41 @@ module DIDKit
|
|
|
49
62
|
did.gsub(/^did\:web\:/, '') if type == :web
|
|
50
63
|
end
|
|
51
64
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
def account_status(request_options = {})
|
|
66
|
+
doc = self.document
|
|
67
|
+
return nil if doc.pds_endpoint.nil?
|
|
68
|
+
|
|
69
|
+
pds_host = uri_origin(doc.pds_endpoint)
|
|
70
|
+
url = URI("#{pds_host}/xrpc/com.atproto.sync.getRepoStatus")
|
|
71
|
+
url.query = URI.encode_www_form(:did => @did)
|
|
56
72
|
|
|
57
|
-
response = get_response(url,
|
|
73
|
+
response = get_response(url, request_options)
|
|
58
74
|
status = response.code.to_i
|
|
59
75
|
is_json = (response['Content-Type'] =~ /^application\/json(;.*)?$/)
|
|
60
76
|
|
|
61
|
-
if status == 200
|
|
62
|
-
|
|
77
|
+
if status == 200 && is_json
|
|
78
|
+
json = JSON.parse(response.body)
|
|
79
|
+
|
|
80
|
+
if json['active'] == true
|
|
81
|
+
:active
|
|
82
|
+
elsif json['active'] == false && json['status'].is_a?(String) && json['status'].length <= 100
|
|
83
|
+
json['status'].to_sym
|
|
84
|
+
else
|
|
85
|
+
raise APIError.new(response)
|
|
86
|
+
end
|
|
63
87
|
elsif status == 400 && is_json && JSON.parse(response.body)['error'] == 'RepoNotFound'
|
|
64
|
-
|
|
65
|
-
elsif status == 404 && is_json && JSON.parse(response.body)['error']
|
|
66
|
-
false
|
|
88
|
+
nil
|
|
67
89
|
else
|
|
68
90
|
raise APIError.new(response)
|
|
69
91
|
end
|
|
70
92
|
end
|
|
71
93
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
94
|
+
def account_active?
|
|
95
|
+
account_status == :active
|
|
96
|
+
end
|
|
75
97
|
|
|
76
|
-
|
|
77
|
-
|
|
98
|
+
def account_exists?
|
|
99
|
+
account_status != nil
|
|
78
100
|
end
|
|
79
101
|
|
|
80
102
|
def ==(other)
|
data/lib/didkit/document.rb
CHANGED
|
@@ -21,24 +21,30 @@ module DIDKit
|
|
|
21
21
|
@did = did
|
|
22
22
|
@json = json
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
@services = parse_services(json['service'] || [])
|
|
25
|
+
@handles = parse_also_known_as(json['alsoKnownAs'] || [])
|
|
26
|
+
end
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
def get_verified_handle
|
|
29
|
+
Resolver.new.get_verified_handle(self)
|
|
30
|
+
end
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
else
|
|
34
|
-
@services = []
|
|
35
|
-
end
|
|
32
|
+
private
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
def parse_services(service_data)
|
|
35
|
+
raise FormatError, "Invalid service data" unless service_data.is_a?(Array) && service_data.all? { |x| x.is_a?(Hash) }
|
|
36
|
+
|
|
37
|
+
services = []
|
|
38
|
+
|
|
39
|
+
service_data.each do |x|
|
|
40
|
+
id, type, endpoint = x.values_at('id', 'type', 'serviceEndpoint')
|
|
41
|
+
|
|
42
|
+
if id.is_a?(String) && id.start_with?('#') && type.is_a?(String) && endpoint.is_a?(String)
|
|
43
|
+
services << ServiceRecord.new(id.gsub(/^#/, ''), type, endpoint)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
Resolver.new.pick_valid_handle(did, handles)
|
|
47
|
+
services
|
|
42
48
|
end
|
|
43
49
|
end
|
|
44
50
|
end
|
data/lib/didkit/plc_importer.rb
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
require 'json'
|
|
2
|
-
require 'open-uri'
|
|
3
2
|
require 'time'
|
|
3
|
+
require 'uri'
|
|
4
4
|
|
|
5
5
|
require_relative 'plc_operation'
|
|
6
|
+
require_relative 'requests'
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# NOTE: this class is pending a rewrite once new APIs are deployed to plc.directory.
|
|
10
|
+
# Things will change here in v. 0.4.
|
|
11
|
+
#
|
|
6
12
|
|
|
7
13
|
module DIDKit
|
|
8
14
|
class PLCImporter
|
|
9
15
|
PLC_SERVICE = 'plc.directory'
|
|
10
16
|
MAX_PAGE = 1000
|
|
11
17
|
|
|
18
|
+
include Requests
|
|
19
|
+
|
|
12
20
|
attr_accessor :ignore_errors, :last_date, :error_handler
|
|
13
21
|
|
|
14
22
|
def initialize(since: nil)
|
|
@@ -22,6 +30,8 @@ module DIDKit
|
|
|
22
30
|
@last_date = Time.now
|
|
23
31
|
@eof = true
|
|
24
32
|
end
|
|
33
|
+
|
|
34
|
+
@last_page_cids = []
|
|
25
35
|
end
|
|
26
36
|
|
|
27
37
|
def plc_service
|
|
@@ -42,13 +52,13 @@ module DIDKit
|
|
|
42
52
|
url = URI("https://#{plc_service}/export")
|
|
43
53
|
url.query = URI.encode_www_form(args)
|
|
44
54
|
|
|
45
|
-
data =
|
|
55
|
+
data = get_data(url, content_type: 'application/jsonlines')
|
|
46
56
|
data.lines.map(&:strip).reject(&:empty?).map { |x| JSON.parse(x) }
|
|
47
57
|
end
|
|
48
58
|
|
|
49
59
|
def fetch_audit_log(did)
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
json = get_json("https://#{plc_service}/#{did}/log/audit", :content_type => :json)
|
|
61
|
+
json.map { |j| PLCOperation.new(j) }
|
|
52
62
|
end
|
|
53
63
|
|
|
54
64
|
def fetch_page
|
|
@@ -57,16 +67,22 @@ module DIDKit
|
|
|
57
67
|
query = @last_date ? { :after => @last_date.utc.iso8601(6) } : {}
|
|
58
68
|
rows = get_export(query)
|
|
59
69
|
|
|
60
|
-
operations = rows.filter_map
|
|
70
|
+
operations = rows.filter_map { |json|
|
|
61
71
|
begin
|
|
62
72
|
PLCOperation.new(json)
|
|
63
73
|
rescue PLCOperation::FormatError, AtHandles::FormatError, ServiceRecord::FormatError => e
|
|
64
74
|
@error_handler ? @error_handler.call(e, json) : raise
|
|
65
75
|
nil
|
|
66
76
|
end
|
|
67
|
-
|
|
77
|
+
}.reject { |op|
|
|
78
|
+
# when you pass the most recent op's timestamp to ?after, it will be returned as the first op again,
|
|
79
|
+
# so we need to use this CID list to filter it out (so pages will usually be 999 items long)
|
|
80
|
+
|
|
81
|
+
@last_page_cids.include?(op.cid)
|
|
82
|
+
}
|
|
68
83
|
|
|
69
84
|
@last_date = operations.last&.created_at || request_time
|
|
85
|
+
@last_page_cids = Set.new(operations.map(&:cid))
|
|
70
86
|
@eof = (rows.length < MAX_PAGE)
|
|
71
87
|
|
|
72
88
|
operations
|
data/lib/didkit/plc_operation.rb
CHANGED
|
@@ -12,13 +12,20 @@ module DIDKit
|
|
|
12
12
|
include AtHandles
|
|
13
13
|
include Services
|
|
14
14
|
|
|
15
|
-
attr_reader :json, :did, :created_at, :type, :handles, :services
|
|
15
|
+
attr_reader :json, :did, :cid, :seq, :created_at, :type, :handles, :services
|
|
16
16
|
|
|
17
17
|
def initialize(json)
|
|
18
18
|
@json = json
|
|
19
|
+
raise FormatError, "Expected argument to be a Hash, got a #{json.class}" unless @json.is_a?(Hash)
|
|
20
|
+
|
|
21
|
+
@seq = json['seq']
|
|
19
22
|
@did = json['did']
|
|
20
23
|
raise FormatError, "Missing DID: #{json}" if @did.nil?
|
|
21
|
-
raise FormatError, "Invalid DID: #{@did}" unless @did.is_a?(String) && @did.start_with?('did:')
|
|
24
|
+
raise FormatError, "Invalid DID: #{@did.inspect}" unless @did.is_a?(String) && @did.start_with?('did:')
|
|
25
|
+
|
|
26
|
+
@cid = json['cid']
|
|
27
|
+
raise FormatError, "Missing CID: #{json}" if @cid.nil?
|
|
28
|
+
raise FormatError, "Invalid CID: #{@cid}" unless @cid.is_a?(String)
|
|
22
29
|
|
|
23
30
|
timestamp = json['createdAt']
|
|
24
31
|
raise FormatError, "Missing createdAt: #{json}" if timestamp.nil?
|
data/lib/didkit/requests.rb
CHANGED
|
@@ -1,28 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'net/http'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
require_relative 'errors'
|
|
6
|
+
|
|
7
|
+
module DIDKit
|
|
8
|
+
module Requests
|
|
9
|
+
def get_response(url, options = {})
|
|
10
|
+
url = URI(url) unless url.is_a?(URI)
|
|
11
|
+
|
|
12
|
+
timeout = options[:timeout] || 15
|
|
13
|
+
|
|
14
|
+
request_options = {
|
|
15
|
+
use_ssl: true,
|
|
16
|
+
open_timeout: timeout,
|
|
17
|
+
read_timeout: timeout
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
redirects = 0
|
|
21
|
+
visited_urls = []
|
|
22
|
+
max_redirects = options[:max_redirects] || 5
|
|
23
|
+
|
|
24
|
+
loop do
|
|
25
|
+
visited_urls << url
|
|
26
|
+
|
|
27
|
+
response = Net::HTTP.start(url.host, url.port, request_options) do |http|
|
|
28
|
+
request = Net::HTTP::Get.new(url)
|
|
29
|
+
http.request(request)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if response.is_a?(Net::HTTPRedirection) && redirects < max_redirects && (location = response['Location'])
|
|
33
|
+
url = URI(location.include?('://') ? location : (uri_origin(url) + location))
|
|
34
|
+
|
|
35
|
+
if visited_urls.include?(url)
|
|
36
|
+
return response
|
|
37
|
+
else
|
|
38
|
+
redirects += 1
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
return response
|
|
42
|
+
end
|
|
43
|
+
end
|
|
9
44
|
end
|
|
10
45
|
|
|
11
|
-
|
|
12
|
-
|
|
46
|
+
def get_data(url, options = {})
|
|
47
|
+
content_type = options.delete(:content_type)
|
|
48
|
+
response = get_response(url, options)
|
|
13
49
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
50
|
+
if response.is_a?(Net::HTTPSuccess) && content_type_matches(response, content_type) && (data = response.body)
|
|
51
|
+
data
|
|
52
|
+
else
|
|
53
|
+
raise APIError.new(response)
|
|
18
54
|
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def get_json(url, options = {})
|
|
58
|
+
JSON.parse(get_data(url, options))
|
|
59
|
+
end
|
|
19
60
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
61
|
+
def content_type_matches(response, expected_type)
|
|
62
|
+
content_type = response['Content-Type']
|
|
63
|
+
|
|
64
|
+
case expected_type
|
|
65
|
+
when String
|
|
66
|
+
content_type == expected_type
|
|
67
|
+
when Regexp
|
|
68
|
+
content_type =~ expected_type
|
|
69
|
+
when :json
|
|
70
|
+
content_type =~ /^application\/json(;.*)?$/
|
|
71
|
+
when nil
|
|
72
|
+
true
|
|
23
73
|
else
|
|
24
|
-
|
|
74
|
+
raise ArgumentError, "Invalid expected_type: #{expected_type.inspect}"
|
|
25
75
|
end
|
|
26
76
|
end
|
|
77
|
+
|
|
78
|
+
# backported from https://github.com/ruby/uri/pull/30/files for older Rubies
|
|
79
|
+
def uri_origin(uri)
|
|
80
|
+
uri = uri.is_a?(URI) ? uri : URI(uri)
|
|
81
|
+
authority = (uri.port == uri.default_port) ? uri.host : "#{uri.host}:#{uri.port}"
|
|
82
|
+
|
|
83
|
+
"#{uri.scheme}://#{authority}"
|
|
84
|
+
end
|
|
27
85
|
end
|
|
28
86
|
end
|
data/lib/didkit/resolver.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
require 'json'
|
|
2
|
-
require 'open-uri'
|
|
3
1
|
require 'net/http'
|
|
4
2
|
require 'resolv'
|
|
5
3
|
|
|
@@ -10,7 +8,6 @@ require_relative 'requests'
|
|
|
10
8
|
module DIDKit
|
|
11
9
|
class Resolver
|
|
12
10
|
RESERVED_DOMAINS = %w(alt arpa example internal invalid local localhost onion test)
|
|
13
|
-
MAX_REDIRECTS = 5
|
|
14
11
|
|
|
15
12
|
include Requests
|
|
16
13
|
|
|
@@ -18,9 +15,14 @@ module DIDKit
|
|
|
18
15
|
|
|
19
16
|
def initialize(options = {})
|
|
20
17
|
@nameserver = options[:nameserver]
|
|
18
|
+
@request_options = options.slice(:timeout, :max_redirects)
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
def resolve_handle(handle)
|
|
22
|
+
if handle.is_a?(DID) || handle =~ DID::GENERIC_REGEXP
|
|
23
|
+
return DID.new(handle)
|
|
24
|
+
end
|
|
25
|
+
|
|
24
26
|
domain = handle.gsub(/^@/, '')
|
|
25
27
|
|
|
26
28
|
return nil if RESERVED_DOMAINS.include?(domain.split('.').last)
|
|
@@ -50,7 +52,7 @@ module DIDKit
|
|
|
50
52
|
|
|
51
53
|
def resolve_handle_by_well_known(domain)
|
|
52
54
|
url = "https://#{domain}/.well-known/atproto-did"
|
|
53
|
-
response = get_response(url,
|
|
55
|
+
response = get_response(url, @request_options)
|
|
54
56
|
|
|
55
57
|
if response.is_a?(Net::HTTPSuccess) && (text = response.body)
|
|
56
58
|
return parse_did_from_well_known(text)
|
|
@@ -73,7 +75,7 @@ module DIDKit
|
|
|
73
75
|
|
|
74
76
|
def parse_did_from_well_known(text)
|
|
75
77
|
text = text.strip
|
|
76
|
-
text.lines.length == 1 && text =~
|
|
78
|
+
text.lines.length == 1 && text =~ DID::GENERIC_REGEXP ? text : nil
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
def resolve_did(did)
|
|
@@ -83,25 +85,23 @@ module DIDKit
|
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
def resolve_did_plc(did)
|
|
86
|
-
|
|
87
|
-
json = JSON.parse(URI.open(url).read)
|
|
88
|
+
json = get_json("https://plc.directory/#{did}", content_type: /^application\/did\+ld\+json(;.+)?$/)
|
|
88
89
|
Document.new(did, json)
|
|
89
90
|
end
|
|
90
91
|
|
|
91
92
|
def resolve_did_web(did)
|
|
92
|
-
|
|
93
|
-
json = JSON.parse(URI.open(url).read)
|
|
93
|
+
json = get_json("https://#{did.web_domain}/.well-known/did.json")
|
|
94
94
|
Document.new(did, json)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
-
def
|
|
98
|
-
document =
|
|
97
|
+
def get_verified_handle(subject)
|
|
98
|
+
document = subject.is_a?(Document) ? subject : resolve_did(subject)
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
first_verified_handle(document.did, document.handles)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
def
|
|
104
|
-
handles.detect { |h| resolve_handle(h) == did }
|
|
103
|
+
def first_verified_handle(did, handles)
|
|
104
|
+
handles.detect { |h| resolve_handle(h) == did.to_s }
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
end
|
data/lib/didkit/services.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
1
3
|
module DIDKit
|
|
2
4
|
module Services
|
|
3
5
|
def get_service(key, type)
|
|
@@ -11,5 +13,16 @@ module DIDKit
|
|
|
11
13
|
def labeler_endpoint
|
|
12
14
|
@labeler_endpoint ||= get_service('atproto_labeler', 'AtprotoLabeler')&.endpoint
|
|
13
15
|
end
|
|
16
|
+
|
|
17
|
+
def pds_host
|
|
18
|
+
pds_endpoint&.then { |x| URI(x).host }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def labeler_host
|
|
22
|
+
labeler_endpoint&.then { |x| URI(x).host }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
alias labeller_endpoint labeler_endpoint
|
|
26
|
+
alias labeller_host labeler_host
|
|
14
27
|
end
|
|
15
28
|
end
|
data/lib/didkit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: didkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kuba Suder
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
|
-
description:
|
|
14
12
|
email:
|
|
15
13
|
- jakub.suder@gmail.com
|
|
16
14
|
executables: []
|
|
@@ -33,14 +31,13 @@ files:
|
|
|
33
31
|
- lib/didkit/services.rb
|
|
34
32
|
- lib/didkit/version.rb
|
|
35
33
|
- sig/didkit.rbs
|
|
36
|
-
homepage: https://
|
|
34
|
+
homepage: https://ruby.sdk.blue
|
|
37
35
|
licenses:
|
|
38
36
|
- Zlib
|
|
39
37
|
metadata:
|
|
40
|
-
bug_tracker_uri: https://
|
|
41
|
-
changelog_uri: https://
|
|
42
|
-
source_code_uri: https://
|
|
43
|
-
post_install_message:
|
|
38
|
+
bug_tracker_uri: https://tangled.org/mackuba.eu/didkit/issues
|
|
39
|
+
changelog_uri: https://tangled.org/mackuba.eu/didkit/blob/master/CHANGELOG.md
|
|
40
|
+
source_code_uri: https://tangled.org/mackuba.eu/didkit
|
|
44
41
|
rdoc_options: []
|
|
45
42
|
require_paths:
|
|
46
43
|
- lib
|
|
@@ -55,8 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
55
52
|
- !ruby/object:Gem::Version
|
|
56
53
|
version: '0'
|
|
57
54
|
requirements: []
|
|
58
|
-
rubygems_version:
|
|
59
|
-
signing_key:
|
|
55
|
+
rubygems_version: 4.0.1
|
|
60
56
|
specification_version: 4
|
|
61
57
|
summary: A library for handling Distributed ID (DID) identifiers used in Bluesky AT
|
|
62
58
|
Protocol
|