Skip to content

Commit 136bb73

Browse files
Merge main into release
2 parents b204e71 + 080a90d commit 136bb73

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+602
-65
lines changed

.changeset/nice-plants-thank.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"@firebase/database-compat": patch
3+
"@firebase/database": patch
4+
"@firebase/firestore": patch
5+
"@firebase/functions": patch
6+
"@firebase/storage": patch
7+
"@firebase/util": patch
8+
---
9+
10+
Auto Enable SSL for Firebase Studio

.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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ export function isBrowserExtension(): boolean;
269269
// @public
270270
export function isCloudflareWorker(): boolean;
271271

272+
// @public
273+
export function isCloudWorkstation(host: string): boolean;
274+
272275
// Warning: (ae-missing-release-tag) "isElectron" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
273276
//
274277
// @public
@@ -395,6 +398,9 @@ export function ordinal(i: number): string;
395398
// @public (undocumented)
396399
export type PartialObserver<T> = Partial<Observer<T>>;
397400

401+
// @public
402+
export function pingServer(endpoint: string): Promise<boolean>;
403+
398404
// Warning: (ae-internal-missing-underscore) The name "promiseWithTimeout" should be prefixed with an underscore because the declaration is marked as @internal
399405
//
400406
// @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-compat/test/database.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,17 @@ describe('Database Tests', () => {
292292
expect((db as any)._delegate._repo.repoInfo_.isUsingEmulator).to.be.false;
293293
});
294294

295+
it('uses ssl when useEmulator is called with ssl specified', () => {
296+
const db = firebase.database();
297+
const cloudWorkstation = 'abc.cloudworkstations.dev';
298+
db.useEmulator(cloudWorkstation, 80);
299+
expect((db as any)._delegate._repo.repoInfo_.isUsingEmulator).to.be.true;
300+
expect((db as any)._delegate._repo.repoInfo_.host).to.equal(
301+
`${cloudWorkstation}:80`
302+
);
303+
expect((db as any)._delegate._repo.repoInfo_.secure).to.be.true;
304+
});
305+
295306
it('cannot call useEmulator after use', () => {
296307
const db = (firebase as any).database();
297308

0 commit comments

Comments
 (0)