Skip to content

Add support for running the emulators in Cloud Workstation #8968

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 66 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
02c52c6
Added test project to fdc
maneesht Apr 11, 2025
37d0f18
Fixed formatting
maneesht Apr 11, 2025
a4d7813
Fix linting
maneesht Apr 11, 2025
170b15a
Excluded example integration from tsconfig
maneesht Apr 11, 2025
ec405ae
Fixed example-integration package.json
maneesht Apr 11, 2025
248f654
Added ssl check for firestore
maneesht Apr 23, 2025
14ef1bc
Added ssl checks for RTDB
maneesht Apr 23, 2025
a8fe7de
Merge remote-tracking branch 'origin/main' into mtewani/add-ssl-check…
maneesht Apr 23, 2025
df4a115
Removed unnecessary files
maneesht Apr 23, 2025
f867d3b
Removed unnecessary data connect changes
maneesht Apr 23, 2025
4f23f33
Create gentle-laws-kneel.md
maneesht Apr 23, 2025
e755d8c
Addressed feedback
maneesht Apr 28, 2025
ceed5ab
Addressed linting issue
maneesht Apr 28, 2025
44e2d8b
Added functions change
maneesht Apr 28, 2025
a1b85e2
Removed linting rules
maneesht Apr 28, 2025
d67b284
Updated useEmulator
maneesht Apr 28, 2025
a8b208b
Updated functions signatures
maneesht Apr 28, 2025
205f9ca
Updated docsite changes
maneesht Apr 28, 2025
4bc9433
Added tests
maneesht Apr 28, 2025
a32ea8b
Updated docsite
maneesht Apr 28, 2025
07f44b6
Fixed formatting
maneesht Apr 28, 2025
d305dec
Only checked if user is on cloudworkstation for ssl
maneesht Apr 29, 2025
fc6469a
Removed ssl from storage compat
maneesht Apr 29, 2025
0a0cb7a
Revert devsite changes
maneesht Apr 29, 2025
25f8be9
Added return type
maneesht Apr 29, 2025
59ab583
Fixed tests
maneesht Apr 29, 2025
d473c45
Removed changeset
maneesht Apr 29, 2025
43b4d6d
Create nice-plants-thank.md
maneesht Apr 29, 2025
5538911
Fixed test
maneesht Apr 29, 2025
54f2d7a
Merge branch 'mtewani/auto-ssl-cloudworkstations' of https://github.c…
maneesht Apr 29, 2025
0b807f9
Fixed firestore test
maneesht Apr 29, 2025
e6f8263
Added public tag to
maneesht Apr 29, 2025
8749b83
Added code to pass on credentials if using a cloud workstation
maneesht Apr 29, 2025
5ae4e9e
Addressed comments
maneesht Apr 24, 2025
9e408ac
Create nine-pugs-crash.md
maneesht Apr 23, 2025
83da6b1
Removed unused import
maneesht Apr 24, 2025
a8df49c
include storage changes
maneesht Apr 24, 2025
903d3b7
Removed unnecessary import
maneesht Apr 25, 2025
7291e56
Fix formatting
maneesht Apr 25, 2025
411b72d
Fixed data connect test
maneesht Apr 25, 2025
3ce5912
Included extra url in externs
maneesht Apr 25, 2025
ff41e76
Fixed fdc tests
maneesht Apr 25, 2025
c014176
Passed in emulator information
maneesht Apr 25, 2025
a57e5e9
Fixed formattign
maneesht Apr 25, 2025
f5930ab
WIP
maneesht Apr 25, 2025
b486cd7
WIP
maneesht Apr 28, 2025
79ea2d0
Got tests to work
maneesht Apr 28, 2025
cdb502c
Fixed up tests
maneesht Apr 28, 2025
c58a289
Fixed formatting
maneesht Apr 28, 2025
80f29d4
Added data connect test for fetches
maneesht Apr 29, 2025
9d76cb3
Cleaned up auth tests
maneesht Apr 29, 2025
2893ee2
Removed only from auth test
maneesht Apr 29, 2025
9d77dbb
More test cleanup
maneesht Apr 29, 2025
2722d47
Merge remote-tracking branch 'origin/main' into mtewani/fix-auth-redi…
maneesht Apr 29, 2025
862fb91
Added test for storage
maneesht Apr 29, 2025
6d63073
Make param required
maneesht Apr 29, 2025
48d56ed
Added test for fetch for firestore
maneesht Apr 29, 2025
7449c01
Fixed linting
maneesht Apr 29, 2025
11a6004
Really fix linting
maneesht Apr 29, 2025
28f8053
Brought back uncommented code
maneesht Apr 29, 2025
2646d94
Removed app check changes
maneesht Apr 30, 2025
c282b7c
Moved emulator calculation to rest connection
maneesht Apr 30, 2025
e61c657
Fixed linting
maneesht Apr 30, 2025
840dc60
Removed isEmulator check
maneesht Apr 30, 2025
e422e05
Updated fetch connection check
maneesht Apr 30, 2025
6d0b75e
Removed unnecessary package change
maneesht Apr 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/nine-pugs-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@firebase/auth": patch
"@firebase/data-connect": patch
"@firebase/database-compat": patch
"@firebase/database": patch
"@firebase/firestore": patch
"@firebase/storage": patch
"@firebase/util": patch
---

