1 | import { EventEmitter } from 'fbemitter';
|
2 | import warning from 'fbjs/lib/warning';
|
3 | import invariant from 'invariant';
|
4 | import { AsyncStorage, DeviceEventEmitter, NativeModules, Platform } from 'react-native';
|
5 | const { ExponentNotifications } = NativeModules;
|
6 | let _emitter;
|
7 | let _initialNotification;
|
8 | function _maybeInitEmitter() {
|
9 | if (!_emitter) {
|
10 | _emitter = new EventEmitter();
|
11 | DeviceEventEmitter.addListener('Exponent.notification', _emitNotification);
|
12 | }
|
13 | }
|
14 | function _emitNotification(notification) {
|
15 | if (typeof notification === 'string') {
|
16 | notification = JSON.parse(notification);
|
17 | }
|
18 |
|
19 | notification = { ...notification };
|
20 | if (typeof notification.data === 'string') {
|
21 | try {
|
22 | notification.data = JSON.parse(notification.data);
|
23 | }
|
24 | catch (e) {
|
25 |
|
26 | }
|
27 | }
|
28 | _emitter.emit('notification', notification);
|
29 | }
|
30 | function _processNotification(notification) {
|
31 | notification = Object.assign({}, notification);
|
32 | if (!notification.data) {
|
33 | notification.data = {};
|
34 | }
|
35 | if (notification.hasOwnProperty('count')) {
|
36 | delete notification.count;
|
37 | }
|
38 |
|
39 | if (Platform.OS === 'ios') {
|
40 | if (notification.android) {
|
41 | delete notification.android;
|
42 | }
|
43 | if (notification.ios) {
|
44 | notification = Object.assign(notification, notification.ios);
|
45 | delete notification.ios;
|
46 | }
|
47 | }
|
48 |
|
49 |
|
50 | if (Platform.OS === 'android') {
|
51 | if (notification.ios) {
|
52 | delete notification.ios;
|
53 | }
|
54 | if (notification.android) {
|
55 | notification = Object.assign(notification, notification.android);
|
56 | delete notification.android;
|
57 | }
|
58 | }
|
59 | return notification;
|
60 | }
|
61 | function _validateNotification(notification) {
|
62 | if (Platform.OS === 'ios') {
|
63 | invariant(!!notification.title && !!notification.body, 'Local notifications on iOS require both a title and a body');
|
64 | }
|
65 | else if (Platform.OS === 'android') {
|
66 | invariant(!!notification.title, 'Local notifications on Android require a title');
|
67 | }
|
68 | }
|
69 | let ASYNC_STORAGE_PREFIX = '__expo_internal_channel_';
|
70 |
|
71 |
|
72 | let IS_USING_NEW_BINARY = typeof ExponentNotifications.createChannel === 'function';
|
73 | async function _legacyReadChannel(id) {
|
74 | try {
|
75 | let channelString = await AsyncStorage.getItem(`${ASYNC_STORAGE_PREFIX}${id}`);
|
76 | if (channelString) {
|
77 | return JSON.parse(channelString);
|
78 | }
|
79 | }
|
80 | catch (e) { }
|
81 | return null;
|
82 | }
|
83 | function _legacyDeleteChannel(id) {
|
84 | return AsyncStorage.removeItem(`${ASYNC_STORAGE_PREFIX}${id}`);
|
85 | }
|
86 | if (Platform.OS === 'android') {
|
87 | AsyncStorage.clear = async function (callback) {
|
88 | try {
|
89 | let keys = await AsyncStorage.getAllKeys();
|
90 | let result = null;
|
91 | if (keys && keys.length) {
|
92 | let filteredKeys = keys.filter(key => !key.startsWith(ASYNC_STORAGE_PREFIX));
|
93 | await AsyncStorage.multiRemove(filteredKeys);
|
94 | }
|
95 | callback && callback();
|
96 | }
|
97 | catch (e) {
|
98 | callback && callback(e);
|
99 | throw e;
|
100 | }
|
101 | };
|
102 | }
|
103 |
|
104 |
|
105 | function _legacySaveChannel(id, channel) {
|
106 | return AsyncStorage.setItem(`${ASYNC_STORAGE_PREFIX}${id}`, JSON.stringify(channel));
|
107 | }
|
108 | export default {
|
109 |
|
110 | _setInitialNotification(notification) {
|
111 | _initialNotification = notification;
|
112 | },
|
113 |
|
114 | getExpoPushTokenAsync() {
|
115 | return ExponentNotifications.getExponentPushTokenAsync();
|
116 | },
|
117 | getDevicePushTokenAsync: (config) => ExponentNotifications.getDevicePushTokenAsync(config || {}),
|
118 | createChannelAndroidAsync(id, channel) {
|
119 | if (Platform.OS === 'ios') {
|
120 | console.warn('createChannelAndroidAsync(...) has no effect on iOS');
|
121 | return Promise.resolve();
|
122 | }
|
123 |
|
124 |
|
125 | if (!IS_USING_NEW_BINARY) {
|
126 | return _legacySaveChannel(id, channel);
|
127 | }
|
128 | return ExponentNotifications.createChannel(id, channel);
|
129 | },
|
130 | deleteChannelAndroidAsync(id) {
|
131 | if (Platform.OS === 'ios') {
|
132 | console.warn('deleteChannelAndroidAsync(...) has no effect on iOS');
|
133 | return Promise.resolve();
|
134 | }
|
135 |
|
136 |
|
137 | if (!IS_USING_NEW_BINARY) {
|
138 | return Promise.resolve();
|
139 | }
|
140 | return ExponentNotifications.deleteChannel(id);
|
141 | },
|
142 |
|
143 | async presentLocalNotificationAsync(notification) {
|
144 | _validateNotification(notification);
|
145 | let nativeNotification = _processNotification(notification);
|
146 | if (Platform.OS === 'ios') {
|
147 | return ExponentNotifications.presentLocalNotification(nativeNotification);
|
148 | }
|
149 | else {
|
150 | let _channel;
|
151 | if (nativeNotification.channelId) {
|
152 | _channel = await _legacyReadChannel(nativeNotification.channelId);
|
153 | }
|
154 | if (IS_USING_NEW_BINARY) {
|
155 |
|
156 | _legacyDeleteChannel(nativeNotification.channelId);
|
157 | return ExponentNotifications.presentLocalNotificationWithChannel(nativeNotification, _channel);
|
158 | }
|
159 | else {
|
160 |
|
161 |
|
162 | if (_channel) {
|
163 | nativeNotification.sound = _channel.sound;
|
164 | nativeNotification.priority = _channel.priority;
|
165 | nativeNotification.vibrate = _channel.vibrate;
|
166 | }
|
167 | return ExponentNotifications.presentLocalNotification(nativeNotification);
|
168 | }
|
169 | }
|
170 | },
|
171 |
|
172 | async scheduleLocalNotificationAsync(notification, options = {}) {
|
173 |
|
174 |
|
175 | const now = Date.now();
|
176 |
|
177 | _validateNotification(notification);
|
178 | let nativeNotification = _processNotification(notification);
|
179 |
|
180 | if (options.time) {
|
181 | let timeAsDateObj = null;
|
182 | if (options.time && typeof options.time === 'number') {
|
183 | timeAsDateObj = new Date(options.time);
|
184 | if (timeAsDateObj.toString() === 'Invalid Date') {
|
185 | timeAsDateObj = null;
|
186 | }
|
187 | }
|
188 | else if (options.time && options.time instanceof Date) {
|
189 | timeAsDateObj = options.time;
|
190 | }
|
191 |
|
192 | if (!timeAsDateObj) {
|
193 | throw new Error(`Provided value for "time" is invalid. Please verify that it's either a number representing Unix Epoch time in milliseconds, or a valid date object.`);
|
194 | }
|
195 |
|
196 |
|
197 | warning(timeAsDateObj.getTime() >= now, `Provided value for "time" is before the current date. Did you possibly pass number of seconds since Unix Epoch instead of number of milliseconds?`);
|
198 |
|
199 | if (Platform.OS === 'ios') {
|
200 | options = {
|
201 | ...options,
|
202 | time: timeAsDateObj.getTime(),
|
203 | };
|
204 | }
|
205 | else {
|
206 | options = {
|
207 | ...options,
|
208 | time: timeAsDateObj,
|
209 | };
|
210 | }
|
211 | }
|
212 | if (options.intervalMs != null && options.repeat != null) {
|
213 | throw new Error(`Pass either the "repeat" option or "intervalMs" option, not both`);
|
214 | }
|
215 |
|
216 | if (options.repeat != null) {
|
217 | const validOptions = new Set(['minute', 'hour', 'day', 'week', 'month', 'year']);
|
218 | if (!validOptions.has(options.repeat)) {
|
219 | throw new Error(`Pass one of ['minute', 'hour', 'day', 'week', 'month', 'year'] as the value for the "repeat" option`);
|
220 | }
|
221 | }
|
222 | if (options.intervalMs != null) {
|
223 | if (Platform.OS === 'ios') {
|
224 | throw new Error(`The "intervalMs" option is not supported on iOS`);
|
225 | }
|
226 | if (options.intervalMs <= 0 || !Number.isInteger(options.intervalMs)) {
|
227 | throw new Error(`Pass an integer greater than zero as the value for the "intervalMs" option`);
|
228 | }
|
229 | }
|
230 | if (Platform.OS === 'ios') {
|
231 | return ExponentNotifications.scheduleLocalNotification(nativeNotification, options);
|
232 | }
|
233 | else {
|
234 | let _channel;
|
235 | if (nativeNotification.channelId) {
|
236 | _channel = await _legacyReadChannel(nativeNotification.channelId);
|
237 | }
|
238 | if (IS_USING_NEW_BINARY) {
|
239 |
|
240 | _legacyDeleteChannel(nativeNotification.channelId);
|
241 | return ExponentNotifications.scheduleLocalNotificationWithChannel(nativeNotification, options, _channel);
|
242 | }
|
243 | else {
|
244 |
|
245 |
|
246 | if (_channel) {
|
247 | nativeNotification.sound = _channel.sound;
|
248 | nativeNotification.priority = _channel.priority;
|
249 | nativeNotification.vibrate = _channel.vibrate;
|
250 | }
|
251 | return ExponentNotifications.scheduleLocalNotification(nativeNotification, options);
|
252 | }
|
253 | }
|
254 | },
|
255 |
|
256 | async dismissNotificationAsync(notificationId) {
|
257 | if (Platform.OS === 'android') {
|
258 | return ExponentNotifications.dismissNotification(notificationId);
|
259 | }
|
260 | else {
|
261 | throw new Error('Dismissing notifications is not supported on iOS');
|
262 | }
|
263 | },
|
264 |
|
265 | async dismissAllNotificationsAsync() {
|
266 | if (Platform.OS === 'android') {
|
267 | return ExponentNotifications.dismissAllNotifications();
|
268 | }
|
269 | else {
|
270 | throw new Error('Dismissing notifications is not supported on iOS');
|
271 | }
|
272 | },
|
273 |
|
274 | cancelScheduledNotificationAsync(notificationId) {
|
275 | return ExponentNotifications.cancelScheduledNotification(notificationId);
|
276 | },
|
277 |
|
278 | cancelAllScheduledNotificationsAsync() {
|
279 | return ExponentNotifications.cancelAllScheduledNotifications();
|
280 | },
|
281 |
|
282 | addListener(listener) {
|
283 | _maybeInitEmitter();
|
284 | if (_initialNotification) {
|
285 | const initialNotification = _initialNotification;
|
286 | _initialNotification = null;
|
287 | setTimeout(() => {
|
288 | _emitNotification(initialNotification);
|
289 | }, 0);
|
290 | }
|
291 | return _emitter.addListener('notification', listener);
|
292 | },
|
293 | async getBadgeNumberAsync() {
|
294 | if (!ExponentNotifications.getBadgeNumberAsync) {
|
295 | return 0;
|
296 | }
|
297 | return ExponentNotifications.getBadgeNumberAsync();
|
298 | },
|
299 | async setBadgeNumberAsync(number) {
|
300 | if (!ExponentNotifications.setBadgeNumberAsync) {
|
301 | return;
|
302 | }
|
303 | return ExponentNotifications.setBadgeNumberAsync(number);
|
304 | },
|
305 | };
|
306 |
|
\ | No newline at end of file |