react-native-notify-kit 9.6.0 → 10.0.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 (107) hide show
  1. package/README.md +87 -5
  2. package/RNNotifeeCore.podspec +2 -0
  3. package/android/src/main/java/app/notifee/core/NotificationManager.java +98 -0
  4. package/android/src/test/java/app/notifee/core/GetDisplayedNotificationsDataTest.java +242 -0
  5. package/cli/bin/react-native-notify-kit +2 -0
  6. package/cli/dist/cli.bundle.js +20834 -0
  7. package/cli/dist/cli.d.ts +2 -0
  8. package/cli/dist/cli.js +87 -0
  9. package/cli/dist/cli.js.map +1 -0
  10. package/cli/dist/commands/initNse.d.ts +12 -0
  11. package/cli/dist/commands/initNse.js +212 -0
  12. package/cli/dist/commands/initNse.js.map +1 -0
  13. package/cli/dist/lib/detectProject.d.ts +15 -0
  14. package/cli/dist/lib/detectProject.js +156 -0
  15. package/cli/dist/lib/detectProject.js.map +1 -0
  16. package/cli/dist/lib/logger.d.ts +4 -0
  17. package/cli/dist/lib/logger.js +24 -0
  18. package/cli/dist/lib/logger.js.map +1 -0
  19. package/cli/dist/lib/patchPodfile.d.ts +12 -0
  20. package/cli/dist/lib/patchPodfile.js +143 -0
  21. package/cli/dist/lib/patchPodfile.js.map +1 -0
  22. package/cli/dist/lib/patchXcodeProject.d.ts +25 -0
  23. package/cli/dist/lib/patchXcodeProject.js +239 -0
  24. package/cli/dist/lib/patchXcodeProject.js.map +1 -0
  25. package/cli/dist/lib/writeTemplates.d.ts +11 -0
  26. package/cli/dist/lib/writeTemplates.js +82 -0
  27. package/cli/dist/lib/writeTemplates.js.map +1 -0
  28. package/cli/dist/templates/Info.plist.tmpl +29 -0
  29. package/cli/dist/templates/NotificationService.swift.tmpl +32 -0
  30. package/cli/dist/templates/NotifyKitNSE.entitlements.tmpl +6 -0
  31. package/cli/dist/templates/templates/Info.plist.tmpl +29 -0
  32. package/cli/dist/templates/templates/NotificationService.swift.tmpl +73 -0
  33. package/cli/dist/templates/templates/NotifyKitNSE.entitlements.tmpl +6 -0
  34. package/dist/NotifeeApiModule.d.ts +17 -0
  35. package/dist/NotifeeApiModule.js +66 -1
  36. package/dist/NotifeeApiModule.js.map +1 -1
  37. package/dist/fcm/index.d.ts +4 -0
  38. package/dist/fcm/index.js +3 -0
  39. package/dist/fcm/index.js.map +1 -0
  40. package/dist/fcm/parseFcmPayload.d.ts +16 -0
  41. package/dist/fcm/parseFcmPayload.js +37 -0
  42. package/dist/fcm/parseFcmPayload.js.map +1 -0
  43. package/dist/fcm/reconstructNotification.d.ts +8 -0
  44. package/dist/fcm/reconstructNotification.js +167 -0
  45. package/dist/fcm/reconstructNotification.js.map +1 -0
  46. package/dist/fcm/types.d.ts +51 -0
  47. package/dist/fcm/types.js +6 -0
  48. package/dist/fcm/types.js.map +1 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/types/Module.d.ts +21 -0
  53. package/dist/types/Notification.d.ts +35 -4
  54. package/dist/types/Notification.js.map +1 -1
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.js +1 -1
  57. package/dist/version.js.map +1 -1
  58. package/jest-mock.js +1 -0
  59. package/package.json +38 -3
  60. package/server/README.md +129 -0
  61. package/server/dist/android.d.ts +8 -0
  62. package/server/dist/android.d.ts.map +1 -0
  63. package/server/dist/android.js +20 -0
  64. package/server/dist/android.js.map +1 -0
  65. package/server/dist/buildPayload.d.ts +3 -0
  66. package/server/dist/buildPayload.d.ts.map +1 -0
  67. package/server/dist/buildPayload.js +82 -0
  68. package/server/dist/buildPayload.js.map +1 -0
  69. package/server/dist/index.d.ts +6 -0
  70. package/server/dist/index.d.ts.map +1 -0
  71. package/server/dist/index.js +12 -0
  72. package/server/dist/index.js.map +1 -0
  73. package/server/dist/ios.d.ts +11 -0
  74. package/server/dist/ios.d.ts.map +1 -0
  75. package/server/dist/ios.js +61 -0
  76. package/server/dist/ios.js.map +1 -0
  77. package/server/dist/serialize.d.ts +11 -0
  78. package/server/dist/serialize.d.ts.map +1 -0
  79. package/server/dist/serialize.js +32 -0
  80. package/server/dist/serialize.js.map +1 -0
  81. package/server/dist/types.d.ts +2 -0
  82. package/server/dist/types.d.ts.map +1 -0
  83. package/server/dist/types.js +3 -0
  84. package/server/dist/types.js.map +1 -0
  85. package/server/dist/validation.d.ts +3 -0
  86. package/server/dist/validation.d.ts.map +1 -0
  87. package/server/dist/validation.js +101 -0
  88. package/server/dist/validation.js.map +1 -0
  89. package/server/package.json +5 -0
  90. package/server/src/android.ts +27 -0
  91. package/server/src/buildPayload.ts +91 -0
  92. package/server/src/index.ts +12 -0
  93. package/server/src/ios.ts +88 -0
  94. package/server/src/serialize.ts +39 -0
  95. package/server/src/types.ts +24 -0
  96. package/server/src/validation.ts +134 -0
  97. package/server/tsconfig.json +25 -0
  98. package/src/NotifeeApiModule.ts +80 -1
  99. package/src/fcm/index.ts +4 -0
  100. package/src/fcm/parseFcmPayload.ts +56 -0
  101. package/src/fcm/reconstructNotification.ts +201 -0
  102. package/src/fcm/types.ts +57 -0
  103. package/src/index.ts +2 -0
  104. package/src/internal/fcmContract.d.ts +150 -0
  105. package/src/types/Module.ts +23 -0
  106. package/src/types/Notification.ts +35 -4
  107. package/src/version.ts +1 -1
