didkit 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7744ae1dc9ba4ca678d641139ffa94931130ee7f91fa93d3ee89e3d6e196ba8d
4
- data.tar.gz: 4111e824027b646aa8b3a21fb3032dfdba26019b6fecaf3f40f2120dfa417e52
3
+ metadata.gz: e1dfc5eee5f96a374d8f67151f8d44f5eafffc8a8e3624d7fe507e783d5ff814
4
+ data.tar.gz: 74f0cd8da39a5e723b806e07560bd15325d2176c93ca746d619975e6b0510c77
5
5
  SHA512:
6
- metadata.gz: ed96e325c2d0e8152b1db1f6a46e34afdbec049e0442f2298a89a2bbd07afb96e3ca166fca40adf1ff90d0da02997b825addd20163eb858c6074771d9ea229ce
7
- data.tar.gz: 9237183a427ad4f5380504334abcfb16a965b0e95ce39693e9aa8d58d24c6acc6c00d7150bd86be18332e02da3cbd379eb516ccb60716a3b09bf11efc155c26c
6
+ metadata.gz: 976f7d9a63ad453dde3064872dafb558cbc80f96db0a61d5243abb68664b360a38ede6cfe5a702df7609b60c6d7c6e917ffc1a0479351bb89c7c693af5ffb3c7
7
+ data.tar.gz: da1e08939b8c19d62ceef10bd48d278b7190fffdbcad65385f9ca36edd9377d0ee37d1b1d78e8ae963304dd06863cdcf9fa992a504b3e219c1f01dbf60248f6a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.2.1] - 2024-03-26
2
+
3
+ - tweaked validations in `Document` and `PLCOperation` to make them more aligned with what might be expected
4
+ - added Ruby stdlib dependencies explicitly to the gemspec
5
+
6
+ ## [0.2.0] - 2024-03-19
7
+
8
+ - added `PLCImporter` class, which lets you import operations from PLC in pages of 1000 through the "export" API
9
+ - implemented parsing of all services from DID doc & operations, not only `atproto_pds` (specifically labeller endpoints)
10
+ - allow setting the nameserver in `Resolver` initializer
11
+
1
12
  ## [0.1.0] - 2024-03-12
2
13
 
3
14
  - rejecting handles from disallowed domains like `.arpa` or `.test`
