expo-pusher-push-notifications 0.3.0 → 0.4.1
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 +50 -31
- package/android/src/main/java/expo/modules/pusherpushnotifications/ExpoPusherPushNotificationsModule.kt +0 -21
- package/android/src/main/java/expo/modules/pusherpushnotifications/NotificationHelper.kt +0 -4
- package/android/src/main/java/expo/modules/pusherpushnotifications/PusherMessagingService.kt +2 -3
- package/build/ExpoPusherPushNotifications.types.d.ts +11 -20
- package/build/ExpoPusherPushNotifications.types.d.ts.map +1 -1
- package/build/ExpoPusherPushNotifications.types.js.map +1 -1
- package/build/ExpoPusherPushNotificationsModule.d.ts +1 -4
- package/build/ExpoPusherPushNotificationsModule.d.ts.map +1 -1
- package/build/ExpoPusherPushNotificationsModule.js.map +1 -1
- package/build/index.d.ts +14 -55
- package/build/index.d.ts.map +1 -1
- package/build/index.js +18 -66
- package/build/index.js.map +1 -1
- package/docs/api.md +13 -14
- package/docs/extras.md +1 -1
- package/docs/interests.md +2 -2
- package/docs/migrating.md +5 -1
- package/docs/setup.md +8 -12
- package/docs/users.md +7 -6
- package/ios/ExpoPusherPushNotificationsModule.swift +2 -27
- package/package.json +3 -4
- package/plugin/build/index.d.ts +3 -3
- package/docs/direct.md +0 -63
package/README.md
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
# expo-pusher-push-notifications
|
|
2
2
|
|
|
3
|
-
Push notifications for Expo
|
|
3
|
+
Push notifications for Expo and React Native, powered by [Pusher Beams](https://pusher.com/beams). Subscribe devices to topics, bind them to your signed-in users, and get custom sounds, Android channels, badges, and local notifications along the way.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
npx expo install expo-pusher-push-notifications
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Expo Go can't load native modules
|
|
9
|
+
Needs Expo SDK 58+ and a development build (Expo Go can't load native modules).
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Quick start
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|---|---|---|
|
|
15
|
-
| Send to everyone following a topic (`news`, `order-42`) | [**Interests**](docs/interests.md) | Pusher Beams |
|
|
16
|
-
| Send to a signed-in user on all their devices | [**Users**](docs/users.md) | Pusher Beams + one auth endpoint |
|
|
17
|
-
| Send from your own server straight to FCM / APNs | [**DeviceToken**](docs/direct.md) | Nothing from Pusher |
|
|
13
|
+
Add the config plugin to `app.json`:
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
```json
|
|
16
|
+
["expo-pusher-push-notifications", { "instanceId": "YOUR_BEAMS_INSTANCE_ID" }]
|
|
17
|
+
```
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
Then in your app:
|
|
22
20
|
|
|
23
21
|
```ts
|
|
24
22
|
import { Interests } from 'expo-pusher-push-notifications';
|
|
@@ -27,26 +25,51 @@ await Interests.start('YOUR_BEAMS_INSTANCE_ID');
|
|
|
27
25
|
await Interests.subscribe('news');
|
|
28
26
|
```
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
That device now gets anything your server publishes to `news`. Full console steps (APNs, Firebase, Beams) are in [Setup](docs/setup.md).
|
|
29
|
+
|
|
30
|
+
## Subscribe to interests (topics)
|
|
31
|
+
|
|
32
|
+
Interests are topics like `news`, `order-42`, or `team-7`. Every device subscribed to one gets the push.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
await Interests.subscribe('order-42');
|
|
36
|
+
await Interests.unsubscribe('order-42');
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
More in [Interests](docs/interests.md).
|
|
40
|
+
|
|
41
|
+
## Bind a device to a user
|
|
42
|
+
|
|
43
|
+
When your backend thinks in user ids ("send this to user-123"), bind the device to the signed-in user. Beams asks your API for an auth token, so you need one small endpoint on your server.
|
|
31
44
|
|
|
32
45
|
```ts
|
|
33
46
|
import { Users } from 'expo-pusher-push-notifications';
|
|
34
47
|
|
|
35
48
|
await Users.start('YOUR_BEAMS_INSTANCE_ID');
|
|
36
|
-
|
|
37
|
-
// sign-
|
|
49
|
+
|
|
50
|
+
// after your own sign-in
|
|
51
|
+
await Users.login('user-123', {
|
|
52
|
+
url: 'https://api.example.com/pusher/beams-auth',
|
|
53
|
+
headers: { Authorization: `Bearer ${sessionToken}` },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// on sign-out
|
|
57
|
+
await Users.logout();
|
|
38
58
|
```
|
|
39
59
|
|
|
40
|
-
|
|
60
|
+
You can use interests and users together. See [Authenticate users](docs/users.md) for the server side.
|
|
41
61
|
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
## Send from your server
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import PushNotifications from '@pusher/push-notifications-server';
|
|
66
|
+
const beams = new PushNotifications({ instanceId, secretKey });
|
|
44
67
|
|
|
45
|
-
|
|
46
|
-
|
|
68
|
+
await beams.publishToInterests(['news'], payload); // everyone on a topic
|
|
69
|
+
await beams.publishToUsers(['user-123'], payload); // one user, all their devices
|
|
47
70
|
```
|
|
48
71
|
|
|
49
|
-
##
|
|
72
|
+
## Extras
|
|
50
73
|
|
|
51
74
|
```ts
|
|
52
75
|
import { addListener, createChannel, setBadgeCount, scheduleLocalNotification } from 'expo-pusher-push-notifications';
|
|
@@ -57,26 +80,22 @@ await setBadgeCount(3);
|
|
|
57
80
|
await scheduleLocalNotification({ title: 'Stand up', seconds: 600, sound: 'alarm.wav' });
|
|
58
81
|
```
|
|
59
82
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```json
|
|
63
|
-
["expo-pusher-push-notifications", { "sounds": ["./assets/alarm.wav"] }]
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
More: [sounds, channels, badges, local notifications](docs/extras.md).
|
|
83
|
+
Custom sounds go in the plugin's `sounds` option. Details in [Extras](docs/extras.md).
|
|
67
84
|
|
|
68
85
|
## Docs
|
|
69
86
|
|
|
70
|
-
- [Setup](docs/setup.md)
|
|
71
|
-
- [Interests](docs/interests.md)
|
|
87
|
+
- [Setup](docs/setup.md)
|
|
88
|
+
- [Interests](docs/interests.md)
|
|
89
|
+
- [Authenticate users](docs/users.md)
|
|
72
90
|
- [Sounds, channels, badges, local notifications](docs/extras.md)
|
|
73
91
|
- [Full API](docs/api.md)
|
|
74
92
|
- [Migrating from react-native-pusher-push-notifications](docs/migrating.md)
|
|
75
93
|
|
|
76
|
-
##
|
|
94
|
+
## Good to know
|
|
77
95
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
96
|
+
- This module owns the iOS notification delegate and the Android FCM service, so don't run it alongside `expo-notifications` or `@react-native-firebase/messaging`.
|
|
97
|
+
- Want a raw FCM / APNs token without Beams? Use [`expo-notifications`](https://docs.expo.dev/versions/latest/sdk/notifications/) instead.
|
|
98
|
+
- Need realtime Pusher **Channels** (websockets)? Use [`pusher-js`](https://github.com/pusher/pusher-js).
|
|
80
99
|
|
|
81
100
|
## License
|
|
82
101
|
|
|
@@ -9,7 +9,6 @@ import android.content.Intent
|
|
|
9
9
|
import android.os.Build
|
|
10
10
|
import android.os.SystemClock
|
|
11
11
|
import androidx.core.app.NotificationManagerCompat
|
|
12
|
-
import com.google.firebase.messaging.FirebaseMessaging
|
|
13
12
|
import com.pusher.pushnotifications.BeamsCallback
|
|
14
13
|
import com.pusher.pushnotifications.PushNotifications
|
|
15
14
|
import com.pusher.pushnotifications.PusherCallbackError
|
|
@@ -76,24 +75,6 @@ class ExpoPusherPushNotificationsModule : Module() {
|
|
|
76
75
|
AsyncFunction("clearAllState") { PushNotifications.clearAllState() }
|
|
77
76
|
AsyncFunction("stop") { PushNotifications.stop() }
|
|
78
77
|
|
|
79
|
-
// ---- Raw device token (FCM) ----
|
|
80
|
-
AsyncFunction("registerForDeviceToken") { promise: Promise ->
|
|
81
|
-
FirebaseMessaging.getInstance().token.addOnCompleteListener { t ->
|
|
82
|
-
if (t.isSuccessful && t.result != null) {
|
|
83
|
-
NotificationHelper.saveToken(context, t.result)
|
|
84
|
-
sendEvent("registered", mapOf("deviceToken" to t.result, "type" to "fcm"))
|
|
85
|
-
promise.resolve(tokenMap(t.result))
|
|
86
|
-
} else promise.reject("E_FCM_TOKEN", t.exception?.message ?: "Failed to get FCM token", t.exception)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
AsyncFunction("getDevicePushToken") { -> NotificationHelper.savedToken(context)?.let { tokenMap(it) } }
|
|
90
|
-
AsyncFunction("deleteDevicePushToken") { promise: Promise ->
|
|
91
|
-
FirebaseMessaging.getInstance().deleteToken().addOnCompleteListener { t ->
|
|
92
|
-
NotificationHelper.saveToken(context, null)
|
|
93
|
-
if (t.isSuccessful) promise.resolve(null) else promise.reject("E_FCM_DELETE", t.exception?.message, t.exception)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
78
|
// ---- Permissions ----
|
|
98
79
|
AsyncFunction("requestPermission") { promise: Promise ->
|
|
99
80
|
val perms = appContext.permissions
|
|
@@ -159,8 +140,6 @@ class ExpoPusherPushNotificationsModule : Module() {
|
|
|
159
140
|
override fun onFailure(error: PusherCallbackError) = promise.reject("E_SET_USER_ID", error.message, null)
|
|
160
141
|
}
|
|
161
142
|
|
|
162
|
-
private fun tokenMap(token: String) = mapOf("type" to "fcm", "platform" to "android", "token" to token)
|
|
163
|
-
|
|
164
143
|
private fun status(): String =
|
|
165
144
|
if (NotificationManagerCompat.from(context).areNotificationsEnabled()) PermissionsStatus.GRANTED.status
|
|
166
145
|
else if (Build.VERSION.SDK_INT >= 33) PermissionsStatus.UNDETERMINED.status else PermissionsStatus.DENIED.status
|
|
@@ -106,10 +106,6 @@ object NotificationHelper {
|
|
|
106
106
|
if (n <= 0) NotificationManagerCompat.from(ctx).cancelAll()
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
fun saveToken(ctx: Context, token: String?) =
|
|
110
|
-
ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().putString("fcm_token", token).apply()
|
|
111
|
-
fun savedToken(ctx: Context) = ctx.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getString("fcm_token", null)
|
|
112
|
-
|
|
113
109
|
fun show(ctx: Context, id: Int, title: String?, body: String?, data: Map<String, String>, sound: String?, channelId: String?) {
|
|
114
110
|
ensureChannels(ctx)
|
|
115
111
|
val launch = ctx.packageManager.getLaunchIntentForPackage(ctx.packageName)?.apply {
|
package/android/src/main/java/expo/modules/pusherpushnotifications/PusherMessagingService.kt
CHANGED
|
@@ -4,8 +4,8 @@ import com.google.firebase.messaging.RemoteMessage
|
|
|
4
4
|
import com.pusher.pushnotifications.fcm.MessagingService
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The app's FCM service. Extends the Beams MessagingService so Beams keeps working,
|
|
8
|
+
* and adds foreground display, custom sounds/channels and badge handling on top.
|
|
9
9
|
*/
|
|
10
10
|
class PusherMessagingService : MessagingService() {
|
|
11
11
|
companion object { @Volatile var module: ExpoPusherPushNotificationsModule? = null }
|
|
@@ -39,7 +39,6 @@ class PusherMessagingService : MessagingService() {
|
|
|
39
39
|
|
|
40
40
|
override fun onNewToken(token: String) {
|
|
41
41
|
super.onNewToken(token)
|
|
42
|
-
NotificationHelper.saveToken(this, token)
|
|
43
42
|
module?.emit("registered", mapOf("deviceToken" to token, "type" to "fcm"))
|
|
44
43
|
}
|
|
45
44
|
}
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Which Beams auth path the app uses.
|
|
3
3
|
*
|
|
4
|
-
* - `interests`:
|
|
5
|
-
* - `users`:
|
|
6
|
-
* - `device`: Raw device-token push. Beams is NOT started. The module obtains the native FCM (Android) /
|
|
7
|
-
* APNs (iOS) device token and hands it to you; your server sends directly to FCM / APNs.
|
|
4
|
+
* - `interests`: devices subscribe to named interests; your server calls Beams `publishToInterests`.
|
|
5
|
+
* - `users`: the device is bound to a user via `setUserId`; your server calls Beams `publishToUsers`.
|
|
8
6
|
*/
|
|
9
|
-
export type PusherTransportMode = 'interests' | 'users'
|
|
7
|
+
export type PusherTransportMode = 'interests' | 'users';
|
|
10
8
|
export interface PusherConfig {
|
|
11
|
-
/** Pusher Beams instance ID.
|
|
12
|
-
instanceId
|
|
13
|
-
/**
|
|
9
|
+
/** Pusher Beams instance ID. */
|
|
10
|
+
instanceId: string;
|
|
11
|
+
/** Beams auth path: `interests` (default) or `users`. */
|
|
14
12
|
mode?: PusherTransportMode;
|
|
15
13
|
/** Ask for notification permission (iOS alert/sound/badge, Android 13+ POST_NOTIFICATIONS) during start. Default true. */
|
|
16
14
|
requestPermission?: boolean;
|
|
17
15
|
}
|
|
18
|
-
/** Remote token endpoint used by the Beams SDK
|
|
16
|
+
/** Remote token endpoint used by the Beams SDK when binding a device to a user. */
|
|
19
17
|
export interface BeamsTokenProvider {
|
|
20
18
|
/** HTTPS endpoint on your backend that returns `{ token: string }`. */
|
|
21
19
|
url: string;
|
|
@@ -23,17 +21,10 @@ export interface BeamsTokenProvider {
|
|
|
23
21
|
queryParams?: Record<string, string>;
|
|
24
22
|
}
|
|
25
23
|
/**
|
|
26
|
-
*
|
|
24
|
+
* User auth: either a remote token provider (recommended), or a pre-fetched Beams token string
|
|
27
25
|
* (the b8ne `setUserId(userId, token)` style).
|
|
28
26
|
*/
|
|
29
27
|
export type BeamsAuth = BeamsTokenProvider | string;
|
|
30
|
-
export type DevicePushTokenType = 'fcm' | 'apns';
|
|
31
|
-
export interface DevicePushToken {
|
|
32
|
-
type: DevicePushTokenType;
|
|
33
|
-
/** FCM registration token (Android) or hex-encoded APNs token (iOS). */
|
|
34
|
-
token: string;
|
|
35
|
-
platform: 'android' | 'ios';
|
|
36
|
-
}
|
|
37
28
|
export interface PusherNotification {
|
|
38
29
|
title?: string;
|
|
39
30
|
body?: string;
|
|
@@ -42,9 +33,9 @@ export interface PusherNotification {
|
|
|
42
33
|
appState: 'foreground' | 'background' | 'opened';
|
|
43
34
|
}
|
|
44
35
|
export interface RegisteredEvent {
|
|
45
|
-
/** Native
|
|
36
|
+
/** Native token the device registered with (FCM on Android, hex APNs on iOS). Beams handles it for you. */
|
|
46
37
|
deviceToken: string;
|
|
47
|
-
type:
|
|
38
|
+
type: 'fcm' | 'apns';
|
|
48
39
|
}
|
|
49
40
|
export interface SubscriptionsChangedEvent {
|
|
50
41
|
interests: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoPusherPushNotifications.types.d.ts","sourceRoot":"","sources":["../src/ExpoPusherPushNotifications.types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ExpoPusherPushNotifications.types.d.ts","sourceRoot":"","sources":["../src/ExpoPusherPushNotifications.types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,OAAO,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,0HAA0H;IAC1H,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,4GAA4G;IAC5G,QAAQ,EAAE,YAAY,GAAG,YAAY,GAAG,QAAQ,CAAC;CAClD;AAED,MAAM,WAAW,eAAe;IAC9B,2GAA2G;IAC3G,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,GAAG,aAAa,CAAC;AAErF,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3E,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,iIAAiI;IACjI,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,oHAAoH;IACpH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,yDAAyD;IACzD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,uCAAuC,GAAG;IACpD,YAAY,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAClD,UAAU,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C,oBAAoB,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACjE,KAAK,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,uCAAuC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoPusherPushNotifications.types.js","sourceRoot":"","sources":["../src/ExpoPusherPushNotifications.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"ExpoPusherPushNotifications.types.js","sourceRoot":"","sources":["../src/ExpoPusherPushNotifications.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Which Beams auth path the app uses.\n *\n * - `interests`: devices subscribe to named interests; your server calls Beams `publishToInterests`.\n * - `users`: the device is bound to a user via `setUserId`; your server calls Beams `publishToUsers`.\n */\nexport type PusherTransportMode = 'interests' | 'users';\n\nexport interface PusherConfig {\n /** Pusher Beams instance ID. */\n instanceId: string;\n /** Beams auth path: `interests` (default) or `users`. */\n mode?: PusherTransportMode;\n /** Ask for notification permission (iOS alert/sound/badge, Android 13+ POST_NOTIFICATIONS) during start. Default true. */\n requestPermission?: boolean;\n}\n\n/** Remote token endpoint used by the Beams SDK when binding a device to a user. */\nexport interface BeamsTokenProvider {\n /** HTTPS endpoint on your backend that returns `{ token: string }`. */\n url: string;\n headers?: Record<string, string>;\n queryParams?: Record<string, string>;\n}\n\n/**\n * User auth: either a remote token provider (recommended), or a pre-fetched Beams token string\n * (the b8ne `setUserId(userId, token)` style).\n */\nexport type BeamsAuth = BeamsTokenProvider | string;\n\nexport interface PusherNotification {\n title?: string;\n body?: string;\n data: Record<string, unknown>;\n /** `foreground`: received while running; `background`: received in background; `opened`: user tapped it. */\n appState: 'foreground' | 'background' | 'opened';\n}\n\nexport interface RegisteredEvent {\n /** Native token the device registered with (FCM on Android, hex APNs on iOS). Beams handles it for you. */\n deviceToken: string;\n type: 'fcm' | 'apns';\n}\n\nexport interface SubscriptionsChangedEvent {\n interests: string[];\n}\n\nexport interface PusherErrorEvent {\n code: string;\n message: string;\n}\n\nexport type PermissionStatus = 'granted' | 'denied' | 'undetermined' | 'provisional';\n\nexport type AndroidImportance = 'min' | 'low' | 'default' | 'high' | 'max';\n\nexport interface AndroidNotificationChannel {\n id: string;\n name: string;\n description?: string;\n importance?: AndroidImportance;\n /** Bundled sound file name WITHOUT path, e.g. `ding.wav` or `ding`. Must be listed in the plugin's `sounds`. `null` = silent. */\n sound?: string | null;\n vibrate?: boolean;\n lightColor?: string;\n showBadge?: boolean;\n}\n\nexport interface LocalNotification {\n title?: string;\n body?: string;\n data?: Record<string, string>;\n /** Bundled sound file name, e.g. `ding.wav`; `default` for system sound. On Android 8+ the channel's sound wins. */\n sound?: string;\n /** Android channel id. Defaults to the plugin's default channel. */\n channelId?: string;\n /** iOS badge to set with this notification. */\n badge?: number;\n}\n\nexport interface ScheduledNotification extends LocalNotification {\n /** Stable id; reuse to replace. Generated if omitted. */\n id?: string;\n /** Fire after this many seconds (>= 1). */\n seconds: number;\n}\n\nexport type ExpoPusherPushNotificationsModuleEvents = {\n notification: (event: PusherNotification) => void;\n registered: (event: RegisteredEvent) => void;\n subscriptionsChanged: (event: SubscriptionsChangedEvent) => void;\n error: (event: PusherErrorEvent) => void;\n};\n\nexport type PusherEventName = keyof ExpoPusherPushNotificationsModuleEvents;\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NativeModule } from 'expo';
|
|
2
|
-
import type { AndroidNotificationChannel, BeamsTokenProvider,
|
|
2
|
+
import type { AndroidNotificationChannel, BeamsTokenProvider, ExpoPusherPushNotificationsModuleEvents, LocalNotification, PermissionStatus, PusherNotification, ScheduledNotification } from './ExpoPusherPushNotifications.types';
|
|
3
3
|
declare class ExpoPusherPushNotificationsNativeModule extends NativeModule<ExpoPusherPushNotificationsModuleEvents> {
|
|
4
4
|
start(instanceId: string): Promise<void>;
|
|
5
5
|
addDeviceInterest(interest: string): Promise<void>;
|
|
@@ -11,9 +11,6 @@ declare class ExpoPusherPushNotificationsNativeModule extends NativeModule<ExpoP
|
|
|
11
11
|
setUserIdWithToken(userId: string, token: string): Promise<void>;
|
|
12
12
|
clearAllState(): Promise<void>;
|
|
13
13
|
stop(): Promise<void>;
|
|
14
|
-
registerForDeviceToken(): Promise<DevicePushToken>;
|
|
15
|
-
getDevicePushToken(): Promise<DevicePushToken | null>;
|
|
16
|
-
deleteDevicePushToken(): Promise<void>;
|
|
17
14
|
requestPermission(): Promise<PermissionStatus>;
|
|
18
15
|
getPermissionStatus(): Promise<PermissionStatus>;
|
|
19
16
|
createChannel(channel: AndroidNotificationChannel): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoPusherPushNotificationsModule.d.ts","sourceRoot":"","sources":["../src/ExpoPusherPushNotificationsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,KAAK,EACV,0BAA0B,EAC1B,kBAAkB,EAClB,
|
|
1
|
+
{"version":3,"file":"ExpoPusherPushNotificationsModule.d.ts","sourceRoot":"","sources":["../src/ExpoPusherPushNotificationsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,MAAM,CAAC;AAEzD,OAAO,KAAK,EACV,0BAA0B,EAC1B,kBAAkB,EAClB,uCAAuC,EACvC,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACtB,MAAM,qCAAqC,CAAC;AAE7C,OAAO,OAAO,uCAAwC,SAAQ,YAAY,CAAC,uCAAuC,CAAC;IACjH,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACxC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAClD,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACrD,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IACtD,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IACvC,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IACrC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3E,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAChE,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAC9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAErB,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAC9C,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAEhD,aAAa,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC;IACjE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACxC,WAAW,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC;IAEpD,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAChC,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAE3C,wBAAwB,CAAC,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;IAC/D,yBAAyB,CAAC,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC;IACpE,2BAA2B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACtD,+BAA+B,IAAI,OAAO,CAAC,IAAI,CAAC;IAChD,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC;IACxC,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAC7D;;AAED,wBAA2G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoPusherPushNotificationsModule.js","sourceRoot":"","sources":["../src/ExpoPusherPushNotificationsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoPusherPushNotificationsModule.js","sourceRoot":"","sources":["../src/ExpoPusherPushNotificationsModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAC;AA0CzD,eAAe,mBAAmB,CAA0C,6BAA6B,CAAC,CAAC","sourcesContent":["import { NativeModule, requireNativeModule } from 'expo';\n\nimport type {\n AndroidNotificationChannel,\n BeamsTokenProvider,\n ExpoPusherPushNotificationsModuleEvents,\n LocalNotification,\n PermissionStatus,\n PusherNotification,\n ScheduledNotification,\n} from './ExpoPusherPushNotifications.types';\n\ndeclare class ExpoPusherPushNotificationsNativeModule extends NativeModule<ExpoPusherPushNotificationsModuleEvents> {\n start(instanceId: string): Promise<void>;\n addDeviceInterest(interest: string): Promise<void>;\n removeDeviceInterest(interest: string): Promise<void>;\n setDeviceInterests(interests: string[]): Promise<void>;\n getDeviceInterests(): Promise<string[]>;\n clearDeviceInterests(): Promise<void>;\n setUserId(userId: string, tokenProvider: BeamsTokenProvider): Promise<void>;\n setUserIdWithToken(userId: string, token: string): Promise<void>;\n clearAllState(): Promise<void>;\n stop(): Promise<void>;\n // permissions\n requestPermission(): Promise<PermissionStatus>;\n getPermissionStatus(): Promise<PermissionStatus>;\n // channels (Android; no-ops on iOS)\n createChannel(channel: AndroidNotificationChannel): Promise<void>;\n deleteChannel(id: string): Promise<void>;\n getChannels(): Promise<AndroidNotificationChannel[]>;\n // badge\n getBadgeCount(): Promise<number>;\n setBadgeCount(count: number): Promise<void>;\n // local\n presentLocalNotification(n: LocalNotification): Promise<string>;\n scheduleLocalNotification(n: ScheduledNotification): Promise<string>;\n cancelScheduledNotification(id: string): Promise<void>;\n cancelAllScheduledNotifications(): Promise<void>;\n dismissAllNotifications(): Promise<void>;\n getInitialNotification(): Promise<PusherNotification | null>;\n}\n\nexport default requireNativeModule<ExpoPusherPushNotificationsNativeModule>('ExpoPusherPushNotifications');\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import type { EventSubscription } from 'expo-modules-core';
|
|
2
|
-
import type { AndroidNotificationChannel, BeamsAuth,
|
|
2
|
+
import type { AndroidNotificationChannel, BeamsAuth, ExpoPusherPushNotificationsModuleEvents, LocalNotification, PermissionStatus, PusherConfig, PusherEventName, PusherNotification, PusherTransportMode, ScheduledNotification } from './ExpoPusherPushNotifications.types';
|
|
3
3
|
export * from './ExpoPusherPushNotifications.types';
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
* - `interests` / `users`: starts the Pusher Beams SDK (requires `instanceId`).
|
|
7
|
-
* - `device`: does NOT start Beams; registers with FCM/APNs and resolves the raw device token.
|
|
8
|
-
* Resolves with the device token in `device` mode, otherwise `null`.
|
|
9
|
-
*/
|
|
10
|
-
export declare function start(config: PusherConfig): Promise<DevicePushToken | null>;
|
|
4
|
+
/** Starts Pusher Beams. */
|
|
5
|
+
export declare function start(config: PusherConfig): Promise<void>;
|
|
11
6
|
export declare function getMode(): PusherTransportMode;
|
|
12
7
|
export declare function subscribe(interest: string): Promise<void>;
|
|
13
8
|
export declare function unsubscribe(interest: string): Promise<void>;
|
|
@@ -16,12 +11,6 @@ export declare function getSubscriptions(): Promise<string[]>;
|
|
|
16
11
|
export declare function clearSubscriptions(): Promise<void>;
|
|
17
12
|
/** Pass a token provider `{ url, headers?, queryParams? }` or a pre-fetched Beams token string. */
|
|
18
13
|
export declare function setUserId(userId: string, auth: BeamsAuth): Promise<void>;
|
|
19
|
-
/** Registers with FCM/APNs (if needed) and returns the native token. Works in any mode. */
|
|
20
|
-
export declare function registerForDeviceToken(): Promise<DevicePushToken>;
|
|
21
|
-
/** Last known native token, or null if not registered yet. */
|
|
22
|
-
export declare function getDevicePushToken(): Promise<DevicePushToken | null>;
|
|
23
|
-
/** Android: deletes the FCM token (a new one is issued on next register). iOS: unregisters from APNs. */
|
|
24
|
-
export declare function deleteDevicePushToken(): Promise<void>;
|
|
25
14
|
export declare function requestPermission(): Promise<PermissionStatus>;
|
|
26
15
|
export declare function getPermissionStatus(): Promise<PermissionStatus>;
|
|
27
16
|
export declare function createChannel(channel: AndroidNotificationChannel): Promise<void>;
|
|
@@ -39,20 +28,20 @@ export declare function dismissAllNotifications(): Promise<void>;
|
|
|
39
28
|
export declare function getInitialNotification(): Promise<PusherNotification | null>;
|
|
40
29
|
export declare function clearAllState(): Promise<void>;
|
|
41
30
|
export declare function stop(): Promise<void>;
|
|
42
|
-
/** Adds an event listener. Events fire
|
|
31
|
+
/** Adds an event listener. Events fire for interests and users alike. */
|
|
43
32
|
export declare function addListener<E extends PusherEventName>(eventName: E, listener: ExpoPusherPushNotificationsModuleEvents[E]): EventSubscription;
|
|
44
33
|
/** Alias for `addListener` (old package's `on()`). */
|
|
45
34
|
export declare const on: typeof addListener;
|
|
46
35
|
/**
|
|
47
|
-
*
|
|
36
|
+
* Pusher Beams **interests** (topic broadcast).
|
|
48
37
|
* Use when many devices should get the same push: "news", "order-42", "team-7".
|
|
49
38
|
* Server: `beams.publishToInterests([...], payload)`.
|
|
50
39
|
*/
|
|
51
40
|
export declare const Interests: {
|
|
52
|
-
/** Start Beams
|
|
41
|
+
/** Start Beams. */
|
|
53
42
|
readonly start: (instanceId: string, opts?: {
|
|
54
43
|
requestPermission?: boolean;
|
|
55
|
-
}) => Promise<
|
|
44
|
+
}) => Promise<void>;
|
|
56
45
|
readonly subscribe: typeof subscribe;
|
|
57
46
|
readonly unsubscribe: typeof unsubscribe;
|
|
58
47
|
readonly set: typeof setSubscriptions;
|
|
@@ -63,43 +52,27 @@ export declare const Interests: {
|
|
|
63
52
|
readonly stop: typeof stop;
|
|
64
53
|
};
|
|
65
54
|
/**
|
|
66
|
-
*
|
|
55
|
+
* Pusher Beams **authenticated users** (push to a person, across all their devices).
|
|
67
56
|
* Use when your backend thinks in user ids: "send to user-123".
|
|
68
57
|
* Server: `beams.publishToUsers(['user-123'], payload)` + a token endpoint (`beams.generateToken`).
|
|
69
58
|
*/
|
|
70
59
|
export declare const Users: {
|
|
71
|
-
/** Start Beams
|
|
60
|
+
/** Start Beams. Call `login()` after your own sign-in. */
|
|
72
61
|
readonly start: (instanceId: string, opts?: {
|
|
73
62
|
requestPermission?: boolean;
|
|
74
|
-
}) => Promise<
|
|
63
|
+
}) => Promise<void>;
|
|
75
64
|
/** Bind this device to `userId`. `auth` is your token endpoint, or a Beams token you already fetched. */
|
|
76
65
|
readonly login: typeof setUserId;
|
|
77
66
|
/** Unbind the device (call on sign-out). */
|
|
78
67
|
readonly logout: typeof clearAllState;
|
|
79
68
|
readonly stop: typeof stop;
|
|
80
69
|
};
|
|
81
|
-
/**
|
|
82
|
-
* Mode 3 — **direct FCM / APNs** (no Pusher at all).
|
|
83
|
-
* Use when your server already talks to FCM HTTP v1 / APNs, or you use another provider.
|
|
84
|
-
* You get the raw native token; send it to your backend and keep it fresh via the `registered` event.
|
|
85
|
-
*/
|
|
86
|
-
export declare const DeviceToken: {
|
|
87
|
-
/** Register with FCM/APNs and resolve the native token. */
|
|
88
|
-
readonly start: (opts?: {
|
|
89
|
-
requestPermission?: boolean;
|
|
90
|
-
}) => Promise<DevicePushToken>;
|
|
91
|
-
readonly get: typeof getDevicePushToken;
|
|
92
|
-
/** Fires whenever the token is issued or rotated. */
|
|
93
|
-
readonly onChange: (cb: (token: DevicePushToken) => void) => EventSubscription;
|
|
94
|
-
/** Delete the token (call on sign-out). */
|
|
95
|
-
readonly delete: typeof deleteDevicePushToken;
|
|
96
|
-
};
|
|
97
70
|
declare const _default: {
|
|
98
71
|
readonly Interests: {
|
|
99
|
-
/** Start Beams
|
|
72
|
+
/** Start Beams. */
|
|
100
73
|
readonly start: (instanceId: string, opts?: {
|
|
101
74
|
requestPermission?: boolean;
|
|
102
|
-
}) => Promise<
|
|
75
|
+
}) => Promise<void>;
|
|
103
76
|
readonly subscribe: typeof subscribe;
|
|
104
77
|
readonly unsubscribe: typeof unsubscribe;
|
|
105
78
|
readonly set: typeof setSubscriptions;
|
|
@@ -110,27 +83,16 @@ declare const _default: {
|
|
|
110
83
|
readonly stop: typeof stop;
|
|
111
84
|
};
|
|
112
85
|
readonly Users: {
|
|
113
|
-
/** Start Beams
|
|
86
|
+
/** Start Beams. Call `login()` after your own sign-in. */
|
|
114
87
|
readonly start: (instanceId: string, opts?: {
|
|
115
88
|
requestPermission?: boolean;
|
|
116
|
-
}) => Promise<
|
|
89
|
+
}) => Promise<void>;
|
|
117
90
|
/** Bind this device to `userId`. `auth` is your token endpoint, or a Beams token you already fetched. */
|
|
118
91
|
readonly login: typeof setUserId;
|
|
119
92
|
/** Unbind the device (call on sign-out). */
|
|
120
93
|
readonly logout: typeof clearAllState;
|
|
121
94
|
readonly stop: typeof stop;
|
|
122
95
|
};
|
|
123
|
-
readonly DeviceToken: {
|
|
124
|
-
/** Register with FCM/APNs and resolve the native token. */
|
|
125
|
-
readonly start: (opts?: {
|
|
126
|
-
requestPermission?: boolean;
|
|
127
|
-
}) => Promise<DevicePushToken>;
|
|
128
|
-
readonly get: typeof getDevicePushToken;
|
|
129
|
-
/** Fires whenever the token is issued or rotated. */
|
|
130
|
-
readonly onChange: (cb: (token: DevicePushToken) => void) => EventSubscription;
|
|
131
|
-
/** Delete the token (call on sign-out). */
|
|
132
|
-
readonly delete: typeof deleteDevicePushToken;
|
|
133
|
-
};
|
|
134
96
|
readonly start: typeof start;
|
|
135
97
|
readonly getMode: typeof getMode;
|
|
136
98
|
readonly subscribe: typeof subscribe;
|
|
@@ -139,9 +101,6 @@ declare const _default: {
|
|
|
139
101
|
readonly getSubscriptions: typeof getSubscriptions;
|
|
140
102
|
readonly clearSubscriptions: typeof clearSubscriptions;
|
|
141
103
|
readonly setUserId: typeof setUserId;
|
|
142
|
-
readonly registerForDeviceToken: typeof registerForDeviceToken;
|
|
143
|
-
readonly getDevicePushToken: typeof getDevicePushToken;
|
|
144
|
-
readonly deleteDevicePushToken: typeof deleteDevicePushToken;
|
|
145
104
|
readonly requestPermission: typeof requestPermission;
|
|
146
105
|
readonly getPermissionStatus: typeof getPermissionStatus;
|
|
147
106
|
readonly createChannel: typeof createChannel;
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,KAAK,EACV,0BAA0B,EAC1B,SAAS,EACT,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/build/index.js
CHANGED
|
@@ -6,36 +6,21 @@ const PREFIX = 'expo-pusher-push-notifications';
|
|
|
6
6
|
function assertBeams(fn) {
|
|
7
7
|
if (!started)
|
|
8
8
|
throw new Error(`${PREFIX}: call start() before ${fn}().`);
|
|
9
|
-
if (currentMode === 'device') {
|
|
10
|
-
throw new Error(`${PREFIX}: ${fn}() is a Pusher Beams API and is unavailable in 'device' mode.`);
|
|
11
|
-
}
|
|
12
9
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Starts the module in one of three modes.
|
|
15
|
-
* - `interests` / `users`: starts the Pusher Beams SDK (requires `instanceId`).
|
|
16
|
-
* - `device`: does NOT start Beams; registers with FCM/APNs and resolves the raw device token.
|
|
17
|
-
* Resolves with the device token in `device` mode, otherwise `null`.
|
|
18
|
-
*/
|
|
10
|
+
/** Starts Pusher Beams. */
|
|
19
11
|
export async function start(config) {
|
|
20
|
-
|
|
12
|
+
if (!config.instanceId)
|
|
13
|
+
throw new Error(`${PREFIX}: instanceId is required.`);
|
|
21
14
|
if (config.requestPermission !== false)
|
|
22
15
|
await Native.requestPermission();
|
|
23
|
-
currentMode = mode;
|
|
24
|
-
if (mode === 'device') {
|
|
25
|
-
const token = await Native.registerForDeviceToken();
|
|
26
|
-
started = true;
|
|
27
|
-
return token;
|
|
28
|
-
}
|
|
29
|
-
if (!config.instanceId)
|
|
30
|
-
throw new Error(`${PREFIX}: instanceId is required for '${mode}' mode.`);
|
|
16
|
+
currentMode = config.mode ?? 'interests';
|
|
31
17
|
await Native.start(config.instanceId);
|
|
32
18
|
started = true;
|
|
33
|
-
return null;
|
|
34
19
|
}
|
|
35
20
|
export function getMode() {
|
|
36
21
|
return currentMode;
|
|
37
22
|
}
|
|
38
|
-
// ----
|
|
23
|
+
// ---- Interests (topics) ----
|
|
39
24
|
export async function subscribe(interest) {
|
|
40
25
|
assertBeams('subscribe');
|
|
41
26
|
await Native.addDeviceInterest(interest);
|
|
@@ -56,7 +41,7 @@ export async function clearSubscriptions() {
|
|
|
56
41
|
assertBeams('clearSubscriptions');
|
|
57
42
|
await Native.clearDeviceInterests();
|
|
58
43
|
}
|
|
59
|
-
// ----
|
|
44
|
+
// ---- Bind a device to a user ----
|
|
60
45
|
/** Pass a token provider `{ url, headers?, queryParams? }` or a pre-fetched Beams token string. */
|
|
61
46
|
export async function setUserId(userId, auth) {
|
|
62
47
|
assertBeams('setUserId');
|
|
@@ -65,19 +50,6 @@ export async function setUserId(userId, auth) {
|
|
|
65
50
|
else
|
|
66
51
|
await Native.setUserId(userId, auth);
|
|
67
52
|
}
|
|
68
|
-
// ---- Mode 3: raw device token (direct FCM / APNs) ----
|
|
69
|
-
/** Registers with FCM/APNs (if needed) and returns the native token. Works in any mode. */
|
|
70
|
-
export async function registerForDeviceToken() {
|
|
71
|
-
return Native.registerForDeviceToken();
|
|
72
|
-
}
|
|
73
|
-
/** Last known native token, or null if not registered yet. */
|
|
74
|
-
export async function getDevicePushToken() {
|
|
75
|
-
return Native.getDevicePushToken();
|
|
76
|
-
}
|
|
77
|
-
/** Android: deletes the FCM token (a new one is issued on next register). iOS: unregisters from APNs. */
|
|
78
|
-
export async function deleteDevicePushToken() {
|
|
79
|
-
await Native.deleteDevicePushToken();
|
|
80
|
-
}
|
|
81
53
|
// ---- Permissions ----
|
|
82
54
|
export function requestPermission() {
|
|
83
55
|
return Native.requestPermission();
|
|
@@ -131,35 +103,30 @@ export function getInitialNotification() {
|
|
|
131
103
|
}
|
|
132
104
|
// ---- Common ----
|
|
133
105
|
export async function clearAllState() {
|
|
134
|
-
|
|
135
|
-
await Native.clearAllState();
|
|
106
|
+
await Native.clearAllState();
|
|
136
107
|
}
|
|
137
108
|
export async function stop() {
|
|
138
|
-
|
|
139
|
-
await Native.deleteDevicePushToken();
|
|
140
|
-
else
|
|
141
|
-
await Native.stop();
|
|
109
|
+
await Native.stop();
|
|
142
110
|
started = false;
|
|
143
111
|
}
|
|
144
|
-
/** Adds an event listener. Events fire
|
|
112
|
+
/** Adds an event listener. Events fire for interests and users alike. */
|
|
145
113
|
export function addListener(eventName, listener) {
|
|
146
114
|
return Native.addListener(eventName, listener);
|
|
147
115
|
}
|
|
148
116
|
/** Alias for `addListener` (old package's `on()`). */
|
|
149
117
|
export const on = addListener;
|
|
150
118
|
// ---------------------------------------------------------------------------
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// is shared and works in every mode.
|
|
119
|
+
// Convenience objects. Use either or both; everything else (sounds,
|
|
120
|
+
// channels, badges, local notifications, events) works the same either way.
|
|
154
121
|
// ---------------------------------------------------------------------------
|
|
155
122
|
/**
|
|
156
|
-
*
|
|
123
|
+
* Pusher Beams **interests** (topic broadcast).
|
|
157
124
|
* Use when many devices should get the same push: "news", "order-42", "team-7".
|
|
158
125
|
* Server: `beams.publishToInterests([...], payload)`.
|
|
159
126
|
*/
|
|
160
127
|
export const Interests = {
|
|
161
|
-
/** Start Beams
|
|
162
|
-
start: (instanceId, opts = {}) => start({ ...opts, instanceId, mode: 'interests' })
|
|
128
|
+
/** Start Beams. */
|
|
129
|
+
start: (instanceId, opts = {}) => start({ ...opts, instanceId, mode: 'interests' }),
|
|
163
130
|
subscribe,
|
|
164
131
|
unsubscribe,
|
|
165
132
|
set: setSubscriptions,
|
|
@@ -170,39 +137,24 @@ export const Interests = {
|
|
|
170
137
|
stop,
|
|
171
138
|
};
|
|
172
139
|
/**
|
|
173
|
-
*
|
|
140
|
+
* Pusher Beams **authenticated users** (push to a person, across all their devices).
|
|
174
141
|
* Use when your backend thinks in user ids: "send to user-123".
|
|
175
142
|
* Server: `beams.publishToUsers(['user-123'], payload)` + a token endpoint (`beams.generateToken`).
|
|
176
143
|
*/
|
|
177
144
|
export const Users = {
|
|
178
|
-
/** Start Beams
|
|
179
|
-
start: (instanceId, opts = {}) => start({ ...opts, instanceId, mode: 'users' })
|
|
145
|
+
/** Start Beams. Call `login()` after your own sign-in. */
|
|
146
|
+
start: (instanceId, opts = {}) => start({ ...opts, instanceId, mode: 'users' }),
|
|
180
147
|
/** Bind this device to `userId`. `auth` is your token endpoint, or a Beams token you already fetched. */
|
|
181
148
|
login: setUserId,
|
|
182
149
|
/** Unbind the device (call on sign-out). */
|
|
183
150
|
logout: clearAllState,
|
|
184
151
|
stop,
|
|
185
152
|
};
|
|
186
|
-
/**
|
|
187
|
-
* Mode 3 — **direct FCM / APNs** (no Pusher at all).
|
|
188
|
-
* Use when your server already talks to FCM HTTP v1 / APNs, or you use another provider.
|
|
189
|
-
* You get the raw native token; send it to your backend and keep it fresh via the `registered` event.
|
|
190
|
-
*/
|
|
191
|
-
export const DeviceToken = {
|
|
192
|
-
/** Register with FCM/APNs and resolve the native token. */
|
|
193
|
-
start: async (opts = {}) => (await start({ ...opts, mode: 'device' })),
|
|
194
|
-
get: getDevicePushToken,
|
|
195
|
-
/** Fires whenever the token is issued or rotated. */
|
|
196
|
-
onChange: (cb) => addListener('registered', ({ deviceToken, type }) => cb({ token: deviceToken, type, platform: type === 'fcm' ? 'android' : 'ios' })),
|
|
197
|
-
/** Delete the token (call on sign-out). */
|
|
198
|
-
delete: deleteDevicePushToken,
|
|
199
|
-
};
|
|
200
153
|
export default {
|
|
201
|
-
Interests, Users,
|
|
154
|
+
Interests, Users,
|
|
202
155
|
start, getMode,
|
|
203
156
|
subscribe, unsubscribe, setSubscriptions, getSubscriptions, clearSubscriptions,
|
|
204
157
|
setUserId,
|
|
205
|
-
registerForDeviceToken, getDevicePushToken, deleteDevicePushToken,
|
|
206
158
|
requestPermission, getPermissionStatus,
|
|
207
159
|
createChannel, deleteChannel, getChannels,
|
|
208
160
|
getBadgeCount, setBadgeCount, incrementBadgeCount,
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,MAAM,MAAM,qCAAqC,CAAC;AAezD,cAAc,qCAAqC,CAAC;AAEpD,IAAI,WAAW,GAAwB,WAAW,CAAC;AACnD,IAAI,OAAO,GAAG,KAAK,CAAC;AAEpB,MAAM,MAAM,GAAG,gCAAgC,CAAC;AAEhD,SAAS,WAAW,CAAC,EAAU;IAC7B,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,yBAAyB,EAAE,KAAK,CAAC,CAAC;IACzE,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,KAAK,EAAE,+DAA+D,CAAC,CAAC;IACnG,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,MAAoB;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,WAAW,CAAC;IACxC,IAAI,MAAM,CAAC,iBAAiB,KAAK,KAAK;QAAE,MAAM,MAAM,CAAC,iBAAiB,EAAE,CAAC;IACzE,WAAW,GAAG,IAAI,CAAC;IACnB,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,sBAAsB,EAAE,CAAC;QACpD,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,iCAAiC,IAAI,SAAS,CAAC,CAAC;IACjG,MAAM,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACtC,OAAO,GAAG,IAAI,CAAC;IACf,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,oCAAoC;AACpC,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAgB;IAC9C,WAAW,CAAC,WAAW,CAAC,CAAC;IACzB,MAAM,MAAM,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AAC3C,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB;IAChD,WAAW,CAAC,aAAa,CAAC,CAAC;IAC3B,MAAM,MAAM,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;AAC9C,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,SAAmB;IACxD,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAChC,MAAM,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;AAC7C,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC,kBAAkB,EAAE,CAAC;AACrC,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,WAAW,CAAC,oBAAoB,CAAC,CAAC;IAClC,MAAM,MAAM,CAAC,oBAAoB,EAAE,CAAC;AACtC,CAAC;AAED,8CAA8C;AAC9C,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAc,EAAE,IAAe;IAC7D,WAAW,CAAC,WAAW,CAAC,CAAC;IACzB,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,MAAM,MAAM,CAAC,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;;QACvE,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,yDAAyD;AACzD,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,OAAO,MAAM,CAAC,sBAAsB,EAAE,CAAC;AACzC,CAAC;AACD,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,OAAO,MAAM,CAAC,kBAAkB,EAAE,CAAC;AACrC,CAAC;AACD,yGAAyG;AACzG,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,MAAM,MAAM,CAAC,qBAAqB,EAAE,CAAC;AACvC,CAAC;AAED,wBAAwB;AACxB,MAAM,UAAU,iBAAiB;IAC/B,OAAO,MAAM,CAAC,iBAAiB,EAAE,CAAC;AACpC,CAAC;AACD,MAAM,UAAU,mBAAmB;IACjC,OAAO,MAAM,CAAC,mBAAmB,EAAE,CAAC;AACtC,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,aAAa,CAAC,OAAmC;IAC/D,OAAO,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;AACvC,CAAC;AACD,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,OAAO,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;AAClC,CAAC;AACD,MAAM,UAAU,WAAW;IACzB,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;AAC9B,CAAC;AAED,kBAAkB;AAClB,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,aAAa,EAAE,CAAC;AAChC,CAAC;AACD,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EAAE,GAAG,CAAC;IAC9C,MAAM,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,CAAC;IACjD,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAC3B,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,wBAAwB,CAAC,CAAoB;IAC3D,OAAO,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC;AAC5C,CAAC;AACD,MAAM,UAAU,yBAAyB,CAAC,CAAwB;IAChE,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,yBAAyB,CAAC,CAAC;IAC3E,OAAO,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AACD,MAAM,UAAU,2BAA2B,CAAC,EAAU;IACpD,OAAO,MAAM,CAAC,2BAA2B,CAAC,EAAE,CAAC,CAAC;AAChD,CAAC;AACD,MAAM,UAAU,+BAA+B;IAC7C,OAAO,MAAM,CAAC,+BAA+B,EAAE,CAAC;AAClD,CAAC;AACD,MAAM,UAAU,uBAAuB;IACrC,OAAO,MAAM,CAAC,uBAAuB,EAAE,CAAC;AAC1C,CAAC;AACD,wEAAwE;AACxE,MAAM,UAAU,sBAAsB;IACpC,OAAO,MAAM,CAAC,sBAAsB,EAAE,CAAC;AACzC,CAAC;AAED,mBAAmB;AACnB,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,WAAW,KAAK,QAAQ;QAAE,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC;AAC7D,CAAC;AACD,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,IAAI,WAAW,KAAK,QAAQ;QAAE,MAAM,MAAM,CAAC,qBAAqB,EAAE,CAAC;;QAC9D,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACzB,OAAO,GAAG,KAAK,CAAC;AAClB,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,WAAW,CACzB,SAAY,EACZ,QAAoD;IAEpD,OAAO,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC;AACD,sDAAsD;AACtD,MAAM,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC;AAE9B,8EAA8E;AAC9E,qEAAqE;AACrE,2EAA2E;AAC3E,qCAAqC;AACrC,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,qCAAqC;IACrC,KAAK,EAAE,CAAC,UAAkB,EAAE,OAAwC,EAAE,EAAE,EAAE,CACxE,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;IACzE,SAAS;IACT,WAAW;IACX,GAAG,EAAE,gBAAgB;IACrB,GAAG,EAAE,gBAAgB;IACrB,KAAK,EAAE,kBAAkB;IACzB,0CAA0C;IAC1C,KAAK,EAAE,aAAa;IACpB,IAAI;CACI,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,wEAAwE;IACxE,KAAK,EAAE,CAAC,UAAkB,EAAE,OAAwC,EAAE,EAAE,EAAE,CACxE,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;IACrE,yGAAyG;IACzG,KAAK,EAAE,SAAS;IAChB,4CAA4C;IAC5C,MAAM,EAAE,aAAa;IACrB,IAAI;CACI,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,2DAA2D;IAC3D,KAAK,EAAE,KAAK,EAAE,OAAwC,EAAE,EAA4B,EAAE,CACpF,CAAC,MAAM,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAoB;IAC/D,GAAG,EAAE,kBAAkB;IACvB,qDAAqD;IACrD,QAAQ,EAAE,CAAC,EAAoC,EAAqB,EAAE,CACpE,WAAW,CAAC,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,CAClD,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAC/E;IACH,2CAA2C;IAC3C,MAAM,EAAE,qBAAqB;CACrB,CAAC;AAEX,eAAe;IACb,SAAS,EAAE,KAAK,EAAE,WAAW;IAC7B,KAAK,EAAE,OAAO;IACd,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB;IAC9E,SAAS;IACT,sBAAsB,EAAE,kBAAkB,EAAE,qBAAqB;IACjE,iBAAiB,EAAE,mBAAmB;IACtC,aAAa,EAAE,aAAa,EAAE,WAAW;IACzC,aAAa,EAAE,aAAa,EAAE,mBAAmB;IACjD,wBAAwB,EAAE,yBAAyB,EAAE,2BAA2B;IAChF,+BAA+B,EAAE,uBAAuB,EAAE,sBAAsB;IAChF,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;CAC5B,CAAC","sourcesContent":["import type { EventSubscription } from 'expo-modules-core';\n\nimport Native from './ExpoPusherPushNotificationsModule';\nimport type {\n AndroidNotificationChannel,\n BeamsAuth,\n DevicePushToken,\n ExpoPusherPushNotificationsModuleEvents,\n LocalNotification,\n PermissionStatus,\n PusherConfig,\n PusherEventName,\n PusherNotification,\n PusherTransportMode,\n ScheduledNotification,\n} from './ExpoPusherPushNotifications.types';\n\nexport * from './ExpoPusherPushNotifications.types';\n\nlet currentMode: PusherTransportMode = 'interests';\nlet started = false;\n\nconst PREFIX = 'expo-pusher-push-notifications';\n\nfunction assertBeams(fn: string): void {\n if (!started) throw new Error(`${PREFIX}: call start() before ${fn}().`);\n if (currentMode === 'device') {\n throw new Error(`${PREFIX}: ${fn}() is a Pusher Beams API and is unavailable in 'device' mode.`);\n }\n}\n\n/**\n * Starts the module in one of three modes.\n * - `interests` / `users`: starts the Pusher Beams SDK (requires `instanceId`).\n * - `device`: does NOT start Beams; registers with FCM/APNs and resolves the raw device token.\n * Resolves with the device token in `device` mode, otherwise `null`.\n */\nexport async function start(config: PusherConfig): Promise<DevicePushToken | null> {\n const mode = config.mode ?? 'interests';\n if (config.requestPermission !== false) await Native.requestPermission();\n currentMode = mode;\n if (mode === 'device') {\n const token = await Native.registerForDeviceToken();\n started = true;\n return token;\n }\n if (!config.instanceId) throw new Error(`${PREFIX}: instanceId is required for '${mode}' mode.`);\n await Native.start(config.instanceId);\n started = true;\n return null;\n}\n\nexport function getMode(): PusherTransportMode {\n return currentMode;\n}\n\n// ---- Mode 1: Beams interests ----\nexport async function subscribe(interest: string): Promise<void> {\n assertBeams('subscribe');\n await Native.addDeviceInterest(interest);\n}\nexport async function unsubscribe(interest: string): Promise<void> {\n assertBeams('unsubscribe');\n await Native.removeDeviceInterest(interest);\n}\nexport async function setSubscriptions(interests: string[]): Promise<void> {\n assertBeams('setSubscriptions');\n await Native.setDeviceInterests(interests);\n}\nexport async function getSubscriptions(): Promise<string[]> {\n assertBeams('getSubscriptions');\n return Native.getDeviceInterests();\n}\nexport async function clearSubscriptions(): Promise<void> {\n assertBeams('clearSubscriptions');\n await Native.clearDeviceInterests();\n}\n\n// ---- Mode 2: Beams authenticated users ----\n/** Pass a token provider `{ url, headers?, queryParams? }` or a pre-fetched Beams token string. */\nexport async function setUserId(userId: string, auth: BeamsAuth): Promise<void> {\n assertBeams('setUserId');\n if (typeof auth === 'string') await Native.setUserIdWithToken(userId, auth);\n else await Native.setUserId(userId, auth);\n}\n\n// ---- Mode 3: raw device token (direct FCM / APNs) ----\n/** Registers with FCM/APNs (if needed) and returns the native token. Works in any mode. */\nexport async function registerForDeviceToken(): Promise<DevicePushToken> {\n return Native.registerForDeviceToken();\n}\n/** Last known native token, or null if not registered yet. */\nexport async function getDevicePushToken(): Promise<DevicePushToken | null> {\n return Native.getDevicePushToken();\n}\n/** Android: deletes the FCM token (a new one is issued on next register). iOS: unregisters from APNs. */\nexport async function deleteDevicePushToken(): Promise<void> {\n await Native.deleteDevicePushToken();\n}\n\n// ---- Permissions ----\nexport function requestPermission(): Promise<PermissionStatus> {\n return Native.requestPermission();\n}\nexport function getPermissionStatus(): Promise<PermissionStatus> {\n return Native.getPermissionStatus();\n}\n\n// ---- Android notification channels (no-ops on iOS) ----\nexport function createChannel(channel: AndroidNotificationChannel): Promise<void> {\n return Native.createChannel(channel);\n}\nexport function deleteChannel(id: string): Promise<void> {\n return Native.deleteChannel(id);\n}\nexport function getChannels(): Promise<AndroidNotificationChannel[]> {\n return Native.getChannels();\n}\n\n// ---- Badge ----\nexport function getBadgeCount(): Promise<number> {\n return Native.getBadgeCount();\n}\nexport function setBadgeCount(count: number): Promise<void> {\n return Native.setBadgeCount(Math.max(0, Math.floor(count)));\n}\nexport async function incrementBadgeCount(by = 1): Promise<number> {\n const next = (await Native.getBadgeCount()) + by;\n await setBadgeCount(next);\n return Math.max(0, next);\n}\n\n// ---- Local notifications ----\nexport function presentLocalNotification(n: LocalNotification): Promise<string> {\n return Native.presentLocalNotification(n);\n}\nexport function scheduleLocalNotification(n: ScheduledNotification): Promise<string> {\n if (!(n.seconds >= 1)) throw new Error(`${PREFIX}: seconds must be >= 1.`);\n return Native.scheduleLocalNotification(n);\n}\nexport function cancelScheduledNotification(id: string): Promise<void> {\n return Native.cancelScheduledNotification(id);\n}\nexport function cancelAllScheduledNotifications(): Promise<void> {\n return Native.cancelAllScheduledNotifications();\n}\nexport function dismissAllNotifications(): Promise<void> {\n return Native.dismissAllNotifications();\n}\n/** The notification whose tap launched the app (cold start), if any. */\nexport function getInitialNotification(): Promise<PusherNotification | null> {\n return Native.getInitialNotification();\n}\n\n// ---- Common ----\nexport async function clearAllState(): Promise<void> {\n if (currentMode !== 'device') await Native.clearAllState();\n}\nexport async function stop(): Promise<void> {\n if (currentMode === 'device') await Native.deleteDevicePushToken();\n else await Native.stop();\n started = false;\n}\n\n/** Adds an event listener. Events fire in all modes. */\nexport function addListener<E extends PusherEventName>(\n eventName: E,\n listener: ExpoPusherPushNotificationsModuleEvents[E]\n): EventSubscription {\n return Native.addListener(eventName, listener);\n}\n/** Alias for `addListener` (old package's `on()`). */\nexport const on = addListener;\n\n// ---------------------------------------------------------------------------\n// Mode-scoped API. Pick ONE of these three and ignore the other two.\n// Everything below (sounds, channels, badges, local notifications, events)\n// is shared and works in every mode.\n// ---------------------------------------------------------------------------\n\n/**\n * Mode 1 — Pusher Beams **interests** (topic broadcast).\n * Use when many devices should get the same push: \"news\", \"order-42\", \"team-7\".\n * Server: `beams.publishToInterests([...], payload)`.\n */\nexport const Interests = {\n /** Start Beams in interests mode. */\n start: (instanceId: string, opts: { requestPermission?: boolean } = {}) =>\n start({ ...opts, instanceId, mode: 'interests' }).then(() => undefined),\n subscribe,\n unsubscribe,\n set: setSubscriptions,\n get: getSubscriptions,\n clear: clearSubscriptions,\n /** Forget everything (call on logout). */\n reset: clearAllState,\n stop,\n} as const;\n\n/**\n * Mode 2 — Pusher Beams **authenticated users** (push to a person, across all their devices).\n * Use when your backend thinks in user ids: \"send to user-123\".\n * Server: `beams.publishToUsers(['user-123'], payload)` + a token endpoint (`beams.generateToken`).\n */\nexport const Users = {\n /** Start Beams in users mode. Call `login()` after your own sign-in. */\n start: (instanceId: string, opts: { requestPermission?: boolean } = {}) =>\n start({ ...opts, instanceId, mode: 'users' }).then(() => undefined),\n /** Bind this device to `userId`. `auth` is your token endpoint, or a Beams token you already fetched. */\n login: setUserId,\n /** Unbind the device (call on sign-out). */\n logout: clearAllState,\n stop,\n} as const;\n\n/**\n * Mode 3 — **direct FCM / APNs** (no Pusher at all).\n * Use when your server already talks to FCM HTTP v1 / APNs, or you use another provider.\n * You get the raw native token; send it to your backend and keep it fresh via the `registered` event.\n */\nexport const DeviceToken = {\n /** Register with FCM/APNs and resolve the native token. */\n start: async (opts: { requestPermission?: boolean } = {}): Promise<DevicePushToken> =>\n (await start({ ...opts, mode: 'device' })) as DevicePushToken,\n get: getDevicePushToken,\n /** Fires whenever the token is issued or rotated. */\n onChange: (cb: (token: DevicePushToken) => void): EventSubscription =>\n addListener('registered', ({ deviceToken, type }) =>\n cb({ token: deviceToken, type, platform: type === 'fcm' ? 'android' : 'ios' })\n ),\n /** Delete the token (call on sign-out). */\n delete: deleteDevicePushToken,\n} as const;\n\nexport default {\n Interests, Users, DeviceToken,\n start, getMode,\n subscribe, unsubscribe, setSubscriptions, getSubscriptions, clearSubscriptions,\n setUserId,\n registerForDeviceToken, getDevicePushToken, deleteDevicePushToken,\n requestPermission, getPermissionStatus,\n createChannel, deleteChannel, getChannels,\n getBadgeCount, setBadgeCount, incrementBadgeCount,\n presentLocalNotification, scheduleLocalNotification, cancelScheduledNotification,\n cancelAllScheduledNotifications, dismissAllNotifications, getInitialNotification,\n clearAllState, stop, addListener, on,\n} as const;\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EventSubscription } from 'expo-modules-core';\n\nimport Native from './ExpoPusherPushNotificationsModule';\nimport type {\n AndroidNotificationChannel,\n BeamsAuth,\n ExpoPusherPushNotificationsModuleEvents,\n LocalNotification,\n PermissionStatus,\n PusherConfig,\n PusherEventName,\n PusherNotification,\n PusherTransportMode,\n ScheduledNotification,\n} from './ExpoPusherPushNotifications.types';\n\nexport * from './ExpoPusherPushNotifications.types';\n\nlet currentMode: PusherTransportMode = 'interests';\nlet started = false;\n\nconst PREFIX = 'expo-pusher-push-notifications';\n\nfunction assertBeams(fn: string): void {\n if (!started) throw new Error(`${PREFIX}: call start() before ${fn}().`);\n}\n\n/** Starts Pusher Beams. */\nexport async function start(config: PusherConfig): Promise<void> {\n if (!config.instanceId) throw new Error(`${PREFIX}: instanceId is required.`);\n if (config.requestPermission !== false) await Native.requestPermission();\n currentMode = config.mode ?? 'interests';\n await Native.start(config.instanceId);\n started = true;\n}\n\nexport function getMode(): PusherTransportMode {\n return currentMode;\n}\n\n// ---- Interests (topics) ----\nexport async function subscribe(interest: string): Promise<void> {\n assertBeams('subscribe');\n await Native.addDeviceInterest(interest);\n}\nexport async function unsubscribe(interest: string): Promise<void> {\n assertBeams('unsubscribe');\n await Native.removeDeviceInterest(interest);\n}\nexport async function setSubscriptions(interests: string[]): Promise<void> {\n assertBeams('setSubscriptions');\n await Native.setDeviceInterests(interests);\n}\nexport async function getSubscriptions(): Promise<string[]> {\n assertBeams('getSubscriptions');\n return Native.getDeviceInterests();\n}\nexport async function clearSubscriptions(): Promise<void> {\n assertBeams('clearSubscriptions');\n await Native.clearDeviceInterests();\n}\n\n// ---- Bind a device to a user ----\n/** Pass a token provider `{ url, headers?, queryParams? }` or a pre-fetched Beams token string. */\nexport async function setUserId(userId: string, auth: BeamsAuth): Promise<void> {\n assertBeams('setUserId');\n if (typeof auth === 'string') await Native.setUserIdWithToken(userId, auth);\n else await Native.setUserId(userId, auth);\n}\n\n// ---- Permissions ----\nexport function requestPermission(): Promise<PermissionStatus> {\n return Native.requestPermission();\n}\nexport function getPermissionStatus(): Promise<PermissionStatus> {\n return Native.getPermissionStatus();\n}\n\n// ---- Android notification channels (no-ops on iOS) ----\nexport function createChannel(channel: AndroidNotificationChannel): Promise<void> {\n return Native.createChannel(channel);\n}\nexport function deleteChannel(id: string): Promise<void> {\n return Native.deleteChannel(id);\n}\nexport function getChannels(): Promise<AndroidNotificationChannel[]> {\n return Native.getChannels();\n}\n\n// ---- Badge ----\nexport function getBadgeCount(): Promise<number> {\n return Native.getBadgeCount();\n}\nexport function setBadgeCount(count: number): Promise<void> {\n return Native.setBadgeCount(Math.max(0, Math.floor(count)));\n}\nexport async function incrementBadgeCount(by = 1): Promise<number> {\n const next = (await Native.getBadgeCount()) + by;\n await setBadgeCount(next);\n return Math.max(0, next);\n}\n\n// ---- Local notifications ----\nexport function presentLocalNotification(n: LocalNotification): Promise<string> {\n return Native.presentLocalNotification(n);\n}\nexport function scheduleLocalNotification(n: ScheduledNotification): Promise<string> {\n if (!(n.seconds >= 1)) throw new Error(`${PREFIX}: seconds must be >= 1.`);\n return Native.scheduleLocalNotification(n);\n}\nexport function cancelScheduledNotification(id: string): Promise<void> {\n return Native.cancelScheduledNotification(id);\n}\nexport function cancelAllScheduledNotifications(): Promise<void> {\n return Native.cancelAllScheduledNotifications();\n}\nexport function dismissAllNotifications(): Promise<void> {\n return Native.dismissAllNotifications();\n}\n/** The notification whose tap launched the app (cold start), if any. */\nexport function getInitialNotification(): Promise<PusherNotification | null> {\n return Native.getInitialNotification();\n}\n\n// ---- Common ----\nexport async function clearAllState(): Promise<void> {\n await Native.clearAllState();\n}\nexport async function stop(): Promise<void> {\n await Native.stop();\n started = false;\n}\n\n/** Adds an event listener. Events fire for interests and users alike. */\nexport function addListener<E extends PusherEventName>(\n eventName: E,\n listener: ExpoPusherPushNotificationsModuleEvents[E]\n): EventSubscription {\n return Native.addListener(eventName, listener);\n}\n/** Alias for `addListener` (old package's `on()`). */\nexport const on = addListener;\n\n// ---------------------------------------------------------------------------\n// Convenience objects. Use either or both; everything else (sounds,\n// channels, badges, local notifications, events) works the same either way.\n// ---------------------------------------------------------------------------\n\n/**\n * Pusher Beams **interests** (topic broadcast).\n * Use when many devices should get the same push: \"news\", \"order-42\", \"team-7\".\n * Server: `beams.publishToInterests([...], payload)`.\n */\nexport const Interests = {\n /** Start Beams. */\n start: (instanceId: string, opts: { requestPermission?: boolean } = {}) =>\n start({ ...opts, instanceId, mode: 'interests' }),\n subscribe,\n unsubscribe,\n set: setSubscriptions,\n get: getSubscriptions,\n clear: clearSubscriptions,\n /** Forget everything (call on logout). */\n reset: clearAllState,\n stop,\n} as const;\n\n/**\n * Pusher Beams **authenticated users** (push to a person, across all their devices).\n * Use when your backend thinks in user ids: \"send to user-123\".\n * Server: `beams.publishToUsers(['user-123'], payload)` + a token endpoint (`beams.generateToken`).\n */\nexport const Users = {\n /** Start Beams. Call `login()` after your own sign-in. */\n start: (instanceId: string, opts: { requestPermission?: boolean } = {}) =>\n start({ ...opts, instanceId, mode: 'users' }),\n /** Bind this device to `userId`. `auth` is your token endpoint, or a Beams token you already fetched. */\n login: setUserId,\n /** Unbind the device (call on sign-out). */\n logout: clearAllState,\n stop,\n} as const;\n\nexport default {\n Interests, Users,\n start, getMode,\n subscribe, unsubscribe, setSubscriptions, getSubscriptions, clearSubscriptions,\n setUserId,\n requestPermission, getPermissionStatus,\n createChannel, deleteChannel, getChannels,\n getBadgeCount, setBadgeCount, incrementBadgeCount,\n presentLocalNotification, scheduleLocalNotification, cancelScheduledNotification,\n cancelAllScheduledNotifications, dismissAllNotifications, getInitialNotification,\n clearAllState, stop, addListener, on,\n} as const;\n"]}
|
package/docs/api.md
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
# Full API
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`Interests` and `Users` are thin wrappers over these flat functions. Use whichever you like. The flat ones are kept for people coming from the old packages.
|
|
4
4
|
|
|
5
|
-
| Function |
|
|
5
|
+
| Function | Used for | Notes |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| `start({ mode?,
|
|
8
|
-
| `getMode()` |
|
|
9
|
-
| `subscribe` / `unsubscribe` / `setSubscriptions` / `getSubscriptions` / `clearSubscriptions` | interests
|
|
10
|
-
| `setUserId(userId, { url, headers?, queryParams? } \| token)` |
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `addListener(event, cb)` / `on(event, cb)` | all | Return an `EventSubscription`. |
|
|
7
|
+
| `start({ instanceId, mode?, requestPermission? })` | everything | `mode` (optional) is the Beams auth path: `interests` (default) or `users`. |
|
|
8
|
+
| `getMode()` | everything | |
|
|
9
|
+
| `subscribe` / `unsubscribe` / `setSubscriptions` / `getSubscriptions` / `clearSubscriptions` | interests | Work on both platforms. |
|
|
10
|
+
| `setUserId(userId, { url, headers?, queryParams? } \| token)` | binding a user | |
|
|
11
|
+
| `requestPermission()` / `getPermissionStatus()` | everything | |
|
|
12
|
+
| `createChannel` / `deleteChannel` / `getChannels` | Android only | |
|
|
13
|
+
| `getBadgeCount` / `setBadgeCount` / `incrementBadgeCount` | everything | |
|
|
14
|
+
| `presentLocalNotification` / `scheduleLocalNotification` / `cancelScheduledNotification` / `cancelAllScheduledNotifications` / `dismissAllNotifications` | everything | |
|
|
15
|
+
| `getInitialNotification()` | everything | |
|
|
16
|
+
| `clearAllState()` / `stop()` | everything | |
|
|
17
|
+
| `addListener(event, cb)` / `on(event, cb)` | everything | Return an `EventSubscription`. |
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
---
|
package/docs/extras.md
CHANGED
package/docs/interests.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Interests (topics)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use interests when many devices should get the same push. Think topics: `news`, `order-42`, `team-7`.
|
|
4
4
|
You need a Pusher Beams instance. Your server calls `publishToInterests`.
|
|
5
5
|
|
|
6
6
|
## Client
|
package/docs/migrating.md
CHANGED
|
@@ -17,7 +17,11 @@ Here is what the ZeptInc and b8ne packages had, and where it lives now:
|
|
|
17
17
|
| `aps.data.incrementBadge` (b8ne, iOS) | Supported on iOS and Android, plus full badge APIs. |
|
|
18
18
|
| Android launch-intent notification delivery (b8ne) | `getInitialNotification()` and an `opened` event on tap. |
|
|
19
19
|
|
|
20
|
-
Neither original had custom sounds, notification channels, local
|
|
20
|
+
Neither original had custom sounds, notification channels, or local and scheduled notifications. Those are new here.
|
|
21
|
+
|
|
22
|
+
## Coming from 0.3
|
|
23
|
+
|
|
24
|
+
0.4 removes raw device-token support (`DeviceToken`, `registerForDeviceToken`, `getDevicePushToken`, `deleteDevicePushToken`, and `mode: 'device'`). This package is Pusher Beams only now. If you need a raw FCM / APNs token for your own server, use [`expo-notifications`](https://docs.expo.dev/versions/latest/sdk/notifications/) and `getDevicePushTokenAsync()` instead.
|
|
21
25
|
|
|
22
26
|
## Steps
|
|
23
27
|
|
package/docs/setup.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# Setup
|
|
2
2
|
|
|
3
|
-
You need this for every mode.
|
|
4
|
-
|
|
5
3
|
## Install
|
|
6
4
|
|
|
7
5
|
```sh
|
|
@@ -17,7 +15,6 @@ npx expo install expo-pusher-push-notifications
|
|
|
17
15
|
"android": { "package": "com.you.app", "googleServicesFile": "./google-services.json" },
|
|
18
16
|
"plugins": [
|
|
19
17
|
["expo-pusher-push-notifications", {
|
|
20
|
-
"mode": "interests",
|
|
21
18
|
"instanceId": "YOUR_BEAMS_INSTANCE_ID",
|
|
22
19
|
"apsEnvironment": "development",
|
|
23
20
|
"sounds": ["./assets/sounds/ding.wav", "./assets/sounds/alarm.caf"],
|
|
@@ -40,23 +37,22 @@ Then run `npx expo prebuild` and build a development build. Expo Go cannot load
|
|
|
40
37
|
|
|
41
38
|
| Option | Default | What it does |
|
|
42
39
|
|---|---|---|
|
|
43
|
-
| `mode` | `interests` | Written to Info.plist and the manifest
|
|
44
|
-
| `instanceId` | none | Beams instance ID.
|
|
40
|
+
| `mode` | `interests` | Which Beams auth path your app uses: `interests`, or `users` if you bind devices to signed-in users. Written to Info.plist and the manifest. You can leave it out. |
|
|
41
|
+
| `instanceId` | none | Beams instance ID. |
|
|
45
42
|
| `apsEnvironment` | `development` | Sets the `aps-environment` entitlement. Use `production` for TestFlight and App Store builds. |
|
|
46
43
|
| `sounds` | `[]` | Sound files to bundle. **Android:** copied to `res/raw` with names lower-cased to `[a-z0-9_]`. **iOS:** copied into the app target and added to its resources. iOS needs `.wav`, `.caf`, or `.aiff` under 30 s. |
|
|
47
44
|
| `androidChannels` | `[]` | Channels created at startup. Set `sound` to a file name from `sounds`, or `null` for a silent channel. |
|
|
48
45
|
| `androidDefaultChannelId` | first channel, or `default` | Used by FCM/Beams notifications that don't name a channel. Also sets `com.google.firebase.messaging.default_notification_channel_id`. |
|
|
49
46
|
| `iosShowInForeground` | `true` | Shows the banner, plays the sound, and updates the badge while the app is in the foreground. |
|
|
50
47
|
|
|
51
|
-
The plugin also adds the `remote-notification` background mode on iOS. On Android, the library manifest declares one FCM service
|
|
48
|
+
The plugin also adds the `remote-notification` background mode on iOS. On Android, the library manifest declares one FCM service (it extends the Beams `MessagingService`), the `POST_NOTIFICATIONS` permission, and a receiver for scheduled local notifications.
|
|
52
49
|
|
|
53
50
|
## Console setup
|
|
54
51
|
|
|
55
|
-
1. **iOS
|
|
56
|
-
2. **Android
|
|
57
|
-
3. **
|
|
58
|
-
4. **
|
|
59
|
-
5. **Mode 3:** your backend keeps the APNs key and the Firebase service account and calls FCM/APNs itself (examples below).
|
|
52
|
+
1. **iOS:** enable the Push Notifications capability. The plugin sets the entitlement. Create an APNs auth key (.p8).
|
|
53
|
+
2. **Android:** create a Firebase project, download `google-services.json`, and point `android.googleServicesFile` at it.
|
|
54
|
+
3. **Beams:** create a Beams instance in the [Pusher dashboard](https://dashboard.pusher.com/beams), then upload your APNs key and your FCM service account to it.
|
|
55
|
+
4. **If you bind devices to users:** add an authenticated endpoint to your backend that returns `{ "token": beamsClient.generateToken(userId) }` (see the [Beams auth docs](https://pusher.com/docs/beams/guides/publish-to-specific-user/)).
|
|
60
56
|
|
|
61
57
|
## Permissions
|
|
62
58
|
|
|
@@ -65,7 +61,7 @@ The plugin also adds the `remote-notification` background mode on iOS. On Androi
|
|
|
65
61
|
## Things that conflict
|
|
66
62
|
|
|
67
63
|
- **iOS:** the module becomes the `UNUserNotificationCenter` delegate so it can show notifications in the foreground and report taps. Don't use it together with another library that also claims the delegate, such as `expo-notifications`.
|
|
68
|
-
- **Android:** only one `FirebaseMessagingService` can receive FCM messages. This module's service handles
|
|
64
|
+
- **Android:** only one `FirebaseMessagingService` can receive FCM messages. This module's service handles it, so remove other FCM services such as `expo-notifications` or `@react-native-firebase/messaging`'s service.
|
|
69
65
|
|
|
70
66
|
|
|
71
67
|
---
|
package/docs/users.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Bind a device to a user
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
You need a Pusher Beams instance and one auth endpoint on your server.
|
|
3
|
+
When your backend thinks in user ids ("send this to user-123"), bind the device to the signed-in user and Beams will deliver to every device they're signed in on.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
How it works: after your own sign-in, the app calls `Users.login(userId, …)`. Beams then asks **your API** for a short-lived auth token, which proves this user really is `user-123`. So you need one authenticated endpoint on your server.
|
|
6
|
+
|
|
7
|
+
## In the app
|
|
7
8
|
|
|
8
9
|
```ts
|
|
9
10
|
import { Users } from 'expo-pusher-push-notifications';
|
|
@@ -23,7 +24,7 @@ await Users.login('user-123', beamsToken);
|
|
|
23
24
|
await Users.logout();
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
##
|
|
27
|
+
## On your server (Node)
|
|
27
28
|
|
|
28
29
|
```js
|
|
29
30
|
// GET /pusher/beams-auth (must be authenticated, and user_id must match the session)
|
|
@@ -49,7 +50,7 @@ See Pusher's [auth guide](https://pusher.com/docs/beams/guides/publish-to-specif
|
|
|
49
50
|
| `Users.logout()` | Unbinds the device from the user. |
|
|
50
51
|
| `Users.stop()` | Stop Beams. |
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
You can still subscribe to interests on a device that's bound to a user.
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
---
|
|
@@ -26,7 +26,6 @@ struct LocalNotificationRecord: Record {
|
|
|
26
26
|
public class ExpoPusherPushNotificationsModule: Module {
|
|
27
27
|
private let beams = PushNotifications.shared
|
|
28
28
|
private var beamsStarted = false
|
|
29
|
-
private var tokenWaiters: [Promise] = []
|
|
30
29
|
private var initialNotification: [String: Any]?
|
|
31
30
|
private lazy var centerDelegate = NotificationCenterDelegate(module: self)
|
|
32
31
|
|
|
@@ -40,8 +39,6 @@ public class ExpoPusherPushNotificationsModule: Module {
|
|
|
40
39
|
ExpoPusherAppDelegateSubscriber.onTokenError = { [weak self] error in
|
|
41
40
|
guard let self = self else { return }
|
|
42
41
|
self.sendEvent("error", ["code": "E_APNS_REGISTRATION", "message": error.localizedDescription])
|
|
43
|
-
let waiters = self.tokenWaiters; self.tokenWaiters = []
|
|
44
|
-
waiters.forEach { $0.reject("E_APNS_REGISTRATION", error.localizedDescription) }
|
|
45
42
|
}
|
|
46
43
|
ExpoPusherAppDelegateSubscriber.onNotification = { [weak self] userInfo, state in
|
|
47
44
|
self?.handleRemote(userInfo, state: state)
|
|
@@ -87,22 +84,6 @@ public class ExpoPusherPushNotificationsModule: Module {
|
|
|
87
84
|
self.beams.stop { self.beamsStarted = false; promise.resolve(nil) }
|
|
88
85
|
}
|
|
89
86
|
|
|
90
|
-
// ---- Raw device token (APNs) ----
|
|
91
|
-
AsyncFunction("registerForDeviceToken") { (promise: Promise) in
|
|
92
|
-
if let token = ExpoPusherAppDelegateSubscriber.lastToken {
|
|
93
|
-
promise.resolve(Self.tokenDict(token)); return
|
|
94
|
-
}
|
|
95
|
-
self.tokenWaiters.append(promise)
|
|
96
|
-
DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() }
|
|
97
|
-
}
|
|
98
|
-
AsyncFunction("getDevicePushToken") { () -> [String: Any]? in
|
|
99
|
-
ExpoPusherAppDelegateSubscriber.lastToken.map(Self.tokenDict)
|
|
100
|
-
}
|
|
101
|
-
AsyncFunction("deleteDevicePushToken") {
|
|
102
|
-
ExpoPusherAppDelegateSubscriber.lastToken = nil
|
|
103
|
-
DispatchQueue.main.async { UIApplication.shared.unregisterForRemoteNotifications() }
|
|
104
|
-
}
|
|
105
|
-
|
|
106
87
|
// ---- Permissions ----
|
|
107
88
|
AsyncFunction("requestPermission") { (promise: Promise) in
|
|
108
89
|
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in
|
|
@@ -149,10 +130,8 @@ public class ExpoPusherPushNotificationsModule: Module {
|
|
|
149
130
|
|
|
150
131
|
private func handleToken(_ token: Data) {
|
|
151
132
|
if beamsStarted { beams.registerDeviceToken(token) }
|
|
152
|
-
let
|
|
153
|
-
sendEvent("registered", ["deviceToken":
|
|
154
|
-
let waiters = tokenWaiters; tokenWaiters = []
|
|
155
|
-
waiters.forEach { $0.resolve(dict) }
|
|
133
|
+
let hex = token.map { String(format: "%02x", $0) }.joined()
|
|
134
|
+
sendEvent("registered", ["deviceToken": hex, "type": "apns"])
|
|
156
135
|
}
|
|
157
136
|
|
|
158
137
|
fileprivate func handleRemote(_ userInfo: [AnyHashable: Any], state: String) {
|
|
@@ -197,10 +176,6 @@ public class ExpoPusherPushNotificationsModule: Module {
|
|
|
197
176
|
return UNNotificationSound(named: UNNotificationSoundName(rawValue: name))
|
|
198
177
|
}
|
|
199
178
|
|
|
200
|
-
static func tokenDict(_ token: Data) -> [String: Any] {
|
|
201
|
-
["type": "apns", "platform": "ios", "token": token.map { String(format: "%02x", $0) }.joined()]
|
|
202
|
-
}
|
|
203
|
-
|
|
204
179
|
static func payload(userInfo: [AnyHashable: Any], state: String) -> [String: Any] {
|
|
205
180
|
let alertAny = (userInfo["aps"] as? [String: Any])?["alert"]
|
|
206
181
|
let alert = alertAny as? [String: Any]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-pusher-push-notifications",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Pusher Beams push notifications for Expo (interests and authenticated users), with custom sounds, Android channels, badges and local notifications",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
@@ -24,8 +24,7 @@
|
|
|
24
24
|
"push-notifications",
|
|
25
25
|
"config-plugin",
|
|
26
26
|
"fcm",
|
|
27
|
-
"apns"
|
|
28
|
-
"device-token"
|
|
27
|
+
"apns"
|
|
29
28
|
],
|
|
30
29
|
"repository": "https://github.com/baraa-rasheed/expo-pusher-push-notifications",
|
|
31
30
|
"bugs": {
|
package/plugin/build/index.d.ts
CHANGED
|
@@ -10,12 +10,12 @@ export interface PusherPluginChannel {
|
|
|
10
10
|
showBadge?: boolean;
|
|
11
11
|
}
|
|
12
12
|
export interface PusherPluginProps {
|
|
13
|
-
/** Pusher Beams instance ID
|
|
13
|
+
/** Pusher Beams instance ID. */
|
|
14
14
|
instanceId?: string;
|
|
15
15
|
/** APNs environment for the aps-environment entitlement. Defaults to "development". */
|
|
16
16
|
apsEnvironment?: 'development' | 'production';
|
|
17
|
-
/**
|
|
18
|
-
mode?: 'interests' | 'users'
|
|
17
|
+
/** Beams auth path written to native config (`interests` or `users`). Defaults to "interests". */
|
|
18
|
+
mode?: 'interests' | 'users';
|
|
19
19
|
/**
|
|
20
20
|
* Sound files (paths relative to the project root) to bundle. Android: copied to res/raw
|
|
21
21
|
* (lower-cased, must be [a-z0-9_]). iOS: added to the main bundle. Use .wav/.caf/.aiff for iOS (<30s).
|
package/docs/direct.md
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Mode 3: direct FCM / APNs (no Pusher)
|
|
2
|
-
|
|
3
|
-
**Use this when** your server already sends to FCM and APNs itself, or you use some other push provider.
|
|
4
|
-
No Beams instance needed. You get the raw native token and do what you want with it.
|
|
5
|
-
|
|
6
|
-
## Client
|
|
7
|
-
|
|
8
|
-
```ts
|
|
9
|
-
import { DeviceToken } from 'expo-pusher-push-notifications';
|
|
10
|
-
|
|
11
|
-
const token = await DeviceToken.start();
|
|
12
|
-
// { type: 'fcm', platform: 'android', token: 'dYz…' }
|
|
13
|
-
// { type: 'apns', platform: 'ios', token: 'a1b2…' }
|
|
14
|
-
await api.saveDevice(token);
|
|
15
|
-
|
|
16
|
-
// Tokens rotate. Keep your backend in sync.
|
|
17
|
-
DeviceToken.onChange((t) => api.saveDevice(t));
|
|
18
|
-
|
|
19
|
-
await DeviceToken.get(); // last known token, or null
|
|
20
|
-
await DeviceToken.delete(); // on sign-out
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Calling a Beams function (`Interests.*`, `Users.*`) in this mode throws.
|
|
24
|
-
|
|
25
|
-
## Server
|
|
26
|
-
|
|
27
|
-
Server, FCM HTTP v1 (Android):
|
|
28
|
-
|
|
29
|
-
```http
|
|
30
|
-
POST https://fcm.googleapis.com/v1/projects/<project-id>/messages:send
|
|
31
|
-
Authorization: Bearer <OAuth2 token from your service account>
|
|
32
|
-
|
|
33
|
-
{ "message": { "token": "<fcm token>",
|
|
34
|
-
"notification": { "title": "Hi", "body": "Direct from FCM" },
|
|
35
|
-
"android": { "notification": { "channel_id": "messages", "sound": "ding" } },
|
|
36
|
-
"data": { "orderId": "42" } } }
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Data-only FCM messages that include `title`/`body` keys in `data` (plus optional `sound`, `android_channel_id`, and `incrementBadge: "true"`) are shown by this module with the right channel and sound, including in the background.
|
|
40
|
-
|
|
41
|
-
Server, APNs (iOS):
|
|
42
|
-
|
|
43
|
-
```http
|
|
44
|
-
POST https://api.push.apple.com/3/device/<apns hex token> (api.sandbox.push.apple.com for development)
|
|
45
|
-
authorization: bearer <JWT signed with your .p8>
|
|
46
|
-
apns-topic: com.you.app
|
|
47
|
-
apns-push-type: alert
|
|
48
|
-
|
|
49
|
-
{ "aps": { "alert": { "title": "Hi", "body": "Direct from APNs" }, "sound": "ding.wav", "badge": 1 }, "orderId": "42" }
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## API
|
|
53
|
-
|
|
54
|
-
| Call | What it does |
|
|
55
|
-
|---|---|
|
|
56
|
-
| `DeviceToken.start({ requestPermission? })` | Registers with FCM/APNs, resolves the token. |
|
|
57
|
-
| `DeviceToken.get()` | Last known token or `null`. |
|
|
58
|
-
| `DeviceToken.onChange(cb)` | Fires when the token is issued or rotated. |
|
|
59
|
-
| `DeviceToken.delete()` | Android: FCM `deleteToken`. iOS: unregister. |
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
[← Back to README](../README.md)
|