Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import UIKit
import Models
import Defaults
import Integration
import DependencyInjection
public final class PushHandler: PushHandling {
private enum Constants {
static let appGroup = "group.elixxir.messenger"
static let usernamesSetting = "isShowingUsernames"
}
@KeyObject(.pushNotifications, defaultValue: false) var isPushEnabled: Bool
let requestAuth: RequestAuth
public static let defaultRequestAuth = UNUserNotificationCenter.current().requestAuthorization
public typealias RequestAuth = (UNAuthorizationOptions, @escaping (Bool, Error?) -> Void) -> Void
public var pushExtractor: PushExtractor
public var contentsBuilder: ContentsBuilder
public var applicationState: () -> UIApplication.State
public init(
requestAuth: @escaping RequestAuth = defaultRequestAuth,
pushExtractor: PushExtractor = .live,
contentsBuilder: ContentsBuilder = .live,
applicationState: @escaping () -> UIApplication.State = { UIApplication.shared.applicationState }
) {
self.requestAuth = requestAuth
self.pushExtractor = pushExtractor
self.contentsBuilder = contentsBuilder
self.applicationState = applicationState
}
public func registerToken(_ token: Data) {
do {
let session = try DependencyInjection.Container.shared.resolve() as SessionType
try session.registerNotifications(token)
} catch {
isPushEnabled = false
}
}
public func requestAuthorization(
_ completion: @escaping (Result<Bool, Error>) -> Void
) {
let options: UNAuthorizationOptions = [.alert, .sound, .badge]
requestAuth(options) { granted, error in
guard let error = error else {
completion(.success(granted))
return
}
completion(.failure(error))
}
}
public func handlePush(
_ userInfo: [AnyHashable: Any],
_ completion: @escaping (UIBackgroundFetchResult) -> Void
) {
do {
guard
let pushes = try pushExtractor.extractFrom(userInfo).get(),
applicationState() == .background,
pushes.isEmpty == false
else {
completion(.noData)
return
}
let content = contentsBuilder.build("New Messages Available", pushes.first!)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(identifier: Bundle.main.bundleIdentifier!, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if error == nil {
completion(.newData)
} else {
completion(.failed)
}
}
} catch {
completion(.failed)
}
}
public func handlePush(
_ request: UNNotificationRequest,
_ completion: @escaping (UNNotificationContent) -> Void
) {
guard let pushes = try? pushExtractor.extractFrom(request.content.userInfo).get(), !pushes.isEmpty,
let defaults = UserDefaults(suiteName: Constants.appGroup) else {
return
}
guard let showSender = defaults.value(forKey: Constants.usernamesSetting) as? Bool, showSender == true else {
pushes.map { ($0.type.unknownSenderContent!, $0) }
.map(contentsBuilder.build)
.forEach { completion($0) }
return
}
let dbManager = GRDBDatabaseManager()
try? dbManager.setup()
let tuples: [(String, Push)] = pushes.compactMap {
guard let userId = $0.source, let contact: Contact = try? dbManager.fetch(.withUserId(userId)).first else {
return ($0.type.unknownSenderContent!, $0)
}
let name = contact.nickname ?? contact.username
return ($0.type.knownSenderContent(name)!, $0)
}
tuples
.map(contentsBuilder.build)
.forEach { completion($0) }
}
public func handleAction(
_ router: PushRouter,
_ userInfo: [AnyHashable : Any],
_ completion: @escaping () -> Void
) {
guard let typeString = userInfo["type"] as? String,
let type = PushType(rawValue: typeString) else {
completion()
return
}
let route: PushRouter.Route
switch type {
case .e2e:
guard let source = userInfo["source"] as? Data else {
completion()
return
}
route = .contactChat(id: source)
case .group:
guard let source = userInfo["source"] as? Data else {
completion()
return
}
route = .groupChat(id: source)
case .request, .groupRq:
route = .requests
case .silent, .`default`:
fatalError("Silent/Default push types should be filtered at this point")
case .reset, .endFT, .confirm:
route = .requests
}
router.navigateTo(route, completion)
}
}