capacitor-push-signal 0.0.3 → 0.0.4

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 CHANGED
@@ -111,6 +111,53 @@ Without `initialize(...)` or that file, `getCredentials()` throws.
111
111
 
112
112
  If the host app already declares its own `FirebaseMessagingService`, only one service can handle `com.google.firebase.MESSAGING_EVENT`. Prefer this plugin’s service or forward events into it.
113
113
 
114
+ ### Detecting a non-Google device: `getDiagnostics()` and `PushSignalError`
115
+
116
+ When Google Play services cannot serve FCM, the plugin 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 JS logs. Before requesting a token it checks Google Play services and retries up to 5 times with backoff and a 10 s per-attempt timeout.
117
+
118
+ `initialize()` and `getCredentials()` reject with `PushSignalError`:
119
+
120
+ ```ts
121
+ import { getCredentials, PushSignalError } from 'capacitor-push-signal';
122
+
123
+ try {
124
+ const credentials = await getCredentials();
125
+ } catch (error) {
126
+ if (error instanceof PushSignalError) {
127
+ console.warn(error.code); // E_GMS_MISSING, E_FCM_TOKEN, ...
128
+ console.warn(error.message); // "... Use HMS Push Kit (Huawei Push) instead of the standard Google service."
129
+ console.warn(error.provider); // 'hms'
130
+ console.warn(error.hint); // actionable sentence
131
+ console.warn(error.diagnostics); // full device/provider snapshot
132
+ }
133
+ }
134
+ ```
135
+
136
+ You can also inspect diagnostics without requesting a token:
137
+
138
+ ```ts
139
+ import { getDiagnostics } from 'capacitor-push-signal';
140
+
141
+ const diagnostics = await getDiagnostics();
142
+ // { platform, gmsAvailable, gmsStatus, manufacturer, brand, model,
143
+ // provider, providerName, providerInstalled?, hint? }
144
+ ```
145
+
146
+ Provider suggested when GMS is unavailable:
147
+
148
+ | Manufacturer | `provider` | Service |
149
+ | ----------------------- | ------------ | ------------------------------ |
150
+ | HUAWEI / HONOR | `hms` | HMS Push Kit (Huawei Push) |
151
+ | Xiaomi / Redmi / POCO | `mi_push` | Mi Push (Xiaomi Push) |
152
+ | OPPO / OnePlus / realme | `oppo_push` | OPPO Push (HeyTap) |
153
+ | vivo / iQOO | `vivo_push` | vivo Push |
154
+ | Meizu | `meizu_push` | Meizu Push |
155
+ | anything else | `unknown` | generic “use another provider” |
156
+
157
+ `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 plugin never logs for you — pass the data wherever you need.
158
+
159
+ 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`, `E_CREDENTIALS`, `E_INIT`, `E_DIAGNOSTICS`.
160
+
114
161
  ## Incoming messages vs taps
115
162
 
116
163
  | App state | iOS | Android (notification payload) | Android (data-only) |
@@ -122,6 +169,66 @@ If the host app already declares its own `FirebaseMessagingService`, only one se
122
169
  - `onNotificationPress` — the user opened the notification, including a cold start.
123
170
  - On iOS, a visible push received in the background or when the app is killed is delivered on tap, not through `onMessage`. That is an OS limit.
124
171
 
