Skip to content

Commit a4de301

Browse files
committed
Address comments
1 parent 80d6688 commit a4de301

1 file changed

Lines changed: 41 additions & 28 deletions

File tree

web/client.js

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
474472
function _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', /^https?:\/\//i);
477-
const wsUrl = _validateEndpoint(url.ws, 'ws', /^wss?:\/\//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 (/^https?:\/\//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: /^https?:\/\//i, expected: 'http:// or https://' },
522+
ws: { schemeRe: /^wss?:\/\//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

Comments
 (0)