summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-06-08 20:50:07 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-06-08 20:50:07 +0200
commit394a9899e6ad92ff3b54455907bb07ed08ba6d43 (patch)
tree6c7026a7be7b06bf5fa6241e20c5dc18f94e7cc6
parente9baaa7e7499acc90d820f823066864d357800f3 (diff)
parent68a2a9a369b55d977c4b60beb61179f8a8423faf (diff)
downloadmullvadvpn-394a9899e6ad92ff3b54455907bb07ed08ba6d43.tar.xz
mullvadvpn-394a9899e6ad92ff3b54455907bb07ed08ba6d43.zip
Merge branch 'implement-redeem-voucher-service-ios-187'
-rw-r--r--ios/MullvadREST/RESTAPIProxy.swift57
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/TunnelManager/RedeemVoucherOperation.swift83
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)
+ }
+}