diff --git a/common/api-review/util.api.md b/common/api-review/util.api.md index 28cc9a160d4..0f8fc13cd3a 100644 --- a/common/api-review/util.api.md +++ b/common/api-review/util.api.md @@ -398,6 +398,9 @@ export function ordinal(i: number): string; // @public (undocumented) export type PartialObserver = Partial>; +// @public +export function pingServer(endpoint: string): Promise; + // Warning: (ae-internal-missing-underscore) The name "promiseWithTimeout" should be prefixed with an underscore because the declaration is marked as @internal // // @internal diff --git a/packages/auth/src/core/auth/emulator.ts b/packages/auth/src/core/auth/emulator.ts index 60cc9403d3d..05f2e5e4bd5 100644 --- a/packages/auth/src/core/auth/emulator.ts +++ b/packages/auth/src/core/auth/emulator.ts @@ -18,7 +18,7 @@ import { Auth } from '../../model/public_types'; import { AuthErrorCode } from '../errors'; import { _assert } from '../util/assert'; import { _castAuth } from './auth_impl'; -import { deepEqual } from '@firebase/util'; +import { deepEqual, isCloudWorkstation, pingServer } from '@firebase/util'; /** * Changes the {@link Auth} instance to communicate with the Firebase Auth Emulator, instead of production @@ -100,6 +100,11 @@ export function connectAuthEmulator( if (!disableWarnings) { emitEmulatorWarning(); } + + // Workaround to get cookies in Firebase Studio + if (isCloudWorkstation(host)) { + void pingServer(`${protocol}//${host}:${port}`); + } } function extractProtocol(url: string): string { diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index dc170809143..c25a09039ac 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -24,6 +24,7 @@ import { import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { Provider } from '@firebase/component'; +import { isCloudWorkstation, pingServer } from '@firebase/util'; import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider'; import { Code, DataConnectError } from '../core/error'; @@ -237,6 +238,10 @@ export function connectDataConnectEmulator( port?: number, sslEnabled = false ): void { + // Workaround to get cookies in Firebase Studio + if (isCloudWorkstation(host)) { + void pingServer(`https://${host}${port ? `:${port}` : ''}`); + } dc.enableEmulator({ host, port, sslEnabled }); } diff --git a/packages/database/src/api/Database.ts b/packages/database/src/api/Database.ts index f247fc6288c..515e278b5c5 100644 --- a/packages/database/src/api/Database.ts +++ b/packages/database/src/api/Database.ts @@ -30,7 +30,8 @@ import { deepEqual, EmulatorMockTokenOptions, getDefaultEmulatorHostnameAndPort, - isCloudWorkstation + isCloudWorkstation, + pingServer } from '@firebase/util'; import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider'; @@ -389,6 +390,11 @@ export function connectDatabaseEmulator( tokenProvider = new EmulatorTokenProvider(token); } + // Workaround to get cookies in Firebase Studio + if (isCloudWorkstation(host)) { + void pingServer(host); + } + // Modify the repo to apply emulator settings repoManagerApplyEmulatorSettings(repo, hostAndPort, options, tokenProvider); } diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index 0757378a74c..a2feb19507f 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -21,7 +21,12 @@ import { FirebaseApp, getApp } from '@firebase/app'; -import { deepEqual, getDefaultEmulatorHostnameAndPort } from '@firebase/util'; +import { + deepEqual, + getDefaultEmulatorHostnameAndPort, + isCloudWorkstation, + pingServer +} from '@firebase/util'; import { User } from '../auth/user'; import { @@ -194,6 +199,11 @@ export function initializeFirestore( ); } + // Workaround to get cookies in Firebase Studio + if (settings.host && isCloudWorkstation(settings.host)) { + void pingServer(settings.host); + } + return provider.initialize({ options: settings, instanceIdentifier: databaseId diff --git a/packages/firestore/src/lite-api/database.ts b/packages/firestore/src/lite-api/database.ts index 294206e54c2..8e7fdb27e90 100644 --- a/packages/firestore/src/lite-api/database.ts +++ b/packages/firestore/src/lite-api/database.ts @@ -27,7 +27,8 @@ import { deepEqual, EmulatorMockTokenOptions, getDefaultEmulatorHostnameAndPort, - isCloudWorkstation + isCloudWorkstation, + pingServer } from '@firebase/util'; import { @@ -333,6 +334,9 @@ export function connectFirestoreEmulator( emulatorOptions: firestore._getEmulatorOptions() }; const newHostSetting = `${host}:${port}`; + if (useSsl) { + void pingServer(`https://${newHostSetting}`); + } if (settings.host !== DEFAULT_HOST && settings.host !== newHostSetting) { logWarn( 'Host has been set in both settings() and connectFirestoreEmulator(), emulator host ' + diff --git a/packages/functions/src/service.ts b/packages/functions/src/service.ts index dce52e1f19d..af9d8898d2e 100644 --- a/packages/functions/src/service.ts +++ b/packages/functions/src/service.ts @@ -30,7 +30,7 @@ import { Provider } from '@firebase/component'; import { FirebaseAuthInternalName } from '@firebase/auth-interop-types'; import { MessagingInternalComponentName } from '@firebase/messaging-interop-types'; import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types'; -import { isCloudWorkstation } from '@firebase/util'; +import { isCloudWorkstation, pingServer } from '@firebase/util'; export const DEFAULT_REGION = 'us-central1'; @@ -179,6 +179,10 @@ export function connectFunctionsEmulator( functionsInstance.emulatorOrigin = `http${ useSsl ? 's' : '' }://${host}:${port}`; + // Workaround to get cookies in Firebase Studio + if (useSsl) { + void pingServer(functionsInstance.emulatorOrigin); + } } /** diff --git a/packages/storage/src/service.ts b/packages/storage/src/service.ts index 8ad31ecb13c..741dd6eaa1a 100644 --- a/packages/storage/src/service.ts +++ b/packages/storage/src/service.ts @@ -45,7 +45,8 @@ import { FirebaseStorage } from './public-types'; import { createMockUserToken, EmulatorMockTokenOptions, - isCloudWorkstation + isCloudWorkstation, + pingServer } from '@firebase/util'; import { Connection, ConnectionType } from './implementation/connection'; @@ -146,6 +147,10 @@ export function connectStorageEmulator( ): void { storage.host = `${host}:${port}`; const useSsl = isCloudWorkstation(host); + // Workaround to get cookies in Firebase Studio + if (useSsl) { + void pingServer(`https://${storage.host}`); + } storage._isUsingEmulator = true; storage._protocol = useSsl ? 'https' : 'http'; const { mockUserToken } = options; diff --git a/packages/util/src/url.ts b/packages/util/src/url.ts index 33cec43bea9..e41d26594c2 100644 --- a/packages/util/src/url.ts +++ b/packages/util/src/url.ts @@ -22,3 +22,15 @@ export function isCloudWorkstation(host: string): boolean { return host.endsWith('.cloudworkstations.dev'); } + +/** + * Makes a fetch request to the given server. + * Mostly used for forwarding cookies in Firebase Studio. + * @public + */ +export async function pingServer(endpoint: string): Promise { + const result = await fetch(endpoint, { + credentials: 'include' + }); + return result.ok; +}