Skip to content

Commit

Permalink
FDC: Implement GMPID (#8486)
Browse files Browse the repository at this point in the history
  • Loading branch information
maneesht committed Sep 19, 2024
1 parent 7273df7 commit c23f54e
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 6 deletions.
1 change: 1 addition & 0 deletions packages/data-connect/src/api/DataConnect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export class DataConnect {
this._transport = new this._transportClass(
this.dataConnectOptions,
this.app.options.apiKey,
this.app.options.appId,
this._authTokenProvider,
this._appCheckTokenProvider,
undefined,
Expand Down
4 changes: 4 additions & 0 deletions packages/data-connect/src/network/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function dcFetch<T, U>(
url: string,
body: U,
{ signal }: AbortController,
appId: string | null,
accessToken: string | null,
appCheckToken: string | null,
_isUsingGen: boolean
Expand All @@ -48,6 +49,9 @@ export function dcFetch<T, U>(
if (accessToken) {
headers['X-Firebase-Auth-Token'] = accessToken;
}
if (appId) {
headers['x-firebase-gmpid'] = appId;
}
if (appCheckToken) {
headers['X-Firebase-AppCheck'] = appCheckToken;
}
Expand Down
1 change: 1 addition & 0 deletions packages/data-connect/src/network/transport/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface CancellableOperation<T> extends PromiseLike<{ data: T }> {
export type TransportClass = new (
options: DataConnectOptions,
apiKey?: string,
appId?: string,
authProvider?: AuthTokenProvider,
appCheckProvider?: AppCheckTokenProvider,
transportOptions?: TransportOptions,
Expand Down
3 changes: 3 additions & 0 deletions packages/data-connect/src/network/transport/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export class RESTTransport implements DataConnectTransport {
constructor(
options: DataConnectOptions,
private apiKey?: string | undefined,
private appId?: string,
private authProvider?: AuthTokenProvider | undefined,
private appCheckProvider?: AppCheckTokenProvider | undefined,
transportOptions?: TransportOptions | undefined,
Expand Down Expand Up @@ -175,6 +176,7 @@ export class RESTTransport implements DataConnectTransport {
variables: body
} as unknown as U, // TODO(mtewani): This is a patch, fix this.
abortController,
this.appId,
this._accessToken,
this._appCheckToken,
this._isUsingGen
Expand Down Expand Up @@ -203,6 +205,7 @@ export class RESTTransport implements DataConnectTransport {
variables: body
} as unknown as U,
abortController,
this.appId,
this._accessToken,
this._appCheckToken,
this._isUsingGen
Expand Down
1 change: 0 additions & 1 deletion packages/data-connect/test/emulatorSeeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ export async function setupQueries(
connection_string:
'postgresql://postgres:secretpassword@localhost:5432/postgres?sslmode=disable'
};
fs.writeFileSync('./emulator.json', JSON.stringify(toWrite));
return fetch(`http://localhost:${EMULATOR_PORT}/setupSchema`, {
method: 'POST',
body: JSON.stringify(toWrite)
Expand Down
20 changes: 18 additions & 2 deletions packages/data-connect/test/unit/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ describe('fetch', () => {
message
});
await expect(
dcFetch('http://localhost', {}, {} as AbortController, null, null, false)
dcFetch(
'http://localhost',
{},
{} as AbortController,
null,
null,
null,
false
)
).to.eventually.be.rejectedWith(message);
});
it('should throw a stringified message when the server responds with an error without a message property in the body', async () => {
Expand All @@ -51,7 +59,15 @@ describe('fetch', () => {
};
mockFetch(json);
await expect(
dcFetch('http://localhost', {}, {} as AbortController, null, null, false)
dcFetch(
'http://localhost',
{},
{} as AbortController,
null,
null,
null,
false
)
).to.eventually.be.rejectedWith(JSON.stringify(json));
});
});
84 changes: 84 additions & 0 deletions packages/data-connect/test/unit/gmpid.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @license
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { deleteApp, initializeApp, FirebaseApp } from '@firebase/app';
import { expect, use } from 'chai';
import * as sinon from 'sinon';
import sinonChai from 'sinon-chai';

import { DataConnect, executeQuery, getDataConnect, queryRef } from '../../src';
import { initializeFetch } from '../../src/network/fetch';

use(sinonChai);
const json = {
message: 'unauthorized'
};
const fakeFetchImpl = sinon.stub().returns(
Promise.resolve({
json: () => {
return Promise.resolve(json);
},
status: 401
} as Response)
);

describe('GMPID Tests', () => {
let dc: DataConnect;
let app: FirebaseApp;
const APPID = 'MYAPPID';
beforeEach(() => {
initializeFetch(fakeFetchImpl);
app = initializeApp({ projectId: 'p', appId: APPID }, 'fdsasdf'); // TODO(mtewani): Replace with util function
dc = getDataConnect(app, { connector: 'c', location: 'l', service: 's' });
});
afterEach(async () => {
await dc._delete();
await deleteApp(app);
});
it('should send a request with the corresponding gmpid if using the app id is specified', async () => {
// @ts-ignore
await executeQuery(queryRef(dc, '')).catch(() => {});
expect(fakeFetchImpl).to.be.calledWithMatch(
'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery',
{
headers: {
['x-firebase-gmpid']: APPID
}
}
);
});
it('should send a request with no gmpid if using the app id is not specified', async () => {
const app2 = initializeApp({ projectId: 'p' }, 'def'); // TODO(mtewani): Replace with util function
const dc2 = getDataConnect(app2, {
connector: 'c',
location: 'l',
service: 's'
});
// @ts-ignore
await executeQuery(queryRef(dc2, '')).catch(() => {});
expect(fakeFetchImpl).to.be.calledWithMatch(
'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery',
{
headers: {
['x-firebase-gmpid']: APPID
}
}
);
await dc2._delete();
await deleteApp(app2);
});
});
6 changes: 3 additions & 3 deletions packages/data-connect/test/unit/queries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe('Queries', () => {
it('[QUERY] should retry auth whenever the fetcher returns with unauthorized', async () => {
initializeFetch(fakeFetchImpl);
const authProvider = new FakeAuthProvider();
const rt = new RESTTransport(options, undefined, authProvider);
const rt = new RESTTransport(options, undefined, undefined, authProvider);
await expect(rt.invokeQuery('test', null)).to.eventually.be.rejectedWith(
json.message
);
Expand All @@ -77,7 +77,7 @@ describe('Queries', () => {
it('[MUTATION] should retry auth whenever the fetcher returns with unauthorized', async () => {
initializeFetch(fakeFetchImpl);
const authProvider = new FakeAuthProvider();
const rt = new RESTTransport(options, undefined, authProvider);
const rt = new RESTTransport(options, undefined, undefined, authProvider);
await expect(rt.invokeMutation('test', null)).to.eventually.be.rejectedWith(
json.message
);
Expand All @@ -86,7 +86,7 @@ describe('Queries', () => {
it("should not retry auth whenever the fetcher returns with unauthorized and the token doesn't change", async () => {
initializeFetch(fakeFetchImpl);
const authProvider = new FakeAuthProvider();
const rt = new RESTTransport(options, undefined, authProvider);
const rt = new RESTTransport(options, undefined, undefined, authProvider);
rt._setLastToken('initial token');
await expect(
rt.invokeQuery('test', null) as Promise<unknown>
Expand Down

0 comments on commit c23f54e

Please sign in to comment.