From d5d380d882d9b0513f67c54123475b5d3b1a7b00 Mon Sep 17 00:00:00 2001 From: Ethan Lee <125412902+ethan-tbd@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:51:06 -0700 Subject: [PATCH] feat: support http package (#72) --- .../web5/lib/src/dids/did_dht/did_dht.dart | 38 ++++------ .../web5/lib/src/dids/did_web/did_web.dart | 16 ++--- packages/web5/pubspec.yaml | 1 + .../web5/test/dids/did_dht/did_dht_test.dart | 56 +++++++++------ packages/web5/test/dids/did_web_test.dart | 72 +++++++------------ packages/web5/test/helpers/mocks.dart | 4 ++ 6 files changed, 89 insertions(+), 98 deletions(-) create mode 100644 packages/web5/test/helpers/mocks.dart diff --git a/packages/web5/lib/src/dids/did_dht/did_dht.dart b/packages/web5/lib/src/dids/did_dht/did_dht.dart index 68dbbdd..d0c8a89 100644 --- a/packages/web5/lib/src/dids/did_dht/did_dht.dart +++ b/packages/web5/lib/src/dids/did_dht/did_dht.dart @@ -1,5 +1,4 @@ -import 'dart:convert'; -import 'dart:io'; +import 'package:http/http.dart' as http; import 'dart:typed_data'; import 'package:web5/src/dids.dart'; @@ -86,16 +85,17 @@ class DidDht { final message = await Bep44Message.create(dnsPacket.encode(), seq, sign); final pkarrUrl = Uri.parse('$gatewayUri/$id'); - final request = await HttpClient().putUrl(pkarrUrl); - - request.headers.contentType = ContentType.binary; - request.add(message); + final response = await http.Client().put( + pkarrUrl, + headers: { + 'Content-Type': 'application/octet-stream', + }, + body: message, + ); - final response = await request.close(); - if (response.statusCode != HttpStatus.ok) { - final body = await response.transform(utf8.decoder).join(); + if (response.statusCode != 200) { throw Exception( - 'Failed to publish DID document. got: ${response.statusCode} $body', + 'Failed to publish DID document. got: ${response.statusCode} ${response.body}', ); } } @@ -111,7 +111,7 @@ class DidDht { static Future resolve( Did did, { String relayUrl = _defaultRelay, - HttpClient? client, + http.Client? client, }) async { if (did.method != methodName) { return DidResolutionResult.withError(DidResolutionError.invalidDid); @@ -127,19 +127,11 @@ class DidDht { final parsedRelayUrl = Uri.parse(relayUrl); final resolutionUrl = parsedRelayUrl.replace(path: did.id); - final httpClient = client ??= HttpClient(); - final request = await httpClient.getUrl(resolutionUrl); - final response = await request.close(); - - final List bytes = []; - await for (var byteList in response) { - bytes.addAll(byteList); - } - - httpClient.close(force: false); + final httpClient = client ??= http.Client(); + final response = await httpClient.get(resolutionUrl); final bep44Message = Bep44Message.verify( - Uint8List.fromList(bytes), + response.bodyBytes, Uint8List.fromList(identityKey), ); @@ -167,6 +159,6 @@ class DidDhtResolver extends DidMethodResolver { String get name => DidDht.methodName; @override - Future resolve(Did did, {HttpClient? options}) async => + Future resolve(Did did, {http.Client? options}) async => DidDht.resolve(did, client: options); } diff --git a/packages/web5/lib/src/dids/did_web/did_web.dart b/packages/web5/lib/src/dids/did_web/did_web.dart index b1229e4..0bd6f56 100644 --- a/packages/web5/lib/src/dids/did_web/did_web.dart +++ b/packages/web5/lib/src/dids/did_web/did_web.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'dart:io'; +import 'package:http/http.dart' as http; import 'package:web5/src/crypto.dart'; import 'package:web5/src/dids/bearer_did.dart'; import 'package:web5/src/dids/did.dart'; @@ -85,7 +85,7 @@ class DidWeb { static Future resolve( Did did, { - HttpClient? client, + http.Client? client, }) async { if (did.method != methodName) { return DidResolutionResult.withError(DidResolutionError.invalidDid); @@ -106,17 +106,15 @@ class DidWeb { if (didUri.path.isEmpty) didUri = didUri.replace(path: '/.well-known'); didUri = didUri.replace(pathSegments: [...didUri.pathSegments, 'did.json']); - final HttpClient httpClient = client ??= HttpClient(); - final HttpClientRequest request = await httpClient.getUrl(didUri); - final HttpClientResponse response = await request.close(); + final httpClient = client ??= http.Client(); + final response = await httpClient.get(didUri); if (response.statusCode != 200) { return DidResolutionResult.withError(DidResolutionError.notFound); } - final String str = await response.transform(utf8.decoder).join(); - final dynamic jsonParsed = json.decode(str); - final DidDocument doc = DidDocument.fromJson(jsonParsed); + final jsonParsed = json.decode(response.body); + final doc = DidDocument.fromJson(jsonParsed); return DidResolutionResult(didDocument: doc); } @@ -127,6 +125,6 @@ class DidWebResolver extends DidMethodResolver { String get name => DidWeb.methodName; @override - Future resolve(Did did, {HttpClient? options}) => + Future resolve(Did did, {http.Client? options}) => DidWeb.resolve(did, client: options); } diff --git a/packages/web5/pubspec.yaml b/packages/web5/pubspec.yaml index 9c77071..4d4c461 100644 --- a/packages/web5/pubspec.yaml +++ b/packages/web5/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: convert: ^3.1.1 cryptography: ^2.7.0 pointycastle: ^3.7.3 + http: ^1.2.0 dev_dependencies: lints: ^3.0.0 diff --git a/packages/web5/test/dids/did_dht/did_dht_test.dart b/packages/web5/test/dids/did_dht/did_dht_test.dart index 5a5d9ac..cab8e7d 100644 --- a/packages/web5/test/dids/did_dht/did_dht_test.dart +++ b/packages/web5/test/dids/did_dht/did_dht_test.dart @@ -1,31 +1,20 @@ -import 'dart:io'; - +import 'package:convert/convert.dart'; +import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; + import 'package:test/test.dart'; import 'package:web5/web5.dart'; -class MockHttpClient extends Mock implements HttpClient {} - -class MockHttpRequest extends Mock implements HttpClientRequest {} - -class MockHttpResponse extends Mock implements HttpClientResponse {} +import '../../helpers/mocks.dart'; -const validDidDhtDocument = - '''{id: did:dht:74hg1efatndi8enx3e4z6c4u8ieh1xfkyay4ntg4dg1w6risu35y, verificationMethod: [{id: 0, type: JsonWebKey2020, controller: did:dht:74hg1efatndi8enx3e4z6c4u8ieh1xfkyay4ntg4dg1w6risu35y, publicKeyJwk: {kty: OKP, alg: EdDSA, kid: a6tCQvXJQIZQZs_A126CcOT7PuP6R3yADH6DJLr1Zkg, crv: Ed25519, x: 7rhpILiIh1OgT8o1fzNTPVHJPKoGAaFE2hmlTxK2nnY}}], service: [{id: kyc-widget, type: kyc-widget, serviceEndpoint: http://localhost:5173}, {id: pfi, type: PFI, serviceEndpoint: http://localhost:8892/ingress/pfi}], assertionMethod: [0], authentication: [0], capabilityDelegation: [0], capabilityInvocation: [0]}'''; +const testVector = + '85ad53bb66db27eba9799d807a1dff1b43823263b72a0824aad94026980048ccbdfc3fdfe9355c243c32f5ed0f40ab3917b925783f6e49b6cd1a73333691e80c000000006625fa11000084000000000300000000035f6b30045f646964343377686674677062646a696878397a653974646e3537357a717a6d347177636365746e66317962696962757a61643772726d7979000010000100001c2000373669643d303b743d303b6b3d7a5468596d6145616138662d365078474c6664336464656e5559784552466b414e61686e66412d6b497341035f7330045f646964343377686674677062646a696878397a653974646e3537357a717a6d347177636365746e66317962696962757a61643772726d7979000010000100001c2000272669643d7066693b743d5046493b73653d68747470733a2f2f6c6f63616c686f73743a39303030045f646964343377686674677062646a696878397a653974646e3537357a717a6d347177636365746e66317962696962757a61643772726d7979000010000100001c20002e2d763d303b766d3d6b303b617574683d6b303b61736d3d6b303b64656c3d6b303b696e763d6b303b7376633d7330'; void main() { - final MockHttpClient mockClient = MockHttpClient(); - final MockHttpRequest request = MockHttpRequest(); - final MockHttpResponse response = MockHttpResponse(); - - setUpAll(() { - registerFallbackValue(Uri()); - }); + late MockHttpClient mockHttpClient; setUp(() { - reset(mockClient); - reset(request); - reset(response); + mockHttpClient = MockHttpClient(); }); group('DidDht', () { @@ -53,13 +42,38 @@ void main() { }); test('should resolve with didDocument if legit', () async { + when( + () => mockHttpClient.get( + Uri.parse( + 'https://diddht.tbddev.org/3whftgpbdjihx9ze9tdn575zqzm4qwccetnf1ybiibuzad7rrmyy', + ), + ), + ).thenAnswer( + (_) async => + http.Response(String.fromCharCodes(hex.decode(testVector)), 200), + ); + final did = Did.parse( 'did:dht:3whftgpbdjihx9ze9tdn575zqzm4qwccetnf1ybiibuzad7rrmyy', ); - final resolutionResult = await DidDht.resolve(did); + final resolutionResult = await DidDht.resolve( + did, + client: mockHttpClient, + ); - expect(resolutionResult.didResolutionMetadata.isEmpty(), isTrue); expect(resolutionResult.didDocument, isNotNull); + expect( + 'did:dht:3whftgpbdjihx9ze9tdn575zqzm4qwccetnf1ybiibuzad7rrmyy', + resolutionResult.didDocument?.id, + ); + + verify( + () => mockHttpClient.get( + Uri.parse( + 'https://diddht.tbddev.org/3whftgpbdjihx9ze9tdn575zqzm4qwccetnf1ybiibuzad7rrmyy', + ), + ), + ).called(1); }); }); } diff --git a/packages/web5/test/dids/did_web_test.dart b/packages/web5/test/dids/did_web_test.dart index eb8d002..4c83df5 100644 --- a/packages/web5/test/dids/did_web_test.dart +++ b/packages/web5/test/dids/did_web_test.dart @@ -1,15 +1,9 @@ -import 'dart:convert'; -import 'dart:io'; - +import 'package:http/http.dart' as http; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; import 'package:web5/web5.dart'; -class MockHttpClient extends Mock implements HttpClient {} - -class MockHttpRequest extends Mock implements HttpClientRequest {} - -class MockHttpResponse extends Mock implements HttpClientResponse {} +import '../helpers/mocks.dart'; const validDidWebDocument = '''{ "id": "did:web:www.linkedin.com", @@ -53,18 +47,10 @@ const validDidWebDocument = '''{ }'''; void main() { - final MockHttpClient mockClient = MockHttpClient(); - final MockHttpRequest request = MockHttpRequest(); - final MockHttpResponse response = MockHttpResponse(); - - setUpAll(() { - registerFallbackValue(Uri()); - }); + late MockHttpClient mockHttpClient; setUp(() { - reset(mockClient); - reset(request); - reset(response); + mockHttpClient = MockHttpClient(); }); group('DidWeb', () { @@ -88,62 +74,58 @@ void main() { }); test('should return did with failed http request', () async { - when(() => response.statusCode).thenReturn(400); - when(() => request.close()).thenAnswer((_) async => response); - when(() => mockClient.getUrl(any())).thenAnswer((_) async => request); + when( + () => mockHttpClient + .get(Uri.parse('https://www.linkedin.com/.well-known/did.json')), + ).thenAnswer((_) async => http.Response('', 400)); final did = Did.parse('did:web:www.linkedin.com'); - final result = await DidWeb.resolve(did, client: mockClient); + final result = await DidWeb.resolve(did, client: mockHttpClient); expect( result, DidResolutionResult.withError(DidResolutionError.notFound), ); + + verify( + () => mockHttpClient + .get(Uri.parse('https://www.linkedin.com/.well-known/did.json')), + ).called(1); }); test('should resolve successfully', () async { - when(() => response.statusCode).thenReturn(200); - when(() => response.transform(utf8.decoder)) - .thenAnswer((_) => Stream.value(validDidWebDocument)); - when(() => request.close()).thenAnswer((_) async => response); when( - () => mockClient.getUrl( - Uri.parse('https://www.linkedin.com/.well-known/did.json'), - ), - ).thenAnswer((_) async => request); + () => mockHttpClient + .get(Uri.parse('https://www.linkedin.com/.well-known/did.json')), + ).thenAnswer((_) async => http.Response(validDidWebDocument, 200)); final did = Did.parse('did:web:www.linkedin.com'); - final result = await DidWeb.resolve(did, client: mockClient); + final result = await DidWeb.resolve(did, client: mockHttpClient); expect(result.didDocument, isNotNull); - expect('did:web:www.linkedin.com', result.didDocument!.id); + expect('did:web:www.linkedin.com', result.didDocument?.id); verify( - () => mockClient - .getUrl(Uri.parse('https://www.linkedin.com/.well-known/did.json')), - ); + () => mockHttpClient + .get(Uri.parse('https://www.linkedin.com/.well-known/did.json')), + ).called(1); }); test('should resolve successfully with paths', () async { - when(() => response.statusCode).thenReturn(200); - when(() => response.transform(utf8.decoder)) - .thenAnswer((_) => Stream.value(validDidWebDocument)); - when(() => request.close()).thenAnswer((_) async => response); when( - () => mockClient.getUrl( - Uri.parse('https://www.remotehost.com:8892/ingress/did.json'), - ), - ).thenAnswer((_) async => request); + () => mockHttpClient + .get(Uri.parse('https://www.remotehost.com:8892/ingress/did.json')), + ).thenAnswer((_) async => http.Response(validDidWebDocument, 200)); final did = Did.parse('did:web:www.remotehost.com%3A8892:ingress'); final result = await DidWeb.resolve( did, - client: mockClient, + client: mockHttpClient, ); expect(result.didDocument, isNotNull); verify( - () => mockClient.getUrl( + () => mockHttpClient.get( Uri.parse('https://www.remotehost.com:8892/ingress/did.json'), ), ); diff --git a/packages/web5/test/helpers/mocks.dart b/packages/web5/test/helpers/mocks.dart new file mode 100644 index 0000000..254543e --- /dev/null +++ b/packages/web5/test/helpers/mocks.dart @@ -0,0 +1,4 @@ +import 'package:http/http.dart' as http; +import 'package:mocktail/mocktail.dart'; + +class MockHttpClient extends Mock implements http.Client {}