didkit 0.2.2 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd7709e2b0973a44b71e939798901a6fd74bc9fa25e284958948fc4fe22de51e
4
- data.tar.gz: b38397ddc0244f9035719dba7eb0fdcd844feb549e9628462da16b4aba662cf4
3
+ metadata.gz: 7f394023d5e7e3f864041dc39e81d1e2730972b6bfe8f3c0a0d0178e27e6557e
4
+ data.tar.gz: e622af3eea9c05a7831c7b4630e16e0a879faede2a38b6605bb622d0ebb2eaf6
5
5
  SHA512:
6
- metadata.gz: 2d5ddb2ce60022530ffd59879d5734d4300a091eb78db13028ae10a55d8ee83981ba6d20a7c933c2b6249245f40adf7458d0687f7349fa042915445e260b2e3a
7
- data.tar.gz: 9a8c796fee2372323a405ec8962671438cbd7263b1265f31eb45a324dfcab7e33557dbf74b1f4b87fed0492467965ef7222569ee7fad2511efb41b7b52d33ed9
6
+ metadata.gz: 1dd40553f1f96d93bb746d1cfc628d3f4a2a34df99634a88775c3673f4ef721c2e13f85110e7a3a0b2880f29ffcbe7da21838378cdd04a3ea346a7ab2ccb2bfc
7
+ data.tar.gz: f32fc83842dcd9a1bf88817aa8a286f5a1bdf42004416f74ec2450104388426456c2d16b598ba4439d9cb6c13f5035d800df2abada369656ac0c883797f05d1d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,33 @@
1
+ ## [0.3.0] - 2025-12-15
2
+
3
+ Breaking changes:
4
+
5
+ * removed `DID#is_known_by_relay?` – it doesn't work anymore, since relays are now non-archival and they expose almost no XRPC routes
6
+ * renamed a few handle-related methods:
7
+ - `get_validated_handle` -> `get_verified_handle`
8
+ - `pick_valid_handle` -> `first_verified_handle`
9
+
10
+ Also:
11
+
12
+ - added `DID#account_status` method, which checks `getRepoStatus` endpoint to tell if an account is active, deactivated, taken down etc.
13
+ - added `DID#account_active?` helper (`account_status == :active`)
14
+ - `DID#account_exists?` now calls `getRepoStatus` (via `account_status`, checking if it's not nil) instead of `getLatestCommit`
15
+ - added `DID#document` which keeps a memoized copy of the document
16
+ - added `pds_host` & `labeler_host` methods to `PLCOperation` and `Document`, which return the PDS/labeller address without the `https://`
17
+ - added `labeller_endpoint` & `labeller_host` aliases for the double-L enjoyers :]
18
+ - added `PLCOperation#cid`
19
+ - `PLCImporter` now removes duplicate operations at the edge of pages returned from the `/export` API
20
+ - rewritten some networking code – all classes now use `Net::HTTP` with consistent options instead of `open-uri`
21
+
22
+ 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.
23
+
24
+ ## [0.2.3] - 2024-07-02
25
+
26
+ - added a `DID#get_audit_log` method that fetches the PLC audit log for a DID
27
+ - added a way to set an error handler in `PLCImporter`
28
+ - reverted the change from 0.2.1 that added Ruby stdlib dependencies explicitly to the gemspec, since this causes more problems than it's worth
29
+ - minor bug fixes
30
+
1
31
  ## [0.2.2] - 2024-04-01
2
32
 
3
33
  - added helpers for checking if a DID is known by (federated with) a relay or if the repo exists on its assigned PDS
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The zlib License
2
2
 
3
- Copyright (c) 2023 Jakub Suder
3
+ Copyright (c) 2025 Jakub Suder
4
4
 
5
5
  This software is provided 'as-is', without any express or implied
6
6
  warranty. In no event will the authors be held liable for any damages
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
- # DIDKit
1
+ # DIDKit 🪪
2
2
 
