Skip to content

Native SSE streaming, browser profile sync, doc clarifications#23

Open
Bineroflux wants to merge 9 commits into
ErenKrt:masterfrom
Bineroflux:master
Open

Native SSE streaming, browser profile sync, doc clarifications#23
Bineroflux wants to merge 9 commits into
ErenKrt:masterfrom
Bineroflux:master

Conversation

@Bineroflux
Copy link
Copy Markdown

@Bineroflux Bineroflux commented May 1, 2026

All code in this PR was written by Claude Code. My role was
iterating with it — making sure each fix shipped with regression
tests and that the cumulative result works in actual production
use using the updated version of the bogdanfinn/tls-client go
library from bogdanfinn/tls-client#247

Summary of changes

Nine commits across three themes:

1. Native SSE / streaming response support

  • Four new methods on NativeTlsClientRequestStream, ReadStream, ReadStreamAll, CancelStream (sync + *Async). Wraps the matching requestStream / readStream / readStreamAll / cancelStream exports that landed in Streaming response support for the cffi interface + bug fixes bogdanfinn/tls-client#247.
  • docs/streaming.md — full usage guide: SSE example, Content-Type-based branching, timeoutMs semantics, single-thread tunneling notes, tuning, troubleshooting. readme.md links to it.

2. Sync to tls-client v1.14.0

  • New profiles: Chrome 146, Brave 146 (new family), Safari iOS 26, Firefox 148 (+ _PSK variants where applicable).
  • Removed stale Chrome132 constant — was silently downgrading every default-constructed BaseTlsClient to Chrome 133 at runtime because the upstream mapper falls back to DefaultClientProfile for unknown ids. Default ctor now uses Chrome133 explicitly. UA literals across the tree aligned to Chrome/133 + OPR/118 for coherence.
  • Wrapper version bump: 1.2.0-stream.1. tls-client-version.txtv1.14.0-stream.

3. Test infra + doc clarifications

  • New NativeTestSetup helper reads TLS_CLIENT_NATIVE_DLL (with a fallback to the existing hardcoded path so the maintainer workflow keeps working). Lets contributors / CI run the Native suites by setting one env var.
  • Should_Override_Host now resolves httpbin.org's IP via DNS at runtime instead of hardcoding a stale AWS LB address that no longer routes there. The IP-based connection is structurally necessary — using a domain would silently mask broken host-override code.
  • New XML docs on the previously-undocumented Request.StreamOutput* properties and the timeout knobs (TimeoutMilliseconds, TimeoutSeconds, TlsClientOptions.Timeout).
  • TimeSpan.Zero is not "no timeout" — corrects several places that gave that advice. The native side now uses the convention "negative value disables the deadline"; Timeout.InfiniteTimeSpan is the right idiom for long-lived SSE streams. 0 keeps meaning "use the 30 s default".

Final notes

  • Every commit has a detailed message explaining why the change was
    made — the root cause, the regression scenario it fixes, what the
    resulting code contractually guarantees, and (where applicable) the
    verification recipe used to prove the regression test catches the
    bug.
  • This PR bundles eight commits. If the scope is too large to land as
    a single unit, the commits are intentionally structured to stand on
    their own: each fix and the streaming feature can be cherry-picked
    or merged in isolation. Pick and choose whichever subset you're
    comfortable taking.
  • The streaming methods depend on the matching native exports
    (requestStream / readStream / readStreamAll / cancelStream)
    that I have also submitted as a separate PR against
    bogdanfinn/tls-client.
    If you'd rather wait for that PR to land before merging this one,
    the wrapper still loads cleanly against a native DLL that doesn't
    export them — NativeTlsClient.IsStreamingSupported reports false
    and the four streaming methods throw a clear
    EntryPointNotFoundException on first use; nothing else regresses.
  • I bumped the package version to 1.2.0-stream.1 so my local
    prerelease feed wouldn't collide with whatever you might tag. Feel
    free to rewrite <Version> in TlsClient.Core.csproj and
    TlsClient.Native.csproj

Bineroflux and others added 9 commits April 29, 2026 22:34
NativeTlsClient gains four new methods (sync + async) backed by the
requestStream / readStream / readStreamAll / cancelStream exports of
the native tls-client library:

  RequestStream   returns once headers are available, body stays open
  ReadStream      polls the next chunk; supports timeout heartbeats
  ReadStreamAll   drains the rest as a normal Response, for the case
                  where Content-Type turns out NOT to be a stream
  CancelStream    idempotent teardown

