@chipmobilesdk/rn-notification 0.3.0 → 0.4.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +174 -109
  2. package/README.md +414 -399
  3. package/package.json +4 -21
  4. package/skills/sdk-rn-notification/SKILL.md +5 -4
  5. package/src/channel/migrate.ts +285 -285
  6. package/src/channel/tone-map.ts +130 -130
  7. package/src/channel/validate-tone.ts +61 -61
  8. package/src/contract/channel.ts +192 -188
  9. package/src/contract/diagnostics.ts +102 -102
  10. package/src/contract/errors.ts +134 -130
  11. package/src/contract/index.ts +10 -10
  12. package/src/contract/interaction.ts +62 -62
  13. package/src/contract/notification.ts +118 -118
  14. package/src/contract/permission.ts +55 -60
  15. package/src/contract/reconcile.ts +88 -88
  16. package/src/contract/recurrence.ts +129 -123
  17. package/src/diagnostics/burst.ts +101 -101
  18. package/src/diagnostics/events.ts +72 -72
  19. package/src/diagnostics/redact.ts +72 -72
  20. package/src/diagnostics/snapshot.ts +67 -64
  21. package/src/engine/adapter.ts +172 -172
  22. package/src/engine/codec.ts +89 -0
  23. package/src/engine/notify-kit-engine.ts +706 -761
  24. package/src/index.ts +208 -208
  25. package/src/initialize.ts +240 -174
  26. package/src/interaction/index.ts +146 -146
  27. package/src/maintenance/cycle.ts +150 -147
  28. package/src/maintenance/permission-refresh.ts +39 -41
  29. package/src/maintenance/reanchor.ts +101 -95
  30. package/src/maintenance/toggle.ts +66 -60
  31. package/src/maintenance/verify.ts +83 -83
  32. package/src/maintenance/window-refresh.ts +64 -61
  33. package/src/permission/index.ts +183 -183
  34. package/src/permission/state.ts +77 -77
  35. package/src/push/envelope.ts +165 -165
  36. package/src/push/fcm-provider.ts +143 -146
  37. package/src/push/gate.ts +190 -190
  38. package/src/push/index.ts +33 -29
  39. package/src/push/instructions.ts +114 -109
  40. package/src/push/provider-adapter.ts +54 -54
  41. package/src/push/signout.ts +65 -65
  42. package/src/reconcile/chunk.ts +53 -53
  43. package/src/reconcile/diff.ts +142 -145
  44. package/src/reconcile/duplicates.ts +73 -73
  45. package/src/reconcile/index.ts +180 -188
  46. package/src/reconcile/truncate.ts +56 -56
  47. package/src/recurrence/clock-guard.ts +73 -71
  48. package/src/recurrence/expand.ts +326 -292
  49. package/src/recurrence/index.ts +270 -177
  50. package/src/repeat/expiry.ts +53 -55
  51. package/src/repeat/index.ts +54 -54
  52. package/src/repeat/marker.ts +72 -72
  53. package/src/repeat/place.ts +73 -73
  54. package/src/schedule/anchor.ts +278 -267
  55. package/src/schedule/index.ts +174 -184
  56. package/src/schedule/partition.ts +88 -85
  57. package/src/schedule/resolve-moment.ts +80 -80
  58. package/src/schedule/validate-content.ts +111 -111
  59. package/src/schedule/validate-identifier.ts +126 -126
  60. package/src/schedule/writer.ts +55 -0
  61. package/src/state/caps.ts +71 -71
  62. package/src/state/dedup-ledger.ts +35 -35
  63. package/src/state/interaction-buffer.ts +73 -73
  64. package/src/state/mmkv-store.ts +75 -75
  65. package/src/state/permission-history.ts +41 -41
  66. package/src/state/record.ts +266 -258
  67. package/src/state/reset.ts +41 -41
  68. package/src/state/series-ledger.ts +91 -84
  69. package/src/state/store.ts +40 -40
  70. package/src/testing/conformance/index.ts +258 -248
  71. package/src/testing/fake-engine.ts +462 -469
  72. package/src/testing/fake-provider.ts +126 -129
  73. package/src/testing/faults.ts +72 -72
  74. package/src/testing/index.ts +23 -28
  75. package/src/testing/measure.ts +337 -337