3
- A small Ruby gem for handling Distributed Identifiers (DIDs) in Bluesky / AT Protocol
3
+ A small Ruby gem for handling Distributed Identifiers (DIDs) in Bluesky / AT Protocol.
4
+
5
+ > [!NOTE]
6
+ > Part of ATProto Ruby SDK: [ruby.sdk.blue](https://ruby.sdk.blue)
4
7
 
5
8
 
6
9
  ## What does it do
@@ -10,75 +13,99 @@ Accounts on Bluesky use identifiers like [did:plc:oio4hkxaop4ao4wz2pp3f4cr](http
10
13
 
11
14
  ## Installation
12
15
 
16
+ From the command line:
17
+
13
18
  gem install didkit
14
19
 
20
+ Or, add this to your `Gemfile`:
15
21
 
16
- ## Usage
22
+ gem 'didkit', '~> 0.3'
17
23
 
18
- Use the `DIDKit::Resolver` class to look up DIDs and handles.
19
24
 
20
- To look up a handle:
25
+ ## Usage
26
+
27
+ The simplest way to use the gem is through the `DIDKit::DID` class, aliased as just `DID`:
21
28
 
22
29
  ```rb
23
- resolver = DIDKit::Resolver.new
24
- resolver.resolve_handle('nytimes.com')
25
- # => #<DIDKit::DID:0x00000001035956b0 @did="did:plc:eclio37ymobqex2ncko63h4r", @type=:plc, @resolved_by=:dns>
30
+ did = DID.resolve_handle('jay.bsky.team')
31
+ # => #<DIDKit::DID:0x0... @did="did:plc:oky5czdrnfjpqslsw2a5iclo",
32
+ # @resolved_by=:dns, @type=:plc>
26
33
  ```
27
34
 
28
- This returns an object of `DIDKit::DID` class (aliased as just `DID`), which tells you:
35
+ This returns a `DID` object, which tells you:
29
36
 
30
37
  - the DID as a string (`#to_s` or `#did`)
31
38
  - the DID type (`#type`, `:plc` or `:web`)
32
39
  - if the handle was resolved via a DNS entry or a `.well-known` file (`#resolved_by`, `:dns` or `:http`)
33
40
 
34
- To go in the other direction – to find an assigned and verified handle given a DID – use `get_validated_handle` (pass DID as a string or an object):
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`:
35
42
 
36
43
  ```rb
37
- resolver.get_validated_handle('did:plc:ewvi7nxzyoun6zhxrhs64oiz')
38
- # => "atproto.com"
44
+ DID.new('did:plc:ewvi7nxzyoun6zhxrhs64oiz').get_verified_handle
45
+ # => "atproto.com"
39
46
  ```
40
47
 
41
- You can also load the DID document using `resolve_did`:
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):
42
49
 
43
50
  ```rb
44
- doc = resolver.resolve_did('did:plc:ragtjsm2j2vknwkz3zp4oxrd')
45
- # => #<DIDKit::Document:0x0000000105d751f8 @did=#<DIDKit::DID:...>, @json={...}>
51
+ did = DID.new('did:plc:ragtjsm2j2vknwkz3zp4oxrd')
46
52
 
47
- doc.handles
48
- # => ["pfrazee.com"]
53
+ did.document.handles
54
+ # => ["pfrazee.com"]
49
55
 
50
- doc.pds_endpoint
51
- # => "https://morel.us-east.host.bsky.network"
56
+ did.document.pds_host
57
+ # => "morel.us-east.host.bsky.network"
52
58
  ```
53
59
 
54
- There are also some helper methods in the `DID` class that create a `Resolver` for you to save you some typing:
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:
55
64
 
56
65
  ```rb
57
- did = DID.resolve_handle('jay.bsky.team')
58
- # => #<DIDKit::DID:0x000000010615ed28 @did="did:plc:oky5czdrnfjpqslsw2a5iclo", @type=:plc, @resolved_by=:dns>
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
+ ```
59
80
 
60
- did.to_s
61
- # => "did:plc:oky5czdrnfjpqslsw2a5iclo"
81
+ ### Configuration
62
82
 
63
- did.get_document
64
- # => #<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.
65
84
 
66
- did.get_validated_handle
67
- # => "jay.bsky.team"
68
- ```
85
+ Currently available options include:
69
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)
70
90
 
71
- ### Configuration
91
+ Example:
72
92
 
73
- You can override the nameserver used for DNS lookups by setting the `nameserver` property in `Resolver`, e.g. to use Google's or CloudFlare's global DNS:
93
+ ```rb
94
+ resolver = DIDKit::Resolver.new(nameserver: '8.8.8.8', timeout: 30)
74
95
 
75
- ```
76
- resolver.nameserver = '8.8.8.8'
77
- ```
96
+ did = resolver.resolve_handle('nytimes.com')
97
+ # => #<DIDKit::DID:0x0... @did="did:plc:eclio37ymobqex2ncko63h4r",
98
+ # @resolved_by=:dns, @type=:plc>
78
99
 
100
+ resolver.resolve_did(did)
101
+ # => #<DIDKit::Document:0x0... @did=#<DIDKit::DID:...>, @json={...}>
102
+
103
+ resolver.get_verified_handle(did)
104
+ # => 'nytimes.com'
105
+ ```
79
106
 
80
107
  ## Credits
81
108
 
82
- Copyright © 2024 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/mackuba.eu)).
109
+ Copyright © 2025 Kuba Suder ([@mackuba.eu](https://bsky.app/profile/did:plc:oio4hkxaop4ao4wz2pp3f4cr)).
83
110
 
84
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,3 +1,6 @@
1
+ require 'json'
2
+ require 'uri'
3
+
1
4
  require_relative 'errors'
2
5
  require_relative 'requests'
3
6
  require_relative 'resolver'
@@ -29,44 +32,65 @@ module DIDKit
29
32
 
30
33
  alias to_s did
31
34
 
35
+ def document
36
+ @document ||= get_document
37
+ end
38
+
32
39
  def get_document
33
40
  Resolver.new.resolve_did(self)
34
41
  end
35
42
 
36
- def get_validated_handle
37
- Resolver.new.get_validated_handle(self)
43
+ def get_verified_handle
44
+ Resolver.new.get_verified_handle(document)
45
+ end
46
+
47
+ def get_audit_log
48
+ if @type == :plc
49
+ PLCImporter.new.fetch_audit_log(self)
50
+ else
51
+ raise DIDError.new("Audit log not supported for did:#{@type}")
52
+ end
38
53
  end
39
54
 
40
55
  def web_domain
41
56
  did.gsub(/^did\:web\:/, '') if type == :web
42
57
  end
43
58
 
44
- def is_known_by_relay?(relay, options = {})
45
- relay_host = relay.include?('://') ? URI(relay).origin : "https://#{relay}"
46
- url = URI("#{relay_host}/xrpc/com.atproto.sync.getLatestCommit")
47
- url.query = URI.encode_www_form(:did => did)
59
+ def account_status(request_options = {})
60
+ doc = self.document
61
+ return nil if doc.pds_endpoint.nil?
62
+
63
+ pds_host = URI(doc.pds_endpoint).origin
64
+ url = URI("#{pds_host}/xrpc/com.atproto.sync.getRepoStatus")
65
+ url.query = URI.encode_www_form(:did => @did)
48
66
 
49
- response = get_response(url, { timeout: 30, max_redirects: 5 }.merge(options))
67
+ response = get_response(url, request_options)
50
68
  status = response.code.to_i
51
69
  is_json = (response['Content-Type'] =~ /^application\/json(;.*)?$/)
52
70
 
53
- if status == 200
54
- true
71
+ if status == 200 && is_json
72
+ json = JSON.parse(response.body)
73
+
74
+ if json['active'] == true
75
+ :active
76
+ elsif json['active'] == false && json['status'].is_a?(String) && json['status'].length <= 100
77
+ json['status'].to_sym
78
+ else
79
+ raise APIError.new(response)
80
+ end
55
81
  elsif status == 400 && is_json && JSON.parse(response.body)['error'] == 'RepoNotFound'
56
- false
57
- elsif status == 404 && is_json && JSON.parse(response.body)['error']
58
- false
82
+ nil
59
83
  else
60
84
  raise APIError.new(response)
61
85
  end
62
86
  end
63
87
 
64
- def account_exists?
65
- doc = get_document
66
- return false if doc.pds_endpoint.nil?
88
+ def account_active?
89
+ account_status == :active
90
+ end
67
91
 
68
- pds_host = URI(doc.pds_endpoint).origin
69
- is_known_by_relay?(pds_host, timeout: 10)
92
+ def account_exists?
93
+ account_status != nil
70
94
  end
71
95
 
72
96
  def ==(other)
@@ -37,8 +37,8 @@ module DIDKit
37
37
  @handles = parse_also_known_as(json['alsoKnownAs'] || [])
38
38
  end
39
39
 
40
- def get_validated_handle
41
- Resolver.new.pick_valid_handle(did, handles)
40
+ def get_verified_handle
41
+ Resolver.new.get_verified_handle(self)
42
42
  end
43
43
  end
44
44
  end
@@ -1,15 +1,23 @@
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
 
12
- attr_accessor :ignore_errors, :last_date
18
+ include Requests
19
+
20
+ attr_accessor :ignore_errors, :last_date, :error_handler
13
21
 
14
22
  def initialize(since: nil)
15
23
  if since.to_s == 'beginning'
@@ -23,36 +31,58 @@ module DIDKit
23
31
  @eof = true
24
32
  end
25
33
 
26
- @ignore_errors = false
34
+ @last_page_cids = []
27
35
  end
28
36
 
29
37
  def plc_service
30
38
  PLC_SERVICE
31
39
  end
32
40
 
41
+ def ignore_errors=(val)
42
+ @ignore_errors = val
43
+
44
+ if val
45
+ @error_handler = proc { |e, j| "(ignore error)" }
46
+ else
47
+ @error_handler = nil
48
+ end
49
+ end
50
+
33
51
  def get_export(args = {})
34
52
  url = URI("https://#{plc_service}/export")
35
53
  url.query = URI.encode_www_form(args)
36
54
 
37
- data = URI.open(url).read
55
+ data = get_data(url, content_type: 'application/jsonlines')
38
56
  data.lines.map(&:strip).reject(&:empty?).map { |x| JSON.parse(x) }
39
57
  end
40
58
 
59
+ def fetch_audit_log(did)
60
+ json = get_json("https://#{plc_service}/#{did}/log/audit", :content_type => :json)
61
+ json.map { |j| PLCOperation.new(j) }
62
+ end
63
+
41
64
  def fetch_page
42
65
  request_time = Time.now
43
66
 
44
67
  query = @last_date ? { :after => @last_date.utc.iso8601(6) } : {}
45
68
  rows = get_export(query)
46
69
 
47
- operations = rows.filter_map do |json|
70
+ operations = rows.filter_map { |json|
48
71
  begin
49
72
  PLCOperation.new(json)
50
- rescue PLCOperation::FormatError => e
51
- ignore_errors ? nil : raise
73
+ rescue PLCOperation::FormatError, AtHandles::FormatError, ServiceRecord::FormatError => e
74
+ @error_handler ? @error_handler.call(e, json) : raise
75
+ nil
52
76
  end
53
- end
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
+ }
54
83
 
55
84
  @last_date = operations.last&.created_at || request_time
85
+ @last_page_cids = Set.new(operations.map(&:cid))
56
86
  @eof = (rows.length < MAX_PAGE)
57
87
 
58
88
  operations
@@ -12,7 +12,7 @@ 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, :created_at, :type, :handles, :services
16
16
 
17
17
  def initialize(json)
18
18
  @json = json
@@ -20,6 +20,10 @@ module DIDKit
20
20
  raise FormatError, "Missing DID: #{json}" if @did.nil?
21
21
  raise FormatError, "Invalid DID: #{@did}" unless @did.is_a?(String) && @did.start_with?('did:')
22
22
 
23
+ @cid = json['cid']
24
+ raise FormatError, "Missing CID: #{json}" if @cid.nil?
25
+ raise FormatError, "Invalid CID: #{@cid}" unless @cid.is_a?(String)
26
+
23
27
  timestamp = json['createdAt']
24
28
  raise FormatError, "Missing createdAt: #{json}" if timestamp.nil?
25
29
  raise FormatError, "Invalid createdAt: #{timestamp.inspect}" unless timestamp.is_a?(String)
@@ -1,27 +1,77 @@
1
- module DIDKit::Requests
2
- def get_response(url, options = {})
3
- url = URI(url) unless url.is_a?(URI)
4
- request_options = { use_ssl: true }
5
-
6
- if timeout = options[:timeout]
7
- request_options[:open_timeout] = timeout
8
- request_options[:read_timeout] = timeout
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 : (url.origin + 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
- redirects = 0
12
- max_redirects = options[:max_redirects] || 0
46
+ def get_data(url, options = {})
47
+ content_type = options.delete(:content_type)
48
+ response = get_response(url, options)
13
49
 
14
- loop do
15
- response = Net::HTTP.start(url.host, url.port, request_options) do |http|
16
- request = Net::HTTP::Get.new(url)
17
- http.request(request)
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
60
+
61
+ def content_type_matches(response, expected_type)
62
+ content_type = response['Content-Type']
19
63
 
20
- if response.is_a?(Net::HTTPRedirection) && redirects < max_redirects && (location = response['Location'])
21
- url = URI(location.include?('://') ? location : (url.origin + location))
22
- redirects += 1
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
- return response
74
+ raise ArgumentError, "Invalid expected_type: #{expected_type.inspect}"
25
75
  end
26
76
  end
27
77
  end
@@ -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,6 +15,7 @@ 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)
@@ -50,7 +48,7 @@ module DIDKit
50
48
 
51
49
  def resolve_handle_by_well_known(domain)
52
50
  url = "https://#{domain}/.well-known/atproto-did"
53
- response = get_response(url, timeout: 10, max_redirects: MAX_REDIRECTS)
51
+ response = get_response(url, @request_options)
54
52
 
55
53
  if response.is_a?(Net::HTTPSuccess) && (text = response.body)
56
54
  return parse_did_from_well_known(text)
@@ -83,25 +81,23 @@ module DIDKit
83
81
  end
84
82
 
85
83
  def resolve_did_plc(did)
86
- url = "https://plc.directory/#{did}"
87
- json = JSON.parse(URI.open(url).read)
84
+ json = get_json("https://plc.directory/#{did}", content_type: /^application\/did\+ld\+json(;.+)?$/)
88
85
  Document.new(did, json)
89
86
  end
90
87
 
91
88
  def resolve_did_web(did)
92
- url = "https://#{did.web_domain}/.well-known/did.json"
93
- json = JSON.parse(URI.open(url).read)
89
+ json = get_json("https://#{did.web_domain}/.well-known/did.json")
94
90
  Document.new(did, json)
95
91
  end
96
92
 
97
- def get_validated_handle(did_or_doc)
98
- document = did_or_doc.is_a?(Document) ? did_or_doc : resolve_did(did_or_doc)
93
+ def get_verified_handle(subject)
94
+ document = subject.is_a?(Document) ? subject : resolve_did(subject)
99
95
 
100
- pick_valid_handle(document.did, document.handles)
96
+ first_verified_handle(document.did, document.handles)
101
97
  end
102
98
 
103
- def pick_valid_handle(did, handles)
104
- handles.detect { |h| resolve_handle(h) == did }
99
+ def first_verified_handle(did, handles)
100
+ handles.detect { |h| resolve_handle(h) == did.to_s }
105
101
  end
106
102
  end
107
103
  end
@@ -3,6 +3,9 @@ require_relative 'errors'
3
3
 
4
4
  module DIDKit
5
5
  class ServiceRecord
6
+ class FormatError < StandardError
7
+ end
8
+
6
9
  attr_reader :key, :type, :endpoint
7
10
 
8
11
  def initialize(key, type, endpoint)
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DIDKit
4
- VERSION = "0.2.2"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,100 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: didkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-03-31 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: json
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.5'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.5'
27
- - !ruby/object:Gem::Dependency
28
- name: net-http
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '0.1'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '0.1'
41
- - !ruby/object:Gem::Dependency
42
- name: open-uri
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '0.1'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '0.1'
55
- - !ruby/object:Gem::Dependency
56
- name: resolv
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '0.1'
62
- type: :runtime
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '0.1'
69
- - !ruby/object:Gem::Dependency
70
- name: time
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '0.3'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '0.3'
83
- - !ruby/object:Gem::Dependency
84
- name: uri
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '0.13'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '0.13'
97
- description:
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
98
12
  email:
99
13
  - jakub.suder@gmail.com
100
14
  executables: []
@@ -124,7 +38,6 @@ metadata:
124
38
  bug_tracker_uri: https://github.com/mackuba/didkit/issues
125
39
  changelog_uri: https://github.com/mackuba/didkit/blob/master/CHANGELOG.md
126
40
  source_code_uri: https://github.com/mackuba/didkit
127
- post_install_message:
128
41
  rdoc_options: []
129
42
  require_paths:
130
43
  - lib
@@ -139,8 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
52
  - !ruby/object:Gem::Version
140
53
  version: '0'
141
54
  requirements: []
142
- rubygems_version: 3.4.10
143
- signing_key:
55
+ rubygems_version: 3.6.9
144
56
  specification_version: 4
145
57
  summary: A library for handling Distributed ID (DID) identifiers used in Bluesky AT
146
58
  Protocol