summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/View controllers/RedeemVoucher/RedeemVoucherInteractor.swift
blob: f0e49ac5cfaad7c553169f5a5f0fe0ae625b9977 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//
//  RedeemVoucherInteractor.swift
//  MullvadVPN
//
//  Created by Mojgan on 2023-08-30.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadREST
import MullvadTypes

final class RedeemVoucherInteractor: @unchecked Sendable {
    private let tunnelManager: TunnelManager
    private let accountsProxy: RESTAccountHandling
    private let shouldVerifyVoucherAsAccount: Bool

    private var tasks: [Cancellable] = []
    private var preferredAccountNumber: String?

    var showLogoutDialog: (() -> Void)?
    var didLogout: ((String) -> Void)?

    init(
        tunnelManager: TunnelManager,
        accountsProxy: RESTAccountHandling,
        verifyVoucherAsAccount: Bool
    ) {
        self.tunnelManager = tunnelManager
        self.accountsProxy = accountsProxy
        self.shouldVerifyVoucherAsAccount = verifyVoucherAsAccount
    }

    func redeemVoucher(
        code: String,
        completion: @escaping (@Sendable (Result<REST.SubmitVoucherResponse, Error>) -> Void)
    ) {
        tasks.append(
            tunnelManager.redeemVoucher(code) { [weak self] result in
                guard let self else { return }
                completion(result)
                guard shouldVerifyVoucherAsAccount,
                    result.error?.isInvalidVoucher ?? false
                else {
                    return
                }
                verifyVoucherAsAccount(code: code)
            })
    }

    func logout() async {
        guard let accountNumber = preferredAccountNumber else { return }
        await tunnelManager.unsetAccount()
        didLogout?(accountNumber)
    }

    func cancelAll() {
        tasks.forEach { $0.cancel() }
    }

    private func verifyVoucherAsAccount(code: String) {
        let task = accountsProxy.getAccountData(
            accountNumber: code,
            retryStrategy: .noRetry
        ) { [weak self] result in
            guard let self,
                case .success = result
            else {
                return
            }
            showLogoutDialog?()
            preferredAccountNumber = code
        }

        tasks.append(task)
    }
}

fileprivate extension Error {
    var isInvalidVoucher: Bool {
        (self as? REST.Error)?.compareErrorCode(.invalidVoucher) ?? false
    }
}