@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +257 -166
  2. package/README.md +420 -410
  3. package/package.json +61 -75
  4. package/skills/sdk-rn-notification/SKILL.md +172 -173
  5. package/src/channel/migrate.ts +285 -289
  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 +142 -134
  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 -129
  17. package/src/diagnostics/burst.ts +101 -101
  18. package/src/diagnostics/events.ts +81 -72
  19. package/src/diagnostics/redact.ts +55 -72
  20. package/src/diagnostics/snapshot.ts +100 -64
  21. package/src/engine/adapter.ts +172 -172
  22. package/src/engine/codec.ts +89 -89
  23. package/src/engine/notify-kit-engine.ts +715 -707
  24. package/src/index.ts +208 -208
  25. package/src/initialize.ts +247 -236
  26. package/src/interaction/index.ts +146 -146
  27. package/src/maintenance/cycle.ts +150 -154
  28. package/src/maintenance/permission-refresh.ts +39 -41
  29. package/src/maintenance/reanchor.ts +101 -101
  30. package/src/maintenance/toggle.ts +66 -64
  31. package/src/maintenance/verify.ts +83 -83
  32. package/src/maintenance/window-refresh.ts +64 -64
  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 +150 -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 -180
  46. package/src/reconcile/truncate.ts +56 -56
  47. package/src/recurrence/clock-guard.ts +73 -71
  48. package/src/recurrence/expand.ts +326 -319
  49. package/src/recurrence/index.ts +270 -259
  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 +294 -267
  55. package/src/schedule/index.ts +174 -181
  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 -55
  61. package/src/state/caps.ts +71 -71
  62. package/src/state/dedup-ledger.ts +35 -35
  63. package/src/state/interaction-buffer.ts +101 -73
  64. package/src/state/mmkv-store.ts +92 -75
  65. package/src/state/permission-history.ts +41 -41
  66. package/src/state/record.ts +270 -263
  67. package/src/state/reset.ts +41 -41
  68. package/src/state/series-ledger.ts +91 -91
  69. package/src/state/store.ts +43 -40
  70. package/src/testing/conformance/index.ts +258 -248
  71. package/src/testing/fake-engine.ts +462 -468
  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,129 +1,126 @@
