From 9e84cbb3ce66e9fe57a1fd928a6f5d9ddad78cea Mon Sep 17 00:00:00 2001 From: Loay Ashraf Date: Thu, 5 Oct 2023 00:57:39 +0300 Subject: [PATCH] v0.1.0 (#47) * feat: add decoding capability in case of failure caused due to HTTP status code Resolves: none. * feat: override User-Agent HTTP header in session configuration (#26) Resolves: none. * Add macOS example (#27) * feat: move iOS example project into new sub folder Resolves: none. * fix: update framework search paths Resolves: none. * feat: add empt macOS example project to workspace Resolves: none. * fix: update framework scheme name Resolves: none. * feat: add files to macOS example directory Resolves: none. * fix: apply public access modifier to DefaultHTTPErrorBody and DefaultNetworkAPIError Resolves: none. * refactor: add description comment for ProcessInfo extension Resolves: none. * fix: add RxNetworkKit bridging header reference Resolves: none. * refactor: apply project recommended settings Resolves: none. * feat: connect viewModel to tableview UI Resolves: none. * feat: complete ViewController class in macOS Example Resolves: none. * refactor: remove old un-needed file Resolves: none. * fix: apply correct image scale for error view Resolves: none. * Apply new version (0.0.2) (#28) * fix: remove tinted button warning Resolves: none. * version: 0.0.2 Resolves: none. * fix: update version for podSpec file Resolves: none. * fix: remove RxDataSources import statement (#31) Resolves: none. * Add CI Workflows For Repository (#33) * feat: add iOS workflow Resolves: none. * fix: update build-ios.yml Resolves: none. * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Update build-ios.yml * Create build-macos.yml * Update build-ios.yml + build-macos.yml * fix error 65 * Update build-ios.yml * update build-ios.yml + build-macos.yml * Update build-macos.yml * Update project.pbxproj * add publish-podspec.yml * add workflow files outside of folders * Update publish-podspec.yml * Update publish-podspec.yml * Update publish-podspec.yml * Create pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * test container workflow * Update build.yml * Update build.yml * add some changes * Update build.yml * add some changes * Update build.yml * refactor: update names + remove comments Resolves: none. * add trigger for trunk push workflow * add dummy project to test SPM integration * add spm-lint.yml * Update spm-lint.yml * Update spm-lint.yml * Update spm-lint.yml * Update spm-lint.yml * update dummy project * update workspace dependencies versions * Update CI Workflows * update CI workflows * Update CI Workflow * Update Dependency Version Rules (#45) * fix: update version rules for SPM to upToNextMajor Resolves: none. * fix: update version rule for SPM in iOS Example project Resolves: none. * fix: update version rules for cocoapods in podspec file Resolves: none. * feat: add WebSocket capability to NetworkManager (#46) Resolves: none. * fix a typo * update dependencies versions Resolves: none. * update Package.swift + podspec file * change xcode version used in CI/CD to 14.3.1 Resolves: none. * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update build.yml * Update build.yml * update workflow files * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-lib-lint.yml * Update pod-trunk-push.yml * Update spm-lint.yml --- .github/workflows/build-ios.yml | 8 +- .github/workflows/build-macos.yml | 4 +- .github/workflows/pod-lib-lint.yml | 8 +- .github/workflows/pod-trunk-push.yml | 8 +- .github/workflows/spm-lint.yml | 2 +- .../iOS/iOS Example.xcodeproj/project.pbxproj | 2 +- Package.swift | 4 +- RxNetworkKit.xcodeproj/project.pbxproj | 56 ++++++++++-- .../xcshareddata/swiftpm/Package.resolved | 8 +- RxNetworkKitX.podspec | 8 +- Source/Manager/NetworkManager.swift | 14 +++ .../Web Socket/Extensions/Reactive+ping.swift | 27 ++++++ .../Extensions/Reactive+receive.swift | 39 ++++++++ .../Web Socket/Extensions/Reactive+send.swift | 29 ++++++ Source/Web Socket/WebSocket.swift | 90 +++++++++++++++++++ Source/Web Socket/WebSocketCloseCode.swift | 10 +++ Source/Web Socket/WebSocketCloseHandler.swift | 38 ++++++++ Source/Web Socket/WebSocketMessage.swift | 10 +++ 18 files changed, 339 insertions(+), 26 deletions(-) create mode 100644 Source/Web Socket/Extensions/Reactive+ping.swift create mode 100644 Source/Web Socket/Extensions/Reactive+receive.swift create mode 100644 Source/Web Socket/Extensions/Reactive+send.swift create mode 100644 Source/Web Socket/WebSocket.swift create mode 100644 Source/Web Socket/WebSocketCloseCode.swift create mode 100644 Source/Web Socket/WebSocketCloseHandler.swift create mode 100644 Source/Web Socket/WebSocketMessage.swift diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 29acff7..7066d7e 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -14,7 +14,7 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '14.3.1' - name: Build Framework env: workspace: RxNetworkKit.xcworkspace @@ -33,7 +33,7 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '14.3.1' - name: Build Framework env: workspace: RxNetworkKit.xcworkspace @@ -53,7 +53,7 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '14.3.1' - name: Build Example env: workspace: RxNetworkKit.xcworkspace @@ -73,7 +73,7 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '14.3.1' - name: Build Example env: workspace: RxNetworkKit.xcworkspace diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index a9d7faa..bc351c2 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -14,7 +14,7 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '14.3.1' - name: Build Framework env: workspace: RxNetworkKit.xcworkspace @@ -34,7 +34,7 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '14.3.1' - name: Build Example env: workspace: RxNetworkKit.xcworkspace diff --git a/.github/workflows/pod-lib-lint.yml b/.github/workflows/pod-lib-lint.yml index f92ecb3..dedf7c6 100644 --- a/.github/workflows/pod-lib-lint.yml +++ b/.github/workflows/pod-lib-lint.yml @@ -23,7 +23,13 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '14.2' + xcode-version: '14.3.1' + - name: Add Missing Lib Files (for iOS 9.0 and macOS 10.9) + run: | + cd /Applications/Xcode_14.3.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib + sudo mkdir arc + cd arc + sudo git clone https://github.com/kamyarelyasi/Libarclite-Files.git . - name: Install Cocoapods run: | sudo gem install cocoapods diff --git a/.github/workflows/pod-trunk-push.yml b/.github/workflows/pod-trunk-push.yml index 2ea9c7e..7f0effb 100644 --- a/.github/workflows/pod-trunk-push.yml +++ b/.github/workflows/pod-trunk-push.yml @@ -21,7 +21,13 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '14.2' + xcode-version: '14.3.1' + - name: Add Missing Lib Files (for iOS 9.0 and macOS 10.9) + run: | + cd /Applications/Xcode_14.3.1.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib + sudo mkdir arc + cd arc + sudo git clone https://github.com/kamyarelyasi/Libarclite-Files.git . - name: Install Cocoapods run: | sudo gem install cocoapods diff --git a/.github/workflows/spm-lint.yml b/.github/workflows/spm-lint.yml index 85c150b..2b1b6f4 100644 --- a/.github/workflows/spm-lint.yml +++ b/.github/workflows/spm-lint.yml @@ -28,7 +28,7 @@ jobs: - name: Set Xcode Version uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: latest-stable + xcode-version: '14.3.1' - name: Update Packages run: | swift package update diff --git a/Examples/iOS/iOS Example.xcodeproj/project.pbxproj b/Examples/iOS/iOS Example.xcodeproj/project.pbxproj index fafc9ed..90971f5 100644 --- a/Examples/iOS/iOS Example.xcodeproj/project.pbxproj +++ b/Examples/iOS/iOS Example.xcodeproj/project.pbxproj @@ -549,7 +549,7 @@ repositoryURL = "https://github.com/RxSwiftCommunity/RxDataSources"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 5.0.2; + minimumVersion = 5.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/Package.swift b/Package.swift index 5b5b387..2f3913c 100644 --- a/Package.swift +++ b/Package.swift @@ -17,8 +17,8 @@ let package = Package( .library(name: "RxNetworkKit", targets: ["RxNetworkKit"]), ], dependencies: [ - .package(url: "https://github.com/ReactiveX/RxSwift.git", .exact("6.5.0")), - .package(url: "https://github.com/RxSwiftCommunity/RxSwiftExt", .exact("6.0.1")), + .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.6.0")), + .package(url: "https://github.com/RxSwiftCommunity/RxSwiftExt", .upToNextMajor(from: "6.2.0")), ], targets: [ .target(name: "RxNetworkKit", dependencies: ["RxSwift", "RxSwiftExt", .product(name: "RxCocoa", package: "RxSwift")], path: "Source"), diff --git a/RxNetworkKit.xcodeproj/project.pbxproj b/RxNetworkKit.xcodeproj/project.pbxproj index d09005c..8488115 100644 --- a/RxNetworkKit.xcodeproj/project.pbxproj +++ b/RxNetworkKit.xcodeproj/project.pbxproj @@ -62,6 +62,13 @@ 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 */; }; + 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 */; }; + C6BDFFEE2ACDF46A0022F675 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6BDFFED2ACDF46A0022F675 /* WebSocket.swift */; }; + C6BDFFF02ACDF4AB0022F675 /* WebSocketCloseHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6BDFFEF2ACDF4AB0022F675 /* WebSocketCloseHandler.swift */; }; + C6BDFFF32ACDF5100022F675 /* WebSocketMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6BDFFF22ACDF50F0022F675 /* WebSocketMessage.swift */; }; + C6BDFFF52ACDF5250022F675 /* WebSocketCloseCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6BDFFF42ACDF5250022F675 /* WebSocketCloseCode.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -118,6 +125,13 @@ 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 = ""; }; + 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 = ""; }; + C6BDFFED2ACDF46A0022F675 /* WebSocket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocket.swift; sourceTree = ""; }; + C6BDFFEF2ACDF4AB0022F675 /* WebSocketCloseHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketCloseHandler.swift; sourceTree = ""; }; + C6BDFFF22ACDF50F0022F675 /* WebSocketMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketMessage.swift; sourceTree = ""; }; + C6BDFFF42ACDF5250022F675 /* WebSocketCloseCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketCloseCode.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -155,6 +169,7 @@ 0B77E04A29D965D30077FBC0 /* Source */ = { isa = PBXGroup; children = ( + C6BDFFE62ACDF3260022F675 /* Web Socket */, 0B77E04B29D965D30077FBC0 /* Custom Requests */, 0B77E05929D965D30077FBC0 /* Request Interceptor */, 0B77E05F29D965D30077FBC0 /* Manager */, @@ -181,9 +196,9 @@ 0B77E04C29D965D30077FBC0 /* Download */ = { isa = PBXGroup; children = ( - 0B77E04D29D965D30077FBC0 /* Reactive+URLSessionDownloadResponse.swift */, 0B77E04E29D965D30077FBC0 /* DownloadEvent.swift */, 0B77E04F29D965D30077FBC0 /* URLSession+DownloadTask.swift */, + 0B77E04D29D965D30077FBC0 /* Reactive+URLSessionDownloadResponse.swift */, 0B77E05029D965D30077FBC0 /* Reactive+URLSessionAdaptedDownloadResponse.swift */, ); path = Download; @@ -196,9 +211,9 @@ 0B77E05329D965D30077FBC0 /* UploadFile.swift */, 0B77E05429D965D30077FBC0 /* URLSession+UploadTask.swift */, 0B77E05529D965D30077FBC0 /* Data+AppendString.swift */, - 0B77E05629D965D30077FBC0 /* Reactive+URLSessionAdaptedUploadResponse.swift */, 0B77E05729D965D30077FBC0 /* UploadEvent.swift */, 0B77E05829D965D30077FBC0 /* Reactive+URLSessionUploadResponse.swift */, + 0B77E05629D965D30077FBC0 /* Reactive+URLSessionAdaptedUploadResponse.swift */, ); path = Upload; sourceTree = ""; @@ -338,6 +353,28 @@ path = Router; sourceTree = ""; }; + C6BDFFE62ACDF3260022F675 /* Web Socket */ = { + isa = PBXGroup; + children = ( + C6BDFFF12ACDF4CB0022F675 /* Extensions */, + C6BDFFED2ACDF46A0022F675 /* WebSocket.swift */, + C6BDFFEF2ACDF4AB0022F675 /* WebSocketCloseHandler.swift */, + C6BDFFF22ACDF50F0022F675 /* WebSocketMessage.swift */, + C6BDFFF42ACDF5250022F675 /* WebSocketCloseCode.swift */, + ); + path = "Web Socket"; + sourceTree = ""; + }; + C6BDFFF12ACDF4CB0022F675 /* Extensions */ = { + isa = PBXGroup; + children = ( + C6BDFFE72ACDF3830022F675 /* Reactive+receive.swift */, + C6BDFFE92ACDF3D90022F675 /* Reactive+send.swift */, + C6BDFFEB2ACDF4100022F675 /* Reactive+ping.swift */, + ); + path = Extensions; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -427,12 +464,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C6BDFFF02ACDF4AB0022F675 /* WebSocketCloseHandler.swift in Sources */, 0B77E0AD29D965D30077FBC0 /* Single+CatchErrors.swift in Sources */, 0B77E0B529D965D30077FBC0 /* NetworkRouter.swift in Sources */, 0B77E09529D965D30077FBC0 /* RequestRetryPolicy.swift in Sources */, 0B77E0B329D965D30077FBC0 /* Reactive+Curl.swift in Sources */, C6A9BEF82A93E2D600459E32 /* HTTPErrorBody.swift in Sources */, 0B77E0A929D965D30077FBC0 /* NetworkError.swift in Sources */, + C6BDFFEA2ACDF3D90022F675 /* Reactive+send.swift in Sources */, 0B77E09F29D965D30077FBC0 /* NWPath+InterfaceType.swift in Sources */, 0B77E09C29D965D30077FBC0 /* HTTPScheme.swift in Sources */, 0B77E0A029D965D30077FBC0 /* NetworkReachabilityStatus.swift in Sources */, @@ -454,11 +493,13 @@ C6A9BEFD2A93FAF100459E32 /* URLSessionConfiguration+setUserAgentHTTPHeader.swift in Sources */, 0B77E09129D965D30077FBC0 /* Reactive+URLSessionAdaptedUploadResponse.swift in Sources */, C6A9BEFA2A93F16200459E32 /* URLSessionConfiguration+setAdditionalHTTPHeader.swift in Sources */, + C6BDFFF32ACDF5100022F675 /* WebSocketMessage.swift in Sources */, 0B77E0A629D965D30077FBC0 /* DefaultNetworkAPIError.swift in Sources */, 0B77E0A429D965D30077FBC0 /* NetworkReachability.swift in Sources */, 0B77E0B229D965D30077FBC0 /* Completable+Retry.swift in Sources */, 0B77E0B029D965D30077FBC0 /* Observable+Retry.swift in Sources */, 0B77E08B29D965D30077FBC0 /* URLSession+DownloadTask.swift in Sources */, + C6BDFFF52ACDF5250022F675 /* WebSocketCloseCode.swift in Sources */, 0B77E0A729D965D30077FBC0 /* NetworkServerError.swift in Sources */, 0B77E0B629D965D30077FBC0 /* NetworkUploadRouter.swift in Sources */, C6A9BEF62A93E2AE00459E32 /* DefaultHTTPErrorBody.swift in Sources */, @@ -469,13 +510,16 @@ 0B77E09629D965D30077FBC0 /* RequestAdapter.swift in Sources */, 0B77E0A829D965D30077FBC0 /* NetworkClientError.swift in Sources */, 0B77E0A229D965D30077FBC0 /* NWInterfaceType+CaseIterable.swift in Sources */, + C6BDFFEE2ACDF46A0022F675 /* WebSocket.swift in Sources */, 0B77E09329D965D30077FBC0 /* Reactive+URLSessionUploadResponse.swift in Sources */, 0B77E09B29D965D30077FBC0 /* HTTPMethod.swift in Sources */, 0B77E0AC29D965D30077FBC0 /* Single+Decode.swift in Sources */, 0B77E0A329D965D30077FBC0 /* NetworkInterfaceType.swift in Sources */, 0B77E09229D965D30077FBC0 /* UploadEvent.swift in Sources */, 0B77E09029D965D30077FBC0 /* Data+AppendString.swift in Sources */, + C6BDFFE82ACDF3830022F675 /* Reactive+receive.swift in Sources */, 0B77E08929D965D30077FBC0 /* Reactive+URLSessionDownloadResponse.swift in Sources */, + C6BDFFEC2ACDF4100022F675 /* Reactive+ping.swift in Sources */, 0B77E0AA29D965D30077FBC0 /* NetworkAPIError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -709,16 +753,16 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/ReactiveX/RxSwift"; requirement = { - kind = exactVersion; - version = 6.5.0; + kind = upToNextMajorVersion; + minimumVersion = 6.6.0; }; }; 0B77E0BE29D969370077FBC0 /* XCRemoteSwiftPackageReference "RxSwiftExt" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/RxSwiftCommunity/RxSwiftExt"; requirement = { - kind = exactVersion; - version = 6.0.1; + kind = upToNextMajorVersion; + minimumVersion = 6.2.0; }; }; /* End XCRemoteSwiftPackageReference section */ diff --git a/RxNetworkKit.xcworkspace/xcshareddata/swiftpm/Package.resolved b/RxNetworkKit.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6ffbce0..e70bc7c 100644 --- a/RxNetworkKit.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/RxNetworkKit.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ReactiveX/RxSwift", "state" : { - "revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8", - "version" : "6.5.0" + "revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4", + "version" : "6.6.0" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/RxSwiftCommunity/RxSwiftExt", "state" : { - "revision" : "827bd11853983383b708feaf7da95c560982b2b8", - "version" : "6.0.1" + "revision" : "eb4adf9f00a21b3efc3869a5218a6d7517e95222", + "version" : "6.2.1" } } ], diff --git a/RxNetworkKitX.podspec b/RxNetworkKitX.podspec index 1104206..86868bd 100644 --- a/RxNetworkKitX.podspec +++ b/RxNetworkKitX.podspec @@ -4,7 +4,7 @@ Pod::Spec.new do |s| s.macos.deployment_target = '11.0' s.name = 'RxNetworkKitX' s.module_name = 'RxNetworkKit' - s.version = '0.0.2' + s.version = '0.1.0' s.summary = 'a lightweight FRP networking framework.' s.description = 'a FRP networking framework built on top of URLSession and uses RxSwift and RxCocoa.' s.homepage = 'https://github.com/loay-ashraf/RxNetworkKit' @@ -12,9 +12,9 @@ Pod::Spec.new do |s| s.author = { 'loay-ashraf' => 'loay.ashraf.96@gmail.com' } s.source = { :git => 'https://github.com/loay-ashraf/RxNetworkKit.git', :tag => s.version.to_s } s.framework = "Foundation" - s.dependency 'RxSwift', '~> 6.5.0' - s.dependency 'RxCocoa', '~> 6.5.0' - s.dependency 'RxSwiftExt', '~> 6.0.1' + s.dependency 'RxSwift', '~> 6.6' + s.dependency 'RxCocoa', '~> 6.6' + s.dependency 'RxSwiftExt', '~> 6.2' s.source_files = 'Source/**/*.{swift,m,h}' s.swift_version = '5.0' end diff --git a/Source/Manager/NetworkManager.swift b/Source/Manager/NetworkManager.swift index db90b27..86cca18 100644 --- a/Source/Manager/NetworkManager.swift +++ b/Source/Manager/NetworkManager.swift @@ -24,6 +24,7 @@ public class NetworkManager { // Apply User-Agent header as a part of HTTP aditional headers. configuration.setUserAgentHTTPHeader() // Initialize manager's properties. + URLSession.rx.shouldLogRequest = { _ in false } self.session = .init(configuration: configuration, delegate: eventMonitor, delegateQueue: nil) self.requestInterceptor = requestInterceptor self.eventMonitor = eventMonitor @@ -172,4 +173,17 @@ public class NetworkManager { .retry(retryMaxAttempts, delay: retryPolicy, shouldRetry: shouldRetry) return observable } + /// Creates websocket object and establishes connection using provided url and protocols. + /// + /// - Parameters: + /// - url: `URL` of websocket server. + /// - protocols: `[String]` websocket connection protocols. + /// - closeHandler: `WebSocketCloseHandler` used to provide close coda and reason upon connection closure. + /// + /// - Returns: `WebSocket` object that represents the connection. + public func webSocket(_ url: URL, _ protocols: [String], _ closeHandler: WebSocketCloseHandler) -> WebSocket { + let task = session.webSocketTask(with: url, protocols: protocols) + let webSocket = WebSocket(task: task, closeHandler: closeHandler) + return webSocket + } } diff --git a/Source/Web Socket/Extensions/Reactive+ping.swift b/Source/Web Socket/Extensions/Reactive+ping.swift new file mode 100644 index 0000000..10ae88b --- /dev/null +++ b/Source/Web Socket/Extensions/Reactive+ping.swift @@ -0,0 +1,27 @@ +// +// Reactive+ping.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 04/10/2023. +// + +import Foundation +import RxSwift + +extension Reactive where Base: URLSessionWebSocketTask { + /// Sends a ping to the websocket server. + /// + /// - Returns: `Completable` observable encapsulating send ping operation. + func ping() -> Completable { + Completable.create { subscription in + base.sendPing { error in + guard let error = error else { + subscription(.completed) + return + } + subscription(.error(error)) + } + return Disposables.create() + } + } +} diff --git a/Source/Web Socket/Extensions/Reactive+receive.swift b/Source/Web Socket/Extensions/Reactive+receive.swift new file mode 100644 index 0000000..701fb75 --- /dev/null +++ b/Source/Web Socket/Extensions/Reactive+receive.swift @@ -0,0 +1,39 @@ +// +// Reactive+receive.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 04/10/2023. +// + +import Foundation +import RxSwift + +extension Reactive where Base: URLSessionWebSocketTask { + /// Starts receiving messages sent from websocket server. + /// + /// - Parameter closeHandler: `WebSocketCloseHandler` used to provide close coda and reason upon connection closure. + /// + /// - Returns: `Observable` object encapsulating received messages. + func receive(closeHandler: WebSocketCloseHandler) -> Observable { + func receive(subscription: AnyObserver) { + base.receive { result in + guard base.closeCode == .invalid else { return } + switch result { + case .success(let message): + subscription.onNext(message) + receive(subscription: subscription) + case .failure(let error): + subscription.onError(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)) + } + } + .share() + } +} diff --git a/Source/Web Socket/Extensions/Reactive+send.swift b/Source/Web Socket/Extensions/Reactive+send.swift new file mode 100644 index 0000000..383f7d1 --- /dev/null +++ b/Source/Web Socket/Extensions/Reactive+send.swift @@ -0,0 +1,29 @@ +// +// Reactive+send.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 04/10/2023. +// + +import Foundation +import RxSwift + +extension Reactive where Base: URLSessionWebSocketTask { + /// Sends message to the websocket server. + /// + /// - Parameter message: `WebSocketMessage` to be sent to websocket server. + /// + /// - Returns: `Completable` observable encapsulating send message operation. + func send(message: WebSocketMessage) -> Completable { + Completable.create { subscription in + base.send(message) { error in + guard let error = error else { + subscription(.completed) + return + } + subscription(.error(error)) + } + return Disposables.create() + } + } +} diff --git a/Source/Web Socket/WebSocket.swift b/Source/Web Socket/WebSocket.swift new file mode 100644 index 0000000..2c86f2a --- /dev/null +++ b/Source/Web Socket/WebSocket.swift @@ -0,0 +1,90 @@ +// +// WebSocket.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 04/10/2023. +// + +import Foundation +import RxSwift +import RxRelay + +public class WebSocket { + public let text: PublishRelay = .init() + public let data: PublishRelay = .init() + public let error: PublishRelay = .init() + private let task: URLSessionWebSocketTask + private let closeHandler: WebSocketCloseHandler + private let receiveObservable: Observable + private let disposeBag: DisposeBag = .init() + + /// Creates `WebSocket` instance with generic tyoe `T`. + /// + /// - Parameters: + /// - task: `URLSessionWebSocketTask` that represents the connection to websocket server. + /// - closeHandler: `WebSocketCloseHandler` used to provide close coda and reason upon connection closure. + init(task: URLSessionWebSocketTask, closeHandler: WebSocketCloseHandler) { + self.task = task + self.closeHandler = closeHandler + self.receiveObservable = task.rx.receive(closeHandler: closeHandler) + setupBindings() + } + /// Resumes current task to establish the connection. + public func connect() { + task.resume() + } + /// Cancels the request associated with current task to close the connection. + public func disconnect() { + let request = task.currentRequest + task.cancel(with: closeHandler.code(for: request), reason: closeHandler.reason(for: request)) + } + /// 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 { + task.rx.send(message: message) + } + /// Sends a ping to the websocket server. + /// + /// - Returns: `Completable` observable encapsulating send ping operation. + public func ping() -> Completable { + task.rx.ping() + } + /// Sets up internal observable bindings. + private func setupBindings() { + receiveObservable + .materialize() + .compactMap({ + guard case .next(let element) = $0, case .string(let text) = element else { return nil } + return text + }) + .bind(to: text) + .disposed(by: disposeBag) + receiveObservable + .materialize() + .compactMap({ + guard case .next(let element) = $0, case .data(let data) = element else { return nil } + return data + }) + .flatMap({ (data: Data) in + if T.self == Data.self { + return Observable.just(data as! T) + } else { + return Observable.just(data) + .decode(type: T.self, decoder: JSONDecoder()) + } + }) + .bind(to: data) + .disposed(by: disposeBag) + receiveObservable + .materialize() + .compactMap({ + guard case .error(let error) = $0 else { return nil } + return error + }) + .bind(to: error) + .disposed(by: disposeBag) + } +} diff --git a/Source/Web Socket/WebSocketCloseCode.swift b/Source/Web Socket/WebSocketCloseCode.swift new file mode 100644 index 0000000..88e3b02 --- /dev/null +++ b/Source/Web Socket/WebSocketCloseCode.swift @@ -0,0 +1,10 @@ +// +// WebSocketCloseCode.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 04/10/2023. +// + +import Foundation + +public typealias WebSocketCloseCode = URLSessionWebSocketTask.CloseCode diff --git a/Source/Web Socket/WebSocketCloseHandler.swift b/Source/Web Socket/WebSocketCloseHandler.swift new file mode 100644 index 0000000..c5e0693 --- /dev/null +++ b/Source/Web Socket/WebSocketCloseHandler.swift @@ -0,0 +1,38 @@ +// +// WebSocketCloseHandler.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 04/10/2023. +// + +import Foundation + +public class WebSocketCloseHandler { + private let _code: (URLRequest?) -> WebSocketCloseCode + private let _reason: (URLRequest?) -> Data? + /// Creates `WebSocketCloseHandler` instance. + /// + /// - Parameters: + /// - code: a closure used to provide close code for a given `URLRequest` object. + /// - reason: a closure used to provide close reason for a given `URLRequest` object. + public init(code: @escaping (URLRequest?) -> WebSocketCloseCode, reason: @escaping (URLRequest?) -> Data?) { + self._code = code + self._reason = reason + } + /// Provides close code for a given request. + /// + /// - Parameter request: `URLRequest?` object used to provide close code. + /// + /// - Returns: `WebSocketCloseCode` close code provided using given request. + func code(for request: URLRequest?) -> WebSocketCloseCode { + _code(request) + } + /// Provides close reason for a given request. + /// + /// - Parameter request: `URLRequest?` object used to provide close reason. + /// + /// - Returns: `Data?` close reason provided using given request. + func reason(for request: URLRequest?) -> Data? { + _reason(request) + } +} diff --git a/Source/Web Socket/WebSocketMessage.swift b/Source/Web Socket/WebSocketMessage.swift new file mode 100644 index 0000000..c79c8b3 --- /dev/null +++ b/Source/Web Socket/WebSocketMessage.swift @@ -0,0 +1,10 @@ +// +// WebSocketMessage.swift +// RxNetworkKit +// +// Created by Loay Ashraf on 04/10/2023. +// + +import Foundation + +public typealias WebSocketMessage = URLSessionWebSocketTask.Message