Skip to content

Commit

Permalink
Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
borut-t committed Sep 4, 2023
1 parent 08888a7 commit 90f7ce8
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 80 deletions.
10 changes: 8 additions & 2 deletions Sources/LinkedIn/LinkedInAuthenticator+Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@

import Foundation

@available(iOS 15.0, *)
public extension LinkedInAuthenticator {
struct Configuration {
let clientId: String
let clientSecret: String
let permissions: String
let redirectUrl: URL
let authEndpoint: URL = "https://www.linkedin.com/oauth/v2/authorization"
let authCancel: URL = "https://www.linkedin.com/oauth/v2/authorization-cancel"
let authCancel: URL = "https://www.linkedin.com/oauth/v2/login-cancel"

public init(clientId: String, clientSecret: String, permissions: String, redirectUrl: URL) {
self.clientId = clientId
self.clientSecret = clientSecret
self.permissions = permissions
self.redirectUrl = redirectUrl
}

func authorizationUrl(state: String) -> URL? {
guard var urlComponents = URLComponents(url: authEndpoint, resolvingAgainstBaseURL: false) else { return nil }
Expand Down
89 changes: 27 additions & 62 deletions Sources/LinkedIn/LinkedInAuthenticator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,83 +6,29 @@
// Copyright © 2023 Povio Inc. All rights reserved.
//

import Foundation
import UIKit
import PovioKitAuthCore
import PovioKitPromise
import SwiftUI

@available(iOS 15.0, *)
public final class LinkedInAuthenticator {
@State private var openWebView = false
private let storage: UserDefaults
private let storageIsAuthenticatedKey = "signIn.isAuthenticated"
private let linkedInAPI: LinkedInAPI

public init(linkedInAPI: LinkedInAPI = .init()) {
public init(storage: UserDefaults? = nil,
linkedInAPI: LinkedInAPI = .init()) {
self.storage = storage ?? .init(suiteName: "povioKit.auth.linkedIn") ?? .standard
self.linkedInAPI = linkedInAPI
}
}

// MARK: - Public Methods
@available(iOS 15.0, *)
extension LinkedInAuthenticator: Authenticator {
/// SignIn user.
///
/// Will return promise with the `Response` object on success or with `Error` on error.
public func signIn(from view: any View,
with configuration: Configuration,
additionalScopes: [String]? = .none) -> Promise<Response> {
Promise { seal in
_ = view.sheet(isPresented: $openWebView) {
LinkedInWebView(with: configuration) { data in
Task {
do {
let response = try await self.loadData(code: data.code, with: configuration)
seal.resolve(with: response)
} catch {
seal.reject(with: error)
}
}
} onFailure: {
seal.reject(with: Error.unhandledAuthorization)
}
}
}
}

/// Clears the signIn footprint and logs out the user immediatelly.
public func signOut() {
// TODO
}

/// Returns the current authentication state.
public var isAuthenticated: Authenticated {
false // TODO
}

/// Boolean if given `url` should be handled.
///
/// Call this from UIApplicationDelegate’s `application:openURL:options:` method.
public func canOpenUrl(_ url: URL, application: UIApplication, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
true // TODO
}
}

// MARK: - Error
@available(iOS 15.0, *)
public extension LinkedInAuthenticator {
enum Error: Swift.Error {
// case system(_ error: Swift.Error)
// case cancelled
case unhandledAuthorization
// case alreadySignedIn
}
}

// MARK: - Private Extension
@available(iOS 15.0, *)
private extension LinkedInAuthenticator {
func loadData(code: String, with configuration: Configuration) async throws -> Response {
public func signIn(authCode: String, configuration: Configuration) async throws -> Response {
let authRequest: LinkedInAPI.LinkedInAuthRequest = .init(
code: code,
code: authCode,
redirectUri: configuration.redirectUrl.absoluteString,
clientId: configuration.clientId,
clientSecret: configuration.clientSecret
Expand All @@ -91,6 +37,8 @@ private extension LinkedInAuthenticator {
let profileResponse = try await linkedInAPI.loadProfile(with: .init(token: authResponse.accessToken))
let emailResponse = try await linkedInAPI.loadEmail(with: .init(token: authResponse.accessToken))

storage.set(true, forKey: storageIsAuthenticatedKey)

let name = [profileResponse.localizedFirstName, profileResponse.localizedLastName].joined(separator: " ")
return Response(
userId: profileResponse.id,
Expand All @@ -100,4 +48,21 @@ private extension LinkedInAuthenticator {
expiresAt: authResponse.expiresIn
)
}

/// Clears the signIn footprint and logs out the user immediatelly.
public func signOut() {
storage.removeObject(forKey: storageIsAuthenticatedKey)
}

/// Returns the current authentication state.
public var isAuthenticated: Authenticated {
storage.bool(forKey: storageIsAuthenticatedKey)
}

/// Boolean if given `url` should be handled.
///
/// Call this from UIApplicationDelegate’s `application:openURL:options:` method.
public func canOpenUrl(_ url: URL, application: UIApplication, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
true
}
}
35 changes: 35 additions & 0 deletions Sources/LinkedIn/WebView/LinkedInSheet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// File.swift
//
//
// Created by Borut Tomazin on 04/09/2023.
//

import SwiftUI

//struct LinkedInSheet: ViewModifier {
// typealias SuccessHandler = (Bool) -> Void
// typealias ErrorHandler = (Error) -> Void
// let isPresented: Binding<Bool>
// let onSuccess: SuccessHandler?
// let onError: ErrorHandler?
//
// func body(content: Content) -> some View {
// content
// .sheet(isPresented: isPresented) {
// LinkedInWebView { data in
// // Task { await viewModel.signInWithLinkedIn() }
// } onFailure: {
// // viewModel.error = .general
// }
// }
// }
//}
//
//extension View {
// func linkedInSheet(isPresented: Binding<Bool>,
// onSuccess: LinkedInSheet.SuccessHandler? = nil,
// onError: LinkedInSheet.ErrorHandler? = nil) -> some View {
// modifier(LinkedInSheet(isPresented: isPresented, onSuccess: onSuccess, onError: onError))
// }
//}
32 changes: 16 additions & 16 deletions Sources/LinkedIn/WebView/LinkedInWebView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ import SwiftUI
import WebKit

@available(iOS 15.0, *)
struct LinkedInWebView: UIViewRepresentable {
public struct LinkedInWebView: UIViewRepresentable {
@Environment(\.dismiss) var dismiss
// create a random string based on the time interval (it will be in the number form) - Needed for state.
typealias SuccessHandler = ((code: String, state: String)) -> Void
typealias ErrorHandler = () -> Void
public typealias SuccessHandler = ((code: String, state: String)) -> Void
public typealias ErrorHandler = () -> Void
private let requestState: String = "\(Int(Date().timeIntervalSince1970))"
private let webView: WKWebView
private let configuration: LinkedInAuthenticator.Configuration
let onSuccess: SuccessHandler?
let onFailure: ErrorHandler?
public let onSuccess: SuccessHandler?
public let onFailure: ErrorHandler?

init(with configuration: LinkedInAuthenticator.Configuration,
onSuccess: @escaping SuccessHandler,
onFailure: @escaping ErrorHandler) {
public init(with configuration: LinkedInAuthenticator.Configuration,
onSuccess: @escaping SuccessHandler,
onFailure: @escaping ErrorHandler) {
self.configuration = configuration
let config = WKWebViewConfiguration()
config.websiteDataStore = WKWebsiteDataStore.default()
Expand All @@ -35,11 +35,11 @@ struct LinkedInWebView: UIViewRepresentable {
webView.navigationDelegate = makeCoordinator()
}

func makeUIView(context: Context) -> some UIView {
public func makeUIView(context: Context) -> some UIView {
webView
}

func updateUIView(_ uiView: UIViewType, context: Context) {
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!")
Expand All @@ -50,25 +50,25 @@ struct LinkedInWebView: UIViewRepresentable {
webView.load(.init(url: authURL))
}

func makeCoordinator() -> Coordinator {
public func makeCoordinator() -> Coordinator {
Coordinator(self, requestState: requestState)
}
}

@available(iOS 15.0, *)
extension LinkedInWebView {
public extension LinkedInWebView {
class Coordinator: NSObject, WKNavigationDelegate {
private let parent: LinkedInWebView
private let requestState: String

init(_ parent: LinkedInWebView, requestState: String) {
public init(_ parent: LinkedInWebView, requestState: String) {
self.parent = parent
self.requestState = requestState
}

func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
public func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url,
url.absoluteString.hasPrefix(parent.configuration.authCancel.absoluteString) {
decisionHandler(.cancel)
Expand Down

0 comments on commit 90f7ce8

Please sign in to comment.