From 84a179b592d11c68767155814b6f2692fd7f914a Mon Sep 17 00:00:00 2001 From: Salakar Date: Wed, 3 Feb 2021 13:51:56 +0000 Subject: [PATCH] feat(firebase_auth): implement support for `useEmulator` (#4263) Co-authored-by: Mike Hardy Co-authored-by: Mike Diarmid --- .github/workflows/firebase_auth.yaml | 18 ++++++++-- .github/workflows/firebase_firestore.yaml | 14 ++++---- .github/workflows/firebase_functions.yaml | 14 ++++---- .github/workflows/scripts/firebase.json | 6 +++- ...emulator.sh => start-firebase-emulator.sh} | 12 ++++--- .../scripts/start-firestore-emulator.sh | 17 ---------- .../plugins/firebase/auth/Constants.java | 2 ++ .../auth/FlutterFirebaseAuthPlugin.java | 15 +++++++++ .../android/app/src/main/AndroidManifest.xml | 1 + .../example/ios/Runner/Info.plist | 5 +++ .../firebase_auth/example/lib/main.dart | 23 +++++++------ .../test_driver/firebase_auth_e2e.dart | 8 +++++ .../ios/Classes/FLTFirebaseAuthPlugin.m | 8 +++++ .../firebase_auth/lib/firebase_auth.dart | 1 + .../firebase_auth/lib/src/firebase_auth.dart | 33 +++++++++++++++++++ .../test/firebase_auth_test.dart | 12 +++++++ .../method_channel_firebase_auth.dart | 13 ++++++++ .../platform_interface_firebase_auth.dart | 11 +++++++ .../method_channel_firebase_auth_test.dart | 17 ++++++++++ .../platform_interface_auth_test.dart | 7 ++++ .../lib/firebase_auth_web.dart | 12 +++++++ .../lib/src/interop/auth.dart | 9 +++++ .../lib/src/interop/auth_interop.dart | 1 + 23 files changed, 209 insertions(+), 50 deletions(-) rename .github/workflows/scripts/{start-functions-emulator.sh => start-firebase-emulator.sh} (61%) delete mode 100755 .github/workflows/scripts/start-firestore-emulator.sh diff --git a/.github/workflows/firebase_auth.yaml b/.github/workflows/firebase_auth.yaml index 3ea322772758..fe3e931754b5 100644 --- a/.github/workflows/firebase_auth.yaml +++ b/.github/workflows/firebase_auth.yaml @@ -9,7 +9,7 @@ on: branches: - master paths-ignore: - - "docs/**" + - "docs/**" env: FLUTTERFIRE_PLUGIN_SCOPE: "*firebase_auth*" @@ -27,9 +27,13 @@ jobs: - name: "Install Flutter" run: ./.github/workflows/scripts/install-flutter.sh stable - name: "Install Tools" - run: ./.github/workflows/scripts/install-tools.sh + run: | + ./.github/workflows/scripts/install-tools.sh + sudo npm i -g firebase-tools - name: "Build Example" run: ./.github/workflows/scripts/build-example.sh android + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive Example" uses: reactivecircus/android-emulator-runner@v2 with: @@ -51,9 +55,12 @@ jobs: - name: "Install Tools" run: | ./.github/workflows/scripts/install-tools.sh + sudo npm i -g firebase-tools flutter config --enable-macos-desktop - name: "Build iOS Example" run: ./.github/workflows/scripts/build-example.sh ios + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive iOS Example" run: ./.github/workflows/scripts/drive-example.sh ios - name: "Build MacOS Example" @@ -62,7 +69,9 @@ jobs: run: ./.github/workflows/scripts/drive-example.sh macos web: - runs-on: ubuntu-latest + # Using macos instead of ubuntu as Web app can't connect to Firebase emulator + # when running on ubuntu. + runs-on: macos-latest timeout-minutes: 15 steps: - uses: actions/checkout@v1 @@ -74,5 +83,8 @@ jobs: run: | ./.github/workflows/scripts/install-tools.sh flutter config --enable-web + sudo npm i -g firebase-tools + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive Example" run: ./.github/workflows/scripts/drive-example.sh web diff --git a/.github/workflows/firebase_firestore.yaml b/.github/workflows/firebase_firestore.yaml index 53ffd00792dc..e41a93bd0697 100644 --- a/.github/workflows/firebase_firestore.yaml +++ b/.github/workflows/firebase_firestore.yaml @@ -32,8 +32,8 @@ jobs: sudo npm i -g firebase-tools - name: "Build Example" run: ./.github/workflows/scripts/build-example.sh android - - name: Start Firestore Emulator - run: cd ./.github/workflows/scripts && ./start-firestore-emulator.sh + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive Example" uses: reactivecircus/android-emulator-runner@v2 with: @@ -61,8 +61,8 @@ jobs: flutter config --enable-macos-desktop - name: "Build iOS Example" run: ./.github/workflows/scripts/build-example.sh ios - - name: Start Firestore Emulator - run: cd ./.github/workflows/scripts && ./start-firestore-emulator.sh + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive iOS Example" run: ./.github/workflows/scripts/drive-example.sh ios - name: "Build MacOS Example" @@ -73,7 +73,7 @@ jobs: run: ./.github/workflows/scripts/drive-example.sh macos web: - # Using macos instead of ubuntu as Web app can't connect to Firebase Firestore emulator + # Using macos instead of ubuntu as Web app can't connect to Firebase emulator # when running on ubuntu. runs-on: macos-latest timeout-minutes: 15 @@ -88,7 +88,7 @@ jobs: ./.github/workflows/scripts/install-tools.sh sudo npm i -g firebase-tools flutter config --enable-web - - name: Start Firestore Emulator - run: cd ./.github/workflows/scripts && ./start-firestore-emulator.sh + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive Example" run: ./.github/workflows/scripts/drive-example.sh web diff --git a/.github/workflows/firebase_functions.yaml b/.github/workflows/firebase_functions.yaml index 286f259299eb..0a9bd9370d81 100644 --- a/.github/workflows/firebase_functions.yaml +++ b/.github/workflows/firebase_functions.yaml @@ -31,8 +31,8 @@ jobs: sudo npm i -g firebase-tools - name: "Build Example" run: ./.github/workflows/scripts/build-example.sh android - - name: Start Functions Emulator - run: cd ./.github/workflows/scripts && ./start-functions-emulator.sh + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive Example" uses: reactivecircus/android-emulator-runner@v2 with: @@ -60,8 +60,8 @@ jobs: sudo npm i -g firebase-tools - name: "Build iOS Example" run: ./.github/workflows/scripts/build-example.sh ios - - name: Start Functions Emulator - run: cd ./.github/workflows/scripts && ./start-functions-emulator.sh + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive iOS Example" run: ./.github/workflows/scripts/drive-example.sh ios - name: "Build MacOS Example" @@ -70,7 +70,7 @@ jobs: run: ./.github/workflows/scripts/drive-example.sh macos web: - # Using macos instead of ubuntu as Web app can't connect to Firebase Functions emulator + # Using macos instead of ubuntu as Web app can't connect to Firebase emulator # when running on ubuntu. runs-on: macos-latest timeout-minutes: 15 @@ -85,7 +85,7 @@ jobs: ./.github/workflows/scripts/install-tools.sh flutter config --enable-web sudo npm i -g firebase-tools - - name: Start Functions Emulator - run: cd ./.github/workflows/scripts && ./start-functions-emulator.sh + - name: Start Firebase Emulator + run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - name: "Drive Example" run: ./.github/workflows/scripts/drive-example.sh web diff --git a/.github/workflows/scripts/firebase.json b/.github/workflows/scripts/firebase.json index 7039bece8290..8cc96bb619e2 100644 --- a/.github/workflows/scripts/firebase.json +++ b/.github/workflows/scripts/firebase.json @@ -9,8 +9,12 @@ "firestore": { "port": "8080" }, + "auth": { + "port": "9099" + }, "ui": { - "enabled": false + "enabled": true, + "port": 4000 } } } diff --git a/.github/workflows/scripts/start-functions-emulator.sh b/.github/workflows/scripts/start-firebase-emulator.sh similarity index 61% rename from .github/workflows/scripts/start-functions-emulator.sh rename to .github/workflows/scripts/start-firebase-emulator.sh index 2d3784102985..4994b5721649 100755 --- a/.github/workflows/scripts/start-functions-emulator.sh +++ b/.github/workflows/scripts/start-firebase-emulator.sh @@ -19,14 +19,16 @@ if [[ ! -d "functions/node_modules" ]]; then cd functions && npm i && cd .. fi +EMU_START_COMMAND="firebase emulators:start --only auth,firestore,functions --project react-native-firebase-testing" + IS_CI="${CI}${CONTINUOUS_INTEGRATION}${BUILD_NUMBER}${RUN_ID}" if [[ -n "${IS_CI}" ]]; then - firebase emulators:start --only functions & - until curl --output /dev/null --head --silent http://localhost:5001; do - echo "Waiting for Functions emulator to come online..." + $EMU_START_COMMAND & + until curl --output /dev/null --silent --fail http://localhost:8080; do + echo "Waiting for Firebase emulator to come online..." sleep 2 done - echo "Functions emulator is online!" + echo "Firebase emulator is online!" else - firebase emulators:start --only functions + $EMU_START_COMMAND fi diff --git a/.github/workflows/scripts/start-firestore-emulator.sh b/.github/workflows/scripts/start-firestore-emulator.sh deleted file mode 100755 index 8b5efb4e7aa7..000000000000 --- a/.github/workflows/scripts/start-firestore-emulator.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -if ! [ -x "$(command -v firebase)" ]; then - echo "❌ Firebase tools CLI is missing." - exit 1 -fi - -IS_CI="${CI}${CONTINUOUS_INTEGRATION}${BUILD_NUMBER}${RUN_ID}" -if [[ -n "${IS_CI}" ]]; then - firebase emulators:start --only firestore & - until curl --output /dev/null --silent --fail http://localhost:8080; do - echo "Waiting for Firestore emulator to come online..." - sleep 2 - done - echo "Firestore emulator is online!" -else - firebase emulators:start --only firestore -fi diff --git a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/Constants.java b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/Constants.java index 80b4fb261f1d..c1bc3444f47c 100644 --- a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/Constants.java +++ b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/Constants.java @@ -77,4 +77,6 @@ public class Constants { public static final String HANDLE_CODE_IN_APP = "handleCodeInApp"; public static final String ACTION_CODE_SETTINGS = "actionCodeSettings"; public static final String AUTO_RETRIEVED_SMS_CODE_FOR_TESTING = "autoRetrievedSmsCodeForTesting"; + public static final String HOST = "host"; + public static final String PORT = "port"; } diff --git a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/FlutterFirebaseAuthPlugin.java b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/FlutterFirebaseAuthPlugin.java index 257cb049a5e6..0ef0cfd43e44 100755 --- a/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/FlutterFirebaseAuthPlugin.java +++ b/packages/firebase_auth/firebase_auth/android/src/main/java/io/flutter/plugins/firebase/auth/FlutterFirebaseAuthPlugin.java @@ -764,6 +764,18 @@ private Task signOut(Map arguments) { }); } + private Task useEmulator(Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + FirebaseAuth firebaseAuth = getAuth(arguments); + String host = (String) arguments.get(Constants.HOST); + int port = (int) arguments.get(Constants.PORT); + firebaseAuth.useEmulator(host, port); + return null; + }); + } + private Task> verifyPasswordResetCode(Map arguments) { return Tasks.call( cachedThreadPool, @@ -1196,6 +1208,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { case "Auth#signOut": methodCallTask = signOut(call.arguments()); break; + case "Auth#useEmulator": + methodCallTask = useEmulator(call.arguments()); + break; case "Auth#verifyPasswordResetCode": methodCallTask = verifyPasswordResetCode(call.arguments()); break; diff --git a/packages/firebase_auth/firebase_auth/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_auth/firebase_auth/example/android/app/src/main/AndroidManifest.xml index e1198ea9146a..fbb945bc3a89 100644 --- a/packages/firebase_auth/firebase_auth/example/android/app/src/main/AndroidManifest.xml +++ b/packages/firebase_auth/firebase_auth/example/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> diff --git a/packages/firebase_auth/firebase_auth/example/ios/Runner/Info.plist b/packages/firebase_auth/firebase_auth/example/ios/Runner/Info.plist index 02dc0debbc71..ae7260455fb3 100644 --- a/packages/firebase_auth/firebase_auth/example/ios/Runner/Info.plist +++ b/packages/firebase_auth/firebase_auth/example/ios/Runner/Info.plist @@ -41,6 +41,11 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/packages/firebase_auth/firebase_auth/example/lib/main.dart b/packages/firebase_auth/firebase_auth/example/lib/main.dart index 0a2c47c1dac1..83a10dfcf5f8 100755 --- a/packages/firebase_auth/firebase_auth/example/lib/main.dart +++ b/packages/firebase_auth/firebase_auth/example/lib/main.dart @@ -12,6 +12,8 @@ import './signin_page.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); + // Uncomment this to use the auth emulator for testing + // await FirebaseAuth.instance.useEmulator('http://localhost:9099'); runApp(AuthExampleApp()); } @@ -22,11 +24,12 @@ class AuthExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Firebase Example App', - theme: ThemeData.dark(), - home: Scaffold( - body: AuthTypeSelector(), - )); + title: 'Firebase Example App', + theme: ThemeData.dark(), + home: Scaffold( + body: AuthTypeSelector(), + ), + ); } } @@ -43,30 +46,30 @@ class AuthTypeSelector extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text("Firebase Example App"), + title: const Text('Firebase Example App'), ), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( + padding: const EdgeInsets.all(16), + alignment: Alignment.center, child: SignInButtonBuilder( icon: Icons.person_add, backgroundColor: Colors.indigo, text: 'Registration', onPressed: () => _pushPage(context, RegisterPage()), ), - padding: const EdgeInsets.all(16), - alignment: Alignment.center, ), Container( + padding: const EdgeInsets.all(16), + alignment: Alignment.center, child: SignInButtonBuilder( icon: Icons.verified_user, backgroundColor: Colors.orange, text: 'Sign In', onPressed: () => _pushPage(context, SignInPage()), ), - padding: const EdgeInsets.all(16), - alignment: Alignment.center, ), ], ), diff --git a/packages/firebase_auth/firebase_auth/example/test_driver/firebase_auth_e2e.dart b/packages/firebase_auth/firebase_auth/example/test_driver/firebase_auth_e2e.dart index f112102484aa..34ea2a84e63c 100644 --- a/packages/firebase_auth/firebase_auth/example/test_driver/firebase_auth_e2e.dart +++ b/packages/firebase_auth/firebase_auth/example/test_driver/firebase_auth_e2e.dart @@ -5,6 +5,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:drive/drive.dart' as drive; +//import 'package:firebase_auth/firebase_auth.dart'; // only needed if you use the Auth Emulator import 'package:firebase_core/firebase_core.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -17,6 +18,13 @@ bool USE_EMULATOR = false; void testsMain() { setUpAll(() async { await Firebase.initializeApp(); + + // Configure the Auth test suite to use the Auth Emulator + // This may not be enabled until the test suite is ported to: + // - have ability to create disabled users + // - have ability to fetch OOB and SMS verification codes + // JS implementation to port to dart here: https://github.com/invertase/react-native-firebase/pull/4552/commits/4c688413cb6516ecfdbd4ea325103d0d8d8d84a8#diff-44ccd5fb03b0d9e447820032866f2494c5a400a52873f0f65518d06aedafe302 + // await FirebaseAuth.instance.useEmulator('http://localhost:9099'); }); runInstanceTests(); diff --git a/packages/firebase_auth/firebase_auth/ios/Classes/FLTFirebaseAuthPlugin.m b/packages/firebase_auth/firebase_auth/ios/Classes/FLTFirebaseAuthPlugin.m index f8788bd5429a..11c1b0b631da 100644 --- a/packages/firebase_auth/firebase_auth/ios/Classes/FLTFirebaseAuthPlugin.m +++ b/packages/firebase_auth/firebase_auth/ios/Classes/FLTFirebaseAuthPlugin.m @@ -212,6 +212,8 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutter [self signInWithEmailLink:call.arguments withMethodCallResult:methodCallResult]; } else if ([@"Auth#signOut" isEqualToString:call.method]) { [self signOut:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Auth#useEmulator" isEqualToString:call.method]) { + [self useEmulator:call.arguments withMethodCallResult:methodCallResult]; } else if ([@"Auth#verifyPasswordResetCode" isEqualToString:call.method]) { [self verifyPasswordResetCode:call.arguments withMethodCallResult:methodCallResult]; } else if ([@"Auth#verifyPhoneNumber" isEqualToString:call.method]) { @@ -552,6 +554,12 @@ - (void)signOut:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult } } +- (void)useEmulator:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRAuth *auth = [self getFIRAuthFromArguments:arguments]; + [auth useEmulatorWithHost:arguments[@"host"] port:[arguments[@"port"] integerValue]]; + result.success(nil); +} + - (void)verifyPasswordResetCode:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { FIRAuth *auth = [self getFIRAuthFromArguments:arguments]; diff --git a/packages/firebase_auth/firebase_auth/lib/firebase_auth.dart b/packages/firebase_auth/firebase_auth/lib/firebase_auth.dart index b781239ab0d2..57ab9ae881c8 100755 --- a/packages/firebase_auth/firebase_auth/lib/firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth/lib/firebase_auth.dart @@ -9,6 +9,7 @@ import 'dart:async'; import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; diff --git a/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart b/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart index 657edb0c4cc8..ef326c0794a3 100644 --- a/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth/lib/src/firebase_auth.dart @@ -83,6 +83,39 @@ class FirebaseAuth extends FirebasePluginPlatform { return null; } + /// Changes this instance to point to an Auth emulator running locally. + /// + /// Set the [origin] of the local emulator, such as "http://localhost:9099" + /// + /// Note: Must be called immediately, prior to accessing auth methods. + /// Do not use with production credentials as emulator traffic is not encrypted. + Future useEmulator(String origin) async { + assert(origin.isNotEmpty); + String mappedOrigin = origin; + + // Android considers localhost as 10.0.2.2 - automatically handle this for users. + if (defaultTargetPlatform == TargetPlatform.android) { + if (mappedOrigin.startsWith('http://localhost')) { + mappedOrigin = + mappedOrigin.replaceFirst('http://localhost', 'http://10.0.2.2'); + } else if (mappedOrigin.startsWith('http://127.0.0.1')) { + mappedOrigin = + mappedOrigin.replaceFirst('http://127.0.0.1', 'http://10.0.2.2'); + } + } + + // Native calls take the host and port split out + final hostPortRegex = RegExp(r'^http:\/\/([\w\d.]+):(\d+)$'); + final RegExpMatch match = hostPortRegex.firstMatch(mappedOrigin); + if (match == null) { + throw ArgumentError('firebase.auth().useEmulator() origin format error'); + } + // Two non-empty groups in RegExp match - which is null-tested - these are non-null now + final String host = match.group(1); + final int port = int.parse(match.group(2)); + await _delegate.useEmulator(host, port); + } + /// Applies a verification code sent to the user by email or other out-of-band /// mechanism. /// diff --git a/packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart b/packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart index cf445d0dd2ef..9f3452dd5af6 100644 --- a/packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart +++ b/packages/firebase_auth/firebase_auth/test/firebase_auth_test.dart @@ -35,6 +35,8 @@ void main() { const String kMockLanguage = 'en'; const String kMockOobCode = 'oobcode'; const String kMockURL = 'http://www.example.com'; + const String kMockHost = 'www.example.com'; + const int kMockPort = 31337; final ActionCodeSettings kMockActionCodeSettings = ActionCodeSettings(url: kMockURL); @@ -168,6 +170,16 @@ void main() { await auth.signInAnonymously(); }); + group('emulator', () { + test('useEmulator() should call delegate method', () async { + // Necessary as we otherwise get a "null is not a Future" error + when(mockAuthPlatform.useEmulator(kMockHost, kMockPort)) + .thenAnswer((i) async {}); + await auth.useEmulator('http://$kMockHost:$kMockPort'); + verify(mockAuthPlatform.useEmulator(kMockHost, kMockPort)); + }); + }); + group('currentUser', () { test('get currentUser', () { User user = auth.currentUser; diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart index 8c453fcc9d8e..6949a39da7d8 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/method_channel/method_channel_firebase_auth.dart @@ -255,6 +255,19 @@ class MethodChannelFirebaseAuth extends FirebaseAuthPlatform { return this; } + @override + Future useEmulator(String host, int port) async { + try { + await channel.invokeMethod('Auth#useEmulator', { + 'appName': app.name, + 'host': host, + 'port': port, + }); + } catch (e) { + throw convertPlatformException(e); + } + } + @override Future applyActionCode(String code) async { try { diff --git a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart index 936893e175ed..6f6bb0a263d6 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/lib/src/platform_interface/platform_interface_firebase_auth.dart @@ -115,6 +115,17 @@ abstract class FirebaseAuthPlatform extends PlatformInterface { throw UnimplementedError("sendAuthChangesEvent() is not implemented"); } + /// Changes this instance to point to an Auth emulator running locally. + /// + /// Set the [host] and [port] of the local emulator, such as "http://localhost" + /// with port 9099 + /// + /// Note: Must be called immediately, prior to accessing auth methods. + /// Do not use with production credentials as emulator traffic is not encrypted. + Future useEmulator(String host, int port) { + throw UnimplementedError('useEmulator() is not implemented'); + } + /// Applies a verification code sent to the user by email or other out-of-band /// mechanism. /// diff --git a/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/method_channel_firebase_auth_test.dart b/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/method_channel_firebase_auth_test.dart index c33d3fc89edc..ee640304ad0b 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/method_channel_firebase_auth_test.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/test/method_channel_tests/method_channel_firebase_auth_test.dart @@ -831,6 +831,23 @@ void main() { }); }); + group('useEmulator()', () { + test('calls useEmulator correctly', () async { + await auth.useEmulator('example.com', 31337); + // check native method was called + expect(log, [ + isMethodCall( + 'Auth#useEmulator', + arguments: { + 'appName': defaultFirebaseAppName, + 'host': 'example.com', + 'port': 31337, + }, + ), + ]); + }); + }); + group('verifyPasswordResetCode()', () { const String testCode = 'testCode'; test('returns a successful result', () async { diff --git a/packages/firebase_auth/firebase_auth_platform_interface/test/platform_interface_tests/platform_interface_auth_test.dart b/packages/firebase_auth/firebase_auth_platform_interface/test/platform_interface_tests/platform_interface_auth_test.dart index 0f7986cf4dc1..1191dbb58681 100644 --- a/packages/firebase_auth/firebase_auth_platform_interface/test/platform_interface_tests/platform_interface_auth_test.dart +++ b/packages/firebase_auth/firebase_auth_platform_interface/test/platform_interface_tests/platform_interface_auth_test.dart @@ -401,6 +401,13 @@ void main() { fail('Should have thrown an [UnimplementedError]'); }); + test('throws if useEmulator', () async { + await expectLater( + () => firebaseAuthPlatform.useEmulator('http://localhost', 9099), + throwsUnimplementedError, + ); + }); + test('throws if verifyPasswordResetCode()', () async { try { await firebaseAuthPlatform.verifyPasswordResetCode('test'); diff --git a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart index 04fa6b695fde..508c5acfe738 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/firebase_auth_web.dart @@ -332,6 +332,18 @@ class FirebaseAuthWeb extends FirebaseAuthPlatform { } } + @override + Future useEmulator(String host, int port) async { + try { + // The generic platform interface is with host and port split to + // centralize logic between android/ios native, but web takes the + // origin as a single string + await _webAuth.useEmulator('http://$host:$port'); + } catch (e) { + throw getFirebaseAuthException(e); + } + } + @override Future verifyPasswordResetCode(String code) async { try { diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart index a7cec0b1f628..9bb6168c17b5 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart @@ -591,6 +591,15 @@ class Auth extends JsObjectWrapper { /// Signs out the current user. Future signOut() => handleThenable(jsObject.signOut()); + /// Configures the Auth instance to work with a local emulator + /// + /// Call with [origin] like 'http://localhost:9099' + /// + /// Note: must be called before using auth methods, do not use + /// with production credentials as local connections are unencrypted + Future useEmulator(String origin) => + handleThenable(jsObject.useEmulator(origin)); + /// Sets the current language to the default device/browser preference. void useDeviceLanguage() => jsObject.useDeviceLanguage(); diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart index d787b41f9efe..8fdf73c74a63 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart @@ -56,6 +56,7 @@ abstract class AuthJsImpl { AuthProviderJsImpl provider); external PromiseJsImpl signInWithRedirect(AuthProviderJsImpl provider); external PromiseJsImpl signOut(); + external PromiseJsImpl useEmulator(String origin); external void useDeviceLanguage(); external PromiseJsImpl verifyPasswordResetCode(String code); }