Skip to content

Keep realtime retrieval from changing the options of HTTP channels - #1376

Merged
sacOO7 merged 4 commits into
integration/v2from
fix/realtime-rest-channel-options
Nov 2, 2026
Merged

sacOO7 merged 4 commits into
integration/v2from
fix/realtime-rest-channel-options

Conversation

@sacOO7

@sacOO7 sacOO7 commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

On a realtime client, five retrieval calls went over REST through HttpClient.Channels.Get(name, realtimeOptions):

  • channel history;
  • presence history;
  • GetMessageAsync;
  • GetMessageVersionsAsync;
  • Annotations.GetAsync.

That call replaces the options of the app's own channel of the same name (RSN3c). Two things went wrong as a result:

  • If the app had set a cipher on client.HttpClient.Channels.Get("x", cipherOptions) and its realtime channel "x" had none, any of those calls dropped the cipher. The app's next REST publish on that channel then went out unencrypted, without any error.
  • In reverse, an encrypted realtime channel started encrypting a plain HTTP channel's publishes.

This is new in 2.0. 1.x used Get(name) without options, and the message, version and annotation retrieval APIs didn't exist. CodeRabbit raised it on #1371. History joined this path in that PR.

Fix

RealtimeChannel.RestChannel now returns the realtime channel's own HttpChannel, built from its current options. It never reads or writes HttpClient.Channels. All five paths go through it.

The instance is cached and rebuilt when the channel's options change. Every change (SetOptions, Channels.Get with options) replaces the ChannelOptions object, and HttpChannel reads its options on each request, so comparing references is enough to never use stale options. A concurrent rebuild only creates a spare instance with the same options. Next pages keep the options of the call that started them.

Tests

ChannelSpecs+RetrievalHttpChannelOptionsSpecs has 2 theories, each run over 6 retrievals: history, history with untilAttach, presence history, GetMessage, GetMessageVersions and annotations get.

  • App cipher, plain realtime channel: the app's HTTP channel object and its options are unchanged, and its next PublishAsync is still encrypted (cipher+aes-256-cbc).
  • Encrypted realtime channel, plain app channel: the app's options are unchanged, and its publish stays plaintext.

Two more tests cover the cache:

  • the instance is reused while the options are unchanged;
  • after SetOptions or Channels.Get with a new cipher, history, presence history and GetMessage decode with the new cipher.

With a cache that never rebuilds, the second test fails.

On integration/v2 without the fix, the 12 HttpClient-channel cases fail. With it, they pass. The existing HistoryAsync_OnACipherChannel_ShouldDecryptTheRetrievedMessages still passes.

CHANGELOG / docs

2.0 "Fixed bugs" in CHANGELOG.md:

  • realtime channel and presence history on a cipher channel decrypt with the realtime channel's options, without changing the HttpClient channel of the same name;
  • HistoryAsync(query, untilAttach: true) no longer modifies the caller's PaginatedRequestParams, and it replaces any fromSerial the caller set;
  • the AuthCallback bullet now says "a string token field".

There is no separate entry for the options replacement itself, because it never shipped.

The untilAttach XML doc on IRealtimeChannel.HistoryAsync(PaginatedRequestParams, bool) now says the attach serial replaces a caller-supplied fromSerial.