@@ -0,0 +1,4 @@
1
+ export { parseFcmPayload } from './parseFcmPayload';
2
+ export type { ParsedPayload } from './parseFcmPayload';
3
+ export { reconstructNotification } from './reconstructNotification';
4
+ export type { FcmRemoteMessage, FcmConfig } from './types';
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Pure function — parses the `notifee_options` blob from an FCM data payload.
3
+ */
4
+
5
+ const PREFIX = '[react-native-notify-kit]';
6
+
7
+ export type ParsedPayload = {
8
+ _v?: number;
9
+ title?: string;
10
+ body?: string;
11
+ android?: Record<string, unknown>;
12
+ ios?: Record<string, unknown>;
13
+ [key: string]: unknown;
14
+ };
15
+
16
+ /**
17
+ * Extracts and JSON-parses the `notifee_options` value from the FCM data map.
18
+ * Returns the parsed object on success, or `null` when:
19
+ * - `data` is undefined/null
20
+ * - `data.notifee_options` is missing
21
+ * - JSON parsing fails (a `console.warn` is emitted)
22
+ */
23
+ export function parseFcmPayload(data: Record<string, string> | undefined): ParsedPayload | null {
24
+ if (!data || typeof data.notifee_options !== 'string') {
25
+ return null;
26
+ }
27
+
28
+ let parsed: ParsedPayload;
29
+ try {
30
+ parsed = JSON.parse(data.notifee_options);
31
+ } catch (e: unknown) {
32
+ const detail = e instanceof Error ? e.message : String(e);
33
+ console.warn(
34
+ `${PREFIX} Failed to parse notifee_options: ${detail}. Falling back to raw title/body.`,
35
+ );
36
+ return null;
37
+ }
38
+
39
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
40
+ console.warn(
41
+ `${PREFIX} notifee_options parsed to a non-object value. Falling back to raw title/body.`,
42
+ );
43
+ return null;
44
+ }
45
+
46
+ // Version check (Rule C5) — coerce to number to handle string _v from
47
+ // intermediaries that stringify FCM data values.
48
+ const versionNum = Number(parsed._v);
49
+ if (!Number.isNaN(versionNum) && versionNum > 1) {
50
+ console.warn(
51
+ `${PREFIX} notifee_options version ${versionNum} is newer than supported version 1. Display may be incomplete.`,
52
+ );
53
+ }
54
+
55
+ return parsed;
56
+ }
@@ -0,0 +1,201 @@
1
+ /*
2
+ * Pure function — builds a Notification object from a parsed notifee_options
3
+ * blob, an FCM remote message, and the current FcmConfig.
4
+ */
5
+
6
+ import { Notification } from '../types/Notification';
7
+ import { NotificationAndroid, AndroidStyle } from '../types/NotificationAndroid';
8
+ import { NotificationIOS } from '../types/NotificationIOS';
9
+ import { isAndroid, isIOS } from '../utils';
10
+ import type { FcmConfig, FcmRemoteMessage } from './types';
11
+ import type { ParsedPayload } from './parseFcmPayload';
12
+
13
+ const PREFIX = '[react-native-notify-kit]';
14
+ const RESERVED_DATA_KEYS = new Set(['notifee_options', 'notifee_data']);
15
+
16
+ const VALID_INTERRUPTION_LEVELS = new Set(['active', 'critical', 'passive', 'timeSensitive']);
17
+
18
+ const STYLE_TYPE_MAP: Record<string, AndroidStyle | undefined> = {
19
+ BIG_TEXT: AndroidStyle.BIGTEXT,
20
+ BIG_PICTURE: AndroidStyle.BIGPICTURE,
21
+ };
22
+
23
+ /**
24
+ * Reconstructs a {@link Notification} object suitable for
25
+ * `displayNotification` from the parsed FCM payload + config defaults.
26
+ */
27
+ export function reconstructNotification(
28
+ parsed: ParsedPayload | null,
29
+ remoteMessage: FcmRemoteMessage,
30
+ config: FcmConfig,
31
+ ): Notification {
32
+ const data = rebuildData(remoteMessage.data);
33
+
34
+ const title =
35
+ parsed?.title ?? remoteMessage.notification?.title ?? remoteMessage.data?.title ?? '';
36
+
37
+ const body = parsed?.body ?? remoteMessage.notification?.body ?? remoteMessage.data?.body ?? '';
38
+
39
+ const notification: Notification = {
40
+ id:
41
+ ((parsed as Record<string, unknown> | null)?.id as string | undefined) ??
42
+ remoteMessage.messageId,
43
+ title,
44
+ body,
45
+ };
46
+
47
+ if (Object.keys(data).length > 0) {
48
+ notification.data = data;
49
+ }
50
+
51
+ if (isAndroid && (parsed?.android || config.defaultChannelId || config.defaultPressAction)) {
52
+ notification.android = buildAndroidConfig(parsed?.android, config);
53
+ }
54
+
55
+ if (isIOS && parsed?.ios) {
56
+ notification.ios = buildIosConfig(parsed.ios);
57
+ }
58
+
59
+ return notification;
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Data reconstruction (Rules C6 / C7)
64
+ // ---------------------------------------------------------------------------
65
+
66
+ function rebuildData(rawData: Record<string, string> | undefined): Record<string, string> {
67
+ if (!rawData) {
68
+ return {};
69
+ }
70
+
71
+ // Start with top-level data keys (excluding reserved)
72
+ const result: Record<string, string> = {};
73
+ for (const [key, value] of Object.entries(rawData)) {
74
+ if (!RESERVED_DATA_KEYS.has(key)) {
75
+ result[key] = value;
76
+ }
77
+ }
78
+
79
+ // Merge notifee_data blob — overrides top-level on conflict (Rule C7)
80
+ const notifeeDataRaw = rawData.notifee_data;
81
+ if (typeof notifeeDataRaw === 'string') {
82
+ try {
83
+ const notifeeData = JSON.parse(notifeeDataRaw) as Record<string, string>;
84
+ if (notifeeData && typeof notifeeData === 'object') {
85
+ Object.assign(result, notifeeData);
86
+ }
87
+ } catch {
88
+ console.warn(`${PREFIX} Failed to parse notifee_data. Using top-level data keys only.`);
89
+ }
90
+ }
91
+
92
+ // Post-merge strip: notifee_data blob could contain reserved keys
93
+ for (const key of RESERVED_DATA_KEYS) {
94
+ delete result[key];
95
+ }
96
+
97
+ return result;
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Android config mapping
102
+ // ---------------------------------------------------------------------------
103
+
104
+ function buildAndroidConfig(
105
+ raw: Record<string, unknown> | undefined,
106
+ config: FcmConfig,
107
+ ): NotificationAndroid {
108
+ const android: NotificationAndroid = {};
109
+
110
+ // channelId: payload > config default
111
+ const channelId = raw?.channelId ?? config.defaultChannelId;
112
+ if (typeof channelId === 'string') {
113
+ android.channelId = channelId;
114
+ }
115
+
116
+ // pressAction: payload > config default
117
+ const pressAction = raw?.pressAction ?? config.defaultPressAction;
118
+ if (pressAction && typeof pressAction === 'object') {
119
+ android.pressAction = pressAction as NotificationAndroid['pressAction'];
120
+ }
121
+
122
+ // Direct string copies
123
+ if (typeof raw?.smallIcon === 'string') android.smallIcon = raw.smallIcon;
124
+ if (typeof raw?.largeIcon === 'string') android.largeIcon = raw.largeIcon;
125
+ if (typeof raw?.color === 'string') android.color = raw.color;
126
+
127
+ // Actions array — pass through (trust server validation)
128
+ if (Array.isArray(raw?.actions)) {
129
+ android.actions = raw.actions as NotificationAndroid['actions'];
130
+ }
131
+
132
+ // Style — enum mapping with defense-in-depth for unknown types
133
+ if (raw?.style && typeof raw.style === 'object') {
134
+ const style = raw.style as { type?: string; text?: string; picture?: string };
135
+ if (typeof style.type === 'string') {
136
+ const mappedType = STYLE_TYPE_MAP[style.type];
137
+ if (mappedType !== undefined) {
138
+ if (mappedType === AndroidStyle.BIGTEXT && typeof style.text === 'string') {
139
+ android.style = { type: AndroidStyle.BIGTEXT, text: style.text };
140
+ } else if (mappedType === AndroidStyle.BIGPICTURE && typeof style.picture === 'string') {
141
+ android.style = { type: AndroidStyle.BIGPICTURE, picture: style.picture };
142
+ } else {
143
+ const field = mappedType === AndroidStyle.BIGTEXT ? 'text' : 'picture';
144
+ console.warn(
145
+ `${PREFIX} android.style.type '${style.type}' present but required '${field}' field missing or not a string. Style ignored.`,
146
+ );
147
+ }
148
+ } else {
149
+ console.warn(`${PREFIX} Unknown android.style.type '${style.type}'. Style ignored.`);
150
+ }
151
+ }
152
+ }
153
+
154
+ return android;
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // iOS config mapping
159
+ // ---------------------------------------------------------------------------
160
+
161
+ function buildIosConfig(raw: Record<string, unknown>): NotificationIOS {
162
+ const ios: NotificationIOS = {};
163
+
164
+ if (typeof raw.sound === 'string') ios.sound = raw.sound;
165
+ if (typeof raw.categoryId === 'string') ios.categoryId = raw.categoryId;
166
+ if (typeof raw.threadId === 'string') ios.threadId = raw.threadId;
167
+
168
+ // interruptionLevel — defense-in-depth for unknown values
169
+ if (typeof raw.interruptionLevel === 'string') {
170
+ if (VALID_INTERRUPTION_LEVELS.has(raw.interruptionLevel)) {
171
+ ios.interruptionLevel = raw.interruptionLevel as NotificationIOS['interruptionLevel'];
172
+ } else {
173
+ console.warn(`${PREFIX} Unknown ios.interruptionLevel '${raw.interruptionLevel}'. Ignored.`);
174
+ }
175
+ }
176
+
177
+ // Attachments — rename identifier → id, filter out invalid entries
178
+ if (Array.isArray(raw.attachments)) {
179
+ ios.attachments = (raw.attachments as Array<unknown>)
180
+ .filter((att): att is Record<string, unknown> => {
181
+ if (att == null || typeof att !== 'object') return false;
182
+ const a = att as Record<string, unknown>;
183
+ if (typeof a.url !== 'string' || a.url.length === 0) {
184
+ console.warn(`${PREFIX} ios.attachments entry has missing or empty url. Skipped.`);
185
+ return false;
186
+ }
187
+ return true;
188
+ })
189
+ .map(att => {
190
+ const mapped: { id?: string; url: string } = {
191
+ url: att.url as string,
192
+ };
193
+ if (typeof att.identifier === 'string') {
194
+ mapped.id = att.identifier;
195
+ }
196
+ return mapped;
197
+ });
198
+ }
199
+
200
+ return ios;
201
+ }
@@ -0,0 +1,57 @@
1
+ /*
2
+ * Types for the FCM message handler. Declared structurally to avoid a peer
3
+ * dependency on @react-native-firebase/messaging.
4
+ */
5
+
6
+ /**
7
+ * Minimal subset of `@react-native-firebase/messaging`'s RemoteMessage that
8
+ * `handleFcmMessage` actually reads.
9
+ */
10
+ export type FcmRemoteMessage = {
11
+ messageId?: string;
12
+ data?: Record<string, string>;
13
+ notification?: {
14
+ title?: string;
15
+ body?: string;
16
+ };
17
+ };
18
+
19
+ /**
20
+ * Configuration for `handleFcmMessage`. Call `setFcmConfig` once
21
+ * at app startup (typically in `index.js` before `registerComponent`).
22
+ */
23
+ export type FcmConfig = {
24
+ /**
25
+ * Default channelId used when `notifee_options.android.channelId` is absent.
26
+ * If this is also absent and the payload has no channelId,
27
+ * `displayNotification` will throw on Android — same behavior as calling
28
+ * `displayNotification` without a channel.
29
+ */
30
+ defaultChannelId?: string;
31
+
32
+ /**
33
+ * Default pressAction when `notifee_options.android.pressAction` is absent.
34
+ * Mirrors the existing behavior: `{ id: 'default', launchActivity: 'default' }`.
35
+ */
36
+ defaultPressAction?: { id: string; launchActivity?: string };
37
+
38
+ /**
39
+ * What to do when `remoteMessage.data.notifee_options` is absent entirely.
40
+ * - `'display'`: build a minimal notification from `remoteMessage.notification`
41
+ * title/body (or `data.title`/`data.body` as a fallback). Uses `defaultChannelId`.
42
+ * - `'ignore'`: return `null` without displaying anything.
43
+ *
44
+ * @default 'display'
45
+ */
46
+ fallbackBehavior?: 'display' | 'ignore';
47
+
48
+ /** iOS-specific options. */
49
+ ios?: {
50
+ /**
51
+ * When `true`, foreground notifications delivered via `handleFcmMessage`
52
+ * are NOT displayed (only events are emitted).
53
+ * @default false
54
+ */
55
+ suppressForegroundBanner?: boolean;
56
+ };
57
+ };
package/src/index.ts CHANGED
@@ -31,3 +31,5 @@ export * from './types/Notification';
31
31
  export * from './types/Trigger';
32
32
  export * from './types/NotificationIOS';
33
33
  export * from './types/NotificationAndroid';
34
+ export * from './types/PowerManagerInfo';
35
+ export type { FcmRemoteMessage, FcmConfig } from './fcm/types';
@@ -0,0 +1,150 @@
1
+ /**
2
+ * FCM wire contract — shared between the server SDK (Node) and the upcoming
3
+ * Phase 2 client `handleFcmMessage`. Types only, no runtime code.
4
+ *
5
+ * This module is the single source of truth for the shape of `notifee_options`
6
+ * embedded in FCM data / APNs payloads. Both sides (server builder, client
7
+ * reader) import from here to guarantee parity.
8
+ *
9
+ * NOT a public API. Consumers should use `react-native-notify-kit/server` for
10
+ * building payloads and the main `react-native-notify-kit` module for client
11
+ * handling. The `internal/` directory exists to hold cross-module contracts
12
+ * that are not for end users.
13
+ */
14
+
15
+ export type NotifyKitPressAction = {
16
+ id: string;
17
+ launchActivity?: string;
18
+ };
19
+
20
+ export type NotifyKitAndroidAction = {
21
+ title: string;
22
+ pressAction: NotifyKitPressAction;
23
+ input?: boolean;
24
+ };
25
+
26
+ export type NotifyKitAndroidStyle =
27
+ | { type: 'BIG_TEXT'; text: string }
28
+ | { type: 'BIG_PICTURE'; picture: string };
29
+
30
+ export type NotifyKitAndroidConfig = {
31
+ channelId?: string;
32
+ smallIcon?: string;
33
+ largeIcon?: string;
34
+ color?: string;
35
+ pressAction?: NotifyKitPressAction;
36
+ actions?: NotifyKitAndroidAction[];
37
+ style?: NotifyKitAndroidStyle;
38
+ };
39
+
40
+ export type NotifyKitIosAttachment = {
41
+ url: string;
42
+ identifier?: string;
43
+ };
44
+
45
+ export type NotifyKitIosInterruptionLevel = 'passive' | 'active' | 'timeSensitive' | 'critical';
46
+
47
+ export type NotifyKitIosConfig = {
48
+ sound?: string;
49
+ categoryId?: string;
50
+ threadId?: string;
51
+ interruptionLevel?: NotifyKitIosInterruptionLevel;
52
+ attachments?: NotifyKitIosAttachment[];
53
+ };
54
+
55
+ export type NotifyKitNotification = {
56
+ /**
57
+ * Optional notification identifier. When provided, it is also used as the
58
+ * FCM collapse key (Android `collapse_key`, iOS `apns-collapse-id`) unless
59
+ * `options.collapseKey` is explicitly set. Collapse key precedence (Rule 7):
60
+ * 1. `options.collapseKey` if set
61
+ * 2. `notification.id` if set
62
+ * 3. Omitted (no collapse behaviour)
63
+ *
64
+ * This means setting `id` implicitly enables notification replacement on the
65
+ * device: a new notification with the same `id` replaces the previous one.
66
+ * If you need `id` for local deduplication without collapse behaviour, set a
67
+ * distinct `options.collapseKey` or leave both unset.
68
+ */
69
+ id?: string;
70
+ title: string;
71
+ body: string;
72
+ data?: Record<string, string>;
73
+ android?: NotifyKitAndroidConfig;
74
+ ios?: NotifyKitIosConfig;
75
+ };
76
+
77
+ export type NotifyKitOptions = {
78
+ androidPriority?: 'high' | 'normal';
79
+ iosBadgeCount?: number;
80
+ ttl?: number;
81
+ collapseKey?: string;
82
+ };
83
+
84
+ export type NotifyKitPayloadInput = {
85
+ token?: string;
86
+ topic?: string;
87
+ condition?: string;
88
+ notification: NotifyKitNotification;
89
+ options?: NotifyKitOptions;
90
+ };
91
+
92
+ export type ApnsInterruptionLevel = 'passive' | 'active' | 'time-sensitive' | 'critical';
93
+
94
+ export type NotifyKitAndroidOutput = {
95
+ priority: 'HIGH' | 'NORMAL';
96
+ collapse_key?: string;
97
+ ttl?: string;
98
+ };
99
+
100
+ export type NotifyKitApnsAps = {
101
+ alert: { title: string; body: string };
102
+ sound?: string;
103
+ category?: string;
104
+ 'mutable-content': 1;
105
+ 'thread-id'?: string;
106
+ 'interruption-level'?: ApnsInterruptionLevel;
107
+ badge?: number;
108
+ };
109
+
110
+ export type NotifyKitApnsHeaders = {
111
+ 'apns-push-type': 'alert';
112
+ 'apns-priority': '10';
113
+ 'apns-collapse-id'?: string;
114
+ 'apns-expiration'?: string;
115
+ };
116
+
117
+ export type NotifyKitApnsPayload = {
118
+ aps: NotifyKitApnsAps;
119
+ notifee_options: string;
120
+ notifee_data?: string;
121
+ };
122
+
123
+ export type NotifyKitApnsOutput = {
124
+ headers: NotifyKitApnsHeaders;
125
+ payload: NotifyKitApnsPayload;
126
+ };
127
+
128
+ export type NotifyKitPayloadOutput = {
129
+ token?: string;
130
+ topic?: string;
131
+ condition?: string;
132
+ data: Record<string, string>;
133
+ android: NotifyKitAndroidOutput;
134
+ apns: NotifyKitApnsOutput;
135
+ sizeBytes: number;
136
+ };
137
+
138
+ export type SerializedNotifeeOptions = {
139
+ _v: 1;
140
+ title: string;
141
+ body: string;
142
+ android?: NotifyKitAndroidConfig;
143
+ ios?: NotifyKitIosConfig;
144
+ };
145
+
146
+ // Android output must never carry a `notification` field (the server enforces
147
+ // data-only delivery for Android to prevent FCM from auto-displaying). This
148
+ // invariant is exercised at runtime by the `never includes a notification
149
+ // field` test in buildPayload.test.ts and is enforced structurally by the
150
+ // shape of NotifyKitAndroidOutput above.
@@ -17,6 +17,7 @@ import {
17
17
  } from './NotificationIOS';
18
18
  import { PowerManagerInfo } from './PowerManagerInfo';
19
19
  import { DisplayedNotification, NotificationSettings, TriggerNotification } from '..';
20
+ import type { FcmConfig, FcmRemoteMessage } from '../fcm/types';
20
21
 
21
22
  export interface Module {
22
23
  /**
@@ -660,6 +661,28 @@ export interface Module {
660
661
  * @platform android
661
662
  */
662
663
  hideNotificationDrawer(): void;
664
+
665
+ /**
666
+ * Processes an FCM remote message produced by the NotifyKit server SDK and
667
+ * displays a Notifee notification according to the embedded `notifee_options`.
668
+ *
669
+ * Safe to call from both `setBackgroundMessageHandler` and `onMessage`.
670
+ *
671
+ * @returns The displayed notification ID, or `null` if the call was an
672
+ * intentional no-op (iOS background, `fallbackBehavior: 'ignore'`,
673
+ * `suppressForegroundBanner`).
674
+ */
675
+ handleFcmMessage(remoteMessage: FcmRemoteMessage): Promise<string | null>;
676
+
677
+ /**
678
+ * Configures defaults for {@link handleFcmMessage}. Call once at app startup,
679
+ * typically in `index.js` before `registerComponent`.
680
+ *
681
+ * Returns Promise for forward compatibility — a future version may persist
682
+ * config across cold starts, which would be async. Currently resolves
683
+ * synchronously.
684
+ */
685
+ setFcmConfig(config: FcmConfig): Promise<void>;
663
686
  }
664
687
 
665
688
  /**
@@ -46,10 +46,41 @@ export interface Notification {
46
46
  body?: string | undefined;
47
47
 
48
48
  /**
49
- * Additional data to store on the notification.
50
- *
51
- * Data can be used to provide additional context to your notification which can be retrieved
52
- * at a later point in time (e.g. via an event).
49
+ * Additional data to associate with the notification.
50
+ *
51
+ * On Android, when populated by `getDisplayedNotifications()`, the `data`
52
+ * field reflects custom keys present in the underlying `Notification.extras`,
53
+ * with the following keys filtered out: prefixes `android.`, `google.`,
54
+ * `gcm.`, `fcm.` (with trailing dot — `fcmRegion` survives), and `notifee`
55
+ * (no trailing dot — library's reserved namespace, `notifeeFoo` is also
56
+ * filtered), plus exact keys `from`, `collapse_key`, `message_type`,
57
+ * `message_id`, `aps`, `fcm_options`.
58
+ *
59
+ * **Important FCM platform limitation on Android**: when the FCM Android SDK
60
+ * auto-displays a `notification`+`data` push while the app is in background
61
+ * or killed, custom `data` fields are placed only on the tap-action
62
+ * `PendingIntent` and never on `Notification.extras`. This is FCM's design
63
+ * (see `CommonNotificationBuilder.createContentIntent` in the firebase-android-sdk
64
+ * source) and cannot be worked around from any library — the PendingIntent's
65
+ * extras are sealed by Android's security model. Tracked in
66
+ * firebase-android-sdk#2639 (open since 2021).
67
+ *
68
+ * The `data` field DOES surface custom keys for: notifications created via
69
+ * `notifee.displayNotification({ data })`, FCM data-only messages handled
70
+ * in `onMessageReceived` followed by an explicit `displayNotification()`,
71
+ * custom `FirebaseMessagingService.handleIntent` overrides that inject
72
+ * extras before display, and notifications posted by other libraries that
73
+ * call `NotificationCompat.Builder.addExtras(bundle)`.
74
+ *
75
+ * Recommended pattern for full control over FCM push notifications on Android:
76
+ * send FCM data-only messages (no `notification` field server-side), handle
77
+ * them in `onMessageReceived`, and render the notification via
78
+ * `notifee.displayNotification()`.
79
+ *
80
+ * Note: the `fcm` filter diverges from iOS, which uses a broader bare-`fcm*`
81
+ * prefix filter (deliberately, to catch `fcm_options`). On Android, only
82
+ * `fcm.` (with dot) and the exact key `fcm_options` are filtered — custom
83
+ * keys like `fcmRegion` are preserved on Android but filtered on iOS.
53
84
  */
54
85
  data?: { [key: string]: string | object | number };
55
86
 
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '9.6.0';
2
+ export const version = '10.0.0';