2022-04-12 13:09:14 +00:00
|
|
|
//
|
2023-05-24 16:19:47 +00:00
|
|
|
// APIWebServices.swift
|
2022-04-12 13:09:14 +00:00
|
|
|
// Passepartout
|
|
|
|
//
|
|
|
|
// Created by Davide De Rosa on 9/14/18.
|
2024-01-14 13:34:21 +00:00
|
|
|
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
2022-04-12 13:09:14 +00:00
|
|
|
//
|
|
|
|
// https://github.com/passepartoutvpn
|
|
|
|
//
|
|
|
|
// This file is part of Passepartout.
|
|
|
|
//
|
|
|
|
// Passepartout is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// Passepartout is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Combine
|
2023-04-04 07:50:45 +00:00
|
|
|
import Foundation
|
2023-05-24 16:19:47 +00:00
|
|
|
import PassepartoutCore
|
|
|
|
import PassepartoutServices
|
2022-04-12 13:09:14 +00:00
|
|
|
|
2023-05-24 16:19:47 +00:00
|
|
|
public final class APIWebServices: WebServices {
|
2022-04-12 13:09:14 +00:00
|
|
|
private enum Group: String {
|
|
|
|
case providers
|
|
|
|
}
|
|
|
|
|
|
|
|
private enum Endpoint: GenericWebEndpoint {
|
|
|
|
case providersIndex
|
|
|
|
|
|
|
|
case providerNetwork(WSProviderName, WSVPNProtocol)
|
2023-03-17 20:55:47 +00:00
|
|
|
|
2022-04-12 13:09:14 +00:00
|
|
|
private var pathName: String {
|
|
|
|
switch self {
|
|
|
|
case .providersIndex:
|
2023-05-28 09:57:35 +00:00
|
|
|
return [Group.providers.rawValue, "index"].joined(separator: "/")
|
2022-04-12 13:09:14 +00:00
|
|
|
|
|
|
|
case .providerNetwork(let providerName, let vpnProtocol):
|
2023-05-28 09:57:35 +00:00
|
|
|
return [Group.providers.rawValue, providerName, vpnProtocol.filename].joined(separator: "/")
|
2022-04-12 13:09:14 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-17 20:55:47 +00:00
|
|
|
|
2022-04-12 13:09:14 +00:00
|
|
|
private var fileType: String {
|
2022-09-04 18:09:31 +00:00
|
|
|
"json"
|
2022-04-12 13:09:14 +00:00
|
|
|
}
|
2023-03-17 20:55:47 +00:00
|
|
|
|
2022-04-12 13:09:14 +00:00
|
|
|
// MARK: GenericWebEndpoint
|
|
|
|
|
|
|
|
var path: String {
|
2023-05-28 09:57:35 +00:00
|
|
|
[pathName, fileType].joined(separator: ".")
|
2022-04-12 13:09:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 16:19:47 +00:00
|
|
|
private let ws: GenericWebServices<APIError>
|
2023-03-17 20:55:47 +00:00
|
|
|
|
2022-04-12 13:09:14 +00:00
|
|
|
public init(_ version: String, _ root: URL, timeout: TimeInterval?, queue: DispatchQueue = .main) {
|
|
|
|
ws = GenericWebServices(version, root, timeout: timeout)
|
|
|
|
}
|
|
|
|
|
|
|
|
public func providersIndex() -> AnyPublisher<WSProvidersIndex, Error> {
|
|
|
|
let request = ws.get(Endpoint.providersIndex)
|
|
|
|
return ws.parse(WSProvidersIndex.self, request: request)
|
|
|
|
.tryMap {
|
|
|
|
guard let value = $0.value else {
|
2023-05-24 16:19:47 +00:00
|
|
|
throw APIError.emptyResponse
|
2022-04-12 13:09:14 +00:00
|
|
|
}
|
|
|
|
return value
|
|
|
|
}.eraseToAnyPublisher()
|
|
|
|
}
|
|
|
|
|
|
|
|
public func providerNetwork(with name: WSProviderName, vpnProtocol: WSVPNProtocol, ifModifiedSince lastModified: Date?) -> AnyPublisher<GenericWebResponse<WSProviderInfrastructure>, Error> {
|
|
|
|
var request = ws.get(Endpoint.providerNetwork(name, vpnProtocol))
|
|
|
|
if let lastModified = lastModified {
|
|
|
|
request.addValue(GenericWebParser.lastModifiedString(date: lastModified), forHTTPHeaderField: "If-Modified-Since")
|
|
|
|
}
|
|
|
|
return ws.parse(WSProviderInfrastructure.self, request: request)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-24 16:19:47 +00:00
|
|
|
enum APIError: GenericWebServicesError, LocalizedError {
|
|
|
|
case http(Int)
|
|
|
|
|
|
|
|
case emptyResponse
|
|
|
|
|
|
|
|
case unknown
|
|
|
|
|
|
|
|
static func httpStatus(_ status: Int) -> APIError {
|
|
|
|
.http(status)
|
|
|
|
}
|
|
|
|
|
|
|
|
var errorDescription: String? {
|
|
|
|
switch self {
|
|
|
|
case .http(let status):
|
|
|
|
return "HTTP \(status)"
|
|
|
|
|
|
|
|
case .emptyResponse:
|
|
|
|
return "Empty response"
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension APIWebServices {
|
|
|
|
public static func bundledServices(withVersion version: String) -> WebServices {
|
2022-04-12 13:09:14 +00:00
|
|
|
guard let apiURL = Bundle.module.url(forResource: "API", withExtension: nil) else {
|
|
|
|
fatalError("Could not find API in bundle")
|
|
|
|
}
|
2023-05-24 16:19:47 +00:00
|
|
|
return APIWebServices(version, apiURL, timeout: nil)
|
2022-04-12 13:09:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private extension WSVPNProtocol {
|
|
|
|
var filename: String {
|
|
|
|
switch self {
|
|
|
|
case .openVPN:
|
|
|
|
return "ovpn"
|
2023-03-17 20:55:47 +00:00
|
|
|
|
2022-04-12 13:09:14 +00:00
|
|
|
case .wireGuard:
|
|
|
|
return "wg"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|