165 lines
4.9 KiB
Swift
165 lines
4.9 KiB
Swift
import Foundation
|
|
|
|
actor RelayAPI {
|
|
static let shared = RelayAPI()
|
|
|
|
func createPairing(displayName: String) async throws -> PairingCreateResponse {
|
|
var request = URLRequest(url: endpoint(path: "pairing/create"))
|
|
request.httpMethod = "POST"
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.httpBody = try JSONEncoder().encode(PairingRequest(displayName: displayName))
|
|
|
|
return try await executeJSON(request)
|
|
}
|
|
|
|
func joinPairing(code: String, displayName: String) async throws -> PairingJoinResponse {
|
|
var request = URLRequest(url: endpoint(path: "pairing/join"))
|
|
request.httpMethod = "POST"
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.httpBody = try JSONEncoder().encode(
|
|
PairingJoinRequest(code: code, displayName: displayName)
|
|
)
|
|
|
|
return try await executeJSON(request)
|
|
}
|
|
|
|
func uploadFile(url: URL, token: String) async throws -> FileUploadResponse {
|
|
let boundary = "Boundary-\(UUID().uuidString)"
|
|
let fileData: Data
|
|
|
|
do {
|
|
fileData = try Data(contentsOf: url)
|
|
} catch {
|
|
throw RelayAPIError.networkError(error)
|
|
}
|
|
|
|
var request = URLRequest(url: endpoint(path: "files"))
|
|
request.httpMethod = "POST"
|
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
request.setValue(
|
|
"multipart/form-data; boundary=\(boundary)",
|
|
forHTTPHeaderField: "Content-Type"
|
|
)
|
|
request.httpBody = multipartBody(
|
|
fileData: fileData,
|
|
fileName: safeFileName(from: url),
|
|
boundary: boundary
|
|
)
|
|
|
|
return try await executeJSON(request)
|
|
}
|
|
|
|
func downloadFile(id: String, token: String) async throws -> Data {
|
|
var request = URLRequest(url: endpoint(path: "files/\(id)"))
|
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
return try await execute(request)
|
|
}
|
|
|
|
private let baseURL = URL(string: "https://fchati.diyaa.de")!
|
|
private let session = URLSession.shared
|
|
|
|
private init() {}
|
|
|
|
private func endpoint(path: String) -> URL {
|
|
baseURL.appendingPathComponent(path)
|
|
}
|
|
|
|
private func executeJSON<Response: Decodable>(_ request: URLRequest) async throws -> Response {
|
|
let data = try await execute(request)
|
|
|
|
do {
|
|
return try JSONDecoder().decode(Response.self, from: data)
|
|
} catch {
|
|
throw RelayAPIError.decodingError(error)
|
|
}
|
|
}
|
|
|
|
private func execute(_ request: URLRequest) async throws -> Data {
|
|
do {
|
|
let (data, response) = try await session.data(for: request)
|
|
|
|
guard let response = response as? HTTPURLResponse else {
|
|
throw RelayAPIError.networkError(URLError(.badServerResponse))
|
|
}
|
|
|
|
guard (200 ... 299).contains(response.statusCode) else {
|
|
throw RelayAPIError.serverError(response.statusCode)
|
|
}
|
|
|
|
return data
|
|
} catch let error as RelayAPIError {
|
|
throw error
|
|
} catch {
|
|
throw RelayAPIError.networkError(error)
|
|
}
|
|
}
|
|
|
|
private func multipartBody(fileData: Data, fileName: String, boundary: String) -> Data {
|
|
var body = Data()
|
|
let escapedFileName = fileName
|
|
.replacingOccurrences(of: "\\", with: "\\\\")
|
|
.replacingOccurrences(of: "\"", with: "\\\"")
|
|
.replacingOccurrences(of: "\r", with: "")
|
|
.replacingOccurrences(of: "\n", with: "")
|
|
|
|
append("--\(boundary)\r\n", to: &body)
|
|
append(
|
|
"Content-Disposition: form-data; name=\"file\"; filename=\"\(escapedFileName)\"\r\n",
|
|
to: &body
|
|
)
|
|
append("Content-Type: application/octet-stream\r\n\r\n", to: &body)
|
|
body.append(fileData)
|
|
append("\r\n--\(boundary)--\r\n", to: &body)
|
|
|
|
return body
|
|
}
|
|
|
|
private func safeFileName(from url: URL) -> String {
|
|
let name = url.lastPathComponent
|
|
return name.isEmpty ? "upload" : name
|
|
}
|
|
|
|
private func append(_ string: String, to data: inout Data) {
|
|
data.append(Data(string.utf8))
|
|
}
|
|
}
|
|
|
|
struct PairingCreateResponse: Codable, Equatable {
|
|
let code: String
|
|
let token: String
|
|
let peerID: String
|
|
let expiresAt: String
|
|
}
|
|
|
|
struct PairingJoinResponse: Codable, Equatable {
|
|
let token: String
|
|
let peerID: String
|
|
let peer: PeerInfo
|
|
}
|
|
|
|
struct PeerInfo: Codable, Equatable {
|
|
let id: String
|
|
let displayName: String
|
|
}
|
|
|
|
struct FileUploadResponse: Codable, Equatable {
|
|
let id: String
|
|
let name: String
|
|
let size: Int
|
|
}
|
|
|
|
enum RelayAPIError: Error {
|
|
case networkError(Error)
|
|
case serverError(Int)
|
|
case decodingError(Error)
|
|
}
|
|
|
|
private struct PairingRequest: Encodable {
|
|
let displayName: String
|
|
}
|
|
|
|
private struct PairingJoinRequest: Encodable {
|
|
let code: String
|
|
let displayName: String
|
|
}
|