Skip to content

Commit dcccbb9

Browse files
committed
Merge branch 'develop' into ir/feat/dynamic-notifications
2 parents 370391d + fc7a393 commit dcccbb9

File tree

14 files changed

+409
-26
lines changed

14 files changed

+409
-26
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

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

5+
## 4.10.7
6+
7+
### Fixes
8+
9+
- Fixes issue returning the `PurchaseResult` from `Superwall.shared.purchase(_:)` when using StoreKit 1 inside a `PurchaseController`.
10+
- Fixes `handleDeepLink` returning true for non-Superwall URLs when called before configuration completes.
11+
12+
## 4.10.6
13+
14+
### Fixes
15+
16+
- Fixes issue that prevented the SDK from being built on old Xcode versions.
17+
518
## 4.10.5
619

720
### Enhancements

Examples/Advanced/Advanced/SuperwallAdvancedApp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ struct SuperwallAdvancedApp: App {
2222

2323
// MARK: - Option 1: Let Superwall handle everything
2424
Superwall.configure(apiKey: apiKey)
25-
25+
2626
// MARK: - Option 2: Use a Purchase Controller with StoreKit
2727
/*
2828
// Step 1 - Create your Purchase Controller

Sources/SuperwallKit/Config/Models/FeatureFlags.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ struct FeatureFlags: Codable, Equatable {
2222
var enableMultiplePaywallUrls: Bool
2323
var enableConfigRefresh: Bool
2424
var enableTextInteraction: Bool
25+
var enableIframeNavigation: Bool
2526

2627
enum CodingKeys: String, CodingKey {
2728
case toggles
@@ -43,6 +44,7 @@ struct FeatureFlags: Codable, Equatable {
4344
enableMultiplePaywallUrls = rawFeatureFlags.value(forKey: "enable_multiple_paywall_urls", default: false)
4445
enableConfigRefresh = rawFeatureFlags.value(forKey: "enable_config_refresh_v2", default: false)
4546
enableTextInteraction = rawFeatureFlags.value(forKey: "enable_text_interaction", default: false)
47+
enableIframeNavigation = rawFeatureFlags.value(forKey: "enable_iframe_navigation", default: false)
4648
}
4749

4850
func encode(to encoder: Encoder) throws {
@@ -57,7 +59,8 @@ struct FeatureFlags: Codable, Equatable {
5759
RawFeatureFlag(key: "enable_none_scheduling_policy", enabled: enableNoneSchedulingPolicy),
5860
RawFeatureFlag(key: "enable_multiple_paywall_urls", enabled: enableMultiplePaywallUrls),
5961
RawFeatureFlag(key: "enable_config_refresh_v2", enabled: enableConfigRefresh),
60-
RawFeatureFlag(key: "enable_text_interaction", enabled: enableTextInteraction)
62+
RawFeatureFlag(key: "enable_text_interaction", enabled: enableTextInteraction),
63+
RawFeatureFlag(key: "enable_iframe_navigation", enabled: enableIframeNavigation)
6164
]
6265

6366
try container.encode(rawFeatureFlags, forKey: .toggles)
@@ -73,7 +76,8 @@ struct FeatureFlags: Codable, Equatable {
7376
enableMultiplePaywallUrls: Bool,
7477
enableConfigRefresh: Bool,
7578
enableTextInteraction: Bool,
76-
enableCELLogging: Bool
79+
enableCELLogging: Bool,
80+
enableIframeNavigation: Bool
7781
) {
7882
self.enableExpressionParameters = enableExpressionParameters
7983
self.enableUserIdSeed = enableUserIdSeed
@@ -84,6 +88,7 @@ struct FeatureFlags: Codable, Equatable {
8488
self.enableMultiplePaywallUrls = enableMultiplePaywallUrls
8589
self.enableConfigRefresh = enableConfigRefresh
8690
self.enableTextInteraction = enableTextInteraction
91+
self.enableIframeNavigation = enableIframeNavigation
8792
}
8893
}
8994

@@ -111,7 +116,8 @@ extension FeatureFlags: Stubbable {
111116
enableMultiplePaywallUrls: true,
112117
enableConfigRefresh: true,
113118
enableTextInteraction: true,
114-
enableCELLogging: true
119+
enableCELLogging: true,
120+
enableIframeNavigation: true
115121
)
116122
}
117123
}

Sources/SuperwallKit/DeepLinkRouter.swift

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ final class DeepLinkRouter {
3838
}
3939

4040
let deepLinkUrl: URL
41-
if url.isSuperwallDeepLink {
41+
let isSuperwallDeepLink = url.isSuperwallDeepLink
42+
43+
if isSuperwallDeepLink {
4244
deepLinkUrl = url.superwallDeepLinkMappedURL
4345

4446
Task { @MainActor in
@@ -52,11 +54,26 @@ final class DeepLinkRouter {
5254
deepLinkUrl = url
5355
}
5456

55-
5657
Task {
5758
await Superwall.shared.track(InternalSuperwallEvent.DeepLink(url: deepLinkUrl))
5859
}
59-
return debugManager.handle(deepLinkUrl: deepLinkUrl)
60+
61+
// Check if this is a debug URL
62+
if debugManager.handle(deepLinkUrl: deepLinkUrl) {
63+
return true
64+
}
65+
66+
// Return true for Superwall deep links (we handled it above)
67+
if isSuperwallDeepLink {
68+
return true
69+
}
70+
71+
// Return true if there's a deepLink_open trigger configured
72+
if configManager.triggersByPlacementName[SuperwallEventObjc.deepLink.description] != nil {
73+
return true
74+
}
75+
76+
return false
6077
}
6178

6279
private func listenToConfig() {
@@ -80,9 +97,54 @@ final class DeepLinkRouter {
8097
}
8198

8299
/// Stores the deep link until it can be handled.
100+
///
101+
/// Called when `handleDeepLink` is invoked before Superwall configuration completes.
102+
/// The URL is always stored so it can be processed once config loads, but the return
103+
/// value indicates whether Superwall will definitely handle this URL.
104+
///
105+
/// - Note: The URL is always stored regardless of return value because the fresh config
106+
/// might have a `deepLink_open` trigger even if cached config doesn't. This ensures
107+
/// deep links aren't lost during app launch. If the URL isn't a Superwall URL,
108+
/// returning `false` allows other handlers in a handler chain to process it.
109+
///
110+
/// - Parameter url: The deep link URL to store.
111+
/// - Returns: `true` if the URL is a Superwall URL that will be handled, `false` otherwise.
83112
static func storeDeepLink(_ url: URL) -> Bool {
113+
// Always store the URL - the fresh config might have deepLink_open trigger
114+
// even if cached config doesn't
84115
pendingDeepLink = url
85-
return true
116+
117+
// Only return true if we're confident Superwall will handle this URL
118+
return isSuperwallURL(url)
119+
}
120+
121+
/// Checks if the URL is one that Superwall will handle.
122+
private static func isSuperwallURL(_ url: URL) -> Bool {
123+
// Superwall universal links (*.superwall.app/app-link/*)
124+
if url.isSuperwallDeepLink {
125+
return true
126+
}
127+
128+
// Redemption codes
129+
if url.redeemableCode != nil {
130+
return true
131+
}
132+
133+
// Debug/preview URLs
134+
if DebugManager.outcomeForDeepLink(url: url) != nil {
135+
return true
136+
}
137+
138+
// Check cached config for deepLink_open trigger
139+
let cache = Cache()
140+
if let config = cache.read(LatestConfig.self) {
141+
let triggers = ConfigLogic.getTriggersByPlacementName(from: config.triggers)
142+
if triggers[SuperwallEventObjc.deepLink.description] != nil {
143+
return true
144+
}
145+
}
146+
147+
return false
86148
}
87149
}
88150

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.10.5
21+
4.10.7
2222
"""

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,33 +200,53 @@ class DeviceHelper {
200200
if #available(iOS 14.5, *) {
201201
networks += [.mir]
202202
}
203+
203204
if #available(iOS 15.0, *) {
204205
networks += [.nanaco]
205206
}
206207
if #available(iOS 15.1, *) {
207208
networks += [.dankort]
208209
}
210+
211+
#if compiler(>=5.7)
209212
if #available(iOS 16.0, *) {
210213
networks += [.bancomat, .nanaco, .waon]
211214
}
215+
#endif
216+
217+
#if compiler(>=5.8)
212218
if #available(iOS 16.4, *) {
213219
networks += [.postFinance]
214220
}
221+
#endif
222+
223+
#if compiler(>=5.9)
215224
if #available(iOS 17.0, *) {
216225
networks += [.tmoney, .pagoBancomat]
217226
}
227+
#endif
228+
229+
#if compiler(>=5.10)
218230
if #available(iOS 17.4, visionOS 1.1, *) {
219231
networks += [.meeza]
220232
}
233+
221234
if #available(iOS 17.5, visionOS 1.2, *) {
222235
networks += [.bankAxept, .NAPAS]
223236
}
237+
#endif
238+
239+
#if compiler(>=6.1)
224240
if #available(iOS 18.4, visionOS 2.4, *) {
225241
networks += [.himyan, .jaywan]
226242
}
243+
#endif
244+
245+
#if compiler(>=6.2)
227246
if #available(iOS 26.0, visionOS 26.0, *) {
228247
networks += [.myDebit]
229248
}
249+
#endif
230250

231251
return PKPaymentAuthorizationViewController.canMakePayments(
232252
usingNetworks: networks

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
extension DeviceHelper {
1010
func currentSwiftVersion() -> String {
11-
#if swift(>=6.1)
12-
return "6.1+"
11+
#if swift(>=6.2)
12+
return "6.2"
13+
#elseif swift(>=6.1)
14+
return "6.1"
1315
#elseif swift(>=6.0)
1416
return "6.0"
1517
#elseif swift(>=5.10)
@@ -108,7 +110,9 @@ extension DeviceHelper {
108110
}
109111

110112
func currentCompilerVersion() -> String {
111-
#if compiler(>=6.1)
113+
#if compiler(>=6.2)
114+
return "6.2"
115+
#elseif compiler(>=6.1)
112116
return "6.1+"
113117
#elseif compiler(>=6.0)
114118
return "6.0"

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class SWWebView: WKWebView {
4343
private let isMac: Bool
4444
private let isOnDeviceCacheEnabled: Bool
4545
private var completion: ((Error?) -> Void)?
46+
private let enableIframeNavigation: Bool
4647

4748
init(
4849
isMac: Bool,
@@ -54,6 +55,7 @@ class SWWebView: WKWebView {
5455
self.messageHandler = messageHandler
5556
self.isOnDeviceCacheEnabled = isOnDeviceCacheEnabled
5657
let featureFlags = factory.makeFeatureFlags()
58+
self.enableIframeNavigation = featureFlags?.enableIframeNavigation ?? false
5759

5860
self.loadingHandler = SWWebViewLoadingHandler(
5961
enableMultiplePaywallUrls: featureFlags?.enableMultiplePaywallUrls == true
@@ -214,6 +216,10 @@ extension SWWebView: WKNavigationDelegate {
214216
if navigationAction.navigationType == .reload {
215217
return .allow
216218
}
219+
if enableIframeNavigation,
220+
navigationAction.targetFrame?.isMainFrame == false {
221+
return .allow
222+
}
217223
return .cancel
218224
}
219225

Sources/SuperwallKit/StoreKit/Transactions/Purchasing/StoreKit 1/ProductPurchaserSK1.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ extension ProductPurchaserSK1: SKPaymentTransactionObserver {
178178
let source = await coordinator.source
179179
if let source = source {
180180
switch source {
181-
case .internal,
182-
.purchaseFunc:
181+
case .internal:
183182
if factory.makeHasExternalPurchaseController() {
184183
return
185184
}
186-
case .observeFunc:
185+
case .purchaseFunc,
186+
.observeFunc:
187187
break
188188
}
189189
} else if skTransaction.transactionState == .purchasing {

Sources/SuperwallKit/Superwall.swift

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -913,16 +913,25 @@ public final class Superwall: NSObject, ObservableObject {
913913
return dependencyContainer.deepLinkRouter.route(url: url)
914914
}
915915

916-
/// Handles a deep link sent to your app to open a preview of your paywall.
916+
/// Handles a deep link sent to your app.
917917
///
918-
/// You can preview your paywall on-device before going live by utilizing paywall previews. This uses a deep link to render a
919-
/// preview of a paywall you've configured on the Superwall dashboard on your device. See
920-
/// [In-App Previews](https://docs.superwall.com/docs/in-app-paywall-previews) for
921-
/// more.
918+
/// This method handles several types of deep links:
919+
/// - **Paywall previews**: Preview paywalls on-device before going live. See
920+
/// [In-App Previews](https://docs.superwall.com/docs/in-app-paywall-previews).
921+
/// - **Redemption codes**: Redeem web checkout codes via deep link.
922+
/// - **Superwall universal links**: Links in the format `*.superwall.app/app-link/*`.
923+
/// - **`deepLink_open` trigger**: Any deep link can trigger a paywall if you've configured
924+
/// a `deepLink_open` trigger in your Superwall dashboard.
922925
///
923-
/// - Parameters:
924-
/// - url: The URL of the deep link.
925-
/// - Returns: A `Bool` that is `true` if the deep link was handled. If called before ``Superwall/configure(apiKey:purchaseController:options:completion:)`` completes then it'll always return `true`.
926+
/// This method is designed to work in a handler chain pattern where multiple handlers
927+
/// process deep links. It returns `true` only for URLs that Superwall will handle,
928+
/// allowing other handlers to process non-Superwall URLs.
929+
///
930+
/// - Parameter url: The URL of the deep link.
931+
/// - Returns: `true` if Superwall will handle this deep link, `false` otherwise.
932+
/// When called before ``Superwall/configure(apiKey:purchaseController:options:completion:)``
933+
/// completes, returns `true` only for recognized Superwall URL formats or if cached
934+
/// config contains a `deepLink_open` trigger.
926935
@discardableResult
927936
public static func handleDeepLink(_ url: URL) -> Bool {
928937
if Superwall.isInitialized,

0 commit comments

Comments
 (0)