88
99import 'dart:async' ;
1010import 'dart:js_interop' ;
11-
11+ import 'dart:js_interop_unsafe' ;
12+ import 'package:flutter/foundation.dart' ;
13+ import 'package:web/web.dart' as web;
1214import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart' ;
1315import 'package:firebase_core_web/firebase_core_web_interop.dart' ;
1416import '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