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/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/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']; 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 41292df675..e871a8e19f 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; @@ -37,10 +38,10 @@ 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; - environmentFallbackHosts(environment: string): string[]; + getPrimaryDomainFromEndpoint(endpoint: 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, @@ -91,10 +93,10 @@ const Defaults = { version, protocolVersion: 3, agent, - getHost, getPort, getHttpScheme, - environmentFallbackHosts, + getPrimaryDomainFromEndpoint, + getEndpointFallbackHosts, getFallbackHosts, getHosts, checkHost, @@ -104,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; } @@ -119,15 +114,51 @@ 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', - ]; +/** + * REC1b2 + */ +function isFqdnIpOrLocalhost(endpoint: string): boolean { + return endpoint.includes('.') || endpoint.includes('::') || endpoint === 'localhost'; +} + +/** + * 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'); +} + +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[] { @@ -138,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 { @@ -152,26 +182,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 = {}; @@ -237,11 +247,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) { @@ -262,18 +296,19 @@ 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 || Defaults.ENDPOINT; if (!options.fallbackHosts && !options.restHost && !options.realtimeHost && !options.port && !options.tlsPort) { - options.fallbackHosts = production ? Defaults.FALLBACK_HOSTS : environmentFallbackHosts(environment); + options.fallbackHosts = getEndpointFallbackHosts(options.environment || endpoint); } - const restHost = options.restHost || (production ? Defaults.REST_HOST : environment + '-' + Defaults.REST_HOST); - const realtimeHost = getRealtimeHost(options, production, environment, loggerToUse); + const primaryDomainFromEnvironment = options.environment && `${options.environment}.realtime.ably.net`; + const primaryDomainFromLegacyOptions = options.restHost || options.realtimeHost || primaryDomainFromEnvironment; + + const primaryDomain = primaryDomainFromLegacyOptions || getPrimaryDomainFromEndpoint(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 +353,7 @@ export function normaliseOptions( return { ...options, - realtimeHost, - restHost, + 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 f1515222f8..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, - ablyEnvironment = environment.ABLY_ENV || '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']) ablyEnvironment = 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 = { - environment: ablyEnvironment, - realtimeHost: realtimeHost, - restHost: restHost, + endpoint: ablyEndpoint, port: port, tlsPort: tlsPort, tls: tls, 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/private_api_recorder.js b/test/common/modules/private_api_recorder.js index cecf6670b8..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', @@ -139,6 +137,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', @@ -155,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/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 0f4b7fa9dc..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 || prefixDomainWithEnvironment('rest.ably.io', ablyGlobals.environment), + const Defaults = ably.Realtime.Platform.Defaults; + var restHost = Defaults.getPrimaryDomainFromEndpoint(ablyGlobals.endpoint), port = ablyGlobals.tls ? ablyGlobals.tlsPort : ablyGlobals.port, scheme = ablyGlobals.tls ? 'https' : 'http'; @@ -23,12 +24,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/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/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/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/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/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 af15253c0a..71fc8d2ce8 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('rest.ably.io', '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) { @@ -340,8 +348,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 @@ -350,7 +357,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, @@ -358,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'); @@ -473,7 +480,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({ @@ -487,7 +494,11 @@ 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'); + helper.recordPrivateApi('read.realtime.options.primaryDomain'); + 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); @@ -500,8 +511,8 @@ 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 realtime = helper.AblyRealtime({ + var goodHost = helper.AblyRest().options.primaryDomain; + 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..58894a934f 100644 --- a/test/realtime/transports.test.js +++ b/test/realtime/transports.test.js @@ -116,9 +116,9 @@ 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, { realtimeHost: helper.unroutableAddress, fallbackHosts: [goodHost] }), + options(helper, { endpoint: helper.unroutableAddress, fallbackHosts: [goodHost] }), ); realtime.connection.on('connected', function () { @@ -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,14 +164,14 @@ 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'); 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 @@ -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/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, }); } 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'); diff --git a/test/rest/defaults.test.js b/test/rest/defaults.test.js index c74f895613..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('rest.ably.io'); - expect(normalisedOptions.realtimeHost).to.equal('realtime.ably.io'); + 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,51 +31,104 @@ 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, '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.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.primaryDomain); 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.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.primaryDomain); + + 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.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.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.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.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -91,33 +142,24 @@ 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 primary domain 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.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.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.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.primaryDomain); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -131,11 +173,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,18 +186,14 @@ 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.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-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.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.primaryDomain]); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(8081); @@ -173,9 +208,9 @@ define(['ably', 'chai'], function (Ably, chai) { * @spec TO3k5 * @spec TO3k6 * @spec TO3d - * @spec RSC15h - * @spec RSC11a - * @specpartial RSC11 - test restHost is overridden by custom value + * @spec REC1d1 + * @spec REC1d2 + * @specpartial RSC25 - test primary domain defined by restHost */ it('Init with given host', function () { const helper = this.test.helper; @@ -183,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); @@ -207,8 +238,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 */ @@ -222,18 +253,14 @@ define(['ably', 'chai'], function (Ably, chai) { null, ); - expect(normalisedOptions.restHost).to.equal('test.org'); - expect(normalisedOptions.realtimeHost).to.equal('ws.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('ws.test.org'); + expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.primaryDomain]); helper.recordPrivateApi('call.Defaults.getPort'); expect(Defaults.getPort(normalisedOptions)).to.equal(443); @@ -246,39 +273,33 @@ 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.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(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.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.primaryDomain); 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 = '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..315e5eb1df 100644 --- a/test/rest/fallbacks.test.js +++ b/test/rest/fallbacks.test.js @@ -14,16 +14,18 @@ define(['shared_helper', 'async', 'chai'], function (Helper, async, chai) { done(err); return; } - goodHost = helper.AblyRest().options.restHost; + goodHost = helper.AblyRest().options.primaryDomain; done(); }); }); - /** @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/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..53e7bfb20d 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)); + helper.recordPrivateApi('read.realtime.options.primaryDomain'); + const baseUri = realtime.baseUri(realtime.options.primaryDomain); helper.recordPrivateApi('read.realtime.options'); const pushRecipient = { 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); });