Skip to content

Commit

Permalink
fix(firebase_auth): pass Persistence value to `FirebaseAuth.instanc…
Browse files Browse the repository at this point in the history
…eFor(app: app, persistence: persistence)` for setting persistence on Web platform (#9138)

Co-authored-by: Mike Diarmid <mike.diarmid@gmail.com>
  • Loading branch information
russellwheatley and Salakar committed Jul 27, 2022
1 parent d8bf818 commit ae7ebaf
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 58 deletions.
11 changes: 6 additions & 5 deletions docs/auth/start.md
Expand Up @@ -211,13 +211,14 @@ On web platforms, the user's authentication state is stored in
[local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
If required, you can change this default behavior to only persist
authentication state for the current session, or not at all. To configure these
settings, call the `setPersistence()` method. (On native platforms, an
[`UnimplementedError`](https://api.flutter.dev/flutter/dart-core/UnimplementedError-class.html)
will be thrown.)
settings, call the following method `FirebaseAuth.instanceFor(app: Firebase.app(), persistence: Persistence.LOCAL);`.
You can still update the persistence for each Auth instance using `setPersistence(Persistence.NONE)`.

```dart
// Disable persistence on web platforms
await FirebaseAuth.instance.setPersistence(Persistence.NONE);
// Disable persistence on web platforms. Must be called on initialization:
final auth = FirebaseAuth.instanceFor(app: Firebase.app(), persistence: Persistence.NONE);
// To change it after initialization, use `setPersistence()`:
await auth.setPersistence(Persistence.LOCAL);
```

## Next Steps
Expand Down
1 change: 0 additions & 1 deletion packages/firebase_auth/firebase_auth/example/lib/main.dart
Expand Up @@ -14,7 +14,6 @@ bool shouldUseFirebaseEmulator = false;
// e.g via `melos run firebase:emulator`.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();

// We're using the manual installation on non-web platforms since Google sign in plugin doesn't yet support Dart initialization.
// See related issue: https://github.com/flutter/flutter/issues/96391
if (!kIsWeb) {
Expand Down
16 changes: 12 additions & 4 deletions packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart
Expand Up @@ -15,6 +15,10 @@ class FirebaseAuth extends FirebasePluginPlatform {
// instance with the default app before a user specifies an app.
FirebaseAuthPlatform? _delegatePackingProperty;

// To set "persistence" on web, it is now required on the v9.0.0 or above Firebase JS SDK to pass the value on calling `initializeAuth()`.
/// https://firebase.google.com/docs/reference/js/auth.md#initializeauth
Persistence? _persistence;

/// Returns the underlying delegate implementation.
///
/// If called and no [_delegatePackingProperty] exists, it will first be
Expand All @@ -23,15 +27,17 @@ class FirebaseAuth extends FirebasePluginPlatform {
_delegatePackingProperty ??= FirebaseAuthPlatform.instanceFor(
app: app,
pluginConstants: pluginConstants,
persistence: _persistence,
);
return _delegatePackingProperty!;
}

/// The [FirebaseApp] for this current Auth instance.
FirebaseApp app;

FirebaseAuth._({required this.app})
: super(app.name, 'plugins.flutter.io/firebase_auth');
FirebaseAuth._({required this.app, Persistence? persistence})
: _persistence = persistence,
super(app.name, 'plugins.flutter.io/firebase_auth');

/// Returns an instance using the default [FirebaseApp].
static FirebaseAuth get instance {
Expand All @@ -41,9 +47,11 @@ class FirebaseAuth extends FirebasePluginPlatform {
}

/// Returns an instance using a specified [FirebaseApp].
factory FirebaseAuth.instanceFor({required FirebaseApp app}) {
/// Note that persistence can only be used on Web and is not supported on other platforms.
factory FirebaseAuth.instanceFor(
{required FirebaseApp app, Persistence? persistence}) {
return _firebaseAuthInstances.putIfAbsent(app.name, () {
return FirebaseAuth._(app: app);
return FirebaseAuth._(app: app, persistence: persistence);
});
}

Expand Down
Expand Up @@ -728,7 +728,8 @@ class MockFirebaseAuth extends Mock
}

@override
FirebaseAuthPlatform delegateFor({FirebaseApp? app}) {
FirebaseAuthPlatform delegateFor(
{FirebaseApp? app, Persistence? persistence}) {
return super.noSuchMethod(
Invocation.method(#delegateFor, [], {#app: app}),
returnValue: TestFirebaseAuthPlatform(),
Expand Down Expand Up @@ -1024,7 +1025,8 @@ class FakeFirebaseAuthPlatform extends Fake
String? tenantId;

@override
FirebaseAuthPlatform delegateFor({required FirebaseApp app}) {
FirebaseAuthPlatform delegateFor(
{required FirebaseApp app, Persistence? persistence}) {
return this;
}

Expand Down Expand Up @@ -1085,7 +1087,8 @@ class TestFirebaseAuthPlatform extends FirebaseAuthPlatform {
}) {}

@override
FirebaseAuthPlatform delegateFor({FirebaseApp? app}) {
FirebaseAuthPlatform delegateFor(
{FirebaseApp? app, Persistence? persistence}) {
return this;
}

Expand Down
7 changes: 5 additions & 2 deletions packages/firebase_auth/firebase_auth/test/user_test.dart
Expand Up @@ -374,7 +374,8 @@ class MockFirebaseAuth extends Mock
}

@override
FirebaseAuthPlatform delegateFor({FirebaseApp? app}) {
FirebaseAuthPlatform delegateFor(
{FirebaseApp? app, Persistence? persistence}) {
return super.noSuchMethod(
Invocation.method(#delegateFor, const [], {#app: app}),
returnValue: TestFirebaseAuthPlatform(),
Expand Down Expand Up @@ -555,7 +556,9 @@ class TestFirebaseAuthPlatform extends FirebaseAuthPlatform {
TestFirebaseAuthPlatform() : super();

@override
FirebaseAuthPlatform delegateFor({FirebaseApp? app}) => this;
FirebaseAuthPlatform delegateFor(
{FirebaseApp? app, Persistence? persistence}) =>
this;

@override
FirebaseAuthPlatform setInitialValues({
Expand Down
Expand Up @@ -187,7 +187,8 @@ class MethodChannelFirebaseAuth extends FirebaseAuthPlatform {
///
/// Instances are cached and reused for incoming event handlers.
@override
FirebaseAuthPlatform delegateFor({required FirebaseApp app}) {
FirebaseAuthPlatform delegateFor(
{required FirebaseApp app, Persistence? persistence}) {
return methodChannelFirebaseAuthInstances.putIfAbsent(app.name, () {
return MethodChannelFirebaseAuth(app: app);
});
Expand Down
Expand Up @@ -48,15 +48,18 @@ abstract class FirebaseAuthPlatform extends PlatformInterface {
static final Object _token = Object();

/// Create an instance using [app] using the existing implementation
factory FirebaseAuthPlatform.instanceFor({
required FirebaseApp app,
required Map<dynamic, dynamic> pluginConstants,
}) {
return FirebaseAuthPlatform.instance.delegateFor(app: app).setInitialValues(
languageCode: pluginConstants['APP_LANGUAGE_CODE'],
currentUser: pluginConstants['APP_CURRENT_USER'] == null
? null
: Map<String, dynamic>.from(pluginConstants['APP_CURRENT_USER']));
factory FirebaseAuthPlatform.instanceFor(
{required FirebaseApp app,
required Map<dynamic, dynamic> pluginConstants,
Persistence? persistence}) {
return FirebaseAuthPlatform.instance
.delegateFor(app: app, persistence: persistence)
.setInitialValues(
languageCode: pluginConstants['APP_LANGUAGE_CODE'],
currentUser: pluginConstants['APP_CURRENT_USER'] == null
? null
: Map<String, dynamic>.from(
pluginConstants['APP_CURRENT_USER']));
}

/// The current default [FirebaseAuthPlatform] instance.
Expand All @@ -79,7 +82,8 @@ abstract class FirebaseAuthPlatform extends PlatformInterface {
/// Enables delegates to create new instances of themselves if a none default
/// [FirebaseApp] instance is required by the user.
@protected
FirebaseAuthPlatform delegateFor({required FirebaseApp app}) {
FirebaseAuthPlatform delegateFor(
{required FirebaseApp app, Persistence? persistence}) {
throw UnimplementedError('delegateFor() is not implemented');
}

Expand Down
Expand Up @@ -32,8 +32,14 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {

Completer<void> _initialized = Completer();

// To set "persistence" on web, it is now required on the v9.0.0 or above Firebase JS SDK to pass the value on calling `initializeAuth()`.
// https://firebase.google.com/docs/reference/js/auth.md#initializeauth
Persistence? _persistence;

/// The entry point for the [FirebaseAuthWeb] class.
FirebaseAuthWeb({required FirebaseApp app}) : super(appInstance: app) {
FirebaseAuthWeb({required FirebaseApp app, Persistence? persistence})
: super(appInstance: app) {
_persistence = persistence;
// Create a app instance broadcast stream for both delegate listener events
_userChangesListeners[app.name] =
StreamController<UserPlatform?>.broadcast();
Expand Down Expand Up @@ -100,13 +106,16 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform {
auth_interop.Auth? _webAuth;

auth_interop.Auth get delegate {
return _webAuth ??=
auth_interop.getAuthInstance(core_interop.app(app.name));
_webAuth ??= auth_interop.getAuthInstance(core_interop.app(app.name),
persistence: _persistence);

return _webAuth!;
}

@override
FirebaseAuthPlatform delegateFor({required FirebaseApp app}) {
return FirebaseAuthWeb(app: app);
FirebaseAuthPlatform delegateFor(
{required FirebaseApp app, Persistence? persistence}) {
return FirebaseAuthWeb(app: app, persistence: persistence);
}

@override
Expand Down
73 changes: 48 additions & 25 deletions packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart
Expand Up @@ -20,11 +20,34 @@ import 'utils/utils.dart';
export 'auth_interop.dart';

/// Given an AppJSImp, return the Auth instance.
Auth getAuthInstance(App app) {
Auth getAuthInstance(App app, {Persistence? persistence}) {
if (persistence != null) {
auth_interop.Persistence setPersistence;
switch (persistence) {
case Persistence.LOCAL:
setPersistence = auth_interop.browserLocalPersistence;
break;
case Persistence.SESSION:
setPersistence = auth_interop.browserSessionPersistence;
break;
case Persistence.NONE:
setPersistence = auth_interop.inMemoryPersistence;
break;
}
return Auth.getInstance(auth_interop.initializeAuth(
app.jsObject,
jsify({
'errorMap': auth_interop.debugErrorMap,
'persistence': setPersistence,
'popupRedirectResolver': auth_interop.browserPopupRedirectResolver
})));
}
// `browserLocalPersistence` is the default persistence setting.
return Auth.getInstance(auth_interop.initializeAuth(
app.jsObject,
jsify({
'errorMap': auth_interop.debugErrorMap,
'persistence': auth_interop.browserLocalPersistence,
'popupRedirectResolver': auth_interop.browserPopupRedirectResolver
})));
}
Expand Down Expand Up @@ -497,30 +520,6 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
handleThenable(auth_interop.sendSignInLinkToEmail(
jsObject, email, actionCodeSettings));

/// Sends a password reset e-mail to the given [email].
/// To confirm password reset, use the [Auth.confirmPasswordReset].
///
/// The optional parameter [actionCodeSettings] is the action code settings.
/// If specified, the state/continue URL will be set as the 'continueUrl'
/// parameter in the password reset link.
/// The default password reset landing page will use this to display
/// a link to go back to the app if it is installed.
///
/// If the [actionCodeSettings] is not specified, no URL is appended to the
/// action URL. The state URL provided must belong to a domain that is
/// whitelisted by the developer in the console. Otherwise an error will be
/// thrown.
///
/// Mobile app redirects will only be applicable if the developer configures
/// and accepts the Firebase Dynamic Links terms of condition.
///
/// The Android package name and iOS bundle ID will be respected only if
/// they are configured in the same Firebase Auth project used.
Future sendPasswordResetEmail(String email,
[auth_interop.ActionCodeSettings? actionCodeSettings]) =>
handleThenable(auth_interop.sendPasswordResetEmail(
jsObject, email, actionCodeSettings));

/// Changes the current type of persistence on the current Auth instance for
/// the currently saved Auth session and applies this type of persistence
/// for future sign-in requests, including sign-in with redirect requests.
Expand Down Expand Up @@ -554,6 +553,30 @@ class Auth extends JsObjectWrapper<auth_interop.AuthJsImpl> {
return handleThenable(auth_interop.setPersistence(jsObject, instance));
}

/// Sends a password reset e-mail to the given [email].
/// To confirm password reset, use the [Auth.confirmPasswordReset].
///
/// The optional parameter [actionCodeSettings] is the action code settings.
/// If specified, the state/continue URL will be set as the 'continueUrl'
/// parameter in the password reset link.
/// The default password reset landing page will use this to display
/// a link to go back to the app if it is installed.
///
/// If the [actionCodeSettings] is not specified, no URL is appended to the
/// action URL. The state URL provided must belong to a domain that is
/// whitelisted by the developer in the console. Otherwise an error will be
/// thrown.
///
/// Mobile app redirects will only be applicable if the developer configures
/// and accepts the Firebase Dynamic Links terms of condition.
///
/// The Android package name and iOS bundle ID will be respected only if
/// they are configured in the same Firebase Auth project used.
Future sendPasswordResetEmail(String email,
[auth_interop.ActionCodeSettings? actionCodeSettings]) =>
handleThenable(auth_interop.sendPasswordResetEmail(
jsObject, email, actionCodeSettings));

/// Asynchronously signs in with the given credentials, and returns any
/// available additional user information, such as user name.
Future<UserCredential> signInWithCredential(
Expand Down
Expand Up @@ -481,7 +481,7 @@ void setupTests() {
group('setPersistence()', () {
test(
'throw an unimplemented error',
() async {
() async {
try {
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
fail('Should have thrown');
Expand All @@ -494,7 +494,7 @@ void setupTests() {

test(
'should set persistence',
() async {
() async {
try {
await FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
} catch (e) {
Expand Down

0 comments on commit ae7ebaf

Please sign in to comment.