blob: 927f78afc117be725ea76a8e962660aa6e95464f [file] [log] [blame]
// Copyright 2017 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.
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
/// Represents user data returned from an identity provider.
class UserInfo {
final Map<String, dynamic> _data;
UserInfo._(this._data);
/// The provider identifier.
String get providerId => _data['providerId'];
/// The provider’s user ID for the user.
String get uid => _data['uid'];
/// The name of the user.
String get displayName => _data['displayName'];
/// The URL of the user’s profile photo.
String get photoUrl => _data['photoUrl'];
/// The user’s email address.
String get email => _data['email'];
@override
String toString() {
return '$runtimeType($_data)';
}
}
/// Represents a user.
class FirebaseUser extends UserInfo {
final List<UserInfo> providerData;
FirebaseUser._(Map<String, dynamic> data)
: providerData = data['providerData']
.map((Map<String, dynamic> info) => new UserInfo._(info))
.toList(),
super._(data);
// Returns true if the user is anonymous; that is, the user account was
// created with signInAnonymously() and has not been linked to another
// account.
bool get isAnonymous => _data['isAnonymous'];
/// Returns true if the user's email is verified.
bool get isEmailVerified => _data['isEmailVerified'];
/// Obtains the id token for the current user, forcing a [refresh] if desired.
///
/// Completes with an error if the user is signed out.
Future<String> getIdToken({bool refresh: false}) {
return FirebaseAuth.channel.invokeMethod('getIdToken', <String, bool>{
'refresh': refresh,
});
}
@override
String toString() {
return '$runtimeType($_data)';
}
}
class FirebaseAuth {
@visibleForTesting
static const MethodChannel channel = const MethodChannel(
'plugins.flutter.io/firebase_auth',
);
final Map<int, StreamController<FirebaseUser>> _authStateChangedControllers =
<int, StreamController<FirebaseUser>>{};
/// Provides an instance of this class corresponding to the default app.
///
/// TODO(jackson): Support for non-default apps.
static FirebaseAuth instance = new FirebaseAuth._();
FirebaseAuth._() {
channel.setMethodCallHandler(_callHandler);
}
/// Receive [FirebaseUser] each time the user signIn or signOut
Stream<FirebaseUser> get onAuthStateChanged {
Future<int> _handle;
StreamController<FirebaseUser> controller;
controller = new StreamController<FirebaseUser>.broadcast(onListen: () {
_handle = channel.invokeMethod('startListeningAuthState');
_handle.then((int handle) {
_authStateChangedControllers[handle] = controller;
});
}, onCancel: () {
_handle.then((int handle) async {
await channel.invokeMethod(
"stopListeningAuthState", <String, int>{"id": handle});
_authStateChangedControllers.remove(handle);
});
});
return controller.stream;
}
/// Asynchronously creates and becomes an anonymous user.
///
/// If there is already an anonymous user signed in, that user will be
/// returned instead. If there is any other existing user signed in, that
/// user will be signed out.
///
/// Will throw a PlatformException if
/// FIRAuthErrorCodeOperationNotAllowed - Indicates that anonymous accounts are not enabled. Enable them in the Auth section of the Firebase console.
/// See FIRAuthErrors for a list of error codes that are common to all API methods.
Future<FirebaseUser> signInAnonymously() async {
final Map<String, dynamic> data =
await channel.invokeMethod('signInAnonymously');
final FirebaseUser currentUser = new FirebaseUser._(data);
return currentUser;
}
Future<FirebaseUser> createUserWithEmailAndPassword({
@required String email,
@required String password,
}) async {
assert(email != null);
assert(password != null);
final Map<String, dynamic> data = await channel.invokeMethod(
'createUserWithEmailAndPassword',
<String, String>{
'email': email,
'password': password,
},
);
final FirebaseUser currentUser = new FirebaseUser._(data);
return currentUser;
}
Future<FirebaseUser> signInWithEmailAndPassword({
@required String email,
@required String password,
}) async {
assert(email != null);
assert(password != null);
final Map<String, dynamic> data = await channel.invokeMethod(
'signInWithEmailAndPassword',
<String, String>{
'email': email,
'password': password,
},
);
final FirebaseUser currentUser = new FirebaseUser._(data);
return currentUser;
}
Future<FirebaseUser> signInWithFacebook(
{@required String accessToken}) async {
assert(accessToken != null);
final Map<String, dynamic> data =
await channel.invokeMethod('signInWithFacebook', <String, String>{
'accessToken': accessToken,
});
final FirebaseUser currentUser = new FirebaseUser._(data);
return currentUser;
}
Future<FirebaseUser> signInWithGoogle({
@required String idToken,
@required String accessToken,
}) async {
assert(idToken != null);
assert(accessToken != null);
final Map<String, dynamic> data = await channel.invokeMethod(
'signInWithGoogle',
<String, String>{
'idToken': idToken,
'accessToken': accessToken,
},
);
final FirebaseUser currentUser = new FirebaseUser._(data);
return currentUser;
}
Future<FirebaseUser> signInWithCustomToken({@required String token}) async {
assert(token != null);
final Map<String, dynamic> data = await channel.invokeMethod(
'signInWithCustomToken',
<String, String>{
'token': token,
},
);
final FirebaseUser currentUser = new FirebaseUser._(data);
return currentUser;
}
Future<Null> signOut() async {
return await channel.invokeMethod("signOut");
}
/// Asynchronously gets current user, or `null` if there is none.
Future<FirebaseUser> currentUser() async {
final Map<String, dynamic> data = await channel.invokeMethod("currentUser");
final FirebaseUser currentUser =
data == null ? null : new FirebaseUser._(data);
return currentUser;
}
/// Links email account with current user and returns [Future<FirebaseUser>]
/// basically current user with addtional email infomation
///
/// throws [PlatformException] when
/// 1. email address is already used
/// 2. wrong email and password provided
Future<FirebaseUser> linkWithEmailAndPassword({
@required String email,
@required String password,
}) async {
assert(email != null);
assert(password != null);
final Map<String, dynamic> data = await channel.invokeMethod(
'linkWithEmailAndPassword',
<String, String>{
'email': email,
'password': password,
},
);
final FirebaseUser currentUser = new FirebaseUser._(data);
return currentUser;
}
/// Links google account with current user and returns [Future<FirebaseUser>]
///
/// throws [PlatformException] when
/// 1. No current user provided (user has not logged in)
/// 2. No google credentials were found for given [idToken] and [accessToken]
/// 3. Google account already linked with another [FirebaseUser]
/// Detailed documentation on possible error causes can be found in [Android docs](https://firebase.google.com/docs/reference/android/com/google/firebase/auth/FirebaseUser#exceptions_4) and [iOS docs](https://firebase.google.com/docs/reference/ios/firebaseauth/api/reference/Classes/FIRUser#/c:objc(cs)FIRUser(im)linkWithCredential:completion:)
/// TODO: Throw custom exceptions with error codes indicating cause of exception
Future<FirebaseUser> linkWithGoogleCredential({
@required String idToken,
@required String accessToken,
}) async {
assert(idToken != null);
assert(accessToken != null);
final Map<String, dynamic> data = await channel.invokeMethod(
'linkWithGoogleCredential',
<String, String>{
'idToken': idToken,
'accessToken': accessToken,
},
);
final FirebaseUser currentUser = new FirebaseUser._(data);
return currentUser;
}
Future<Null> _callHandler(MethodCall call) async {
switch (call.method) {
case "onAuthStateChanged":
_onAuthStageChangedHandler(call);
break;
}
return null;
}
void _onAuthStageChangedHandler(MethodCall call) {
final Map<String, dynamic> data = call.arguments["user"];
final int id = call.arguments["id"];
final FirebaseUser currentUser =
data != null ? new FirebaseUser._(data) : null;
_authStateChangedControllers[id].add(currentUser);
}
}