From 8533c1b7e710c7cff584b5559d390188cf0528f4 Mon Sep 17 00:00:00 2001 From: Colin Ihlenfeldt Date: Tue, 19 May 2026 11:27:44 +0200 Subject: [PATCH] fix: await useAuthEmulator before completing Yust.initialize FirebaseAuth.useAuthEmulator(host, port) returns a Future. On web the underlying JS connectAuthEmulator does not take effect synchronously, so any auth call (e.g. signInWithMicrosoft) started before that future resolves hits production endpoints instead of the emulator. Expose a `ready` future on the auth services and await it from Yust.initialize so callers can rely on the emulator being fully wired when initialization returns. The Dart-only and mocked variants get a no-op `ready` to keep the interface symmetric. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/src/services/yust_auth_service_dart.dart | 5 +++++ .../services/yust_auth_service_flutter.dart | 18 ++++++++++++------ lib/src/services/yust_auth_service_mocked.dart | 3 +++ lib/src/yust.dart | 3 +++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/src/services/yust_auth_service_dart.dart b/lib/src/services/yust_auth_service_dart.dart index f5872549..559543a5 100644 --- a/lib/src/services/yust_auth_service_dart.dart +++ b/lib/src/services/yust_auth_service_dart.dart @@ -20,6 +20,11 @@ class YustAuthService { final Yust _yust; final ServiceAccountCredentials? _credentials; + /// Resolves once the service is ready. On Dart there is nothing async to + /// wait for, so this is always already completed — kept here to match the + /// Flutter-side API used in [Yust.initialize]. + final Future ready = Future.value(); + /// The Firebase Auth ID (uid) used to generate tokens for backend-to-backend /// API calls. When set, [getJWTToken] signs in as this user via a custom /// token exchange instead of throwing [UnsupportedError]. diff --git a/lib/src/services/yust_auth_service_flutter.dart b/lib/src/services/yust_auth_service_flutter.dart index a05d7615..1210b533 100644 --- a/lib/src/services/yust_auth_service_flutter.dart +++ b/lib/src/services/yust_auth_service_flutter.dart @@ -15,19 +15,25 @@ class YustAuthService { late final FirebaseAuth _fireAuth; late final Yust _yust; + /// Resolves once any emulator wiring has been applied. Callers that perform + /// auth operations during initialization must await this — `useAuthEmulator` + /// returns a Future that the constructor cannot await, and on web the + /// underlying JS `connectAuthEmulator` does not take effect synchronously. + final Future ready; + YustAuthService( Yust yust, { String? emulatorAddress, ServiceAccountCredentials? credentials, String? backendAuthId, - }) : _fireAuth = FirebaseAuth.instance, - _yust = yust { - if (emulatorAddress != null) { - _fireAuth.useAuthEmulator(emulatorAddress, 9099); - } + }) : ready = emulatorAddress != null + ? FirebaseAuth.instance.useAuthEmulator(emulatorAddress, 9099) + : Future.value() { + _fireAuth = FirebaseAuth.instance; + _yust = yust; } - YustAuthService.mocked(Yust yust) { + YustAuthService.mocked(Yust yust) : ready = Future.value() { throw UnsupportedError('Not supported in Flutter Environment'); } diff --git a/lib/src/services/yust_auth_service_mocked.dart b/lib/src/services/yust_auth_service_mocked.dart index b568b3d8..e8627693 100644 --- a/lib/src/services/yust_auth_service_mocked.dart +++ b/lib/src/services/yust_auth_service_mocked.dart @@ -7,6 +7,9 @@ import 'yust_auth_service.dart'; class YustAuthServiceMocked implements YustAuthService { final Yust _yust; + @override + final Future ready = Future.value(); + YustAuthServiceMocked(Yust yust) : _yust = yust; @override diff --git a/lib/src/yust.dart b/lib/src/yust.dart index ba909e48..65629209 100644 --- a/lib/src/yust.dart +++ b/lib/src/yust.dart @@ -190,6 +190,9 @@ class Yust { credentials: credentials, backendAuthId: backendAuthId, ); + // On web, `useAuthEmulator` is async — sign-ins started before it + // resolves will hit production endpoints. + await Yust.authService.ready; Yust.fileService = YustFileService( authClient: authClient, emulatorAddress: emulatorAddress,