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 -} diff --git a/Package.swift b/Package.swift index 1475912..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"]), @@ -17,16 +16,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")] ), @@ -51,7 +47,6 @@ let package = Package( name: "PovioKitAuthFacebook", dependencies: [ "PovioKitAuthCore", - .product(name: "PovioKitCore", package: "PovioKit"), .product(name: "FacebookLogin", package: "facebook-ios-sdk") ], path: "Sources/Facebook", @@ -60,8 +55,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/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, 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..ab24be1 100644 --- a/Sources/LinkedIn/API/LinkedInAPI.swift +++ b/Sources/LinkedIn/API/LinkedInAPI.swift @@ -7,18 +7,36 @@ // import Foundation -import PovioKitNetworking -public final class LinkedInAPI { - private let client: AlamofireNetworkClient +public struct LinkedInAPI { + 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 decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase decoder.dateDecodingStrategy = .custom { decoder in @@ -27,47 +45,50 @@ public extension LinkedInAPI { return Date().addingTimeInterval(TimeInterval(secondsRemaining)) } - let encoder = JSONEncoder() - encoder.keyEncodingStrategy = .convertToSnakeCase + let response = try await client.request( + method: "POST", + url: url, + headers: ["Content-Type": "application/x-www-form-urlencoded"], + decodeTo: LinkedInAuthResponse.self, + with: decoder + ) - return try await client - .request( - method: .post, - endpoint: Endpoints.accessToken, - encode: request, - parameterEncoder: .urlEncoder(encoder: encoder) - ) - .validate() - .decode(LinkedInAuthResponse.self, decoder: decoder) - .asAsync + 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 - return try await client - .request( - method: .get, - endpoint: Endpoints.profile, - headers: ["Authorization": "Bearer \(request.token)"] - ) - .validate() - .decode(LinkedInProfileResponse.self, decoder: decoder) - .asAsync + let response = try await client.request( + method: "GET", + url: url, + headers: ["Authorization": "Bearer \(request.token)"], + decodeTo: LinkedInProfileResponse.self, + with: decoder + ) + + return response } 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: ["Authorization": "Bearer \(request.token)"], + decodeTo: LinkedInEmailResponse.self + ) + + guard let emailObject = response.elements.first?.handle else { + throw Error.invalidResponse + } + + return emailObject } } @@ -75,5 +96,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..a5de27d --- /dev/null +++ b/Sources/LinkedIn/Core/HttpClient.swift @@ -0,0 +1,32 @@ +// +// HttpClient.swift +// PovioKitAuth +// +// Created by Borut Tomazin on 22/05/2024. +// Copyright © 2024 Povio Inc. All rights reserved. +// + +import Foundation + +struct HttpClient { + 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 + + 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 try decoder.decode(decode, from: data) + } +} 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 }