@chipmobilesdk/rn-notification 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +156 -0
- package/README.md +108 -12
- package/package.json +10 -2
- package/skills/sdk-rn-notification/SKILL.md +6 -5
- package/src/advanced.ts +35 -0
- package/src/channel/migrate.ts +176 -158
- package/src/channel/tone-map.ts +2 -10
- package/src/contract/errors.ts +21 -6
- package/src/contract/hash.ts +26 -0
- package/src/diagnostics/events.ts +10 -1
- package/src/diagnostics/redact.ts +9 -26
- package/src/diagnostics/snapshot.ts +34 -1
- package/src/engine/adapter.ts +21 -1
- package/src/engine/notify-kit/events.ts +155 -0
- package/src/engine/notify-kit/module.ts +148 -0
- package/src/engine/notify-kit/platform/android.ts +172 -0
- package/src/engine/notify-kit/platform/index.ts +55 -0
- package/src/engine/notify-kit/platform/ios.ts +54 -0
- package/src/engine/notify-kit/platform/types.ts +40 -0
- package/src/engine/notify-kit-engine.ts +96 -468
- package/src/index.ts +9 -0
- package/src/initialize.ts +10 -3
- package/src/notifications.ts +195 -0
- package/src/permission/index.ts +65 -4
- package/src/push/fcm-provider.ts +28 -11
- package/src/push/gate.ts +27 -5
- package/src/reconcile/diff.ts +38 -27
- package/src/reconcile/index.ts +13 -4
- package/src/recurrence/index.ts +2 -1
- package/src/repeat/expiry.ts +2 -6
- package/src/repeat/index.ts +45 -1
- package/src/repeat/marker.ts +1 -22
- package/src/repeat/place.ts +74 -35
- package/src/schedule/anchor.ts +35 -19
- package/src/schedule/index.ts +10 -4
- package/src/schedule/partition.ts +0 -19
- package/src/schedule/validate-identifier.ts +8 -6
- package/src/schedule/writer.ts +13 -4
- package/src/state/caps.ts +28 -3
- package/src/state/interaction-buffer.ts +30 -2
- package/src/state/mmkv-store.ts +54 -37
- package/src/state/permission-history.ts +25 -0
- package/src/state/record.ts +114 -20
- package/src/state/store.ts +3 -0
- package/src/testing/fake-engine.ts +17 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,161 @@
|
|
|
1
1
|
# @chipmobilesdk/rn-notification
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `createNotifications(config, deps?)` assembles the whole package in one call. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
8
|
+
|
|
9
|
+
- It returns the scheduler, reconciler, recurrence, permissions, channels, interactions
|
|
10
|
+
and snapshot, wired over one engine, one state store and the one redacting
|
|
11
|
+
diagnostic emitter, plus `onInteraction`, `runMaintenance`, `canScheduleExactly` and
|
|
12
|
+
`dispose`. The engine, store, clock and time zone default to the real ones and can
|
|
13
|
+
be injected.
|
|
14
|
+
- `canScheduleExactly` follows the exact-alarm permission the package last read,
|
|
15
|
+
instead of each app passing its own callback (often `() => true`).
|
|
16
|
+
- Automatic maintenance has its standard steps registered: re-anchoring after a
|
|
17
|
+
time-zone change and the permission re-read, plus recurrence window refresh when you
|
|
18
|
+
pass `recurrenceRules`.
|
|
19
|
+
- The individual factories stay exported from the root, and are also available from
|
|
20
|
+
the new `@chipmobilesdk/rn-notification/advanced` entrypoint for apps that wire the
|
|
21
|
+
package themselves.
|
|
22
|
+
- Push: `getAvailability()` no longer fetches a token (which registered the device
|
|
23
|
+
with FCM, and APNs on iOS, just to answer the question). The new
|
|
24
|
+
`gate.handleBackground(raw)` runs a background payload through the same
|
|
25
|
+
normalization and persisted deduplication as a foreground one. Several
|
|
26
|
+
`onTokenChange` subscriptions no longer leak each other, and `dispose()` releases
|
|
27
|
+
them all.
|
|
28
|
+
- The README starts from `createNotifications`; its quick start is checked against a
|
|
29
|
+
type-checked example in the tests.
|
|
30
|
+
|
|
31
|
+
- Fewer reads across the native bridge, storage written per collection, and one
|
|
32
|
+
pending budget for the whole app. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
33
|
+
|
|
34
|
+
- A reconcile, a group or owner cancel, and a tone apply each read the pending set
|
|
35
|
+
(or the channel list) once per pass instead of once per operation. Reconciling 100
|
|
36
|
+
changes now makes at most two full reads of the trigger list; it used to make one
|
|
37
|
+
or more per change. Scheduling one notification asks for identifiers only where the
|
|
38
|
+
engine can list them (`pendingIds`, optional on the engine port).
|
|
39
|
+
- Persisted state is stored under one key per collection. Recording a push
|
|
40
|
+
deduplication entry writes the dedup ledger alone, where every update used to
|
|
41
|
+
serialize the whole record. A record written by an earlier version is migrated on
|
|
42
|
+
first load.
|
|
43
|
+
- The series and migration ledgers no longer evict live entries at their cap. Only
|
|
44
|
+
exhausted series and retired channels are dropped; if live entries alone exceed the
|
|
45
|
+
cap they are all kept and the new `STATE_CAP_EXCEEDED` diagnostic says so. Evicting a
|
|
46
|
+
live entry reset a series' count and made a migrated tone look new.
|
|
47
|
+
- Reconciliation budgets a domain against the platform ceiling minus what other
|
|
48
|
+
domains, series and direct schedules already hold. Two domains could previously
|
|
49
|
+
overflow iOS's 64 pending notifications together while each reported no truncation.
|
|
50
|
+
|
|
51
|
+
- Three fixes: what a tone change cancels, background interactions, and permanent denial. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
52
|
+
|
|
53
|
+
- `channels.apply()` cancels a pending notification only when it is on a tone the
|
|
54
|
+
package applied earlier and the configuration no longer declares. It used to cancel
|
|
55
|
+
everything whose tone was not in the list passed to that call, which included
|
|
56
|
+
notifications scheduled by other code through the same engine.
|
|
57
|
+
- Taps, actions and dismissals that arrive while the app is in the background are
|
|
58
|
+
delivered. `registerBackgroundNotificationHandler` takes an optional `onInteraction`
|
|
59
|
+
callback and an optional `store`: a running app's `onInteraction` handlers get the
|
|
60
|
+
event with `appState: 'background'`; otherwise the callback does; with no callback, or
|
|
61
|
+
one that throws, the event is kept in the store and delivered on the next start.
|
|
62
|
+
Before, background actions such as "Taken" or "Snooze" never reached the app.
|
|
63
|
+
- `permanentlyDenied` is now reported: after one refused display request on iOS, two on
|
|
64
|
+
Android 13+, and `DISPLAY_PERMISSION_PERMANENT` is emitted. The count resets once the
|
|
65
|
+
permission is seen granted. After `requestExactAlarmPermission()` the package re-reads
|
|
66
|
+
the capability when the user returns from system settings and notifies
|
|
67
|
+
`onPermissionChange`.
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- The notify-kit engine is split by platform. Android and iOS behaviour live in separate
|
|
72
|
+
strategy files chosen once when the engine is created, instead of about fifteen
|
|
73
|
+
`Platform.OS` branches in one 790-line file. `createNotifyKitEngine` accepts an optional
|
|
74
|
+
`platform` for testing one platform's behaviour. No behaviour change. ([#19](https://github.com/operationd8979/chip-mobile-sdk/pull/19))
|
|
75
|
+
|
|
76
|
+
## 0.5.0
|
|
77
|
+
|
|
78
|
+
### Minor Changes
|
|
79
|
+
|
|
80
|
+
- Privacy (NOT-14), on top of `NotificationError` now extending `SdkError` from the new
|
|
81
|
+
`@chipmobilesdk/rn-core` dependency: ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
82
|
+
|
|
83
|
+
- Text the user types into a reply action is no longer written to disk. It is held in
|
|
84
|
+
memory, so a handler registered later in the same process still receives
|
|
85
|
+
`inputText`; after process death the event arrives without it.
|
|
86
|
+
- A state snapshot reports pending entries with their content and routing values
|
|
87
|
+
reduced to their shape (`[string:12]`). Pass `verbose: () => true` to
|
|
88
|
+
`createSnapshot` to include the values while debugging.
|
|
89
|
+
- A `NotificationError`'s `context`, and every diagnostic context, also has token-,
|
|
90
|
+
URL- and e-mail-shaped strings removed under any key.
|
|
91
|
+
|
|
92
|
+
- One diagnostic stream: install a sink with `installSdkSink` from
|
|
93
|
+
`@chipmobilesdk/rn-core` and it receives what every package observes (events,
|
|
94
|
+
failures, warnings) as `SdkDiagnostic`s, redacted. Listeners are notified through
|
|
95
|
+
rn-core, so a listener that throws no longer stops the others nor fails the call that
|
|
96
|
+
notified, and its error is reported rather than swallowed. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
97
|
+
|
|
98
|
+
- rn-ads: a banner's auto-refresh is a new cycle, so its `loaded` and `impression`
|
|
99
|
+
events are no longer deduplicated away; the dedupe memory is bounded. A throwing
|
|
100
|
+
status, consent or placement listener is reported as an `internal_error` event.
|
|
101
|
+
- rn-i18n: new `onDiagnostic` config receives every diagnostic (`I18nDiagnostic`,
|
|
102
|
+
with a stable `code`); without it, warnings go to `console.warn` in development
|
|
103
|
+
builds only. The warn-once memory is bounded.
|
|
104
|
+
- rn-chart: now depends on rn-core for its diagnostics.
|
|
105
|
+
- rn-payment: status, entitlement and pending listeners report a throw to the logger
|
|
106
|
+
instead of swallowing it.
|
|
107
|
+
|
|
108
|
+
- Native peers load through `lazyPeer` from `@chipmobilesdk/rn-core`, on first use,
|
|
109
|
+
and a missing one is each package's own typed error. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
110
|
+
|
|
111
|
+
- rn-auth: creating `auth` no longer loads react-native-app-auth or
|
|
112
|
+
react-native-keychain; each loads on first use. New entrypoints
|
|
113
|
+
`@chipmobilesdk/rn-auth/app-auth` and `/keychain`. The default platform comes from
|
|
114
|
+
rn-core.
|
|
115
|
+
- rn-camera: a preview whose VisionCamera feed cannot load fails the session with
|
|
116
|
+
`cameraUnavailable` (new `controller.reportSurfaceUnavailable()`), instead of
|
|
117
|
+
showing black with no error. A missing VisionCamera module is `cameraUnavailable`.
|
|
118
|
+
- rn-notification: `createMmkvStore` reports a fallback to memory as
|
|
119
|
+
`STATE_STORE_DEGRADED` (to the SDK sinks and a new `onDegraded` option) and
|
|
120
|
+
exposes `durable`.
|
|
121
|
+
- rn-payment: an iOS app validates its catalog against the iOS product ids; the
|
|
122
|
+
default platform used to be Android everywhere. A missing react-native-iap is
|
|
123
|
+
`PAYMENT_UNAVAILABLE`.
|
|
124
|
+
- rn-theme: `buildThemes` runs without React Native (new `hairlineWidth` option) and
|
|
125
|
+
importing the package no longer loads Unistyles; `applyThemeMode` loads it on
|
|
126
|
+
first use.
|
|
127
|
+
- rn-ads, rn-local-db: the same loading, with the same error codes as before.
|
|
128
|
+
|
|
129
|
+
- Formatters come from rn-core's cache instead of being built per call. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
130
|
+
|
|
131
|
+
- rn-notification: resolving a time zone builds one `Intl.DateTimeFormat` per zone
|
|
132
|
+
instead of one per occurrence; expanding 32 series of 32 occurrences went from
|
|
133
|
+
about 230 ms to under 30 ms on Node. Zone validity is memoized.
|
|
134
|
+
- rn-i18n: number, currency and date formatters are cached. A bad option (an unknown
|
|
135
|
+
currency such as `usd1`) goes through the invalid-format policy instead of
|
|
136
|
+
throwing out of a render, and a locale's `defaultCurrency` must be a three-letter
|
|
137
|
+
ISO 4217 code at startup.
|
|
138
|
+
|
|
139
|
+
### Patch Changes
|
|
140
|
+
|
|
141
|
+
- One app-lifecycle source, and a startup that needs no extra frame. ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17))
|
|
142
|
+
|
|
143
|
+
- rn-core: `appLifecycle()`, a shared, reference-counted `AppState` subscription that
|
|
144
|
+
reports changes only (`active`, `inactive`, `background`), with `onForeground` for a
|
|
145
|
+
return from the background and `createManualLifecycle` in `./testing`.
|
|
146
|
+
- rn-camera, rn-ads and rn-notification hear the lifecycle through it instead of
|
|
147
|
+
subscribing to `AppState` each, so an app holds one native listener. The camera
|
|
148
|
+
controller's core layer no longer imports `react-native`.
|
|
149
|
+
- rn-payment: `syncOnForeground(engine)` and `<PaymentProvider syncOnForeground>` call
|
|
150
|
+
`handleForeground()` on each return from the background. Off by default; calling
|
|
151
|
+
`handleForeground()` yourself works as before.
|
|
152
|
+
- rn-i18n: `i18n.initSync()` resolves the locale before the first render when the
|
|
153
|
+
storage adapter has the new optional `getItemSync`, and returns false otherwise.
|
|
154
|
+
`localeStorageFromKeyValue(port)` builds such an adapter from rn-core's
|
|
155
|
+
`KeyValuePort` (MMKV).
|
|
156
|
+
- Updated dependencies ([#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17), [#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17), [#17](https://github.com/operationd8979/chip-mobile-sdk/pull/17)):
|
|
157
|
+
- @chipmobilesdk/rn-core@0.1.0
|
|
158
|
+
|
|
3
159
|
## 0.4.1
|
|
4
160
|
|
|
5
161
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -17,6 +17,55 @@ Every notification feature re-solves the same problems, and most of them are dis
|
|
|
17
17
|
- Display permission and exact-alarm permission are **different things**, asked separately, and a refusal must not break the user's actual work.
|
|
18
18
|
- Android channels are **immutable once created** — changing one is a migration, not an assignment.
|
|
19
19
|
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
`createNotifications` assembles the whole package: scheduler, reconciler, recurrence,
|
|
23
|
+
permissions, channels, interactions, a diagnostic snapshot, and automatic maintenance.
|
|
24
|
+
It applies your tones, loads persisted state, and schedules exactly only while the
|
|
25
|
+
exact-alarm permission allows it.
|
|
26
|
+
|
|
27
|
+
<!-- quick-start -->
|
|
28
|
+
```ts
|
|
29
|
+
import { createNotifications } from '@chipmobilesdk/rn-notification';
|
|
30
|
+
|
|
31
|
+
export async function setUpNotifications(navigate: (routing: unknown) => void) {
|
|
32
|
+
const notifications = await createNotifications({
|
|
33
|
+
tones: [
|
|
34
|
+
{
|
|
35
|
+
id: 'reminder',
|
|
36
|
+
name: 'Reminders',
|
|
37
|
+
urgency: 'high',
|
|
38
|
+
sound: { enabled: true },
|
|
39
|
+
vibration: true,
|
|
40
|
+
badge: true,
|
|
41
|
+
version: 1,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await notifications.permissions.requestDisplayPermission();
|
|
47
|
+
|
|
48
|
+
await notifications.scheduler.schedule({
|
|
49
|
+
id: 'task:42:due', // derived from your data, never random
|
|
50
|
+
content: { title: 'Pay the rent', body: 'Due today' },
|
|
51
|
+
tone: 'reminder',
|
|
52
|
+
moment: { at: Date.parse('2026-12-01T08:00:00') },
|
|
53
|
+
routing: { screen: 'task', taskId: '42' },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
notifications.onInteraction(async event => {
|
|
57
|
+
if (event.kind === 'tap') navigate(event.routing);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return notifications;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
<!-- /quick-start -->
|
|
64
|
+
|
|
65
|
+
The individual factories (`createScheduler`, `createReconciler`, `initialize`, the
|
|
66
|
+
maintenance steps, ...) remain available, from the package root and from
|
|
67
|
+
`@chipmobilesdk/rn-notification/advanced`, for an app that needs a different wiring.
|
|
68
|
+
|
|
20
69
|
## Installation
|
|
21
70
|
|
|
22
71
|
```sh
|
|
@@ -150,18 +199,25 @@ The package re-anchors wall-clock notifications when it notices the device zone
|
|
|
150
199
|
| Applies `config.tones` (and any channel migration) during `initialize()` | `reconcile()` — the desired set |
|
|
151
200
|
| Runs every step on `runtime.registry` when the app returns to the foreground — with the trigger `timeZoneChange` when the device zone moved meanwhile | |
|
|
152
201
|
|
|
153
|
-
|
|
202
|
+
`createNotifications` registers the standard steps for you: re-anchoring after a
|
|
203
|
+
time-zone change, re-reading permissions, and — when you pass your recurrence rules —
|
|
204
|
+
refreshing their windows. Steps that need inputs only your app has are added on
|
|
205
|
+
`notifications.runtime.registry`:
|
|
154
206
|
|
|
155
207
|
```ts
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
runtime.registry.register(
|
|
160
|
-
runtime.
|
|
161
|
-
|
|
162
|
-
|
|
208
|
+
const notifications = await createNotifications(config, {
|
|
209
|
+
recurrenceRules: () => myRules, // the rules your app registered; the package never stores them
|
|
210
|
+
});
|
|
211
|
+
notifications.runtime.registry.register(
|
|
212
|
+
createVerifyStep({ engine: notifications.runtime.engine, expectedIds: () => myExpectedIds }),
|
|
213
|
+
);
|
|
214
|
+
// On teardown (tests, a sign-out that rebuilds everything):
|
|
215
|
+
notifications.dispose();
|
|
163
216
|
```
|
|
164
217
|
|
|
218
|
+
Wiring it yourself with `initialize()` and the step factories (from `./advanced`) still
|
|
219
|
+
works; then registering the steps is yours.
|
|
220
|
+
|
|
165
221
|
Before 0.4.0 nothing triggered these steps; they ran only when the app called `runMaintenance()` itself. A cycle that throws is reported as `MAINTENANCE_FAILED`, because nothing awaits it.
|
|
166
222
|
|
|
167
223
|
**Reconciliation never runs on its own.** Only you know what your data implies; a package that guessed would cancel notifications it does not understand.
|
|
@@ -298,6 +354,13 @@ await permissions.openSettings('display'); // per-permission
|
|
|
298
354
|
|
|
299
355
|
Six states, never collapsed: `notAsked`, `granted`, `denied`, `permanentlyDenied`, `provisional`, `notRequired`.
|
|
300
356
|
|
|
357
|
+
`permanentlyDenied` means the system will not show its prompt again, so offer Settings
|
|
358
|
+
(`shouldOfferSettings(state)`). The platform itself only ever says `denied`; the package
|
|
359
|
+
counts refusals — one on iOS, two on Android 13+ — and forgets them once the permission
|
|
360
|
+
is seen granted. The exact-alarm capability is granted on a system settings screen, so
|
|
361
|
+
`requestExactAlarmPermission()` answers with the state before the user chose; the real
|
|
362
|
+
answer is re-read when they return to the app and reaches `onPermissionChange`.
|
|
363
|
+
|
|
301
364
|
**Ask after intent, never on first launch.** A prompt at the wrong moment is a permanent denial.
|
|
302
365
|
|
|
303
366
|
**A refusal never fails your operation.** The user's work completes; the lost capability is reported separately.
|
|
@@ -310,7 +373,28 @@ notifications.onInteraction(async event => {
|
|
|
310
373
|
});
|
|
311
374
|
```
|
|
312
375
|
|
|
313
|
-
One handler, identical shape in foreground, background, and cold start.
|
|
376
|
+
One handler, identical shape in foreground, background, and cold start (`event.appState` says which).
|
|
377
|
+
|
|
378
|
+
**Background actions need the module-scope handler.** The engine has one background
|
|
379
|
+
handler per process, registered in `index.js`. Pass it your store and, optionally, a
|
|
380
|
+
callback:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
registerBackgroundNotificationHandler({
|
|
384
|
+
engine,
|
|
385
|
+
store: createMmkvStore(), // an interaction nobody handled is kept for the next start
|
|
386
|
+
onInteraction: async event => {
|
|
387
|
+
// Optional: handle "Taken" or "Snooze" right away, inside the background window.
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
While the app is running, a background tap or action goes to your `onInteraction`
|
|
393
|
+
handlers as usual. In a process started only for the event, it goes to the callback
|
|
394
|
+
above; without one (or if it throws) it is kept in the store and delivered when the app
|
|
395
|
+
next starts. Before 0.6.0 these events were dropped. Events that arrive before your handler registers are **buffered durably** — they survive process death, which is exactly what a cold-start tap is.
|
|
396
|
+
|
|
397
|
+
Text the user typed into a reply action (`inputText`) is the exception: it is free text, so it is **never written to disk**. It is held in memory beside the buffer, so a handler that registers later in the same process still receives it; if the process dies first, the event is delivered without `inputText`.
|
|
314
398
|
|
|
315
399
|
When the target is gone, call `reportTargetMissing()` and pick your own fallback. No platform error is ever shown to the user.
|
|
316
400
|
|
|
@@ -340,6 +424,14 @@ Foreground pushes are **never auto-displayed** — presentation is your decision
|
|
|
340
424
|
|
|
341
425
|
Local schedules take precedence over push for the same identifier. A server that wants to override sends `cancel` then `schedule`.
|
|
342
426
|
|
|
427
|
+
## Channel changes and notifications already scheduled
|
|
428
|
+
|
|
429
|
+
`channels.apply()` cancels a pending notification only when it sits on a tone this
|
|
430
|
+
package applied earlier and the configuration no longer declares. Notifications
|
|
431
|
+
scheduled by other code through the same engine, and tones the package has never
|
|
432
|
+
applied, are left alone (0.6.0; before, anything not in the list passed to `apply()` was
|
|
433
|
+
cancelled).
|
|
434
|
+
|
|
343
435
|
## What is persisted
|
|
344
436
|
|
|
345
437
|
Six collections, in app-private, backup-excluded storage:
|
|
@@ -353,7 +445,7 @@ Six collections, in app-private, backup-excluded storage:
|
|
|
353
445
|
| Permission history | 64 | — |
|
|
354
446
|
| Last reconcile summary | 1 | — |
|
|
355
447
|
|
|
356
|
-
**No notification content, no push token, no desired set.** The interaction buffer is the sole content-bearing exception, and it evicts on delivery.
|
|
448
|
+
**No notification content, no push token, no desired set, no typed reply.** The interaction buffer is the sole content-bearing exception (its routing payload), and it evicts on delivery.
|
|
357
449
|
|
|
358
450
|
Corrupt state is **discarded and rebuilt**, never blocking. What you lose: deduplication history and buffered interactions. What is unaffected: **every notification already scheduled with the OS**.
|
|
359
451
|
|
|
@@ -404,9 +496,13 @@ Delivery accuracy: within **60 s** with exact alarms; within **15 min** without,
|
|
|
404
496
|
|
|
405
497
|
## Diagnostic identifiers
|
|
406
498
|
|
|
407
|
-
|
|
499
|
+
`NotificationError` extends `SdkError` from `@chipmobilesdk/rn-core` (`isSdkError`, `error.pkg === 'rn-notification'`), and its `context` is redacted when the error is built: content keys become their shape, and other strings lose anything shaped like a token, URL or e-mail address.
|
|
500
|
+
|
|
501
|
+
The state snapshot (`createSnapshot`) reports the pending set entry by entry, but a snapshot is what gets pasted into a bug report, so each entry's content and routing values are reduced to their shape (`[string:12]`). Pass `verbose: () => true` to include the values while debugging.
|
|
502
|
+
|
|
503
|
+
45 stable identifiers across validation, reconciliation, permissions, channels, interaction, push, state, and volume. Every one is reachable through the fault injector. See `ALL_DIAGNOSTIC_CODES`.
|
|
408
504
|
|
|
409
|
-
Some are **reported outcomes, not failures** — the operation succeeded and something you should know happened alongside it: `TRUNCATED_OVER_CEILING`, `EXACT_ALARM_UNAVAILABLE`, `TARGET_MISSING`, `PENDING_SET_DISCREPANCY`, `BURST_THRESHOLD_EXCEEDED`, `PAYLOAD_VERSION_SKEW`, `PAYLOAD_DUPLICATE`, `STATE_CAP_EVICTED`, `STATE_CORRUPT_REBUILT`, `DND_BYPASS_UNAVAILABLE`, `TONE_LABELS_REFRESHED`, `REPEAT_ALERT_UNAVAILABLE`, `REPEAT_STOP_HANDLER_MISSING`. Use `isNonFailure(code)`.
|
|
505
|
+
Some are **reported outcomes, not failures** — the operation succeeded and something you should know happened alongside it: `TRUNCATED_OVER_CEILING`, `EXACT_ALARM_UNAVAILABLE`, `TARGET_MISSING`, `PENDING_SET_DISCREPANCY`, `BURST_THRESHOLD_EXCEEDED`, `PAYLOAD_VERSION_SKEW`, `PAYLOAD_DUPLICATE`, `STATE_CAP_EVICTED`, `STATE_CAP_EXCEEDED`, `STATE_CORRUPT_REBUILT`, `DND_BYPASS_UNAVAILABLE`, `TONE_LABELS_REFRESHED`, `REPEAT_ALERT_UNAVAILABLE`, `REPEAT_STOP_HANDLER_MISSING`. Use `isNonFailure(code)`.
|
|
410
506
|
|
|
411
507
|
## License
|
|
412
508
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chipmobilesdk/rn-notification",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"react-native": "./src/index.ts",
|
|
12
12
|
"default": "./src/index.ts"
|
|
13
13
|
},
|
|
14
|
+
"./advanced": {
|
|
15
|
+
"types": "./src/advanced.ts",
|
|
16
|
+
"react-native": "./src/advanced.ts",
|
|
17
|
+
"default": "./src/advanced.ts"
|
|
18
|
+
},
|
|
14
19
|
"./push": {
|
|
15
20
|
"types": "./src/push/index.ts",
|
|
16
21
|
"react-native": "./src/push/index.ts",
|
|
@@ -31,9 +36,12 @@
|
|
|
31
36
|
],
|
|
32
37
|
"sideEffects": false,
|
|
33
38
|
"scripts": {
|
|
34
|
-
"typecheck": "tsc -p tsconfig.json
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json",
|
|
35
40
|
"pack:check": "npm pack --dry-run"
|
|
36
41
|
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@chipmobilesdk/rn-core": "^0.1.0"
|
|
44
|
+
},
|
|
37
45
|
"peerDependencies": {
|
|
38
46
|
"@react-native-firebase/app": ">=26.0.0",
|
|
39
47
|
"@react-native-firebase/messaging": ">=26.0.0",
|
|
@@ -35,11 +35,12 @@ Three things cause most of the bugs, and none is visible in the type signatures:
|
|
|
35
35
|
2. **Wall-clock is the default anchoring mode** — an 08:00 reminder stays 08:00 after the
|
|
36
36
|
user changes time zone. One case re-anchoring cannot fix: read
|
|
37
37
|
`### What re-anchoring cannot fix` before promising exact behaviour to anyone.
|
|
38
|
-
3. **Reconciliation is never automatic.**
|
|
39
|
-
|
|
40
|
-
on every foreground return
|
|
41
|
-
and you did not call `reconcile()` means the device is stale, by
|
|
42
|
-
|
|
38
|
+
3. **Reconciliation is never automatic.** Start from `createNotifications(config, deps)`:
|
|
39
|
+
it registers re-anchoring and the permission re-read, plus window refresh when you pass
|
|
40
|
+
`recurrenceRules`, and runs them on every foreground return. It never invents a desired
|
|
41
|
+
set: data changed and you did not call `reconcile()` means the device is stale, by
|
|
42
|
+
design. Wiring with `initialize()` and the factories from `./advanced` leaves step
|
|
43
|
+
registration to you.
|
|
43
44
|
|
|
44
45
|
Read `## Choosing identifiers`, `## Choosing an anchoring mode`, and
|
|
45
46
|
`## Maintenance versus reconciliation` before designing anything.
|
package/src/advanced.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@chipmobilesdk/rn-notification/advanced`: the individual factories, for an app
|
|
3
|
+
* that wires the package itself instead of using `createNotifications` (NOT-07).
|
|
4
|
+
*
|
|
5
|
+
* The package root still exports them too, for compatibility; new code that needs
|
|
6
|
+
* them should import from here, where they are documented as the advanced path.
|
|
7
|
+
*/
|
|
8
|
+
export { initialize } from './initialize';
|
|
9
|
+
export type { NotificationRuntime, RuntimeDependencies } from './initialize';
|
|
10
|
+
export { createScheduler } from './schedule';
|
|
11
|
+
export type { Scheduler, ScheduleDependencies } from './schedule';
|
|
12
|
+
export { createReconciler } from './reconcile';
|
|
13
|
+
export type { Reconciler, ReconcileDependencies } from './reconcile';
|
|
14
|
+
export { createRecurrence } from './recurrence';
|
|
15
|
+
export type { Recurrence, RecurrenceDependencies } from './recurrence';
|
|
16
|
+
export { createChannels } from './channel/migrate';
|
|
17
|
+
export type { ChannelDependencies, Channels } from './channel/migrate';
|
|
18
|
+
export { createToneResolver } from './channel/tone-map';
|
|
19
|
+
export { createInteractions } from './interaction';
|
|
20
|
+
export type { InteractionDependencies, Interactions } from './interaction';
|
|
21
|
+
export { createSnapshot } from './diagnostics/snapshot';
|
|
22
|
+
export type { Snapshot, SnapshotDependencies } from './diagnostics/snapshot';
|
|
23
|
+
export { createBurstDetector } from './diagnostics/burst';
|
|
24
|
+
export { createPermissions } from './permission';
|
|
25
|
+
export type { PermissionDependencies, Permissions } from './permission';
|
|
26
|
+
export { createPermissionRefreshStep } from './maintenance/permission-refresh';
|
|
27
|
+
export { createReAnchorStep } from './maintenance/reanchor';
|
|
28
|
+
export { createVerifyStep } from './maintenance/verify';
|
|
29
|
+
export { createWindowRefreshStep } from './maintenance/window-refresh';
|
|
30
|
+
export { MaintenanceController } from './maintenance/toggle';
|
|
31
|
+
export { MaintenanceRegistry } from './maintenance/cycle';
|
|
32
|
+
export type { MaintenanceContext, MaintenanceStep, MaintenanceTrigger } from './maintenance/cycle';
|
|
33
|
+
export { StateManager } from './state/record';
|
|
34
|
+
export { createMmkvStore } from './state/mmkv-store';
|
|
35
|
+
export { createPlatformTimeZoneReader } from './schedule/anchor';
|