From 1dfd85899b90fe3ac9c720d7091afa7686df5a40 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Fri, 21 Feb 2025 15:38:43 +0000 Subject: [PATCH 1/7] Add endpoint option This implements ADR-119[1], which specifies the client connection options to update requests to the endpoints implemented as part of ADR-042[2]. The endpoint may be one of the following: * a routing policy name (such as main) * a nonprod routing policy name (such as nonprod:sandbox) * a FQDN such as foo.example.com The endpoint option is not valid with any of environment, restHost or realtimeHost, but we still intend to support the legacy options. If the client has been configured to use any of these legacy options, then they should continue to work in the same way, using the same primary and fallback hostnames. If the client has not been explicitly configured, then the hostnames will change to the new ably.net domain when the package is upgraded. [1] https://ably.atlassian.net/wiki/spaces/ENG/pages/3428810778/ADR-119+ClientOptions+for+new+DNS+structure [2] https://ably.atlassian.net/wiki/spaces/ENG/pages/1791754276/ADR-042+DNS+Restructure --- ably.d.ts | 15 +- src/common/lib/util/defaults.ts | 89 ++++---- test/common/globals/environment.js | 2 +- test/common/modules/private_api_recorder.js | 1 + test/common/modules/testapp_manager.js | 12 +- .../browser/template/playwright/index.tsx | 2 +- .../browser/template/src/index-default.ts | 2 +- .../browser/template/src/index-modular.ts | 6 +- test/package/browser/template/src/sandbox.ts | 2 +- test/realtime/connectivity.test.js | 2 +- test/realtime/init.test.js | 7 +- test/rest/defaults.test.js | 202 ++++++++++++------ 12 files changed, 213 insertions(+), 129 deletions(-) diff --git a/ably.d.ts b/ably.d.ts index c1e267ba96..e34cc6f6a2 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -391,7 +391,12 @@ export interface ClientOptions extends AuthOptions { echoMessages?: boolean; /** - * Enables a [custom environment](https://ably.com/docs/platform-customization) to be used with the Ably service. + * Set a routing policy or FQDN to connect to Ably. See [platform customization](https://ably.com/docs/platform-customization). + */ + endpoint?: string; + + /** + * @deprecated This property is deprecated and will be removed in a future version. Use the {@link ClientOptions.endpoint} client option instead. */ environment?: string; @@ -423,16 +428,12 @@ export interface ClientOptions extends AuthOptions { queueMessages?: boolean; /** - * Enables a non-default Ably host to be specified. For development environments only. The default value is `rest.ably.io`. - * - * @defaultValue `"rest.ably.io"` + * @deprecated This property is deprecated and will be removed in a future version. Use the {@link ClientOptions.endpoint} client option instead. */ restHost?: string; /** - * Enables a non-default Ably host to be specified for realtime connections. For development environments only. The default value is `realtime.ably.io`. - * - * @defaultValue `"realtime.ably.io"` + * @deprecated This property is deprecated and will be removed in a future version. Use the {@link ClientOptions.endpoint} client option instead. */ realtimeHost?: string; diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 41292df675..943807ea65 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -13,6 +13,7 @@ import { ModularPlugins } from '../client/modularplugins'; let agent = 'ably-js/' + version; type CompleteDefaults = IDefaults & { + ENDPOINT: string; ENVIRONMENT: string; REST_HOST: string; REALTIME_HOST: string; @@ -40,7 +41,7 @@ type CompleteDefaults = IDefaults & { getHost(options: ClientOptions, host?: string | null, ws?: boolean): string; getPort(options: ClientOptions, tls?: boolean): number | undefined; getHttpScheme(options: ClientOptions): string; - environmentFallbackHosts(environment: string): string[]; + getEndpointFallbackHosts(endpoint: string): string[]; getFallbackHosts(options: NormalisedClientOptions): string[]; getHosts(options: NormalisedClientOptions, ws?: boolean): string[]; checkHost(host: string): void; @@ -57,15 +58,16 @@ type CompleteDefaults = IDefaults & { }; const Defaults = { + ENDPOINT: 'main', ENVIRONMENT: '', REST_HOST: 'rest.ably.io', REALTIME_HOST: 'realtime.ably.io', FALLBACK_HOSTS: [ - 'A.ably-realtime.com', - 'B.ably-realtime.com', - 'C.ably-realtime.com', - 'D.ably-realtime.com', - 'E.ably-realtime.com', + 'main.a.fallback.ably-realtime.com', + 'main.b.fallback.ably-realtime.com', + 'main.c.fallback.ably-realtime.com', + 'main.d.fallback.ably-realtime.com', + 'main.e.fallback.ably-realtime.com', ], PORT: 80, TLS_PORT: 443, @@ -94,7 +96,7 @@ const Defaults = { getHost, getPort, getHttpScheme, - environmentFallbackHosts, + getEndpointFallbackHosts, getFallbackHosts, getHosts, checkHost, @@ -119,15 +121,34 @@ export function getHttpScheme(options: ClientOptions): string { return options.tls ? 'https://' : 'http://'; } -// construct environment fallback hosts as per RSC15i -export function environmentFallbackHosts(environment: string): string[] { - return [ - environment + '-a-fallback.ably-realtime.com', - environment + '-b-fallback.ably-realtime.com', - environment + '-c-fallback.ably-realtime.com', - environment + '-d-fallback.ably-realtime.com', - environment + '-e-fallback.ably-realtime.com', - ]; +function isFqdnIpOrLocalhost(endpoint: string): boolean { + return endpoint.includes('.') || endpoint.includes('::') || endpoint === 'localhost'; +} + +export function getEndpointHostname(endpoint: string): string { + if (isFqdnIpOrLocalhost(endpoint)) return endpoint; + + if (endpoint.startsWith('nonprod:')) { + const routingPolicyId = endpoint.replace('nonprod:', ''); + return `${routingPolicyId}.realtime.ably-nonprod.net`; + } + + return `${endpoint}.realtime.ably.net`; +} + +export function getEndpointFallbackHosts(endpoint: string): string[] { + if (isFqdnIpOrLocalhost(endpoint)) return []; + + if (endpoint.startsWith('nonprod:')) { + const routingPolicyId = endpoint.replace('nonprod:', ''); + return endpointFallbacks(routingPolicyId, 'ably-realtime-nonprod.com'); + } + + return endpointFallbacks(endpoint, 'ably-realtime.com'); +} + +export function endpointFallbacks(routingPolicyId: string, domain: string): string[] { + return ['a', 'b', 'c', 'd', 'e'].map((id) => `${routingPolicyId}.${id}.fallback.${domain}`); } export function getFallbackHosts(options: NormalisedClientOptions): string[] { @@ -152,26 +173,6 @@ function checkHost(host: string): void { } } -function getRealtimeHost(options: ClientOptions, production: boolean, environment: string, logger: Logger): string { - if (options.realtimeHost) return options.realtimeHost; - /* prefer setting realtimeHost to restHost as a custom restHost typically indicates - * a development environment is being used that can't be inferred by the library */ - if (options.restHost) { - Logger.logAction( - logger, - Logger.LOG_MINOR, - 'Defaults.normaliseOptions', - 'restHost is set to "' + - options.restHost + - '" but realtimeHost is not set, so setting realtimeHost to "' + - options.restHost + - '" too. If this is not what you want, please set realtimeHost explicitly.', - ); - return options.restHost; - } - return production ? Defaults.REALTIME_HOST : environment + '-' + Defaults.REALTIME_HOST; -} - function getTimeouts(options: ClientOptions) { /* Allow values passed in options to override default timeouts */ const timeouts: Record = {}; @@ -262,18 +263,16 @@ export function normaliseOptions( if (!('queueMessages' in options)) options.queueMessages = true; - /* infer hosts and fallbacks based on the configured environment */ - const environment = (options.environment && String(options.environment).toLowerCase()) || Defaults.ENVIRONMENT; - const production = !environment || environment === 'production'; + /* infer hosts and fallbacks based on the specified endpoint */ + const endpoint = options.endpoint || options.environment || Defaults.ENDPOINT; if (!options.fallbackHosts && !options.restHost && !options.realtimeHost && !options.port && !options.tlsPort) { - options.fallbackHosts = production ? Defaults.FALLBACK_HOSTS : environmentFallbackHosts(environment); + options.fallbackHosts = getEndpointFallbackHosts(endpoint); } - const restHost = options.restHost || (production ? Defaults.REST_HOST : environment + '-' + Defaults.REST_HOST); - const realtimeHost = getRealtimeHost(options, production, environment, loggerToUse); + const primaryDomain = options.restHost || options.realtimeHost || getEndpointHostname(endpoint); - (options.fallbackHosts || []).concat(restHost, realtimeHost).forEach(checkHost); + (options.fallbackHosts || []).concat(primaryDomain).forEach(checkHost); options.port = options.port || Defaults.PORT; options.tlsPort = options.tlsPort || Defaults.TLS_PORT; @@ -318,8 +317,8 @@ export function normaliseOptions( return { ...options, - realtimeHost, - restHost, + restHost: primaryDomain, + realtimeHost: primaryDomain, maxMessageSize: options.maxMessageSize || Defaults.maxMessageSize, timeouts, connectivityCheckParams, diff --git a/test/common/globals/environment.js b/test/common/globals/environment.js index f1515222f8..1bc23e85b1 100644 --- a/test/common/globals/environment.js +++ b/test/common/globals/environment.js @@ -3,7 +3,7 @@ define(function (require) { var defaultLogLevel = 4, environment = isBrowser ? window.__env__ || {} : process.env, - ablyEnvironment = environment.ABLY_ENV || 'sandbox', + ablyEnvironment = environment.ABLY_ENV || 'nonprod:sandbox', realtimeHost = environment.ABLY_REALTIME_HOST, restHost = environment.ABLY_REST_HOST, port = environment.ABLY_PORT || 80, diff --git a/test/common/modules/private_api_recorder.js b/test/common/modules/private_api_recorder.js index cecf6670b8..941f269a65 100644 --- a/test/common/modules/private_api_recorder.js +++ b/test/common/modules/private_api_recorder.js @@ -139,6 +139,7 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths) 'replace.transport.onProtocolMessage', 'replace.transport.send', 'serialize.recoveryKey', + 'write.Defaults.ENDPOINT', 'write.Defaults.ENVIRONMENT', 'write.Defaults.wsConnectivityCheckUrl', 'write.Objects._DEFAULTS.gcGracePeriod', diff --git a/test/common/modules/testapp_manager.js b/test/common/modules/testapp_manager.js index 0f4b7fa9dc..a5243cffff 100644 --- a/test/common/modules/testapp_manager.js +++ b/test/common/modules/testapp_manager.js @@ -3,7 +3,7 @@ /* testapp module is responsible for setting up and tearing down apps in the test environment */ define(['globals', 'ably'], function (ablyGlobals, ably) { - var restHost = ablyGlobals.restHost || prefixDomainWithEnvironment('rest.ably.io', ablyGlobals.environment), + var restHost = ablyGlobals.restHost || getHostname(ablyGlobals.environment), port = ablyGlobals.tls ? ablyGlobals.tlsPort : ablyGlobals.port, scheme = ablyGlobals.tls ? 'https' : 'http'; @@ -23,12 +23,12 @@ define(['globals', 'ably'], function (ablyGlobals, ably) { } } - function prefixDomainWithEnvironment(domain, environment) { - if (environment.toLowerCase() === 'production') { - return domain; - } else { - return environment + '-' + domain; + function getHostname(endpoint) { + if (endpoint.startsWith('nonprod:')) { + return `${endpoint.replace('nonprod:', '')}.realtime.ably-nonprod.net`; } + + return `${endpoint}.realtime.ably.net`; } function toBase64(helper, str) { diff --git a/test/package/browser/template/playwright/index.tsx b/test/package/browser/template/playwright/index.tsx index 5b17f5d566..9646af16c3 100644 --- a/test/package/browser/template/playwright/index.tsx +++ b/test/package/browser/template/playwright/index.tsx @@ -9,7 +9,7 @@ beforeMount(async ({ App }) => { const client = new Ably.Realtime({ key, - environment: 'sandbox', + endpoint: 'nonprod:sandbox', }); return ( diff --git a/test/package/browser/template/src/index-default.ts b/test/package/browser/template/src/index-default.ts index 862d0bd9bc..31d2db0b84 100644 --- a/test/package/browser/template/src/index-default.ts +++ b/test/package/browser/template/src/index-default.ts @@ -15,7 +15,7 @@ async function attachChannel(channel: Ably.RealtimeChannel) { globalThis.testAblyPackage = async function () { const key = await createSandboxAblyAPIKey(); - const realtime = new Ably.Realtime({ key, environment: 'sandbox' }); + const realtime = new Ably.Realtime({ key, endpoint: 'nonprod:sandbox' }); const channel = realtime.channels.get('channel'); await attachChannel(channel); diff --git a/test/package/browser/template/src/index-modular.ts b/test/package/browser/template/src/index-modular.ts index 46b06e4617..6c4e537a64 100644 --- a/test/package/browser/template/src/index-modular.ts +++ b/test/package/browser/template/src/index-modular.ts @@ -24,7 +24,11 @@ async function checkStandaloneFunction() { globalThis.testAblyPackage = async function () { const key = await createSandboxAblyAPIKey(); - const realtime = new BaseRealtime({ key, environment: 'sandbox', plugins: { WebSocketTransport, FetchRequest } }); + const realtime = new BaseRealtime({ + key, + endpoint: 'nonprod:sandbox', + plugins: { WebSocketTransport, FetchRequest }, + }); const channel = realtime.channels.get('channel'); await attachChannel(channel); diff --git a/test/package/browser/template/src/sandbox.ts b/test/package/browser/template/src/sandbox.ts index 54c5f2e64a..ed3c0b3d74 100644 --- a/test/package/browser/template/src/sandbox.ts +++ b/test/package/browser/template/src/sandbox.ts @@ -5,7 +5,7 @@ export async function createSandboxAblyAPIKey(withOptions?: object) { ...testAppSetup.post_apps, ...(withOptions ?? {}), }; - const response = await fetch('https://sandbox-rest.ably.io/apps', { + const response = await fetch('https://sandbox.realtime.ably-nonprod.net/apps', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(postData), diff --git a/test/realtime/connectivity.test.js b/test/realtime/connectivity.test.js index 2fa1db0706..d6df14edfb 100644 --- a/test/realtime/connectivity.test.js +++ b/test/realtime/connectivity.test.js @@ -136,7 +136,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { it('succeeds with plain url', function (done) { const helper = this.test.helper; Helper.whenPromiseSettles( - helper.AblyRealtime(options(helper, 'sandbox-rest.ably.io/time')).http.checkConnectivity(), + helper.AblyRealtime(options(helper, 'sandbox.realtime.ably-nonprod.net/time')).http.checkConnectivity(), function (err, res) { try { expect( diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index af15253c0a..54494b7a49 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -279,7 +279,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { var realtime = new Ably.Realtime({ key: 'not_a.real:key', autoConnect: false }); helper.recordPrivateApi('read.connectionManager.httpHosts'); var defaultHost = realtime.connection.connectionManager.httpHosts[0]; - expect(defaultHost).to.equal('rest.ably.io', 'Verify correct default rest host chosen'); + expect(defaultHost).to.equal('main.realtime.ably.net', 'Verify correct default rest host chosen'); realtime.close(); done(); } catch (err) { @@ -340,8 +340,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { /** * Check changing the default fallback hosts and changing httpMaxRetryCount. * - * @spec RSC12 - * @spec RTN17b2 + * @spec REC2a2 * @spec TO3k2 * @spec TO3l5 * @specpartial RSC11 - test override endpoint using restHost @@ -473,7 +472,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { } }); - /** @spec RTN17b2 */ + /** @spec REC2c */ it('init_fallbacks_once_connected', function (done) { const helper = this.test.helper; var realtime = helper.AblyRealtime({ diff --git a/test/rest/defaults.test.js b/test/rest/defaults.test.js index c74f895613..9ed3d2e45a 100644 --- a/test/rest/defaults.test.js +++ b/test/rest/defaults.test.js @@ -24,8 +24,8 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.normaliseOptions'); var normalisedOptions = Defaults.normaliseOptions({}, null, null); - expect(normalisedOptions.restHost).to.equal('rest.ably.io'); - expect(normalisedOptions.realtimeHost).to.equal('realtime.ably.io'); + expect(normalisedOptions.restHost).to.equal('main.realtime.ably.net'); + expect(normalisedOptions.realtimeHost).to.equal('main.realtime.ably.net'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.FALLBACK_HOSTS.sort()); @@ -35,49 +35,127 @@ define(['ably', 'chai'], function (Ably, chai) { expect(Defaults.getHosts(normalisedOptions).length).to.equal(4); expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'rest.ably.io', false)).to.deep.equal('rest.ably.io'); - expect(Defaults.getHost(normalisedOptions, 'rest.ably.io', true)).to.equal('realtime.ably.io'); + expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', false)).to.deep.equal( + 'main.realtime.ably.net', + ); + expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', true)).to.equal('main.realtime.ably.net'); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); }); /** - * @spec TO3k1 - * @spec TO3k2 - * @spec TO3k3 + * @spec TO3k8 * @spec TO3k4 * @spec TO3k5 * @spec TO3k6 * @spec TO3d - * @spec RSC15h - * @specpartial RSC11 - test default value for restHost - * @specpartial RTN2 - test default value for realtimeHost - * @specpartial RSC15e - primary host for REST is restHost - * @specpartial RTN17a - primary host for realtime is realtimeHost - * @specpartial RSC11b - test with environment set to 'production' - * @specpartial RSC15g2 - test with environment set to 'production' - * @specpartial RTC1e - test with environment set to 'production' + * @spec REC1a */ - it('Init with production environment', function () { + it('Init with given endpoint', function () { const helper = this.test.helper; helper.recordPrivateApi('call.Defaults.normaliseOptions'); - var normalisedOptions = Defaults.normaliseOptions({ environment: 'production' }, null, null); + var normalisedOptions = Defaults.normaliseOptions({ endpoint: 'nonprod:sandbox' }, null, null); - expect(normalisedOptions.restHost).to.equal('rest.ably.io'); - expect(normalisedOptions.realtimeHost).to.equal('realtime.ably.io'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); - expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.FALLBACK_HOSTS.sort()); + expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal( + Defaults.getEndpointFallbackHosts('nonprod:sandbox').sort(), + ); expect(normalisedOptions.tls).to.equal(true); helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(4); expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'rest.ably.io', false)).to.deep.equal('rest.ably.io'); - expect(Defaults.getHost(normalisedOptions, 'rest.ably.io', true)).to.deep.equal('realtime.ably.io'); + expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', false)).to.deep.equal( + 'sandbox.realtime.ably-nonprod.net', + ); + expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', true)).to.deep.equal( + 'sandbox.realtime.ably-nonprod.net', + ); + + helper.recordPrivateApi('call.Defaults.getPort'); + expect(Defaults.getPort(normalisedOptions)).to.equal(443); + }); + + /** + * @spec TO3k8 + * @spec REC1b2 + */ + it('Init with given endpoint as FQDN', function () { + const helper = this.test.helper; + + helper.recordPrivateApi('call.Defaults.normaliseOptions'); + var normalisedOptions = Defaults.normaliseOptions({ endpoint: 'example.com' }, null, null); + + helper.recordPrivateApi('call.Defaults.getHosts'); + expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(1); + helper.recordPrivateApi('call.Defaults.getHost'); + expect(Defaults.getHost(normalisedOptions, 'example.com', false)).to.deep.equal('example.com'); + expect(Defaults.getHost(normalisedOptions, 'example.com', true)).to.deep.equal('example.com'); + + helper.recordPrivateApi('call.Defaults.getPort'); + expect(Defaults.getPort(normalisedOptions)).to.equal(443); + }); + + /** + * @spec TO3k8 + * @spec REC1b2 + */ + it('Init with given endpoint as IPv4 address', function () { + const helper = this.test.helper; + + helper.recordPrivateApi('call.Defaults.normaliseOptions'); + var normalisedOptions = Defaults.normaliseOptions({ endpoint: '127.0.0.1' }, null, null); + + helper.recordPrivateApi('call.Defaults.getHosts'); + console.log(Defaults.getHosts(normalisedOptions)); + expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(1); + helper.recordPrivateApi('call.Defaults.getHost'); + expect(Defaults.getHost(normalisedOptions, '127.0.0.1', false)).to.deep.equal('127.0.0.1'); + expect(Defaults.getHost(normalisedOptions, '127.0.0.1', true)).to.deep.equal('127.0.0.1'); + + helper.recordPrivateApi('call.Defaults.getPort'); + expect(Defaults.getPort(normalisedOptions)).to.equal(443); + }); + + /** + * @spec TO3k8 + * @spec REC1b2 + */ + it('Init with given endpoint as IPv6 address', function () { + const helper = this.test.helper; + + helper.recordPrivateApi('call.Defaults.normaliseOptions'); + var normalisedOptions = Defaults.normaliseOptions({ endpoint: '::1' }, null, null); + + helper.recordPrivateApi('call.Defaults.getHosts'); + expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(1); + helper.recordPrivateApi('call.Defaults.getHost'); + expect(Defaults.getHost(normalisedOptions, '::1', false)).to.deep.equal('::1'); + expect(Defaults.getHost(normalisedOptions, '::1', true)).to.deep.equal('::1'); + + helper.recordPrivateApi('call.Defaults.getPort'); + expect(Defaults.getPort(normalisedOptions)).to.equal(443); + }); + + /** + * @spec TO3k8 + * @spec REC1b2 + */ + it('Init with given endpoint as localhost', function () { + const helper = this.test.helper; + + helper.recordPrivateApi('call.Defaults.normaliseOptions'); + var normalisedOptions = Defaults.normaliseOptions({ endpoint: 'localhost' }, null, null); + + helper.recordPrivateApi('call.Defaults.getHosts'); + expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(1); + helper.recordPrivateApi('call.Defaults.getHost'); + expect(Defaults.getHost(normalisedOptions, 'localhost', false)).to.deep.equal('localhost'); + expect(Defaults.getHost(normalisedOptions, 'localhost', true)).to.deep.equal('localhost'); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -91,32 +169,31 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3k5 * @spec TO3k6 * @spec TO3d - * @spec RSC11b - * @spec RSC15i - * @specpartial RSC11 - test restHost is overridden by environment - * @specpartial RSC15g2 - test with environment set other than 'production' - * @specpartial RTC1e - test with environment set other than 'production' + * @specpartial REC1d1 - test restHost is overridden by environment + * @specpartial REC1c - test with environment set other than 'production' */ it('Init with given environment', function () { const helper = this.test.helper; helper.recordPrivateApi('call.Defaults.normaliseOptions'); - var normalisedOptions = Defaults.normaliseOptions({ environment: 'sandbox' }, null, null); + var normalisedOptions = Defaults.normaliseOptions({ environment: 'main' }, null, null); - expect(normalisedOptions.restHost).to.equal('sandbox-rest.ably.io'); - expect(normalisedOptions.realtimeHost).to.equal('sandbox-realtime.ably.io'); + expect(normalisedOptions.restHost).to.equal('main.realtime.ably.net'); + expect(normalisedOptions.realtimeHost).to.equal('main.realtime.ably.net'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); - expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.environmentFallbackHosts('sandbox').sort()); + expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.getEndpointFallbackHosts('main').sort()); expect(normalisedOptions.tls).to.equal(true); helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(4); expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', false)).to.deep.equal('sandbox-rest.ably.io'); - expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', true)).to.deep.equal( - 'sandbox-realtime.ably.io', + expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', false)).to.deep.equal( + 'main.realtime.ably.net', + ); + expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', true)).to.deep.equal( + 'main.realtime.ably.net', ); helper.recordPrivateApi('call.Defaults.getPort'); @@ -131,11 +208,8 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3k5 * @spec TO3k6 * @spec TO3d - * @spec RSC11b - * @spec RSC15i - * @specpartial RSC11 - test restHost is overridden by environment - * @specpartial RSC15g2 - test with environment set other than 'production' - * @specpartial RTC1e - test with environment set other than 'production' + * @specpartial REC1d1 - test restHost is overridden by environment + * @specpartial REC1c - test with environment set other than 'production' */ it('Init with local environment and non-default ports', function () { const helper = this.test.helper; @@ -147,8 +221,8 @@ define(['ably', 'chai'], function (Ably, chai) { null, ); - expect(normalisedOptions.restHost).to.equal('local-rest.ably.io'); - expect(normalisedOptions.realtimeHost).to.equal('local-realtime.ably.io'); + expect(normalisedOptions.restHost).to.equal('local.realtime.ably.net'); + expect(normalisedOptions.realtimeHost).to.equal('local.realtime.ably.net'); expect(normalisedOptions.port).to.equal(8080); expect(normalisedOptions.tlsPort).to.equal(8081); expect(normalisedOptions.fallbackHosts).to.equal(undefined); @@ -157,8 +231,12 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.restHost]); helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'local-rest.ably.io', false)).to.deep.equal('local-rest.ably.io'); - expect(Defaults.getHost(normalisedOptions, 'local-rest.ably.io', true)).to.deep.equal('local-realtime.ably.io'); + expect(Defaults.getHost(normalisedOptions, 'local.realtime.ably.net', false)).to.deep.equal( + 'local.realtime.ably.net', + ); + expect(Defaults.getHost(normalisedOptions, 'local.realtime.ably.net', true)).to.deep.equal( + 'local.realtime.ably.net', + ); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(8081); @@ -173,8 +251,8 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3k5 * @spec TO3k6 * @spec TO3d - * @spec RSC15h - * @spec RSC11a + * @spec REC1d1 + * @spec REC1d2 * @specpartial RSC11 - test restHost is overridden by custom value */ it('Init with given host', function () { @@ -207,8 +285,8 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3k5 * @spec TO3k6 * @spec TO3d - * @spec RSC15h - * @spec RSC11a + * @spec REC1d1 + * @spec REC1d2 * @specpartial RSC11 - test restHost is overridden by custom value * @specpartial RTN17a - primary host for realtime can be overridden by realtimeHost */ @@ -246,39 +324,41 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3k5 * @spec TO3k6 * @spec TO3d - * @spec RSC11b - * @spec RSC15i - * @specpartial RSC11 - test restHost is overridden by environment - * @specpartial RSC15g2 - test with environment set other than 'production' + * @specpartial REC1d1 - test restHost is overridden by environment + * @specpartial REC1c - test with environment set other than 'production' */ it('Init with no endpoint-related options and given default environment', function () { const helper = this.test.helper; - helper.recordPrivateApi('write.Defaults.ENVIRONMENT'); - Defaults.ENVIRONMENT = 'sandbox'; + helper.recordPrivateApi('write.Defaults.ENDPOINT'); + Defaults.ENDPOINT = 'nonprod:sandbox'; helper.recordPrivateApi('call.Defaults.normaliseOptions'); var normalisedOptions = Defaults.normaliseOptions({}, null, null); - expect(normalisedOptions.restHost).to.equal('sandbox-rest.ably.io'); - expect(normalisedOptions.realtimeHost).to.equal('sandbox-realtime.ably.io'); + expect(normalisedOptions.restHost).to.equal('sandbox.realtime.ably-nonprod.net'); + expect(normalisedOptions.realtimeHost).to.equal('sandbox.realtime.ably-nonprod.net'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); - expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.environmentFallbackHosts('sandbox').sort()); + expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal( + Defaults.getEndpointFallbackHosts('nonprod:sandbox').sort(), + ); expect(normalisedOptions.tls).to.equal(true); helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.equal(4); expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', false)).to.deep.equal('sandbox-rest.ably.io'); - expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', true)).to.deep.equal( - 'sandbox-realtime.ably.io', + expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', false)).to.deep.equal( + 'sandbox.realtime.ably-nonprod.net', + ); + expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', true)).to.deep.equal( + 'sandbox.realtime.ably-nonprod.net', ); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); - helper.recordPrivateApi('write.Defaults.ENVIRONMENT'); - Defaults.ENVIRONMENT = ''; + helper.recordPrivateApi('write.Defaults.ENDPOINT'); + Defaults.ENDPOINT = ''; }); // TODO once https://github.com/ably/ably-js/issues/1424 is fixed, this should also test the case where the useBinaryProtocol option is not specified From 880628efadf029890ab0f67b94614b9c437c95a9 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 19 Jun 2025 17:33:42 +0100 Subject: [PATCH 2/7] chore: refactor test, add client options validation - replaced `restHost` and `realtimeHost` in tests (where it's possible) - fixed tests according to the new spec - add client options validation according to the spec --- src/common/lib/util/defaults.ts | 52 +++++++++++++++++-- test/common/globals/environment.js | 6 +-- test/common/modules/client_module.js | 8 +++ test/common/modules/shared_helper.js | 6 +++ test/common/modules/testapp_manager.js | 2 +- .../browser/template/src/index-objects.ts | 2 +- test/realtime/api.test.js | 36 +++++++++++++ test/realtime/failure.test.js | 4 +- test/realtime/init.test.js | 4 +- test/realtime/transports.test.js | 4 +- test/rest/defaults.test.js | 8 +-- test/rest/fallbacks.test.js | 44 ++++++++++++++-- test/rest/request.test.js | 4 +- 13 files changed, 156 insertions(+), 24 deletions(-) diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 943807ea65..8288c6769b 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -121,29 +121,46 @@ export function getHttpScheme(options: ClientOptions): string { return options.tls ? 'https://' : 'http://'; } +/** + * REC1b2 + */ function isFqdnIpOrLocalhost(endpoint: string): boolean { return endpoint.includes('.') || endpoint.includes('::') || endpoint === 'localhost'; } -export function getEndpointHostname(endpoint: string): string { +/** + * REC1b + */ +export function getPrimaryDomainFromEndpoint(endpoint: string): string { + // REC1b2 (endpoint is a valid hostname) if (isFqdnIpOrLocalhost(endpoint)) return endpoint; + // REC1b3 (endpoint in form "nonprod:[id]") if (endpoint.startsWith('nonprod:')) { const routingPolicyId = endpoint.replace('nonprod:', ''); return `${routingPolicyId}.realtime.ably-nonprod.net`; } + // REC1b4 (endpoint in form "[id]") return `${endpoint}.realtime.ably.net`; } +/** + * REC2c + * + * @returns default callbacks based on endpoint client option + */ export function getEndpointFallbackHosts(endpoint: string): string[] { + // REC2c2 if (isFqdnIpOrLocalhost(endpoint)) return []; + // REC2c3 if (endpoint.startsWith('nonprod:')) { const routingPolicyId = endpoint.replace('nonprod:', ''); return endpointFallbacks(routingPolicyId, 'ably-realtime-nonprod.com'); } + // REC2c1 return endpointFallbacks(endpoint, 'ably-realtime.com'); } @@ -238,11 +255,35 @@ export function objectifyOptions( return optionsObj; } +function checkIfClientOptionsAreValid(options: ClientOptions) { + // REC1b + if (options.endpoint && (options.environment || options.restHost || options.realtimeHost)) { + // RSC1b + throw new ErrorInfo( + 'The `endpoint` option cannot be used in conjunction with the `environment`, `restHost`, or `realtimeHost` options.', + 40106, + 400, + ); + } + + // REC1c + if (options.environment && (options.restHost || options.realtimeHost)) { + // RSC1b + throw new ErrorInfo( + 'The `environment` option cannot be used in conjunction with the `restHost`, or `realtimeHost` options.', + 40106, + 400, + ); + } +} + export function normaliseOptions( options: ClientOptions, MsgPack: MsgPack | null, logger: Logger | null, // should only be omitted by tests ): NormalisedClientOptions { + checkIfClientOptionsAreValid(options); + const loggerToUse = logger ?? Logger.defaultLogger; if (typeof options.recover === 'function' && options.closeOnUnload === true) { @@ -264,13 +305,16 @@ export function normaliseOptions( if (!('queueMessages' in options)) options.queueMessages = true; /* infer hosts and fallbacks based on the specified endpoint */ - const endpoint = options.endpoint || options.environment || Defaults.ENDPOINT; + const endpoint = options.endpoint || Defaults.ENDPOINT; if (!options.fallbackHosts && !options.restHost && !options.realtimeHost && !options.port && !options.tlsPort) { - options.fallbackHosts = getEndpointFallbackHosts(endpoint); + options.fallbackHosts = getEndpointFallbackHosts(options.environment || endpoint); } - const primaryDomain = options.restHost || options.realtimeHost || getEndpointHostname(endpoint); + const primaryDomainFromEnvironment = options.environment && `${options.environment}.realtime.ably.net`; + const primaryDomainFromLegacyOptions = options.restHost || options.realtimeHost || primaryDomainFromEnvironment; + + const primaryDomain = primaryDomainFromLegacyOptions || getPrimaryDomainFromEndpoint(endpoint); (options.fallbackHosts || []).concat(primaryDomain).forEach(checkHost); diff --git a/test/common/globals/environment.js b/test/common/globals/environment.js index 1bc23e85b1..8a62d4539e 100644 --- a/test/common/globals/environment.js +++ b/test/common/globals/environment.js @@ -3,7 +3,7 @@ define(function (require) { var defaultLogLevel = 4, environment = isBrowser ? window.__env__ || {} : process.env, - ablyEnvironment = environment.ABLY_ENV || 'nonprod:sandbox', + ablyEndpoint = environment.ABLY_ENV || 'nonprod:sandbox', realtimeHost = environment.ABLY_REALTIME_HOST, restHost = environment.ABLY_REST_HOST, port = environment.ABLY_PORT || 80, @@ -23,7 +23,7 @@ define(function (require) { query[keyValue[0]] = keyValue[1]; } - if (query['env']) ablyEnvironment = query['env']; + if (query['env']) ablyEndpoint = query['env']; if (query['realtime_host']) realtimeHost = query['realtime_host']; if (query['rest_host']) restHost = query['rest_host']; if (query['port']) port = query['port']; @@ -63,7 +63,7 @@ define(function (require) { } return (module.exports = { - environment: ablyEnvironment, + endpoint: restHost ? undefined : ablyEndpoint, realtimeHost: realtimeHost, restHost: restHost, port: port, diff --git a/test/common/modules/client_module.js b/test/common/modules/client_module.js index 786a0cd225..1ae117ad7c 100644 --- a/test/common/modules/client_module.js +++ b/test/common/modules/client_module.js @@ -35,10 +35,18 @@ define(['ably', 'globals', 'test/common/modules/testapp_module'], function (Ably return new Ably.Realtime(ablyClientOptions(helper, options)); } + function ablyRealtimeWithoutEndpoint(helper, options) { + helper = helper.addingHelperFunction('ablyRealtime'); + const clientOptions = ablyClientOptions(helper, options); + delete clientOptions.endpoint; + return new Ably.Realtime(clientOptions); + } + return (module.exports = { Ably: Ably, AblyRest: ablyRest, AblyRealtime: ablyRealtime, + AblyRealtimeWithoutEndpoint: ablyRealtimeWithoutEndpoint, ablyClientOptions, }); }); diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 9b1a1b15fd..bb6891784b 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -459,6 +459,12 @@ define([ return client; } + AblyRealtimeWithoutEndpoint(options) { + const client = clientModule.AblyRealtimeWithoutEndpoint(this, options); + SharedHelper.activeClients.push(client); + return client; + } + /* Slightly crude catch-all hook to close any dangling realtime clients left open * after a test fails without calling closeAndFinish */ closeActiveClients() { diff --git a/test/common/modules/testapp_manager.js b/test/common/modules/testapp_manager.js index a5243cffff..ba2288c22b 100644 --- a/test/common/modules/testapp_manager.js +++ b/test/common/modules/testapp_manager.js @@ -3,7 +3,7 @@ /* testapp module is responsible for setting up and tearing down apps in the test environment */ define(['globals', 'ably'], function (ablyGlobals, ably) { - var restHost = ablyGlobals.restHost || getHostname(ablyGlobals.environment), + var restHost = ablyGlobals.restHost || getHostname(ablyGlobals.endpoint), port = ablyGlobals.tls ? ablyGlobals.tlsPort : ablyGlobals.port, scheme = ablyGlobals.tls ? 'https' : 'http'; diff --git a/test/package/browser/template/src/index-objects.ts b/test/package/browser/template/src/index-objects.ts index ada25ba889..a9442a3fe7 100644 --- a/test/package/browser/template/src/index-objects.ts +++ b/test/package/browser/template/src/index-objects.ts @@ -35,7 +35,7 @@ type ExplicitRootType = { globalThis.testAblyPackage = async function () { const key = await createSandboxAblyAPIKey(); - const realtime = new Ably.Realtime({ key, environment: 'sandbox', plugins: { Objects } }); + const realtime = new Ably.Realtime({ key, endpoint: 'nonprod:sandbox', plugins: { Objects } }); const channel = realtime.channels.get('channel', { modes: ['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH'] }); // check Objects can be accessed diff --git a/test/realtime/api.test.js b/test/realtime/api.test.js index 8aa1373bc0..ff78d184ef 100644 --- a/test/realtime/api.test.js +++ b/test/realtime/api.test.js @@ -24,6 +24,42 @@ define(['ably', 'chai'], function (Ably, chai) { ); }); + /** + * @spec REC1b1 + * @spec REC1c1 + */ + it('constructor with conflict client options', function () { + expect( + () => + new Ably.Realtime({ + endpoint: 'nonprod:sandbox', + environment: 'sandbox', + }), + ) + .to.throw() + .with.property('code', 40106); + + expect( + () => + new Ably.Realtime({ + environment: 'nonprod:sandbox', + restHost: 'localhost', + }), + ) + .to.throw() + .with.property('code', 40106); + + expect( + () => + new Ably.Realtime({ + endpoint: 'nonprod:sandbox', + restHost: 'localhost', + }), + ) + .to.throw() + .with.property('code', 40106); + }); + /** * @spec RSE1 * @spec RSE2 diff --git a/test/realtime/failure.test.js b/test/realtime/failure.test.js index f397bd7fc8..789c1d3476 100644 --- a/test/realtime/failure.test.js +++ b/test/realtime/failure.test.js @@ -127,7 +127,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async var connectionEvents = []; helper.recordPrivateApi('pass.clientOption.webSocketConnectTimeout'); - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimeWithoutEndpoint({ transports: transports, realtimeHost: 'invalid', restHost: 'invalid', @@ -213,7 +213,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async it('disconnected_backoff_' + transport, function (done) { const helper = this.test.helper; var disconnectedRetryTimeout = 150; - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimeWithoutEndpoint({ disconnectedRetryTimeout: disconnectedRetryTimeout, realtimeHost: 'invalid', restHost: 'invalid', diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 54494b7a49..551ee90fa9 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -349,7 +349,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { it('init_fallbacks', function (done) { const helper = this.test.helper; try { - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimeWithoutEndpoint({ key: 'not_a.real:key', restHost: 'a', httpMaxRetryCount: 2, @@ -500,7 +500,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { it('init_fallbacks_once_connected_2', function (done) { const helper = this.test.helper; var goodHost = helper.AblyRest().options.realtimeHost; - var realtime = helper.AblyRealtime({ + var realtime = helper.AblyRealtimeWithoutEndpoint({ httpMaxRetryCount: 3, restHost: 'a', fallbackHosts: [goodHost, 'b', 'c'], diff --git a/test/realtime/transports.test.js b/test/realtime/transports.test.js index 3c6b760037..2fe3f057e0 100644 --- a/test/realtime/transports.test.js +++ b/test/realtime/transports.test.js @@ -118,7 +118,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai const helper = this.test.helper; const goodHost = helper.AblyRest().options.realtimeHost; const realtime = helper.AblyRealtime( - options(helper, { realtimeHost: helper.unroutableAddress, fallbackHosts: [goodHost] }), + options(helper, { endpoint: helper.unroutableAddress, fallbackHosts: [goodHost] }), ); realtime.connection.on('connected', function () { @@ -168,7 +168,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai helper.recordPrivateApi('pass.clientOption.wsConnectivityCheckUrl'); const realtime = helper.AblyRealtime( options(helper, { - realtimeHost: helper.unroutableAddress, + endpoint: helper.unroutableAddress, // use unroutable host ws connectivity check to simulate no internet wsConnectivityCheckUrl: helper.unroutableWssAddress, // ensure ws slow timeout procs and performs ws connectivity check, which would fail due to unroutable host diff --git a/test/rest/defaults.test.js b/test/rest/defaults.test.js index 9ed3d2e45a..978e6ebe4d 100644 --- a/test/rest/defaults.test.js +++ b/test/rest/defaults.test.js @@ -301,7 +301,9 @@ define(['ably', 'chai'], function (Ably, chai) { ); expect(normalisedOptions.restHost).to.equal('test.org'); - expect(normalisedOptions.realtimeHost).to.equal('ws.test.org'); + // The default behavior uses a single endpoint for both Rest and Realtime, + // with developer-only option restHost taking precedence over realtimeHost + expect(normalisedOptions.realtimeHost).to.equal('test.org'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); expect(normalisedOptions.fallbackHosts).to.equal(undefined); @@ -311,7 +313,7 @@ define(['ably', 'chai'], function (Ably, chai) { expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.restHost]); helper.recordPrivateApi('call.Defaults.getHost'); expect(Defaults.getHost(normalisedOptions, 'test.org', false)).to.deep.equal('test.org'); - expect(Defaults.getHost(normalisedOptions, 'test.org', true)).to.deep.equal('ws.test.org'); + expect(Defaults.getHost(normalisedOptions, 'test.org', true)).to.deep.equal('test.org'); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -358,7 +360,7 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); helper.recordPrivateApi('write.Defaults.ENDPOINT'); - Defaults.ENDPOINT = ''; + Defaults.ENDPOINT = 'main'; }); // TODO once https://github.com/ably/ably-js/issues/1424 is fixed, this should also test the case where the useBinaryProtocol option is not specified diff --git a/test/rest/fallbacks.test.js b/test/rest/fallbacks.test.js index b6a77507b6..7399a24edf 100644 --- a/test/rest/fallbacks.test.js +++ b/test/rest/fallbacks.test.js @@ -19,11 +19,13 @@ define(['shared_helper', 'async', 'chai'], function (Helper, async, chai) { }); }); - /** @spec RSC15f */ + /** + * @spec RSC15f + */ it('Store working fallback', async function () { const helper = this.test.helper; var rest = helper.AblyRest({ - restHost: helper.unroutableHost, + endpoint: helper.unroutableHost, fallbackHosts: [goodHost], httpRequestTimeout: 3000, }); @@ -61,6 +63,40 @@ define(['shared_helper', 'async', 'chai'], function (Helper, async, chai) { expect(currentFallback.validUntil > now, 'Check validUntil has been re-set').to.be.ok; }); + /** + * @spec RSC25 + * @spec RTN17i + */ + it('Should use the primary domain as the first attempted for every connection attempt', async function () { + const helper = this.test.helper; + const rest = helper.AblyRest({ + endpoint: helper.unroutableHost, + fallbackHosts: [goodHost], + httpRequestTimeout: 3000, + }); + const originDoUri = rest.http.doUri.bind(rest.http); + const recordedHttpRequests = []; + + rest.http.doUri = (method, uri, ...rest) => { + recordedHttpRequests.push(uri); + return originDoUri(method, uri, ...rest); + }; + + await rest.time(); + expect(recordedHttpRequests.length).to.be.eq(2); + expect(recordedHttpRequests[0]).to.be.eq(`https://${helper.unroutableHost}:443/time`); + expect(recordedHttpRequests[1]).to.be.eq('https://sandbox.realtime.ably-nonprod.net:443/time'); + + recordedHttpRequests.length = 0; + helper.recordPrivateApi('write.rest._currentFallback.validUntil'); + rest._currentFallback.validUntil = Date.now() - 1000; + + await rest.time(); + expect(recordedHttpRequests.length).to.be.eq(2); + expect(recordedHttpRequests[0]).to.be.eq(`https://${helper.unroutableHost}:443/time`); + expect(recordedHttpRequests[1]).to.be.eq('https://sandbox.realtime.ably-nonprod.net:443/time'); + }); + describe('Max elapsed time for host retries', function () { /** @spec TO3l6 */ it('can timeout after default host', async function () { @@ -69,7 +105,7 @@ define(['shared_helper', 'async', 'chai'], function (Helper, async, chai) { // set httpMaxRetryDuration lower than httpRequestTimeout so it would timeout after default host attempt const httpMaxRetryDuration = Math.floor(httpRequestTimeout / 2); const rest = helper.AblyRest({ - restHost: helper.unroutableHost, + endpoint: helper.unroutableHost, fallbackHosts: [helper.unroutableHost], httpRequestTimeout, httpMaxRetryDuration, @@ -96,7 +132,7 @@ define(['shared_helper', 'async', 'chai'], function (Helper, async, chai) { // set httpMaxRetryDuration higher than httpRequestTimeout and lower than 2*httpRequestTimeout so it would timeout after first fallback host retry attempt const httpMaxRetryDuration = Math.floor(httpRequestTimeout * 1.5); const rest = helper.AblyRest({ - restHost: helper.unroutableHost, + endpoint: helper.unroutableHost, fallbackHosts: [helper.unroutableHost, helper.unroutableHost], httpRequestTimeout, httpMaxRetryDuration, diff --git a/test/rest/request.test.js b/test/rest/request.test.js index c54a7f7430..ba43723cbd 100644 --- a/test/rest/request.test.js +++ b/test/rest/request.test.js @@ -92,7 +92,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async */ it('request_network_error', async function () { const helper = this.test.helper; - rest = helper.AblyRest({ restHost: helper.unroutableAddress }); + rest = helper.AblyRest({ endpoint: helper.unroutableAddress }); try { var res = await rest.request('get', '/time', 3, null, null, null); } catch (err) { @@ -220,7 +220,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async /** @specpartial RSC19f - tests put, patch, delete methods are supported */ it('check' + method, async function () { const helper = this.test.helper.withParameterisedTestTitle('check'); - var restEcho = helper.AblyRest({ useBinaryProtocol: false, restHost: echoServerHost, tls: true }); + var restEcho = helper.AblyRest({ useBinaryProtocol: false, endpoint: echoServerHost, tls: true }); var res = await restEcho.request(method, '/methods', 3, {}, {}, {}); expect(res.items[0] && res.items[0].method).to.equal(method); }); From 5b6c87ea5f9b1e5a5ea94e405644e1a1b170f4db Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 19 Jun 2025 17:46:47 +0100 Subject: [PATCH 3/7] chore: increase minimal bundle size by 1kb --- scripts/moduleReport.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/moduleReport.ts b/scripts/moduleReport.ts index ef7e6ec8bb..2bd4c8bb75 100644 --- a/scripts/moduleReport.ts +++ b/scripts/moduleReport.ts @@ -6,7 +6,7 @@ import { gzip } from 'zlib'; import Table from 'cli-table'; // The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel) -const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 102, gzip: 31 }; +const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 103, gzip: 31 }; const baseClientNames = ['BaseRest', 'BaseRealtime']; From a7ac263912ca3cfb34016aa4829c2710797dae27 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 19 Jun 2025 18:00:38 +0100 Subject: [PATCH 4/7] chore: remove messageId format check in batch publish --- test/rest/batch.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/rest/batch.test.js b/test/rest/batch.test.js index ce6ccc7248..050d12a018 100644 --- a/test/rest/batch.test.js +++ b/test/rest/batch.test.js @@ -69,7 +69,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { expect(batchResults[0].results).to.have.lengthOf(2); expect(batchResults[0].results[0].channel).to.equal('channel0'); - expect(batchResults[0].results[0].messageId).to.include(':0'); + expect(batchResults[0].results[0].messageId).to.not.be.empty; expect('error' in batchResults[0].results[0]).to.be.false; expect(batchResults[0].results[1].channel).to.equal('channel3'); @@ -81,7 +81,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { expect(batchResults[1].results).to.have.lengthOf(2); expect(batchResults[1].results[0].channel).to.equal('channel4'); - expect(batchResults[1].results[0].messageId).to.include(':0'); + expect(batchResults[1].results[0].messageId).to.not.be.empty; expect('error' in batchResults[1].results[0]).to.be.false; expect(batchResults[1].results[1].channel).to.equal('channel5'); @@ -147,7 +147,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { expect(batchResult.results).to.have.lengthOf(2); expect(batchResult.results[0].channel).to.equal('channel0'); - expect(batchResult.results[0].messageId).to.include(':0'); + expect(batchResult.results[0].messageId).to.not.be.empty; expect('error' in batchResult.results[0]).to.be.false; expect(batchResult.results[1].channel).to.equal('channel3'); From 0231be363dff0d0f511a8d9f0181c3913ed14f72 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 19 Jun 2025 18:09:00 +0100 Subject: [PATCH 5/7] chore: fix create JWT tests --- test/rest/auth.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/rest/auth.test.js b/test/rest/auth.test.js index 90c5c07a5a..69a3b249fb 100644 --- a/test/rest/auth.test.js +++ b/test/rest/auth.test.js @@ -400,11 +400,14 @@ define(['chai', 'shared_helper', 'async', 'globals'], function (chai, Helper, as testJWTAuthParams('Basic rest JWT', {}); testJWTAuthParams('Rest JWT with return type ', { returnType: 'jwt' }); /* The embedded tests rely on the echoserver getting a token from realtime, so won't work against a local realtime */ - if (globals.environment !== 'local') { - testJWTAuthParams('Rest embedded JWT', { jwtType: 'embedded', environment: globals.environment }); + if (globals.endpoint !== 'local') { + testJWTAuthParams('Rest embedded JWT', { + jwtType: 'embedded', + environment: globals.endpoint.replace('nonprod:', ''), + }); testJWTAuthParams('Rest embedded JWT with encryption', { jwtType: 'embedded', - environment: globals.environment, + environment: globals.endpoint.replace('nonprod:', ''), encrypted: 1, }); } From 29898796bbf1737bf9b72db56efec7b2ff408b5e Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 20 Jun 2025 00:11:43 +0100 Subject: [PATCH 6/7] refactor: replace `restHost` and `realtimeHost` with `primaryDomain` - Refactored tests, using `primaryDomain` instead of `restHost` and `realtimeHost`. - Updated implementation to align with new naming conventions. - Adjusted specs and test expectations accordingly. --- .vscode/launch.json | 2 +- CONTRIBUTING.md | 4 +- src/common/lib/transport/comettransport.ts | 2 +- src/common/lib/transport/connectionmanager.ts | 12 +-- src/common/lib/util/defaults.ts | 19 +--- src/common/types/ClientOptions.ts | 3 +- test/common/globals/environment.js | 12 +-- test/common/modules/private_api_recorder.js | 12 +-- test/common/modules/testapp_manager.js | 3 +- test/realtime/init.test.js | 33 ++++--- test/realtime/transports.test.js | 19 ++-- test/rest/defaults.test.js | 97 ++++--------------- test/rest/fallbacks.test.js | 2 +- test/rest/message.test.js | 4 +- test/rest/push.test.js | 6 +- 15 files changed, 80 insertions(+), 150 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f7be7651f3..f2bb7bc3b7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "request": "launch", "name": "Debug Test", "env": { - "ABLY_ENV": "sandbox" + "ABLY_ENDPOINT": "nonprod:sandbox" }, "cwd": "${workspaceFolder}", "runtimeExecutable": "node", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd39a646e0..cd4bb2ee21 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,9 +139,7 @@ Run the following command to fix linting/formatting issues All tests are run against the sandbox environment by default. However, the following environment variables can be set before running the Karma server to change the environment the tests are run against. -- `ABLY_ENV` - defaults to sandbox, however this can be set to another known environment such as 'staging' -- `ABLY_REALTIME_HOST` - explicitly tell the client library to use an alternate host for real-time websocket communication. -- `ABLY_REST_HOST` - explicitly tell the client library to use an alternate host for REST communication. +- `ABLY_ENDPOINT` - defaults to nonprod:sandbox, however this can be set to another known prod / nonprod routing policy id or primary domain - `ABLY_PORT` - non-TLS port to use for the tests, defaults to 80 - `ABLY_TLS_PORT` - TLS port to use for the tests, defaults to 443 - `ABLY_USE_TLS` - true or false to enable/disable use of TLS respectively diff --git a/src/common/lib/transport/comettransport.ts b/src/common/lib/transport/comettransport.ts index a4d3d557ca..104a521f3c 100644 --- a/src/common/lib/transport/comettransport.ts +++ b/src/common/lib/transport/comettransport.ts @@ -77,7 +77,7 @@ abstract class CometTransport extends Transport { Transport.prototype.connect.call(this); const params = this.params; const options = params.options; - const host = Defaults.getHost(options, params.host); + const host = params.host || options.primaryDomain; const port = Defaults.getPort(options); const cometScheme = options.tls ? 'https://' : 'http://'; diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 3017dd7c71..bde4745b5d 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -185,8 +185,7 @@ class ConnectionManager extends EventEmitter { baseTransport?: TransportName; webSocketTransportAvailable?: true; transportPreference: string | null; - httpHosts: string[]; - wsHosts: string[]; + domains: string[]; activeProtocol: null | Protocol; pendingTransport?: Transport; proposedTransport?: Transport; @@ -293,8 +292,7 @@ class ConnectionManager extends EventEmitter { this.baseTransport = TransportNames.Comet; } - this.httpHosts = Defaults.getHosts(options); - this.wsHosts = Defaults.getHosts(options, true); + this.domains = Defaults.getHosts(options); this.activeProtocol = null; this.host = null; this.lastAutoReconnectAttempt = null; @@ -323,7 +321,7 @@ class ConnectionManager extends EventEmitter { this.logger, Logger.LOG_MICRO, 'Realtime.ConnectionManager()', - 'http hosts = [' + this.httpHosts + ']', + 'http domains = [' + this.domains + ']', ); if (!this.transports.length) { @@ -835,7 +833,7 @@ class ConnectionManager extends EventEmitter { * setting an instance variable to force fallback hosts to be used (if * any) here. Bit of a kludge, but no real better alternatives without * rewriting the entire thing */ - if (state === 'disconnected' && error && (error.statusCode as number) > 500 && this.httpHosts.length > 1) { + if (state === 'disconnected' && error && (error.statusCode as number) > 500 && this.domains.length > 1) { this.unpersistTransportPreference(); this.forceFallbackHost = true; /* and try to connect again to try a fallback host without waiting for the usual 15s disconnectedRetryTimeout */ @@ -1533,7 +1531,7 @@ class ConnectionManager extends EventEmitter { this.notifyState({ state: this.states.connecting.failState as string, error: err }); }; - const candidateHosts = ws ? this.wsHosts.slice() : this.httpHosts.slice(); + const candidateHosts = this.domains.slice(); const hostAttemptCb = (fatal: boolean, transport: Transport) => { if (connectCount !== this.connectCounter) { diff --git a/src/common/lib/util/defaults.ts b/src/common/lib/util/defaults.ts index 8288c6769b..e871a8e19f 100644 --- a/src/common/lib/util/defaults.ts +++ b/src/common/lib/util/defaults.ts @@ -38,9 +38,9 @@ type CompleteDefaults = IDefaults & { version: string; protocolVersion: number; agent: string; - getHost(options: ClientOptions, host?: string | null, ws?: boolean): string; getPort(options: ClientOptions, tls?: boolean): number | undefined; getHttpScheme(options: ClientOptions): string; + getPrimaryDomainFromEndpoint(endpoint: string): string; getEndpointFallbackHosts(endpoint: string): string[]; getFallbackHosts(options: NormalisedClientOptions): string[]; getHosts(options: NormalisedClientOptions, ws?: boolean): string[]; @@ -93,9 +93,9 @@ const Defaults = { version, protocolVersion: 3, agent, - getHost, getPort, getHttpScheme, + getPrimaryDomainFromEndpoint, getEndpointFallbackHosts, getFallbackHosts, getHosts, @@ -106,13 +106,6 @@ const Defaults = { defaultPostHeaders, }; -export function getHost(options: ClientOptions, host?: string | null, ws?: boolean): string { - if (ws) host = (host == options.restHost && options.realtimeHost) || host || options.realtimeHost; - else host = host || options.restHost; - - return host as string; -} - export function getPort(options: ClientOptions, tls?: boolean): number | undefined { return tls || options.tls ? options.tlsPort : options.port; } @@ -176,9 +169,8 @@ export function getFallbackHosts(options: NormalisedClientOptions): string[] { return fallbackHosts ? Utils.arrChooseN(fallbackHosts, httpMaxRetryCount) : []; } -export function getHosts(options: NormalisedClientOptions, ws?: boolean): string[] { - const hosts = [options.restHost].concat(getFallbackHosts(options)); - return ws ? hosts.map((host) => getHost(options, host, true)) : hosts; +export function getHosts(options: NormalisedClientOptions): string[] { + return [options.primaryDomain].concat(getFallbackHosts(options)); } function checkHost(host: string): void { @@ -361,8 +353,7 @@ export function normaliseOptions( return { ...options, - restHost: primaryDomain, - realtimeHost: primaryDomain, + primaryDomain: primaryDomain, maxMessageSize: options.maxMessageSize || Defaults.maxMessageSize, timeouts, connectivityCheckParams, diff --git a/src/common/types/ClientOptions.ts b/src/common/types/ClientOptions.ts index 5930d29488..ad4c6e21dd 100644 --- a/src/common/types/ClientOptions.ts +++ b/src/common/types/ClientOptions.ts @@ -17,8 +17,7 @@ export default interface ClientOptions extends API.ClientOptions; diff --git a/test/common/globals/environment.js b/test/common/globals/environment.js index 8a62d4539e..aeb49fc7bc 100644 --- a/test/common/globals/environment.js +++ b/test/common/globals/environment.js @@ -3,9 +3,7 @@ define(function (require) { var defaultLogLevel = 4, environment = isBrowser ? window.__env__ || {} : process.env, - ablyEndpoint = environment.ABLY_ENV || 'nonprod:sandbox', - realtimeHost = environment.ABLY_REALTIME_HOST, - restHost = environment.ABLY_REST_HOST, + ablyEndpoint = environment.ABLY_ENDPOINT || 'nonprod:sandbox', port = environment.ABLY_PORT || 80, tlsPort = environment.ABLY_TLS_PORT || 443, tls = 'ABLY_USE_TLS' in environment ? environment.ABLY_USE_TLS.toLowerCase() !== 'false' : true, @@ -23,9 +21,7 @@ define(function (require) { query[keyValue[0]] = keyValue[1]; } - if (query['env']) ablyEndpoint = query['env']; - if (query['realtime_host']) realtimeHost = query['realtime_host']; - if (query['rest_host']) restHost = query['rest_host']; + if (query['endpoint']) ablyEndpoint = query['endpoint']; if (query['port']) port = query['port']; if (query['tls_port']) tlsPort = query['tls_port']; if (query['tls']) tls = query['tls'].toLowerCase() !== 'false'; @@ -63,9 +59,7 @@ define(function (require) { } return (module.exports = { - endpoint: restHost ? undefined : ablyEndpoint, - realtimeHost: realtimeHost, - restHost: restHost, + endpoint: ablyEndpoint, port: port, tlsPort: tlsPort, tls: tls, diff --git a/test/common/modules/private_api_recorder.js b/test/common/modules/private_api_recorder.js index 941f269a65..d242ab6da3 100644 --- a/test/common/modules/private_api_recorder.js +++ b/test/common/modules/private_api_recorder.js @@ -10,8 +10,6 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths) 'call.BufferUtils.toArrayBuffer', 'call.BufferUtils.utf8Encode', 'call.ConnectionManager.supportedTransports', - 'call.Defaults.getHost', - 'call.Defaults.getHost', 'call.Defaults.getHosts', 'call.Defaults.getPort', 'call.Defaults.normaliseOptions', @@ -97,7 +95,7 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths) 'read.connectionManager.connectionId', 'read.connectionManager.connectionId', 'read.connectionManager.connectionStateTtl', - 'read.connectionManager.httpHosts', + 'read.connectionManager.domains', 'read.connectionManager.msgSerial', 'read.connectionManager.options', 'read.connectionManager.options.timeouts.httpMaxRetryDuration', @@ -110,14 +108,14 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths) 'read.realtime.options', 'read.realtime.options.key', 'read.realtime.options.maxMessageSize', - 'read.realtime.options.realtimeHost', + 'read.realtime.options.primaryDomain', 'read.realtime.options.token', 'read.realtime.options.useBinaryProtocol', 'read.rest._currentFallback', 'read.rest._currentFallback.host', 'read.rest._currentFallback.validUntil', 'read.rest.options.key', - 'read.rest.options.realtimeHost', + 'read.rest.options.primaryDomain', 'read.rest.options.token', 'read.rest.serverTimeOffset', 'read.transport.params.mode', @@ -156,9 +154,9 @@ define(['test/support/output_directory_paths'], function (outputDirectoryPaths) 'write.connectionManager.connectionKey', 'write.connectionManager.lastActivity', 'write.connectionManager.msgSerial', - 'write.connectionManager.wsHosts', + 'write.connectionManager.domains', 'write.realtime.options.echoMessages', - 'write.realtime.options.realtimeHost', + 'write.realtime.options.primaryDomain', 'write.realtime.options.wsConnectivityCheckUrl', 'write.realtime.options.timeouts.realtimeRequestTimeout', 'write.rest._currentFallback.validUntil', diff --git a/test/common/modules/testapp_manager.js b/test/common/modules/testapp_manager.js index ba2288c22b..d4638e99a6 100644 --- a/test/common/modules/testapp_manager.js +++ b/test/common/modules/testapp_manager.js @@ -3,7 +3,8 @@ /* testapp module is responsible for setting up and tearing down apps in the test environment */ define(['globals', 'ably'], function (ablyGlobals, ably) { - var restHost = ablyGlobals.restHost || getHostname(ablyGlobals.endpoint), + const Defaults = ably.Realtime.Platform.Defaults; + var restHost = Defaults.getPrimaryDomainFromEndpoint(ablyGlobals.endpoint), port = ablyGlobals.tls ? ablyGlobals.tlsPort : ablyGlobals.port, scheme = ablyGlobals.tls ? 'https' : 'http'; diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 551ee90fa9..5a1d801b55 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -267,19 +267,27 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { }); /** - * Check default httpHost selection. + * Check primary domain selection. * @specpartial RSC11 - tests only default value */ - it('init_defaulthost', function (done) { + it('init_primary_domain', function (done) { const helper = this.test.helper; try { /* want to check the default host when no custom environment or custom * host set, so not using helpers.realtime this time, which will use a * test env */ - var realtime = new Ably.Realtime({ key: 'not_a.real:key', autoConnect: false }); - helper.recordPrivateApi('read.connectionManager.httpHosts'); - var defaultHost = realtime.connection.connectionManager.httpHosts[0]; - expect(defaultHost).to.equal('main.realtime.ably.net', 'Verify correct default rest host chosen'); + const realtime = new Ably.Realtime({ key: 'not_a.real:key', autoConnect: false }); + helper.recordPrivateApi('read.connectionManager.domains'); + helper.recordPrivateApi('read.realtime.options.primaryDomain'); + expect(realtime.options.primaryDomain).to.equal( + 'main.realtime.ably.net', + 'Verify correct primary domain chosen', + ); + const primaryDomainOnDomainsList = realtime.connection.connectionManager.domains[0]; + expect(primaryDomainOnDomainsList).to.equal( + 'main.realtime.ably.net', + 'Verify correct primary domain on the domains list', + ); realtime.close(); done(); } catch (err) { @@ -357,12 +365,12 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { fallbackHosts: ['b', 'c', 'd', 'e'], }); /* Note: uses internal knowledge of connectionManager */ - helper.recordPrivateApi('read.connectionManager.httpHosts'); - expect(realtime.connection.connectionManager.httpHosts.length).to.equal( + helper.recordPrivateApi('read.connectionManager.domains'); + expect(realtime.connection.connectionManager.domains.length).to.equal( 3, 'Verify hosts list is the expected length', ); - expect(realtime.connection.connectionManager.httpHosts[0]).to.equal('a', 'Verify given restHost is first'); + expect(realtime.connection.connectionManager.domains[0]).to.equal('a', 'Verify given restHost is first'); /* Replace chooseTransportForHost with a spy, then try calling * chooseHttpTransport to see what host is picked */ helper.recordPrivateApi('replace.connectionManager.tryATransport'); @@ -486,7 +494,10 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { var hosts = new Ably.Rest._Http()._getHosts(realtime); /* restHost rather than realtimeHost as that's what connectionManager * knows about; converted to realtimeHost by the websocketTransport */ - expect(hosts[0]).to.equal(realtime.options.realtimeHost, 'Check connected realtime host is the first option'); + expect(hosts[0]).to.equal( + realtime.options.primaryDomain, + 'Check connected realtime host is the first option', + ); expect(hosts.length).to.equal(4, 'Check also have three fallbacks'); } catch (err) { helper.closeAndFinish(done, realtime, err); @@ -499,7 +510,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { /** @specpartial RTN17e */ it('init_fallbacks_once_connected_2', function (done) { const helper = this.test.helper; - var goodHost = helper.AblyRest().options.realtimeHost; + var goodHost = helper.AblyRest().options.primaryDomain; var realtime = helper.AblyRealtimeWithoutEndpoint({ httpMaxRetryCount: 3, restHost: 'a', diff --git a/test/realtime/transports.test.js b/test/realtime/transports.test.js index 2fe3f057e0..58894a934f 100644 --- a/test/realtime/transports.test.js +++ b/test/realtime/transports.test.js @@ -116,7 +116,7 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai /** @nospec */ it('ws_primary_host_fails', function (done) { const helper = this.test.helper; - const goodHost = helper.AblyRest().options.realtimeHost; + const goodHost = helper.AblyRest().options.primaryDomain; const realtime = helper.AblyRealtime( options(helper, { endpoint: helper.unroutableAddress, fallbackHosts: [goodHost] }), ); @@ -129,7 +129,10 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai helper.monitorConnection(done, realtime); }); - /** @specpartial RTN14d */ + /** + * @spec REC3b + * @specpartial RTN14d + */ it('no_internet_connectivity', function (done) { const helper = this.test.helper; Config.WebSocket = FakeWebSocket; @@ -161,8 +164,8 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai /** @nospec */ it('ws_can_reconnect_after_ws_connectivity_fail', function (done) { const helper = this.test.helper; - helper.recordPrivateApi('read.realtime.options.realtimeHost'); - const goodHost = helper.AblyRest().options.realtimeHost; + helper.recordPrivateApi('read.realtime.options.primaryDomain'); + const goodHost = helper.AblyRest().options.primaryDomain; helper.recordPrivateApi('pass.clientOption.webSocketSlowTimeout'); helper.recordPrivateApi('pass.clientOption.wsConnectivityCheckUrl'); @@ -200,10 +203,10 @@ define(['shared_helper', 'async', 'chai', 'ably'], function (Helper, async, chai connection.connectionManager.tryATransport = tryATransportOriginal; helper.recordPrivateApi('write.realtime.options.wsConnectivityCheckUrl'); realtime.options.wsConnectivityCheckUrl = originialWsCheckUrl; - helper.recordPrivateApi('write.realtime.options.realtimeHost'); - realtime.options.realtimeHost = goodHost; - helper.recordPrivateApi('write.connectionManager.wsHosts'); - realtime.connection.connectionManager.wsHosts = [goodHost]; + helper.recordPrivateApi('write.realtime.options.primaryDomain'); + realtime.options.primaryDomain = goodHost; + helper.recordPrivateApi('write.connectionManager.domains'); + realtime.connection.connectionManager.domains = [goodHost]; cb(); }, diff --git a/test/rest/defaults.test.js b/test/rest/defaults.test.js index 978e6ebe4d..e04c2d0714 100644 --- a/test/rest/defaults.test.js +++ b/test/rest/defaults.test.js @@ -13,10 +13,9 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3k6 * @spec TO3d * @spec RSC15h - * @specpartial RSC11 - test default value for restHost - * @specpartial RTN2 - test default value for realtimeHost - * @specpartial RSC15e - primary host for REST is restHost - * @specpartial RTN17a - primary host for realtime is realtimeHost + * @specpartial RTN2 - test default value for realtime websocket connection + * @specpartial RSC25 - primary domain for REST is `primaryDomain` + * @specpartial RTN17i - primary domain for realtime is `primaryDomain` */ it('Init with no endpoint-related options', function () { const helper = this.test.helper; @@ -24,8 +23,7 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.normaliseOptions'); var normalisedOptions = Defaults.normaliseOptions({}, null, null); - expect(normalisedOptions.restHost).to.equal('main.realtime.ably.net'); - expect(normalisedOptions.realtimeHost).to.equal('main.realtime.ably.net'); + expect(normalisedOptions.primaryDomain).to.equal('main.realtime.ably.net'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.FALLBACK_HOSTS.sort()); @@ -33,13 +31,7 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.equal(4); - expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', false)).to.deep.equal( - 'main.realtime.ably.net', - ); - expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', true)).to.equal('main.realtime.ably.net'); - + expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.primaryDomain); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); }); @@ -67,14 +59,7 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(4); - expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', false)).to.deep.equal( - 'sandbox.realtime.ably-nonprod.net', - ); - expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', true)).to.deep.equal( - 'sandbox.realtime.ably-nonprod.net', - ); + expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.primaryDomain); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -92,9 +77,6 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(1); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'example.com', false)).to.deep.equal('example.com'); - expect(Defaults.getHost(normalisedOptions, 'example.com', true)).to.deep.equal('example.com'); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -113,9 +95,6 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); console.log(Defaults.getHosts(normalisedOptions)); expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(1); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, '127.0.0.1', false)).to.deep.equal('127.0.0.1'); - expect(Defaults.getHost(normalisedOptions, '127.0.0.1', true)).to.deep.equal('127.0.0.1'); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -133,9 +112,6 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(1); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, '::1', false)).to.deep.equal('::1'); - expect(Defaults.getHost(normalisedOptions, '::1', true)).to.deep.equal('::1'); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -153,9 +129,6 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(1); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'localhost', false)).to.deep.equal('localhost'); - expect(Defaults.getHost(normalisedOptions, 'localhost', true)).to.deep.equal('localhost'); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -169,7 +142,7 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3k5 * @spec TO3k6 * @spec TO3d - * @specpartial REC1d1 - test restHost is overridden by environment + * @specpartial REC1d1 - test primary domain is overridden by environment * @specpartial REC1c - test with environment set other than 'production' */ it('Init with given environment', function () { @@ -178,8 +151,7 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.normaliseOptions'); var normalisedOptions = Defaults.normaliseOptions({ environment: 'main' }, null, null); - expect(normalisedOptions.restHost).to.equal('main.realtime.ably.net'); - expect(normalisedOptions.realtimeHost).to.equal('main.realtime.ably.net'); + expect(normalisedOptions.primaryDomain).to.equal('main.realtime.ably.net'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.getEndpointFallbackHosts('main').sort()); @@ -187,14 +159,7 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(4); - expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', false)).to.deep.equal( - 'main.realtime.ably.net', - ); - expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', true)).to.deep.equal( - 'main.realtime.ably.net', - ); + expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.primaryDomain); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -221,22 +186,14 @@ define(['ably', 'chai'], function (Ably, chai) { null, ); - expect(normalisedOptions.restHost).to.equal('local.realtime.ably.net'); - expect(normalisedOptions.realtimeHost).to.equal('local.realtime.ably.net'); + expect(normalisedOptions.primaryDomain).to.equal('local.realtime.ably.net'); expect(normalisedOptions.port).to.equal(8080); expect(normalisedOptions.tlsPort).to.equal(8081); expect(normalisedOptions.fallbackHosts).to.equal(undefined); expect(normalisedOptions.tls).to.equal(true); helper.recordPrivateApi('call.Defaults.getHosts'); - expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.restHost]); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'local.realtime.ably.net', false)).to.deep.equal( - 'local.realtime.ably.net', - ); - expect(Defaults.getHost(normalisedOptions, 'local.realtime.ably.net', true)).to.deep.equal( - 'local.realtime.ably.net', - ); + expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.primaryDomain]); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(8081); @@ -253,7 +210,7 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3d * @spec REC1d1 * @spec REC1d2 - * @specpartial RSC11 - test restHost is overridden by custom value + * @specpartial RSC25 - test primary domain defined by restHost */ it('Init with given host', function () { const helper = this.test.helper; @@ -261,18 +218,14 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.normaliseOptions'); var normalisedOptions = Defaults.normaliseOptions({ restHost: 'test.org' }, null, null); - expect(normalisedOptions.restHost).to.equal('test.org'); - expect(normalisedOptions.realtimeHost).to.equal('test.org'); + expect(normalisedOptions.primaryDomain).to.equal('test.org'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); expect(normalisedOptions.fallbackHosts).to.equal(undefined); expect(normalisedOptions.tls).to.equal(true); helper.recordPrivateApi('call.Defaults.getHosts'); - expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.restHost]); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'test.org', false)).to.deep.equal('test.org'); - expect(Defaults.getHost(normalisedOptions, 'test.org', true)).to.deep.equal('test.org'); + expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.primaryDomain]); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -300,20 +253,14 @@ define(['ably', 'chai'], function (Ably, chai) { null, ); - expect(normalisedOptions.restHost).to.equal('test.org'); - // The default behavior uses a single endpoint for both Rest and Realtime, - // with developer-only option restHost taking precedence over realtimeHost - expect(normalisedOptions.realtimeHost).to.equal('test.org'); + expect(normalisedOptions.primaryDomain).to.equal('test.org'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); expect(normalisedOptions.fallbackHosts).to.equal(undefined); expect(normalisedOptions.tls).to.equal(true); helper.recordPrivateApi('call.Defaults.getHosts'); - expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.restHost]); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'test.org', false)).to.deep.equal('test.org'); - expect(Defaults.getHost(normalisedOptions, 'test.org', true)).to.deep.equal('test.org'); + expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.primaryDomain]); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -337,8 +284,7 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.normaliseOptions'); var normalisedOptions = Defaults.normaliseOptions({}, null, null); - expect(normalisedOptions.restHost).to.equal('sandbox.realtime.ably-nonprod.net'); - expect(normalisedOptions.realtimeHost).to.equal('sandbox.realtime.ably-nonprod.net'); + expect(normalisedOptions.primaryDomain).to.equal('sandbox.realtime.ably-nonprod.net'); expect(normalisedOptions.port).to.equal(80); expect(normalisedOptions.tlsPort).to.equal(443); expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal( @@ -348,14 +294,7 @@ define(['ably', 'chai'], function (Ably, chai) { helper.recordPrivateApi('call.Defaults.getHosts'); expect(Defaults.getHosts(normalisedOptions).length).to.equal(4); - expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost); - helper.recordPrivateApi('call.Defaults.getHost'); - expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', false)).to.deep.equal( - 'sandbox.realtime.ably-nonprod.net', - ); - expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', true)).to.deep.equal( - 'sandbox.realtime.ably-nonprod.net', - ); + expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.primaryDomain); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); diff --git a/test/rest/fallbacks.test.js b/test/rest/fallbacks.test.js index 7399a24edf..315e5eb1df 100644 --- a/test/rest/fallbacks.test.js +++ b/test/rest/fallbacks.test.js @@ -14,7 +14,7 @@ define(['shared_helper', 'async', 'chai'], function (Helper, async, chai) { done(err); return; } - goodHost = helper.AblyRest().options.restHost; + goodHost = helper.AblyRest().options.primaryDomain; done(); }); }); diff --git a/test/rest/message.test.js b/test/rest/message.test.js index 633a08cf9c..14af617483 100644 --- a/test/rest/message.test.js +++ b/test/rest/message.test.js @@ -172,7 +172,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async /* easiest way to get the host we're using for tests */ var helper = this.test.helper, dummyRest = helper.AblyRest(), - host = dummyRest.options.restHost, + host = dummyRest.options.primaryDomain, /* Add the same host as a bunch of fallback hosts, so after the first * request 'fails' we retry on the same host using the fallback mechanism */ rest = helper.AblyRest({ @@ -219,7 +219,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, Helper, async await channel.publish([{ name: 'one' }, { name: 'two' }]); var page = await channel.history({ direction: 'forwards' }); /* TODO uncomment when idempotent publishing works on sandbox - * until then, test with ABLY_ENV=idempotent-dev + * until then, test with ABLY_ENDPOINT=idempotent-dev * test.equal(page.items.length, 2, 'Only one message (with two items) should have been published'); */ expect(page.items[0].id).to.equal(idOne, 'Check message id 1 preserved in history'); diff --git a/test/rest/push.test.js b/test/rest/push.test.js index 66a7f4857c..8556600b61 100644 --- a/test/rest/push.test.js +++ b/test/rest/push.test.js @@ -129,9 +129,8 @@ define(['ably', 'shared_helper', 'async', 'chai', 'test/support/push_channel_tra }; helper.recordPrivateApi('read.realtime.options'); - helper.recordPrivateApi('call.Defaults.getHost'); helper.recordPrivateApi('call.realtime.baseUri'); - var baseUri = realtime.baseUri(Ably.Rest.Platform.Defaults.getHost(realtime.options)); + var baseUri = realtime.baseUri(realtime.options.primaryDomain); var pushRecipient = { transportType: 'ablyChannel', channel: 'pushenabled:foo', @@ -413,9 +412,8 @@ define(['ably', 'shared_helper', 'async', 'chai', 'test/support/push_channel_tra const channel = realtime.channels.get(channelName); - helper.recordPrivateApi('call.Defaults.getHost'); helper.recordPrivateApi('read.realtime.options'); - const baseUri = realtime.baseUri(Ably.Rest.Platform.Defaults.getHost(realtime.options)); + const baseUri = realtime.baseUri(realtime.options.primaryDomain); helper.recordPrivateApi('read.realtime.options'); const pushRecipient = { From 1f1a8119cc51854a043470e1d7661c3f7d3b8ca7 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 24 Jun 2025 09:48:29 +0100 Subject: [PATCH 7/7] fix: record missing primaryDomain private API usage --- test/realtime/init.test.js | 1 + test/rest/push.test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/realtime/init.test.js b/test/realtime/init.test.js index 5a1d801b55..71fc8d2ce8 100644 --- a/test/realtime/init.test.js +++ b/test/realtime/init.test.js @@ -494,6 +494,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) { var hosts = new Ably.Rest._Http()._getHosts(realtime); /* restHost rather than realtimeHost as that's what connectionManager * knows about; converted to realtimeHost by the websocketTransport */ + helper.recordPrivateApi('read.realtime.options.primaryDomain'); expect(hosts[0]).to.equal( realtime.options.primaryDomain, 'Check connected realtime host is the first option', diff --git a/test/rest/push.test.js b/test/rest/push.test.js index 8556600b61..53e7bfb20d 100644 --- a/test/rest/push.test.js +++ b/test/rest/push.test.js @@ -412,7 +412,7 @@ define(['ably', 'shared_helper', 'async', 'chai', 'test/support/push_channel_tra const channel = realtime.channels.get(channelName); - helper.recordPrivateApi('read.realtime.options'); + helper.recordPrivateApi('read.realtime.options.primaryDomain'); const baseUri = realtime.baseUri(realtime.options.primaryDomain); helper.recordPrivateApi('read.realtime.options');