Class: DIDKit::DID

Inherits:
Object
  • Object
show all
Includes:
Requests
Defined in:
lib/didkit/did.rb

Overview

Represents a DID identifier (account on the ATProto network). This class serves as an entry point to various lookup helpers. For convenience it can also be accessed as just DID without the DIDKit:: prefix.

Examples:

Resolving a handle

did = DID.resolve_handle('bsky.app')

Constant Summary collapse

GENERIC_REGEXP =
/\Adid\:\w+\:.+\z/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(did, resolved_by = nil) ⇒ DID

Create a DID object from a DID string.

Parameters:

  • did (String, DID)

    DID string or another DID object

  • resolved_by (Symbol, nil) (defaults to: nil)

    optionally, how the DID was looked up (:dns or :http)

Raises:

  • (DIDError)

    when the DID format or type is invalid



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/didkit/did.rb', line 58

def initialize(did, resolved_by = nil)
  if did.is_a?(DID)
    did = did.to_s
  end

  if did =~ GENERIC_REGEXP
    @did = did
    @type = did.split(':')[1].to_sym
  else
    raise DIDError.new("Invalid DID format")
  end

  if @type != :plc && @type != :web
    raise DIDError.new("Unrecognized DID type: #{@type}")
  end

  @resolved_by = resolved_by
end

Instance Attribute Details

#didString (readonly) Also known as: to_s

Returns DID identifier string.

Returns:

  • (String)

    DID identifier string



44
45
46
# File 'lib/didkit/did.rb', line 44

def did
  @did
end

#resolved_bySymbol? (readonly)

Returns :dns or :http if the DID was looked up using one of those methods.

Returns:

  • (Symbol, nil)

    :dns or :http if the DID was looked up using one of those methods



47
48
49
# File 'lib/didkit/did.rb', line 47

def resolved_by
  @resolved_by
end

#typeSymbol (readonly)

Returns DID type (:plc or :web).

Returns:

  • (Symbol)

    DID type (:plc or :web)



41
42
43
# File 'lib/didkit/did.rb', line 41

def type
  @type
end

Class Method Details

.resolve_handle(handle) ⇒ DID?

Resolve a handle into a DID. Looks up the given ATProto domain handle using the DNS TXT method and the HTTP .well-known method and returns a DID if one is assigned using either of the methods.

If a DID string or a DIDKit::DID object is passed, it simply returns that DID, so you can use this method to pass it an input string from the user which can be a DID or handle, without having to check which one it is.

Parameters:

  • handle (String, DID)

    a domain handle (may start with an ‘@`) or a DID string

Returns:

  • (DID, nil)

    resolved DID if found, nil otherwise



36
37
38
# File 'lib/didkit/did.rb', line 36

def self.resolve_handle(handle)
  Resolver.new.resolve_handle(handle)
end

Instance Method Details

#==(other) ⇒ Boolean

Compares the DID to another DID object or string.

Parameters:

  • other (DID, String)

    other DID to compare with

Returns:

  • (Boolean)

    true if it’s the same DID



189
190
191
192
193
194
195
196
197
# File 'lib/didkit/did.rb', line 189

def ==(other)
  if other.is_a?(String)
    self.did == other
  elsif other.is_a?(DID)
    self.did == other.did
  else
    false
  end
end

#account_active?Boolean

Checks if the account is seen as active on its own PDS, using the getRepoStatus endpoint. This is a helper which calls the #account_status method and checks if the status is :active.

Returns:

  • (Boolean)

    true if the returned status is active

Raises:

  • (APIError)

    when the response is invalid



170
171
172
# File 'lib/didkit/did.rb', line 170

def 
   == :active
end

#account_exists?Boolean

Checks if the account exists its own PDS, using the getRepoStatus endpoint. This is a helper which calls the #account_status method and checks if the repo is found at all.

Returns:

  • (Boolean)

    true if the returned status is valid, false if repo is not found

Raises:

  • (APIError)

    when the response is invalid



180
181
182
# File 'lib/didkit/did.rb', line 180

def 
   != nil
end

#account_status(request_options = {}) ⇒ Symbol?

Checks the status of the account/repo on its own PDS using the getRepoStatus endpoint.

Parameters:

  • request_options (Hash) (defaults to: {})

    request options to override

Options Hash (request_options):

  • :timeout (Integer)

    request timeout (default: 15)

  • :max_redirects (Integer)

    maximum number of redirects to follow (default: 5)

Returns:

  • (Symbol, nil)

    :active, or returned inactive status, or nil if account is not found

Raises:

  • (APIError)

    when the response is invalid



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/didkit/did.rb', line 135

def (request_options = {})
  doc = self.document
  return nil if doc.pds_endpoint.nil?

  pds_host = uri_origin(doc.pds_endpoint)
  url = URI("#{pds_host}/xrpc/com.atproto.sync.getRepoStatus")
  url.query = URI.encode_www_form(:did => @did)

  response = get_response(url, request_options)
  status = response.code.to_i
  is_json = (response['Content-Type'] =~ /^application\/json(;.*)?$/)

  if status == 200 && is_json
    json = JSON.parse(response.body)

    if json['active'] == true
      :active
    elsif json['active'] == false && json['status'].is_a?(String) && json['status'].length <= 100
      json['status'].to_sym
    else
      raise APIError.new(response)
    end
  elsif status == 400 && is_json && JSON.parse(response.body)['error'] == 'RepoNotFound'
    nil
  else
    raise APIError.new(response)
  end
end

#documentDocument

Returns or looks up the DID document with the DID’s identity details from an appropriate source. This method caches the document in a local variable if it’s called again.

Returns:



82
83
84
# File 'lib/didkit/did.rb', line 82

def document
  @document ||= get_document
end

#get_audit_logArray<PLCOperation>

Fetches the PLC audit log (list of all previous operations) for a did:plc DID.

Returns:

  • (Array<PLCOperation>)

    list of PLC operations in the audit log

Raises:

  • (DIDError)

    when the DID is not a did:plc



110
111
112
113
114
115
116
# File 'lib/didkit/did.rb', line 110

def get_audit_log
  if @type == :plc
    PLCImporter.new.fetch_audit_log(self)
  else
    raise DIDError.new("Audit log not supported for did:#{@type}")
  end
end

#get_documentDocument

Looks up the DID document with the DID’s identity details from an appropriate source.

Returns:



89
90
91
# File 'lib/didkit/did.rb', line 89

def get_document
  Resolver.new.resolve_did(self)
end

#get_verified_handleString?

Returns the first verified handle assigned to this DID.

Looks up the domain handles assigned to this DID in its DID document, checks if they are verified (i.e. assigned correctly to this DID using DNS TXT or .well-known) and returns the first handle that validates correctly, or nil if none matches.

Returns:

  • (String, nil)

    verified handle domain, if found



101
102
103
# File 'lib/didkit/did.rb', line 101

def get_verified_handle
  Resolver.new.get_verified_handle(document)
end

#web_domainString?

Returns the domain portion of a did:web identifier.

Returns:

  • (String, nil)

    DID domain if the DID is a did:web, nil for did:plc



122
123
124
# File 'lib/didkit/did.rb', line 122

def web_domain
  did.gsub(/^did\:web\:/, '') if type == :web
end