Fix Auth Redirects on Firebase Studio
4 changes: 3 additions & 1 deletion common/api-review/storage.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
constructor(
app: FirebaseApp, _authProvider: Provider<FirebaseAuthInternalName>,
_appCheckProvider: Provider<AppCheckInternalComponentName>,
_url?: string | undefined, _firebaseVersion?: string | undefined);
_url?: string | undefined, _firebaseVersion?: string | undefined, _isUsingEmulator?: boolean);
readonly app: FirebaseApp;
// (undocumented)
readonly _appCheckProvider: Provider<AppCheckInternalComponentName>;
Expand All @@ -77,6 +77,8 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
_getAuthToken(): Promise<string | null>;
get host(): string;
set host(host: string);
// (undocumented)
_isUsingEmulator: boolean;
// Warning: (ae-forgotten-export) The symbol "ConnectionType" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "RequestInfo" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "Connection" needs to be exported by the entry point index.d.ts
Expand Down
24 changes: 24 additions & 0 deletions packages/auth/src/api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ describe('api/_performApiRequest', () => {
auth = await testAuth();
});

afterEach(() => {
sinon.restore();
});

context('with regular requests', () => {
beforeEach(mockFetch.setUp);
afterEach(mockFetch.tearDown);
Expand All @@ -80,6 +84,26 @@ describe('api/_performApiRequest', () => {
expect(mock.calls[0].headers!.get(HttpHeader.X_CLIENT_VERSION)).to.eq(
'testSDK/0.0.0'
);
expect(mock.calls[0].fullRequest?.credentials).to.be.undefined;
});

it('should set credentials to "include" when using IDX and emulator', async () => {
const mock = mockEndpoint(Endpoint.SIGN_UP, serverResponse);
auth.emulatorConfig = {
host: 'https://something.cloudworkstations.dev',
protocol: '',
port: 8,
options: {
disableWarnings: false
}
};
await _performApiRequest<typeof request, typeof serverResponse>(
auth,
HttpMethod.POST,
Endpoint.SIGN_UP,
request
);
expect(mock.calls[0].fullRequest?.credentials).to.eq('include');
});

it('should set the device language if available', async () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/auth/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
* limitations under the License.
*/

import { FirebaseError, isCloudflareWorker, querystring } from '@firebase/util';
import {
FirebaseError,
isCloudflareWorker,
isCloudWorkstation,
querystring
} from '@firebase/util';

import { AuthErrorCode, NamedErrorParams } from '../core/errors';
import {
Expand Down Expand Up @@ -177,6 +182,10 @@ export async function _performApiRequest<T, V>(
fetchArgs.referrerPolicy = 'no-referrer';
}

if (auth.emulatorConfig && isCloudWorkstation(auth.emulatorConfig.host)) {
fetchArgs.credentials = 'include';
}

return FetchProvider.fetch()(
await _getFinalTarget(auth, auth.config.apiHost, path, query),
fetchArgs
Expand Down
4 changes: 3 additions & 1 deletion packages/auth/test/helpers/mock_fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface Call {
request?: object | string;
method?: string;
headers: Headers;
fullRequest?: RequestInit;
}

export interface Route {
Expand Down Expand Up @@ -59,7 +60,8 @@ const fakeFetch: typeof fetch = (
calls.push({
request: requestBody,
method: request?.method,
headers
headers,
fullRequest: request
});

return Promise.resolve(
Expand Down
18 changes: 12 additions & 6 deletions packages/data-connect/src/network/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@
* limitations under the License.
*/

import { isCloudWorkstation } from '@firebase/util';

import {
Code,
DataConnectError,
DataConnectOperationError,
DataConnectOperationFailureResponse
} from '../core/error';
import { SDK_VERSION } from '../core/version';
import { logDebug, logError } from '../logger';
import { logError } from '../logger';

import { CallerSdkType, CallerSdkTypeEnum } from './transport';

Expand Down Expand Up @@ -58,7 +60,8 @@ export function dcFetch<T, U>(
accessToken: string | null,
appCheckToken: string | null,
_isUsingGen: boolean,
_callerSdkType: CallerSdkType
_callerSdkType: CallerSdkType,
_isUsingEmulator: boolean
): Promise<{ data: T; errors: Error[] }> {
if (!connectFetch) {
throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!');
Expand All @@ -77,14 +80,17 @@ export function dcFetch<T, U>(
headers['X-Firebase-AppCheck'] = appCheckToken;
}
const bodyStr = JSON.stringify(body);
logDebug(`Making request out to ${url} with body: ${bodyStr}`);

return connectFetch(url, {
const fetchOptions: RequestInit = {
body: bodyStr,
method: 'POST',
headers,
signal
})
};
if (isCloudWorkstation(url) && _isUsingEmulator) {
fetchOptions.credentials = 'include';
}

return connectFetch(url, fetchOptions)
.catch(err => {
throw new DataConnectError(
Code.OTHER,
Expand Down
8 changes: 6 additions & 2 deletions packages/data-connect/src/network/transport/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class RESTTransport implements DataConnectTransport {
private _accessToken: string | null = null;
private _appCheckToken: string | null = null;
private _lastToken: string | null = null;
private _isUsingEmulator = false;
constructor(
options: DataConnectOptions,
private apiKey?: string | undefined,
Expand Down Expand Up @@ -93,6 +94,7 @@ export class RESTTransport implements DataConnectTransport {
}
useEmulator(host: string, port?: number, isSecure?: boolean): void {
this._host = host;
this._isUsingEmulator = true;
if (typeof port === 'number') {
this._port = port;
}
Expand Down Expand Up @@ -182,7 +184,8 @@ export class RESTTransport implements DataConnectTransport {
this._accessToken,
this._appCheckToken,
this._isUsingGen,
this._callerSdkType
this._callerSdkType,
this._isUsingEmulator
)
);
return withAuth;
Expand All @@ -208,7 +211,8 @@ export class RESTTransport implements DataConnectTransport {
this._accessToken,
this._appCheckToken,
this._isUsingGen,
this._callerSdkType
this._callerSdkType,
this._isUsingEmulator
);
});
return taskResult;
Expand Down
43 changes: 38 additions & 5 deletions packages/data-connect/test/unit/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@
import { expect, use } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import * as sinon from 'sinon';
import sinonChai from 'sinon-chai';

import { dcFetch, initializeFetch } from '../../src/network/fetch';
import { CallerSdkType, CallerSdkTypeEnum } from '../../src/network/transport';
use(chaiAsPromised);
use(sinonChai);
function mockFetch(json: object, reject: boolean): sinon.SinonStub {
const fakeFetchImpl = sinon.stub().returns(
Promise.resolve({
Expand Down Expand Up @@ -57,7 +59,8 @@ describe('fetch', () => {
null,
null,
false,
CallerSdkTypeEnum.Base
CallerSdkTypeEnum.Base,
false
)
).to.eventually.be.rejectedWith(message);
});
Expand All @@ -81,7 +84,8 @@ describe('fetch', () => {
null,
null,
false,
CallerSdkTypeEnum.Base
CallerSdkTypeEnum.Base,
false
)
).to.eventually.be.rejectedWith(JSON.stringify(json));
});
Expand Down Expand Up @@ -112,7 +116,8 @@ describe('fetch', () => {
null,
null,
false,
CallerSdkTypeEnum.Base
CallerSdkTypeEnum.Base,
false
)
).to.eventually.be.rejected.then(error => {
expect(error.response.data).to.eq(json.data);
Expand Down Expand Up @@ -143,7 +148,8 @@ describe('fetch', () => {
null,
null,
false, // _isUsingGen is false
callerSdkType as CallerSdkType
callerSdkType as CallerSdkType,
false
);

let expectedHeaderRegex: RegExp;
Expand Down Expand Up @@ -191,7 +197,8 @@ describe('fetch', () => {
null,
null,
true, // _isUsingGen is true
callerSdkType as CallerSdkType
callerSdkType as CallerSdkType,
false
);

let expectedHeaderRegex: RegExp;
Expand All @@ -215,4 +222,30 @@ describe('fetch', () => {
}
}
});
it('should call credentials include if using emulator on cloud workstation', async () => {
const json = {
code: 200,
message1: 'success'
};
const fakeFetchImpl = mockFetch(json, false);
await dcFetch(
'https://abc.cloudworkstations.dev',
{
name: 'n',
operationName: 'n',
variables: {}
},
{} as AbortController,
null,
null,
null,
true, // _isUsingGen is true
CallerSdkTypeEnum.Base,
true
);
expect(fakeFetchImpl).to.have.been.calledWithMatch(
'https://abc.cloudworkstations.dev',
{ credentials: 'include' }
);
});
});
1 change: 1 addition & 0 deletions packages/firestore/externs.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"packages/util/dist/src/defaults.d.ts",
"packages/util/dist/src/emulator.d.ts",
"packages/util/dist/src/environment.d.ts",
"packages/util/dist/src/url.d.ts",
"packages/util/dist/src/compat.d.ts",
"packages/util/dist/src/global.d.ts",
"packages/util/dist/src/obj.d.ts",
Expand Down
3 changes: 2 additions & 1 deletion packages/firestore/src/core/database_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export class DatabaseInfo {
readonly forceLongPolling: boolean,
readonly autoDetectLongPolling: boolean,
readonly longPollingOptions: ExperimentalLongPollingOptions,
readonly useFetchStreams: boolean
readonly useFetchStreams: boolean,
readonly isUsingEmulator: boolean
) {}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/firestore/src/lite-api/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export function makeDatabaseInfo(
settings.experimentalForceLongPolling,
settings.experimentalAutoDetectLongPolling,
cloneLongPollingOptions(settings.experimentalLongPollingOptions),
settings.useFetchStreams
settings.useFetchStreams,
settings.isUsingEmulator
);
}
3 changes: 3 additions & 0 deletions packages/firestore/src/lite-api/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export class FirestoreSettingsImpl {
readonly useFetchStreams: boolean;
readonly localCache?: FirestoreLocalCache;

readonly isUsingEmulator: boolean;

// Can be a google-auth-library or gapi client.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
credentials?: any;
Expand All @@ -130,6 +132,7 @@ export class FirestoreSettingsImpl {
this.host = settings.host;
this.ssl = settings.ssl ?? DEFAULT_SSL;
}
this.isUsingEmulator = settings.emulatorOptions !== undefined;

this.credentials = settings.credentials;
this.ignoreUndefinedProperties = !!settings.ignoreUndefinedProperties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export class WebChannelConnection extends RestConnection {
rpcName: string,
url: string,
headers: StringMap,
body: Req
body: Req,
_forwardCredentials: boolean
): Promise<Resp> {
const streamId = generateUniqueDebugId();
return new Promise((resolve: Resolver<Resp>, reject: Rejecter) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,22 @@ export class FetchConnection extends RestConnection {
rpcName: string,
url: string,
headers: StringMap,
body: Req
body: Req,
forwardCredentials: boolean
): Promise<Resp> {
const requestJson = JSON.stringify(body);
let response: Response;

try {
response = await fetch(url, {
const fetchArgs: RequestInit = {
method: 'POST',
headers,
body: requestJson
});
};
if (forwardCredentials) {
fetchArgs.credentials = 'include';
}
response = await fetch(url, fetchArgs);
} catch (e) {
const err = e as { status: number | undefined; statusText: string };
throw new FirestoreError(
Expand Down
Loading
Loading