Skip to content

Commit 6d8b119

Browse files
committed
Merge branch 'main' into dl/genai
2 parents cc1726d + 080a90d commit 6d8b119

File tree

36 files changed

+374
-50
lines changed

36 files changed

+374
-50
lines changed

.changeset/nine-pugs-crash.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@firebase/auth": patch
3+
"@firebase/data-connect": patch
4+
"@firebase/database-compat": patch
5+
"@firebase/database": patch
6+
"@firebase/firestore": patch
7+
"@firebase/storage": patch
8+
"@firebase/util": patch
9+
---
10+
11+
Fix Auth Redirects on Firebase Studio

common/api-review/storage.api.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
5858
constructor(
5959
app: FirebaseApp, _authProvider: Provider<FirebaseAuthInternalName>,
6060
_appCheckProvider: Provider<AppCheckInternalComponentName>,
61-
_url?: string | undefined, _firebaseVersion?: string | undefined);
61+
_url?: string | undefined, _firebaseVersion?: string | undefined, _isUsingEmulator?: boolean);
6262
readonly app: FirebaseApp;
6363
// (undocumented)
6464
readonly _appCheckProvider: Provider<AppCheckInternalComponentName>;
@@ -77,6 +77,8 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
7777
_getAuthToken(): Promise<string | null>;
7878
get host(): string;
7979
set host(host: string);
80+
// (undocumented)
81+
_isUsingEmulator: boolean;
8082
// Warning: (ae-forgotten-export) The symbol "ConnectionType" needs to be exported by the entry point index.d.ts
8183
// Warning: (ae-forgotten-export) The symbol "RequestInfo" needs to be exported by the entry point index.d.ts
8284
// Warning: (ae-forgotten-export) The symbol "Connection" needs to be exported by the entry point index.d.ts

common/api-review/util.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ export function ordinal(i: number): string;
398398
// @public (undocumented)
399399
export type PartialObserver<T> = Partial<Observer<T>>;
400400

401+
// @public
402+
export function pingServer(endpoint: string): Promise<boolean>;
403+
401404
// Warning: (ae-internal-missing-underscore) The name "promiseWithTimeout" should be prefixed with an underscore because the declaration is marked as @internal
402405
//
403406
// @internal

packages/auth/src/api/index.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ describe('api/_performApiRequest', () => {
6060
auth = await testAuth();
6161
});
6262

