From 687cd6c3a4c50680dc8c631a5b3e1776088cf719 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 12 Feb 2025 19:08:55 -0500 Subject: [PATCH] fix(dc): Use emulated credentials with Data Connect Emulator --- .../data-connect-api-client-internal.ts | 61 +++++++++++++++---- .../data-connect-api-client-internal.spec.ts | 4 +- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/data-connect/data-connect-api-client-internal.ts b/src/data-connect/data-connect-api-client-internal.ts index 969c8492f5..1f7b2da05c 100644 --- a/src/data-connect/data-connect-api-client-internal.ts +++ b/src/data-connect/data-connect-api-client-internal.ts @@ -25,10 +25,15 @@ import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { ConnectorConfig, ExecuteGraphqlResponse, GraphqlOptions } from './data-connect-api'; -// Data Connect backend constants -const DATA_CONNECT_HOST = 'https://firebasedataconnect.googleapis.com'; -const DATA_CONNECT_API_URL_FORMAT = - '{host}/v1alpha/projects/{projectId}/locations/{locationId}/services/{serviceId}:{endpointId}'; +const API_VERSION = 'v1alpha'; + +/** The Firebase Data Connect backend base URL format. */ +const FIREBASE_DATA_CONNECT_BASE_URL_FORMAT = + 'https://firebasedataconnect.googleapis.com/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}:{endpointId}'; + +/** Firebase Data Connect base URl format when using the Data Connect emultor. */ +const FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT = + 'http://{host}/{version}/projects/{projectId}/locations/{locationId}/services/{serviceId}:{endpointId}'; const EXECUTE_GRAPH_QL_ENDPOINT = 'executeGraphql'; const EXECUTE_GRAPH_QL_READ_ENDPOINT = 'executeGraphqlRead'; @@ -52,7 +57,7 @@ export class DataConnectApiClient { DATA_CONNECT_ERROR_CODE_MAPPING.INVALID_ARGUMENT, 'First argument passed to getDataConnect() must be a valid Firebase app instance.'); } - this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); + this.httpClient = new DataConnectHttpClient(app as FirebaseApp); } /** @@ -101,13 +106,12 @@ export class DataConnectApiClient { 'GraphqlOptions must be a non-null object'); } } - const host = (process.env.DATA_CONNECT_EMULATOR_HOST || DATA_CONNECT_HOST); const data = { query, ...(options?.variables && { variables: options?.variables }), ...(options?.operationName && { operationName: options?.operationName }), }; - return this.getUrl(host, this.connectorConfig.location, this.connectorConfig.serviceId, endpoint) + return this.getUrl(API_VERSION, this.connectorConfig.location, this.connectorConfig.serviceId, endpoint) .then(async (url) => { const request: HttpRequestConfig = { method: 'POST', @@ -133,18 +137,25 @@ export class DataConnectApiClient { }); } - private async getUrl(host: string, locationId: string, serviceId: string, endpointId: string): Promise { + private async getUrl(version: string, locationId: string, serviceId: string, endpointId: string): Promise { return this.getProjectId() .then((projectId) => { const urlParams = { - host, + version, projectId, locationId, serviceId, endpointId }; - const baseUrl = utils.formatString(DATA_CONNECT_API_URL_FORMAT, urlParams); - return utils.formatString(baseUrl); + let urlFormat: string; + if (useEmulator()) { + urlFormat = utils.formatString(FIREBASE_DATA_CONNECT_EMULATOR_BASE_URL_FORMAT, { + host: emulatorHost() + }); + } else { + urlFormat = FIREBASE_DATA_CONNECT_BASE_URL_FORMAT; + } + return utils.formatString(urlFormat, urlParams); }); } @@ -188,6 +199,34 @@ export class DataConnectApiClient { } } +/** + * Data Connect-specific HTTP client which uses the special "owner" token + * when communicating with the Data Connect Emulator. + */ +class DataConnectHttpClient extends AuthorizedHttpClient { + + protected getToken(): Promise { + if (useEmulator()) { + return Promise.resolve('owner'); + } + + return super.getToken(); + } + +} + +function emulatorHost(): string | undefined { + return process.env.DATA_CONNECT_EMULATOR_HOST +} + +/** + * When true the SDK should communicate with the Data Connect Emulator for all API + * calls and also produce unsigned tokens. + */ +export function useEmulator(): boolean { + return !!emulatorHost(); +} + interface ErrorResponse { error?: ServerError; } 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 93b7120703..9787a2d7c9 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 @@ -209,7 +209,7 @@ describe('DataConnectApiClient', () => { }); it('should use DATA_CONNECT_EMULATOR_HOST if set', () => { - process.env.DATA_CONNECT_EMULATOR_HOST = 'http://localhost:9000'; + process.env.DATA_CONNECT_EMULATOR_HOST = 'localhost:9399'; const stub = sandbox .stub(HttpClient.prototype, 'send') .resolves(utils.responseFrom(TEST_RESPONSE, 200)); @@ -217,7 +217,7 @@ describe('DataConnectApiClient', () => { .then(() => { expect(stub).to.have.been.calledOnce.and.calledWith({ method: 'POST', - url: `http://localhost:9000/v1alpha/projects/test-project/locations/${connectorConfig.location}/services/${connectorConfig.serviceId}:executeGraphql`, + url: `http://localhost:9399/v1alpha/projects/test-project/locations/${connectorConfig.location}/services/${connectorConfig.serviceId}:executeGraphql`, headers: EXPECTED_HEADERS, data: { query: 'query' } });