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,249 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
// MARK: - Logging
|
|
5
|
+
|
|
6
|
+
/// Leveled logger using the same numeric levels as the JS `LogLevel` enum.
|
|
7
|
+
enum QPushLog {
|
|
8
|
+
static var level: Int = 3 // Warn
|
|
9
|
+
|
|
10
|
+
static func error(_ message: @autoclosure () -> String) { log(2, "ERROR", message()) }
|
|
11
|
+
static func warn(_ message: @autoclosure () -> String) { log(3, "WARN", message()) }
|
|
12
|
+
static func info(_ message: @autoclosure () -> String) { log(4, "INFO", message()) }
|
|
13
|
+
static func debug(_ message: @autoclosure () -> String) { log(5, "DEBUG", message()) }
|
|
14
|
+
|
|
15
|
+
private static func log(_ threshold: Int, _ tag: String, _ message: String) {
|
|
16
|
+
guard level >= threshold else { return }
|
|
17
|
+
NSLog("[QPush] %@: %@", tag, message)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// MARK: - Storage
|
|
22
|
+
|
|
23
|
+
/// Key/value storage shared with the JS layer. `app_id`, `server_url` and
|
|
24
|
+
/// `subscription_id` are mirrored into the App Group `group.{bundle_id}.qpush`
|
|
25
|
+
/// so the Notification Service Extension can read them.
|
|
26
|
+
final class QPushStorage {
|
|
27
|
+
static let shared = QPushStorage()
|
|
28
|
+
|
|
29
|
+
static let appIdKey = "app_id"
|
|
30
|
+
static let serverUrlKey = "server_url"
|
|
31
|
+
static let subscriptionIdKey = "subscription_id"
|
|
32
|
+
static let sharedKeys: Set<String> = [appIdKey, serverUrlKey, subscriptionIdKey]
|
|
33
|
+
|
|
34
|
+
private static let autoOpenUrlsKey = "qpush_native_auto_open_urls"
|
|
35
|
+
/// Info.plist switch (Boolean) disabling URL opening on notification tap.
|
|
36
|
+
private static let suppressLaunchUrlsPlistKey = "QPush_suppress_launch_urls"
|
|
37
|
+
/// Optional Info.plist override of the App Group name.
|
|
38
|
+
static let appGroupPlistKey = "QPushAppGroup"
|
|
39
|
+
|
|
40
|
+
private let defaults: UserDefaults
|
|
41
|
+
private let appGroupDefaults: UserDefaults?
|
|
42
|
+
|
|
43
|
+
private init() {
|
|
44
|
+
defaults = UserDefaults(suiteName: "com.qpush.sdk.storage") ?? UserDefaults.standard
|
|
45
|
+
appGroupDefaults = UserDefaults(suiteName: QPushStorage.appGroupName())
|
|
46
|
+
// Mirror existing values (e.g. the App Group was added after install).
|
|
47
|
+
for key in QPushStorage.sharedKeys {
|
|
48
|
+
if let value = defaults.string(forKey: key), appGroupDefaults?.string(forKey: key) != value {
|
|
49
|
+
appGroupDefaults?.set(value, forKey: key)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// `group.{main app bundle id}.qpush`, overridable with `QPushAppGroup` in Info.plist.
|
|
55
|
+
static func appGroupName() -> String {
|
|
56
|
+
if let custom = Bundle.main.object(forInfoDictionaryKey: appGroupPlistKey) as? String, !custom.isEmpty {
|
|
57
|
+
return custom
|
|
58
|
+
}
|
|
59
|
+
let bundleId = Bundle.main.bundleIdentifier ?? ""
|
|
60
|
+
return "group.\(bundleId).qpush"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func string(_ key: String) -> String? {
|
|
64
|
+
return defaults.string(forKey: key)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func set(_ value: String, forKey key: String) {
|
|
68
|
+
defaults.set(value, forKey: key)
|
|
69
|
+
if QPushStorage.sharedKeys.contains(key) {
|
|
70
|
+
appGroupDefaults?.set(value, forKey: key)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func remove(_ key: String) {
|
|
75
|
+
defaults.removeObject(forKey: key)
|
|
76
|
+
if QPushStorage.sharedKeys.contains(key) {
|
|
77
|
+
appGroupDefaults?.removeObject(forKey: key)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var autoOpenUrls: Bool {
|
|
82
|
+
get {
|
|
83
|
+
if defaults.object(forKey: QPushStorage.autoOpenUrlsKey) != nil {
|
|
84
|
+
return defaults.bool(forKey: QPushStorage.autoOpenUrlsKey)
|
|
85
|
+
}
|
|
86
|
+
let suppress = Bundle.main.object(forInfoDictionaryKey: QPushStorage.suppressLaunchUrlsPlistKey) as? Bool ?? false
|
|
87
|
+
return !suppress
|
|
88
|
+
}
|
|
89
|
+
set {
|
|
90
|
+
defaults.set(newValue, forKey: QPushStorage.autoOpenUrlsKey)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// MARK: - HTTP
|
|
96
|
+
|
|
97
|
+
/// Native calls that do not need the JS runtime (notification open / receipt).
|
|
98
|
+
enum QPushHTTP {
|
|
99
|
+
static let sdkVersion = "qpush-react-native/1.0.0"
|
|
100
|
+
private static let deviceTypeIOS = 0
|
|
101
|
+
private static let maxAttempts = 3
|
|
102
|
+
|
|
103
|
+
static func reportReceived(notificationId: String) {
|
|
104
|
+
guard var body = baseBody() else { return }
|
|
105
|
+
body["device_type"] = deviceTypeIOS
|
|
106
|
+
put(path: "/notifications/\(encode(notificationId))/report_received", body: body, attempt: 1)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
static func reportOpened(notificationId: String, actionId: String?) {
|
|
110
|
+
guard var body = baseBody() else { return }
|
|
111
|
+
body["opened"] = true
|
|
112
|
+
body["device_type"] = deviceTypeIOS
|
|
113
|
+
if let actionId = actionId, !actionId.isEmpty { body["action_id"] = actionId }
|
|
114
|
+
put(path: "/notifications/\(encode(notificationId))", body: body, attempt: 1)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private static func encode(_ segment: String) -> String {
|
|
118
|
+
var allowed = CharacterSet.urlPathAllowed
|
|
119
|
+
allowed.remove(charactersIn: "/")
|
|
120
|
+
return segment.addingPercentEncoding(withAllowedCharacters: allowed) ?? segment
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static func trimTrailingSlashes(_ value: String) -> String {
|
|
124
|
+
var result = value
|
|
125
|
+
while result.hasSuffix("/") { result.removeLast() }
|
|
126
|
+
return result
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static func baseBody() -> [String: Any]? {
|
|
130
|
+
let storage = QPushStorage.shared
|
|
131
|
+
guard let appId = storage.string(QPushStorage.appIdKey), !appId.isEmpty else {
|
|
132
|
+
QPushLog.warn("Cannot report notification event: SDK was never initialized")
|
|
133
|
+
return nil
|
|
134
|
+
}
|
|
135
|
+
var body: [String: Any] = ["app_id": appId]
|
|
136
|
+
if let subscriptionId = storage.string(QPushStorage.subscriptionIdKey) {
|
|
137
|
+
body["subscription_id"] = subscriptionId
|
|
138
|
+
}
|
|
139
|
+
return body
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private static func put(path: String, body: [String: Any], attempt: Int) {
|
|
143
|
+
let storage = QPushStorage.shared
|
|
144
|
+
guard let base = storage.string(QPushStorage.serverUrlKey), !base.isEmpty,
|
|
145
|
+
let url = URL(string: QPushHTTP.trimTrailingSlashes(base) + path),
|
|
146
|
+
let data = try? JSONSerialization.data(withJSONObject: body, options: [])
|
|
147
|
+
else { return }
|
|
148
|
+
|
|
149
|
+
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 15)
|
|
150
|
+
request.httpMethod = "PUT"
|
|
151
|
+
request.httpBody = data
|
|
152
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
153
|
+
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
154
|
+
request.setValue(sdkVersion, forHTTPHeaderField: "SDK-Version")
|
|
155
|
+
if let subscriptionId = storage.string(QPushStorage.subscriptionIdKey) {
|
|
156
|
+
request.setValue(subscriptionId, forHTTPHeaderField: "QPush-Subscription-Id")
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
URLSession.shared.dataTask(with: request) { _, response, error in
|
|
160
|
+
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
|
|
161
|
+
QPushLog.debug("PUT \(path) -> \(status)")
|
|
162
|
+
let retryable = error != nil || status == 429 || status >= 500
|
|
163
|
+
if retryable && attempt < QPushHTTP.maxAttempts {
|
|
164
|
+
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(attempt)) {
|
|
165
|
+
QPushHTTP.put(path: path, body: body, attempt: attempt + 1)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}.resume()
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// MARK: - Device info
|
|
173
|
+
|
|
174
|
+
enum QPushDeviceInfo {
|
|
175
|
+
static func collect() -> [String: Any] {
|
|
176
|
+
var info: [String: Any] = ["platform": "ios"]
|
|
177
|
+
info["deviceModel"] = modelIdentifier()
|
|
178
|
+
info["deviceOs"] = UIDevice.current.systemVersion
|
|
179
|
+
if let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
|
|
180
|
+
info["appVersion"] = version
|
|
181
|
+
}
|
|
182
|
+
info["timezone"] = TimeZone.current.identifier
|
|
183
|
+
let locale = Locale.preferredLanguages.first ?? Locale.current.identifier
|
|
184
|
+
info["locale"] = locale
|
|
185
|
+
if let language = locale.split(whereSeparator: { $0 == "-" || $0 == "_" }).first {
|
|
186
|
+
info["language"] = String(language).lowercased()
|
|
187
|
+
}
|
|
188
|
+
if let country = regionCode() { info["country"] = country }
|
|
189
|
+
info["rooted"] = false
|
|
190
|
+
if let testType = provisioningTestType() {
|
|
191
|
+
info["testType"] = testType
|
|
192
|
+
} else {
|
|
193
|
+
info["testType"] = NSNull()
|
|
194
|
+
}
|
|
195
|
+
return info
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private static func regionCode() -> String? {
|
|
199
|
+
if #available(iOS 16.0, *) {
|
|
200
|
+
return Locale.current.region?.identifier
|
|
201
|
+
}
|
|
202
|
+
return Locale.current.regionCode
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// `iPhone15,2`-style identifier (simulator: the simulated model).
|
|
206
|
+
static func modelIdentifier() -> String {
|
|
207
|
+
if let simulated = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] {
|
|
208
|
+
return simulated
|
|
209
|
+
}
|
|
210
|
+
var systemInfo = utsname()
|
|
211
|
+
uname(&systemInfo)
|
|
212
|
+
let mirror = Mirror(reflecting: systemInfo.machine)
|
|
213
|
+
let identifier = mirror.children.reduce(into: "") { result, element in
|
|
214
|
+
guard let value = element.value as? Int8, value != 0 else { return }
|
|
215
|
+
result.append(Character(UnicodeScalar(UInt8(value))))
|
|
216
|
+
}
|
|
217
|
+
return identifier.isEmpty ? UIDevice.current.model : identifier
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// `1` development (sandbox APNs), `2` ad-hoc, `nil` App Store / TestFlight / enterprise.
|
|
221
|
+
static func provisioningTestType() -> Int? {
|
|
222
|
+
#if targetEnvironment(simulator)
|
|
223
|
+
return 1
|
|
224
|
+
#else
|
|
225
|
+
guard let path = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision"),
|
|
226
|
+
let data = FileManager.default.contents(atPath: path),
|
|
227
|
+
let content = String(data: data, encoding: .isoLatin1),
|
|
228
|
+
let start = content.range(of: "<?xml"),
|
|
229
|
+
let end = content.range(of: "</plist>")
|
|
230
|
+
else {
|
|
231
|
+
return nil
|
|
232
|
+
}
|
|
233
|
+
let plistText = String(content[start.lowerBound..<end.upperBound])
|
|
234
|
+
guard let plistData = plistText.data(using: .isoLatin1),
|
|
235
|
+
let plist = (try? PropertyListSerialization.propertyList(from: plistData, options: [], format: nil)) as? [String: Any]
|
|
236
|
+
else {
|
|
237
|
+
return nil
|
|
238
|
+
}
|
|
239
|
+
let entitlements = plist["Entitlements"] as? [String: Any]
|
|
240
|
+
if let environment = entitlements?["aps-environment"] as? String, environment == "development" {
|
|
241
|
+
return 1
|
|
242
|
+
}
|
|
243
|
+
if plist["ProvisionedDevices"] != nil {
|
|
244
|
+
return 2
|
|
245
|
+
}
|
|
246
|
+
return nil
|
|
247
|
+
#endif
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
7
|
+
<key>CFBundleDisplayName</key>
|
|
8
|
+
<string>QPushNotificationServiceExtension</string>
|
|
9
|
+
<key>CFBundleExecutable</key>
|
|
10
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
11
|
+
<key>CFBundleIdentifier</key>
|
|
12
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
13
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
14
|
+
<string>6.0</string>
|
|
15
|
+
<key>CFBundleName</key>
|
|
16
|
+
<string>$(PRODUCT_NAME)</string>
|
|
17
|
+
<key>CFBundlePackageType</key>
|
|
18
|
+
<string>XPC!</string>
|
|
19
|
+
<key>CFBundleShortVersionString</key>
|
|
20
|
+
<string>{{VERSION}}</string>
|
|
21
|
+
<key>CFBundleVersion</key>
|
|
22
|
+
<string>{{BUILD_NUMBER}}</string>
|
|
23
|
+
<key>QPushAppGroup</key>
|
|
24
|
+
<string>{{APP_GROUP}}</string>
|
|
25
|
+
<key>NSExtension</key>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>NSExtensionPointIdentifier</key>
|
|
28
|
+
<string>com.apple.usernotifications.service</string>
|
|
29
|
+
<key>NSExtensionPrincipalClass</key>
|
|
30
|
+
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
|
31
|
+
</dict>
|
|
32
|
+
<key>NSAppTransportSecurity</key>
|
|
33
|
+
<dict>
|
|
34
|
+
<key>NSAllowsLocalNetworking</key>
|
|
35
|
+
<true/>
|
|
36
|
+
</dict>
|
|
37
|
+
</dict>
|
|
38
|
+
</plist>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>com.apple.security.application-groups</key>
|
|
6
|
+
<array>
|
|
7
|
+
<string>{{APP_GROUP}}</string>
|
|
8
|
+
</array>
|
|
9
|
+
</dict>
|
|
10
|
+
</plist>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import UserNotifications
|
|
2
|
+
|
|
3
|
+
// Provided by the `QPushPushExtension` pod. If you prefer not to use the pod,
|
|
4
|
+
// add `QPushNotificationServiceExtension.swift` (from
|
|
5
|
+
// node_modules/react-native-qpush/ios/Extension/) to this target instead.
|
|
6
|
+
#if canImport(QPushPushExtension)
|
|
7
|
+
import QPushPushExtension
|
|
8
|
+
#endif
|
|
9
|
+
|
|
10
|
+
class NotificationService: UNNotificationServiceExtension {
|
|
11
|
+
var contentHandler: ((UNNotificationContent) -> Void)?
|
|
12
|
+
var receivedRequest: UNNotificationRequest!
|
|
13
|
+
var bestAttemptContent: UNMutableNotificationContent?
|
|
14
|
+
|
|
15
|
+
override func didReceive(
|
|
16
|
+
_ request: UNNotificationRequest,
|
|
17
|
+
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
|
|
18
|
+
) {
|
|
19
|
+
self.receivedRequest = request
|
|
20
|
+
self.contentHandler = contentHandler
|
|
21
|
+
self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
|
22
|
+
|
|
23
|
+
guard let bestAttemptContent = bestAttemptContent else {
|
|
24
|
+
contentHandler(request.content)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Customize `bestAttemptContent` here if you need to, then let QPush
|
|
29
|
+
// attach the image, register the action buttons and confirm delivery.
|
|
30
|
+
QPushNotificationServiceExtension.didReceiveNotificationExtensionRequest(
|
|
31
|
+
request,
|
|
32
|
+
with: bestAttemptContent,
|
|
33
|
+
withContentHandler: contentHandler
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
override func serviceExtensionTimeWillExpire() {
|
|
38
|
+
// Called just before the extension is terminated: deliver what we have.
|
|
39
|
+
guard let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent else { return }
|
|
40
|
+
if QPushNotificationServiceExtension.serviceExtensionTimeWillExpireRequest(receivedRequest, with: bestAttemptContent) {
|
|
41
|
+
contentHandler(bestAttemptContent)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TurboModule spec of the native module `QPushPush`.
|
|
3
|
+
*
|
|
4
|
+
* The native implementations are classic bridge modules
|
|
5
|
+
* (`ReactContextBaseJavaModule` / `RCTEventEmitter`). They work on the old
|
|
6
|
+
* architecture and, through React Native's interop layer, on the New
|
|
7
|
+
* Architecture / bridgeless mode. This spec documents the contract and gives
|
|
8
|
+
* typed access through `TurboModuleRegistry` (which falls back to
|
|
9
|
+
* `NativeModules` on the old architecture). Codegen is intentionally not
|
|
10
|
+
* configured in package.json, so no generated code is required at build time.
|
|
11
|
+
*/
|
|
12
|
+
import type { TurboModule } from 'react-native';
|
|
13
|
+
export interface Spec extends TurboModule {
|
|
14
|
+
getItem(key: string): Promise<string | null>;
|
|
15
|
+
setItem(key: string, value: string): Promise<void>;
|
|
16
|
+
removeItem(key: string): Promise<void>;
|
|
17
|
+
/** DeviceInfo (see src/core/subscription.ts). */
|
|
18
|
+
getDeviceInfo(): Promise<Object>;
|
|
19
|
+
/**
|
|
20
|
+
* Android: FCM params from android_params.js (or null for the default
|
|
21
|
+
* FirebaseApp). iOS: ignored. Resolves `{ token, notificationTypes?, error? }`.
|
|
22
|
+
*/
|
|
23
|
+
registerForPush(fcmParams: Object | null): Promise<Object>;
|
|
24
|
+
/** 'notDetermined' | 'denied' | 'granted' | 'provisional' | 'ephemeral' */
|
|
25
|
+
getPermissionStatus(): Promise<string>;
|
|
26
|
+
requestPermission(fallbackToSettings: boolean): Promise<boolean>;
|
|
27
|
+
registerForProvisionalAuthorization(): Promise<boolean>;
|
|
28
|
+
canRequestPermission(): Promise<boolean>;
|
|
29
|
+
clearAll(): void;
|
|
30
|
+
removeNotification(notificationId: string): void;
|
|
31
|
+
removeGroupedNotifications(groupKey: string): void;
|
|
32
|
+
clearBadge(): Promise<number>;
|
|
33
|
+
setJsReady(): void;
|
|
34
|
+
displayForegroundNotification(displayId: string): void;
|
|
35
|
+
preventForegroundDisplay(displayId: string): void;
|
|
36
|
+
setAutoOpenUrls(enabled: boolean): void;
|
|
37
|
+
setLogLevel(level: number): void;
|
|
38
|
+
beginBackgroundTask(): Promise<number>;
|
|
39
|
+
endBackgroundTask(taskId: number): void;
|
|
40
|
+
addListener(eventName: string): void;
|
|
41
|
+
removeListeners(count: number): void;
|
|
42
|
+
}
|
|
43
|
+
declare const _default: Spec | null;
|
|
44
|
+
export default _default;
|
package/lib/QPush.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { QPushCore } from './core/QPushCore';
|
|
2
|
+
import type { PlatformAdapter } from './core/platform';
|
|
3
|
+
import type { NotificationClickEvent, NotificationWillDisplayEvent } from './notification';
|
|
4
|
+
import type { QPushInitOptions, LogLevel, NotificationPermission, PushSubscriptionChangedState, UserChangedState } from './types';
|
|
5
|
+
/** Events of `QPush.Notifications.addEventListener`. */
|
|
6
|
+
export interface NotificationEventTypeMap {
|
|
7
|
+
click: NotificationClickEvent;
|
|
8
|
+
foregroundWillDisplay: NotificationWillDisplayEvent;
|
|
9
|
+
permissionChange: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type NotificationEventName = keyof NotificationEventTypeMap;
|
|
12
|
+
/** Events of `QPush.User.pushSubscription.addEventListener`. */
|
|
13
|
+
export interface PushSubscriptionEventTypeMap {
|
|
14
|
+
change: PushSubscriptionChangedState;
|
|
15
|
+
}
|
|
16
|
+
/** Events of `QPush.User.addEventListener`. */
|
|
17
|
+
export interface UserEventTypeMap {
|
|
18
|
+
change: UserChangedState;
|
|
19
|
+
}
|
|
20
|
+
/** @internal Testing hook: replaces the platform used by the singleton. */
|
|
21
|
+
export declare function __setPlatformFactoryForTests(factory: () => PlatformAdapter): QPushCore;
|
|
22
|
+
/**
|
|
23
|
+
* QPush SDK entry point.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* QPush.initialize('APP_ID'); // qpush cloud: https://qpush.dev/api/v1
|
|
27
|
+
* await QPush.Notifications.requestPermission(true);
|
|
28
|
+
* QPush.login('user-123');
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare const QPush: {
|
|
32
|
+
/**
|
|
33
|
+
* Initializes the SDK. Call once, as early as possible (e.g. in index.js or
|
|
34
|
+
* your root component). Safe to call API methods right after it.
|
|
35
|
+
*/
|
|
36
|
+
initialize(appId: string, options?: QPushInitOptions): void;
|
|
37
|
+
/** Identifies the current user with your user id (`external_id`). */
|
|
38
|
+
login(externalId: string): void;
|
|
39
|
+
/** Detaches the device from the identified user (a new anonymous user is created). */
|
|
40
|
+
logout(): void;
|
|
41
|
+
/** When `true`, nothing is sent until `setConsentGiven(true)`. Call before `initialize`. */
|
|
42
|
+
setConsentRequired(required: boolean): void;
|
|
43
|
+
setConsentGiven(given: boolean): void;
|
|
44
|
+
Debug: {
|
|
45
|
+
/** Sets the SDK log level (JS console and native logs). */
|
|
46
|
+
setLogLevel(level: LogLevel): void;
|
|
47
|
+
};
|
|
48
|
+
Notifications: {
|
|
49
|
+
/**
|
|
50
|
+
* Shows the OS permission prompt (Android 13+ / iOS). With
|
|
51
|
+
* `fallbackToSettings`, opens the app notification settings when the
|
|
52
|
+
* prompt can no longer be shown (permission permanently denied).
|
|
53
|
+
*/
|
|
54
|
+
requestPermission(fallbackToSettings?: boolean): Promise<boolean>;
|
|
55
|
+
/** `true` when the app can currently display notifications. */
|
|
56
|
+
getPermissionAsync(): Promise<boolean>;
|
|
57
|
+
/** Detailed native permission state. */
|
|
58
|
+
permissionNative(): Promise<NotificationPermission>;
|
|
59
|
+
/** `true` when calling `requestPermission` would show the OS prompt. */
|
|
60
|
+
canRequestPermission(): Promise<boolean>;
|
|
61
|
+
/** iOS 12+: quiet "provisional" authorization without a prompt. Android: same as getPermissionAsync. */
|
|
62
|
+
registerForProvisionalAuthorization(): Promise<boolean>;
|
|
63
|
+
addEventListener<K extends NotificationEventName>(event: K, listener: (event: NotificationEventTypeMap[K]) => void): void;
|
|
64
|
+
removeEventListener<K extends NotificationEventName>(event: K, listener: (event: NotificationEventTypeMap[K]) => void): void;
|
|
65
|
+
/** Removes every QPush notification from the notification center and clears the badge. */
|
|
66
|
+
clearAll(): void;
|
|
67
|
+
/** Removes a single notification by its QPush notification id. */
|
|
68
|
+
removeNotification(notificationId: string): void;
|
|
69
|
+
/** Removes the notifications of a group (Android group / iOS thread-id). */
|
|
70
|
+
removeGroupedNotifications(groupKey: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Enables / disables opening the notification URL on tap (default enabled).
|
|
73
|
+
* Persisted natively, so it also applies to cold-start taps on next launches.
|
|
74
|
+
* Build-time switch: Android manifest meta-data `com.qpush.suppressLaunchURLs`
|
|
75
|
+
* / iOS Info.plist `QPush_suppress_launch_urls`.
|
|
76
|
+
*/
|
|
77
|
+
setAutoOpenUrls(enabled: boolean): void;
|
|
78
|
+
};
|
|
79
|
+
User: {
|
|
80
|
+
pushSubscription: {
|
|
81
|
+
/** Subscription id of this device, `null` until the server created it. */
|
|
82
|
+
getIdAsync(): Promise<string | null>;
|
|
83
|
+
/** FCM token (Android) / hex APNs token (iOS). */
|
|
84
|
+
getTokenAsync(): Promise<string | null>;
|
|
85
|
+
/** `true` when the device can receive pushes (permission + token + not opted out). */
|
|
86
|
+
getOptedInAsync(): Promise<boolean>;
|
|
87
|
+
/** Re-enables pushes; prompts for permission when it was never asked. */
|
|
88
|
+
optIn(): void;
|
|
89
|
+
/** Disables pushes for this device without touching the OS permission. */
|
|
90
|
+
optOut(): void;
|
|
91
|
+
addEventListener<K extends keyof PushSubscriptionEventTypeMap>(_event: K, listener: (event: PushSubscriptionEventTypeMap[K]) => void): void;
|
|
92
|
+
removeEventListener<K extends keyof PushSubscriptionEventTypeMap>(_event: K, listener: (event: PushSubscriptionEventTypeMap[K]) => void): void;
|
|
93
|
+
};
|
|
94
|
+
/** Adds or updates a tag. Values are stored as strings. */
|
|
95
|
+
addTag(key: string, value: string | number | boolean): void;
|
|
96
|
+
addTags(tags: Record<string, string | number | boolean>): void;
|
|
97
|
+
removeTag(key: string): void;
|
|
98
|
+
removeTags(keys: string[]): void;
|
|
99
|
+
/** Local copy of the user's tags (refreshed from the server on app start and login). */
|
|
100
|
+
getTags(): Promise<Record<string, string>>;
|
|
101
|
+
/** Adds a custom alias (e.g. `crm_id`). Use `QPush.login` for `external_id`. */
|
|
102
|
+
addAlias(label: string, id: string): void;
|
|
103
|
+
addAliases(aliases: Record<string, string>): void;
|
|
104
|
+
removeAlias(label: string): void;
|
|
105
|
+
removeAliases(labels: string[]): void;
|
|
106
|
+
/** ISO 639-1 language code, e.g. `pt`. Defaults to the device language. */
|
|
107
|
+
setLanguage(language: string): void;
|
|
108
|
+
/** QPush user id (`qpush_id`), `null` until the server created the user. */
|
|
109
|
+
getQPushId(): Promise<string | null>;
|
|
110
|
+
/** Alias of `getQPushId` for code migrated from OneSignal. */
|
|
111
|
+
getOnesignalId(): Promise<string | null>;
|
|
112
|
+
getExternalId(): Promise<string | null>;
|
|
113
|
+
addEventListener<K extends keyof UserEventTypeMap>(_event: K, listener: (event: UserEventTypeMap[K]) => void): void;
|
|
114
|
+
removeEventListener<K extends keyof UserEventTypeMap>(_event: K, listener: (event: UserEventTypeMap[K]) => void): void;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
export type QPushType = typeof QPush;
|