@chipmobilesdk/rn-notification 0.4.0 → 0.5.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 +257 -166
- package/README.md +420 -410
- package/package.json +61 -75
- package/skills/sdk-rn-notification/SKILL.md +172 -173
- package/src/channel/migrate.ts +285 -289
- package/src/channel/tone-map.ts +130 -130
- package/src/channel/validate-tone.ts +61 -61
- package/src/contract/channel.ts +192 -188
- package/src/contract/diagnostics.ts +102 -102
- package/src/contract/errors.ts +142 -134
- package/src/contract/index.ts +10 -10
- package/src/contract/interaction.ts +62 -62
- package/src/contract/notification.ts +118 -118
- package/src/contract/permission.ts +55 -60
- package/src/contract/reconcile.ts +88 -88
- package/src/contract/recurrence.ts +129 -129
- package/src/diagnostics/burst.ts +101 -101
- package/src/diagnostics/events.ts +81 -72
- package/src/diagnostics/redact.ts +55 -72
- package/src/diagnostics/snapshot.ts +100 -64
- package/src/engine/adapter.ts +172 -172
- package/src/engine/codec.ts +89 -89
- package/src/engine/notify-kit-engine.ts +715 -707
- package/src/index.ts +208 -208
- package/src/initialize.ts +247 -236
- package/src/interaction/index.ts +146 -146
- package/src/maintenance/cycle.ts +150 -154
- package/src/maintenance/permission-refresh.ts +39 -41
- package/src/maintenance/reanchor.ts +101 -101
- package/src/maintenance/toggle.ts +66 -64
- package/src/maintenance/verify.ts +83 -83
- package/src/maintenance/window-refresh.ts +64 -64
- package/src/permission/index.ts +183 -183
- package/src/permission/state.ts +77 -77
- package/src/push/envelope.ts +165 -165
- package/src/push/fcm-provider.ts +150 -146
- package/src/push/gate.ts +190 -190
- package/src/push/index.ts +33 -29
- package/src/push/instructions.ts +114 -109
- package/src/push/provider-adapter.ts +54 -54
- package/src/push/signout.ts +65 -65
- package/src/reconcile/chunk.ts +53 -53
- package/src/reconcile/diff.ts +142 -145
- package/src/reconcile/duplicates.ts +73 -73
- package/src/reconcile/index.ts +180 -180
- package/src/reconcile/truncate.ts +56 -56
- package/src/recurrence/clock-guard.ts +73 -71
- package/src/recurrence/expand.ts +326 -319
- package/src/recurrence/index.ts +270 -259
- package/src/repeat/expiry.ts +53 -55
- package/src/repeat/index.ts +54 -54
- package/src/repeat/marker.ts +72 -72
- package/src/repeat/place.ts +73 -73
- package/src/schedule/anchor.ts +294 -267
- package/src/schedule/index.ts +174 -181
- package/src/schedule/partition.ts +88 -85
- package/src/schedule/resolve-moment.ts +80 -80
- package/src/schedule/validate-content.ts +111 -111
- package/src/schedule/validate-identifier.ts +126 -126
- package/src/schedule/writer.ts +55 -55
- package/src/state/caps.ts +71 -71
- package/src/state/dedup-ledger.ts +35 -35
- package/src/state/interaction-buffer.ts +101 -73
- package/src/state/mmkv-store.ts +92 -75
- package/src/state/permission-history.ts +41 -41
- package/src/state/record.ts +270 -263
- package/src/state/reset.ts +41 -41
- package/src/state/series-ledger.ts +91 -91
- package/src/state/store.ts +43 -40
- package/src/testing/conformance/index.ts +258 -248
- package/src/testing/fake-engine.ts +462 -468
- package/src/testing/fake-provider.ts +126 -129
- package/src/testing/faults.ts +72 -72
- package/src/testing/index.ts +23 -28
- package/src/testing/measure.ts +337 -337
package/src/engine/codec.ts
CHANGED
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The metadata codec for a notification's platform data bag.
|
|
3
|
-
*
|
|
4
|
-
* Anchoring, series origin, tags, routing and actions have no engine
|
|
5
|
-
* equivalent, and `getPending` must return them (FR-010) or a consumer cannot
|
|
6
|
-
* diff against intent. They travel as strings in the free-form data bag, which
|
|
7
|
-
* is the one field both platforms carry through unchanged.
|
|
8
|
-
*
|
|
9
|
-
* Shared by the real engine and the fake. When the fake stored requests
|
|
10
|
-
* verbatim it could not lose a field the real engine lost — `actions` never
|
|
11
|
-
* came back from a device, so every reconcile replaced every notification that
|
|
12
|
-
* had them, and the fake-backed tests never saw it.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { EngineScheduleRequest } from './adapter';
|
|
16
|
-
import type { Action, AnchoringMode, PendingEntry, RoutingPayload } from '../contract/notification';
|
|
17
|
-
|
|
18
|
-
/** Everything `getPending` reports that only the data bag can carry. */
|
|
19
|
-
export type PackedMetadata = Pick<
|
|
20
|
-
PendingEntry,
|
|
21
|
-
| 'anchoring'
|
|
22
|
-
| 'anchoringGeneration'
|
|
23
|
-
| 'tone'
|
|
24
|
-
| 'routing'
|
|
25
|
-
| 'actions'
|
|
26
|
-
| 'ownerTag'
|
|
27
|
-
| 'groupTag'
|
|
28
|
-
| 'seriesId'
|
|
29
|
-
| 'mayBeDelayed'
|
|
30
|
-
| 'pushCreated'
|
|
31
|
-
>;
|
|
32
|
-
|
|
33
|
-
export function packData(request: EngineScheduleRequest): Record<string, string> {
|
|
34
|
-
return {
|
|
35
|
-
__cms_anchoring: request.request.moment.anchoring ?? 'wallClock',
|
|
36
|
-
// The IANA zone, not the tone. Storing the wrong value here does not merely
|
|
37
|
-
// mislabel an entry: the re-anchoring pass feeds it straight to
|
|
38
|
-
// Intl.DateTimeFormat, which throws a hard Hermes error on an invalid zone.
|
|
39
|
-
...(request.anchoringGeneration !== undefined
|
|
40
|
-
? { __cms_generation: request.anchoringGeneration }
|
|
41
|
-
: {}),
|
|
42
|
-
__cms_tone: request.request.tone,
|
|
43
|
-
__cms_approximate: String(request.approximate),
|
|
44
|
-
__cms_pushCreated: String(request.pushCreated ?? false),
|
|
45
|
-
...(request.seriesId !== undefined ? { __cms_series: request.seriesId } : {}),
|
|
46
|
-
...(request.request.ownerTag !== undefined ? { __cms_owner: request.request.ownerTag } : {}),
|
|
47
|
-
...(request.request.groupTag !== undefined ? { __cms_group: request.request.groupTag } : {}),
|
|
48
|
-
...(request.request.routing !== undefined
|
|
49
|
-
? { __cms_routing: JSON.stringify(request.request.routing) }
|
|
50
|
-
: {}),
|
|
51
|
-
...(request.request.actions !== undefined
|
|
52
|
-
? { __cms_actions: JSON.stringify(request.request.actions) }
|
|
53
|
-
: {}),
|
|
54
|
-
// A stop marker's whole payload. It travels here rather than in `ownerTag`
|
|
55
|
-
// or `routing` so it can never collide with a value a consumer owns.
|
|
56
|
-
...(request.repeatStop !== undefined
|
|
57
|
-
? {
|
|
58
|
-
__cms_stop_target: request.repeatStop.targetId,
|
|
59
|
-
__cms_stop_due: String(request.repeatStop.dueAt),
|
|
60
|
-
}
|
|
61
|
-
: {}),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** A JSON field we cannot parse is reported as absent: the notification is still valid. */
|
|
66
|
-
function parseJson<T>(raw: string | undefined): T | undefined {
|
|
67
|
-
if (typeof raw !== 'string') return undefined;
|
|
68
|
-
try {
|
|
69
|
-
return JSON.parse(raw) as T;
|
|
70
|
-
} catch {
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function unpackData(data: Record<string, string>): PackedMetadata {
|
|
76
|
-
const anchoring: AnchoringMode = data.__cms_anchoring === 'absolute' ? 'absolute' : 'wallClock';
|
|
77
|
-
return {
|
|
78
|
-
anchoring,
|
|
79
|
-
anchoringGeneration: anchoring === 'absolute' ? undefined : data.__cms_generation,
|
|
80
|
-
tone: data.__cms_tone ?? '',
|
|
81
|
-
routing: parseJson<RoutingPayload>(data.__cms_routing),
|
|
82
|
-
actions: parseJson<readonly Action[]>(data.__cms_actions),
|
|
83
|
-
ownerTag: data.__cms_owner,
|
|
84
|
-
groupTag: data.__cms_group,
|
|
85
|
-
seriesId: data.__cms_series,
|
|
86
|
-
mayBeDelayed: data.__cms_approximate === 'true',
|
|
87
|
-
pushCreated: data.__cms_pushCreated === 'true',
|
|
88
|
-
};
|
|
89
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The metadata codec for a notification's platform data bag.
|
|
3
|
+
*
|
|
4
|
+
* Anchoring, series origin, tags, routing and actions have no engine
|
|
5
|
+
* equivalent, and `getPending` must return them (FR-010) or a consumer cannot
|
|
6
|
+
* diff against intent. They travel as strings in the free-form data bag, which
|
|
7
|
+
* is the one field both platforms carry through unchanged.
|
|
8
|
+
*
|
|
9
|
+
* Shared by the real engine and the fake. When the fake stored requests
|
|
10
|
+
* verbatim it could not lose a field the real engine lost — `actions` never
|
|
11
|
+
* came back from a device, so every reconcile replaced every notification that
|
|
12
|
+
* had them, and the fake-backed tests never saw it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { EngineScheduleRequest } from './adapter';
|
|
16
|
+
import type { Action, AnchoringMode, PendingEntry, RoutingPayload } from '../contract/notification';
|
|
17
|
+
|
|
18
|
+
/** Everything `getPending` reports that only the data bag can carry. */
|
|
19
|
+
export type PackedMetadata = Pick<
|
|
20
|
+
PendingEntry,
|
|
21
|
+
| 'anchoring'
|
|
22
|
+
| 'anchoringGeneration'
|
|
23
|
+
| 'tone'
|
|
24
|
+
| 'routing'
|
|
25
|
+
| 'actions'
|
|
26
|
+
| 'ownerTag'
|
|
27
|
+
| 'groupTag'
|
|
28
|
+
| 'seriesId'
|
|
29
|
+
| 'mayBeDelayed'
|
|
30
|
+
| 'pushCreated'
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
export function packData(request: EngineScheduleRequest): Record<string, string> {
|
|
34
|
+
return {
|
|
35
|
+
__cms_anchoring: request.request.moment.anchoring ?? 'wallClock',
|
|
36
|
+
// The IANA zone, not the tone. Storing the wrong value here does not merely
|
|
37
|
+
// mislabel an entry: the re-anchoring pass feeds it straight to
|
|
38
|
+
// Intl.DateTimeFormat, which throws a hard Hermes error on an invalid zone.
|
|
39
|
+
...(request.anchoringGeneration !== undefined
|
|
40
|
+
? { __cms_generation: request.anchoringGeneration }
|
|
41
|
+
: {}),
|
|
42
|
+
__cms_tone: request.request.tone,
|
|
43
|
+
__cms_approximate: String(request.approximate),
|
|
44
|
+
__cms_pushCreated: String(request.pushCreated ?? false),
|
|
45
|
+
...(request.seriesId !== undefined ? { __cms_series: request.seriesId } : {}),
|
|
46
|
+
...(request.request.ownerTag !== undefined ? { __cms_owner: request.request.ownerTag } : {}),
|
|
47
|
+
...(request.request.groupTag !== undefined ? { __cms_group: request.request.groupTag } : {}),
|
|
48
|
+
...(request.request.routing !== undefined
|
|
49
|
+
? { __cms_routing: JSON.stringify(request.request.routing) }
|
|
50
|
+
: {}),
|
|
51
|
+
...(request.request.actions !== undefined
|
|
52
|
+
? { __cms_actions: JSON.stringify(request.request.actions) }
|
|
53
|
+
: {}),
|
|
54
|
+
// A stop marker's whole payload. It travels here rather than in `ownerTag`
|
|
55
|
+
// or `routing` so it can never collide with a value a consumer owns.
|
|
56
|
+
...(request.repeatStop !== undefined
|
|
57
|
+
? {
|
|
58
|
+
__cms_stop_target: request.repeatStop.targetId,
|
|
59
|
+
__cms_stop_due: String(request.repeatStop.dueAt),
|
|
60
|
+
}
|
|
61
|
+
: {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A JSON field we cannot parse is reported as absent: the notification is still valid. */
|
|
66
|
+
function parseJson<T>(raw: string | undefined): T | undefined {
|
|
67
|
+
if (typeof raw !== 'string') return undefined;
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(raw) as T;
|
|
70
|
+
} catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function unpackData(data: Record<string, string>): PackedMetadata {
|
|
76
|
+
const anchoring: AnchoringMode = data.__cms_anchoring === 'absolute' ? 'absolute' : 'wallClock';
|
|
77
|
+
return {
|
|
78
|
+
anchoring,
|
|
79
|
+
anchoringGeneration: anchoring === 'absolute' ? undefined : data.__cms_generation,
|
|
80
|
+
tone: data.__cms_tone ?? '',
|
|
81
|
+
routing: parseJson<RoutingPayload>(data.__cms_routing),
|
|
82
|
+
actions: parseJson<readonly Action[]>(data.__cms_actions),
|
|
83
|
+
ownerTag: data.__cms_owner,
|
|
84
|
+
groupTag: data.__cms_group,
|
|
85
|
+
seriesId: data.__cms_series,
|
|
86
|
+
mayBeDelayed: data.__cms_approximate === 'true',
|
|
87
|
+
pushCreated: data.__cms_pushCreated === 'true',
|
|
88
|
+
};
|
|
89
|
+
}
|