Skip to content

Commit

Permalink
fix(auth, web): unsubscribe from stream handlers after "hot restart" (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
russellwheatley committed Jun 6, 2024
1 parent daaef12 commit a76c886
Showing 1 changed file with 46 additions and 12 deletions.
58 changes: 46 additions & 12 deletions packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

import 'dart:async';
import 'dart:js_interop';

import 'dart:js_interop_unsafe';
import 'package:flutter/foundation.dart';
import 'package:web/web.dart' as web;
import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart';
import 'package:firebase_core_web/firebase_core_web_interop.dart';
import 'package:http_parser/http_parser.dart';
Expand Down Expand Up @@ -382,8 +384,7 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
jsObject.onAuthStateChanged(nextWrapper.toJS, errorWrapper.toJS);

await completer.future;

await (unsubscribe.callAsFunction() as JSPromise<JSAny?>?)?.toDart;
unsubscribe.callAsFunction();
}

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

String get authStateWindowsKey => 'flutterfire-${app.name}_authStateChanges';
String get idTokenStateWindowsKey => 'flutterfire-${app.name}_idTokenChanges';

// No way to unsubscribe from event listeners on hot reload so we set on the windows object
// and clean up on hot restart if it exists.
// See: https://github.com/firebase/flutterfire/issues/7064
void _unsubscribeWindowsListener(String key) {
if (kDebugMode) {
final unsubscribe = web.window.getProperty(key.toJS);
if (unsubscribe != null) {
(unsubscribe as JSFunction).callAsFunction();
}
}
}

void _setWindowsListener(String key, JSFunction unsubscribe) {
if (kDebugMode) {
web.window.setProperty(key.toJS, unsubscribe);
}
}

void _removeWindowsListener(String key) {
if (kDebugMode) {
web.window.delete(key.toJS);
}
}

/// Sends events when the users sign-in state changes.
///
/// After 4.0.0, this is only triggered on sign-in or sign-out.
/// To keep the old behavior, see [onIdTokenChanged].
///
/// If the value is `null`, there is no signed-in user.
Stream<User?> get onAuthStateChanged {
_unsubscribeWindowsListener(authStateWindowsKey);

if (_changeController == null) {
final nextWrapper = (auth_interop.UserJsImpl? user) {
_changeController!.add(User.getInstance(user));
Expand All @@ -410,15 +440,17 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {

void startListen() {
assert(_onAuthUnsubscribe == null);
_onAuthUnsubscribe =
final unsubscribe =
jsObject.onAuthStateChanged(nextWrapper.toJS, errorWrapper.toJS);
_onAuthUnsubscribe = unsubscribe;
_setWindowsListener(authStateWindowsKey, unsubscribe);
}

Future<void> stopListen() async {
await (_onAuthUnsubscribe!.callAsFunction() as JSPromise<JSAny?>?)
?.toDart;
void stopListen() {
_onAuthUnsubscribe!.callAsFunction();
_onAuthUnsubscribe = null;
_changeController = null;
_removeWindowsListener(authStateWindowsKey);
}

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

void startListen() {
assert(_onIdTokenChangedUnsubscribe == null);
_onIdTokenChangedUnsubscribe =
final unsubscribe =
jsObject.onIdTokenChanged(nextWrapper.toJS, errorWrapper.toJS);
_onIdTokenChangedUnsubscribe = unsubscribe;
_setWindowsListener(idTokenStateWindowsKey, unsubscribe);
}

Future<void> stopListen() async {
await (_onIdTokenChangedUnsubscribe!.callAsFunction()
as JSPromise<JSAny?>?)
?.toDart;
void stopListen() {
_onIdTokenChangedUnsubscribe!.callAsFunction();
_onIdTokenChangedUnsubscribe = null;
_idTokenChangedController = null;
_removeWindowsListener(idTokenStateWindowsKey);
}

_idTokenChangedController = StreamController<User?>.broadcast(
Expand Down

0 comments on commit a76c886

Please sign in to comment.