@@ -358,6 +358,96 @@ extension Session: WebViewDelegate {
358
358
currentVisit. cancel ( )
359
359
visit ( currentVisit. visitable)
360
360
}
361
+
362
+ /// Called by the Turbo bridge when a visit request fails with a non-HTTP status code,
363
+ /// suggesting it may be the result of a cross-origin redirect visit.
364
+ ///
365
+ /// Determining a cross-origin redirect is not possible in JavaScript using the Fetch API
366
+ /// due to CORS restrictions, so verification is performed on the native side.
367
+ /// If a redirect is detected, a cross-origin redirect visit is proposed; otherwise,
368
+ /// the visit is failed.
369
+ ///
370
+ /// - Parameters:
371
+ /// - webView: The web view bridge.
372
+ /// - location: The original visit location requested.
373
+ /// - identifier: A unique identifier for the visit.
374
+ func webView( _ webView: WebViewBridge , didFailRequestWithNonHttpStatusToLocation location: URL , identifier: String ) {
375
+ log ( " didFailRequestWithNonHttpStatusToLocation " ,
376
+ [ " location " : location,
377
+ " visitIdentifier " : identifier]
378
+ )
379
+
380
+ Task {
381
+ await resolveRedirect ( to: location, identifier: identifier)
382
+ }
383
+ }
384
+
385
+ private func resolveRedirect( to location: URL , identifier: String ) async {
386
+ do {
387
+ let result = try await RedirectHandler ( ) . resolve ( location: location)
388
+ switch result {
389
+ case . noRedirect:
390
+ log ( " resolveRedirect: no redirect " ,
391
+ [ " location " : location,
392
+ " visitIdentifier " : identifier]
393
+ )
394
+ await failCurrentVisit (
395
+ with: TurboError . http ( statusCode: 0 ) ,
396
+ visitIdentifier: identifier
397
+ )
398
+ case . sameOriginRedirect( let url) :
399
+ // Same-domain redirects are handled by Turbo.
400
+ // Handling them here could lead to an infinite loop.
401
+ log ( " resolveRedirect: same domain redirect " ,
402
+ [ " location " : location,
403
+ " redirectLocation " : url,
404
+ " visitIdentifier " : identifier]
405
+ )
406
+ await failCurrentVisit (
407
+ with: TurboError . http ( statusCode: 0 ) ,
408
+ visitIdentifier: identifier
409
+ )
410
+ case . crossOriginRedirect( let url) :
411
+ await visitProposedToCrossOriginRedirect (
412
+ location: location,
413
+ redirectLocation: url,
414
+ visitIdentifier: identifier
415
+ )
416
+ }
417
+ } catch {
418
+ await failCurrentVisit (
419
+ with: error,
420
+ visitIdentifier: identifier
421
+ )
422
+ }
423
+ }
424
+
425
+ @MainActor
426
+ private func failCurrentVisit( with error: Error , visitIdentifier: String ) {
427
+ // This is only relevant to `JavaScriptVisit`, as `ColdBootVisit` currently
428
+ // doesn't go through the same flow.
429
+ guard let visit = currentVisit as? JavaScriptVisit ,
430
+ visit. identifier == visitIdentifier else { return }
431
+
432
+ visit. fail ( with: error)
433
+ }
434
+
435
+ @MainActor
436
+ private func visitProposedToCrossOriginRedirect(
437
+ location: URL ,
438
+ redirectLocation: URL ,
439
+ visitIdentifier: String ) {
440
+ log ( " visitProposedToCrossOriginRedirect " ,
441
+ [ " location " : location,
442
+ " redirectLocation " : redirectLocation,
443
+ " visitIdentifier " : visitIdentifier]
444
+ )
445
+
446
+ guard let visit = currentVisit as? JavaScriptVisit ,
447
+ visit. identifier == visitIdentifier else { return }
448
+
449
+ delegate? . session ( self , didProposeVisitToCrossOriginRedirect: redirectLocation)
450
+ }
361
451
}
362
452
363
453
extension Session : WKNavigationDelegate {
0 commit comments