172
+ ## Rich / image notifications
173
+
174
+ `PushMessage.imageUrl` is resolved from (first match wins):
175
+
176
+ 1. FCM `notification.image` / `RemoteMessage.notification.imageUrl` (Android)
177
+ 2. `data.image`
178
+ 3. `data.imageUrl`
179
+ 4. iOS also: `fcm_options.image` (FCM → APNs)
180
+
181
+ | App state | iOS | Android |
182
+ | --- | --- | --- |
183
+ | Foreground | Needs a host **Notification Service Extension** (NSE) to attach the image; the plugin forwards `imageUrl` to JS | Plugin downloads the image and shows `BigPictureStyle` in the tray |
184
+ | Background / killed | NSE attaches the image when `mutable-content: 1` is set | FCM system tray usually shows `notification.image` without plugin code |
185
+
186
+ ### Android payload (FCM HTTP v1)
187
+
188
+ Put the image on both `notification` (background tray) and `data` (foreground path / JS):
189
+
190
+ ```json
191
+ {
192
+ "message": {
193
+ "token": "...",
194
+ "notification": {
195
+ "title": "Hello",
196
+ "body": "With image",
197
+ "image": "https://example.com/photo.jpg"
198
+ },
199
+ "data": {
200
+ "image": "https://example.com/photo.jpg"
201
+ }
202
+ }
203
+ }
204
+ ```
205
+
206
+ ### iOS payload (APNs)
207
+
208
+ ```json
209
+ {
210
+ "aps": {
211
+ "alert": { "title": "Hello", "body": "With image" },
212
+ "mutable-content": 1,
213
+ "sound": "default"
214
+ },
215
+ "image": "https://example.com/photo.jpg"
216
+ }
217
+ ```
218
+
219
+ ### iOS Notification Service Extension
220
+
221
+ iOS never downloads remote images for you. Add an NSE to the **host** Capacitor app (not installed by `npx cap sync`).
222
+
223
+ 1. Xcode → **File → New → Target → Notification Service Extension**.
224
+ 2. Copy [`example-app/ios/App/NotificationService/NotificationService.swift`](example-app/ios/App/NotificationService/NotificationService.swift) into the extension (see also that folder’s README).
225
+ 3. Keep the extension bundle id under your app id (e.g. `com.example.app.NotificationService`).
226
+ 4. Send pushes with `mutable-content: 1` and `image` / `imageUrl`.
227
+
228
+ The example app already wires a **NotificationService** target for reference.
229
+
230
+ If image download fails, both platforms fall back to a normal title/body notification.
231
+
125
232
  ## Web
126
233
 
127
234
  `initialize()` resolves. `getCredentials()` throws. Listeners (`onMessage`, `onNotificationPress`) are no-ops.
@@ -132,6 +239,7 @@ If the host app already declares its own `FirebaseMessagingService`, only one se
132
239
 
