diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2023-06-08 20:50:07 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2023-06-08 20:50:07 +0200 |
| commit | 394a9899e6ad92ff3b54455907bb07ed08ba6d43 (patch) | |
| tree | 6c7026a7be7b06bf5fa6241e20c5dc18f94e7cc6 | |
| parent | e9baaa7e7499acc90d820f823066864d357800f3 (diff) | |
| parent | 68a2a9a369b55d977c4b60beb61179f8a8423faf (diff) | |
| download | mullvadvpn-394a9899e6ad92ff3b54455907bb07ed08ba6d43.tar.xz mullvadvpn-394a9899e6ad92ff3b54455907bb07ed08ba6d43.zip | |
Merge branch 'implement-redeem-voucher-service-ios-187'
| -rw-r--r-- | ios/MullvadREST/RESTAPIProxy.swift | 57 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift | 83 |
3 files changed, 144 insertions, 0 deletions
diff --git a/ios/MullvadREST/RESTAPIProxy.swift b/ios/MullvadREST/RESTAPIProxy.swift index 81384026a3..25b80a2c50 100644 --- a/ios/MullvadREST/RESTAPIProxy.swift +++ b/ios/MullvadREST/RESTAPIProxy.swift @@ -208,6 +208,50 @@ extension REST { completionHandler: completionHandler ) } + + public func submitVoucher( + voucherCode: String, + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping CompletionHandler<SubmitVoucherResponse> + ) -> Cancellable { + let requestHandler = AnyRequestHandler( + createURLRequest: { endpoint, authorization in + var requestBuilder = try self.requestFactory.createRequestBuilder( + endpoint: endpoint, + method: .post, + pathTemplate: "submit-voucher" + ) + + requestBuilder.setAuthorization(authorization) + + try requestBuilder.setHTTPBody(value: SubmitVoucherRequest(voucherCode: voucherCode)) + + return requestBuilder.getRequest() + }, + authorizationProvider: createAuthorizationProvider(accountNumber: accountNumber) + ) + + let responseHandler = AnyResponseHandler { response, data -> ResponseHandlerResult<SubmitVoucherResponse> in + if HTTPStatus.isSuccess(response.statusCode) { + return .decoding { + try self.responseDecoder.decode(SubmitVoucherResponse.self, from: data) + } + } else { + return .unhandledResponse( + try? self.responseDecoder.decode(ServerErrorResponse.self, from: data) + ) + } + } + + return addOperation( + name: "submit-voucher", + retryStrategy: retryStrategy, + requestHandler: requestHandler, + responseHandler: responseHandler, + completionHandler: completionHandler + ) + } } // MARK: - Response types @@ -269,4 +313,17 @@ extension REST { self.metadata = metadata } } + + private struct SubmitVoucherRequest: Encodable { + let voucherCode: String + } + + public struct SubmitVoucherResponse: Decodable { + public let timeAdded: Int + public let newExpiry: Date + + public var dateComponents: DateComponents { + return DateComponents(second: timeAdded) + } + } } diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 73a534a971..5914fadb0f 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -403,6 +403,7 @@ E1FD0DF528AA7CE400299DB4 /* StatusActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */; }; F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */; }; F07BF2582A26112D00042943 /* InputTextFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */; }; + F07BF2622A26279100042943 /* RedeemVoucherOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */; }; F07CFF2029F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */; }; F0C2AEFD2A0BB5CC00986207 /* NotificationProviderIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */; }; /* End PBXBuildFile section */ @@ -1115,6 +1116,7 @@ E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusActivityView.swift; sourceTree = "<group>"; }; F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncreasedHitButton.swift; sourceTree = "<group>"; }; F07BF2572A26112D00042943 /* InputTextFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextFormatterTests.swift; sourceTree = "<group>"; }; + F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherOperation.swift; sourceTree = "<group>"; }; F07CFF1F29F2720E008C0343 /* RegisteredDeviceInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisteredDeviceInAppNotificationProvider.swift; sourceTree = "<group>"; }; F0C2AEFC2A0BB5CC00986207 /* NotificationProviderIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationProviderIdentifier.swift; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -1383,6 +1385,7 @@ children = ( 588527B1276B3F0700BAA373 /* LoadTunnelConfigurationOperation.swift */, 58F2E147276A307400A79513 /* MapConnectionStatusOperation.swift */, + F07BF2612A26279100042943 /* RedeemVoucherOperation.swift */, 58F2E14B276A61C000A79513 /* RotateKeyOperation.swift */, 586E54FA27A2DF6D0029B88B /* SendTunnelProviderMessageOperation.swift */, 588527B3276B4F2F00BAA373 /* SetAccountOperation.swift */, @@ -2962,6 +2965,7 @@ 5893C6FC29C311E9009090D1 /* ApplicationRouter.swift in Sources */, 58E25F812837BBBB002CFB2C /* SceneDelegate.swift in Sources */, 5867771629097C5B006F721F /* ProductState.swift in Sources */, + F07BF2622A26279100042943 /* RedeemVoucherOperation.swift in Sources */, 585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */, 5820676426E771DB00655B05 /* TunnelManagerErrors.swift in Sources */, 585B4B8726D9098900555C4C /* TunnelStatusNotificationProvider.swift in Sources */, diff --git a/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift b/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift new file mode 100644 index 0000000000..d87c61b37e --- /dev/null +++ b/ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift @@ -0,0 +1,83 @@ +// +// RedeemVoucherOperation.swift +// MullvadVPN +// +// Created by pronebird on 29/03/2023. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadLogging +import MullvadREST +import MullvadTypes +import Operations + +class RedeemVoucherOperation: ResultOperation<REST.SubmitVoucherResponse> { + private let logger = Logger(label: "RedeemVoucherOperation") + private let interactor: TunnelInteractor + + private let voucherCode: String + private let apiProxy: REST.APIProxy + private var task: Cancellable? + + init( + dispatchQueue: DispatchQueue, + interactor: TunnelInteractor, + voucherCode: String, + apiProxy: REST.APIProxy + ) { + self.interactor = interactor + self.voucherCode = voucherCode + self.apiProxy = apiProxy + + super.init(dispatchQueue: dispatchQueue) + } + + override func main() { + guard case let .loggedIn(accountData, _) = interactor.deviceState else { + finish(result: .failure(InvalidDeviceStateError())) + return + } + task = apiProxy.submitVoucher( + voucherCode: voucherCode, + accountNumber: accountData.number, + retryStrategy: .default + ) { result in + self.dispatchQueue.async { + self.didReceiveVoucherResponse(result: result) + } + } + } + + override func operationDidCancel() { + task?.cancel() + task = nil + } + + private func didReceiveVoucherResponse(result: Result<REST.SubmitVoucherResponse, Error>) { + let result = result.inspectError { error in + guard !error.isOperationCancellationError else { return } + + self.logger.error( + error: error, + message: "Failed to redeem voucher." + ) + }.tryMap { voucherResponse in + switch interactor.deviceState { + case .loggedIn(var storedAccountData, let storedDeviceData): + storedAccountData.expiry = voucherResponse.newExpiry + + let newDeviceState = DeviceState.loggedIn(storedAccountData, storedDeviceData) + + interactor.setDeviceState(newDeviceState, persist: true) + + return voucherResponse + + default: + throw InvalidDeviceStateError() + } + } + + finish(result: result) + } +} |
