@@ -224,9 +224,7 @@ class _WsLink {
224224 _failAll ( err ) {
225225 const cause = err instanceof Error ? err : new Error ( String ( err ) ) ;
226226 for ( const p of this . _pending . values ( ) ) {
227- try {
228- p . reject ( cause ) ;
229- } catch ( _ ) { }
227+ p . reject ( cause ) ;
230228 }
231229 this . _pending . clear ( ) ;
232230 }
@@ -360,7 +358,6 @@ export class RosClient {
360358 constructor ( url , options = { } ) {
361359 this . options = options ;
362360 if ( options . reconnect ) {
363- // eslint-disable-next-line no-console
364361 console . warn (
365362 'rclnodejs/web: reconnect is not yet implemented; ignoring option'
366363 ) ;
@@ -417,7 +414,7 @@ export class RosClient {
417414 if ( ! this . _wsUrl ) {
418415 throw Object . assign (
419416 new Error (
420- 'subscribe requires a WebSocket endpoint; connect() was given an HTTP-only URL with no WS sibling'
417+ 'no WebSocket endpoint available ; connect() was given an HTTP-only URL with no WS sibling'
421418 ) ,
422419 { code : 'transport_unavailable' }
423420 ) ;
@@ -467,21 +464,25 @@ export class RosClient {
467464
468465/**
469466 * Resolve the user-supplied `connect()` URL into an `{httpUrl, wsUrl}`
470- * pair. Either may be absent — then the corresponding link is not
471- * constructed and the client returns `transport_unavailable` for any
472- * verb that needs it.
467+ * pair plus a `wsExplicit` flag (true when the caller named a WS URL
468+ * themselves, false when we derived one from an HTTP base). Either
469+ * URL may be `null` — the corresponding link then isn't constructed
470+ * and verbs needing it reject with `transport_unavailable`.
473471 */
474472function _resolveUrls ( url ) {
473+ // Form 1: explicit { http, ws } pair.
475474 if ( url && typeof url === 'object' && ! Array . isArray ( url ) ) {
476- const httpUrl = _validateEndpoint ( url . http , 'http' , / ^ h t t p s ? : \/ \/ / i ) ;
477- const wsUrl = _validateEndpoint ( url . ws , 'ws' , / ^ w s s ? : \/ \/ / i ) ;
475+ const httpUrl = _validateEndpoint ( url . http , 'http' ) ;
476+ const wsUrl = _validateEndpoint ( url . ws , 'ws' ) ;
478477 if ( ! httpUrl && ! wsUrl ) {
479478 throw new TypeError (
480479 'connect({http, ws}): at least one of http or ws must be provided'
481480 ) ;
482481 }
483482 return { httpUrl, wsUrl, wsExplicit : ! ! wsUrl } ;
484483 }
484+
485+ // Form 2: single URL string. Pick the transport from the scheme.
485486 if ( typeof url !== 'string' || ! url ) {
486487 throw new TypeError (
487488 'connect(url): url must be a non-empty string or {http, ws}'
@@ -491,40 +492,52 @@ function _resolveUrls(url) {
491492 return { httpUrl : null , wsUrl : url , wsExplicit : true } ;
492493 }
493494 if ( / ^ h t t p s ? : \/ \/ / i. test ( url ) ) {
494- // HTTP base URL: derive a sibling WS URL by swapping the scheme
495- // and pointing at /capability. Lazy — we don't open it until the
496- // user actually calls subscribe().
497- let wsUrl ;
498- try {
499- const u = new URL ( url ) ;
500- u . protocol = u . protocol === 'https:' ? 'wss:' : 'ws:' ;
501- u . pathname = ( u . pathname . replace ( / \/ + $ / , '' ) || '' ) + '/capability' ;
502- wsUrl = u . toString ( ) ;
503- } catch ( _ ) {
504- wsUrl = null ;
505- }
506- return { httpUrl : url , wsUrl, wsExplicit : false } ;
495+ // HTTP base URL: derive a sibling WS URL lazily — we don't open
496+ // it until the user actually calls subscribe().
497+ return { httpUrl : url , wsUrl : _deriveWsSibling ( url ) , wsExplicit : false } ;
507498 }
508499 throw new TypeError (
509500 `connect(url): unrecognised URL scheme: ${ url } (expected ws://, wss://, http://, or https://)`
510501 ) ;
511502}
512503
504+ // Swap http(s) → ws(s) and append the default `/capability` path.
505+ // Returns null if the URL can't be parsed; verbs that need WS will
506+ // then surface a clear `transport_unavailable` error themselves.
507+ function _deriveWsSibling ( httpUrl ) {
508+ try {
509+ const u = new URL ( httpUrl ) ;
510+ u . protocol = u . protocol === 'https:' ? 'wss:' : 'ws:' ;
511+ u . pathname = u . pathname . replace ( / \/ + $ / , '' ) + '/capability' ;
512+ return u . toString ( ) ;
513+ } catch ( _ ) {
514+ return null ;
515+ }
516+ }
517+
518+ // Per-field config for `_validateEndpoint`. Keeps the validator
519+ // itself a straight three-step check (presence, type, scheme).
520+ const _ENDPOINT_FIELDS = {
521+ http : { schemeRe : / ^ h t t p s ? : \/ \/ / i, expected : 'http:// or https://' } ,
522+ ws : { schemeRe : / ^ w s s ? : \/ \/ / i, expected : 'ws:// or wss://' } ,
523+ } ;
524+
513525/**
514- * Validate one endpoint of an `{http, ws}` pair. Returns the trimmed
515- * URL on success, `null` if the field is absent, or throws a clear
516- * TypeError if the field is present but not a usable string .
526+ * Validate one endpoint of an `{http, ws}` pair. Returns the URL
527+ * unchanged on success, `null` when the field is absent, or throws
528+ * a clear `TypeError` when present but malformed .
517529 */
518- function _validateEndpoint ( value , field , schemeRe ) {
530+ function _validateEndpoint ( value , field ) {
519531 if ( value === undefined || value === null ) return null ;
532+ const { schemeRe, expected } = _ENDPOINT_FIELDS [ field ] ;
520533 if ( typeof value !== 'string' || ! value ) {
521534 throw new TypeError (
522535 `connect({${ field } }): ${ field } must be a non-empty string, got ${ typeof value } `
523536 ) ;
524537 }
525538 if ( ! schemeRe . test ( value ) ) {
526539 throw new TypeError (
527- `connect({${ field } }): ${ field } must start with ${ field === 'http' ? 'http:// or https://' : 'ws:// or wss://' } (got ${ value } )`
540+ `connect({${ field } }): ${ field } must start with ${ expected } (got ${ value } )`
528541 ) ;
529542 }
530543 return value ;
0 commit comments