From fd29f5c54ed2161cdfb4f5a15b5351680d9b0325 Mon Sep 17 00:00:00 2001 From: Borut Tomazin Date: Wed, 22 May 2024 12:50:44 +0200 Subject: [PATCH] Remove PovioKit dep from LinkedIn pckg. --- Package.swift | 4 +- 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, 115 insertions(+), 67 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 72cc3ad..90366f6 100644 --- a/Package.swift +++ b/Package.swift @@ -17,7 +17,6 @@ let package = Package( .library(name: "PovioKitAuthLinkedIn", targets: ["PovioKitAuthLinkedIn"]) ], dependencies: [ - .package(url: "https://github.com/poviolabs/PovioKit", .upToNextMajor(from: "4.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")), ], @@ -57,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 }