@chipmobilesdk/rn-notification 0.1.2 → 0.2.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +80 -42
  2. package/README.md +399 -306
  3. package/package.json +2 -1
  4. package/skills/sdk-rn-notification/SKILL.md +68 -3
  5. package/src/channel/migrate.ts +86 -12
  6. package/src/channel/tone-map.ts +130 -90
  7. package/src/channel/validate-tone.ts +61 -0
  8. package/src/contract/channel.ts +188 -89
  9. package/src/contract/diagnostics.ts +102 -102
  10. package/src/contract/errors.ts +130 -123
  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 +60 -60
  15. package/src/contract/reconcile.ts +88 -88
  16. package/src/contract/recurrence.ts +123 -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 +64 -64
  21. package/src/engine/adapter.ts +172 -79
  22. package/src/engine/notify-kit-engine.ts +761 -535
  23. package/src/index.ts +24 -0
  24. package/src/initialize.ts +174 -174
  25. package/src/interaction/index.ts +146 -146
  26. package/src/maintenance/cycle.ts +147 -147
  27. package/src/maintenance/permission-refresh.ts +41 -41
  28. package/src/maintenance/reanchor.ts +95 -95
  29. package/src/maintenance/toggle.ts +60 -60
  30. package/src/maintenance/verify.ts +83 -83
  31. package/src/maintenance/window-refresh.ts +61 -61
  32. package/src/permission/index.ts +183 -183
  33. package/src/permission/state.ts +77 -77
  34. package/src/push/envelope.ts +165 -165
  35. package/src/push/fcm-provider.ts +146 -146
  36. package/src/push/gate.ts +190 -190
  37. package/src/push/instructions.ts +109 -109
  38. package/src/push/provider-adapter.ts +54 -54
  39. package/src/push/signout.ts +65 -65
  40. package/src/reconcile/chunk.ts +53 -53
  41. package/src/reconcile/diff.ts +145 -145
  42. package/src/reconcile/duplicates.ts +73 -73
  43. package/src/reconcile/index.ts +14 -1
  44. package/src/reconcile/truncate.ts +56 -56
  45. package/src/recurrence/clock-guard.ts +71 -71
  46. package/src/recurrence/expand.ts +292 -292
  47. package/src/repeat/expiry.ts +55 -0
  48. package/src/repeat/index.ts +54 -0
  49. package/src/repeat/marker.ts +72 -0
  50. package/src/repeat/place.ts +73 -0
  51. package/src/schedule/anchor.ts +267 -267
  52. package/src/schedule/index.ts +184 -173
  53. package/src/schedule/partition.ts +85 -85
  54. package/src/schedule/resolve-moment.ts +80 -80
  55. package/src/schedule/validate-content.ts +111 -111
  56. package/src/schedule/validate-identifier.ts +126 -114
  57. package/src/state/caps.ts +71 -71
  58. package/src/state/interaction-buffer.ts +73 -73
  59. package/src/state/mmkv-store.ts +71 -71
  60. package/src/state/permission-history.ts +41 -41
  61. package/src/state/record.ts +258 -246
  62. package/src/state/reset.ts +41 -41
  63. package/src/state/series-ledger.ts +84 -84
  64. package/src/state/store.ts +40 -40
  65. package/src/testing/conformance/index.ts +248 -194
  66. package/src/testing/fake-engine.ts +469 -284
  67. package/src/testing/fake-provider.ts +129 -129
  68. package/src/testing/faults.ts +72 -72
  69. package/src/testing/measure.ts +336 -333
@@ -1,84 +1,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 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 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,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
+ }