1import { EventEmitter, Subscription, CodedError, UnavailabilityError } from 'expo-modules-core';
2
3import { Notification, NotificationBehavior } from './Notifications.types';
4import NotificationsHandlerModule from './NotificationsHandlerModule';
5
6/**
7 * @hidden
8 */
9export class NotificationTimeoutError extends CodedError {
10  info: { notification: Notification; id: string };
11  constructor(notificationId: string, notification: Notification) {
12    super('ERR_NOTIFICATION_TIMEOUT', `Notification handling timed out for ID ${notificationId}.`);
13    this.info = { id: notificationId, notification };
14  }
15}
16
17// @docsMissing
18export type NotificationHandlingError = NotificationTimeoutError | Error;
19
20export interface NotificationHandler {
21  /**
22   * A function accepting an incoming notification returning a `Promise` resolving to a behavior ([`NotificationBehavior`](#notificationbehavior))
23   * applicable to the notification
24   * @param notification An object representing the notification.
25   */
26  handleNotification: (notification: Notification) => Promise<NotificationBehavior>;
27  /**
28   * A function called whenever an incoming notification is handled successfully.
29   * @param notificationId Identifier of the notification.
30   */
31  handleSuccess?: (notificationId: string) => void;
32  /**
33   * A function called whenever handling of an incoming notification fails.
34   * @param notificationId Identifier of the notification.
35   * @param error An error which occurred in form of `NotificationHandlingError` object.
36   */
37  handleError?: (notificationId: string, error: NotificationHandlingError) => void;
38}
39
40type HandleNotificationEvent = {
41  id: string;
42  notification: Notification;
43};
44
45type HandleNotificationTimeoutEvent = HandleNotificationEvent;
46
47// Web uses SyntheticEventEmitter
48const notificationEmitter = new EventEmitter(NotificationsHandlerModule);
49
50const handleNotificationEventName = 'onHandleNotification';
51const handleNotificationTimeoutEventName = 'onHandleNotificationTimeout';
52
53let handleSubscription: Subscription | null = null;
54let handleTimeoutSubscription: Subscription | null = null;
55
56/**
57 * When a notification is received while the app is running, using this function you can set a callback that will decide
58 * whether the notification should be shown to the user or not.
59 *
60 * When a notification is received, `handleNotification` is called with the incoming notification as an argument.
61 * The function should respond with a behavior object within 3 seconds, otherwise, the notification will be discarded.
62 * If the notification is handled successfully, `handleSuccess` is called with the identifier of the notification,
63 * otherwise (or on timeout) `handleError` will be called.
64 *
65 * The default behavior when the handler is not set or does not respond in time is not to show the notification.
66 * @param handler A single parameter which should be either `null` (if you want to clear the handler) or a [`NotificationHandler`](#notificationhandler) object.
67 *
68 * @example Implementing a notification handler that always shows the notification when it is received.
69 * ```jsx
70 * import * as Notifications from 'expo-notifications';
71 *
72 * Notifications.setNotificationHandler({
73 *   handleNotification: async () => ({
74 *     shouldShowAlert: true,
75 *     shouldPlaySound: false,
76 *     shouldSetBadge: false,
77 *   }),
78 * });
79 * ```
80 * @header inForeground
81 */
82export function setNotificationHandler(handler: NotificationHandler | null): void {
83  if (handleSubscription) {
84    handleSubscription.remove();
85    handleSubscription = null;
86  }
87  if (handleTimeoutSubscription) {
88    handleTimeoutSubscription.remove();
89    handleTimeoutSubscription = null;
90  }
91
92  if (handler) {
93    handleSubscription = notificationEmitter.addListener<HandleNotificationEvent>(
94      handleNotificationEventName,
95      async ({ id, notification }) => {
96        if (!NotificationsHandlerModule.handleNotificationAsync) {
97          handler.handleError?.(
98            id,
99            new UnavailabilityError('Notifications', 'handleNotificationAsync')
100          );
101          return;
102        }
103
104        try {
105          const behavior = await handler.handleNotification(notification);
106          await NotificationsHandlerModule.handleNotificationAsync(id, behavior);
107          handler.handleSuccess?.(id);
108        } catch (error) {
109          handler.handleError?.(id, error);
110        }
111      }
112    );
113
114    handleTimeoutSubscription = notificationEmitter.addListener<HandleNotificationTimeoutEvent>(
115      handleNotificationTimeoutEventName,
116      ({ id, notification }) =>
117        handler.handleError?.(id, new NotificationTimeoutError(id, notification))
118    );
119  }
120}
121