@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A. Prokopenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# @symbiote-native/notifications
|
|
2
|
+
|
|
3
|
+
A wrapper package for [SymbioteNative](../../README.md) that makes
|
|
4
|
+
[`expo-notifications`](https://github.com/expo/expo/tree/main/packages/expo-notifications) —
|
|
5
|
+
permissions, device/Expo push tokens, scheduling, presentation, badges, Android channels, iOS/
|
|
6
|
+
Android categories, and the background notification task hook — usable from **every** adapter:
|
|
7
|
+
React, Vue, Svelte, Solid, and Angular. Built the same way as
|
|
8
|
+
[`@symbiote-native/local-auth`](../local-auth): an `expo-modules-core`-based wrapper (see the
|
|
9
|
+
`symbiote-expo-native-module` project skill for the full mechanism — why `expo-modules-core` is
|
|
10
|
+
depended on directly and never the `expo` meta-package, why the upstream JS is hand-ported into
|
|
11
|
+
`core/` rather than imported, and how autolinking picks up the native module).
|
|
12
|
+
|
|
13
|
+
Every export is a plain async function or a module-level listener registration — no hook/
|
|
14
|
+
composable/service to wrap, so the React, Vue, Svelte, Solid, and Angular entry points are plain
|
|
15
|
+
re-exports of the same `core`.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
**New app:**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx @symbiote-native/cli new my-app --notifications
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Existing SymbioteNative app:**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx @symbiote-native/cli add --notifications
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Either way: installs `@symbiote-native/notifications` and wires the native autolinking
|
|
32
|
+
automatically — see [`@symbiote-native/cli`](../cli). Read [past the wiring
|
|
33
|
+
table](#required-one-time-steps-this-linker-does-not-cover--read-before-shipping-push) before
|
|
34
|
+
shipping push, though — the CLI can't automate app-specific native config like a Firebase
|
|
35
|
+
project.
|
|
36
|
+
|
|
37
|
+
<details>
|
|
38
|
+
<summary>Manual install (no CLI — installing and wiring native autolinking by hand)</summary>
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install @symbiote-native/notifications
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`expo-notifications` and `expo-modules-core` come along as regular, pinned dependencies — never
|
|
45
|
+
install either yourself, and never add the `expo` meta-package to your project.
|
|
46
|
+
|
|
47
|
+
### Required one-time step: native autolinking wiring
|
|
48
|
+
|
|
49
|
+
Same one-time step as every other `expo-modules-core` package this project ships — see
|
|
50
|
+
[`@symbiote-native/local-auth`'s README](../local-auth/README.md#required-one-time-step-native-autolinking-wiring)
|
|
51
|
+
and the `symbiote-expo-native-module` project skill. The per-package half of that table (the
|
|
52
|
+
Gradle dependency and 13 native-module map entries) is generated by
|
|
53
|
+
[`@symbiote-native/expo-modules-link`](../expo-modules-link) from this package's `native-link.json`
|
|
54
|
+
on every install.
|
|
55
|
+
|
|
56
|
+
</details>
|
|
57
|
+
|
|
58
|
+
### Required one-time steps this linker does NOT cover — read before shipping push
|
|
59
|
+
|
|
60
|
+
Unlike every package this project has wrapped before it, `expo-notifications` needs real,
|
|
61
|
+
app-specific native configuration that a thin JS wrapper cannot supply on its own. Read upstream's
|
|
62
|
+
own config plugin (`plugin/src/withNotifications{Android,IOS}.ts`) before assuming any of this is
|
|
63
|
+
optional — none of it is generated by `@symbiote-native/expo-modules-link`, because none of it fits
|
|
64
|
+
that linker's "fixed value, same for every app" contract (§9 of the `symbiote-expo-native-module`
|
|
65
|
+
skill):
|
|
66
|
+
|
|
67
|
+
| Platform | What you must add yourself | Why |
|
|
68
|
+
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| Android | A real Firebase project + `google-services.json`, plus the Google Services Gradle plugin | `android/build.gradle` inside `expo-notifications` depends directly on `com.google.firebase:firebase-messaging` — push delivery is FCM, not a generic APNs-style relay, and this is a real exception to "expo-modules-core only" (see the `symbiote-expo-native-module` skill §10d) |
|
|
70
|
+
| Android | A 96×96 all-white PNG copied into `res/drawable-*dpi/notification_icon.png`, plus two `<meta-data>` entries on `<application>`: `com.google.firebase.messaging.default_notification_icon` and `expo.modules.notifications.default_notification_icon`, both `@drawable/notification_icon` | The status-bar icon Android draws for a notification your app did not explicitly build with a custom small icon |
|
|
71
|
+
| Android | Optionally, `android:color` via `@color/notification_icon_color` and the two matching `default_notification_color` meta-data entries | Tint applied to the small icon above |
|
|
72
|
+
| Android | Optionally, `com.google.firebase.messaging.default_notification_channel_id` meta-data | Which channel an FCM-delivered notification lands in when the payload does not name one |
|
|
73
|
+
| Android | Copy any custom sound file into `res/raw/` | `INotificationContentInput.sound` accepts a custom filename on iOS; Android channels carry their own `sound` field instead — see `setNotificationChannelAsync` |
|
|
74
|
+
| iOS | The **Push Notifications** capability + `aps-environment` entitlement (`development` or `production`) in Xcode | `getDevicePushTokenAsync`/`getExpoPushTokenAsync` need APNs registration, which needs this entitlement present — omitting it is a silent registration failure, not a thrown error |
|
|
75
|
+
| iOS | Add any custom sound file as a bundle resource in Xcode | Same as Android's `res/raw/` step, for `INotificationContentInput.sound` |
|
|
76
|
+
| iOS | Optionally, `UIBackgroundModes` including `remote-notification` in `Info.plist` | Lets a background/silent push wake the app to run a task registered via `registerTaskAsync` |
|
|
77
|
+
| Both | [`@symbiote-native/task-manager`](../task-manager) installed, with a task `defineTask`'d at module scope before `registerTaskAsync` runs | `registerTaskAsync`/`unregisterTaskAsync` here are thin wrappers over expo-notifications' own `BackgroundNotificationTasksModule`, which upstream itself only works when `expo-task-manager` (this project: `@symbiote-native/task-manager`) is linked — see the `symbiote-expo-native-module` skill §10d |
|
|
78
|
+
|
|
79
|
+
None of the above is required just to schedule and present **local** notifications — permissions,
|
|
80
|
+
scheduling, presentation, badges, channels, and categories all work with zero app-level native
|
|
81
|
+
config beyond the linker step. Firebase, the icon/color meta-data, and the entitlement are only
|
|
82
|
+
needed once you send a **remote/push** notification.
|
|
83
|
+
|
|
84
|
+
## Shape
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
src/core/ permissions.ts, tokens.ts, presenter.ts, badge.ts, scheduler.ts,
|
|
88
|
+
categories.ts, channels.ts, handler.ts, emitter.ts, background-task.ts,
|
|
89
|
+
native-modules.ts (13 requireNativeModule() resolutions), utils.ts,
|
|
90
|
+
types.ts (I-prefixed, ported from upstream's Notifications.types.ts /
|
|
91
|
+
Tokens.types.ts / NotificationChannel(Group)Manager.types.ts /
|
|
92
|
+
NotificationScheduler.types.ts)
|
|
93
|
+
src/angular/index.ts export * from '../core'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto `src/core/`.
|
|
97
|
+
`./angular` stays a physical file/subpath since Angular ships through a separate `ngc`/AOT build
|
|
98
|
+
(`build-ngc/`).
|
|
99
|
+
|
|
100
|
+
## Use it
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import * as Notifications from '@symbiote-native/notifications';
|
|
104
|
+
|
|
105
|
+
Notifications.setNotificationHandler({
|
|
106
|
+
handleNotification: async () => ({
|
|
107
|
+
shouldShowBanner: true,
|
|
108
|
+
shouldShowList: true,
|
|
109
|
+
shouldPlaySound: false,
|
|
110
|
+
shouldSetBadge: false,
|
|
111
|
+
}),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const { granted } = await Notifications.requestPermissionsAsync();
|
|
115
|
+
if (granted) {
|
|
116
|
+
const identifier = await Notifications.scheduleNotificationAsync({
|
|
117
|
+
content: { title: "Time's up!", body: 'Change sides!' },
|
|
118
|
+
trigger: {
|
|
119
|
+
type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL,
|
|
120
|
+
seconds: 60,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const subscription = Notifications.addNotificationReceivedListener(
|
|
125
|
+
notification => {
|
|
126
|
+
console.log(notification.request.content.title);
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
// later: subscription.remove();
|
|
130
|
+
|
|
131
|
+
await Notifications.cancelScheduledNotificationAsync(identifier);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Identical import surface on every adapter — `@symbiote-native/notifications/react`, `/vue`,
|
|
136
|
+
`/svelte`, `/solid`, `/angular` all re-export the same functions.
|
|
137
|
+
|
|
138
|
+
## API
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// permissions
|
|
142
|
+
getPermissionsAsync(): Promise<INotificationPermissionsStatus>
|
|
143
|
+
requestPermissionsAsync(permissions?: INotificationPermissionsRequest): Promise<INotificationPermissionsStatus>
|
|
144
|
+
|
|
145
|
+
// tokens
|
|
146
|
+
addPushTokenListener(listener: IPushTokenListener): EventSubscription
|
|
147
|
+
getDevicePushTokenAsync(): Promise<IDevicePushToken>
|
|
148
|
+
getExpoPushTokenAsync(options?: IExpoPushTokenOptions): Promise<IExpoPushToken>
|
|
149
|
+
setAutoServerRegistrationEnabledAsync(enabled: boolean): Promise<void>
|
|
150
|
+
unregisterForNotificationsAsync(): Promise<void>
|
|
151
|
+
subscribeToTopicAsync(topic: string): Promise<null> // @platform android
|
|
152
|
+
unsubscribeFromTopicAsync(topic: string): Promise<null> // @platform android
|
|
153
|
+
|
|
154
|
+
// presentation
|
|
155
|
+
getPresentedNotificationsAsync(): Promise<INotification[]>
|
|
156
|
+
dismissNotificationAsync(notificationIdentifier: string): Promise<void>
|
|
157
|
+
dismissAllNotificationsAsync(): Promise<void>
|
|
158
|
+
|
|
159
|
+
// badge
|
|
160
|
+
getBadgeCountAsync(): Promise<number>
|
|
161
|
+
setBadgeCountAsync(badgeCount: number): Promise<boolean>
|
|
162
|
+
|
|
163
|
+
// scheduling
|
|
164
|
+
scheduleNotificationAsync(request: INotificationRequestInput): Promise<string>
|
|
165
|
+
getAllScheduledNotificationsAsync(): Promise<INotificationRequest[]>
|
|
166
|
+
cancelScheduledNotificationAsync(identifier: string): Promise<void>
|
|
167
|
+
cancelAllScheduledNotificationsAsync(): Promise<void>
|
|
168
|
+
getNextTriggerDateAsync(trigger: ISchedulableNotificationTriggerInput): Promise<number | null>
|
|
169
|
+
|
|
170
|
+
// categories
|
|
171
|
+
getNotificationCategoriesAsync(): Promise<INotificationCategory[]>
|
|
172
|
+
setNotificationCategoryAsync(identifier: string, actions: INotificationAction[], options?: INotificationCategoryOptions): Promise<INotificationCategory>
|
|
173
|
+
deleteNotificationCategoryAsync(identifier: string): Promise<boolean>
|
|
174
|
+
|
|
175
|
+
// channels — @platform android (no-op returning [] / null / void off Android)
|
|
176
|
+
getNotificationChannelsAsync(): Promise<INotificationChannel[]>
|
|
177
|
+
getNotificationChannelAsync(channelId: string): Promise<INotificationChannel | null>
|
|
178
|
+
setNotificationChannelAsync(channelId: string, channel: INotificationChannelInput): Promise<INotificationChannel | null>
|
|
179
|
+
deleteNotificationChannelAsync(channelId: string): Promise<void>
|
|
180
|
+
getNotificationChannelGroupsAsync(): Promise<INotificationChannelGroup[]>
|
|
181
|
+
getNotificationChannelGroupAsync(groupId: string): Promise<INotificationChannelGroup | null>
|
|
182
|
+
setNotificationChannelGroupAsync(groupId: string, group: INotificationChannelGroupInput): Promise<INotificationChannelGroup | null>
|
|
183
|
+
deleteNotificationChannelGroupAsync(groupId: string): Promise<void>
|
|
184
|
+
|
|
185
|
+
// handler / emitter
|
|
186
|
+
setNotificationHandler(handler: INotificationHandler | null): void
|
|
187
|
+
addNotificationReceivedListener(listener: (event: INotification) => void): EventSubscription
|
|
188
|
+
addNotificationsDroppedListener(listener: () => void): EventSubscription // @platform android
|
|
189
|
+
addNotificationResponseReceivedListener(listener: (event: INotificationResponse) => void): EventSubscription
|
|
190
|
+
getLastNotificationResponse(): INotificationResponse | null
|
|
191
|
+
clearLastNotificationResponse(): void
|
|
192
|
+
addNotificationResponseClearedListener(listener: () => void): EventSubscription
|
|
193
|
+
DEFAULT_ACTION_IDENTIFIER: string
|
|
194
|
+
|
|
195
|
+
// background task (needs @symbiote-native/task-manager, see "Required one-time steps" above)
|
|
196
|
+
registerTaskAsync(taskName: string): Promise<null>
|
|
197
|
+
unregisterTaskAsync(taskName: string): Promise<null>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Plus the enums `IosAlertStyle`, `IosAllowsPreviews`, `IosAuthorizationStatus`,
|
|
201
|
+
`AndroidNotificationVisibility`, `AndroidAudioContentType`, `AndroidImportance`,
|
|
202
|
+
`AndroidAudioUsage`, `AndroidNotificationPriority`, `SchedulableTriggerInputTypes`,
|
|
203
|
+
`BackgroundNotificationTaskResult`, `PermissionStatus` (re-exported from `expo-modules-core`, same
|
|
204
|
+
precedent `@symbiote-native/sensors` uses), and the full `I`-prefixed type surface for content,
|
|
205
|
+
triggers, and native-facing payloads — see `src/core/types.ts`.
|
|
206
|
+
|
|
207
|
+
## What this port deliberately does not carry over from upstream
|
|
208
|
+
|
|
209
|
+
- **No automatic push-token server-resync daemon.** Upstream's `DevicePushTokenAutoRegistration.fx.ts`
|
|
210
|
+
wires a permanent, module-load background listener that re-POSTs a rolled device token to Expo's
|
|
211
|
+
push backend with exponential retry/backoff (via the `abort-controller` package), whenever
|
|
212
|
+
registration was left enabled. This port ships `setAutoServerRegistrationEnabledAsync` (used
|
|
213
|
+
internally by `getExpoPushTokenAsync`) but not the daemon itself — call `getExpoPushTokenAsync()`
|
|
214
|
+
again from your own `addPushTokenListener` callback for the same effect, without an unconditional
|
|
215
|
+
background retry loop and its extra dependency.
|
|
216
|
+
- **No `expo-constants`/`expo-application` defaults.** Upstream's `getExpoPushTokenAsync` fills in
|
|
217
|
+
`projectId` from `Constants.expoConfig.extra.eas.projectId` and `applicationId` from
|
|
218
|
+
`Application.applicationId` when omitted. Neither package is part of this port (per the
|
|
219
|
+
`symbiote-expo-native-module` skill, this project depends on `expo-modules-core` only) — pass
|
|
220
|
+
both explicitly. [`@symbiote-native/application`](../application)'s own `applicationId` export
|
|
221
|
+
covers the second one.
|
|
222
|
+
- **No `useLastNotificationResponse` React hook.** This package's whole surface is
|
|
223
|
+
adapter-agnostic plain functions by design — read the last response with
|
|
224
|
+
`getLastNotificationResponse()` and subscribe to changes with
|
|
225
|
+
`addNotificationResponseClearedListener`/`addNotificationResponseReceivedListener` directly in
|
|
226
|
+
whichever lifecycle your framework prefers.
|
|
227
|
+
- **The deprecated `*Async` aliases for `getLastNotificationResponse`/`clearLastNotificationResponse`**
|
|
228
|
+
are not ported — call the non-`Async` forms upstream itself now recommends.
|
|
229
|
+
- **`SetBadgeCountOptions.web`** (a `badgin` options bag) is web-only; this project targets
|
|
230
|
+
iOS/Android and `setBadgeCountAsync` takes no options here.
|
|
231
|
+
|
|
232
|
+
## Notes
|
|
233
|
+
|
|
234
|
+
- **Android channel/channel-group functions are Android-only, like upstream, but collapsed into
|
|
235
|
+
one function each.** Upstream splits every one of these into a `.ts` (iOS no-op) and a
|
|
236
|
+
`.android.ts` (real) file picked by Metro's platform-extension resolution; this port has no
|
|
237
|
+
separate build step per platform to exploit that split for, so each function branches on
|
|
238
|
+
`Platform.OS === 'android'` at runtime instead. Off Android they resolve to the same documented
|
|
239
|
+
no-op values (`[]`/`null`/void) without touching native.
|
|
240
|
+
- **13 native modules autolink from one `expo-notifications` install** —
|
|
241
|
+
`ExpoNotificationPermissionsModule`, `ExpoPushTokenManager`, `ExpoTopicSubscriptionModule`,
|
|
242
|
+
`NotificationsServerRegistrationModule`, `ExpoNotificationPresenter`, `ExpoBadgeModule`,
|
|
243
|
+
`ExpoNotificationScheduler`, `ExpoNotificationCategoriesModule`,
|
|
244
|
+
`ExpoNotificationChannelManager`, `ExpoNotificationChannelGroupManager`,
|
|
245
|
+
`ExpoNotificationsHandlerModule`, `ExpoNotificationsEmitter`,
|
|
246
|
+
`ExpoBackgroundNotificationTasksModule` — see `native-link.json`.
|
|
247
|
+
- **`setNotificationHandler`'s callback must respond within 3 seconds**, matching upstream — a
|
|
248
|
+
slow `handleNotification` implementation causes native to discard the notification and call
|
|
249
|
+
`handleError` with a `NotificationTimeoutError` instead.
|
|
250
|
+
|
|
251
|
+
## Test it
|
|
252
|
+
|
|
253
|
+
No Fabric/Descriptor angle at all — every function here is a pure async-function surface plus
|
|
254
|
+
event-emitter subscriptions, never a view or per-instance state. Tests inject a fake native-module
|
|
255
|
+
object in place of the real `requireNativeModule` resolution (`vi.mock('./native-modules', …)`)
|
|
256
|
+
and, for the emitter/handler modules, a fake `LegacyEventEmitter` whose listener registry is
|
|
257
|
+
shared across instances so a test can fire an event the same way native would — no `installFabric()`,
|
|
258
|
+
no ViewConfig. See `src/core/*.test.ts`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../core/index.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { BackgroundNotificationTaskResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Starts delivering notification-received/response events (and, when the app is terminated,
|
|
4
|
+
* headless background notifications) into a task already defined with
|
|
5
|
+
* `TaskManager.defineTask(taskName, …)`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerTaskAsync(taskName: string): Promise<null>;
|
|
8
|
+
/** Stops delivering into a task registered with `registerTaskAsync`. */
|
|
9
|
+
export declare function unregisterTaskAsync(taskName: string): Promise<null>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Ported from expo-notifications @ sdk-57's registerTaskAsync.ts / unregisterTaskAsync.ts.
|
|
2
|
+
//
|
|
3
|
+
// Runs on `@symbiote-native/task-manager` under the hood, exactly like upstream runs on
|
|
4
|
+
// `expo-task-manager` — `TaskManager.defineTask(taskName, executor)` must be called first, at
|
|
5
|
+
// the top of the JS bundle (outside any component), then this function tells native to start
|
|
6
|
+
// delivering into that task. See this package's README for the full wiring, including why the
|
|
7
|
+
// task also needs `@symbiote-native/task-manager` installed.
|
|
8
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
9
|
+
import { backgroundNotificationTasksModule } from './native-modules.js';
|
|
10
|
+
export { BackgroundNotificationTaskResult } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Starts delivering notification-received/response events (and, when the app is terminated,
|
|
13
|
+
* headless background notifications) into a task already defined with
|
|
14
|
+
* `TaskManager.defineTask(taskName, …)`.
|
|
15
|
+
*/
|
|
16
|
+
export async function registerTaskAsync(taskName) {
|
|
17
|
+
if (!backgroundNotificationTasksModule.registerTaskAsync) {
|
|
18
|
+
throw new UnavailabilityError('Notifications', 'registerTaskAsync');
|
|
19
|
+
}
|
|
20
|
+
return backgroundNotificationTasksModule.registerTaskAsync(taskName);
|
|
21
|
+
}
|
|
22
|
+
/** Stops delivering into a task registered with `registerTaskAsync`. */
|
|
23
|
+
export async function unregisterTaskAsync(taskName) {
|
|
24
|
+
if (!backgroundNotificationTasksModule.unregisterTaskAsync) {
|
|
25
|
+
throw new UnavailabilityError('Notifications', 'unregisterTaskAsync');
|
|
26
|
+
}
|
|
27
|
+
return backgroundNotificationTasksModule.unregisterTaskAsync(taskName);
|
|
28
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Current app-icon badge count. `0` if unset — not every Android launcher supports badges. */
|
|
2
|
+
export declare function getBadgeCountAsync(): Promise<number>;
|
|
3
|
+
/**
|
|
4
|
+
* Sets the app-icon badge. `0` clears it. On iOS this needs the `allowBadge` permission
|
|
5
|
+
* (`requestPermissionsAsync`) or it resolves to `false`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function setBadgeCountAsync(badgeCount: number): Promise<boolean>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Ported from expo-notifications @ sdk-57's getBadgeCountAsync.ts / setBadgeCountAsync.ts.
|
|
2
|
+
//
|
|
3
|
+
// ponytail: upstream's `SetBadgeCountOptions.web` (a `badgin` options bag) is web-only and this
|
|
4
|
+
// project targets iOS/Android — not ported.
|
|
5
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
6
|
+
import { badgeModule } from './native-modules.js';
|
|
7
|
+
/** Current app-icon badge count. `0` if unset — not every Android launcher supports badges. */
|
|
8
|
+
export async function getBadgeCountAsync() {
|
|
9
|
+
if (!badgeModule.getBadgeCountAsync) {
|
|
10
|
+
throw new UnavailabilityError('Notifications', 'getBadgeCountAsync');
|
|
11
|
+
}
|
|
12
|
+
return badgeModule.getBadgeCountAsync();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sets the app-icon badge. `0` clears it. On iOS this needs the `allowBadge` permission
|
|
16
|
+
* (`requestPermissionsAsync`) or it resolves to `false`.
|
|
17
|
+
*/
|
|
18
|
+
export async function setBadgeCountAsync(badgeCount) {
|
|
19
|
+
if (!badgeModule.setBadgeCountAsync) {
|
|
20
|
+
throw new UnavailabilityError('Notifications', 'setBadgeCountAsync');
|
|
21
|
+
}
|
|
22
|
+
return badgeModule.setBadgeCountAsync(badgeCount);
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { INotificationAction, INotificationCategory, INotificationCategoryOptions } from './types';
|
|
2
|
+
/** Every registered notification category. */
|
|
3
|
+
export declare function getNotificationCategoriesAsync(): Promise<INotificationCategory[]>;
|
|
4
|
+
/**
|
|
5
|
+
* Registers a category of action buttons under `identifier`, referenced later via
|
|
6
|
+
* `INotificationContentInput.categoryIdentifier`. Avoid `:`/`-` in `identifier`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function setNotificationCategoryAsync(identifier: string, actions: INotificationAction[], options?: INotificationCategoryOptions): Promise<INotificationCategory>;
|
|
9
|
+
/** Deletes a category. Resolves `false` if it did not exist. */
|
|
10
|
+
export declare function deleteNotificationCategoryAsync(identifier: string): Promise<boolean>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Ported from expo-notifications @ sdk-57's getNotificationCategoriesAsync.ts,
|
|
2
|
+
// setNotificationCategoryAsync.ts, deleteNotificationCategoryAsync.ts.
|
|
3
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
4
|
+
import { notificationCategoriesModule } from './native-modules.js';
|
|
5
|
+
/** Every registered notification category. */
|
|
6
|
+
export async function getNotificationCategoriesAsync() {
|
|
7
|
+
if (!notificationCategoriesModule.getNotificationCategoriesAsync) {
|
|
8
|
+
throw new UnavailabilityError('Notifications', 'getNotificationCategoriesAsync');
|
|
9
|
+
}
|
|
10
|
+
return notificationCategoriesModule.getNotificationCategoriesAsync();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Registers a category of action buttons under `identifier`, referenced later via
|
|
14
|
+
* `INotificationContentInput.categoryIdentifier`. Avoid `:`/`-` in `identifier`.
|
|
15
|
+
*/
|
|
16
|
+
export async function setNotificationCategoryAsync(identifier, actions, options) {
|
|
17
|
+
if (!notificationCategoriesModule.setNotificationCategoryAsync) {
|
|
18
|
+
throw new UnavailabilityError('Notifications', 'setNotificationCategoryAsync');
|
|
19
|
+
}
|
|
20
|
+
return notificationCategoriesModule.setNotificationCategoryAsync(identifier, actions, options);
|
|
21
|
+
}
|
|
22
|
+
/** Deletes a category. Resolves `false` if it did not exist. */
|
|
23
|
+
export async function deleteNotificationCategoryAsync(identifier) {
|
|
24
|
+
if (!notificationCategoriesModule.deleteNotificationCategoryAsync) {
|
|
25
|
+
throw new UnavailabilityError('Notifications', 'deleteNotificationCategoryAsync');
|
|
26
|
+
}
|
|
27
|
+
return notificationCategoriesModule.deleteNotificationCategoryAsync(identifier);
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type INotificationChannel, type INotificationChannelGroup, type INotificationChannelGroupInput, type INotificationChannelInput } from './types';
|
|
2
|
+
/** @platform android — empty array on every other platform. */
|
|
3
|
+
export declare function getNotificationChannelsAsync(): Promise<INotificationChannel[]>;
|
|
4
|
+
/** @platform android — `null` on every other platform. */
|
|
5
|
+
export declare function getNotificationChannelAsync(channelId: string): Promise<INotificationChannel | null>;
|
|
6
|
+
/**
|
|
7
|
+
* Creates or updates a channel. Only the name and description may change once a channel
|
|
8
|
+
* exists — an Android OS limitation. @platform android — `null` on every other platform.
|
|
9
|
+
*/
|
|
10
|
+
export declare function setNotificationChannelAsync(channelId: string, channel: INotificationChannelInput): Promise<INotificationChannel | null>;
|
|
11
|
+
/** @platform android — no-op on every other platform. */
|
|
12
|
+
export declare function deleteNotificationChannelAsync(channelId: string): Promise<void>;
|
|
13
|
+
/** @platform android — empty array on every other platform. */
|
|
14
|
+
export declare function getNotificationChannelGroupsAsync(): Promise<INotificationChannelGroup[]>;
|
|
15
|
+
/** @platform android — `null` on every other platform. */
|
|
16
|
+
export declare function getNotificationChannelGroupAsync(groupId: string): Promise<INotificationChannelGroup | null>;
|
|
17
|
+
/** @platform android — `null` on every other platform. */
|
|
18
|
+
export declare function setNotificationChannelGroupAsync(groupId: string, group: INotificationChannelGroupInput): Promise<INotificationChannelGroup | null>;
|
|
19
|
+
/** @platform android — no-op on every other platform. */
|
|
20
|
+
export declare function deleteNotificationChannelGroupAsync(groupId: string): Promise<void>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Ported from expo-notifications @ sdk-57's get/set/deleteNotificationChannel(Group)Async.ts —
|
|
2
|
+
// upstream splits each of these into a `.ts` (iOS no-op) and a `.android.ts` (real) file picked
|
|
3
|
+
// by Metro's platform extension resolution; this port collapses each pair into one function with
|
|
4
|
+
// a runtime `Platform.OS === 'android'` branch, since this package has no separate build step
|
|
5
|
+
// per platform to exploit the file-split for.
|
|
6
|
+
import { Platform, UnavailabilityError } from 'expo-modules-core';
|
|
7
|
+
import { notificationChannelGroupManager, notificationChannelManager, } from './native-modules.js';
|
|
8
|
+
import { AndroidImportance, } from './types.js';
|
|
9
|
+
const ANDROID_ONLY_NOTICE = 'Notification channels are only supported on Android.';
|
|
10
|
+
function isAndroid() {
|
|
11
|
+
return Platform.OS === 'android';
|
|
12
|
+
}
|
|
13
|
+
/** @platform android — empty array on every other platform. */
|
|
14
|
+
export async function getNotificationChannelsAsync() {
|
|
15
|
+
if (!isAndroid()) {
|
|
16
|
+
console.debug(ANDROID_ONLY_NOTICE);
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
if (!notificationChannelManager.getNotificationChannelsAsync) {
|
|
20
|
+
throw new UnavailabilityError('Notifications', 'getNotificationChannelsAsync');
|
|
21
|
+
}
|
|
22
|
+
return ((await notificationChannelManager.getNotificationChannelsAsync()) ?? []);
|
|
23
|
+
}
|
|
24
|
+
/** @platform android — `null` on every other platform. */
|
|
25
|
+
export async function getNotificationChannelAsync(channelId) {
|
|
26
|
+
if (!isAndroid()) {
|
|
27
|
+
console.debug(ANDROID_ONLY_NOTICE);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (!notificationChannelManager.getNotificationChannelAsync) {
|
|
31
|
+
throw new UnavailabilityError('Notifications', 'getNotificationChannelAsync');
|
|
32
|
+
}
|
|
33
|
+
return notificationChannelManager.getNotificationChannelAsync(channelId);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates or updates a channel. Only the name and description may change once a channel
|
|
37
|
+
* exists — an Android OS limitation. @platform android — `null` on every other platform.
|
|
38
|
+
*/
|
|
39
|
+
export async function setNotificationChannelAsync(channelId, channel) {
|
|
40
|
+
if (!isAndroid()) {
|
|
41
|
+
console.debug(ANDROID_ONLY_NOTICE);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
if (!notificationChannelManager.setNotificationChannelAsync) {
|
|
45
|
+
throw new UnavailabilityError('Notifications', 'setNotificationChannelAsync');
|
|
46
|
+
}
|
|
47
|
+
if (channel.importance === AndroidImportance.UNSPECIFIED) {
|
|
48
|
+
console.warn(`[notifications] Channel "${channelId}" importance is UNSPECIFIED, which can error on some Android versions — use AndroidImportance.DEFAULT instead.`);
|
|
49
|
+
}
|
|
50
|
+
return notificationChannelManager.setNotificationChannelAsync(channelId, channel);
|
|
51
|
+
}
|
|
52
|
+
/** @platform android — no-op on every other platform. */
|
|
53
|
+
export async function deleteNotificationChannelAsync(channelId) {
|
|
54
|
+
if (!isAndroid()) {
|
|
55
|
+
console.debug(ANDROID_ONLY_NOTICE);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!notificationChannelManager.deleteNotificationChannelAsync) {
|
|
59
|
+
throw new UnavailabilityError('Notifications', 'deleteNotificationChannelAsync');
|
|
60
|
+
}
|
|
61
|
+
return notificationChannelManager.deleteNotificationChannelAsync(channelId);
|
|
62
|
+
}
|
|
63
|
+
/** @platform android — empty array on every other platform. */
|
|
64
|
+
export async function getNotificationChannelGroupsAsync() {
|
|
65
|
+
if (!isAndroid()) {
|
|
66
|
+
console.debug(ANDROID_ONLY_NOTICE);
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
if (!notificationChannelGroupManager.getNotificationChannelGroupsAsync) {
|
|
70
|
+
throw new UnavailabilityError('Notifications', 'getNotificationChannelGroupsAsync');
|
|
71
|
+
}
|
|
72
|
+
return notificationChannelGroupManager.getNotificationChannelGroupsAsync();
|
|
73
|
+
}
|
|
74
|
+
/** @platform android — `null` on every other platform. */
|
|
75
|
+
export async function getNotificationChannelGroupAsync(groupId) {
|
|
76
|
+
if (!isAndroid()) {
|
|
77
|
+
console.debug(ANDROID_ONLY_NOTICE);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (!notificationChannelGroupManager.getNotificationChannelGroupAsync) {
|
|
81
|
+
throw new UnavailabilityError('Notifications', 'getNotificationChannelGroupAsync');
|
|
82
|
+
}
|
|
83
|
+
return notificationChannelGroupManager.getNotificationChannelGroupAsync(groupId);
|
|
84
|
+
}
|
|
85
|
+
/** @platform android — `null` on every other platform. */
|
|
86
|
+
export async function setNotificationChannelGroupAsync(groupId, group) {
|
|
87
|
+
if (!isAndroid()) {
|
|
88
|
+
console.debug(ANDROID_ONLY_NOTICE);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (!notificationChannelGroupManager.setNotificationChannelGroupAsync) {
|
|
92
|
+
throw new UnavailabilityError('Notifications', 'setNotificationChannelGroupAsync');
|
|
93
|
+
}
|
|
94
|
+
return notificationChannelGroupManager.setNotificationChannelGroupAsync(groupId, group);
|
|
95
|
+
}
|
|
96
|
+
/** @platform android — no-op on every other platform. */
|
|
97
|
+
export async function deleteNotificationChannelGroupAsync(groupId) {
|
|
98
|
+
if (!isAndroid()) {
|
|
99
|
+
console.debug(ANDROID_ONLY_NOTICE);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!notificationChannelGroupManager.deleteNotificationChannelGroupAsync) {
|
|
103
|
+
throw new UnavailabilityError('Notifications', 'deleteNotificationChannelGroupAsync');
|
|
104
|
+
}
|
|
105
|
+
return notificationChannelGroupManager.deleteNotificationChannelGroupAsync(groupId);
|
|
106
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import type { INotification, INotificationResponse } from './types';
|
|
3
|
+
export declare const DEFAULT_ACTION_IDENTIFIER = "expo.modules.notifications.actions.DEFAULT";
|
|
4
|
+
/** Fires whenever a notification is received while the app is running. */
|
|
5
|
+
export declare function addNotificationReceivedListener(listener: (event: INotification) => void): EventSubscription;
|
|
6
|
+
/**
|
|
7
|
+
* Fires whenever the server (Firebase Cloud Messaging) dropped queued notifications.
|
|
8
|
+
* @platform android
|
|
9
|
+
*/
|
|
10
|
+
export declare function addNotificationsDroppedListener(listener: () => void): EventSubscription;
|
|
11
|
+
/** Fires whenever the user interacts with a notification (e.g. taps it). */
|
|
12
|
+
export declare function addNotificationResponseReceivedListener(listener: (event: INotificationResponse) => void): EventSubscription;
|
|
13
|
+
/**
|
|
14
|
+
* The most recently received notification response, or `null` if the app was not opened by
|
|
15
|
+
* one.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getLastNotificationResponse(): INotificationResponse | null;
|
|
18
|
+
/**
|
|
19
|
+
* Clears the last notification response, e.g. once its route has been handled and should not
|
|
20
|
+
* be re-applied on the next read.
|
|
21
|
+
*/
|
|
22
|
+
export declare function clearLastNotificationResponse(): void;
|
|
23
|
+
/** Fires whenever `clearLastNotificationResponse` runs. */
|
|
24
|
+
export declare function addNotificationResponseClearedListener(listener: () => void): EventSubscription;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Ported from expo-notifications @ sdk-57's NotificationsEmitter.ts.
|
|
2
|
+
import { LegacyEventEmitter, UnavailabilityError, } from 'expo-modules-core';
|
|
3
|
+
import { dlog } from '@symbiote-native/engine';
|
|
4
|
+
import { notificationsEmitterModule } from './native-modules.js';
|
|
5
|
+
import { mapNotification, mapNotificationResponse } from './utils.js';
|
|
6
|
+
const emitter = new LegacyEventEmitter(notificationsEmitterModule);
|
|
7
|
+
const RECEIVED_EVENT = 'onDidReceiveNotification';
|
|
8
|
+
const DROPPED_EVENT = 'onNotificationsDeleted';
|
|
9
|
+
const RESPONSE_RECEIVED_EVENT = 'onDidReceiveNotificationResponse';
|
|
10
|
+
const RESPONSE_CLEARED_EVENT = 'onDidClearNotificationResponse';
|
|
11
|
+
export const DEFAULT_ACTION_IDENTIFIER = 'expo.modules.notifications.actions.DEFAULT';
|
|
12
|
+
/** Fires whenever a notification is received while the app is running. */
|
|
13
|
+
export function addNotificationReceivedListener(listener) {
|
|
14
|
+
return emitter.addListener(RECEIVED_EVENT, notification => {
|
|
15
|
+
dlog(() => `[notifications] received ${notification.request.identifier}`);
|
|
16
|
+
listener(mapNotification(notification));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Fires whenever the server (Firebase Cloud Messaging) dropped queued notifications.
|
|
21
|
+
* @platform android
|
|
22
|
+
*/
|
|
23
|
+
export function addNotificationsDroppedListener(listener) {
|
|
24
|
+
return emitter.addListener(DROPPED_EVENT, listener);
|
|
25
|
+
}
|
|
26
|
+
/** Fires whenever the user interacts with a notification (e.g. taps it). */
|
|
27
|
+
export function addNotificationResponseReceivedListener(listener) {
|
|
28
|
+
return emitter.addListener(RESPONSE_RECEIVED_EVENT, response => {
|
|
29
|
+
dlog(() => `[notifications] response ${response.actionIdentifier} for ${response.notification.request.identifier}`);
|
|
30
|
+
listener(mapNotificationResponse(response));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The most recently received notification response, or `null` if the app was not opened by
|
|
35
|
+
* one.
|
|
36
|
+
*/
|
|
37
|
+
export function getLastNotificationResponse() {
|
|
38
|
+
if (!notificationsEmitterModule.getLastNotificationResponse) {
|
|
39
|
+
throw new UnavailabilityError('Notifications', 'getLastNotificationResponse');
|
|
40
|
+
}
|
|
41
|
+
const response = notificationsEmitterModule.getLastNotificationResponse();
|
|
42
|
+
return response ? mapNotificationResponse(response) : response;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Clears the last notification response, e.g. once its route has been handled and should not
|
|
46
|
+
* be re-applied on the next read.
|
|
47
|
+
*/
|
|
48
|
+
export function clearLastNotificationResponse() {
|
|
49
|
+
if (!notificationsEmitterModule.clearLastNotificationResponse) {
|
|
50
|
+
throw new UnavailabilityError('Notifications', 'clearLastNotificationResponse');
|
|
51
|
+
}
|
|
52
|
+
notificationsEmitterModule.clearLastNotificationResponse();
|
|
53
|
+
emitter.emit(RESPONSE_CLEARED_EVENT, []);
|
|
54
|
+
}
|
|
55
|
+
/** Fires whenever `clearLastNotificationResponse` runs. */
|
|
56
|
+
export function addNotificationResponseClearedListener(listener) {
|
|
57
|
+
return emitter.addListener(RESPONSE_CLEARED_EVENT, listener);
|
|
58
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CodedError } from 'expo-modules-core';
|
|
2
|
+
import type { INotification, INotificationHandler } from './types';
|
|
3
|
+
export declare class NotificationTimeoutError extends CodedError {
|
|
4
|
+
info: {
|
|
5
|
+
notification: INotification;
|
|
6
|
+
id: string;
|
|
7
|
+
};
|
|
8
|
+
constructor(notificationId: string, notification: INotification);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Sets the callback deciding whether/how a notification received while the app is running
|
|
12
|
+
* should be shown. Must respond within 3 seconds or the notification is discarded. Passing
|
|
13
|
+
* `null` clears the handler — the default (no handler) is to not show anything.
|
|
14
|
+
*/
|
|
15
|
+
export declare function setNotificationHandler(handler: INotificationHandler | null): void;
|