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,368 @@
|
|
|
1
|
+
package com.qpush.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import android.net.Uri
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.provider.Settings
|
|
8
|
+
import androidx.core.app.ActivityCompat
|
|
9
|
+
import androidx.core.app.NotificationManagerCompat
|
|
10
|
+
import androidx.core.content.ContextCompat
|
|
11
|
+
import com.facebook.react.bridge.Arguments
|
|
12
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
13
|
+
import com.facebook.react.bridge.Promise
|
|
14
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
15
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
16
|
+
import com.facebook.react.bridge.ReactMethod
|
|
17
|
+
import com.facebook.react.bridge.ReadableMap
|
|
18
|
+
import com.facebook.react.bridge.WritableMap
|
|
19
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
20
|
+
import com.facebook.react.modules.core.PermissionAwareActivity
|
|
21
|
+
import com.facebook.react.modules.core.PermissionListener
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Native module `QPushPush`. The user / subscription / HTTP state machine
|
|
25
|
+
* lives in TypeScript; this module only does what must be native: storage,
|
|
26
|
+
* FCM token, permission, notifications and events.
|
|
27
|
+
*/
|
|
28
|
+
class QPushPushModule(private val reactContext: ReactApplicationContext) :
|
|
29
|
+
ReactContextBaseJavaModule(reactContext), LifecycleEventListener {
|
|
30
|
+
|
|
31
|
+
companion object {
|
|
32
|
+
const val NAME = "QPushPush"
|
|
33
|
+
private const val POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS"
|
|
34
|
+
private const val PERMISSION_REQUEST_CODE = 0xBEAC
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Callbacks waiting for the app to come back to the foreground (settings screen, permission dialog). */
|
|
38
|
+
private val resumeWaiters = ArrayList<() -> Unit>()
|
|
39
|
+
|
|
40
|
+
init {
|
|
41
|
+
reactContext.addLifecycleEventListener(this)
|
|
42
|
+
QPushEventBus.attach(this)
|
|
43
|
+
if (reactContext.hasCurrentActivity()) QPushEventBus.hostResumed = true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
override fun getName(): String = NAME
|
|
47
|
+
|
|
48
|
+
override fun invalidate() {
|
|
49
|
+
QPushEventBus.detach(this)
|
|
50
|
+
reactContext.removeLifecycleEventListener(this)
|
|
51
|
+
super.invalidate()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Events
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
internal fun canEmit(): Boolean = reactContext.hasActiveReactInstance()
|
|
59
|
+
|
|
60
|
+
internal fun sendEvent(name: String, body: Map<String, Any?>) {
|
|
61
|
+
reactContext
|
|
62
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
63
|
+
.emit(name, toWritableMap(body))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private fun toWritableMap(body: Map<String, Any?>): WritableMap {
|
|
67
|
+
val map = Arguments.createMap()
|
|
68
|
+
for ((key, value) in body) {
|
|
69
|
+
when (value) {
|
|
70
|
+
null -> map.putNull(key)
|
|
71
|
+
is String -> map.putString(key, value)
|
|
72
|
+
is Boolean -> map.putBoolean(key, value)
|
|
73
|
+
is Int -> map.putInt(key, value)
|
|
74
|
+
is Long -> map.putDouble(key, value.toDouble())
|
|
75
|
+
is Double -> map.putDouble(key, value)
|
|
76
|
+
is Float -> map.putDouble(key, value.toDouble())
|
|
77
|
+
else -> map.putString(key, value.toString())
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return map
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@ReactMethod
|
|
84
|
+
fun addListener(eventName: String) {
|
|
85
|
+
// Required by NativeEventEmitter.
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@ReactMethod
|
|
89
|
+
fun removeListeners(count: Int) {
|
|
90
|
+
// Required by NativeEventEmitter.
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@ReactMethod
|
|
94
|
+
fun setJsReady() {
|
|
95
|
+
QPushEventBus.setJsReady()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@ReactMethod
|
|
99
|
+
fun displayForegroundNotification(displayId: String) {
|
|
100
|
+
QPushEventBus.display(displayId)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@ReactMethod
|
|
104
|
+
fun preventForegroundDisplay(displayId: String) {
|
|
105
|
+
QPushEventBus.prevent(displayId)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// LifecycleEventListener
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
override fun onHostResume() {
|
|
113
|
+
QPushEventBus.hostResumed = true
|
|
114
|
+
val waiters: List<() -> Unit>
|
|
115
|
+
synchronized(resumeWaiters) {
|
|
116
|
+
waiters = ArrayList(resumeWaiters)
|
|
117
|
+
resumeWaiters.clear()
|
|
118
|
+
}
|
|
119
|
+
for (waiter in waiters) {
|
|
120
|
+
try {
|
|
121
|
+
waiter()
|
|
122
|
+
} catch (e: Exception) {
|
|
123
|
+
QPushLog.e("Resume callback failed", e)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override fun onHostPause() {
|
|
129
|
+
QPushEventBus.hostResumed = false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override fun onHostDestroy() {
|
|
133
|
+
QPushEventBus.hostResumed = false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun onNextResume(callback: () -> Unit) {
|
|
137
|
+
synchronized(resumeWaiters) { resumeWaiters.add(callback) }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Storage
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
@ReactMethod
|
|
145
|
+
fun getItem(key: String, promise: Promise) {
|
|
146
|
+
try {
|
|
147
|
+
promise.resolve(QPushStore.get(reactContext, key))
|
|
148
|
+
} catch (e: Exception) {
|
|
149
|
+
promise.reject("qpush_storage", e.message, e)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@ReactMethod
|
|
154
|
+
fun setItem(key: String, value: String, promise: Promise) {
|
|
155
|
+
try {
|
|
156
|
+
QPushStore.set(reactContext, key, value)
|
|
157
|
+
promise.resolve(null)
|
|
158
|
+
} catch (e: Exception) {
|
|
159
|
+
promise.reject("qpush_storage", e.message, e)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@ReactMethod
|
|
164
|
+
fun removeItem(key: String, promise: Promise) {
|
|
165
|
+
try {
|
|
166
|
+
QPushStore.remove(reactContext, key)
|
|
167
|
+
promise.resolve(null)
|
|
168
|
+
} catch (e: Exception) {
|
|
169
|
+
promise.reject("qpush_storage", e.message, e)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Device & push registration
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
@ReactMethod
|
|
178
|
+
fun getDeviceInfo(promise: Promise) {
|
|
179
|
+
try {
|
|
180
|
+
promise.resolve(QPushDeviceInfo.collect(reactContext))
|
|
181
|
+
} catch (e: Exception) {
|
|
182
|
+
promise.reject("qpush_device_info", e.message, e)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@ReactMethod
|
|
187
|
+
fun registerForPush(fcmParams: ReadableMap?, promise: Promise) {
|
|
188
|
+
val params = fcmParams?.let { parseFcmParams(it) }
|
|
189
|
+
if (fcmParams != null && params == null) {
|
|
190
|
+
QPushLog.w("Incomplete FCM params from the server, falling back to the default FirebaseApp")
|
|
191
|
+
}
|
|
192
|
+
QPushFcm.register(reactContext, params) { result ->
|
|
193
|
+
val map = Arguments.createMap()
|
|
194
|
+
if (result.token != null) map.putString("token", result.token) else map.putNull("token")
|
|
195
|
+
result.notificationTypes?.let { map.putInt("notificationTypes", it) }
|
|
196
|
+
result.error?.let { map.putString("error", it) }
|
|
197
|
+
promise.resolve(map)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private fun parseFcmParams(map: ReadableMap): FcmParams? {
|
|
202
|
+
fun read(key: String): String? =
|
|
203
|
+
if (map.hasKey(key) && !map.isNull(key)) map.getString(key)?.takeIf { it.isNotEmpty() } else null
|
|
204
|
+
val projectId = read("project_id") ?: return null
|
|
205
|
+
val appId = read("app_id") ?: return null
|
|
206
|
+
val apiKey = read("api_key") ?: return null
|
|
207
|
+
val senderId = read("sender_id") ?: return null
|
|
208
|
+
return FcmParams(projectId, appId, apiKey, senderId)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Permission
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
private fun notificationsEnabled(): Boolean =
|
|
216
|
+
NotificationManagerCompat.from(reactContext).areNotificationsEnabled()
|
|
217
|
+
|
|
218
|
+
private fun hasPostPermission(): Boolean =
|
|
219
|
+
Build.VERSION.SDK_INT < 33 ||
|
|
220
|
+
ContextCompat.checkSelfPermission(reactContext, POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
|
221
|
+
|
|
222
|
+
private fun permissionStatus(): String = when {
|
|
223
|
+
hasPostPermission() && notificationsEnabled() -> "granted"
|
|
224
|
+
Build.VERSION.SDK_INT >= 33 && !hasPostPermission() && !QPushStore.isPermissionRequested(reactContext) ->
|
|
225
|
+
"notDetermined"
|
|
226
|
+
else -> "denied"
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
@ReactMethod
|
|
230
|
+
fun getPermissionStatus(promise: Promise) {
|
|
231
|
+
promise.resolve(permissionStatus())
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@ReactMethod
|
|
235
|
+
fun canRequestPermission(promise: Promise) {
|
|
236
|
+
if (Build.VERSION.SDK_INT < 33 || hasPostPermission()) {
|
|
237
|
+
promise.resolve(false)
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
val activity = reactContext.currentActivity
|
|
241
|
+
val canAsk = !QPushStore.isPermissionRequested(reactContext) ||
|
|
242
|
+
(activity != null && ActivityCompat.shouldShowRequestPermissionRationale(activity, POST_NOTIFICATIONS))
|
|
243
|
+
promise.resolve(canAsk)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@ReactMethod
|
|
247
|
+
fun registerForProvisionalAuthorization(promise: Promise) {
|
|
248
|
+
// iOS only concept; on Android report whether notifications can be shown.
|
|
249
|
+
promise.resolve(notificationsEnabled() && hasPostPermission())
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@ReactMethod
|
|
253
|
+
fun requestPermission(fallbackToSettings: Boolean, promise: Promise) {
|
|
254
|
+
if (hasPostPermission() && notificationsEnabled()) {
|
|
255
|
+
promise.resolve(true)
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
if (Build.VERSION.SDK_INT < 33 || hasPostPermission()) {
|
|
259
|
+
// No runtime prompt exists: notifications were disabled in system settings.
|
|
260
|
+
if (fallbackToSettings) openSettingsAndResolve(promise) else promise.resolve(false)
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
val activity = reactContext.currentActivity
|
|
265
|
+
if (activity == null) {
|
|
266
|
+
QPushLog.w("requestPermission: no current activity")
|
|
267
|
+
promise.resolve(false)
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
val requestedBefore = QPushStore.isPermissionRequested(reactContext)
|
|
271
|
+
val canPrompt = !requestedBefore || ActivityCompat.shouldShowRequestPermissionRationale(activity, POST_NOTIFICATIONS)
|
|
272
|
+
if (!canPrompt) {
|
|
273
|
+
// Permanently denied: the system will not show the dialog any more.
|
|
274
|
+
if (fallbackToSettings) openSettingsAndResolve(promise) else promise.resolve(false)
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
QPushStore.setPermissionRequested(reactContext)
|
|
279
|
+
val permissionAware = activity as? PermissionAwareActivity
|
|
280
|
+
if (permissionAware != null) {
|
|
281
|
+
permissionAware.requestPermissions(
|
|
282
|
+
arrayOf(POST_NOTIFICATIONS),
|
|
283
|
+
PERMISSION_REQUEST_CODE,
|
|
284
|
+
PermissionListener { requestCode, _, grantResults ->
|
|
285
|
+
if (requestCode != PERMISSION_REQUEST_CODE) return@PermissionListener false
|
|
286
|
+
val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED
|
|
287
|
+
promise.resolve(granted && notificationsEnabled())
|
|
288
|
+
true
|
|
289
|
+
},
|
|
290
|
+
)
|
|
291
|
+
} else {
|
|
292
|
+
// Not a ReactActivity: request and read the result when the dialog closes.
|
|
293
|
+
onNextResume { promise.resolve(hasPostPermission() && notificationsEnabled()) }
|
|
294
|
+
ActivityCompat.requestPermissions(activity, arrayOf(POST_NOTIFICATIONS), PERMISSION_REQUEST_CODE)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private fun openSettingsAndResolve(promise: Promise) {
|
|
299
|
+
if (!openNotificationSettings()) {
|
|
300
|
+
promise.resolve(false)
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
onNextResume { promise.resolve(hasPostPermission() && notificationsEnabled()) }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private fun openNotificationSettings(): Boolean {
|
|
307
|
+
val intent = if (Build.VERSION.SDK_INT >= 26) {
|
|
308
|
+
Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).putExtra(Settings.EXTRA_APP_PACKAGE, reactContext.packageName)
|
|
309
|
+
} else {
|
|
310
|
+
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", reactContext.packageName, null))
|
|
311
|
+
}
|
|
312
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
313
|
+
return try {
|
|
314
|
+
val activity = reactContext.currentActivity
|
|
315
|
+
if (activity != null) activity.startActivity(intent) else reactContext.startActivity(intent)
|
|
316
|
+
true
|
|
317
|
+
} catch (e: Exception) {
|
|
318
|
+
QPushLog.w("Could not open notification settings", e)
|
|
319
|
+
false
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// Notifications
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
@ReactMethod
|
|
328
|
+
fun clearAll() {
|
|
329
|
+
QPushNotificationRenderer.clearAll(reactContext)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@ReactMethod
|
|
333
|
+
fun removeNotification(notificationId: String) {
|
|
334
|
+
QPushNotificationRenderer.remove(reactContext, notificationId)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
@ReactMethod
|
|
338
|
+
fun removeGroupedNotifications(groupKey: String) {
|
|
339
|
+
QPushNotificationRenderer.removeGroup(reactContext, groupKey)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
@ReactMethod
|
|
343
|
+
fun clearBadge(promise: Promise) {
|
|
344
|
+
// Launcher badges follow the notifications on Android.
|
|
345
|
+
promise.resolve(0)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
@ReactMethod
|
|
349
|
+
fun setAutoOpenUrls(enabled: Boolean) {
|
|
350
|
+
QPushStore.setAutoOpenUrls(reactContext, enabled)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@ReactMethod
|
|
354
|
+
fun setLogLevel(level: Double) {
|
|
355
|
+
QPushLog.level = level.toInt()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
@ReactMethod
|
|
359
|
+
fun beginBackgroundTask(promise: Promise) {
|
|
360
|
+
// Not needed on Android (the process keeps running in background).
|
|
361
|
+
promise.resolve(-1)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@ReactMethod
|
|
365
|
+
fun endBackgroundTask(taskId: Double) {
|
|
366
|
+
// No-op on Android.
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.qpush.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class QPushPushPackage : ReactPackage {
|
|
9
|
+
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION")
|
|
10
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
11
|
+
listOf(QPushPushModule(reactContext))
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
|
14
|
+
emptyList()
|
|
15
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
package com.qpush.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import android.content.pm.PackageManager
|
|
6
|
+
import org.json.JSONArray
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* SharedPreferences shared by the JS layer (through the module's
|
|
10
|
+
* getItem/setItem) and native code that may run without the JS runtime
|
|
11
|
+
* (FirebaseMessagingService, notification trampoline).
|
|
12
|
+
*/
|
|
13
|
+
internal object QPushStore {
|
|
14
|
+
private const val PREFS_NAME = "qpush_sdk"
|
|
15
|
+
|
|
16
|
+
// Written by the JS layer.
|
|
17
|
+
const val KEY_APP_ID = "app_id"
|
|
18
|
+
const val KEY_SERVER_URL = "server_url"
|
|
19
|
+
const val KEY_SUBSCRIPTION_ID = "subscription_id"
|
|
20
|
+
|
|
21
|
+
// Native only.
|
|
22
|
+
private const val KEY_AUTO_OPEN_URLS = "qpush_native_auto_open_urls"
|
|
23
|
+
private const val KEY_PERMISSION_REQUESTED = "qpush_native_permission_requested"
|
|
24
|
+
private const val KEY_RECENT_NOTIFICATIONS = "qpush_native_recent_notification_ids"
|
|
25
|
+
const val KEY_FCM_MODE = "qpush_native_fcm_mode"
|
|
26
|
+
const val KEY_DEFAULT_APP_TOKEN = "qpush_native_default_app_token"
|
|
27
|
+
|
|
28
|
+
private const val META_SUPPRESS_LAUNCH_URLS = "com.qpush.suppressLaunchURLs"
|
|
29
|
+
private const val MAX_RECENT_NOTIFICATIONS = 100
|
|
30
|
+
|
|
31
|
+
private val lock = Any()
|
|
32
|
+
|
|
33
|
+
fun prefs(context: Context): SharedPreferences =
|
|
34
|
+
context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
35
|
+
|
|
36
|
+
fun get(context: Context, key: String): String? = prefs(context).getString(key, null)
|
|
37
|
+
|
|
38
|
+
/** Synchronous write: callers run off the main thread and may be killed right after. */
|
|
39
|
+
fun set(context: Context, key: String, value: String) {
|
|
40
|
+
prefs(context).edit().putString(key, value).commit()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fun remove(context: Context, key: String) {
|
|
44
|
+
prefs(context).edit().remove(key).commit()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fun isPermissionRequested(context: Context): Boolean =
|
|
48
|
+
prefs(context).getBoolean(KEY_PERMISSION_REQUESTED, false)
|
|
49
|
+
|
|
50
|
+
fun setPermissionRequested(context: Context) {
|
|
51
|
+
prefs(context).edit().putBoolean(KEY_PERMISSION_REQUESTED, true).apply()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fun setAutoOpenUrls(context: Context, enabled: Boolean) {
|
|
55
|
+
prefs(context).edit().putBoolean(KEY_AUTO_OPEN_URLS, enabled).apply()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Runtime setting wins; otherwise the manifest meta-data `com.qpush.suppressLaunchURLs`. */
|
|
59
|
+
fun autoOpenUrls(context: Context): Boolean {
|
|
60
|
+
val prefs = prefs(context)
|
|
61
|
+
if (prefs.contains(KEY_AUTO_OPEN_URLS)) return prefs.getBoolean(KEY_AUTO_OPEN_URLS, true)
|
|
62
|
+
return !manifestFlag(context, META_SUPPRESS_LAUNCH_URLS)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Suppress("DEPRECATION")
|
|
66
|
+
fun manifestFlag(context: Context, key: String): Boolean {
|
|
67
|
+
return try {
|
|
68
|
+
val info = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
|
|
69
|
+
info.metaData?.getBoolean(key, false) ?: false
|
|
70
|
+
} catch (e: Exception) {
|
|
71
|
+
false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Suppress("DEPRECATION")
|
|
76
|
+
fun manifestString(context: Context, key: String): String? {
|
|
77
|
+
return try {
|
|
78
|
+
val info = context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
|
|
79
|
+
info.metaData?.get(key)?.toString()
|
|
80
|
+
} catch (e: Exception) {
|
|
81
|
+
null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Remembers notification ids to drop FCM duplicates. Returns `true` when the
|
|
87
|
+
* id was already seen.
|
|
88
|
+
*/
|
|
89
|
+
fun markNotificationSeen(context: Context, notificationId: String): Boolean {
|
|
90
|
+
synchronized(lock) {
|
|
91
|
+
val prefs = prefs(context)
|
|
92
|
+
val ids = try {
|
|
93
|
+
JSONArray(prefs.getString(KEY_RECENT_NOTIFICATIONS, "[]"))
|
|
94
|
+
} catch (e: Exception) {
|
|
95
|
+
JSONArray()
|
|
96
|
+
}
|
|
97
|
+
for (i in 0 until ids.length()) {
|
|
98
|
+
if (ids.optString(i) == notificationId) return true
|
|
99
|
+
}
|
|
100
|
+
val updated = JSONArray()
|
|
101
|
+
val start = maxOf(0, ids.length() - (MAX_RECENT_NOTIFICATIONS - 1))
|
|
102
|
+
for (i in start until ids.length()) updated.put(ids.optString(i))
|
|
103
|
+
updated.put(notificationId)
|
|
104
|
+
prefs.edit().putString(KEY_RECENT_NOTIFICATIONS, updated.toString()).commit()
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<!-- Name of the default notification channel (qpush_default). Override it in
|
|
4
|
+
your app's res/values*/strings.xml to localize it. -->
|
|
5
|
+
<string name="qpush_default_channel_name">Notifications</string>
|
|
6
|
+
</resources>
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./plugin/withQPush');
|