Skip to content

Commit 5a6e268

Browse files
committed
initial commit
0 parents  commit 5a6e268

16 files changed

+1128
-0
lines changed

.gitignore

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
### macOS ###
2+
# General
3+
.DS_Store
4+
.AppleDouble
5+
.LSOverride
6+
7+
# Icon must end with two \r
8+
Icon
9+
10+
11+
# Thumbnails
12+
._*
13+
14+
# Files that might appear in the root of a volume
15+
.DocumentRevisions-V100
16+
.fseventsd
17+
.Spotlight-V100
18+
.TemporaryItems
19+
.Trashes
20+
.VolumeIcon.icns
21+
.com.apple.timemachine.donotpresent
22+
23+
# Directories potentially created on remote AFP share
24+
.AppleDB
25+
.AppleDesktop
26+
Network Trash Folder
27+
Temporary Items
28+
.apdisk
29+
30+
### macOS Patch ###
31+
# iCloud generated files
32+
*.icloud
33+
34+
### Swift ###
35+
# Xcode
36+
#
37+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
38+
39+
## User settings
40+
xcuserdata/
41+
42+
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
43+
*.xcscmblueprint
44+
*.xccheckout
45+
46+
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
47+
build/
48+
DerivedData/
49+
*.moved-aside
50+
*.pbxuser
51+
!default.pbxuser
52+
*.mode1v3
53+
!default.mode1v3
54+
*.mode2v3
55+
!default.mode2v3
56+
*.perspectivev3
57+
!default.perspectivev3
58+
59+
## Obj-C/Swift specific
60+
*.hmap
61+
62+
## App packaging
63+
*.ipa
64+
*.dSYM.zip
65+
*.dSYM
66+
67+
## Playgrounds
68+
timeline.xctimeline
69+
playground.xcworkspace
70+
71+
# Swift Package Manager
72+
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
73+
# Packages/
74+
# Package.pins
75+
# Package.resolved
76+
# *.xcodeproj
77+
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
78+
# hence it is not needed unless you have added a package configuration file to your project
79+
.swiftpm
80+
81+
.build/
82+
83+
# CocoaPods
84+
# We recommend against adding the Pods directory to your .gitignore. However
85+
# you should judge for yourself, the pros and cons are mentioned at:
86+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
87+
# Pods/
88+
# Add this line if you want to avoid checking in source code from the Xcode workspace
89+
# *.xcworkspace
90+
91+
# Carthage
92+
# Add this line if you want to avoid checking in source code from Carthage dependencies.
93+
# Carthage/Checkouts
94+
95+
Carthage/Build/
96+
97+
# Accio dependency management
98+
Dependencies/
99+
.accio/
100+
101+
# fastlane
102+
# It is recommended to not store the screenshots in the git repo.
103+
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
104+
# For more information about the recommended setup visit:
105+
# https://docs.fastlane.tools/best-practices/source-control/#source-control
106+
107+
fastlane/report.xml
108+
fastlane/Preview.html
109+
fastlane/screenshots/**/*.png
110+
fastlane/test_output
111+
112+
# Code Injection
113+
# After new code Injection tools there's a generated folder /iOSInjectionProject
114+
# https://github.com/johnno1962/injectionforxcode
115+
116+
iOSInjectionProject/
117+
118+
### SwiftPackageManager ###
119+
Packages
120+
xcuserdata
121+
*.xcodeproj
122+
123+
124+
### SwiftPM ###

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) [2025] [TaeJoongYoon]
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Package.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// swift-tools-version: 5.9
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "SwiftWebSocketClient",
8+
platforms: [
9+
.macOS(.v12),
10+
.iOS(.v15),
11+
.tvOS(.v15),
12+
.watchOS(.v8)
13+
],
14+
products: [
15+
.library(
16+
name: "SwiftWebSocketClient",
17+
targets: ["SwiftWebSocketClient"]
18+
),
19+
],
20+
targets: [
21+
.target(
22+
name: "SwiftWebSocketClient"
23+
),
24+
.testTarget(
25+
name: "SwiftWebSocketClientTests",
26+
dependencies: ["SwiftWebSocketClient"]
27+
),
28+
]
29+
)

