forked from mirrors/linphone-iphone
206 lines
8 KiB
Swift
206 lines
8 KiB
Swift
/*
|
|
* Copyright (c) 2010-2020 Belledonne Communications SARL.
|
|
*
|
|
* This file is part of linphone-iphone
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import Foundation
|
|
import Combine
|
|
import NetworkExtension
|
|
import UserNotifications
|
|
import linphonesw
|
|
import os
|
|
import linphone
|
|
|
|
let APP_GROUP_ID = "group.org.linphone.phone.msgNotification"
|
|
|
|
extension String: Error {}
|
|
|
|
class LocalPushProvider: NEAppPushProvider {
|
|
var core: Core? = nil
|
|
let log = LoggingService.Instance
|
|
var logDelegate: LinphoneLoggingServiceManager!
|
|
var coreDelegateStub : CoreDelegateStub? = nil
|
|
let defaults = UserDefaults.init(suiteName: APP_GROUP_ID)
|
|
var coreIteratorTimer:Timer? = nil
|
|
var aggretatorTimer:Timer? = nil
|
|
var aggregagor:[ChatMessage] = []
|
|
|
|
func chatRoomMuted(chatRoom:ChatRoom) -> Bool {
|
|
if let chatroomsPushStatus = defaults?.dictionary(forKey: "chatroomsPushStatus"), let from = chatRoom.peerAddress?.asStringUriOnly() {
|
|
if ((chatroomsPushStatus[from] as? String) == "disabled") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func createCore() throws {
|
|
coreDelegateStub = CoreDelegateStub(
|
|
onMessageReceived: { (core:Core, chatRoom:ChatRoom, message:ChatMessage) -> Void in
|
|
if (self.ignoredContentTypes.contains(message.contentType)) {
|
|
self.log.error(message: "Received unexpected content type.\(message.contentType)")
|
|
} else if (!self.chatRoomMuted(chatRoom: chatRoom)) {
|
|
self.aggregagor.append(message)
|
|
}
|
|
}
|
|
)
|
|
guard let configString = providerConfiguration?["coreconfig"] as? String, let config = Config.newFromBuffer(buffer: configString) else {
|
|
log.error(message: "Unable to get core config through provider configuration")
|
|
throw "Unable to get core config through provider configuration"
|
|
}
|
|
logDelegate = try LinphoneLoggingServiceManager(config: config, log: log, domain: "LocalPushProvider")
|
|
|
|
// Ensure a separate UUID from app is used, use previously generated one or a new one if n/a.
|
|
|
|
if let uuid = defaults?.string(forKey: "misc_uuid") {
|
|
config.setString(section: "misc", key: "uuid", value: uuid)
|
|
} else {
|
|
config.cleanEntry(section: "misc", key: "uuid")
|
|
}
|
|
|
|
log.message(message: "Creating LocalPushProvider core with configuration : \(config.dump())")
|
|
core = try Factory.Instance.createCoreWithConfig(config: config, systemContext: nil)
|
|
core?.autoIterateEnabled = false // 20ms auto-iterations are too frequent for NE, sometimes it gets killed by the OS. (triggers limit exceed of 150 wakeups per second over 300 seconds)
|
|
core?.addDelegate(delegate: coreDelegateStub!)
|
|
coreIteratorTimer = Timer(timeInterval: 0.1, target: self, selector: #selector(iterate), userInfo: nil, repeats: true) // 0.1 second
|
|
RunLoop.main.add(coreIteratorTimer!, forMode: .default)
|
|
|
|
let aggregateTime = config.getInt(section: "local_push", key: "notif_aggregation_period", defaultValue: 3)
|
|
aggretatorTimer = Timer(timeInterval: TimeInterval(aggregateTime), target: self, selector: #selector(flushAggregator), userInfo: nil, repeats: true) // 0.1 second
|
|
RunLoop.main.add(aggretatorTimer!, forMode: .default)
|
|
|
|
|
|
core?.accountList.forEach { account in
|
|
let params = account.params?.clone()
|
|
params?.expires = 60 // handleTimerEvent(), called by the OS, refreshes registers every 60 seconds, we don't need to expiration longer as if it's not refreshed it means the extension is not reachable.
|
|
account.params = params
|
|
}
|
|
}
|
|
|
|
@objc func iterate() {
|
|
core?.iterate()
|
|
}
|
|
|
|
@objc func flushAggregator() {
|
|
if (aggregagor.count == 1) {
|
|
showLocalNotification(message: aggregagor[0])
|
|
} else if (aggregagor.count > 1) {
|
|
var displayNames : [String] = []
|
|
aggregagor.forEach { message in
|
|
let displayName = getDisplayName(message: message)
|
|
if (!displayNames.contains(displayName)) {
|
|
displayNames.append(displayName)
|
|
}
|
|
}
|
|
displayNames.sort()
|
|
showMultipleMessagesNotifications(count: aggregagor.count,displayNames: displayNames.joined(separator: ","))
|
|
}
|
|
aggregagor.removeAll()
|
|
}
|
|
|
|
|
|
|
|
// MARK: - NEAppPushProvider Life Cycle
|
|
|
|
override func start(completionHandler: @escaping (Error?) -> Void) {
|
|
do {
|
|
if (core == nil) {
|
|
try createCore()
|
|
log.message(message: "Creating core")
|
|
}
|
|
try core?.start()
|
|
coreIteratorTimer?.fire()
|
|
aggretatorTimer?.fire()
|
|
log.message(message: "Core started")
|
|
// Keep freshly generated UUID after start to avoid re-creating one every time the NE is starting.
|
|
if (core?.config?.hasEntry(section: "misc", key: "uuid") != 0) {
|
|
defaults?.set(core?.config?.getString(section: "misc", key: "uuid", defaultString: ""), forKey: "misc_uuid")
|
|
log.message(message: "storing generated UUID \(String(describing: defaults?.string(forKey: "misc_uuid")))")
|
|
}
|
|
completionHandler(nil)
|
|
} catch {
|
|
completionHandler(error)
|
|
}
|
|
}
|
|
|
|
override func stop(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
|
log.message(message: "Received stop for reason \(reason)")
|
|
core?.stopAsync()
|
|
coreIteratorTimer?.invalidate()
|
|
aggretatorTimer?.invalidate()
|
|
flushAggregator()
|
|
completionHandler()
|
|
}
|
|
|
|
override func handleTimerEvent() {
|
|
log.message(message: "Refreshing registers (handleTimerEvent)")
|
|
core?.refreshRegisters()
|
|
}
|
|
|
|
// MARK: - Notify User
|
|
|
|
let ignoredContentTypes = ["message/imdn+xml","application/im-iscomposing+xml"]
|
|
|
|
func showMultipleMessagesNotifications(count:Int, displayNames: String) {
|
|
let content = UNMutableNotificationContent()
|
|
content.title = NSLocalizedString("%s messages received", comment: "").replacingOccurrences(of: "%s", with: String(count))
|
|
content.body = NSLocalizedString("from: %s", comment: "").replacingOccurrences(of: "%s", with: displayNames)
|
|
content.sound = UNNotificationSound(named: UNNotificationSoundName("msg.caf"))
|
|
content.categoryIdentifier = "app_active"
|
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
UNUserNotificationCenter.current().add(request) { error in
|
|
if let error = error {
|
|
self.log.error(message: "Error submitting local notification: \(error)")
|
|
return
|
|
}
|
|
self.log.message(message: "Local notification posted successfully")
|
|
}
|
|
}
|
|
|
|
func getDisplayName(message:ChatMessage) -> String {
|
|
let fromAddr = message.chatRoom?.peerAddress?.asStringUriOnly()
|
|
var displayName = fromAddr?.getDisplayNameFromSipAddress(lc: core!, logger: log, groupId: APP_GROUP_ID)
|
|
displayName = displayName != nil ? displayName : message.chatRoom?.peerAddress?.displayName
|
|
displayName = displayName != nil && displayName?.isEmpty != true ? displayName :message.chatRoom?.peerAddress?.username
|
|
return displayName!
|
|
}
|
|
|
|
func showLocalNotification(message: ChatMessage) {
|
|
|
|
var messageContent = ""
|
|
if (message.hasConferenceInvitationContent()) {
|
|
messageContent = NSLocalizedString("📅 You are invited to a meeting", comment: "")
|
|
} else {
|
|
messageContent = message.hasTextContent() ? message.utf8Text ?? "" : "🗻"
|
|
}
|
|
|
|
let content = UNMutableNotificationContent()
|
|
content.title = getDisplayName(message:message)
|
|
content.body = messageContent
|
|
content.sound = UNNotificationSound(named: UNNotificationSoundName("msg.caf"))
|
|
content.categoryIdentifier = "app_active"
|
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
UNUserNotificationCenter.current().add(request) { error in
|
|
if let error = error {
|
|
self.log.error(message: "Error submitting local notification: \(error)")
|
|
return
|
|
}
|
|
self.log.message(message: "Local notification posted successfully")
|
|
}
|
|
}
|
|
|
|
}
|