1import 'constants.dart'; 2import 'handle_helpers.dart'; 3 4/// Represents a DID document as defined by W3C DID Core spec. 5/// 6/// This is a simplified version focused on atProto needs. 7/// See: https://www.w3.org/TR/did-core/ 8class DidDocument { 9 /// The DID subject (the DID itself) 10 final String id; 11 12 /// Alternative identifiers (used for atProto handles: at://handle) 13 final List<String>? alsoKnownAs; 14 15 /// Service endpoints (used to find PDS URL) 16 final List<DidService>? service; 17 18 /// Verification methods for authentication 19 final List<dynamic>? verificationMethod; 20 21 /// Authentication methods 22 final List<dynamic>? authentication; 23 24 /// Optional controller DIDs 25 final dynamic controller; // Can be String or List<String> 26 27 /// The @context field 28 final dynamic context; 29 30 const DidDocument({ 31 required this.id, 32 this.alsoKnownAs, 33 this.service, 34 this.verificationMethod, 35 this.authentication, 36 this.controller, 37 this.context, 38 }); 39 40 /// Parses a DID document from JSON. 41 factory DidDocument.fromJson(Map<String, dynamic> json) { 42 return DidDocument( 43 id: json['id'] as String, 44 alsoKnownAs: 45 (json['alsoKnownAs'] as List<dynamic>?) 46 ?.map((e) => e as String) 47 .toList(), 48 service: 49 (json['service'] as List<dynamic>?) 50 ?.map((e) => DidService.fromJson(e as Map<String, dynamic>)) 51 .toList(), 52 verificationMethod: json['verificationMethod'] as List<dynamic>?, 53 authentication: json['authentication'] as List<dynamic>?, 54 controller: json['controller'], 55 context: json['@context'], 56 ); 57 } 58 59 /// Converts the DID document to JSON. 60 Map<String, dynamic> toJson() { 61 final map = <String, dynamic>{'id': id}; 62 63 if (context != null) map['@context'] = context; 64 if (alsoKnownAs != null) map['alsoKnownAs'] = alsoKnownAs; 65 if (service != null) { 66 map['service'] = service!.map((s) => s.toJson()).toList(); 67 } 68 if (verificationMethod != null) { 69 map['verificationMethod'] = verificationMethod; 70 } 71 if (authentication != null) map['authentication'] = authentication; 72 if (controller != null) map['controller'] = controller; 73 74 return map; 75 } 76 77 /// Extracts the atProto PDS URL from the DID document. 78 /// 79 /// Returns null if no PDS service is found. 80 String? extractPdsUrl() { 81 if (service == null) return null; 82 83 for (final s in service!) { 84 // Check for standard atproto_pds service 85 if (s.id == atprotoServiceId && s.type == atprotoServiceType) { 86 if (s.serviceEndpoint is String) { 87 return s.serviceEndpoint as String; 88 } 89 } 90 91 // Also check if type matches (some implementations may vary on id) 92 if (s.type == atprotoServiceType && s.serviceEndpoint is String) { 93 return s.serviceEndpoint as String; 94 } 95 } 96 97 return null; 98 } 99 100 /// Extracts the raw atProto handle from the DID document. 101 /// 102 /// Returns null if no handle is found in alsoKnownAs. 103 String? extractAtprotoHandle() { 104 if (alsoKnownAs == null) return null; 105 106 for (final aka in alsoKnownAs!) { 107 if (aka.startsWith('at://')) { 108 // Strip off "at://" prefix 109 return aka.substring(5); 110 } 111 } 112 113 return null; 114 } 115 116 /// Extracts a validated, normalized atProto handle from the DID document. 117 /// 118 /// Returns null if no valid handle is found. 119 String? extractNormalizedHandle() { 120 final handle = extractAtprotoHandle(); 121 if (handle == null) return null; 122 return asNormalizedHandle(handle); 123 } 124} 125 126/// Represents a service endpoint in a DID document. 127class DidService { 128 /// Service ID (e.g., "#atproto_pds") 129 final String id; 130 131 /// Service type (e.g., "AtprotoPersonalDataServer") 132 final String type; 133 134 /// Service endpoint URL 135 final dynamic serviceEndpoint; // Can be String, Map, or List 136 137 const DidService({ 138 required this.id, 139 required this.type, 140 required this.serviceEndpoint, 141 }); 142 143 /// Parses a service from JSON. 144 factory DidService.fromJson(Map<String, dynamic> json) { 145 return DidService( 146 id: json['id'] as String, 147 type: json['type'] as String, 148 serviceEndpoint: json['serviceEndpoint'], 149 ); 150 } 151 152 /// Converts the service to JSON. 153 Map<String, dynamic> toJson() { 154 return {'id': id, 'type': type, 'serviceEndpoint': serviceEndpoint}; 155 } 156}