react-native-push-signal 0.1.8 → 0.1.10

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 (37) hide show
  1. package/PushSignal.podspec +1 -1
  2. package/README.md +64 -0
  3. package/README.ru.md +64 -0
  4. package/android/build.gradle +1 -0
  5. package/android/src/main/AndroidManifest.xml +7 -0
  6. package/android/src/main/java/com/pushsignal/PushModels.kt +23 -0
  7. package/android/src/main/java/com/pushsignal/PushSignalCenter.kt +236 -12
  8. package/android/src/main/java/com/pushsignal/PushSignalModule.kt +30 -2
  9. package/ios/PushSignal.mm +18 -0
  10. package/lib/module/NativePushSignal.js.map +1 -1
  11. package/lib/module/PushSignalError.js +29 -0
  12. package/lib/module/PushSignalError.js.map +1 -0
  13. package/lib/module/index.js +2 -1
  14. package/lib/module/index.js.map +1 -1
  15. package/lib/module/pushSignal.js +3 -0
  16. package/lib/module/pushSignal.js.map +1 -1
  17. package/lib/module/pushSignal.native.js +51 -4
  18. package/lib/module/pushSignal.native.js.map +1 -1
  19. package/lib/typescript/src/NativePushSignal.d.ts +19 -0
  20. package/lib/typescript/src/NativePushSignal.d.ts.map +1 -1
  21. package/lib/typescript/src/PushSignalError.d.ts +22 -0
  22. package/lib/typescript/src/PushSignalError.d.ts.map +1 -0
  23. package/lib/typescript/src/index.d.ts +3 -2
  24. package/lib/typescript/src/index.d.ts.map +1 -1
  25. package/lib/typescript/src/pushSignal.d.ts +2 -1
  26. package/lib/typescript/src/pushSignal.d.ts.map +1 -1
  27. package/lib/typescript/src/pushSignal.native.d.ts +2 -1
  28. package/lib/typescript/src/pushSignal.native.d.ts.map +1 -1
  29. package/lib/typescript/src/types.d.ts +18 -0
  30. package/lib/typescript/src/types.d.ts.map +1 -1
  31. package/package.json +16 -6
  32. package/src/NativePushSignal.ts +20 -0
  33. package/src/PushSignalError.ts +29 -0
  34. package/src/index.tsx +4 -0
  35. package/src/pushSignal.native.tsx +68 -4
  36. package/src/pushSignal.tsx +5 -0
  37. package/src/types.ts +28 -0
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
11
11
  s.authors = package["author"]
12
12
 
13
13
  s.platforms = { :ios => min_ios_version_supported }
14
- s.source = { :git => "https://antonseagull.com.git", :tag => "#{s.version}" }
14
+ s.source = { :git => "https://github.com/AntonSeagull/react-native-push-signal.git", :tag => "#{s.version}" }
15
15
 
16
16
  s.source_files = "ios/**/*.{h,m,mm,cpp}"
17
17
  s.private_header_files = "ios/**/*.h"
package/README.md CHANGED
@@ -108,6 +108,70 @@ Without `initialize(...)` or that file, `getCredentials()` throws.
108
108
 
109
109
  If the host app already declares its own `FirebaseMessagingService`, only one service can handle `com.google.firebase.MESSAGING_EVENT`. Prefer this library’s service or forward events into it.
110
110
 
111
+ ### When it fails on Xiaomi: `SERVICE_NOT_AVAILABLE`
112
+
113
+ If `getCredentials()` throws `Failed to get an FCM token: SERVICE_NOT_AVAILABLE`, the FCM token could not be obtained. Your server never receives a token, so there is nowhere to send pushes — delivery itself is not the problem.
114
+
115
+ Before requesting the token the library checks Google Play services and retries a few times with backoff, but some causes can only be fixed on the device:
116
+
117
+ - **Google account.** The device must be signed into a Google account. FCM does not issue a token without one.
118
+ - **Google Play services.** They must be installed, enabled and up to date. On a China-only ROM without GMS, FCM cannot work at all — you need another push provider; see [Detecting a non-Google device](#detecting-a-non-google-device-getdiagnostics-and-pushsignalerror).
119
+ - **Network.** Check connectivity and disable VPN and private DNS.
120
+ - **MIUI / HyperOS.** Enable autostart and remove battery restrictions: Settings → Apps → your app → Autostart; Battery → No restrictions. Allow background data for the app.
121
+
122
+ What to look for in the logs:
123
+
124
+ ```sh
125
+ adb logcat | grep -iE "SERVICE_NOT_AVAILABLE|FirebaseMessaging|PushSignal"
126
+ ```
127
+
128
+ ### Detecting a non-Google device: `getDiagnostics()` and `PushSignalError`
129
+
130
+ When Google Play services cannot serve FCM, the library detects it and tells you which service to use instead — both in the error message and as structured data, so the cause is visible in React Native logs.
131
+
132
+ `initialize()` and `getCredentials()` reject with `PushSignalError`:
133
+
134
+ ```ts
135
+ import { getCredentials, PushSignalError } from 'react-native-push-signal';
136
+
137
+ try {
138
+ const credentials = await getCredentials();
139
+ } catch (error) {
140
+ if (error instanceof PushSignalError) {
141
+ console.warn(error.code); // E_GMS_MISSING, E_FCM_TOKEN, ...
142
+ console.warn(error.message); // "... Use HMS Push Kit (Huawei Push) instead of the standard Google service."
143
+ console.warn(error.provider); // 'hms'
144
+ console.warn(error.hint); // actionable sentence
145
+ console.warn(error.diagnostics); // full device/provider snapshot
146
+ }
147
+ }
148
+ ```
149
+
150
+ You can also inspect diagnostics without requesting a token:
151
+
152
+ ```ts
153
+ import { getDiagnostics } from 'react-native-push-signal';
154
+
155
+ const diagnostics = await getDiagnostics();
156
+ // { platform, gmsAvailable, gmsStatus, manufacturer, brand, model,
157
+ // provider, providerName, providerInstalled?, hint? }
158
+ ```
159
+
160
+ Provider suggested when GMS is unavailable:
161
+
162
+ | Manufacturer | `provider` | Service |
163
+ | --------------------------- | ------------ | --------------------------------------- |
164
+ | HUAWEI / HONOR | `hms` | HMS Push Kit (Huawei Push) |
165
+ | Xiaomi / Redmi / POCO | `mi_push` | Mi Push (Xiaomi Push) |
166
+ | OPPO / OnePlus / realme | `oppo_push` | OPPO Push (HeyTap) |
167
+ | vivo / iQOO | `vivo_push` | vivo Push |
168
+ | Meizu | `meizu_push` | Meizu Push |
169
+ | anything else | `unknown` | generic “use another provider” |
170
+
171
+ `providerInstalled` is `true` only when the provider service app was positively found on the device (`com.huawei.hwid`, `com.xiaomi.xmsf`); otherwise it is omitted. iOS always reports `provider: 'apns'`. The library never logs for you — pass the data wherever you need.
172
+
173
+ Error codes: `E_GMS_MISSING`, `E_GMS_DISABLED`, `E_GMS_UPDATE_REQUIRED`, `E_GMS_INVALID`, `E_GMS_UNAVAILABLE`, `E_FCM_TOKEN`, `E_FIREBASE_CONFIG`, `E_NOT_INITIALIZED`.
174
+
111
175
  ## Incoming messages vs taps
112
176
 
113
177
  | App state | iOS | Android (notification payload) | Android (data-only) |
package/README.ru.md CHANGED
@@ -108,6 +108,70 @@ apply plugin: "com.google.gms.google-services"
108
108
 
109
109
  Если в хост-приложении уже есть свой `FirebaseMessagingService`, обработать `com.google.firebase.MESSAGING_EVENT` может только один сервис. Оставьте сервис этой библиотеки или пробрасывайте события в него.
110
110
 
111
+ ### Если не работает на Xiaomi: `SERVICE_NOT_AVAILABLE`
112
+
113
+ Если `getCredentials()` бросает `Failed to get an FCM token: SERVICE_NOT_AVAILABLE`, значит не удалось получить FCM-токен. Сервер не получает токен и слать пуши некуда — сама доставка здесь ни при чём.
114
+
115
+ Перед запросом токена библиотека проверяет Google Play services и делает несколько попыток с паузами, но часть причин лечится только на устройстве:
116
+
117
+ - **Google-аккаунт.** На устройстве должен быть выполнен вход в Google-аккаунт. Без него FCM токен не выдаёт.
118
+ - **Google Play services.** Должны быть установлены, включены и обновлены. На китайской прошивке без GMS FCM не заработает в принципе — нужен другой провайдер пушей; см. [Определение не‑Google устройства](#определение-неgoogle-устройства-getdiagnostics-и-pushsignalerror).
119
+ - **Сеть.** Проверьте интернет, отключите VPN и приватный DNS.
120
+ - **MIUI / HyperOS.** Включите автозапуск и снимите ограничения батареи: Настройки → Приложения → «ваше приложение» → Автозапуск; Батарея → Без ограничений. Разрешите приложению фоновые данные.
121
+
122
+ Что смотреть в логах:
123
+
124
+ ```sh
125
+ adb logcat | grep -iE "SERVICE_NOT_AVAILABLE|FirebaseMessaging|PushSignal"
126
+ ```
127
+
128
+ ### Определение не‑Google устройства: `getDiagnostics()` и `PushSignalError`
129
+
130
+ Когда Google Play services не могут обслуживать FCM, библиотека это определяет и подсказывает, какой сервис использовать вместо стандартного — в тексте ошибки и в структурированном виде, чтобы причина была видна в логах React Native.
131
+
132
+ `initialize()` и `getCredentials()` отклоняются с `PushSignalError`:
133
+
134
+ ```ts
135
+ import { getCredentials, PushSignalError } from 'react-native-push-signal';
136
+
137
+ try {
138
+ const credentials = await getCredentials();
139
+ } catch (error) {
140
+ if (error instanceof PushSignalError) {
141
+ console.warn(error.code); // E_GMS_MISSING, E_FCM_TOKEN, ...
142
+ console.warn(error.message); // "... Use HMS Push Kit (Huawei Push) instead of the standard Google service."
143
+ console.warn(error.provider); // 'hms'
144
+ console.warn(error.hint); // готовая подсказка
145
+ console.warn(error.diagnostics); // полный снимок устройства и провайдера
146
+ }
147
+ }
148
+ ```
149
+
150
+ Диагностику можно получить и без запроса токена:
151
+
152
+ ```ts
153
+ import { getDiagnostics } from 'react-native-push-signal';
154
+
155
+ const diagnostics = await getDiagnostics();
156
+ // { platform, gmsAvailable, gmsStatus, manufacturer, brand, model,
157
+ // provider, providerName, providerInstalled?, hint? }
158
+ ```
159
+
160
+ Провайдер, который предлагается при недоступных GMS:
161
+
162
+ | Производитель | `provider` | Сервис |
163
+ | ------------------------ | ------------ | ---------------------------------- |
164
+ | HUAWEI / HONOR | `hms` | HMS Push Kit (Huawei Push) |
165
+ | Xiaomi / Redmi / POCO | `mi_push` | Mi Push (Xiaomi Push) |
166
+ | OPPO / OnePlus / realme | `oppo_push` | OPPO Push (HeyTap) |
167
+ | vivo / iQOO | `vivo_push` | vivo Push |
168
+ | Meizu | `meizu_push` | Meizu Push |
169
+ | остальные | `unknown` | общая подсказка «нужен другой» |
170
+
171
+ `providerInstalled` равно `true` только если сервис-приложение провайдера действительно найдено на устройстве (`com.huawei.hwid`, `com.xiaomi.xmsf`), иначе поле отсутствует. iOS всегда сообщает `provider: 'apns'`. Библиотека сама ничего не логирует — передавайте данные туда, куда нужно.
172
+
173
+ Коды ошибок: `E_GMS_MISSING`, `E_GMS_DISABLED`, `E_GMS_UPDATE_REQUIRED`, `E_GMS_INVALID`, `E_GMS_UNAVAILABLE`, `E_FCM_TOKEN`, `E_FIREBASE_CONFIG`, `E_NOT_INITIALIZED`.
174
+
111
175
  ## Входящее сообщение и тап
112
176
 
113
177
  | Состояние | iOS | Android (notification payload) | Android (data-only) |
@@ -51,5 +51,6 @@ dependencies {
51
51
  implementation "com.facebook.react:react-android"
52
52
  implementation "androidx.activity:activity-ktx:1.10.1"
53
53
  implementation "androidx.core:core-ktx:1.16.0"
54
+ implementation "com.google.android.gms:play-services-base:18.5.0"
54
55
  implementation "com.google.firebase:firebase-messaging:24.1.2"
55
56
  }
@@ -1,6 +1,13 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
2
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
3
3
 
4
+ <!-- Android 11+ package visibility: lets isPackageInstalled() confirm an
5
+ alternative push provider on the device. -->
6
+ <queries>
7
+ <package android:name="com.huawei.hwid" />
8
+ <package android:name="com.xiaomi.xmsf" />
9
+ </queries>
10
+
4
11
  <application>
5
12
  <provider
6
13
  android:name="com.pushsignal.PushSignalInitProvider"
@@ -13,3 +13,26 @@ data class AndroidFirebaseConfig(
13
13
  val current_key: String?,
14
14
  val project_number: String?,
15
15
  )
16
+
17
+ data class PushDiagnostics(
18
+ val platform: String,
19
+ val gmsAvailable: Boolean,
20
+ val gmsStatus: Int,
21
+ val manufacturer: String,
22
+ val brand: String,
23
+ val model: String,
24
+ val provider: String,
25
+ val providerName: String,
26
+ val providerInstalled: Boolean?,
27
+ val hint: String?,
28
+ )
29
+
30
+ /**
31
+ * Carries a machine-readable [code] to JavaScript, so the host app can branch on
32
+ * the exact reason (missing GMS, disabled GMS, FCM token failure, ...).
33
+ */
34
+ class PushSignalException(
35
+ val code: String,
36
+ message: String,
37
+ cause: Throwable? = null,
38
+ ) : IllegalStateException(message, cause)
@@ -7,12 +7,16 @@ import android.app.NotificationManager
7
7
  import android.app.PendingIntent
8
8
  import android.content.Context
9
9
  import android.content.Intent
10
+ import android.content.pm.PackageManager
10
11
  import android.os.Build
11
12
  import android.os.Bundle
12
13
  import android.os.Handler
13
14
  import android.os.Looper
15
+ import android.util.Log
14
16
  import androidx.activity.ComponentActivity
15
17
  import androidx.core.app.NotificationCompat
18
+ import com.google.android.gms.common.ConnectionResult
19
+ import com.google.android.gms.common.GoogleApiAvailability
16
20
  import com.google.android.gms.tasks.Tasks
17
21
  import com.google.firebase.FirebaseApp
18
22
  import com.google.firebase.FirebaseOptions
@@ -22,10 +26,28 @@ import java.util.Collections
22
26
  import java.util.UUID
23
27
  import java.util.WeakHashMap
24
28
  import java.util.concurrent.CopyOnWriteArrayList
29
+ import java.util.concurrent.TimeUnit
25
30
 
26
31
  internal object PushSignalCenter : Application.ActivityLifecycleCallbacks {
32
+ private const val TAG = "PushSignal"
27
33
  private const val EXTRA_HANDLED = "pushsignal.handled"
28
34
  private const val CHANNEL_ID = "push_signal_default"
35
+ private const val TOKEN_TIMEOUT_SECONDS = 10L
36
+ private const val TOKEN_MAX_ATTEMPTS = 5
37
+ private const val TOKEN_INITIAL_RETRY_DELAY_MS = 1_000L
38
+ private const val TOKEN_MAX_RETRY_DELAY_MS = 8_000L
39
+
40
+ private const val PROVIDER_FCM = "fcm"
41
+ private const val PROVIDER_HMS = "hms"
42
+ private const val PROVIDER_MI_PUSH = "mi_push"
43
+ private const val PROVIDER_OPPO_PUSH = "oppo_push"
44
+ private const val PROVIDER_VIVO_PUSH = "vivo_push"
45
+ private const val PROVIDER_MEIZU_PUSH = "meizu_push"
46
+ private const val PROVIDER_UNKNOWN = "unknown"
47
+
48
+ /** Service apps used to positively confirm an alternative provider on the device. */
49
+ private const val PACKAGE_HMS = "com.huawei.hwid"
50
+ private const val PACKAGE_MI_PUSH = "com.xiaomi.xmsf"
29
51
 
30
52
  private val lock = Any()
31
53
  private val mainHandler = Handler(Looper.getMainLooper())
@@ -98,7 +120,9 @@ internal object PushSignalCenter : Application.ActivityLifecycleCallbacks {
98
120
 
99
121
  fun fetchToken(): String {
100
122
  val context = application
101
- ?: throw IllegalStateException("PushSignal is not initialized")
123
+ ?: throw PushSignalException("E_NOT_INITIALIZED", "PushSignal is not initialized")
124
+
125
+ ensurePlayServices(context)
102
126
 
103
127
  try {
104
128
  if (FirebaseApp.getApps(context).isEmpty()) {
@@ -106,29 +130,229 @@ internal object PushSignalCenter : Application.ActivityLifecycleCallbacks {
106
130
  }
107
131
  FirebaseApp.getInstance()
108
132
  } catch (error: IllegalStateException) {
109
- throw IllegalStateException(
133
+ throw PushSignalException(
134
+ "E_FIREBASE_CONFIG",
110
135
  "Firebase is not configured. Call initialize({ project_id, mobilesdk_app_id, current_key, project_number }) or add google-services.json.",
111
136
  error
112
137
  )
113
138
  }
114
139
 
115
- val task = FirebaseMessaging.getInstance().token
116
- val token = try {
117
- Tasks.await(task)
118
- } catch (error: Exception) {
119
- throw IllegalStateException(
120
- "Failed to get an FCM token. Call initialize({ project_id, mobilesdk_app_id, current_key, project_number }) or add google-services.json.",
121
- error
122
- )
123
- }
140
+ val token = awaitTokenWithRetry(context)
124
141
 
125
142
  if (token.isNullOrEmpty()) {
126
- throw IllegalStateException("Firebase returned an empty FCM token")
143
+ throw PushSignalException("E_FCM_TOKEN", "Firebase returned an empty FCM token")
127
144
  }
128
145
 
129
146
  return token
130
147
  }
131
148
 
149
+ /**
150
+ * Fails fast with an actionable message when Google Play services cannot serve FCM
151
+ * (for example a China-only ROM without GMS). The message names the provider the
152
+ * device should use instead, so the cause is obvious in React Native logs.
153
+ */
154
+ private fun ensurePlayServices(context: Context) {
155
+ val status = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
156
+ if (status == ConnectionResult.SUCCESS) {
157
+ return
158
+ }
159
+
160
+ val code = when (status) {
161
+ ConnectionResult.SERVICE_MISSING -> "E_GMS_MISSING"
162
+ ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED -> "E_GMS_UPDATE_REQUIRED"
163
+ ConnectionResult.SERVICE_DISABLED -> "E_GMS_DISABLED"
164
+ ConnectionResult.SERVICE_INVALID -> "E_GMS_INVALID"
165
+ else -> "E_GMS_UNAVAILABLE"
166
+ }
167
+
168
+ val reason = when (status) {
169
+ ConnectionResult.SERVICE_MISSING ->
170
+ "Google Play services are missing on this device, so the standard Google push service (FCM) cannot be used."
171
+ ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED ->
172
+ "Google Play services are outdated. Update them in the Play Store and try again."
173
+ ConnectionResult.SERVICE_DISABLED ->
174
+ "Google Play services are disabled. Enable them in the device settings and try again."
175
+ ConnectionResult.SERVICE_INVALID ->
176
+ "Google Play services are invalid or corrupted on this device. Reinstalling them usually helps."
177
+ else ->
178
+ "Google Play services are unavailable (code $status), so the standard Google push service (FCM) cannot be used."
179
+ }
180
+
181
+ val diagnostics = diagnose(context)
182
+ val message = listOfNotNull(reason, diagnostics.hint).joinToString(" ")
183
+ Log.w(TAG, "Play services check failed ($code): $message")
184
+ throw PushSignalException(code, message)
185
+ }
186
+
187
+ /**
188
+ * Collects device and provider diagnostics without throwing. Used both by the
189
+ * `getDiagnostics` API and to enrich errors with a concrete replacement service.
190
+ */
191
+ fun diagnose(context: Context): PushDiagnostics {
192
+ val status = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
193
+ val gmsAvailable = status == ConnectionResult.SUCCESS
194
+ val provider = resolveProvider(context, gmsAvailable)
195
+ val hint = providerHint(provider, gmsAvailable)
196
+
197
+ return PushDiagnostics(
198
+ platform = "android_os",
199
+ gmsAvailable = gmsAvailable,
200
+ gmsStatus = status,
201
+ manufacturer = Build.MANUFACTURER ?: "",
202
+ brand = Build.BRAND ?: "",
203
+ model = Build.MODEL ?: "",
204
+ provider = provider.id,
205
+ providerName = provider.name,
206
+ providerInstalled = provider.installed,
207
+ hint = hint,
208
+ )
209
+ }
210
+
211
+ private data class ProviderInfo(
212
+ val id: String,
213
+ val name: String,
214
+ val installed: Boolean?,
215
+ )
216
+
217
+ /**
218
+ * Picks the push provider this device should use. Checks the standard Google
219
+ * service first, then positively confirms an installed alternative, then falls
220
+ * back to the manufacturer mapping. Class reflection cannot see another
221
+ * provider's SDK unless the host bundles it, so package checks and the
222
+ * manufacturer are the reliable signals.
223
+ */
224
+ private fun resolveProvider(context: Context, gmsAvailable: Boolean): ProviderInfo {
225
+ if (gmsAvailable) {
226
+ return ProviderInfo(PROVIDER_FCM, "Firebase Cloud Messaging (FCM)", true)
227
+ }
228
+
229
+ if (isPackageInstalled(context, PACKAGE_HMS)) {
230
+ return ProviderInfo(PROVIDER_HMS, "HMS Push Kit (Huawei Push)", true)
231
+ }
232
+ if (isPackageInstalled(context, PACKAGE_MI_PUSH)) {
233
+ return ProviderInfo(PROVIDER_MI_PUSH, "Mi Push (Xiaomi Push)", true)
234
+ }
235
+
236
+ val keys = setOf(Build.MANUFACTURER, Build.BRAND)
237
+ .filterNotNull()
238
+ .map { it.uppercase() }
239
+
240
+ return when {
241
+ keys.any { it.contains("HUAWEI") || it.contains("HONOR") } ->
242
+ ProviderInfo(PROVIDER_HMS, "HMS Push Kit (Huawei Push)", false)
243
+ keys.any { it.contains("XIAOMI") || it.contains("REDMI") || it.contains("POCO") } ->
244
+ ProviderInfo(PROVIDER_MI_PUSH, "Mi Push (Xiaomi Push)", false)
245
+ keys.any { it.contains("OPPO") || it.contains("ONEPLUS") || it.contains("REALME") } ->
246
+ ProviderInfo(PROVIDER_OPPO_PUSH, "OPPO Push (HeyTap)", false)
247
+ keys.any { it.contains("VIVO") || it.contains("IQOO") } ->
248
+ ProviderInfo(PROVIDER_VIVO_PUSH, "vivo Push", false)
249
+ keys.any { it.contains("MEIZU") } ->
250
+ ProviderInfo(PROVIDER_MEIZU_PUSH, "Meizu Push", false)
251
+ else ->
252
+ ProviderInfo(PROVIDER_UNKNOWN, "unknown", null)
253
+ }
254
+ }
255
+
256
+ private fun providerHint(provider: ProviderInfo, gmsAvailable: Boolean): String? {
257
+ if (gmsAvailable) {
258
+ return null
259
+ }
260
+ val device = listOfNotNull(Build.MANUFACTURER, Build.BRAND)
261
+ .filter { it.isNotBlank() }
262
+ .distinct()
263
+ .joinToString("/")
264
+
265
+ return when (provider.id) {
266
+ PROVIDER_HMS ->
267
+ "This device ($device) has no usable Google Play services, so FCM will not issue a token. Use HMS Push Kit (Huawei Push) instead of the standard Google service."
268
+ PROVIDER_MI_PUSH ->
269
+ "This device ($device) has no usable Google Play services, so FCM will not issue a token. Use Mi Push (Xiaomi Push) instead of the standard Google service."
270
+ PROVIDER_OPPO_PUSH ->
271
+ "This device ($device) has no usable Google Play services, so FCM will not issue a token. Use OPPO Push (HeyTap) instead of the standard Google service."
272
+ PROVIDER_VIVO_PUSH ->
273
+ "This device ($device) has no usable Google Play services, so FCM will not issue a token. Use vivo Push instead of the standard Google service."
274
+ PROVIDER_MEIZU_PUSH ->
275
+ "This device ($device) has no usable Google Play services, so FCM will not issue a token. Use Meizu Push instead of the standard Google service."
276
+ else ->
277
+ "No usable Google Play services were found and no known push provider was detected on this ROM, so FCM cannot be used. Integrate another push provider (HMS, Mi Push, OPPO/vivo/Meizu Push)."
278
+ }
279
+ }
280
+
281
+ private fun isPackageInstalled(context: Context, packageName: String): Boolean {
282
+ return try {
283
+ context.packageManager.getPackageInfo(packageName, 0)
284
+ true
285
+ } catch (_: PackageManager.NameNotFoundException) {
286
+ false
287
+ } catch (_: Exception) {
288
+ false
289
+ }
290
+ }
291
+
292
+ /**
293
+ * FCM returns SERVICE_NOT_AVAILABLE for transient conditions (no Google account yet,
294
+ * Play services still starting, flaky network). Xiaomi/MIUI devices hit this often,
295
+ * so retry with backoff before surfacing the failure.
296
+ */
297
+ private fun awaitTokenWithRetry(context: Context): String? {
298
+ var delayMs = TOKEN_INITIAL_RETRY_DELAY_MS
299
+ var lastError: Exception? = null
300
+
301
+ for (attempt in 1..TOKEN_MAX_ATTEMPTS) {
302
+ try {
303
+ return Tasks.await(
304
+ FirebaseMessaging.getInstance().token,
305
+ TOKEN_TIMEOUT_SECONDS,
306
+ TimeUnit.SECONDS
307
+ )
308
+ } catch (error: Exception) {
309
+ lastError = error
310
+ if (attempt == TOKEN_MAX_ATTEMPTS || !isRetryableTokenError(error)) {
311
+ break
312
+ }
313
+ Log.w(TAG, "FCM token attempt $attempt/$TOKEN_MAX_ATTEMPTS failed: ${error.message}. Retrying in ${delayMs}ms")
314
+ try {
315
+ Thread.sleep(delayMs)
316
+ } catch (_: InterruptedException) {
317
+ Thread.currentThread().interrupt()
318
+ break
319
+ }
320
+ delayMs = (delayMs * 2).coerceAtMost(TOKEN_MAX_RETRY_DELAY_MS)
321
+ }
322
+ }
323
+
324
+ val cause = lastError
325
+ val diagnostics = diagnose(context)
326
+ val message = listOfNotNull(
327
+ "Failed to get an FCM token: ${cause?.message ?: "unknown error"}.",
328
+ "Make sure the device is signed into a Google account, Google Play services are up to date, " +
329
+ "and the app is allowed to use background data.",
330
+ "Call initialize({ project_id, mobilesdk_app_id, current_key, project_number }) or add google-services.json.",
331
+ diagnostics.hint,
332
+ ).joinToString(" ")
333
+ Log.w(TAG, "Failed to get an FCM token: $message")
334
+ throw PushSignalException("E_FCM_TOKEN", message, cause)
335
+ }
336
+
337
+ private fun isRetryableTokenError(error: Throwable): Boolean {
338
+ var current: Throwable? = error
339
+ while (current != null) {
340
+ if (current is java.io.IOException || current is java.util.concurrent.TimeoutException) {
341
+ return true
342
+ }
343
+ val message = current.message?.uppercase() ?: ""
344
+ if (
345
+ message.contains("SERVICE_NOT_AVAILABLE") ||
346
+ message.contains("TIMEOUT") ||
347
+ message.contains("INTERNAL_SERVER_ERROR")
348
+ ) {
349
+ return true
350
+ }
351
+ current = current.cause
352
+ }
353
+ return false
354
+ }
355
+
132
356
  fun emitMessage(remoteMessage: RemoteMessage) {
133
357
  val message = remoteMessage.toPushMessage()
134
358
  runOnMain { deliverMessage(message) }
@@ -26,7 +26,8 @@ class PushSignalModule(reactContext: ReactApplicationContext) :
26
26
  if (error == null) {
27
27
  promise.resolve(null)
28
28
  } else {
29
- promise.reject("E_INIT", error.message, error)
29
+ val code = (error as? PushSignalException)?.code ?: "E_INIT"
30
+ promise.reject(code, error.message, error)
30
31
  }
31
32
  }
32
33
  }
@@ -42,7 +43,19 @@ class PushSignalModule(reactContext: ReactApplicationContext) :
42
43
  }
43
44
  promise.resolve(result)
44
45
  } catch (error: Exception) {
45
- promise.reject("E_CREDENTIALS", error.message, error)
46
+ val code = (error as? PushSignalException)?.code ?: "E_CREDENTIALS"
47
+ promise.reject(code, error.message, error)
48
+ }
49
+ }
50
+ }
51
+
52
+ override fun getDiagnostics(promise: Promise) {
53
+ executor.execute {
54
+ try {
55
+ PushSignalCenter.attach(reactApplicationContext)
56
+ promise.resolve(PushSignalCenter.diagnose(reactApplicationContext).toWritableMap())
57
+ } catch (error: Exception) {
58
+ promise.reject("E_DIAGNOSTICS", error.message, error)
46
59
  }
47
60
  }
48
61
  }
@@ -86,3 +99,18 @@ private fun PushMessage.toWritableMap(): WritableMap {
86
99
  map.putMap("data", dataMap)
87
100
  return map
88
101
  }
102
+
103
+ private fun PushDiagnostics.toWritableMap(): WritableMap {
104
+ val map = Arguments.createMap()
105
+ map.putString("platform", platform)
106
+ map.putBoolean("gmsAvailable", gmsAvailable)
107
+ map.putInt("gmsStatus", gmsStatus)
108
+ map.putString("manufacturer", manufacturer)
109
+ map.putString("brand", brand)
110
+ map.putString("model", model)
111
+ map.putString("provider", provider)
112
+ map.putString("providerName", providerName)
113
+ providerInstalled?.let { map.putBoolean("providerInstalled", it) }
114
+ hint?.let { map.putString("hint", it) }
115
+ return map
116
+ }
package/ios/PushSignal.mm CHANGED
@@ -1,5 +1,6 @@
1
1
  #import "PushSignal.h"
2
2
  #import "PushSignalCenter.h"
3
+ #import <UIKit/UIKit.h>
3
4
 
4
5
  @implementation PushSignal {
5
6
  BOOL _listening;
@@ -22,6 +23,23 @@
22
23
  }];
