react-native-qpush 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +439 -2
- package/android/build.gradle +77 -0
- package/android/gradle.properties +5 -0
- package/android/proguard-rules.pro +5 -0
- package/android/src/main/AndroidManifest.xml +29 -0
- package/android/src/main/java/com/qpush/reactnative/QPushDeviceInfo.kt +96 -0
- package/android/src/main/java/com/qpush/reactnative/QPushEventBus.kt +153 -0
- package/android/src/main/java/com/qpush/reactnative/QPushFcm.kt +108 -0
- package/android/src/main/java/com/qpush/reactnative/QPushHttp.kt +92 -0
- package/android/src/main/java/com/qpush/reactnative/QPushImages.kt +103 -0
- package/android/src/main/java/com/qpush/reactnative/QPushLog.kt +39 -0
- package/android/src/main/java/com/qpush/reactnative/QPushMessagingService.kt +77 -0
- package/android/src/main/java/com/qpush/reactnative/QPushNotificationOpenActivity.kt +121 -0
- package/android/src/main/java/com/qpush/reactnative/QPushNotificationRenderer.kt +256 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPayload.kt +114 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPushModule.kt +368 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPushPackage.kt +15 -0
- package/android/src/main/java/com/qpush/reactnative/QPushStore.kt +108 -0
- package/android/src/main/res/values/qpush_strings.xml +6 -0
- package/app.plugin.js +1 -0
- package/ios/Extension/QPushNotificationServiceExtension.swift +261 -0
- package/ios/Extension/QPushPushExtension.podspec +34 -0
- package/ios/QPushPush.m +62 -0
- package/ios/QPushPush.swift +186 -0
- package/ios/QPushPushBridge.h +5 -0
- package/ios/QPushPushCore.swift +457 -0
- package/ios/QPushPushHooks.m +237 -0
- package/ios/QPushPushSupport.swift +249 -0
- package/ios_nse_template/Info.plist +38 -0
- package/ios_nse_template/NotificationService.entitlements +10 -0
- package/ios_nse_template/NotificationService.swift +44 -0
- package/lib/NativeQPushPush.d.ts +44 -0
- package/lib/NativeQPushPush.js +4 -0
- package/lib/QPush.d.ts +117 -0
- package/lib/QPush.js +206 -0
- package/lib/core/QPushCore.d.ts +148 -0
- package/lib/core/QPushCore.js +1387 -0
- package/lib/core/constants.d.ts +37 -0
- package/lib/core/constants.js +40 -0
- package/lib/core/events.d.ts +11 -0
- package/lib/core/events.js +40 -0
- package/lib/core/http.d.ts +41 -0
- package/lib/core/http.js +123 -0
- package/lib/core/logger.d.ts +18 -0
- package/lib/core/logger.js +43 -0
- package/lib/core/operationQueue.d.ts +117 -0
- package/lib/core/operationQueue.js +264 -0
- package/lib/core/operations.d.ts +80 -0
- package/lib/core/operations.js +88 -0
- package/lib/core/payload.d.ts +20 -0
- package/lib/core/payload.js +187 -0
- package/lib/core/platform.d.ts +72 -0
- package/lib/core/platform.js +7 -0
- package/lib/core/state.d.ts +51 -0
- package/lib/core/state.js +74 -0
- package/lib/core/storage.d.ts +34 -0
- package/lib/core/storage.js +68 -0
- package/lib/core/subscription.d.ts +67 -0
- package/lib/core/subscription.js +98 -0
- package/lib/core/tags.d.ts +39 -0
- package/lib/core/tags.js +123 -0
- package/lib/core/url.d.ts +17 -0
- package/lib/core/url.js +64 -0
- package/lib/core/uuid.d.ts +2 -0
- package/lib/core/uuid.js +18 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +15 -0
- package/lib/notification.d.ts +67 -0
- package/lib/notification.js +73 -0
- package/lib/reactNativePlatform.d.ts +3 -0
- package/lib/reactNativePlatform.js +164 -0
- package/lib/types.d.ts +157 -0
- package/lib/types.js +31 -0
- package/package.json +70 -4
- package/plugin/withQPush.js +330 -0
- package/react-native-qpush.podspec +30 -0
- package/react-native.config.js +18 -0
- package/src/NativeQPushPush.ts +57 -0
- package/src/QPush.ts +292 -0
- package/src/core/QPushCore.ts +1515 -0
- package/src/core/constants.ts +42 -0
- package/src/core/events.ts +34 -0
- package/src/core/http.ts +146 -0
- package/src/core/logger.ts +42 -0
- package/src/core/operationQueue.ts +324 -0
- package/src/core/operations.ts +166 -0
- package/src/core/payload.ts +175 -0
- package/src/core/platform.ts +84 -0
- package/src/core/state.ts +127 -0
- package/src/core/storage.ts +76 -0
- package/src/core/subscription.ts +133 -0
- package/src/core/tags.ts +116 -0
- package/src/core/url.ts +58 -0
- package/src/core/uuid.ts +13 -0
- package/src/index.ts +23 -0
- package/src/notification.ts +113 -0
- package/src/reactNativePlatform.ts +172 -0
- package/src/types.ts +174 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UserNotifications
|
|
3
|
+
|
|
4
|
+
/// Helper for the Notification Service Extension (NSE) target.
|
|
5
|
+
///
|
|
6
|
+
/// Shipped as the `QPushPushExtension` pod (no React Native dependency, app
|
|
7
|
+
/// extension safe). For each QPush notification it:
|
|
8
|
+
/// - downloads `qpush.image` and attaches it,
|
|
9
|
+
/// - registers a category `qpush_<notification id>` with the `buttons`,
|
|
10
|
+
/// - calls `PUT /notifications/{id}/report_received`,
|
|
11
|
+
/// then hands the content back to the system.
|
|
12
|
+
///
|
|
13
|
+
/// The config (`app_id`, `server_url`, `subscription_id`) is read from the App
|
|
14
|
+
/// Group `group.{main app bundle id}.qpush` written by the main app SDK.
|
|
15
|
+
@objc(QPushNotificationServiceExtension)
|
|
16
|
+
public final class QPushNotificationServiceExtension: NSObject {
|
|
17
|
+
private static let lock = NSLock()
|
|
18
|
+
private static var completedRequests: [String] = []
|
|
19
|
+
private static let downloadTimeout: TimeInterval = 20
|
|
20
|
+
private static let reportTimeout: TimeInterval = 10
|
|
21
|
+
private static let maxCategories = 30
|
|
22
|
+
private static let categoryListKey = "qpush_nse_category_ids"
|
|
23
|
+
|
|
24
|
+
/// Call from `didReceive(_:withContentHandler:)`.
|
|
25
|
+
@objc(didReceiveNotificationExtensionRequest:withMutableNotificationContent:withContentHandler:)
|
|
26
|
+
public static func didReceiveNotificationExtensionRequest(
|
|
27
|
+
_ request: UNNotificationRequest,
|
|
28
|
+
with content: UNMutableNotificationContent,
|
|
29
|
+
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
|
|
30
|
+
) {
|
|
31
|
+
let userInfo = request.content.userInfo
|
|
32
|
+
guard let qpush = userInfo["qpush"] as? [String: Any],
|
|
33
|
+
let notificationId = qpush["id"] as? String,
|
|
34
|
+
!notificationId.isEmpty
|
|
35
|
+
else {
|
|
36
|
+
// Not a QPush notification: deliver untouched.
|
|
37
|
+
deliver(request.identifier, content, contentHandler)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let group = DispatchGroup()
|
|
42
|
+
let resultLock = NSLock()
|
|
43
|
+
var attachment: UNNotificationAttachment?
|
|
44
|
+
var categoryIdentifier: String?
|
|
45
|
+
|
|
46
|
+
group.enter()
|
|
47
|
+
reportReceived(notificationId: notificationId) { group.leave() }
|
|
48
|
+
|
|
49
|
+
if let image = qpush["image"] as? String, !image.isEmpty, let url = URL(string: image) {
|
|
50
|
+
group.enter()
|
|
51
|
+
downloadAttachment(from: url) { downloaded in
|
|
52
|
+
resultLock.lock()
|
|
53
|
+
attachment = downloaded
|
|
54
|
+
resultLock.unlock()
|
|
55
|
+
group.leave()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if let buttons = qpush["buttons"] as? [[String: Any]], !buttons.isEmpty {
|
|
60
|
+
group.enter()
|
|
61
|
+
registerCategory(identifier: "qpush_\(notificationId)", buttons: buttons) { identifier in
|
|
62
|
+
resultLock.lock()
|
|
63
|
+
categoryIdentifier = identifier
|
|
64
|
+
resultLock.unlock()
|
|
65
|
+
group.leave()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
group.notify(queue: .main) {
|
|
70
|
+
resultLock.lock()
|
|
71
|
+
let finalAttachment = attachment
|
|
72
|
+
let finalCategory = categoryIdentifier
|
|
73
|
+
resultLock.unlock()
|
|
74
|
+
QPushNotificationServiceExtension.deliver(request.identifier, content, contentHandler) {
|
|
75
|
+
if let finalAttachment = finalAttachment { content.attachments = [finalAttachment] }
|
|
76
|
+
if let finalCategory = finalCategory { content.categoryIdentifier = finalCategory }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Call from `serviceExtensionTimeWillExpire()`. Returns `true` when the
|
|
82
|
+
/// caller must still call the content handler (QPush did not deliver yet).
|
|
83
|
+
@discardableResult
|
|
84
|
+
@objc(serviceExtensionTimeWillExpireRequest:withMutableNotificationContent:)
|
|
85
|
+
public static func serviceExtensionTimeWillExpireRequest(
|
|
86
|
+
_ request: UNNotificationRequest,
|
|
87
|
+
with content: UNMutableNotificationContent?
|
|
88
|
+
) -> Bool {
|
|
89
|
+
return markCompleted(request.identifier)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// MARK: - Delivery bookkeeping (the content handler must be called once)
|
|
93
|
+
|
|
94
|
+
private static func markCompleted(_ identifier: String) -> Bool {
|
|
95
|
+
lock.lock()
|
|
96
|
+
defer { lock.unlock() }
|
|
97
|
+
if completedRequests.contains(identifier) { return false }
|
|
98
|
+
completedRequests.append(identifier)
|
|
99
|
+
if completedRequests.count > 50 { completedRequests.removeFirst(completedRequests.count - 50) }
|
|
100
|
+
return true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static func deliver(
|
|
104
|
+
_ identifier: String,
|
|
105
|
+
_ content: UNMutableNotificationContent,
|
|
106
|
+
_ contentHandler: @escaping (UNNotificationContent) -> Void,
|
|
107
|
+
mutate: (() -> Void)? = nil
|
|
108
|
+
) {
|
|
109
|
+
guard markCompleted(identifier) else { return }
|
|
110
|
+
mutate?()
|
|
111
|
+
contentHandler(content)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: - App Group config
|
|
115
|
+
|
|
116
|
+
/// `QPushAppGroup` in the extension's Info.plist, else `group.{host app bundle id}.qpush`.
|
|
117
|
+
static func appGroupName() -> String {
|
|
118
|
+
if let custom = Bundle.main.object(forInfoDictionaryKey: "QPushAppGroup") as? String, !custom.isEmpty {
|
|
119
|
+
return custom
|
|
120
|
+
}
|
|
121
|
+
return "group.\(hostAppBundleIdentifier()).qpush"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Bundle id of the containing app (`App.app/PlugIns/X.appex` -> `App.app`).
|
|
125
|
+
static func hostAppBundleIdentifier() -> String {
|
|
126
|
+
let bundleURL = Bundle.main.bundleURL
|
|
127
|
+
if bundleURL.pathExtension == "appex" {
|
|
128
|
+
let appURL = bundleURL.deletingLastPathComponent().deletingLastPathComponent()
|
|
129
|
+
if let identifier = Bundle(url: appURL)?.bundleIdentifier {
|
|
130
|
+
return identifier
|
|
131
|
+
}
|
|
132
|
+
// Fallback: com.example.app.NotificationExtension -> com.example.app
|
|
133
|
+
if let own = Bundle.main.bundleIdentifier, let dot = own.lastIndex(of: ".") {
|
|
134
|
+
return String(own[..<dot])
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return Bundle.main.bundleIdentifier ?? ""
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private static func sharedDefaults() -> UserDefaults? {
|
|
141
|
+
return UserDefaults(suiteName: appGroupName())
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// MARK: - Delivery receipt
|
|
145
|
+
|
|
146
|
+
private static func reportReceived(notificationId: String, completion: @escaping () -> Void) {
|
|
147
|
+
guard let defaults = sharedDefaults(),
|
|
148
|
+
let appId = defaults.string(forKey: "app_id"), !appId.isEmpty,
|
|
149
|
+
let serverUrl = defaults.string(forKey: "server_url"), !serverUrl.isEmpty
|
|
150
|
+
else {
|
|
151
|
+
NSLog("[QPush] NSE: app_id/server_url missing in App Group %@ (open the app once after installing)", appGroupName())
|
|
152
|
+
completion()
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
var base = serverUrl
|
|
156
|
+
while base.hasSuffix("/") { base.removeLast() }
|
|
157
|
+
var allowed = CharacterSet.urlPathAllowed
|
|
158
|
+
allowed.remove(charactersIn: "/")
|
|
159
|
+
let encodedId = notificationId.addingPercentEncoding(withAllowedCharacters: allowed) ?? notificationId
|
|
160
|
+
guard let url = URL(string: "\(base)/notifications/\(encodedId)/report_received") else {
|
|
161
|
+
completion()
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
var body: [String: Any] = ["app_id": appId, "device_type": 0]
|
|
165
|
+
let subscriptionId = defaults.string(forKey: "subscription_id")
|
|
166
|
+
if let subscriptionId = subscriptionId { body["subscription_id"] = subscriptionId }
|
|
167
|
+
|
|
168
|
+
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: reportTimeout)
|
|
169
|
+
request.httpMethod = "PUT"
|
|
170
|
+
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: [])
|
|
171
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
172
|
+
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
173
|
+
request.setValue("qpush-react-native/1.0.0", forHTTPHeaderField: "SDK-Version")
|
|
174
|
+
if let subscriptionId = subscriptionId {
|
|
175
|
+
request.setValue(subscriptionId, forHTTPHeaderField: "QPush-Subscription-Id")
|
|
176
|
+
}
|
|
177
|
+
URLSession.shared.dataTask(with: request) { _, _, _ in
|
|
178
|
+
completion()
|
|
179
|
+
}.resume()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// MARK: - Image attachment
|
|
183
|
+
|
|
184
|
+
private static func downloadAttachment(from url: URL, completion: @escaping (UNNotificationAttachment?) -> Void) {
|
|
185
|
+
var request = URLRequest(url: url)
|
|
186
|
+
request.timeoutInterval = downloadTimeout
|
|
187
|
+
URLSession.shared.downloadTask(with: request) { location, response, error in
|
|
188
|
+
guard error == nil, let location = location else {
|
|
189
|
+
completion(nil)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
let fileExtension = QPushNotificationServiceExtension.attachmentExtension(url: url, mimeType: response?.mimeType)
|
|
193
|
+
let destination = FileManager.default.temporaryDirectory
|
|
194
|
+
.appendingPathComponent("qpush-\(UUID().uuidString)")
|
|
195
|
+
.appendingPathExtension(fileExtension)
|
|
196
|
+
do {
|
|
197
|
+
// The downloaded file is deleted when this closure returns: move it now.
|
|
198
|
+
try FileManager.default.moveItem(at: location, to: destination)
|
|
199
|
+
let attachment = try UNNotificationAttachment(identifier: "qpush-image", url: destination, options: nil)
|
|
200
|
+
completion(attachment)
|
|
201
|
+
} catch {
|
|
202
|
+
NSLog("[QPush] NSE: could not attach %@: %@", url.absoluteString, error.localizedDescription)
|
|
203
|
+
completion(nil)
|
|
204
|
+
}
|
|
205
|
+
}.resume()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private static func attachmentExtension(url: URL, mimeType: String?) -> String {
|
|
209
|
+
let supported = ["jpg", "jpeg", "png", "gif", "heic", "mp4", "m4v", "mov", "mp3", "m4a", "wav", "aiff"]
|
|
210
|
+
let fromPath = url.pathExtension.lowercased()
|
|
211
|
+
if supported.contains(fromPath) { return fromPath }
|
|
212
|
+
switch mimeType?.lowercased() {
|
|
213
|
+
case "image/png": return "png"
|
|
214
|
+
case "image/gif": return "gif"
|
|
215
|
+
case "image/heic": return "heic"
|
|
216
|
+
case "video/mp4": return "mp4"
|
|
217
|
+
case "audio/mpeg": return "mp3"
|
|
218
|
+
default: return "jpg"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// MARK: - Action buttons
|
|
223
|
+
|
|
224
|
+
private static func registerCategory(
|
|
225
|
+
identifier: String,
|
|
226
|
+
buttons: [[String: Any]],
|
|
227
|
+
completion: @escaping (String?) -> Void
|
|
228
|
+
) {
|
|
229
|
+
let actions: [UNNotificationAction] = buttons.compactMap { button in
|
|
230
|
+
guard let id = button["id"] as? String, !id.isEmpty,
|
|
231
|
+
let text = button["text"] as? String, !text.isEmpty
|
|
232
|
+
else { return nil }
|
|
233
|
+
return UNNotificationAction(identifier: id, title: text, options: [.foreground])
|
|
234
|
+
}
|
|
235
|
+
guard !actions.isEmpty else {
|
|
236
|
+
completion(nil)
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
let category = UNNotificationCategory(identifier: identifier, actions: actions, intentIdentifiers: [], options: [])
|
|
240
|
+
let center = UNUserNotificationCenter.current()
|
|
241
|
+
|
|
242
|
+
// Remember recent QPush categories so old ones can be pruned.
|
|
243
|
+
let defaults = sharedDefaults()
|
|
244
|
+
var recent = (defaults?.stringArray(forKey: categoryListKey) ?? []).filter { $0 != identifier }
|
|
245
|
+
recent.append(identifier)
|
|
246
|
+
if recent.count > maxCategories { recent.removeFirst(recent.count - maxCategories) }
|
|
247
|
+
defaults?.set(recent, forKey: categoryListKey)
|
|
248
|
+
let keep = Set(recent)
|
|
249
|
+
|
|
250
|
+
center.getNotificationCategories { existing in
|
|
251
|
+
var categories = existing.filter { !$0.identifier.hasPrefix("qpush_") || keep.contains($0.identifier) }
|
|
252
|
+
categories = categories.filter { $0.identifier != identifier }
|
|
253
|
+
categories.insert(category)
|
|
254
|
+
center.setNotificationCategories(categories)
|
|
255
|
+
// Reading back makes sure the new category is registered before delivery.
|
|
256
|
+
center.getNotificationCategories { _ in
|
|
257
|
+
completion(identifier)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
|
|
4
|
+
|
|
5
|
+
# Helper for the Notification Service Extension target. No React Native
|
|
6
|
+
# dependency, app-extension safe. Add it to the NSE target in your Podfile:
|
|
7
|
+
#
|
|
8
|
+
# target 'QPushNotificationServiceExtension' do
|
|
9
|
+
# pod 'QPushPushExtension', :path => '../node_modules/react-native-qpush/ios/Extension'
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# It lives here, not in the package root, so that autolinking (which picks a
|
|
13
|
+
# podspec from the package root) always links react-native-qpush.podspec
|
|
14
|
+
# into the app target.
|
|
15
|
+
Pod::Spec.new do |s|
|
|
16
|
+
s.name = "QPushPushExtension"
|
|
17
|
+
s.version = package["version"]
|
|
18
|
+
s.summary = "Notification Service Extension helper for react-native-qpush"
|
|
19
|
+
s.homepage = package["homepage"]
|
|
20
|
+
s.license = package["license"]
|
|
21
|
+
s.authors = package["author"]
|
|
22
|
+
|
|
23
|
+
s.platforms = { :ios => "13.0" }
|
|
24
|
+
s.source = { :git => package["repository"]["url"], :tag => "v#{s.version}" }
|
|
25
|
+
|
|
26
|
+
s.source_files = "*.swift"
|
|
27
|
+
s.swift_version = "5.0"
|
|
28
|
+
s.frameworks = "UserNotifications"
|
|
29
|
+
|
|
30
|
+
s.pod_target_xcconfig = {
|
|
31
|
+
"APPLICATION_EXTENSION_API_ONLY" => "YES",
|
|
32
|
+
"DEFINES_MODULE" => "YES",
|
|
33
|
+
}
|
|
34
|
+
end
|
package/ios/QPushPush.m
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Objective-C bridge exposing the Swift `QPushPush` module to React Native.
|
|
2
|
+
// Every selector here must match the `@objc(...)` name in QPushPush.swift.
|
|
3
|
+
|
|
4
|
+
#import <React/RCTBridgeModule.h>
|
|
5
|
+
#import <React/RCTEventEmitter.h>
|
|
6
|
+
|
|
7
|
+
@interface RCT_EXTERN_MODULE(QPushPush, RCTEventEmitter)
|
|
8
|
+
|
|
9
|
+
// Storage
|
|
10
|
+
RCT_EXTERN_METHOD(getItem:(NSString *)key
|
|
11
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
12
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
13
|
+
RCT_EXTERN_METHOD(setItem:(NSString *)key
|
|
14
|
+
value:(NSString *)value
|
|
15
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
16
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
17
|
+
RCT_EXTERN_METHOD(removeItem:(NSString *)key
|
|
18
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
19
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
20
|
+
|
|
21
|
+
// Device & registration
|
|
22
|
+
RCT_EXTERN_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
|
|
23
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
24
|
+
RCT_EXTERN_METHOD(registerForPush:(NSDictionary *)fcmParams
|
|
25
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
26
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
27
|
+
|
|
28
|
+
// Permission
|
|
29
|
+
RCT_EXTERN_METHOD(getPermissionStatus:(RCTPromiseResolveBlock)resolve
|
|
30
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
31
|
+
RCT_EXTERN_METHOD(requestPermission:(BOOL)fallbackToSettings
|
|
32
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
33
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
34
|
+
RCT_EXTERN_METHOD(registerForProvisionalAuthorization:(RCTPromiseResolveBlock)resolve
|
|
35
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
36
|
+
RCT_EXTERN_METHOD(canRequestPermission:(RCTPromiseResolveBlock)resolve
|
|
37
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
38
|
+
|
|
39
|
+
// Notifications
|
|
40
|
+
RCT_EXTERN_METHOD(clearAll)
|
|
41
|
+
RCT_EXTERN_METHOD(removeNotification:(NSString *)notificationId)
|
|
42
|
+
RCT_EXTERN_METHOD(removeGroupedNotifications:(NSString *)groupKey)
|
|
43
|
+
RCT_EXTERN_METHOD(clearBadge:(RCTPromiseResolveBlock)resolve
|
|
44
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
45
|
+
|
|
46
|
+
// Events
|
|
47
|
+
RCT_EXTERN_METHOD(setJsReady)
|
|
48
|
+
RCT_EXTERN_METHOD(displayForegroundNotification:(NSString *)displayId)
|
|
49
|
+
RCT_EXTERN_METHOD(preventForegroundDisplay:(NSString *)displayId)
|
|
50
|
+
|
|
51
|
+
// Settings
|
|
52
|
+
RCT_EXTERN_METHOD(setAutoOpenUrls:(BOOL)enabled)
|
|
53
|
+
RCT_EXTERN_METHOD(setLogLevel:(double)level)
|
|
54
|
+
|
|
55
|
+
// Background flush
|
|
56
|
+
RCT_EXTERN_METHOD(beginBackgroundTask:(RCTPromiseResolveBlock)resolve
|
|
57
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
58
|
+
RCT_EXTERN_METHOD(endBackgroundTask:(double)taskId)
|
|
59
|
+
|
|
60
|
+
// addListener: / removeListeners: are exported by RCTEventEmitter.
|
|
61
|
+
|
|
62
|
+
@end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import UserNotifications
|
|
4
|
+
|
|
5
|
+
/// React Native module `QPushPush` (exported by QPushPush.m).
|
|
6
|
+
///
|
|
7
|
+
/// The user / subscription / HTTP state machine lives in TypeScript; this
|
|
8
|
+
/// module only does what must be native: storage (+ App Group mirroring),
|
|
9
|
+
/// APNs token, permission, notification center and events.
|
|
10
|
+
@objc(QPushPush)
|
|
11
|
+
class QPushPush: RCTEventEmitter {
|
|
12
|
+
private(set) var hasListeners = false
|
|
13
|
+
|
|
14
|
+
override init() {
|
|
15
|
+
super.init()
|
|
16
|
+
QPushPushCore.shared.attach(self)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// RCTEventEmitter adopts RCTBridgeModule, so Swift sees this as inherited.
|
|
20
|
+
@objc
|
|
21
|
+
override static func requiresMainQueueSetup() -> Bool {
|
|
22
|
+
// Touches UIApplication / UNUserNotificationCenter at init.
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override func supportedEvents() -> [String]! {
|
|
27
|
+
return QPushPushCore.supportedEvents
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override func startObserving() {
|
|
31
|
+
hasListeners = true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override func stopObserving() {
|
|
35
|
+
hasListeners = false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// MARK: - Storage
|
|
39
|
+
|
|
40
|
+
@objc(getItem:resolver:rejecter:)
|
|
41
|
+
func getItem(_ key: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
42
|
+
resolve(QPushPushCore.orNull(QPushStorage.shared.string(key)))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@objc(setItem:value:resolver:rejecter:)
|
|
46
|
+
func setItem(
|
|
47
|
+
_ key: String,
|
|
48
|
+
value: String,
|
|
49
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
50
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
51
|
+
) {
|
|
52
|
+
QPushStorage.shared.set(value, forKey: key)
|
|
53
|
+
resolve(NSNull())
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@objc(removeItem:resolver:rejecter:)
|
|
57
|
+
func removeItem(_ key: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
58
|
+
QPushStorage.shared.remove(key)
|
|
59
|
+
resolve(NSNull())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// MARK: - Device & registration
|
|
63
|
+
|
|
64
|
+
@objc(getDeviceInfo:rejecter:)
|
|
65
|
+
func getDeviceInfo(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
66
|
+
DispatchQueue.main.async {
|
|
67
|
+
resolve(QPushDeviceInfo.collect())
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// `fcmParams` is Android only and ignored here.
|
|
72
|
+
@objc(registerForPush:resolver:rejecter:)
|
|
73
|
+
func registerForPush(
|
|
74
|
+
_ fcmParams: NSDictionary?,
|
|
75
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
76
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
77
|
+
) {
|
|
78
|
+
QPushPushCore.shared.registerForPush { result in
|
|
79
|
+
resolve(result)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// MARK: - Permission
|
|
84
|
+
|
|
85
|
+
@objc(getPermissionStatus:rejecter:)
|
|
86
|
+
func getPermissionStatus(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
87
|
+
QPushPushCore.shared.permissionStatus { status in
|
|
88
|
+
resolve(status)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@objc(requestPermission:resolver:rejecter:)
|
|
93
|
+
func requestPermission(
|
|
94
|
+
_ fallbackToSettings: Bool,
|
|
95
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
96
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
97
|
+
) {
|
|
98
|
+
QPushPushCore.shared.requestPermission(fallbackToSettings: fallbackToSettings) { granted in
|
|
99
|
+
resolve(granted)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@objc(registerForProvisionalAuthorization:rejecter:)
|
|
104
|
+
func registerForProvisionalAuthorization(
|
|
105
|
+
_ resolve: @escaping RCTPromiseResolveBlock,
|
|
106
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
107
|
+
) {
|
|
108
|
+
QPushPushCore.shared.registerForProvisionalAuthorization { granted in
|
|
109
|
+
resolve(granted)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@objc(canRequestPermission:rejecter:)
|
|
114
|
+
func canRequestPermission(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
115
|
+
QPushPushCore.shared.canRequestPermission { canRequest in
|
|
116
|
+
resolve(canRequest)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// MARK: - Notifications
|
|
121
|
+
|
|
122
|
+
@objc(clearAll)
|
|
123
|
+
func clearAll() {
|
|
124
|
+
QPushPushCore.shared.clearAll()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@objc(removeNotification:)
|
|
128
|
+
func removeNotification(_ notificationId: String) {
|
|
129
|
+
QPushPushCore.shared.removeNotification(notificationId)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@objc(removeGroupedNotifications:)
|
|
133
|
+
func removeGroupedNotifications(_ groupKey: String) {
|
|
134
|
+
QPushPushCore.shared.removeGroupedNotifications(groupKey)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@objc(clearBadge:rejecter:)
|
|
138
|
+
func clearBadge(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
139
|
+
QPushPushCore.shared.clearBadge { previous in
|
|
140
|
+
resolve(previous)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// MARK: - Events
|
|
145
|
+
|
|
146
|
+
@objc(setJsReady)
|
|
147
|
+
func setJsReady() {
|
|
148
|
+
QPushPushCore.shared.setJsReady()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@objc(displayForegroundNotification:)
|
|
152
|
+
func displayForegroundNotification(_ displayId: String) {
|
|
153
|
+
QPushPushCore.shared.displayForeground(displayId)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@objc(preventForegroundDisplay:)
|
|
157
|
+
func preventForegroundDisplay(_ displayId: String) {
|
|
158
|
+
QPushPushCore.shared.preventForeground(displayId)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// MARK: - Settings
|
|
162
|
+
|
|
163
|
+
@objc(setAutoOpenUrls:)
|
|
164
|
+
func setAutoOpenUrls(_ enabled: Bool) {
|
|
165
|
+
QPushStorage.shared.autoOpenUrls = enabled
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@objc(setLogLevel:)
|
|
169
|
+
func setLogLevel(_ level: Double) {
|
|
170
|
+
QPushLog.level = Int(level)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// MARK: - Background flush
|
|
174
|
+
|
|
175
|
+
@objc(beginBackgroundTask:rejecter:)
|
|
176
|
+
func beginBackgroundTask(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
|
|
177
|
+
QPushPushCore.shared.beginBackgroundTask { taskId in
|
|
178
|
+
resolve(taskId)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@objc(endBackgroundTask:)
|
|
183
|
+
func endBackgroundTask(_ taskId: Double) {
|
|
184
|
+
QPushPushCore.shared.endBackgroundTask(Int(taskId))
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Makes React Native's Objective-C API visible to the Swift files of this pod
|
|
2
|
+
// (CocoaPods includes public headers in the pod's umbrella header, which the
|
|
3
|
+
// Swift code of the same module can see).
|
|
4
|
+
#import <React/RCTBridgeModule.h>
|
|
5
|
+
#import <React/RCTEventEmitter.h>
|