@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 ADDED
@@ -0,0 +1,12 @@
1
+ # @knocklabs/expo
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a82e897: Move KnockExpoPushNotificationProvider to @knocklabs/expo
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [a82e897]
12
+ - @knocklabs/react-native@0.4.0
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":"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"}
@@ -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,3 @@
1
+ export * from './modules/push';
2
+ export * from '@knocklabs/react-native';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export * from './KnockExpoPushNotificationProvider';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export * from "./modules/push";
2
+ export * from "@knocklabs/react-native";
@@ -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";