Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various patch changes #8

Merged
merged 1 commit into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions Sources/AppleMapsKit/AppleMapsClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,11 @@ public struct AppleMapsClient: Sendable {
) async throws -> [Eta] {
var destinationCoordinates: [(latitude: Double, longitude: Double)] = []
for destination in destinations {
try await destinationCoordinates.append(self.getCoordinate(from: destination))
try await destinationCoordinates.append(getCoordinate(from: destination))
}

return try await self.eta(
from: self.getCoordinate(from: origin),
return try await eta(
from: getCoordinate(from: origin),
to: destinationCoordinates,
transportType: transportType,
departureDate: departureDate,
Expand Down Expand Up @@ -520,8 +520,7 @@ public struct AppleMapsClient: Sendable {
/// - Throws: Error response object.
private func httpGet(url: URL) async throws -> ByteBuffer {
var headers = HTTPHeaders()
let accessToken = try await authorizationProvider.validToken().accessToken
headers.add(name: "Authorization", value: "Bearer \(accessToken)")
headers.add(name: "Authorization", value: "Bearer \(try await authorizationProvider.accessToken)")

var request = HTTPClientRequest(url: url.absoluteString)
request.headers = headers
Expand Down
2 changes: 1 addition & 1 deletion Sources/AppleMapsKit/AppleMapsKitError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ public struct AppleMapsKitError: Error, Sendable {

extension AppleMapsKitError: CustomStringConvertible {
public var description: String {
"AppleMapsKitError(errorType: \(self.errorType))"
"AppleMapsKitError(errorType: \(errorType))"
}
}
150 changes: 71 additions & 79 deletions Sources/AppleMapsKit/Authorization/AuthorizationProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,101 +28,93 @@ actor AuthorizationProvider {
self.key = key
}

func validToken() async throws -> TokenResponse {
// If we're currently refreshing a token, await the value for our refresh task to make sure we return the refreshed token.
if let refreshTask {
return try await refreshTask.value
}
var accessToken: String {
get async throws {
// If we're currently refreshing a token, await the value for our refresh task to make sure we return the refreshed token.
if let refreshTask {
return try await refreshTask.value.accessToken
}

// If we don't have a current token, we request a new one.
guard let currentToken else {
return try await refreshToken()
}
// If we don't have a current token, we request a new one.
guard let currentToken else {
return try await newToken
}

if currentToken.isValid {
return currentToken
}
if currentToken.isValid {
return currentToken.accessToken
}

// None of the above applies so we'll need to refresh the token.
return try await refreshToken()
// None of the above applies so we'll need to refresh the token.
return try await newToken
}
}

private func refreshToken() async throws -> TokenResponse {
if let refreshTask {
return try await refreshTask.value
}
private var newToken: String {
get async throws {
// If we're currently refreshing a token, await the value for our refresh task to make sure we return the refreshed token.
if let refreshTask {
return try await refreshTask.value.accessToken
}

let task = Task { () throws -> TokenResponse in
defer { refreshTask = nil }
let authToken = try await createJWT(teamID: teamID, keyID: keyID, key: key)
let newToken = try await getAccessToken(authToken: authToken)
currentToken = newToken
return newToken
}
// If we don't have a current token, we request a new one.
let task = Task { () throws -> TokenResponse in
defer { refreshTask = nil }
let newToken = try await tokenResponse
currentToken = newToken
return newToken
}

self.refreshTask = task
return try await task.value
refreshTask = task
return try await task.value.accessToken
}
}
}

extension AuthorizationProvider {
/// Makes an HTTP request to exchange Auth token for Access token.
///
/// - Parameters:
/// - httpClient: The HTTP client to use.
/// - authToken: The authorization token.
///
/// - Throws: Error response object.
///
/// - Returns: An access token.
private func getAccessToken(authToken: String) async throws -> TokenResponse {
var headers = HTTPHeaders()
headers.add(name: "Authorization", value: "Bearer \(authToken)")

var request = HTTPClientRequest(url: "\(apiServer)/v1/token")
request.headers = headers

let response = try await httpClient.execute(request, timeout: .seconds(30))

if response.status == .ok {
return try await JSONDecoder().decode(TokenResponse.self, from: response.body.collect(upTo: 1024 * 1024))
} else {
throw try await JSONDecoder().decode(ErrorResponse.self, from: response.body.collect(upTo: 1024 * 1024))
private var tokenResponse: TokenResponse {
get async throws {
var headers = HTTPHeaders()
headers.add(name: "Authorization", value: "Bearer \(try await jwtToken)")

var request = HTTPClientRequest(url: "\(apiServer)/v1/token")
request.headers = headers

let response = try await httpClient.execute(request, timeout: .seconds(30))

if response.status == .ok {
return try await JSONDecoder().decode(TokenResponse.self, from: response.body.collect(upTo: 1024 * 1024))
} else {
throw try await JSONDecoder().decode(ErrorResponse.self, from: response.body.collect(upTo: 1024 * 1024))
}
}
}

/// Creates a JWT token, which is auth token in this context.
///
/// - Parameters:
/// - teamID: A 10-character Team ID obtained from your Apple Developer account.
/// - keyID: A 10-character key identifier that provides the ID of the private key that you obtain from your Apple Developer account.
/// - key: A MapKit JS private key.
///
/// - Returns: A JWT token represented as `String`.
private func createJWT(teamID: String, keyID: String, key: String) async throws -> String {
let keys = try await JWTKeyCollection().add(ecdsa: ES256PrivateKey(pem: key))

var header = JWTHeader()
header.alg = "ES256"
header.kid = keyID
header.typ = "JWT"

struct Payload: JWTPayload {
let iss: IssuerClaim
let iat: IssuedAtClaim
let exp: ExpirationClaim

func verify(using key: some JWTAlgorithm) throws {
try self.exp.verifyNotExpired()
private var jwtToken: String {
get async throws {
let keys = try await JWTKeyCollection().add(ecdsa: ES256PrivateKey(pem: key))

var header = JWTHeader()
header.alg = "ES256"
header.kid = keyID
header.typ = "JWT"

struct Payload: JWTPayload {
let iss: IssuerClaim
let iat: IssuedAtClaim
let exp: ExpirationClaim

func verify(using key: some JWTAlgorithm) throws {
try exp.verifyNotExpired()
}
}
}

let payload = Payload(
iss: IssuerClaim(value: teamID),
iat: IssuedAtClaim(value: Date()),
exp: ExpirationClaim(value: Date().addingTimeInterval(30 * 60))
)
let payload = Payload(
iss: IssuerClaim(value: teamID),
iat: IssuedAtClaim(value: Date()),
exp: ExpirationClaim(value: Date().addingTimeInterval(30 * 60))
)

return try await keys.sign(payload, header: header)
return try await keys.sign(payload, header: header)
}
}
}
2 changes: 1 addition & 1 deletion Sources/AppleMapsKit/DTOs/ErrorResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public struct ErrorResponse: Error, Codable, Sendable {

extension ErrorResponse: CustomStringConvertible {
public var description: String {
var result = #"AppleMapsKitError(message: \#(self.message ?? "nil")"#
var result = #"AppleMapsKitError(message: \#(message ?? "nil")"#

if let details {
result.append(", details: \(details)")
Expand Down
46 changes: 10 additions & 36 deletions Tests/AppleMapsKitTests/AppleMapsKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,7 @@ struct AppleMapsKitTests {
"Geocode",
arguments: zip(
[(37.78, -122.42), nil],
[
nil,
MapRegion(
northLatitude: 38, eastLongitude: -122.1, southLatitude: 37.5,
westLongitude: -122.5
),
]
[nil, MapRegion(northLatitude: 38, eastLongitude: -122.1, southLatitude: 37.5, westLongitude: -122.5)]
)
)
func geocode(searchLocation: (latitude: Double, longitude: Double)?, searchRegion: MapRegion?) async throws {
Expand Down Expand Up @@ -69,13 +63,7 @@ struct AppleMapsKitTests {
"Search",
arguments: zip(
[(37.78, -122.42), nil],
[
nil,
MapRegion(
northLatitude: 38, eastLongitude: -122.1, southLatitude: 37.5,
westLongitude: -122.5
),
]
[nil, MapRegion(northLatitude: 38, eastLongitude: -122.1, southLatitude: 37.5, westLongitude: -122.5)]
)
)
func search(searchLocation: (latitude: Double, longitude: Double)?, searchRegion: MapRegion?) async throws {
Expand Down Expand Up @@ -103,19 +91,19 @@ struct AppleMapsKitTests {
}

@Test("Search with invalid Result Type") func searchWithInvalidResultType() async throws {
do {
let _ = try await client.search(
await #expect {
try await client.search(
for: "eiffel tower",
resultTypeFilter: [.pointOfInterest, .physicalFeature, .poi, .address, .query]
)
Issue.record("This call should throw an error")
} catch let error as AppleMapsKitError {
#expect(error.errorType.base == .invalidSearchResultType)
} throws: { error in
guard let error = error as? AppleMapsKitError else { return false }
return error.errorType.base == .invalidSearchResultType
}
}

@Test("Search with Page Token") func searchWithPageToken() async throws {
try await withKnownIssue {
await withKnownIssue {
let searchResponse = try await client.search(
for: "eiffel tower",
resultTypeFilter: [.pointOfInterest, .physicalFeature, .poi, .address],
Expand All @@ -125,22 +113,14 @@ struct AppleMapsKitTests {
)
let results = try #require(searchResponse.results)
#expect(!results.isEmpty)
} when: {
credentialsAreInvalid
}
}

@Test(
"Search Auto Complete",
arguments: zip(
[(37.78, -122.42), nil],
[
nil,
MapRegion(
northLatitude: 38, eastLongitude: -122.1, southLatitude: 37.5,
westLongitude: -122.5
),
]
[nil, MapRegion(northLatitude: 38, eastLongitude: -122.1, southLatitude: 37.5, westLongitude: -122.5)]
)
)
func searchAutoComplete(searchLocation: (latitude: Double, longitude: Double)?, searchRegion: MapRegion?) async throws {
Expand Down Expand Up @@ -169,13 +149,7 @@ struct AppleMapsKitTests {
"Directions",
arguments: zip(
[(37.7857, -122.4011), nil],
[
nil,
MapRegion(
northLatitude: 38, eastLongitude: -122.1, southLatitude: 37.5,
westLongitude: -122.5
),
]
[nil, MapRegion(northLatitude: 38, eastLongitude: -122.1, southLatitude: 37.5, westLongitude: -122.5)]
)
)
func directions(searchLocation: (latitude: Double, longitude: Double)?, searchRegion: MapRegion?) async throws {
Expand Down