Skip to content

Commit a76c886

Browse files
fix(auth, web): unsubscribe from stream handlers after "hot restart" (#12908)
1 parent daaef12 commit a76c886

File tree

1 file changed

+46
-12
lines changed
  • packages/firebase_auth/firebase_auth_web/lib/src/interop

1 file changed

+46
-12
lines changed

packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart

+46-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
import 'dart:async';
1010
import 'dart:js_interop';
11-
11+
import 'dart:js_interop_unsafe';
12+
import 'package:flutter/foundation.dart';
13+
import 'package:web/web.dart' as web;
1214
import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';
1315
import 'package:firebase_core_web/firebase_core_web_interop.dart';
1416
import 'package:http_parser/http_parser.dart';
@@ -382,8 +384,7 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
382384
jsObject.onAuthStateChanged(nextWrapper.toJS, errorWrapper.toJS);
383385

384386
await completer.future;
385-
386-
await (unsubscribe.callAsFunction() as JSPromise<JSAny?>?)?.toDart;
387+
unsubscribe.callAsFunction();
387388
}
388389

389390
JSFunction? _onAuthUnsubscribe;
@@ -394,13 +395,42 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
394395
// ignore: close_sinks
395396
StreamController<User?>? _changeController;
396397

398+
String get authStateWindowsKey => 'flutterfire-${app.name}_authStateChanges';
399+
String get idTokenStateWindowsKey => 'flutterfire-${app.name}_idTokenChanges';
400+
401+
// No way to unsubscribe from event listeners on hot reload so we set on the windows object
402+
// and clean up on hot restart if it exists.
403+
// See: https://github.com/firebase/flutterfire/issues/7064
404+
void _unsubscribeWindowsListener(String key) {
405+
if (kDebugMode) {
406+
final unsubscribe = web.window.getProperty(key.toJS);
407+
if (unsubscribe != null) {
408+
(unsubscribe as JSFunction).callAsFunction();
409+
}
410+
}
411+
}
412+
413+
void _setWindowsListener(String key, JSFunction unsubscribe) {
414+
if (kDebugMode) {
415+
web.window.setProperty(key.toJS, unsubscribe);
416+
}
417+
}
418+
419+
void _removeWindowsListener(String key) {
420+
if (kDebugMode) {
421+
web.window.delete(key.toJS);
422+
}
423+
}
424+
397425
/// Sends events when the users sign-in state changes.
398426
///
399427
/// After 4.0.0, this is only triggered on sign-in or sign-out.
400428
/// To keep the old behavior, see [onIdTokenChanged].
401429
///
402430
/// If the value is `null`, there is no signed-in user.
403431
Stream<User?> get onAuthStateChanged {
432+
_unsubscribeWindowsListener(authStateWindowsKey);
433+
404434
if (_changeController == null) {
405435
final nextWrapper = (auth_interop.UserJsImpl? user) {
406436
_changeController!.add(User.getInstance(user));
@@ -410,15 +440,17 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
410440

411441
void startListen() {
412442
assert(_onAuthUnsubscribe == null);
413-
_onAuthUnsubscribe =
443+
final unsubscribe =
414444
jsObject.onAuthStateChanged(nextWrapper.toJS, errorWrapper.toJS);
445+
_onAuthUnsubscribe = unsubscribe;
446+
_setWindowsListener(authStateWindowsKey, unsubscribe);
415447
}
416448

417-
Future<void> stopListen() async {
418-
await (_onAuthUnsubscribe!.callAsFunction() as JSPromise<JSAny?>?)
419-
?.toDart;
449+
void stopListen() {
450+
_onAuthUnsubscribe!.callAsFunction();
420451
_onAuthUnsubscribe = null;
421452
_changeController = null;
453+
_removeWindowsListener(authStateWindowsKey);
422454
}
423455

424456
_changeController = StreamController<User?>.broadcast(
@@ -444,6 +476,7 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
444476
///
445477
/// If the value is `null`, there is no signed-in user.
446478
Stream<User?> get onIdTokenChanged {
479+
_unsubscribeWindowsListener(idTokenStateWindowsKey);
447480
if (_idTokenChangedController == null) {
448481
final nextWrapper = (auth_interop.UserJsImpl? user) {
449482
_idTokenChangedController!.add(User.getInstance(user));
@@ -453,16 +486,17 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
453486

454487
void startListen() {
455488
assert(_onIdTokenChangedUnsubscribe == null);
456-
_onIdTokenChangedUnsubscribe =
489+
final unsubscribe =
457490
jsObject.onIdTokenChanged(nextWrapper.toJS, errorWrapper.toJS);
491+
_onIdTokenChangedUnsubscribe = unsubscribe;
492+
_setWindowsListener(idTokenStateWindowsKey, unsubscribe);
458493
}
459494

460-
Future<void> stopListen() async {
461-
await (_onIdTokenChangedUnsubscribe!.callAsFunction()
462-
as JSPromise<JSAny?>?)
463-
?.toDart;
495+
void stopListen() {
496+
_onIdTokenChangedUnsubscribe!.callAsFunction();
464497
_onIdTokenChangedUnsubscribe = null;
465498
_idTokenChangedController = null;
499+
_removeWindowsListener(idTokenStateWindowsKey);
466500
}
467501

468502
_idTokenChangedController = StreamController<User?>.broadcast(

0 commit comments

Comments
 (0)