1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- import { Server , Socket , createServer } from 'node:net' ;
15+ import { Server , createServer } from 'node:net' ;
1616import tls from 'node:tls' ;
1717import { promisify } from 'node:util' ;
1818import { AuthClient , GoogleAuth } from 'google-auth-library' ;
@@ -22,6 +22,8 @@ import {IpAddressTypes} from './ip-addresses';
2222import { AuthTypes } from './auth-types' ;
2323import { SQLAdminFetcher } from './sqladmin-fetcher' ;
2424import { CloudSQLConnectorError } from './errors' ;
25+ import { SocketWrapper , SocketWrapperOptions } from './socket-wrapper' ;
26+ import stream from 'node:stream' ;
2527
2628// These Socket types are subsets from nodejs definitely typed repo, ref:
2729// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/ae0fe42ff0e6e820e8ae324acf4f8e944aa1b2b7/types/node/v18/net.d.ts#L437
@@ -42,19 +44,21 @@ export declare interface UnixSocketOptions {
4244export declare interface ConnectionOptions {
4345 authType ?: AuthTypes ;
4446 ipType ?: IpAddressTypes ;
45- instanceConnectionName : string ;
47+ instanceConnectionName ? : string ;
4648}
4749
4850export declare interface SocketConnectionOptions extends ConnectionOptions {
4951 listenOptions : UnixSocketOptions ;
5052}
5153
5254interface StreamFunction {
53- ( ) : tls . TLSSocket ;
55+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
56+ ( ...opts : any | undefined ) : stream . Duplex ;
5457}
5558
5659interface PromisedStreamFunction {
57- ( ) : Promise < tls . TLSSocket > ;
60+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
61+ ( ...opts : any | undefined ) : Promise < stream . Duplex > ;
5862}
5963
6064// DriverOptions is the interface describing the object returned by
@@ -86,11 +90,17 @@ class CloudSQLInstanceMap extends Map {
8690 authType : AuthTypes ;
8791 instanceConnectionName : string ;
8892 sqlAdminFetcher : SQLAdminFetcher ;
89- } ) : Promise < void > {
93+ } ) : Promise < CloudSQLInstance > {
9094 // in case an instance to that connection name has already
9195 // been setup there's no need to set it up again
9296 if ( this . has ( instanceConnectionName ) ) {
9397 const instance = this . get ( instanceConnectionName ) ;
98+ if ( ! instance ) {
99+ throw new CloudSQLConnectorError ( {
100+ message : `Cannot find info for instance: ${ instanceConnectionName } ` ,
101+ code : 'ENOINSTANCEINFO' ,
102+ } ) ;
103+ }
94104 if ( instance . authType && instance . authType !== authType ) {
95105 throw new CloudSQLConnectorError ( {
96106 message :
@@ -100,42 +110,23 @@ class CloudSQLInstanceMap extends Map {
100110 code : 'EMISMATCHAUTHTYPE' ,
101111 } ) ;
102112 }
103- return ;
113+ return instance ;
104114 }
115+
105116 const connectionInstance = await CloudSQLInstance . getCloudSQLInstance ( {
106117 ipType,
107118 authType,
108119 instanceConnectionName,
109120 sqlAdminFetcher : sqlAdminFetcher ,
110121 } ) ;
111- this . set ( instanceConnectionName , connectionInstance ) ;
112- }
113-
114- getInstance ( {
115- instanceConnectionName,
116- authType,
117- } : {
118- instanceConnectionName : string ;
119- authType : AuthTypes ;
120- } ) : CloudSQLInstance {
121- const connectionInstance = this . get ( instanceConnectionName ) ;
122122 if ( ! connectionInstance ) {
123123 throw new CloudSQLConnectorError ( {
124124 message : `Cannot find info for instance: ${ instanceConnectionName } ` ,
125125 code : 'ENOINSTANCEINFO' ,
126126 } ) ;
127- } else if (
128- connectionInstance . authType &&
129- connectionInstance . authType !== authType
130- ) {
131- throw new CloudSQLConnectorError ( {
132- message :
133- `getOptions called for instance ${ instanceConnectionName } with authType ${ authType } , ` +
134- `but was previously called with authType ${ connectionInstance . authType } . ` +
135- 'If you require both for your use case, please use a new connector object.' ,
136- code : 'EMISMATCHAUTHTYPE' ,
137- } ) ;
138127 }
128+ this . set ( instanceConnectionName , connectionInstance ) ;
129+
139130 return connectionInstance ;
140131 }
141132}
@@ -158,7 +149,7 @@ export class Connector {
158149 private readonly instances : CloudSQLInstanceMap ;
159150 private readonly sqlAdminFetcher : SQLAdminFetcher ;
160151 private readonly localProxies : Set < Server > ;
161- private readonly sockets : Set < Socket > ;
152+ private readonly sockets : Set < stream . Duplex > ;
162153
163154 constructor ( opts : ConnectorOptions = { } ) {
164155 this . instances = new CloudSQLInstanceMap ( ) ;
@@ -171,79 +162,130 @@ export class Connector {
171162 this . localProxies = new Set ( ) ;
172163 this . sockets = new Set ( ) ;
173164 }
174-
175- // Connector.getOptions is a method that accepts a Cloud SQL instance
176- // connection name along with the connection type and returns an object
177- // that can be used to configure a driver to be used with Cloud SQL. e.g:
178- //
179- // const connector = new Connector()
180- // const opts = await connector.getOptions({
181- // ipType: 'PUBLIC',
182- // instanceConnectionName: 'PROJECT:REGION:INSTANCE',
183- // });
184- // const pool = new Pool(opts)
185- // const res = await pool.query('SELECT * FROM pg_catalog.pg_tables;')
186- async getOptions ( {
165+ async loadInstance ( {
187166 authType = AuthTypes . PASSWORD ,
188167 ipType = IpAddressTypes . PUBLIC ,
189168 instanceConnectionName,
190- } : ConnectionOptions ) : Promise < DriverOptions > {
191- const { instances} = this ;
192- await instances . loadInstance ( {
169+ } : ConnectionOptions ) : Promise < CloudSQLInstance > {
170+ if ( ! instanceConnectionName ) {
171+ throw new CloudSQLConnectorError ( {
172+ code : 'ENOTCONFIGURED' ,
173+ message : 'Instance connection name missing.' ,
174+ } ) ;
175+ }
176+
177+ const inst = await this . instances . loadInstance ( {
193178 ipType,
194179 authType,
195180 instanceConnectionName,
196181 sqlAdminFetcher : this . sqlAdminFetcher ,
197182 } ) ;
198183
199- return {
200- stream ( ) {
201- const cloudSqlInstance = instances . getInstance ( {
202- instanceConnectionName,
203- authType,
204- } ) ;
205- const {
206- instanceInfo,
207- ephemeralCert,
208- host,
209- port,
210- privateKey,
211- serverCaCert,
212- serverCaMode,
213- dnsName,
214- } = cloudSqlInstance ;
184+ return inst ;
185+ }
186+
187+ async connect ( {
188+ authType = AuthTypes . PASSWORD ,
189+ ipType = IpAddressTypes . PUBLIC ,
190+ instanceConnectionName,
191+ } : ConnectionOptions ) : Promise < tls . TLSSocket > {
192+ if ( ! instanceConnectionName ) {
193+ throw new CloudSQLConnectorError ( {
194+ code : 'ENOTCONFIGURED' ,
195+ message : 'Instance connection name missing.' ,
196+ } ) ;
197+ }
215198
216- if (
217- instanceInfo &&
218- ephemeralCert &&
219- host &&
220- port &&
221- privateKey &&
222- serverCaCert
223- ) {
224- const tlsSocket = getSocket ( {
225- instanceInfo,
226- ephemeralCert,
227- host,
228- port,
229- privateKey,
230- serverCaCert,
231- serverCaMode,
232- dnsName : instanceInfo . domainName || dnsName , // use the configured domain name, or the instance dnsName.
233- } ) ;
234- tlsSocket . once ( 'error' , ( ) => {
235- cloudSqlInstance . forceRefresh ( ) ;
236- } ) ;
237- tlsSocket . once ( 'secureConnect' , async ( ) => {
238- cloudSqlInstance . setEstablishedConnection ( ) ;
239- } ) ;
240- return tlsSocket ;
199+ const cloudSqlInstance = await this . loadInstance ( {
200+ ipType,
201+ authType,
202+ instanceConnectionName,
203+ } ) ;
204+
205+ const {
206+ instanceInfo,
207+ ephemeralCert,
208+ host,
209+ port,
210+ privateKey,
211+ serverCaCert,
212+ serverCaMode,
213+ dnsName,
214+ } = cloudSqlInstance ;
215+
216+ if (
217+ instanceInfo &&
218+ ephemeralCert &&
219+ host &&
220+ port &&
221+ privateKey &&
222+ serverCaCert
223+ ) {
224+ const tlsSocket = getSocket ( {
225+ instanceInfo,
226+ ephemeralCert,
227+ host,
228+ port,
229+ privateKey,
230+ serverCaCert,
231+ serverCaMode,
232+ dnsName : instanceInfo . domainName || dnsName , // use the configured domain name, or the instance dnsName.
233+ } ) ;
234+ tlsSocket . once ( 'error' , ( ) => {
235+ cloudSqlInstance . forceRefresh ( ) ;
236+ } ) ;
237+ tlsSocket . once ( 'secureConnect' , async ( ) => {
238+ cloudSqlInstance . setEstablishedConnection ( ) ;
239+ } ) ;
240+ return tlsSocket ;
241+ }
242+ throw new CloudSQLConnectorError ( {
243+ message : 'Invalid Cloud SQL Instance info' ,
244+ code : 'EBADINSTANCEINFO' ,
245+ } ) ;
246+ }
247+
248+ getOptions ( {
249+ authType = AuthTypes . PASSWORD ,
250+ ipType = IpAddressTypes . PUBLIC ,
251+ instanceConnectionName,
252+ } : ConnectionOptions ) : DriverOptions {
253+ // bring 'this' into a closure-scope variable.
254+ //eslint-disable-next-line @typescript-eslint/no-this-alias
255+ const connector = this ;
256+ return {
257+ stream ( opts ) {
258+ let host ;
259+ let startConnection = false ;
260+ if ( opts ) {
261+ if ( opts ?. config ?. host ) {
262+ // Mysql driver passes the host in the options, and expects
263+ // this to start the connection.
264+ host = opts ?. config ?. host ;
265+ startConnection = true ;
266+ }
267+ if ( opts ?. host ) {
268+ // Sql Server (Tedious) driver passes host in the options
269+ // this to start the connection.
270+ host = opts ?. host ;
271+ startConnection = true ;
272+ }
273+ } else {
274+ // Postgres driver does not pass options.
275+ // Postgres will call Socket.connect(port,host).
276+ startConnection = false ;
241277 }
242278
243- throw new CloudSQLConnectorError ( {
244- message : 'Invalid Cloud SQL Instance info' ,
245- code : 'EBADINSTANCEINFO' ,
246- } ) ;
279+ return new SocketWrapper ( new SocketWrapperOptions ( {
280+ connector,
281+ host,
282+ startConnection,
283+ connectionConfig : {
284+ authType,
285+ ipType,
286+ instanceConnectionName,
287+ } ,
288+ } ) ) ;
247289 } ,
248290 } ;
249291 }
@@ -265,8 +307,8 @@ export class Connector {
265307 instanceConnectionName,
266308 } ) ;
267309 return {
268- async connector ( ) {
269- return driverOptions . stream ( ) ;
310+ async connector ( opts ) {
311+ return driverOptions . stream ( opts ) ;
270312 } ,
271313 // note: the connector handles a secured encrypted connection
272314 // with that in mind, the driver encryption is disabled here
0 commit comments