NYTLoginView.swift (2757B)
1 import SwiftUI 2 import WebKit 3 4 /// Presents the NYT login page in a WKWebView. After the user signs in, 5 /// the NYT site redirects to nytimes.com/crosswords. We detect that 6 /// redirect and extract the NYT-S cookie from the web view's cookie store. 7 struct NYTLoginView: View { 8 @Environment(NYTAuthService.self) private var nytAuth 9 @Environment(\.dismiss) private var dismiss 10 11 var body: some View { 12 NavigationStack { 13 NYTWebView( 14 url: NYTAuthService.loginURL, 15 onSignInDetected: { cookieStore in 16 await nytAuth.completeSignIn(cookieStore: cookieStore) 17 dismiss() 18 } 19 ) 20 .ignoresSafeArea(edges: .bottom) 21 .navigationTitle("Sign In") 22 .navigationBarTitleDisplayMode(.inline) 23 .toolbar { 24 ToolbarItem(placement: .cancellationAction) { 25 Button("Cancel") { dismiss() } 26 } 27 } 28 } 29 } 30 } 31 32 // MARK: - WKWebView wrapper 33 34 private struct NYTWebView: UIViewRepresentable { 35 let url: URL 36 let onSignInDetected: @MainActor (WKHTTPCookieStore) async -> Void 37 38 func makeCoordinator() -> Coordinator { 39 Coordinator(onSignInDetected: onSignInDetected) 40 } 41 42 func makeUIView(context: Context) -> WKWebView { 43 let config = WKWebViewConfiguration() 44 let webView = WKWebView(frame: .zero, configuration: config) 45 webView.navigationDelegate = context.coordinator 46 webView.load(URLRequest(url: url)) 47 return webView 48 } 49 50 func updateUIView(_ uiView: WKWebView, context: Context) {} 51 52 final class Coordinator: NSObject, WKNavigationDelegate { 53 let onSignInDetected: @MainActor (WKHTTPCookieStore) async -> Void 54 private var didComplete = false 55 56 init(onSignInDetected: @escaping @MainActor (WKHTTPCookieStore) async -> Void) { 57 self.onSignInDetected = onSignInDetected 58 } 59 60 func webView( 61 _ webView: WKWebView, 62 decidePolicyFor navigationAction: WKNavigationAction 63 ) async -> WKNavigationActionPolicy { 64 guard !didComplete, 65 let url = navigationAction.request.url, 66 url.host?.contains("nytimes.com") == true, 67 url.path.hasPrefix("/crosswords") else { 68 return .allow 69 } 70 71 // The user has signed in and is being redirected to the crosswords 72 // page. Extract cookies and signal completion. 73 didComplete = true 74 let cookieStore = webView.configuration.websiteDataStore.httpCookieStore 75 await onSignInDetected(cookieStore) 76 return .cancel 77 } 78 } 79 }