@maccesar/titools 4.23.0 → 4.23.1
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/package.json
CHANGED
|
@@ -79,6 +79,26 @@ if (OS_ANDROID) {
|
|
|
79
79
|
|
|
80
80
|
**Wrap the listeners.** `Ti.App.addEventListener('resumed', mostrarAviso)` passes the event object as the first argument, and if the function's first parameter is an Intent, the code blows up inside `getStringExtra`. The same trap catches `Alloy.Events.on('algo', unaFuncionConParametros)`.
|
|
81
81
|
|
|
82
|
+
### Registering on Android: two calls that go quiet
|
|
83
|
+
|
|
84
|
+
The channel has to exist and be handed to the module **before** asking for the token, and its id has to match the `default_notification_channel_id` meta-data in `tiapp.xml`. Mismatch them and the notification is posted against a channel nobody declared.
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
modulo.notificationChannel = Ti.Android.NotificationManager.createNotificationChannel({
|
|
88
|
+
id: 'default_channel', // the same id tiapp.xml declares
|
|
89
|
+
name: L('push_channel_name'),
|
|
90
|
+
importance: Ti.Android.IMPORTANCE_DEFAULT
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
modulo.registerForPushNotifications()
|
|
94
|
+
|
|
95
|
+
// Firebase does not announce a token it already had, so didRefreshRegistrationToken
|
|
96
|
+
// may never fire on a device that registered before. Ask for it once, a moment later.
|
|
97
|
+
setTimeout(() => guardarToken(modulo.fcmToken), 4000)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
That last line is the one that bites. On a fresh install the event fires and everything looks fine; on a device that already had a token from an earlier run it never fires, the app never sends the token to the server, and every push to that device is addressed to nobody. Make `guardarToken` idempotent — it will be called more than once with the same value.
|
|
101
|
+
|
|
82
102
|
## 2. The payload decides how much of the module runs
|
|
83
103
|
|
|
84
104
|
Android distinguishes a *notification message* from a *data message*, and the difference is not cosmetic.
|
|
@@ -151,11 +171,34 @@ Everything above is what the published module needs. A module that fires `didOpe
|
|
|
151
171
|
Verified on a physical Android 15 phone and an API 35 emulator, each row confirmed by a log line naming the route. The first three ran back to back on one process id with a game in progress, which survived; only the fourth starts a new process.
|
|
152
172
|
|
|
153
173
|
```javascript
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
174
|
+
if (OS_ANDROID) {
|
|
175
|
+
// With the runtime alive the launcher Intent is not delivered a second time,
|
|
176
|
+
// so this event is the only way in.
|
|
177
|
+
modulo.addEventListener('didOpenNotification', (e) => {
|
|
178
|
+
abrirPanel((e.message && e.message.data) || {})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
// On a cold start the module does not exist yet when the notification is
|
|
182
|
+
// opened, so the payload travels in the Intent. This is that route.
|
|
183
|
+
const desdeElIntent = (origen) => {
|
|
184
|
+
const contenido = Ti.Android.rootActivity.intent.getStringExtra('fcm_data')
|
|
185
|
+
|
|
186
|
+
if (!contenido) {
|
|
187
|
+
log.debug(`Nothing in the Intent (${origen})`)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
modulo.clearLastData()
|
|
192
|
+
abrirPanel(JSON.parse(contenido))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Ti.App.addEventListener('resumed', () => desdeElIntent('resumed'))
|
|
196
|
+
desdeElIntent('arranque')
|
|
197
|
+
}
|
|
157
198
|
```
|
|
158
199
|
|
|
200
|
+
`resumed` stays even though the event covers the warm tap: it is what catches an Intent the module left behind on a version that still writes one. When the event delivered the payload the Intent is empty and the handler returns without opening anything, which is the exclusivity the section below describes.
|
|
201
|
+
|
|
159
202
|
The payload sits under `message.data`, the same place `didReceiveMessage` puts it on both its live and its cold-start path, so one accessor covers a tap and an arrival. The first version of the event wrapped it directly in `message`; if the module you have predates 3.6.0, read `e.message` instead, or log the event once and look.
|
|
160
203
|
|
|
161
204
|
The Intent is still how a cold start arrives, so keep that path. Check `timodule.xml` in your installed module for the event, or just add the listener and watch whether it fires: a module without it ignores the registration silently.
|
|
@@ -249,7 +292,15 @@ Verified case by case on an iPad. The third row is the one that does not work on
|
|
|
249
292
|
|
|
250
293
|
```javascript
|
|
251
294
|
Ti.Network.registerForPushNotifications({
|
|
252
|
-
success: (e) => {
|
|
295
|
+
success: (e) => {
|
|
296
|
+
// e.deviceToken is the APNs one. It is not what you send to a server that
|
|
297
|
+
// pushes through FCM, and Firebase picks it up on its own — see below.
|
|
298
|
+
log.info(`APNs token: ${e.deviceToken ? 'received' : 'EMPTY'}`)
|
|
299
|
+
|
|
300
|
+
modulo.fetchToken((r) => {
|
|
301
|
+
if (r && r.token) { guardarToken(r.token) }
|
|
302
|
+
})
|
|
303
|
+
},
|
|
253
304
|
error: (e) => { log.error(e.error) },
|
|
254
305
|
callback: (e) => {
|
|
255
306
|
// Foreground, background and closed all arrive here.
|
|
@@ -274,6 +325,16 @@ if (OS_IOS) {
|
|
|
274
325
|
}
|
|
275
326
|
```
|
|
276
327
|
|
|
328
|
+
**The token comes from `fetchToken`, not from the property.** Right after APNs answers, `modulo.fcmToken` is empty: Firebase still has to exchange the APNs token for its own, and that exchange has no fixed duration. Waiting does not fix it — four seconds was still empty on a device. `fetchToken` calls back when the token exists, which is the only way to know.
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
// The apnsToken is never assigned by hand. Firebase's swizzling does it, and
|
|
332
|
+
// setting it yourself produces BadDeviceToken when you send.
|
|
333
|
+
modulo.fetchToken((e) => {
|
|
334
|
+
if (e && e.token) { guardarToken(e.token) }
|
|
335
|
+
})
|
|
336
|
+
```
|
|
337
|
+
|
|
277
338
|
**A data-only message shows nothing on iOS.** Android reads the text from `data`; iOS reads it from the APNs alert. Send both, they do not interfere:
|
|
278
339
|
|
|
279
340
|
```php
|
|
@@ -323,3 +384,6 @@ Do not test with the app added to the battery whitelist. That measures a phone y
|
|
|
323
384
|
| iOS registers but never gets a token | Firebase loaded after APNs answered | Section 8 |
|
|
324
385
|
| iOS receives the push and does nothing when tapped | Empty `callback` | Section 8 |
|
|
325
386
|
| Nothing arrives for twenty minutes, then five at once | OEM battery management | Section 9 |
|
|
387
|
+
| Push works on a fresh install and never on a reinstall | `didRefreshRegistrationToken` does not fire for a token Firebase already had | Section 1 |
|
|
388
|
+
| iOS: `fcmToken` is empty right after the permission is granted | It is; the APNs-for-FCM exchange has no fixed duration. Use `fetchToken` | Section 8 |
|
|
389
|
+
| iOS: `BadDeviceToken` when sending | `apnsToken` was assigned by hand instead of leaving it to the swizzling | Section 8 |
|