The existing Request() path is unchanged. Older native libraries that
don't expose the streaming entry points still load; IsStreamingSupported
reports false and streaming methods throw a clear error on first use.

  * TlsClient.Core: new request/response models (ReadStreamRequest,
    ReadStreamAllRequest, CancelStreamRequest, StreamStartResponse,
    StreamChunkResponse with EOF / Timeout / Error states and a
    GetChunkBytes helper)
  * docs/streaming.md (linked from readme.md): usage guide covering
    SSE, Content-Type-based branching, timeout semantics, single-
    thread tunneling, tuning knobs and troubleshooting
  * csproj versions bumped to 1.1.0-stream.1; GeneratePackageOnBuild
    removed in favour of explicit dotnet pack
  * prepare.js takes an optional PACKAGE_VERSION env var so generated
    platform packages can be stamped independently of TLS_CLIENT_VERSION
  * .gitignore excludes generated platform package dirs, build/temp
    DLL staging, local-nuget pack output, and ad-hoc /runtimes/ staging

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…client identifiers

Sync TlsClientIdentifier with the latest entries in the underlying
tls-client library's MappedTLSClients map:

  Chrome146       chrome_146
  Chrome146Psk    chrome_146_PSK
  Brave146        brave_146           (new Brave region; Chromium-derived)
  Brave146Psk     brave_146_PSK
  SafariIos260    safari_ios_26_0
  Firefox148      firefox_148

Purely additive — no existing constants are removed and no defaults change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rome133

The chrome_132 entry was removed from the underlying tls-client library's
MappedTLSClients map at some point. Sending it across the FFI silently
falls through to DefaultClientProfile (currently Chrome_133), which means
every default-constructed BaseTlsClient was effectively running with a
Chrome 133 fingerprint while still claiming to be Chrome 132.

Align the C# layer with what the native side actually uses:

  * Remove TlsClientIdentifier.Chrome132 (no longer mapped by Go).
  * BaseTlsClient parameterless constructor now uses Chrome133, with the
    User-Agent bumped from Chrome/132.0.0.0 / OPR/117 to Chrome/133.0.0.0
    / OPR/118 to keep the identifier and UA coherent.
  * Update test fixtures (TlsTests, PerformanceTests) that referenced
    Chrome132 directly so the project still compiles.

This is a breaking change for anyone explicitly referencing
TlsClientIdentifier.Chrome132 in their own code. The behavioural impact
is nil — those callers were already getting Chrome 133 at runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e doc

Follow-up to the Chrome132 removal. Two related fixes:

  * Native.Tests/HeaderTests.Should_Add_UserAgent_Header_Default was
    asserting the default UA contains "Chrome/132.0.0.0 ... OPR/117.0.0.0",
    but BaseTlsClient's default ctor now emits the Chrome 133 / OPR 118
    string. The assertion was stale and would have failed at runtime.
    `dotnet build` did not catch it.
  * On the API side, the parameterless ApiTlsClient / ApiTlsClientOptions
    constructors paired Chrome133 (TLS identifier) with Chrome/132.0.0.0
    in the User-Agent — a fingerprint/UA mismatch that predates this
    cleanup but is fixed here for coherence with the Native default.

Changes:
  - ApiTlsClient(Uri, string) and ApiTlsClientOptions(Uri, string): UA
    bumped from Chrome/132.0.0.0 / OPR/117.0.0.0 to Chrome/133.0.0.0 /
    OPR/118.0.0.0.
  - Native and API HeaderTests UA literals updated to match the new
    defaults.
  - src/TlsClient.Api/README.md sample updated to match.
  - src/Providers/TlsClient.Provider.HttpClient/README.md: replaced
    `TlsClientIdentifier.Chrome132` (deleted by the previous commit, so
    the snippet would no longer compile) with `Chrome133`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every static fixture in TlsClient.Native.Tests and TlsClient.Native.RestSharp.Tests
hardcoded the maintainer's local path:

    NativeTlsClient.Initialize("D:\Tools\tls-client-windows-64-1.13.1.dll");

That makes the suites unrunnable for any contributor (or CI runner) that
doesn't replicate that exact path on disk. Introduce a tiny per-project
NativeTestSetup helper that reads the TLS_CLIENT_NATIVE_DLL environment
variable and falls back to the original hardcoded path when unset, so the
existing maintainer workflow keeps working unchanged.

