diff --git a/.changeset/hip-chefs-attack.md b/.changeset/hip-chefs-attack.md new file mode 100644 index 00000000000..84d7f6ea05b --- /dev/null +++ b/.changeset/hip-chefs-attack.md @@ -0,0 +1,5 @@ +--- +"@firebase/data-connect": patch +--- + +Fix issue where ipv6 addresses weren't getting parsed correctly when setting FIREBASE_DATA_CONNECT_EMULATOR_HOST diff --git a/packages/data-connect/src/api/DataConnect.ts b/packages/data-connect/src/api/DataConnect.ts index dc170809143..b03613d1171 100644 --- a/packages/data-connect/src/api/DataConnect.ts +++ b/packages/data-connect/src/api/DataConnect.ts @@ -57,8 +57,8 @@ export interface ConnectorConfig { */ export interface TransportOptions { host: string; - sslEnabled?: boolean; port?: number; + sslEnabled?: boolean; } const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = @@ -71,11 +71,22 @@ const FIREBASE_DATA_CONNECT_EMULATOR_HOST_VAR = * @internal */ export function parseOptions(fullHost: string): TransportOptions { - const [protocol, hostName] = fullHost.split('://'); - const isSecure = protocol === 'https'; - const [host, portAsString] = hostName.split(':'); - const port = Number(portAsString); - return { host, port, sslEnabled: isSecure }; + const trimmedHost = fullHost.trim(); + if (fullHost.includes('://')) { + const [protocol, host] = trimmedHost.split('://'); + if (protocol !== 'http' && protocol !== 'https') { + throw new DataConnectError( + Code.INVALID_ARGUMENT, + `Protocol ${protocol} is not supported. Use 'http' or 'https' instead.` + ); + } + const isSecure = protocol === 'https'; + return { host, sslEnabled: isSecure }; + } + return { + host: trimmedHost, + sslEnabled: false + }; } /** * DataConnectOptions including project id @@ -237,7 +248,8 @@ export function connectDataConnectEmulator( port?: number, sslEnabled = false ): void { - dc.enableEmulator({ host, port, sslEnabled }); + const hostWithPort = port ? `${host}:${port}` : host; + dc.enableEmulator({ host: hostWithPort, sslEnabled }); } /** diff --git a/packages/data-connect/src/core/QueryManager.ts b/packages/data-connect/src/core/QueryManager.ts index 8b7c59aea85..e8997614a6d 100644 --- a/packages/data-connect/src/core/QueryManager.ts +++ b/packages/data-connect/src/core/QueryManager.ts @@ -221,9 +221,6 @@ export class QueryManager { return newR; } - enableEmulator(host: string, port: number): void { - this.transport.useEmulator(host, port); - } } function compareDates(str1: string, str2: string): boolean { const date1 = new Date(str1); diff --git a/packages/data-connect/src/util/url.ts b/packages/data-connect/src/util/url.ts index 50630582761..5cd66cd6a01 100644 --- a/packages/data-connect/src/util/url.ts +++ b/packages/data-connect/src/util/url.ts @@ -16,27 +16,16 @@ */ import { DataConnectOptions, TransportOptions } from '../api/DataConnect'; -import { Code, DataConnectError } from '../core/error'; -import { logError } from '../logger'; export function urlBuilder( projectConfig: DataConnectOptions, transportOptions: TransportOptions ): string { const { connector, location, projectId: project, service } = projectConfig; - const { host, sslEnabled, port } = transportOptions; + const { host, sslEnabled } = transportOptions; const protocol = sslEnabled ? 'https' : 'http'; const realHost = host || `firebasedataconnect.googleapis.com`; - let baseUrl = `${protocol}://${realHost}`; - if (typeof port === 'number') { - baseUrl += `:${port}`; - } else if (typeof port !== 'undefined') { - logError('Port type is of an invalid type'); - throw new DataConnectError( - Code.INVALID_ARGUMENT, - 'Incorrect type for port passed in!' - ); - } + const baseUrl = `${protocol}://${realHost}`; return `${baseUrl}/v1/projects/${project}/locations/${location}/services/${service}/connectors/${connector}`; } export function addToken(url: string, apiKey?: string): string { diff --git a/packages/data-connect/test/dataconnect/index.esm.js b/packages/data-connect/test/dataconnect/index.esm.js deleted file mode 100644 index 6c7c8f8a49a..00000000000 --- a/packages/data-connect/test/dataconnect/index.esm.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @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 { - getDataConnect, - queryRef, - mutationRef, - executeQuery, - executeMutation -} from 'firebase/data-connect'; - -export const connectorConfig = { - connector: 'test', - service: 'dataconnect', - location: 'us-central1' -}; - -function validateArgs(dcOrVars, vars, validateVars) { - let dcInstance; - let realVars; - // TODO(mtewani); Check what happens if this is undefined. - if (dcOrVars && 'dataConnectOptions' in dcOrVars) { - dcInstance = dcOrVars; - realVars = vars; - } else { - dcInstance = getDataConnect(connectorConfig); - realVars = dcOrVars; - } - if (!dcInstance || (!realVars && validateVars)) { - throw new Error('You didn\t pass in the vars!'); - } - return { dc: dcInstance, vars: realVars }; -} diff --git a/packages/data-connect/test/dataconnect/logAddMovieVariables.json b/packages/data-connect/test/dataconnect/logAddMovieVariables.json deleted file mode 100644 index 92e237b649f..00000000000 --- a/packages/data-connect/test/dataconnect/logAddMovieVariables.json +++ /dev/null @@ -1 +0,0 @@ -{"Name":"addMovie","Kind":"mutation","Variables":[{"Name":"id","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""},{"Name":"name","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""},{"Name":"genre","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""},{"Name":"description","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""}],"Response":[{"Name":"movie_insert","TypeName":"Movie_Key","TypeInfo":{"Name":"Movie_Key","Kind":"TypeKey","Fields":[{"Name":"id","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""}],"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":{"Name":"Movie_Key","Kind":"TypeKey","Fields":[{"Name":"id","TypeName":"String","TypeInfo":{"Name":"String","Kind":"NativeScalar","Fields":null,"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"FullTypeInfo":null,"Attribute":"NonNull","DefaultValue":null,"Description":""}],"KeyTypeInfo":null,"HasPrimaryKeyFields":false,"Description":""},"Attribute":"NonNull","DefaultValue":null,"Description":""}],"Description":"# Example mutations\n# TODO: Replace with a really good illustrative example from devrel!\nmutation createOrder($name: String!) {\n order_insert(data : {name: $name})\n}"} \ No newline at end of file diff --git a/packages/data-connect/test/dataconnect/logListAllMoviesMovies.json b/packages/data-connect/test/dataconnect/logListAllMoviesMovies.json deleted file mode 100644 index ec747fa47dd..00000000000 --- a/packages/data-connect/test/dataconnect/logListAllMoviesMovies.json +++ /dev/null @@ -1 +0,0 @@ -null \ No newline at end of file diff --git a/packages/data-connect/test/dataconnect/logListMovieIdsMovies.json b/packages/data-connect/test/dataconnect/logListMovieIdsMovies.json deleted file mode 100644 index ec747fa47dd..00000000000 --- a/packages/data-connect/test/dataconnect/logListMovieIdsMovies.json +++ /dev/null @@ -1 +0,0 @@ -null \ No newline at end of file diff --git a/packages/data-connect/test/dataconnect/movies.tools.json b/packages/data-connect/test/dataconnect/movies.tools.json deleted file mode 100644 index f6938f8c163..00000000000 --- a/packages/data-connect/test/dataconnect/movies.tools.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "connector": "movies", - "location": "us-central1", - "service": "dataconnect", - "tools": [] -} \ No newline at end of file diff --git a/packages/data-connect/test/dataconnect/test.tools.json b/packages/data-connect/test/dataconnect/test.tools.json deleted file mode 100644 index a048e8da310..00000000000 --- a/packages/data-connect/test/dataconnect/test.tools.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "connector": "test", - "location": "us-central1", - "service": "dataconnect", - "tools": [] -} \ No newline at end of file diff --git a/packages/data-connect/test/unit/dataconnect.test.ts b/packages/data-connect/test/unit/dataconnect.test.ts index 76d04e3b502..b53be406dbd 100644 --- a/packages/data-connect/test/unit/dataconnect.test.ts +++ b/packages/data-connect/test/unit/dataconnect.test.ts @@ -18,7 +18,7 @@ import { deleteApp, initializeApp } from '@firebase/app'; import { expect } from 'chai'; -import { getDataConnect } from '../../src'; +import { getDataConnect, parseOptions } from '../../src'; describe('Data Connect Test', () => { beforeEach(() => {}); @@ -60,4 +60,37 @@ describe('Data Connect Test', () => { expect(dc.app.options.projectId).to.eq(projectId); await deleteApp(customApp); }); + it('should parse env var correctly with http://', async () => { + const parsedHost = parseOptions('http://localhost'); + expect(parsedHost.host).to.eq('localhost'); + expect(parsedHost.sslEnabled).to.be.false; + }); + it('should parse env var correctly with port', async () => { + const parsedHost = parseOptions('localhost:8080'); + expect(parsedHost.host).to.eq('localhost:8080'); + expect(parsedHost.sslEnabled).to.be.false; + }); + it('should parse env var correctly with https://', async () => { + const parsedHost = parseOptions('https://localhost'); + expect(parsedHost.host).to.eq('localhost'); + expect(parsedHost.sslEnabled).to.be.true; + }); + it('should parse ipv6 addresses correctly', async () => { + const host = '2001:0db8:85a3:0000:0000:8a2e:0370:7334'; + const parsedHost = parseOptions(host); + expect(parsedHost.host).to.eq(host); + expect(parsedHost.sslEnabled).to.be.false; + }); + it('should parse ipv6 localhost addresses correctly', async () => { + const host = '[::1]:8080'; + const parsedHost = parseOptions(host); + expect(parsedHost.host).to.eq(host); + expect(parsedHost.sslEnabled).to.be.false; + }); + it('should throw for non-http protocols', async () => { + const host = 'ftp://localhost'; + expect(() => parseOptions(host)).to.throw( + "Protocol ftp is not supported. Use 'http' or 'https' instead." + ); + }); });