forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBillingWebFlow.swift
More file actions
137 lines (116 loc) · 3.73 KB
/
Copy pathBillingWebFlow.swift
File metadata and controls
137 lines (116 loc) · 3.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import OmiTheme
import SwiftUI
import WebKit
struct BillingWebFlow: Identifiable {
let id = UUID()
let title: String
let url: URL
let completionURLs: [String]
}
enum BillingWebFlowOutcome {
case completed
case cancelled
case dismissed
}
struct BillingWebFlowSheet: View {
let flow: BillingWebFlow
let onComplete: (BillingWebFlowOutcome) -> Void
var body: some View {
VStack(spacing: 0) {
HStack(spacing: OmiSpacing.md) {
Text(flow.title)
.scaledFont(size: OmiType.heading, weight: .semibold)
.foregroundColor(Ink.primary)
Spacer()
Button("Close") {
onComplete(.dismissed)
}
.buttonStyle(OmiButtonStyle(.primary, size: .compact))
}
.padding(.horizontal, OmiSpacing.xl)
.padding(.vertical, OmiSpacing.lg)
.background(Ink.rowFill)
GlassSeparator()
BillingWebView(flow: flow, onComplete: onComplete)
.frame(minWidth: 860, minHeight: 680)
}
.background(Ink.surface)
}
}
struct BillingWebView: NSViewRepresentable {
let flow: BillingWebFlow
let onComplete: (BillingWebFlowOutcome) -> Void
func makeCoordinator() -> Coordinator {
Coordinator(flow: flow, onComplete: onComplete)
}
func makeNSView(context: Context) -> WKWebView {
let configuration = WKWebViewConfiguration()
configuration.defaultWebpagePreferences.allowsContentJavaScript = true
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.navigationDelegate = context.coordinator
webView.uiDelegate = context.coordinator
webView.setValue(false, forKey: "drawsBackground")
webView.load(URLRequest(url: flow.url))
return webView
}
func updateNSView(_ nsView: WKWebView, context: Context) {}
final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {
let flow: BillingWebFlow
let onComplete: (BillingWebFlowOutcome) -> Void
var completionHandled = false
init(flow: BillingWebFlow, onComplete: @escaping (BillingWebFlowOutcome) -> Void) {
self.flow = flow
self.onComplete = onComplete
}
@MainActor
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping @MainActor @Sendable (WKNavigationActionPolicy) -> Void
) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
if let matchedCompletionURL = flow.completionURLs.compactMap(URL.init(string:)).first(where: {
Self.urlsMatchCompletion(url, completionURL: $0)
}) {
if matchedCompletionURL.pathComponents.last == "cancel" {
finish(.cancelled)
} else {
finish(.completed)
}
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
private static func urlsMatchCompletion(_ url: URL, completionURL: URL) -> Bool {
guard url.scheme == completionURL.scheme,
url.host == completionURL.host,
url.path == completionURL.path
else {
return false
}
return url.query == completionURL.query || completionURL.query == nil
}
func webView(
_ webView: WKWebView,
createWebViewWith configuration: WKWebViewConfiguration,
for navigationAction: WKNavigationAction,
windowFeatures: WKWindowFeatures
) -> WKWebView? {
if navigationAction.targetFrame == nil, let requestURL = navigationAction.request.url {
webView.load(URLRequest(url: requestURL))
}
return nil
}
func finish(_ outcome: BillingWebFlowOutcome) {
guard !completionHandled else { return }
completionHandled = true
DispatchQueue.main.async {
self.onComplete(outcome)
}
}
}
}