1
- /**
2
- * In-memory push provider for tests.
3
- *
4
- * Exists so the entire push surface — token lifecycle, receipt in every app
5
- * state, malformed payloads, redelivery, and provider unavailability — is
6
- * provable with no live provider and no device.
7
- *
8
- * It is also the second adapter that proves the swap costs a consumer nothing:
9
- * running the full push suite against this instead of the real provider must
10
- * pass with zero changes to consuming code.
11
- */
12
-
13
- import type {
14
- PushAvailability,
15
- PushProviderAdapter,
16
- } from '../push/provider-adapter';
17
- import type { Unsubscribe } from '../contract/interaction';
18
-
19
- export interface FakeProviderOptions {
20
- readonly name?: string;
21
- readonly availability?: PushAvailability;
22
- readonly initialToken?: string;
23
- /** When true, `getToken` resolves only after `releaseToken()` is called. */
24
- readonly withholdToken?: boolean;
25
- readonly invalidationSucceeds?: boolean;
26
- }
27
-
28
- export interface FakeProviderControls {
29
- /** Delivers a raw payload as the provider would. */
30
- deliver(raw: unknown): void;
31
- /** Queues the payload that launched the app. */
32
- queueInitial(raw: unknown): void;
33
- /** Simulates the platform rotating the registration token. */
34
- rotateToken(token: string): void;
35
- /** Resolves a withheld token, so "waits for availability" is testable. */
36
- releaseToken(token: string): void;
37
- setAvailability(availability: PushAvailability): void;
38
- readonly invalidated: boolean;
39
- }
40
-
41
- export type FakeProvider = PushProviderAdapter & FakeProviderControls;
42
-
43
- export function createFakeProvider(options: FakeProviderOptions = {}): FakeProvider {
44
- let availability: PushAvailability = options.availability ?? 'available';
45
- let token = options.withholdToken === true ? undefined : (options.initialToken ?? 'fake-token-1');
46
- let invalidated = false;
47
- let messageHandlers: ((raw: unknown) => void)[] = [];
48
- let tokenHandlers: ((token: string) => void)[] = [];
49
- let initial: unknown | undefined;
50
- let waiters: ((token: string) => void)[] = [];
51
-
52
- return {
53
- name: options.name ?? 'fake-provider',
54
-
55
- async getAvailability() {
56
- return availability;
57
- },
58
-
59
- async getToken() {
60
- if (token !== undefined) return token;
61
- // Waits rather than returning a placeholder — an empty string presented
62
- // as a token is worse than not answering yet.
63
- return new Promise<string>(resolve => {
64
- waiters.push(resolve);
65
- });
66
- },
67
-
68
- onTokenChange(handler): Unsubscribe {
69
- tokenHandlers.push(handler);
70
- return () => {
71
- tokenHandlers = tokenHandlers.filter(h => h !== handler);
72
- };
73
- },
74
-
75
- async invalidateToken() {
76
- invalidated = true;
77
- token = undefined;
78
- return options.invalidationSucceeds ?? true;
79
- },
80
-
81
- subscribeToMessages(handler): Unsubscribe {
82
- messageHandlers.push(handler);
83
- return () => {
84
- messageHandlers = messageHandlers.filter(h => h !== handler);
85
- };
86
- },
87
-
88
- async takeInitialMessage() {
89
- const raw = initial;
90
- initial = undefined;
91
- return raw;
92
- },
93
-
94
- // --- FakeProviderControls ---------------------------------------------
95
-
96
- deliver(raw) {
97
- for (const handler of messageHandlers) {
98
- handler(raw);
99
- }
100
- },
101
-
102
- queueInitial(raw) {
103
- initial = raw;
104
- },
105
-
106
- rotateToken(next) {
107
- token = next;
108
- for (const handler of tokenHandlers) {
109
- handler(next);
110
- }
111
- },
112
-
113
- releaseToken(next) {
114
- token = next;
115
- for (const resolve of waiters) {
116
- resolve(next);
117
- }
118
- waiters = [];
119
- },
120
-
121
- setAvailability(next) {
122
- availability = next;
123
- },
124
-
125
- get invalidated() {
126
- return invalidated;
127
- },
128
- };
129
- }
1
+ /**
2
+ * In-memory push provider for tests.
3
+ *
4
+ * Exists so the entire push surface — token lifecycle, receipt in every app
5
+ * state, malformed payloads, redelivery, and provider unavailability — is
6
+ * provable with no live provider and no device.
7
+ *
8
+ * It is also the second adapter that proves the swap costs a consumer nothing:
9
+ * running the full push suite against this instead of the real provider must
10
+ * pass with zero changes to consuming code.
11
+ */
12
+
13
+ import type { PushAvailability, PushProviderAdapter } from '../push/provider-adapter';
14
+ import type { Unsubscribe } from '../contract/interaction';
15
+
16
+ export interface FakeProviderOptions {
17
+ readonly name?: string;
18
+ readonly availability?: PushAvailability;
19
+ readonly initialToken?: string;
20
+ /** When true, `getToken` resolves only after `releaseToken()` is called. */
21
+ readonly withholdToken?: boolean;
22
+ readonly invalidationSucceeds?: boolean;
23
+ }
24
+
25
+ export interface FakeProviderControls {
26
+ /** Delivers a raw payload as the provider would. */
27
+ deliver(raw: unknown): void;
28
+ /** Queues the payload that launched the app. */
29
+ queueInitial(raw: unknown): void;
30
+ /** Simulates the platform rotating the registration token. */
31
+ rotateToken(token: string): void;
32
+ /** Resolves a withheld token, so "waits for availability" is testable. */
33
+ releaseToken(token: string): void;
34
+ setAvailability(availability: PushAvailability): void;
35
+ readonly invalidated: boolean;
36
+ }
37
+
38
+ export type FakeProvider = PushProviderAdapter & FakeProviderControls;
39
+
40
+ export function createFakeProvider(options: FakeProviderOptions = {}): FakeProvider {
41
+ let availability: PushAvailability = options.availability ?? 'available';
42
+ let token = options.withholdToken === true ? undefined : (options.initialToken ?? 'fake-token-1');
43
+ let invalidated = false;
44
+ let messageHandlers: ((raw: unknown) => void)[] = [];
45
+ let tokenHandlers: ((token: string) => void)[] = [];
46
+ let initial: unknown | undefined;
47
+ let waiters: ((token: string) => void)[] = [];
48
+
49
+ return {
50
+ name: options.name ?? 'fake-provider',
51
+
52
+ async getAvailability() {
53
+ return availability;
54
+ },
55
+
56
+ async getToken() {
57
+ if (token !== undefined) return token;
58
+ // Waits rather than returning a placeholder — an empty string presented
59
+ // as a token is worse than not answering yet.
60
+ return new Promise<string>(resolve => {
61
+ waiters.push(resolve);
62
+ });
63
+ },
64
+
65
+ onTokenChange(handler): Unsubscribe {
66
+ tokenHandlers.push(handler);
67
+ return () => {
68
+ tokenHandlers = tokenHandlers.filter(h => h !== handler);
69
+ };
70
+ },
71
+
72
+ async invalidateToken() {
73
+ invalidated = true;
74
+ token = undefined;
75
+ return options.invalidationSucceeds ?? true;
76
+ },
77
+
78
+ subscribeToMessages(handler): Unsubscribe {
79
+ messageHandlers.push(handler);
80
+ return () => {
81
+ messageHandlers = messageHandlers.filter(h => h !== handler);
82
+ };
83
+ },
84
+
85
+ async takeInitialMessage() {
86
+ const raw = initial;
87
+ initial = undefined;
88
+ return raw;
89
+ },
90
+
91
+ // --- FakeProviderControls ---------------------------------------------
92
+
93
+ deliver(raw) {
94
+ for (const handler of messageHandlers) {
95
+ handler(raw);
96
+ }
97
+ },
98
+
99
+ queueInitial(raw) {
100
+ initial = raw;
101
+ },
102
+
103
+ rotateToken(next) {
104
+ token = next;
105
+ for (const handler of tokenHandlers) {
106
+ handler(next);
107
+ }
108
+ },
109
+
110
+ releaseToken(next) {
111
+ token = next;
112
+ for (const resolve of waiters) {
113
+ resolve(next);
114
+ }
115
+ waiters = [];
116
+ },
117
+
118
+ setAvailability(next) {
119
+ availability = next;
120
+ },
121
+
122
+ get invalidated() {
123
+ return invalidated;
124
+ },
125
+ };
126
+ }
@@ -1,72 +1,72 @@
1
- /**
2
- * Fault injection.
3
- *
4
- * Every documented failure condition must be reachable on demand, or the claim
5
- * that each has its own distinct diagnostic is untestable. This is what the
6
- * demo app's fault panel and the conformance suite both drive.
7
- */
8
-
9
- import { ALL_DIAGNOSTIC_CODES, type DiagnosticCode } from '../contract/errors';
10
-
11
- export type FaultKind = DiagnosticCode;
12
-
13
- export interface FaultRecord {
14
- readonly code: DiagnosticCode;
15
- readonly at: number;
16
- }
17
-
18
- export interface Disposable {
19
- dispose(): void;
20
- }
21
-
22
- export interface FaultInjectorOptions {
23
- readonly now?: () => number;
24
- readonly emit?: (code: string, message: string, context?: Record<string, unknown>) => void;
25
- }
26
-
27
- /**
28
- * Triggers a named condition and records that it fired.
29
- *
30
- * Deliberately a thin mechanism: it proves each identifier is reachable and
31
- * distinct, which is the property under test. Making the *real* condition occur
32
- * is each module's own test's job.
33
- */
34
- export function createFaultInjector(options: FaultInjectorOptions = {}) {
35
- const now = options.now ?? (() => Date.now());
36
- const emit = options.emit ?? (() => {});
37
- const fired: FaultRecord[] = [];
38
-
39
- return {
40
- /** Every condition a consumer can reach from the fault panel. */
41
- available: ALL_DIAGNOSTIC_CODES,
42
-
43
- inject(kind: FaultKind): Disposable {
44
- const record: FaultRecord = { code: kind, at: now() };
45
- fired.push(record);
46
- emit(kind, `Injected fault: ${kind}`, { injected: true });
47
-
48
- return {
49
- dispose() {
50
- const index = fired.indexOf(record);
51
- if (index >= 0) fired.splice(index, 1);
52
- },
53
- };
54
- },
55
-
56
- get fired(): readonly FaultRecord[] {
57
- return fired;
58
- },
59
-
60
- /** Which documented conditions have not yet been exercised. */
61
- unexercised(): readonly DiagnosticCode[] {
62
- const seen = new Set(fired.map(record => record.code));
63
- return ALL_DIAGNOSTIC_CODES.filter(code => !seen.has(code));
64
- },
65
-
66
- reset(): void {
67
- fired.length = 0;
68
- },
69
- };
70
- }
71
-
72
- export type FaultInjector = ReturnType<typeof createFaultInjector>;
1
+ /**
2
+ * Fault injection.
3
+ *
4
+ * Every documented failure condition must be reachable on demand, or the claim
5
+ * that each has its own distinct diagnostic is untestable. This is what the
6
+ * demo app's fault panel and the conformance suite both drive.
7
+ */
8
+
9
+ import { ALL_DIAGNOSTIC_CODES, type DiagnosticCode } from '../contract/errors';
10
+
11
+ export type FaultKind = DiagnosticCode;
12
+
13
+ export interface FaultRecord {
14
+ readonly code: DiagnosticCode;
15
+ readonly at: number;
16
+ }
17
+
18
+ export interface Disposable {
19
+ dispose(): void;
20
+ }
21
+
22
+ export interface FaultInjectorOptions {
23
+ readonly now?: () => number;
24
+ readonly emit?: (code: string, message: string, context?: Record<string, unknown>) => void;
25
+ }
26
+
27
+ /**
28
+ * Triggers a named condition and records that it fired.
29
+ *
30
+ * Deliberately a thin mechanism: it proves each identifier is reachable and
31
+ * distinct, which is the property under test. Making the *real* condition occur
32
+ * is each module's own test's job.
33
+ */
34
+ export function createFaultInjector(options: FaultInjectorOptions = {}) {
35
+ const now = options.now ?? (() => Date.now());
36
+ const emit = options.emit ?? (() => {});
37
+ const fired: FaultRecord[] = [];
38
+
39
+ return {
40
+ /** Every condition a consumer can reach from the fault panel. */
41
+ available: ALL_DIAGNOSTIC_CODES,
42
+
43
+ inject(kind: FaultKind): Disposable {
44
+ const record: FaultRecord = { code: kind, at: now() };
45
+ fired.push(record);
46
+ emit(kind, `Injected fault: ${kind}`, { injected: true });
47
+
48
+ return {
49
+ dispose() {
50
+ const index = fired.indexOf(record);
51
+ if (index >= 0) fired.splice(index, 1);
52
+ },
53
+ };
54
+ },
55
+
56
+ get fired(): readonly FaultRecord[] {
57
+ return fired;
58
+ },
59
+
60
+ /** Which documented conditions have not yet been exercised. */
61
+ unexercised(): readonly DiagnosticCode[] {
62
+ const seen = new Set(fired.map(record => record.code));
63
+ return ALL_DIAGNOSTIC_CODES.filter(code => !seen.has(code));
64
+ },
65
+
66
+ reset(): void {
67
+ fired.length = 0;
68
+ },
69
+ };
70
+ }
71
+
72
+ export type FaultInjector = ReturnType<typeof createFaultInjector>;
@@ -1,28 +1,23 @@
1
- /**
2
- * Test-only surface: `@chipmobilesdk/rn-notification/testing`
3
- *
4
- * Kept behind its own subpath so fakes and fault injection never reach a
5
- * production bundle. Everything here exists so that scheduling, reconciliation,
6
- * permission transitions, tone mapping, and interaction routing are provable
7
- * with no device, no emulator, and no live provider.
8
- */
9
-
10
- export { createFakeEngine } from './fake-engine';
11
- export { createFakeProvider } from './fake-provider';
12
- export { createFaultInjector } from './faults';
13
- export { runConformanceSuite } from './conformance';
14
- export { runMeasurementHarness, PERF_BUDGETS } from './measure';
15
- export type { MeasurementReport, Measurement, BudgetName, MeasureOptions } from './measure';
16
- export type { ConformanceReport, ConformanceCheck, ConformanceOptions } from './conformance';
17
- export type { FaultInjector, FaultKind, FaultRecord, Disposable } from './faults';
18
- export type { FakeProvider, FakeProviderOptions, FakeProviderControls } from './fake-provider';
19
- export type {
20
- FakeEngine,
21
- FakeEngineOptions,
22
- FakeControls,
23
- RecordedOperation,
24
- } from './fake-engine';
25
-
26
- /** In-memory state store, for testing state behavior without a native module. */
27
- export { createMemoryStore } from '../state/store';
28
- export type { StateStore } from '../state/store';
1
+ /**
2
+ * Test-only surface: `@chipmobilesdk/rn-notification/testing`
3
+ *
4
+ * Kept behind its own subpath so fakes and fault injection never reach a
5
+ * production bundle. Everything here exists so that scheduling, reconciliation,
6
+ * permission transitions, tone mapping, and interaction routing are provable
7
+ * with no device, no emulator, and no live provider.
8
+ */
9
+
10
+ export { createFakeEngine } from './fake-engine';
11
+ export { createFakeProvider } from './fake-provider';
12
+ export { createFaultInjector } from './faults';
13
+ export { runConformanceSuite } from './conformance';
14
+ export { runMeasurementHarness, PERF_BUDGETS } from './measure';
15
+ export type { MeasurementReport, Measurement, BudgetName, MeasureOptions } from './measure';
16
+ export type { ConformanceReport, ConformanceCheck, ConformanceOptions } from './conformance';
17
+ export type { FaultInjector, FaultKind, FaultRecord, Disposable } from './faults';
18
+ export type { FakeProvider, FakeProviderOptions, FakeProviderControls } from './fake-provider';
19
+ export type { FakeEngine, FakeEngineOptions, FakeControls, RecordedOperation } from './fake-engine';
20
+
21
+ /** In-memory state store, for testing state behavior without a native module. */
22
+ export { createMemoryStore } from '../state/store';
23
+ export type { StateStore } from '../state/store';