README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# SwiftWebSocketClient
2+
3+
[![Swift Version](https://img.shields.io/badge/Swift-5.9-orange.svg)]()
4+
[![Platform](https://img.shields.io/badge/Platform-iOS%20|%20macOS%20|%20watchOS%20|%20tvOS-blue.svg)]()
5+
[![License](https://img.shields.io/badge/License-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT)
6+
7+
A modern, easy-to-use WebSocket client for Swift, powered by Combine and async/await.
8+
9+
## ✨ Features
10+
11+
- [x] Modern Concurrency: Built with `async/await` and exposes streams via Combine `Publisher`.
12+
- [x] Protocol-Oriented: Easy to test and extend.
13+
- [x] Fluent Builder API: Intuitive and chainable configuration.
14+
- [x] Extensible with Decorators: Add custom functionalities like auto-reconnection with ease.
15+
- [x] Dual Backend Support: Choose between `URLSession` and `Network.framework`.
16+
17+
## 📋 Requirements
18+
19+
- iOS 15.0+ / macOS 12.0+ / watchOS 8.0+ / tvOS 15.0+
20+
- Swift 5.9+
21+
22+
## 📦 Installation
23+
24+
### Swift Package Manager
25+
26+
You can add SwiftWebSocketClient to your project by adding it as a package dependency.
27+
28+
1. In Xcode, open your project and navigate to **File > Add Packages...**
29+
2. Paste the repository URL: `https://github.com/TaeJoongYoon/SwiftWebSocketClient.git`
30+
3. Follow the prompts to add the package to your target.
31+
32+
## 🚀 Quick Start
33+
34+
Here's a basic example of how to connect and listen for messages:
35+
36+
```swift
37+
import SwiftWebSocketClient
38+
import Combine
39+
40+
let url = URL(string: "wss://yourwebsocketserver.com")!
41+
let client = WebSocketBuilder(url: url).build(implementation: .urlSession)
42+
43+
var cancellables = Set<AnyCancellable>()
44+
45+
// Subscribe to status updates
46+
client.status
47+
.sink { status in
48+
print("Connection Status: \(status)")
49+
50+
// When connected, send a message.
51+
if case .connected = status {
52+
Task {
53+
print("Sending 'Hello' to the server.")
54+
try? await client.send(message: .string("Hello from Swift!"))
55+
}
56+
}
57+
}
58+
.store(in: &cancellables)
59+
60+
// Subscribe to incoming messages
61+
client.message
62+
.sink { message in
63+
switch message {
64+
case .string(let text):
65+
print("Received message: \(text)")
66+
case .data(let data):
67+
print("Received data: \(data.count) bytes")
68+
}
69+
}
70+
.store(in: &cancellables)
71+
72+
// Connect to the server
73+
client.connect(autoListen: true)
74+
```
75+
76+
## 📄 License
77+
78+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// WebSocketBuilder+Configuration.swift
3+
// SwiftWebSocketClient
4+
//
5+
// Created by Tae joong Yoon on 9/5/25.
6+
//
7+
8+
import Foundation
9+
10+
public extension WebSocketBuilder {
11+
/// A protocol for configurations that can be applied to a `WebSocketClient`.
12+
///
13+
/// Each configuration is responsible for wrapping the client with a `Decorator`
14+
/// to add new functionality, such as automatic reconnection or logging.
15+
protocol Configuration {
16+
/// Applies the configuration to a given `WebSocketClient` instance.
17+
///
18+
/// - Parameter client: The client to be configured or wrapped.
19+
/// - Returns: A new `Decorator` instance that wraps the client.
20+
func apply(to client: WebSocketClient) -> any Decorator
21+
}
22+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// WebSocketBuilder+Decorator.swift
3+
// SwiftWebSocketClient
4+
//
5+
// Created by Tae joong Yoon on 9/5/25.
6+
//
7+
8+
import Foundation
9+
import Combine
10+
11+
public extension WebSocketBuilder {
12+
/// A protocol that enables the Decorator pattern for `WebSocketClient`.
13+
///
14+
/// Conforming to this protocol provides default implementations for all `WebSocketClient` methods,
15+
/// forwarding calls to the underlying `client`. This allows you to create decorators
16+
/// by only overriding the methods you need to customize.
17+
protocol Decorator: WebSocketClient {
18+
/// The underlying `WebSocketClient` instance that this decorator enhances.
19+
var client: WebSocketClient { get }
20+
}
21+
}
22+
23+
/// Default implementations that forward calls to the wrapped client.
24+
public extension WebSocketBuilder.Decorator {
25+
var status: AnyPublisher<WebSocketStatus, Never> { client.status }
26+
var message: AnyPublisher<WebSocketMessage, Never> { client.message }
27+
28+
func connect(autoListen: Bool) {
29+
client.connect(autoListen: autoListen)
30+
}
31+
32+
func ping() async throws {
33+
try await client.ping()
34+
}
35+
36+
func send(message: WebSocketMessage) async throws {
37+
try await client.send(message: message)
38+
}
39+
40+
func listen() async throws {
41+
try await client.listen()
42+
}
43+
44+
func disconnect(closeCode: WebSocketCloseCode, reason: Data?) {
45+
client.disconnect(closeCode: closeCode, reason: reason)
46+
}
47+
}

0 commit comments

Comments
 (0)