From 86b80156fd8085a79532773055985e2110341ec3 Mon Sep 17 00:00:00 2001 From: NaiJi Date: Sun, 30 Oct 2022 18:21:38 +0400 Subject: [PATCH] refactor: Generalize DKIM usage into DnsRecords - Replace raw DKIM String object with a general DnsRecord structure - Implement network utils for common operations with networking concepts and structures - Implement initializing page pop up to re-try server deployment in case of a random networking error --- .../graphql_maps/schema/schema.graphql | 8 + .../graphql_maps/schema/schema.graphql.dart | 216 +++++++++++++++++ .../graphql_maps/schema/schema.graphql.g.dart | 22 ++ .../schema/server_settings.graphql | 6 +- .../schema/server_settings.graphql.dart | 218 ++---------------- .../schema/server_settings.graphql.g.dart | 27 +-- .../graphql_maps/schema/services.graphql | 6 +- .../graphql_maps/schema/services.graphql.dart | 213 ++--------------- .../schema/services.graphql.g.dart | 26 +-- .../graphql_maps/server_api/server.dart | 24 +- .../dns_providers/cloudflare/cloudflare.dart | 13 +- .../rest_maps/dns_providers/dns_provider.dart | 4 +- .../cubit/dns_records/dns_records_cubit.dart | 98 +------- .../cubit/dns_records/dns_records_state.dart | 41 ---- .../server_installation_repository.dart | 10 +- lib/logic/models/json/dns_records.dart | 4 +- lib/logic/models/service.dart | 5 +- lib/ui/pages/dns_details/dns_details.dart | 1 + lib/utils/network_utils.dart | 135 +++++++++++ 19 files changed, 456 insertions(+), 621 deletions(-) create mode 100644 lib/utils/network_utils.dart diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql index 2f60c969..5da67b2c 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql @@ -348,4 +348,12 @@ enum UserType { type Users { allUsers: [User!]! getUser(username: String!): User +} + +fragment dnsRecordFields on DnsRecord { + content + name + priority + recordType + ttl } \ No newline at end of file diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart index aa2d2c1e..7187e0e2 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart @@ -1,3 +1,5 @@ +import 'package:gql/ast.dart'; +import 'package:graphql/client.dart' as graphql; import 'package:json_annotation/json_annotation.dart'; import 'package:selfprivacy/utils/scalars.dart'; part 'schema.graphql.g.dart'; @@ -736,6 +738,220 @@ enum Enum$UserType { $unknown } +@JsonSerializable(explicitToJson: true) +class Fragment$dnsRecordFields { + Fragment$dnsRecordFields( + {required this.content, + required this.name, + this.priority, + required this.recordType, + required this.ttl, + required this.$__typename}); + + @override + factory Fragment$dnsRecordFields.fromJson(Map json) => + _$Fragment$dnsRecordFieldsFromJson(json); + + final String content; + + final String name; + + final int? priority; + + final String recordType; + + final int ttl; + + @JsonKey(name: '__typename') + final String $__typename; + + Map toJson() => _$Fragment$dnsRecordFieldsToJson(this); + int get hashCode { + final l$content = content; + final l$name = name; + final l$priority = priority; + final l$recordType = recordType; + final l$ttl = ttl; + final l$$__typename = $__typename; + return Object.hashAll( + [l$content, l$name, l$priority, l$recordType, l$ttl, l$$__typename]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (!(other is Fragment$dnsRecordFields) || + runtimeType != other.runtimeType) return false; + final l$content = content; + final lOther$content = other.content; + if (l$content != lOther$content) return false; + final l$name = name; + final lOther$name = other.name; + if (l$name != lOther$name) return false; + final l$priority = priority; + final lOther$priority = other.priority; + if (l$priority != lOther$priority) return false; + final l$recordType = recordType; + final lOther$recordType = other.recordType; + if (l$recordType != lOther$recordType) return false; + final l$ttl = ttl; + final lOther$ttl = other.ttl; + if (l$ttl != lOther$ttl) return false; + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) return false; + return true; + } +} + +extension UtilityExtension$Fragment$dnsRecordFields + on Fragment$dnsRecordFields { + CopyWith$Fragment$dnsRecordFields get copyWith => + CopyWith$Fragment$dnsRecordFields(this, (i) => i); +} + +abstract class CopyWith$Fragment$dnsRecordFields { + factory CopyWith$Fragment$dnsRecordFields(Fragment$dnsRecordFields instance, + TRes Function(Fragment$dnsRecordFields) then) = + _CopyWithImpl$Fragment$dnsRecordFields; + + factory CopyWith$Fragment$dnsRecordFields.stub(TRes res) = + _CopyWithStubImpl$Fragment$dnsRecordFields; + + TRes call( + {String? content, + String? name, + int? priority, + String? recordType, + int? ttl, + String? $__typename}); +} + +class _CopyWithImpl$Fragment$dnsRecordFields + implements CopyWith$Fragment$dnsRecordFields { + _CopyWithImpl$Fragment$dnsRecordFields(this._instance, this._then); + + final Fragment$dnsRecordFields _instance; + + final TRes Function(Fragment$dnsRecordFields) _then; + + static const _undefined = {}; + + TRes call( + {Object? content = _undefined, + Object? name = _undefined, + Object? priority = _undefined, + Object? recordType = _undefined, + Object? ttl = _undefined, + Object? $__typename = _undefined}) => + _then(Fragment$dnsRecordFields( + content: content == _undefined || content == null + ? _instance.content + : (content as String), + name: name == _undefined || name == null + ? _instance.name + : (name as String), + priority: + priority == _undefined ? _instance.priority : (priority as int?), + recordType: recordType == _undefined || recordType == null + ? _instance.recordType + : (recordType as String), + ttl: ttl == _undefined || ttl == null ? _instance.ttl : (ttl as int), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String))); +} + +class _CopyWithStubImpl$Fragment$dnsRecordFields + implements CopyWith$Fragment$dnsRecordFields { + _CopyWithStubImpl$Fragment$dnsRecordFields(this._res); + + TRes _res; + + call( + {String? content, + String? name, + int? priority, + String? recordType, + int? ttl, + String? $__typename}) => + _res; +} + +const fragmentDefinitiondnsRecordFields = FragmentDefinitionNode( + name: NameNode(value: 'dnsRecordFields'), + typeCondition: TypeConditionNode( + on: NamedTypeNode( + name: NameNode(value: 'DnsRecord'), isNonNull: false)), + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'content'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'name'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'priority'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'recordType'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: 'ttl'), + alias: null, + arguments: [], + directives: [], + selectionSet: null), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null) + ])); +const documentNodeFragmentdnsRecordFields = DocumentNode(definitions: [ + fragmentDefinitiondnsRecordFields, +]); + +extension ClientExtension$Fragment$dnsRecordFields on graphql.GraphQLClient { + void writeFragment$dnsRecordFields( + {required Fragment$dnsRecordFields data, + required Map idFields, + bool broadcast = true}) => + this.writeFragment( + graphql.FragmentRequest( + idFields: idFields, + fragment: const graphql.Fragment( + fragmentName: 'dnsRecordFields', + document: documentNodeFragmentdnsRecordFields)), + data: data.toJson(), + broadcast: broadcast); + Fragment$dnsRecordFields? readFragment$dnsRecordFields( + {required Map idFields, bool optimistic = true}) { + final result = this.readFragment( + graphql.FragmentRequest( + idFields: idFields, + fragment: const graphql.Fragment( + fragmentName: 'dnsRecordFields', + document: documentNodeFragmentdnsRecordFields)), + optimistic: optimistic); + return result == null ? null : Fragment$dnsRecordFields.fromJson(result); + } +} + const possibleTypesMap = { 'MutationReturnInterface': { 'ApiKeyMutationReturn', diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.g.dart b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.g.dart index d3008d30..7d1280c8 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.g.dart +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.g.dart @@ -123,3 +123,25 @@ Map _$Input$UserMutationInputToJson( 'username': instance.username, 'password': instance.password, }; + +Fragment$dnsRecordFields _$Fragment$dnsRecordFieldsFromJson( + Map json) => + Fragment$dnsRecordFields( + content: json['content'] as String, + name: json['name'] as String, + priority: json['priority'] as int?, + recordType: json['recordType'] as String, + ttl: json['ttl'] as int, + $__typename: json['__typename'] as String, + ); + +Map _$Fragment$dnsRecordFieldsToJson( + Fragment$dnsRecordFields instance) => + { + 'content': instance.content, + 'name': instance.name, + 'priority': instance.priority, + 'recordType': instance.recordType, + 'ttl': instance.ttl, + '__typename': instance.$__typename, + }; diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql index d8a1d62d..83e5f06e 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql @@ -35,11 +35,7 @@ query DomainInfo { hostname provider requiredDnsRecords { - content - name - priority - recordType - ttl + ...dnsRecordFields } } } diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart index c0a70151..5d036afa 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart @@ -1444,36 +1444,9 @@ const documentNodeQueryDomainInfo = DocumentNode(definitions: [ arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ - FieldNode( - name: NameNode(value: 'content'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'name'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'priority'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'recordType'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'ttl'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), + FragmentSpreadNode( + name: NameNode(value: 'dnsRecordFields'), + directives: []), FieldNode( name: NameNode(value: '__typename'), alias: null, @@ -1502,6 +1475,7 @@ const documentNodeQueryDomainInfo = DocumentNode(definitions: [ directives: [], selectionSet: null) ])), + fragmentDefinitiondnsRecordFields, ]); Query$DomainInfo _parserFn$Query$DomainInfo(Map data) => Query$DomainInfo.fromJson(data); @@ -1699,8 +1673,7 @@ class Query$DomainInfo$system$domainInfo { @JsonKey(unknownEnumValue: Enum$DnsProvider.$unknown) final Enum$DnsProvider provider; - final List - requiredDnsRecords; + final List requiredDnsRecords; @JsonKey(name: '__typename') final String $__typename; @@ -1775,14 +1748,12 @@ abstract class CopyWith$Query$DomainInfo$system$domainInfo { {String? domain, String? hostname, Enum$DnsProvider? provider, - List? - requiredDnsRecords, + List? requiredDnsRecords, String? $__typename}); TRes requiredDnsRecords( - Iterable Function( + Iterable Function( Iterable< - CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords< - Query$DomainInfo$system$domainInfo$requiredDnsRecords>>) + CopyWith$Fragment$dnsRecordFields>) _fn); } @@ -1815,21 +1786,20 @@ class _CopyWithImpl$Query$DomainInfo$system$domainInfo requiredDnsRecords: requiredDnsRecords == _undefined || requiredDnsRecords == null ? _instance.requiredDnsRecords - : (requiredDnsRecords as List< - Query$DomainInfo$system$domainInfo$requiredDnsRecords>), + : (requiredDnsRecords as List), $__typename: $__typename == _undefined || $__typename == null ? _instance.$__typename : ($__typename as String))); TRes requiredDnsRecords( - Iterable Function( + Iterable Function( Iterable< - CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords< - Query$DomainInfo$system$domainInfo$requiredDnsRecords>>) + CopyWith$Fragment$dnsRecordFields< + Fragment$dnsRecordFields>>) _fn) => call( - requiredDnsRecords: _fn(_instance.requiredDnsRecords.map((e) => - CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords( - e, (i) => i))).toList()); + requiredDnsRecords: _fn(_instance.requiredDnsRecords + .map((e) => CopyWith$Fragment$dnsRecordFields(e, (i) => i))) + .toList()); } class _CopyWithStubImpl$Query$DomainInfo$system$domainInfo @@ -1842,168 +1812,12 @@ class _CopyWithStubImpl$Query$DomainInfo$system$domainInfo {String? domain, String? hostname, Enum$DnsProvider? provider, - List? - requiredDnsRecords, + List? requiredDnsRecords, String? $__typename}) => _res; requiredDnsRecords(_fn) => _res; } -@JsonSerializable(explicitToJson: true) -class Query$DomainInfo$system$domainInfo$requiredDnsRecords { - Query$DomainInfo$system$domainInfo$requiredDnsRecords( - {required this.content, - required this.name, - this.priority, - required this.recordType, - required this.ttl, - required this.$__typename}); - - @override - factory Query$DomainInfo$system$domainInfo$requiredDnsRecords.fromJson( - Map json) => - _$Query$DomainInfo$system$domainInfo$requiredDnsRecordsFromJson(json); - - final String content; - - final String name; - - final int? priority; - - final String recordType; - - final int ttl; - - @JsonKey(name: '__typename') - final String $__typename; - - Map toJson() => - _$Query$DomainInfo$system$domainInfo$requiredDnsRecordsToJson(this); - int get hashCode { - final l$content = content; - final l$name = name; - final l$priority = priority; - final l$recordType = recordType; - final l$ttl = ttl; - final l$$__typename = $__typename; - return Object.hashAll( - [l$content, l$name, l$priority, l$recordType, l$ttl, l$$__typename]); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (!(other is Query$DomainInfo$system$domainInfo$requiredDnsRecords) || - runtimeType != other.runtimeType) return false; - final l$content = content; - final lOther$content = other.content; - if (l$content != lOther$content) return false; - final l$name = name; - final lOther$name = other.name; - if (l$name != lOther$name) return false; - final l$priority = priority; - final lOther$priority = other.priority; - if (l$priority != lOther$priority) return false; - final l$recordType = recordType; - final lOther$recordType = other.recordType; - if (l$recordType != lOther$recordType) return false; - final l$ttl = ttl; - final lOther$ttl = other.ttl; - if (l$ttl != lOther$ttl) return false; - final l$$__typename = $__typename; - final lOther$$__typename = other.$__typename; - if (l$$__typename != lOther$$__typename) return false; - return true; - } -} - -extension UtilityExtension$Query$DomainInfo$system$domainInfo$requiredDnsRecords - on Query$DomainInfo$system$domainInfo$requiredDnsRecords { - CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords< - Query$DomainInfo$system$domainInfo$requiredDnsRecords> - get copyWith => - CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords( - this, (i) => i); -} - -abstract class CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords< - TRes> { - factory CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords( - Query$DomainInfo$system$domainInfo$requiredDnsRecords instance, - TRes Function(Query$DomainInfo$system$domainInfo$requiredDnsRecords) - then) = - _CopyWithImpl$Query$DomainInfo$system$domainInfo$requiredDnsRecords; - - factory CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords.stub( - TRes res) = - _CopyWithStubImpl$Query$DomainInfo$system$domainInfo$requiredDnsRecords; - - TRes call( - {String? content, - String? name, - int? priority, - String? recordType, - int? ttl, - String? $__typename}); -} - -class _CopyWithImpl$Query$DomainInfo$system$domainInfo$requiredDnsRecords - implements - CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords { - _CopyWithImpl$Query$DomainInfo$system$domainInfo$requiredDnsRecords( - this._instance, this._then); - - final Query$DomainInfo$system$domainInfo$requiredDnsRecords _instance; - - final TRes Function(Query$DomainInfo$system$domainInfo$requiredDnsRecords) - _then; - - static const _undefined = {}; - - TRes call( - {Object? content = _undefined, - Object? name = _undefined, - Object? priority = _undefined, - Object? recordType = _undefined, - Object? ttl = _undefined, - Object? $__typename = _undefined}) => - _then(Query$DomainInfo$system$domainInfo$requiredDnsRecords( - content: content == _undefined || content == null - ? _instance.content - : (content as String), - name: name == _undefined || name == null - ? _instance.name - : (name as String), - priority: - priority == _undefined ? _instance.priority : (priority as int?), - recordType: recordType == _undefined || recordType == null - ? _instance.recordType - : (recordType as String), - ttl: ttl == _undefined || ttl == null ? _instance.ttl : (ttl as int), - $__typename: $__typename == _undefined || $__typename == null - ? _instance.$__typename - : ($__typename as String))); -} - -class _CopyWithStubImpl$Query$DomainInfo$system$domainInfo$requiredDnsRecords< - TRes> - implements - CopyWith$Query$DomainInfo$system$domainInfo$requiredDnsRecords { - _CopyWithStubImpl$Query$DomainInfo$system$domainInfo$requiredDnsRecords( - this._res); - - TRes _res; - - call( - {String? content, - String? name, - int? priority, - String? recordType, - int? ttl, - String? $__typename}) => - _res; -} - @JsonSerializable(explicitToJson: true) class Variables$Mutation$ChangeTimezone { Variables$Mutation$ChangeTimezone({required this.timezone}); diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.g.dart b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.g.dart index fc66954b..c928b177 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.g.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.g.dart @@ -190,8 +190,7 @@ Query$DomainInfo$system$domainInfo _$Query$DomainInfo$system$domainInfoFromJson( unknownValue: Enum$DnsProvider.$unknown), requiredDnsRecords: (json['requiredDnsRecords'] as List) .map((e) => - Query$DomainInfo$system$domainInfo$requiredDnsRecords.fromJson( - e as Map)) + Fragment$dnsRecordFields.fromJson(e as Map)) .toList(), $__typename: json['__typename'] as String, ); @@ -212,30 +211,6 @@ const _$Enum$DnsProviderEnumMap = { Enum$DnsProvider.$unknown: r'$unknown', }; -Query$DomainInfo$system$domainInfo$requiredDnsRecords - _$Query$DomainInfo$system$domainInfo$requiredDnsRecordsFromJson( - Map json) => - Query$DomainInfo$system$domainInfo$requiredDnsRecords( - content: json['content'] as String, - name: json['name'] as String, - priority: json['priority'] as int?, - recordType: json['recordType'] as String, - ttl: json['ttl'] as int, - $__typename: json['__typename'] as String, - ); - -Map - _$Query$DomainInfo$system$domainInfo$requiredDnsRecordsToJson( - Query$DomainInfo$system$domainInfo$requiredDnsRecords instance) => - { - 'content': instance.content, - 'name': instance.name, - 'priority': instance.priority, - 'recordType': instance.recordType, - 'ttl': instance.ttl, - '__typename': instance.$__typename, - }; - Variables$Mutation$ChangeTimezone _$Variables$Mutation$ChangeTimezoneFromJson( Map json) => Variables$Mutation$ChangeTimezone( diff --git a/lib/logic/api_maps/graphql_maps/schema/services.graphql b/lib/logic/api_maps/graphql_maps/schema/services.graphql index ce464426..7386c362 100644 --- a/lib/logic/api_maps/graphql_maps/schema/services.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/services.graphql @@ -10,11 +10,7 @@ query AllServices { description displayName dnsRecords { - content - name - priority - recordType - ttl + ...dnsRecordFields } id isEnabled diff --git a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart index 411186ad..a31058c4 100644 --- a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart @@ -312,36 +312,9 @@ const documentNodeQueryAllServices = DocumentNode(definitions: [ arguments: [], directives: [], selectionSet: SelectionSetNode(selections: [ - FieldNode( - name: NameNode(value: 'content'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'name'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'priority'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'recordType'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), - FieldNode( - name: NameNode(value: 'ttl'), - alias: null, - arguments: [], - directives: [], - selectionSet: null), + FragmentSpreadNode( + name: NameNode(value: 'dnsRecordFields'), + directives: []), FieldNode( name: NameNode(value: '__typename'), alias: null, @@ -456,6 +429,7 @@ const documentNodeQueryAllServices = DocumentNode(definitions: [ directives: [], selectionSet: null) ])), + fragmentDefinitiondnsRecordFields, ]); Query$AllServices _parserFn$Query$AllServices(Map data) => Query$AllServices.fromJson(data); @@ -679,7 +653,7 @@ class Query$AllServices$services$allServices { final String displayName; - final List? dnsRecords; + final List? dnsRecords; final String id; @@ -807,7 +781,7 @@ abstract class CopyWith$Query$AllServices$services$allServices { TRes call( {String? description, String? displayName, - List? dnsRecords, + List? dnsRecords, String? id, bool? isEnabled, bool? isMovable, @@ -818,10 +792,9 @@ abstract class CopyWith$Query$AllServices$services$allServices { String? url, String? $__typename}); TRes dnsRecords( - Iterable? Function( + Iterable? Function( Iterable< - CopyWith$Query$AllServices$services$allServices$dnsRecords< - Query$AllServices$services$allServices$dnsRecords>>?) + CopyWith$Fragment$dnsRecordFields>?) _fn); CopyWith$Query$AllServices$services$allServices$storageUsage get storageUsage; @@ -860,8 +833,7 @@ class _CopyWithImpl$Query$AllServices$services$allServices : (displayName as String), dnsRecords: dnsRecords == _undefined ? _instance.dnsRecords - : (dnsRecords - as List?), + : (dnsRecords as List?), id: id == _undefined || id == null ? _instance.id : (id as String), isEnabled: isEnabled == _undefined || isEnabled == null ? _instance.isEnabled @@ -887,15 +859,15 @@ class _CopyWithImpl$Query$AllServices$services$allServices ? _instance.$__typename : ($__typename as String))); TRes dnsRecords( - Iterable? Function( + Iterable? Function( Iterable< - CopyWith$Query$AllServices$services$allServices$dnsRecords< - Query$AllServices$services$allServices$dnsRecords>>?) + CopyWith$Fragment$dnsRecordFields< + Fragment$dnsRecordFields>>?) _fn) => call( - dnsRecords: _fn(_instance.dnsRecords?.map((e) => - CopyWith$Query$AllServices$services$allServices$dnsRecords( - e, (i) => i)))?.toList()); + dnsRecords: _fn(_instance.dnsRecords + ?.map((e) => CopyWith$Fragment$dnsRecordFields(e, (i) => i))) + ?.toList()); CopyWith$Query$AllServices$services$allServices$storageUsage get storageUsage { final local$storageUsage = _instance.storageUsage; @@ -913,7 +885,7 @@ class _CopyWithStubImpl$Query$AllServices$services$allServices call( {String? description, String? displayName, - List? dnsRecords, + List? dnsRecords, String? id, bool? isEnabled, bool? isMovable, @@ -931,159 +903,6 @@ class _CopyWithStubImpl$Query$AllServices$services$allServices _res); } -@JsonSerializable(explicitToJson: true) -class Query$AllServices$services$allServices$dnsRecords { - Query$AllServices$services$allServices$dnsRecords( - {required this.content, - required this.name, - this.priority, - required this.recordType, - required this.ttl, - required this.$__typename}); - - @override - factory Query$AllServices$services$allServices$dnsRecords.fromJson( - Map json) => - _$Query$AllServices$services$allServices$dnsRecordsFromJson(json); - - final String content; - - final String name; - - final int? priority; - - final String recordType; - - final int ttl; - - @JsonKey(name: '__typename') - final String $__typename; - - Map toJson() => - _$Query$AllServices$services$allServices$dnsRecordsToJson(this); - int get hashCode { - final l$content = content; - final l$name = name; - final l$priority = priority; - final l$recordType = recordType; - final l$ttl = ttl; - final l$$__typename = $__typename; - return Object.hashAll( - [l$content, l$name, l$priority, l$recordType, l$ttl, l$$__typename]); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (!(other is Query$AllServices$services$allServices$dnsRecords) || - runtimeType != other.runtimeType) return false; - final l$content = content; - final lOther$content = other.content; - if (l$content != lOther$content) return false; - final l$name = name; - final lOther$name = other.name; - if (l$name != lOther$name) return false; - final l$priority = priority; - final lOther$priority = other.priority; - if (l$priority != lOther$priority) return false; - final l$recordType = recordType; - final lOther$recordType = other.recordType; - if (l$recordType != lOther$recordType) return false; - final l$ttl = ttl; - final lOther$ttl = other.ttl; - if (l$ttl != lOther$ttl) return false; - final l$$__typename = $__typename; - final lOther$$__typename = other.$__typename; - if (l$$__typename != lOther$$__typename) return false; - return true; - } -} - -extension UtilityExtension$Query$AllServices$services$allServices$dnsRecords - on Query$AllServices$services$allServices$dnsRecords { - CopyWith$Query$AllServices$services$allServices$dnsRecords< - Query$AllServices$services$allServices$dnsRecords> - get copyWith => - CopyWith$Query$AllServices$services$allServices$dnsRecords( - this, (i) => i); -} - -abstract class CopyWith$Query$AllServices$services$allServices$dnsRecords< - TRes> { - factory CopyWith$Query$AllServices$services$allServices$dnsRecords( - Query$AllServices$services$allServices$dnsRecords instance, - TRes Function(Query$AllServices$services$allServices$dnsRecords) - then) = - _CopyWithImpl$Query$AllServices$services$allServices$dnsRecords; - - factory CopyWith$Query$AllServices$services$allServices$dnsRecords.stub( - TRes res) = - _CopyWithStubImpl$Query$AllServices$services$allServices$dnsRecords; - - TRes call( - {String? content, - String? name, - int? priority, - String? recordType, - int? ttl, - String? $__typename}); -} - -class _CopyWithImpl$Query$AllServices$services$allServices$dnsRecords - implements - CopyWith$Query$AllServices$services$allServices$dnsRecords { - _CopyWithImpl$Query$AllServices$services$allServices$dnsRecords( - this._instance, this._then); - - final Query$AllServices$services$allServices$dnsRecords _instance; - - final TRes Function(Query$AllServices$services$allServices$dnsRecords) _then; - - static const _undefined = {}; - - TRes call( - {Object? content = _undefined, - Object? name = _undefined, - Object? priority = _undefined, - Object? recordType = _undefined, - Object? ttl = _undefined, - Object? $__typename = _undefined}) => - _then(Query$AllServices$services$allServices$dnsRecords( - content: content == _undefined || content == null - ? _instance.content - : (content as String), - name: name == _undefined || name == null - ? _instance.name - : (name as String), - priority: - priority == _undefined ? _instance.priority : (priority as int?), - recordType: recordType == _undefined || recordType == null - ? _instance.recordType - : (recordType as String), - ttl: ttl == _undefined || ttl == null ? _instance.ttl : (ttl as int), - $__typename: $__typename == _undefined || $__typename == null - ? _instance.$__typename - : ($__typename as String))); -} - -class _CopyWithStubImpl$Query$AllServices$services$allServices$dnsRecords - implements - CopyWith$Query$AllServices$services$allServices$dnsRecords { - _CopyWithStubImpl$Query$AllServices$services$allServices$dnsRecords( - this._res); - - TRes _res; - - call( - {String? content, - String? name, - int? priority, - String? recordType, - int? ttl, - String? $__typename}) => - _res; -} - @JsonSerializable(explicitToJson: true) class Query$AllServices$services$allServices$storageUsage { Query$AllServices$services$allServices$storageUsage( diff --git a/lib/logic/api_maps/graphql_maps/schema/services.graphql.g.dart b/lib/logic/api_maps/graphql_maps/schema/services.graphql.g.dart index 405ab468..52663401 100644 --- a/lib/logic/api_maps/graphql_maps/schema/services.graphql.g.dart +++ b/lib/logic/api_maps/graphql_maps/schema/services.graphql.g.dart @@ -62,8 +62,7 @@ Query$AllServices$services$allServices displayName: json['displayName'] as String, dnsRecords: (json['dnsRecords'] as List?) ?.map((e) => - Query$AllServices$services$allServices$dnsRecords.fromJson( - e as Map)) + Fragment$dnsRecordFields.fromJson(e as Map)) .toList(), id: json['id'] as String, isEnabled: json['isEnabled'] as bool, @@ -107,29 +106,6 @@ const _$Enum$ServiceStatusEnumEnumMap = { Enum$ServiceStatusEnum.$unknown: r'$unknown', }; -Query$AllServices$services$allServices$dnsRecords - _$Query$AllServices$services$allServices$dnsRecordsFromJson( - Map json) => - Query$AllServices$services$allServices$dnsRecords( - content: json['content'] as String, - name: json['name'] as String, - priority: json['priority'] as int?, - recordType: json['recordType'] as String, - ttl: json['ttl'] as int, - $__typename: json['__typename'] as String, - ); - -Map _$Query$AllServices$services$allServices$dnsRecordsToJson( - Query$AllServices$services$allServices$dnsRecords instance) => - { - 'content': instance.content, - 'name': instance.name, - 'priority': instance.priority, - 'recordType': instance.recordType, - 'ttl': instance.ttl, - '__typename': instance.$__typename, - }; - Query$AllServices$services$allServices$storageUsage _$Query$AllServices$services$allServices$storageUsageFromJson( Map json) => diff --git a/lib/logic/api_maps/graphql_maps/server_api/server.dart b/lib/logic/api_maps/graphql_maps/server_api/server.dart index 82aa24be..c2cda13b 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server.dart @@ -13,6 +13,7 @@ import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart'; import 'package:selfprivacy/logic/models/json/backup.dart'; import 'package:selfprivacy/logic/models/json/device_token.dart'; +import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -256,11 +257,8 @@ class ServerApi extends ApiMap return key; } - @Deprecated( - 'Server now aware of all required DNS records. More general approach has to be implemented', - ) - Future getDkim() async { - String? dkim; + Future> getDnsRecords() async { + List records = []; QueryResult response; try { @@ -269,21 +267,17 @@ class ServerApi extends ApiMap if (response.hasException) { print(response.exception.toString()); } - dkim = response.parsedData!.system.domainInfo.requiredDnsRecords - .firstWhere( - ( - final Query$DomainInfo$system$domainInfo$requiredDnsRecords - dnsRecord, - ) => - dnsRecord.name == 'selector._domainkey' && - dnsRecord.recordType == 'TXT', + records = response.parsedData!.system.domainInfo.requiredDnsRecords + .map( + (final Fragment$dnsRecordFields fragment) => + DnsRecord.fromGraphQL(fragment), ) - .content; + .toList(); } catch (e) { print(e); } - return dkim; + return records; } Future>> getApiTokens() async { diff --git a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart index 3088de67..c79f0987 100644 --- a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart +++ b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare.dart @@ -242,25 +242,18 @@ class CloudflareApi extends DnsProviderApi { } @override - Future setDkim( - final String dkimRecordString, + Future setDnsRecord( + final DnsRecord record, final ServerDomain domain, ) async { final String domainZoneId = domain.zoneId; final String url = '$rootAddress/zones/$domainZoneId/dns_records'; - final DnsRecord dkimRecord = DnsRecord( - type: 'TXT', - name: 'selector._domainkey', - content: dkimRecordString, - ttl: 18000, - ); - final Dio client = await getClient(); try { await client.post( url, - data: dkimRecord.toJson(), + data: record.toJson(), ); } catch (e) { print(e); diff --git a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart index 6680975e..3ff6222e 100644 --- a/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart +++ b/lib/logic/api_maps/rest_maps/dns_providers/dns_provider.dart @@ -19,8 +19,8 @@ abstract class DnsProviderApi extends ApiMap { required final ServerDomain domain, final String? ip4, }); - Future setDkim( - final String dkimRecordString, + Future setDnsRecord( + final DnsRecord record, final ServerDomain domain, ); Future getZoneId(final String domain); diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index 29d19d81..6159b9be 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -7,6 +7,7 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server.dart'; +import 'package:selfprivacy/utils/network_utils.dart'; part 'dns_records_state.dart'; @@ -30,7 +31,7 @@ class DnsRecordsCubit emit( DnsRecordsState( dnsState: DnsRecordsStatus.refreshing, - dnsRecords: _getDesiredDnsRecords( + dnsRecords: getDesiredDnsRecords( serverInstallationCubit.state.serverDomain?.domainName, '', '', @@ -46,9 +47,10 @@ class DnsRecordsCubit final List records = await dnsProviderApiFactory! .getDnsProvider() .getDnsRecords(domain: domain); - final String? dkimPublicKey = await api.getDkim(); + final String? dkimPublicKey = + extractDkimRecord(await api.getDnsRecords())?.content; final List desiredRecords = - _getDesiredDnsRecords(domain.domainName, ipAddress, dkimPublicKey); + getDesiredDnsRecords(domain.domainName, ipAddress, dkimPublicKey); final List foundRecords = []; for (final DesiredDnsRecord record in desiredRecords) { if (record.description == 'record.dkim') { @@ -121,7 +123,6 @@ class DnsRecordsCubit emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing)); final ServerDomain? domain = serverInstallationCubit.state.serverDomain; final String? ipAddress = serverInstallationCubit.state.serverDetails?.ip4; - final String? dkimPublicKey = await api.getDkim(); final DnsProviderApi dnsProviderApi = dnsProviderApiFactory!.getDnsProvider(); await dnsProviderApi.removeSimilarRecords(domain: domain!); @@ -129,88 +130,13 @@ class DnsRecordsCubit domain: domain, ip4: ipAddress, ); - await dnsProviderApi.setDkim(dkimPublicKey ?? '', domain); + + final List records = await api.getDnsRecords(); + final DnsRecord? dkimRecord = extractDkimRecord(records); + if (dkimRecord != null) { + await dnsProviderApi.setDnsRecord(dkimRecord, domain); + } + await load(); } - - List _getDesiredDnsRecords( - final String? domainName, - final String? ipAddress, - final String? dkimPublicKey, - ) { - if (domainName == null || ipAddress == null) { - return []; - } - return [ - DesiredDnsRecord( - name: domainName, - content: ipAddress, - description: 'record.root', - ), - DesiredDnsRecord( - name: 'api.$domainName', - content: ipAddress, - description: 'record.api', - ), - DesiredDnsRecord( - name: 'cloud.$domainName', - content: ipAddress, - description: 'record.cloud', - ), - DesiredDnsRecord( - name: 'git.$domainName', - content: ipAddress, - description: 'record.git', - ), - DesiredDnsRecord( - name: 'meet.$domainName', - content: ipAddress, - description: 'record.meet', - ), - DesiredDnsRecord( - name: 'social.$domainName', - content: ipAddress, - description: 'record.social', - ), - DesiredDnsRecord( - name: 'password.$domainName', - content: ipAddress, - description: 'record.password', - ), - DesiredDnsRecord( - name: 'vpn.$domainName', - content: ipAddress, - description: 'record.vpn', - ), - DesiredDnsRecord( - name: domainName, - content: domainName, - description: 'record.mx', - type: 'MX', - category: DnsRecordsCategory.email, - ), - DesiredDnsRecord( - name: '_dmarc.$domainName', - content: 'v=DMARC1; p=none', - description: 'record.dmarc', - type: 'TXT', - category: DnsRecordsCategory.email, - ), - DesiredDnsRecord( - name: domainName, - content: 'v=spf1 a mx ip4:$ipAddress -all', - description: 'record.spf', - type: 'TXT', - category: DnsRecordsCategory.email, - ), - if (dkimPublicKey != null) - DesiredDnsRecord( - name: 'selector._domainkey.$domainName', - content: dkimPublicKey, - description: 'record.dkim', - type: 'TXT', - category: DnsRecordsCategory.email, - ), - ]; - } } diff --git a/lib/logic/cubit/dns_records/dns_records_state.dart b/lib/logic/cubit/dns_records/dns_records_state.dart index 4b39d014..222dda66 100644 --- a/lib/logic/cubit/dns_records/dns_records_state.dart +++ b/lib/logic/cubit/dns_records/dns_records_state.dart @@ -7,12 +7,6 @@ enum DnsRecordsStatus { error, } -enum DnsRecordsCategory { - services, - email, - other, -} - class DnsRecordsState extends ServerInstallationDependendState { const DnsRecordsState({ this.dnsState = DnsRecordsStatus.uninitialized, @@ -37,38 +31,3 @@ class DnsRecordsState extends ServerInstallationDependendState { dnsRecords: dnsRecords ?? this.dnsRecords, ); } - -class DesiredDnsRecord { - const DesiredDnsRecord({ - required this.name, - required this.content, - this.type = 'A', - this.description = '', - this.category = DnsRecordsCategory.services, - this.isSatisfied = false, - }); - - final String name; - final String type; - final String content; - final String description; - final DnsRecordsCategory category; - final bool isSatisfied; - - DesiredDnsRecord copyWith({ - final String? name, - final String? type, - final String? content, - final String? description, - final DnsRecordsCategory? category, - final bool? isSatisfied, - }) => - DesiredDnsRecord( - name: name ?? this.name, - type: type ?? this.type, - content: content ?? this.content, - description: description ?? this.description, - category: category ?? this.category, - isSatisfied: isSatisfied ?? this.isSatisfied, - ); -} diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 4bd500fd..c4a3e7ff 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -21,10 +21,12 @@ import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/json/device_token.dart'; +import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/message.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/ui/components/action_button/action_button.dart'; import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart'; +import 'package:selfprivacy/utils/network_utils.dart'; class IpNotFoundException implements Exception { IpNotFoundException(this.message); @@ -286,7 +288,7 @@ class ServerInstallationRepository { ], ), ); - } else if (e.response!.data['error']['code'] == 'resource_unavailable') { + } else { final NavigationService nav = getIt.get(); nav.showPopUpDialog( BrandAlert( @@ -390,15 +392,15 @@ class ServerInstallationRepository { dnsProviderApiFactory!.getDnsProvider(); final ServerApi api = ServerApi(); - String dkimRecordString = ''; + late DnsRecord record; try { - dkimRecordString = (await api.getDkim())!; + record = extractDkimRecord(await api.getDnsRecords())!; } catch (e) { print(e); rethrow; } - await dnsProviderApi.setDkim(dkimRecordString, cloudFlareDomain); + await dnsProviderApi.setDnsRecord(record, cloudFlareDomain); } Future isHttpServerWorking() async { diff --git a/lib/logic/models/json/dns_records.dart b/lib/logic/models/json/dns_records.dart index 1bb385b5..c4799876 100644 --- a/lib/logic/models/json/dns_records.dart +++ b/lib/logic/models/json/dns_records.dart @@ -1,5 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; part 'dns_records.g.dart'; @@ -15,7 +15,7 @@ class DnsRecord { }); DnsRecord.fromGraphQL( - final Query$AllServices$services$allServices$dnsRecords record, + final Fragment$dnsRecordFields record, ) : this( type: record.recordType, name: record.name, diff --git a/lib/logic/models/service.dart b/lib/logic/models/service.dart index 79bd8f7e..898d2965 100644 --- a/lib/logic/models/service.dart +++ b/lib/logic/models/service.dart @@ -23,7 +23,10 @@ class Service { // Decode the base64 encoded svg icon to text. svgIcon: utf8.decode(base64.decode(service.svgIcon)), dnsRecords: service.dnsRecords - ?.map((final record) => DnsRecord.fromGraphQL(record)) + ?.map( + (final Fragment$dnsRecordFields record) => + DnsRecord.fromGraphQL(record), + ) .toList() ?? [], url: service.url, diff --git a/lib/ui/pages/dns_details/dns_details.dart b/lib/ui/pages/dns_details/dns_details.dart index 8bf264d6..692921eb 100644 --- a/lib/ui/pages/dns_details/dns_details.dart +++ b/lib/ui/pages/dns_details/dns_details.dart @@ -6,6 +6,7 @@ import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/ui/components/brand_cards/filled_card.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; +import 'package:selfprivacy/utils/network_utils.dart'; class DnsDetailsPage extends StatefulWidget { const DnsDetailsPage({super.key}); diff --git a/lib/utils/network_utils.dart b/lib/utils/network_utils.dart new file mode 100644 index 00000000..8b75728f --- /dev/null +++ b/lib/utils/network_utils.dart @@ -0,0 +1,135 @@ +import 'package:selfprivacy/logic/models/json/dns_records.dart'; + +enum DnsRecordsCategory { + services, + email, + other, +} + +class DesiredDnsRecord { + const DesiredDnsRecord({ + required this.name, + required this.content, + this.type = 'A', + this.description = '', + this.category = DnsRecordsCategory.services, + this.isSatisfied = false, + }); + + final String name; + final String type; + final String content; + final String description; + final DnsRecordsCategory category; + final bool isSatisfied; + + DesiredDnsRecord copyWith({ + final String? name, + final String? type, + final String? content, + final String? description, + final DnsRecordsCategory? category, + final bool? isSatisfied, + }) => + DesiredDnsRecord( + name: name ?? this.name, + type: type ?? this.type, + content: content ?? this.content, + description: description ?? this.description, + category: category ?? this.category, + isSatisfied: isSatisfied ?? this.isSatisfied, + ); +} + +List getDesiredDnsRecords( + final String? domainName, + final String? ipAddress, + final String? dkimPublicKey, +) { + if (domainName == null || ipAddress == null) { + return []; + } + return [ + DesiredDnsRecord( + name: domainName, + content: ipAddress, + description: 'record.root', + ), + DesiredDnsRecord( + name: 'api.$domainName', + content: ipAddress, + description: 'record.api', + ), + DesiredDnsRecord( + name: 'cloud.$domainName', + content: ipAddress, + description: 'record.cloud', + ), + DesiredDnsRecord( + name: 'git.$domainName', + content: ipAddress, + description: 'record.git', + ), + DesiredDnsRecord( + name: 'meet.$domainName', + content: ipAddress, + description: 'record.meet', + ), + DesiredDnsRecord( + name: 'social.$domainName', + content: ipAddress, + description: 'record.social', + ), + DesiredDnsRecord( + name: 'password.$domainName', + content: ipAddress, + description: 'record.password', + ), + DesiredDnsRecord( + name: 'vpn.$domainName', + content: ipAddress, + description: 'record.vpn', + ), + DesiredDnsRecord( + name: domainName, + content: domainName, + description: 'record.mx', + type: 'MX', + category: DnsRecordsCategory.email, + ), + DesiredDnsRecord( + name: '_dmarc.$domainName', + content: 'v=DMARC1; p=none', + description: 'record.dmarc', + type: 'TXT', + category: DnsRecordsCategory.email, + ), + DesiredDnsRecord( + name: domainName, + content: 'v=spf1 a mx ip4:$ipAddress -all', + description: 'record.spf', + type: 'TXT', + category: DnsRecordsCategory.email, + ), + if (dkimPublicKey != null) + DesiredDnsRecord( + name: 'selector._domainkey.$domainName', + content: dkimPublicKey, + description: 'record.dkim', + type: 'TXT', + category: DnsRecordsCategory.email, + ), + ]; +} + +DnsRecord? extractDkimRecord(final List records) { + DnsRecord? dkimRecord; + + for (final DnsRecord record in records) { + if (record.type == 'TXT' && record.name == 'selector._domainkey') { + dkimRecord = record; + } + } + + return dkimRecord; +}