From dba6f3e2557c431cfbeffb00a77da4f651738749 Mon Sep 17 00:00:00 2001 From: Vladimir Babin Date: Tue, 22 Sep 2026 18:46:04 +0300 Subject: [PATCH 1/2] fix(auth,web): fail onWaitInitState instead of hanging when the first auth state is an error When the JS SDK's first onAuthStateChanged callback fires with an error (e.g. IndexedDB _openDb rejection at page load), onWaitInitState never completed its completer and dereferenced a null _changeController, so Firebase.initializeApp() hung forever and the app stayed on a blank page. Complete the completer with the error, unsubscribe in either case, and surface it from registerWith as a FirebaseAuthException via guardAuthExceptions. Fixes #18703 --- .../lib/firebase_auth_web.dart | 5 +- .../lib/src/interop/auth.dart | 20 ++++-- .../test/on_wait_init_state_test.dart | 65 +++++++++++++++++++ 3 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 packages/firebase_auth/firebase_auth_web/test/on_wait_init_state_test.dart diff --git a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart index e864cde65f89..e2651d89c78d 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart @@ -84,7 +84,10 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { } } } - await authDelegate.onWaitInitState(); + await guardAuthExceptions( + authDelegate.onWaitInitState, + auth: authDelegate, + ); }, ); FirebaseAuthPlatform.instance = FirebaseAuthWeb.instance; diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart index 7e14c3d0981e..449002e7f6f7 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart @@ -383,19 +383,29 @@ class Auth extends JsObjectWrapper { /// To preserve behavior on web and mobile we store the initial user /// in `_initUser` and add it manually to the `_changeController`. Future onWaitInitState() async { - final completer = Completer(); + final completer = Completer(); final nextWrapper = (auth_interop.UserJsImpl? user) { _initUser = User.getInstance(user); - completer.complete(); + if (!completer.isCompleted) { + completer.complete(); + } }; - final errorWrapper = (JSAny e) => _changeController!.addError(e); + final errorWrapper = (JSAny e) { + _changeController?.addError(e); + if (!completer.isCompleted) { + completer.completeError(e); + } + }; final unsubscribe = jsObject.onAuthStateChanged(nextWrapper.toJS, errorWrapper.toJS); - await completer.future; - unsubscribe.callAsFunction(); + try { + await completer.future; + } finally { + unsubscribe.callAsFunction(); + } } JSFunction? _onAuthUnsubscribe; diff --git a/packages/firebase_auth/firebase_auth_web/test/on_wait_init_state_test.dart b/packages/firebase_auth/firebase_auth_web/test/on_wait_init_state_test.dart new file mode 100644 index 000000000000..59bdb0fa7be9 --- /dev/null +++ b/packages/firebase_auth/firebase_auth_web/test/on_wait_init_state_test.dart @@ -0,0 +1,65 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('browser') +library; + +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'package:firebase_auth_web/src/interop/auth.dart' as auth_interop; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + /// Minimal stand-in for the JS `Auth` object: `onAuthStateChanged` invokes + /// either the next or the error callback once, and records unsubscribe. + ({auth_interop.Auth auth, bool Function() unsubscribed}) fakeAuth({ + JSObject? error, + }) { + var unsubscribed = false; + final jsAuth = JSObject(); + jsAuth['onAuthStateChanged'] = (JSFunction next, JSFunction? onError) { + if (error != null) { + onError!.callAsFunction(null, error); + } else { + // ignore: avoid_redundant_argument_values + next.callAsFunction(null, null); + } + return (() { + unsubscribed = true; + }).toJS; + }.toJS; + return ( + auth: auth_interop.Auth.getInstance(jsAuth as auth_interop.AuthJsImpl), + unsubscribed: () => unsubscribed, + ); + } + + test('onWaitInitState completes when the first auth state arrives', () async { + final fake = fakeAuth(); + + await fake.auth.onWaitInitState(); + + expect(fake.unsubscribed(), isTrue); + }); + + test( + 'onWaitInitState fails instead of hanging when the first auth state is an error', + () async { + final error = JSObject(); + error['code'] = 'auth/internal-error'.toJS; + final fake = fakeAuth(error: error); + + await expectLater( + fake.auth.onWaitInitState().timeout(const Duration(seconds: 2)), + throwsA( + predicate( + (e) => (e as JSObject)['code'].dartify() == 'auth/internal-error', + ), + ), + ); + expect(fake.unsubscribed(), isTrue); + }, + ); +} From 37e61e0ca19a4fe2269b69ed447324d89a3a8b70 Mon Sep 17 00:00:00 2001 From: Vladimir Babin Date: Fri, 25 Sep 2026 05:07:15 +0300 Subject: [PATCH 2/2] fix(auth,web): complete the init wait before forwarding the error --- .../firebase_auth/firebase_auth_web/lib/src/interop/auth.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart index 449002e7f6f7..00e3413cb6f3 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart @@ -392,10 +392,10 @@ class Auth extends JsObjectWrapper { }; final errorWrapper = (JSAny e) { - _changeController?.addError(e); if (!completer.isCompleted) { completer.completeError(e); } + _changeController?.addError(e); }; final unsubscribe =