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 self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
LoggingService.Instance.logLevel = LogLevel.Debug
Factory.Instance.logCollectionPath = Factory.Instance.getDataDir(context: UnsafeMutablePointer<Int8>(mutating: (appGroupName as NSString).utf8String))
Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled)
Log.info("[msgNotificationService] start msgNotificationService extension")
/*
if (VFSUtil.vfsEnabled(groupName: AppServices.config.appGroupName) && !VFSUtil.activateVFS()) {
VFSUtil.log("[VFS] Error unable to activate.", .error)
}
*/
if let bestAttemptContent = bestAttemptContent { LoggingService.Instance.logLevel = LogLevel.Debug
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" { Factory.Instance.logCollectionPath = Factory.Instance.getDataDir(context: UnsafeMutablePointer<Int8>(mutating: (appGroupName as NSString).utf8String))
bestAttemptContent.title = String(localized: "Voicemail") Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled)
bestAttemptContent.body = String(localized: "New message")
contentHandler(bestAttemptContent) Log.info("[msgNotificationService] start msgNotificationService extension")
return /*
} if (VFSUtil.vfsEnabled(groupName: AppServices.config.appGroupName) && !VFSUtil.activateVFS()) {
VFSUtil.log("[VFS] Error unable to activate.", .error)
createCore() }
if !lc!.config!.getBool(section: "app", key: "disable_chat_feature", defaultValue: false) { */
Log.info("received push payload : \(bestAttemptContent.userInfo.debugDescription)")
if let bestAttemptContent = bestAttemptContent {
if let defaultAccountParams = lc?.defaultAccount?.params, defaultAccountParams.publishEnabled == true { 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" {
let params = defaultAccountParams bestAttemptContent.title = String(localized: "Voicemail")
let clonedParams = params.clone() bestAttemptContent.body = String(localized: "New message")
clonedParams?.publishEnabled = false contentHandler(bestAttemptContent)
lc?.defaultAccount?.params = clonedParams return
} }
/* createCore()
let defaults = UserDefaults.init(suiteName: AppServices.config.appGroupName) if !lc!.config!.getBool(section: "app", key: "disable_chat_feature", defaultValue: false) {
if let chatroomsPushStatus = defaults?.dictionary(forKey: "chatroomsPushStatus") { Log.info("received push payload : \(bestAttemptContent.userInfo.debugDescription)")
let aps = bestAttemptContent.userInfo["aps"] as? NSDictionary
let alert = aps?["alert"] as? NSDictionary if let defaultAccountParams = lc?.defaultAccount?.params, defaultAccountParams.publishEnabled == true {
let fromAddresses = alert?["loc-args"] as? [String] let params = defaultAccountParams
let clonedParams = params.clone()
if let from = fromAddresses?.first { clonedParams?.publishEnabled = false
if ((chatroomsPushStatus[from] as? String) == "disabled") { lc?.defaultAccount?.params = clonedParams
NotificationService.log.message(message: "message comes from a muted chatroom, ignore it") }
contentHandler(UNNotificationContent())
} /*
} let defaults = UserDefaults.init(suiteName: AppServices.config.appGroupName)
} if let chatroomsPushStatus = defaults?.dictionary(forKey: "chatroomsPushStatus") {
*/ let aps = bestAttemptContent.userInfo["aps"] as? NSDictionary
if let chatRoomInviteAddr = bestAttemptContent.userInfo["chat-room-addr"] as? String, !chatRoomInviteAddr.isEmpty { let alert = aps?["alert"] as? NSDictionary
Log.info("fetch chat room for invite, addr: \(chatRoomInviteAddr), ignore it") let fromAddresses = alert?["loc-args"] as? [String]
if let chatRoom = lc?.getNewChatRoomFromConfAddr(chatRoomAddr: chatRoomInviteAddr) {
Log.info("chat room invite received from: \(chatRoom.subject ?? "unknown")") if let from = fromAddresses?.first {
/* if ((chatroomsPushStatus[from] as? String) == "disabled") {
bestAttemptContent.title = NSLocalizedString("GC_MSG", comment: "") NotificationService.log.message(message: "message comes from a muted chatroom, ignore it")
if chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) { contentHandler(UNNotificationContent())
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! if let chatRoomInviteAddr = bestAttemptContent.userInfo["chat-room-addr"] as? String, !chatRoomInviteAddr.isEmpty {
} else { Log.info("fetch chat room for invite, addr: \(chatRoomInviteAddr), ignore it")
bestAttemptContent.body = String(chatRoom.peerAddress!.asStringUriOnly().dropFirst(4)) if let chatRoom = lc?.getNewChatRoomFromConfAddr(chatRoomAddr: chatRoomInviteAddr) {
} Log.info("chat room invite received from: \(chatRoom.subject ?? "unknown")")
} else { /*
bestAttemptContent.body = "Peer Address Error" bestAttemptContent.title = NSLocalizedString("GC_MSG", comment: "")
} if chatRoom.hasCapability(mask: ChatRoom.Capabilities.OneToOne.rawValue) {
} else { if chatRoom.peerAddress != nil {
bestAttemptContent.body = chatRoom.subject! if chatRoom.peerAddress!.displayName != nil && chatRoom.peerAddress!.displayName!.isEmpty != true {
} bestAttemptContent.body = chatRoom.peerAddress!.displayName!
contentHandler(bestAttemptContent) } else if chatRoom.peerAddress!.username != nil {
return bestAttemptContent.body = chatRoom.peerAddress!.username!
*/ } else {
} bestAttemptContent.body = String(chatRoom.peerAddress!.asStringUriOnly().dropFirst(4))
stopCore() }
contentHandler(UNNotificationContent()) } else {
return bestAttemptContent.body = "Peer Address Error"
}
} else if let callId = bestAttemptContent.userInfo["call-id"] as? String { } else {
Log.info("fetch msg for callid ["+callId+"]") bestAttemptContent.body = chatRoom.subject!
let message = lc!.getNewMessageFromCallid(callId: callId) }
contentHandler(bestAttemptContent)
if let message = message { return
let nilParams: ConferenceParams? = nil */
if let peerAddr = message.peerAddr }
, let chatroom = lc!.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: peerAddr, participants: nil), chatroom.muted { stopCore()
Log.info("message comes from a muted chatroom, ignore it") contentHandler(UNNotificationContent())
stopCore() return
contentHandler(UNNotificationContent())
return } else if let callId = bestAttemptContent.userInfo["call-id"] as? String {
} Log.info("fetch msg for callid ["+callId+"]")
let msgData = parseMessage(message: message) let message = lc!.getNewMessageFromCallid(callId: callId)
// Extension only upates app's badge when main shared core is Off = extension's core is On. if let message = message {
// Otherwise, the app will update the badge.
if lc?.globalState == GlobalState.On, let badge = updateBadge() as NSNumber? { let nilParams: ConferenceParams? = nil
bestAttemptContent.badge = badge if let peerAddr = message.peerAddr
} , let chatroom = lc!.searchChatRoom(params: nilParams, localAddr: nil, remoteAddr: peerAddr, participants: nil), chatroom.muted {
Log.info("message comes from a muted chatroom, ignore it")
stopCore() stopCore()
bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "msg.caf")) contentHandler(UNNotificationContent())
bestAttemptContent.title = String(localized: "notification_chat_message_received_title") return
if let subtitle = msgData?.subtitle { }
bestAttemptContent.subtitle = subtitle let msgData = parseMessage(message: message)
}
if let body = msgData?.body { // Extension only upates app's badge when main shared core is Off = extension's core is On.
bestAttemptContent.body = body // Otherwise, the app will update the badge.
} if lc?.globalState == GlobalState.On, let badge = updateBadge() as NSNumber? {
bestAttemptContent.badge = badge
bestAttemptContent.categoryIdentifier = "msg_cat" }
bestAttemptContent.userInfo.updateValue(msgData?.callId as Any, forKey: "CallId") bestAttemptContent.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "msg.caf"))
bestAttemptContent.userInfo.updateValue(msgData?.from as Any, forKey: "from") bestAttemptContent.title = String(localized: "notification_chat_message_received_title")
bestAttemptContent.userInfo.updateValue(msgData?.peerAddr as Any, forKey: "peer_addr") if let subtitle = msgData?.subtitle {
bestAttemptContent.userInfo.updateValue(msgData?.localAddr as Any, forKey: "local_addr") bestAttemptContent.subtitle = subtitle
if message.reactionContent != " " { }
contentHandler(bestAttemptContent) if let body = msgData?.body {
} else { bestAttemptContent.body = body
contentHandler(UNNotificationContent()) }
}
bestAttemptContent.categoryIdentifier = "msg_cat"
return
} else { bestAttemptContent.userInfo.updateValue(msgData?.callId as Any, forKey: "CallId")
Log.info("Message not found for callid ["+callId+"]") bestAttemptContent.userInfo.updateValue(msgData?.from as Any, forKey: "from")
bestAttemptContent.userInfo.updateValue(msgData?.peerAddr as Any, forKey: "peer_addr")
bestAttemptContent.userInfo.updateValue(msgData?.localAddr as Any, forKey: "local_addr")
// start remaining time count 25 instead of 30 to make sure we keep at least 5 seconds to finish the message processing
let remainingTime = max(0, 25 - Date.now.timeIntervalSince(timeStart))
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,113 +233,113 @@ 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
} }
} }
// swiftlint:enable identifier_name // swiftlint:enable identifier_name