Skip to content

[Swift] Add support for window.open delegation#82

Merged
markmur merged 6 commits into
mainfrom
swift/window.open_request
May 15, 2026
Merged

[Swift] Add support for window.open delegation#82
markmur merged 6 commits into
mainfrom
swift/window.open_request

Conversation

@markmur
Copy link
Copy Markdown
Contributor

@markmur markmur commented May 13, 2026

Adds default handling of the UCP window.open delegation. When checkout fires ec.window.open_request, the kit opens the URL via UIApplication and responds with success; if canOpenURL returns false, the kit replies with window_open_rejected_error per spec.

The kit advertises window.open in the ec_delegate URL query param and echoes the intersection of merchant-requested ∩ kit-supported delegations in the ec.ready response. Consumers can override the default behaviour via Client.onWindowOpenRequest(_:).


Before you merge

Important

  • I've added tests to support my implementation
  • I have read and agree with the Contribution Guidelines
  • I have read and agree with the Code of Conduct
  • I've updated the relevant platform README (platforms/swift/README.md and/or platforms/android/README.md)

Releasing a new Swift version?
  • I have bumped the version in platforms/swift/ShopifyCheckoutKit.podspec
  • I have bumped the version in platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift
  • I have updated platforms/swift/CHANGELOG.md
  • I have updated the SwiftPM/CocoaPods version snippets in platforms/swift/README.md (major version only)
Releasing a new Android version?
  • I have bumped the versionName in platforms/android/lib/build.gradle
  • I have updated platforms/android/CHANGELOG.md
  • I have updated the Gradle/Maven version snippets in platforms/android/README.md

Tip

See the Contributing documentation for the full release process per platform.

@markmur markmur self-assigned this May 13, 2026
@markmur markmur force-pushed the swift/window.open_request branch 9 times, most recently from 998f068 to 5bff799 Compare May 13, 2026 17:26
@markmur markmur marked this pull request as ready for review May 13, 2026 17:41
@markmur markmur requested a review from a team as a code owner May 13, 2026 17:41
$0.colorScheme = .automatic
$0.tintColor = ColorPalette.primaryColor
$0.preloading.enabled = true
$0.preloading.enabled = false
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling preloading in the sample app by default

Comment thread platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebView.swift Outdated
Comment thread platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift Outdated
Comment thread platforms/swift/Tests/ShopifyCheckoutKitTests/CheckoutWebViewTests.swift Outdated
switch windowOpen {
case .default:
return base
case .safariViewController:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my understanding this means it will default to opening a new webview for unhandled links right?

Copy link
Copy Markdown
Contributor Author

@markmur markmur May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is sample app code. The smart default remains the same and will pull the buyer out of the app and into Safari, but the Sample offers an alternative which opens the link in a Safari webview inside of the app.

I'd like to change the smart default to this going foward

Comment thread platforms/swift/Sources/ShopifyCheckoutKit/CheckoutWebView.swift Outdated
@markmur markmur force-pushed the swift/window.open_request branch from f0a146f to 5759dba Compare May 14, 2026 10:52
@markmur markmur added the #gsd:50662 Rebase Checkout Kit on UCP label May 14, 2026
@markmur markmur force-pushed the swift/window.open_request branch from b059dd3 to 9de70cd Compare May 14, 2026 12:27
@markmur markmur force-pushed the swift/window.open_request branch 2 times, most recently from 5628bde to 0eb4b68 Compare May 14, 2026 16:22
markmur added 5 commits May 14, 2026 17:23
Adds default handling of the UCP window.open delegation. When checkout
fires ec.window.open_request, the kit opens the URL via UIApplication
and responds with success; if canOpenURL returns false, the kit replies
with window_open_rejected_error per spec.

The kit advertises window.open in the ec_delegate URL query param and
echoes the intersection of merchant-requested ∩ kit-supported delegations
in the ec.ready response. Consumers can override the default behaviour
via Client.onWindowOpenRequest(_:).
- SwiftFormat: hoist try, wrap arguments/function bodies, extension access control
- Replace force-unwraps in CheckoutProtocolURLTests with try #require
- Use CapturedURLHolder reference to avoid Swift 6 captured-var warnings in
  link-dispatch tests
- Use XCTestCase expectation+wait pattern for deterministic main-runloop draining
@markmur markmur force-pushed the swift/window.open_request branch 3 times, most recently from a91994b to 3f6ebf5 Compare May 14, 2026 16:29
@markmur markmur force-pushed the swift/window.open_request branch from 3f6ebf5 to 1393e81 Compare May 14, 2026 16:32
Comment on lines +289 to +302
// Handle non-HTTP links triggered on external surfaces by opening them with UIApplication
// Scenarios include:
// - mailto:, tel: etc
// - Deep links on offsite payment sites
//
if CheckoutURL(from: url).isDeepLink() {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
OSLogger.shared.debug("Deep link intercepted: \(url.absoluteString) - allowed")
return decisionHandler(.allow)
} else {
OSLogger.shared.debug("Deep link intercepted: \(url.absoluteString) - rejected")
return decisionHandler(.cancel)
}
Copy link
Copy Markdown
Contributor Author

@markmur markmur May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This an important change. Previously we delegated this to consumers, where we used the "smart default" to open the URL using UIApplication or allowed consumers to override.

The difference now is that it's handled internally. Link open requests come from checkout via the protocol only.

}

