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.
Files changed (98) hide show
  1. package/README.md +439 -2
  2. package/android/build.gradle +77 -0
  3. package/android/gradle.properties +5 -0
  4. package/android/proguard-rules.pro +5 -0
  5. package/android/src/main/AndroidManifest.xml +29 -0
  6. package/android/src/main/java/com/qpush/reactnative/QPushDeviceInfo.kt +96 -0
  7. package/android/src/main/java/com/qpush/reactnative/QPushEventBus.kt +153 -0
  8. package/android/src/main/java/com/qpush/reactnative/QPushFcm.kt +108 -0
  9. package/android/src/main/java/com/qpush/reactnative/QPushHttp.kt +92 -0
  10. package/android/src/main/java/com/qpush/reactnative/QPushImages.kt +103 -0
  11. package/android/src/main/java/com/qpush/reactnative/QPushLog.kt +39 -0
  12. package/android/src/main/java/com/qpush/reactnative/QPushMessagingService.kt +77 -0
  13. package/android/src/main/java/com/qpush/reactnative/QPushNotificationOpenActivity.kt +121 -0
  14. package/android/src/main/java/com/qpush/reactnative/QPushNotificationRenderer.kt +256 -0
  15. package/android/src/main/java/com/qpush/reactnative/QPushPayload.kt +114 -0
  16. package/android/src/main/java/com/qpush/reactnative/QPushPushModule.kt +368 -0
  17. package/android/src/main/java/com/qpush/reactnative/QPushPushPackage.kt +15 -0
  18. package/android/src/main/java/com/qpush/reactnative/QPushStore.kt +108 -0
  19. package/android/src/main/res/values/qpush_strings.xml +6 -0
  20. package/app.plugin.js +1 -0
  21. package/ios/Extension/QPushNotificationServiceExtension.swift +261 -0
  22. package/ios/Extension/QPushPushExtension.podspec +34 -0
  23. package/ios/QPushPush.m +62 -0
  24. package/ios/QPushPush.swift +186 -0
  25. package/ios/QPushPushBridge.h +5 -0
  26. package/ios/QPushPushCore.swift +457 -0
  27. package/ios/QPushPushHooks.m +237 -0
  28. package/ios/QPushPushSupport.swift +249 -0
  29. package/ios_nse_template/Info.plist +38 -0
  30. package/ios_nse_template/NotificationService.entitlements +10 -0
  31. package/ios_nse_template/NotificationService.swift +44 -0
  32. package/lib/NativeQPushPush.d.ts +44 -0
  33. package/lib/NativeQPushPush.js +4 -0
  34. package/lib/QPush.d.ts +117 -0
  35. package/lib/QPush.js +206 -0
  36. package/lib/core/QPushCore.d.ts +148 -0
  37. package/lib/core/QPushCore.js +1387 -0
  38. package/lib/core/constants.d.ts +37 -0
  39. package/lib/core/constants.js +40 -0
  40. package/lib/core/events.d.ts +11 -0
  41. package/lib/core/events.js +40 -0
  42. package/lib/core/http.d.ts +41 -0
  43. package/lib/core/http.js +123 -0
  44. package/lib/core/logger.d.ts +18 -0
  45. package/lib/core/logger.js +43 -0
  46. package/lib/core/operationQueue.d.ts +117 -0
  47. package/lib/core/operationQueue.js +264 -0
  48. package/lib/core/operations.d.ts +80 -0
  49. package/lib/core/operations.js +88 -0
  50. package/lib/core/payload.d.ts +20 -0
  51. package/lib/core/payload.js +187 -0
  52. package/lib/core/platform.d.ts +72 -0
  53. package/lib/core/platform.js +7 -0
  54. package/lib/core/state.d.ts +51 -0
  55. package/lib/core/state.js +74 -0
  56. package/lib/core/storage.d.ts +34 -0
  57. package/lib/core/storage.js +68 -0
  58. package/lib/core/subscription.d.ts +67 -0
  59. package/lib/core/subscription.js +98 -0
  60. package/lib/core/tags.d.ts +39 -0
  61. package/lib/core/tags.js +123 -0
  62. package/lib/core/url.d.ts +17 -0
  63. package/lib/core/url.js +64 -0
  64. package/lib/core/uuid.d.ts +2 -0
  65. package/lib/core/uuid.js +18 -0
  66. package/lib/index.d.ts +7 -0
  67. package/lib/index.js +15 -0
  68. package/lib/notification.d.ts +67 -0
  69. package/lib/notification.js +73 -0
  70. package/lib/reactNativePlatform.d.ts +3 -0
  71. package/lib/reactNativePlatform.js +164 -0
  72. package/lib/types.d.ts +157 -0
  73. package/lib/types.js +31 -0
  74. package/package.json +70 -4
  75. package/plugin/withQPush.js +330 -0
  76. package/react-native-qpush.podspec +30 -0
  77. package/react-native.config.js +18 -0
  78. package/src/NativeQPushPush.ts +57 -0
  79. package/src/QPush.ts +292 -0
  80. package/src/core/QPushCore.ts +1515 -0
  81. package/src/core/constants.ts +42 -0
  82. package/src/core/events.ts +34 -0
  83. package/src/core/http.ts +146 -0
  84. package/src/core/logger.ts +42 -0
  85. package/src/core/operationQueue.ts +324 -0
  86. package/src/core/operations.ts +166 -0
  87. package/src/core/payload.ts +175 -0
  88. package/src/core/platform.ts +84 -0
  89. package/src/core/state.ts +127 -0
  90. package/src/core/storage.ts +76 -0
  91. package/src/core/subscription.ts +133 -0
  92. package/src/core/tags.ts +116 -0
  93. package/src/core/url.ts +58 -0
  94. package/src/core/uuid.ts +13 -0
  95. package/src/index.ts +23 -0
  96. package/src/notification.ts +113 -0
  97. package/src/reactNativePlatform.ts +172 -0
  98. 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');