// _____ _
// |_ _| |_ _ _ ___ ___ _ __ __ _
// | | | ' \| '_/ -_) -_) ' \/ _` |_
// |_| |_||_|_| \___\___|_|_|_\__,_(_)
//
// Threema iOS Client
// Copyright (c) 2020 Threema GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License, version 3,
// as published by the Free Software Foundation.
//
// 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
import Foundation
/// Errors for `ThreemaPushNotification` parsing
enum ThreemaPushNotificationError: Error, Equatable {
case unknownCommand(String)
case keyNotFoundOrTypeMissmatch(ThreemaPushNotificationDictionary)
}
/// Represents the payload in a push notifcation keyed with "threema"
public class ThreemaPushNotification: NSObject {
/// A command that is part of of the push notification payload
enum Command: String, Codable {
/// A new message in a chat with a single person
case newMessage = "newmsg"
/// A new message in a group chat
case newGroupMessage = "newgroupmsg"
/// Creates a new instance depending on the string
/// - Parameter string: String representing a command
/// - Throws: `ThreemaPushNotificationError` if command string is unknown
init(from string: String) throws {
switch string {
case ThreemaPushNotificationDictionary.Command.newMessage.rawValue:
self = .newMessage
case ThreemaPushNotificationDictionary.Command.newGroupMessage.rawValue:
self = .newGroupMessage
default:
throw ThreemaPushNotificationError.unknownCommand(string)
}
}
}
let command: Command
/// Message sender
///
/// Needed to open the correct chat
let from: String
/// Nickname set by sender (for themself)
let nickname: String?
let messageId: String
/// Indicates if the push notification is related to an incoming voip call
let voip: Bool?
/// Parse an incoming push payload dictionary
/// - Parameter dictionary: Dictionary to parse
/// - Throws: `ThreemaPushNotificationError` if a required key is missing or a value cannot be parsed
init(from dictionary: [String: Any]) throws {
let commandString = try ThreemaPushNotification.decode(String.self, forKey: .commandKey, in: dictionary)
command = try Command(from: commandString)
from = try ThreemaPushNotification.decode(String.self, forKey: .fromKey, in: dictionary)
nickname = try? ThreemaPushNotification.decode(String.self, forKey: .nicknameKey, in: dictionary)
messageId = try ThreemaPushNotification.decode(String.self, forKey: .messageIdKey, in: dictionary)
// For backwards compatiblity the voip key is also a string,
// but in the future it could be a bool
if let voipString = try? ThreemaPushNotification.decode(String.self, forKey: .voipKey, in: dictionary) {
if voipString == ThreemaPushNotificationDictionary.Bool.true.rawValue {
voip = true
} else if voipString == ThreemaPushNotificationDictionary.Bool.false.rawValue {
voip = false
} else {
voip = nil
}
} else {
voip = try? ThreemaPushNotification.decode(Bool.self, forKey: .voipKey, in: dictionary)
}
}
private static func decode(_ type: T.Type, forKey key: ThreemaPushNotificationDictionary, in dictionary: [String: Any]) throws -> T {
if let decodedValue = dictionary[key.rawValue] as? T {
return decodedValue
} else {
throw ThreemaPushNotificationError.keyNotFoundOrTypeMissmatch(key)
}
}
/// Initalizer for `NSCoding`
/// - Parameter coder: Coder to decode from
public required init?(coder: NSCoder) {
guard let commandString = coder.decodeObject(forKey: ThreemaPushNotificationDictionary.commandKey.rawValue) as? String,
let command = Command(rawValue: commandString) else {
return nil
}
self.command = command
guard let from = coder.decodeObject(forKey: ThreemaPushNotificationDictionary.fromKey.rawValue) as? String else {
return nil
}
self.from = from
self.nickname = coder.decodeObject(forKey: ThreemaPushNotificationDictionary.nicknameKey.rawValue) as? String
guard let messageId = coder.decodeObject(forKey: ThreemaPushNotificationDictionary.messageIdKey.rawValue) as? String else {
return nil
}
self.messageId = messageId
self.voip = coder.decodeObject(forKey: ThreemaPushNotificationDictionary.voipKey.rawValue) as? Bool
}
}
// MARK: - NSCoding
extension ThreemaPushNotification: NSCoding {
// We still need to use NSCoding (instead of Codable), because there are dependencies
// used in `PendingMessage` that are in Obj-C.
// When we can remove this, `ThreemaPushNotification` can become a struct.
public func encode(with coder: NSCoder) {
coder.encode(command.rawValue, forKey: ThreemaPushNotificationDictionary.commandKey.rawValue)
coder.encode(from, forKey: ThreemaPushNotificationDictionary.fromKey.rawValue)
coder.encode(nickname, forKey: ThreemaPushNotificationDictionary.nicknameKey.rawValue)
coder.encode(messageId, forKey: ThreemaPushNotificationDictionary.messageIdKey.rawValue)
coder.encode(voip, forKey: ThreemaPushNotificationDictionary.voipKey.rawValue)
}
}