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 } 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,19 @@ 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+ ( ) : stream . Duplex ;
5456}
5557
5658interface PromisedStreamFunction {
57- ( ) : Promise < tls . TLSSocket > ;
59+ ( ) : Promise < stream . Duplex > ;
5860}
5961
6062// DriverOptions is the interface describing the object returned by
@@ -86,11 +88,17 @@ class CloudSQLInstanceMap extends Map {
8688 authType : AuthTypes ;
8789 instanceConnectionName : string ;
8890 sqlAdminFetcher : SQLAdminFetcher ;
89- } ) : Promise < void > {
91+ } ) : Promise < CloudSQLInstance > {
9092 // in case an instance to that connection name has already
9193 // been setup there's no need to set it up again
9294 if ( this . has ( instanceConnectionName ) ) {
9395 const instance = this . get ( instanceConnectionName ) ;
96+ if ( ! instance ) {
97+ throw new CloudSQLConnectorError ( {
98+ message : `Cannot find info for instance: ${ instanceConnectionName } ` ,
99+ code : 'ENOINSTANCEINFO' ,
100+ } ) ;
101+ }
94102 if ( instance . authType && instance . authType !== authType ) {
95103 throw new CloudSQLConnectorError ( {
96104 message :
@@ -100,42 +108,23 @@ class CloudSQLInstanceMap extends Map {
100108 code : 'EMISMATCHAUTHTYPE' ,
101109 } ) ;
102110 }
103- return ;
111+ return instance ;
104112 }
113+
105114 const connectionInstance = await CloudSQLInstance . getCloudSQLInstance ( {
106115 ipType,
107116 authType,
108117 instanceConnectionName,
109118 sqlAdminFetcher : sqlAdminFetcher ,
110119 } ) ;
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 ) ;
122120 if ( ! connectionInstance ) {
123121 throw new CloudSQLConnectorError ( {
124122 message : `Cannot find info for instance: ${ instanceConnectionName } ` ,
125123 code : 'ENOINSTANCEINFO' ,
126124 } ) ;
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- } ) ;
138125 }
126+ this . set ( instanceConnectionName , connectionInstance ) ;
127+
139128 return connectionInstance ;
140129 }
141130}
@@ -158,7 +147,7 @@ export class Connector {
158147 private readonly instances : CloudSQLInstanceMap ;
159148 private readonly sqlAdminFetcher : SQLAdminFetcher ;
160149 private readonly localProxies : Set < Server > ;
161- private readonly sockets : Set < Socket > ;
150+ private readonly sockets : Set < stream . Duplex > ;
162151
163152 constructor ( opts : ConnectorOptions = { } ) {
164153 this . instances = new CloudSQLInstanceMap ( ) ;
@@ -171,78 +160,107 @@ export class Connector {
171160 this . localProxies = new Set ( ) ;
172161 this . sockets = new Set ( ) ;
173162 }
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 ( {
163+ async loadInstance ( {
187164 authType = AuthTypes . PASSWORD ,
188165 ipType = IpAddressTypes . PUBLIC ,
189166 instanceConnectionName,
190- } : ConnectionOptions ) : Promise < DriverOptions > {
191- const { instances} = this ;
192- await instances . loadInstance ( {
167+ } : ConnectionOptions ) : Promise < CloudSQLInstance > {
168+ if ( ! instanceConnectionName ) {
169+ throw new CloudSQLConnectorError ( {
170+ code : 'ENOTCONFIGURED' ,
171+ message : 'Instance connection name missing.' ,
172+ } ) ;
173+ }
174+
175+ const inst = await this . instances . loadInstance ( {
193176 ipType,
194177 authType,
195178 instanceConnectionName,
196179 sqlAdminFetcher : this . sqlAdminFetcher ,
197180 } ) ;
198181
182+ return inst ;
183+ }
184+
185+ async connect ( {
186+ authType = AuthTypes . PASSWORD ,
187+ ipType = IpAddressTypes . PUBLIC ,
188+ instanceConnectionName,
189+ } : ConnectionOptions ) : Promise < tls . TLSSocket > {
190+ if ( ! instanceConnectionName ) {
191+ throw new CloudSQLConnectorError ( {
192+ code : 'ENOTCONFIGURED' ,
193+ message : 'Instance connection name missing.' ,
194+ } ) ;
195+ }
196+
197+ const cloudSqlInstance = await this . loadInstance ( {
198+ ipType,
199+ authType,
200+ instanceConnectionName,
201+ } ) ;
202+
203+ const {
204+ instanceInfo,
205+ ephemeralCert,
206+ host,
207+ port,
208+ privateKey,
209+ serverCaCert,
210+ serverCaMode,
211+ dnsName,
212+ } = cloudSqlInstance ;
213+
214+ if (
215+ instanceInfo &&
216+ ephemeralCert &&
217+ host &&
218+ port &&
219+ privateKey &&
220+ serverCaCert
221+ ) {
222+ const tlsSocket = getSocket ( {
223+ instanceInfo,
224+ ephemeralCert,
225+ host,
226+ port,
227+ privateKey,
228+ serverCaCert,
229+ serverCaMode,
230+ dnsName : instanceInfo . domainName || dnsName , // use the configured domain name, or the instance dnsName.
231+ } ) ;
232+ tlsSocket . once ( 'error' , ( ) => {
233+ cloudSqlInstance . forceRefresh ( ) ;
234+ } ) ;
235+ tlsSocket . once ( 'secureConnect' , async ( ) => {
236+ cloudSqlInstance . setEstablishedConnection ( ) ;
237+ } ) ;
238+ return tlsSocket ;
239+ }
240+ throw new CloudSQLConnectorError ( {
241+ message : 'Invalid Cloud SQL Instance info' ,
242+ code : 'EBADINSTANCEINFO' ,
243+ } ) ;
244+ }
245+
246+ getOptions ( {
247+ authType = AuthTypes . PASSWORD ,
248+ ipType = IpAddressTypes . PUBLIC ,
249+ instanceConnectionName,
250+ } : ConnectionOptions ) : DriverOptions {
251+ // bring 'this' into a closure-scope variable.
252+ //eslint-disable-next-line @typescript-eslint/no-this-alias
253+ const connector = this ;
199254 return {
200255 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 ;
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- }
256+ return new SocketWrapper ( {
257+ connector,
242258
243- throw new CloudSQLConnectorError ( {
244- message : 'Invalid Cloud SQL Instance info' ,
245- code : 'EBADINSTANCEINFO' ,
259+ connectionConfig : {
260+ authType,
261+ ipType,
262+ instanceConnectionName,
263+ } ,
246264 } ) ;
247265 } ,
248266 } ;
0 commit comments