extension CheckoutProtocol {
public static let windowOpen = DelegationDescriptor<WindowOpenRequest, WindowOpenResult>(
Copy link
Copy Markdown
Contributor

@tiagocandido tiagocandido May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-platform mismatch with Android PR #107: there WindowOpenRequest, WindowOpenResult, windowOpen, and Client.on(DelegationDescriptor) are internal, and ec.window.open_request short-circuits to the kit default — consumers can't override. Here all four are public (WindowOpen.swift:26,56,78; Client.swift:53) and userContentController tries client?.process (:257) before defaultsClient (:262).

Module split rules out plain internal@_spi(KitInternal) public is the lightest fix. Worth aligning before merge; once shipped, taking it back is a breaking change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These AI generated comments are so hard to read 😵‍💫

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on Android the equivalent stuff is internal and the kit always handles window.open. Here it's public and the consumer's client gets first dibs, so Swift consumers can override and Android consumers can't. These comes from the feedback on the Android PR. I'm just pointing out disparity.

/// `UIApplication.shared.open(...)` after a `canOpenURL` check.
static let defaultsClient = CheckoutProtocol.Client()
.on(CheckoutProtocol.windowOpen) { request in
guard UIApplication.shared.canOpenURL(request.url) else {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canOpenURL here (and at :295) is gated by LSApplicationQueriesSchemes — if a consumer hasn't declared the scheme in Info.plist, this returns false even when the system has a handler. Same shape kiftio flagged on Android (<queries> brittleness on API 30+). Fix: skip the precheck and use UIApplication.shared.open(_:options:completionHandler:) — the completion reports the real result from the system.

tiagocandido added a commit that referenced this pull request May 15, 2026
### What changes are you making?

Completes the `window.open` UCP delegation handshake on Android, bringing it to parity with [Swift PR #82](#82). The kit advertises `window.open` in the `ec.ready` response, handles `ec.window.open_request` internally via an `ACTION_VIEW` Intent, and narrows the native link-intercept to non-http schemes. The legacy consumer-facing link-click hooks are removed in favor of fully kit-internal handling.

### Before you merge

> [!IMPORTANT]
>
> - [x] I've added tests to support my implementation
> - [x] I have read and agree with the [Contribution Guidelines](./CONTRIBUTING.md)
> - [x] I have read and agree with the [Code of Conduct](./CODE_OF_CONDUCT.md)
> - [ ] I've updated the relevant platform README (`platforms/swift/README.md` and/or `platforms/android/README.md`)

---

<details>
<summary>Releasing a new Swift version?</summary>

- [ ] I have bumped the version in `platforms/swift/ShopifyCheckoutKit.podspec`
- [ ] I have bumped the version in `platforms/swift/Sources/ShopifyCheckoutKit/ShopifyCheckoutKit.swift`
- [ ] I have updated `platforms/swift/CHANGELOG.md`
- [ ] I have updated the SwiftPM/CocoaPods version snippets in `platforms/swift/README.md` (major version only)

</details>

<details>
<summary>Releasing a new Android version?</summary>

- [ ] I have bumped the `versionName` in `platforms/android/lib/build.gradle`
- [ ] I have updated `platforms/android/CHANGELOG.md`
- [ ] I have updated the Gradle/Maven version snippets in `platforms/android/README.md`

</details>

> [!TIP]
> See the [Contributing documentation](./CONTRIBUTING.md) for the full release process per platform.
@markmur markmur merged commit c3565fa into main May 15, 2026
18 checks passed
@markmur markmur deleted the swift/window.open_request branch May 15, 2026 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

#gsd:50662 Rebase Checkout Kit on UCP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants