react-native-notify-kit 10.7.2 → 10.8.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.
- package/README.md +2 -0
- package/android/src/main/java/app/notifee/core/ForegroundService.java +944 -93
- package/android/src/test/java/app/notifee/core/ForegroundServiceTest.java +4281 -57
- package/android/src/test/java/app/notifee/core/NotificationManagerActionRoutingTest.java +37 -0
- package/dist/fcm/parseFcmPayload.d.ts +1 -0
- package/dist/fcm/parseFcmPayload.js.map +1 -1
- package/dist/fcm/reconstructNotification.js +96 -8
- package/dist/fcm/reconstructNotification.js.map +1 -1
- package/dist/types/Notification.d.ts +25 -1
- package/dist/types/Notification.js +10 -0
- package/dist/types/Notification.js.map +1 -1
- package/dist/validators/validateAndroidPressAction.js +3 -0
- package/dist/validators/validateAndroidPressAction.js.map +1 -1
- package/dist/validators/validateIOSNotification.js +5 -2
- package/dist/validators/validateIOSNotification.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/NotifeeCore/NotifeeCore.m +36 -3
- package/ios/NotifeeCore/NotifeeCoreExtensionHelper.m +82 -3
- package/ios/NotifeeCore/NotifeeCoreFcmIdentity.h +91 -0
- package/package.json +1 -1
- package/server/README.md +5 -3
- package/server/dist/buildPayload.d.ts.map +1 -1
- package/server/dist/buildPayload.js +2 -0
- package/server/dist/buildPayload.js.map +1 -1
- package/server/dist/ios.d.ts.map +1 -1
- package/server/dist/ios.js +8 -1
- package/server/dist/ios.js.map +1 -1
- package/server/dist/serialize.d.ts +2 -0
- package/server/dist/serialize.d.ts.map +1 -1
- package/server/dist/serialize.js +6 -0
- package/server/dist/serialize.js.map +1 -1
- package/server/dist/types.d.ts +1 -1
- package/server/dist/types.d.ts.map +1 -1
- package/server/dist/validation.d.ts.map +1 -1
- package/server/dist/validation.js +160 -6
- package/server/dist/validation.js.map +1 -1
- package/server/src/buildPayload.ts +2 -0
- package/server/src/ios.ts +8 -1
- package/server/src/serialize.ts +8 -0
- package/server/src/types.ts +1 -0
- package/server/src/validation.ts +187 -6
- package/src/fcm/parseFcmPayload.ts +1 -0
- package/src/fcm/reconstructNotification.ts +105 -10
- package/src/internal/fcmContract.d.ts +57 -12
- package/src/types/Notification.ts +27 -0
- package/src/validators/validateAndroidPressAction.ts +4 -0
- package/src/validators/validateIOSNotification.ts +7 -2
- package/src/version.ts +1 -1
package/server/src/validation.ts
CHANGED
|
@@ -2,6 +2,17 @@ import type { NotifyKitPayloadInput } from './types';
|
|
|
2
2
|
|
|
3
3
|
const PREFIX = '[react-native-notify-kit/server]';
|
|
4
4
|
const RESERVED_DATA_KEYS = ['notifee_options', 'notifee_data'] as const;
|
|
5
|
+
const BIG_TEXT_STYLE_KEYS = new Set(['type', 'text', 'title', 'summary']);
|
|
6
|
+
const BIG_PICTURE_STYLE_KEYS = new Set(['type', 'picture', 'title', 'summary', 'largeIcon']);
|
|
7
|
+
const PRESS_ACTION_KEYS = new Set(['id', 'launchActivity', 'mainComponent', 'launchActivityFlags']);
|
|
8
|
+
const ACTION_KEYS = new Set(['title', 'pressAction', 'icon', 'input']);
|
|
9
|
+
const INPUT_KEYS = new Set([
|
|
10
|
+
'allowFreeFormInput',
|
|
11
|
+
'allowGeneratedReplies',
|
|
12
|
+
'choices',
|
|
13
|
+
'editableChoices',
|
|
14
|
+
'placeholder',
|
|
15
|
+
]);
|
|
5
16
|
|
|
6
17
|
function err(category: string, message: string): Error {
|
|
7
18
|
return new Error(`${PREFIX} ${category}: ${message}`);
|
|
@@ -11,6 +22,119 @@ function isNonEmptyString(value: unknown): value is string {
|
|
|
11
22
|
return typeof value === 'string' && value.length > 0;
|
|
12
23
|
}
|
|
13
24
|
|
|
25
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
26
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const prototype = Object.getPrototypeOf(value);
|
|
30
|
+
return prototype === Object.prototype || prototype === null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function rejectUnsupportedKeys(
|
|
34
|
+
value: Record<string, unknown>,
|
|
35
|
+
supported: Set<string>,
|
|
36
|
+
path: string,
|
|
37
|
+
): void {
|
|
38
|
+
for (const key of Object.keys(value)) {
|
|
39
|
+
if (!supported.has(key)) {
|
|
40
|
+
throw err('Android', `${path}.${key} is not supported`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function validatePressAction(value: unknown, path: string): void {
|
|
46
|
+
if (!isPlainObject(value)) {
|
|
47
|
+
throw err('Android', `${path} must be an object`);
|
|
48
|
+
}
|
|
49
|
+
rejectUnsupportedKeys(value, PRESS_ACTION_KEYS, path);
|
|
50
|
+
if (!isNonEmptyString(value.id)) {
|
|
51
|
+
throw err('Android', `${path}.id must be a non-empty string`);
|
|
52
|
+
}
|
|
53
|
+
if (value.id === '__NOTIFEE_OPT_OUT__') {
|
|
54
|
+
throw err('Android', `${path}.id is reserved for explicit null`);
|
|
55
|
+
}
|
|
56
|
+
if (value.launchActivity !== undefined && typeof value.launchActivity !== 'string') {
|
|
57
|
+
throw err('Android', `${path}.launchActivity must be a string`);
|
|
58
|
+
}
|
|
59
|
+
if (value.mainComponent !== undefined && typeof value.mainComponent !== 'string') {
|
|
60
|
+
throw err('Android', `${path}.mainComponent must be a string`);
|
|
61
|
+
}
|
|
62
|
+
if (value.launchActivityFlags !== undefined) {
|
|
63
|
+
if (!Array.isArray(value.launchActivityFlags)) {
|
|
64
|
+
throw err(
|
|
65
|
+
'Android',
|
|
66
|
+
`${path}.launchActivityFlags must be an array of AndroidLaunchActivityFlag values`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
for (const flag of value.launchActivityFlags) {
|
|
70
|
+
if (typeof flag !== 'number' || !Number.isInteger(flag) || flag < 0 || flag > 20) {
|
|
71
|
+
throw err(
|
|
72
|
+
'Android',
|
|
73
|
+
`${path}.launchActivityFlags must be an array of AndroidLaunchActivityFlag values`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validateActionInput(value: unknown, path: string): void {
|
|
81
|
+
if (value === true) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!isPlainObject(value)) {
|
|
85
|
+
throw err('Android', `${path} must be true or an AndroidInput object`);
|
|
86
|
+
}
|
|
87
|
+
rejectUnsupportedKeys(value, INPUT_KEYS, path);
|
|
88
|
+
|
|
89
|
+
for (const key of ['allowFreeFormInput', 'allowGeneratedReplies', 'editableChoices'] as const) {
|
|
90
|
+
if (Object.prototype.hasOwnProperty.call(value, key) && typeof value[key] !== 'boolean') {
|
|
91
|
+
throw err('Android', `${path}.${key} must be a boolean`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (Object.prototype.hasOwnProperty.call(value, 'choices')) {
|
|
95
|
+
if (!Array.isArray(value.choices) || value.choices.length === 0) {
|
|
96
|
+
throw err('Android', `${path}.choices must be a non-empty array of strings`);
|
|
97
|
+
}
|
|
98
|
+
for (const choice of value.choices) {
|
|
99
|
+
if (typeof choice !== 'string') {
|
|
100
|
+
throw err('Android', `${path}.choices must be a non-empty array of strings`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (
|
|
105
|
+
Object.prototype.hasOwnProperty.call(value, 'placeholder') &&
|
|
106
|
+
typeof value.placeholder !== 'string'
|
|
107
|
+
) {
|
|
108
|
+
throw err('Android', `${path}.placeholder must be a string`);
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
value.allowFreeFormInput === false &&
|
|
112
|
+
(!Array.isArray(value.choices) || value.choices.length === 0)
|
|
113
|
+
) {
|
|
114
|
+
throw err('Android', `${path}.allowFreeFormInput requires choices when false`);
|
|
115
|
+
}
|
|
116
|
+
if (value.editableChoices === true && value.allowFreeFormInput === false) {
|
|
117
|
+
throw err('Android', `${path}.editableChoices requires allowFreeFormInput when true`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function validateAction(value: unknown, path: string): void {
|
|
122
|
+
if (!isPlainObject(value)) {
|
|
123
|
+
throw err('Android', `${path} must be an AndroidAction object`);
|
|
124
|
+
}
|
|
125
|
+
rejectUnsupportedKeys(value, ACTION_KEYS, path);
|
|
126
|
+
if (!isNonEmptyString(value.title)) {
|
|
127
|
+
throw err('Android', `${path}.title must be a non-empty string`);
|
|
128
|
+
}
|
|
129
|
+
validatePressAction(value.pressAction, `${path}.pressAction`);
|
|
130
|
+
if (value.icon !== undefined && !isNonEmptyString(value.icon)) {
|
|
131
|
+
throw err('Android', `${path}.icon must be a non-empty string`);
|
|
132
|
+
}
|
|
133
|
+
if (value.input !== undefined) {
|
|
134
|
+
validateActionInput(value.input, `${path}.input`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
14
138
|
export function validateInput(input: NotifyKitPayloadInput): void {
|
|
15
139
|
if (input === null || typeof input !== 'object') {
|
|
16
140
|
throw err('Validation', 'input must be an object');
|
|
@@ -52,6 +176,9 @@ export function validateInput(input: NotifyKitPayloadInput): void {
|
|
|
52
176
|
if (!isNonEmptyString(notification.body)) {
|
|
53
177
|
throw err('Validation', 'notification.body is required and must be a non-empty string');
|
|
54
178
|
}
|
|
179
|
+
if (notification.subtitle !== undefined && typeof notification.subtitle !== 'string') {
|
|
180
|
+
throw err('Validation', 'notification.subtitle must be a string when provided');
|
|
181
|
+
}
|
|
55
182
|
|
|
56
183
|
if (notification.data !== undefined) {
|
|
57
184
|
if (notification.data === null || typeof notification.data !== 'object') {
|
|
@@ -75,6 +202,59 @@ export function validateInput(input: NotifyKitPayloadInput): void {
|
|
|
75
202
|
}
|
|
76
203
|
}
|
|
77
204
|
|
|
205
|
+
if (notification.android !== undefined) {
|
|
206
|
+
if (!isPlainObject(notification.android)) {
|
|
207
|
+
throw err('Android', 'notification.android must be an object');
|
|
208
|
+
}
|
|
209
|
+
if (
|
|
210
|
+
notification.android.pressAction !== undefined &&
|
|
211
|
+
notification.android.pressAction !== null
|
|
212
|
+
) {
|
|
213
|
+
validatePressAction(notification.android.pressAction, 'notification.android.pressAction');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const circularLargeIcon = notification.android?.circularLargeIcon;
|
|
218
|
+
if (circularLargeIcon !== undefined && typeof circularLargeIcon !== 'boolean') {
|
|
219
|
+
throw err('Android', 'notification.android.circularLargeIcon must be a boolean when provided');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const style = notification.android?.style;
|
|
223
|
+
if (style !== undefined) {
|
|
224
|
+
if (style === null || typeof style !== 'object' || Array.isArray(style)) {
|
|
225
|
+
throw err('Android', 'notification.android.style must be a supported style object');
|
|
226
|
+
}
|
|
227
|
+
if (style.type !== 'BIG_TEXT' && style.type !== 'BIG_PICTURE') {
|
|
228
|
+
throw err('Android', 'notification.android.style type must be BIG_TEXT or BIG_PICTURE');
|
|
229
|
+
}
|
|
230
|
+
const supportedKeys = style.type === 'BIG_TEXT' ? BIG_TEXT_STYLE_KEYS : BIG_PICTURE_STYLE_KEYS;
|
|
231
|
+
for (const key of Object.keys(style)) {
|
|
232
|
+
if (!supportedKeys.has(key)) {
|
|
233
|
+
throw err('Android', `notification.android.style.${key} is not supported`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (style.type === 'BIG_TEXT' && !isNonEmptyString(style.text)) {
|
|
237
|
+
throw err('Android', 'notification.android.style.text must be a non-empty string');
|
|
238
|
+
}
|
|
239
|
+
if (style.type === 'BIG_PICTURE' && !isNonEmptyString(style.picture)) {
|
|
240
|
+
throw err('Android', 'notification.android.style.picture must be a non-empty string');
|
|
241
|
+
}
|
|
242
|
+
if (style.title !== undefined && typeof style.title !== 'string') {
|
|
243
|
+
throw err('Android', 'notification.android.style.title must be a string when provided');
|
|
244
|
+
}
|
|
245
|
+
if (style.summary !== undefined && typeof style.summary !== 'string') {
|
|
246
|
+
throw err('Android', 'notification.android.style.summary must be a string when provided');
|
|
247
|
+
}
|
|
248
|
+
if (
|
|
249
|
+
style.type === 'BIG_PICTURE' &&
|
|
250
|
+
style.largeIcon !== undefined &&
|
|
251
|
+
style.largeIcon !== null &&
|
|
252
|
+
typeof style.largeIcon !== 'string'
|
|
253
|
+
) {
|
|
254
|
+
throw err('Android', 'notification.android.style.largeIcon must be a string or null');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
78
258
|
const attachments = notification.ios?.attachments;
|
|
79
259
|
if (attachments !== undefined) {
|
|
80
260
|
if (!Array.isArray(attachments)) {
|
|
@@ -133,14 +313,15 @@ export function validateInput(input: NotifyKitPayloadInput): void {
|
|
|
133
313
|
}
|
|
134
314
|
|
|
135
315
|
const actions = notification.android?.actions;
|
|
136
|
-
if (
|
|
316
|
+
if (actions !== undefined) {
|
|
317
|
+
if (!Array.isArray(actions)) {
|
|
318
|
+
throw err('Android', 'notification.android.actions must be an array');
|
|
319
|
+
}
|
|
137
320
|
const pressActionIds = new Set<string>();
|
|
138
321
|
|
|
139
|
-
for (const action of actions) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
322
|
+
for (const [index, action] of actions.entries()) {
|
|
323
|
+
validateAction(action, `notification.android.actions[${index}]`);
|
|
324
|
+
const pressActionId = action.pressAction.id;
|
|
144
325
|
|
|
145
326
|
if (pressActionIds.has(pressActionId)) {
|
|
146
327
|
throw err(
|
|
@@ -20,6 +20,46 @@ const STYLE_TYPE_MAP: Record<string, AndroidStyle | undefined> = {
|
|
|
20
20
|
BIG_TEXT: AndroidStyle.BIGTEXT,
|
|
21
21
|
BIG_PICTURE: AndroidStyle.BIGPICTURE,
|
|
22
22
|
};
|
|
23
|
+
const ANDROID_INPUT_KEYS = new Set([
|
|
24
|
+
'allowFreeFormInput',
|
|
25
|
+
'allowGeneratedReplies',
|
|
26
|
+
'choices',
|
|
27
|
+
'editableChoices',
|
|
28
|
+
'placeholder',
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
function assertFcmPressAction(
|
|
32
|
+
value: unknown,
|
|
33
|
+
path: string,
|
|
34
|
+
): asserts value is NonNullable<NotificationAndroid['pressAction']> {
|
|
35
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
36
|
+
throw new Error(`${PREFIX} ${path} must be an object`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const pressAction = value as Record<string, unknown>;
|
|
40
|
+
if (typeof pressAction.id !== 'string' || pressAction.id.length === 0) {
|
|
41
|
+
throw new Error(`${PREFIX} ${path}.id must be a non-empty string`);
|
|
42
|
+
}
|
|
43
|
+
if (pressAction.id === '__NOTIFEE_OPT_OUT__') {
|
|
44
|
+
throw new Error(`${PREFIX} ${path}.id is reserved for explicit null`);
|
|
45
|
+
}
|
|
46
|
+
if (pressAction.launchActivity !== undefined && typeof pressAction.launchActivity !== 'string') {
|
|
47
|
+
throw new Error(`${PREFIX} ${path}.launchActivity must be a string`);
|
|
48
|
+
}
|
|
49
|
+
if (pressAction.mainComponent !== undefined && typeof pressAction.mainComponent !== 'string') {
|
|
50
|
+
throw new Error(`${PREFIX} ${path}.mainComponent must be a string`);
|
|
51
|
+
}
|
|
52
|
+
if (pressAction.launchActivityFlags !== undefined) {
|
|
53
|
+
if (!Array.isArray(pressAction.launchActivityFlags)) {
|
|
54
|
+
throw new Error(`${PREFIX} ${path}.launchActivityFlags must be an array`);
|
|
55
|
+
}
|
|
56
|
+
for (const flag of pressAction.launchActivityFlags) {
|
|
57
|
+
if (typeof flag !== 'number' || !Number.isInteger(flag) || flag < 0 || flag > 20) {
|
|
58
|
+
throw new Error(`${PREFIX} ${path}.launchActivityFlags contains an invalid flag`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
23
63
|
|
|
24
64
|
/**
|
|
25
65
|
* Reconstructs a {@link Notification} object suitable for
|
|
@@ -47,6 +87,10 @@ export function reconstructNotification(
|
|
|
47
87
|
body,
|
|
48
88
|
};
|
|
49
89
|
|
|
90
|
+
if (typeof parsed?.subtitle === 'string') {
|
|
91
|
+
notification.subtitle = parsed.subtitle;
|
|
92
|
+
}
|
|
93
|
+
|
|
50
94
|
if (Object.keys(data).length > 0) {
|
|
51
95
|
notification.data = data;
|
|
52
96
|
}
|
|
@@ -116,15 +160,21 @@ function buildAndroidConfig(
|
|
|
116
160
|
android.channelId = channelId;
|
|
117
161
|
}
|
|
118
162
|
|
|
119
|
-
//
|
|
120
|
-
const pressAction = raw?.pressAction
|
|
121
|
-
if (pressAction
|
|
122
|
-
android.pressAction =
|
|
163
|
+
// Explicit null opts out of the body tap; only absence uses the configured default.
|
|
164
|
+
const pressAction = raw?.pressAction === undefined ? config.defaultPressAction : raw.pressAction;
|
|
165
|
+
if (pressAction === null) {
|
|
166
|
+
android.pressAction = null;
|
|
167
|
+
} else if (pressAction !== undefined) {
|
|
168
|
+
assertFcmPressAction(pressAction, 'android.pressAction');
|
|
169
|
+
android.pressAction = pressAction;
|
|
123
170
|
}
|
|
124
171
|
|
|
125
172
|
// Direct string copies
|
|
126
173
|
if (typeof raw?.smallIcon === 'string') android.smallIcon = raw.smallIcon;
|
|
127
174
|
if (typeof raw?.largeIcon === 'string') android.largeIcon = raw.largeIcon;
|
|
175
|
+
if (typeof raw?.circularLargeIcon === 'boolean') {
|
|
176
|
+
android.circularLargeIcon = raw.circularLargeIcon;
|
|
177
|
+
}
|
|
128
178
|
if (typeof raw?.color === 'string') android.color = raw.color;
|
|
129
179
|
|
|
130
180
|
// Timestamp fields — preserve only values accepted by the canonical Android validator
|
|
@@ -133,21 +183,66 @@ function buildAndroidConfig(
|
|
|
133
183
|
android.timestamp = raw.timestamp;
|
|
134
184
|
}
|
|
135
185
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
186
|
+
// Legacy input:false means no reply. Other malformed inputs must fail here:
|
|
187
|
+
// the normal validator accepts some of them as a default RemoteInput.
|
|
188
|
+
if (raw?.actions !== undefined) {
|
|
189
|
+
if (!Array.isArray(raw.actions)) {
|
|
190
|
+
throw new Error(`${PREFIX} android.actions must be an array`);
|
|
191
|
+
}
|
|
192
|
+
android.actions = raw.actions.map((action, index) => {
|
|
193
|
+
if (action === null || typeof action !== 'object' || Array.isArray(action)) {
|
|
194
|
+
return action;
|
|
195
|
+
}
|
|
196
|
+
if (action.pressAction !== undefined) {
|
|
197
|
+
assertFcmPressAction(action.pressAction, `android.actions[${index}].pressAction`);
|
|
198
|
+
}
|
|
199
|
+
const input = action.input;
|
|
200
|
+
if (input === false) {
|
|
201
|
+
const withoutInput = { ...action };
|
|
202
|
+
delete withoutInput.input;
|
|
203
|
+
return withoutInput;
|
|
204
|
+
}
|
|
205
|
+
if (
|
|
206
|
+
input !== undefined &&
|
|
207
|
+
input !== true &&
|
|
208
|
+
(input === null || typeof input !== 'object' || Array.isArray(input))
|
|
209
|
+
) {
|
|
210
|
+
throw new Error(`${PREFIX} android.actions[${index}].input is invalid`);
|
|
211
|
+
}
|
|
212
|
+
if (input !== undefined && input !== true) {
|
|
213
|
+
for (const key of Object.keys(input)) {
|
|
214
|
+
if (!ANDROID_INPUT_KEYS.has(key)) {
|
|
215
|
+
throw new Error(`${PREFIX} android.actions[${index}].input.${key} is unsupported`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return action;
|
|
220
|
+
}) as NonNullable<NotificationAndroid['actions']>;
|
|
139
221
|
}
|
|
140
222
|
|
|
141
223
|
// Style — enum mapping with defense-in-depth for unknown types
|
|
142
224
|
if (raw?.style && typeof raw.style === 'object') {
|
|
143
|
-
const style = raw.style as
|
|
225
|
+
const style = raw.style as Record<string, unknown>;
|
|
144
226
|
if (typeof style.type === 'string') {
|
|
145
227
|
const mappedType = STYLE_TYPE_MAP[style.type];
|
|
146
228
|
if (mappedType !== undefined) {
|
|
147
229
|
if (mappedType === AndroidStyle.BIGTEXT && typeof style.text === 'string') {
|
|
148
|
-
android.style = {
|
|
230
|
+
android.style = {
|
|
231
|
+
type: AndroidStyle.BIGTEXT,
|
|
232
|
+
text: style.text,
|
|
233
|
+
...(typeof style.title === 'string' ? { title: style.title } : {}),
|
|
234
|
+
...(typeof style.summary === 'string' ? { summary: style.summary } : {}),
|
|
235
|
+
};
|
|
149
236
|
} else if (mappedType === AndroidStyle.BIGPICTURE && typeof style.picture === 'string') {
|
|
150
|
-
android.style = {
|
|
237
|
+
android.style = {
|
|
238
|
+
type: AndroidStyle.BIGPICTURE,
|
|
239
|
+
picture: style.picture,
|
|
240
|
+
...(typeof style.title === 'string' ? { title: style.title } : {}),
|
|
241
|
+
...(typeof style.summary === 'string' ? { summary: style.summary } : {}),
|
|
242
|
+
...(style.largeIcon === null || typeof style.largeIcon === 'string'
|
|
243
|
+
? { largeIcon: style.largeIcon }
|
|
244
|
+
: {}),
|
|
245
|
+
};
|
|
151
246
|
} else {
|
|
152
247
|
const field = mappedType === AndroidStyle.BIGTEXT ? 'text' : 'picture';
|
|
153
248
|
console.warn(
|
|
@@ -12,27 +12,69 @@
|
|
|
12
12
|
* that are not for end users.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
// Numeric wire values of the public AndroidLaunchActivityFlag enum (0 through 20).
|
|
16
|
+
export type NotifyKitAndroidLaunchActivityFlag =
|
|
17
|
+
| 0
|
|
18
|
+
| 1
|
|
19
|
+
| 2
|
|
20
|
+
| 3
|
|
21
|
+
| 4
|
|
22
|
+
| 5
|
|
23
|
+
| 6
|
|
24
|
+
| 7
|
|
25
|
+
| 8
|
|
26
|
+
| 9
|
|
27
|
+
| 10
|
|
28
|
+
| 11
|
|
29
|
+
| 12
|
|
30
|
+
| 13
|
|
31
|
+
| 14
|
|
32
|
+
| 15
|
|
33
|
+
| 16
|
|
34
|
+
| 17
|
|
35
|
+
| 18
|
|
36
|
+
| 19
|
|
37
|
+
| 20;
|
|
38
|
+
|
|
15
39
|
export type NotifyKitPressAction = {
|
|
16
40
|
id: string;
|
|
17
41
|
launchActivity?: string;
|
|
42
|
+
mainComponent?: string;
|
|
43
|
+
launchActivityFlags?: NotifyKitAndroidLaunchActivityFlag[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type NotifyKitAndroidInput = {
|
|
47
|
+
allowFreeFormInput?: boolean;
|
|
48
|
+
allowGeneratedReplies?: boolean;
|
|
49
|
+
choices?: string[];
|
|
50
|
+
editableChoices?: boolean;
|
|
51
|
+
placeholder?: string;
|
|
18
52
|
};
|
|
19
53
|
|
|
20
54
|
export type NotifyKitAndroidAction = {
|
|
21
55
|
title: string;
|
|
22
56
|
pressAction: NotifyKitPressAction;
|
|
23
|
-
|
|
57
|
+
icon?: string;
|
|
58
|
+
input?: true | NotifyKitAndroidInput;
|
|
24
59
|
};
|
|
25
60
|
|
|
26
61
|
export type NotifyKitAndroidStyle =
|
|
27
|
-
| { type: 'BIG_TEXT'; text: string }
|
|
28
|
-
| {
|
|
62
|
+
| { type: 'BIG_TEXT'; text: string; title?: string; summary?: string }
|
|
63
|
+
| {
|
|
64
|
+
type: 'BIG_PICTURE';
|
|
65
|
+
picture: string;
|
|
66
|
+
title?: string;
|
|
67
|
+
summary?: string;
|
|
68
|
+
largeIcon?: string | null;
|
|
69
|
+
};
|
|
29
70
|
|
|
30
71
|
export type NotifyKitAndroidConfig = {
|
|
31
72
|
channelId?: string;
|
|
32
73
|
smallIcon?: string;
|
|
33
74
|
largeIcon?: string;
|
|
75
|
+
circularLargeIcon?: boolean;
|
|
34
76
|
color?: string;
|
|
35
|
-
pressAction?: NotifyKitPressAction;
|
|
77
|
+
pressAction?: NotifyKitPressAction | null;
|
|
36
78
|
actions?: NotifyKitAndroidAction[];
|
|
37
79
|
style?: NotifyKitAndroidStyle;
|
|
38
80
|
showTimestamp?: boolean;
|
|
@@ -56,20 +98,21 @@ export type NotifyKitIosConfig = {
|
|
|
56
98
|
|
|
57
99
|
export type NotifyKitNotification = {
|
|
58
100
|
/**
|
|
59
|
-
* Optional notification identifier.
|
|
60
|
-
*
|
|
61
|
-
*
|
|
101
|
+
* Optional public notification identifier. It is serialized in the _v:1
|
|
102
|
+
* options blob and used by NotifyKit display, events, readback and cancel.
|
|
103
|
+
* It also defaults the FCM transport collapse key (Android `collapseKey`,
|
|
104
|
+
* iOS `apns-collapse-id`) unless `options.collapseKey` is explicitly set.
|
|
105
|
+
* Collapse key precedence (Rule 7):
|
|
62
106
|
* 1. `options.collapseKey` if set
|
|
63
107
|
* 2. `notification.id` if set
|
|
64
108
|
* 3. Omitted (no collapse behaviour)
|
|
65
109
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* If you need `id` for local deduplication without collapse behaviour, set a
|
|
69
|
-
* distinct `options.collapseKey` or leave both unset.
|
|
110
|
+
* Collapse controls transport coalescing. It does not guarantee replacement
|
|
111
|
+
* of a notification that has already been delivered.
|
|
70
112
|
*/
|
|
71
113
|
id?: string;
|
|
72
114
|
title: string;
|
|
115
|
+
subtitle?: string;
|
|
73
116
|
body: string;
|
|
74
117
|
data?: Record<string, string>;
|
|
75
118
|
android?: NotifyKitAndroidConfig;
|
|
@@ -100,7 +143,7 @@ export type NotifyKitAndroidOutput = {
|
|
|
100
143
|
};
|
|
101
144
|
|
|
102
145
|
export type NotifyKitApnsAps = {
|
|
103
|
-
alert: { title: string; body: string };
|
|
146
|
+
alert: { title: string; body: string; subtitle?: string };
|
|
104
147
|
sound?: string;
|
|
105
148
|
category?: string;
|
|
106
149
|
'mutable-content': 1;
|
|
@@ -141,7 +184,9 @@ export type NotifyKitPayloadOutput = NotifyKitPayloadTarget & {
|
|
|
141
184
|
|
|
142
185
|
export type SerializedNotifeeOptions = {
|
|
143
186
|
_v: 1;
|
|
187
|
+
id?: string;
|
|
144
188
|
title: string;
|
|
189
|
+
subtitle?: string;
|
|
145
190
|
body: string;
|
|
146
191
|
android?: NotifyKitAndroidConfig;
|
|
147
192
|
ios?: NotifyKitIosConfig;
|
|
@@ -417,6 +417,17 @@ export enum EventType {
|
|
|
417
417
|
* Event type is sent when a notification wants to start a foreground service but a foreground service is already started.
|
|
418
418
|
*/
|
|
419
419
|
FG_ALREADY_EXIST = 8,
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Event type for an Android Foreground Service timeout handled by the native timeout path.
|
|
423
|
+
*
|
|
424
|
+
* Delivery to JavaScript is best-effort; JavaScript execution is not guaranteed.
|
|
425
|
+
* This event does not confirm completed Android service teardown, guarantee cancellation of
|
|
426
|
+
* JavaScript work, or authorize a new Foreground Service start.
|
|
427
|
+
*
|
|
428
|
+
* @platform android
|
|
429
|
+
*/
|
|
430
|
+
FG_TIMEOUT = 9,
|
|
420
431
|
}
|
|
421
432
|
|
|
422
433
|
/**
|
|
@@ -436,6 +447,7 @@ export interface EventDetail {
|
|
|
436
447
|
* - [`EventType.DELIVERED`](/react-native/reference/eventtype#delivered)
|
|
437
448
|
* - [`EventType.TRIGGER_NOTIFICATION_CREATED`](/react-native/reference/eventtype#trigger_notification_created)
|
|
438
449
|
* - [`EventType.FG_ALREADY_EXIST`](/react-native/reference/eventtype#fg_already_exist)
|
|
450
|
+
* - [`EventType.FG_TIMEOUT`](/react-native/reference/eventtype#fg_timeout)
|
|
439
451
|
*/
|
|
440
452
|
notification?: Notification;
|
|
441
453
|
|
|
@@ -494,6 +506,21 @@ export interface EventDetail {
|
|
|
494
506
|
* @platform android API Level >= 28
|
|
495
507
|
*/
|
|
496
508
|
blocked?: boolean;
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* The start command identifier reported by Android's timeout callback for `EventType.FG_TIMEOUT`.
|
|
512
|
+
*
|
|
513
|
+
* @platform android
|
|
514
|
+
*/
|
|
515
|
+
startId?: number;
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* The Foreground Service type reported by Android's timeout callback for `EventType.FG_TIMEOUT`.
|
|
519
|
+
* A value of `-1` is preserved when the callback overload does not provide an explicit type.
|
|
520
|
+
*
|
|
521
|
+
* @platform android
|
|
522
|
+
*/
|
|
523
|
+
fgsType?: number;
|
|
497
524
|
}
|
|
498
525
|
|
|
499
526
|
/**
|
|
@@ -19,6 +19,10 @@ export default function validateAndroidPressAction(
|
|
|
19
19
|
throw new Error("'id' expected a non-empty string value.");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
if (pressAction.id === '__NOTIFEE_OPT_OUT__') {
|
|
23
|
+
throw new Error("'id' is reserved for an explicit null pressAction.");
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
const out: NotificationPressAction = {
|
|
23
27
|
id: pressAction.id,
|
|
24
28
|
};
|
|
@@ -133,8 +133,13 @@ export default function validateIOSNotification(ios?: NotificationIOS): Notifica
|
|
|
133
133
|
/**
|
|
134
134
|
* badgeCount
|
|
135
135
|
*/
|
|
136
|
-
if (objectHasProperty(ios, 'badgeCount')) {
|
|
137
|
-
if (
|
|
136
|
+
if (objectHasProperty(ios, 'badgeCount') && ios.badgeCount !== null) {
|
|
137
|
+
if (
|
|
138
|
+
!isNumber(ios.badgeCount) ||
|
|
139
|
+
!Number.isFinite(ios.badgeCount) ||
|
|
140
|
+
!Number.isInteger(ios.badgeCount) ||
|
|
141
|
+
ios.badgeCount < 0
|
|
142
|
+
) {
|
|
138
143
|
throw new Error("'notification.ios.badgeCount' expected a number value >=0.");
|
|
139
144
|
}
|
|
140
145
|
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '10.
|
|
2
|
+
export const version = '10.8.1';
|