{"id":20684117,"url":"https://github.com/urlaunched-com/udfwebsocketsclient","last_synced_at":"2026-04-17T16:31:31.880Z","repository":{"id":199121203,"uuid":"702181517","full_name":"urlaunched-com/UDFWebSocketsClient","owner":"urlaunched-com","description":"SwiftUI-UDF WebSockets Client","archived":false,"fork":false,"pushed_at":"2025-12-04T12:46:28.000Z","size":38,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-07T20:51:03.722Z","etag":null,"topics":["actioncable","redux","swiftui-udf","udf","websocket","websocket-client"],"latest_commit_sha":null,"homepage":"https://www.urlaunched.com/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/urlaunched-com.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-10-08T18:20:44.000Z","updated_at":"2025-12-04T12:46:31.000Z","dependencies_parsed_at":"2023-11-07T19:30:06.877Z","dependency_job_id":"91e8ea3e-b3ad-4336-bab2-239cc07b714f","html_url":"https://github.com/urlaunched-com/UDFWebSocketsClient","commit_stats":null,"previous_names":["urlaunched-com/udfwebsocketsclient"],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/urlaunched-com/UDFWebSocketsClient","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urlaunched-com%2FUDFWebSocketsClient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urlaunched-com%2FUDFWebSocketsClient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urlaunched-com%2FUDFWebSocketsClient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urlaunched-com%2FUDFWebSocketsClient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/urlaunched-com","download_url":"https://codeload.github.com/urlaunched-com/UDFWebSocketsClient/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/urlaunched-com%2FUDFWebSocketsClient/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31936525,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-17T12:37:54.787Z","status":"ssl_error","status_checked_at":"2026-04-17T12:37:25.095Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["actioncable","redux","swiftui-udf","udf","websocket","websocket-client"],"created_at":"2024-11-16T22:19:19.067Z","updated_at":"2026-04-17T16:31:31.874Z","avatar_url":"https://github.com/urlaunched-com.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# UDFWebSocketsClient\nSwiftUI-UDF WebSockets Client\n\nUDFWebSocketsClient is a Swift-based library designed to facilitate WebSocket communication with reactive support using Combine. The library is built on top of `ActionCableSwift`, `NIO`, and `WebSocketKit`, allowing seamless WebSocket integration with a UDF (Unidirectional Data Flow) architecture.\n\n## Features\n\n- **WebSocket connection management**: Open and close WebSocket connections with ease.\n- **Reactive WebSocket communication**: Support for Combine-based publishers to process incoming WebSocket messages.\n- **ActionCable integration**: Leverage `ActionCableSwift` to interact with ActionCable servers.\n- **Thread-safe state management**: Utilize atomic properties and ensure safe concurrent access to WebSocket resources.\n- **Customizable Mappers**: Map WebSocket messages into specific outputs and corresponding actions based on your application state.\n- **Debounce and Event Handling**: Control WebSocket message processing with built-in debouncing and isolated state handling.\n\n## Installation\n\n### Swift Package Manager\n\nTo add UDFWebSocketsClient to your project, add the following dependency to your `Package.swift`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/youarelaunched/UDFWebSocketsClient.git\", from: \"0.3.0\")\n]\n```\n\n## Getting Started\n\n### Basic Usage\n\nHere’s a simple example of how to use WSS (WebSocket Service) for managing WebSocket connections and sending/receiving messages.\n\n#### Initialize the WebSocket Connection\n\n# UDFWebSocketsClient Middleware Example\n\nThis example demonstrates how to implement and use a WebSocket client with UDF middleware to manage WebSocket connections, process incoming messages, and map them to actions in a SwiftUI-based application using the **UDF** architecture.\n\n## WebSocketsMiddleware Overview\n\nThe `WebSocketsMiddleware` class handles WebSocket connections and channel subscriptions using the `SocketChannelEffect`. It manages WebSocket lifecycle events, including connecting and disconnecting clients and channels, and maps incoming messages into actions using the `outputMapper` and `actionMapper`.\n\nThe following example includes a `WebSocketsMiddleware` implementation with both a live environment for production and a test environment for testing purposes.\n\n### WebSocketsMiddleware Implementation\n\n*Middleware subscription:*\n\n```swift\nstore.subscribeAsync(WebSocketsMiddleware.self, on: DispatchQueue(label: \"WebSocketsMiddleware\", qos: .background))\n```\n*Middleware example:*\n\n```swift\nimport Foundation\nimport UDF\nimport Combine\nimport API\nimport UDFWebSocketsClient\n\nfinal class WebSocketsMiddleware: BaseReducibleMiddleware\u003cAppState\u003e {\n    enum Cancellation: Hashable {\n        case socketChannelChats\n        case socketChannelChat(Chat.ID)\n        case socketChannelNotifications\n    }\n\n    struct Environment {\n        var clientBuilder: (_ token: String) -\u003e ACClient\n        var channelBuilder: (_ client: ACClient?, _ name: String, _ identifier: [String: Any]?) -\u003e ACChannel?\n    }\n\n    var environment: Environment!\n    private var client: ACClient? = nil\n\n    func reduce(_ action: some Action, for state: AppState) {\n        switch action {\n        case is Actions.Logout:\n            cancelAll()\n            client?.disconnect()\n            client = nil\n\n        case let action as Actions.DidReceiveCurrentUser:\n            guard client == nil, let token = action.user.token else { break }\n            client = environment.clientBuilder(token)\n            client?.connect()\n\n            runNotificationSocketEffects(state)\n\n        case let action as Actions.UpdateAppStatus where action.appStatus == .active \u0026\u0026 client == nil \u0026\u0026 state.userForm.isLoggedIn == true:\n            client = environment.clientBuilder(state.userForm.token)\n            client?.connect()\n\n            runNotificationSocketEffects(state)\n\n        case let action as Actions.ConnectChat:\n            run(\n                SocketChannelEffect(\n                    store: store,\n                    channelBuilder: { [unowned self] in\n                        self.environment.channelBuilder(\n                            self.client,\n                            SocketChannel.chat(),\n                            [SocketChannel.Param.chatId(): String(action.id.value)]\n                        )\n                    },\n                    outputMapper: ChatChannelOutputMapper(),\n                    actionMapper: ChatChannelActionsMapper(),\n                    flowId: WebSocketFlow.id,\n                    queue: queue\n                ),\n                cancellation: Cancellation.socketChannelChat(action.id)\n            )\n\n        case let action as Actions.DisconnectChat:\n            cancel(by: Cancellation.socketChannelChat(action.id))\n\n        default:\n            break\n        }\n    }\n\n    func runNotificationSocketEffects(_ state: AppState) {\n        run(\n            SocketChannelEffect(\n                store: store,\n                channelBuilder: { [unowned self] in\n                    environment.channelBuilder(client, SocketChannel.notification(), nil)\n                },\n                outputMapper: NotificationChannelOutputMapper(),\n                actionMapper: NotificationChannelActionMapper(),\n                flowId: WebSocketFlow.id,\n                queue: queue\n            ),\n            cancellation: Cancellation.socketChannelNotifications\n        )\n    }\n}\n\nextension WebSocketsMiddleware {\n\n    static func buildLiveEnvironment(for store: some Store\u003cAppState\u003e) -\u003e Environment {\n        Environment(\n            clientBuilder: { token in\n                let host: String = EnvironmentConfig.value(for: .baseAPIKey)\n                let cableToken = token.dropFirst(7)\n                let ws: WSS = .init(stringURL: \"wss://\\(host)/cable?token=\\(cableToken)\")\n                let clientOptions: ACClientOptions = .init(debug: false, reconnect: true)\n                return ACClient(ws: ws, options: clientOptions)\n            },\n            channelBuilder: { client, name, identifier in\n                let channelOptions: ACChannelOptions = .init(buffering: false, autoSubscribe: true)\n                return client?.makeChannel(name: name, identifier: identifier ?? [:], options: channelOptions)\n            }\n        )\n    }\n\n    static func buildTestEnvironment(for store: some Store\u003cAppState\u003e) -\u003e Environment {\n        Environment(\n            clientBuilder: { token in\n                let ws: WSS = .init(stringURL: \"ws://localhost:3001/cable\")\n                let clientOptions: ACClientOptions = .init(debug: false, reconnect: true)\n                return ACClient(ws: ws, options: clientOptions)\n            },\n            channelBuilder: { client, name, identifier in\n                let channelOptions: ACChannelOptions = .init(buffering: false, autoSubscribe: true)\n                return client?.makeChannel(name: name, identifier: identifier ?? [:], options: channelOptions)\n            }\n        )\n    }\n}\n\n```\n\n*ChannelActionsMapping implementation example:*\n``` swift\nimport Foundation\nimport UDF\nimport UDFWebSocketsClient\n\nstruct NotificationChannelActionMapper: ChannelActionsMapping {\n    func mapAction(from output: NotificationChannelOutput, state: AppState) -\u003e any Action {\n        let flowId = WebSocketFlow.id\n\n        return ActionGroup {\n            switch output {\n            case .newChat(let chat):\n                if let lastMessage = chat.lastMessage?.asMessage {\n                    Actions.DidLoadItem(item: lastMessage, id: flowId)\n                    Actions.DidLoadNestedItem(parentId: chat.id, item: lastMessage.id, id: flowId)\n                }\n                Actions.DidLoadItem(item: chat.asChat, id: flowId)\n            case .updateChat(let chat):\n                Actions.DidUpdateItem(item: chat.asChat, id: flowId)\n                if (state.chatForm.chatId?.value != chat.id || !state.chatForm.isChatConnected),\n                   let lastMessageRemote = chat.lastMessage {\n                    let lastMessage = lastMessageRemote.asMessage\n                    Actions.DidLoadItem(item: lastMessage, id: flowId)\n                    Actions.DidLoadNestedItem(parentId: chat.asChat.id, item: lastMessage.id, id: flowId)\n                    Actions.DidLoadItem(item: lastMessageRemote.user.asUser, id: flowId)\n                }\n            }\n        }\n    }\n}\n```\n*ACCChannelOutputMapping implementation example:*\n``` swift\nimport Foundation\nimport API\nimport UDFWebSocketsClient\n\nstruct NotificationChannelOutputMapper: ACCChannelOutputMapping {\n    enum MessageType: String, Codable {\n        case newChat = \"new_chat\"\n        case updateChat = \"update_chat\"\n    }\n\n    let decoder: JSONDecoder = {\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        return decoder\n    }()\n\n    func map(from message: ACMessage) -\u003e NotificationChannelOutput? {\n        guard\n            message.type == .message,\n            let messageTypeValue = message.typeData,\n            let messageType = decoder.decodeSafely(\n                MessageType.self, from: messageTypeValue\n            ),\n            let textData = message.textData\n        else {\n            return nil\n        }\n\n        switch messageType {\n        case .newChat:\n            return decoder.decodeSafely(ChatRemote.self, from: textData).map(Output.newChat)\n        case .updateChat:\n            return decoder.decodeSafely(ChatRemote.self, from: textData).map(Output.updateChat)\n        }\n    }\n}\n\n```\n\n## Error Handling\n\nWhen errors occur (such as WebSocket disconnections or message parsing issues), SocketChannelEffect automatically maps these errors to actions and can be handled in your application’s state.\n\n## License\n\nThis project is licensed under the MIT License. See the LICENSE file for more information.\n\n## Contributing\n\nContributions are welcome! If you have any bug reports, feature requests, or suggestions, please open an issue or submit a pull request.\n\n## Contact\n\nFor questions or support, feel free to contact us at urlaunched.com.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furlaunched-com%2Fudfwebsocketsclient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Furlaunched-com%2Fudfwebsocketsclient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Furlaunched-com%2Fudfwebsocketsclient/lists"}