Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions .claude/skills/uts-to-kotlin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -676,13 +676,12 @@ Recognise a **proxy** spec by a reference to `create_proxy_session()`, proxy `ru

A **direct-sandbox** spec (no `create_proxy_session`, no rules — just happy-path interop against `nonprod:sandbox`) uses the same `SandboxApp` provisioning and the same `runTest` / `@BeforeAll`+`runBlocking` lifecycle as a proxy test, but **drops all proxy wiring**: no `ProxyManager.ensureProxy()`, no `ProxySession`, no `connectThroughProxy`. The client connects straight to the sandbox host. `ChannelHistoryTest` (realtime) and `ObjectsLifecycleTest` (liveobjects) are the reference examples — read one before translating a direct-sandbox spec.

**Client wiring** — point both transports at the sandbox host (explicit hosts auto-disable fallback hosts, so no `fallbackHosts`):
**Client wiring** — point the client at the sandbox endpoint:

```kotlin
private fun newClient(useBinaryProtocol: Boolean): AblyRealtime = TestRealtimeClient {
key = app.defaultKey
realtimeHost = SandboxApp.sandboxHost // sandbox.realtime.ably-nonprod.net
restHost = SandboxApp.sandboxHost
endpoint = SandboxApp.sandboxEndpoint // nonprod:sandbox
this.useBinaryProtocol = useBinaryProtocol
autoConnect = false
}
Expand Down Expand Up @@ -728,7 +727,7 @@ Three helpers live under `uts/src/main/kotlin/io/ably/lib/uts/infra/integration/

- **`ProxyManager`** (`infra/integration/proxy/ProxyManager.kt`, package `io.ably.lib.uts.infra.integration.proxy`) — downloads/starts the shared `uts-proxy` process. Call `ProxyManager.ensureProxy()` once per suite in setup.
- **`ProxySession`** (`infra/integration/proxy/ProxySession.kt`, same package) — one programmable session wrapping the proxy control API; also defines the `connectThroughProxy` extension and the rule-builder helpers.
- **`SandboxApp`** (`infra/integration/SandboxApp.kt`, package `io.ably.lib.uts.infra.integration`) — provisions/deletes a sandbox test app from the shared `test-app-setup.json` in ably-common. `SandboxApp.create()` returns a `SandboxApp` with `appId`, `defaultKey`, and `keys` (`defaultKey` is a full-capability `appId.keyId:keySecret`); `app.delete()` tears it down. Provision in suite setup, delete in teardown. Also owns the single upstream sandbox host constant `SandboxApp.sandboxHost` (`sandbox.realtime.ably-nonprod.net`, the resolved `nonprod:sandbox` endpoint) — the default target of every `ProxySession` (both `realtimeHost` and `restHost`), and what direct-sandbox clients set `realtimeHost` / `restHost` from.
- **`SandboxApp`** (`infra/integration/SandboxApp.kt`, package `io.ably.lib.uts.infra.integration`) — provisions/deletes a sandbox test app from the shared `test-app-setup.json` in ably-common. `SandboxApp.create()` returns a `SandboxApp` with `appId`, `defaultKey`, and `keys` (`defaultKey` is a full-capability `appId.keyId:keySecret`); `app.delete()` tears it down. Provision in suite setup, delete in teardown. Also owns `SandboxApp.sandboxEndpoint` (`nonprod:sandbox`), which direct-sandbox clients set as their `endpoint`, and `SandboxApp.sandboxHost` (`sandbox.realtime.ably-nonprod.net`, the hostname that endpoint resolves to) — the default upstream target of every `ProxySession`.

Import what the tier needs: a **direct-sandbox** test imports `io.ably.lib.uts.infra.integration.SandboxApp` plus `io.ably.lib.uts.infra.unit.TestRealtimeClient` and `io.ably.lib.uts.infra.{awaitState, pollUntil}`; a **proxy** test additionally imports `io.ably.lib.uts.infra.integration.proxy.{ProxyManager, ProxySession, connectThroughProxy}`.

Expand Down Expand Up @@ -799,11 +798,11 @@ val client = TestRealtimeClient {
}
```

ably-java has **no `endpoint` ClientOptions field**; `connectThroughProxy` sets the discrete host fields for you:
`connectThroughProxy` sets the proxy-def options for you:

| Proxy-def option | What `connectThroughProxy` sets |
|---|---|
| `endpoint: "localhost"` | `realtimeHost` **and** `restHost` = `session.proxyHost` (`"localhost"`) |
| `endpoint: "localhost"` | `endpoint = session.proxyHost` (`"localhost"`) |
| `port: proxy_port` | `port = session.proxyPort` |
| `tls: false` | `tls = false` |
| `useBinaryProtocol: false` | already the `ClientOptionsBuilder` default — left untouched |
Expand Down
7 changes: 3 additions & 4 deletions .claude/skills/uts-to-kotlin/references/objects-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -623,10 +623,9 @@ REST provisioning" convention survives even though the module's internals are vi
> envelope on the legacy `sandbox-rest.ably.io` host; both were aligned upstream — to this V2 shape and to
> the canonical nonprod sandbox host `sandbox.realtime.ably-nonprod.net` — in ably/specification#497.
>
> **Sandbox host.** `provisionObjectsViaRest` sets `restHost = SandboxApp.sandboxHost`
> (`sandbox.realtime.ably-nonprod.net`) — the same nonprod host `SandboxApp` and the realtime clients use,
> **not** `environment="sandbox"` (which resolves to the legacy `sandbox-rest.ably.io`, and
> can't be combined with `restHost` per `Hosts.java` TO3k2/TO3k3). The REST call hits the live sandbox
> **Sandbox host.** `provisionObjectsViaRest` sets `endpoint = SandboxApp.sandboxEndpoint`
> (`nonprod:sandbox`, resolving to `sandbox.realtime.ably-nonprod.net`) — the same endpoint `SandboxApp`
> and the realtime clients use. The REST call hits the live sandbox
> today, and the realtime client observes the provisioned data through the SDK's OBJECT_SYNC +
> `RealtimeObject.get()` (both implemented).

Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,11 @@ for the JRE-specific library:

./gradlew core:testRealtimeSuite

To run tests against a specific host, specify in the environment:
To run tests against a specific endpoint, specify it in the environment:

env ABLY_ENV=staging ./gradlew testRealtimeSuite
env ABLY_ENDPOINT=nonprod:staging ./gradlew testRealtimeSuite

Tests will run against the sandbox environment by default.
Tests will run against the `nonprod:sandbox` endpoint by default.

Tests can be run on the Android-specific library. An Android device must be connected,
either a real device or the Android emulator.
Expand Down
50 changes: 50 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,53 @@ find src -name '*.java' -o -name '*.kt' | xargs sed -i \
| `TokenDetails.fromJSON(…)`, `TokenRequest.fromJSON(…)` | `fromJsonElement(…)` |
| `ClientOptions.fallbackHostsUseDefault` | Drop it. Default fallback hosts apply automatically; set `fallbackHosts` only for custom hosts. |
| `RegistrationToken.Type.GCM` (Android) | `RegistrationToken.Type.FCM` |

## 5. Choose the host with `endpoint`

2.0 replaces `ClientOptions.environment`, `restHost` and `realtimeHost` with a single
`ClientOptions.endpoint` option, and the builder methods `environment(…)`, `restHost(…)` and
`realtimeHost(…)` with `endpoint(…)`. REST requests and the realtime connection now always use the
same host.

| 1.x | 2.0 |
|-----|-----|
| No host options | No change; traffic moves to `main.realtime.ably.net` (see below) |
| `environment = "sandbox"` | `endpoint = "nonprod:sandbox"` |
| `environment = "acme"` (dedicated cluster) | `endpoint = "acme"` |
| `restHost` / `realtimeHost = "localhost"` | `endpoint = "localhost"` |
| `restHost` and `realtimeHost` set to the same custom host | `endpoint = "<that host>"` |
| `restHost` and `realtimeHost` set to different hosts | Not supported; use one host that serves both, or contact Ably |
| Custom `fallbackHosts` | Unchanged |

How `endpoint` resolves:

| `endpoint` | Primary host | Default fallback hosts |
|------------|--------------|------------------------|
| Unset | `main.realtime.ably.net` | `main.[a-e].fallback.ably-realtime.com` |
| A routing policy name, e.g. `acme` | `acme.realtime.ably.net` | `acme.[a-e].fallback.ably-realtime.com` |
| `nonprod:` plus a name, e.g. `nonprod:sandbox` | `sandbox.realtime.ably-nonprod.net` | `sandbox.[a-e].fallback.ably-realtime-nonprod.com` |
| A hostname (contains `.` or `::`, or is `localhost`) | The value as given | None |

An explicit `fallbackHosts` always replaces the default fallback hosts. A custom `port` or `tlsPort`
no longer disables the default fallback hosts.

**Before (1.x):**

```java
ClientOptions options = new ClientOptions("xVLyHw.MHOCLg:...");
options.environment = "sandbox";
AblyRealtime realtime = new AblyRealtime(options);
```

**After (2.0):**

```java
PubSubRealtimeClient realtime = PubSubServer.realtimeClientBuilder()
.key("xVLyHw.MHOCLg:...")
.endpoint("nonprod:sandbox")
.build();
```

**Firewalls and proxies.** 1.x connected to `rest.ably.io`, `realtime.ably.io` and
`[a-e].ably-realtime.com` by default. If your network restricts outbound traffic, allow
`*.realtime.ably.net` and `*.fallback.ably-realtime.com` before upgrading.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void android_http_header_test() {
ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr);
opts.tls = false;
opts.port = server.getListeningPort();
opts.restHost = "localhost";
opts.endpoint = "localhost";
PubSubHttpClient ably = HttpClientFactory.create(opts);

ably.time();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ PubSubHttpClient getAbly() throws AblyException {
throw AblyException.fromErrorInfo(new ErrorInfo("Unable to get Ably library instance; no device identity token", 40000, 400));
}
Log.v(TAG, "getAbly(): returning Ably instance using deviceIdentityToken");
// TODO: We need to persist Ably client options such as the environment with `deviceIdentityToken` and use these options during initialization.
// TODO: We need to persist Ably client options such as the endpoint with `deviceIdentityToken` and use these options during initialization.
return (ably = HttpClientFactory.create(new ClientOptions(deviceIdentityToken)));
}

