From 95ce5bec0593af2084bae5b1629fa20c5f7d2dc9 Mon Sep 17 00:00:00 2001 From: Borut Tomazin Date: Wed, 22 May 2024 09:05:44 +0200 Subject: [PATCH 1/7] Remove PovioKit dep from FB pckg. --- Package.swift | 1 - Sources/Facebook/FacebookAuthenticator.swift | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 1475912..fb08073 100644 --- a/Package.swift +++ b/Package.swift @@ -51,7 +51,6 @@ let package = Package( name: "PovioKitAuthFacebook", dependencies: [ "PovioKitAuthCore", - .product(name: "PovioKitCore", package: "PovioKit"), .product(name: "FacebookLogin", package: "facebook-ios-sdk") ], path: "Sources/Facebook", diff --git a/Sources/Facebook/FacebookAuthenticator.swift b/Sources/Facebook/FacebookAuthenticator.swift index d811bd4..90bd25a 100644 --- a/Sources/Facebook/FacebookAuthenticator.swift +++ b/Sources/Facebook/FacebookAuthenticator.swift @@ -8,7 +8,6 @@ import Foundation import FacebookLogin -import PovioKitCore import PovioKitAuthCore public final class FacebookAuthenticator { @@ -106,7 +105,7 @@ private extension FacebookAuthenticator { do { let data = try JSONSerialization.data(withJSONObject: response, options: []) - let object = try data.decode(GraphResponse.self, with: decoder) + let object = try decoder.decode(GraphResponse.self, from: data) let authResponse = Response( userId: object.id, From e388eb0e9d2facb2acc8dee86dc622ac35941716 Mon Sep 17 00:00:00 2001 From: Borut Tomazin Date: Wed, 22 May 2024 12:50:44 +0200 Subject: [PATCH 2/7] Remove PovioKit dep from LinkedIn pckg. --- Package.swift | 8 +- Sources/LinkedIn/API/EndpointEncodable.swift | 23 ----- .../LinkedIn/API/LinkedInAPI+Endpoints.swift | 5 +- Sources/LinkedIn/API/LinkedInAPI.swift | 95 ++++++++++++------- Sources/LinkedIn/Core/HttpClient.swift | 35 +++++++ Sources/LinkedIn/Core/URL+PovioKitAuth.swift | 18 ++++ .../LinkedIn/WebView/LinkedInWebView.swift | 2 - 7 files changed, 116 insertions(+), 70 deletions(-) delete mode 100644 Sources/LinkedIn/API/EndpointEncodable.swift create mode 100644 Sources/LinkedIn/Core/HttpClient.swift create mode 100644 Sources/LinkedIn/Core/URL+PovioKitAuth.swift diff --git a/Package.swift b/Package.swift index fb08073..90366f6 100644 --- a/Package.swift +++ b/Package.swift @@ -17,16 +17,13 @@ let package = Package( .library(name: "PovioKitAuthLinkedIn", targets: ["PovioKitAuthLinkedIn"]) ], dependencies: [ - .package(url: "https://github.com/poviolabs/PovioKit", .upToNextMajor(from: "3.0.0")), .package(url: "https://github.com/google/GoogleSignIn-iOS", .upToNextMajor(from: "7.0.0")), .package(url: "https://github.com/facebook/facebook-ios-sdk", .upToNextMajor(from: "17.0.0")), ], targets: [ .target( name: "PovioKitAuthCore", - dependencies: [ - .product(name: "PovioKitPromise", package: "PovioKit"), - ], + dependencies: [], path: "Sources/Core", resources: [.copy("../../Resources/PrivacyInfo.xcprivacy")] ), @@ -59,8 +56,7 @@ let package = Package( .target( name: "PovioKitAuthLinkedIn", dependencies: [ - "PovioKitAuthCore", - .product(name: "PovioKitNetworking", package: "PovioKit") + "PovioKitAuthCore" ], path: "Sources/LinkedIn", resources: [.copy("../../Resources/PrivacyInfo.xcprivacy")] diff --git a/Sources/LinkedIn/API/EndpointEncodable.swift b/Sources/LinkedIn/API/EndpointEncodable.swift deleted file mode 100644 index b49f054..0000000 --- a/Sources/LinkedIn/API/EndpointEncodable.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// EndpointEncodable.swift -// PovioKitAuth -// -// Created by Borut Tomazin on 04/09/2023. -// Copyright © 2024 Povio Inc. All rights reserved. -// - -import Foundation -import PovioKitNetworking - -protocol EndpointEncodable: URLConvertible { - typealias Path = String - - var path: Path { get } - var url: String { get } -} - -extension EndpointEncodable { - func asURL() throws -> URL { - .init(stringLiteral: url) - } -} diff --git a/Sources/LinkedIn/API/LinkedInAPI+Endpoints.swift b/Sources/LinkedIn/API/LinkedInAPI+Endpoints.swift index 81f6198..ed4c977 100644 --- a/Sources/LinkedIn/API/LinkedInAPI+Endpoints.swift +++ b/Sources/LinkedIn/API/LinkedInAPI+Endpoints.swift @@ -7,15 +7,14 @@ // import Foundation -import PovioKitNetworking extension LinkedInAPI { - enum Endpoints: EndpointEncodable { + enum Endpoints { case accessToken case profile case email - var path: Path { + var path: String { switch self { case .accessToken: return "accessToken" diff --git a/Sources/LinkedIn/API/LinkedInAPI.swift b/Sources/LinkedIn/API/LinkedInAPI.swift index b2bd4b8..e9c4766 100644 --- a/Sources/LinkedIn/API/LinkedInAPI.swift +++ b/Sources/LinkedIn/API/LinkedInAPI.swift @@ -7,18 +7,42 @@ // import Foundation -import PovioKitNetworking public final class LinkedInAPI { - private let client: AlamofireNetworkClient + private let client: HttpClient = .init() - public init(client: AlamofireNetworkClient = .init()) { - self.client = client - } + public init() {} } public extension LinkedInAPI { func login(with request: LinkedInAuthRequest) async throws -> LinkedInAuthResponse { + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let jsonData = try encoder.encode(request) + let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) + guard let jsonDict = jsonObject as? [String: Any] else { + throw Error.invalidRequest + } + + guard var components = URLComponents(string: Endpoints.accessToken.url) else { + throw Error.invalidUrl + } + + let queryItems: [URLQueryItem] = jsonDict.compactMap { key, value in + (value as? String).map { .init(name: key, value: $0) } + } + + components.queryItems = queryItems + guard let url = components.url else { + throw Error.invalidUrl + } + + let response = try await client.request( + method: "POST", + url: url, + headers: [.init(name: "Content-Type", value: "application/x-www-form-urlencoded")] + ) + let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .custom { decoder in @@ -26,48 +50,44 @@ public extension LinkedInAPI { let secondsRemaining = try container.decode(Int.self) return Date().addingTimeInterval(TimeInterval(secondsRemaining)) } + let decoded = try decoder.decode(LinkedInAuthResponse.self, from: response) - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase - - return try await client - .request( - method: .post, - endpoint: Endpoints.accessToken, - encode: request, - parameterEncoder: .urlEncoder(encoder: encoder) - ) - .validate() - .decode(LinkedInAuthResponse.self, decoder: decoder) - .asAsync + return decoded } func loadProfile(with request: LinkedInProfileRequest) async throws -> LinkedInProfileResponse { + guard let url = URL(string: Endpoints.profile.url) else { throw Error.invalidUrl } + + let response = try await client.request( + method: "GET", + url: url, + headers: [.init(name: "Authorization", value: "Bearer \(request.token)")] + ) + let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .iso8601 + let decoded = try decoder.decode(LinkedInProfileResponse.self, from: response) - return try await client - .request( - method: .get, - endpoint: Endpoints.profile, - headers: ["Authorization": "Bearer \(request.token)"] - ) - .validate() - .decode(LinkedInProfileResponse.self, decoder: decoder) - .asAsync + return decoded } func loadEmail(with request: LinkedInProfileRequest) async throws -> LinkedInEmailValueResponse { - return try await client - .request( - method: .get, - endpoint: Endpoints.email, - headers: ["Authorization": "Bearer \(request.token)"]) - .validate() - .decode(LinkedInEmailResponse.self) - .compactMap { $0.elements.first?.handle } - .asAsync + guard let url = URL(string: Endpoints.email.url) else { throw Error.invalidUrl } + + let response = try await client.request( + method: "GET", + url: url, + headers: [.init(name: "Authorization", value: "Bearer \(request.token)")] + ) + + let decoded = try JSONDecoder().decode(LinkedInEmailResponse.self, from: response) + + guard let emailObject = decoded.elements.first?.handle else { + throw Error.invalidResponse + } + + return emailObject } } @@ -75,5 +95,8 @@ public extension LinkedInAPI { public extension LinkedInAPI { enum Error: Swift.Error { case missingParameters + case invalidUrl + case invalidRequest + case invalidResponse } } diff --git a/Sources/LinkedIn/Core/HttpClient.swift b/Sources/LinkedIn/Core/HttpClient.swift new file mode 100644 index 0000000..b0f2366 --- /dev/null +++ b/Sources/LinkedIn/Core/HttpClient.swift @@ -0,0 +1,35 @@ +// +// HttpClient.swift +// PovioKitAuth +// +// Created by Borut Tomazin on 22/05/2024. +// Copyright © 2024 Povio Inc. All rights reserved. +// + +import Foundation + +class HttpClient { + func request(method: String, url: URL, headers: [Header]) async throws -> Data { + var urlRequest = URLRequest(url: url) + urlRequest.httpMethod = method + headers.forEach { + urlRequest.setValue($0.value, forHTTPHeaderField: $0.name) + } + + let (data, response) = try await URLSession.shared.data(for: urlRequest) + + if let httpResponse = response as? HTTPURLResponse, + !(200...299).contains(httpResponse.statusCode) { + throw NSError(domain: "HTTP Error", code: httpResponse.statusCode, userInfo: nil) + } + + return data + } +} + +extension HttpClient { + struct Header { + let name: String + let value: String + } +} diff --git a/Sources/LinkedIn/Core/URL+PovioKitAuth.swift b/Sources/LinkedIn/Core/URL+PovioKitAuth.swift new file mode 100644 index 0000000..5160797 --- /dev/null +++ b/Sources/LinkedIn/Core/URL+PovioKitAuth.swift @@ -0,0 +1,18 @@ +// +// URL+PovioKitAuth.swift +// PovioKitAuth +// +// Created by Borut Tomazin on 22/05/2024. +// Copyright © 2024 Povio Inc. All rights reserved. +// + +import Foundation + +extension URL: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + guard let url = URL(string: value) else { + fatalError("Invalid URL string!") + } + self = url + } +} diff --git a/Sources/LinkedIn/WebView/LinkedInWebView.swift b/Sources/LinkedIn/WebView/LinkedInWebView.swift index 6f1ba22..bb26b86 100644 --- a/Sources/LinkedIn/WebView/LinkedInWebView.swift +++ b/Sources/LinkedIn/WebView/LinkedInWebView.swift @@ -6,7 +6,6 @@ // Copyright © 2024 Povio Inc. All rights reserved. // -import PovioKitCore import SwiftUI import WebKit @@ -40,7 +39,6 @@ public struct LinkedInWebView: UIViewRepresentable { public func updateUIView(_ uiView: UIViewType, context: Context) { guard let webView = uiView as? WKWebView else { return } guard let authURL = configuration.authorizationUrl(state: requestState) else { - Logger.error("Failed to geet auth url!") dismiss() return } From fdc1b202892d4df30415aaf233fb9d9a2eb26bdb Mon Sep 17 00:00:00 2001 From: Borut Tomazin Date: Tue, 21 May 2024 12:14:36 +0200 Subject: [PATCH 3/7] Ignore resolved file. --- .gitignore | 1 + Package.resolved | 68 ------------------------------------------------ 2 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 Package.resolved diff --git a/.gitignore b/.gitignore index 3b29812..5922fda 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ DerivedData/ .swiftpm/config/registries.json .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc +Package.resolved diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 0fe7538..0000000 --- a/Package.resolved +++ /dev/null @@ -1,68 +0,0 @@ -{ - "pins" : [ - { - "identity" : "alamofire", - "kind" : "remoteSourceControl", - "location" : "https://github.com/Alamofire/Alamofire", - "state" : { - "revision" : "f455c2975872ccd2d9c81594c658af65716e9b9a", - "version" : "5.9.1" - } - }, - { - "identity" : "appauth-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/openid/AppAuth-iOS.git", - "state" : { - "revision" : "c89ed571ae140f8eb1142735e6e23d7bb8c34cb2", - "version" : "1.7.5" - } - }, - { - "identity" : "facebook-ios-sdk", - "kind" : "remoteSourceControl", - "location" : "https://github.com/facebook/facebook-ios-sdk", - "state" : { - "revision" : "09eb5b0cb74127a360e0bc74a2d75ec1fd351e48", - "version" : "17.0.0" - } - }, - { - "identity" : "googlesignin-ios", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GoogleSignIn-iOS", - "state" : { - "revision" : "a7965d134c5d3567026c523e0a8a583f73b62b0d", - "version" : "7.1.0" - } - }, - { - "identity" : "gtm-session-fetcher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/gtm-session-fetcher.git", - "state" : { - "revision" : "0382ca27f22fb3494cf657d8dc356dc282cd1193", - "version" : "3.4.1" - } - }, - { - "identity" : "gtmappauth", - "kind" : "remoteSourceControl", - "location" : "https://github.com/google/GTMAppAuth.git", - "state" : { - "revision" : "5d7d66f647400952b1758b230e019b07c0b4b22a", - "version" : "4.1.1" - } - }, - { - "identity" : "poviokit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/poviolabs/PovioKit", - "state" : { - "revision" : "95371aebca733fa67be58c0239e12a97d1fcf163", - "version" : "3.3.1" - } - } - ], - "version" : 2 -} From 93fb84c06a2c01f6bfb4cc759ad63d6af02d7773 Mon Sep 17 00:00:00 2001 From: Borut Tomazin Date: Mon, 27 May 2024 08:23:08 +0200 Subject: [PATCH 4/7] Value type. --- Sources/LinkedIn/API/LinkedInAPI.swift | 2 +- Sources/LinkedIn/Core/HttpClient.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/LinkedIn/API/LinkedInAPI.swift b/Sources/LinkedIn/API/LinkedInAPI.swift index e9c4766..3da5504 100644 --- a/Sources/LinkedIn/API/LinkedInAPI.swift +++ b/Sources/LinkedIn/API/LinkedInAPI.swift @@ -8,7 +8,7 @@ import Foundation -public final class LinkedInAPI { +public struct LinkedInAPI { private let client: HttpClient = .init() public init() {} diff --git a/Sources/LinkedIn/Core/HttpClient.swift b/Sources/LinkedIn/Core/HttpClient.swift index b0f2366..c00327a 100644 --- a/Sources/LinkedIn/Core/HttpClient.swift +++ b/Sources/LinkedIn/Core/HttpClient.swift @@ -8,7 +8,7 @@ import Foundation -class HttpClient { +struct HttpClient { func request(method: String, url: URL, headers: [Header]) async throws -> Data { var urlRequest = URLRequest(url: url) urlRequest.httpMethod = method From 70d9456063d58aaec7fcc74cde857d4ad7caf474 Mon Sep 17 00:00:00 2001 From: Borut Tomazin Date: Mon, 27 May 2024 08:46:04 +0200 Subject: [PATCH 5/7] Remove macos support. --- Package.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 90366f6..5411593 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,8 +6,7 @@ import PackageDescription let package = Package( name: "PovioKitAuth", platforms: [ - .iOS(.v13), - .macOS(.v13) + .iOS(.v13) ], products: [ .library(name: "PovioKitAuthCore", targets: ["PovioKitAuthCore"]), From dd8a2bc491936eea0e76941dd04646702625f46b Mon Sep 17 00:00:00 2001 From: Borut Tomazin Date: Mon, 27 May 2024 12:01:14 +0200 Subject: [PATCH 6/7] Headers update. --- Sources/LinkedIn/API/LinkedInAPI.swift | 6 +++--- Sources/LinkedIn/Core/HttpClient.swift | 13 ++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/Sources/LinkedIn/API/LinkedInAPI.swift b/Sources/LinkedIn/API/LinkedInAPI.swift index 3da5504..f3476c2 100644 --- a/Sources/LinkedIn/API/LinkedInAPI.swift +++ b/Sources/LinkedIn/API/LinkedInAPI.swift @@ -40,7 +40,7 @@ public extension LinkedInAPI { let response = try await client.request( method: "POST", url: url, - headers: [.init(name: "Content-Type", value: "application/x-www-form-urlencoded")] + headers: ["Content-Type": "application/x-www-form-urlencoded"] ) let decoder = JSONDecoder() @@ -61,7 +61,7 @@ public extension LinkedInAPI { let response = try await client.request( method: "GET", url: url, - headers: [.init(name: "Authorization", value: "Bearer \(request.token)")] + headers: ["Authorization": "Bearer \(request.token)"] ) let decoder = JSONDecoder() @@ -78,7 +78,7 @@ public extension LinkedInAPI { let response = try await client.request( method: "GET", url: url, - headers: [.init(name: "Authorization", value: "Bearer \(request.token)")] + headers: ["Authorization": "Bearer \(request.token)"] ) let decoded = try JSONDecoder().decode(LinkedInEmailResponse.self, from: response) diff --git a/Sources/LinkedIn/Core/HttpClient.swift b/Sources/LinkedIn/Core/HttpClient.swift index c00327a..cbaa7f8 100644 --- a/Sources/LinkedIn/Core/HttpClient.swift +++ b/Sources/LinkedIn/Core/HttpClient.swift @@ -9,12 +9,10 @@ import Foundation struct HttpClient { - func request(method: String, url: URL, headers: [Header]) async throws -> Data { + func request(method: String, url: URL, headers: [String: String]?) async throws -> Data { var urlRequest = URLRequest(url: url) urlRequest.httpMethod = method - headers.forEach { - urlRequest.setValue($0.value, forHTTPHeaderField: $0.name) - } + urlRequest.allHTTPHeaderFields = headers let (data, response) = try await URLSession.shared.data(for: urlRequest) @@ -26,10 +24,3 @@ struct HttpClient { return data } } - -extension HttpClient { - struct Header { - let name: String - let value: String - } -} From a3d953122be4fa969af23a13bc2b8621922112d8 Mon Sep 17 00:00:00 2001 From: Borut Tomazin Date: Wed, 5 Jun 2024 10:14:28 +0200 Subject: [PATCH 7/7] Move decoding to client. --- Sources/LinkedIn/API/LinkedInAPI.swift | 39 +++++++++++++------------- Sources/LinkedIn/Core/HttpClient.swift | 10 +++++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Sources/LinkedIn/API/LinkedInAPI.swift b/Sources/LinkedIn/API/LinkedInAPI.swift index f3476c2..ab24be1 100644 --- a/Sources/LinkedIn/API/LinkedInAPI.swift +++ b/Sources/LinkedIn/API/LinkedInAPI.swift @@ -37,12 +37,6 @@ public extension LinkedInAPI { throw Error.invalidUrl } - let response = try await client.request( - method: "POST", - url: url, - headers: ["Content-Type": "application/x-www-form-urlencoded"] - ) - let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .custom { decoder in @@ -50,26 +44,34 @@ public extension LinkedInAPI { let secondsRemaining = try container.decode(Int.self) return Date().addingTimeInterval(TimeInterval(secondsRemaining)) } - let decoded = try decoder.decode(LinkedInAuthResponse.self, from: response) - return decoded + let response = try await client.request( + method: "POST", + url: url, + headers: ["Content-Type": "application/x-www-form-urlencoded"], + decodeTo: LinkedInAuthResponse.self, + with: decoder + ) + + return response } func loadProfile(with request: LinkedInProfileRequest) async throws -> LinkedInProfileResponse { guard let url = URL(string: Endpoints.profile.url) else { throw Error.invalidUrl } + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + decoder.dateDecodingStrategy = .iso8601 + let response = try await client.request( method: "GET", url: url, - headers: ["Authorization": "Bearer \(request.token)"] + headers: ["Authorization": "Bearer \(request.token)"], + decodeTo: LinkedInProfileResponse.self, + with: decoder ) - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - decoder.dateDecodingStrategy = .iso8601 - let decoded = try decoder.decode(LinkedInProfileResponse.self, from: response) - - return decoded + return response } func loadEmail(with request: LinkedInProfileRequest) async throws -> LinkedInEmailValueResponse { @@ -78,12 +80,11 @@ public extension LinkedInAPI { let response = try await client.request( method: "GET", url: url, - headers: ["Authorization": "Bearer \(request.token)"] + headers: ["Authorization": "Bearer \(request.token)"], + decodeTo: LinkedInEmailResponse.self ) - let decoded = try JSONDecoder().decode(LinkedInEmailResponse.self, from: response) - - guard let emailObject = decoded.elements.first?.handle else { + guard let emailObject = response.elements.first?.handle else { throw Error.invalidResponse } diff --git a/Sources/LinkedIn/Core/HttpClient.swift b/Sources/LinkedIn/Core/HttpClient.swift index cbaa7f8..a5de27d 100644 --- a/Sources/LinkedIn/Core/HttpClient.swift +++ b/Sources/LinkedIn/Core/HttpClient.swift @@ -9,7 +9,13 @@ import Foundation struct HttpClient { - func request(method: String, url: URL, headers: [String: String]?) async throws -> Data { + func request( + method: String, + url: URL, + headers: [String: String]?, + decodeTo decode: D.Type, + with decoder: JSONDecoder = .init() + ) async throws -> D { var urlRequest = URLRequest(url: url) urlRequest.httpMethod = method urlRequest.allHTTPHeaderFields = headers @@ -21,6 +27,6 @@ struct HttpClient { throw NSError(domain: "HTTP Error", code: httpResponse.statusCode, userInfo: nil) } - return data + return try decoder.decode(decode, from: data) } }