23
24
  }
24
25
 
26
+ - (void)getDiagnostics:(RCTPromiseResolveBlock)resolve
27
+ reject:(RCTPromiseRejectBlock)reject {
28
+ (void)reject;
29
+ UIDevice *device = [UIDevice currentDevice];
30
+ resolve(@{
31
+ @"platform": @"ios",
32
+ @"gmsAvailable": @YES,
33
+ @"gmsStatus": @0,
34
+ @"manufacturer": @"Apple",
35
+ @"brand": @"Apple",
36
+ @"model": device.model ?: @"iOS device",
37
+ @"provider": @"apns",
38
+ @"providerName": @"Apple Push Notification service (APNs)",
39
+ @"providerInstalled": @YES,
40
+ });
41
+ }
42
+
25
43
  - (void)startListening {
26
44
  if (_listening) {
27
45
  return;
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativePushSignal.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAuBlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,YAAY,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativePushSignal.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AA2ClD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,YAAY,CAAC","ignoreList":[]}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Error thrown by `initialize` and `getCredentials`.
5
+ *
6
+ * The message already contains a human-readable, actionable hint when the
7
+ * standard Google service (FCM/GMS) cannot be used. Structured details are
8
+ * available on the instance so the host app can decide how to log or report them.
9
+ */
10
+ export class PushSignalError extends Error {
11
+ /** Machine-readable code, e.g. `E_GMS_MISSING` or `E_FCM_TOKEN`. */
12
+
13
+ /** Suggested alternative push provider, when the standard service is unusable. */
14
+
15
+ /** Actionable sentence: which service to use instead of the standard one. */
16
+
17
+ /** Full device/provider diagnostics if they could be collected. */
18
+
19
+ /** Original error from the native layer. */
20
+
21
+ constructor(code, message, cause) {
22
+ super(message);
23
+ this.name = 'PushSignalError';
24
+ this.code = code;
25
+ this.cause = cause;
26
+ Object.setPrototypeOf(this, PushSignalError.prototype);
27
+ }
28
+ }
29
+ //# sourceMappingURL=PushSignalError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["PushSignalError","Error","constructor","code","message","cause","name","Object","setPrototypeOf","prototype"],"sourceRoot":"../../src","sources":["PushSignalError.ts"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,eAAe,SAASC,KAAK,CAAC;EACzC;;EAEA;;EAEA;;EAEA;;EAEA;;EAGAC,WAAWA,CAACC,IAAY,EAAEC,OAAe,EAAEC,KAAe,EAAE;IAC1D,KAAK,CAACD,OAAO,CAAC;IACd,IAAI,CAACE,IAAI,GAAG,iBAAiB;IAC7B,IAAI,CAACH,IAAI,GAAGA,IAAI;IAChB,IAAI,CAACE,KAAK,GAAGA,KAAK;IAClBE,MAAM,CAACC,cAAc,CAAC,IAAI,EAAER,eAAe,CAACS,SAAS,CAAC;EACxD;AACF","ignoreList":[]}
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
2
 
3
- export { getCredentials, initialize, onMessage, onNotificationPress } from './pushSignal';
3
+ export { PushSignalError } from "./PushSignalError.js";
4
+ export { getCredentials, getDiagnostics, initialize, onMessage, onNotificationPress } from './pushSignal';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["getCredentials","initialize","onMessage","onNotificationPress"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAQA,SACEA,cAAc,EACdC,UAAU,EACVC,SAAS,EACTC,mBAAmB,QACd,cAAc","ignoreList":[]}
1
+ {"version":3,"names":["PushSignalError","getCredentials","getDiagnostics","initialize","onMessage","onNotificationPress"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAUA,SAASA,eAAe,QAAQ,sBAAmB;AACnD,SACEC,cAAc,EACdC,cAAc,EACdC,UAAU,EACVC,SAAS,EACTC,mBAAmB,QACd,cAAc","ignoreList":[]}
@@ -4,6 +4,9 @@ export async function initialize(_config = {}) {}
4
4
  export async function getCredentials() {
5
5
  throw new Error('Push credentials are not supported on web');
6
6
  }
7
+ export async function getDiagnostics() {
8
+ throw new Error('Push diagnostics are not supported on web');
9
+ }
7
10
  export function onMessage(_listener) {
8
11
  return () => {};
9
12
  }
@@ -1 +1 @@
1
- {"version":3,"names":["initialize","_config","getCredentials","Error","onMessage","_listener","onNotificationPress"],"sourceRoot":"../../src","sources":["pushSignal.tsx"],"mappings":";;AAOA,OAAO,eAAeA,UAAUA,CAC9BC,OAA8B,GAAG,CAAC,CAAC,EACpB,CAAC;AAElB,OAAO,eAAeC,cAAcA,CAAA,EAA6B;EAC/D,MAAM,IAAIC,KAAK,CAAC,2CAA2C,CAAC;AAC9D;AAEA,OAAO,SAASC,SAASA,CAACC,SAA4B,EAAc;EAClE,OAAO,MAAM,CAAC,CAAC;AACjB;AAEA,OAAO,SAASC,mBAAmBA,CACjCD,SAAyC,EAC7B;EACZ,OAAO,MAAM,CAAC,CAAC;AACjB","ignoreList":[]}
1
+ {"version":3,"names":["initialize","_config","getCredentials","Error","getDiagnostics","onMessage","_listener","onNotificationPress"],"sourceRoot":"../../src","sources":["pushSignal.tsx"],"mappings":";;AAQA,OAAO,eAAeA,UAAUA,CAC9BC,OAA8B,GAAG,CAAC,CAAC,EACpB,CAAC;AAElB,OAAO,eAAeC,cAAcA,CAAA,EAA6B;EAC/D,MAAM,IAAIC,KAAK,CAAC,2CAA2C,CAAC;AAC9D;AAEA,OAAO,eAAeC,cAAcA,CAAA,EAA6B;EAC/D,MAAM,IAAID,KAAK,CAAC,2CAA2C,CAAC;AAC9D;AAEA,OAAO,SAASE,SAASA,CAACC,SAA4B,EAAc;EAClE,OAAO,MAAM,CAAC,CAAC;AACjB;AAEA,OAAO,SAASC,mBAAmBA,CACjCD,SAAyC,EAC7B;EACZ,OAAO,MAAM,CAAC,CAAC;AACjB","ignoreList":[]}
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  import NativePushSignal from "./NativePushSignal.js";
4
+ import { PushSignalError } from "./PushSignalError.js";
4
5
  const messageListeners = new Set();
5
6
  const pressListeners = new Set();
6
7
  let nativeCallbacksBound = false;
@@ -28,6 +29,40 @@ function normalizeCredentials(raw) {
28
29
  environment: raw.environment
29
30
  };
30
31
  }
32
+ function normalizeDiagnostics(raw) {
33
+ return {
34
+ platform: raw.platform,
35
+ gmsAvailable: raw.gmsAvailable,
36
+ gmsStatus: raw.gmsStatus,
37
+ manufacturer: raw.manufacturer,
38
+ brand: raw.brand,
39
+ model: raw.model,
40
+ provider: raw.provider,
41
+ providerName: raw.providerName,
42
+ providerInstalled: raw.providerInstalled ?? undefined,
43
+ hint: raw.hint ?? undefined
44
+ };
45
+ }
46
+ function toPushSignalError(error, fallbackCode) {
47
+ const candidate = error;
48
+ return new PushSignalError(candidate?.code ?? fallbackCode, candidate?.message ?? 'PushSignal request failed', error);
49
+ }
50
+
51
+ /**
52
+ * Attaches structured diagnostics to an error on a best-effort basis. Failing to
53
+ * collect them must never hide the original, already actionable error.
54
+ */
55
+ async function enrichError(error) {
56
+ try {
57
+ const diagnostics = normalizeDiagnostics(await NativePushSignal.getDiagnostics());
58
+ error.diagnostics = diagnostics;
59
+ error.provider = diagnostics.provider;
60
+ error.hint = diagnostics.hint;
61
+ } catch {
62
+ // Diagnostics are optional; keep the original error.
63
+ }
64
+ return error;
65
+ }
31
66
  function bindNativeCallbacks() {
32
67
  if (nativeCallbacksBound) {
33
68
  return;
@@ -49,11 +84,23 @@ function bindNativeCallbacks() {
49
84
  });
50
85
  NativePushSignal.startListening();
51
86
  }
52
- export function initialize(config = {}) {
53
- return NativePushSignal.initialize(config);
87
+ export async function initialize(config = {}) {
88
+ try {
89
+ await NativePushSignal.initialize(config);
90
+ } catch (error) {
91
+ throw await enrichError(toPushSignalError(error, 'E_INIT'));
92
+ }
93
+ }
94
+ export async function getCredentials() {
95
+ try {
96
+ const raw = await NativePushSignal.getCredentials();
97
+ return normalizeCredentials(raw);
98
+ } catch (error) {
99
+ throw await enrichError(toPushSignalError(error, 'E_CREDENTIALS'));
100
+ }
54
101
  }
55
- export function getCredentials() {
56
- return NativePushSignal.getCredentials().then(normalizeCredentials);
102
+ export async function getDiagnostics() {
103
+ return normalizeDiagnostics(await NativePushSignal.getDiagnostics());
57
104
  }
58
105
  export function onMessage(listener) {
59
106
  bindNativeCallbacks();
@@ -1 +1 @@
1
- {"version":3,"names":["NativePushSignal","messageListeners","Set","pressListeners","nativeCallbacksBound","normalizeMessage","raw","data","key","value","Object","entries","String","id","title","body","normalizeCredentials","platform","token","environment","bindNativeCallbacks","onMessage","message","listener","Promise","resolve","then","undefined","onNotificationPress","forEach","startListening","initialize","config","getCredentials","add","delete"],"sourceRoot":"../../src","sources":["pushSignal.native.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["NativePushSignal","PushSignalError","messageListeners","Set","pressListeners","nativeCallbacksBound","normalizeMessage","raw","data","key","value","Object","entries","String","id","title","body","normalizeCredentials","platform","token","environment","normalizeDiagnostics","gmsAvailable","gmsStatus","manufacturer","brand","model","provider","providerName","providerInstalled","undefined","hint","toPushSignalError","error","fallbackCode","candidate","code","message","enrichError","diagnostics","getDiagnostics","bindNativeCallbacks","onMessage","listener","Promise","resolve","then","onNotificationPress","forEach","startListening","initialize","config","getCredentials","add","delete"],"sourceRoot":"../../src","sources":["pushSignal.native.tsx"],"mappings":"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","ignoreList":[]}
@@ -10,9 +10,28 @@ export type NativePushCredentials = {
10
10
  token: string;
11
11
  environment?: string;
12
12
  };
13
+ export type NativePushDiagnostics = {
14
+ platform: string;
15
+ /** True when Google Play services can serve FCM; false on a ROM without GMS. */
16
+ gmsAvailable: boolean;
17
+ /** Raw GoogleApiAvailability status code. 0 means SUCCESS. */
18
+ gmsStatus: number;
19
+ manufacturer: string;
20
+ brand: string;
21
+ model: string;
22
+ /** Machine-readable provider id: fcm, hms, mi_push, oppo_push, vivo_push, meizu_push, apns, unknown. */
23
+ provider: string;
24
+ /** Human-readable provider name, e.g. "HMS Push Kit". */
25
+ providerName: string;
26
+ /** True when the provider service app was found on the device. */
27
+ providerInstalled: boolean;
28
+ /** Actionable sentence explaining which service to use instead of the standard one. */
29
+ hint?: string;
30
+ };
13
31
  export interface Spec extends TurboModule {
14
32
  initialize(config: Object): Promise<void>;
15
33
  getCredentials(): Promise<NativePushCredentials>;
34
+ getDiagnostics(): Promise<NativePushDiagnostics>;
16
35
  startListening(): void;
17
36
  readonly onMessage: CodegenTypes.EventEmitter<NativePushMessage>;
18
37
  readonly onNotificationPress: CodegenTypes.EventEmitter<NativePushMessage>;
@@ -1 +1 @@
1
- {"version":3,"file":"NativePushSignal.d.ts","sourceRoot":"","sources":["../../../src/NativePushSignal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG9D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,cAAc,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjD,cAAc,IAAI,IAAI,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;IACjE,QAAQ,CAAC,mBAAmB,EAAE,YAAY,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;CAC5E;;AAED,wBAAoE"}
1
+ {"version":3,"file":"NativePushSignal.d.ts","sourceRoot":"","sources":["../../../src/NativePushSignal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAG9D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,YAAY,EAAE,OAAO,CAAC;IACtB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,wGAAwG;IACxG,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,iBAAiB,EAAE,OAAO,CAAC;IAC3B,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,cAAc,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjD,cAAc,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACjD,cAAc,IAAI,IAAI,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;IACjE,QAAQ,CAAC,mBAAmB,EAAE,YAAY,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;CAC5E;;AAED,wBAAoE"}
@@ -0,0 +1,22 @@
1
+ import type { PushDiagnostics, PushProvider } from './types.js';
2
+ /**
3
+ * Error thrown by `initialize` and `getCredentials`.
4
+ *
5
+ * The message already contains a human-readable, actionable hint when the
6
+ * standard Google service (FCM/GMS) cannot be used. Structured details are
7
+ * available on the instance so the host app can decide how to log or report them.
8
+ */
9
+ export declare class PushSignalError extends Error {
10
+ /** Machine-readable code, e.g. `E_GMS_MISSING` or `E_FCM_TOKEN`. */
11
+ readonly code: string;
12
+ /** Suggested alternative push provider, when the standard service is unusable. */
13
+ provider?: PushProvider;
14
+ /** Actionable sentence: which service to use instead of the standard one. */
15
+ hint?: string;
16
+ /** Full device/provider diagnostics if they could be collected. */
17
+ diagnostics?: PushDiagnostics;
18
+ /** Original error from the native layer. */
19
+ readonly cause?: unknown;
20
+ constructor(code: string, message: string, cause?: unknown);
21
+ }
22
+ //# sourceMappingURL=PushSignalError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PushSignalError.d.ts","sourceRoot":"","sources":["../../../src/PushSignalError.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAS,CAAC;AAE7D;;;;;;GAMG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,4CAA4C;IAC5C,SAAkB,KAAK,CAAC,EAAE,OAAO,CAAC;gBAEtB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAO3D"}
@@ -1,3 +1,4 @@
1
- export type { AndroidFirebaseConfig, OnMessageListener, PushCredentials, PushEnvironment, PushMessage, PushPlatform, } from './types.js';
2
- export { getCredentials, initialize, onMessage, onNotificationPress, } from './pushSignal';
1
+ export type { AndroidFirebaseConfig, OnMessageListener, PushCredentials, PushDiagnostics, PushEnvironment, PushMessage, PushPlatform, PushProvider, } from './types.js';
2
+ export { PushSignalError } from './PushSignalError.js';
3
+ export { getCredentials, getDiagnostics, initialize, onMessage, onNotificationPress, } from './pushSignal';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,WAAW,EACX,YAAY,GACb,MAAM,YAAS,CAAC;AACjB,OAAO,EACL,cAAc,EACd,UAAU,EACV,SAAS,EACT,mBAAmB,GACpB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,eAAe,EACf,WAAW,EACX,YAAY,EACZ,YAAY,GACb,MAAM,YAAS,CAAC;AACjB,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAmB,CAAC;AACpD,OAAO,EACL,cAAc,EACd,cAAc,EACd,UAAU,EACV,SAAS,EACT,mBAAmB,GACpB,MAAM,cAAc,CAAC"}
@@ -1,6 +1,7 @@
1
- import type { AndroidFirebaseConfig, OnMessageListener, PushCredentials, PushMessage } from './types.js';
1
+ import type { AndroidFirebaseConfig, OnMessageListener, PushCredentials, PushDiagnostics, PushMessage } from './types.js';
2
2
  export declare function initialize(_config?: AndroidFirebaseConfig): Promise<void>;
3
3
  export declare function getCredentials(): Promise<PushCredentials>;
4
+ export declare function getDiagnostics(): Promise<PushDiagnostics>;
4
5
  export declare function onMessage(_listener: OnMessageListener): () => void;
5
6
  export declare function onNotificationPress(_listener: (message: PushMessage) => void): () => void;
6
7
  //# sourceMappingURL=pushSignal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pushSignal.d.ts","sourceRoot":"","sources":["../../../src/pushSignal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,WAAW,EACZ,MAAM,YAAS,CAAC;AAEjB,wBAAsB,UAAU,CAC9B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CAAG;AAEnB,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,CAAC,CAE/D;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAElE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GACxC,MAAM,IAAI,CAEZ"}
1
+ {"version":3,"file":"pushSignal.d.ts","sourceRoot":"","sources":["../../../src/pushSignal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,WAAW,EACZ,MAAM,YAAS,CAAC;AAEjB,wBAAsB,UAAU,CAC9B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CAAG;AAEnB,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,CAAC,CAE/D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,CAAC,CAE/D;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAElE;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GACxC,MAAM,IAAI,CAEZ"}
@@ -1,6 +1,7 @@
1
- import type { AndroidFirebaseConfig, OnMessageListener, PushCredentials, PushMessage } from './types.js';
1
+ import type { AndroidFirebaseConfig, OnMessageListener, PushCredentials, PushDiagnostics, PushMessage } from './types.js';
2
2
  export declare function initialize(config?: AndroidFirebaseConfig): Promise<void>;
3
3
  export declare function getCredentials(): Promise<PushCredentials>;
4
+ export declare function getDiagnostics(): Promise<PushDiagnostics>;
4
5
  export declare function onMessage(listener: OnMessageListener): () => void;
5
6
  export declare function onNotificationPress(listener: (message: PushMessage) => void): () => void;
6
7
  //# sourceMappingURL=pushSignal.native.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pushSignal.native.d.ts","sourceRoot":"","sources":["../../../src/pushSignal.native.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EAEf,WAAW,EAEZ,MAAM,YAAS,CAAC;AAyEjB,wBAAgB,UAAU,CAAC,MAAM,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,wBAAgB,cAAc,IAAI,OAAO,CAAC,eAAe,CAAC,CAEzD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAMjE;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GACvC,MAAM,IAAI,CAMZ"}
1
+ {"version":3,"file":"pushSignal.native.d.ts","sourceRoot":"","sources":["../../../src/pushSignal.native.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,eAAe,EAEf,WAAW,EAGZ,MAAM,YAAS,CAAC;AAsHjB,wBAAsB,UAAU,CAC9B,MAAM,GAAE,qBAA0B,GACjC,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,CAAC,CAO/D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,CAAC,CAE/D;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAMjE;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GACvC,MAAM,IAAI,CAMZ"}
@@ -13,6 +13,24 @@ export interface PushMessage {
13
13
  data: Record<string, string>;
14
14
  }
15
15
  export type OnMessageListener = (message: PushMessage) => void | Promise<void>;
16
+ export type PushProvider = 'fcm' | 'hms' | 'mi_push' | 'oppo_push' | 'vivo_push' | 'meizu_push' | 'apns' | 'unknown';
17
+ export interface PushDiagnostics {
18
+ platform: PushPlatform;
19
+ /** True when Google Play services can serve FCM; false on a ROM without GMS. */
20
+ gmsAvailable: boolean;
21
+ /** Raw GoogleApiAvailability status code. 0 means SUCCESS. */
22
+ gmsStatus: number;
23
+ manufacturer: string;
24
+ brand: string;
25
+ model: string;
26
+ provider: PushProvider;
27
+ /** Human-readable provider name, e.g. "HMS Push Kit". */
28
+ providerName: string;
29
+ /** True only when the provider service app was positively found on the device. */
30
+ providerInstalled?: boolean;
31
+ /** Actionable sentence explaining which service to use instead of the standard one. */
32
+ hint?: string;
33
+ }
16
34
  export interface AndroidFirebaseConfig {
17
35
  project_id?: string;
18
36
  mobilesdk_app_id?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,YAAY,CAAC;AAChD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,YAAY,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/E,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,YAAY,CAAC;AAChD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,YAAY,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE/E,MAAM,MAAM,YAAY,GACpB,KAAK,GACL,KAAK,GACL,SAAS,GACT,WAAW,GACX,WAAW,GACX,YAAY,GACZ,MAAM,GACN,SAAS,CAAC;AAEd,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,gFAAgF;IAChF,YAAY,EAAE,OAAO,CAAC;IACtB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-native-push-signal",
3
- "version": "0.1.8",
4
- "description": "Push",
3
+ "version": "0.1.10",
4
+ "description": "React Native push notifications with direct APNs and FCM integration — get device tokens for your server and listen for incoming messages and taps",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
7
7
  "exports": {
@@ -45,18 +45,28 @@
45
45
  "keywords": [
46
46
  "react-native",
47
47
  "ios",
48
- "android"
48
+ "android",
49
+ "push",
50
+ "push-notifications",
51
+ "notifications",
52
+ "apns",
53
+ "fcm",
54
+ "firebase",
55
+ "firebase-cloud-messaging",
56
+ "device-token",
57
+ "push-token",
58
+ "remote-notifications"
49
59
  ],
50
60
  "repository": {
51
61
  "type": "git",
52
- "url": "git+https://antonseagull.com.git"
62
+ "url": "git+https://github.com/AntonSeagull/react-native-push-signal.git"
53
63
  },
54
64
  "author": "AntonSeagull <hi@antonseagull.com> (https://antonseagull.com)",
55
65
  "license": "MIT",
56
66
  "bugs": {
57
- "url": "https://antonseagull.com/issues"
67
+ "url": "https://github.com/AntonSeagull/react-native-push-signal/issues"
58
68
  },
59
- "homepage": "https://antonseagull.com#readme",
69
+ "homepage": "https://github.com/AntonSeagull/react-native-push-signal#readme",
60
70
  "publishConfig": {
61
71
  "registry": "https://registry.npmjs.org/"
62
72
  },
@@ -14,9 +14,29 @@ export type NativePushCredentials = {
14
14
  environment?: string;
15
15
  };
16
16
 
17
+ export type NativePushDiagnostics = {
18
+ platform: string;
19
+ /** True when Google Play services can serve FCM; false on a ROM without GMS. */
20
+ gmsAvailable: boolean;
21
+ /** Raw GoogleApiAvailability status code. 0 means SUCCESS. */
22
+ gmsStatus: number;
23
+ manufacturer: string;
24
+ brand: string;
25
+ model: string;
26
+ /** Machine-readable provider id: fcm, hms, mi_push, oppo_push, vivo_push, meizu_push, apns, unknown. */
27
+ provider: string;
28
+ /** Human-readable provider name, e.g. "HMS Push Kit". */
29
+ providerName: string;
30
+ /** True when the provider service app was found on the device. */
31
+ providerInstalled: boolean;
32
+ /** Actionable sentence explaining which service to use instead of the standard one. */
33
+ hint?: string;
34
+ };
35
+
17
36
  export interface Spec extends TurboModule {
18
37
  initialize(config: Object): Promise<void>;
19
38
  getCredentials(): Promise<NativePushCredentials>;
39
+ getDiagnostics(): Promise<NativePushDiagnostics>;
20
40
  startListening(): void;
21
41
  readonly onMessage: CodegenTypes.EventEmitter<NativePushMessage>;
22
42
  readonly onNotificationPress: CodegenTypes.EventEmitter<NativePushMessage>;
@@ -0,0 +1,29 @@
1
+ import type { PushDiagnostics, PushProvider } from './types';
2
+
3
+ /**
4
+ * Error thrown by `initialize` and `getCredentials`.
5
+ *
6
+ * The message already contains a human-readable, actionable hint when the
7
+ * standard Google service (FCM/GMS) cannot be used. Structured details are
8
+ * available on the instance so the host app can decide how to log or report them.
9
+ */
10
+ export class PushSignalError extends Error {
11
+ /** Machine-readable code, e.g. `E_GMS_MISSING` or `E_FCM_TOKEN`. */
12
+ readonly code: string;
13
+ /** Suggested alternative push provider, when the standard service is unusable. */
14
+ provider?: PushProvider;
15
+ /** Actionable sentence: which service to use instead of the standard one. */
16
+ hint?: string;
17
+ /** Full device/provider diagnostics if they could be collected. */
18
+ diagnostics?: PushDiagnostics;
19
+ /** Original error from the native layer. */
20
+ override readonly cause?: unknown;
21
+
22
+ constructor(code: string, message: string, cause?: unknown) {
23
+ super(message);
24
+ this.name = 'PushSignalError';
25
+ this.code = code;
26
+ this.cause = cause;
27
+ Object.setPrototypeOf(this, PushSignalError.prototype);
28
+ }
29
+ }
package/src/index.tsx CHANGED
@@ -2,12 +2,16 @@ export type {
2
2
  AndroidFirebaseConfig,
3
3
  OnMessageListener,
4
4
  PushCredentials,
5
+ PushDiagnostics,
5
6
  PushEnvironment,
6
7
  PushMessage,
7
8
  PushPlatform,
9
+ PushProvider,
8
10
  } from './types';
11
+ export { PushSignalError } from './PushSignalError';
9
12
  export {
10
13
  getCredentials,
14
+ getDiagnostics,
11
15
  initialize,
12
16
  onMessage,
13
17
  onNotificationPress,
@@ -1,11 +1,15 @@
1
1
  import NativePushSignal from './NativePushSignal';
2
+ import type { NativePushDiagnostics } from './NativePushSignal';
3
+ import { PushSignalError } from './PushSignalError';
2
4
  import type {
3
5
  AndroidFirebaseConfig,
4
6
  OnMessageListener,
5
7
  PushCredentials,
8
+ PushDiagnostics,
6
9
  PushEnvironment,
7
10
  PushMessage,
8
11
  PushPlatform,
12
+ PushProvider,
9
13
  } from './types';
10
14
 
11
15
  const messageListeners = new Set<OnMessageListener>();
@@ -50,6 +54,51 @@ function normalizeCredentials(raw: {
50
54
  };
51
55
  }
52
56
 
57
+ function normalizeDiagnostics(raw: NativePushDiagnostics): PushDiagnostics {
58
+ return {
59
+ platform: raw.platform as PushPlatform,
60
+ gmsAvailable: raw.gmsAvailable,
61
+ gmsStatus: raw.gmsStatus,
62
+ manufacturer: raw.manufacturer,
63
+ brand: raw.brand,
64
+ model: raw.model,
65
+ provider: raw.provider as PushProvider,
66
+ providerName: raw.providerName,
67
+ providerInstalled: raw.providerInstalled ?? undefined,
68
+ hint: raw.hint ?? undefined,
69
+ };
70
+ }
71
+
72
+ function toPushSignalError(
73
+ error: unknown,
74
+ fallbackCode: string
75
+ ): PushSignalError {
76
+ const candidate = error as { code?: string; message?: string } | undefined;
77
+ return new PushSignalError(
78
+ candidate?.code ?? fallbackCode,
79
+ candidate?.message ?? 'PushSignal request failed',
80
+ error
81
+ );
82
+ }
83
+
84
+ /**
85
+ * Attaches structured diagnostics to an error on a best-effort basis. Failing to
86
+ * collect them must never hide the original, already actionable error.
87
+ */
88
+ async function enrichError(error: PushSignalError): Promise<PushSignalError> {
89
+ try {
90
+ const diagnostics = normalizeDiagnostics(
91
+ await NativePushSignal.getDiagnostics()
92
+ );
93
+ error.diagnostics = diagnostics;
94
+ error.provider = diagnostics.provider;
95
+ error.hint = diagnostics.hint;
96
+ } catch {
97
+ // Diagnostics are optional; keep the original error.
98
+ }
99
+ return error;
100
+ }
101
+
53
102
  function bindNativeCallbacks() {
54
103
  if (nativeCallbacksBound) {
55
104
  return;
@@ -79,12 +128,27 @@ function bindNativeCallbacks() {
79
128
  NativePushSignal.startListening();
80
129
  }
81
130
 
82
- export function initialize(config: AndroidFirebaseConfig = {}): Promise<void> {
83
- return NativePushSignal.initialize(config);
131
+ export async function initialize(
132
+ config: AndroidFirebaseConfig = {}
133
+ ): Promise<void> {
134
+ try {
135
+ await NativePushSignal.initialize(config);
136
+ } catch (error) {
137
+ throw await enrichError(toPushSignalError(error, 'E_INIT'));
138
+ }
139
+ }
140
+
141
+ export async function getCredentials(): Promise<PushCredentials> {
142
+ try {
143
+ const raw = await NativePushSignal.getCredentials();
144
+ return normalizeCredentials(raw);
145
+ } catch (error) {
146
+ throw await enrichError(toPushSignalError(error, 'E_CREDENTIALS'));
147
+ }
84
148
  }
85
149
 
86
- export function getCredentials(): Promise<PushCredentials> {
87
- return NativePushSignal.getCredentials().then(normalizeCredentials);
150
+ export async function getDiagnostics(): Promise<PushDiagnostics> {
151
+ return normalizeDiagnostics(await NativePushSignal.getDiagnostics());
88
152
  }
89
153
 
90
154
  export function onMessage(listener: OnMessageListener): () => void {
@@ -2,6 +2,7 @@ import type {
2
2
  AndroidFirebaseConfig,
3
3
  OnMessageListener,
4
4
  PushCredentials,
5
+ PushDiagnostics,
5
6
  PushMessage,
6
7
  } from './types';
7
8
 
@@ -13,6 +14,10 @@ export async function getCredentials(): Promise<PushCredentials> {
13
14
  throw new Error('Push credentials are not supported on web');
14
15
  }
15
16
 
17
+ export async function getDiagnostics(): Promise<PushDiagnostics> {
18
+ throw new Error('Push diagnostics are not supported on web');
19
+ }
20
+
16
21
  export function onMessage(_listener: OnMessageListener): () => void {
17
22
  return () => {};
18
23
  }
package/src/types.ts CHANGED
@@ -17,6 +17,34 @@ export interface PushMessage {
17
17
 
18
18
  export type OnMessageListener = (message: PushMessage) => void | Promise<void>;
19
19
 
20
+ export type PushProvider =
21
+ | 'fcm'
22
+ | 'hms'
23
+ | 'mi_push'
24
+ | 'oppo_push'
25
+ | 'vivo_push'
26
+ | 'meizu_push'
27
+ | 'apns'
28
+ | 'unknown';
29
+
30
+ export interface PushDiagnostics {
31
+ platform: PushPlatform;
32
+ /** True when Google Play services can serve FCM; false on a ROM without GMS. */
33
+ gmsAvailable: boolean;
34
+ /** Raw GoogleApiAvailability status code. 0 means SUCCESS. */
35
+ gmsStatus: number;
36
+ manufacturer: string;
37
+ brand: string;
38
+ model: string;
39
+ provider: PushProvider;
40
+ /** Human-readable provider name, e.g. "HMS Push Kit". */
41
+ providerName: string;
42
+ /** True only when the provider service app was positively found on the device. */
43
+ providerInstalled?: boolean;
44
+ /** Actionable sentence explaining which service to use instead of the standard one. */
45
+ hint?: string;
46
+ }
47
+
20
48
  export interface AndroidFirebaseConfig {
21
49
  project_id?: string;
22
50
  mobilesdk_app_id?: string;