@@ -0,0 +1,13 @@
1
+ module DIDKit
2
+ module AtHandles
3
+ class FormatError < StandardError
4
+ end
5
+
6
+ def parse_also_known_as(aka)
7
+ raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.is_a?(Array)
8
+ raise FormatError, "Invalid alsoKnownAs: #{aka.inspect}" unless aka.all? { |x| x.is_a?(String) }
9
+
10
+ aka.select { |x| x =~ %r(\Aat://[^/]+\z) }.map { |x| x.gsub('at://', '') }
11
+ end
12
+ end
13
+ end
@@ -1,11 +1,17 @@
1
+ require_relative 'at_handles'
1
2
  require_relative 'resolver'
3
+ require_relative 'service_record'
4
+ require_relative 'services'
2
5
 
3
6
  module DIDKit
4
7
  class Document
5
8
  class FormatError < StandardError
6
9
  end
7
10
 
8
- attr_reader :json, :did, :pds_endpoint, :handles
11
+ include AtHandles
12
+ include Services
13
+
14
+ attr_reader :json, :did, :handles, :services
9
15
 
10
16
  def initialize(did, json)
11
17
  raise FormatError, "Missing id field" if json['id'].nil?
@@ -15,29 +21,20 @@ module DIDKit
15
21
  @did = did
16
22
  @json = json
17
23
 
18
- service = json['service']
19
- raise FormatError, "Missing service key" if service.nil?
20
- raise FormatError, "Invalid service data" unless service.is_a?(Array) && service.all? { |x| x.is_a?(Hash) }
21
-
22
- if pds = service.detect { |x| x['id'] == '#atproto_pds' }
23
- raise FormatError, "Missing PDS type" unless pds['type']
24
- raise FormatError, "Invalid PDS type" unless pds['type'] == 'AtprotoPersonalDataServer'
25
- raise FormatError, "Missing PDS endpoint" unless pds['serviceEndpoint']
26
- raise FormatError, "Invalid PDS endpoint" unless pds['serviceEndpoint'].is_a?(String)
27
- raise FormatError, "Invalid PDS endpoint" unless pds['serviceEndpoint'] =~ %r(://)
24
+ if service = json['service']
25
+ raise FormatError, "Invalid service data" unless service.is_a?(Array) && service.all? { |x| x.is_a?(Hash) }
28
26
 
29
- @pds_endpoint = pds['serviceEndpoint']
30
- end
27
+ @services = service.filter_map { |x|
28
+ id, type, endpoint = x.values_at('id', 'type', 'serviceEndpoint')
29
+ next unless id.is_a?(String) && id.start_with?('#') && type.is_a?(String) && endpoint.is_a?(String)
31
30
 
32
- if aka = json['alsoKnownAs']
33
- raise FormatError, "Invalid alsoKnownAs" unless aka.is_a?(Array)
34
- raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x.is_a?(String) }
35
- raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x =~ %r(\Aat://[^/]+\z) }
36
-
37
- @handles = aka.map { |x| x.gsub('at://', '') }
31
+ ServiceRecord.new(id.gsub(/^#/, ''), type, endpoint)
32
+ }
38
33
  else
39
- @handles = []
34
+ @services = []
40
35
  end
36
+
37
+ @handles = parse_also_known_as(json['alsoKnownAs'] || [])
41
38
  end
42
39
 
43
40
  def get_validated_handle
@@ -1,5 +1,4 @@
1
1
  require 'json'
2
- require 'net/http'
3
2
  require 'open-uri'
4
3
  require 'time'
5
4
 
@@ -8,21 +7,42 @@ require_relative 'plc_operation'
8
7
  module DIDKit
9
8
  class PLCImporter
10
9
  PLC_SERVICE = 'plc.directory'
10
+ MAX_PAGE = 1000
11
11
 
12
- attr_accessor :ignore_errors
12
+ attr_accessor :ignore_errors, :last_date
13
+
14
+ def initialize(since: nil)
15
+ if since.to_s == 'beginning'
16
+ @last_date = nil
17
+ elsif since.is_a?(String)
18
+ @last_date = Time.parse(since)
19
+ elsif since
20
+ @last_date = since
21
+ else
22
+ @last_date = Time.now
23
+ @eof = true
24
+ end
13
25
 
14
- def initialize(last_date = Time.now)
15
- @last_date = last_date
16
26
  @ignore_errors = false
17
27
  end
18
28
 
19
- def fetch
20
- url = URI("https://#{PLC_SERVICE}/export")
21
- url.query = URI.encode_www_form(:after => @last_date.utc.iso8601(6)) if @last_date
22
- request_time = Time.now
29
+ def plc_service
30
+ PLC_SERVICE
31
+ end
32
+
33
+ def get_export(args = {})
34
+ url = URI("https://#{plc_service}/export")
35
+ url.query = URI.encode_www_form(args)
23
36
 
24
37
  data = URI.open(url).read
25
- rows = data.lines.map(&:strip).reject(&:empty?).map { |x| JSON.parse(x) }
38
+ data.lines.map(&:strip).reject(&:empty?).map { |x| JSON.parse(x) }
39
+ end
40
+
41
+ def fetch_page
42
+ request_time = Time.now
43
+
44
+ query = @last_date ? { :after => @last_date.utc.iso8601(6) } : {}
45
+ rows = get_export(query)
26
46
 
27
47
  operations = rows.filter_map do |json|
28
48
  begin
@@ -32,14 +52,22 @@ module DIDKit
32
52
  end
33
53
  end
34
54
 
35
- block.call(operations)
55
+ @last_date = operations.last&.created_at || request_time
56
+ @eof = (rows.length < MAX_PAGE)
36
57
 
37
- if rows.length == 1000
38
- @last_date = operations.last.created_at || request_time
39
- run_update
40
- else
41
- @last_date = request_time
58
+ operations
59
+ end
60
+
61
+ def fetch(&block)
62
+ loop do
63
+ operations = fetch_page
64
+ block.call(operations)
65
+ break if eof?
42
66
  end
43
67
  end
68
+
69
+ def eof?
70
+ !!@eof
71
+ end
44
72
  end
45
73
  end
@@ -1,56 +1,57 @@
1
1
  require 'time'
2
2
 
3
+ require_relative 'at_handles'
4
+ require_relative 'service_record'
5
+ require_relative 'services'
6
+
3
7
  module DIDKit
4
8
  class PLCOperation
5
9
  class FormatError < StandardError
6
10
  end
7
11
 
8
- attr_reader :did, :created_at, :type, :pds_endpoint, :handles
12
+ include AtHandles
13
+ include Services
14
+
15
+ attr_reader :json, :did, :created_at, :type, :handles, :services
9
16
 
10
17
  def initialize(json)
18
+ @json = json
11
19
  @did = json['did']
12
- raise FormatError, "Missing DID" if @did.nil?
13
- raise FormatError, "Invalid DID" unless @did.is_a?(String) && @did.start_with?('did:')
20
+ raise FormatError, "Missing DID: #{json}" if @did.nil?
21
+ raise FormatError, "Invalid DID: #{@did}" unless @did.is_a?(String) && @did.start_with?('did:')
14
22
 
15
23
  timestamp = json['createdAt']
16
- raise FormatError, "Missing createdAt" if timestamp.nil?
17
- raise FormatError, "Invalid createdAt" unless timestamp.is_a?(String)
24
+ raise FormatError, "Missing createdAt: #{json}" if timestamp.nil?
25
+ raise FormatError, "Invalid createdAt: #{timestamp.inspect}" unless timestamp.is_a?(String)
18
26
 
19
27
  @created_at = Time.parse(timestamp)
20
28
 
21
29
  operation = json['operation']
22
- raise FormatError, "Missing operation key" if operation.nil?
23
- raise FormatError, "Invalid operation data" unless operation.is_a?(Hash)
30
+ raise FormatError, "Missing operation key: #{json}" if operation.nil?
31
+ raise FormatError, "Invalid operation data: #{operation.inspect}" unless operation.is_a?(Hash)
24
32
 
25
33
  type = operation['type']
26
- raise FormatError, "Missing type" if type.nil?
34
+ raise FormatError, "Missing operation type: #{json}" if type.nil?
27
35
 
28
36
  @type = type.to_sym
29
37
  return unless @type == :plc_operation
30
38
 
31
39
  services = operation['services']
32
- raise FormatError, "Missing services key" if services.nil?
33
- raise FormatError, "Invalid services data" unless services.is_a?(Hash)
34
-
35
- if pds = services['atproto_pds']
36
- raise FormatError, "Invalid PDS data" unless pds.is_a?(Hash)
37
- raise FormatError, "Missing PDS type" unless pds['type']
38
- raise FormatError, "Invalid PDS type" unless pds['type'] == 'AtprotoPersonalDataServer'
39
- raise FormatError, "Missing PDS endpoint" unless pds['endpoint']
40
- raise FormatError, "Invalid PDS endpoint" unless pds['endpoint'].is_a?(String) && pds['endpoint'] =~ %r(://)
41
-
42
- @pds_endpoint = pds['endpoint']
43
- end
44
-
45
- if aka = operation['alsoKnownAs']
46
- raise FormatError, "Invalid alsoKnownAs" unless aka.is_a?(Array)
47
- raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x.is_a?(String) }
48
- raise FormatError, "Invalid alsoKnownAs" unless aka.all? { |x| x =~ %r(\Aat://[^/]+\z) }
49
-
50
- @handles = aka.map { |x| x.gsub('at://', '') }
51
- else
52
- @handles = []
53
- end
40
+ raise FormatError, "Missing services key: #{json}" if services.nil?
41
+ raise FormatError, "Invalid services data: #{services}" unless services.is_a?(Hash)
42
+
43
+ @services = services.map { |k, x|
44
+ type, endpoint = x.values_at('type', 'endpoint')
45
+
46
+ raise FormatError, "Missing service type" unless type
47
+ raise FormatError, "Invalid service type: #{type.inspect}" unless type.is_a?(String)
48
+ raise FormatError, "Missing service endpoint" unless endpoint
49
+ raise FormatError, "Invalid service endpoint: #{endpoint.inspect}" unless endpoint.is_a?(String)
50
+
51
+ ServiceRecord.new(k, type, endpoint)
52
+ }
53
+
54
+ @handles = parse_also_known_as(operation['alsoKnownAs'])
54
55
  end
55
56
  end
56
57
  end
@@ -13,6 +13,10 @@ module DIDKit
13
13
 
14
14
  attr_accessor :nameserver
15
15
 
16
+ def initialize(options = {})
17
+ @nameserver = options[:nameserver]
18
+ end
19
+
16
20
  def resolve_handle(handle)
17
21
  domain = handle.gsub(/^@/, '')
18
22
 
@@ -0,0 +1,20 @@
1
+ require 'uri'
2
+ require_relative 'errors'
3
+
4
+ module DIDKit
5
+ class ServiceRecord
6
+ attr_reader :key, :type, :endpoint
7
+
8
+ def initialize(key, type, endpoint)
9
+ begin
10
+ uri = URI(endpoint)
11
+ rescue URI::Error
12
+ raise FormatError, "Invalid service endpoint: #{endpoint.inspect}"
13
+ end
14
+
15
+ @key = key
16
+ @type = type
17
+ @endpoint = endpoint
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module DIDKit
2
+ module Services
3
+ def get_service(key, type)
4
+ @services&.detect { |s| s.key == key && s.type == type }
5
+ end
6
+
7
+ def pds_endpoint
8
+ @pds_endpoint ||= get_service('atproto_pds', 'AtprotoPersonalDataServer')&.endpoint
9
+ end
10
+
11
+ def labeler_endpoint
12
+ @labeler_endpoint ||= get_service('atproto_labeler', 'AtprotoLabeler')&.endpoint
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DIDKit
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/didkit.rb CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  require_relative "didkit/did"
4
4
  require_relative "didkit/document"
5
+ require_relative "didkit/plc_importer"
6
+ require_relative "didkit/plc_operation"
7
+ require_relative "didkit/resolver"
5
8
  require_relative "didkit/version"
6
9
 
7
10
  module DIDKit
metadata CHANGED
@@ -1,15 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: didkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuba Suder
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-12 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-03-26 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'
13
97
  description:
14
98
  email:
15
99
  - jakub.suder@gmail.com
@@ -21,12 +105,15 @@ files:
21
105
  - LICENSE.txt
22
106
  - README.md
23
107
  - lib/didkit.rb
108
+ - lib/didkit/at_handles.rb
24
109
  - lib/didkit/did.rb
25
110
  - lib/didkit/document.rb
26
111
  - lib/didkit/errors.rb
27
112
  - lib/didkit/plc_importer.rb
28
113
  - lib/didkit/plc_operation.rb
29
114
  - lib/didkit/resolver.rb
115
+ - lib/didkit/service_record.rb
116
+ - lib/didkit/services.rb
30
117
  - lib/didkit/version.rb
31
118
  - sig/didkit.rbs
32
119
  homepage: https://github.com/mackuba/didkit