-
Notifications
You must be signed in to change notification settings - Fork 30
[ECO-5551] Use endpoint as default connection option (ADR-119) #2109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughReplaces ARTFallbackHosts with a new ARTDomainSelector; introduces endpoint and connectivityCheckUrl options on ARTClientOptions; routes REST/Realtime/WebSocket host resolution and fallback logic through ARTDomainSelector; updates tests, project/module maps, CI, and scripts to endpoint/primaryDomain semantics. Changes
sequenceDiagram
participant Client as Client / Tests
participant Options as ARTClientOptions
participant Selector as ARTDomainSelector
participant Rest as ARTRest
participant Realtime as ARTRealtime
Note over Client,Options: Client configures options (endpoint or legacy fields)
Client->>Options: set endpoint / restHost / realtimeHost / fallbackHosts
Options->>Selector: initWithEndpointClientOption(..., fallbackHostsUseDefault: ...)
Selector-->>Options: primaryDomain / fallbackDomains
Client->>Rest: REST request (host = selector.primaryDomain)
Rest->>Selector: on failure check fallbackDomains
alt fallback available
Rest->>Rest: switch host -> next fallback and retry
Rest->>Client: return retry result
else no fallback
Rest->>Client: report no-fallback error
end
Client->>Realtime: open realtime (host = selector.primaryDomain)
Realtime->>Selector: on transport failure get fallbackDomains
Realtime->>Realtime: reconnect using fallback hosts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45–75 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
a9e9148 to
5512392
Compare
5512392 to
0cd88a0
Compare
3795b94 to
c0284d8
Compare
129a643 to
624b1d1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (6)
Test/AblyTests/Tests/RestClientTests.swift (1)
177-181: Existing TODO’d expectations remain ineffective but are already trackedThe
expect(publishTask.error).toEventually(beNil(), …)checks intest__015__…API_keyandtest__018__…tokenare still ineffective becausepublishTask.errorstarts asnil. This was already raised and is tracked in GitHub issue #2145, so leaving the TODOs in place in this PR is reasonable.Also applies to: 201-207
Test/AblyTests/Tests/ARTDomainSelectorTests.swift (1)
400-411: Endpoint"nonprod:"producing.realtime.ably-nonprod.netstill looks like an undesirable edge caseThis test codifies that a
nonprod:endpoint with no routing ID yields a primary domain of.realtime.ably-nonprod.net(leading dot, empty policy ID). That hostname is highly likely to be invalid or at least unusable, and it makes a simple typo ("nonprod:"vs"nonprod:sandbox") fail at runtime in a non-obvious way.Consider tightening the parsing in
ARTDomainSelectorso that a nonprod-prefixed endpoint with an empty/whitespace-only suffix is treated as invalid or as “no nonprod routing ID” (e.g. falling back to the default primary domain or surfacing configuration as invalid). If you make that change, this test should be updated to assert the new, safer behavior instead of the leading-dot hostname.Source/ARTDomainSelector.m (2)
66-88: Endpoint classification is too naive for IP/IPv6 literalsThe hostname-vs-routing-policy decision only checks:
containsString:@"."containsString:@"::"- equality with
"localhost"Anything else non-empty is treated as a routing policy ID. This will misclassify several valid endpoint forms, e.g.:
- Fully expanded IPv6 literals without
::(2001:0db8:1:2:3:4:5:6).- Bracketed IPv6 with optional port (
[::1]:8080).- Potential other IP literal syntaxes.
Those values will be turned into routing policies and combined with
.realtime.ably.net, which is almost certainly not what the user intended.Consider normalizing and validating IP literals explicitly (e.g. strip brackets/ports/zone IDs, then detect IPv4/IPv6 using a system API like
inet_ptonor a focused regex) before falling back to the routing-policy heuristics. That way:
- All valid IPv4/IPv6 endpoints are consistently treated as hostnames.
- Only “clean” nonprod/prod routing IDs go down the policyId path.
81-88:endpoint = "nonprod:"yields an emptypolicyIdand leading-dot hostWhen
endpointClientOptionhas the"nonprod:"prefix but nothing afterwards,policyIdbecomes the empty string. For the NonProductionRoutingPolicy branch this produces:
- Primary domain:
.realtime.ably-nonprod.net- Fallbacks:
.a.fallback.ably-realtime-nonprod.com, etc.Those hostnames are almost certainly invalid and arise from a plausible misconfiguration or typo.
It would be safer to treat an empty/whitespace-only suffix after
"nonprod:"as “no routing ID” and either:
- fall back to the default primary/fallback domains, or
- surface an explicit configuration error.
If you change the behavior, remember to update the corresponding Swift test (
test__028__ARTDomainSelector__should_handle_nonprod_with_empty_id) to assert the new semantics.Source/ARTClientOptions.m (2)
90-99: setEndpoint still enforces REC1b1 even when clearing/propagating a nil endpoint (can break copy and legacy-only configs)
-setEndpoint:unconditionally raises if any legacy options are set, regardless of the value passed:- (void)setEndpoint:(NSString *)endpoint { // REC1b1: endpoint cannot be used with deprecated options if (self.hasEnvironment || self.hasCustomRestHost || self.hasCustomRealtimeHost) { [NSException raise:...]; } _endpoint = endpoint; _domainSelector = nil; }This means:
- Copying an options instance that only uses
environment/restHost/realtimeHost(withendpoint == nil) will still invokesetEndpoint:nilincopyWithZone:, andhasEnvironment/hasCustom*Hostmay already be true, causingcopyto raise even though no endpoint is actually being configured.- Clearing an endpoint (setting it back to
nil/empty) after legacy options have been set will also throw, even though the resulting configuration is valid (endpoint no longer in use).REC1b1 only needs to be enforced when a non-empty endpoint is set. Suggest gating the check on
endpointitself:- (void)setEndpoint:(NSString *)endpoint { - // REC1b1: endpoint cannot be used with deprecated options - if (self.hasEnvironment || self.hasCustomRestHost || self.hasCustomRealtimeHost) { - [NSException raise:NSInvalidArgumentException - format:@"The `endpoint` option cannot be used in conjunction with the `environment`, `restHost`, or `realtimeHost` options."]; - } - _endpoint = endpoint; - // Reset domain selector when endpoint changes - _domainSelector = nil; -} + (void)setEndpoint:(NSString *)endpoint { + // Only enforce REC1b1 when actually setting a non-empty endpoint + if (endpoint && [endpoint isNotEmptyString]) { + if (self.hasEnvironment || self.hasCustomRestHost || self.hasCustomRealtimeHost) { + [NSException raise:NSInvalidArgumentException + format:@"The `endpoint` option cannot be used in conjunction with the `environment`, `restHost`, or `realtimeHost` options."]; + } + } + _endpoint = endpoint; + // Reset domain selector when endpoint changes (including clearing it) + _domainSelector = nil; +}This preserves the REC1b1 constraint for real endpoint usage while allowing
copyWithZone:and “clear endpoint” flows to work for legacy-only configurations.
319-324: isProductionEnvironment now returns NO when environment is nil; likely diverges from historical “nil == production” semanticsCurrent implementation:
- (BOOL)isProductionEnvironment { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" return [[self.environment lowercaseString] isEqualToString:[ARTDefaultProductionEnvironment lowercaseString]]; #pragma clang diagnostic pop }When
environment == nil(the default case for clients not explicitly configuring a non‑production environment),-[NSString lowercaseString]is sent tonil, and the expression evaluates toNO. Historically, “no environment configured” has been treated as production, so this inverts that meaning.If call sites still rely on the traditional behavior (which the method name suggests), this should instead treat
nilor"production"(case‑insensitive) as production, e.g.:- (BOOL)isProductionEnvironment { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" NSString *env = [[self environment] lowercaseString]; NSString *prod = [ARTDefaultProductionEnvironment lowercaseString]; return (env == nil || [env isEqualToString:prod]); #pragma clang diagnostic pop }This keeps legacy semantics while still allowing explicit non‑production environments to be detected.
🧹 Nitpick comments (2)
Source/ARTClientOptions.m (2)
193-215: Confirm DomainSelector’s host outputs are bare hostnames suitable for NSURLComponents.host (esp. for IPv6/FQDN/IP cases)
restUrlComponentsandrealtimeUrlForHost:now do:components.host = self.primaryDomain; // rest ... components.host = host; // realtimewith
primaryDomainand thehostargument ultimately coming fromARTDomainSelector.For
NSURLComponents.hostto remain valid across all supported endpoint forms (routing policy, FQDN, IPv4, IPv6, localhost),ARTDomainSelectorneeds to guarantee that:
- Hosts are bare hostnames or IP literals, without embedded ports.
- IPv6 literals, if supported, are in the form expected by
NSURLComponents(no[]wrapping in thehostproperty; ports handled viacomponents.port).Given ADR‑119 explicitly allows FQDN/IP/localhost endpoints, it would be good to double‑check DomainSelector’s outputs (and add tests) to ensure they are always safe to assign to
NSURLComponents.hostwithout additional normalization.
22-22: Global default endpoint is simple but relies on call order; consider whether it ever needs to change after first options instantiationYou now have:
NSString *ARTDefaultEndpoint = nil; @implementation ARTClientOptions { NSString *_endpoint; ... } - (instancetype)initDefaults { ... _endpoint = ARTDefaultEndpoint; ... _connectivityCheckUrl = [ARTDefault connectivityCheckUrl]; } + (void)setDefaultEndpoint:(NSString *)endpoint { ARTDefaultEndpoint = endpoint; }This works fine if
+setDefaultEndpoint:is only ever called once, before anyARTClientOptionsare created (e.g., from a library initialization path or tests). If there’s any scenario where the default endpoint might be updated at runtime, already‑constructedARTClientOptionsinstances will keep the old value because they snapshotARTDefaultEndpointininitDefaults.If runtime changes are not required, this is acceptable as‑is; otherwise, consider either:
- Having
initDefaultsread a source of truth that can change (e.g.ARTDefault), or- Documenting that
setDefaultEndpoint:is a one‑time, pre‑instantiation configuration hook.Also applies to: 32-38, 48-48, 80-80, 302-304
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (25)
.github/workflows/integration-test.yaml(1 hunks)Ably.xcodeproj/project.pbxproj(9 hunks)Examples/Tests/TestsTests/TestsTests.swift(3 hunks)Scripts/log-environment-information.sh(1 hunks)Source/ARTClientOptions.m(8 hunks)Source/ARTDefault.m(4 hunks)Source/ARTDomainSelector.m(1 hunks)Source/ARTFallbackHosts.m(0 hunks)Source/ARTRealtime.m(3 hunks)Source/ARTRest.m(7 hunks)Source/ARTWebSocketTransport.m(5 hunks)Source/Ably.modulemap(1 hunks)Source/PrivateHeaders/Ably/ARTClientOptions+Private.h(2 hunks)Source/PrivateHeaders/Ably/ARTDefault+Private.h(2 hunks)Source/PrivateHeaders/Ably/ARTDomainSelector.h(1 hunks)Source/PrivateHeaders/Ably/ARTFallbackHosts.h(0 hunks)Source/include/Ably/ARTClientOptions.h(2 hunks)Source/include/Ably/ARTDefault.h(1 hunks)Source/include/module.modulemap(1 hunks)Test/AblyTests/Test Utilities/TestUtilities.swift(4 hunks)Test/AblyTests/Tests/ARTDomainSelectorTests.swift(1 hunks)Test/AblyTests/Tests/PushAdminTests.swift(1 hunks)Test/AblyTests/Tests/RealtimeClientConnectionTests.swift(22 hunks)Test/AblyTests/Tests/RealtimeClientTests.swift(1 hunks)Test/AblyTests/Tests/RestClientTests.swift(35 hunks)
💤 Files with no reviewable changes (2)
- Source/ARTFallbackHosts.m
- Source/PrivateHeaders/Ably/ARTFallbackHosts.h
🚧 Files skipped from review as they are similar to previous changes (8)
- Scripts/log-environment-information.sh
- Source/Ably.modulemap
- Source/ARTWebSocketTransport.m
- Source/include/Ably/ARTClientOptions.h
- Source/PrivateHeaders/Ably/ARTDefault+Private.h
- Test/AblyTests/Tests/PushAdminTests.swift
- Source/include/module.modulemap
- Source/ARTRealtime.m
🧰 Additional context used
🧬 Code graph analysis (3)
Test/AblyTests/Tests/RealtimeClientTests.swift (2)
Test/AblyTests/Tests/RealtimeClientConnectionTests.swift (3)
deprecated(3843-3890)deprecated(3991-4029)deprecated(4074-4111)Test/AblyTests/Tests/RestClientTests.swift (13)
deprecated(302-314)deprecated(317-330)deprecated(354-364)deprecated(367-374)deprecated(377-382)deprecated(385-390)deprecated(418-424)deprecated(865-894)deprecated(896-900)deprecated(903-930)deprecated(1014-1039)deprecated(1098-1122)deprecated(1226-1235)
Test/AblyTests/Tests/RestClientTests.swift (3)
Test/AblyTests/Test Utilities/TestUtilities.swift (7)
match(1516-1524)publishTestMessage(484-486)publishTestMessage(488-490)getTestEndpoint(626-635)at(1431-1433)at(1440-1445)extract(1526-1537)Test/AblyTests/Test Utilities/Test.swift (1)
uniqueChannelName(15-32)Test/AblyTests/Tests/RealtimeClientTests.swift (1)
deprecated(156-180)
Test/AblyTests/Tests/RealtimeClientConnectionTests.swift (3)
Test/AblyTests/Test Utilities/TestUtilities.swift (8)
match(1516-1524)at(1431-1433)at(1440-1445)data(934-936)dispose(1639-1645)splitDone(248-258)clientOptions(144-159)commonAppSetup(107-142)Test/AblyTests/Tests/RealtimeClientTests.swift (1)
deprecated(156-180)Test/AblyTests/Test Utilities/DataGatherer.swift (2)
waitForData(36-57)waitForData(62-64)
🪛 Clang (14.0.6)
Source/PrivateHeaders/Ably/ARTDomainSelector.h
[error] 1-1: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
🪛 SwiftLint (0.57.0)
Test/AblyTests/Tests/RestClientTests.swift
[Warning] 181-181: TODOs should be resolved (this check is useless bc publi...)
(todo)
[Warning] 206-206: TODOs should be resolved (this check is useless bc publi...)
(todo)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: check
- GitHub Check: check
- GitHub Check: check (iOS, test_iOS18_4)
- GitHub Check: check (tvOS, test_tvOS18_4)
- GitHub Check: check (macOS, test_macOS)
🔇 Additional comments (30)
.github/workflows/integration-test.yaml (1)
35-35: ABLY_ENV change is correctly applied; no other integration-test workflow files exist to replicate.Verification confirms only one
integration-test.yamlfile exists in.github/workflows/, with the correctABLY_ENV: nonprod:sandboxvalue at line 35. The environment variable naming aligns with ADR-119's convention for non-production sandbox routing policies. The comment on line 11 serves as a template reminder for future variants, but currently there are no other integration-test-*.yaml files requiring updates.Ably.xcodeproj/project.pbxproj (2)
360-365: ARTDomainSelector file wiring looks correct and consistent.
PBXBuildFileentries forARTDomainSelector.m/.hcorrectly point at the newPBXFileReferenceIDs.- File references place
ARTDomainSelector.munderSource/UtilitiesandARTDomainSelector.hunderPrivateHeaders/Ably, matching existing private utilities.- The Utilities group now includes both
.hand.m, so Xcode navigation and grouping remain coherent.No issues here; this wiring is good to go.
Also applies to: 1224-1225, 1993-1994
2142-2142: DomainSelector is correctly hooked into all framework targets as a private utility.
ARTDomainSelector.his added once to each Headers phase for iOS, macOS, and tvOS withATTRIBUTES = (Private,), aligning with its intended internal role.ARTDomainSelector.mis added once to each Sources phase for the same three targets, reusing the correspondingPBXBuildFileentries.- No duplicate or dangling references for these new build-file IDs.
Assuming DomainSelector is intentionally non‑public API, this target integration looks correct.
Also applies to: 2318-2318, 2527-2527, 2966-2966, 3110-3110, 3254-3254
Examples/Tests/TestsTests/TestsTests.swift (1)
21-21: Endpoint/nonprod and ably.com background URLs look consistent with ADR-119Using
https://sandbox.realtime.ably-nonprod.net:443/appsplusoptions.endpoint = "nonprod:sandbox"keeps sandbox provisioning and client connections aligned on the same nonprod routing policy, and switching the backgroundURLSessionpings tohttps://ably.comis purely cosmetic and safe here.Also applies to: 50-50, 67-78
Test/AblyTests/Test Utilities/TestUtilities.swift (1)
119-119: Test utilities correctly pivoted to endpoint/domainSelector
- Using
https://\(options.domainSelector.primaryDomain):\(options.tlsPort)/appskeeps sandbox app provisioning in lockstep with whatever endpoint/domainSelector the tests are exercising.- Wiring
clientOptionsviaoptions.endpoint = getTestEndpoint()and deriving that fromABLY_ENDPOINT→ABLY_ENV→"nonprod:sandbox"cleanly centralizes test configuration.- Dropping the environment query item from the JWT endpoint and only sending
encryptedmatches the new endpoint-centric model.Given this is test-only code, the existing
infoDictionary!force unwrap is acceptable.Also applies to: 146-158, 607-607, 626-635
Test/AblyTests/Tests/RealtimeClientTests.swift (1)
156-180: Deprecation wrapper around realtimeHost test is appropriateMarking the
realtimeHost-based test as deprecated (with a clear message) is a good way to preserve coverage for legacy behavior without surfacing compiler warnings while endpoint/domainSelector become primary.Source/ARTRest.m (1)
19-20: DomainSelector-driven REST host/fallback behavior is wired correctly
- Resetting
currentFallbackHost/prioritizedHostonfallbackRetryExpirationand restoring the host viaoptions.domainSelector.primaryDomainkeeps RSC15f semantics while honoring endpoint/environment-derived defaults.- Using
options.domainSelector.fallbackDomainsas the source forARTFallbackand promoting a successful fallback toprioritizedHostmaintains the expected REC2 fallback behavior.- Setting the
Hostheader explicitly to the fallback host for retried requests (// RSC15j) is important to keep SNI/Host consistent with the URL when going through intermediaries.currentHostnow defers todomainSelector.primaryDomain, which matches the new endpoint-centric configuration.internetIsUp:now respects a configurableconnectivityCheckUrlwith a sane default fromARTDefault, which is a straightforward, backward-compatible enhancement.- Exposing
+deviceAccessQueueand reusing it in the device accessors centralizes synchronization around the sharedARTLocalDevicewithout changing behavior.No issues spotted with these changes.
Also applies to: 375-383, 441-452, 460-463, 515-521, 677-689, 793-802
Source/PrivateHeaders/Ably/ARTDomainSelector.h (1)
1-40: ARTDomainSelector interface matches the endpoint/REC designExplicitly requiring all domain-affecting client options in the initializer, disabling
init, and exposingprimaryDomain/fallbackDomainsas read‑only properties gives a clear, constrained surface for domain resolution. This is a solid foundation for the endpoint‑centric behavior used elsewhere in the PR.Test/AblyTests/Tests/RestClientTests.swift (1)
45-47: Endpoint/default-domain and fallback host tests are consistent with DomainSelector semantics
- All expectations that were previously tied to
ably.iohave been updated to assert againstmain.realtime.ably.netandmain.[a-e].fallback.ably-realtime.com, matching the new default primary domain and default fallback domain family.- The new REC1 tests (
test__026a…027d) correctly capture:
- endpoint + any deprecated option (environment/restHost/realtimeHost) being invalid,
- endpoint as FQDN →
primaryDomain == endpoint,- endpoint as nonprod routing policy (
"nonprod:sandbox") →sandbox.realtime.ably-nonprod.net,- endpoint as production routing policy (
"test") →test.realtime.ably.net,- environment/restHost/realtimeHost overriding the default primary domain as specified.
- The new REC2 tests around
options.domainSelector.fallbackDomainscover:
- implicit default fallback list for the default primary domain,
- empty fallback list when the primary domain is explicitly configured as a domain name,
- nonprod routing policy endpoints (
nonprod:sandbox) mapping tosandbox.[a-e].fallback.ably-realtime-nonprod.com,- environment and deprecated host options yielding the expected implicit fallback lists or empty lists.
- Tests using
ARTClientOptions.setDefaultEndpoint(getTestEndpoint())andoptions.endpoint = …ensure both static and per-instance endpoint configuration surfaces are exercised.- RSC15h/RSC25/RSC15m/RSC15n host-fallback tests now drive against
ARTDefault.fallbackHosts()andoptions.domainSelector.fallbackDomains, so they meaningfully validate the new selector‑based fallback behavior.These changes give good coverage of the new endpoint/domainSelector model across default, custom, routing-policy, and deprecated-option scenarios.
Also applies to: 66-67, 112-112, 134-135, 392-403, 428-448, 450-476, 515-531, 552-575, 585-607, 685-694, 903-930, 932-957, 959-985, 987-1011, 1041-1067, 1070-1095, 1180-1183
Source/include/Ably/ARTDefault.h (1)
13-19: ARTDefault host/fallback accessors match new usageKeeping
+restHost,+realtimeHost, and thefallbackHostsAPIs public is consistent with how tests and DomainSelector now consume defaults, and the minor signature tidy-up doesn’t change behavior.Test/AblyTests/Tests/RealtimeClientConnectionTests.swift (13)
44-56: Fallback host URL expectations now correctly align withARTDomainSelectordefaultsThe updated comment and regex expectations for
testUsesAlternativeHostOnResponse(defaultmain.realtime.ably.netandmain.[a-e].fallback.ably-realtime.com) match the new default primary domain and fallback domains exposed viaARTDomainSelector. The sequencing (primary then fallback) is still validated as before and looks consistent with REC2.
181-195: Default realtime connection host assertion matches new primary domain
test__017__Connection__url__should_connect_to_the_default_hostnow assertsurl.host == "main.realtime.ably.net". This is consistent withARTDomainSelector’s default policy (main.realtime.ably.net), so the change keeps the test aligned with the new endpoint-first behavior.
2178-2203: Using a non-routable IP viaendpointis a good way to exercise RTN14c timeout behaviorSetting
options.endpoint = "10.255.255.1"(non-routable) for RTN14c ensures the connection attempt goes to a single, unreachable host classified as a hostname/IP, so no automatic fallbacks are used. The subsequent timing assertions againstrealtimeRequestTimeoutremain valid and cleanly exercise the timeout path under the new endpoint semantics.
3843-3890: Deprecation annotations aroundrealtimeHosttests are appropriateMarking the
realtimeHost-based host-fallback tests as@available(*, deprecated, ...)avoids compiler warnings while keeping coverage for legacy options. This is a sensible transition pattern while endpoint becomes the preferred configuration surface.
4031-4071: REC2a2 customfallbackHoststest remains correct after domain changes
test__089__Connection__Host_Fallback__applies_when_an_array_of_ClientOptions_fallbackHosts_is_providednow expects the first connection tomain.realtime.ably.netand subsequent attempts to the explicit custom fallback hosts[f-j].ably-realtime.com. This matches the new default primary domain while still verifying that customfallbackHostsfully override generated fallbacks.
4073-4111: DeprecatedfallbackHostsUseDefaultpath still correctly uses generated default fallbacks
test__089b__...fallbackHostsUseDefault...now asserts that withfallbackHostsUseDefault = trueand a customport, the client first connects tomain.realtime.ably.netand then amain.[a-e].fallback.ably-realtime.comhost. That matchesARTDomainSelector.defaultFallbackDomainsand confirms the deprecated flag still routes through the REC2c1-style defaults.
4189-4260: Helper_test__091and wrappers correctly validate primary vs fallback hosts for different endpointsThe new
_test__091__Connection__Host_Fallback__every_connection_is_first_attempted_to_the_primary_host_main_realtime_ably_net(endpoint:test:)plus its three wrappers verify:
endpoint == nil→ first URL usesmain.realtime.ably.net, then amain.[a-e].fallback.ably-realtime.comhost.endpoint == "nonprod:sandbox"→ first URL usessandbox.realtime.ably-nonprod.net, thensandbox.[a-e].fallback.ably-realtime-nonprod.com.endpoint == "test"→ first URL usestest.realtime.ably.net, thentest.[a-e].fallback.ably-realtime.com.This matches the primary/fallback logic in
ARTDomainSelectorfor default, nonprod, and production routing-policy endpoints and provides good regression coverage for REC2c1/3/4 under the new endpoint API.
4264-4353: Host retry ordering tests correctly reusedomainSelector.fallbackDomainsfor prod/nonprod endpoints
_test__092__Connection__Host_Fallback__should_retry_hosts_in_random_order_after_checkin_if_an_internet_connection_is_availablenow:
- Drives fallback generation via
options.domainSelector.fallbackDomainsfor the current endpoint (nil,"nonprod:sandbox", or"test").- Uses the injected
shuffleArrayInExpectedHostOrderto produce a deterministic “random” sequence.- Extracts hostnames from both websocket URLs and the connectivity-check HTTP requests, verifying that the actual retry sequence exactly matches the expected fallback host list for each endpoint variant.
The three wrapper tests for prod/sandbox/test ensure this behavior is exercised for all relevant domain families. The logic looks consistent with the fallback-domain construction in
ARTDomainSelector.
4471-4512: EmptyfallbackHostsarray correctly disables fallback behavior
test__095__Connection__Host_Fallback__won_t_use_fallback_hosts_feature_if_an_empty_array_is_providedconfirms:
- With
fallbackHosts = [], a hostUnreachable error still causes CONNECTING → DISCONNECTED → CONNECTING transitions.- Only two connection attempts occur, both to
main.realtime.ably.net, and no generated fallback hosts are used.This matches the intended semantics that an explicit empty array means “no fallbacks”, and it aligns with
ARTDomainSelector.fallbackDomainsreturning the provided empty list verbatim.
4835-4903: Reachability now correctly targets the primary domain fromdomainSelectorIn tests 109 and 110, the assertions that
reachability.host == client.internal.options.domainSelector.primaryDomainensure that:
- OS reachability checks are aligned with whatever REC1 primary domain is selected (default or endpoint-based).
- Simulated reachability changes drive the expected DISCONNECTED transitions in both CONNECTING and CONNECTED states.
This is a good update from older environment/host assumptions and keeps network-change behavior tied to the same domain-selection source used elsewhere.
4948-4953: RTN20c test correctly pins reconnection host against the original primary domainIn
test__106_b__...restart_the_pending_connection_attempt..., capturinglet primaryDomain = options.domainSelector.primaryDomainbefore overridingoptions.endpointwith a non-routable IP and then settingoptions.testOptions.reconnectionRealtimeHost = primaryDomainensures:
- The initial failed connection uses the “bad” endpoint, exercising the reachability path.
- The subsequent reconnection attempt uses the original, valid primary domain.
This keeps reconnection semantics coherent with domain selection while still allowing the test to force a CONNECTING → DISCONNECTED → CONNECTING cycle via an unreachable endpoint.
4546-4556: Fallback HTTP requests remain tied to the selected fallback hostIn
test__096__Connection__Host_Fallback__client_is_connected_to_a_fallback_host_endpoint_should_do_HTTP_requests_to_the_same_data_centre, the updated regex checking//main.[a-e].fallback.ably-realtime.comfor the websocket URL, and then asserting that subsequent RESTtimerequests go to the same host, matches the new default fallback-domain schema and keeps the “same data centre” invariant intact.
4180-4185: Bad-request (400) path correctly kept on primary host after domain changes
test__090__Connection__Host_Fallback__should_not_use_an_alternative_host_when_the_client_receives_a_bad_requestnow asserts that both connection attempts targetmain.realtime.ably.net. This continues to verify that 400-class fake responses do not trigger fallback-host usage under the new domain scheme.Source/ARTDomainSelector.m (1)
148-214: Fallback-domain generation logic matches REC2 and is internally consistentThe
fallbackDomainsimplementation:
- Honors explicit
fallbackHostsClientOption(including an explicit empty array) before any generated defaults.- Respects
fallbackHostsUseDefaultby routing throughdefaultFallbackDomains.- Generates:
- Default:
main.[a–e].fallback.ably-realtime.com.- Nonprod routing policy:
<policyId>.[a–e].fallback.ably-realtime-nonprod.com.- Prod routing policy and legacy env:
<policyId>.[a–e].fallback.ably-realtime.com.- Hostname/legacy host: no automatic fallbacks.
defaultFallbackDomainsalso correctly returns themain.[a–e].fallback.ably-realtime.comset. This aligns with the new tests and REC2 requirements.Source/PrivateHeaders/Ably/ARTClientOptions+Private.h (1)
18-26: ExposingdomainSelectoronARTClientOptionsis a clean internal API choiceThe new readonly
domainSelectorproperty and+setDefaultEndpoint:class method give internal code and tests a single, well-defined source of truth for REC1/REC2 domain behavior. This avoids duplicating domain/fallback state onARTClientOptionsitself and matches the direction suggested in earlier reviews. TherealtimeUrlForHost:helper is also a sensible internal utility for constructing alternate-host URLs without mutating options.Source/ARTClientOptions.m (2)
105-126: DomainSelector invalidation on all relevant options looks correct and keeps host resolution coherentResetting
_domainSelectorinsetEndpoint:,setEnvironment:,setRestHost:,setRealtimeHost:,setFallbackHosts:, andsetFallbackHostsUseDefault:ensures that any change to the inputs that drive domain selection will force a freshARTDomainSelectorinstance next timedomainSelector,primaryDomain, orfallbackDomainsare accessed.Given how much logic now sits inside
ARTDomainSelector, this pattern is important to avoid stale host/fallback state leaking between configurations. The current implementation (lazy creation + explicit resets on all the right setters) looks consistent and appropriate.Also applies to: 130-187, 284-300
217-265: copyWithZone correctly propagates new endpoint/domain-related state, assuming setEndpoint is gated as aboveThe updated
copyWithZone::
- Copies
endpointandconnectivityCheckUrl, so the clone preserves the new configuration surface.- Copies
_fallbackHostsand_fallbackHostsUseDefaultvia ivars, intentionally bypassing the setters (which enforce mutual-exclusion rules) — this is appropriate for a faithful copy.- Reapplies legacy overrides (
restHost,realtimeHost,environment) only when the source ivars are non‑nil, so defaults are not “invented” on the clone.Once
setEndpoint:is updated to only enforce REC1b1 for non‑empty endpoints (see earlier comment), this method should no longer raise when copying legacy‑only options and will produce an options object semantically equivalent to the original.Source/ARTDefault.m (3)
4-7: DomainSelector integration for default hosts/fallbacks looks consistent with the new endpoint-centric model
ARTDefaultnow usesARTDomainSelectorin:
+fallbackHostsWithEnvironment:to derive fallback domains for a given environment.+fallbackHoststo get the default fallback set.+restHost/+realtimeHostto resolve the primary default domain.This mirrors how
ARTClientOptionsdelegates domain selection, which should keep host and fallback behavior consistent across “static default” and “per‑client options” code paths. The constructor arguments (nil endpoint, nil custom hosts,fallbackHostsUseDefault:false) look coherent for “library defaults” that are independent of any particular client options instance.Also applies to: 25-55
9-12: Mutable global defaults for TTL and message sizes are wired up correctlyThe introduction of:
ARTConnectionStateTtlARTMaxProductionMessageSizeARTMaxSandboxMessageSizecombined with:
+connectionStateTtland+setConnectionStateTtl:+maxMessageSizewith DEBUG/!DEBUG switching+setMaxMessageSize:,+setMaxProductionMessageSize:,+setMaxSandboxMessageSize:preserves the prior behavior while allowing tests (and consumers, if needed) to adjust these values at runtime. The use of
@synchronized(self)in the setters is sufficient to avoid racy updates on these simple globals in typical SDK usage.Also applies to: 68-90, 92-118
13-13: connectivityCheckUrl constant and helper are straightforward and align with the new ClientOptions defaultDefining:
NSString *const ARTDefaultConnectivityCheckUrl = @"internet-up.ably-realtime.com/is-the-internet-up.txt"; ... + (NSString *)connectivityCheckUrl { return [NSString stringWithFormat:@"https://%@", ARTDefaultConnectivityCheckUrl]; }and then wiring this into
ARTClientOptionsvia_connectivityCheckUrl = [ARTDefault connectivityCheckUrl];gives a single, centralized source of truth for the connectivity check endpoint. Thehttps://prefix is handled here, and the host/path remain unchanged from the existing “is-the-internet-up” check, so this looks good.Also applies to: 128-130
624b1d1 to
4059131
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
Source/ARTDomainSelector.m (1)
66-118: Endpoint policy parsing is clear, but IPv6/IP literal detection remains too naiveThe REC1 policy parsing (endpoint → env → restHost → realtimeHost → default) is straightforward and matches the documented comments. However, the “hostname vs routing policy” heuristic:
if ([endpointClientOption containsString:@"."] || [endpointClientOption containsString:@"::"] || [endpointClientOption isEqualToString:@"localhost"])will still misclassify many valid IP literals as routing-policy IDs (e.g. full IPv6 addresses without
"::", bracketed IPv6 with ports, IPv4 addresses, or IPv6 with zone IDs). That means a user passing an IP literal asendpointmay silently get routed via REC1b3/b4 rather than REC1b2.Consider normalizing and validating the endpoint as an IP literal before deciding it’s a routing policy, for example:
- Strip brackets (
[::1]), ports (:8080), and any zone suffix (%eth0).- Use
inet_ptonfor IPv4/IPv6 detection.- Treat any successfully parsed IP literal as a hostname (REC1b2 path), not as a routing policy.
Also consider guarding against an empty policy ID after
nonprod:(e.g.endpoint = "nonprod:") to avoid generating".realtime.ably-nonprod.net".
🧹 Nitpick comments (5)
Source/ARTRealtime.m (1)
217-237: ARTDomainSelector import here is benign but currently unused
ARTRealtimeInternalrelies ongetClientOptions].domainSelectorrather than directly on ARTDomainSelector symbols, so this import isn’t strictly necessary. It’s harmless, but could be removed later if you’re cleaning up includes.Source/ARTClientOptions.m (4)
90-99: Be careful thatsetEndpoint:+copyWithZone:don’t throw when simply copying or clearing options
setEndpoint:,setEnvironment:,setRestHost:, andsetRealtimeHost:correctly enforce the REC1b1 / REC1c1 mutual‑exclusion rules for endpoint vs legacy options, and they reset_domainSelectorwhen inputs change, which is good.However, because:
setEndpoint:unconditionally checksself.hasEnvironment || self.hasCustomRestHost || self.hasCustomRealtimeHost, andcopyWithZone:always doesoptions.endpoint = self.endpointbefore copying legacy fields,there are edge cases where you may raise
NSInvalidArgumentExceptioneven though the effective configuration doesn’t introduce a new endpoint. Examples to consider:
- Copying an options instance configured only via
environment/ custom hosts but with a nil or default endpoint.- Clearing an endpoint (setting it to
nilor an empty string) on an instance that already has legacy host/environment configured.To make these flows safer while still enforcing REC1b1, consider:
- Gating the conflict check in
setEndpoint:on the incoming argument being non‑nil/non‑empty, e.g.:- (void)setEndpoint:(NSString *)endpoint { - // REC1b1: endpoint cannot be used with deprecated options - if (self.hasEnvironment || self.hasCustomRestHost || self.hasCustomRealtimeHost) { + (void)setEndpoint:(NSString *)endpoint { + // REC1b1: endpoint cannot be used with deprecated options + if (endpoint && [endpoint isNotEmptyString] && + (self.hasEnvironment || self.hasCustomRestHost || self.hasCustomRealtimeHost)) { [NSException raise:NSInvalidArgumentException format:@"The `endpoint` option cannot be used in conjunction with the `environment`, `restHost`, or `realtimeHost` options."]; } _endpoint = endpoint; _domainSelector = nil; }
- Alternatively, bypassing the setter when copying (
options->_endpoint = self->_endpoint;) if you’re confident the source object already satisfied the invariants.This would still reject genuinely mixed configurations, but avoid surprising exceptions when copying or clearing options.
Also applies to: 130-187, 217-248
193-215: rest/realtime URL helpers now shareprimaryDomain– confirm this matches desired host split
restUrlComponents,restUrl, andrealtimeUrlnow all derive their host fromprimaryDomain(either directly or viarealtimeUrlForHost:), andrestHost/realtimeHostfall back todomainSelector.primaryDomainwhen no custom host is set. This is internally consistent; just double‑check thatARTDomainSelector.primaryDomainis indeed the correct canonical host for both REST and Realtime in the ADR‑119 world (i.e. you don’t still expect a distinct “rest” vs “realtime” hostname pair by default). If separate defaults are still intended, you may want distinct helpers or a small abstraction around “rest primary domain” vs “realtime primary domain”.
302-305:setDefaultEndpoint:as a process‑wide default is fine but worth documenting as global stateHaving a global
ARTDefaultEndpointthat influences subsequentinitDefaultscalls is a reasonable way to support test setups and app‑wide defaults. It might be worth documenting (in comments or higher‑level docs) that this is global mutable state affecting all futureARTClientOptionsinstances, so it’s not something applications should typically change at runtime outside of controlled test harnesses.
319-324:isProductionEnvironmentsemantics no longer treat “no environment” as production
isProductionEnvironmentcurrently returns:return [[self.environment lowercaseString] isEqualToString:[ARTDefaultProductionEnvironment lowercaseString]];Given
environmentisnilby default, this now reportsNOwhen no environment is configured, whereas historically “no environment set” has been equivalent to production. If any callers use!isProductionEnvironmentto branch non‑production behavior, this change could misclassify the default configuration.To preserve the usual meaning while still handling explicit environment overrides, consider:
- (BOOL)isProductionEnvironment { - return [[self.environment lowercaseString] isEqualToString:[ARTDefaultProductionEnvironment lowercaseString]]; -} + (BOOL)isProductionEnvironment { + NSString *env = [self.environment lowercaseString]; + NSString *prod = [ARTDefaultProductionEnvironment lowercaseString]; + // Treat a nil/unspecified environment as production. + return (env == nil || [env isEqualToString:prod]); +}(or an equivalent implementation using
hasEnvironment).That keeps behavior intuitive for both unset and explicitly “production” environments.
Also applies to: 326-331
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (25)
.github/workflows/integration-test.yaml(1 hunks)Ably.xcodeproj/project.pbxproj(9 hunks)Examples/Tests/TestsTests/TestsTests.swift(3 hunks)Scripts/log-environment-information.sh(1 hunks)Source/ARTClientOptions.m(8 hunks)Source/ARTDefault.m(4 hunks)Source/ARTDomainSelector.m(1 hunks)Source/ARTFallbackHosts.m(0 hunks)Source/ARTRealtime.m(3 hunks)Source/ARTRest.m(7 hunks)Source/ARTWebSocketTransport.m(5 hunks)Source/Ably.modulemap(1 hunks)Source/PrivateHeaders/Ably/ARTClientOptions+Private.h(2 hunks)Source/PrivateHeaders/Ably/ARTDefault+Private.h(2 hunks)Source/PrivateHeaders/Ably/ARTDomainSelector.h(1 hunks)Source/PrivateHeaders/Ably/ARTFallbackHosts.h(0 hunks)Source/include/Ably/ARTClientOptions.h(2 hunks)Source/include/Ably/ARTDefault.h(1 hunks)Source/include/module.modulemap(1 hunks)Test/AblyTests/Test Utilities/TestUtilities.swift(4 hunks)Test/AblyTests/Tests/ARTDomainSelectorTests.swift(1 hunks)Test/AblyTests/Tests/PushAdminTests.swift(1 hunks)Test/AblyTests/Tests/RealtimeClientConnectionTests.swift(22 hunks)Test/AblyTests/Tests/RealtimeClientTests.swift(1 hunks)Test/AblyTests/Tests/RestClientTests.swift(35 hunks)
💤 Files with no reviewable changes (2)
- Source/ARTFallbackHosts.m
- Source/PrivateHeaders/Ably/ARTFallbackHosts.h
🚧 Files skipped from review as they are similar to previous changes (6)
- Scripts/log-environment-information.sh
- Source/Ably.modulemap
- Test/AblyTests/Tests/PushAdminTests.swift
- Source/include/module.modulemap
- Test/AblyTests/Tests/ARTDomainSelectorTests.swift
- Examples/Tests/TestsTests/TestsTests.swift
🧰 Additional context used
🧬 Code graph analysis (1)
Test/AblyTests/Tests/RestClientTests.swift (1)
Test/AblyTests/Test Utilities/TestUtilities.swift (5)
match(1516-1524)getTestEndpoint(626-635)at(1431-1433)at(1440-1445)extract(1526-1537)
🪛 Clang (14.0.6)
Source/PrivateHeaders/Ably/ARTDomainSelector.h
[error] 1-1: 'Foundation/Foundation.h' file not found
(clang-diagnostic-error)
🪛 SwiftLint (0.57.0)
Test/AblyTests/Tests/RestClientTests.swift
[Warning] 181-181: TODOs should be resolved (this check is useless bc publi...)
(todo)
[Warning] 206-206: TODOs should be resolved (this check is useless bc publi...)
(todo)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: build
- GitHub Check: check (tvOS, test_tvOS18_4)
- GitHub Check: check (macOS, test_macOS)
- GitHub Check: check (iOS, test_iOS18_4)
- GitHub Check: check
- GitHub Check: check
🔇 Additional comments (44)
Ably.xcodeproj/project.pbxproj (1)
360-365: ARTDomainSelector added cleanly across file references, groups, and all targets
ARTDomainSelector.m/.hhave matchingPBXFileReferenceandPBXBuildFileentries, are placed under theUtilitiesgroup, and are correctly wired into the iOS/macOS/tvOS Sources and Headers phases as Private headers with no dangling IDs. This wiring looks consistent and should compile fine.Also applies to: 1224-1225, 1993-1994, 2142-2142, 2318-2318, 2527-2527, 2965-2967, 3110-3111, 3254-3255
Test/AblyTests/Test Utilities/TestUtilities.swift (3)
115-141: Usingoptions.domainSelector.primaryDomainfor app creation looks correctSwitching the test app creation URL to
options.domainSelector.primaryDomainkeeps tests aligned with the REC1/endpoint resolution path, and avoids coupling tests to legacy rest/realtime host behavior. No issues spotted here.
145-159: Endpoint-based test client options wiring is consistentSetting
options.endpoint = getTestEndpoint()is the right way to feed the new domain selection path in tests. The rest of the options setup (debug, key, token, dispatch queues) remains unchanged and compatible.
598-607: JWT helper no longer passes environment – confirm this is intentionalDropping any environment-related query param and only sending
encrypted(plus keyName/keySecret/etc.) changes how the echo server might infer routing. If the echo endpoint now relies purely on the API key and global routing config, this is fine; otherwise it might still expect an environment-like hint.Can you confirm the echo JWT service used here is independent of
environment/endpointinputs and that no server-side logic relied on the old env param?Source/ARTDomainSelector.m (3)
120-146: Primary-domain construction matches REC1 semanticsThe switch in
primaryDomaincorrectly yields:
- Default:
main.realtime.ably.net- Hostname: the raw hostname
- Non-production routing policy:
<policyId>.realtime.ably-nonprod.net- Production routing / legacy environment:
<policyId>.realtime.ably.net- Legacy host: the raw hostname
This aligns with the policy comments and REC1* references; no issues found.
148-204: Fallback-domain generation is consistent with REC2 and legacy behaviorsThe fallback selection order—explicit
fallbackHostsClientOption→fallbackHostsUseDefault→ policy-derived fallbacks—is sensible and matches the REC2a/b/c comments. For routing-policy and legacy-environment cases, generating<policyId>.[a–e].fallback.<tld>looks correct, and returning@[]for hostname/legacy-host is appropriate (no implicit fallbacks on arbitrary hostnames).
206-214:defaultFallbackDomainsimplementation & doc are now alignedThe helper now clearly documents and returns
["main.a.fallback.ably-realtime.com", ..., "main.e.fallback.ably-realtime.com"], matching the internal constants and REC2 default behavior. All good here.Source/ARTDefault.m (4)
4-14: New default constants and connectivity URL are coherentThe introduction of
ARTDefaultAPIVersion,ARTDefaultProductionEnvironment, connection-state TTL, production/sandbox message sizes, andARTDefaultConnectivityCheckUrlconsolidates mutable defaults into explicit globals. The connectivity check URLhttps://internet-up.ably-realtime.com/is-the-internet-up.txtmatches the new non-sandbox infrastructure and is suitable as a single canonical check endpoint.
25-36: UsingARTDomainSelectorfromARTDefaultmatches endpoint/env semantics
fallbackHostsWithEnvironment:,fallbackHosts,restHost, andrealtimeHostnow delegate toARTDomainSelectorwithendpoint=niland (optionally)environment, which:
- Preserves legacy
environmentbehavior via the LegacyEnvironment policy.- Makes the no-env case return the REC1 default primary domain and REC2 default fallbacks.
This keeps
ARTDefault’s public API consistent with the new endpoint-first domain resolution.
68-118: Max message size & TTL setters map cleanly onto new globals
connectionStateTtl,maxMessageSize,maxSandboxMessageSize,maxProductionMessageSizeand the corresponding setters correctly manipulate the new global variables, with DEBUG builds using sandbox limits and release builds using production limits. Synchronization via@synchronized(self)is sufficient for these rarely-mutated shared values.
128-130:connectivityCheckUrlhelper is simple and sufficientReturning a formatted
https://URL fromARTDefaultConnectivityCheckUrlis straightforward and matches howARTRest.internetIsUpis expected to work. No issues here..github/workflows/integration-test.yaml (1)
32-36: CIABLY_ENVvalue now matches endpoint-style nonprod routing policyUpdating
ABLY_ENVtononprod:sandboxis consistent withgetTestEndpoint()(which usesABLY_ENVwhenABLY_ENDPOINTis unset) and with the new non-production routing-policy semantics from ADR-119. This should exercise the DomainSelector nonprod path in integration tests as intended.Source/ARTWebSocketTransport.m (4)
69-71: Introducing_hostivar is a good decoupling stepStoring the host separately from
optionsavoids the previous pattern where the transport mutated client options just to change its hostname. This makes later host overrides (e.g. fallbacks) more straightforward.
76-90: Initializing_hostfromoptions.domainSelector.primaryDomainis correctSeeding
_hostwithoptions.domainSelector.primaryDomainat construction ensures WebSocket transports always use the REC1-resolved primary domain by default, independent of legacy rest/realtimeHost options. Copyingoptionsafterward keeps other option values stable for the lifetime of the transport.
195-208: WebSocket URL now correctly respects the transport’s current hostSwitching to:
NSURL *url = [urlComponents URLRelativeToURL:[options realtimeUrlForHost:self.host]];means reconnects and fallbacks that call
setHost:will correctly affect subsequent WebSocket URLs without mutating options. This aligns the transport’s behavior with the new DomainSelector-driven host model.
251-257: Host getter/setter now operate purely on transport-local state
setHost:andhostnow wrap the_hostivar instead of reading/writing client options. That keeps responsibility for the active host within the transport and avoids side effects on shared configuration.Source/include/Ably/ARTDefault.h (1)
13-17: Public API additions for fallback hosts align with DomainSelector usageExposing
+fallbackHostsand+fallbackHostsWithEnvironment:here matches the implementation inARTDefault.mand gives SDK consumers a REC2-compliant way to inspect fallback behavior. Using the samerestHost/realtimeHostsignatures keeps the public surface stable.Source/PrivateHeaders/Ably/ARTDefault+Private.h (1)
3-14: Private default and connectivity accessors are consistent with implementationDeclaring
ARTDefaultProductionEnvironment, the max message size accessors,setConnectionStateTtl:,setMaxMessageSize:, andconnectivityCheckUrlhere matches the backing symbols and methods inARTDefault.m. This provides the expected private hooks for tests and internal helpers without altering the public surface.Source/ARTRealtime.m (3)
1497-1519: Fallback reconnection behavior remains consistent with new host source
reconnectWithFallbackstill operates on_fallbacksas before, and only the population of_fallbackshas changed elsewhere to usedomainSelector.fallbackDomains. The reconnect sequence itself (internet check, prioritizedHost override, reconnect) is unchanged and remains compliant with RTN17’s requirements.
1521-1528: RSC15m check now correctly depends on the presence of DomainSelector fallbacksUpdating
shouldRetryWithFallbackForError:options:to:// RSC15m return options.domainSelector.fallbackDomains.count > 0;ensures fallback retries are only considered when the configured endpoint/env/hosts actually yield a non-empty fallback set. This nicely decouples RSC15m from legacy ARTFallbackHosts logic and aligns it with ADR-119’s endpoint semantics.
1671-1688: Realtime fallback initialization now usesdomainSelector.fallbackDomainsWhen the transport fails with a retryable error and no
_fallbacksexist, initializing:NSArray *hosts = clientOptions.domainSelector.fallbackDomains; _fallbacks = [[ARTFallback alloc] initWithFallbackHosts:hosts shuffleArray:clientOptions.testOptions.shuffleArray];replaces ARTFallbackHosts-based lookup with DomainSelector-derived fallbacks. This maintains shuffle behavior and cleanly supports:
- Default/routing-policy/env endpoints → generated fallback list.
- Hostname/legacy host → empty fallback list, skipping fallback attempts.
Behavior for empty
_fallbacksis already handled below, so this change looks correct.Test/AblyTests/Tests/RealtimeClientTests.swift (1)
156-157: Appropriate deprecation annotation for testing deprecated API.The
@available(*, deprecated, ...)annotation correctly suppresses compiler warnings while testing the deprecatedrealtimeHostproperty. The message clearly documents why this test is marked deprecated and when the annotation should be removed.Source/PrivateHeaders/Ably/ARTDomainSelector.h (1)
1-42: Well-designed domain selector interface.The
ARTDomainSelectorinterface is clean and well-documented:
- Clear parameter documentation for the initializer
- Appropriate use of nullable annotations
- Correctly disables default
initwithNS_UNAVAILABLE- Read-only properties for
primaryDomainandfallbackDomainsalign with the REC1/REC2 specificationsThe static analysis error about
Foundation/Foundation.hnot being found is a false positive from running clang in isolation without the proper SDK paths.Source/ARTRest.m (3)
376-382: Correct fallback retry expiration handling with domain selector.The reset logic correctly clears
currentFallbackHostandprioritizedHost, then switches back todomainSelector.primaryDomainwhen the fallback retry timeout expires. This aligns with RSC15f.
449-462: Fallback host resolution correctly migrated to ARTDomainSelector.The fallback mechanism now sources hosts from
domainSelector.fallbackDomainsinstead of the removedARTFallbackHosts. The RSC15j spec reference for setting theHostheader is correctly placed.
677-690: Connectivity check URL is now configurable per REC3a/REC3b.The implementation correctly uses
options.connectivityCheckUrlwith a fallback toARTDefault.connectivityCheckUrl. The inline spec references (REC3a, REC3b) help document the behavior.Test/AblyTests/Tests/RestClientTests.swift (6)
45-46: Test URL patterns correctly updated for new domain scheme.The fallback host tests now correctly expect
main.realtime.ably.netfor the primary domain andmain.[a-e].fallback.ably-realtime.comfor fallback domains, aligning with the ADR-119 implementation.
316-351: Comprehensive REC1b tests for endpoint option behavior.These tests properly validate the endpoint option behavior:
- REC1b1: Invalid to combine endpoint with deprecated options
- REC1b2: FQDN endpoint becomes primary domain directly
- REC1b3: Nonprod routing policy resolves correctly
- REC1b4: Production routing policy resolves correctly
The deprecation annotations are appropriately applied where deprecated APIs are used.
1069-1095: REC2c4 test now correctly validates all three request URLs.The assertion indices are now correct:
at(0)for the primary domain,at(1)andat(2)for the two fallback requests. This addresses the issue from the previous review where both fallback assertions incorrectly used index 1.
1185-1253: Comprehensive REC2c tests for implicit fallback domain resolution.These tests thoroughly validate the fallback domain resolution rules:
- REC2c1: Default fallback domains (main.[a-e].fallback.ably-realtime.com)
- REC2c2: Explicit domain → empty fallback set
- REC2c3: Nonprod routing policy fallback domains
- REC2c4: Production routing policy fallback domains
- REC2c5: Deprecated environment option fallback domains
- REC2c6: Deprecated restHost/realtimeHost → empty fallback set
181-181: TODO tracked in issue #2145.The TODO comment about the ineffective assertion is already tracked in GitHub issue #2145, as noted in the past review comments.
206-206: TODO tracked in issue #2145.Same as above - this TODO is covered by the existing GitHub issue.
Source/include/Ably/ARTClientOptions.h (3)
22-26: Well-documented endpoint property addition.The
endpointproperty documentation clearly explains the three accepted value types (routing policy name, nonprod routing policy name, or FQDN) and links to the Platform Customization documentation.
30-38: Consistent deprecation messaging for legacy host properties.Both
restHostandrealtimeHostare properly deprecated with clear messages directing users to theendpointoption. The@deprecatedDoxygen tag in the doc comment and theDEPRECATED_MSG_ATTRIBUTEmacro provide both documentation and compiler warnings.
61-64:connectivityCheckUrlis properly copied in-copyWithZone:.Verification confirms that the
connectivityCheckUrlproperty is correctly assigned at line 239 ofSource/ARTClientOptions.mwithin the-copyWithZone:method implementation (options.connectivityCheckUrl = self.connectivityCheckUrl;). No action needed.Test/AblyTests/Tests/RealtimeClientConnectionTests.swift (6)
24-56: Host and fallback expectations align with new endpoint semanticsThe updated regex expectations for the default primary host (
main.realtime.ably.net) and fallback pattern (main.[a-e].fallback.ably-realtime.com), along with the analogous checks in the RTN17h/REC2* tests, correctly reflect the ADR‑119 routing model and keep the assertions implementation‑agnostic via regex rather than hardcoding specific letters. This looks solid and gives good coverage of the new default/fallback behavior.Also applies to: 4055-4071, 4101-4111, 4180-4185
2180-2203: Usingendpointwith a non‑routable IP cleanly exercises timeout behaviorSwitching this test to drive failure via
options.endpoint = "10.255.255.1"rather than older host/environment knobs is a good fit with ADR‑119. It keeps the test focused on realtime request timeout behavior while validating the endpoint surface for IPs. No issues spotted here.
4189-4242: Coverage for primary host selection across prod/nonprod/test endpoints looks goodThe shared helper
_test__091__...every_connection_is_first_attempted_to_the_primary_host_main_realtime_ably_netplus the three concrete tests (default,nonprod:sandbox,test) nicely validate:
- Primary host is always hit first.
- Nonprod routing policy names map to the expected
*.realtime.ably-nonprod.net/*.fallback.ably-realtime-nonprod.comdomains.- Plain policy names (e.g.
"test") map to the*.realtime.ably.net/*.fallback.ably-realtime.comdomains.The use of regex and
dropFirst(8)fornonprod:is a reasonable way to keep tests robust against minor formatting changes.Also applies to: 4244-4260
4264-4353: Fallback retry ordering tests strongly exercise domain selector + REC3 behavior
_test__092__...retry_hosts_in_random_order_after_checkin(and the prod/sandbox/test variants) give thorough coverage of:
- Using
domainSelector.fallbackDomainsas the source of truth for retry ordering.- The interaction between the deterministic
shuffleArrayInExpectedHostOrdertest hook and the observed retry sequence.- The “internet up” check-in semantics between fallback host changes.
These tests appear well‑constructed and should quickly catch regressions in fallback ordering or REC3 connectivity‑check wiring.
4471-4512: EmptyfallbackHostscorrectly disables fallback behavior
test__095__...won_t_use_fallback_hosts_feature_if_an_empty_array_is_providedclearly documents and verifies that an explicitly emptyfallbackHostsarray disables fallback usage, even when the error type would otherwise qualify. The DataGatherer‑based assertion on connection attempts and states is precise and matches the intent described in the comments.
4837-4867: Reachability tests now correctly couple todomainSelector.primaryDomainUpdating the RTN20* tests to assert that
TestReachability.hostequalsclient.internal.options.domainSelector.primaryDomain(rather than legacy realtimeHost/environment fields) gives good end‑to‑end validation that:
- Reachability is monitoring the same primary domain that connections target under the endpoint/domain selector model.
- OS‑driven connectivity changes transition the connection state machine as expected in both CONNECTING and CONNECTED scenarios, and in DISCONNECTED/SUSPENDED with reconnection.
This is a clean way to exercise the new domain selection pipeline via existing reachability hooks.
Also applies to: 4871-4903, 4919-4937, 4947-5013
Source/PrivateHeaders/Ably/ARTClientOptions+Private.h (1)
7-8: ExposingdomainSelectorvia the private header simplifies callersForward‑declaring
ARTDomainSelectorand exposing a readonlydomainSelectorplusrealtimeUrlForHost:and+setDefaultEndpoint:onARTClientOptionsis a good cleanup compared to duplicating domain/fallback properties. It makes the intent behind domain selection clearer and keeps legacy host/environment handling encapsulated inARTClientOptions.m. No issues from the interface perspective.Also applies to: 18-26
Source/ARTClientOptions.m (2)
32-38: Endpoint and domainSelector initialization matches ADR‑119 defaultsThe new ivars for
_endpoint,_restHost,_realtimeHost,_environment, and_domainSelector, combined with initializing_endpointfrom the process‑wideARTDefaultEndpointand_connectivityCheckUrlfromARTDefault, look consistent with the ADR‑119 goal of centralizing domain selection. CachingARTDomainSelectorand resetting it whenever inputs change is a sensible approach.Also applies to: 48-54, 80-82
276-283: Domain selector invalidation on fallback changes is correctThe modifications to
setFallbackHosts:andsetFallbackHostsUseDefault:to reset_domainSelectorensure that any cached primary/fallback domains are recomputed when fallback configuration changes. This matches how you handle endpoint/environment/host changes and should keep the selector state consistent.Also applies to: 284-301
5caebe3 to
799fece
Compare
| @interface ARTDomainSelector () | ||
|
|
||
| @property (nonatomic) ARTPrimaryDomainSelectionPolicy primaryDomainSelectionPolicy; | ||
| @property (nonatomic, nullable) NSString *policyId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please can you document these properties and explain for which primaryDomainSelectionPolicy values they are valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source/ARTDomainSelector.m
Outdated
| The policy that the library will use to determine its REC1 primary domain. | ||
| */ | ||
| typedef NS_ENUM(NSInteger, ARTPrimaryDomainSelectionPolicy) { | ||
| /// REC1a: The `endpoint` client option has not been specified. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For each of these please can you document which of the policyId and hostname properties are relevant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's going on in this commit "Fix detection of production environment"? Is it fixing something that was already broken or just a mistake in the implementation of the endpoint client option?
Also:
- why are we re-parsing the
endpointclient option? - when would the "environment" client option start with
nonprod? - what's even the purpose of this check? it seems like we are trying to impose a different outbound message size check for non-production environments (introduced in 9f4bb56?) but why? Realtime already has the ability to communicate a different maxMessageSize, why do we need to hardcode anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was broken and you are right, neither this check not the property itself is not needed (and was not needed). I've removed both, but let me know if you would like to see it in a separated PR (now I think the maxMessageSize test value is not needed in the SDK code itself either and should be set before each test that relies on it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind the change being in this PR given that it's in a separate commit, but doesn't it need to go before the endpoint commit? otherwise (and I haven't checked which) presumably one of the following is the case:
- you've "fixed" the non-prod check in the
endpointcommit and then immediately removed it (seems unnecessary and as mentioned the previous "fix" for it didn't seem right), or - you've not fixed the non-prod check in the
endpointcommit in which case theendpointcommit is broken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we still seem to have ARTMaxProductionMessageSize and ARTMaxSandboxMessageSize if I'm reading correctly?
799fece to
68b842f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (4)
Source/ARTDefault.m (1)
24-53: Confirm DomainSelector configuration forfallbackHostsWithEnvironment:/restHost/realtimeHostAll three helpers construct a fresh
ARTDomainSelectorwithendpointClientOption:nil,fallbackHostsClientOption:nil, the supplied (or nil)environmentClientOption, andfallbackHostsUseDefault:false, then useprimaryDomain/fallbackDomains. That’s conceptually neat, but it subtly changes responsibility fromARTDefault/ARTFallbackHoststoARTDomainSelector. Please confirm that:
fallbackDomainsreturns the expected legacy fallbacks for production and non‑prod environments with this argument set (especially withfallbackHostsUseDefault:false), andrestHost/realtimeHostreturning the sameprimaryDomainmatches your compatibility expectations for any caller that previously relied on distinct defaults.If
ARTDomainSelectorTestsalready cover these static helpers, that should be sufficient.Source/ARTClientOptions.m (3)
22-38: Default endpoint & connectivity URL wiring are sensible; be aware of global stateInitialising
_endpointfrom the process‑wideARTDefaultEndpointand_connectivityCheckUrlfrom[ARTDefault connectivityCheckUrl]gives you a single place to adjust defaults (especially useful in tests). Just keep in mind that+setDefaultEndpoint:only affects instances created after the call; existingARTClientOptionswith their ownendpointordomainSelectorwon’t see that change. If this is intended purely for test setup, that behavior is fine, but it might be worth documenting that implicit lifecycle assumption where this helper is used.Also applies to: 48-54, 80-81
130-191: Legacyenvironment/restHost/realtimeHostconflict checks are strict; consider allowing clearsThe mutual‑exclusion rules between
endpoint,environment, and the legacy host options insetEnvironment:,setRestHost:, andsetRealtimeHost:are consistent with ADR‑119 and nicely symmetric. One side effect, though, is that these checks also run when the new value isnil(i.e. when a caller wants to clear an option), so you can’t currently “unset”environmentwhile anendpointis configured, or vice versa. That’s probably fine for production (options are usually set once), but it may be slightly awkward in tests or reconfiguration code. If you want to make clears cheaper, you could gate the conflict checks on non‑empty values, e.g. only run them whenenvironment/hostorendpointare non‑empty strings.
284-305: Fallback hosts invalidation and default endpoint API are coherent; watch for test cross‑talkResetting
_domainSelectorinsetFallbackHosts:andsetFallbackHostsUseDefault:ensuresprimaryDomain/fallbackDomainsalways reflect the latest legacy fallback settings, andhasEnvironmentcorrectly encapsulates the deprecatedenvironmentproperty. The simple+setDefaultEndpoint:setter forARTDefaultEndpointis fine for a process‑wide default, but because it’s a global mutable string, it’s easy for tests or embedding apps to accidentally leak configuration across call sites. If that becomes a problem, a follow‑up could introduce a small helper to save/restore the default endpoint around tests, or document a convention for doing so.Also applies to: 319-324
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
Source/ARTClientOptions.m(8 hunks)Source/ARTConnection.m(1 hunks)Source/ARTDefault.m(4 hunks)Source/PrivateHeaders/Ably/ARTClientOptions+Private.h(2 hunks)Source/PrivateHeaders/Ably/ARTDefault+Private.h(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- Source/PrivateHeaders/Ably/ARTDefault+Private.h
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: check (tvOS, test_tvOS18_4)
- GitHub Check: check (iOS, test_iOS18_4)
- GitHub Check: check (macOS, test_macOS)
- GitHub Check: check
- GitHub Check: build
- GitHub Check: check
🔇 Additional comments (5)
Source/ARTConnection.m (1)
199-203: Confirm environment‑agnosticmaxMessageSizedefaultWhen
_maxMessageSizeis unset, this now always returns[ARTDefault maxMessageSize], removing any prior prod vs sandbox branching. That looks consistent with the new endpoint/domain‑selector model, but it is a behavior change for clients relying on legacy sandbox defaults.Please double‑check that
ARTDefault.maxMessageSizematches the effective legacy defaults across all environments (prod, sandbox, nonprod), and consider adding a focused test to lock this behavior in.Source/PrivateHeaders/Ably/ARTClientOptions+Private.h (1)
7-24: Private exposure ofdomainSelector/URL helpers is consistent and minimalThe added
domainSelectorproperty,setDefaultEndpoint:class method, andrealtimeUrlForHost:helper line up cleanly with the implementation inARTClientOptions.mand keep the internal surface focused while supporting the new endpoint/domain‑selection flow. No changes requested.Source/ARTDefault.m (1)
6-13: Centralised defaults for API version, TTL, max message size, and connectivity URL look goodExtracting these values into module‑level constants and wiring
connectionStateTtl,maxMessageSize, andconnectivityCheckUrlthrough them makes the defaults clear and keeps the setters simple. The@synchronizedwrites on the scalar globals are adequate for the usual “tests tweak then restore” pattern. Please just double‑check that the concrete numbers forARTConnectionStateTtl,ARTMaxProductionMessageSize/ARTMaxSandboxMessageSize, and theARTDefaultConnectivityCheckUrlhost/path are what you intend per ADR/spec/backend limits.Also applies to: 67-81, 83-97, 107-109
Source/ARTClientOptions.m (2)
88-126: Endpoint / DomainSelector lazy initialisation and invalidation look correctThe
setEndpoint:implementation enforcing REC1b1 againsthasEnvironment/hasCustomRestHost/hasCustomRealtimeHost, coupled with resetting_domainSelectoron change, and the lazydomainSelectorthat’s constructed from_endpoint,_fallbackHosts,_environment,_restHost,_realtimeHost, and_fallbackHostsUseDefault, all hang together well.primaryDomain/fallbackDomainssimply forwarding intodomainSelectormakes the rest of the class read clearly. No changes requested here.
193-215: Host/URL builders now consistently useprimaryDomain; confirm it is always a bare host
restUrlComponentsusingself.primaryDomain(rather than a legacyrestHostresolver) andrealtimeUrldelegating torealtimeUrlForHost:self.primaryDomaincleanly route both REST and Realtime throughARTDomainSelector. This looks correct as long asprimaryDomainis always a hostname without scheme or port; ifendpointcan ever be configured as a full URL or include a port,NSURLComponents.hostwould misinterpret it. From the surrounding code it appearsendpointis intended to be a routing policy/FQDN/IP, not a URL, but it would be good to ensure tests cover odd cases like IPv6 literals and non‑standard ports.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
Source/ARTDomainSelector.m (1)
70-98: Endpoint “hostname vs routing policy” detection is still too naïve (IPv6 and IP literals)The
endpointClientOptionclassification still only looks for".","::"or"localhost"to decide it’s a hostname:if ([endpointClientOption containsString:@"."] || [endpointClientOption containsString:@"::"] || [endpointClientOption isEqualToString:@"localhost"]) { self.primaryDomainSelectionPolicy = ARTPrimaryDomainSelectionPolicyHostname; self.hostname = endpointClientOption; return; }This will misclassify several legitimate endpoint forms:
- IPv6 literals without
::, e.g.@"2001:db8:1:2:3:4:5:6"→ treated as a routing policy ID and turned into2001:db8:1:2:3:4:5:6.realtime.ably.net.- Bracketed IPv6 with ports, or IPv6 with zone IDs (e.g.
[::1]:8080,fe80::1%en0), which don’t necessarily contain"::"in the raw string you want to interpret.- Any future IP/host formats that don’t contain
"."but are still valid host tokens.Given
endpointis intended to accept fully qualified hostnames and IP literals as well as routing policy IDs, this heuristic can lead to incorrect primary domains and bypass the “no automatic fallback for explicit host/IP” rule.Consider:
- Normalizing potential IP literals (strip brackets, ports, zone IDs), then using
inet_ptonfor IPv4/IPv6 to decide “IP literal vs not”.- Treating “localhost” explicitly as you do now.
- Falling back to a “contains '.' → hostname” rule when it’s not an IP literal.
- Only then treating the residual cases as routing policy IDs (after the
nonprod:prefix check).This will avoid misclassifying valid IP endpoints as routing policies while keeping the current routing‑policy behavior intact.
🧹 Nitpick comments (1)
Source/ARTDomainSelector.m (1)
40-45: Minor docstring typos/wording forpolicyIdandhostnameTwo small nits:
- Line 40: “primary and fallback domains names” → “primary and fallback domain names”.
- Line 43: “Sores the value of the primary domain…” → “Stores the value of the primary domain…”.
Tidying these up will make the internal docs easier to read, especially since they explain which policies use
policyIdvshostname.-/// Prefix used for construction of the primary and fallback domains names (for `ARTPrimaryDomainSelectionPolicyNonProductionRoutingPolicy`, `ARTPrimaryDomainSelectionPolicyProductionRoutingPolicy` and `ARTPrimaryDomainSelectionPolicyLegacyEnvironment` values of ``primaryDomainSelectionPolicy``). +/// Prefix used for construction of the primary and fallback domain names (for `ARTPrimaryDomainSelectionPolicyNonProductionRoutingPolicy`, `ARTPrimaryDomainSelectionPolicyProductionRoutingPolicy` and `ARTPrimaryDomainSelectionPolicyLegacyEnvironment` values of ``primaryDomainSelectionPolicy``). @@ -/// Sores the value of the primary domain if assigned directly (for `ARTPrimaryDomainSelectionPolicyHostname` and `ARTPrimaryDomainSelectionPolicyLegacyHost` values of ``primaryDomainSelectionPolicy``). +/// Stores the value of the primary domain if assigned directly (for `ARTPrimaryDomainSelectionPolicyHostname` and `ARTPrimaryDomainSelectionPolicyLegacyHost` values of ``primaryDomainSelectionPolicy``).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
Source/ARTDomainSelector.m(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: build
- GitHub Check: check (macOS, test_macOS)
- GitHub Check: check (iOS, test_iOS18_4)
- GitHub Check: check (tvOS, test_tvOS18_4)
- GitHub Check: check
- GitHub Check: check
🔇 Additional comments (2)
Source/ARTDomainSelector.m (2)
124-145: Confirm LegacyEnvironment primary domain behavior vs ECO‑5551 requirementFor
ARTPrimaryDomainSelectionPolicyLegacyEnvironmentthe primary domain is:return [NSString stringWithFormat:@"%@.%@.%@", self.policyId, ARTDefaultRoutingSubdomain, ARTDefaultPrimaryTLD];i.e.
<environment>.realtime.ably.net.ECO‑5551 / ADR‑119 state that when legacy options are explicitly configured, existing primary/fallback hostname behavior should be preserved. Switching primary for
environmentfrom the legacy domains toably.netmay be intentional, but if “preserve” is meant literally here, this path might need to stay on the legacy primary TLD instead.Please double‑check this against ADR‑119 and the other language implementations (e.g. ably‑js / ably‑go) to ensure the LegacyEnvironment case matches the intended cross‑SDK behavior.
152-218: Fallback domain behavior and defaults look consistent with REC2The fallback logic appears coherent and matches the described REC2 behavior:
- Explicit
fallbackHostsClientOptionshort‑circuits everything else.- Deprecated
fallbackHostsUseDefaultreusesdefaultFallbackDomains.- Default policy and
LegacyEnvironment/routing‑policy cases generate fivea–efallbacks with the appropriate TLD:
- Default:
main.[a-e].fallback.ably-realtime.com.- Nonprod routing policy:
<policy>.[a-e].fallback.ably-realtime-nonprod.com.- Production + legacy environment:
<policy>.[a-e].fallback.ably-realtime.com.- Hostname/LegacyHost correctly return no automatic fallbacks.
defaultFallbackDomains’s comment now matches the actual return values.This aligns with the earlier REC2 description and the “no fallbacks for explicit hosts” requirement.
Closes #2098
Summary by CodeRabbit
New Features
Deprecations
Removals
Bug Fixes
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.