11const array = require ( '@barchart/common-js/lang/array' ) ,
2+ assert = require ( '@barchart/common-js/lang/assert' ) ,
23 object = require ( '@barchart/common-js/lang/object' ) ;
34
45const ConnectionBase = require ( './ConnectionBase' ) ,
@@ -21,6 +22,11 @@ module.exports = (() => {
2122
2223 const _API_VERSION = 4 ;
2324
25+ const mode = {
26+ credentials : 'credentials' ,
27+ token : 'token'
28+ } ;
29+
2430 const state = {
2531 connecting : 'CONNECTING' ,
2632 authenticating : 'LOGGING_IN' ,
@@ -46,6 +52,9 @@ module.exports = (() => {
4652 const _RECONNECT_INTERVAL = 5000 ;
4753 const _WATCHDOG_INTERVAL = 10000 ;
4854
55+ const regex = { } ;
56+ regex . hostname = / ^ (?: ( w s s | w s ) : \/ \/ ) ? ( .+ ?) (?: : ( \d + ) ) ? $ / i;
57+
4958 function ConnectionInternal ( marketState , instance ) {
5059 const __logger = LoggerFactory . getLogger ( '@barchart/marketdata-api-js' ) ;
5160
@@ -60,6 +69,7 @@ module.exports = (() => {
6069
6170 let __connection = null ;
6271 let __connectionState = state . disconnected ;
72+ let __connectionCount = 0 ;
6373
6474 let __paused = false ;
6575
@@ -94,9 +104,12 @@ module.exports = (() => {
94104 } ;
95105
96106 const __loginInfo = {
107+ mode : null ,
97108 hostname : null ,
98109 username : null ,
99- password : null
110+ password : null ,
111+ jwtProvider : null ,
112+ jwtPromise : null
100113 } ;
101114
102115 let __decoder = null ;
@@ -154,18 +167,19 @@ module.exports = (() => {
154167 *
155168 * @private
156169 * @param {String } hostname
157- * @param {String } username
158- * @param {String } password
170+ * @param {String= } username
171+ * @param {String= } password
159172 * @param {WebSocketAdapterFactory } webSocketAdapterFactory
160173 * @param {XmlParserFactory } xmlParserFactory
174+ * @param {Callbacks.JwtProvider } jwtProvider
161175 */
162- function initializeConnection ( hostname , username , password , webSocketAdapterFactory , xmlParserFactory ) {
176+ function initializeConnection ( hostname , username , password , webSocketAdapterFactory , xmlParserFactory , jwtProvider ) {
163177 __connectionFactory = webSocketAdapterFactory ;
164178 __xmlParserFactory = xmlParserFactory ;
165179
166180 __reconnectAllowed = true ;
167181
168- connect ( hostname , username , password ) ;
182+ connect ( hostname , username , password , jwtProvider ) ;
169183 }
170184
171185 /**
@@ -179,9 +193,12 @@ module.exports = (() => {
179193
180194 __reconnectAllowed = false ;
181195
196+ __loginInfo . mode = null ;
197+ __loginInfo . hostname = null ;
182198 __loginInfo . username = null ;
183199 __loginInfo . password = null ;
184- __loginInfo . hostname = null ;
200+ __loginInfo . jwtProvider = null ;
201+ __loginInfo . jwtPromise = null ;
185202
186203 __knownConsumerSymbols = { } ;
187204 __pendingProfileSymbols = { } ;
@@ -197,24 +214,62 @@ module.exports = (() => {
197214 disconnect ( ) ;
198215 }
199216
217+ /**
218+ * Attempts to read a JWT from an external provider.
219+ *
220+ * @private
221+ * @param {Callbacks.JwtProvider } jwtProvider
222+ * @returns {Promise<string|null> }
223+ */
224+ function getJwt ( jwtProvider ) {
225+ const connectionCount = __connectionCount ;
226+
227+ return Promise . resolve ( )
228+ . then ( ( ) => {
229+ __logger . log ( `Connection [ ${ __instance } ]: Requesting JWT for connection attempt [ ${ connectionCount } ].` ) ;
230+
231+ return jwtProvider ( ) ;
232+ } ) . then ( ( jwt ) => {
233+ __logger . log ( `Connection [ ${ __instance } ]: Request for JWT was successful for connection attempt [ ${ connectionCount } ].` ) ;
234+
235+ if ( __connectionCount !== connectionCount ) {
236+ return null ;
237+ }
238+
239+ if ( typeof ( jwt ) !== 'string' ) {
240+ __logger . warn ( `Connection [ ${ __instance } ]: Unable to extract JWT.` ) ;
241+
242+ return null ;
243+ }
244+
245+ return jwt ;
246+ } ) . catch ( ( e ) => {
247+ __logger . warn ( `Connection [ ${ __instance } ]: Request for JWT failed for connection attempt [ ${ connectionCount } ].` ) ;
248+
249+ return null ;
250+ } ) ;
251+ }
252+
200253 /**
201254 * Attempts to establish a connection to JERQ.
202255 *
203256 * @private
204257 * @param {String } hostname
205- * @param {String } username
206- * @param {String } password
258+ * @param {String= } username
259+ * @param {String= } password
260+ * @param {Callbacks.JwtProvider= } jwtProvider
207261 */
208- function connect ( hostname , username , password ) {
209- if ( ! hostname ) {
210- throw new Error ( 'Unable to connect, the "hostname" argument is required.' ) ;
211- }
262+ function connect ( hostname , username , password , jwtProvider ) {
263+ assert . argumentIsRequired ( hostname , 'hostname' , String ) ;
264+ assert . argumentIsOptional ( username , 'username' , String ) ;
265+ assert . argumentIsOptional ( password , 'password' , String ) ;
266+ assert . argumentIsOptional ( jwtProvider , 'jwtProvider' , Function ) ;
212267
213- if ( ! username ) {
268+ if ( ! username && ! jwtProvider ) {
214269 throw new Error ( 'Unable to connect, the "username" argument is required.' ) ;
215270 }
216271
217- if ( ! password ) {
272+ if ( ! password && ! jwtProvider ) {
218273 throw new Error ( 'Unable to connect, the "password" argument is required.' ) ;
219274 }
220275
@@ -226,28 +281,67 @@ module.exports = (() => {
226281
227282 ensureExchangeMetadata ( ) ;
228283
229- __logger . log ( `Connection [ ${ __instance } ]: Initializing. Version [ ${ version } ]. Using [ ${ username } @ ${ hostname } ].` ) ;
284+ __connectionCount = __connectionCount + 1 ;
230285
231- __xmlParser = __xmlParserFactory . build ( ) ;
286+ __logger . log ( `Connection [ ${ __instance } ]: Starting connection attempt [ ${ __connectionCount } ] using [ ${ jwtProvider ? 'JWT' : 'credentials-based' } ] authentication.` ) ;
232287
233288 let protocol ;
289+ let host ;
234290 let port ;
235291
236292 if ( hostname === 'localhost' ) {
237293 protocol = 'ws' ;
294+ host = 'localhost' ;
238295 port = 8080 ;
239296 } else {
240- protocol = 'wss' ;
241- port = 443 ;
297+ const match = hostname . match ( regex . hostname ) ;
298+
299+ if ( match !== null && match [ 1 ] ) {
300+ protocol = match [ 1 ] ;
301+ } else {
302+ protocol = 'wss' ;
303+ }
304+
305+ if ( match !== null && match [ 2 ] ) {
306+ host = match [ 2 ] ;
307+ } else {
308+ host = hostname ;
309+ }
310+
311+ if ( match !== null && match [ 3 ] ) {
312+ port = parseInt ( match [ 3 ] ) ;
313+ } else {
314+ port = protocol === 'ws' ? 80 : 443 ;
315+ }
242316 }
243317
244- __loginInfo . username = username ;
245- __loginInfo . password = password ;
246318 __loginInfo . hostname = hostname ;
247319
320+ __loginInfo . username = null ;
321+ __loginInfo . password = null ;
322+
323+ __loginInfo . jwtProvider = null ;
324+ __loginInfo . jwtPromise = null ;
325+
326+ if ( jwtProvider ) {
327+ __loginInfo . mode = mode . token ;
328+
329+ __loginInfo . jwtProvider = jwtProvider ;
330+ __loginInfo . jwtPromise = getJwt ( jwtProvider ) ;
331+ } else {
332+ __loginInfo . mode = mode . credentials ;
333+
334+ __loginInfo . username = username ;
335+ __loginInfo . password = password ;
336+ }
337+
338+ __xmlParser = __xmlParserFactory . build ( ) ;
339+
248340 __connectionState = state . disconnected ;
249341
250- __connection = __connectionFactory . build ( `${ protocol } ://${ __loginInfo . hostname } :${ port } /jerq` ) ;
342+ __logger . log ( `Connection [ ${ __instance } ]: Opening connection to [ ${ protocol } ://${ host } :${ port } ].` ) ;
343+
344+ __connection = __connectionFactory . build ( `${ protocol } ://${ host } :${ port } /jerq` ) ;
251345 __connection . binaryType = 'arraybuffer' ;
252346
253347 __decoder = __connection . getDecoder ( ) ;
@@ -307,7 +401,7 @@ module.exports = (() => {
307401 if ( __reconnectAllowed ) {
308402 __logger . log ( `Connection [ ${ __instance } ]: Scheduling reconnect attempt.` ) ;
309403
310- const reconnectAction = ( ) => connect ( __loginInfo . hostname , __loginInfo . username , __loginInfo . password ) ;
404+ const reconnectAction = ( ) => connect ( __loginInfo . hostname , __loginInfo . username , __loginInfo . password , __loginInfo . jwtProvider ) ;
311405 const reconnectDelay = _RECONNECT_INTERVAL + Math . floor ( Math . random ( ) * _WATCHDOG_INTERVAL ) ;
312406
313407 setTimeout ( reconnectAction , reconnectDelay ) ;
@@ -836,9 +930,39 @@ module.exports = (() => {
836930 if ( lines . some ( line => line === '+++' ) ) {
837931 __connectionState = state . authenticating ;
838932
839- __logger . log ( `Connection [ ${ __instance } ]: Sending credentials.` ) ;
933+ let connectionCount = __connectionCount ;
840934
841- __connection . send ( `LOGIN ${ __loginInfo . username } :${ __loginInfo . password } VERSION=${ _API_VERSION } \r\n` ) ;
935+ if ( __loginInfo . mode === mode . credentials ) {
936+ __connection . send ( `LOGIN ${ __loginInfo . username } :${ __loginInfo . password } VERSION=${ _API_VERSION } \r\n` ) ;
937+
938+ return ;
939+ }
940+
941+ if ( __loginInfo . mode === mode . token ) {
942+ const jwtPromise = __loginInfo . jwtPromise || Promise . resolve ( null ) ;
943+
944+ jwtPromise . then ( ( jwt ) => {
945+ if ( __connectionCount !== connectionCount ) {
946+ return ;
947+ }
948+
949+ if ( __connectionState !== state . authenticating ) {
950+ return ;
951+ }
952+
953+ if ( jwt === null ) {
954+ broadcastEvent ( 'events' , { event : 'jwt acquisition failed' } ) ;
955+
956+ disconnect ( ) ;
957+
958+ return ;
959+ }
960+
961+ __connection . send ( `TOKEN ${ jwt } VERSION=${ _API_VERSION } \r\n` ) ;
962+ } ) ;
963+
964+ return ;
965+ }
842966 }
843967 } else if ( __connectionState === state . authenticating ) {
844968 const lines = message . split ( '\n' ) ;
@@ -1847,7 +1971,7 @@ module.exports = (() => {
18471971 }
18481972
18491973 _connect ( webSocketAdapterFactory , xmlParserFactory ) {
1850- this . _internal . connect ( this . getHostname ( ) , this . getUsername ( ) , this . getPassword ( ) , webSocketAdapterFactory , xmlParserFactory ) ;
1974+ this . _internal . connect ( this . getHostname ( ) , this . getUsername ( ) , this . getPassword ( ) , webSocketAdapterFactory , xmlParserFactory , this . getJwtProvider ( ) ) ;
18511975 }
18521976
18531977 _disconnect ( ) {
0 commit comments