63+
afterEach(() => {
64+
sinon.restore();
65+
});
66+
6367
context('with regular requests', () => {
6468
beforeEach(mockFetch.setUp);
6569
afterEach(mockFetch.tearDown);
@@ -80,6 +84,26 @@ describe('api/_performApiRequest', () => {
8084
expect(mock.calls[0].headers!.get(HttpHeader.X_CLIENT_VERSION)).to.eq(
8185
'testSDK/0.0.0'
8286
);
87+
expect(mock.calls[0].fullRequest?.credentials).to.be.undefined;
88+
});
89+
90+
it('should set credentials to "include" when using IDX and emulator', async () => {
91+
const mock = mockEndpoint(Endpoint.SIGN_UP, serverResponse);
92+
auth.emulatorConfig = {
93+
host: 'https://something.cloudworkstations.dev',
94+
protocol: '',
95+
port: 8,
96+
options: {
97+
disableWarnings: false
98+
}
99+
};
100+
await _performApiRequest<typeof request, typeof serverResponse>(
101+
auth,
102+
HttpMethod.POST,
103+
Endpoint.SIGN_UP,
104+
request
105+
);
106+
expect(mock.calls[0].fullRequest?.credentials).to.eq('include');
83107
});
84108

85109
it('should set the device language if available', async () => {

packages/auth/src/api/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { FirebaseError, isCloudflareWorker, querystring } from '@firebase/util';
18+
import {
19+
FirebaseError,
20+
isCloudflareWorker,
21+
isCloudWorkstation,
22+
querystring
23+
} from '@firebase/util';
1924

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

185+
if (auth.emulatorConfig && isCloudWorkstation(auth.emulatorConfig.host)) {
186+
fetchArgs.credentials = 'include';
187+
}
188+
180189
return FetchProvider.fetch()(
181190
await _getFinalTarget(auth, auth.config.apiHost, path, query),
182191
fetchArgs

packages/auth/src/core/auth/emulator.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { Auth } from '../../model/public_types';
1818
import { AuthErrorCode } from '../errors';
1919
import { _assert } from '../util/assert';
2020
import { _castAuth } from './auth_impl';
21-
import { deepEqual } from '@firebase/util';
21+
import { deepEqual, isCloudWorkstation, pingServer } from '@firebase/util';
2222

2323
/**
2424
* Changes the {@link Auth} instance to communicate with the Firebase Auth Emulator, instead of production
@@ -100,6 +100,11 @@ export function connectAuthEmulator(
100100
if (!disableWarnings) {
101101
emitEmulatorWarning();
102102
}
103+
104+
// Workaround to get cookies in Firebase Studio
105+
if (isCloudWorkstation(host)) {
106+
void pingServer(`${protocol}//${host}:${port}`);
107+
}
103108
}
104109

105110
function extractProtocol(url: string): string {

packages/auth/test/helpers/mock_fetch.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface Call {
2222
request?: object | string;
2323
method?: string;
2424
headers: Headers;
25+
fullRequest?: RequestInit;
2526
}
2627

2728
export interface Route {
@@ -59,7 +60,8 @@ const fakeFetch: typeof fetch = (
5960
calls.push({
6061
request: requestBody,
6162
method: request?.method,
62-
headers
63+
headers,
64+
fullRequest: request
6365
});
6466

6567
return Promise.resolve(

packages/data-connect/src/api/DataConnect.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types';
2525
import { FirebaseAuthInternalName } from '@firebase/auth-interop-types';
2626
import { Provider } from '@firebase/component';
27+
import { isCloudWorkstation, pingServer } from '@firebase/util';
2728

2829
import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider';
2930
import { Code, DataConnectError } from '../core/error';
@@ -237,6 +238,10 @@ export function connectDataConnectEmulator(
237238
port?: number,
238239
sslEnabled = false
239240
): void {
241+
// Workaround to get cookies in Firebase Studio
242+
if (isCloudWorkstation(host)) {
243+
void pingServer(`https://${host}${port ? `:${port}` : ''}`);
244+
}
240245
dc.enableEmulator({ host, port, sslEnabled });
241246
}
242247

packages/data-connect/src/network/fetch.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { isCloudWorkstation } from '@firebase/util';
19+
1820
import {
1921
Code,
2022
DataConnectError,
2123
DataConnectOperationError,
2224
DataConnectOperationFailureResponse
2325
} from '../core/error';
2426
import { SDK_VERSION } from '../core/version';
25-
import { logDebug, logError } from '../logger';
27+
import { logError } from '../logger';
2628

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

@@ -58,7 +60,8 @@ export function dcFetch<T, U>(
5860
accessToken: string | null,
5961
appCheckToken: string | null,
6062
_isUsingGen: boolean,
61-
_callerSdkType: CallerSdkType
63+
_callerSdkType: CallerSdkType,
64+
_isUsingEmulator: boolean
6265
): Promise<{ data: T; errors: Error[] }> {
6366
if (!connectFetch) {
6467
throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!');
@@ -77,14 +80,17 @@ export function dcFetch<T, U>(
7780
headers['X-Firebase-AppCheck'] = appCheckToken;
7881
}
7982
const bodyStr = JSON.stringify(body);
80-
logDebug(`Making request out to ${url} with body: ${bodyStr}`);
81-
82-
return connectFetch(url, {
83+
const fetchOptions: RequestInit = {
8384
body: bodyStr,
8485
method: 'POST',
8586
headers,
8687
signal
87-
})
88+
};
89+
if (isCloudWorkstation(url) && _isUsingEmulator) {
90+
fetchOptions.credentials = 'include';
91+
}
92+
93+
return connectFetch(url, fetchOptions)
8894
.catch(err => {
8995
throw new DataConnectError(
9096
Code.OTHER,

packages/data-connect/src/network/transport/rest.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class RESTTransport implements DataConnectTransport {
3636
private _accessToken: string | null = null;
3737
private _appCheckToken: string | null = null;
3838
private _lastToken: string | null = null;
39+
private _isUsingEmulator = false;
3940
constructor(
4041
options: DataConnectOptions,
4142
private apiKey?: string | undefined,
@@ -93,6 +94,7 @@ export class RESTTransport implements DataConnectTransport {
9394
}
9495
useEmulator(host: string, port?: number, isSecure?: boolean): void {
9596
this._host = host;
97+
this._isUsingEmulator = true;
9698
if (typeof port === 'number') {
9799
this._port = port;
98100
}
@@ -182,7 +184,8 @@ export class RESTTransport implements DataConnectTransport {
182184
this._accessToken,
183185
this._appCheckToken,
184186
this._isUsingGen,
185-
this._callerSdkType
187+
this._callerSdkType,
188+
this._isUsingEmulator
186189
)
187190
);
188191
return withAuth;
@@ -208,7 +211,8 @@ export class RESTTransport implements DataConnectTransport {
208211
this._accessToken,
209212
this._appCheckToken,
210213
this._isUsingGen,
211-
this._callerSdkType
214+
this._callerSdkType,
215+
this._isUsingEmulator
212216
);
213217
});
214218
return taskResult;

packages/data-connect/test/unit/fetch.test.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
import { expect, use } from 'chai';
1919
import chaiAsPromised from 'chai-as-promised';
2020
import * as sinon from 'sinon';
21+
import sinonChai from 'sinon-chai';
2122

2223
import { dcFetch, initializeFetch } from '../../src/network/fetch';
2324
import { CallerSdkType, CallerSdkTypeEnum } from '../../src/network/transport';
2425
use(chaiAsPromised);
26+
use(sinonChai);
2527
function mockFetch(json: object, reject: boolean): sinon.SinonStub {
2628
const fakeFetchImpl = sinon.stub().returns(
2729
Promise.resolve({
@@ -57,7 +59,8 @@ describe('fetch', () => {
5759
null,
5860
null,
5961
false,
60-
CallerSdkTypeEnum.Base
62+
CallerSdkTypeEnum.Base,
63+
false
6164
)
6265
).to.eventually.be.rejectedWith(message);
6366
});
@@ -81,7 +84,8 @@ describe('fetch', () => {
8184
null,
8285
null,
8386
false,
84-
CallerSdkTypeEnum.Base
87+
CallerSdkTypeEnum.Base,
88+
false
8589
)
8690
).to.eventually.be.rejectedWith(JSON.stringify(json));
8791
});
@@ -112,7 +116,8 @@ describe('fetch', () => {
112116
null,
113117
null,
114118
false,
115-
CallerSdkTypeEnum.Base
119+
CallerSdkTypeEnum.Base,
120+
false
116121
)
117122
).to.eventually.be.rejected.then(error => {
118123
expect(error.response.data).to.eq(json.data);
@@ -143,7 +148,8 @@ describe('fetch', () => {
143148
null,
144149
null,
145150
false, // _isUsingGen is false
146-
callerSdkType as CallerSdkType
151+
callerSdkType as CallerSdkType,
152+
false
147153
);
148154

149155
let expectedHeaderRegex: RegExp;
@@ -191,7 +197,8 @@ describe('fetch', () => {
191197
null,
192198
null,
193199
true, // _isUsingGen is true
194-
callerSdkType as CallerSdkType
200+
callerSdkType as CallerSdkType,
201+
false
195202
);
196203

197204
let expectedHeaderRegex: RegExp;
@@ -215,4 +222,30 @@ describe('fetch', () => {
215222
}
216223
}
217224
});
225+
it('should call credentials include if using emulator on cloud workstation', async () => {
226+
const json = {
227+
code: 200,
228+
message1: 'success'
229+
};
230+
const fakeFetchImpl = mockFetch(json, false);
231+
await dcFetch(
232+
'https://abc.cloudworkstations.dev',
233+
{
234+
name: 'n',
235+
operationName: 'n',
236+
variables: {}
237+
},
238+
{} as AbortController,
239+
null,
240+
null,
241+
null,
242+
true, // _isUsingGen is true
243+
CallerSdkTypeEnum.Base,
244+
true
245+
);
246+
expect(fakeFetchImpl).to.have.been.calledWithMatch(
247+
'https://abc.cloudworkstations.dev',
248+
{ credentials: 'include' }
249+
);
250+
});
218251
});

packages/database/src/api/Database.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import {
3030
deepEqual,
3131
EmulatorMockTokenOptions,
3232
getDefaultEmulatorHostnameAndPort,
33-
isCloudWorkstation
33+
isCloudWorkstation,
34+
pingServer
3435
} from '@firebase/util';
3536

3637
import { AppCheckTokenProvider } from '../core/AppCheckTokenProvider';
@@ -389,6 +390,11 @@ export function connectDatabaseEmulator(
389390
tokenProvider = new EmulatorTokenProvider(token);
390391
}
391392

393+
// Workaround to get cookies in Firebase Studio
394+
if (isCloudWorkstation(host)) {
395+
void pingServer(host);
396+
}
397+
392398
// Modify the repo to apply emulator settings
393399
repoManagerApplyEmulatorSettings(repo, hostAndPort, options, tokenProvider);
394400
}

packages/firestore/externs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"packages/util/dist/src/defaults.d.ts",
3131
"packages/util/dist/src/emulator.d.ts",
3232
"packages/util/dist/src/environment.d.ts",
33+
"packages/util/dist/src/url.d.ts",
3334
"packages/util/dist/src/compat.d.ts",
3435
"packages/util/dist/src/global.d.ts",
3536
"packages/util/dist/src/obj.d.ts",

packages/firestore/src/api/database.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import {
2121
FirebaseApp,
2222
getApp
2323
} from '@firebase/app';
24-
import { deepEqual, getDefaultEmulatorHostnameAndPort } from '@firebase/util';
24+
import {
25+
deepEqual,
26+
getDefaultEmulatorHostnameAndPort,
27+
isCloudWorkstation,
28+
pingServer
29+
} from '@firebase/util';
2530

2631
import { User } from '../auth/user';
2732
import {
@@ -194,6 +199,11 @@ export function initializeFirestore(
194199
);
195200
}
196201

202+
// Workaround to get cookies in Firebase Studio
203+
if (settings.host && isCloudWorkstation(settings.host)) {
204+
void pingServer(settings.host);
205+
}
206+
197207
return provider.initialize({
198208
options: settings,
199209
instanceIdentifier: databaseId

0 commit comments

Comments
 (0)