Support Action Cable Swift development by giving a ⭐️
Action Cable Swift is a client library being released for Action Cable Rails 5 which makes it easy to add real-time features to your app. This Swift client inspired by "Swift-ActionCableClient", but it not support now and I created Action-Cable-Swift.
To install, simply:
Add the following line to your Package.swift
// ...
.package(name: "ActionCableSwift", url: "https://github.com/nerzh/Action-Cable-Swift.git", from: "0.3.2"),
targets: [
.target(
name: "YourPackageName",
dependencies: [
.product(name: "ActionCableSwift", package: "ActionCableSwift")
])
// ...
Add the following line to your Podfile
pod 'ActionCableSwift'
and you can import ActionCableSwift
import ActionCableSwift
ACWebSocketProtocol
protocol.Websocket-kit is SPM(Swift Package Manager) client library built on Swift-NIO
Package.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
This is propertyWrapper for threadsafe access to webSocket instance
import Foundation
@propertyWrapper
struct Atomic<Value> {
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
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<Void> = 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](https://raw.github.com/nerzh/Action-Cable-Swift/master/data))
}
func send(data: Data, _ completion: (() -> Void)?) {
let promise: EventLoopPromise<Void>? = ws?.eventLoop.next().makePromise(of: Void.self)
ws?.send([UInt8](https://raw.github.com/nerzh/Action-Cable-Swift/master/data), promise: promise)
promise?.futureResult.whenComplete { (_) in
completion?()
}
}
func send(text: String) {
ws?.send(text)
}
func send(text: String, _ completion: (() -> Void)?) {
let promise: EventLoopPromise<Void>? = ws?.eventLoop.next().makePromise(of: Void.self)
ws?.send(text, promise: promise)
promise?.futureResult.whenComplete { (_) in
completion?()
}
}
}
pod 'Starscream', '~> 4.0.0'
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
}
}
}
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(ws: ws, options: clientOptions)
/// pass headers to connect
/// on server you can get this with env['HTTP_COOKIE']
client.headers = ["COOKIE": "Value"]
/// make channel
/// buffering - buffering messages if disconnect and flush after reconnect
let channelOptions: ACChannelOptions = .init(buffering: true, autoSubscribe: true)
/// params to subscribe passed inside the identifier dictionary
let identifier: [String: Any] = ["key": "value"]
let channel: ACChannel = client.makeChannel(name: "RoomChannel", identifier: identifier, options: channelOptions)
// !!! Make sure that the client and channel objects is declared "globally" and lives while your socket connection is needed
channel.addOnSubscribe { (channel, optionalMessage) in
print(optionalMessage)
}
channel.addOnMessage { (channel, optionalMessage) in
print(optionalMessage)
}
channel.addOnPing { (channel, optionalMessage) in
print("ping")
}
/// Connect
client.connect()
client.addOnConnected { (headers) in
try? channel.subscribe()
}
func addOnMessage(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
func addOnSubscribe(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
func addOnUnsubscribe(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
func addOnRejectSubscription(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
func addOnPing(_ handler: @escaping (_ channel: ACChannel, _ message: ACMessage?) -> Void)
// Send an action
channel.addOnSubscribe { (channel, optionalMessage) in
try? channel.sendMessage(actionName: "speak", params: ["test": 10101010101])
}
client.headers = [
"Authorization": "sometoken"
]
Any Web Socket Library, e.g.
Me
ActionCableSwift is available under the MIT license. See the LICENSE file for more info.
link |
Stars: 20 |
Last commit: 2 weeks ago |
Rails not able to get Binary Data. Only string message. Maked default request as string instead Binary Data and add flag for send as Binary Data
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics