Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/common/lib/client/baseclient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Logger, { LoggerOptions } from '../util/logger';
import Defaults from '../util/defaults';
import Defaults, { formatHostForUri } from '../util/defaults';
import Auth from './auth';
import { HttpPaginatedResponse, PaginatedResult } from './paginatedresource';
import ErrorInfo from '../types/errorinfo';
Expand Down Expand Up @@ -205,7 +205,7 @@ class BaseClient {
}

baseUri(host: string) {
return Defaults.getHttpScheme(this.options) + host + ':' + Defaults.getPort(this.options, false);
return Defaults.getHttpScheme(this.options) + formatHostForUri(host) + ':' + Defaults.getPort(this.options, false);
}

async stats(params?: RequestParams): Promise<PaginatedResult<Stats>> {
Expand Down
4 changes: 2 additions & 2 deletions src/common/lib/transport/comettransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ProtocolMessage, {
} from '../types/protocolmessage';
import Transport from './transport';
import Logger from '../util/logger';
import Defaults from '../util/defaults';
import Defaults, { formatHostForUri } from '../util/defaults';
import ConnectionErrors from './connectionerrors';
import Auth from '../client/auth';
import ErrorInfo from '../types/errorinfo';
Expand Down Expand Up @@ -81,7 +81,7 @@ abstract class CometTransport extends Transport {
const port = Defaults.getPort(options);
const cometScheme = options.tls ? 'https://' : 'http://';

this.baseUri = cometScheme + host + ':' + port + '/comet/';
this.baseUri = cometScheme + formatHostForUri(host) + ':' + port + '/comet/';
const connectUri = this.baseUri + 'connect';
Logger.logAction(this.logger, Logger.LOG_MINOR, 'CometTransport.connect()', 'uri: ' + connectUri);
Utils.whenPromiseSettles(this.auth.getAuthParams(), (err: Error | null, authParams?: Record<string, any>) => {
Expand Down
4 changes: 2 additions & 2 deletions src/common/lib/transport/websockettransport.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Platform from 'common/platform';
import * as Utils from '../util/utils';
import Transport from './transport';
import Defaults from '../util/defaults';
import Defaults, { formatHostForUri } from '../util/defaults';
import Logger from '../util/logger';
import ProtocolMessage, {
serialize as serializeProtocolMessage,
Expand Down Expand Up @@ -52,7 +52,7 @@ class WebSocketTransport extends Transport {
params = this.params,
options = params.options;
const wsScheme = options.tls ? 'wss://' : 'ws://';
const wsUri = wsScheme + this.wsHost + ':' + Defaults.getPort(options) + '/';
const wsUri = wsScheme + formatHostForUri(this.wsHost) + ':' + Defaults.getPort(options) + '/';
Logger.logAction(this.logger, Logger.LOG_MINOR, 'WebSocketTransport.connect()', 'uri: ' + wsUri);
Utils.whenPromiseSettles(
this.auth.getAuthParams(),
Expand Down
11 changes: 11 additions & 0 deletions src/common/lib/util/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@ export function getHttpScheme(options: ClientOptions): string {
return options.tls ? 'https://' : 'http://';
}

/**
* REC1b2 / RFC 3986 §3.2.2: an IPv6 literal used as a URI host must be enclosed in '[' and ']'.
* Host identity is stored unbracketed; this is applied only when concatenating a URI.
*/
export function formatHostForUri(host: string): string {
if (host.includes(':') && !host.startsWith('[')) {
return '[' + host + ']';
}
return host;
}

Comment thread
ttypic marked this conversation as resolved.
/**
* REC1b2
*/
Expand Down
12 changes: 0 additions & 12 deletions test/uts/deviations.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,6 @@ These tests assert spec behavior but are skipped by default because they are kno

---

### fallback: REC1b2 - IPv6 endpoint address not bracketed

**Spec (REC1b2)**: IPv6 addresses should be supported as endpoint values.

**ably-js behavior**: URL construction produces `https://::1:443/time` instead of `https://[::1]:443/time`.

**Test**: `REC1b2 - endpoint as IPv6 address`.

**Issue**: [#2198](https://github.com/ably/ably-js/issues/2198)

---

### options_types: AO2 - authMethod default not stored

**Spec (AO2)**: `authMethod` should default to `'GET'` and be stored in auth options.
Expand Down
17 changes: 5 additions & 12 deletions test/uts/rest/unit/fallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,8 +816,6 @@ describe('uts/rest/unit/fallback', function () {

// UTS: rest/unit/REC1b2/endpoint-ipv6-address-2
it('REC1b2 - endpoint as IPv6 address', async function () {
// DEVIATION: see deviations.md
if (!process.env.RUN_DEVIATIONS) this.skip();
const captured: any[] = [];
const mock = new MockHttpClient({
onConnectionAttempt: (conn) => conn.respond_with_success(),
Expand All @@ -828,17 +826,12 @@ describe('uts/rest/unit/fallback', function () {
});
installMockHttp(mock);

// Spec: endpoint '::1' should be treated as an explicit IPv6 hostname.
// DEVIATION: ably-js constructs an invalid URI (no brackets around IPv6). See deviations.md.
try {
const client = new Ably.Rest({ key: 'app.key:secret', useBinaryProtocol: false, endpoint: '::1' });
await client.time();
const client = new Ably.Rest({ key: 'app.key:secret', useBinaryProtocol: false, endpoint: '::1' });
await client.time();

expect(captured).to.have.length(1);
expect(captured[0].url.hostname).to.satisfy((h: string) => h === '::1' || h === '[::1]');
} catch (e) {
expect.fail('IPv6 endpoint should work, but ably-js threw: ' + (e as Error).message);
}
expect(captured).to.have.length(1);
expect(captured[0].url.hostname).to.satisfy((h: string) => h === '::1' || h === '[::1]');
expect(captured[0].url.href).to.include('[::1]');
});

// UTS: rest/unit/REC1b3/nonprod-routing-policy-0
Expand Down
Loading