diff --git a/.changeset/wet-badgers-nail.md b/.changeset/wet-badgers-nail.md new file mode 100644 index 00000000000..47182420bb9 --- /dev/null +++ b/.changeset/wet-badgers-nail.md @@ -0,0 +1,6 @@ +--- +"@firebase/auth": patch +"firebase": patch +--- + +Add the `useEmulator()` function and `emulatorConfig` to the `firebase` package externs diff --git a/packages/auth/src/auth.js b/packages/auth/src/auth.js index 8bb60114736..e2863a8dd1e 100644 --- a/packages/auth/src/auth.js +++ b/packages/auth/src/auth.js @@ -309,7 +309,7 @@ fireauth.Auth.prototype.useEmulator = function(url, options) { const disableBanner = options ? !!options['disableWarnings'] : false; this.emitEmulatorWarning_(disableBanner); // Persist the config. - this.emulatorConfig_ = { url }; + this.emulatorConfig_ = {url, disableWarnings: disableBanner}; // Disable app verification. this.settings_().setAppVerificationDisabledForTesting(true); // Update RPC handler endpoints. @@ -353,10 +353,22 @@ fireauth.Auth.prototype.emitEmulatorWarning_ = function(disableBanner) { /** - * @return {?fireauth.constants.EmulatorSettings} + * @return {?fireauth.constants.EmulatorConfig} */ fireauth.Auth.prototype.getEmulatorConfig = function() { - return this.emulatorConfig_; + if (!this.emulatorConfig_) { + return null; + } + const uri = goog.Uri.parse(this.emulatorConfig_.url); + return /** @type {!fireauth.constants.EmulatorConfig} */ ( + fireauth.object.makeReadonlyCopy({ + 'protocol': uri.getScheme(), + 'host': uri.getDomain(), + 'port': uri.getPort(), + 'options': fireauth.object.makeReadonlyCopy({ + 'disableWarnings': this.emulatorConfig_.disableWarnings, + }), + })); } @@ -470,6 +482,19 @@ fireauth.Auth.prototype.initializeReadableWritableProps_ = function() { // Initialize to null. /** @private {?string} The current Auth instance's tenant ID. */ this.tenantId_ = null; + + // Add the emulator configuration property (readonly). + Object.defineProperty(/** @type {!Object} */ (this), 'emulatorConfig', { + /** + * @this {!Object} + * @return {?fireauth.constants.EmulatorConfig} The emulator config if + * enabled. + */ + get: function() { + return this.getEmulatorConfig(); + }, + enumerable: false + }); }; diff --git a/packages/auth/src/defines.js b/packages/auth/src/defines.js index be41f997312..a2b123904aa 100644 --- a/packages/auth/src/defines.js +++ b/packages/auth/src/defines.js @@ -173,9 +173,33 @@ fireauth.constants.OIDC_PREFIX = 'oidc.'; * The settings of an Auth emulator. The fields are: * * @typedef {{ * url: string, + * disableWarnings: boolean, * }} */ -fireauth.constants.EmulatorSettings; \ No newline at end of file +fireauth.constants.EmulatorSettings; + + +/** + * The (externally visible) emulator configuration, used for + * getEmulatorConfig(). The fields are: + * + * @typedef {{ + * protocol: string, + * host: string, + * port: (number|null), + * options: { + * disableWarnings: boolean, + * } + * }} + */ +fireauth.constants.EmulatorConfig; \ No newline at end of file diff --git a/packages/auth/src/exports_auth.js b/packages/auth/src/exports_auth.js index 37f23211720..fbbf123818e 100644 --- a/packages/auth/src/exports_auth.js +++ b/packages/auth/src/exports_auth.js @@ -225,7 +225,9 @@ fireauth.exportlib.exportPrototypeProperties( fireauth.args.string(), fireauth.args.null(), 'tenantId') - } + }, + // emulatorConfig is omitted here as it is readonly and therefore does not + // need argument validation. }); // Exports firebase.auth.Auth.Persistence. diff --git a/packages/auth/test/auth_test.js b/packages/auth/test/auth_test.js index 701df7fcc3b..ab32b990184 100644 --- a/packages/auth/test/auth_test.js +++ b/packages/auth/test/auth_test.js @@ -952,15 +952,19 @@ function testUseEmulator() { auth1.useEmulator('http://emulator.test.domain:1234'); assertObjectEquals( { - url: 'http://emulator.test.domain:1234', + protocol: 'http', + host: 'emulator.test.domain', + port: 1234, + options: {disableWarnings: false}, }, - auth1.getEmulatorConfig()); + auth1.emulatorConfig); // Should notify the RPC handler. assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertObjectEquals( { url: 'http://emulator.test.domain:1234', + disableWarnings: false, }, fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() .getArgument(0) @@ -986,9 +990,12 @@ function testUseEmulator() { auth1.useEmulator('http://emulator.test.domain:1234'); assertObjectEquals( { - url: 'http://emulator.test.domain:1234', + protocol: 'http', + host: 'emulator.test.domain', + port: 1234, + options: {disableWarnings: false}, }, - auth1.getEmulatorConfig()); + auth1.emulatorConfig); assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertEquals(1, fireauth.util.consoleInfo.getCallCount()); @@ -997,9 +1004,12 @@ function testUseEmulator() { auth1.useEmulator('http://emulator.other.domain:9876'); assertObjectEquals( { - url: 'http://emulator.test.domain:1234', + protocol: 'http', + host: 'emulator.test.domain', + port: 1234, + options: {disableWarnings: false}, }, - auth1.getEmulatorConfig()); + auth1.emulatorConfig); assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); } @@ -1030,16 +1040,20 @@ function testUseEmulator_withDisableWarnings() { auth1.useEmulator( 'http://emulator.test.domain:1234', {disableWarnings: true}); assertObjectEquals( - { - url: 'http://emulator.test.domain:1234', - }, - auth1.getEmulatorConfig()); + { + protocol: 'http', + host: 'emulator.test.domain', + port: 1234, + options: {disableWarnings: true}, + }, + auth1.emulatorConfig); // Should notify the RPC handler. assertEquals( 1, fireauth.RpcHandler.prototype.updateEmulatorConfig.getCallCount()); assertObjectEquals( { url: 'http://emulator.test.domain:1234', + disableWarnings: true, }, fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() .getArgument(0)); @@ -1057,6 +1071,76 @@ function testUseEmulator_withDisableWarnings() { } +function testEmulatorConfig() { + app1 = firebase.initializeApp(config1, appId1); + auth1 = app1.auth(); + + // Update the emulator config. + auth1.useEmulator( + 'http://emulator.test.domain:1234', {disableWarnings: true}); + assertObjectEquals( + { + protocol: 'http', + host: 'emulator.test.domain', + port: 1234, + options: {disableWarnings: true}, + }, + auth1.emulatorConfig); +} + + +/** + * Asserts that the port is correctly set to null if no port supplied. + */ +function testEmulatorConfig_noPortSpecified() { + app1 = firebase.initializeApp(config1, appId1); + auth1 = app1.auth(); + + // Update the emulator config. + auth1.useEmulator('http://emulator.test.domain'); + assertObjectEquals( + { + protocol: 'http', + host: 'emulator.test.domain', + port: null, + options: {disableWarnings: false}, + }, + auth1.emulatorConfig); +} + + +/** + * Asserts that the port is correctly assigned 0 if specifically set to 0 for + * some reason. Also checks https protocol. + */ +function testEmulatorConfig_portZeroAndHttpsSpecified() { + app1 = firebase.initializeApp(config1, appId1); + auth1 = app1.auth(); + + // Update the emulator config. + auth1.useEmulator('https://emulator.test.domain:0'); + assertObjectEquals( + { + protocol: 'https', + host: 'emulator.test.domain', + port: 0, + options: {disableWarnings: false}, + }, + auth1.emulatorConfig); +} + + +/** + * Asserts that the function returns null if useEmulator is not called. + */ +function testEmulatorConfig_nullIfNoEmulatorConfig() { + app1 = firebase.initializeApp(config1, appId1); + auth1 = app1.auth(); + + assertNull(auth1.emulatorConfig); +} + + function testGetSetTenantId() { app1 = firebase.initializeApp(config1, appId1); auth1 = app1.auth(); @@ -2317,59 +2401,6 @@ function testAuth_authEventManager() { } -function testAuth_authEventManager_withEmulator() { - // Test Auth event manager. - fireauth.AuthEventManager.ENABLED = true; - stubs.reset(); - initializeMockStorage(); - var expectedManager = { - 'subscribe': goog.testing.recordFunction(), - 'unsubscribe': goog.testing.recordFunction(), - 'clearRedirectResult': goog.testing.recordFunction() - }; - // Return stub manager. - stubs.replace( - fireauth.AuthEventManager, - 'getManager', - function (authDomain, apiKey, appName, emulatorConfig) { - assertEquals('subdomain.firebaseapp.com', authDomain); - assertEquals('API_KEY', apiKey); - assertEquals(appId1, appName); - assertObjectEquals(emulatorConfig, { - url: 'http://emulator.test.domain:1234' - }); - return expectedManager; - }); - asyncTestCase.waitForSignals(1); - app1 = firebase.initializeApp(config3, appId1); - auth1 = app1.auth(); - auth1.useEmulator('http://emulator.test.domain:1234'); - // Test manager initialized and Auth subscribed. - auth1.onIdTokenChanged(function (user) { - var manager = fireauth.AuthEventManager.getManager( - config3['authDomain'], config3['apiKey'], app1.name, { - url: 'http://emulator.test.domain:1234', - }); - assertEquals(expectedManager, manager); - assertEquals(0, expectedManager.unsubscribe.getCallCount()); - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals( - auth1, expectedManager.subscribe.getLastCall().getArgument(0)); - assertEquals(0, expectedManager.clearRedirectResult.getCallCount()); - // Delete should trigger unsubscribe and redirect result clearing. - auth1.delete(); - // After destroy, Auth should be unsubscribed. - assertEquals(1, expectedManager.subscribe.getCallCount()); - assertEquals(1, expectedManager.unsubscribe.getCallCount()); - // Redirect result should also be cleared. - assertEquals(1, expectedManager.clearRedirectResult.getCallCount()); - assertEquals( - auth1, expectedManager.unsubscribe.getLastCall().getArgument(0)); - asyncTestCase.signal(); - }); -} - - /** Asserts that AuthEventManager can pass through emulator settings. */ function testAuth_authEventManager_withEmulator() { // Test Auth event manager. @@ -2390,7 +2421,8 @@ function testAuth_authEventManager_withEmulator() { assertEquals('API_KEY', apiKey); assertEquals(appId1, appName); assertObjectEquals(emulatorConfig, { - url: 'http://emulator.host:1234' + url: 'http://emulator.host:1234', + disableWarnings: false, }); return expectedManager; }); @@ -2402,7 +2434,8 @@ function testAuth_authEventManager_withEmulator() { auth1.onIdTokenChanged(function (user) { var manager = fireauth.AuthEventManager.getManager( config3['authDomain'], config3['apiKey'], app1.name, { - url: 'http://emulator.host:1234' + url: 'http://emulator.host:1234', + disableWarnings: false, }); assertEquals(expectedManager, manager); assertEquals(0, expectedManager.unsubscribe.getCallCount()); @@ -2667,7 +2700,8 @@ function testAuth_initState_signedInStatus_withEmulator() { .getThis()); assertObjectEquals( { - url: 'http://emulator.test.domain:1234' + url: 'http://emulator.test.domain:1234', + disableWarnings: false, }, fireauth.RpcHandler.prototype.updateEmulatorConfig.getLastCall() .getArgument(0)); @@ -4526,6 +4560,7 @@ function testAuth_signInWithIdTokenResponse_withEmulator() { var expectedOptions = Object.assign({}, config3); expectedOptions['emulatorConfig'] = { url: 'http://emulator.test.domain:1234', + disableWarnings: false, }; // The newly signed in user. var user1 = new fireauth.AuthUser( diff --git a/packages/firebase/externs/firebase-auth-externs.js b/packages/firebase/externs/firebase-auth-externs.js index c588ea5e2b4..f286f66cf64 100644 --- a/packages/firebase/externs/firebase-auth-externs.js +++ b/packages/firebase/externs/firebase-auth-externs.js @@ -1218,6 +1218,47 @@ firebase.auth.Auth.prototype.app; */ firebase.auth.Auth.prototype.currentUser; +/** + * The full emulator configuration as set on `auth().emulatorConfig`. + * + * + * @typedef {{ + * protocol: string, + * host: string, + * port: (number|null), + * options: { + * disableWarnings: boolean, + * } + * }} + */ +firebase.auth.EmulatorConfig; + +/** + * The current emulator configuration, or null if not set. + * + * @type {firebase.auth.EmulatorConfig|null} + */ +firebase.auth.Auth.prototype.emulatorConfig; + +/** + * Configures the SDK to communicate with the Firebase Auth emulator. + * + * This must be called before any other Auth SDK actions are taken. + * + * Options can include `disableWarnings`. When set to true, the SDK will not + * display a warning banner at the bottom of the page. + * + * @param {string} url The full emulator url including scheme and port. + * @param {!Object=} options Options for configuring the SDK's emulator config. + */ +firebase.auth.Auth.prototype.useEmulator = function (url, options) {}; + /** * The current Auth instance's tenant ID. This is a readable/writable * property. When you set the tenant ID of an Auth instance, all future