Add a wait time of up to 3 second inside the app extension, then iterate to try and receive the NOTIFY required for imdn sending

This commit is contained in:
QuentinA 2026-03-06 12:49:48 +01:00 committed by Quentin Arguillere
parent b8efad4980
commit fa9be23c2d

View file

@ -28,195 +28,201 @@ import Firebase
var LINPHONE_DUMMY_SUBJECT = "dummy subject" var LINPHONE_DUMMY_SUBJECT = "dummy subject"
let appGroupName: String = { let appGroupName: String = {
Bundle.main.object(forInfoDictionaryKey: "APP_GROUP_NAME") as? String Bundle.main.object(forInfoDictionaryKey: "APP_GROUP_NAME") as? String
?? { ?? {
fatalError("APP_GROUP_NAME not defined in Info.plist") fatalError("APP_GROUP_NAME not defined in Info.plist")
}() }()
}() }()
extension String { extension String {
func getDisplayNameFromSipAddress(lc: Core) -> String? { func getDisplayNameFromSipAddress(lc: Core) -> String? {
Log.info("looking for display name for \(self)") Log.info("looking for display name for \(self)")
let defaults = UserDefaults.init(suiteName: appGroupName) let defaults = UserDefaults.init(suiteName: appGroupName)
let addressBook = defaults?.dictionary(forKey: "addressBook") let addressBook = defaults?.dictionary(forKey: "addressBook")
if addressBook == nil { if addressBook == nil {
Log.info("address book not found in userDefaults") Log.info("address book not found in userDefaults")
return nil return nil
} }
var usePrefix = true var usePrefix = true
if let account = lc.defaultAccount, let params = account.params { if let account = lc.defaultAccount, let params = account.params {
usePrefix = params.useInternationalPrefixForCallsAndChats usePrefix = params.useInternationalPrefixForCallsAndChats
} }
if let simpleAddr = lc.interpretUrl(url: self, applyInternationalPrefix: usePrefix) { if let simpleAddr = lc.interpretUrl(url: self, applyInternationalPrefix: usePrefix) {
simpleAddr.clean() simpleAddr.clean()
let nomalSipaddr = simpleAddr.asString() let nomalSipaddr = simpleAddr.asString()
if let displayName = addressBook?[nomalSipaddr] as? String { if let displayName = addressBook?[nomalSipaddr] as? String {
Log.info("display name for \(self): \(displayName)") Log.info("display name for \(self): \(displayName)")
return displayName return displayName
} }
} }
Log.info("display name for \(self) not found in userDefaults") Log.info("display name for \(self) not found in userDefaults")
return nil return nil
} }
} }
struct MsgData: Codable { struct MsgData: Codable {
var from: String? var from: String?
var body: String? var body: String?
var subtitle: String? var subtitle: String?
var callId: String? var callId: String?
var localAddr: String? var localAddr: String?
var peerAddr: String? var peerAddr: String?
} }
class NotificationService: UNNotificationServiceExtension { class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)? var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent? var bestAttemptContent: UNMutableNotificationContent?
var lc: Core? var lc: Core?
override init() { override init() {
super.init() super.init()
#if USE_CRASHLYTICS #if USE_CRASHLYTICS
FirebaseApp.configure() FirebaseApp.configure()
#endif #endif
} }
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
let timeStart = Date.now
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
self.contentHandler = contentHandler LoggingService.Instance.logLevel = LogLevel.Debug
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) Factory.Instance.logCollectionPath = Factory.Instance.getDataDir(context: UnsafeMutablePointer<Int8>(mutating: (appGroupName as NSString).utf8String))
Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled)
LoggingService.Instance.logLevel = LogLevel.Debug Log.info("[msgNotificationService] start msgNotificationService extension")
Factory.Instance.logCollectionPath = Factory.Instance.getDataDir(context: UnsafeMutablePointer<Int8>(mutating: (appGroupName as NSString).utf8String)) /*
Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled) if (VFSUtil.vfsEnabled(groupName: AppServices.config.appGroupName) && !VFSUtil.activateVFS()) {
VFSUtil.log("[VFS] Error unable to activate.", .error)
}
*/
Log.info("[msgNotificationService] start msgNotificationService extension") if let bestAttemptContent = bestAttemptContent {
/* if let aps = request.content.userInfo["aps"] as? [String: Any], let alert = aps["alert"] as? [String: Any], let locKey = alert["loc-key"] as? String, locKey == "MWI_NOTIFY_STR" {
if (VFSUtil.vfsEnabled(groupName: AppServices.config.appGroupName) && !VFSUtil.activateVFS()) { bestAttemptContent.title = String(localized: "Voicemail")
VFSUtil.log("[VFS] Error unable to activate.", .error) bestAttemptContent.body = String(localized: "New message")
} contentHandler(bestAttemptContent)
*/ return
}
if let bestAttemptContent = bestAttemptContent { createCore()
if let aps = request.content.userInfo["aps"] as? [String: Any], let alert = aps["alert"] as? [String: Any], let locKey = alert["loc-key"] as? String, locKey == "MWI_NOTIFY_STR" { if !lc!.config!.getBool(section: "app", key: "disable_chat_feature", defaultValue: false) {
bestAttemptContent.title = String(localized: "Voicemail") Log.info("received push payload : \(bestAttemptContent.userInfo.debugDescription)")
bestAttemptContent.body = String(localized: "New message")
contentHandler(bestAttemptContent)
return
}
createCore() if let defaultAccountParams = lc?.defaultAccount?.params, defaultAccountParams.publishEnabled == true {
if !lc!.config!.getBool(section: "app", key: "disable_chat_feature", defaultValue: false) { let params = defaultAccountParams
Log.info("received push payload : \(bestAttemptContent.userInfo.debugDescription)") let clonedParams = params.clone()
clonedParams?.publishEnabled = false
lc?.defaultAccount?.params = clonedParams
}
if let defaultAccountParams = lc?.defaultAccount?.params, defaultAccountParams.publishEnabled == true { /*
let params = defaultAccountParams let defaults = UserDefaults.init(suiteName: AppServices.config.appGroupName)
let clonedParams = params.clone() if let chatroomsPushStatus = defaults?.dictionary(forKey: "chatroomsPushStatus") {
clonedParams?.publishEnabled = false let aps = bestAttemptContent.userInfo["aps"] as? NSDictionary
lc?.defaultAccount?.params = clonedParams let alert = aps?["alert"] as? NSDictionary
} let fromAddresses = alert?["loc-args"] as? [String]
/* if let from = fromAddresses?.first {
let defaults = UserDefaults.init(suiteName: AppServices.config.appGroupName) if ((chatroomsPushStatus[from] as? String) == "disabled") {
if let chatroomsPushStatus = defaults?.dictionary(forKey: "chatroomsPushStatus") { NotificationService.log.message(message: "message comes from a muted chatroom, ignore it")
let aps = bestAttemptContent.userInfo["aps"] as? NSDictionary contentHandler(UNNotificationContent())
let alert = aps?["alert"] as? NSDictionary }
let fromAddresses = alert?["loc-args"] as? [String] }
}
*/
if let chatRoomInviteAddr = bestAttemptContent.userInfo["chat-room-addr"] as? String, !chatRoomInviteAddr.isEmpty {
Log.info("fetch chat room for invite, addr: \(chatRoomInviteAddr), ignore it")
if let chatRoom = lc?.getNewChatRoomFromConfAddr(chatRoomAddr: chatRoomInviteAddr) {
Log.info("chat room invite received from: \(chatRoom.subject ?? "unknown")")
/*
bestAttemptContent.title = NSLocalizedString("GC_MSG", comment: "")
if chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) {
if chatRoom.peerAddress != nil {
if chatRoom.peerAddress!.displayName != nil && chatRoom.peerAddress!.displayName!.isEmpty != true {
bestAttemptContent.body = chatRoom.peerAddress!.displayName!
} else if chatRoom.peerAddress!.username != nil {
bestAttemptContent.body = chatRoom.peerAddress!.username!
} else {
bestAttemptContent.body = String(chatRoom.peerAddress!.asStringUriOnly().dropFirst(4))
}
} else {
bestAttemptContent.body = "Peer Address Error"
}
} else {
bestAttemptContent.body = chatRoom.subject!
}
contentHandler(bestAttemptContent)
return
*/
}
stopCore()
contentHandler(UNNotificationContent())
return
if let from = fromAddresses?.first { } else if let callId = bestAttemptContent.userInfo["call-id"] as? String {
if ((chatroomsPushStatus[from] as? String) == "disabled") { Log.info("fetch msg for callid ["+callId+"]")
NotificationService.log.message(message: "message comes from a muted chatroom, ignore it") let message = lc!.getNewMessageFromCallid(callId: callId)
contentHandler(UNNotificationContent())
}
}
}
*/
if let chatRoomInviteAddr = bestAttemptContent.userInfo["chat-room-addr"] as? String, !chatRoomInviteAddr.isEmpty {
Log.info("fetch chat room for invite, addr: \(chatRoomInviteAddr), ignore it")
if let chatRoom = lc?.getNewChatRoomFromConfAddr(chatRoomAddr: chatRoomInviteAddr) {
Log.info("chat room invite received from: \(chatRoom.subject ?? "unknown")")
/*
bestAttemptContent.title = NSLocalizedString("GC_MSG", comment: "")
if chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) {
if chatRoom.peerAddress != nil {
if chatRoom.peerAddress!.displayName != nil && chatRoom.peerAddress!.displayName!.isEmpty != true {
bestAttemptContent.body = chatRoom.peerAddress!.displayName!
} else if chatRoom.peerAddress!.username != nil {
bestAttemptContent.body = chatRoom.peerAddress!.username!
} else {
bestAttemptContent.body = String(chatRoom.peerAddress!.asStringUriOnly().dropFirst(4))
}
} else {
bestAttemptContent.body = "Peer Address Error"
}
} else {
bestAttemptContent.body = chatRoom.subject!
}
contentHandler(bestAttemptContent)
return
*/
}
stopCore()
contentHandler(UNNotificationContent())
return
} else if let callId = bestAttemptContent.userInfo["call-id"] as? String { if let message = message {
Log.info("fetch msg for callid ["+callId+"]")
let message = lc!.getNewMessageFromCallid(callId: callId)
if let message = message { let nilParams: ConferenceParams? = nil
let nilParams: ConferenceParams? = nil if let peerAddr = message.peerAddr
if let peerAddr = message.peerAddr , let chatroom = lc!.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: peerAddr, participants: nil), chatroom.muted {
, let chatroom = lc!.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: peerAddr, participants: nil), chatroom.muted { Log.info("message comes from a muted chatroom, ignore it")
Log.info("message comes from a muted chatroom, ignore it") stopCore()
stopCore() contentHandler(UNNotificationContent())
contentHandler(UNNotificationContent()) return
return }
} let msgData = parseMessage(message: message)
let msgData = parseMessage(message: message)
// Extension only upates app's badge when main shared core is Off = extension's core is On. // Extension only upates app's badge when main shared core is Off = extension's core is On.
// Otherwise, the app will update the badge. // Otherwise, the app will update the badge.
if lc?.globalState == GlobalState.On, let badge = updateBadge() as NSNumber? { if lc?.globalState == GlobalState.On, let badge = updateBadge() as NSNumber? {
bestAttemptContent.badge = badge bestAttemptContent.badge = badge
} }
stopCore() bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "msg.caf"))
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "msg.caf")) bestAttemptContent.title = String(localized: "notification_chat_message_received_title")
bestAttemptContent.title = String(localized: "notification_chat_message_received_title") if let subtitle = msgData?.subtitle {
if let subtitle = msgData?.subtitle { bestAttemptContent.subtitle = subtitle
bestAttemptContent.subtitle = subtitle }
} if let body = msgData?.body {
if let body = msgData?.body { bestAttemptContent.body = body
bestAttemptContent.body = body }
}
bestAttemptContent.categoryIdentifier = "msg_cat" bestAttemptContent.categoryIdentifier = "msg_cat"
bestAttemptContent.userInfo.updateValue(msgData?.callId as Any, forKey: "CallId") bestAttemptContent.userInfo.updateValue(msgData?.callId as Any, forKey: "CallId")
bestAttemptContent.userInfo.updateValue(msgData?.from as Any, forKey: "from") bestAttemptContent.userInfo.updateValue(msgData?.from as Any, forKey: "from")
bestAttemptContent.userInfo.updateValue(msgData?.peerAddr as Any, forKey: "peer_addr") bestAttemptContent.userInfo.updateValue(msgData?.peerAddr as Any, forKey: "peer_addr")
bestAttemptContent.userInfo.updateValue(msgData?.localAddr as Any, forKey: "local_addr") bestAttemptContent.userInfo.updateValue(msgData?.localAddr as Any, forKey: "local_addr")
if message.reactionContent != " " {
contentHandler(bestAttemptContent)
} else {
contentHandler(UNNotificationContent())
}
return // start remaining time count 25 instead of 30 to make sure we keep at least 5 seconds to finish the message processing
} else { let remainingTime = max(0, 25 - Date.now.timeIntervalSince(timeStart))
Log.info("Message not found for callid ["+callId+"]") DispatchQueue.main.asyncAfter(deadline: .now() + min(3, remainingTime)) {
self.lc?.iterate()
self.stopCore()
if message.reactionContent != " " {
contentHandler(bestAttemptContent)
} else {
contentHandler(UNNotificationContent())
}
}
return
} else {
Log.info("Message not found for callid ["+callId+"]")
stopCore() stopCore()
contentHandler(UNNotificationContent()) contentHandler(UNNotificationContent())
return return
} }
} else { } else {
stopCore() stopCore()
contentHandler(UNNotificationContent()) contentHandler(UNNotificationContent())
@ -227,112 +233,112 @@ class NotificationService: UNNotificationServiceExtension {
contentHandler(UNNotificationContent()) contentHandler(UNNotificationContent())
return return
} }
} }
} }
override func serviceExtensionTimeWillExpire() { override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system. // Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
Log.warn("serviceExtensionTimeWillExpire") Log.warn("serviceExtensionTimeWillExpire")
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
NSLog("[msgNotificationService] serviceExtensionTimeWillExpire") NSLog("[msgNotificationService] serviceExtensionTimeWillExpire")
bestAttemptContent.categoryIdentifier = "app_active" bestAttemptContent.categoryIdentifier = "app_active"
if let chatRoomInviteAddr = bestAttemptContent.userInfo["chat-room-addr"] as? String, !chatRoomInviteAddr.isEmpty { if let chatRoomInviteAddr = bestAttemptContent.userInfo["chat-room-addr"] as? String, !chatRoomInviteAddr.isEmpty {
/* /*
bestAttemptContent.title = NSLocalizedString("GC_MSG", comment: "") bestAttemptContent.title = NSLocalizedString("GC_MSG", comment: "")
bestAttemptContent.body = "" bestAttemptContent.body = ""
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName("msg.caf")) bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName("msg.caf"))
*/ */
let _ = lc?.getNewChatRoomFromConfAddr(chatRoomAddr: chatRoomInviteAddr) let _ = lc?.getNewChatRoomFromConfAddr(chatRoomAddr: chatRoomInviteAddr)
stopCore() stopCore()
contentHandler(UNNotificationContent()) contentHandler(UNNotificationContent())
return return
} else if let callId = bestAttemptContent.userInfo["call-id"] as? String { } else if let callId = bestAttemptContent.userInfo["call-id"] as? String {
stopCore() stopCore()
bestAttemptContent.title = String(localized: "notification_chat_message_received_title") bestAttemptContent.title = String(localized: "notification_chat_message_received_title")
bestAttemptContent.body = NSLocalizedString("IM_MSG", comment: "") bestAttemptContent.body = NSLocalizedString("IM_MSG", comment: "")
contentHandler(bestAttemptContent) contentHandler(bestAttemptContent)
return return
} else { } else {
stopCore() stopCore()
contentHandler(UNNotificationContent()) contentHandler(UNNotificationContent())
return return
} }
} }
} }
func parseMessage(message: PushNotificationMessage) -> MsgData? { func parseMessage(message: PushNotificationMessage) -> MsgData? {
var content = "" var content = ""
if message.isConferenceInvitationNew { if message.isConferenceInvitationNew {
content = String(localized: "message_meeting_invitation_notification") content = String(localized: "message_meeting_invitation_notification")
} else if message.isConferenceInvitationUpdate { } else if message.isConferenceInvitationUpdate {
content = String(localized: "message_meeting_invitation_updated_notification") content = String(localized: "message_meeting_invitation_updated_notification")
} else if message.isConferenceInvitationCancellation { } else if message.isConferenceInvitationCancellation {
content = String(localized: "message_meeting_invitation_cancelled_notification") content = String(localized: "message_meeting_invitation_cancelled_notification")
} else { } else {
content = message.isText ? message.textContent! : "🗻" content = message.isText ? message.textContent! : "🗻"
} }
let fromAddr = message.fromAddr?.username let fromAddr = message.fromAddr?.username
let callId = message.callId let callId = message.callId
let localUri = message.localAddr?.asStringUriOnly() let localUri = message.localAddr?.asStringUriOnly()
let peerUri = message.peerAddr?.asStringUriOnly() let peerUri = message.peerAddr?.asStringUriOnly()
let reactionContent = message.reactionContent let reactionContent = message.reactionContent
let from: String let from: String
if let fromDisplayName = message.fromAddr?.asStringUriOnly().getDisplayNameFromSipAddress(lc: lc!) { if let fromDisplayName = message.fromAddr?.asStringUriOnly().getDisplayNameFromSipAddress(lc: lc!) {
from = fromDisplayName from = fromDisplayName
} else { } else {
from = fromAddr! from = fromAddr!
} }
var msgData = MsgData(from: fromAddr, body: "", subtitle: "", callId: callId, localAddr: localUri, peerAddr: peerUri) var msgData = MsgData(from: fromAddr, body: "", subtitle: "", callId: callId, localAddr: localUri, peerAddr: peerUri)
if let showMsg = lc!.config?.getBool(section: "ui", key: "display_notification_content", defaultValue: true), showMsg == true { if let showMsg = lc!.config?.getBool(section: "ui", key: "display_notification_content", defaultValue: true), showMsg == true {
msgData.subtitle = message.subject ?? from msgData.subtitle = message.subject ?? from
if reactionContent == nil { if reactionContent == nil {
msgData.body = (message.subject != nil ? "\(from): " : "") + content msgData.body = (message.subject != nil ? "\(from): " : "") + content
} else { } else {
msgData.body = String(format: String(localized: "notification_chat_message_reaction_received"), from, reactionContent!, content) msgData.body = String(format: String(localized: "notification_chat_message_reaction_received"), from, reactionContent!, content)
} }
} else { } else {
if let subject = message.subject { if let subject = message.subject {
msgData.body = subject + ": " + from msgData.body = subject + ": " + from
} else { } else {
msgData.body = from msgData.body = from
} }
} }
Log.info("received msg size : \(content.count) \n") Log.info("received msg size : \(content.count) \n")
return msgData return msgData
} }
func createCore() { func createCore() {
Log.info("[msgNotificationService] create core") Log.info("[msgNotificationService] create core")
let factoryPath = FileUtil.bundleFilePath("linphonerc-factory")! let factoryPath = FileUtil.bundleFilePath("linphonerc-factory")!
let config = Config.newForSharedCore(appGroupId: appGroupName, configFilename: "linphonerc", factoryConfigFilename: factoryPath)! let config = Config.newForSharedCore(appGroupId: appGroupName, configFilename: "linphonerc", factoryConfigFilename: factoryPath)!
lc = try? Factory.Instance.createSharedCoreWithConfig(config: config, systemContext: nil, appGroupId: appGroupName, mainCore: false) lc = try? Factory.Instance.createSharedCoreWithConfig(config: config, systemContext: nil, appGroupId: appGroupName, mainCore: false)
} }
func stopCore() { func stopCore() {
Log.info("stop core") Log.info("stop core")
if let lc = lc { if let lc = lc {
lc.stop() lc.stop()
} }
} }
func updateBadge() -> Int { func updateBadge() -> Int {
var count = 0 var count = 0
count += lc!.unreadChatMessageCount count += lc!.unreadChatMessageCount
count += lc!.missedCallsCount count += lc!.missedCallsCount
count += lc!.callsNb count += lc!.callsNb
Log.info("badge: \(count)\n") Log.info("badge: \(count)\n")
return count return count
} }
} }