Skip to content

Commit e02e3c8

Browse files
authored
Merge pull request #403 from superwall/develop
4.11.1
2 parents c2eccb9 + 2fd57ba commit e02e3c8

7 files changed

Lines changed: 67 additions & 102 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,21 @@
22

33
The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub.
44

5+
## 4.11.1
6+
7+
### Fixes
8+
9+
- Fixes issue where `isApplePayAvailable` being calculated off the main thread could cause a crash.
10+
- Fixes potential crashes in WebKit navigation delegate methods.
11+
512
## 4.11.0
613

714
### Enhancements
815

916
- Adds the ability to override introductory offer eligibility via the paywall editor.
1017
- Adds dynamic notification support and scheduling.
1118
- Adds `refreshConfiguration()` to manually refresh the SDK configuration. This should only be used in wrapper SDKs in development for hot reloading.
12-
- Adds `offerType`, `subscriptionGroupId` and `store` to SubscriptionTransaction` and `NonSubscriptionTransaction`.
19+
- Adds `offerType`, `subscriptionGroupId` and `store` to `SubscriptionTransaction` and `NonSubscriptionTransaction`.
1320

1421
### Fixes
1522

Sources/SuperwallKit/Misc/Constants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ let sdkVersion = """
1818
*/
1919

2020
let sdkVersion = """
21-
4.11.0
21+
4.11.1
2222
"""

Sources/SuperwallKit/Misc/DispatchQueueBacked.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ public final class DispatchQueueBacked<T>: @unchecked Sendable {
2121

2222
public var wrappedValue: T {
2323
get {
24-
queue.sync {
25-
value
26-
}
24+
queue.sync { value }
2725
}
2826
set {
2927
queue.async { [weak self] in
@@ -42,3 +40,14 @@ public final class DispatchQueueBacked<T>: @unchecked Sendable {
4240
}
4341
}
4442
}
43+
44+
extension DispatchQueueBacked where T == Bool {
45+
/// Returns `true` if already handled, otherwise marks handled and returns `false`.
46+
func testAndSetTrue() -> Bool {
47+
queue.sync {
48+
if value { return true }
49+
value = true
50+
return false
51+
}
52+
}
53+
}

Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift

Lines changed: 14 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -173,84 +173,24 @@ class DeviceHelper {
173173
return ProcessInfo.processInfo.isLowPowerModeEnabled ? "true" : "false"
174174
}
175175

176-
var isApplePayAvailable: Bool {
177-
var networks: [PKPaymentNetwork] = [
178-
.amex,
179-
.cartesBancaires,
180-
.chinaUnionPay,
181-
.discover,
182-
.eftpos,
183-
.electron,
184-
.elo,
185-
.idCredit,
186-
.interac,
187-
.JCB,
188-
.mada,
189-
.maestro,
190-
.masterCard,
191-
.privateLabel,
192-
.quicPay,
193-
.suica,
194-
.visa,
195-
.vPay
196-
]
197-
if #available(iOS 14.0, *) {
198-
networks += [.barcode, .girocard]
199-
}
200-
if #available(iOS 14.5, *) {
201-
networks += [.mir]
202-
}
203-
204-
if #available(iOS 15.0, *) {
205-
networks += [.nanaco]
206-
}
207-
if #available(iOS 15.1, *) {
208-
networks += [.dankort]
209-
}
210-
211-
#if compiler(>=5.7)
212-
if #available(iOS 16.0, *) {
213-
networks += [.bancomat, .nanaco, .waon]
214-
}
215-
#endif
216-
217-
#if compiler(>=5.8)
218-
if #available(iOS 16.4, *) {
219-
networks += [.postFinance]
220-
}
221-
#endif
222-
223-
#if compiler(>=5.9)
224-
if #available(iOS 17.0, *) {
225-
networks += [.tmoney, .pagoBancomat]
226-
}
227-
#endif
228-
229-
#if compiler(>=5.10)
230-
if #available(iOS 17.4, visionOS 1.1, *) {
231-
networks += [.meeza]
232-
}
233-
234-
if #available(iOS 17.5, visionOS 1.2, *) {
235-
networks += [.bankAxept, .NAPAS]
236-
}
237-
#endif
176+
@DispatchQueueBacked
177+
private var cachedIsApplePayAvailable: Bool?
238178

239-
#if compiler(>=6.1)
240-
if #available(iOS 18.4, visionOS 2.4, *) {
241-
networks += [.himyan, .jaywan]
179+
func getIsApplePayAvailable() async -> Bool {
180+
if let cached = cachedIsApplePayAvailable {
181+
return cached
242182
}
243-
#endif
244183

245-
#if compiler(>=6.2)
246-
if #available(iOS 26.0, visionOS 26.0, *) {
247-
networks += [.myDebit]
184+
let result = await MainActor.run {
185+
guard PKPaymentAuthorizationViewController.canMakePayments() else {
186+
return false
187+
}
188+
let availableNetworks = PKPaymentRequest.availableNetworks()
189+
return PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: availableNetworks)
248190
}
249-
#endif
250191

251-
return PKPaymentAuthorizationViewController.canMakePayments(
252-
usingNetworks: networks
253-
)
192+
cachedIsApplePayAvailable = result
193+
return result
254194
}
255195

256196
let bundleId: String = {
@@ -623,7 +563,7 @@ class DeviceHelper {
623563
radioType: radioType,
624564
interfaceStyle: interfaceStyle,
625565
isLowPowerModeEnabled: isLowPowerModeEnabled == "true",
626-
isApplePayAvailable: isApplePayAvailable,
566+
isApplePayAvailable: await getIsApplePayAvailable(),
627567
bundleId: bundleId,
628568
appInstallDate: appInstalledAtString,
629569
isMac: isMac,

Sources/SuperwallKit/Paywall/View Controller/CheckoutWebViewController.swift

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ final class CheckoutWebViewController: UIViewController {
1414
private let webView: WKWebView
1515
private let url: URL
1616
var onDismiss: (() -> Void)?
17+
@DispatchQueueBacked
1718
var hasHandledRedemption = false
1819

1920
deinit {
@@ -209,27 +210,28 @@ final class CheckoutWebViewController: UIViewController {
209210
extension CheckoutWebViewController: WKNavigationDelegate {
210211
func webView(
211212
_ webView: WKWebView,
212-
decidePolicyFor navigationAction: WKNavigationAction
213-
) async -> WKNavigationActionPolicy {
213+
decidePolicyFor navigationAction: WKNavigationAction,
214+
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
215+
) {
214216
guard let url = navigationAction.request.url else {
215-
return .allow
217+
decisionHandler(.allow)
218+
return
216219
}
217220

218221
// Block Superwall deep links from checkout to prevent duplicate redemptions
219222
if url.isSuperwallDeepLink {
220-
if hasHandledRedemption {
221-
return .cancel
223+
if $hasHandledRedemption.testAndSetTrue() {
224+
decisionHandler(.cancel)
225+
return
222226
}
223227

224-
hasHandledRedemption = true
225-
await MainActor.run {
226-
_ = Superwall.shared.dependencyContainer.deepLinkRouter.route(url: url)
227-
}
228-
return .cancel
228+
_ = Superwall.shared.dependencyContainer.deepLinkRouter.route(url: url)
229+
decisionHandler(.cancel)
230+
return
229231
}
230232

231233
// Allow all other navigation types for payment flows and SSO authentication
232-
return .allow
234+
decisionHandler(.allow)
233235
}
234236

235237
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {

Sources/SuperwallKit/Paywall/View Controller/Web View/SWWebView.swift

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,37 +190,44 @@ extension SWWebView: SWWebViewLoadingDelegate {
190190
extension SWWebView: WKNavigationDelegate {
191191
func webView(
192192
_ webView: WKWebView,
193-
decidePolicyFor navigationResponse: WKNavigationResponse
194-
) async -> WKNavigationResponsePolicy {
193+
decidePolicyFor navigationResponse: WKNavigationResponse,
194+
decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void
195+
) {
195196
guard let statusCode = (navigationResponse.response as? HTTPURLResponse)?.statusCode else {
196197
// if there's no http status code to act on, exit and allow navigation
197-
return .allow
198+
decisionHandler(.allow)
199+
return
198200
}
199201

200202
// Track paywall errors
201203
if statusCode >= 400 {
202204
completion?(WebViewError.network(statusCode))
203-
return .cancel
205+
decisionHandler(.cancel)
206+
return
204207
}
205208

206-
return .allow
209+
decisionHandler(.allow)
207210
}
208211

209212
func webView(
210213
_ webView: WKWebView,
211-
decidePolicyFor navigationAction: WKNavigationAction
212-
) async -> WKNavigationActionPolicy {
214+
decidePolicyFor navigationAction: WKNavigationAction,
215+
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
216+
) {
213217
if webView.isLoading {
214-
return .allow
218+
decisionHandler(.allow)
219+
return
215220
}
216221
if navigationAction.navigationType == .reload {
217-
return .allow
222+
decisionHandler(.allow)
223+
return
218224
}
219225
if enableIframeNavigation,
220226
navigationAction.targetFrame?.isMainFrame == false {
221-
return .allow
227+
decisionHandler(.allow)
228+
return
222229
}
223-
return .cancel
230+
decisionHandler(.cancel)
224231
}
225232

226233
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {

SuperwallKit.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Pod::Spec.new do |s|
22

33
s.name = "SuperwallKit"
4-
s.version = "4.11.0"
4+
s.version = "4.11.1"
55
s.summary = "Superwall: In-App Paywalls Made Easy"
66
s.description = "Paywall infrastructure for mobile apps :) we make things like editing your paywall and running price tests as easy as clicking a few buttons. superwall.com"
77

0 commit comments

Comments
 (0)