diff --git a/packages/react-native-callingx/ios/CallingxImpl.swift b/packages/react-native-callingx/ios/CallingxImpl.swift index f372e4a154..51c7100c90 100644 --- a/packages/react-native-callingx/ios/CallingxImpl.swift +++ b/packages/react-native-callingx/ios/CallingxImpl.swift @@ -108,6 +108,8 @@ import stream_react_native_webrtc if CallingxImpl.sharedProvider == nil { CallingxImpl.sharedProvider = CXProvider(configuration: Settings.getProviderConfiguration()) } + + callKeepCallController = CXCallController() callKeepProvider = CallingxImpl.sharedProvider callKeepProvider?.setDelegate(nil, queue: nil) @@ -128,7 +130,8 @@ import stream_react_native_webrtc // MARK: - Class Methods @objc public static func initializeIfNeeded() { - _ = getSharedInstance() // ensures the shared instance is created and CXProvider delegate is set + _ = getSharedInstance() // ensures the shared instance is created and CXProvider delegate is set + CallingxImpl.sharedProvider?.configuration = Settings.getProviderConfiguration() } @objc public static func reportNewIncomingCall( @@ -379,29 +382,8 @@ import stream_react_native_webrtc // MARK: - Setup Methods @objc public func setup(options: [String: Any]) { - callKeepCallController = CXCallController() - Settings.setSettings(options) - - // This is mostly needed for very first setup, as we need to override the default - // provider configuration which is set in the constructor. - // IMPORTANT: We override the CXProvider instance only when there are no calls in - // flight, otherwise we'd destroy CallKit state/events for a live call. We check our - // own storage rather than CXCallObserver here: the observer trails registration and - // can briefly report empty (e.g. a VoIP-push call reported just before setup runs), - // which would wrongly tear down the provider mid-call. - if (CallingxImpl.uuidStorage?.count() ?? 0) == 0 { - let oldProvider = CallingxImpl.sharedProvider - let newProvider = CXProvider(configuration: Settings.getProviderConfiguration()) - newProvider.setDelegate(self, queue: nil) - - CallingxImpl.sharedProvider = newProvider - callKeepProvider = newProvider - - oldProvider?.setDelegate(nil, queue: nil) - oldProvider?.invalidate() - } - + CallingxImpl.sharedProvider?.configuration = Settings.getProviderConfiguration() isSetup = true } @@ -615,10 +597,13 @@ import stream_react_native_webrtc return } + CallingxImpl.sharedProvider?.configuration = Settings.getProviderConfiguration() + let call = storage.getOrCreateCall(forCid: callId, isOutgoing: true) call.markStartedConnecting() // outgoing: will be reported via reportOutgoingCall(startedConnectingAt:) - let handleType = Settings.getHandleType("generic") + let handleTypeString = Settings.getSettings()["handleType"] as? String + let handleType = Settings.getHandleType(handleTypeString ?? "generic") let callHandle = CXHandle(type: handleType, value: phoneNumber) let startCallAction = CXStartCallAction(call: call.uuid, handle: callHandle) startCallAction.isVideo = hasVideo diff --git a/packages/react-native-callingx/ios/Settings.swift b/packages/react-native-callingx/ios/Settings.swift index bcaae3ae8b..d770bad276 100644 --- a/packages/react-native-callingx/ios/Settings.swift +++ b/packages/react-native-callingx/ios/Settings.swift @@ -5,23 +5,36 @@ import UIKit @objcMembers public class Settings: NSObject { private static let settingsKey = "CallingxSettings" + // In-memory cache of the settings dictionary + private static var cachedSettings: [String: Any]? + private static let cacheQueue = DispatchQueue(label: "io.getstream.callingx.settings") + public static func getSettings() -> [String: Any] { - return UserDefaults.standard.dictionary(forKey: settingsKey) ?? [:] + return cacheQueue.sync { + if let cached = cachedSettings { + return cached + } + cachedSettings = UserDefaults.standard.dictionary(forKey: settingsKey) ?? [:] + return cachedSettings! + } } public static func setSettings(_ options: [String: Any]?) { CallingxLog.settings.debugPublic("[setSettings] options = \(String(describing: options))") - var settings: [String: Any] = getSettings() + cacheQueue.sync { + // Load lazily here rather than via getSettings() to avoid re-entrant sync on cacheQueue. + var settings = cachedSettings ?? UserDefaults.standard.dictionary(forKey: settingsKey) ?? [:] - if let options = options { - for (key, value) in options { - settings[key] = value + if let options = options { + for (key, value) in options { + settings[key] = value + } } - } - UserDefaults.standard.set(settings, forKey: settingsKey) - UserDefaults.standard.synchronize() + cachedSettings = settings + UserDefaults.standard.set(settings, forKey: settingsKey) + } } public static func getShouldRejectCallWhenBusy() -> Bool {