react-native-notify-kit 9.7.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 (101) hide show
  1. package/README.md +71 -2
  2. package/RNNotifeeCore.podspec +2 -0
  3. package/cli/bin/react-native-notify-kit +2 -0
  4. package/cli/dist/cli.bundle.js +20834 -0
  5. package/cli/dist/cli.d.ts +2 -0
  6. package/cli/dist/cli.js +87 -0
  7. package/cli/dist/cli.js.map +1 -0
  8. package/cli/dist/commands/initNse.d.ts +12 -0
  9. package/cli/dist/commands/initNse.js +212 -0
  10. package/cli/dist/commands/initNse.js.map +1 -0
  11. package/cli/dist/lib/detectProject.d.ts +15 -0
  12. package/cli/dist/lib/detectProject.js +156 -0
  13. package/cli/dist/lib/detectProject.js.map +1 -0
  14. package/cli/dist/lib/logger.d.ts +4 -0
  15. package/cli/dist/lib/logger.js +24 -0
  16. package/cli/dist/lib/logger.js.map +1 -0
  17. package/cli/dist/lib/patchPodfile.d.ts +12 -0
  18. package/cli/dist/lib/patchPodfile.js +143 -0
  19. package/cli/dist/lib/patchPodfile.js.map +1 -0
  20. package/cli/dist/lib/patchXcodeProject.d.ts +25 -0
  21. package/cli/dist/lib/patchXcodeProject.js +239 -0
  22. package/cli/dist/lib/patchXcodeProject.js.map +1 -0
  23. package/cli/dist/lib/writeTemplates.d.ts +11 -0
  24. package/cli/dist/lib/writeTemplates.js +82 -0
  25. package/cli/dist/lib/writeTemplates.js.map +1 -0
  26. package/cli/dist/templates/Info.plist.tmpl +29 -0
  27. package/cli/dist/templates/NotificationService.swift.tmpl +32 -0
  28. package/cli/dist/templates/NotifyKitNSE.entitlements.tmpl +6 -0
  29. package/cli/dist/templates/templates/Info.plist.tmpl +29 -0
  30. package/cli/dist/templates/templates/NotificationService.swift.tmpl +73 -0
  31. package/cli/dist/templates/templates/NotifyKitNSE.entitlements.tmpl +6 -0
  32. package/dist/NotifeeApiModule.d.ts +17 -0
  33. package/dist/NotifeeApiModule.js +66 -1
  34. package/dist/NotifeeApiModule.js.map +1 -1
  35. package/dist/fcm/index.d.ts +4 -0
  36. package/dist/fcm/index.js +3 -0
  37. package/dist/fcm/index.js.map +1 -0
  38. package/dist/fcm/parseFcmPayload.d.ts +16 -0
  39. package/dist/fcm/parseFcmPayload.js +37 -0
  40. package/dist/fcm/parseFcmPayload.js.map +1 -0
  41. package/dist/fcm/reconstructNotification.d.ts +8 -0
  42. package/dist/fcm/reconstructNotification.js +167 -0
  43. package/dist/fcm/reconstructNotification.js.map +1 -0
  44. package/dist/fcm/types.d.ts +51 -0
  45. package/dist/fcm/types.js +6 -0
  46. package/dist/fcm/types.js.map +1 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +1 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/types/Module.d.ts +21 -0
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/dist/version.js.map +1 -1
  54. package/package.json +38 -3
  55. package/server/README.md +129 -0
  56. package/server/dist/android.d.ts +8 -0
  57. package/server/dist/android.d.ts.map +1 -0
  58. package/server/dist/android.js +20 -0
  59. package/server/dist/android.js.map +1 -0
  60. package/server/dist/buildPayload.d.ts +3 -0
  61. package/server/dist/buildPayload.d.ts.map +1 -0
  62. package/server/dist/buildPayload.js +82 -0
  63. package/server/dist/buildPayload.js.map +1 -0
  64. package/server/dist/index.d.ts +6 -0
  65. package/server/dist/index.d.ts.map +1 -0
  66. package/server/dist/index.js +12 -0
  67. package/server/dist/index.js.map +1 -0
  68. package/server/dist/ios.d.ts +11 -0
  69. package/server/dist/ios.d.ts.map +1 -0
  70. package/server/dist/ios.js +61 -0
  71. package/server/dist/ios.js.map +1 -0
  72. package/server/dist/serialize.d.ts +11 -0
  73. package/server/dist/serialize.d.ts.map +1 -0
  74. package/server/dist/serialize.js +32 -0
  75. package/server/dist/serialize.js.map +1 -0
  76. package/server/dist/types.d.ts +2 -0
  77. package/server/dist/types.d.ts.map +1 -0
  78. package/server/dist/types.js +3 -0
  79. package/server/dist/types.js.map +1 -0
  80. package/server/dist/validation.d.ts +3 -0
  81. package/server/dist/validation.d.ts.map +1 -0
  82. package/server/dist/validation.js +101 -0
  83. package/server/dist/validation.js.map +1 -0
  84. package/server/package.json +5 -0
  85. package/server/src/android.ts +27 -0
  86. package/server/src/buildPayload.ts +91 -0
  87. package/server/src/index.ts +12 -0
  88. package/server/src/ios.ts +88 -0
  89. package/server/src/serialize.ts +39 -0
  90. package/server/src/types.ts +24 -0
  91. package/server/src/validation.ts +134 -0
  92. package/server/tsconfig.json +25 -0
  93. package/src/NotifeeApiModule.ts +80 -1
  94. package/src/fcm/index.ts +4 -0
  95. package/src/fcm/parseFcmPayload.ts +56 -0
  96. package/src/fcm/reconstructNotification.ts +201 -0
  97. package/src/fcm/types.ts +57 -0
  98. package/src/index.ts +2 -0
  99. package/src/internal/fcmContract.d.ts +150 -0
  100. package/src/types/Module.ts +23 -0
  101. package/src/version.ts +1 -1
@@ -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
  /**
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '9.7.0';
2
+ export const version = '10.0.0';