crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

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 }