mirror of
https://github.com/xtool-org/xtool.git
synced 2026-02-04 11:53:30 +01:00
123 lines
4.5 KiB
Swift
123 lines
4.5 KiB
Swift
//
|
|
// DeveloperServicesClient.swift
|
|
// Supercharge
|
|
//
|
|
// Created by Kabir Oberai on 24/07/19.
|
|
// Copyright © 2019 Kabir Oberai. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import Dependencies
|
|
|
|
public struct DeveloperServicesClient: Sendable {
|
|
|
|
public enum Error: LocalizedError {
|
|
case noData
|
|
case malformedRequest
|
|
case apiError(code: Int, reason: String?)
|
|
|
|
public var errorDescription: String? {
|
|
switch self {
|
|
case .noData:
|
|
return NSLocalizedString(
|
|
"developer_services_client.error.no_data", value: "No data", comment: ""
|
|
)
|
|
case .malformedRequest:
|
|
return NSLocalizedString(
|
|
"developer_services_client.error.malformed_request", value: "Malformed request", comment: ""
|
|
)
|
|
case .apiError(let code, let reason?):
|
|
return reason.withCString { reasonC in
|
|
"\(code)".withCString { codeC in
|
|
String.localizedStringWithFormat(
|
|
NSLocalizedString(
|
|
"developer_services_client.error.sign_in_error.reason_and_code",
|
|
value: "%s (%s)",
|
|
comment: "First reason, then code"
|
|
),
|
|
reasonC, codeC
|
|
)
|
|
}
|
|
}
|
|
case .apiError(let code, _):
|
|
return "\(code)".withCString {
|
|
String.localizedStringWithFormat(
|
|
NSLocalizedString(
|
|
"developer_services_client.error.sign_in_error.only_code",
|
|
value: "An unknown error occurred (%s)",
|
|
comment: ""
|
|
),
|
|
$0
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Dependency(\.deviceInfoProvider) var deviceInfoProvider
|
|
@Dependency(\.anisetteDataProvider) var anisetteDataProvider
|
|
@Dependency(\.httpClient) var httpClient
|
|
|
|
public let loginToken: DeveloperServicesLoginToken
|
|
|
|
public init(loginToken: DeveloperServicesLoginToken) {
|
|
self.loginToken = loginToken
|
|
}
|
|
|
|
public init(authData: XcodeAuthData) {
|
|
self.loginToken = authData.loginToken
|
|
}
|
|
|
|
private func send<R: DeveloperServicesRequest>(
|
|
_ request: R,
|
|
anisetteData: AnisetteData
|
|
) async throws -> R.Value {
|
|
guard let url = request.apiVersion.url(forAction: request.action) else {
|
|
throw Error.malformedRequest
|
|
}
|
|
|
|
let deviceInfo = try deviceInfoProvider.fetch()
|
|
|
|
var httpRequest = HTTPRequest(url: url, method: "POST")
|
|
let acceptedLanguages = Locale.preferredLanguages.joined(separator: ", ")
|
|
|
|
httpRequest.headers["Accept-Language"] = acceptedLanguages
|
|
httpRequest.headers["Accept"] = request.apiVersion.accept
|
|
httpRequest.headers["Content-Type"] = request.apiVersion.contentType
|
|
httpRequest.headers["User-Agent"] = "Xcode"
|
|
request.methodOverride.map { httpRequest.headers["X-HTTP-Method-Override"] = $0 }
|
|
|
|
httpRequest.headers[DeviceInfo.xcodeVersionKey] = DeviceInfo.xcodeVersion
|
|
httpRequest.headers[DeviceInfo.clientInfoKey] = deviceInfo.clientInfo.clientString
|
|
httpRequest.headers[DeviceInfo.deviceIDKey] = deviceInfo.deviceID
|
|
|
|
httpRequest.headers["X-Apple-I-Identity-Id"] = loginToken.adsid
|
|
httpRequest.headers["X-Apple-App-Info"] = AppTokenKey.xcode.rawValue
|
|
httpRequest.headers["X-Apple-GS-Token"] = loginToken.token
|
|
|
|
anisetteData.dictionary.forEach { httpRequest.headers[$0] = $1 }
|
|
|
|
httpRequest.body = try .buffer(request.apiVersion.body(withParameters: request.parameters))
|
|
|
|
request.configure(urlRequest: &httpRequest)
|
|
|
|
let resp = try await httpClient.makeRequest(httpRequest)
|
|
|
|
// we don't throw if data is nil because sometimes no data is
|
|
// okay (eg in the case of EmptyResponse)
|
|
let data = resp.body ?? Data()
|
|
|
|
// String(data: data, encoding: .utf8).map { print("\(url): \($0)") }
|
|
|
|
let decoded: R.Response = try request.apiVersion.decode(response: data)
|
|
|
|
return try await request.parse(decoded)
|
|
}
|
|
|
|
public func send<R: DeveloperServicesRequest>(_ request: R) async throws -> R.Value {
|
|
let anisetteData = try await anisetteDataProvider.fetchAnisetteData()
|
|
return try await self.send(request, anisetteData: anisetteData)
|
|
}
|
|
|
|
}
|