@@ -1,84 +1,91 @@
1
- /**
2
- * The recurrence series ledger.
3
- *
4
- * What makes automatic maintenance possible without a desired set from the
5
- * consumer: the package remembers which series it owns, how far each has been
6
- * expanded, and how many occurrences a count-limited series has already
7
- * emitted. None of that is derivable from the platform's pending set, which
8
- * knows only about individual notifications.
9
- *
10
- * It also defines what maintenance is allowed to touch. An occurrence belongs
11
- * to a series in this ledger, so cancelling it is within scope; anything else
12
- * is a directly-scheduled notification the package does not own.
13
- */
14
-
15
- import type { StateManager, SeriesEntry } from './record';
16
- import type { RecurrenceRule } from '../contract/recurrence';
17
- import type { AnchoringMode } from '../contract/notification';
18
-
19
- export interface SeriesRecord {
20
- readonly seriesId: string;
21
- readonly anchoring: AnchoringMode;
22
- readonly expandedThrough: number;
23
- readonly emittedCount: number;
24
- readonly exhausted: boolean;
25
- readonly at: number;
26
- }
27
-
28
- export function readSeries(state: StateManager, seriesId: string): SeriesRecord | undefined {
29
- return state.read().seriesLedger.find(entry => entry.seriesId === seriesId);
30
- }
31
-
32
- export function listSeries(state: StateManager): readonly SeriesRecord[] {
33
- return state.read().seriesLedger;
34
- }
35
-
36
- /** Records or updates a series. Replaces in place so a re-registration cannot duplicate. */
37
- export function upsertSeries(
38
- state: StateManager,
39
- rule: RecurrenceRule,
40
- update: { readonly expandedThrough: number; readonly emittedCount: number; readonly exhausted: boolean },
41
- now: number,
42
- ): void {
43
- const entry: SeriesEntry = {
44
- at: now,
45
- seriesId: rule.id,
46
- anchoring: rule.anchoring ?? 'wallClock',
47
- expandedThrough: update.expandedThrough,
48
- emittedCount: update.emittedCount,
49
- exhausted: update.exhausted,
50
- };
51
-
52
- state.update('seriesLedger', current => [
53
- ...current.filter(existing => existing.seriesId !== rule.id),
54
- entry,
55
- ]);
56
- }
57
-
58
- export function removeSeries(state: StateManager, seriesId: string): void {
59
- state.update('seriesLedger', current => current.filter(entry => entry.seriesId !== seriesId));
60
- }
61
-
62
- /**
63
- * Identifiers the package owns.
64
- *
65
- * An occurrence identifier is `<seriesId>#<instant>`, so ownership is decided
66
- * by prefix rather than by an enumeration the ledger would have to keep in sync
67
- * with the device. That keeps the check correct even for occurrences scheduled
68
- * before the app last restarted.
69
- */
70
- export function ownsIdentifier(state: StateManager, id: string): boolean {
71
- const separator = id.lastIndexOf('#');
72
- if (separator === -1) return false;
73
-
74
- const seriesId = id.slice(0, separator);
75
- return state.read().seriesLedger.some(entry => entry.seriesId === seriesId);
76
- }
77
-
78
- /** Every occurrence identifier currently attributable to a tracked series. */
79
- export function ownedIdentifiers(
80
- state: StateManager,
81
- pendingIds: readonly string[],
82
- ): ReadonlySet<string> {
83
- return new Set(pendingIds.filter(id => ownsIdentifier(state, id)));
84
- }
1
+ /**
2
+ * The recurrence series ledger.
3
+ *
4
+ * What makes automatic maintenance possible without a desired set from the
5
+ * consumer: the package remembers which series it owns, how far each has been
6
+ * expanded, and how many occurrences a count-limited series has already
7
+ * emitted. None of that is derivable from the platform's pending set, which
8
+ * knows only about individual notifications.
9
+ *
10
+ * It also defines what maintenance is allowed to touch. An occurrence belongs
11
+ * to a series in this ledger, so cancelling it is within scope; anything else
12
+ * is a directly-scheduled notification the package does not own.
13
+ */
14
+
15
+ import type { StateManager, SeriesEntry } from './record';
16
+ import type { RecurrenceRule } from '../contract/recurrence';
17
+ import type { AnchoringMode } from '../contract/notification';
18
+
19
+ export interface SeriesRecord {
20
+ readonly seriesId: string;
21
+ readonly anchoring: AnchoringMode;
22
+ readonly expandedThrough: number;
23
+ readonly emittedCount: number;
24
+ readonly exhausted: boolean;
25
+ readonly lastEmittedKey?: string;
26
+ readonly at: number;
27
+ }
28
+
29
+ export function readSeries(state: StateManager, seriesId: string): SeriesRecord | undefined {
30
+ return state.read().seriesLedger.find(entry => entry.seriesId === seriesId);
31
+ }
32
+
33
+ export function listSeries(state: StateManager): readonly SeriesRecord[] {
34
+ return state.read().seriesLedger;
35
+ }
36
+
37
+ /** Records or updates a series. Replaces in place so a re-registration cannot duplicate. */
38
+ export function upsertSeries(
39
+ state: StateManager,
40
+ rule: RecurrenceRule,
41
+ update: {
42
+ readonly expandedThrough: number;
43
+ readonly emittedCount: number;
44
+ readonly exhausted: boolean;
45
+ readonly lastEmittedKey?: string;
46
+ },
47
+ now: number,
48
+ ): void {
49
+ const entry: SeriesEntry = {
50
+ at: now,
51
+ seriesId: rule.id,
52
+ anchoring: rule.anchoring ?? 'wallClock',
53
+ expandedThrough: update.expandedThrough,
54
+ emittedCount: update.emittedCount,
55
+ exhausted: update.exhausted,
56
+ ...(update.lastEmittedKey !== undefined ? { lastEmittedKey: update.lastEmittedKey } : {}),
57
+ };
58
+
59
+ state.update('seriesLedger', current => [
60
+ ...current.filter(existing => existing.seriesId !== rule.id),
61
+ entry,
62
+ ]);
63
+ }
64
+
65
+ export function removeSeries(state: StateManager, seriesId: string): void {
66
+ state.update('seriesLedger', current => current.filter(entry => entry.seriesId !== seriesId));
67
+ }
68
+
69
+ /**
70
+ * Identifiers the package owns.
71
+ *
72
+ * An occurrence identifier is `<seriesId>#<instant>`, so ownership is decided
73
+ * by prefix rather than by an enumeration the ledger would have to keep in sync
74
+ * with the device. That keeps the check correct even for occurrences scheduled
75
+ * before the app last restarted.
76
+ */
77
+ export function ownsIdentifier(state: StateManager, id: string): boolean {
78
+ const separator = id.lastIndexOf('#');
79
+ if (separator === -1) return false;
80
+
81
+ const seriesId = id.slice(0, separator);
82
+ return state.read().seriesLedger.some(entry => entry.seriesId === seriesId);
83
+ }
84
+
85
+ /** Every occurrence identifier currently attributable to a tracked series. */
86
+ export function ownedIdentifiers(
87
+ state: StateManager,
88
+ pendingIds: readonly string[],
89
+ ): ReadonlySet<string> {
90
+ return new Set(pendingIds.filter(id => ownsIdentifier(state, id)));
91
+ }
@@ -1,40 +1,40 @@
1
- /**
2
- * The storage port behind the persisted state record.
3
- *
4
- * Kept as a port so the pure state logic is testable with no native module,
5
- * and so MMKV stays confined to one file. This is internal — a consumer can
6
- * reset or clear state, but can never read or write the record directly.
7
- */
8
-
9
- export interface StateStore {
10
- getString(key: string): string | undefined;
11
- setString(key: string, value: string): void;
12
- delete(key: string): void;
13
- clearAll(): void;
14
- /** Approximate on-disk size, for the health report. */
15
- approximateBytes(): number;
16
- }
17
-
18
- /** In-memory store, used by tests and as the fallback when MMKV is unavailable. */
19
- export function createMemoryStore(): StateStore {
20
- const map = new Map<string, string>();
21
- return {
22
- getString: key => map.get(key),
23
- setString: (key, value) => {
24
- map.set(key, value);
25
- },
26
- delete: key => {
27
- map.delete(key);
28
- },
29
- clearAll: () => {
30
- map.clear();
31
- },
32
- approximateBytes: () => {
33
- let total = 0;
34
- for (const [key, value] of map) {
35
- total += key.length + value.length;
36
- }
37
- return total;
38
- },
39
- };
40
- }
1
+ /**
2
+ * The storage port behind the persisted state record.
3
+ *
4
+ * Kept as a port so the pure state logic is testable with no native module,
5
+ * and so MMKV stays confined to one file. This is internal — a consumer can
6
+ * reset or clear state, but can never read or write the record directly.
7
+ */
8
+
9
+ export interface StateStore {
10
+ getString(key: string): string | undefined;
11
+ setString(key: string, value: string): void;
12
+ delete(key: string): void;
13
+ clearAll(): void;
14
+ /** Approximate on-disk size, for the health report. */
15
+ approximateBytes(): number;
16
+ }
17
+
18
+ /** In-memory store, used by tests and as the fallback when MMKV is unavailable. */
19
+ export function createMemoryStore(): StateStore {
20
+ const map = new Map<string, string>();
21
+ return {
22
+ getString: key => map.get(key),
23
+ setString: (key, value) => {
24
+ map.set(key, value);
25
+ },
26
+ delete: key => {
27
+ map.delete(key);
28
+ },
29
+ clearAll: () => {
30
+ map.clear();
31
+ },
32
+ approximateBytes: () => {
33
+ let total = 0;
34
+ for (const [key, value] of map) {
35
+ total += key.length + value.length;
36
+ }
37
+ return total;
38
+ },
39
+ };
40
+ }