Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup

- Fixes network requests that can never succeed, such as those with an invalid API key, taking up to a minute to fail instead of failing straight away. Timeouts and server errors still retry as before.
- Fixes failed network requests being reported as a decoding error rather than the HTTP error that actually occurred.
- Prevents unused App Tracking Transparency support from triggering App Store Connect tracking warnings.

## 4.16.1

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ extension PermissionHandler {
return currentStatus
}

let status = await proxy.requestTrackingAuthorization()
let status = await proxy.requestAuthorization()
let permissionStatus = status.toTrackingPermissionStatus

return permissionStatus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,15 @@ final class TrackingManagerProxy: NSObject {
static let mangledRequestTrackingSelector =
"erdhrfgGenpxvatNhgubevmngvbaJvguPbzcyrgvbaUnaqyre:"

static var trackingManagerClass: AnyClass? {
NSClassFromString(mangledTrackingManagerClassName.rot13())
private let trackingManagerClass: AnyClass?

init(
trackingManagerClass: AnyClass? = NSClassFromString(
TrackingManagerProxy.mangledTrackingManagerClassName.rot13()
)
) {
self.trackingManagerClass = trackingManagerClass
super.init()
}

@objc var trackingStatusSelectorName: String {
Expand All @@ -43,23 +50,27 @@ final class TrackingManagerProxy: NSObject {
}

func trackingAuthorizationStatus() -> Int {
let cls: AnyClass = Self.trackingManagerClass ?? FakeTrackingManager.self
guard let trackingManagerClass else {
return FakeTrackingAuthorizationStatus.notDetermined.rawValue
}
let sel = NSSelectorFromString(trackingStatusSelectorName)

guard let imp = Self.classIMP(cls, sel) else {
guard let imp = Self.classIMP(trackingManagerClass, sel) else {
return FakeTrackingAuthorizationStatus.notDetermined.rawValue
}

typealias Function = @convention(c) (AnyObject, Selector) -> Int
let function = unsafeBitCast(imp, to: Function.self)
return function(cls as AnyObject, sel)
return function(trackingManagerClass as AnyObject, sel)
}

func requestTrackingAuthorization() async -> Int {
let cls: AnyClass = Self.trackingManagerClass ?? FakeTrackingManager.self
func requestAuthorization() async -> Int {
guard let trackingManagerClass else {
return FakeTrackingAuthorizationStatus.notDetermined.rawValue
}
let sel = NSSelectorFromString(requestTrackingSelectorName)

guard let imp = Self.classIMP(cls, sel) else {
guard let imp = Self.classIMP(trackingManagerClass, sel) else {
return FakeTrackingAuthorizationStatus.notDetermined.rawValue
}

Expand All @@ -70,7 +81,7 @@ final class TrackingManagerProxy: NSObject {

typealias Function = @convention(c) (AnyObject, Selector, AnyObject) -> Void
let function = unsafeBitCast(imp, to: Function.self)
function(cls as AnyObject, sel, completion as AnyObject)
function(trackingManagerClass as AnyObject, sel, completion as AnyObject)
}
}
}
6 changes: 5 additions & 1 deletion Sources/SuperwallKit/Permissions/PermissionHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ final class PermissionHandler: PermissionHandling {
static let contacts = "NSContactsUsageDescription"
static let locationWhenInUse = "NSLocationWhenInUseUsageDescription"
static let locationAlways = "NSLocationAlwaysAndWhenInUseUsageDescription"
static let tracking = "NSUserTrackingUsageDescription"
// ROT13("NSUserTrackingUsageDescription")
static let mangledTracking = "AFHfreGenpxvatHfntrQrfpevcgvba"
static var tracking: String {
mangledTracking.rot13()
}
static let microphone = "NSMicrophoneUsageDescription"
}

Expand Down
4 changes: 0 additions & 4 deletions SuperwallKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,6 @@
B0AD4A89AD5101360F93652D /* SubscriptionTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2ACDC7427B6340E9D86F9B0F /* SubscriptionTransaction.swift */; };
B0B0AD9409CEFE7CA8225146 /* Array+SafeRemove.swift in Sources */ = {isa = PBXBuildFile; fileRef = C855DE8F5341D67C614E3AF5 /* Array+SafeRemove.swift */; };
B0DC8290B081B74CC65E9305 /* CELEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79E9DBDDA7FEE63C15FBEAF /* CELEvaluatorTests.swift */; };
B0F7F66E24C7AEDE460F34FE /* FakeTrackingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F38AD737B1CFECEB2AAF85 /* FakeTrackingManager.swift */; };
B10030CC414C2C341487F4B8 /* PaywallViewControllerDelegateAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990461F7A9B2F3ED62B3A628 /* PaywallViewControllerDelegateAdapter.swift */; };
B146C134ABE092C3C9ACADEC /* PurchaseResult+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = A524F7AAE90E48C3B8D7E99A /* PurchaseResult+Internal.swift */; };
B15607185B9E4229C6C4F240 /* SK2StoreTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3B96E2A1A289D96267EC0BC /* SK2StoreTransaction.swift */; };
Expand Down Expand Up @@ -1110,7 +1109,6 @@
D5E2D026C30691F11D4E839F /* SurveyShowCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyShowCondition.swift; sourceTree = "<group>"; };
D6340ACDA40937ACAC66FA3D /* EntitlementPriorityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntitlementPriorityTests.swift; sourceTree = "<group>"; };
D69BCC259F5FBE15AB02D662 /* PermissionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionHandler.swift; sourceTree = "<group>"; };
D6F38AD737B1CFECEB2AAF85 /* FakeTrackingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeTrackingManager.swift; sourceTree = "<group>"; };
D7434029CB9E4680C85D3FB6 /* PermissionHandler+Microphone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PermissionHandler+Microphone.swift"; sourceTree = "<group>"; };
D7B0C7BDA06D25D9D5A865A3 /* TestModeManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestModeManagerTests.swift; sourceTree = "<group>"; };
D7E232690489360042465DB2 /* Redeemable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Redeemable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1372,7 +1370,6 @@
isa = PBXGroup;
children = (
019FA4010BA11D24C68B8544 /* FakeTrackingAuthorizationStatus.swift */,
D6F38AD737B1CFECEB2AAF85 /* FakeTrackingManager.swift */,
20DA18E503D82C213BC5567B /* PermissionsHandler+Tracking.swift */,
A78C5C57C3C92444EBAC2E38 /* TrackingManagerProxy.swift */,
);
Expand Down Expand Up @@ -3486,7 +3483,6 @@
11798EDE58E5D225E5414F2E /* FakeLocationAuthorizationStatus.swift in Sources */,
BD6ABB9DB883BC62D2407392 /* FakeLocationManager.swift in Sources */,
E9D95044254D79D2439D7B3E /* FakeTrackingAuthorizationStatus.swift in Sources */,
B0F7F66E24C7AEDE460F34FE /* FakeTrackingManager.swift in Sources */,
E0F3648081AB86077201EB5D /* FeatureFlags.swift in Sources */,
ED1C693657DA7FCBAE2DDDC6 /* FeatureGatingBehaviour.swift in Sources */,
767974DF68CE67AE2066E3D6 /* FileManagerMigrator.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,22 @@ struct TrackingManagerProxyTests {
// Should return a valid ATTrackingManager.AuthorizationStatus value (0-3)
#expect(status >= 0 && status <= 3)
}
}

// MARK: - FakeTrackingManager Tests
@Test func trackingPlistKey_isCorrectlyDecoded() {
#expect(PermissionHandler.PlistKey.tracking == "NSUserTrackingUsageDescription")
}

@Test func missingManager_trackingAuthorizationStatus_returnsNotDetermined() {
let proxy = TrackingManagerProxy(trackingManagerClass: nil)
let status = proxy.trackingAuthorizationStatus()

@Suite
struct FakeTrackingManagerTests {
@Test func trackingAuthorizationStatus_returnsNotDetermined() {
let status = FakeTrackingManager.trackingAuthorizationStatus
#expect(status == FakeTrackingAuthorizationStatus.notDetermined.rawValue)
}

@Test func requestTrackingAuthorization_callsCompletionWithNotDetermined() async {
await withCheckedContinuation { continuation in
FakeTrackingManager.requestTrackingAuthorization { status in
#expect(status == FakeTrackingAuthorizationStatus.notDetermined.rawValue)
continuation.resume()
}
}
@Test func missingManager_requestAuthorization_returnsNotDetermined() async {
let proxy = TrackingManagerProxy(trackingManagerClass: nil)
let status = await proxy.requestAuthorization()

#expect(status == FakeTrackingAuthorizationStatus.notDetermined.rawValue)
}
}