133
240
  * [`initialize(...)`](#initialize)
134
241
  * [`getCredentials()`](#getcredentials)
242
+ * [`getDiagnostics()`](#getdiagnostics)
135
243
  * [`checkPermissions()`](#checkpermissions)
136
244
  * [`requestPermissions()`](#requestpermissions)
137
245
  * [`startListening()`](#startlistening)
@@ -173,6 +281,23 @@ Returns APNs (iOS) or FCM (Android) credentials for your server.
173
281
  --------------------
174
282
 
175
283
 
284
+ ### getDiagnostics()
285
+
286
+ ```typescript
287
+ getDiagnostics() => Promise<PushDiagnostics>
288
+ ```
289
+
290
+ Inspect the device push provider without requesting a token.
291
+
292
+ On Android it reports whether Google Play services can serve FCM and, when
293
+ they cannot, which service to use instead (HMS, Mi Push, OPPO/vivo/Meizu).
294
+ On iOS it always reports APNs.
295
+
296
+ **Returns:** <code>Promise&lt;<a href="#pushdiagnostics">PushDiagnostics</a>&gt;</code>
297
+
298
+ --------------------
299
+
300
+
176
301
  ### checkPermissions()
177
302
 
178
303
  ```typescript
@@ -265,11 +390,26 @@ addListener(eventName: 'onNotificationPress', listenerFunc: (message: PushMessag
265
390
 
266
391
  | Prop | Type |
267
392
  | ----------------- | ----------------------------------------------------------- |
268
- | **`platform`** | <code><a href="#pushplatform">PushPlatform</a></code> |
269
393
  | **`token`** | <code>string</code> |
270
394
  | **`environment`** | <code><a href="#pushenvironment">PushEnvironment</a></code> |
271
395
 
272
396
 
397
+ #### PushDiagnostics
398
+
399
+ | Prop | Type | Description |
400
+ | ----------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------- |
401
+ | **`platform`** | <code><a href="#pushplatform">PushPlatform</a></code> | |
402
+ | **`gmsAvailable`** | <code>boolean</code> | True when Google Play services can serve FCM; false on a ROM without GMS. |
403
+ | **`gmsStatus`** | <code>number</code> | Raw GoogleApiAvailability status code. 0 means SUCCESS. |
404
+ | **`manufacturer`** | <code>string</code> | |
405
+ | **`brand`** | <code>string</code> | |
406
+ | **`model`** | <code>string</code> | |
407
+ | **`provider`** | <code><a href="#pushprovider">PushProvider</a></code> | |
408
+ | **`providerName`** | <code>string</code> | Human-readable provider name, e.g. "HMS Push Kit". |
409
+ | **`providerInstalled`** | <code>boolean</code> | True only when the provider service app was positively found on the device. |
410
+ | **`hint`** | <code>string</code> | Actionable sentence explaining which service to use instead of the standard one. |
411
+
412
+
273
413
  #### PermissionStatus
274
414
 
275
415
  | Prop | Type |
@@ -286,25 +426,31 @@ addListener(eventName: 'onNotificationPress', listenerFunc: (message: PushMessag
286
426
 
287
427
  #### PushMessage
288
428
 
289
- | Prop | Type |
290
- | ----------- | --------------------------------------------------------------- |
291
- | **`id`** | <code>string</code> |
292
- | **`title`** | <code>string</code> |
293
- | **`body`** | <code>string</code> |
294
- | **`data`** | <code><a href="#record">Record</a>&lt;string, string&gt;</code> |
429
+ | Prop | Type | Description |
430
+ | -------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------- |
431
+ | **`id`** | <code>string</code> | |
432
+ | **`title`** | <code>string</code> | |
433
+ | **`body`** | <code>string</code> | |
434
+ | **`imageUrl`** | <code>string</code> | Resolved image URL from FCM notification.image / data.image / data.imageUrl. |
435
+ | **`data`** | <code><a href="#record">Record</a>&lt;string, string&gt;</code> | |
295
436
 
296
437
 
297
438
  ### Type Aliases
298
439
 
299
440
 
441
+ #### PushEnvironment
442
+
443
+ <code>'sandbox' | 'production'</code>
444
+
445
+
300
446
  #### PushPlatform
301
447
 
302
448
  <code>'ios' | 'android'</code>
303
449
 
304
450
 
305
- #### PushEnvironment
451
+ #### PushProvider
306
452
 
307
- <code>'sandbox' | 'production'</code>
453
+ <code>'fcm' | 'hms' | 'mi_push' | 'oppo_push' | 'vivo_push' | 'meizu_push' | 'apns' | 'unknown'</code>
308
454
 
309
455
 
310
456
  #### PermissionState
@@ -59,6 +59,7 @@ dependencies {
59
59
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
60
60
  implementation "androidx.activity:activity-ktx:1.10.1"
61
61
  implementation "androidx.core:core-ktx:1.16.0"
62
+ implementation "com.google.android.gms:play-services-base:18.5.0"
62
63
  implementation "com.google.firebase:firebase-messaging:24.1.2"
63
64
  testImplementation "junit:junit:$junitVersion"
64
65
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
@@ -1,7 +1,15 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <uses-permission android:name="android.permission.INTERNET" />
3
4
  <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
4
5
 
6
+ <!-- Android 11+ package visibility: lets isPackageInstalled() confirm an
7
+ alternative push provider on the device. -->
8
+ <queries>
9
+ <package android:name="com.huawei.hwid" />
10
+ <package android:name="com.xiaomi.xmsf" />
11
+ </queries>
12
+
5
13
  <application>
6
14
  <provider
7
15
  android:name="com.antonseagull.cap.push.signal.PushSignalInitProvider"
@@ -5,6 +5,7 @@ data class PushMessage(
5
5
  val title: String?,
6
6
  val body: String?,
7
7
  val data: Map<String, String>,
8
+ val imageUrl: String? = null,
8
9
  )
9
10
 
10
11
  data class AndroidFirebaseConfig(
@@ -13,3 +14,26 @@ data class AndroidFirebaseConfig(
13
14
  val current_key: String?,
14
15
  val project_number: String?,
15
16
  )
17
+
18
+ data class PushDiagnostics(
19
+ val platform: String,
20
+ val gmsAvailable: Boolean,
21
+ val gmsStatus: Int,
22
+ val manufacturer: String,
23
+ val brand: String,
24
+ val model: String,
25
+ val provider: String,
26
+ val providerName: String,
27
+ val providerInstalled: Boolean?,
28
+ val hint: String?,
29
+ )
30
+
31
+ /**
32
+ * Carries a machine-readable [code] to JavaScript, so the host app can branch on
33
+ * the exact reason (missing GMS, disabled GMS, FCM token failure, ...).
34
+ */
35
+ class PushSignalException(
36
+ val code: String,
37
+ message: String,
38
+ cause: Throwable? = null,
39
+ ) : IllegalStateException(message, cause)