crossmate

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

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 }