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

Chore/Remove PovioKit dependency #30

Merged
merged 7 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Package.resolved
68 changes: 0 additions & 68 deletions Package.resolved

This file was deleted.

9 changes: 2 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
),
Expand All @@ -51,7 +48,6 @@ let package = Package(
name: "PovioKitAuthFacebook",
dependencies: [
"PovioKitAuthCore",
.product(name: "PovioKitCore", package: "PovioKit"),
.product(name: "FacebookLogin", package: "facebook-ios-sdk")
],
path: "Sources/Facebook",
Expand All @@ -60,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")]
Expand Down
3 changes: 1 addition & 2 deletions Sources/Facebook/FacebookAuthenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import Foundation
import FacebookLogin
import PovioKitCore
import PovioKitAuthCore

public final class FacebookAuthenticator {
Expand Down Expand Up @@ -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,
Expand Down
23 changes: 0 additions & 23 deletions Sources/LinkedIn/API/EndpointEncodable.swift

This file was deleted.

5 changes: 2 additions & 3 deletions Sources/LinkedIn/API/LinkedInAPI+Endpoints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
95 changes: 59 additions & 36 deletions Sources/LinkedIn/API/LinkedInAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,73 +7,96 @@
//

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
let container = try decoder.singleValueContainer()
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
}
}

// MARK: - Error
public extension LinkedInAPI {
enum Error: Swift.Error {
case missingParameters
case invalidUrl
case invalidRequest
case invalidResponse
}
}
35 changes: 35 additions & 0 deletions Sources/LinkedIn/Core/HttpClient.swift
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the HttpClient part of the LinkedIn folder? Can we move it out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's internal implementation only needed by LinkedIn target.

Original file line number Diff line number Diff line change
@@ -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)
tonikocjan marked this conversation as resolved.
Show resolved Hide resolved
urlRequest.httpMethod = method
headers.forEach {
tonikocjan marked this conversation as resolved.
Show resolved Hide resolved
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
}
}
18 changes: 18 additions & 0 deletions Sources/LinkedIn/Core/URL+PovioKitAuth.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
2 changes: 0 additions & 2 deletions Sources/LinkedIn/WebView/LinkedInWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// Copyright © 2024 Povio Inc. All rights reserved.
//

import PovioKitCore
import SwiftUI
import WebKit

Expand Down Expand Up @@ -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
}
Expand Down
Loading