@symbiote-native/notifications 0.1.2 → 0.1.3
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/README.md +94 -58
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +1 -0
- package/build/angular/services/last-notification-response.service/index.d.ts +8 -0
- package/build/angular/services/last-notification-response.service/index.js +78 -0
- package/build/core/auto-registration.d.ts +5 -0
- package/build/core/auto-registration.js +65 -0
- package/build/core/backoff.d.ts +6 -0
- package/build/core/backoff.js +15 -0
- package/build/core/device-token.d.ts +7 -0
- package/build/core/device-token.js +35 -0
- package/build/core/emitter.d.ts +4 -0
- package/build/core/emitter.js +8 -0
- package/build/core/index.d.ts +3 -1
- package/build/core/index.js +3 -1
- package/build/core/last-notification-response.d.ts +2 -0
- package/build/core/last-notification-response.js +12 -0
- package/build/core/registration-abort.d.ts +2 -0
- package/build/core/registration-abort.js +10 -0
- package/build/core/token-metadata.d.ts +4 -0
- package/build/core/token-metadata.js +34 -0
- package/build/core/tokens.d.ts +4 -22
- package/build/core/tokens.js +41 -99
- package/build/core/update-device-push-token.d.ts +3 -0
- package/build/core/update-device-push-token.js +122 -0
- package/build/react/hooks/use-last-notification-response/index.d.ts +2 -0
- package/build/react/hooks/use-last-notification-response/index.js +22 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.js +2 -0
- package/build/solid/index.d.ts +2 -0
- package/build/solid/index.js +2 -0
- package/build/solid/primitives/create-last-notification-response.d.ts +3 -0
- package/build/solid/primitives/create-last-notification-response.js +20 -0
- package/build/svelte/index.d.ts +2 -0
- package/build/svelte/index.js +2 -0
- package/build/svelte/use-last-notification-response.svelte.d.ts +4 -0
- package/build/svelte/use-last-notification-response.svelte.js +25 -0
- package/build/vue/composables/use-last-notification-response/index.d.ts +3 -0
- package/build/vue/composables/use-last-notification-response/index.js +20 -0
- package/build/vue/index.d.ts +2 -0
- package/build/vue/index.js +2 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +1 -0
- package/build-ngc/angular/index.js.map +1 -1
- package/build-ngc/angular/services/last-notification-response.service/index.d.ts +11 -0
- package/build-ngc/angular/services/last-notification-response.service/index.js +37 -0
- package/build-ngc/angular/services/last-notification-response.service/index.js.map +1 -0
- package/build-ngc/core/auto-registration.d.ts +5 -0
- package/build-ngc/core/auto-registration.js +66 -0
- package/build-ngc/core/auto-registration.js.map +1 -0
- package/build-ngc/core/backoff.d.ts +6 -0
- package/build-ngc/core/backoff.js +16 -0
- package/build-ngc/core/backoff.js.map +1 -0
- package/build-ngc/core/device-token.d.ts +7 -0
- package/build-ngc/core/device-token.js +36 -0
- package/build-ngc/core/device-token.js.map +1 -0
- package/build-ngc/core/emitter.d.ts +4 -0
- package/build-ngc/core/emitter.js +8 -0
- package/build-ngc/core/emitter.js.map +1 -1
- package/build-ngc/core/index.d.ts +3 -1
- package/build-ngc/core/index.js +3 -1
- package/build-ngc/core/index.js.map +1 -1
- package/build-ngc/core/last-notification-response.d.ts +2 -0
- package/build-ngc/core/last-notification-response.js +13 -0
- package/build-ngc/core/last-notification-response.js.map +1 -0
- package/build-ngc/core/registration-abort.d.ts +2 -0
- package/build-ngc/core/registration-abort.js +11 -0
- package/build-ngc/core/registration-abort.js.map +1 -0
- package/build-ngc/core/token-metadata.d.ts +4 -0
- package/build-ngc/core/token-metadata.js +35 -0
- package/build-ngc/core/token-metadata.js.map +1 -0
- package/build-ngc/core/tokens.d.ts +4 -22
- package/build-ngc/core/tokens.js +41 -99
- package/build-ngc/core/tokens.js.map +1 -1
- package/build-ngc/core/update-device-push-token.d.ts +3 -0
- package/build-ngc/core/update-device-push-token.js +123 -0
- package/build-ngc/core/update-device-push-token.js.map +1 -0
- package/package.json +24 -23
- package/src/angular/index.ts +1 -0
- package/src/angular/services/last-notification-response.service/index.ts +54 -0
- package/src/core/auto-registration.ts +105 -0
- package/src/core/backoff.ts +24 -0
- package/src/core/device-token.ts +54 -0
- package/src/core/emitter.ts +10 -0
- package/src/core/index.ts +8 -0
- package/src/core/last-notification-response.ts +19 -0
- package/src/core/registration-abort.ts +12 -0
- package/src/core/token-metadata.ts +42 -0
- package/src/core/tokens.ts +71 -133
- package/src/core/update-device-push-token.ts +173 -0
- package/src/react/hooks/use-last-notification-response/index.ts +35 -0
- package/src/react/index.ts +2 -0
- package/src/solid/index.ts +2 -0
- package/src/solid/primitives/create-last-notification-response.ts +35 -0
- package/src/svelte/index.ts +2 -0
- package/src/svelte/svelte-ambient.d.ts +3 -0
- package/src/svelte/svelte-compile.test-helper.ts +7 -0
- package/src/svelte/use-last-notification-response.svelte.ts +45 -0
- package/src/vue/composables/use-last-notification-response/index.ts +35 -0
- package/src/vue/index.ts +2 -0
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# @symbiote-native/notifications
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
Remind the user at the right moment, reach them with push when the app is closed, and know which
|
|
4
|
+
notification they tapped. One API for every [SymbioteNative](../../README.md) adapter (React, Vue,
|
|
5
|
+
Svelte, Solid and Angular).
|
|
6
|
+
|
|
7
|
+
It wraps [`expo-notifications`](https://github.com/expo/expo/tree/main/packages/expo-notifications):
|
|
8
|
+
permissions, device and Expo push tokens, scheduling, presentation, badges, Android channels, iOS
|
|
9
|
+
and Android categories, and the background notification task hook. Built the same way as
|
|
8
10
|
[`@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
|
|
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).
|
|
11
|
+
`symbiote-expo-native-module` project skill for the full mechanism).
|
|
12
12
|
|
|
13
|
-
Every export is a plain async function or a module-level listener registration
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
Every core export is a plain async function or a module-level listener registration. One
|
|
14
|
+
adapter-specific hook - `useLastNotificationResponse` - is ported to all five adapters (originally
|
|
15
|
+
dropped as React-only; since reversed, see "Push-token resync and defaults" below).
|
|
16
16
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
@@ -29,24 +29,24 @@ npx @symbiote-native/cli add --notifications
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Either way: installs `@symbiote-native/notifications` and wires the native autolinking
|
|
32
|
-
automatically
|
|
32
|
+
automatically - see [`@symbiote-native/cli`](../cli). Read [past the wiring
|
|
33
33
|
table](#required-one-time-steps-this-linker-does-not-cover--read-before-shipping-push) before
|
|
34
|
-
shipping push, though
|
|
34
|
+
shipping push, though - the CLI can't automate app-specific native config like a Firebase
|
|
35
35
|
project.
|
|
36
36
|
|
|
37
37
|
<details>
|
|
38
|
-
<summary>Manual install (no CLI
|
|
38
|
+
<summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
npm install @symbiote-native/notifications
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
`expo-notifications` and `expo-modules-core` come along as regular, pinned dependencies
|
|
44
|
+
`expo-notifications` and `expo-modules-core` come along as regular, pinned dependencies - never
|
|
45
45
|
install either yourself, and never add the `expo` meta-package to your project.
|
|
46
46
|
|
|
47
47
|
### Required one-time step: native autolinking wiring
|
|
48
48
|
|
|
49
|
-
Same one-time step as every other `expo-modules-core` package this project ships
|
|
49
|
+
Same one-time step as every other `expo-modules-core` package this project ships - see
|
|
50
50
|
[`@symbiote-native/local-auth`'s README](../local-auth/README.md#required-one-time-step-native-autolinking-wiring)
|
|
51
51
|
and the `symbiote-expo-native-module` project skill. The per-package half of that table (the
|
|
52
52
|
Gradle dependency and 13 native-module map entries) is generated by
|
|
@@ -55,28 +55,28 @@ on every install.
|
|
|
55
55
|
|
|
56
56
|
</details>
|
|
57
57
|
|
|
58
|
-
### Required one-time steps this linker does NOT cover
|
|
58
|
+
### Required one-time steps this linker does NOT cover - read before shipping push
|
|
59
59
|
|
|
60
60
|
Unlike every package this project has wrapped before it, `expo-notifications` needs real,
|
|
61
61
|
app-specific native configuration that a thin JS wrapper cannot supply on its own. Read upstream's
|
|
62
62
|
own config plugin (`plugin/src/withNotifications{Android,IOS}.ts`) before assuming any of this is
|
|
63
|
-
optional
|
|
63
|
+
optional - none of it is generated by `@symbiote-native/expo-modules-link`, because none of it fits
|
|
64
64
|
that linker's "fixed value, same for every app" contract (§9 of the `symbiote-expo-native-module`
|
|
65
65
|
skill):
|
|
66
66
|
|
|
67
67
|
| Platform | What you must add yourself | Why |
|
|
68
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`
|
|
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
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
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
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
|
|
74
|
-
| iOS | The **Push Notifications** capability + `aps-environment` entitlement (`development` or `production`) in Xcode | `getDevicePushTokenAsync`/`getExpoPushTokenAsync` need APNs registration, which needs this entitlement present
|
|
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
75
|
| iOS | Add any custom sound file as a bundle resource in Xcode | Same as Android's `res/raw/` step, for `INotificationContentInput.sound` |
|
|
76
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
|
|
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
78
|
|
|
79
|
-
None of the above is required just to schedule and present **local** notifications
|
|
79
|
+
None of the above is required just to schedule and present **local** notifications - permissions,
|
|
80
80
|
scheduling, presentation, badges, channels, and categories all work with zero app-level native
|
|
81
81
|
config beyond the linker step. Firebase, the icon/color meta-data, and the entitlement are only
|
|
82
82
|
needed once you send a **remote/push** notification.
|
|
@@ -90,12 +90,19 @@ src/core/ permissions.ts, tokens.ts, presenter.ts, badge.ts, sch
|
|
|
90
90
|
types.ts (I-prefixed, ported from upstream's Notifications.types.ts /
|
|
91
91
|
Tokens.types.ts / NotificationChannel(Group)Manager.types.ts /
|
|
92
92
|
NotificationScheduler.types.ts)
|
|
93
|
-
src/
|
|
93
|
+
src/core/last-notification-response.ts determineNextResponse - the dedup rule every adapter's
|
|
94
|
+
useLastNotificationResponse shares
|
|
95
|
+
src/react/hooks/use-last-notification-response/
|
|
96
|
+
src/vue/composables/use-last-notification-response/
|
|
97
|
+
src/solid/primitives/create-last-notification-response.ts
|
|
98
|
+
src/svelte/use-last-notification-response.svelte.ts
|
|
99
|
+
src/angular/services/last-notification-response.service/ LastNotificationResponseService,
|
|
100
|
+
connect(): Signal<IMaybeNotificationResponse>
|
|
94
101
|
```
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
Every adapter barrel (`src/{react,vue,solid,svelte}/index.ts`, `src/angular/index.ts`) re-exports
|
|
104
|
+
`../core` plus its own `useLastNotificationResponse`/`createLastNotificationResponse`/
|
|
105
|
+
`LastNotificationResponseService`.
|
|
99
106
|
|
|
100
107
|
## Use it
|
|
101
108
|
|
|
@@ -132,7 +139,7 @@ if (granted) {
|
|
|
132
139
|
}
|
|
133
140
|
```
|
|
134
141
|
|
|
135
|
-
Identical import surface on every adapter
|
|
142
|
+
Identical import surface on every adapter - `@symbiote-native/notifications/react`, `/vue`,
|
|
136
143
|
`/svelte`, `/solid`, `/angular` all re-export the same functions.
|
|
137
144
|
|
|
138
145
|
## API
|
|
@@ -172,7 +179,7 @@ getNotificationCategoriesAsync(): Promise<INotificationCategory[]>
|
|
|
172
179
|
setNotificationCategoryAsync(identifier: string, actions: INotificationAction[], options?: INotificationCategoryOptions): Promise<INotificationCategory>
|
|
173
180
|
deleteNotificationCategoryAsync(identifier: string): Promise<boolean>
|
|
174
181
|
|
|
175
|
-
// channels
|
|
182
|
+
// channels - @platform android (no-op returning [] / null / void off Android)
|
|
176
183
|
getNotificationChannelsAsync(): Promise<INotificationChannel[]>
|
|
177
184
|
getNotificationChannelAsync(channelId: string): Promise<INotificationChannel | null>
|
|
178
185
|
setNotificationChannelAsync(channelId: string, channel: INotificationChannelInput): Promise<INotificationChannel | null>
|
|
@@ -197,35 +204,43 @@ registerTaskAsync(taskName: string): Promise<null>
|
|
|
197
204
|
unregisterTaskAsync(taskName: string): Promise<null>
|
|
198
205
|
```
|
|
199
206
|
|
|
207
|
+
Per-adapter: `useLastNotificationResponse()` (React/Vue/Solid/Svelte) /
|
|
208
|
+
`inject(LastNotificationResponseService).connect()` (Angular) - the last tapped/received response,
|
|
209
|
+
deduped by notification identifier, kept in sync with `addNotificationResponseReceivedListener` and
|
|
210
|
+
`addNotificationResponseClearedListener`.
|
|
211
|
+
|
|
200
212
|
Plus the enums `IosAlertStyle`, `IosAllowsPreviews`, `IosAuthorizationStatus`,
|
|
201
213
|
`AndroidNotificationVisibility`, `AndroidAudioContentType`, `AndroidImportance`,
|
|
202
214
|
`AndroidAudioUsage`, `AndroidNotificationPriority`, `SchedulableTriggerInputTypes`,
|
|
203
215
|
`BackgroundNotificationTaskResult`, `PermissionStatus` (re-exported from `expo-modules-core`, same
|
|
204
216
|
precedent `@symbiote-native/sensors` uses), and the full `I`-prefixed type surface for content,
|
|
205
|
-
triggers, and native-facing payloads
|
|
206
|
-
|
|
207
|
-
##
|
|
208
|
-
|
|
209
|
-
- **
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
|
|
218
|
-
`
|
|
219
|
-
`
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
-
|
|
223
|
-
|
|
224
|
-
`
|
|
225
|
-
`
|
|
226
|
-
|
|
227
|
-
- **The deprecated
|
|
228
|
-
|
|
217
|
+
triggers, and native-facing payloads - see `src/core/types.ts`.
|
|
218
|
+
|
|
219
|
+
## Push-token resync and defaults
|
|
220
|
+
|
|
221
|
+
- **Token resync is ported, and explicit.** Upstream's `DevicePushTokenAutoRegistration.fx.ts`
|
|
222
|
+
re-POSTs a rolled device token to Expo's push backend with exponential backoff whenever
|
|
223
|
+
registration was left enabled. Here it is `installPushTokenAutoRegistration()` (`core/auto-registration.ts`,
|
|
224
|
+
with the upstream `backoff`/`updateDevicePushTokenAsync` helpers and their tests): idempotent, and
|
|
225
|
+
installed by `setAutoServerRegistrationEnabledAsync(true)` (so also by `getExpoPushTokenAsync`). Call it
|
|
226
|
+
once at app startup as well, so a token that rolled while the app was closed is re-sent too. It is
|
|
227
|
+
explicit rather than a module-load side effect because Metro's `inlineRequires` skips a barrel
|
|
228
|
+
re-export that nothing names as a value.
|
|
229
|
+
- **`applicationId` and `development` default like upstream**, from
|
|
230
|
+
[`@symbiote-native/application`](../application) (its `applicationId` and the iOS push-notification
|
|
231
|
+
service environment). `projectId` still has to be passed: upstream reads it from
|
|
232
|
+
`expo-constants`, and `@symbiote-native/constants` does not exist yet.
|
|
233
|
+
- **`useLastNotificationResponse` is ported to every adapter** (React, Vue, Solid, Svelte,
|
|
234
|
+
Angular) - each wraps `getLastNotificationResponse()` +
|
|
235
|
+
`addNotificationResponseClearedListener`/`addNotificationResponseReceivedListener` with the
|
|
236
|
+
shared `determineNextResponse` dedup rule from `core/last-notification-response.ts` behind its
|
|
237
|
+
own lifecycle (hook/composable/primitive/rune/service). Angular's `LastNotificationResponseService`
|
|
238
|
+
matches `PreventScreenCaptureService`'s `connect()` shape.
|
|
239
|
+
- **The deprecated `getLastNotificationResponseAsync`/`clearLastNotificationResponseAsync`** are ported
|
|
240
|
+
too, as thin wrappers over the sync forms, exactly like upstream.
|
|
241
|
+
|
|
242
|
+
## Scope exclusions
|
|
243
|
+
|
|
229
244
|
- **`SetBadgeCountOptions.web`** (a `badgin` options bag) is web-only; this project targets
|
|
230
245
|
iOS/Android and `setBadgeCountAsync` takes no options here.
|
|
231
246
|
|
|
@@ -237,22 +252,43 @@ triggers, and native-facing payloads — see `src/core/types.ts`.
|
|
|
237
252
|
separate build step per platform to exploit that split for, so each function branches on
|
|
238
253
|
`Platform.OS === 'android'` at runtime instead. Off Android they resolve to the same documented
|
|
239
254
|
no-op values (`[]`/`null`/void) without touching native.
|
|
240
|
-
- **13 native modules autolink from one `expo-notifications` install**
|
|
255
|
+
- **13 native modules autolink from one `expo-notifications` install** -
|
|
241
256
|
`ExpoNotificationPermissionsModule`, `ExpoPushTokenManager`, `ExpoTopicSubscriptionModule`,
|
|
242
257
|
`NotificationsServerRegistrationModule`, `ExpoNotificationPresenter`, `ExpoBadgeModule`,
|
|
243
258
|
`ExpoNotificationScheduler`, `ExpoNotificationCategoriesModule`,
|
|
244
259
|
`ExpoNotificationChannelManager`, `ExpoNotificationChannelGroupManager`,
|
|
245
260
|
`ExpoNotificationsHandlerModule`, `ExpoNotificationsEmitter`,
|
|
246
|
-
`ExpoBackgroundNotificationTasksModule`
|
|
247
|
-
-
|
|
261
|
+
`ExpoBackgroundNotificationTasksModule` - see `native-link.json`.
|
|
262
|
+
- **Push does not work on emulators or simulators.** Test remote push on a physical device; local
|
|
263
|
+
notifications work everywhere.
|
|
264
|
+
- **`setNotificationHandler`'s callback must respond within 3 seconds**, matching upstream - a
|
|
248
265
|
slow `handleNotification` implementation causes native to discard the notification and call
|
|
249
266
|
`handleError` with a `NotificationTimeoutError` instead.
|
|
250
267
|
|
|
268
|
+
## Common questions
|
|
269
|
+
|
|
270
|
+
- **No push token.** Pass `projectId`. On Android create a channel first (`setNotificationChannelAsync`
|
|
271
|
+
before `getExpoPushTokenAsync`; Android 13's prompt needs a channel) and set up FCM credentials and
|
|
272
|
+
`google-services.json`.
|
|
273
|
+
- **Nothing shows while the app is open.** Call `setNotificationHandler` with `shouldShowBanner` and
|
|
274
|
+
`shouldShowList` `true`.
|
|
275
|
+
- **Silent or late on Android.** Create the channel with high importance and send `priority: 'high'`.
|
|
276
|
+
- **Nothing when the app is closed.** Check the token is registered on the server, FCM V1 credentials
|
|
277
|
+
on Android, and test on a physical device.
|
|
278
|
+
- **Which notification was tapped?** `useLastNotificationResponse` or
|
|
279
|
+
`addNotificationResponseReceivedListener`.
|
|
280
|
+
- **Reminders need push?** No: scheduled local notifications need no Firebase or APNs.
|
|
281
|
+
|
|
282
|
+
Sources: [Expo docs: Notifications](https://docs.expo.dev/versions/latest/sdk/notifications/),
|
|
283
|
+
[Expo docs: push notifications setup](https://docs.expo.dev/push-notifications/push-notifications-setup/),
|
|
284
|
+
[expo/expo#49638](https://github.com/expo/expo/issues/49638),
|
|
285
|
+
[expo/expo#9745](https://github.com/expo/expo/issues/9745).
|
|
286
|
+
|
|
251
287
|
## Test it
|
|
252
288
|
|
|
253
|
-
No Fabric/Descriptor angle at all
|
|
289
|
+
No Fabric/Descriptor angle at all - every function here is a pure async-function surface plus
|
|
254
290
|
event-emitter subscriptions, never a view or per-instance state. Tests inject a fake native-module
|
|
255
291
|
object in place of the real `requireNativeModule` resolution (`vi.mock('./native-modules', …)`)
|
|
256
292
|
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
|
|
293
|
+
shared across instances so a test can fire an event the same way native would - no `installFabric()`,
|
|
258
294
|
no ViewConfig. See `src/core/*.test.ts`.
|
package/build/angular/index.d.ts
CHANGED
package/build/angular/index.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Signal } from '@angular/core';
|
|
2
|
+
import type { IMaybeNotificationResponse } from '../../../core';
|
|
3
|
+
export declare class LastNotificationResponseService {
|
|
4
|
+
private readonly injector;
|
|
5
|
+
private readonly lastResponse;
|
|
6
|
+
private isConnected;
|
|
7
|
+
connect(): Signal<IMaybeNotificationResponse>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Angular twin of `../../../react`'s `useLastNotificationResponse`, `connect()` shape combining
|
|
2
|
+
// `PermissionsService`'s signal return with `ScreenshotListenerService`'s effect+injector cleanup
|
|
3
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
4
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
5
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
6
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
7
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
8
|
+
var _, done = false;
|
|
9
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
10
|
+
var context = {};
|
|
11
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
12
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
13
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
14
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
15
|
+
if (kind === "accessor") {
|
|
16
|
+
if (result === void 0) continue;
|
|
17
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
18
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
19
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
20
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
21
|
+
}
|
|
22
|
+
else if (_ = accept(result)) {
|
|
23
|
+
if (kind === "field") initializers.unshift(_);
|
|
24
|
+
else descriptor[key] = _;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
28
|
+
done = true;
|
|
29
|
+
};
|
|
30
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
31
|
+
var useValue = arguments.length > 2;
|
|
32
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
33
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
34
|
+
}
|
|
35
|
+
return useValue ? value : void 0;
|
|
36
|
+
};
|
|
37
|
+
import { effect, Injectable, Injector, signal, inject, } from '@angular/core';
|
|
38
|
+
import { addNotificationResponseClearedListener, addNotificationResponseReceivedListener, determineNextResponse, getLastNotificationResponse, } from '../../../core/index.js';
|
|
39
|
+
let LastNotificationResponseService = (() => {
|
|
40
|
+
let _classDecorators = [Injectable({ providedIn: 'root' })];
|
|
41
|
+
let _classDescriptor;
|
|
42
|
+
let _classExtraInitializers = [];
|
|
43
|
+
let _classThis;
|
|
44
|
+
var LastNotificationResponseService = class {
|
|
45
|
+
static { _classThis = this; }
|
|
46
|
+
static {
|
|
47
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
48
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
49
|
+
LastNotificationResponseService = _classThis = _classDescriptor.value;
|
|
50
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
51
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
52
|
+
}
|
|
53
|
+
injector = inject(Injector);
|
|
54
|
+
lastResponse = signal(undefined);
|
|
55
|
+
isConnected = false;
|
|
56
|
+
connect() {
|
|
57
|
+
if (!this.isConnected) {
|
|
58
|
+
this.isConnected = true;
|
|
59
|
+
effect(onCleanup => {
|
|
60
|
+
this.lastResponse.update(prev => determineNextResponse(prev, getLastNotificationResponse()));
|
|
61
|
+
const subscription = addNotificationResponseReceivedListener(next => {
|
|
62
|
+
this.lastResponse.update(prev => determineNextResponse(prev, next));
|
|
63
|
+
});
|
|
64
|
+
const clearedSubscription = addNotificationResponseClearedListener(() => {
|
|
65
|
+
this.lastResponse.set(null);
|
|
66
|
+
});
|
|
67
|
+
onCleanup(() => {
|
|
68
|
+
subscription.remove();
|
|
69
|
+
clearedSubscription.remove();
|
|
70
|
+
});
|
|
71
|
+
}, { injector: this.injector });
|
|
72
|
+
}
|
|
73
|
+
return this.lastResponse.asReadonly();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
return LastNotificationResponseService = _classThis;
|
|
77
|
+
})();
|
|
78
|
+
export { LastNotificationResponseService };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Порт `DevicePushTokenAutoRegistration.fx.ts`, ресинк токена на сервер Expo
|
|
2
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
3
|
+
import { addPushTokenListener, getDevicePushTokenAsync } from './device-token.js';
|
|
4
|
+
import { serverRegistrationModule } from './native-modules.js';
|
|
5
|
+
import { startAbortableRegistration } from './registration-abort.js';
|
|
6
|
+
import { hasDeviceTokenChangedAsync, updateDevicePushTokenAsync, } from './update-device-push-token.js';
|
|
7
|
+
async function updatePushTokenAsync(token) {
|
|
8
|
+
if (!(await hasDeviceTokenChangedAsync(token)))
|
|
9
|
+
return;
|
|
10
|
+
await updateDevicePushTokenAsync(startAbortableRegistration(), token);
|
|
11
|
+
}
|
|
12
|
+
function isEnabledRegistration(info) {
|
|
13
|
+
if (!info)
|
|
14
|
+
return false;
|
|
15
|
+
try {
|
|
16
|
+
const registration = JSON.parse(info);
|
|
17
|
+
return registration?.isEnabled === true;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.warn('[notifications] Error encountered while fetching registration information for auto token updates.', error);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Досылает токен, если регистрация включена, а сервер его не получил
|
|
25
|
+
export async function handlePersistedRegistrationInfoAsync(registrationInfo) {
|
|
26
|
+
if (!isEnabledRegistration(registrationInfo))
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
29
|
+
// Регистрация включена, поэтому запрос "нового" токена безопасен
|
|
30
|
+
await updatePushTokenAsync(await getDevicePushTokenAsync());
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.warn('[notifications] Error encountered while updating server registration with latest device push token.', error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
let removeListener = null;
|
|
37
|
+
// Upstream ставит подписку при загрузке модуля, здесь явно, т.к. под `inlineRequires` barrel
|
|
38
|
+
// не гарантирует выполнение побочного эффекта
|
|
39
|
+
export function installPushTokenAutoRegistration() {
|
|
40
|
+
if (removeListener)
|
|
41
|
+
return removeListener;
|
|
42
|
+
const getRegistrationInfoAsync = serverRegistrationModule.getRegistrationInfoAsync;
|
|
43
|
+
if (!getRegistrationInfoAsync) {
|
|
44
|
+
console.warn('[notifications] Error encountered while fetching auto-registration state, new tokens will not be automatically registered on server.', new UnavailabilityError('ServerRegistrationModule', 'getRegistrationInfoAsync'));
|
|
45
|
+
return () => undefined;
|
|
46
|
+
}
|
|
47
|
+
const subscription = addPushTokenListener(async (token) => {
|
|
48
|
+
try {
|
|
49
|
+
if (isEnabledRegistration(await serverRegistrationModule.getRegistrationInfoAsync?.())) {
|
|
50
|
+
await updatePushTokenAsync(token);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.warn('[notifications] Error encountered while updating server registration with latest device push token.', error);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
getRegistrationInfoAsync().then(handlePersistedRegistrationInfoAsync, error => {
|
|
58
|
+
console.error('[notifications] Error reading persisted server registration info: ', error);
|
|
59
|
+
});
|
|
60
|
+
removeListener = () => {
|
|
61
|
+
subscription.remove();
|
|
62
|
+
removeListener = null;
|
|
63
|
+
};
|
|
64
|
+
return removeListener;
|
|
65
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function computeNextBackoffInterval(initialBackoff: number, previousRetryCount: number, { multiplier, randomizationFactor, minBackoff, maxBackoff, }?: {
|
|
2
|
+
multiplier?: number | undefined;
|
|
3
|
+
randomizationFactor?: number | undefined;
|
|
4
|
+
minBackoff?: number | undefined;
|
|
5
|
+
maxBackoff?: number | undefined;
|
|
6
|
+
}): number;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Ported from expo-notifications sdk-57 utils/backoff.ts, itself from ide/backoff
|
|
2
|
+
export function computeNextBackoffInterval(initialBackoff, previousRetryCount, { multiplier = 1.5, randomizationFactor = 0.25, minBackoff = initialBackoff, maxBackoff = Infinity, } = {}) {
|
|
3
|
+
if (initialBackoff <= 0)
|
|
4
|
+
initialBackoff = 1;
|
|
5
|
+
if (previousRetryCount < 0)
|
|
6
|
+
previousRetryCount = 0;
|
|
7
|
+
if (multiplier < 1)
|
|
8
|
+
multiplier = 1;
|
|
9
|
+
if (randomizationFactor < 0 || randomizationFactor > 1)
|
|
10
|
+
randomizationFactor = 0;
|
|
11
|
+
const nextBackoff = initialBackoff * multiplier ** previousRetryCount;
|
|
12
|
+
// Jitter within the negative to positive range of the randomization factor
|
|
13
|
+
const jitterFactor = 1 - randomizationFactor + 2 * randomizationFactor * Math.random();
|
|
14
|
+
return Math.min(Math.max(nextBackoff * jitterFactor, minBackoff), maxBackoff);
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type EventSubscription } from 'expo-modules-core';
|
|
2
|
+
import type { IDevicePushToken } from './types';
|
|
3
|
+
export type IPushTokenListener = (token: IDevicePushToken) => void;
|
|
4
|
+
/** Fires whenever the OS rolls the device push token while the app is running */
|
|
5
|
+
export declare function addPushTokenListener(listener: IPushTokenListener): EventSubscription;
|
|
6
|
+
/** Returns the native FCM (Android) or APNs (iOS) device push token */
|
|
7
|
+
export declare function getDevicePushTokenAsync(): Promise<IDevicePushToken>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Отдельно от `tokens.ts`, чтобы `auto-registration.ts` не зависел от него по кругу
|
|
2
|
+
import { Platform, UnavailabilityError, } from 'expo-modules-core';
|
|
3
|
+
import { dlog } from '@symbiote-native/engine';
|
|
4
|
+
import { pushTokenManager } from './native-modules.js';
|
|
5
|
+
// Пакет только под iOS/Android, поэтому `Platform.OS` сужаем один раз здесь, а не кастами
|
|
6
|
+
function nativePlatform() {
|
|
7
|
+
if (Platform.OS === 'ios' || Platform.OS === 'android') {
|
|
8
|
+
return Platform.OS;
|
|
9
|
+
}
|
|
10
|
+
throw new Error(`[notifications] unsupported platform: ${Platform.OS}`);
|
|
11
|
+
}
|
|
12
|
+
/** Fires whenever the OS rolls the device push token while the app is running */
|
|
13
|
+
export function addPushTokenListener(listener) {
|
|
14
|
+
return pushTokenManager.addListener('onDevicePushToken', ({ devicePushToken }) => {
|
|
15
|
+
listener({ data: devicePushToken, type: nativePlatform() });
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
let nativeTokenPromise = null;
|
|
19
|
+
/** Returns the native FCM (Android) or APNs (iOS) device push token */
|
|
20
|
+
export async function getDevicePushTokenAsync() {
|
|
21
|
+
if (!pushTokenManager.getDevicePushTokenAsync) {
|
|
22
|
+
throw new UnavailabilityError('Notifications', 'getDevicePushTokenAsync');
|
|
23
|
+
}
|
|
24
|
+
let devicePushToken;
|
|
25
|
+
if (nativeTokenPromise) {
|
|
26
|
+
devicePushToken = await nativeTokenPromise;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
nativeTokenPromise = pushTokenManager.getDevicePushTokenAsync();
|
|
30
|
+
devicePushToken = await nativeTokenPromise;
|
|
31
|
+
nativeTokenPromise = null;
|
|
32
|
+
}
|
|
33
|
+
dlog('[notifications] getDevicePushTokenAsync resolved');
|
|
34
|
+
return { type: nativePlatform(), data: devicePushToken };
|
|
35
|
+
}
|
package/build/core/emitter.d.ts
CHANGED
|
@@ -20,5 +20,9 @@ export declare function getLastNotificationResponse(): INotificationResponse | n
|
|
|
20
20
|
* be re-applied on the next read.
|
|
21
21
|
*/
|
|
22
22
|
export declare function clearLastNotificationResponse(): void;
|
|
23
|
+
/** @deprecated Use `getLastNotificationResponse` instead */
|
|
24
|
+
export declare function getLastNotificationResponseAsync(): Promise<INotificationResponse | null>;
|
|
25
|
+
/** @deprecated Use `clearLastNotificationResponse` instead */
|
|
26
|
+
export declare function clearLastNotificationResponseAsync(): Promise<void>;
|
|
23
27
|
/** Fires whenever `clearLastNotificationResponse` runs. */
|
|
24
28
|
export declare function addNotificationResponseClearedListener(listener: () => void): EventSubscription;
|
package/build/core/emitter.js
CHANGED
|
@@ -52,6 +52,14 @@ export function clearLastNotificationResponse() {
|
|
|
52
52
|
notificationsEmitterModule.clearLastNotificationResponse();
|
|
53
53
|
emitter.emit(RESPONSE_CLEARED_EVENT, []);
|
|
54
54
|
}
|
|
55
|
+
/** @deprecated Use `getLastNotificationResponse` instead */
|
|
56
|
+
export async function getLastNotificationResponseAsync() {
|
|
57
|
+
return getLastNotificationResponse();
|
|
58
|
+
}
|
|
59
|
+
/** @deprecated Use `clearLastNotificationResponse` instead */
|
|
60
|
+
export async function clearLastNotificationResponseAsync() {
|
|
61
|
+
clearLastNotificationResponse();
|
|
62
|
+
}
|
|
55
63
|
/** Fires whenever `clearLastNotificationResponse` runs. */
|
|
56
64
|
export function addNotificationResponseClearedListener(listener) {
|
|
57
65
|
return emitter.addListener(RESPONSE_CLEARED_EVENT, listener);
|
package/build/core/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export { getPermissionsAsync, requestPermissionsAsync } from './permissions';
|
|
2
2
|
export { addPushTokenListener, getDevicePushTokenAsync, getExpoPushTokenAsync, setAutoServerRegistrationEnabledAsync, subscribeToTopicAsync, unregisterForNotificationsAsync, unsubscribeFromTopicAsync, type IPushTokenListener, } from './tokens';
|
|
3
|
+
export { installPushTokenAutoRegistration, type IDevicePushTokenRegistration, } from './auto-registration';
|
|
3
4
|
export { dismissAllNotificationsAsync, dismissNotificationAsync, getPresentedNotificationsAsync, } from './presenter';
|
|
4
5
|
export { getBadgeCountAsync, setBadgeCountAsync } from './badge';
|
|
5
6
|
export { cancelAllScheduledNotificationsAsync, cancelScheduledNotificationAsync, getAllScheduledNotificationsAsync, getNextTriggerDateAsync, scheduleNotificationAsync, } from './scheduler';
|
|
6
7
|
export { deleteNotificationCategoryAsync, getNotificationCategoriesAsync, setNotificationCategoryAsync, } from './categories';
|
|
7
8
|
export { deleteNotificationChannelAsync, deleteNotificationChannelGroupAsync, getNotificationChannelAsync, getNotificationChannelGroupAsync, getNotificationChannelGroupsAsync, getNotificationChannelsAsync, setNotificationChannelAsync, setNotificationChannelGroupAsync, } from './channels';
|
|
8
9
|
export { NotificationTimeoutError, setNotificationHandler } from './handler';
|
|
9
|
-
export { DEFAULT_ACTION_IDENTIFIER, addNotificationReceivedListener, addNotificationResponseClearedListener, addNotificationResponseReceivedListener, addNotificationsDroppedListener, clearLastNotificationResponse, getLastNotificationResponse, } from './emitter';
|
|
10
|
+
export { DEFAULT_ACTION_IDENTIFIER, addNotificationReceivedListener, addNotificationResponseClearedListener, addNotificationResponseReceivedListener, addNotificationsDroppedListener, clearLastNotificationResponse, clearLastNotificationResponseAsync, getLastNotificationResponse, getLastNotificationResponseAsync, } from './emitter';
|
|
10
11
|
export { BackgroundNotificationTaskResult, registerTaskAsync, unregisterTaskAsync, } from './background-task';
|
|
12
|
+
export { determineNextResponse } from './last-notification-response';
|
|
11
13
|
export type { IAudioAttributes, IAudioAttributesInput, IBeaconRegion, ICalendarNotificationTrigger, ICalendarTriggerInput, IChannelAwareTriggerInput, ICircularRegion, IDailyNotificationTrigger, IDailyTriggerInput, IDateTriggerInput, IDevicePushToken, IExpoPushToken, IExpoPushTokenOptions, IFirebaseRemoteMessage, IFirebaseRemoteMessageNotification, IInterruptionLevel, IIosNotificationPermissionsRequest, ILocationNotificationTrigger, IMaybeNotificationResponse, IMonthlyNotificationTrigger, IMonthlyTriggerInput, INativeCalendarTriggerInput, INativeChannelAwareTriggerInput, INativeDailyTriggerInput, INativeDateTriggerInput, INativeDevicePushToken, INativeMonthlyTriggerInput, INativeNotificationTriggerInput, INativeTimeIntervalTriggerInput, INativeWeeklyTriggerInput, INativeYearlyTriggerInput, INotification, INotificationAction, INotificationBehavior, INotificationCategory, INotificationCategoryOptions, INotificationChannel, INotificationChannelGroup, INotificationChannelGroupInput, INotificationChannelInput, INotificationContent, INotificationContentAndroid, INotificationContentAttachmentIos, INotificationContentInput, INotificationContentIos, INotificationHandler, INotificationPermissionsRequest, INotificationPermissionsStatus, INotificationRequest, INotificationRequestInput, INotificationResponse, INotificationTaskPayload, INotificationTrigger, INotificationTriggerInput, IPushNotificationTrigger, IRegion, ISchedulableNotificationTriggerInput, ITimeIntervalNotificationTrigger, ITimeIntervalTriggerInput, IUnknownNotificationTrigger, IWeeklyNotificationTrigger, IWeeklyTriggerInput, IYearlyNotificationTrigger, IYearlyTriggerInput, PermissionExpiration, PermissionResponse, } from './types';
|
|
12
14
|
export { AndroidAudioContentType, AndroidAudioUsage, AndroidImportance, AndroidNotificationPriority, AndroidNotificationVisibility, IosAlertStyle, IosAllowsPreviews, IosAuthorizationStatus, PermissionStatus, SchedulableTriggerInputTypes, } from './types';
|
package/build/core/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export { getPermissionsAsync, requestPermissionsAsync } from './permissions.js';
|
|
2
2
|
export { addPushTokenListener, getDevicePushTokenAsync, getExpoPushTokenAsync, setAutoServerRegistrationEnabledAsync, subscribeToTopicAsync, unregisterForNotificationsAsync, unsubscribeFromTopicAsync, } from './tokens.js';
|
|
3
|
+
export { installPushTokenAutoRegistration, } from './auto-registration.js';
|
|
3
4
|
export { dismissAllNotificationsAsync, dismissNotificationAsync, getPresentedNotificationsAsync, } from './presenter.js';
|
|
4
5
|
export { getBadgeCountAsync, setBadgeCountAsync } from './badge.js';
|
|
5
6
|
export { cancelAllScheduledNotificationsAsync, cancelScheduledNotificationAsync, getAllScheduledNotificationsAsync, getNextTriggerDateAsync, scheduleNotificationAsync, } from './scheduler.js';
|
|
6
7
|
export { deleteNotificationCategoryAsync, getNotificationCategoriesAsync, setNotificationCategoryAsync, } from './categories.js';
|
|
7
8
|
export { deleteNotificationChannelAsync, deleteNotificationChannelGroupAsync, getNotificationChannelAsync, getNotificationChannelGroupAsync, getNotificationChannelGroupsAsync, getNotificationChannelsAsync, setNotificationChannelAsync, setNotificationChannelGroupAsync, } from './channels.js';
|
|
8
9
|
export { NotificationTimeoutError, setNotificationHandler } from './handler.js';
|
|
9
|
-
export { DEFAULT_ACTION_IDENTIFIER, addNotificationReceivedListener, addNotificationResponseClearedListener, addNotificationResponseReceivedListener, addNotificationsDroppedListener, clearLastNotificationResponse, getLastNotificationResponse, } from './emitter.js';
|
|
10
|
+
export { DEFAULT_ACTION_IDENTIFIER, addNotificationReceivedListener, addNotificationResponseClearedListener, addNotificationResponseReceivedListener, addNotificationsDroppedListener, clearLastNotificationResponse, clearLastNotificationResponseAsync, getLastNotificationResponse, getLastNotificationResponseAsync, } from './emitter.js';
|
|
10
11
|
export { BackgroundNotificationTaskResult, registerTaskAsync, unregisterTaskAsync, } from './background-task.js';
|
|
12
|
+
export { determineNextResponse } from './last-notification-response.js';
|
|
11
13
|
export { AndroidAudioContentType, AndroidAudioUsage, AndroidImportance, AndroidNotificationPriority, AndroidNotificationVisibility, IosAlertStyle, IosAllowsPreviews, IosAuthorizationStatus, PermissionStatus, SchedulableTriggerInputTypes, } from './types.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Framework-agnostic twin of expo-notifications' `determineNextResponse` - every adapter's
|
|
2
|
+
// `useLastNotificationResponse` shares this dedup rule instead of re-deriving it
|
|
3
|
+
export function determineNextResponse(prevResponse, newResponse) {
|
|
4
|
+
if (!newResponse)
|
|
5
|
+
return null;
|
|
6
|
+
if (!prevResponse)
|
|
7
|
+
return newResponse;
|
|
8
|
+
return prevResponse.notification.request.identifier !==
|
|
9
|
+
newResponse.notification.request.identifier
|
|
10
|
+
? newResponse
|
|
11
|
+
: prevResponse;
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Один активный запрос ресинка токена, новый прерывает предыдущий
|
|
2
|
+
let lastAbortController = null;
|
|
3
|
+
export function abortPendingRegistration() {
|
|
4
|
+
lastAbortController?.abort();
|
|
5
|
+
}
|
|
6
|
+
export function startAbortableRegistration() {
|
|
7
|
+
abortPendingRegistration();
|
|
8
|
+
lastAbortController = new AbortController();
|
|
9
|
+
return lastAbortController.signal;
|
|
10
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IDevicePushToken } from './types';
|
|
2
|
+
export declare function getDeviceIdAsync(): Promise<string>;
|
|
3
|
+
export declare function getTypeOfToken(devicePushToken: IDevicePushToken): string;
|
|
4
|
+
export declare function shouldUseDevelopmentNotificationService(): Promise<boolean>;
|