@@ -13,6 +13,7 @@ import { ModularPlugins } from '../client/modularplugins';
1313let agent = 'ably-js/' + version ;
1414
1515type 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
5960const 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-
114109export 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
133164export 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
146176function 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-
175185function 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+
240272export 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,
0 commit comments