Token request docs (#23)

This is a separate docs-only commit:

  • UPDATING.md: the CreateTokenRequestObject[Async] row says that CreateTokenRequest[Async] returns the signed token request as a JSON string. Return it unchanged from an AuthUrl endpoint (as application/json) or from an AuthCallback, and use JsonHelper.Deserialize<TokenRequest> / JsonHelper.Serialize for a TokenRequest object.
  • CHANGELOG.md: the [Obsolete]-removed bullet names the replacement, and the 1.2.15 date is corrected to 2024-04-19, its tag date.
  • README.md: shows JsonHelper.Deserialize<TokenRequest>(tokenRequest). The snippet compiles and runs.
  • IAblyAuth XML docs: say the result is JSON.

The code is unchanged by decision. CreateTokenRequest[Async] returns JSON on purpose: issues 110 and 1268, where serialising the object with other serializers produced bodies Ably rejects.

Validation

  • ./build.sh --target=Build.NetStandard: 0 warnings, 0 errors.
  • ./build.sh --target=Test.NetStandard.Unit.WithRetry --framework=net6.0: 1507 total, 1488 passed, 19 skipped, 0 failed.
  • An independent validator passed 246c1d26. It reproduced the bug in both directions for all five paths, failing without the fix and passing with it, and checked that next pages keep the original call's options. It also reviewed both commits and the docs against the code.
  • An independent validator passed 25994387, the cache. It checked that no options state goes stale, ran a concurrent stress test with SetOptions, and ran the reproductions for the original code, the per-call version, a cache that never rebuilds, and the final code.

Spec and ably-java context

  • RSN3c: Channels#get(name, options) updates an existing channel's options. It is soft-deprecated, and it describes the app's own call. Nothing in the spec asks the library to apply realtime options to the app's REST channels.
  • ably-java's realtime history and presence history decode with the realtime channel's options on each request, and message retrieval and annotations use their own objects built from those options. None of them touch the REST channel cache.

Summary by CodeRabbit

  • Bug Fixes

    • Realtime history and other retrieval operations now use the channel’s current options, including encryption settings. Updated options are applied to subsequent requests.
    • History queries with untilAttach: true use the attach serial instead of any supplied fromSerial, without changing the caller’s query parameters.
  • Documentation

    • Clarified that token-request methods return signed request JSON and how to deserialize and handle it.
    • Updated migration guidance and corrected a release date in the changelog.

Realtime history, presence history, GetMessageAsync,
GetMessageVersionsAsync and Annotations.GetAsync go over REST with the
realtime channel's options. They got the REST channel from
HttpClient.Channels with those options, which replaces the options of
the app's channel of the same name (RSN3c). A realtime channel without
a cipher therefore removed the cipher the app had set on that HTTP
channel, so its later REST publishes went out unencrypted, and a
realtime channel with a cipher encrypted the publishes of a plain one.

Build a new HttpChannel from the realtime channel's current options for
each retrieval instead, as ably-java does without touching its REST
channels. Next pages keep the options of the call that started them.

Add tests for both directions over all the retrievals, document that
untilAttach replaces a caller-supplied fromSerial, and add the
CHANGELOG entries for the earlier history fixes. This bug never shipped,
so it gets no entry of its own.
Auth.CreateTokenRequest[Async] returns the signed token request as a
JSON string by design, so the docs now say so and explain how to use
it. UPDATING.md's row for the removed CreateTokenRequestObject[Async]
says to return the JSON unchanged from an AuthUrl endpoint (as
application/json) or an AuthCallback, and to use JsonHelper to turn it
into a TokenRequest and back, since other JSON serializers don't
produce Ably's token request format. The CHANGELOG bullet names the
replacement, the README shows the JsonHelper call, and the IAblyAuth
XML docs say the result is JSON.

Also correct the 1.2.15 release date in the CHANGELOG to 2024-04-19,
the date of its tag.
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 35 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 58cff1b9-eec1-4225-96b3-fde3488d7c77
Commits

Reviewing files that changed from the base of the PR and between 2599438 and f9ffcb3.

Files selected for processing: 1
  • CHANGELOG.md
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cd4c0619-8836-493f-afaf-5230de38c9ac
Commits

Reviewing files that changed from the base of the PR and between f09a40a and 2599438.

Files selected for processing: 7
  • CHANGELOG.md
  • README.md
  • UPDATING.md
  • src/Ably.PubSub.Shared/IAblyAuth.cs
  • src/Ably.PubSub.Shared/Realtime/IRealtimeChannel.cs
  • src/Ably.PubSub.Shared/Realtime/RealtimeChannel.cs
  • src/Ably.PubSub.Tests.Shared/Realtime/ChannelSpecs.cs

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The pull request updates token-request JSON guidance and examples. It also changes realtime channels to cache a REST channel tied to current channel options, with tests for retrieval reuse and option changes.

Changes

Token-request guidance

Layer / File(s) Summary
Token-request JSON contract and examples
src/Ably.PubSub.Shared/IAblyAuth.cs, CHANGELOG.md, UPDATING.md, README.md
API comments, migration guidance, and examples specify that token requests are JSON strings and describe how callers can deserialize and return them. The changelog also clarifies AuthCallback JSON interpretation and corrects the 1.2.15 release date.

Realtime REST-channel retrieval

Layer / File(s) Summary
REST-channel ownership and option updates
src/Ably.PubSub.Shared/Realtime/IRealtimeChannel.cs, src/Ably.PubSub.Shared/Realtime/RealtimeChannel.cs, src/Ably.PubSub.Tests.Shared/Realtime/ChannelSpecs.cs, CHANGELOG.md
RealtimeChannel caches and reuses its REST channel while the options object remains unchanged, and replaces it when the options change. Tests cover retrieval reuse, option changes, and preservation of a same-name application HttpChannel. Documentation clarifies the untilAttach query behavior and realtime history options.

Estimated code review effort: 2 (Simple) | ~12 minutes

Change: Bug fix

Suggested reviewers: umair-ably

Merge Risk: Low · previous review 25994

The release note misstates how callback JSON containing an issued field is handled. Correcting it is a bounded documentation follow-up.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: preventing realtime retrieval from altering HTTP channel options.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 3 functions across 4 files. (3 skipped: 3 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Share

A rabbit checks the token string,
Then hops where realtime channels sing.
The REST path keeps its options near,
New settings bring a channel clear.
Soft paws approve the tested flow.

@sacOO7

sacOO7 commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

RestChannel built a new HttpChannel on every retrieval. Keep one per
realtime channel instead, rebuilt when the channel's options change.
Every change replaces the ChannelOptions object (the Options setter,
SetOptions, and Channels.Get with options), and HttpChannel reads its
options on each request, so comparing the cached channel's options
reference with the current one never leaves stale options in use. A
concurrent rebuild only creates a spare instance with the same
options. The cached channel is still separate from HttpClient.Channels.

Add tests that the instance is reused while the options are unchanged,
and that retrieval decodes with the new cipher after SetOptions or
Channels.Get with options.

@coderabbitai coderabbitai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

  • Fix comments: Fix CodeRabbit comments on this PR

Comment thread CHANGELOG.md Resolved
A JSON object returned from AuthCallback is also treated as a
serialized TokenDetails when it has an issued field (TokenDetails.IsToken),
not only when it has a string token field.
@sacOO7
sacOO7 merged commit fd4de79 into integration/v2 Nov 2, 2026
16 checks passed
@sacOO7
sacOO7 deleted the fix/realtime-rest-channel-options branch November 2, 2026 11:21

This branch was successfully deployed

1 active deployment
staging/pull/1376/features — f9ffcb3a Deployed Nov 2, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant