UNPKG

6.32 kBJavaScriptView Raw
1// @flow
2
3import {
4 EventEmitter,
5 EventSubscription,
6} from 'fbemitter';
7
8import invariant from 'invariant';
9
10import {
11 DeviceEventEmitter,
12 NativeModules,
13 Platform,
14} from 'react-native';
15
16const {
17 ExponentNotifications,
18} = NativeModules;
19
20type Notification = {
21 origin: 'selected' | 'received';
22 data: any;
23 remote: boolean;
24 isMultiple: boolean;
25}
26
27type LocalNotification = {
28 title: string;
29 // How should we deal with body being required on iOS but not on Android?
30 body?: string;
31 data?: any;
32 ios?: {
33 sound?: boolean;
34 };
35 android?: {
36 sound?: boolean;
37 icon?: string;
38 color?: string;
39 priority?: string;
40 sticky?: boolean;
41 vibrate?: boolean | Array<number>;
42 link?: string;
43 };
44}
45
46// Android assigns unique number to each notification natively.
47// Since that's not supported on iOS, we generate an unique string.
48type LocalNotificationId = string | number;
49
50let _emitter;
51let _initialNotification;
52
53function _maybeInitEmitter() {
54 if (!_emitter) {
55 _emitter = new EventEmitter();
56 DeviceEventEmitter.addListener('Exponent.notification', _emitNotification);
57 }
58}
59
60function _emitNotification(notification) {
61 if (typeof notification === 'string') {
62 notification = JSON.parse(notification);
63 }
64
65 /* Don't mutate the original notification */
66 notification = { ...notification };
67
68 if (typeof notification.data === 'string') {
69 try {
70 notification.data = JSON.parse(notification.data);
71 } catch(e) {
72 // It's actually just a string, that's fine
73 }
74 }
75
76 _emitter.emit('notification', notification);
77}
78
79function _processNotification(notification) {
80 notification = Object.assign({}, notification);
81
82 if (!notification.data) {
83 notification.data = {};
84 }
85
86 if (notification.hasOwnProperty('count')) {
87 delete notification.count;
88 }
89
90 // Delete any Android properties on iOS and merge the iOS properties on root
91 // notification object
92 if (Platform.OS === 'ios') {
93 if (notification.android) {
94 delete notification.android;
95 }
96
97 if (notification.ios) {
98 notification = Object.assign(notification, notification.ios);
99 delete notification.ios;
100 }
101 }
102
103 // Delete any iOS properties on Android and merge the Android properties on
104 // root notification object
105 if (Platform.OS === 'android') {
106 if (notification.ios) {
107 delete notification.ios;
108 }
109
110 if (notification.android) {
111 notification = Object.assign(notification, notification.android);
112 delete notification.android;
113 }
114 }
115
116 return notification;
117}
118
119function _validateNotification(notification) {
120 if (Platform.OS === 'ios') {
121 invariant(
122 !!notification.title && !!notification.body,
123 'Local notifications on iOS require both a title and a body'
124 );
125 } else if (Platform.OS === 'android') {
126 invariant(
127 !!notification.title,
128 'Local notifications on Android require a title'
129 );
130 }
131}
132
133export default {
134 /* Only used internally to initialize the notification from top level props */
135 _setInitialNotification(notification: Notification) {
136 _initialNotification = notification;
137 },
138
139 /* Re-export, we can add flow here if we want as well */
140 getExponentPushTokenAsync: ExponentNotifications.getExponentPushTokenAsync,
141
142 /* Shows a notification instantly */
143 presentLocalNotificationAsync(notification: LocalNotification): Promise<LocalNotificationId> {
144 _validateNotification(notification);
145 notification = _processNotification(notification);
146
147 return ExponentNotifications.presentLocalNotification(notification);
148 },
149
150 /* Schedule a notification at a later date */
151 async scheduleLocalNotificationAsync(
152 notification: LocalNotification,
153 options: {
154 time?: Date | number;
155 repeat?: 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year';
156 }
157 ): Promise<LocalNotificationId> {
158 _validateNotification(notification);
159 notification = _processNotification(notification);
160
161 if (Platform.OS === 'ios') {
162 if (options.time && options.time instanceof Date) {
163 options = {
164 ...options,
165 time: options.time.getTime(),
166 };
167 }
168 } else {
169 if (options.time && typeof options.time === 'number') {
170 options = {
171 ...options,
172 time: new Date(options.time),
173 };
174 }
175 }
176
177 return ExponentNotifications.scheduleLocalNotification(notification, options);
178 },
179
180 /* Dismiss currently shown notification with ID (Android only) */
181 async dismissNotificationAsync(notificationId: LocalNotificationId): Promise<void> {
182 if (Platform.OS === 'android') {
183 return ExponentNotifications.dismissNotification(notificationId);
184 } else {
185 return Promise.reject('Dismissing notifications is not supported on iOS');
186 }
187 },
188
189 /* Dismiss all currently shown notifications (Android only) */
190 async dismissAllNotificationsAsync(): Promise<void> {
191 if (Platform.OS === 'android') {
192 return ExponentNotifications.dismissAllNotifications();
193 } else {
194 return Promise.reject('Dismissing notifications is not supported on iOS');
195 }
196 },
197
198 /* Cancel scheduled notification notification with ID */
199 async cancelScheduledNotificationAsync(notificationId: LocalNotificationId): Promise<void> {
200 return ExponentNotifications.cancelScheduledNotification(notificationId);
201 },
202
203 /* Cancel all scheduled notifications */
204 async cancelAllScheduledNotificationsAsync(): Promise<void> {
205 return ExponentNotifications.cancelAllScheduledNotifications();
206 },
207
208 /* Primary public api */
209 addListener(listener: Function): EventSubscription {
210 _maybeInitEmitter();
211
212 if (_initialNotification) {
213 const initialNotification = _initialNotification;
214 _initialNotification = null;
215 setTimeout(() => {
216 _emitNotification(initialNotification);
217 }, 0);
218 }
219
220 return _emitter.addListener('notification', listener);
221 },
222
223 async getBadgeNumberAsync(): Promise<number> {
224 if (!ExponentNotifications.getBadgeNumberAsync) {
225 return 0;
226 }
227 return ExponentNotifications.getBadgeNumberAsync();
228 },
229
230 async setBadgeNumberAsync(number: number): Promise<void> {
231 if (!ExponentNotifications.setBadgeNumberAsync) {
232 return;
233 }
234 return ExponentNotifications.setBadgeNumberAsync(number);
235 },
236};