Skip to content

Commit 0b349a7

Browse files
authored
Merge pull request #51 from makinosp:develop
refact
2 parents af8186e + 6dc4569 commit 0b349a7

File tree

6 files changed

+86
-39
lines changed

6 files changed

+86
-39
lines changed

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import PackageDescription
66
let package = Package(
77
name: "VRCKit",
88
platforms: [
9-
.macOS(.v12)
9+
.macOS(.v12),
10+
.iOS(.v15),
11+
.tvOS(.v15),
12+
.watchOS(.v6)
1013
],
1114
products: [
1215
// Products define the executables and libraries a package produces, and make them visible to other packages.

Sources/VRCKit/Client.swift

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ public struct ErrorResponse: Codable, Error {
2424
//
2525
// MARK: API Client
2626
//
27-
@available(macOS 12.0, *)
28-
@available(iOS 15.0, *)
29-
public class APIClient {
27+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
28+
public final class APIClient {
3029
typealias HTTPResponse = (data: Data, response: HTTPURLResponse)
3130

3231
private var username: String?
@@ -41,26 +40,49 @@ public class APIClient {
4140
case json = "application/json"
4241
}
4342

43+
/// Initializes the API client with optional username and password.
44+
/// - Parameters:
45+
/// - username: The username for basic authentication.
46+
/// - password: The password for basic authentication.
4447
public init(username: String? = nil, password: String? = nil) {
4548
self.username = username
4649
self.password = password
4750
}
4851

52+
/// Retrieves the cookies stored for the VRChat API domain.
53+
/// - Returns: An array of `HTTPCookie` objects.
4954
public var cookies: [HTTPCookie] {
50-
HTTPCookieStorage.shared.cookies(for: domainUrl) ?? []
55+
guard let cookies = HTTPCookieStorage.shared.cookies(for: domainUrl) else { return [] }
56+
return cookies
5157
}
5258

59+
/// Deletes all cookies stored for the VRChat API domain.
5360
public func deleteCookies() {
54-
for cookie in cookies {
55-
HTTPCookieStorage.shared.deleteCookie(cookie)
56-
}
61+
cookies.forEach { HTTPCookieStorage.shared.deleteCookie($0) }
5762
}
5863

59-
var encodedAuthorization: String {
60-
"Basic \(((username ?? "") + ":" + (password ?? "")).data(using: .utf8)!.base64EncodedString())"
64+
/// Encodes the given username and password into a Basic Authentication token.
65+
/// - Parameters:
66+
/// - username: The username for authentication.
67+
/// - password: The password associated with the username.
68+
/// - Returns: A Basic Authentication token string.
69+
/// - Throws: `VRCKitError.unexpectedError` if the username and password cannot be converted to UTF-8 data.
70+
func encodeAuthorization(_ username: String, _ password: String) throws -> String {
71+
let authString = "\(username):\(password)"
72+
guard let payload = authString.data(using: .utf8) else {
73+
throw VRCKitError.unexpectedError
74+
}
75+
return "Basic \(payload.base64EncodedString())"
6176
}
6277

63-
/// Request to API
78+
/// Sends a request to the API.
79+
/// - Parameters:
80+
/// - url: The URL for the request.
81+
/// - httpMethod: The HTTP method to use for the request.
82+
/// - basic: Whether to include basic authorization.
83+
/// - httpBody: The HTTP body to include in the request.
84+
/// - Returns: A tuple containing the data and the HTTP response.
85+
/// - Throws: `VRCKitError` if an error occurs during the request.
6486
func request(
6587
url: URL,
6688
httpMethod: HttpMethod,
@@ -70,15 +92,16 @@ public class APIClient {
7092
var request = URLRequest(url: url)
7193
request.httpMethod = httpMethod.description
7294

73-
// Authorization
74-
if basic {
75-
request.addValue(encodedAuthorization, forHTTPHeaderField: "Authorization")
95+
// Add authorization header if required.
96+
if basic, let username = username, let password = password {
97+
let authorizationHeader = try encodeAuthorization(username, password)
98+
request.addValue(authorizationHeader, forHTTPHeaderField: "Authorization")
7699
}
77100

78-
// Cookie
101+
// Add cookies to the request headers.
79102
request.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: cookies)
80103

81-
// HTTP Body
104+
// Add HTTP body and content type if body is provided.
82105
if let httpBody = httpBody {
83106
request.addValue(ContentType.json.rawValue, forHTTPHeaderField: "Content-Type")
84107
request.httpBody = httpBody
@@ -92,8 +115,7 @@ public class APIClient {
92115
}
93116
}
94117

95-
@available(macOS 12.0, *)
96-
@available(iOS 15.0, *)
118+
@available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
97119
extension APIClient.HttpMethod: CustomStringConvertible {
98120
var description: String {
99121
self.rawValue.uppercased()

Sources/VRCKit/Errors.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,31 @@
77

88
import Foundation
99

10+
/// An enumeration that represents various errors that can occur in the VRCKit framework.
11+
/// Conforms to the `Error` and `LocalizedError` protocols for better error handling and localization.
1012
public enum VRCKitError: Error, LocalizedError {
11-
public typealias RawValue = String
13+
private typealias RawValue = String
1214

15+
/// Represents an error from the API with details.
1316
case apiError(_ details: String)
17+
18+
/// Represents an error indicating that the client has been deallocated.
1419
case clientDeallocated
15-
case encodingError
20+
21+
/// Represents an error indicating an invalid response was received.
1622
case invalidResponseError
23+
24+
/// Represents an error indicating an invalid request with additional details.
1725
case invalidRequest(_ details: String)
26+
27+
/// Represents an unexpected error.
1828
case unexpectedError
1929

30+
/// Provides a localized description of the error.
2031
public var errorDescription: String? {
2132
switch self {
2233
case .apiError(let _):
2334
"API Error"
24-
case .encodingError:
25-
"Encoding Error"
2635
case .clientDeallocated:
2736
"Client Deallocated"
2837
case .invalidResponseError:
@@ -34,6 +43,7 @@ public enum VRCKitError: Error, LocalizedError {
3443
}
3544
}
3645

46+
/// Provides a localized failure reason for the error.
3747
public var failureReason: String? {
3848
switch self {
3949
case .apiError(let details), .invalidRequest(let details):

Sources/VRCKit/Protocols/LocationProtocol.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@
55
// Created by makinosp on 2024/06/16.
66
//
77

8-
@available(macOS 12.0, *)
9-
@available(iOS 15.0, *)
8+
/// Represents a type that has a location string.
109
public protocol LocationRepresentable {
1110
var location: String { get }
11+
var isVisible: Bool { get }
1212
}
1313

14-
@available(macOS 12.0, *)
15-
@available(iOS 15.0, *)
1614
public extension LocationRepresentable {
15+
/// Determines if the location is visible based on predefined criteria.
1716
var isVisible: Bool {
1817
!["private", "offline", "traveling"].contains(location)
1918
}

Sources/VRCKit/Protocols/ProfileProtocols.swift

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
import Foundation
99

10-
/// Common properties the 3 structures User, Friend, and UserDetail
11-
@available(macOS 12.0, *)
12-
@available(iOS 15.0, *)
10+
/// A protocol representing common properties for user profile elements.
11+
/// This protocol can be adopted by structures such as User, Friend, and UserDetail.
12+
@available(iOS 13.0, *)
1313
public protocol ProfileElementRepresentable: Hashable, Identifiable {
1414
var bio: String? { get }
1515
var bioLinks: [String]? { get }
@@ -28,16 +28,15 @@ public protocol ProfileElementRepresentable: Hashable, Identifiable {
2828
var friendKey: String { get }
2929
}
3030

31-
/// Common properties the 2 structures User and UserDetail
32-
@available(macOS 12.0, *)
33-
@available(iOS 15.0, *)
31+
/// A protocol representing detailed profile properties for users.
32+
/// This protocol extends ProfileElementRepresentable and can be adopted by structures like User and UserDetail.
33+
@available(iOS 13.0, *)
3434
public protocol ProfileDetailRepresentable: ProfileElementRepresentable {
3535
var dateJoined: String { get }
3636
var lastActivity: Date { get }
3737
}
3838

39-
@available(macOS 12.0, *)
40-
@available(iOS 15.0, *)
39+
@available(iOS 13.0, *)
4140
public extension ProfileElementRepresentable {
4241
var thumbnailUrl: URL? {
4342
if let userIcon = userIcon, !userIcon.isEmpty {

Sources/VRCKit/Util.swift

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// Util.swift
3-
//
3+
//
44
//
55
// Created by makinosp on 2024/03/10.
66
//
@@ -12,20 +12,30 @@ public final class Util {
1212
private var decoder = JSONDecoder()
1313
private var encoder = JSONEncoder()
1414

15-
public init() {
15+
/// Initializes the `Util` class, setting up custom encoding and decoding strategies.
16+
init() {
1617
decoder.dateDecodingStrategy = .formatted(.iso8601Full)
1718
decoder.keyDecodingStrategy = .convertFromSnakeCase
1819
encoder.dateEncodingStrategy = .formatted(.iso8601Full)
1920
}
2021

21-
public func urlComponents(_ string: String) throws -> URLComponents {
22+
/// Creates URL components from a given string.
23+
/// - Parameter string: The URL string to be converted into `URLComponents`.
24+
/// - Throws: `VRCKitError.invalidRequest` if the URL string is invalid.
25+
/// - Returns: A `URLComponents` object created from the input string.
26+
func urlComponents(_ string: String) throws -> URLComponents {
2227
guard let urlComponents = URLComponents(string: string) else {
2328
throw VRCKitError.invalidRequest("Bad URL: \(string)")
2429
}
2530
return urlComponents
2631
}
2732

28-
public func decode<T: Decodable>(_ data: Data) throws -> T {
33+
/// Decodes JSON data into a specified `Decodable` type.
34+
/// - Parameter data: The JSON data to decode.
35+
/// - Throws: `VRCKitError.apiError` if the decoded data contains an API error.
36+
/// - Throws: `DecodingError` if the decoding process fails.
37+
/// - Returns: The decoded object of type `T`.
38+
func decode<T: Decodable>(_ data: Data) throws -> T {
2939
do {
3040
return try decoder.decode(T.self, from: data)
3141
} catch {
@@ -44,7 +54,11 @@ public final class Util {
4454
}
4555
}
4656

47-
public func encode(_ data: Encodable) throws -> Data {
57+
/// Encodes an `Encodable` object into JSON data.
58+
/// - Parameter data: The object to encode.
59+
/// - Throws: An error if the encoding process fails.
60+
/// - Returns: The encoded JSON data.
61+
func encode(_ data: Encodable) throws -> Data {
4862
try encoder.encode(data)
4963
}
5064
}

0 commit comments

Comments
 (0)