diff --git a/packages/shorebird_cli/lib/src/auth/auth.dart b/packages/shorebird_cli/lib/src/auth/auth.dart index 4f9c0837..c253a5f2 100644 --- a/packages/shorebird_cli/lib/src/auth/auth.dart +++ b/packages/shorebird_cli/lib/src/auth/auth.dart @@ -52,14 +52,9 @@ class Auth { final shorebirdConfigDir = _shorebirdConfigDir; if (shorebirdConfigDir == null) return; - final sessionFile = File(p.join(shorebirdConfigDir, _sessionFileName)) - ..createSync(recursive: true); - - if (!sessionFile.existsSync()) { - sessionFile.createSync(recursive: true); - } - - sessionFile.writeAsStringSync(json.encode(session.toJson())); + File(p.join(shorebirdConfigDir, _sessionFileName)) + ..createSync(recursive: true) + ..writeAsStringSync(json.encode(session.toJson())); } void _clearSession() { diff --git a/packages/shorebird_code_push_api_client/example/main.dart b/packages/shorebird_code_push_api_client/example/main.dart new file mode 100644 index 00000000..384624bb --- /dev/null +++ b/packages/shorebird_code_push_api_client/example/main.dart @@ -0,0 +1,13 @@ +// ignore_for_file: unused_local_variable + +import 'package:shorebird_code_push_api_client/src/shorebird_code_push_api_client.dart'; + +Future main() async { + final client = ShorebirdCodePushApiClient(apiKey: ''); + + // Download the latest engine revision. + final engine = await client.downloadEngine('latest'); + + // Create a new release. + await client.createRelease('path/to/release/libapp.so'); +} diff --git a/packages/shorebird_code_push_api_client/lib/src/shorebird_code_push_api_client.dart b/packages/shorebird_code_push_api_client/lib/src/shorebird_code_push_api_client.dart index 522947bb..4aef682a 100644 --- a/packages/shorebird_code_push_api_client/lib/src/shorebird_code_push_api_client.dart +++ b/packages/shorebird_code_push_api_client/lib/src/shorebird_code_push_api_client.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:http/http.dart' as http; @@ -7,14 +8,21 @@ import 'package:http/http.dart' as http; /// {@endtemplate} class ShorebirdCodePushApiClient { /// {@macro shorebird_code_push_api_client} - ShorebirdCodePushApiClient({http.Client? httpClient, Uri? hostedUri}) - : _httpClient = httpClient ?? http.Client(), + ShorebirdCodePushApiClient({ + required String apiKey, + http.Client? httpClient, + Uri? hostedUri, + }) : _apiKey = apiKey, + _httpClient = httpClient ?? http.Client(), _hostedUri = hostedUri ?? Uri.https('shorebird-code-push-api-cypqazu4da-uc.a.run.app'); + final String _apiKey; final http.Client _httpClient; final Uri _hostedUri; + Map get _apiKeyHeader => {'x-api-key': _apiKey}; + /// Upload the artifact at [path] to the /// Shorebird CodePush API as a new release. Future createRelease(String path) async { @@ -24,10 +32,28 @@ class ShorebirdCodePushApiClient { ); final file = await http.MultipartFile.fromPath('file', path); request.files.add(file); + request.headers.addAll(_apiKeyHeader); final response = await _httpClient.send(request); if (response.statusCode != HttpStatus.created) { throw Exception('${response.statusCode} ${response.reasonPhrase}'); } } + + /// Download the specified revision of the shorebird engine. + Future downloadEngine(String revision) async { + final request = http.Request( + 'GET', + Uri.parse('$_hostedUri/api/v1/engines/$revision'), + ); + request.headers.addAll(_apiKeyHeader); + + final response = await _httpClient.send(request); + + if (response.statusCode != HttpStatus.ok) { + throw Exception('${response.statusCode} ${response.reasonPhrase}'); + } + + return response.stream.toBytes(); + } } diff --git a/packages/shorebird_code_push_api_client/test/src/shorebird_code_push_api_client_test.dart b/packages/shorebird_code_push_api_client/test/src/shorebird_code_push_api_client_test.dart index 1fcde73b..c589dd19 100644 --- a/packages/shorebird_code_push_api_client/test/src/shorebird_code_push_api_client_test.dart +++ b/packages/shorebird_code_push_api_client/test/src/shorebird_code_push_api_client_test.dart @@ -13,6 +13,8 @@ class _FakeBaseRequest extends Fake implements http.BaseRequest {} void main() { group('ShorebirdCodePushApiClient', () { + const apiKey = 'api-key'; + late http.Client httpClient; late ShorebirdCodePushApiClient shorebirdCodePushApiClient; @@ -23,12 +25,13 @@ void main() { setUp(() { httpClient = _MockHttpClient(); shorebirdCodePushApiClient = ShorebirdCodePushApiClient( + apiKey: apiKey, httpClient: httpClient, ); }); test('can be instantiated', () { - expect(ShorebirdCodePushApiClient(), isNotNull); + expect(ShorebirdCodePushApiClient(apiKey: apiKey), isNotNull); }); group('createRelease', () { @@ -71,5 +74,44 @@ void main() { ); }); }); + + group('downloadEngine', () { + const engineRevision = 'engine-revision'; + test('throws an exception if the http request fails', () async { + when(() => httpClient.send(any())).thenAnswer((_) async { + return http.StreamedResponse( + Stream.empty(), + 400, + ); + }); + + expect( + shorebirdCodePushApiClient.downloadEngine(engineRevision), + throwsA(isA()), + ); + }); + + test('sends a request to the correct url', () async { + when(() => httpClient.send(any())).thenAnswer((_) async { + return http.StreamedResponse( + Stream.empty(), + HttpStatus.ok, + ); + }); + + await shorebirdCodePushApiClient.downloadEngine(engineRevision); + + final request = verify(() => httpClient.send(captureAny())) + .captured + .single as http.Request; + + expect( + request.url, + Uri.parse( + 'https://shorebird-code-push-api-cypqazu4da-uc.a.run.app/api/v1/engines/$engineRevision', + ), + ); + }); + }); }); }