@symbiote-native/notifications 0.1.0
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/LICENSE +21 -0
- package/README.md +258 -0
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +1 -0
- package/build/core/background-task.d.ts +9 -0
- package/build/core/background-task.js +28 -0
- package/build/core/badge.d.ts +7 -0
- package/build/core/badge.js +23 -0
- package/build/core/categories.d.ts +10 -0
- package/build/core/categories.js +28 -0
- package/build/core/channels.d.ts +20 -0
- package/build/core/channels.js +106 -0
- package/build/core/emitter.d.ts +24 -0
- package/build/core/emitter.js +58 -0
- package/build/core/handler.d.ts +15 -0
- package/build/core/handler.js +50 -0
- package/build/core/index.d.ts +12 -0
- package/build/core/index.js +11 -0
- package/build/core/native-modules.d.ts +82 -0
- package/build/core/native-modules.js +33 -0
- package/build/core/permissions.d.ts +8 -0
- package/build/core/permissions.js +30 -0
- package/build/core/presenter.d.ts +7 -0
- package/build/core/presenter.js +27 -0
- package/build/core/scheduler.d.ts +13 -0
- package/build/core/scheduler.js +193 -0
- package/build/core/tokens.d.ts +30 -0
- package/build/core/tokens.js +192 -0
- package/build/core/types.d.ts +633 -0
- package/build/core/types.js +116 -0
- package/build/core/utils.d.ts +6 -0
- package/build/core/utils.js +53 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +2 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/background-task.d.ts +9 -0
- package/build-ngc/core/background-task.js +29 -0
- package/build-ngc/core/background-task.js.map +1 -0
- package/build-ngc/core/badge.d.ts +7 -0
- package/build-ngc/core/badge.js +24 -0
- package/build-ngc/core/badge.js.map +1 -0
- package/build-ngc/core/categories.d.ts +10 -0
- package/build-ngc/core/categories.js +29 -0
- package/build-ngc/core/categories.js.map +1 -0
- package/build-ngc/core/channels.d.ts +20 -0
- package/build-ngc/core/channels.js +107 -0
- package/build-ngc/core/channels.js.map +1 -0
- package/build-ngc/core/emitter.d.ts +24 -0
- package/build-ngc/core/emitter.js +59 -0
- package/build-ngc/core/emitter.js.map +1 -0
- package/build-ngc/core/handler.d.ts +15 -0
- package/build-ngc/core/handler.js +51 -0
- package/build-ngc/core/handler.js.map +1 -0
- package/build-ngc/core/index.d.ts +12 -0
- package/build-ngc/core/index.js +12 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-modules.d.ts +82 -0
- package/build-ngc/core/native-modules.js +34 -0
- package/build-ngc/core/native-modules.js.map +1 -0
- package/build-ngc/core/permissions.d.ts +8 -0
- package/build-ngc/core/permissions.js +31 -0
- package/build-ngc/core/permissions.js.map +1 -0
- package/build-ngc/core/presenter.d.ts +7 -0
- package/build-ngc/core/presenter.js +28 -0
- package/build-ngc/core/presenter.js.map +1 -0
- package/build-ngc/core/scheduler.d.ts +13 -0
- package/build-ngc/core/scheduler.js +194 -0
- package/build-ngc/core/scheduler.js.map +1 -0
- package/build-ngc/core/tokens.d.ts +30 -0
- package/build-ngc/core/tokens.js +193 -0
- package/build-ngc/core/tokens.js.map +1 -0
- package/build-ngc/core/types.d.ts +633 -0
- package/build-ngc/core/types.js +117 -0
- package/build-ngc/core/types.js.map +1 -0
- package/build-ngc/core/utils.d.ts +6 -0
- package/build-ngc/core/utils.js +54 -0
- package/build-ngc/core/utils.js.map +1 -0
- package/native-link.json +88 -0
- package/package.json +150 -0
- package/src/angular/index.ts +1 -0
- package/src/core/background-task.ts +32 -0
- package/src/core/badge.ts +26 -0
- package/src/core/categories.ts +60 -0
- package/src/core/channels.ts +183 -0
- package/src/core/emitter.ts +95 -0
- package/src/core/handler.ts +95 -0
- package/src/core/index.ts +144 -0
- package/src/core/native-modules.ts +209 -0
- package/src/core/permissions.ts +46 -0
- package/src/core/presenter.ts +45 -0
- package/src/core/scheduler.ts +265 -0
- package/src/core/tokens.ts +272 -0
- package/src/core/types.ts +767 -0
- package/src/core/utils.ts +82 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Resolves expo-notifications' 13 native modules by the same names their own
|
|
2
|
+
// `<Module>.native.ts` one-liners use (`.vendors/expo` @ origin/sdk-57,
|
|
3
|
+
// `packages/expo-notifications/src/*.native.ts`) — see this package's `native-link.json` for
|
|
4
|
+
// the matching Android Kotlin `importPath`/`className` for each.
|
|
5
|
+
//
|
|
6
|
+
// Every method is declared optional so a call site checks availability itself and throws an
|
|
7
|
+
// `UnavailabilityError`, matching upstream's own per-platform capability checks rather than
|
|
8
|
+
// assuming every native module implements its whole surface — same pattern
|
|
9
|
+
// packages/task-manager/src/core/native-module.ts and packages/local-auth use.
|
|
10
|
+
import {
|
|
11
|
+
NativeModule,
|
|
12
|
+
Platform,
|
|
13
|
+
requireNativeModule,
|
|
14
|
+
requireOptionalNativeModule,
|
|
15
|
+
type EventSubscription,
|
|
16
|
+
type ProxyNativeModule,
|
|
17
|
+
} from 'expo-modules-core';
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
INotification,
|
|
21
|
+
INotificationAction,
|
|
22
|
+
INotificationBehavior,
|
|
23
|
+
INotificationCategory,
|
|
24
|
+
INotificationCategoryOptions,
|
|
25
|
+
INotificationChannel,
|
|
26
|
+
INotificationChannelGroup,
|
|
27
|
+
INotificationChannelGroupInput,
|
|
28
|
+
INotificationChannelInput,
|
|
29
|
+
INotificationContentInput,
|
|
30
|
+
INotificationPermissionsRequest,
|
|
31
|
+
INotificationPermissionsStatus,
|
|
32
|
+
INotificationRequest,
|
|
33
|
+
INotificationResponse,
|
|
34
|
+
INativeNotificationTriggerInput,
|
|
35
|
+
} from './types';
|
|
36
|
+
|
|
37
|
+
// -- Permissions --------------------------------------------------------------------------
|
|
38
|
+
export interface INativePermissionsModule extends ProxyNativeModule {
|
|
39
|
+
getPermissionsAsync?(): Promise<INotificationPermissionsStatus>;
|
|
40
|
+
requestPermissionsAsync?(
|
|
41
|
+
request: INotificationPermissionsRequest[keyof INotificationPermissionsRequest],
|
|
42
|
+
): Promise<INotificationPermissionsStatus>;
|
|
43
|
+
}
|
|
44
|
+
export const notificationPermissionsModule =
|
|
45
|
+
requireNativeModule<INativePermissionsModule>(
|
|
46
|
+
'ExpoNotificationPermissionsModule',
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// -- Push token (also an EventEmitter — onDevicePushToken) --------------------------------
|
|
50
|
+
export type IPushTokenManagerEvents = {
|
|
51
|
+
onDevicePushToken: (params: { devicePushToken: string }) => void;
|
|
52
|
+
};
|
|
53
|
+
export class PushTokenManagerModule extends NativeModule<IPushTokenManagerEvents> {
|
|
54
|
+
getDevicePushTokenAsync?(): Promise<string>;
|
|
55
|
+
unregisterForNotificationsAsync?(): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export const pushTokenManager = requireNativeModule<PushTokenManagerModule>(
|
|
58
|
+
'ExpoPushTokenManager',
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// -- Topic subscription (Android FCM topics only) ------------------------------------------
|
|
62
|
+
// Upstream has no iOS native module for this at all — topics are an FCM/Android-only concept —
|
|
63
|
+
// and resolves TopicSubscriptionModule per-platform (a real native module on Android, a plain
|
|
64
|
+
// no-op JS object everywhere else). requireOptionalNativeModule mirrors that: a hard
|
|
65
|
+
// requireNativeModule here would throw at import time on iOS before any screen code runs.
|
|
66
|
+
export interface INativeTopicSubscriptionModule extends ProxyNativeModule {
|
|
67
|
+
subscribeToTopicAsync?(topic: string): Promise<null>;
|
|
68
|
+
unsubscribeFromTopicAsync?(topic: string): Promise<null>;
|
|
69
|
+
}
|
|
70
|
+
export const topicSubscriptionModule: INativeTopicSubscriptionModule =
|
|
71
|
+
Platform.OS === 'android'
|
|
72
|
+
? requireNativeModule<INativeTopicSubscriptionModule>(
|
|
73
|
+
'ExpoTopicSubscriptionModule',
|
|
74
|
+
)
|
|
75
|
+
: (requireOptionalNativeModule<INativeTopicSubscriptionModule>(
|
|
76
|
+
'ExpoTopicSubscriptionModule',
|
|
77
|
+
) ?? {});
|
|
78
|
+
|
|
79
|
+
// -- Server registration (Expo push service device-id / registration-blob storage) --------
|
|
80
|
+
export interface INativeServerRegistrationModule extends ProxyNativeModule {
|
|
81
|
+
getInstallationIdAsync?(): Promise<string>;
|
|
82
|
+
getRegistrationInfoAsync?(): Promise<string | undefined | null>;
|
|
83
|
+
setRegistrationInfoAsync?(registrationInfo: string | null): Promise<void>;
|
|
84
|
+
}
|
|
85
|
+
export const serverRegistrationModule =
|
|
86
|
+
requireNativeModule<INativeServerRegistrationModule>(
|
|
87
|
+
'NotificationsServerRegistrationModule',
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// -- Presenter (tray) -----------------------------------------------------------------------
|
|
91
|
+
export interface INativePresenterModule extends ProxyNativeModule {
|
|
92
|
+
getPresentedNotificationsAsync?(): Promise<INotification[]>;
|
|
93
|
+
dismissNotificationAsync?(identifier: string): Promise<void>;
|
|
94
|
+
dismissAllNotificationsAsync?(): Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
export const notificationPresenterModule =
|
|
97
|
+
requireNativeModule<INativePresenterModule>('ExpoNotificationPresenter');
|
|
98
|
+
|
|
99
|
+
// -- Badge ------------------------------------------------------------------------------------
|
|
100
|
+
export interface INativeBadgeModule extends ProxyNativeModule {
|
|
101
|
+
getBadgeCountAsync?(): Promise<number>;
|
|
102
|
+
setBadgeCountAsync?(badgeCount: number): Promise<boolean>;
|
|
103
|
+
}
|
|
104
|
+
export const badgeModule =
|
|
105
|
+
requireNativeModule<INativeBadgeModule>('ExpoBadgeModule');
|
|
106
|
+
|
|
107
|
+
// -- Scheduler ----------------------------------------------------------------------------
|
|
108
|
+
export interface INativeSchedulerModule extends ProxyNativeModule {
|
|
109
|
+
getAllScheduledNotificationsAsync?(): Promise<INotificationRequest[]>;
|
|
110
|
+
scheduleNotificationAsync?(
|
|
111
|
+
identifier: string,
|
|
112
|
+
content: INotificationContentInput,
|
|
113
|
+
trigger: INativeNotificationTriggerInput,
|
|
114
|
+
): Promise<string>;
|
|
115
|
+
cancelScheduledNotificationAsync?(identifier: string): Promise<void>;
|
|
116
|
+
cancelAllScheduledNotificationsAsync?(): Promise<void>;
|
|
117
|
+
getNextTriggerDateAsync?(
|
|
118
|
+
trigger: INativeNotificationTriggerInput,
|
|
119
|
+
): Promise<number>;
|
|
120
|
+
}
|
|
121
|
+
export const notificationScheduler =
|
|
122
|
+
requireNativeModule<INativeSchedulerModule>('ExpoNotificationScheduler');
|
|
123
|
+
|
|
124
|
+
// -- Categories -----------------------------------------------------------------------------
|
|
125
|
+
export interface INativeCategoriesModule extends ProxyNativeModule {
|
|
126
|
+
getNotificationCategoriesAsync?(): Promise<INotificationCategory[]>;
|
|
127
|
+
setNotificationCategoryAsync?(
|
|
128
|
+
identifier: string,
|
|
129
|
+
actions: INotificationAction[],
|
|
130
|
+
options?: INotificationCategoryOptions,
|
|
131
|
+
): Promise<INotificationCategory>;
|
|
132
|
+
deleteNotificationCategoryAsync?(identifier: string): Promise<boolean>;
|
|
133
|
+
}
|
|
134
|
+
export const notificationCategoriesModule =
|
|
135
|
+
requireNativeModule<INativeCategoriesModule>(
|
|
136
|
+
'ExpoNotificationCategoriesModule',
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// -- Android channels / channel groups -----------------------------------------------------
|
|
140
|
+
export interface INativeChannelManagerModule extends ProxyNativeModule {
|
|
141
|
+
getNotificationChannelsAsync?(): Promise<INotificationChannel[] | null>;
|
|
142
|
+
getNotificationChannelAsync?(
|
|
143
|
+
channelId: string,
|
|
144
|
+
): Promise<INotificationChannel | null>;
|
|
145
|
+
setNotificationChannelAsync?(
|
|
146
|
+
channelId: string,
|
|
147
|
+
channel: INotificationChannelInput,
|
|
148
|
+
): Promise<INotificationChannel | null>;
|
|
149
|
+
deleteNotificationChannelAsync?(channelId: string): Promise<void>;
|
|
150
|
+
}
|
|
151
|
+
// Android-only concept, same as topicSubscriptionModule above — a hard requireNativeModule
|
|
152
|
+
// here throws at import time on iOS before any screen code runs.
|
|
153
|
+
export const notificationChannelManager: INativeChannelManagerModule =
|
|
154
|
+
Platform.OS === 'android'
|
|
155
|
+
? requireNativeModule<INativeChannelManagerModule>(
|
|
156
|
+
'ExpoNotificationChannelManager',
|
|
157
|
+
)
|
|
158
|
+
: (requireOptionalNativeModule<INativeChannelManagerModule>(
|
|
159
|
+
'ExpoNotificationChannelManager',
|
|
160
|
+
) ?? {});
|
|
161
|
+
|
|
162
|
+
export interface INativeChannelGroupManagerModule extends ProxyNativeModule {
|
|
163
|
+
getNotificationChannelGroupsAsync?(): Promise<INotificationChannelGroup[]>;
|
|
164
|
+
getNotificationChannelGroupAsync?(
|
|
165
|
+
groupId: string,
|
|
166
|
+
): Promise<INotificationChannelGroup | null>;
|
|
167
|
+
setNotificationChannelGroupAsync?(
|
|
168
|
+
groupId: string,
|
|
169
|
+
group: INotificationChannelGroupInput,
|
|
170
|
+
): Promise<INotificationChannelGroup | null>;
|
|
171
|
+
deleteNotificationChannelGroupAsync?(groupId: string): Promise<void>;
|
|
172
|
+
}
|
|
173
|
+
export const notificationChannelGroupManager: INativeChannelGroupManagerModule =
|
|
174
|
+
Platform.OS === 'android'
|
|
175
|
+
? requireNativeModule<INativeChannelGroupManagerModule>(
|
|
176
|
+
'ExpoNotificationChannelGroupManager',
|
|
177
|
+
)
|
|
178
|
+
: (requireOptionalNativeModule<INativeChannelGroupManagerModule>(
|
|
179
|
+
'ExpoNotificationChannelGroupManager',
|
|
180
|
+
) ?? {});
|
|
181
|
+
|
|
182
|
+
// -- Handler / Emitter (legacy ProxyNativeModule + LegacyEventEmitter, same as upstream) ---
|
|
183
|
+
export interface INativeHandlerModule extends ProxyNativeModule {
|
|
184
|
+
handleNotificationAsync?(
|
|
185
|
+
notificationId: string,
|
|
186
|
+
behavior: INotificationBehavior,
|
|
187
|
+
): Promise<void>;
|
|
188
|
+
}
|
|
189
|
+
export const notificationsHandlerModule =
|
|
190
|
+
requireNativeModule<INativeHandlerModule>('ExpoNotificationsHandlerModule');
|
|
191
|
+
|
|
192
|
+
export interface INativeEmitterModule extends ProxyNativeModule {
|
|
193
|
+
getLastNotificationResponse?(): INotificationResponse | null;
|
|
194
|
+
clearLastNotificationResponse?(): void;
|
|
195
|
+
}
|
|
196
|
+
export const notificationsEmitterModule =
|
|
197
|
+
requireNativeModule<INativeEmitterModule>('ExpoNotificationsEmitter');
|
|
198
|
+
|
|
199
|
+
// -- Background task registration ----------------------------------------------------------
|
|
200
|
+
export interface INativeBackgroundTasksModule extends ProxyNativeModule {
|
|
201
|
+
registerTaskAsync(taskName: string): Promise<null>;
|
|
202
|
+
unregisterTaskAsync(taskName: string): Promise<null>;
|
|
203
|
+
}
|
|
204
|
+
export const backgroundNotificationTasksModule =
|
|
205
|
+
requireNativeModule<INativeBackgroundTasksModule>(
|
|
206
|
+
'ExpoBackgroundNotificationTasksModule',
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
export type { EventSubscription };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Ported from expo-notifications @ sdk-57's NotificationPermissions.ts.
|
|
2
|
+
import { Platform, UnavailabilityError } from 'expo-modules-core';
|
|
3
|
+
|
|
4
|
+
import { dlog } from '@symbiote-native/engine';
|
|
5
|
+
|
|
6
|
+
import { notificationPermissionsModule } from './native-modules';
|
|
7
|
+
import type {
|
|
8
|
+
INotificationPermissionsRequest,
|
|
9
|
+
INotificationPermissionsStatus,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
/** Checks (without prompting) whether the app can currently display alerts, play sounds, etc. */
|
|
13
|
+
export async function getPermissionsAsync(): Promise<INotificationPermissionsStatus> {
|
|
14
|
+
if (!notificationPermissionsModule.getPermissionsAsync) {
|
|
15
|
+
throw new UnavailabilityError('Notifications', 'getPermissionsAsync');
|
|
16
|
+
}
|
|
17
|
+
const result = await notificationPermissionsModule.getPermissionsAsync();
|
|
18
|
+
dlog(() => `[notifications] getPermissionsAsync -> ${result.status}`);
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Prompts the user for notification permissions. Defaults to alerts + badge + sound on iOS —
|
|
24
|
+
* on Android every permission is already granted and there is nothing to prompt for.
|
|
25
|
+
*/
|
|
26
|
+
export async function requestPermissionsAsync(
|
|
27
|
+
permissions?: INotificationPermissionsRequest,
|
|
28
|
+
): Promise<INotificationPermissionsStatus> {
|
|
29
|
+
if (!notificationPermissionsModule.requestPermissionsAsync) {
|
|
30
|
+
throw new UnavailabilityError('Notifications', 'requestPermissionsAsync');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const requested: INotificationPermissionsRequest = permissions ?? {
|
|
34
|
+
ios: { allowAlert: true, allowBadge: true, allowSound: true },
|
|
35
|
+
};
|
|
36
|
+
const platformRequest =
|
|
37
|
+
requested[Platform.OS as keyof INotificationPermissionsRequest];
|
|
38
|
+
|
|
39
|
+
dlog('[notifications] requestPermissionsAsync');
|
|
40
|
+
const result =
|
|
41
|
+
await notificationPermissionsModule.requestPermissionsAsync(
|
|
42
|
+
platformRequest,
|
|
43
|
+
);
|
|
44
|
+
dlog(() => `[notifications] requestPermissionsAsync -> ${result.status}`);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Ported from expo-notifications @ sdk-57's getPresentedNotificationsAsync.ts,
|
|
2
|
+
// dismissNotificationAsync.ts, dismissAllNotificationsAsync.ts.
|
|
3
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
4
|
+
|
|
5
|
+
import { notificationPresenterModule } from './native-modules';
|
|
6
|
+
import type { INotification } from './types';
|
|
7
|
+
import { mapNotification } from './utils';
|
|
8
|
+
|
|
9
|
+
/** Notifications currently in the tray (Notification Center). Empty on Android below API 23. */
|
|
10
|
+
export async function getPresentedNotificationsAsync(): Promise<
|
|
11
|
+
INotification[]
|
|
12
|
+
> {
|
|
13
|
+
if (!notificationPresenterModule.getPresentedNotificationsAsync) {
|
|
14
|
+
throw new UnavailabilityError(
|
|
15
|
+
'Notifications',
|
|
16
|
+
'getPresentedNotificationsAsync',
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const notifications =
|
|
20
|
+
await notificationPresenterModule.getPresentedNotificationsAsync();
|
|
21
|
+
return notifications.map(mapNotification);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Removes one notification from the tray by its identifier. */
|
|
25
|
+
export async function dismissNotificationAsync(
|
|
26
|
+
notificationIdentifier: string,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
if (!notificationPresenterModule.dismissNotificationAsync) {
|
|
29
|
+
throw new UnavailabilityError('Notifications', 'dismissNotificationAsync');
|
|
30
|
+
}
|
|
31
|
+
return notificationPresenterModule.dismissNotificationAsync(
|
|
32
|
+
notificationIdentifier,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Removes every one of this app's notifications from the tray. */
|
|
37
|
+
export async function dismissAllNotificationsAsync(): Promise<void> {
|
|
38
|
+
if (!notificationPresenterModule.dismissAllNotificationsAsync) {
|
|
39
|
+
throw new UnavailabilityError(
|
|
40
|
+
'Notifications',
|
|
41
|
+
'dismissAllNotificationsAsync',
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return notificationPresenterModule.dismissAllNotificationsAsync();
|
|
45
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Ported from expo-notifications @ sdk-57's scheduleNotificationAsync.ts,
|
|
2
|
+
// getAllScheduledNotificationsAsync.ts, cancelScheduledNotificationAsync.ts,
|
|
3
|
+
// cancelAllScheduledNotificationsAsync.ts, getNextTriggerDateAsync.ts.
|
|
4
|
+
import { Platform, UnavailabilityError } from 'expo-modules-core';
|
|
5
|
+
|
|
6
|
+
import { dlog } from '@symbiote-native/engine';
|
|
7
|
+
|
|
8
|
+
import { notificationScheduler } from './native-modules';
|
|
9
|
+
import {
|
|
10
|
+
SchedulableTriggerInputTypes,
|
|
11
|
+
type INativeCalendarTriggerInput,
|
|
12
|
+
type INativeDailyTriggerInput,
|
|
13
|
+
type INativeDateTriggerInput,
|
|
14
|
+
type INativeMonthlyTriggerInput,
|
|
15
|
+
type INativeNotificationTriggerInput,
|
|
16
|
+
type INativeTimeIntervalTriggerInput,
|
|
17
|
+
type INativeWeeklyTriggerInput,
|
|
18
|
+
type INativeYearlyTriggerInput,
|
|
19
|
+
type INotificationRequest,
|
|
20
|
+
type INotificationRequestInput,
|
|
21
|
+
type INotificationTriggerInput,
|
|
22
|
+
type ISchedulableNotificationTriggerInput,
|
|
23
|
+
} from './types';
|
|
24
|
+
import { hasValidTriggerObject, mapNotificationRequest } from './utils';
|
|
25
|
+
|
|
26
|
+
function uuid(): string {
|
|
27
|
+
// Same fallback expo-modules-core's own `uuid.v4()` boils down to on native — a real RFC4122
|
|
28
|
+
// v4 id, no extra dependency for one call site.
|
|
29
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, char => {
|
|
30
|
+
const random = (Math.random() * 16) | 0;
|
|
31
|
+
const value = char === 'x' ? random : (random & 0x3) | 0x8;
|
|
32
|
+
return value.toString(16);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type DateComponent = 'month' | 'day' | 'weekday' | 'hour' | 'minute';
|
|
37
|
+
|
|
38
|
+
function daysInMonth(month: number, year?: number): number {
|
|
39
|
+
return new Date(year ?? 2000, month + 1, 0).getDate();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validateDateComponents(
|
|
43
|
+
trigger: Record<string, unknown>,
|
|
44
|
+
components: readonly DateComponent[],
|
|
45
|
+
): void {
|
|
46
|
+
for (const component of components) {
|
|
47
|
+
if (!(component in trigger)) {
|
|
48
|
+
throw new TypeError(`The ${component} parameter needs to be present`);
|
|
49
|
+
}
|
|
50
|
+
const value = trigger[component];
|
|
51
|
+
if (typeof value !== 'number') {
|
|
52
|
+
throw new TypeError(`The ${component} parameter should be a number`);
|
|
53
|
+
}
|
|
54
|
+
switch (component) {
|
|
55
|
+
case 'month':
|
|
56
|
+
if (value < 0 || value > 11) {
|
|
57
|
+
throw new RangeError(
|
|
58
|
+
`The month parameter needs to be between 0 and 11. Found: ${value}`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
case 'day': {
|
|
63
|
+
const month =
|
|
64
|
+
typeof trigger.month === 'number'
|
|
65
|
+
? trigger.month
|
|
66
|
+
: new Date().getMonth();
|
|
67
|
+
const max = daysInMonth(month);
|
|
68
|
+
if (value < 1 || value > max) {
|
|
69
|
+
throw new RangeError(
|
|
70
|
+
`The day parameter for month ${month} must be between 1 and ${max}. Found: ${value}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case 'weekday':
|
|
76
|
+
if (value < 1 || value > 7) {
|
|
77
|
+
throw new RangeError(
|
|
78
|
+
`The weekday parameter needs to be between 1 and 7. Found: ${value}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
case 'hour':
|
|
83
|
+
if (value < 0 || value > 23) {
|
|
84
|
+
throw new RangeError(
|
|
85
|
+
`The hour parameter needs to be between 0 and 23. Found: ${value}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case 'minute':
|
|
90
|
+
if (value < 0 || value > 59) {
|
|
91
|
+
throw new RangeError(
|
|
92
|
+
`The minute parameter needs to be between 0 and 59. Found: ${value}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Translates a public `INotificationTriggerInput` into the shape native expects. */
|
|
101
|
+
export function parseTrigger(
|
|
102
|
+
trigger: INotificationTriggerInput,
|
|
103
|
+
): INativeNotificationTriggerInput {
|
|
104
|
+
if (trigger === null) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (trigger === undefined) {
|
|
108
|
+
throw new TypeError(
|
|
109
|
+
'Encountered an `undefined` notification trigger. Pass an explicit `null` to trigger immediately.',
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (!hasValidTriggerObject(trigger)) {
|
|
113
|
+
throw new TypeError(
|
|
114
|
+
'The `trigger` object needs a `type` or `channelId` entry.',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if ('type' in trigger) {
|
|
119
|
+
if (trigger.type === SchedulableTriggerInputTypes.DATE) {
|
|
120
|
+
const timestamp =
|
|
121
|
+
trigger.date instanceof Date ? trigger.date.getTime() : trigger.date;
|
|
122
|
+
const result: INativeDateTriggerInput = { type: 'date', timestamp };
|
|
123
|
+
if (trigger.channelId) result.channelId = trigger.channelId;
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (trigger.type === SchedulableTriggerInputTypes.CALENDAR) {
|
|
128
|
+
const { repeats, ...rest } = trigger;
|
|
129
|
+
return {
|
|
130
|
+
...rest,
|
|
131
|
+
repeats: Boolean(repeats),
|
|
132
|
+
type: 'calendar',
|
|
133
|
+
} satisfies INativeCalendarTriggerInput;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (trigger.type === SchedulableTriggerInputTypes.DAILY) {
|
|
137
|
+
validateDateComponents(trigger, ['hour', 'minute']);
|
|
138
|
+
const result: INativeDailyTriggerInput = {
|
|
139
|
+
type: 'daily',
|
|
140
|
+
hour: trigger.hour,
|
|
141
|
+
minute: trigger.minute,
|
|
142
|
+
};
|
|
143
|
+
if (trigger.channelId) result.channelId = trigger.channelId;
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (trigger.type === SchedulableTriggerInputTypes.WEEKLY) {
|
|
148
|
+
validateDateComponents(trigger, ['weekday', 'hour', 'minute']);
|
|
149
|
+
const result: INativeWeeklyTriggerInput = {
|
|
150
|
+
type: 'weekly',
|
|
151
|
+
weekday: trigger.weekday,
|
|
152
|
+
hour: trigger.hour,
|
|
153
|
+
minute: trigger.minute,
|
|
154
|
+
};
|
|
155
|
+
if (trigger.channelId) result.channelId = trigger.channelId;
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (trigger.type === SchedulableTriggerInputTypes.MONTHLY) {
|
|
160
|
+
validateDateComponents(trigger, ['day', 'hour', 'minute']);
|
|
161
|
+
const result: INativeMonthlyTriggerInput = {
|
|
162
|
+
type: 'monthly',
|
|
163
|
+
day: trigger.day,
|
|
164
|
+
hour: trigger.hour,
|
|
165
|
+
minute: trigger.minute,
|
|
166
|
+
};
|
|
167
|
+
if (trigger.channelId) result.channelId = trigger.channelId;
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (trigger.type === SchedulableTriggerInputTypes.YEARLY) {
|
|
172
|
+
validateDateComponents(trigger, ['month', 'day', 'hour', 'minute']);
|
|
173
|
+
const result: INativeYearlyTriggerInput = {
|
|
174
|
+
type: 'yearly',
|
|
175
|
+
month: trigger.month,
|
|
176
|
+
day: trigger.day,
|
|
177
|
+
hour: trigger.hour,
|
|
178
|
+
minute: trigger.minute,
|
|
179
|
+
};
|
|
180
|
+
if (trigger.channelId) result.channelId = trigger.channelId;
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (trigger.type === SchedulableTriggerInputTypes.TIME_INTERVAL) {
|
|
185
|
+
const result: INativeTimeIntervalTriggerInput = {
|
|
186
|
+
type: 'timeInterval',
|
|
187
|
+
seconds: trigger.seconds,
|
|
188
|
+
repeats: trigger.repeats ?? false,
|
|
189
|
+
};
|
|
190
|
+
if (trigger.channelId) result.channelId = trigger.channelId;
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (Platform.OS === 'android' && 'channelId' in trigger) {
|
|
196
|
+
return { type: 'channel', channelId: trigger.channelId };
|
|
197
|
+
}
|
|
198
|
+
// No notion of channels off Android — deliver immediately.
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Fetches every notification currently scheduled for future delivery. */
|
|
203
|
+
export async function getAllScheduledNotificationsAsync(): Promise<
|
|
204
|
+
INotificationRequest[]
|
|
205
|
+
> {
|
|
206
|
+
if (!notificationScheduler.getAllScheduledNotificationsAsync) {
|
|
207
|
+
throw new UnavailabilityError(
|
|
208
|
+
'Notifications',
|
|
209
|
+
'getAllScheduledNotificationsAsync',
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const requests =
|
|
213
|
+
await notificationScheduler.getAllScheduledNotificationsAsync();
|
|
214
|
+
return requests.map(mapNotificationRequest);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Schedules a notification. Returns the identifier used to cancel it later. */
|
|
218
|
+
export async function scheduleNotificationAsync(
|
|
219
|
+
request: INotificationRequestInput,
|
|
220
|
+
): Promise<string> {
|
|
221
|
+
if (!notificationScheduler.scheduleNotificationAsync) {
|
|
222
|
+
throw new UnavailabilityError('Notifications', 'scheduleNotificationAsync');
|
|
223
|
+
}
|
|
224
|
+
const identifier = request.identifier ?? uuid();
|
|
225
|
+
dlog(() => `[notifications] scheduleNotificationAsync ${identifier}`);
|
|
226
|
+
return notificationScheduler.scheduleNotificationAsync(
|
|
227
|
+
identifier,
|
|
228
|
+
request.content,
|
|
229
|
+
parseTrigger(request.trigger),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Cancels one scheduled notification by its identifier. */
|
|
234
|
+
export async function cancelScheduledNotificationAsync(
|
|
235
|
+
identifier: string,
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
if (!notificationScheduler.cancelScheduledNotificationAsync) {
|
|
238
|
+
throw new UnavailabilityError(
|
|
239
|
+
'Notifications',
|
|
240
|
+
'cancelScheduledNotificationAsync',
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return notificationScheduler.cancelScheduledNotificationAsync(identifier);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Cancels every scheduled notification. */
|
|
247
|
+
export async function cancelAllScheduledNotificationsAsync(): Promise<void> {
|
|
248
|
+
if (!notificationScheduler.cancelAllScheduledNotificationsAsync) {
|
|
249
|
+
throw new UnavailabilityError(
|
|
250
|
+
'Notifications',
|
|
251
|
+
'cancelAllScheduledNotificationsAsync',
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return notificationScheduler.cancelAllScheduledNotificationsAsync();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** The next Unix timestamp (ms) a schedulable trigger would fire at, or `null` if never. */
|
|
258
|
+
export async function getNextTriggerDateAsync(
|
|
259
|
+
trigger: ISchedulableNotificationTriggerInput,
|
|
260
|
+
): Promise<number | null> {
|
|
261
|
+
if (!notificationScheduler.getNextTriggerDateAsync) {
|
|
262
|
+
throw new UnavailabilityError('Notifications', 'getNextTriggerDateAsync');
|
|
263
|
+
}
|
|
264
|
+
return notificationScheduler.getNextTriggerDateAsync(parseTrigger(trigger));
|
|
265
|
+
}
|