Skip to content

Commit c23f54e

Browse files
authored
FDC: Implement GMPID (#8486)
1 parent 7273df7 commit c23f54e

File tree

8 files changed

+114
-6
lines changed

8 files changed

+114
-6
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export class DataConnect {
160160
this._transport = new this._transportClass(
161161
this.dataConnectOptions,
162162
this.app.options.apiKey,
163+
this.app.options.appId,
163164
this._authTokenProvider,
164165
this._appCheckTokenProvider,
165166
undefined,

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

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function dcFetch<T, U>(
3434
url: string,
3535
body: U,
3636
{ signal }: AbortController,
37+
appId: string | null,
3738
accessToken: string | null,
3839
appCheckToken: string | null,
3940
_isUsingGen: boolean
@@ -48,6 +49,9 @@ export function dcFetch<T, U>(
4849
if (accessToken) {
4950
headers['X-Firebase-Auth-Token'] = accessToken;
5051
}
52+
if (appId) {
53+
headers['x-firebase-gmpid'] = appId;
54+
}
5155
if (appCheckToken) {
5256
headers['X-Firebase-AppCheck'] = appCheckToken;
5357
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface CancellableOperation<T> extends PromiseLike<{ data: T }> {
4545
export type TransportClass = new (
4646
options: DataConnectOptions,
4747
apiKey?: string,
48+
appId?: string,
4849
authProvider?: AuthTokenProvider,
4950
appCheckProvider?: AppCheckTokenProvider,
5051
transportOptions?: TransportOptions,

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

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class RESTTransport implements DataConnectTransport {
3939
constructor(
4040
options: DataConnectOptions,
4141
private apiKey?: string | undefined,
42+
private appId?: string,
4243
private authProvider?: AuthTokenProvider | undefined,
4344
private appCheckProvider?: AppCheckTokenProvider | undefined,
4445
transportOptions?: TransportOptions | undefined,
@@ -175,6 +176,7 @@ export class RESTTransport implements DataConnectTransport {
175176
variables: body
176177
} as unknown as U, // TODO(mtewani): This is a patch, fix this.
177178
abortController,
179+
this.appId,
178180
this._accessToken,
179181
this._appCheckToken,
180182
this._isUsingGen
@@ -203,6 +205,7 @@ export class RESTTransport implements DataConnectTransport {
203205
variables: body
204206
} as unknown as U,
205207
abortController,
208+
this.appId,
206209
this._accessToken,
207210
this._appCheckToken,
208211
this._isUsingGen

packages/data-connect/test/emulatorSeeder.ts

-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ export async function setupQueries(
8282
connection_string:
8383
'postgresql://postgres:secretpassword@localhost:5432/postgres?sslmode=disable'
8484
};
85-
fs.writeFileSync('./emulator.json', JSON.stringify(toWrite));
8685
return fetch(`http://localhost:${EMULATOR_PORT}/setupSchema`, {
8786
method: 'POST',
8887
body: JSON.stringify(toWrite)

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

+18-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,15 @@ describe('fetch', () => {
4040
message
4141
});
4242
await expect(
43-
dcFetch('http://localhost', {}, {} as AbortController, null, null, false)
43+
dcFetch(
44+
'http://localhost',
45+
{},
46+
{} as AbortController,
47+
null,
48+
null,
49+
null,
50+
false
51+
)
4452
).to.eventually.be.rejectedWith(message);
4553
});
4654
it('should throw a stringified message when the server responds with an error without a message property in the body', async () => {
@@ -51,7 +59,15 @@ describe('fetch', () => {
5159
};
5260
mockFetch(json);
5361
await expect(
54-
dcFetch('http://localhost', {}, {} as AbortController, null, null, false)
62+
dcFetch(
63+
'http://localhost',
64+
{},
65+
{} as AbortController,
66+
null,
67+
null,
68+
null,
69+
false
70+
)
5571
).to.eventually.be.rejectedWith(JSON.stringify(json));
5672
});
5773
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* @license
3+
* Copyright 2024 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { deleteApp, initializeApp, FirebaseApp } from '@firebase/app';
19+
import { expect, use } from 'chai';
20+
import * as sinon from 'sinon';
21+
import sinonChai from 'sinon-chai';
22+
23+
import { DataConnect, executeQuery, getDataConnect, queryRef } from '../../src';
24+
import { initializeFetch } from '../../src/network/fetch';
25+
26+
use(sinonChai);
27+
const json = {
28+
message: 'unauthorized'
29+
};
30+
const fakeFetchImpl = sinon.stub().returns(
31+
Promise.resolve({
32+
json: () => {
33+
return Promise.resolve(json);
34+
},
35+
status: 401
36+
} as Response)
37+
);
38+
39+
describe('GMPID Tests', () => {
40+
let dc: DataConnect;
41+
let app: FirebaseApp;
42+
const APPID = 'MYAPPID';
43+
beforeEach(() => {
44+
initializeFetch(fakeFetchImpl);
45+
app = initializeApp({ projectId: 'p', appId: APPID }, 'fdsasdf'); // TODO(mtewani): Replace with util function
46+
dc = getDataConnect(app, { connector: 'c', location: 'l', service: 's' });
47+
});
48+
afterEach(async () => {
49+
await dc._delete();
50+
await deleteApp(app);
51+
});
52+
it('should send a request with the corresponding gmpid if using the app id is specified', async () => {
53+
// @ts-ignore
54+
await executeQuery(queryRef(dc, '')).catch(() => {});
55+
expect(fakeFetchImpl).to.be.calledWithMatch(
56+
'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery',
57+
{
58+
headers: {
59+
['x-firebase-gmpid']: APPID
60+
}
61+
}
62+
);
63+
});
64+
it('should send a request with no gmpid if using the app id is not specified', async () => {
65+
const app2 = initializeApp({ projectId: 'p' }, 'def'); // TODO(mtewani): Replace with util function
66+
const dc2 = getDataConnect(app2, {
67+
connector: 'c',
68+
location: 'l',
69+
service: 's'
70+
});
71+
// @ts-ignore
72+
await executeQuery(queryRef(dc2, '')).catch(() => {});
73+
expect(fakeFetchImpl).to.be.calledWithMatch(
74+
'https://firebasedataconnect.googleapis.com/v1alpha/projects/p/locations/l/services/s/connectors/c:executeQuery',
75+
{
76+
headers: {
77+
['x-firebase-gmpid']: APPID
78+
}
79+
}
80+
);
81+
await dc2._delete();
82+
await deleteApp(app2);
83+
});
84+
});

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe('Queries', () => {
6868
it('[QUERY] should retry auth whenever the fetcher returns with unauthorized', async () => {
6969
initializeFetch(fakeFetchImpl);
7070
const authProvider = new FakeAuthProvider();
71-
const rt = new RESTTransport(options, undefined, authProvider);
71+
const rt = new RESTTransport(options, undefined, undefined, authProvider);
7272
await expect(rt.invokeQuery('test', null)).to.eventually.be.rejectedWith(
7373
json.message
7474
);
@@ -77,7 +77,7 @@ describe('Queries', () => {
7777
it('[MUTATION] should retry auth whenever the fetcher returns with unauthorized', async () => {
7878
initializeFetch(fakeFetchImpl);
7979
const authProvider = new FakeAuthProvider();
80-
const rt = new RESTTransport(options, undefined, authProvider);
80+
const rt = new RESTTransport(options, undefined, undefined, authProvider);
8181
await expect(rt.invokeMutation('test', null)).to.eventually.be.rejectedWith(
8282
json.message
8383
);
@@ -86,7 +86,7 @@ describe('Queries', () => {
8686
it("should not retry auth whenever the fetcher returns with unauthorized and the token doesn't change", async () => {
8787
initializeFetch(fakeFetchImpl);
8888
const authProvider = new FakeAuthProvider();
89-
const rt = new RESTTransport(options, undefined, authProvider);
89+
const rt = new RESTTransport(options, undefined, undefined, authProvider);
9090
rt._setLastToken('initial token');
9191
await expect(
9292
rt.invokeQuery('test', null) as Promise<unknown>

0 commit comments

Comments
 (0)