Each fixture now calls:

    NativeTlsClient.Initialize(NativeTestSetup.DllPath);

Set TLS_CLIENT_NATIVE_DLL to the DLL produced for your platform before
running `dotnet test` to override the default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Underlying tls-client-version.txt rolls v1.11.2-stream → v1.14.0-stream,
picking up the new Chrome 146 / Brave 146 / Safari iOS 26 / Firefox 148
profiles, the Chrome 132 removal that already landed in this fork, and
the streaming-related TimeoutSeconds semantics tweak (negative value
disables the deadline for long-lived SSE streams).

The wrapper packages bump from 1.1.0-stream.1 to 1.2.0-stream.1:

  - TlsClient.Core
  - TlsClient.Native

Minor bump reflects the additive identifier surface (commit ccb2e31),
the breaking Chrome132 removal (commit 096333c), and the test-infra
refactor (commit 824cf46). The platform-specific package
(TlsClient.Native.win-x64) is regenerated from the template by
build/native-builder/prepare.js with PACKAGE_VERSION=1.2.0-stream.1
and is not tracked in git.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The fixture hardcoded 35.169.229.34 as the target IP. The IP is
structurally necessary — the test verifies that RequestHostOverride
actually populates the Host: header, which can only be observed when
the connection bypasses DNS (otherwise the header derives from the URL
and the override does nothing). The hardcoded value, though, was an
AWS load balancer address that no longer routes to httpbin, so the
test failed environmentally.

Resolve httpbin.org's current IPv4 address via Dns.GetHostAddresses at
test time. The test stays a real exercise of host override and now
self-heals when the upstream CDN rotates addresses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The three Stream* knobs on Request — StreamOutputPath, StreamOutputBlockSize
and StreamOutputEOFSymbol — were public surface but had no XML docs at all.
Now that the underlying native side reliably preserves binary content on
the stream-to-file path (tls-client commit 1322ba3 dropped the binary-
corrupting charset.NewReader from that path), capture the contract:

  StreamOutputPath
    • response body is streamed to disk; Response.Body is empty
    • bytes are byte-exact (post-fix) — including binary content
    • file is opened with O_APPEND, so a stale file is appended to
    • independent of the streaming RequestStream / ReadStream API

  StreamOutputBlockSize
    • per-Read buffer size on the native side
    • shared with the streaming API; defaults differ
      (1024 B stream-to-file / 4096 B streaming)

  StreamOutputEOFSymbol
    • optional completion sentinel for file tailers
    • ignored when StreamOutputPath is null

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tls-client commit aa72213 reworked the cffi-side timeout resolver so
TimeoutSeconds / TimeoutMilliseconds now carries a three-way meaning:

    null / 0  → use the native default (30 s)
    > 0       → explicit deadline (whole request, including body reads)
    < 0       → disable the deadline entirely (long-lived SSE / streaming)

Several wrapper-side docs and snippets predated that change and told
callers to "set Timeout to 0" / "TimeSpan.Zero" to disable the
deadline. Under the new resolver that's wrong — 0 is interpreted as
"use the default", and a long SSE stream gets cut at 30 s. The fix is
to set the duration to a negative value; the idiomatic .NET sentinel
Timeout.InfiniteTimeSpan (-1 ms) round-trips correctly through
BaseTlsClient.PrepareRequest's (int)Options.Timeout.TotalMilliseconds.

  * docs/streaming.md
    - SSE example switches Timeout = TimeSpan.Zero → InfiniteTimeSpan
    - tuning table row spells out the three-way semantics explicitly
    - troubleshooting row corrects the "set it to 0" advice
  * NativeTlsClient.RequestStream XML doc
    - drops "set TimeoutMilliseconds to 0 (or a deliberately large
      value)"; explains the disable-via-InfiniteTimeSpan path
  * Request.TimeoutMilliseconds / TimeoutSeconds
    - new XML docs covering the three cases plus the InfiniteTimeSpan
      → -1 forwarding from TlsClientOptions
  * TlsClientOptions.Timeout
    - new XML doc calling out the TimeSpan.Zero foot-gun and pointing
      at Timeout.InfiniteTimeSpan for streaming

Behaviour-preserving for the wrapper itself; only docs change. Existing
callers that already pass a non-zero positive duration are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant