1import { Platform, UnavailabilityError, uuid } from 'expo-modules-core';
2import NotificationScheduler from './NotificationScheduler';
3/**
4 * Schedules a notification to be triggered in the future.
5 * > **Note:** Please note that this does not mean that the notification will be presented when it is triggered.
6 * For the notification to be presented you have to set a notification handler with [`setNotificationHandler`](#notificationssetnotificationhandlerhandler)
7 * that will return an appropriate notification behavior. For more information see the example below.
8 * @param request An object describing the notification to be triggered.
9 * @return Returns a Promise resolving to a string which is a notification identifier you can later use to cancel the notification or to identify an incoming notification.
10 * @example
11 * # Schedule the notification that will trigger once, in one minute from now
12 * ```ts
13 * import * as Notifications from 'expo-notifications';
14 *
15 * Notifications.scheduleNotificationAsync({
16 *   content: {
17 *     title: "Time's up!",
18 *     body: 'Change sides!',
19 *   },
20 *   trigger: {
21 *     seconds: 60,
22 *   },
23 * });
24 * ```
25 *
26 * # Schedule the notification that will trigger repeatedly, every 20 minutes
27 * ```ts
28 * import * as Notifications from 'expo-notifications';
29 *
30 * Notifications.scheduleNotificationAsync({
31 *   content: {
32 *     title: 'Remember to drink water!',
33 *   },
34 *   trigger: {
35 *     seconds: 60 * 20,
36 *     repeats: true,
37 *   },
38 * });
39 * ```
40 *
41 * # Schedule the notification that will trigger once, at the beginning of next hour
42 * ```ts
43 * import * as Notifications from 'expo-notifications';
44 *
45 * const trigger = new Date(Date.now() + 60 * 60 * 1000);
46 * trigger.setMinutes(0);
47 * trigger.setSeconds(0);
48 *
49 * Notifications.scheduleNotificationAsync({
50 *   content: {
51 *     title: 'Happy new hour!',
52 *   },
53 *   trigger,
54 * });
55 * ```
56 * @header schedule
57 */
58export default async function scheduleNotificationAsync(request) {
59    if (!NotificationScheduler.scheduleNotificationAsync) {
60        throw new UnavailabilityError('Notifications', 'scheduleNotificationAsync');
61    }
62    return await NotificationScheduler.scheduleNotificationAsync(request.identifier ?? uuid.v4(), request.content, parseTrigger(request.trigger));
63}
64const DAILY_TRIGGER_EXPECTED_DATE_COMPONENTS = [
65    'hour',
66    'minute',
67];
68const WEEKLY_TRIGGER_EXPECTED_DATE_COMPONENTS = [
69    'weekday',
70    'hour',
71    'minute',
72];
73const YEARLY_TRIGGER_EXPECTED_DATE_COMPONENTS = [
74    'day',
75    'month',
76    'hour',
77    'minute',
78];
79export function parseTrigger(userFacingTrigger) {
80    if (userFacingTrigger === null) {
81        return null;
82    }
83    if (userFacingTrigger === undefined) {
84        throw new TypeError('Encountered an `undefined` notification trigger. If you want to trigger the notification immediately, pass in an explicit `null` value.');
85    }
86    if (isDateTrigger(userFacingTrigger)) {
87        return parseDateTrigger(userFacingTrigger);
88    }
89    else if (isDailyTriggerInput(userFacingTrigger)) {
90        validateDateComponentsInTrigger(userFacingTrigger, DAILY_TRIGGER_EXPECTED_DATE_COMPONENTS);
91        return {
92            type: 'daily',
93            channelId: userFacingTrigger.channelId,
94            hour: userFacingTrigger.hour,
95            minute: userFacingTrigger.minute,
96        };
97    }
98    else if (isWeeklyTriggerInput(userFacingTrigger)) {
99        validateDateComponentsInTrigger(userFacingTrigger, WEEKLY_TRIGGER_EXPECTED_DATE_COMPONENTS);
100        return {
101            type: 'weekly',
102            channelId: userFacingTrigger.channelId,
103            weekday: userFacingTrigger.weekday,
104            hour: userFacingTrigger.hour,
105            minute: userFacingTrigger.minute,
106        };
107    }
108    else if (isYearlyTriggerInput(userFacingTrigger)) {
109        validateDateComponentsInTrigger(userFacingTrigger, YEARLY_TRIGGER_EXPECTED_DATE_COMPONENTS);
110        return {
111            type: 'yearly',
112            channelId: userFacingTrigger.channelId,
113            day: userFacingTrigger.day,
114            month: userFacingTrigger.month,
115            hour: userFacingTrigger.hour,
116            minute: userFacingTrigger.minute,
117        };
118    }
119    else if (isSecondsPropertyMisusedInCalendarTriggerInput(userFacingTrigger)) {
120        throw new TypeError('Could not have inferred the notification trigger type: if you want to use a time interval trigger, pass in only `seconds` with or without `repeats` property; if you want to use calendar-based trigger, pass in `second`.');
121    }
122    else if ('seconds' in userFacingTrigger) {
123        return {
124            type: 'timeInterval',
125            channelId: userFacingTrigger.channelId,
126            seconds: userFacingTrigger.seconds,
127            repeats: userFacingTrigger.repeats ?? false,
128        };
129    }
130    else if (isCalendarTrigger(userFacingTrigger)) {
131        const { repeats, ...calendarTrigger } = userFacingTrigger;
132        return { type: 'calendar', value: calendarTrigger, repeats };
133    }
134    else {
135        return Platform.select({
136            default: null,
137            android: { type: 'channel', channelId: userFacingTrigger.channelId },
138        });
139    }
140}
141function isCalendarTrigger(trigger) {
142    const { channelId, ...triggerWithoutChannelId } = trigger;
143    return Object.keys(triggerWithoutChannelId).length > 0;
144}
145function isDateTrigger(trigger) {
146    return (trigger instanceof Date ||
147        typeof trigger === 'number' ||
148        (typeof trigger === 'object' && 'date' in trigger));
149}
150function parseDateTrigger(trigger) {
151    if (trigger instanceof Date || typeof trigger === 'number') {
152        return { type: 'date', timestamp: toTimestamp(trigger) };
153    }
154    return { type: 'date', timestamp: toTimestamp(trigger.date), channelId: trigger.channelId };
155}
156function toTimestamp(date) {
157    if (date instanceof Date) {
158        return date.getTime();
159    }
160    return date;
161}
162function isDailyTriggerInput(trigger) {
163    if (typeof trigger !== 'object')
164        return false;
165    const { channelId, ...triggerWithoutChannelId } = trigger;
166    return (Object.keys(triggerWithoutChannelId).length ===
167        DAILY_TRIGGER_EXPECTED_DATE_COMPONENTS.length + 1 &&
168        DAILY_TRIGGER_EXPECTED_DATE_COMPONENTS.every((component) => component in triggerWithoutChannelId) &&
169        'repeats' in triggerWithoutChannelId &&
170        triggerWithoutChannelId.repeats === true);
171}
172function isWeeklyTriggerInput(trigger) {
173    if (typeof trigger !== 'object')
174        return false;
175    const { channelId, ...triggerWithoutChannelId } = trigger;
176    return (Object.keys(triggerWithoutChannelId).length ===
177        WEEKLY_TRIGGER_EXPECTED_DATE_COMPONENTS.length + 1 &&
178        WEEKLY_TRIGGER_EXPECTED_DATE_COMPONENTS.every((component) => component in triggerWithoutChannelId) &&
179        'repeats' in triggerWithoutChannelId &&
180        triggerWithoutChannelId.repeats === true);
181}
182function isYearlyTriggerInput(trigger) {
183    if (typeof trigger !== 'object')
184        return false;
185    const { channelId, ...triggerWithoutChannelId } = trigger;
186    return (Object.keys(triggerWithoutChannelId).length ===
187        YEARLY_TRIGGER_EXPECTED_DATE_COMPONENTS.length + 1 &&
188        YEARLY_TRIGGER_EXPECTED_DATE_COMPONENTS.every((component) => component in triggerWithoutChannelId) &&
189        'repeats' in triggerWithoutChannelId &&
190        triggerWithoutChannelId.repeats === true);
191}
192function isSecondsPropertyMisusedInCalendarTriggerInput(trigger) {
193    const { channelId, ...triggerWithoutChannelId } = trigger;
194    return (
195    // eg. { seconds: ..., repeats: ..., hour: ... }
196    ('seconds' in triggerWithoutChannelId &&
197        'repeats' in triggerWithoutChannelId &&
198        Object.keys(triggerWithoutChannelId).length > 2) ||
199        // eg. { seconds: ..., hour: ... }
200        ('seconds' in triggerWithoutChannelId &&
201            !('repeats' in triggerWithoutChannelId) &&
202            Object.keys(triggerWithoutChannelId).length > 1));
203}
204function validateDateComponentsInTrigger(trigger, components) {
205    const anyTriggerType = trigger;
206    components.forEach((component) => {
207        if (!(component in anyTriggerType)) {
208            throw new TypeError(`The ${component} parameter needs to be present`);
209        }
210        if (typeof anyTriggerType[component] !== 'number') {
211            throw new TypeError(`The ${component} parameter should be a number`);
212        }
213        switch (component) {
214            case 'month': {
215                const { month } = anyTriggerType;
216                if (month < 0 || month > 11) {
217                    throw new RangeError(`The month parameter needs to be between 0 and 11. Found: ${month}`);
218                }
219                break;
220            }
221            case 'day': {
222                const { day, month } = anyTriggerType;
223                const daysInGivenMonth = daysInMonth(month);
224                if (day < 1 || day > daysInGivenMonth) {
225                    throw new RangeError(`The day parameter for month ${month} must be between 1 and ${daysInGivenMonth}. Found: ${day}`);
226                }
227                break;
228            }
229            case 'weekday': {
230                const { weekday } = anyTriggerType;
231                if (weekday < 1 || weekday > 7) {
232                    throw new RangeError(`The weekday parameter needs to be between 1 and 7. Found: ${weekday}`);
233                }
234                break;
235            }
236            case 'hour': {
237                const { hour } = anyTriggerType;
238                if (hour < 0 || hour > 23) {
239                    throw new RangeError(`The hour parameter needs to be between 0 and 23. Found: ${hour}`);
240                }
241                break;
242            }
243            case 'minute': {
244                const { minute } = anyTriggerType;
245                if (minute < 0 || minute > 59) {
246                    throw new RangeError(`The minute parameter needs to be between 0 and 59. Found: ${minute}`);
247                }
248                break;
249            }
250        }
251    });
252}
253/**
254 * Determines the number of days in the given month (or January if omitted).
255 * If year is specified, it will include leap year logic, else it will always assume a leap year
256 */
257function daysInMonth(month = 0, year) {
258    return new Date(year ?? 2000, month + 1, 0).getDate();
259}
260//# sourceMappingURL=scheduleNotificationAsync.js.map