Expand Down
22 changes: 3 additions & 19 deletions device/src/commonMain/kotlin/io/ably/pubsub/device/PubSubDevice.kt
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,12 @@ public object PubSubDevice {
public fun headers(headers: Map<String, String>): ClientBuilder = apply { options.headers = headers }

/**
* Sets [ClientOptions.restHost].
* Sets [ClientOptions.endpoint].
*
* @param restHost the value to set.
* @param endpoint the value to set.
* @return this builder.
*/
public fun restHost(restHost: String): ClientBuilder = apply { options.restHost = restHost }
public fun endpoint(endpoint: String): ClientBuilder = apply { options.endpoint = endpoint }

/**
* Sets [ClientOptions.port].
Expand Down Expand Up @@ -232,14 +232,6 @@ public object PubSubDevice {
*/
public fun proxy(proxy: ProxyOptions): ClientBuilder = apply { options.proxy = proxy }

/**
* Sets [ClientOptions.environment].
*
* @param environment the value to set.
* @return this builder.
*/
public fun environment(environment: String): ClientBuilder = apply { options.environment = environment }

/**
* Sets [ClientOptions.idempotentRestPublishing].
*
Expand Down Expand Up @@ -344,14 +336,6 @@ public object PubSubDevice {
*/
public fun agents(agents: Map<String, String?>): ClientBuilder = apply { options.agents = agents }

/**
* Sets [ClientOptions.realtimeHost].
*
* @param realtimeHost the value to set.
* @return this builder.
*/
public fun realtimeHost(realtimeHost: String): ClientBuilder = apply { options.realtimeHost = realtimeHost }

/**
* Sets [ClientOptions.autoConnect].
*
Expand Down
4 changes: 2 additions & 2 deletions examples/src/main/kotlin/com/ably/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ class MainActivity : ComponentActivity() {
// A throwaway client that only signs a token; it never connects.
PubSubDevice.clientBuilder()
.key(apiKey)
.environment("sandbox")
.endpoint("nonprod:sandbox")
.autoConnect(false)
.build()
.use { it.auth.requestToken(null, null) }
}
)
environment("sandbox")
endpoint("nonprod:sandbox")
} else {
key(BuildConfig.ABLY_KEY)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import io.ably.pubsub.realtime.PubSubRealtimeClient
@Composable
fun MainScreen(realtimeClient: PubSubRealtimeClient) {
var selectedTab by remember { mutableIntStateOf(0) }
val isSandbox = realtimeClient.options.environment == "sandbox"
val isSandbox = realtimeClient.options.endpoint == "nonprod:sandbox"

val tabs = listOf(
TabItem("Color Voting", Icons.Default.Favorite),
Expand Down
4 changes: 1 addition & 3 deletions lib/src/main/java/io/ably/pubsub/debug/DebugOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ public DebugOptions copy() {
copied.logLevel = logLevel;
copied.logHandler = logHandler;
copied.tls = tls;
copied.restHost = restHost;
copied.realtimeHost = realtimeHost;
copied.endpoint = endpoint;
copied.port = port;
copied.tlsPort = tlsPort;
copied.autoConnect = autoConnect;
Expand All @@ -60,7 +59,6 @@ public DebugOptions copy() {
copied.echoMessages = echoMessages;
copied.recover = recover;
copied.proxy = proxy;
copied.environment = environment;
copied.idempotentRestPublishing = idempotentRestPublishing;
copied.httpOpenTimeout = httpOpenTimeout;
copied.httpRequestTimeout = httpRequestTimeout;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/main/java/io/ably/pubsub/http/HttpCore.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public HttpCore(ClientOptions options, Auth auth, PlatformAgentProvider platform
this.platformAgentProvider = platformAgentProvider;
this.scheme = options.tls ? "https://" : "http://";
this.port = Defaults.getPort(options);
this.hosts = new Hosts(options.restHost, Defaults.HOST_REST, options);
this.hosts = new Hosts(options);
ProxyOptions proxyOptions = options.proxy;
if (proxyOptions != null) {
String proxyHost = proxyOptions.host;
Expand Down
13 changes: 12 additions & 1 deletion lib/src/main/java/io/ably/pubsub/http/HttpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,20 @@ public static String encodeURIComponent(String str) {
return builder.toString();
}

/**
* Formats a host for the authority component of a URL, bracketing an IPv6 literal
* (RFC 3986 section 3.2.2) so that its colons are not mistaken for the port separator.
*
* @param host a hostname, IPv4 address, or IPv6 address, bracketed or not
* @return the host, safe to place between a scheme and a port
*/
public static String hostForUrl(String host) {
return host.indexOf(':') >= 0 && !host.startsWith("[") ? '[' + host + ']' : host;
}

static URL buildURL(String scheme, String host, int port, String path, Param[] params) {
StringBuilder builder = new StringBuilder(scheme)
.append(host)
.append(hostForUrl(host))
.append(':')
.append(port)
.append(HttpUtils.encodeParams(path, params));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ public ConnectionManager(final PubSubRealtimeClient ably, final Connection conne
this.liveObjectsPlugin = liveObjectsPlugin;

ClientOptions options = ably.options;
this.hosts = new Hosts(options.realtimeHost, Defaults.HOST_REALTIME, options);
this.hosts = new Hosts(options);

/* debug options */
ITransport.Factory transportFactory = null;
Expand Down
75 changes: 65 additions & 10 deletions lib/src/main/java/io/ably/pubsub/transport/Defaults.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ public class Defaults {
public static final String ABLY_AGENT_HEADER = "Ably-Agent";

/* Hosts */
public static final String[] HOST_FALLBACKS = { "A.ably-realtime.com", "B.ably-realtime.com", "C.ably-realtime.com", "D.ably-realtime.com", "E.ably-realtime.com" };
public static final String HOST_REST = "rest.ably.io";
public static final String HOST_REALTIME = "realtime.ably.io";
/* REC1a: the endpoint used when ClientOptions#endpoint is unset */
public static final String ENDPOINT = "main";
/* REC2c1: the fallback domains for the default endpoint */
public static final String[] HOST_FALLBACKS = getEndpointFallbackHosts(ENDPOINT);
public static final int PORT = 80;
public static final int TLS_PORT = 443;

Expand Down Expand Up @@ -74,14 +75,68 @@ public static int getPort(ClientOptions options) {
: ((options.port != 0) ? options.port : Defaults.PORT);
}

/* Construct environment fallback hosts as per RSC15i */
public static String[] getEnvironmentFallbackHosts(String environment) {
/**
* Resolves the primary domain that both REST requests and realtime connections use.
* <p>
* Spec: REC1
*
* @param endpoint the configured endpoint, or null for the default
* @return the primary domain
*/
public static String getPrimaryDomain(String endpoint) {
if (endpoint == null) {
endpoint = ENDPOINT;
}
/* REC1b2 */
if (isHostname(endpoint)) {
return endpoint;
Comment on lines +91 to +92

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai please check how mandatory is this with respect to spec and rest of the code, check if current fix is enough, accordingly expand on the above

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline lib/src/main/java/io/ably/pubsub/http/HttpUtils.java --items all
ast-grep outline lib/src/main/java/io/ably/pubsub/transport/WebSocketTransport.java --items all
ast-grep outline lib/src/test/java/io/ably/pubsub/http/HttpUtilsTest.java --items all
rg -n -C3 'hostForUrl|buildURL\(|new URI\(|new URL\(|InetSocketAddress\(|getHost\(' lib/src/main/java --glob '*.java'
rg -n -C3 'REC1b2|ADR.?119|::1|IPv6' --glob '*.java' --glob '*.md' --glob '*gradle*'

Length of output: 22332


🤖 web_search executed:

Ably SDK specification REC1b2 endpoint hostname contains "::" ADR-119 IPv6; RFC 3986 IPv6 host square brackets URI authority

Length of output: 796


🏁 Script executed:

#!/bin/bash
sed -n '185,220p' lib/src/main/java/io/ably/pubsub/http/HttpUtils.java
sed -n '110,160p' lib/src/main/java/io/ably/pubsub/transport/WebSocketTransport.java
ast-grep outline lib/src/main/java/io/ably/pubsub/http/HttpCore.java --items all --match 'httpExecute|retry|Retry'
rg -n -C4 'getHost\(|getAuthority\(|InetSocketAddress\(|URI\.create\(|new URI\(|new URL\(|wsUri|setHost\(' --glob '*.java' --glob '*.kt' -g '!**/test/**' -g '!**/androidTest/**'
fd -i 'WebSocket|HttpEngine|HttpClient|ProxySocket'
gh api repos/ably/ably-common/contents --jq '.[].name'

Length of output: 26775


🏁 Script executed:

#!/bin/bash
ast-grep outline lib/src/main/java/io/ably/pubsub/http/HttpCore.java --items all
rg -n -C5 'buildURL|url\.|new HttpRequest|preferred|fallback|host =' lib/src/main/java/io/ably/pubsub/http/HttpCore.java
for file in network-client-okhttp/src/main/java/io/ably/pubsub/network/OkHttpEngine.java network-client-okhttp/src/main/java/io/ably/pubsub/network/OkHttpWebSocketEngine.java lib/src/test/java/io/ably/pubsub/transport/WebSocketTransportTest.java; do
  ast-grep outline "$file" --items all
done
rg -n -C4 '\.url\(|create\(|IPv6|::1|ws://|wss://' network-client-okhttp/src/main/java/io/ably/pubsub/network/OkHttpEngine.java network-client-okhttp/src/main/java/io/ably/pubsub/network/OkHttpWebSocketEngine.java lib/src/test/java/io/ably/pubsub/transport/WebSocketTransportTest.java
gh api 'repos/ably/ably-common/git/trees/main?recursive=1' --jq '.tree[].path' | grep -Ei 'spec|feature|119|endpoint'

Length of output: 11442


@sacOO7 IPv6 bracketing is required for valid transport URLs. The current fix is sufficient for the URL-formatting defect identified above. An actual IPv6 request and connection test would add confidence, but I would not make those tests mandatory for this fix.

Specification requirement

The endpoint contract supplied for this PR treats values containing :: as hostnames and preserves them unchanged. Therefore, Defaults.getPrimaryDomain("::1") returning "::1" is correct. Do not add brackets in Defaults.

When a transport places that address in a URL, RFC 3986, Section 3.2.2 requires brackets:

Raw host:      ::1
REST URL:      http://[::1]:8080/time
WebSocket URL: ws://[::1]:8080/

This is a URL syntax requirement, not an optional formatting choice. I could not independently retrieve the authoritative REC1/ADR-119 text, so the Ably-specific conclusion relies on the endpoint contract supplied here.

Current implementation

I verified these paths:

  • HttpUtils.hostForUrl brackets colon-containing hosts and preserves already-bracketed hosts.
  • HttpUtils.buildURL applies the helper when constructing REST endpoint URLs.
  • WebSocketTransport.connect applies the same helper before passing the URL to the WebSocket engine.
  • The default WebSocket engine uses URI.create(url). The OkHttp WebSocket engine passes the URL to Request.Builder.url(url).
  • Host selection retains the original host. WebSocketTransport.getHost() still returns params.host.

The fix therefore operates at the correct boundary. It covers HTTP/HTTPS and WS/WSS without changing host comparisons. No further change to Defaults.java is needed for this finding.

Test coverage

HttpUtilsTest covers unbracketed IPv6, already-bracketed IPv6, unchanged non-IPv6 hosts, and HTTP URL host/port parsing.

The most useful small addition would be a mocked WebSocketTransport test that asserts the URL passed to WebSocketEngine.create contains [::1], while getHost() remains "::1".

Real IPv6 loopback tests would check network-engine behavior beyond URL construction. They are useful additional coverage, but the evidence available does not establish them as a specification requirement or a blocker for this formatting fix.

This assessment is source-level verification. I did not run the tests or establish end-to-end IPv6 connectivity.

You are interacting with an AI system.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai so we are good with current changes right?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sacOO7 Yes—the current changes are sufficient for this IPv6 URL-formatting issue, based on the source review.

Keep Defaults.getPrimaryDomain("::1") unchanged. The REST and WebSocket URL builders now add the required brackets.

Additional WebSocket or IPv6 loopback tests are optional coverage, not blockers for this fix. This does not establish end-to-end IPv6 connectivity or approval of the entire PR.

You are interacting with an AI system.

}
/* REC1b3 */
if (endpoint.startsWith(NONPROD_PREFIX)) {
return endpoint.substring(NONPROD_PREFIX.length()) + ".realtime.ably-nonprod.net";
}
/* REC1b4 */
return endpoint + ".realtime.ably.net";
}

/**
* Resolves the default fallback domains for an endpoint, used when ClientOptions#fallbackHosts is unset.
* <p>
* Spec: REC2c
*
* @param endpoint the configured endpoint, or null for the default
* @return the fallback domains; empty when the endpoint is a hostname
*/
public static String[] getEndpointFallbackHosts(String endpoint) {
if (endpoint == null) {
endpoint = ENDPOINT;
}
/* REC2c2 */
if (isHostname(endpoint)) {
return new String[0];
}
/* REC2c3 */
if (endpoint.startsWith(NONPROD_PREFIX)) {
return fallbackHosts(endpoint.substring(NONPROD_PREFIX.length()), "ably-realtime-nonprod.com");
}
/* REC2c1, REC2c4 */
return fallbackHosts(endpoint, "ably-realtime.com");
}

private static final String NONPROD_PREFIX = "nonprod:";

/* REC1b2: an endpoint is a hostname if it contains '.' or '::', or is "localhost" */
private static boolean isHostname(String endpoint) {
return endpoint.contains(".") || endpoint.contains("::") || endpoint.equals("localhost");
}

private static String[] fallbackHosts(String routingPolicyId, String domain) {
return new String[] {
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"
routingPolicyId + ".a.fallback." + domain,
routingPolicyId + ".b.fallback." + domain,
routingPolicyId + ".c.fallback." + domain,
routingPolicyId + ".d.fallback." + domain,
routingPolicyId + ".e.fallback." + domain
};
}
}
Loading
Loading