diff --git a/jest.setup.ts b/jest.setup.ts index 02f79725bf..fb6110baaa 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -9,6 +9,10 @@ jest.doMock('react-native', () => { }, NativeModules: { ...ReactNative.NativeModules, + RNFBAdMobModule: {}, + RNFBAdMobInterstitialModule: {}, + RNFBAdMobRewardedModule: {}, + RNFBAdsConsentModule: {}, RNFBAppModule: { NATIVE_FIREBASE_APPS: [ { @@ -25,13 +29,23 @@ jest.doMock('react-native', () => { options: {}, }, ], + addListener: jest.fn(), + eventsAddListener: jest.fn(), + eventsNotifyReady: jest.fn(), + }, + RNFBAuthModule: { + APP_LANGUAGE: { + '[DEFAULT]': 'en-US', + }, + APP_USER: { + '[DEFAULT]': 'jestUser', + }, + addAuthStateListener: jest.fn(), + addIdTokenListener: jest.fn(), + useEmulator: jest.fn(), }, - RNFBPerfModule: {}, - RNFBAdMobModule: {}, - RNFBAdMobInterstitialModule: {}, - RNFBAdMobRewardedModule: {}, - RNFBAdsConsentModule: {}, RNFBCrashlyticsModule: {}, + RNFBPerfModule: {}, }, }, ReactNative, diff --git a/packages/auth/__tests__/auth.test.ts b/packages/auth/__tests__/auth.test.ts new file mode 100644 index 0000000000..458a029619 --- /dev/null +++ b/packages/auth/__tests__/auth.test.ts @@ -0,0 +1,46 @@ +import auth, { firebase } from '../lib'; + +describe('Auth', () => { + describe('namespace', () => { + it('accessible from firebase.app()', () => { + const app = firebase.app(); + expect(app.auth).toBeDefined(); + expect(app.auth().useEmulator).toBeDefined(); + }); + }); + + describe('useEmulator()', () => { + it('useEmulator requires a string url', () => { + // @ts-ignore because we pass an invalid argument... + expect(() => auth().useEmulator()).toThrow( + 'firebase.auth().useEmulator() takes a non-empty string', + ); + expect(() => auth().useEmulator('')).toThrow( + 'firebase.auth().useEmulator() takes a non-empty string', + ); + // @ts-ignore because we pass an invalid argument... + expect(() => auth().useEmulator(123)).toThrow( + 'firebase.auth().useEmulator() takes a non-empty string', + ); + }); + + it('useEmulator requires a well-formed url', () => { + // No http:// + expect(() => auth().useEmulator('localhost:9099')).toThrow( + 'firebase.auth().useEmulator() unable to parse host and port from url', + ); + // No port + expect(() => auth().useEmulator('http://localhost')).toThrow( + 'firebase.auth().useEmulator() unable to parse host and port from url', + ); + }); + + it('useEmulator -> remaps Android loopback to host', () => { + const foo = auth().useEmulator('http://localhost:9099'); + expect(foo).toEqual(['10.0.2.2', 9099]); + + const bar = auth().useEmulator('http://127.0.0.1:9099'); + expect(bar).toEqual(['10.0.2.2', 9099]); + }); + }); +}); diff --git a/packages/auth/lib/index.d.ts b/packages/auth/lib/index.d.ts index 8c317ed0d5..ecae068cb5 100644 --- a/packages/auth/lib/index.d.ts +++ b/packages/auth/lib/index.d.ts @@ -1640,6 +1640,14 @@ export namespace FirebaseAuthTypes { * @param userAccessGroup A string of the keychain id i.e. "TEAMID.com.example.group1" */ useUserAccessGroup(userAccessGroup: string): Promise; + /** + * Modify this Auth instance to communicate with the Firebase Auth emulator. + * This must be called synchronously immediately following the first call to firebase.auth(). + * Do not use with production credentials as emulator traffic is not encrypted. + * + * @param url: emulator URL, must have host and port (eg, 'http://localhost:9099') + */ + useEmulator(url: string): void; } } diff --git a/packages/auth/lib/index.js b/packages/auth/lib/index.js index da7a2cc59f..367a98b220 100644 --- a/packages/auth/lib/index.js +++ b/packages/auth/lib/index.js @@ -334,6 +334,33 @@ class FirebaseAuthModule extends FirebaseModule { 'firebase.auth().useDeviceLanguage() is unsupported by the native Firebase SDKs.', ); } + + useEmulator(url) { + if (!url || !isString(url) || url === '') { + throw new Error('firebase.auth().useEmulator() takes a non-empty string'); + } + + let _url = url; + if (isAndroid && _url) { + if (_url.startsWith('http://localhost')) { + _url = _url.replace('http://localhost', 'http://10.0.2.2'); + } + if (_url.startsWith('http://127.0.0.1')) { + _url = _url.replace('http://127.0.0.1', 'http://10.0.2.2'); + } + } + + // Native calls take the host and port split out + const hostPortRegex = /^http:\/\/([\w\d.]+):(\d+)$/; + const urlMatches = _url.match(hostPortRegex); + if (!urlMatches) { + throw new Error('firebase.auth().useEmulator() unable to parse host and port from url'); + } + const host = urlMatches[1]; + const port = parseInt(urlMatches[2], 10); + this.native.useEmulator(host, port); + return [host, port]; // undocumented return, useful for unit testing + } } // import { SDK_VERSION } from '@react-native-firebase/auth';