Skip to content

Commit 4ab1c98

Browse files
committed
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
1 parent f6fbe35 commit 4ab1c98

File tree

10 files changed

+103
-80
lines changed

10 files changed

+103
-80
lines changed

ably.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,11 @@ export interface ClientOptions<Plugins = CorePlugins> extends AuthOptions {
390390
*/
391391
echoMessages?: boolean;
392392

393+
/**
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+
393398
/**
394399
* Enables a [custom environment](https://ably.com/docs/platform-customization) to be used with the Ably service.
395400
*/

src/common/lib/util/defaults.ts

Lines changed: 38 additions & 42 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;
@@ -40,7 +41,7 @@ type CompleteDefaults = IDefaults & {
4041
getHost(options: ClientOptions, host?: string | null, ws?: boolean): string;
4142
getPort(options: ClientOptions, tls?: boolean): number | undefined;
4243
getHttpScheme(options: ClientOptions): string;
43-
environmentFallbackHosts(environment: 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,
@@ -94,7 +96,7 @@ const Defaults = {
9496
getHost,
9597
getPort,
9698
getHttpScheme,
97-
environmentFallbackHosts,
99+
getEndpointFallbackHosts,
98100
getFallbackHosts,
99101
getHosts,
100102
checkHost,
@@ -119,15 +121,30 @@ export function getHttpScheme(options: ClientOptions): string {
119121
return options.tls ? 'https://' : 'http://';
120122
}
121123

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-
];
124+
export function getEndpointHostname(endpoint: string): string {
125+
if (endpoint.includes('.') || endpoint.includes('::') || endpoint.includes('localhost')) {
126+
return endpoint;
127+
}
128+
129+
if (endpoint.startsWith('nonprod:')) {
130+
const root = endpoint.replace('nonprod:', '');
131+
return `${root}.realtime.ably-nonprod.net`;
132+
}
133+
134+
return `${endpoint}.realtime.ably.net`;
135+
}
136+
137+
export function getEndpointFallbackHosts(endpoint: string): string[] {
138+
if (endpoint.startsWith('nonprod:')) {
139+
const root = endpoint.replace('nonprod:', '');
140+
return endpointFallbacks(root, 'ably-realtime-nonprod.com');
141+
}
142+
143+
return endpointFallbacks(endpoint, 'ably-realtime.com');
144+
}
145+
146+
export function endpointFallbacks(root: string, domain: string): string[] {
147+
return ['a', 'b', 'c', 'd', 'e'].map((id) => `${root}.${id}.fallback.${domain}`);
131148
}
132149

133150
export function getFallbackHosts(options: NormalisedClientOptions): string[] {
@@ -152,26 +169,6 @@ function checkHost(host: string): void {
152169
}
153170
}
154171

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-
175172
function getTimeouts(options: ClientOptions) {
176173
/* Allow values passed in options to override default timeouts */
177174
const timeouts: Record<string, number> = {};
@@ -262,16 +259,15 @@ export function normaliseOptions(
262259

263260
if (!('queueMessages' in options)) options.queueMessages = true;
264261

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';
262+
/* infer hosts and fallbacks based on the specified endpoint */
263+
const endpoint = options.endpoint || options.environment || Defaults.ENDPOINT;
268264

269265
if (!options.fallbackHosts && !options.restHost && !options.realtimeHost && !options.port && !options.tlsPort) {
270-
options.fallbackHosts = production ? Defaults.FALLBACK_HOSTS : environmentFallbackHosts(environment);
266+
options.fallbackHosts = getEndpointFallbackHosts(endpoint);
271267
}
272268

273-
const restHost = options.restHost || (production ? Defaults.REST_HOST : environment + '-' + Defaults.REST_HOST);
274-
const realtimeHost = getRealtimeHost(options, production, environment, loggerToUse);
269+
const restHost = options.restHost || getEndpointHostname(endpoint);
270+
const realtimeHost = options.realtimeHost || getEndpointHostname(endpoint);
275271

276272
(options.fallbackHosts || []).concat(restHost, realtimeHost).forEach(checkHost);
277273

test/common/globals/environment.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
define(function (require) {
44
var defaultLogLevel = 4,
55
environment = isBrowser ? window.__env__ || {} : process.env,
6-
ablyEnvironment = environment.ABLY_ENV || 'sandbox',
6+
ablyEnvironment = environment.ABLY_ENV || 'nonprod:sandbox',
77
realtimeHost = environment.ABLY_REALTIME_HOST,
88
restHost = environment.ABLY_REST_HOST,
99
port = environment.ABLY_PORT || 80,

test/common/modules/testapp_manager.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
/* testapp module is responsible for setting up and tearing down apps in the test environment */
55
define(['globals', 'ably'], function (ablyGlobals, ably) {
6-
var restHost = ablyGlobals.restHost || prefixDomainWithEnvironment('rest.ably.io', ablyGlobals.environment),
6+
var restHost = ablyGlobals.restHost || setHostname(ablyGlobals.environment),
77
port = ablyGlobals.tls ? ablyGlobals.tlsPort : ablyGlobals.port,
88
scheme = ablyGlobals.tls ? 'https' : 'http';
99

@@ -23,12 +23,12 @@ define(['globals', 'ably'], function (ablyGlobals, ably) {
2323
}
2424
}
2525

26-
function prefixDomainWithEnvironment(domain, environment) {
27-
if (environment.toLowerCase() === 'production') {
28-
return domain;
29-
} else {
30-
return environment + '-' + domain;
26+
function setHostname(endpoint) {
27+
if (endpoint.startsWith('nonprod:')) {
28+
return `${endpoint.replace('nonprod:', '')}.realtime.ably-nonprod.net`;
3129
}
30+
31+
return `${endpoint}.realtime.ably.net`;
3232
}
3333

3434
function toBase64(helper, str) {

test/package/browser/template/playwright/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ beforeMount(async ({ App }) => {
99

1010
const client = new Ably.Realtime({
1111
key,
12-
environment: 'sandbox',
12+
environment: 'nonprod:sandbox',
1313
});
1414

1515
return (

test/package/browser/template/src/index-default.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ async function attachChannel(channel: Ably.RealtimeChannel) {
1515
globalThis.testAblyPackage = async function () {
1616
const key = await createSandboxAblyAPIKey();
1717

18-
const realtime = new Ably.Realtime({ key, environment: 'sandbox' });
18+
const realtime = new Ably.Realtime({ key, environment: 'nonprod:sandbox' });
1919

2020
const channel = realtime.channels.get('channel');
2121
await attachChannel(channel);

test/package/browser/template/src/index-modular.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ async function checkStandaloneFunction() {
2424
globalThis.testAblyPackage = async function () {
2525
const key = await createSandboxAblyAPIKey();
2626

27-
const realtime = new BaseRealtime({ key, environment: 'sandbox', plugins: { WebSocketTransport, FetchRequest } });
27+
const realtime = new BaseRealtime({
28+
key,
29+
environment: 'nonprod:sandbox',
30+
plugins: { WebSocketTransport, FetchRequest },
31+
});
2832

2933
const channel = realtime.channels.get('channel');
3034
await attachChannel(channel);

test/package/browser/template/src/sandbox.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export async function createSandboxAblyAPIKey(withOptions?: object) {
55
...testAppSetup.post_apps,
66
...(withOptions ?? {}),
77
};
8-
const response = await fetch('https://sandbox-rest.ably.io/apps', {
8+
const response = await fetch('https://sandbox.realtime.ably-nonprod.net/apps', {
99
method: 'POST',
1010
headers: { 'Content-Type': 'application/json' },
1111
body: JSON.stringify(postData),

test/realtime/connectivity.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, Helper, chai) {
136136
it('succeeds with plain url', function (done) {
137137
const helper = this.test.helper;
138138
Helper.whenPromiseSettles(
139-
helper.AblyRealtime(options(helper, 'sandbox-rest.ably.io/time')).http.checkConnectivity(),
139+
helper.AblyRealtime(options(helper, 'sandbox.realtime.ably-nonprod.net/time')).http.checkConnectivity(),
140140
function (err, res) {
141141
try {
142142
expect(

test/rest/defaults.test.js

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ define(['ably', 'chai'], function (Ably, chai) {
2424
helper.recordPrivateApi('call.Defaults.normaliseOptions');
2525
var normalisedOptions = Defaults.normaliseOptions({}, null, null);
2626

27-
expect(normalisedOptions.restHost).to.equal('rest.ably.io');
28-
expect(normalisedOptions.realtimeHost).to.equal('realtime.ably.io');
27+
expect(normalisedOptions.restHost).to.equal('main.realtime.ably.net');
28+
expect(normalisedOptions.realtimeHost).to.equal('main.realtime.ably.net');
2929
expect(normalisedOptions.port).to.equal(80);
3030
expect(normalisedOptions.tlsPort).to.equal(443);
3131
expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.FALLBACK_HOSTS.sort());
@@ -35,8 +35,10 @@ define(['ably', 'chai'], function (Ably, chai) {
3535
expect(Defaults.getHosts(normalisedOptions).length).to.equal(4);
3636
expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost);
3737
helper.recordPrivateApi('call.Defaults.getHost');
38-
expect(Defaults.getHost(normalisedOptions, 'rest.ably.io', false)).to.deep.equal('rest.ably.io');
39-
expect(Defaults.getHost(normalisedOptions, 'rest.ably.io', true)).to.equal('realtime.ably.io');
38+
expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', false)).to.deep.equal(
39+
'main.realtime.ably.net',
40+
);
41+
expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', true)).to.equal('main.realtime.ably.net');
4042

4143
helper.recordPrivateApi('call.Defaults.getPort');
4244
expect(Defaults.getPort(normalisedOptions)).to.equal(443);
@@ -65,8 +67,8 @@ define(['ably', 'chai'], function (Ably, chai) {
6567
helper.recordPrivateApi('call.Defaults.normaliseOptions');
6668
var normalisedOptions = Defaults.normaliseOptions({ environment: 'production' }, null, null);
6769

68-
expect(normalisedOptions.restHost).to.equal('rest.ably.io');
69-
expect(normalisedOptions.realtimeHost).to.equal('realtime.ably.io');
70+
expect(normalisedOptions.restHost).to.equal('main.realtime.ably.net');
71+
expect(normalisedOptions.realtimeHost).to.equal('main.realtime.ably.net');
7072
expect(normalisedOptions.port).to.equal(80);
7173
expect(normalisedOptions.tlsPort).to.equal(443);
7274
expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.FALLBACK_HOSTS.sort());
@@ -76,8 +78,12 @@ define(['ably', 'chai'], function (Ably, chai) {
7678
expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(4);
7779
expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost);
7880
helper.recordPrivateApi('call.Defaults.getHost');
79-
expect(Defaults.getHost(normalisedOptions, 'rest.ably.io', false)).to.deep.equal('rest.ably.io');
80-
expect(Defaults.getHost(normalisedOptions, 'rest.ably.io', true)).to.deep.equal('realtime.ably.io');
81+
expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', false)).to.deep.equal(
82+
'main.realtime.ably.net',
83+
);
84+
expect(Defaults.getHost(normalisedOptions, 'main.realtime.ably.net', true)).to.deep.equal(
85+
'main.realtime.ably.net',
86+
);
8187

8288
helper.recordPrivateApi('call.Defaults.getPort');
8389
expect(Defaults.getPort(normalisedOptions)).to.equal(443);
@@ -101,22 +107,26 @@ define(['ably', 'chai'], function (Ably, chai) {
101107
const helper = this.test.helper;
102108

103109
helper.recordPrivateApi('call.Defaults.normaliseOptions');
104-
var normalisedOptions = Defaults.normaliseOptions({ environment: 'sandbox' }, null, null);
110+
var normalisedOptions = Defaults.normaliseOptions({ environment: 'nonprod:sandbox' }, null, null);
105111

106-
expect(normalisedOptions.restHost).to.equal('sandbox-rest.ably.io');
107-
expect(normalisedOptions.realtimeHost).to.equal('sandbox-realtime.ably.io');
112+
expect(normalisedOptions.restHost).to.equal('sandbox.realtime.ably-nonprod.net');
113+
expect(normalisedOptions.realtimeHost).to.equal('sandbox.realtime.ably-nonprod.net');
108114
expect(normalisedOptions.port).to.equal(80);
109115
expect(normalisedOptions.tlsPort).to.equal(443);
110-
expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.environmentFallbackHosts('sandbox').sort());
116+
expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(
117+
Defaults.getEndpointFallbackHosts('nonprod:sandbox').sort(),
118+
);
111119
expect(normalisedOptions.tls).to.equal(true);
112120

113121
helper.recordPrivateApi('call.Defaults.getHosts');
114122
expect(Defaults.getHosts(normalisedOptions).length).to.deep.equal(4);
115123
expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost);
116124
helper.recordPrivateApi('call.Defaults.getHost');
117-
expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', false)).to.deep.equal('sandbox-rest.ably.io');
118-
expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', true)).to.deep.equal(
119-
'sandbox-realtime.ably.io',
125+
expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', false)).to.deep.equal(
126+
'sandbox.realtime.ably-nonprod.net',
127+
);
128+
expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', true)).to.deep.equal(
129+
'sandbox.realtime.ably-nonprod.net',
120130
);
121131

122132
helper.recordPrivateApi('call.Defaults.getPort');
@@ -147,8 +157,8 @@ define(['ably', 'chai'], function (Ably, chai) {
147157
null,
148158
);
149159

150-
expect(normalisedOptions.restHost).to.equal('local-rest.ably.io');
151-
expect(normalisedOptions.realtimeHost).to.equal('local-realtime.ably.io');
160+
expect(normalisedOptions.restHost).to.equal('local.realtime.ably.net');
161+
expect(normalisedOptions.realtimeHost).to.equal('local.realtime.ably.net');
152162
expect(normalisedOptions.port).to.equal(8080);
153163
expect(normalisedOptions.tlsPort).to.equal(8081);
154164
expect(normalisedOptions.fallbackHosts).to.equal(undefined);
@@ -157,8 +167,12 @@ define(['ably', 'chai'], function (Ably, chai) {
157167
helper.recordPrivateApi('call.Defaults.getHosts');
158168
expect(Defaults.getHosts(normalisedOptions)).to.deep.equal([normalisedOptions.restHost]);
159169
helper.recordPrivateApi('call.Defaults.getHost');
160-
expect(Defaults.getHost(normalisedOptions, 'local-rest.ably.io', false)).to.deep.equal('local-rest.ably.io');
161-
expect(Defaults.getHost(normalisedOptions, 'local-rest.ably.io', true)).to.deep.equal('local-realtime.ably.io');
170+
expect(Defaults.getHost(normalisedOptions, 'local.realtime.ably.net', false)).to.deep.equal(
171+
'local.realtime.ably.net',
172+
);
173+
expect(Defaults.getHost(normalisedOptions, 'local.realtime.ably.net', true)).to.deep.equal(
174+
'local.realtime.ably.net',
175+
);
162176

163177
helper.recordPrivateApi('call.Defaults.getPort');
164178
expect(Defaults.getPort(normalisedOptions)).to.equal(8081);
@@ -255,24 +269,28 @@ define(['ably', 'chai'], function (Ably, chai) {
255269
const helper = this.test.helper;
256270

257271
helper.recordPrivateApi('write.Defaults.ENVIRONMENT');
258-
Defaults.ENVIRONMENT = 'sandbox';
272+
Defaults.ENVIRONMENT = 'nonprod:sandbox';
259273
helper.recordPrivateApi('call.Defaults.normaliseOptions');
260274
var normalisedOptions = Defaults.normaliseOptions({}, null, null);
261275

262-
expect(normalisedOptions.restHost).to.equal('sandbox-rest.ably.io');
263-
expect(normalisedOptions.realtimeHost).to.equal('sandbox-realtime.ably.io');
276+
expect(normalisedOptions.restHost).to.equal('sandbox.realtime.ably-nonprod.net');
277+
expect(normalisedOptions.realtimeHost).to.equal('sandbox.realtime.ably-nonprod.net');
264278
expect(normalisedOptions.port).to.equal(80);
265279
expect(normalisedOptions.tlsPort).to.equal(443);
266-
expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(Defaults.environmentFallbackHosts('sandbox').sort());
280+
expect(normalisedOptions.fallbackHosts.sort()).to.deep.equal(
281+
Defaults.getEndpointFallbackHosts('nonprod:sandbox').sort(),
282+
);
267283
expect(normalisedOptions.tls).to.equal(true);
268284

269285
helper.recordPrivateApi('call.Defaults.getHosts');
270286
expect(Defaults.getHosts(normalisedOptions).length).to.equal(4);
271287
expect(Defaults.getHosts(normalisedOptions)[0]).to.deep.equal(normalisedOptions.restHost);
272288
helper.recordPrivateApi('call.Defaults.getHost');
273-
expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', false)).to.deep.equal('sandbox-rest.ably.io');
274-
expect(Defaults.getHost(normalisedOptions, 'sandbox-rest.ably.io', true)).to.deep.equal(
275-
'sandbox-realtime.ably.io',
289+
expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', false)).to.deep.equal(
290+
'sandbox.realtime.ably-nonprod.net',
291+
);
292+
expect(Defaults.getHost(normalisedOptions, 'sandbox.realtime.ably-nonprod.net', true)).to.deep.equal(
293+
'sandbox.realtime.ably-nonprod.net',
276294
);
277295

278296
helper.recordPrivateApi('call.Defaults.getPort');

0 commit comments

Comments
 (0)