diff --git a/Xcodes/Backend/AppState+Install.swift b/Xcodes/Backend/AppState+Install.swift index 2c9fc84..39a4dec 100644 --- a/Xcodes/Backend/AppState+Install.swift +++ b/Xcodes/Backend/AppState+Install.swift @@ -515,6 +515,36 @@ extension AppState { } } } + + func updateACToken(_ token: String) { + // if let cookieStorage = AppleAPI.Current.network.session.configuration.httpCookieStorage { + print (AppleAPI.Current.network.session.configuration.httpCookieStorage) + let cookie = HTTPCookie(properties: [ + .domain: "appstoreconnect.apple.com", + .path: "/olympus/v1/session", + .name: "myacinfo", + .value: token, + .secure: "TRUE", + .expires: Date(timeIntervalSinceNow: 31556926) + ])! + + AppleAPI.Current.network.session.configuration.httpCookieStorage?.setCookie(cookie) + + validateSession() + .sink( + receiveCompletion: { _ in + self.authenticationState = .authenticated + }, + receiveValue: { _ in } + ) + .store(in: &cancellables) + + + print (AppleAPI.Current.network.session.configuration.httpCookieStorage) + + + + } } extension AppState { diff --git a/Xcodes/Frontend/XcodeList/BottomStatusBar.swift b/Xcodes/Frontend/XcodeList/BottomStatusBar.swift index ac4b214..0275432 100644 --- a/Xcodes/Frontend/XcodeList/BottomStatusBar.swift +++ b/Xcodes/Frontend/XcodeList/BottomStatusBar.swift @@ -8,6 +8,8 @@ import Foundation import SwiftUI +import Combine +import WebKit struct BottomStatusModifier: ViewModifier { @EnvironmentObject var appState: AppState @@ -15,6 +17,9 @@ struct BottomStatusModifier: ViewModifier { @SwiftUI.Environment(\.openURL) var openURL: OpenURLAction + @State var showWebLogin: Bool = false + @State var openProgress = false + func body(content: Content) -> some View { VStack(spacing: 0) { content @@ -42,7 +47,51 @@ struct BottomStatusModifier: ViewModifier { } .frame(maxWidth: .infinity, maxHeight: 30, alignment: .leading) } + .sheet(isPresented: $showWebLogin) { + VStack(spacing: 0) { + AppleWebLoginView { token in + populateOlympus(token: token) + } + Divider().padding(.horizontal, -16) + HStack { + Text("Please sign in to Apple in this page.") + Spacer() + Button("Cancel") { + showWebLogin = false + } + } + .padding(8) + } + .frame(width: 800, height: 500) + } + } + + func populateOlympus(token: String) { + showWebLogin = false + openProgress = true + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { + + appState.updateACToken(token) + +// var request = URLRequest(url: URL(string: "https://appstoreconnect.apple.com/olympus/v1/session")!) +// request.setValue("myacinfo=\(token);", forHTTPHeaderField: "Cookie") +// URLSession.shared.dataTask(with: request) { data, _, _ in +// guard let data else { return } +// guard let dic = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return } +// guard let userData = dic["user"] as? [String: Any] else { return } +// guard let userDataRaw = try? JSONSerialization.data(withJSONObject: userData) else { return } +// //guard let user = try? JSONDecoder().decode(User.self, from: userDataRaw) else { return } +// +// DispatchQueue.main.async { +// openProgress = false +// } +//// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { +//// self.user = user +//// } +// }.resume() + } + } } extension View { @@ -86,3 +135,167 @@ struct Previews_BottomStatusBar_Previews: PreviewProvider { } } } + +public struct AppleWebLoginView: View { + let onCredentialUpdate: (String) -> Void + + public init(onCredentialUpdate: @escaping (String) -> Void) { + self.onCredentialUpdate = onCredentialUpdate + } + + @State var showProgressOverlay = true + + public var body: some View { + AppleWebLoginUI { + showProgressOverlay = false + } onCredentialUpdate: { credential in + onCredentialUpdate(credential) + } + .overlay(loadingIndicator) + } + + @ViewBuilder + var loadingIndicator: some View { + if showProgressOverlay { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .padding() + } + } +} +public struct AppleWebLoginUI: NSViewRepresentable { + let onFirstLoadComplete: () -> Void + let onCredentialUpdate: (String) -> Void + + public init( + onFirstLoadComplete: @escaping () -> Void, + onCredentialUpdate: @escaping (String) -> Void + ) { + self.onFirstLoadComplete = onFirstLoadComplete + self.onCredentialUpdate = onCredentialUpdate + } + + public func makeCoordinator() -> CoordinateCore { + .init() + } + + public func makeNSView(context: Context) -> NSView { + context.coordinator.core.installFirstLoadCompleteTrap(onFirstLoadComplete) + context.coordinator.core.installCredentialPopulationTrap(onCredentialUpdate) + return context.coordinator.core.webView + } + + public func updateNSView(_: NSView, context _: Context) {} +} +public extension AppleWebLoginUI { + class CoordinateCore { + public let core = AppleWebLoginCore() + + public init() {} + } +} +private let loginURL = URL(string: "https://developer.apple.com/services-account/download?path=/Developer_Tools/Xcode_16.2_beta_1/Xcode_16.2_beta_1.xip")! + +public class AppleWebLoginCore: NSObject, WKUIDelegate, WKNavigationDelegate { + var webView: WKWebView { + associatedWebView + } + + private let associatedWebView: WKWebView + private var dataPopulationTimer: Timer? = nil + private var firstLoadComplete = false + + public private(set) var onFirstLoadComplete: (() -> Void)? + public private(set) var onCredentialPopulation: ((String) -> Void)? + + override public init() { + let contentController = WKUserContentController() + let configuration = WKWebViewConfiguration() + configuration.defaultWebpagePreferences.allowsContentJavaScript = true + configuration.userContentController = contentController + configuration.preferences.setValue(true, forKey: "allowFileAccessFromFileURLs") + configuration.websiteDataStore = .nonPersistent() + + associatedWebView = .init( + frame: CGRect(x: 0, y: 0, width: 1920, height: 1080), + configuration: configuration + ) + associatedWebView.isHidden = true + + super.init() + + associatedWebView.uiDelegate = self + associatedWebView.navigationDelegate = self + + associatedWebView.load(.init(url: loginURL)) + + #if DEBUG + if associatedWebView.responds(to: Selector(("setInspectable:"))) { + associatedWebView.perform(Selector(("setInspectable:")), with: true) + } + #endif + + let dataPopulationTimer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in + guard let self else { return } + removeUnwantedElements() + populateData() + } + RunLoop.main.add(dataPopulationTimer, forMode: .common) + self.dataPopulationTimer = dataPopulationTimer + } + + deinit { + dataPopulationTimer?.invalidate() + onCredentialPopulation = nil + } + + public func webView(_: WKWebView, didFinish _: WKNavigation!) { + guard !firstLoadComplete else { return } + defer { firstLoadComplete = true } + associatedWebView.isHidden = false + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.onFirstLoadComplete?() + self.onFirstLoadComplete = nil + } + } + + public func installFirstLoadCompleteTrap(_ block: @escaping () -> Void) { + onFirstLoadComplete = block + } + + public func installCredentialPopulationTrap(_ block: @escaping (String) -> Void) { + onCredentialPopulation = block + } + + private func removeUnwantedElements() { + let removeElements = """ + Element.prototype.remove = function() { + this.parentElement.removeChild(this); + } + NodeList.prototype.remove = HTMLCollection.prototype.remove = function() { + for(var i = this.length - 1; i >= 0; i--) { + if(this[i] && this[i].parentElement) { + this[i].parentElement.removeChild(this[i]); + } + } + } + document.getElementById("globalheader").remove(); + document.getElementById("ac-localnav").remove(); + document.getElementById("ac-globalfooter").remove(); + document.getElementsByClassName('landing__animation').remove(); + """ + associatedWebView.evaluateJavaScript(removeElements) { _, _ in + } + } + + private func populateData() { + guard let onCredentialPopulation else { return } + associatedWebView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in + for cookie in cookies where cookie.name == "myacinfo" { + let value = cookie.value + onCredentialPopulation(value) + self.onCredentialPopulation = nil + } + } + } +} diff --git a/Xcodes/Resources/Localizable.xcstrings b/Xcodes/Resources/Localizable.xcstrings index 24fc217..bc7ff4d 100644 --- a/Xcodes/Resources/Localizable.xcstrings +++ b/Xcodes/Resources/Localizable.xcstrings @@ -18274,6 +18274,9 @@ } } } + }, + "Please sign in to Apple in this page." : { + }, "Preferences" : { "localizations" : {