From c55ba7d74f51675cb37aabafd76c68051f1773bb Mon Sep 17 00:00:00 2001 From: OleH Date: Sat, 16 Dec 2023 09:57:17 +0100 Subject: [PATCH 1/2] Update README.md --- README.md | 308 ++++-------------------------------------------------- 1 file changed, 18 insertions(+), 290 deletions(-) diff --git a/README.md b/README.md index 438270f..fd2092f 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,13 @@ To install, simply: #### Swift Package Manager +#### ⚠️ For iOS before 13 version, please use 0.4.0 + Add the following line to your `Package.swift` ```swift // ... - .package(name: "ActionCableSwift", url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "0.3.2"), + .package(name: "ActionCableSwift", url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "1.0.0"), targets: [ .target( name: "YourPackageName", @@ -30,292 +32,8 @@ Add the following line to your `Package.swift` // ... ``` -#### Cocoa Pods - -Add the following line to your `Podfile` - -```ruby - pod 'ActionCableSwift' -``` - -and you can import ActionCableSwift - -```swift - import ActionCableSwift -``` # Usage ---- - -## Your WebSocketService should to implement the `ACWebSocketProtocol` protocol. - ---- - -### Use with [Websocket-kit](https://github.com/vapor/websocket-kit) - -#### I highly recommend not using Starscream to implement a WebSocket, because they have a strange implementation that does not allow conveniently reconnecting to a remote server after disconnecting. There is also a cool and fast alternative from the [Swift Server Work Group (SSWG)](https://swift.org/server/), package named [Websocket-kit](https://github.com/vapor/websocket-kit). - -[Websocket-kit](https://github.com/vapor/websocket-kit) is SPM(Swift Package Manager) client library built on [Swift-NIO](https://github.com/apple/swift-nio) - -Package.swift -```swift - // ... - dependencies: [ - .package(name: "ActionCableSwift", url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "0.3.0"), - .package(name: "websocket-kit", url: "https://github.com/vapor/websocket-kit.git", .upToNextMinor(from: "2.0.0")) - ], - targets: [ - .target( - name: "YourPackageName", - dependencies: [ - .product(name: "ActionCableSwift", package: "ActionCableSwift"), - .product(name: "WebSocketKit", package: "websocket-kit") - ]) - // ... -``` - - -or inside xcode - - -Снимок экрана 2020-08-28 в 14 05 21 - - -
- SPOILER: Recommended implementation WSS based on Websocket-kit(Swift-NIO) - - - This is propertyWrapper for threadsafe access to webSocket instance - - ```swift - import Foundation - - @propertyWrapper - struct Atomic { - - private var value: Value - private let lock = NSLock() - - init(wrappedValue value: Value) { - self.value = value - } - - var wrappedValue: Value { - get { return load() } - set { store(newValue: newValue) } - } - - func load() -> Value { - lock.lock() - defer { lock.unlock() } - return value - } - - mutating func store(newValue: Value) { - lock.lock() - defer { lock.unlock() } - value = newValue - } - } - - ``` - -This is implementation WSS - - ```swift -import NIO -import NIOHTTP1 -import NIOWebSocket -import WebSocketKit - -final class WSS: ACWebSocketProtocol { - - var url: URL - private var eventLoopGroup: EventLoopGroup - @Atomic var ws: WebSocket? - - init(stringURL: String, coreCount: Int = System.coreCount) { - url = URL(string: stringURL)! - eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: coreCount) - } - - var onConnected: ((_ headers: [String : String]?) -> Void)? - var onDisconnected: ((_ reason: String?) -> Void)? - var onCancelled: (() -> Void)? - var onText: ((_ text: String) -> Void)? - var onBinary: ((_ data: Data) -> Void)? - var onPing: (() -> Void)? - var onPong: (() -> Void)? - - func connect(headers: [String : String]?) { - - var httpHeaders: HTTPHeaders = .init() - headers?.forEach({ (name, value) in - httpHeaders.add(name: name, value: value) - }) - let promise: EventLoopPromise = eventLoopGroup.next().makePromise(of: Void.self) - - WebSocket.connect(to: url.absoluteString, - headers: httpHeaders, - on: eventLoopGroup - ) { ws in - self.ws = ws - - ws.onPing { [weak self] (ws) in - self?.onPing?() - } - - ws.onPong { [weak self] (ws) in - self?.onPong?() - } - - ws.onClose.whenComplete { [weak self] (result) in - switch result { - case .success: - self?.onDisconnected?(nil) - self?.onCancelled?() - case let .failure(error): - self?.onDisconnected?(error.localizedDescription) - self?.onCancelled?() - } - } - - ws.onText { (ws, text) in - self.onText?(text) - } - - ws.onBinary { (ws, buffer) in - var data: Data = Data() - data.append(contentsOf: buffer.readableBytesView) - self.onBinary?(data) - } - - }.cascade(to: promise) - - promise.futureResult.whenSuccess { [weak self] (_) in - guard let self = self else { return } - self.onConnected?(nil) - } - } - - func disconnect() { - ws?.close(promise: nil) - } - - func send(data: Data) { - ws?.send([UInt8](data)) - } - - func send(data: Data, _ completion: (() -> Void)?) { - let promise: EventLoopPromise? = ws?.eventLoop.next().makePromise(of: Void.self) - ws?.send([UInt8](data), promise: promise) - promise?.futureResult.whenComplete { (_) in - completion?() - } - } - - func send(text: String) { - ws?.send(text) - } - - func send(text: String, _ completion: (() -> Void)?) { - let promise: EventLoopPromise? = ws?.eventLoop.next().makePromise(of: Void.self) - ws?.send(text, promise: promise) - promise?.futureResult.whenComplete { (_) in - completion?() - } - } -} - ``` -
- ---- - -### Use with [Starscream](https://github.com/daltoniam/Starscream) - -```ruby - pod 'Starscream', '~> 4.0.0' -``` -
- SPOILER: If you still want to use "Starscream", then you can to copy this code for websocket client - -```swift -import Foundation -import Starscream - -class WSS: ACWebSocketProtocol, WebSocketDelegate { - - var url: URL - var ws: WebSocket - - init(stringURL: String) { - url = URL(string: stringURL)! - ws = WebSocket(request: URLRequest(url: url)) - ws.delegate = self - } - - var onConnected: ((_ headers: [String : String]?) -> Void)? - var onDisconnected: ((_ reason: String?) -> Void)? - var onCancelled: (() -> Void)? - var onText: ((_ text: String) -> Void)? - var onBinary: ((_ data: Data) -> Void)? - var onPing: (() -> Void)? - var onPong: (() -> Void)? - - func connect(headers: [String : String]?) { - ws.request.allHTTPHeaderFields = headers - ws.connect() - } - - func disconnect() { - ws.disconnect() - } - - func send(data: Data) { - ws.write(data: data) - } - - func send(data: Data, _ completion: (() -> Void)?) { - ws.write(data: data, completion: completion) - } - - func send(text: String) { - ws.write(string: text) - } - - func send(text: String, _ completion: (() -> Void)?) { - ws.write(string: text, completion: completion) - } - - func didReceive(event: WebSocketEvent, client: WebSocket) { - switch event { - case .connected(let headers): - onConnected?(headers) - case .disconnected(let reason, let code): - onDisconnected?(reason) - case .text(let string): - onText?(string) - case .binary(let data): - onBinary?(data) - case .ping(_): - onPing?() - case .pong(_): - onPong?() - case .cancelled: - onCancelled?() - default: break - } - } -} - -``` -
- ---- - -### Next step to use ActionCableSwift - - ```swift import ActionCableSwift @@ -324,7 +42,7 @@ let ws: WSS = .init(stringURL: "ws://localhost:3001/cable") /// action cable client let clientOptions: ACClientOptions = .init(debug: false, reconnect: true) -let client: ACClient = .init(ws: ws, options: clientOptions) +let client: ACClient = .init(stringURL: "ws://localhost:3001/cable", options: clientOptions) /// pass headers to connect /// on server you can get this with env['HTTP_COOKIE'] client.headers = ["COOKIE": "Value"] @@ -402,13 +120,23 @@ client.headers = [ --- -## Requirements +# If you want to implement your own WebSocket Provider, you should to implement the `ACWebSocketProtocol` protocol and use another initializator for ACClient -Any Web Socket Library, e.g. +```swift +import ActionCableSwift -[Websocket-kit](https://github.com/vapor/websocket-kit) +/// web socket client +let ws: YourWSS = .init(stringURL: "ws://localhost:3001/cable") + +/// action cable client +let clientOptions: ACClientOptions = .init(debug: false, reconnect: true) +let client: ACClient = .init(ws: ws, options: clientOptions) +``` -[Starscream](https://github.com/daltoniam/Starscream) +--- +## Requirements + +[Websocket-kit](https://github.com/vapor/websocket-kit) ## Author From 1de922318a324cd76fe88c8825eb56cfa30c16f4 Mon Sep 17 00:00:00 2001 From: OleH Date: Sat, 16 Dec 2023 09:57:46 +0100 Subject: [PATCH 2/2] Update README.md --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index fd2092f..cdedda8 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,6 @@ Add the following line to your `Package.swift` ```swift import ActionCableSwift -/// web socket client -let ws: WSS = .init(stringURL: "ws://localhost:3001/cable") - /// action cable client let clientOptions: ACClientOptions = .init(debug: false, reconnect: true) let client: ACClient = .init(stringURL: "ws://localhost:3001/cable", options: clientOptions)