@knocklabs/expo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +146 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/modules/push/KnockExpoPushNotificationProvider.js +2 -0
- package/dist/cjs/modules/push/KnockExpoPushNotificationProvider.js.map +1 -0
- package/dist/esm/index.mjs +7 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/modules/push/KnockExpoPushNotificationProvider.mjs +176 -0
- package/dist/esm/modules/push/KnockExpoPushNotificationProvider.mjs.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/modules/push/KnockExpoPushNotificationProvider.d.ts +19 -0
- package/dist/types/modules/push/KnockExpoPushNotificationProvider.d.ts.map +1 -0
- package/dist/types/modules/push/index.d.ts +2 -0
- package/dist/types/modules/push/index.d.ts.map +1 -0
- package/package.json +80 -0
- package/src/index.ts +2 -0
- package/src/modules/push/KnockExpoPushNotificationProvider.tsx +323 -0
- package/src/modules/push/index.ts +1 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# Knock Expo SDK
|
2
|
+
|
3
|
+
A set of components for integrating [Knock](https://knock.app) in-app notifications into an Expo + React Native application.
|
4
|
+
|
5
|
+
> Not using Expo? See our vanilla [React Native SDK](../react-native/README.md).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Via NPM:
|
10
|
+
|
11
|
+
```
|
12
|
+
npm install @knocklabs/expo
|
13
|
+
```
|
14
|
+
|
15
|
+
Via Yarn:
|
16
|
+
|
17
|
+
```
|
18
|
+
yarn add @knocklabs/expo
|
19
|
+
```
|
20
|
+
|
21
|
+
## Migrating from `@knocklabs/react-native`
|
22
|
+
|
23
|
+
As of `@knocklabs/react-native` v0.4.0, `KnockExpoPushNotificationProvider` has moved to our Expo SDK. To migrate:
|
24
|
+
|
25
|
+
1. Remove `@knocklabs/react-native` from your project
|
26
|
+
|
27
|
+
NPM:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
npm uninstall @knocklabs/react-native
|
31
|
+
```
|
32
|
+
|
33
|
+
Yarn:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
yarn remove @knocklabs/react-native
|
37
|
+
```
|
38
|
+
|
39
|
+
1. Install `@knocklabs/expo`
|
40
|
+
|
41
|
+
NPM:
|
42
|
+
|
43
|
+
```bash
|
44
|
+
npm install @knocklabs/expo
|
45
|
+
```
|
46
|
+
|
47
|
+
Yarn:
|
48
|
+
|
49
|
+
```bash
|
50
|
+
yarn add @knocklabs/expo
|
51
|
+
```
|
52
|
+
|
53
|
+
1. Update any import statements from `@knocklabs/react-native` to `@knocklabs/expo`
|
54
|
+
|
55
|
+
From:
|
56
|
+
|
57
|
+
```js
|
58
|
+
import {
|
59
|
+
KnockExpoPushNotificationProvider,
|
60
|
+
KnockFeedProvider,
|
61
|
+
KnockProvider,
|
62
|
+
NotificationIconButton,
|
63
|
+
} from "@knocklabs/react-native";
|
64
|
+
```
|
65
|
+
|
66
|
+
To:
|
67
|
+
|
68
|
+
```js
|
69
|
+
import {
|
70
|
+
KnockExpoPushNotificationProvider,
|
71
|
+
KnockFeedProvider,
|
72
|
+
KnockProvider,
|
73
|
+
NotificationIconButton,
|
74
|
+
} from "@knocklabs/expo";
|
75
|
+
```
|
76
|
+
|
77
|
+
## Configuration
|
78
|
+
|
79
|
+
To configure the feed you will need:
|
80
|
+
|
81
|
+
1. A public API key (found in the Knock dashboard)
|
82
|
+
1. A user ID, and optionally an auth token for production environments
|
83
|
+
1. If integrating an in-app feed, a feed channel ID (found in the Knock dashboard)
|
84
|
+
|
85
|
+
## Usage
|
86
|
+
|
87
|
+
You can integrate the feed into your app as follows:
|
88
|
+
|
89
|
+
```jsx
|
90
|
+
import {
|
91
|
+
KnockFeedProvider,
|
92
|
+
KnockProvider,
|
93
|
+
NotificationFeedContainer,
|
94
|
+
} from "@knocklabs/expo";
|
95
|
+
|
96
|
+
const YourAppLayout = () => {
|
97
|
+
const [isVisible, setIsVisible] = useState(false);
|
98
|
+
const notifButtonRef = useRef(null);
|
99
|
+
|
100
|
+
return (
|
101
|
+
<KnockProvider apiKey={process.env.KNOCK_PUBLIC_API_KEY} userId={userId}>
|
102
|
+
<KnockFeedProvider feedId={process.env.KNOCK_FEED_ID}>
|
103
|
+
<NotificationFeedContainer>
|
104
|
+
<Text>Notifications go in here!</Text>
|
105
|
+
</NotificationFeedContainer>
|
106
|
+
</KnockFeedProvider>
|
107
|
+
</KnockProvider>
|
108
|
+
);
|
109
|
+
};
|
110
|
+
```
|
111
|
+
|
112
|
+
## Headless usage
|
113
|
+
|
114
|
+
Alternatively, if you don't want to use our components you can render the feed in a headless mode using our hooks:
|
115
|
+
|
116
|
+
```jsx
|
117
|
+
import { useAuthenticatedKnockClient, useNotifications } from "@knocklabs/expo";
|
118
|
+
import create from "zustand";
|
119
|
+
|
120
|
+
const YourAppLayout = () => {
|
121
|
+
const knockClient = useAuthenticatedKnockClient(
|
122
|
+
process.env.KNOCK_PUBLIC_API_KEY,
|
123
|
+
currentUser.id,
|
124
|
+
);
|
125
|
+
|
126
|
+
const notificationFeed = useNotifications(
|
127
|
+
knockClient,
|
128
|
+
process.env.KNOCK_FEED_ID,
|
129
|
+
);
|
130
|
+
|
131
|
+
const useNotificationStore = create(notificationFeed.store);
|
132
|
+
const { metadata } = useNotificationStore();
|
133
|
+
|
134
|
+
useEffect(() => {
|
135
|
+
notificationFeed.fetch();
|
136
|
+
}, [notificationFeed]);
|
137
|
+
|
138
|
+
return <Text>Total unread: {metadata.unread_count}</Text>;
|
139
|
+
};
|
140
|
+
```
|
141
|
+
|
142
|
+
## Related links
|
143
|
+
|
144
|
+
- [Signup for Knock](https://knock.app)
|
145
|
+
- [Knock documentation](https://docs.knock.app)
|
146
|
+
- [Knock dashboard](https://dashboard.knock.app)
|
@@ -0,0 +1,2 @@
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./modules/push/KnockExpoPushNotificationProvider.js"),o=require("@knocklabs/react-native");exports.KnockExpoPushNotificationProvider=t.KnockExpoPushNotificationProvider;exports.useExpoPushNotifications=t.useExpoPushNotifications;Object.keys(o).forEach(e=>{e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:()=>o[e]})});
|
2
|
+
//# sourceMappingURL=index.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
@@ -0,0 +1,2 @@
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const w=require("react/jsx-runtime"),_=require("@knocklabs/react-core"),j=require("expo-constants"),D=require("expo-device"),R=require("expo-notifications"),i=require("react"),q=e=>e&&typeof e=="object"&&"default"in e?e:{default:e};function C(e){if(e&&typeof e=="object"&&"default"in e)return e;const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const c in e)if(c!=="default"){const l=Object.getOwnPropertyDescriptor(e,c);Object.defineProperty(a,c,l.get?l:{enumerable:!0,get:()=>e[c]})}}return a.default=e,Object.freeze(a)}const h=q(j),O=C(D),r=C(R);r.setNotificationHandler({handleNotification:async()=>({shouldShowAlert:!0,shouldPlaySound:!0,shouldSetBadge:!0})});const A=async e=>({shouldShowAlert:!0,shouldPlaySound:!0,shouldSetBadge:!0}),x=i.createContext(void 0);async function H(){const{status:e}=await r.getPermissionsAsync();if(e!=="granted"){const{status:a}=await r.requestPermissionsAsync();return a}return e}async function F(){try{return!h.default.expoConfig||!h.default.expoConfig.extra||!h.default.expoConfig.extra.eas?(console.error("[Knock] Expo Project ID is not defined in the app configuration."),null):await r.getExpoPushTokenAsync({projectId:h.default.expoConfig.extra.eas.projectId})}catch(e){return console.error("[Knock] Error getting Expo push token:",e),null}}async function M(){return O.isDevice?await H()!=="granted"?(console.warn("[Knock] Push notification permission not granted"),null):F():(console.warn("[Knock] Must use physical device for Push Notifications"),null)}const B=({knockExpoChannelId:e,customNotificationHandler:a,children:c,autoRegister:l=!0})=>{const[d,m]=i.useState(null),n=_.useKnockClient(),[g,v]=i.useState(()=>()=>{}),[k,y]=i.useState(()=>()=>{}),S=i.useCallback(t=>{v(()=>t)},[]),b=i.useCallback(t=>{y(()=>t)},[]),p=i.useCallback(async()=>{try{n.log("[Knock] Registering for push notifications");const t=await M();n.log(`[Knock] Token received: ${t==null?void 0:t.data}`),t!=null&&t.data&&(n.log(`[Knock] Setting push token: ${t.data}`),m(t.data))}catch(t){console.error("[Knock] Error registering for push notifications:",t)}},[n]),P=i.useCallback(async(t,s)=>{const o=t.request.content.data.knock_message_id;return n.messages.updateStatus(o,s)},[n]),u=i.useCallback(async(t,s)=>n.user.setChannelData({channelId:s,channelData:{tokens:t}}),[n]),N=i.useCallback(async(t,s)=>{n.user.getChannelData({channelId:s}).then(o=>{const f=o.data.tokens;if(!f.includes(t))return f.push(t),u(f,s);n.log("[Knock] registerPushTokenToChannel success")}).catch(o=>u([t],s))},[n,u]),K=i.useCallback(async(t,s)=>{n.user.getChannelData({channelId:s}).then(o=>{const T=o.data.tokens.filter(E=>E!==t);return n.log("unregisterPushTokenFromChannel success"),u(T,s)}).catch(o=>{console.error("[Knock] Error unregistering push token from channel:",o)})},[n,u]);return i.useEffect(()=>{r.setNotificationHandler({handleNotification:a??A}),l&&p().then(()=>{d&&N(d,e).then(o=>{n.log("[Knock] setChannelData success")}).catch(o=>{console.error("[Knock] Error in setting push token or channel data",o)})}).catch(o=>{console.error("[Knock] Error in setting push token or channel data",o)});const t=r.addNotificationReceivedListener(o=>{n.log("[Knock] Expo Push Notification received in foreground:"),P(o,"interacted"),g(o)}),s=r.addNotificationResponseReceivedListener(o=>{n.log("[Knock] Expo Push Notification was interacted with"),P(o.notification,"interacted"),k(o)});return()=>{r.removeNotificationSubscription(t),r.removeNotificationSubscription(s)}},[p,g,k,a,d,e,n]),w.jsx(x.Provider,{value:{expoPushToken:d,registerForPushNotifications:p,registerPushTokenToChannel:N,unregisterPushTokenFromChannel:K,onNotificationReceived:S,onNotificationTapped:b},children:c})},L=()=>{const e=i.useContext(x);if(e===void 0)throw new Error("[Knock] useExpoPushNotifications must be used within a PushNotificationProvider");return e};exports.KnockExpoPushNotificationProvider=B;exports.useExpoPushNotifications=L;
|
2
|
+
//# sourceMappingURL=KnockExpoPushNotificationProvider.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.js","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import {\n ChannelData,\n Message,\n MessageEngagementStatus,\n} from \"@knocklabs/client\";\nimport { useKnockClient } from \"@knocklabs/react-core\";\nimport Constants from \"expo-constants\";\nimport * as Device from \"expo-device\";\nimport * as Notifications from \"expo-notifications\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\n\nexport interface KnockExpoPushNotificationContextType {\n expoPushToken: string | null;\n registerForPushNotifications: () => Promise<void>;\n registerPushTokenToChannel(token: string, channelId: string): Promise<void>;\n unregisterPushTokenFromChannel(\n token: string,\n channelId: string,\n ): Promise<void>;\n onNotificationReceived: (\n handler: (notification: Notifications.Notification) => void,\n ) => void;\n onNotificationTapped: (\n handler: (response: Notifications.NotificationResponse) => void,\n ) => void;\n}\n\nNotifications.setNotificationHandler({\n handleNotification: async () => {\n return {\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: true,\n };\n },\n});\n\nconst defaultNotificationHandler = async (\n _notification: Notifications.Notification,\n): Promise<Notifications.NotificationBehavior> => {\n return {\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: true,\n };\n};\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\nexport interface KnockExpoPushNotificationProviderProps {\n knockExpoChannelId: string;\n customNotificationHandler?: (\n notification: Notifications.Notification,\n ) => Promise<Notifications.NotificationBehavior>;\n children?: React.ReactElement;\n autoRegister?: boolean;\n}\n\nasync function requestPushPermissionIfNeeded(): Promise<string> {\n const { status: existingStatus } = await Notifications.getPermissionsAsync();\n\n if (existingStatus !== \"granted\") {\n const { status } = await Notifications.requestPermissionsAsync();\n return status;\n }\n\n return existingStatus;\n}\n\nasync function getExpoPushToken(): Promise<Notifications.ExpoPushToken | null> {\n try {\n if (\n !Constants.expoConfig ||\n !Constants.expoConfig.extra ||\n !Constants.expoConfig.extra.eas\n ) {\n console.error(\n \"[Knock] Expo Project ID is not defined in the app configuration.\",\n );\n return null;\n }\n const token = await Notifications.getExpoPushTokenAsync({\n projectId: Constants.expoConfig.extra.eas.projectId,\n });\n return token;\n } catch (error) {\n console.error(\"[Knock] Error getting Expo push token:\", error);\n return null;\n }\n}\n\nasync function requestPermissionAndGetPushToken(): Promise<Notifications.ExpoPushToken | null> {\n // Check for device support\n if (!Device.isDevice) {\n console.warn(\"[Knock] Must use physical device for Push Notifications\");\n return null;\n }\n\n const permissionStatus = await requestPushPermissionIfNeeded();\n\n if (permissionStatus !== \"granted\") {\n console.warn(\"[Knock] Push notification permission not granted\");\n return null;\n }\n\n return getExpoPushToken();\n}\n\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n children,\n autoRegister = true,\n}) => {\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n const knockClient = useKnockClient();\n\n const [notificationReceivedHandler, setNotificationReceivedHandler] =\n useState<(notification: Notifications.Notification) => void>(\n () => () => {},\n );\n\n const [notificationTappedHandler, setNotificationTappedHandler] = useState<\n (response: Notifications.NotificationResponse) => void\n >(() => () => {});\n\n const handleNotificationReceived = useCallback(\n (handler: (notification: Notifications.Notification) => void) => {\n setNotificationReceivedHandler(() => handler);\n },\n [],\n );\n\n const handleNotificationTapped = useCallback(\n (handler: (response: Notifications.NotificationResponse) => void) => {\n setNotificationTappedHandler(() => handler);\n },\n [],\n );\n\n const registerForPushNotifications = useCallback(async (): Promise<void> => {\n try {\n knockClient.log(`[Knock] Registering for push notifications`);\n const token = await requestPermissionAndGetPushToken();\n knockClient.log(`[Knock] Token received: ${token?.data}`);\n if (token?.data) {\n knockClient.log(`[Knock] Setting push token: ${token.data}`);\n setExpoPushToken(token.data);\n }\n } catch (error) {\n console.error(`[Knock] Error registering for push notifications:`, error);\n }\n }, [knockClient]);\n\n const updateKnockMessageStatusFromNotification = useCallback(\n async (\n notification: Notifications.Notification,\n status: MessageEngagementStatus,\n ): Promise<Message> => {\n const messageId = notification.request.content.data[\"knock_message_id\"];\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n const registerNewTokenDataOnServer = useCallback(\n async (tokens: string[], channelId: string): Promise<ChannelData> => {\n return knockClient.user.setChannelData({\n channelId: channelId,\n channelData: { tokens: tokens },\n });\n },\n [knockClient],\n );\n\n const registerPushTokenToChannel = useCallback(\n async (token: string, channelId: string): Promise<void> => {\n knockClient.user\n .getChannelData({ channelId: channelId })\n .then((result: ChannelData) => {\n const tokens: string[] = result.data[\"tokens\"];\n if (!tokens.includes(token)) {\n tokens.push(token);\n return registerNewTokenDataOnServer(tokens, channelId);\n }\n knockClient.log(\"[Knock] registerPushTokenToChannel success\");\n })\n .catch((_) => {\n // No data registered on that channel for that user, we'll create a new record\n return registerNewTokenDataOnServer([token], channelId);\n });\n },\n [knockClient, registerNewTokenDataOnServer],\n );\n\n const unregisterPushTokenFromChannel = useCallback(\n async (token: string, channelId: string): Promise<void> => {\n knockClient.user\n .getChannelData({ channelId: channelId })\n .then((result: ChannelData) => {\n const tokens: string[] = result.data[\"tokens\"];\n const updatedTokens = tokens.filter(\n (channelToken) => channelToken !== token,\n );\n knockClient.log(\"unregisterPushTokenFromChannel success\");\n return registerNewTokenDataOnServer(updatedTokens, channelId);\n })\n .catch((error) => {\n console.error(\n `[Knock] Error unregistering push token from channel:`,\n error,\n );\n });\n },\n [knockClient, registerNewTokenDataOnServer],\n );\n\n useEffect(() => {\n Notifications.setNotificationHandler({\n handleNotification:\n customNotificationHandler ?? defaultNotificationHandler,\n });\n\n if (autoRegister) {\n registerForPushNotifications()\n .then(() => {\n if (expoPushToken) {\n registerPushTokenToChannel(expoPushToken, knockExpoChannelId)\n .then((_result) => {\n knockClient.log(\"[Knock] setChannelData success\");\n })\n .catch((_error) => {\n console.error(\n \"[Knock] Error in setting push token or channel data\",\n _error,\n );\n });\n }\n })\n .catch((_error) => {\n console.error(\n \"[Knock] Error in setting push token or channel data\",\n _error,\n );\n });\n }\n\n const notificationReceivedSubscription =\n Notifications.addNotificationReceivedListener((notification) => {\n knockClient.log(\n \"[Knock] Expo Push Notification received in foreground:\",\n );\n updateKnockMessageStatusFromNotification(notification, \"interacted\");\n notificationReceivedHandler(notification);\n });\n\n const notificationResponseSubscription =\n Notifications.addNotificationResponseReceivedListener((response) => {\n knockClient.log(\"[Knock] Expo Push Notification was interacted with\");\n updateKnockMessageStatusFromNotification(\n response.notification,\n \"interacted\",\n );\n notificationTappedHandler(response);\n });\n\n return () => {\n Notifications.removeNotificationSubscription(\n notificationReceivedSubscription,\n );\n Notifications.removeNotificationSubscription(\n notificationResponseSubscription,\n );\n };\n\n // TODO: Remove when possible and ensure dependency array is correct\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n registerForPushNotifications,\n notificationReceivedHandler,\n notificationTappedHandler,\n customNotificationHandler,\n expoPushToken,\n knockExpoChannelId,\n knockClient,\n ]);\n\n return (\n <KnockExpoPushNotificationContext.Provider\n value={{\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived: handleNotificationReceived,\n onNotificationTapped: handleNotificationTapped,\n }}\n >\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\nexport const useExpoPushNotifications =\n (): KnockExpoPushNotificationContextType => {\n const context = useContext(KnockExpoPushNotificationContext);\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a PushNotificationProvider\",\n );\n }\n return context;\n };\n"],"names":["Notifications","defaultNotificationHandler","_notification","KnockExpoPushNotificationContext","createContext","requestPushPermissionIfNeeded","existingStatus","status","getExpoPushToken","Constants","error","requestPermissionAndGetPushToken","Device","KnockExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","children","autoRegister","expoPushToken","setExpoPushToken","useState","knockClient","useKnockClient","notificationReceivedHandler","setNotificationReceivedHandler","notificationTappedHandler","setNotificationTappedHandler","handleNotificationReceived","useCallback","handler","handleNotificationTapped","registerForPushNotifications","token","updateKnockMessageStatusFromNotification","notification","messageId","registerNewTokenDataOnServer","tokens","channelId","registerPushTokenToChannel","result","_","unregisterPushTokenFromChannel","updatedTokens","channelToken","useEffect","_result","_error","notificationReceivedSubscription","notificationResponseSubscription","response","jsx","useExpoPushNotifications","context","useContext"],"mappings":"ipBAiCAA,EAAc,uBAAuB,CACnC,mBAAoB,UACX,CACL,gBAAiB,GACjB,gBAAiB,GACjB,eAAgB,EAAA,EAGtB,CAAC,EAED,MAAMC,EAA6B,MACjCC,IAEO,CACL,gBAAiB,GACjB,gBAAiB,GACjB,eAAgB,EAAA,GAIdC,EAAmCC,EAAAA,cAEvC,MAAS,EAWX,eAAeC,GAAiD,CAC9D,KAAM,CAAE,OAAQC,CAAA,EAAmB,MAAMN,EAAc,oBAAoB,EAE3E,GAAIM,IAAmB,UAAW,CAChC,KAAM,CAAE,OAAAC,CAAW,EAAA,MAAMP,EAAc,wBAAwB,EACxD,OAAAO,CACT,CAEO,OAAAD,CACT,CAEA,eAAeE,GAAgE,CACzE,GAAA,CAEA,MAAA,CAACC,EAAU,QAAA,YACX,CAACA,UAAU,WAAW,OACtB,CAACA,EAAA,QAAU,WAAW,MAAM,KAEpB,QAAA,MACN,kEAAA,EAEK,MAEK,MAAMT,EAAc,sBAAsB,CACtD,UAAWS,EAAAA,QAAU,WAAW,MAAM,IAAI,SAAA,CAC3C,QAEMC,EAAO,CACN,eAAA,MAAM,yCAA0CA,CAAK,EACtD,IACT,CACF,CAEA,eAAeC,GAAgF,CAEzF,OAACC,EAAO,SAKa,MAAMP,MAEN,WACvB,QAAQ,KAAK,kDAAkD,EACxD,MAGFG,EAAiB,GAXtB,QAAQ,KAAK,yDAAyD,EAC/D,KAWX,CAEO,MAAMK,EAET,CAAC,CACH,mBAAAC,EACA,0BAAAC,EACA,SAAAC,EACA,aAAAC,EAAe,EACjB,IAAM,CACJ,KAAM,CAACC,EAAeC,CAAgB,EAAIC,WAAwB,IAAI,EAChEC,EAAcC,EAAAA,iBAEd,CAACC,EAA6BC,CAA8B,EAChEJ,EAAA,SACE,IAAM,IAAM,CAAC,CAAA,EAGX,CAACK,EAA2BC,CAA4B,EAAIN,EAAAA,SAEhE,IAAM,IAAM,CAAA,CAAE,EAEVO,EAA6BC,EAAA,YAChCC,GAAgE,CAC/DL,EAA+B,IAAMK,CAAO,CAC9C,EACA,CAAC,CAAA,EAGGC,EAA2BF,EAAA,YAC9BC,GAAoE,CACnEH,EAA6B,IAAMG,CAAO,CAC5C,EACA,CAAC,CAAA,EAGGE,EAA+BH,EAAAA,YAAY,SAA2B,CACtE,GAAA,CACFP,EAAY,IAAI,4CAA4C,EACtD,MAAAW,EAAQ,MAAMrB,IACpBU,EAAY,IAAI,2BAA2BW,GAAA,YAAAA,EAAO,IAAI,EAAE,EACpDA,GAAA,MAAAA,EAAO,OACTX,EAAY,IAAI,+BAA+BW,EAAM,IAAI,EAAE,EAC3Db,EAAiBa,EAAM,IAAI,SAEtBtB,EAAO,CACN,QAAA,MAAM,oDAAqDA,CAAK,CAC1E,CAAA,EACC,CAACW,CAAW,CAAC,EAEVY,EAA2CL,EAAA,YAC/C,MACEM,EACA3B,IACqB,CACrB,MAAM4B,EAAYD,EAAa,QAAQ,QAAQ,KAAK,iBACpD,OAAOb,EAAY,SAAS,aAAac,EAAW5B,CAAM,CAC5D,EACA,CAACc,CAAW,CAAA,EAGRe,EAA+BR,EAAA,YACnC,MAAOS,EAAkBC,IAChBjB,EAAY,KAAK,eAAe,CACrC,UAAAiB,EACA,YAAa,CAAE,OAAAD,CAAe,CAAA,CAC/B,EAEH,CAAChB,CAAW,CAAA,EAGRkB,EAA6BX,EAAA,YACjC,MAAOI,EAAeM,IAAqC,CAC7CjB,EAAA,KACT,eAAe,CAAE,UAAAiB,CAAA,CAAsB,EACvC,KAAME,GAAwB,CACvB,MAAAH,EAAmBG,EAAO,KAAK,OACrC,GAAI,CAACH,EAAO,SAASL,CAAK,EACxB,OAAAK,EAAO,KAAKL,CAAK,EACVI,EAA6BC,EAAQC,CAAS,EAEvDjB,EAAY,IAAI,4CAA4C,CAAA,CAC7D,EACA,MAAOoB,GAECL,EAA6B,CAACJ,CAAK,EAAGM,CAAS,CACvD,CACL,EACA,CAACjB,EAAae,CAA4B,CAAA,EAGtCM,EAAiCd,EAAA,YACrC,MAAOI,EAAeM,IAAqC,CAC7CjB,EAAA,KACT,eAAe,CAAE,UAAAiB,CAAA,CAAsB,EACvC,KAAME,GAAwB,CAE7B,MAAMG,EADmBH,EAAO,KAAK,OACR,OAC1BI,GAAiBA,IAAiBZ,CAAA,EAErC,OAAAX,EAAY,IAAI,wCAAwC,EACjDe,EAA6BO,EAAeL,CAAS,CAAA,CAC7D,EACA,MAAO5B,GAAU,CACR,QAAA,MACN,uDACAA,CAAA,CACF,CACD,CACL,EACA,CAACW,EAAae,CAA4B,CAAA,EAG5CS,OAAAA,EAAAA,UAAU,IAAM,CACd7C,EAAc,uBAAuB,CACnC,mBACEe,GAA6Bd,CAAA,CAChC,EAEGgB,GAC2Bc,EAAA,EAC1B,KAAK,IAAM,CACNb,GACFqB,EAA2BrB,EAAeJ,CAAkB,EACzD,KAAMgC,GAAY,CACjBzB,EAAY,IAAI,gCAAgC,CAAA,CACjD,EACA,MAAO0B,GAAW,CACT,QAAA,MACN,sDACAA,CAAA,CACF,CACD,CACL,CACD,EACA,MAAOA,GAAW,CACT,QAAA,MACN,sDACAA,CAAA,CACF,CACD,EAGL,MAAMC,EACJhD,EAAc,gCAAiCkC,GAAiB,CAClDb,EAAA,IACV,wDAAA,EAEFY,EAAyCC,EAAc,YAAY,EACnEX,EAA4BW,CAAY,CAAA,CACzC,EAEGe,EACJjD,EAAc,wCAAyCkD,GAAa,CAClE7B,EAAY,IAAI,oDAAoD,EACpEY,EACEiB,EAAS,aACT,YAAA,EAEFzB,EAA0ByB,CAAQ,CAAA,CACnC,EAEH,MAAO,IAAM,CACGlD,EAAA,+BACZgD,CAAA,EAEYhD,EAAA,+BACZiD,CAAA,CACF,CACF,EAIC,CACDlB,EACAR,EACAE,EACAV,EACAG,EACAJ,EACAO,CAAA,CACD,EAGC8B,EAAA,IAAChD,EAAiC,SAAjC,CACC,MAAO,CACL,cAAAe,EACA,6BAAAa,EACA,2BAAAQ,EACA,+BAAAG,EACA,uBAAwBf,EACxB,qBAAsBG,CACxB,EAEC,SAAAd,CAAA,CAAA,CAGP,EAEaoC,EACX,IAA4C,CACpC,MAAAC,EAAUC,aAAWnD,CAAgC,EAC3D,GAAIkD,IAAY,OACd,MAAM,IAAI,MACR,iFAAA,EAGG,OAAAA,CACT"}
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import { KnockExpoPushNotificationProvider as r, useExpoPushNotifications as t } from "./modules/push/KnockExpoPushNotificationProvider.mjs";
|
2
|
+
export * from "@knocklabs/react-native";
|
3
|
+
export {
|
4
|
+
r as KnockExpoPushNotificationProvider,
|
5
|
+
t as useExpoPushNotifications
|
6
|
+
};
|
7
|
+
//# sourceMappingURL=index.mjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
|
@@ -0,0 +1,176 @@
|
|
1
|
+
import { jsx as R } from "react/jsx-runtime";
|
2
|
+
import { useKnockClient as D } from "@knocklabs/react-core";
|
3
|
+
import f from "expo-constants";
|
4
|
+
import * as b from "expo-device";
|
5
|
+
import * as s from "expo-notifications";
|
6
|
+
import { createContext as A, useState as h, useCallback as r, useEffect as H, useContext as _ } from "react";
|
7
|
+
s.setNotificationHandler({
|
8
|
+
handleNotification: async () => ({
|
9
|
+
shouldShowAlert: !0,
|
10
|
+
shouldPlaySound: !0,
|
11
|
+
shouldSetBadge: !0
|
12
|
+
})
|
13
|
+
});
|
14
|
+
const j = async (n) => ({
|
15
|
+
shouldShowAlert: !0,
|
16
|
+
shouldPlaySound: !0,
|
17
|
+
shouldSetBadge: !0
|
18
|
+
}), m = A(void 0);
|
19
|
+
async function q() {
|
20
|
+
const { status: n } = await s.getPermissionsAsync();
|
21
|
+
if (n !== "granted") {
|
22
|
+
const { status: c } = await s.requestPermissionsAsync();
|
23
|
+
return c;
|
24
|
+
}
|
25
|
+
return n;
|
26
|
+
}
|
27
|
+
async function F() {
|
28
|
+
try {
|
29
|
+
return !f.expoConfig || !f.expoConfig.extra || !f.expoConfig.extra.eas ? (console.error(
|
30
|
+
"[Knock] Expo Project ID is not defined in the app configuration."
|
31
|
+
), null) : await s.getExpoPushTokenAsync({
|
32
|
+
projectId: f.expoConfig.extra.eas.projectId
|
33
|
+
});
|
34
|
+
} catch (n) {
|
35
|
+
return console.error("[Knock] Error getting Expo push token:", n), null;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
async function B() {
|
39
|
+
return b.isDevice ? await q() !== "granted" ? (console.warn("[Knock] Push notification permission not granted"), null) : F() : (console.warn("[Knock] Must use physical device for Push Notifications"), null);
|
40
|
+
}
|
41
|
+
const I = ({
|
42
|
+
knockExpoChannelId: n,
|
43
|
+
customNotificationHandler: c,
|
44
|
+
children: N,
|
45
|
+
autoRegister: x = !0
|
46
|
+
}) => {
|
47
|
+
const [u, v] = h(null), o = D(), [p, K] = h(
|
48
|
+
() => () => {
|
49
|
+
}
|
50
|
+
), [g, y] = h(() => () => {
|
51
|
+
}), C = r(
|
52
|
+
(t) => {
|
53
|
+
K(() => t);
|
54
|
+
},
|
55
|
+
[]
|
56
|
+
), S = r(
|
57
|
+
(t) => {
|
58
|
+
y(() => t);
|
59
|
+
},
|
60
|
+
[]
|
61
|
+
), l = r(async () => {
|
62
|
+
try {
|
63
|
+
o.log("[Knock] Registering for push notifications");
|
64
|
+
const t = await B();
|
65
|
+
o.log(`[Knock] Token received: ${t == null ? void 0 : t.data}`), t != null && t.data && (o.log(`[Knock] Setting push token: ${t.data}`), v(t.data));
|
66
|
+
} catch (t) {
|
67
|
+
console.error("[Knock] Error registering for push notifications:", t);
|
68
|
+
}
|
69
|
+
}, [o]), k = r(
|
70
|
+
async (t, i) => {
|
71
|
+
const e = t.request.content.data.knock_message_id;
|
72
|
+
return o.messages.updateStatus(e, i);
|
73
|
+
},
|
74
|
+
[o]
|
75
|
+
), a = r(
|
76
|
+
async (t, i) => o.user.setChannelData({
|
77
|
+
channelId: i,
|
78
|
+
channelData: { tokens: t }
|
79
|
+
}),
|
80
|
+
[o]
|
81
|
+
), P = r(
|
82
|
+
async (t, i) => {
|
83
|
+
o.user.getChannelData({ channelId: i }).then((e) => {
|
84
|
+
const d = e.data.tokens;
|
85
|
+
if (!d.includes(t))
|
86
|
+
return d.push(t), a(d, i);
|
87
|
+
o.log("[Knock] registerPushTokenToChannel success");
|
88
|
+
}).catch((e) => a([t], i));
|
89
|
+
},
|
90
|
+
[o, a]
|
91
|
+
), T = r(
|
92
|
+
async (t, i) => {
|
93
|
+
o.user.getChannelData({ channelId: i }).then((e) => {
|
94
|
+
const E = e.data.tokens.filter(
|
95
|
+
(w) => w !== t
|
96
|
+
);
|
97
|
+
return o.log("unregisterPushTokenFromChannel success"), a(E, i);
|
98
|
+
}).catch((e) => {
|
99
|
+
console.error(
|
100
|
+
"[Knock] Error unregistering push token from channel:",
|
101
|
+
e
|
102
|
+
);
|
103
|
+
});
|
104
|
+
},
|
105
|
+
[o, a]
|
106
|
+
);
|
107
|
+
return H(() => {
|
108
|
+
s.setNotificationHandler({
|
109
|
+
handleNotification: c ?? j
|
110
|
+
}), x && l().then(() => {
|
111
|
+
u && P(u, n).then((e) => {
|
112
|
+
o.log("[Knock] setChannelData success");
|
113
|
+
}).catch((e) => {
|
114
|
+
console.error(
|
115
|
+
"[Knock] Error in setting push token or channel data",
|
116
|
+
e
|
117
|
+
);
|
118
|
+
});
|
119
|
+
}).catch((e) => {
|
120
|
+
console.error(
|
121
|
+
"[Knock] Error in setting push token or channel data",
|
122
|
+
e
|
123
|
+
);
|
124
|
+
});
|
125
|
+
const t = s.addNotificationReceivedListener((e) => {
|
126
|
+
o.log(
|
127
|
+
"[Knock] Expo Push Notification received in foreground:"
|
128
|
+
), k(e, "interacted"), p(e);
|
129
|
+
}), i = s.addNotificationResponseReceivedListener((e) => {
|
130
|
+
o.log("[Knock] Expo Push Notification was interacted with"), k(
|
131
|
+
e.notification,
|
132
|
+
"interacted"
|
133
|
+
), g(e);
|
134
|
+
});
|
135
|
+
return () => {
|
136
|
+
s.removeNotificationSubscription(
|
137
|
+
t
|
138
|
+
), s.removeNotificationSubscription(
|
139
|
+
i
|
140
|
+
);
|
141
|
+
};
|
142
|
+
}, [
|
143
|
+
l,
|
144
|
+
p,
|
145
|
+
g,
|
146
|
+
c,
|
147
|
+
u,
|
148
|
+
n,
|
149
|
+
o
|
150
|
+
]), /* @__PURE__ */ R(
|
151
|
+
m.Provider,
|
152
|
+
{
|
153
|
+
value: {
|
154
|
+
expoPushToken: u,
|
155
|
+
registerForPushNotifications: l,
|
156
|
+
registerPushTokenToChannel: P,
|
157
|
+
unregisterPushTokenFromChannel: T,
|
158
|
+
onNotificationReceived: C,
|
159
|
+
onNotificationTapped: S
|
160
|
+
},
|
161
|
+
children: N
|
162
|
+
}
|
163
|
+
);
|
164
|
+
}, O = () => {
|
165
|
+
const n = _(m);
|
166
|
+
if (n === void 0)
|
167
|
+
throw new Error(
|
168
|
+
"[Knock] useExpoPushNotifications must be used within a PushNotificationProvider"
|
169
|
+
);
|
170
|
+
return n;
|
171
|
+
};
|
172
|
+
export {
|
173
|
+
I as KnockExpoPushNotificationProvider,
|
174
|
+
O as useExpoPushNotifications
|
175
|
+
};
|
176
|
+
//# sourceMappingURL=KnockExpoPushNotificationProvider.mjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.mjs","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import {\n ChannelData,\n Message,\n MessageEngagementStatus,\n} from \"@knocklabs/client\";\nimport { useKnockClient } from \"@knocklabs/react-core\";\nimport Constants from \"expo-constants\";\nimport * as Device from \"expo-device\";\nimport * as Notifications from \"expo-notifications\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from \"react\";\n\nexport interface KnockExpoPushNotificationContextType {\n expoPushToken: string | null;\n registerForPushNotifications: () => Promise<void>;\n registerPushTokenToChannel(token: string, channelId: string): Promise<void>;\n unregisterPushTokenFromChannel(\n token: string,\n channelId: string,\n ): Promise<void>;\n onNotificationReceived: (\n handler: (notification: Notifications.Notification) => void,\n ) => void;\n onNotificationTapped: (\n handler: (response: Notifications.NotificationResponse) => void,\n ) => void;\n}\n\nNotifications.setNotificationHandler({\n handleNotification: async () => {\n return {\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: true,\n };\n },\n});\n\nconst defaultNotificationHandler = async (\n _notification: Notifications.Notification,\n): Promise<Notifications.NotificationBehavior> => {\n return {\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: true,\n };\n};\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\nexport interface KnockExpoPushNotificationProviderProps {\n knockExpoChannelId: string;\n customNotificationHandler?: (\n notification: Notifications.Notification,\n ) => Promise<Notifications.NotificationBehavior>;\n children?: React.ReactElement;\n autoRegister?: boolean;\n}\n\nasync function requestPushPermissionIfNeeded(): Promise<string> {\n const { status: existingStatus } = await Notifications.getPermissionsAsync();\n\n if (existingStatus !== \"granted\") {\n const { status } = await Notifications.requestPermissionsAsync();\n return status;\n }\n\n return existingStatus;\n}\n\nasync function getExpoPushToken(): Promise<Notifications.ExpoPushToken | null> {\n try {\n if (\n !Constants.expoConfig ||\n !Constants.expoConfig.extra ||\n !Constants.expoConfig.extra.eas\n ) {\n console.error(\n \"[Knock] Expo Project ID is not defined in the app configuration.\",\n );\n return null;\n }\n const token = await Notifications.getExpoPushTokenAsync({\n projectId: Constants.expoConfig.extra.eas.projectId,\n });\n return token;\n } catch (error) {\n console.error(\"[Knock] Error getting Expo push token:\", error);\n return null;\n }\n}\n\nasync function requestPermissionAndGetPushToken(): Promise<Notifications.ExpoPushToken | null> {\n // Check for device support\n if (!Device.isDevice) {\n console.warn(\"[Knock] Must use physical device for Push Notifications\");\n return null;\n }\n\n const permissionStatus = await requestPushPermissionIfNeeded();\n\n if (permissionStatus !== \"granted\") {\n console.warn(\"[Knock] Push notification permission not granted\");\n return null;\n }\n\n return getExpoPushToken();\n}\n\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n children,\n autoRegister = true,\n}) => {\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n const knockClient = useKnockClient();\n\n const [notificationReceivedHandler, setNotificationReceivedHandler] =\n useState<(notification: Notifications.Notification) => void>(\n () => () => {},\n );\n\n const [notificationTappedHandler, setNotificationTappedHandler] = useState<\n (response: Notifications.NotificationResponse) => void\n >(() => () => {});\n\n const handleNotificationReceived = useCallback(\n (handler: (notification: Notifications.Notification) => void) => {\n setNotificationReceivedHandler(() => handler);\n },\n [],\n );\n\n const handleNotificationTapped = useCallback(\n (handler: (response: Notifications.NotificationResponse) => void) => {\n setNotificationTappedHandler(() => handler);\n },\n [],\n );\n\n const registerForPushNotifications = useCallback(async (): Promise<void> => {\n try {\n knockClient.log(`[Knock] Registering for push notifications`);\n const token = await requestPermissionAndGetPushToken();\n knockClient.log(`[Knock] Token received: ${token?.data}`);\n if (token?.data) {\n knockClient.log(`[Knock] Setting push token: ${token.data}`);\n setExpoPushToken(token.data);\n }\n } catch (error) {\n console.error(`[Knock] Error registering for push notifications:`, error);\n }\n }, [knockClient]);\n\n const updateKnockMessageStatusFromNotification = useCallback(\n async (\n notification: Notifications.Notification,\n status: MessageEngagementStatus,\n ): Promise<Message> => {\n const messageId = notification.request.content.data[\"knock_message_id\"];\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n const registerNewTokenDataOnServer = useCallback(\n async (tokens: string[], channelId: string): Promise<ChannelData> => {\n return knockClient.user.setChannelData({\n channelId: channelId,\n channelData: { tokens: tokens },\n });\n },\n [knockClient],\n );\n\n const registerPushTokenToChannel = useCallback(\n async (token: string, channelId: string): Promise<void> => {\n knockClient.user\n .getChannelData({ channelId: channelId })\n .then((result: ChannelData) => {\n const tokens: string[] = result.data[\"tokens\"];\n if (!tokens.includes(token)) {\n tokens.push(token);\n return registerNewTokenDataOnServer(tokens, channelId);\n }\n knockClient.log(\"[Knock] registerPushTokenToChannel success\");\n })\n .catch((_) => {\n // No data registered on that channel for that user, we'll create a new record\n return registerNewTokenDataOnServer([token], channelId);\n });\n },\n [knockClient, registerNewTokenDataOnServer],\n );\n\n const unregisterPushTokenFromChannel = useCallback(\n async (token: string, channelId: string): Promise<void> => {\n knockClient.user\n .getChannelData({ channelId: channelId })\n .then((result: ChannelData) => {\n const tokens: string[] = result.data[\"tokens\"];\n const updatedTokens = tokens.filter(\n (channelToken) => channelToken !== token,\n );\n knockClient.log(\"unregisterPushTokenFromChannel success\");\n return registerNewTokenDataOnServer(updatedTokens, channelId);\n })\n .catch((error) => {\n console.error(\n `[Knock] Error unregistering push token from channel:`,\n error,\n );\n });\n },\n [knockClient, registerNewTokenDataOnServer],\n );\n\n useEffect(() => {\n Notifications.setNotificationHandler({\n handleNotification:\n customNotificationHandler ?? defaultNotificationHandler,\n });\n\n if (autoRegister) {\n registerForPushNotifications()\n .then(() => {\n if (expoPushToken) {\n registerPushTokenToChannel(expoPushToken, knockExpoChannelId)\n .then((_result) => {\n knockClient.log(\"[Knock] setChannelData success\");\n })\n .catch((_error) => {\n console.error(\n \"[Knock] Error in setting push token or channel data\",\n _error,\n );\n });\n }\n })\n .catch((_error) => {\n console.error(\n \"[Knock] Error in setting push token or channel data\",\n _error,\n );\n });\n }\n\n const notificationReceivedSubscription =\n Notifications.addNotificationReceivedListener((notification) => {\n knockClient.log(\n \"[Knock] Expo Push Notification received in foreground:\",\n );\n updateKnockMessageStatusFromNotification(notification, \"interacted\");\n notificationReceivedHandler(notification);\n });\n\n const notificationResponseSubscription =\n Notifications.addNotificationResponseReceivedListener((response) => {\n knockClient.log(\"[Knock] Expo Push Notification was interacted with\");\n updateKnockMessageStatusFromNotification(\n response.notification,\n \"interacted\",\n );\n notificationTappedHandler(response);\n });\n\n return () => {\n Notifications.removeNotificationSubscription(\n notificationReceivedSubscription,\n );\n Notifications.removeNotificationSubscription(\n notificationResponseSubscription,\n );\n };\n\n // TODO: Remove when possible and ensure dependency array is correct\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n registerForPushNotifications,\n notificationReceivedHandler,\n notificationTappedHandler,\n customNotificationHandler,\n expoPushToken,\n knockExpoChannelId,\n knockClient,\n ]);\n\n return (\n <KnockExpoPushNotificationContext.Provider\n value={{\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived: handleNotificationReceived,\n onNotificationTapped: handleNotificationTapped,\n }}\n >\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\nexport const useExpoPushNotifications =\n (): KnockExpoPushNotificationContextType => {\n const context = useContext(KnockExpoPushNotificationContext);\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a PushNotificationProvider\",\n );\n }\n return context;\n };\n"],"names":["Notifications","defaultNotificationHandler","_notification","KnockExpoPushNotificationContext","createContext","requestPushPermissionIfNeeded","existingStatus","status","getExpoPushToken","Constants","error","requestPermissionAndGetPushToken","Device","KnockExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","children","autoRegister","expoPushToken","setExpoPushToken","useState","knockClient","useKnockClient","notificationReceivedHandler","setNotificationReceivedHandler","notificationTappedHandler","setNotificationTappedHandler","handleNotificationReceived","useCallback","handler","handleNotificationTapped","registerForPushNotifications","token","updateKnockMessageStatusFromNotification","notification","messageId","registerNewTokenDataOnServer","tokens","channelId","registerPushTokenToChannel","result","_","unregisterPushTokenFromChannel","updatedTokens","channelToken","useEffect","_result","_error","notificationReceivedSubscription","notificationResponseSubscription","response","jsx","useExpoPushNotifications","context","useContext"],"mappings":"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"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,yBAAyB,CAAC"}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { default as React } from 'react';
|
2
|
+
import * as Notifications from "expo-notifications";
|
3
|
+
export interface KnockExpoPushNotificationContextType {
|
4
|
+
expoPushToken: string | null;
|
5
|
+
registerForPushNotifications: () => Promise<void>;
|
6
|
+
registerPushTokenToChannel(token: string, channelId: string): Promise<void>;
|
7
|
+
unregisterPushTokenFromChannel(token: string, channelId: string): Promise<void>;
|
8
|
+
onNotificationReceived: (handler: (notification: Notifications.Notification) => void) => void;
|
9
|
+
onNotificationTapped: (handler: (response: Notifications.NotificationResponse) => void) => void;
|
10
|
+
}
|
11
|
+
export interface KnockExpoPushNotificationProviderProps {
|
12
|
+
knockExpoChannelId: string;
|
13
|
+
customNotificationHandler?: (notification: Notifications.Notification) => Promise<Notifications.NotificationBehavior>;
|
14
|
+
children?: React.ReactElement;
|
15
|
+
autoRegister?: boolean;
|
16
|
+
}
|
17
|
+
export declare const KnockExpoPushNotificationProvider: React.FC<KnockExpoPushNotificationProviderProps>;
|
18
|
+
export declare const useExpoPushNotifications: () => KnockExpoPushNotificationContextType;
|
19
|
+
//# sourceMappingURL=KnockExpoPushNotificationProvider.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAMN,MAAM,OAAO,CAAC;AAEf,MAAM,WAAW,oCAAoC;IACnD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,4BAA4B,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,8BAA8B,CAC5B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,sBAAsB,EAAE,CACtB,OAAO,EAAE,CAAC,YAAY,EAAE,aAAa,CAAC,YAAY,KAAK,IAAI,KACxD,IAAI,CAAC;IACV,oBAAoB,EAAE,CACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC,oBAAoB,KAAK,IAAI,KAC5D,IAAI,CAAC;CACX;AA0BD,MAAM,WAAW,sCAAsC;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,CAAC,EAAE,CAC1B,YAAY,EAAE,aAAa,CAAC,YAAY,KACrC,OAAO,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;IACjD,QAAQ,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAoDD,eAAO,MAAM,iCAAiC,EAAE,KAAK,CAAC,EAAE,CACtD,sCAAsC,CAkMvC,CAAC;AAEF,eAAO,MAAM,wBAAwB,QAC/B,oCAQH,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC"}
|
package/package.json
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
{
|
2
|
+
"name": "@knocklabs/expo",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"author": "@knocklabs",
|
5
|
+
"license": "MIT",
|
6
|
+
"main": "dist/cjs/index.js",
|
7
|
+
"module": "dist/esm/index.mjs",
|
8
|
+
"types": "dist/types/index.d.ts",
|
9
|
+
"typings": "dist/types/index.d.ts",
|
10
|
+
"react-native": "./src/index.ts",
|
11
|
+
"exports": {
|
12
|
+
".": {
|
13
|
+
"require": "./dist/cjs/index.js",
|
14
|
+
"types": "./dist/types/index.d.ts",
|
15
|
+
"react-native": "./src/index.ts",
|
16
|
+
"default": "./dist/esm/index.mjs"
|
17
|
+
}
|
18
|
+
},
|
19
|
+
"files": [
|
20
|
+
"dist",
|
21
|
+
"src",
|
22
|
+
"README.md"
|
23
|
+
],
|
24
|
+
"scripts": {
|
25
|
+
"clean": "rimraf dist",
|
26
|
+
"dev": "tsc && vite build --watch",
|
27
|
+
"build": "yarn clean && yarn build:esm && yarn build:cjs",
|
28
|
+
"build:esm": "BUILD_TARGET=esm; tsc && vite build",
|
29
|
+
"build:cjs": "BUILD_TARGET=cjs; tsc && vite build",
|
30
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
31
|
+
"format": "prettier \"src/**/*.{js,ts,tsx}\" --write",
|
32
|
+
"format:check": "prettier \"src/**/*.{js,ts,tsx}\" --check",
|
33
|
+
"type:check": "tsc --noEmit",
|
34
|
+
"preview": "vite preview"
|
35
|
+
},
|
36
|
+
"repository": {
|
37
|
+
"type": "git",
|
38
|
+
"url": "git+https://github.com/knocklabs/javascript.git"
|
39
|
+
},
|
40
|
+
"bugs": {
|
41
|
+
"url": "https://github.com/knocklabs/javascript/issues"
|
42
|
+
},
|
43
|
+
"peerDependencies": {
|
44
|
+
"expo": "*",
|
45
|
+
"expo-constants": "*",
|
46
|
+
"expo-device": "*",
|
47
|
+
"expo-notifications": "*",
|
48
|
+
"react": "*",
|
49
|
+
"react-native": "*"
|
50
|
+
},
|
51
|
+
"dependencies": {
|
52
|
+
"@knocklabs/client": "^0.10.13",
|
53
|
+
"@knocklabs/react-core": "^0.2.25",
|
54
|
+
"@knocklabs/react-native": "^0.4.0",
|
55
|
+
"react-native-gesture-handler": "^2.19.0",
|
56
|
+
"react-native-render-html": "^6.3.4",
|
57
|
+
"react-native-svg": "^15.6.0"
|
58
|
+
},
|
59
|
+
"devDependencies": {
|
60
|
+
"@types/react": "^18.3.6",
|
61
|
+
"@types/react-native-htmlview": "^0.16.5",
|
62
|
+
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
63
|
+
"@typescript-eslint/parser": "^8.8.1",
|
64
|
+
"@vitejs/plugin-react": "^4.3.2",
|
65
|
+
"eslint": "^8.56.0",
|
66
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
67
|
+
"eslint-plugin-react-refresh": "^0.4.4",
|
68
|
+
"expo": ">=51.0.24",
|
69
|
+
"expo-constants": ">=16.0.2",
|
70
|
+
"expo-device": ">=6.0.2",
|
71
|
+
"expo-notifications": ">=0.28.16",
|
72
|
+
"react": "^18.2.0",
|
73
|
+
"react-native": "^0.73.4",
|
74
|
+
"rimraf": "^6.0.1",
|
75
|
+
"typescript": "^5.6.2",
|
76
|
+
"vite": "^5.0.0",
|
77
|
+
"vite-plugin-dts": "^3.6.3",
|
78
|
+
"vite-plugin-no-bundle": "^4.0.0"
|
79
|
+
}
|
80
|
+
}
|
package/src/index.ts
ADDED
@@ -0,0 +1,323 @@
|
|
1
|
+
import {
|
2
|
+
ChannelData,
|
3
|
+
Message,
|
4
|
+
MessageEngagementStatus,
|
5
|
+
} from "@knocklabs/client";
|
6
|
+
import { useKnockClient } from "@knocklabs/react-core";
|
7
|
+
import Constants from "expo-constants";
|
8
|
+
import * as Device from "expo-device";
|
9
|
+
import * as Notifications from "expo-notifications";
|
10
|
+
import React, {
|
11
|
+
createContext,
|
12
|
+
useCallback,
|
13
|
+
useContext,
|
14
|
+
useEffect,
|
15
|
+
useState,
|
16
|
+
} from "react";
|
17
|
+
|
18
|
+
export interface KnockExpoPushNotificationContextType {
|
19
|
+
expoPushToken: string | null;
|
20
|
+
registerForPushNotifications: () => Promise<void>;
|
21
|
+
registerPushTokenToChannel(token: string, channelId: string): Promise<void>;
|
22
|
+
unregisterPushTokenFromChannel(
|
23
|
+
token: string,
|
24
|
+
channelId: string,
|
25
|
+
): Promise<void>;
|
26
|
+
onNotificationReceived: (
|
27
|
+
handler: (notification: Notifications.Notification) => void,
|
28
|
+
) => void;
|
29
|
+
onNotificationTapped: (
|
30
|
+
handler: (response: Notifications.NotificationResponse) => void,
|
31
|
+
) => void;
|
32
|
+
}
|
33
|
+
|
34
|
+
Notifications.setNotificationHandler({
|
35
|
+
handleNotification: async () => {
|
36
|
+
return {
|
37
|
+
shouldShowAlert: true,
|
38
|
+
shouldPlaySound: true,
|
39
|
+
shouldSetBadge: true,
|
40
|
+
};
|
41
|
+
},
|
42
|
+
});
|
43
|
+
|
44
|
+
const defaultNotificationHandler = async (
|
45
|
+
_notification: Notifications.Notification,
|
46
|
+
): Promise<Notifications.NotificationBehavior> => {
|
47
|
+
return {
|
48
|
+
shouldShowAlert: true,
|
49
|
+
shouldPlaySound: true,
|
50
|
+
shouldSetBadge: true,
|
51
|
+
};
|
52
|
+
};
|
53
|
+
|
54
|
+
const KnockExpoPushNotificationContext = createContext<
|
55
|
+
KnockExpoPushNotificationContextType | undefined
|
56
|
+
>(undefined);
|
57
|
+
|
58
|
+
export interface KnockExpoPushNotificationProviderProps {
|
59
|
+
knockExpoChannelId: string;
|
60
|
+
customNotificationHandler?: (
|
61
|
+
notification: Notifications.Notification,
|
62
|
+
) => Promise<Notifications.NotificationBehavior>;
|
63
|
+
children?: React.ReactElement;
|
64
|
+
autoRegister?: boolean;
|
65
|
+
}
|
66
|
+
|
67
|
+
async function requestPushPermissionIfNeeded(): Promise<string> {
|
68
|
+
const { status: existingStatus } = await Notifications.getPermissionsAsync();
|
69
|
+
|
70
|
+
if (existingStatus !== "granted") {
|
71
|
+
const { status } = await Notifications.requestPermissionsAsync();
|
72
|
+
return status;
|
73
|
+
}
|
74
|
+
|
75
|
+
return existingStatus;
|
76
|
+
}
|
77
|
+
|
78
|
+
async function getExpoPushToken(): Promise<Notifications.ExpoPushToken | null> {
|
79
|
+
try {
|
80
|
+
if (
|
81
|
+
!Constants.expoConfig ||
|
82
|
+
!Constants.expoConfig.extra ||
|
83
|
+
!Constants.expoConfig.extra.eas
|
84
|
+
) {
|
85
|
+
console.error(
|
86
|
+
"[Knock] Expo Project ID is not defined in the app configuration.",
|
87
|
+
);
|
88
|
+
return null;
|
89
|
+
}
|
90
|
+
const token = await Notifications.getExpoPushTokenAsync({
|
91
|
+
projectId: Constants.expoConfig.extra.eas.projectId,
|
92
|
+
});
|
93
|
+
return token;
|
94
|
+
} catch (error) {
|
95
|
+
console.error("[Knock] Error getting Expo push token:", error);
|
96
|
+
return null;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
async function requestPermissionAndGetPushToken(): Promise<Notifications.ExpoPushToken | null> {
|
101
|
+
// Check for device support
|
102
|
+
if (!Device.isDevice) {
|
103
|
+
console.warn("[Knock] Must use physical device for Push Notifications");
|
104
|
+
return null;
|
105
|
+
}
|
106
|
+
|
107
|
+
const permissionStatus = await requestPushPermissionIfNeeded();
|
108
|
+
|
109
|
+
if (permissionStatus !== "granted") {
|
110
|
+
console.warn("[Knock] Push notification permission not granted");
|
111
|
+
return null;
|
112
|
+
}
|
113
|
+
|
114
|
+
return getExpoPushToken();
|
115
|
+
}
|
116
|
+
|
117
|
+
export const KnockExpoPushNotificationProvider: React.FC<
|
118
|
+
KnockExpoPushNotificationProviderProps
|
119
|
+
> = ({
|
120
|
+
knockExpoChannelId,
|
121
|
+
customNotificationHandler,
|
122
|
+
children,
|
123
|
+
autoRegister = true,
|
124
|
+
}) => {
|
125
|
+
const [expoPushToken, setExpoPushToken] = useState<string | null>(null);
|
126
|
+
const knockClient = useKnockClient();
|
127
|
+
|
128
|
+
const [notificationReceivedHandler, setNotificationReceivedHandler] =
|
129
|
+
useState<(notification: Notifications.Notification) => void>(
|
130
|
+
() => () => {},
|
131
|
+
);
|
132
|
+
|
133
|
+
const [notificationTappedHandler, setNotificationTappedHandler] = useState<
|
134
|
+
(response: Notifications.NotificationResponse) => void
|
135
|
+
>(() => () => {});
|
136
|
+
|
137
|
+
const handleNotificationReceived = useCallback(
|
138
|
+
(handler: (notification: Notifications.Notification) => void) => {
|
139
|
+
setNotificationReceivedHandler(() => handler);
|
140
|
+
},
|
141
|
+
[],
|
142
|
+
);
|
143
|
+
|
144
|
+
const handleNotificationTapped = useCallback(
|
145
|
+
(handler: (response: Notifications.NotificationResponse) => void) => {
|
146
|
+
setNotificationTappedHandler(() => handler);
|
147
|
+
},
|
148
|
+
[],
|
149
|
+
);
|
150
|
+
|
151
|
+
const registerForPushNotifications = useCallback(async (): Promise<void> => {
|
152
|
+
try {
|
153
|
+
knockClient.log(`[Knock] Registering for push notifications`);
|
154
|
+
const token = await requestPermissionAndGetPushToken();
|
155
|
+
knockClient.log(`[Knock] Token received: ${token?.data}`);
|
156
|
+
if (token?.data) {
|
157
|
+
knockClient.log(`[Knock] Setting push token: ${token.data}`);
|
158
|
+
setExpoPushToken(token.data);
|
159
|
+
}
|
160
|
+
} catch (error) {
|
161
|
+
console.error(`[Knock] Error registering for push notifications:`, error);
|
162
|
+
}
|
163
|
+
}, [knockClient]);
|
164
|
+
|
165
|
+
const updateKnockMessageStatusFromNotification = useCallback(
|
166
|
+
async (
|
167
|
+
notification: Notifications.Notification,
|
168
|
+
status: MessageEngagementStatus,
|
169
|
+
): Promise<Message> => {
|
170
|
+
const messageId = notification.request.content.data["knock_message_id"];
|
171
|
+
return knockClient.messages.updateStatus(messageId, status);
|
172
|
+
},
|
173
|
+
[knockClient],
|
174
|
+
);
|
175
|
+
|
176
|
+
const registerNewTokenDataOnServer = useCallback(
|
177
|
+
async (tokens: string[], channelId: string): Promise<ChannelData> => {
|
178
|
+
return knockClient.user.setChannelData({
|
179
|
+
channelId: channelId,
|
180
|
+
channelData: { tokens: tokens },
|
181
|
+
});
|
182
|
+
},
|
183
|
+
[knockClient],
|
184
|
+
);
|
185
|
+
|
186
|
+
const registerPushTokenToChannel = useCallback(
|
187
|
+
async (token: string, channelId: string): Promise<void> => {
|
188
|
+
knockClient.user
|
189
|
+
.getChannelData({ channelId: channelId })
|
190
|
+
.then((result: ChannelData) => {
|
191
|
+
const tokens: string[] = result.data["tokens"];
|
192
|
+
if (!tokens.includes(token)) {
|
193
|
+
tokens.push(token);
|
194
|
+
return registerNewTokenDataOnServer(tokens, channelId);
|
195
|
+
}
|
196
|
+
knockClient.log("[Knock] registerPushTokenToChannel success");
|
197
|
+
})
|
198
|
+
.catch((_) => {
|
199
|
+
// No data registered on that channel for that user, we'll create a new record
|
200
|
+
return registerNewTokenDataOnServer([token], channelId);
|
201
|
+
});
|
202
|
+
},
|
203
|
+
[knockClient, registerNewTokenDataOnServer],
|
204
|
+
);
|
205
|
+
|
206
|
+
const unregisterPushTokenFromChannel = useCallback(
|
207
|
+
async (token: string, channelId: string): Promise<void> => {
|
208
|
+
knockClient.user
|
209
|
+
.getChannelData({ channelId: channelId })
|
210
|
+
.then((result: ChannelData) => {
|
211
|
+
const tokens: string[] = result.data["tokens"];
|
212
|
+
const updatedTokens = tokens.filter(
|
213
|
+
(channelToken) => channelToken !== token,
|
214
|
+
);
|
215
|
+
knockClient.log("unregisterPushTokenFromChannel success");
|
216
|
+
return registerNewTokenDataOnServer(updatedTokens, channelId);
|
217
|
+
})
|
218
|
+
.catch((error) => {
|
219
|
+
console.error(
|
220
|
+
`[Knock] Error unregistering push token from channel:`,
|
221
|
+
error,
|
222
|
+
);
|
223
|
+
});
|
224
|
+
},
|
225
|
+
[knockClient, registerNewTokenDataOnServer],
|
226
|
+
);
|
227
|
+
|
228
|
+
useEffect(() => {
|
229
|
+
Notifications.setNotificationHandler({
|
230
|
+
handleNotification:
|
231
|
+
customNotificationHandler ?? defaultNotificationHandler,
|
232
|
+
});
|
233
|
+
|
234
|
+
if (autoRegister) {
|
235
|
+
registerForPushNotifications()
|
236
|
+
.then(() => {
|
237
|
+
if (expoPushToken) {
|
238
|
+
registerPushTokenToChannel(expoPushToken, knockExpoChannelId)
|
239
|
+
.then((_result) => {
|
240
|
+
knockClient.log("[Knock] setChannelData success");
|
241
|
+
})
|
242
|
+
.catch((_error) => {
|
243
|
+
console.error(
|
244
|
+
"[Knock] Error in setting push token or channel data",
|
245
|
+
_error,
|
246
|
+
);
|
247
|
+
});
|
248
|
+
}
|
249
|
+
})
|
250
|
+
.catch((_error) => {
|
251
|
+
console.error(
|
252
|
+
"[Knock] Error in setting push token or channel data",
|
253
|
+
_error,
|
254
|
+
);
|
255
|
+
});
|
256
|
+
}
|
257
|
+
|
258
|
+
const notificationReceivedSubscription =
|
259
|
+
Notifications.addNotificationReceivedListener((notification) => {
|
260
|
+
knockClient.log(
|
261
|
+
"[Knock] Expo Push Notification received in foreground:",
|
262
|
+
);
|
263
|
+
updateKnockMessageStatusFromNotification(notification, "interacted");
|
264
|
+
notificationReceivedHandler(notification);
|
265
|
+
});
|
266
|
+
|
267
|
+
const notificationResponseSubscription =
|
268
|
+
Notifications.addNotificationResponseReceivedListener((response) => {
|
269
|
+
knockClient.log("[Knock] Expo Push Notification was interacted with");
|
270
|
+
updateKnockMessageStatusFromNotification(
|
271
|
+
response.notification,
|
272
|
+
"interacted",
|
273
|
+
);
|
274
|
+
notificationTappedHandler(response);
|
275
|
+
});
|
276
|
+
|
277
|
+
return () => {
|
278
|
+
Notifications.removeNotificationSubscription(
|
279
|
+
notificationReceivedSubscription,
|
280
|
+
);
|
281
|
+
Notifications.removeNotificationSubscription(
|
282
|
+
notificationResponseSubscription,
|
283
|
+
);
|
284
|
+
};
|
285
|
+
|
286
|
+
// TODO: Remove when possible and ensure dependency array is correct
|
287
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
288
|
+
}, [
|
289
|
+
registerForPushNotifications,
|
290
|
+
notificationReceivedHandler,
|
291
|
+
notificationTappedHandler,
|
292
|
+
customNotificationHandler,
|
293
|
+
expoPushToken,
|
294
|
+
knockExpoChannelId,
|
295
|
+
knockClient,
|
296
|
+
]);
|
297
|
+
|
298
|
+
return (
|
299
|
+
<KnockExpoPushNotificationContext.Provider
|
300
|
+
value={{
|
301
|
+
expoPushToken,
|
302
|
+
registerForPushNotifications,
|
303
|
+
registerPushTokenToChannel,
|
304
|
+
unregisterPushTokenFromChannel,
|
305
|
+
onNotificationReceived: handleNotificationReceived,
|
306
|
+
onNotificationTapped: handleNotificationTapped,
|
307
|
+
}}
|
308
|
+
>
|
309
|
+
{children}
|
310
|
+
</KnockExpoPushNotificationContext.Provider>
|
311
|
+
);
|
312
|
+
};
|
313
|
+
|
314
|
+
export const useExpoPushNotifications =
|
315
|
+
(): KnockExpoPushNotificationContextType => {
|
316
|
+
const context = useContext(KnockExpoPushNotificationContext);
|
317
|
+
if (context === undefined) {
|
318
|
+
throw new Error(
|
319
|
+
"[Knock] useExpoPushNotifications must be used within a PushNotificationProvider",
|
320
|
+
);
|
321
|
+
}
|
322
|
+
return context;
|
323
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./KnockExpoPushNotificationProvider";
|