diff --git a/package.json b/package.json index ffd2a8da9e..bf0126d792 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "test:integration": "mocha test/integration/*.ts --slow 5000 --timeout 20000 --require ts-node/register", "test:coverage": "nyc npm run test:unit", "lint:src": "eslint src/ --ext .ts", + "lint:src:fix": "eslint src/ --ext .ts --fix", "lint:test": "eslint test/ --ext .ts", + "lint:test:fix": "eslint test/ --ext .ts --fix", "apidocs": "run-s api-extractor:local api-documenter", "api-extractor": "node generate-reports.js", "api-extractor:local": "npm run build && node generate-reports.js --local", diff --git a/src/data-connect/data-connect-api-client-internal.ts b/src/data-connect/data-connect-api-client-internal.ts index 3ef99bacea..79a72431ac 100644 --- a/src/data-connect/data-connect-api-client-internal.ts +++ b/src/data-connect/data-connect-api-client-internal.ts @@ -38,9 +38,17 @@ const FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT = const EXECUTE_GRAPH_QL_ENDPOINT = 'executeGraphql'; const EXECUTE_GRAPH_QL_READ_ENDPOINT = 'executeGraphqlRead'; -const DATA_CONNECT_CONFIG_HEADERS = { - 'X-Firebase-Client': `fire-admin-node/${utils.getSdkVersion()}` -}; + +function getHeaders(isUsingGen: boolean): { [key: string]: string } { + const headerValue = { + 'X-Firebase-Client': `fire-admin-node/${utils.getSdkVersion()}`, + 'X-Goog-Api-Client': utils.getMetricsHeader(), + }; + if (isUsingGen) { + headerValue['X-Goog-Api-Client'] += ' admin-js/gen'; + } + return headerValue; +} /** * Class that facilitates sending requests to the Firebase Data Connect backend API. @@ -50,6 +58,7 @@ const DATA_CONNECT_CONFIG_HEADERS = { export class DataConnectApiClient { private readonly httpClient: HttpClient; private projectId?: string; + private isUsingGen = false; constructor(private readonly connectorConfig: ConnectorConfig, private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { @@ -59,6 +68,14 @@ export class DataConnectApiClient { } this.httpClient = new DataConnectHttpClient(app as FirebaseApp); } + + /** + * Update whether the SDK is using a generated one or not. + * @param isUsingGen + */ + setIsUsingGen(isUsingGen: boolean): void { + this.isUsingGen = isUsingGen; + } /** * Execute arbitrary GraphQL, including both read and write queries @@ -117,7 +134,7 @@ export class DataConnectApiClient { const request: HttpRequestConfig = { method: 'POST', url, - headers: DATA_CONNECT_CONFIG_HEADERS, + headers: getHeaders(this.isUsingGen), data, }; const resp = await this.httpClient.send(request); diff --git a/src/data-connect/data-connect.ts b/src/data-connect/data-connect.ts index a689423257..366b756327 100644 --- a/src/data-connect/data-connect.ts +++ b/src/data-connect/data-connect.ts @@ -72,6 +72,14 @@ export class DataConnect { this.client = new DataConnectApiClient(connectorConfig, app); } + /** + * @param isUsingGen + * @internal + */ + useGen(isUsingGen: boolean): void { + this.client.setIsUsingGen(isUsingGen); + } + /** * Execute an arbitrary GraphQL query or mutation * diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index 3e5aa754d8..6e95619e6d 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -1092,7 +1092,9 @@ export class AuthorizedHttpClient extends HttpClient { requestCopy.httpAgent = this.app.options.httpAgent; } - requestCopy.headers['X-Goog-Api-Client'] = getMetricsHeader() + if (!requestCopy.headers['X-Goog-Api-Client']) { + requestCopy.headers['X-Goog-Api-Client'] = getMetricsHeader() + } return super.send(requestCopy); }); @@ -1126,7 +1128,9 @@ export class AuthorizedHttp2Client extends Http2Client { requestCopy.headers['x-goog-user-project'] = quotaProjectId; } - requestCopy.headers['X-Goog-Api-Client'] = getMetricsHeader() + if (!requestCopy.headers['X-Goog-Api-Client']) { + requestCopy.headers['X-Goog-Api-Client'] = getMetricsHeader() + } return super.send(requestCopy); }); diff --git a/test/unit/data-connect/data-connect-api-client-internal.spec.ts b/test/unit/data-connect/data-connect-api-client-internal.spec.ts index 8086802861..67c1541cd6 100644 --- a/test/unit/data-connect/data-connect-api-client-internal.spec.ts +++ b/test/unit/data-connect/data-connect-api-client-internal.spec.ts @@ -46,6 +46,12 @@ describe('DataConnectApiClient', () => { 'X-Goog-Api-Client': getMetricsHeader(), }; + const EXPECTED_HEADERS_WITH_GEN = { + 'Authorization': 'Bearer mock-token', + 'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`, + 'X-Goog-Api-Client': getMetricsHeader() + ' admin-js/gen', + }; + const EMULATOR_EXPECTED_HEADERS = { 'Authorization': 'Bearer owner', 'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`, @@ -229,6 +235,35 @@ describe('DataConnectApiClient', () => { }); }); }); + it('should use gen headers if set on success', () => { + interface UsersResponse { + users: [ + user: { + id: string; + name: string; + address: string; + } + ]; + } + apiClient.setIsUsingGen(true); + const stub = sandbox + .stub(HttpClient.prototype, 'send') + .resolves(utils.responseFrom(TEST_RESPONSE, 200)); + return apiClient.executeGraphql('query', {}) + .then((resp) => { + expect(resp.data.users).to.be.not.empty; + expect(resp.data.users[0].name).to.be.not.undefined; + expect(resp.data.users[0].address).to.be.not.undefined; + expect(resp.data.users).to.deep.equal(TEST_RESPONSE.data.users); + expect(stub).to.have.been.calledOnce.and.calledWith({ + method: 'POST', + url: `https://firebasedataconnect.googleapis.com/v1/projects/test-project/locations/${connectorConfig.location}/services/${connectorConfig.serviceId}:executeGraphql`, + headers: EXPECTED_HEADERS_WITH_GEN, + data: { query: 'query' } + }); + apiClient.setIsUsingGen(false); + }); + }); }); });