Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Sources/SuperwallKit/Misc/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ let sdkVersion = """
*/

let sdkVersion = """
4.12.4
4.12.5
"""
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
5 changes: 5 additions & 0 deletions Sources/SuperwallKit/Permissions/PermissionHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -69,6 +70,8 @@ final class PermissionHandler: PermissionHandling {
return checkCameraPermission()
case .tracking:
return checkTrackingPermission()
case .microphone:
return checkMicrophonePermission()
}
}

Expand All @@ -88,6 +91,8 @@ final class PermissionHandler: PermissionHandling {
return await requestCameraPermission()
case .tracking:
return await requestTrackingPermission()
case .microphone:
return await requestMicrophonePermission()
}
}
}
1 change: 1 addition & 0 deletions Sources/SuperwallKit/Permissions/PermissionType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ enum PermissionType: String, Decodable {
case contacts
case camera
case tracking
case microphone
}
2 changes: 1 addition & 1 deletion SuperwallKit.podspec
Original file line number Diff line number Diff line change
@@ -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"

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}