Add WebSocketClient and BonjourService targets#15
Open
luizmb wants to merge 5 commits into
Open
Conversation
Adds two new SPM targets with dual Combine / DeferredTask+DeferredStream APIs, designed to compile on Linux even where the functionality is unavailable. WebSocketClient - WebSocketMessage (unconditional) — platform-agnostic text/data enum - WebSocketConnection (unconditional) — lazy struct of DeferredTask/DeferredStream closures; no Apple types in the public surface - WebSocketConnection+Darwin.swift (#if canImport(Darwin)) — URLSessionWebSocketTask implementation using callback-recursive receive (no async/await in the stream) - Combine: WebSocket, WebSocketPublisher, URLSessionWebSocketTaskProtocol — all under #if canImport(Combine); publisher output is WebSocketMessage for consistency with the async API BonjourService - BonjourServiceInfo, BonjourConnection (unconditional) — platform-agnostic browse result and TCP connection structs; NWEndpoint and NWConnection never appear in the public API - BonjourBrowseEvent, BonjourListenEvent + error types (unconditional) — use only BonjourServiceInfo, BonjourConnection, UInt16 - NWBrowserStream, NWListenerStream (#if canImport(Network)) — convert NW types to platform-agnostic events; BonjourConnection+Darwin wraps NWConnection with the same callback-recursive pattern as WebSocketReceiveDelegate - Combine: NWBrowserPublisher, NWListenerPublisher, NWEndpointPublisher, NetServiceBrowserPublisher, NetServicePublisher — all under #if canImport(Combine) - Extensions: NWBrowser+Extensions (#if canImport(Network)), NetService+Extensions (#if canImport(Darwin)); IP.swift (#if canImport(Network)) with FoundationExtensions dependency inlined as two private Data helpers Core - DemandBuffer vendored from CombineExt with package access level so both WebSocketClient and BonjourService Combine publishers can share it without making it part of the public API Package.swift - Removes external CombineWebSocket and CombineBonjour dependencies; NetworkTools is now self-contained - Adds Core dependency to both new targets README updated with full Combine and async examples for all four browse/advertise × Combine/async combinations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- opening_brace: double-space before { in stop() methods → single space
- statement_position: else on new line → same line as closing brace
- multiline_arguments / multiline_parameters: one arg per line
- cyclomatic_complexity: extract browseResultsChangedHandler body into
handle(change:) method in NWBrowserStreamDelegate
- superfluous_disable_command: remove discouraged_optional_collection and
type_name disable comments that no longer triggered
- discouraged_optional_collection: use block-level disable/enable in
NWBrowser+Extensions wrapping the extension
- trailing_newline: remove extra blank line from BonjourBrowseEvent.swift
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both types were structs of closures, which has two problems: 1. Freely copyable — multiple "owners" sharing one resource with no clear responsibility for closing it. 2. close was a DeferredTask<Void> stored property — nothing calls it unless the caller remembers to .run() it; no RAII, no cleanup on scope exit. Fix: make both final classes with a cancelAction: () -> Void and deinit that fires the action. The connection now lives exactly as long as someone holds a reference, mirroring how Combine's WebSocket closes when its AnyCancellable is cancelled. - close() becomes a synchronous func (like AnyCancellable.cancel()) instead of a stored DeferredTask — explicit early close before last reference drops. - deinit provides automatic RAII-style cleanup. - send/receive/ping remain DeferredTask/DeferredStream stored properties for FP composability; only close is imperative. Also: - Extract private makeConnection(_:) helper in WebSocketConnection+Darwin to eliminate factory code duplication across the three URL overloads. - Add webSocketConnection(with:protocols:) overload to match the Combine API. - Update README close() example. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ption bridge bonjourBrowserStream(browser: NWBrowser): - Adds a pre-built-browser overload alongside the serviceType convenience. - Refactors NWBrowserStreamDelegate to take an NWBrowser in its designated init; the serviceType variant now delegates to bonjourBrowserStream(browser:). bonjourResolve(_:timeout:) -> DeferredTask<Result<ResolvedServiceInfo, Error>>: - New ResolvedServiceInfo (unconditional) carries host, ips, port, txt as platform-agnostic types (strings and UInt16). - Convenience webSocketURL(path:secure:) on ResolvedServiceInfo builds a ws:// / wss:// URL from the resolved address, handling IPv6 brackets. - BonjourResolve+Darwin.swift implements resolution via NetService internally (the only API that resolves a name to IP without opening a connection); the public API is clean and platform-agnostic. - NetServiceResolveDelegate uses a self-retention pattern (strongSelf) to stay alive across the withCheckedContinuation suspension. ServiceDescription ↔ BonjourServiceInfo bridge: - init(_: BonjourServiceInfo) on ServiceDescription for Combine callers. - init(_: ServiceDescription) on BonjourServiceInfo for async callers. Fix four new lint violations introduced by class rewrites: - vertical_whitespace_opening_braces in WebSocketConnection and BonjourConnection. - opening_brace double-space in stop() methods. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CombineWebSocketandCombineBonjourdependencies — NetworkTools is now self-containedWebSocketClienttarget: Combine (WebSocket,WebSocketPublisher) + async (WebSocketConnection) APIsBonjourServicetarget: Combine (NWBrowserPublisher,NWListenerPublisher, + 3 legacy NetService publishers) + async (bonjourBrowserStream,bonjourListenerStream) APIsDemandBuffer(from CombineExt) intoCorewithpackageaccess levelLinux / cross-platform design
Both targets compile on Linux even though the functionality is unavailable there. The public API surface never exposes Apple-specific types:
WebSocketMessageURLSessionWebSocketTask.MessageWebSocketConnectionBonjourServiceInfoNWEndpointBonjourConnectionNWConnectionBonjourBrowseEvent/BonjourListenEventNWEndpoint/NWConnectionApple implementations are guarded:
#if canImport(Darwin)—WebSocketConnection+Darwin.swift,NetService+Extensions.swift#if canImport(Network)— all NW* stream files,BonjourConnection+Darwin.swift,IP.swift#if canImport(Combine)— all Combine publishersA future Linux/NIO implementation would produce the same unconditional types from different factories with no API change for callers.
Test plan
swift buildsucceeds with zero errors and warnings on macOSWebSocketConnectionandBonjourConnectionstructs are referenceable without any platform guardWebSocketMessage/BonjourBrowseEvent/BonjourListenEventbonjourListenerStreamyieldsBonjourListenEvent.newConnection(BonjourConnection)—NWConnectionis not visible to callersCore,NetworkClient,NetworkServer,Multipeer) still build cleanly🤖 Generated with Claude Code