diff --git a/packages/celest_auth/example/celest/lib/client.dart b/packages/celest_auth/example/celest/lib/client.dart index fca02407..b668ada0 100644 --- a/packages/celest_auth/example/celest/lib/client.dart +++ b/packages/celest_auth/example/celest/lib/client.dart @@ -6,6 +6,7 @@ library; // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:io' as _$io; +import 'package:celest_core/celest_core.dart'; import 'package:celest_core/src/util/globals.dart'; import 'package:http/http.dart' as _$http; @@ -25,21 +26,19 @@ enum CelestEnvironment { }; } -class Celest { +class Celest with CelestBase { var _initialized = false; late CelestEnvironment _currentEnvironment; + @override late _$http.Client httpClient = _$http.Client(); late Uri _baseUri; final _functions = CelestFunctions(); - late final CelestAuth _auth = CelestAuth( - baseUri: _baseUri, - httpClient: httpClient, - ); + late final CelestAuth _auth = CelestAuth(this); T _checkInitialized(T Function() value) { if (!_initialized) { @@ -52,6 +51,7 @@ class Celest { CelestEnvironment get currentEnvironment => _checkInitialized(() => _currentEnvironment); + @override Uri get baseUri => _checkInitialized(() => _baseUri); CelestFunctions get functions => _checkInitialized(() => _functions); @@ -59,6 +59,9 @@ class Celest { CelestAuth get auth => _checkInitialized(() => _auth); void init({CelestEnvironment environment = CelestEnvironment.local}) { + if (environment != _currentEnvironment) { + _auth.signOut(); + } _currentEnvironment = environment; _baseUri = environment.baseUri; _auth.init(); diff --git a/packages/celest_auth/example/celest/lib/src/client/auth.dart b/packages/celest_auth/example/celest/lib/src/client/auth.dart index 850f36bf..a8573c92 100644 --- a/packages/celest_auth/example/celest/lib/src/client/auth.dart +++ b/packages/celest_auth/example/celest/lib/src/client/auth.dart @@ -6,28 +6,10 @@ library; // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:celest_auth/src/auth.dart' as _$auth; import 'package:celest_auth/src/flows/email_flow.dart' as _$email_flow; -import 'package:celest_auth/src/state/auth_state.dart' as _$auth_state; -import 'package:http/http.dart' as _$http; +import 'package:celest_core/celest_core.dart'; -class CelestAuth implements _$auth.Auth { - CelestAuth({ - required Uri baseUri, - required _$http.Client httpClient, - }) : _hub = _$auth.AuthImpl( - baseUri: baseUri, - httpClient: httpClient, - ); +extension type CelestAuth._(_$auth.AuthImpl _hub) implements _$auth.Auth { + CelestAuth(CelestBase celest) : _hub = _$auth.AuthImpl(celest); - final _$auth.AuthImpl _hub; - - late final _$email_flow.Email email = _$email_flow.Email(_hub); - - @override - void init() => _hub.init(); - - @override - _$auth_state.AuthState get authState => _hub.authState; - - @override - Stream<_$auth_state.AuthState> get authStateChanges => _hub.authStateChanges; + _$email_flow.Email get email => _$email_flow.Email(_hub); } diff --git a/packages/celest_auth/lib/src/auth.dart b/packages/celest_auth/lib/src/auth.dart index ccdcf377..a90f532c 100644 --- a/packages/celest_auth/lib/src/auth.dart +++ b/packages/celest_auth/lib/src/auth.dart @@ -3,13 +3,11 @@ import 'dart:async'; import 'package:celest_auth/src/flows/auth_flow.dart'; import 'package:celest_auth/src/platform/auth_platform.dart'; import 'package:celest_auth/src/state/auth_state.dart'; -// ignore: implementation_imports +import 'package:celest_core/celest_core.dart'; import 'package:celest_core/src/auth/auth_client.dart'; -// ignore: implementation_imports import 'package:celest_core/src/storage/local/local_storage.dart'; -// ignore: implementation_imports import 'package:celest_core/src/storage/secure/secure_storage.dart'; -import 'package:http/http.dart' as http; +import 'package:stream_transform/stream_transform.dart'; /// Coordinates and delegates to the various [AuthFlow] to orchestrate /// authentication activities. @@ -17,19 +15,21 @@ import 'package:http/http.dart' as http; /// Generated Celest clients extend this class and mix in the various /// [AuthFlow]s supported by the backend. abstract interface class Auth { - /// Initializes Celest Auth. + /// **NOTE**: Must be called before any other getters or methods are accessed. /// - /// Must be called before any other getters or methods are accessed. - void init(); + /// Initializes Celest Auth, returning the initial [AuthState]. + Future init(); AuthState get authState; Stream get authStateChanges; + + /// Signs out the current user, if any. + void signOut(); } final class AuthImpl implements Auth { - AuthImpl({ - required this.baseUri, - required this.httpClient, + AuthImpl( + this.celest, { LocalStorage? localStorage, SecureStorage? secureStorage, }) : localStorage = localStorage ?? LocalStorage(), @@ -41,20 +41,27 @@ final class AuthImpl implements Auth { const Unauthenticated(); // TODO(dnys1): const AuthInitializing(); @override - Stream get authStateChanges => _authStateController.stream; + Stream get authStateChanges => + _authStateController.stream.startWith(_authState); final StreamController _authStateController = - StreamController.broadcast(sync: true); + StreamController.broadcast(); late final StreamSubscription _authStateSubscription; StreamSubscription? _authFlowSubscription; @override - void init() { - _authStateSubscription = - authStateChanges.listen((state) => _authState = state); + Future init() { + return _init ??= Future.sync(() { + _authStateSubscription = + _authStateController.stream.listen((state) => _authState = state); + // TODO(dnys1): Proper init + return authStateChanges.first; + }); } + Future? _init; + Future> requestFlow() async { switch (_authState) { @@ -70,9 +77,10 @@ final class AuthImpl implements Auth { 'User is already authenticated. Sign out before continuing.', ); case Unauthenticated() || NeedsReauthentication(): - await _authFlowSubscription?.cancel(); + unawaited(_authFlowSubscription?.cancel()); final previousState = _authState; final controller = StreamController( + sync: true, onCancel: () => _authStateController.add(previousState), ); _authFlowSubscription = controller.stream.listen( @@ -87,16 +95,18 @@ final class AuthImpl implements Auth { } } - final Uri baseUri; - final http.Client httpClient; + @override + void signOut() { + localStorage.delete('userId'); + secureStorage.delete('cork'); + _authStateController.add(const Unauthenticated()); + } + final CelestBase celest; final LocalStorage localStorage; final SecureStorage secureStorage; - late final AuthClient protocol = AuthClient( - baseUri: baseUri, - httpClient: httpClient, - ); + late final AuthClient protocol = AuthClient(celest); late final AuthPlatform platform = AuthPlatform(protocol: protocol); Future close() async { diff --git a/packages/celest_auth/lib/src/flows/email_flow.dart b/packages/celest_auth/lib/src/flows/email_flow.dart index 30327551..a46e5c52 100644 --- a/packages/celest_auth/lib/src/flows/email_flow.dart +++ b/packages/celest_auth/lib/src/flows/email_flow.dart @@ -8,11 +8,7 @@ import 'package:celest_core/src/auth/auth_protocol.dart'; import 'package:celest_core/src/auth/otp/otp_types.dart'; import 'package:state_notifier/state_notifier.dart'; -final class Email { - Email(this._hub); - - final AuthImpl _hub; - +extension type Email(AuthImpl _hub) { Future signUp({ required String email, }) async { diff --git a/packages/celest_auth/pubspec.yaml b/packages/celest_auth/pubspec.yaml index a39033b8..572a8f97 100644 --- a/packages/celest_auth/pubspec.yaml +++ b/packages/celest_auth/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: shelf: ^1.4.1 shelf_router: ^1.1.4 state_notifier: ^1.0.0 + stream_transform: ^2.1.0 web: ^0.5.0 dev_dependencies: diff --git a/packages/celest_core/lib/celest_core.dart b/packages/celest_core/lib/celest_core.dart index 08c55a9b..61d480d5 100644 --- a/packages/celest_core/lib/celest_core.dart +++ b/packages/celest_core/lib/celest_core.dart @@ -5,6 +5,9 @@ library; export 'src/auth/auth_exception.dart'; export 'src/auth/user.dart'; +/// Base +export 'src/base/celest_base.dart'; + /// Exceptions export 'src/exception/celest_exception.dart'; export 'src/exception/cloud_exception.dart'; diff --git a/packages/celest_core/lib/src/auth/auth_client.dart b/packages/celest_core/lib/src/auth/auth_client.dart index f8b555d5..0a88bbaf 100644 --- a/packages/celest_core/lib/src/auth/auth_client.dart +++ b/packages/celest_core/lib/src/auth/auth_client.dart @@ -3,41 +3,24 @@ import 'package:celest_core/src/auth/auth_protocol.dart'; import 'package:celest_core/src/auth/otp/otp_types.dart'; import 'package:celest_core/src/auth/passkeys/passkey_types.dart'; import 'package:celest_core/src/base/base_protocol.dart'; -import 'package:http/http.dart' as http; final class AuthClient implements AuthProtocol { - AuthClient({ - required this.baseUri, - http.Client? httpClient, - }) : _client = httpClient ?? http.Client(); + AuthClient(this.celest); - final http.Client _client; - final Uri baseUri; + final CelestBase celest; @override - late final PasskeyClient passkeys = PasskeyClient( - baseUri: baseUri, - httpClient: _client, - ); + late final PasskeyClient passkeys = PasskeyClient(celest); @override - late final EmailClient email = EmailClient( - baseUri: baseUri, - httpClient: _client, - ); + late final EmailClient email = EmailClient(celest); } final class PasskeyClient with BaseProtocol implements PasskeyProtocol { - PasskeyClient({ - required this.baseUri, - required this.httpClient, - }); + PasskeyClient(this.celest); @override - final http.Client httpClient; - - @override - final Uri baseUri; + final CelestBase celest; @override Future authenticate({ @@ -63,16 +46,10 @@ final class PasskeyClient with BaseProtocol implements PasskeyProtocol { } final class EmailClient with BaseProtocol implements EmailProtocol { - EmailClient({ - required this.baseUri, - required this.httpClient, - }); - - @override - final http.Client httpClient; + EmailClient(this.celest); @override - final Uri baseUri; + final CelestBase celest; @override Future sendOtp({ diff --git a/packages/celest_core/lib/src/base/base_protocol.dart b/packages/celest_core/lib/src/base/base_protocol.dart index 647c5dff..7338ff19 100644 --- a/packages/celest_core/lib/src/base/base_protocol.dart +++ b/packages/celest_core/lib/src/base/base_protocol.dart @@ -1,17 +1,17 @@ import 'dart:convert'; +import 'package:celest_core/src/base/celest_base.dart'; import 'package:http/http.dart' as http; mixin BaseProtocol { - http.Client get httpClient; - Uri get baseUri; + CelestBase get celest; Future> postJson( String path, Map json, ) async { - final uri = baseUri.resolve(path); - final resp = await httpClient.post( + final uri = celest.baseUri.resolve(path); + final resp = await celest.httpClient.post( uri, body: jsonEncode(json), headers: { diff --git a/packages/celest_core/lib/src/base/celest_base.dart b/packages/celest_core/lib/src/base/celest_base.dart new file mode 100644 index 00000000..dc6d5430 --- /dev/null +++ b/packages/celest_core/lib/src/base/celest_base.dart @@ -0,0 +1,6 @@ +import 'package:http/http.dart' as http; + +abstract mixin class CelestBase { + http.Client get httpClient; + Uri get baseUri; +}