Skip to content

Commit

Permalink
add sources
Browse files Browse the repository at this point in the history
  • Loading branch information
Maks-Jago committed Oct 8, 2023
1 parent b0c513b commit 72402f4
Show file tree
Hide file tree
Showing 11 changed files with 440 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

104 changes: 104 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"pins" : [
{
"identity" : "action-cable-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nerzh/Action-Cable-Swift",
"state" : {
"revision" : "6b85886cc01a30c2b6322019271181c120bfc4b0",
"version" : "0.4.0"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
"version" : "1.2.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections",
"state" : {
"revision" : "a902f1823a7ff3c9ab2fba0f992396b948eda307",
"version" : "1.0.5"
}
},
{
"identity" : "swift-extensions-pack",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nerzh/swift-extensions-pack.git",
"state" : {
"revision" : "aaf0dc4df497630c9076475d33f42f47fc10a26c",
"version" : "0.5.9"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "3db5c4aeee8100d2db6f1eaf3864afdad5dc68fd",
"version" : "2.59.0"
}
},
{
"identity" : "swift-nio-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-extras.git",
"state" : {
"revision" : "fb70a0f5e984f23be48b11b4f1909f3bee016178",
"version" : "1.19.1"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "320bd978cceb8e88c125dcbb774943a92f6286e9",
"version" : "2.25.0"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "e7403c35ca6bb539a7ca353b91cc2d8ec0362d58",
"version" : "1.19.0"
}
},
{
"identity" : "swift-regular-expression",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nerzh/swift-regular-expression.git",
"state" : {
"revision" : "19c3e569a8e81da6f7f4ee3b73028c25737d3706",
"version" : "0.2.4"
}
},
{
"identity" : "swiftui-udf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Maks-Jago/SwiftUI-UDF",
"state" : {
"revision" : "2ceecff1da919f717c7581a56b0af37e96abdcb2",
"version" : "1.4.1-alpha.6"
}
},
{
"identity" : "websocket-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/websocket-kit",
"state" : {
"revision" : "53fe0639a98903858d0196b699720decb42aee7b",
"version" : "2.14.0"
}
}
],
"version" : 2
}
32 changes: 32 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "UDFWebSocketsClient",
platforms: [
.iOS(.v14),
.macOS(.v11)
],
products: [
.library(
name: "UDFWebSocketsClient",
targets: ["UDFWebSocketsClient"]),
],
dependencies: [
.package(url: "https://github.com/Maks-Jago/SwiftUI-UDF", from: "1.4.1-alpha.6"),
.package(url: "https://github.com/nerzh/Action-Cable-Swift", from: "0.4.0"),
.package(url: "https://github.com/vapor/websocket-kit", from: "2.0.0"),
],
targets: [
.target(
name: "UDFWebSocketsClient",
dependencies: [
.product(name: "UDF", package: "SwiftUI-UDF"),
.product(name: "ActionCableSwift", package: "Action-Cable-Swift"),
.product(name: "WebSocketKit", package: "websocket-kit")
]
)
]
)
85 changes: 85 additions & 0 deletions Sources/UDFWebSocketsClient/ACChannelPublisher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@

import Foundation
import Combine
import ActionCableSwift

public struct ACChannelPublisher<Mapper: ACCChannelOutputMapping>: Publisher {
public typealias Failure = Error
public typealias Output = Mapper.Output

public var mapper: Mapper
public var channelBuilder: () -> ACChannel?

public init(mapper: Mapper, channelBuilder: @escaping () -> ACChannel?) {
self.mapper = mapper
self.channelBuilder = channelBuilder
}

public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
subscriber.receive(
subscription: ChannelSubscription(
subscriber: subscriber,
channelBuilder: channelBuilder,
mapper: mapper
)
)
}

private final class ChannelSubscription<S: Subscriber>: NSObject, Subscription where S.Input == Output, S.Failure == Failure {
var subscriber: S?

var channel: ACChannel?
var mapper: Mapper
var channelBuilder: () -> ACChannel?

init(subscriber: S, channelBuilder: @escaping () -> ACChannel?, mapper: Mapper) {
self.channelBuilder = channelBuilder
self.mapper = mapper
super.init()
self.subscriber = subscriber
}

func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}

guard let channel = channelBuilder() else {
return
}

let channelName = channel.channelName

channel.addOnMessage({ [weak self] ch, message in
guard ch.channelName == channelName, let message else {
return
}

if let output = self?.mapper.map(from: message) {
_ = self?.subscriber?.receive(output)
}
})

channel.addOnUnsubscribe { [weak self] ch, _ in
guard ch.channelName == channelName else {
return
}
self?.cancel()
}

self.channel = channel
if !channel.options.autoSubscribe {
try? channel.subscribe()
}
}

func cancel() {
do {
try channel?.unsubscribe()
} catch {
_ = subscriber?.receive(completion: .failure(error))
}
subscriber = nil
}
}
}
30 changes: 30 additions & 0 deletions Sources/UDFWebSocketsClient/Atomic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

import Foundation

@propertyWrapper
public struct Atomic<Value> {

private var value: Value
private let lock = NSLock()

public init(wrappedValue value: Value) {
self.value = value
}

public 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
}
}
13 changes: 13 additions & 0 deletions Sources/UDFWebSocketsClient/Extensions/ACMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

import Foundation
import ActionCableSwift

public extension ACMessage {
var typeData: Data? {
(message?["type"] as? String)?.data(using: .utf8)
}

var textData: Data? {
(message?["data"] as? String)?.data(using: .utf8)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import ActionCableSwift

public protocol ACCChannelOutputMapping {
associatedtype Output

func map(from message: ACMessage) -> Output?
}
10 changes: 10 additions & 0 deletions Sources/UDFWebSocketsClient/Protocols/ChannelActionsMapping.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import Foundation
import UDF

public protocol ChannelActionsMapping {
associatedtype Output
associatedtype State: AppReducer

func mapAction(from output: Output, state: State) -> any Action
}
41 changes: 41 additions & 0 deletions Sources/UDFWebSocketsClient/SocketChannelEffect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

import Foundation
import UDF
import Combine

public struct SocketChannelEffect<FlowID: Hashable, OM: ACCChannelOutputMapping, AM: ChannelActionsMapping>: Effectable where OM.Output == AM.Output {
unowned var store: any Store<AM.State>
var channelBuilder: () -> ACChannel?
var outputMapper: OM
var actionMapper: AM
var flowId: FlowID

public init(
store: any Store<AM.State>,
channelBuilder: @escaping () -> ACChannel?,
outputMapper: OM,
actionMapper: AM,
flowId: FlowID
) {
self.store = store
self.channelBuilder = channelBuilder
self.outputMapper = outputMapper
self.actionMapper = actionMapper
self.flowId = flowId
}

public var upstream: AnyPublisher<any Action, Never> {
ACChannelPublisher(
mapper: outputMapper,
channelBuilder: channelBuilder
)
.flatMap { output in
Publishers.IsolatedState(from: store)
.map { state in
actionMapper.mapAction(from: output, state: state)
}
}
.catch { Just(Actions.Error(error: $0.localizedDescription, id: flowId)) }
.eraseToAnyPublisher()
}
}
4 changes: 4 additions & 0 deletions Sources/UDFWebSocketsClient/Types.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

import Foundation

@_exported import ActionCableSwift
Loading

0 comments on commit 72402f4

Please sign in to comment.