Skip to content

Commit cce1bda

Browse files
authored
Merge 2989879 into db99519
2 parents db99519 + 2989879 commit cce1bda

30 files changed

+394
-245
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"request": "launch",
1010
"name": "Debug Test",
1111
"env": {
12-
"ABLY_ENV": "sandbox"
12+
"ABLY_ENDPOINT": "nonprod:sandbox"
1313
},
1414
"cwd": "${workspaceFolder}",
1515
"runtimeExecutable": "node",

CONTRIBUTING.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,7 @@ Run the following command to fix linting/formatting issues
139139

140140
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.
141141

142-
- `ABLY_ENV` - defaults to sandbox, however this can be set to another known environment such as 'staging'
143-
- `ABLY_REALTIME_HOST` - explicitly tell the client library to use an alternate host for real-time websocket communication.
144-
- `ABLY_REST_HOST` - explicitly tell the client library to use an alternate host for REST communication.
142+
- `ABLY_ENDPOINT` - defaults to nonprod:sandbox, however this can be set to another known prod / nonprod routing policy id or primary domain
145143
- `ABLY_PORT` - non-TLS port to use for the tests, defaults to 80
146144
- `ABLY_TLS_PORT` - TLS port to use for the tests, defaults to 443
147145
- `ABLY_USE_TLS` - true or false to enable/disable use of TLS respectively

ably.d.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,12 @@ export interface ClientOptions<Plugins = CorePlugins> extends AuthOptions {
391391
echoMessages?: boolean;
392392

393393
/**
394-
* Enables a [custom environment](https://ably.com/docs/platform-customization) to be used with the Ably service.
394+
* Set a routing policy or FQDN to connect to Ably. See [platform customization](https://ably.com/docs/platform-customization).
395+
*/
396+
endpoint?: string;
397+
398+
/**
399+
* @deprecated This property is deprecated and will be removed in a future version. Use the {@link ClientOptions.endpoint} client option instead.
395400
*/
396401
environment?: string;
397402

@@ -423,16 +428,12 @@ export interface ClientOptions<Plugins = CorePlugins> extends AuthOptions {
423428
queueMessages?: boolean;
424429

425430
/**
426-
* Enables a non-default Ably host to be specified. For development environments only. The default value is `rest.ably.io`.
427-
*
428-
* @defaultValue `"rest.ably.io"`
431+
* @deprecated This property is deprecated and will be removed in a future version. Use the {@link ClientOptions.endpoint} client option instead.
429432
*/
430433
restHost?: string;
431434

432435
/**
433-
* Enables a non-default Ably host to be specified for realtime connections. For development environments only. The default value is `realtime.ably.io`.
434-
*
435-
* @defaultValue `"realtime.ably.io"`
436+
* @deprecated This property is deprecated and will be removed in a future version. Use the {@link ClientOptions.endpoint} client option instead.
436437
*/
437438
realtimeHost?: string;
438439

scripts/moduleReport.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { gzip } from 'zlib';
66
import Table from 'cli-table';
77

88
// The maximum size we allow for a minimal useful Realtime bundle (i.e. one that can subscribe to a channel)
9-
const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 102, gzip: 31 };
9+
const minimalUsefulRealtimeBundleSizeThresholdsKiB = { raw: 103, gzip: 31 };
1010

1111
const baseClientNames = ['BaseRest', 'BaseRealtime'];
1212

src/common/lib/transport/comettransport.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ abstract class CometTransport extends Transport {
7777
Transport.prototype.connect.call(this);
7878
const params = this.params;
7979
const options = params.options;
80-
const host = Defaults.getHost(options, params.host);
80+
const host = params.host || options.primaryDomain;
8181
const port = Defaults.getPort(options);
8282
const cometScheme = options.tls ? 'https://' : 'http://';
8383

src/common/lib/transport/connectionmanager.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,7 @@ class ConnectionManager extends EventEmitter {
185185
baseTransport?: TransportName;
186186
webSocketTransportAvailable?: true;
187187
transportPreference: string | null;
188-
httpHosts: string[];
189-
wsHosts: string[];
188+
domains: string[];
190189
activeProtocol: null | Protocol;
191190
pendingTransport?: Transport;
192191
proposedTransport?: Transport;
@@ -293,8 +292,7 @@ class ConnectionManager extends EventEmitter {
293292
this.baseTransport = TransportNames.Comet;
294293
}
295294

296-
this.httpHosts = Defaults.getHosts(options);
297-
this.wsHosts = Defaults.getHosts(options, true);
295+
this.domains = Defaults.getHosts(options);
298296
this.activeProtocol = null;
299297
this.host = null;
300298
this.lastAutoReconnectAttempt = null;
@@ -323,7 +321,7 @@ class ConnectionManager extends EventEmitter {
323321
this.logger,
324322
Logger.LOG_MICRO,
325323
'Realtime.ConnectionManager()',
326-
'http hosts = [' + this.httpHosts + ']',
324+
'http domains = [' + this.domains + ']',
327325
);
328326

329327
if (!this.transports.length) {
@@ -835,7 +833,7 @@ class ConnectionManager extends EventEmitter {
835833
* setting an instance variable to force fallback hosts to be used (if
836834
* any) here. Bit of a kludge, but no real better alternatives without
837835
* rewriting the entire thing */
838-
if (state === 'disconnected' && error && (error.statusCode as number) > 500 && this.httpHosts.length > 1) {
836+
if (state === 'disconnected' && error && (error.statusCode as number) > 500 && this.domains.length > 1) {
839837
this.unpersistTransportPreference();
840838
this.forceFallbackHost = true;
841839
/* 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 {
15331531
this.notifyState({ state: this.states.connecting.failState as string, error: err });
15341532
};
15351533

1536-
const candidateHosts = ws ? this.wsHosts.slice() : this.httpHosts.slice();
1534+
const candidateHosts = this.domains.slice();
15371535

15381536
const hostAttemptCb = (fatal: boolean, transport: Transport) => {
15391537
if (connectCount !== this.connectCounter) {

src/common/lib/util/defaults.ts

Lines changed: 91 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ModularPlugins } from '../client/modularplugins';
1313
let agent = 'ably-js/' + version;
1414

1515
type CompleteDefaults = IDefaults & {
16+
ENDPOINT: string;
1617
ENVIRONMENT: string;
1718
REST_HOST: string;
1819
REALTIME_HOST: string;
@@ -37,10 +38,10 @@ type CompleteDefaults = IDefaults & {
3738
version: string;
3839
protocolVersion: number;
3940
agent: string;
40-
getHost(options: ClientOptions, host?: string | null, ws?: boolean): string;
4141
getPort(options: ClientOptions, tls?: boolean): number | undefined;
4242
getHttpScheme(options: ClientOptions): string;
43-
environmentFallbackHosts(environment: string): string[];
43+
getPrimaryDomainFromEndpoint(endpoint: string): string;
44+
getEndpointFallbackHosts(endpoint: string): string[];
4445
getFallbackHosts(options: NormalisedClientOptions): string[];
4546
getHosts(options: NormalisedClientOptions, ws?: boolean): string[];
4647
checkHost(host: string): void;
@@ -57,15 +58,16 @@ type CompleteDefaults = IDefaults & {
5758
};
5859

5960
const Defaults = {
61+
ENDPOINT: 'main',
6062
ENVIRONMENT: '',
6163
REST_HOST: 'rest.ably.io',
6264
REALTIME_HOST: 'realtime.ably.io',
6365
FALLBACK_HOSTS: [
64-
'A.ably-realtime.com',
65-
'B.ably-realtime.com',
66-
'C.ably-realtime.com',
67-
'D.ably-realtime.com',
68-
'E.ably-realtime.com',
66+
'main.a.fallback.ably-realtime.com',
67+
'main.b.fallback.ably-realtime.com',
68+
'main.c.fallback.ably-realtime.com',
69+
'main.d.fallback.ably-realtime.com',
70+
'main.e.fallback.ably-realtime.com',
6971
],
7072
PORT: 80,
7173
TLS_PORT: 443,
@@ -91,10 +93,10 @@ const Defaults = {
9193
version,
9294
protocolVersion: 3,
9395
agent,
94-
getHost,
9596
getPort,
9697
getHttpScheme,
97-
environmentFallbackHosts,
98+
getPrimaryDomainFromEndpoint,
99+
getEndpointFallbackHosts,
98100
getFallbackHosts,
99101
getHosts,
100102
checkHost,
@@ -104,13 +106,6 @@ const Defaults = {
104106
defaultPostHeaders,
105107
};
106108

107-
export function getHost(options: ClientOptions, host?: string | null, ws?: boolean): string {
108-
if (ws) host = (host == options.restHost && options.realtimeHost) || host || options.realtimeHost;
109-
else host = host || options.restHost;
110-
111-
return host as string;
112-
}
113-
114109
export function getPort(options: ClientOptions, tls?: boolean): number | undefined {
115110
return tls || options.tls ? options.tlsPort : options.port;
116111
}
@@ -119,15 +114,51 @@ export function getHttpScheme(options: ClientOptions): string {
119114
return options.tls ? 'https://' : 'http://';
120115
}
121116

122-
// construct environment fallback hosts as per RSC15i
123-
export function environmentFallbackHosts(environment: string): string[] {
124-
return [
125-
environment + '-a-fallback.ably-realtime.com',
126-
environment + '-b-fallback.ably-realtime.com',
127-
environment + '-c-fallback.ably-realtime.com',
128-
environment + '-d-fallback.ably-realtime.com',
129-
environment + '-e-fallback.ably-realtime.com',
130-
];
117+
/**
118+
* REC1b2
119+
*/
120+
function isFqdnIpOrLocalhost(endpoint: string): boolean {
121+
return endpoint.includes('.') || endpoint.includes('::') || endpoint === 'localhost';
122+
}
123+
124+
/**
125+
* REC1b
126+
*/
127+
export function getPrimaryDomainFromEndpoint(endpoint: string): string {
128+
// REC1b2 (endpoint is a valid hostname)
129+
if (isFqdnIpOrLocalhost(endpoint)) return endpoint;
130+
131+
// REC1b3 (endpoint in form "nonprod:[id]")
132+
if (endpoint.startsWith('nonprod:')) {
133+
const routingPolicyId = endpoint.replace('nonprod:', '');
134+
return `${routingPolicyId}.realtime.ably-nonprod.net`;
135+
}
136+
137+
// REC1b4 (endpoint in form "[id]")
138+
return `${endpoint}.realtime.ably.net`;
139+
}
140+
141+
/**
142+
* REC2c
143+
*
144+
* @returns default callbacks based on endpoint client option
145+
*/
146+
export function getEndpointFallbackHosts(endpoint: string): string[] {
147+
// REC2c2
148+
if (isFqdnIpOrLocalhost(endpoint)) return [];
149+
150+
// REC2c3
151+
if (endpoint.startsWith('nonprod:')) {
152+
const routingPolicyId = endpoint.replace('nonprod:', '');
153+
return endpointFallbacks(routingPolicyId, 'ably-realtime-nonprod.com');
154+
}
155+
156+
// REC2c1
157+
return endpointFallbacks(endpoint, 'ably-realtime.com');
158+
}
159+
160+
export function endpointFallbacks(routingPolicyId: string, domain: string): string[] {
161+
return ['a', 'b', 'c', 'd', 'e'].map((id) => `${routingPolicyId}.${id}.fallback.${domain}`);
131162
}
132163

133164
export function getFallbackHosts(options: NormalisedClientOptions): string[] {
@@ -138,9 +169,8 @@ export function getFallbackHosts(options: NormalisedClientOptions): string[] {
138169
return fallbackHosts ? Utils.arrChooseN(fallbackHosts, httpMaxRetryCount) : [];
139170
}
140171

141-
export function getHosts(options: NormalisedClientOptions, ws?: boolean): string[] {
142-
const hosts = [options.restHost].concat(getFallbackHosts(options));
143-
return ws ? hosts.map((host) => getHost(options, host, true)) : hosts;
172+
export function getHosts(options: NormalisedClientOptions): string[] {
173+
return [options.primaryDomain].concat(getFallbackHosts(options));
144174
}
145175

146176
function checkHost(host: string): void {
@@ -152,26 +182,6 @@ function checkHost(host: string): void {
152182
}
153183
}
154184

155-
function getRealtimeHost(options: ClientOptions, production: boolean, environment: string, logger: Logger): string {
156-
if (options.realtimeHost) return options.realtimeHost;
157-
/* prefer setting realtimeHost to restHost as a custom restHost typically indicates
158-
* a development environment is being used that can't be inferred by the library */
159-
if (options.restHost) {
160-
Logger.logAction(
161-
logger,
162-
Logger.LOG_MINOR,
163-
'Defaults.normaliseOptions',
164-
'restHost is set to "' +
165-
options.restHost +
166-
'" but realtimeHost is not set, so setting realtimeHost to "' +
167-
options.restHost +
168-
'" too. If this is not what you want, please set realtimeHost explicitly.',
169-
);
170-
return options.restHost;
171-
}
172-
return production ? Defaults.REALTIME_HOST : environment + '-' + Defaults.REALTIME_HOST;
173-
}
174-
175185
function getTimeouts(options: ClientOptions) {
176186
/* Allow values passed in options to override default timeouts */
177187
const timeouts: Record<string, number> = {};
@@ -237,11 +247,35 @@ export function objectifyOptions(
237247
return optionsObj;
238248
}
239249

250+
function checkIfClientOptionsAreValid(options: ClientOptions) {
251+
// REC1b
252+
if (options.endpoint && (options.environment || options.restHost || options.realtimeHost)) {
253+
// RSC1b
254+
throw new ErrorInfo(
255+
'The `endpoint` option cannot be used in conjunction with the `environment`, `restHost`, or `realtimeHost` options.',
256+
40106,
257+
400,
258+
);
259+
}
260+
261+
// REC1c
262+
if (options.environment && (options.restHost || options.realtimeHost)) {
263+
// RSC1b
264+
throw new ErrorInfo(
265+
'The `environment` option cannot be used in conjunction with the `restHost`, or `realtimeHost` options.',
266+
40106,
267+
400,
268+
);
269+
}
270+
}
271+
240272
export function normaliseOptions(
241273
options: ClientOptions,
242274
MsgPack: MsgPack | null,
243275
logger: Logger | null, // should only be omitted by tests
244276
): NormalisedClientOptions {
277+
checkIfClientOptionsAreValid(options);
278+
245279
const loggerToUse = logger ?? Logger.defaultLogger;
246280

247281
if (typeof options.recover === 'function' && options.closeOnUnload === true) {
@@ -262,18 +296,19 @@ export function normaliseOptions(
262296

263297
if (!('queueMessages' in options)) options.queueMessages = true;
264298

265-
/* infer hosts and fallbacks based on the configured environment */
266-
const environment = (options.environment && String(options.environment).toLowerCase()) || Defaults.ENVIRONMENT;
267-
const production = !environment || environment === 'production';
299+
/* infer hosts and fallbacks based on the specified endpoint */
300+
const endpoint = options.endpoint || Defaults.ENDPOINT;
268301

269302
if (!options.fallbackHosts && !options.restHost && !options.realtimeHost && !options.port && !options.tlsPort) {
270-
options.fallbackHosts = production ? Defaults.FALLBACK_HOSTS : environmentFallbackHosts(environment);
303+
options.fallbackHosts = getEndpointFallbackHosts(options.environment || endpoint);
271304
}
272305

273-
const restHost = options.restHost || (production ? Defaults.REST_HOST : environment + '-' + Defaults.REST_HOST);
274-
const realtimeHost = getRealtimeHost(options, production, environment, loggerToUse);
306+
const primaryDomainFromEnvironment = options.environment && `${options.environment}.realtime.ably.net`;
307+
const primaryDomainFromLegacyOptions = options.restHost || options.realtimeHost || primaryDomainFromEnvironment;
308+
309+
const primaryDomain = primaryDomainFromLegacyOptions || getPrimaryDomainFromEndpoint(endpoint);
275310

276-
(options.fallbackHosts || []).concat(restHost, realtimeHost).forEach(checkHost);
311+
(options.fallbackHosts || []).concat(primaryDomain).forEach(checkHost);
277312

278313
options.port = options.port || Defaults.PORT;
279314
options.tlsPort = options.tlsPort || Defaults.TLS_PORT;
@@ -318,8 +353,7 @@ export function normaliseOptions(
318353

319354
return {
320355
...options,
321-
realtimeHost,
322-
restHost,
356+
primaryDomain: primaryDomain,
323357
maxMessageSize: options.maxMessageSize || Defaults.maxMessageSize,
324358
timeouts,
325359
connectivityCheckParams,

src/common/types/ClientOptions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ export default interface ClientOptions extends API.ClientOptions<API.CorePlugins
1717
export type NormalisedClientOptions = Modify<
1818
ClientOptions,
1919
{
20-
realtimeHost: string;
21-
restHost: string;
20+
primaryDomain: string;
2221
keyName?: string;
2322
keySecret?: string;
2423
timeouts: Record<string, number>;

0 commit comments

Comments
 (0)