diff --git a/.github/workflows/build-deploy-docs.yml b/.github/workflows/build-deploy-docs.yml index 64be3b1..7419c84 100644 --- a/.github/workflows/build-deploy-docs.yml +++ b/.github/workflows/build-deploy-docs.yml @@ -10,8 +10,8 @@ permissions: id-token: write jobs: - build-deploy-docs: - name: Build and Deploy Docs + build-docs: + name: Build Docs runs-on: macos-13 steps: @@ -27,11 +27,32 @@ jobs: scheme: RxNetworkKit run: | xcodebuild docbuild -workspace "${workspace}" -scheme "${scheme}" -derivedDataPath derivedData | xcpretty - - name: Perpare Docs for Deployment - run: | mkdir docArchives cp -R `find derivedData -type d -name "*.doccarchive"` docArchives + zip -r RxNetworkKitDocCArchive.zip docArchives/RxNetworkKit.doccarchive + - name: Upload Docs Archive + uses: actions/upload-artifact@v3 + with: + name: doc-archive-zip + path: RxNetworkKitDocCArchive.zip + + deploy-docs: + name: Deploy Docs + runs-on: macos-13 + needs: build-docs + + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Download Docs Archive + uses: actions/download-artifact@v3 + with: + name: doc-archive-zip + - name: Perpare Docs for Deployment + run: | + unzip RxNetworkKitDocCArchive.zip $(xcrun --find docc) process-archive transform-for-static-hosting docArchives/RxNetworkKit.doccarchive --hosting-base-path RxNetworkKit --output-path docs + echo "" > docs/index.html; - name: Upload Docs artifact uses: actions/upload-pages-artifact@v1 with: diff --git a/Docs.docc/Pages/NetworkManager.md b/Docs.docc/Pages/NetworkManager.md index 21e055b..3061998 100644 --- a/Docs.docc/Pages/NetworkManager.md +++ b/Docs.docc/Pages/NetworkManager.md @@ -34,4 +34,5 @@ - ``WebSocketMessage`` - ``WebSocketCloseCode`` - ``WebSocketCloseHandler`` +- ``WebSocketError`` - ``NetworkManager/webSocket(_:_:_:)`` diff --git a/Examples/iOS/iOS Example/View Model/ViewModel.swift b/Examples/iOS/iOS Example/View Model/ViewModel.swift index dfe174f..aa5517e 100644 --- a/Examples/iOS/iOS Example/View Model/ViewModel.swift +++ b/Examples/iOS/iOS Example/View Model/ViewModel.swift @@ -40,7 +40,8 @@ class ViewModel { // Create default sequence with default API call request. let loadObservable = viewState .filter( { ![.idle, .loading(loadType: .paginate), .error].contains($0) }) - .flatMapLatest{ _ in + .flatMapLatest{ [weak self] _ in + guard let self = self else { return Observable<[Model]>.empty().materialize() } let single: Single<[Model]> = self.networkManager.request(Router.default) return single .asObservable() diff --git a/Examples/macOS/macOS Example/View Model/ViewModel.swift b/Examples/macOS/macOS Example/View Model/ViewModel.swift index dfe174f..aa5517e 100644 --- a/Examples/macOS/macOS Example/View Model/ViewModel.swift +++ b/Examples/macOS/macOS Example/View Model/ViewModel.swift @@ -40,7 +40,8 @@ class ViewModel { // Create default sequence with default API call request. let loadObservable = viewState .filter( { ![.idle, .loading(loadType: .paginate), .error].contains($0) }) - .flatMapLatest{ _ in + .flatMapLatest{ [weak self] _ in + guard let self = self else { return Observable<[Model]>.empty().materialize() } let single: Single<[Model]> = self.networkManager.request(Router.default) return single .asObservable() diff --git a/RxNetworkKit.xcodeproj/project.pbxproj b/RxNetworkKit.xcodeproj/project.pbxproj index c6a5a47..936b8d2 100644 --- a/RxNetworkKit.xcodeproj/project.pbxproj +++ b/RxNetworkKit.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ C6A9BEFA2A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9BEF92A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift */; }; C6A9BEFD2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9BEFC2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift */; }; C6A9BEFF2A93FB1D00459E32 /* ProcessInfo+operatingSystemName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9BEFE2A93FB1D00459E32 /* ProcessInfo+operatingSystemName.swift */; }; + C6B4B4C42AD47A2F009073ED /* WebSocketError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B4B4C32AD47A2F009073ED /* WebSocketError.swift */; }; C6BDFFE82ACDF3830022F675 /* Reactive+receive.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6BDFFE72ACDF3830022F675 /* Reactive+receive.swift */; }; C6BDFFEA2ACDF3D90022F675 /* Reactive+send.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6BDFFE92ACDF3D90022F675 /* Reactive+send.swift */; }; C6BDFFEC2ACDF4100022F675 /* Reactive+ping.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6BDFFEB2ACDF4100022F675 /* Reactive+ping.swift */; }; @@ -127,6 +128,7 @@ C6A9BEF92A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+setAdditionalHTTPHeader.swift"; sourceTree = ""; }; C6A9BEFC2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+setUserAgentHTTPHeader.swift"; sourceTree = ""; }; C6A9BEFE2A93FB1D00459E32 /* ProcessInfo+operatingSystemName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+operatingSystemName.swift"; sourceTree = ""; }; + C6B4B4C32AD47A2F009073ED /* WebSocketError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketError.swift; sourceTree = ""; }; C6BDFFE72ACDF3830022F675 /* Reactive+receive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Reactive+receive.swift"; sourceTree = ""; }; C6BDFFE92ACDF3D90022F675 /* Reactive+send.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Reactive+send.swift"; sourceTree = ""; }; C6BDFFEB2ACDF4100022F675 /* Reactive+ping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Reactive+ping.swift"; sourceTree = ""; }; @@ -364,6 +366,7 @@ C6BDFFEF2ACDF4AB0022F675 /* WebSocketCloseHandler.swift */, C6BDFFF22ACDF50F0022F675 /* WebSocketMessage.swift */, C6BDFFF42ACDF5250022F675 /* WebSocketCloseCode.swift */, + C6B4B4C32AD47A2F009073ED /* WebSocketError.swift */, ); path = "Web Socket"; sourceTree = ""; @@ -498,6 +501,7 @@ 0B77E09129D965D30077FBC0 /* Reactive+URLSessionAdaptedUploadResponse.swift in Sources */, C6A9BEFA2A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift in Sources */, C6BDFFF32ACDF5100022F675 /* WebSocketMessage.swift in Sources */, + C6B4B4C42AD47A2F009073ED /* WebSocketError.swift in Sources */, 0B77E0A629D965D30077FBC0 /* DefaultNetworkAPIError.swift in Sources */, 0B77E0A429D965D30077FBC0 /* NetworkReachability.swift in Sources */, 0B77E0B229D965D30077FBC0 /* Completable+Retry.swift in Sources */, diff --git a/Source/Web Socket/Extensions/Reactive+ping.swift b/Source/Web Socket/Extensions/Reactive+ping.swift index 10ae88b..deea8d2 100644 --- a/Source/Web Socket/Extensions/Reactive+ping.swift +++ b/Source/Web Socket/Extensions/Reactive+ping.swift @@ -9,6 +9,7 @@ import Foundation import RxSwift extension Reactive where Base: URLSessionWebSocketTask { + /// Sends a ping to the websocket server. /// /// - Returns: `Completable` observable encapsulating send ping operation. @@ -19,9 +20,10 @@ extension Reactive where Base: URLSessionWebSocketTask { subscription(.completed) return } - subscription(.error(error)) + subscription(.error(WebSocketError.ping(error: error))) } return Disposables.create() } } + } diff --git a/Source/Web Socket/Extensions/Reactive+receive.swift b/Source/Web Socket/Extensions/Reactive+receive.swift index 701fb75..988d0b9 100644 --- a/Source/Web Socket/Extensions/Reactive+receive.swift +++ b/Source/Web Socket/Extensions/Reactive+receive.swift @@ -23,16 +23,13 @@ extension Reactive where Base: URLSessionWebSocketTask { subscription.onNext(message) receive(subscription: subscription) case .failure(let error): - subscription.onError(error) + subscription.onError(WebSocketError.receive(error: error)) } } } return Observable.create { subscription in receive(subscription: subscription) - return Disposables.create { - let request = base.currentRequest - base.cancel(with: closeHandler.code(for: request), reason: closeHandler.reason(for: request)) - } + return Disposables.create() } .share() } diff --git a/Source/Web Socket/Extensions/Reactive+send.swift b/Source/Web Socket/Extensions/Reactive+send.swift index 383f7d1..693ae8b 100644 --- a/Source/Web Socket/Extensions/Reactive+send.swift +++ b/Source/Web Socket/Extensions/Reactive+send.swift @@ -9,6 +9,7 @@ import Foundation import RxSwift extension Reactive where Base: URLSessionWebSocketTask { + /// Sends message to the websocket server. /// /// - Parameter message: `WebSocketMessage` to be sent to websocket server. @@ -21,9 +22,10 @@ extension Reactive where Base: URLSessionWebSocketTask { subscription(.completed) return } - subscription(.error(error)) + subscription(.error(WebSocketError.send(error: error))) } return Disposables.create() } } + } diff --git a/Source/Web Socket/WebSocket.swift b/Source/Web Socket/WebSocket.swift index 7edb256..7c7a644 100644 --- a/Source/Web Socket/WebSocket.swift +++ b/Source/Web Socket/WebSocket.swift @@ -17,7 +17,7 @@ public class WebSocket { /// a generic `PublishRelay` object for data messages received from websocket server. public let data: PublishRelay = .init() /// a `PublishRelay` object for errors encountered while receiving/sending from/to websocket server. - public let error: PublishRelay = .init() + public let error: PublishRelay = .init() private let task: URLSessionWebSocketTask private let closeHandler: WebSocketCloseHandler private let receiveObservable: Observable @@ -35,6 +35,11 @@ public class WebSocket { setupBindings() } + /// Destroys current `WebSocket` instance and cancels current task. + deinit { + disconnect() + } + /// Resumes current task to establish the connection. public func connect() { task.resume() @@ -49,17 +54,23 @@ public class WebSocket { /// Sends message to the websocket server. /// /// - Parameter message: `WebSocketMessage` to be sent to websocket server. - /// - /// - Returns: `Completable` observable encapsulating send message operation. - public func send(_ message: WebSocketMessage) -> Completable { + public func send(_ message: WebSocketMessage) { task.rx.send(message: message) + .subscribe(onError: { error in + guard let error = error as? WebSocketError else { return } + self.error.accept(error) + }) + .disposed(by: disposeBag) } /// Sends a ping to the websocket server. - /// - /// - Returns: `Completable` observable encapsulating send ping operation. - public func ping() -> Completable { + public func ping() { task.rx.ping() + .subscribe(onError: { error in + guard let error = error as? WebSocketError else { return } + self.error.accept(error) + }) + .disposed(by: disposeBag) } /// Sets up internal observable bindings. @@ -92,6 +103,7 @@ public class WebSocket { .materialize() .compactMap({ guard case .error(let error) = $0 else { return nil } + guard let error = error as? WebSocketError else { return nil } return error }) .bind(to: error) diff --git a/Source/Web Socket/WebSocketError.swift b/Source/Web Socket/WebSocketError.swift new file mode 100644 index 0000000..5b8f2a9 --- /dev/null +++ b/Source/Web Socket/WebSocketError.swift @@ -0,0 +1,15 @@ +// +// WebSocketError.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 09/10/2023. +// + +import Foundation + +/// An enumeration that contains cases where error is received from websocket. +public enum WebSocketError: Error { + case receive(error: Error) + case send(error: Error) + case ping(error: Error) +}