NYTLoginView.swift (2923B)
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 { 26 dismiss() 27 } label: { 28 Image(systemName: "xmark") 29 } 30 .accessibilityLabel("Cancel") 31 } 32 } 33 } 34 } 35 } 36 37 // MARK: - WKWebView wrapper 38 39 private struct NYTWebView: UIViewRepresentable { 40 let url: URL 41 let onSignInDetected: @MainActor (WKHTTPCookieStore) async -> Void 42 43 func makeCoordinator() -> Coordinator { 44 Coordinator(onSignInDetected: onSignInDetected) 45 } 46 47 func makeUIView(context: Context) -> WKWebView { 48 let config = WKWebViewConfiguration() 49 let webView = WKWebView(frame: .zero, configuration: config) 50 webView.navigationDelegate = context.coordinator 51 webView.load(URLRequest(url: url)) 52 return webView 53 } 54 55 func updateUIView(_ uiView: WKWebView, context: Context) {} 56 57 final class Coordinator: NSObject, WKNavigationDelegate { 58 let onSignInDetected: @MainActor (WKHTTPCookieStore) async -> Void 59 private var didComplete = false 60 61 init(onSignInDetected: @escaping @MainActor (WKHTTPCookieStore) async -> Void) { 62 self.onSignInDetected = onSignInDetected 63 } 64 65 func webView( 66 _ webView: WKWebView, 67 decidePolicyFor navigationAction: WKNavigationAction 68 ) async -> WKNavigationActionPolicy { 69 guard !didComplete, 70 let url = navigationAction.request.url, 71 url.host?.contains("nytimes.com") == true, 72 url.path.hasPrefix("/crosswords") else { 73 return .allow 74 } 75 76 // The user has signed in and is being redirected to the crosswords 77 // page. Extract cookies and signal completion. 78 didComplete = true 79 let cookieStore = webView.configuration.websiteDataStore.httpCookieStore 80 await onSignInDetected(cookieStore) 81 return .cancel 82 } 83 } 84 }