From 154ae891fa36847e5efdfc32df525b333b32ced2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:51:07 +0100 Subject: [PATCH 1/3] Add in microphone permission request --- ...AuthorizationStatus+PermissionStatus.swift | 14 ++++++++ .../PermissionHandler+Microphone.swift | 36 +++++++++++++++++++ .../Permissions/PermissionHandler.swift | 5 +++ .../Permissions/PermissionType.swift | 1 + 4 files changed, 56 insertions(+) create mode 100644 Sources/SuperwallKit/Permissions/Handlers/PermissionHandler+Microphone.swift diff --git a/Sources/SuperwallKit/Permissions/AuthorizationStatus+PermissionStatus.swift b/Sources/SuperwallKit/Permissions/AuthorizationStatus+PermissionStatus.swift index 2b24685a95..b86eb11a46 100644 --- a/Sources/SuperwallKit/Permissions/AuthorizationStatus+PermissionStatus.swift +++ b/Sources/SuperwallKit/Permissions/AuthorizationStatus+PermissionStatus.swift @@ -55,6 +55,20 @@ extension AVAuthorizationStatus { } } +extension AVAudioSession.RecordPermission { + var toPermissionStatus: PermissionStatus { + switch self { + case .granted: + return .granted + case .denied, + .undetermined: + return .denied + @unknown default: + return .unsupported + } + } +} + extension Int { var toContactsPermissionStatus: PermissionStatus { // CNAuthorizationStatus: diff --git a/Sources/SuperwallKit/Permissions/Handlers/PermissionHandler+Microphone.swift b/Sources/SuperwallKit/Permissions/Handlers/PermissionHandler+Microphone.swift new file mode 100644 index 0000000000..f09c396633 --- /dev/null +++ b/Sources/SuperwallKit/Permissions/Handlers/PermissionHandler+Microphone.swift @@ -0,0 +1,36 @@ +// +// PermissionHandler+Microphone.swift +// SuperwallKit +// +// Created by Yusuf Tör on 13/01/2026. +// + +import AVFoundation + +extension PermissionHandler { + func checkMicrophonePermission() -> PermissionStatus { + return AVAudioSession.sharedInstance().recordPermission.toPermissionStatus + } + + @MainActor + func requestMicrophonePermission() async -> PermissionStatus { + guard hasPlistKey(PlistKey.microphone) else { + await showMissingPlistKeyAlert( + for: PlistKey.microphone, + permissionName: "Microphone" + ) + return .unsupported + } + + let currentStatus = checkMicrophonePermission() + if currentStatus == .granted { + return .granted + } + + return await withCheckedContinuation { continuation in + AVAudioSession.sharedInstance().requestRecordPermission { granted in + continuation.resume(returning: granted ? .granted : .denied) + } + } + } +} diff --git a/Sources/SuperwallKit/Permissions/PermissionHandler.swift b/Sources/SuperwallKit/Permissions/PermissionHandler.swift index c9448cf03a..9675457978 100644 --- a/Sources/SuperwallKit/Permissions/PermissionHandler.swift +++ b/Sources/SuperwallKit/Permissions/PermissionHandler.swift @@ -21,6 +21,7 @@ final class PermissionHandler: PermissionHandling { static let locationWhenInUse = "NSLocationWhenInUseUsageDescription" static let locationAlways = "NSLocationAlwaysAndWhenInUseUsageDescription" static let tracking = "NSUserTrackingUsageDescription" + static let microphone = "NSMicrophoneUsageDescription" } func hasPlistKey(_ key: String) -> Bool { @@ -69,6 +70,8 @@ final class PermissionHandler: PermissionHandling { return checkCameraPermission() case .tracking: return checkTrackingPermission() + case .microphone: + return checkMicrophonePermission() } } @@ -88,6 +91,8 @@ final class PermissionHandler: PermissionHandling { return await requestCameraPermission() case .tracking: return await requestTrackingPermission() + case .microphone: + return await requestMicrophonePermission() } } } diff --git a/Sources/SuperwallKit/Permissions/PermissionType.swift b/Sources/SuperwallKit/Permissions/PermissionType.swift index c711d70abd..40292a2d7a 100644 --- a/Sources/SuperwallKit/Permissions/PermissionType.swift +++ b/Sources/SuperwallKit/Permissions/PermissionType.swift @@ -17,4 +17,5 @@ enum PermissionType: String, Decodable { case contacts case camera case tracking + case microphone } From ca4d6eaf4f155afb623fccb697ee84ada69a8734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Tue, 13 Jan 2026 13:54:24 +0100 Subject: [PATCH 2/3] Updated version and changelog, add tests --- CHANGELOG.md | 6 +++ CLAUDE.md | 7 ++- Sources/SuperwallKit/Misc/Constants.swift | 2 +- SuperwallKit.podspec | 2 +- .../MicrophonePermissionTests.swift | 47 +++++++++++++++++++ 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 Tests/SuperwallKitTests/Permissions/MicrophonePermissionTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a175ef7dd..f80b5cf1d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/superwall/Superwall-iOS/releases) on GitHub. +## 4.12.5 + +### Enhancements + +- Adds microphone permission request support. + ## 4.12.4 ### Enhancements diff --git a/CLAUDE.md b/CLAUDE.md index 2af473f8f1..e49dc9f9be 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,8 +66,11 @@ SuperwallKit is an iOS SDK for remote paywall configuration and A/B testing. The ### Version Management -- Version is defined in `Sources/SuperwallKit/Misc/Constants.swift` line 21 -- Pre-commit hook automatically syncs version to `SuperwallKit.podspec` +When bumping the version, update all three files: +1. `Sources/SuperwallKit/Misc/Constants.swift` (line 21) +2. `SuperwallKit.podspec` (s.version) +3. `CHANGELOG.md` (add new version entry at top) + - Follows semantic versioning ### Testing diff --git a/Sources/SuperwallKit/Misc/Constants.swift b/Sources/SuperwallKit/Misc/Constants.swift index 53538fb2f4..f295fd5f51 100644 --- a/Sources/SuperwallKit/Misc/Constants.swift +++ b/Sources/SuperwallKit/Misc/Constants.swift @@ -18,5 +18,5 @@ let sdkVersion = """ */ let sdkVersion = """ -4.12.4 +4.12.5 """ diff --git a/SuperwallKit.podspec b/SuperwallKit.podspec index 9d2b31bad0..2c21a5eb16 100644 --- a/SuperwallKit.podspec +++ b/SuperwallKit.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SuperwallKit" - s.version = "4.12.4" + s.version = "4.12.5" s.summary = "Superwall: In-App Paywalls Made Easy" 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" diff --git a/Tests/SuperwallKitTests/Permissions/MicrophonePermissionTests.swift b/Tests/SuperwallKitTests/Permissions/MicrophonePermissionTests.swift new file mode 100644 index 0000000000..8ce767ce4d --- /dev/null +++ b/Tests/SuperwallKitTests/Permissions/MicrophonePermissionTests.swift @@ -0,0 +1,47 @@ +// +// MicrophonePermissionTests.swift +// SuperwallKitTests +// +// Created by Yusuf Tör on 13/01/2026. +// + +import AVFoundation +import Foundation +import Testing +@testable import SuperwallKit + +@Suite +struct MicrophonePermissionConversionTests { + @Test func toPermissionStatus_granted_returnsGranted() { + let status = AVAudioSession.RecordPermission.granted + #expect(status.toPermissionStatus == .granted) + } + + @Test func toPermissionStatus_denied_returnsDenied() { + let status = AVAudioSession.RecordPermission.denied + #expect(status.toPermissionStatus == .denied) + } + + @Test func toPermissionStatus_undetermined_returnsDenied() { + let status = AVAudioSession.RecordPermission.undetermined + #expect(status.toPermissionStatus == .denied) + } +} + +@Suite +struct PermissionTypeMicrophoneTests { + @Test func microphoneCase_exists() { + let permission = PermissionType.microphone + #expect(permission.rawValue == "microphone") + } + + @Test func microphone_isDecodable() throws { + let json = """ + "microphone" + """.data(using: .utf8)! + + let decoder = JSONDecoder() + let result = try decoder.decode(PermissionType.self, from: json) + #expect(result == .microphone) + } +} From defb07ab700dd1e756f74a733bcfe4a467915a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20To=CC=88r?= <3296904+yusuftor@users.noreply.github.com> Date: Thu, 15 Jan 2026 21:05:52 +0100 Subject: [PATCH 3/3] Makes provisional notifications count as unauthorised --- CHANGELOG.md | 4 ++++ .../Transactions/Notifications/NotificationScheduler.swift | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f80b5cf1d4..9b05d89bec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ The changelog for `SuperwallKit`. Also see the [releases](https://github.com/sup - Adds microphone permission request support. +### Fixes + +- Fixes issue where the notification permission prompt would not appear if provisional notification permission was already granted. + ## 4.12.4 ### Enhancements diff --git a/Sources/SuperwallKit/StoreKit/Transactions/Notifications/NotificationScheduler.swift b/Sources/SuperwallKit/StoreKit/Transactions/Notifications/NotificationScheduler.swift index 5868d3b57f..dfccb17c33 100644 --- a/Sources/SuperwallKit/StoreKit/Transactions/Notifications/NotificationScheduler.swift +++ b/Sources/SuperwallKit/StoreKit/Transactions/Notifications/NotificationScheduler.swift @@ -164,8 +164,7 @@ actor NotificationScheduler { let settings = await notificationCenter.notificationSettings() switch settings.authorizationStatus { case .authorized, - .ephemeral, - .provisional: + .ephemeral: return true default: return false