@knocklabs/expo 0.6.7 → 0.7.0-rc.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 +63 -0
- package/README.md +17 -0
- package/dist/cjs/modules/push/KnockExpoPushNotificationProvider.js +1 -1
- package/dist/cjs/modules/push/KnockExpoPushNotificationProvider.js.map +1 -1
- package/dist/esm/modules/push/KnockExpoPushNotificationProvider.mjs +65 -58
- package/dist/esm/modules/push/KnockExpoPushNotificationProvider.mjs.map +1 -1
- package/dist/types/modules/push/KnockExpoPushNotificationProvider.d.ts.map +1 -1
- package/package.json +14 -13
- package/src/modules/push/KnockExpoPushNotificationProvider.tsx +18 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# @knocklabs/expo
|
|
2
2
|
|
|
3
|
+
## 0.7.0-rc.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d2f7948: Add an `enabled` prop to `KnockProvider` (and an `enabled` option to `useAuthenticatedKnockClient`).
|
|
8
|
+
|
|
9
|
+
When `enabled` is `false`, the provider still renders its children but the Knock client sits idle: no identify call, no API requests, no websocket. Set it to `true` and it connects like a login; set it back to `false` and it disconnects and clears its data like a logout. It defaults to `true`, so existing code is unaffected.
|
|
10
|
+
|
|
11
|
+
Use this instead of conditionally mounting `KnockProvider`, for example to wait for a user token that loads asynchronously:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<KnockProvider
|
|
15
|
+
apiKey={apiKey}
|
|
16
|
+
user={{ id: userId }}
|
|
17
|
+
userToken={userToken}
|
|
18
|
+
enabled={Boolean(userId && userToken)}
|
|
19
|
+
/>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Also fixed:
|
|
23
|
+
|
|
24
|
+
- `useFeedSettings` no longer calls `GET /v1/users/undefined/feeds/.../settings` when there's no user.
|
|
25
|
+
- `KnockProvider` now disconnects its client (websocket, token-refresh timer, listener) when it unmounts, instead of leaving them running.
|
|
26
|
+
|
|
27
|
+
- d2f7948: Add `useKnockAuthState()` and make Slack, MS Teams, and Expo respond to sign-in changes.
|
|
28
|
+
|
|
29
|
+
- New `useKnockAuthState(knock)` hook re-renders when the user signs in, signs out, or switches.
|
|
30
|
+
- Slack and MS Teams connection status now re-checks when the user changes, instead of checking once and sticking with that result.
|
|
31
|
+
- Expo waits for a signed-in user before registering for push notifications, so logged-out users don't see the OS permission prompt. A notification tapped while logged out no longer tries to update its status.
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- Updated dependencies [d2f7948]
|
|
36
|
+
- Updated dependencies [d2f7948]
|
|
37
|
+
- Updated dependencies [d2f7948]
|
|
38
|
+
- @knocklabs/client@0.22.0-rc.0
|
|
39
|
+
- @knocklabs/react-core@0.14.0-rc.0
|
|
40
|
+
- @knocklabs/react-native@0.10.0-rc.0
|
|
41
|
+
|
|
42
|
+
## 0.6.9
|
|
43
|
+
|
|
44
|
+
### Patch Changes
|
|
45
|
+
|
|
46
|
+
- Updated dependencies [3d7a041]
|
|
47
|
+
- @knocklabs/client@0.21.15
|
|
48
|
+
- @knocklabs/react-core@0.13.16
|
|
49
|
+
- @knocklabs/react-native@0.9.9
|
|
50
|
+
|
|
51
|
+
## 0.6.8
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- dd1b724: Expose `./package.json` in each package's `exports` map. This restores the ability for tooling (bundlers, test mockers such as Storybook/Vitest, and version checks) to resolve the package manifest, which the `exports` field otherwise blocks.
|
|
56
|
+
- Updated dependencies [dd1b724]
|
|
57
|
+
- Updated dependencies [70e7669]
|
|
58
|
+
- Updated dependencies [3dd0aa8]
|
|
59
|
+
- Updated dependencies [3dd0aa8]
|
|
60
|
+
- Updated dependencies [b6c9be4]
|
|
61
|
+
- Updated dependencies [b6c9be4]
|
|
62
|
+
- @knocklabs/client@0.21.14
|
|
63
|
+
- @knocklabs/react-core@0.13.15
|
|
64
|
+
- @knocklabs/react-native@0.9.8
|
|
65
|
+
|
|
3
66
|
## 0.6.7
|
|
4
67
|
|
|
5
68
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -111,6 +111,23 @@ const YourAppLayout = () => {
|
|
|
111
111
|
};
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
## Deferring activity with `enabled`
|
|
115
|
+
|
|
116
|
+
`KnockProvider` takes an `enabled` prop (default `true`). When it's `false`, the provider still renders its children but the Knock client sits idle: it doesn't identify the user, make any API requests, or open a websocket. Auto push-notification registration also waits for `enabled` to become `true`, so logged-out users never see the OS permission prompt. Set it back to `true` and it connects like a login; set it to `false` again and it disconnects and clears its data like a logout.
|
|
117
|
+
|
|
118
|
+
This is the recommended way to gate the provider on a complete identity — for example an enhanced-security user token that loads asynchronously — instead of conditionally mounting `KnockProvider`:
|
|
119
|
+
|
|
120
|
+
```jsx
|
|
121
|
+
<KnockProvider
|
|
122
|
+
apiKey={process.env.KNOCK_PUBLIC_API_KEY}
|
|
123
|
+
user={{ id: userId }}
|
|
124
|
+
userToken={userToken}
|
|
125
|
+
enabled={Boolean(userId && userToken)}
|
|
126
|
+
>
|
|
127
|
+
{/* ... */}
|
|
128
|
+
</KnockProvider>
|
|
129
|
+
```
|
|
130
|
+
|
|
114
131
|
## Headless usage
|
|
115
132
|
|
|
116
133
|
Alternatively, if you don't want to use our components you can render the feed in a headless mode using our hooks:
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime"),P=require("@knocklabs/react-core"),K=require("@knocklabs/react-native"),o=require("react"),v=require("./getNotificationsModule.js"),d=require("./utils.js"),x=o.createContext(void 0);function M(){const r=o.useContext(x);if(r===void 0)throw new Error("[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider");return r}const m=({knockExpoChannelId:r,customNotificationHandler:c,setupAndroidNotificationChannel:h=d.setupDefaultAndroidChannel,children:E,autoRegister:k=!0})=>{const e=P.useKnockClient(),{status:C}=P.useKnockAuthState(e),g=C==="authenticated",{registerPushTokenToChannel:u,unregisterPushTokenFromChannel:R}=K.usePushNotifications(),[S,T]=o.useState(null),p=o.useRef(()=>{}),N=o.useRef(()=>{}),b=o.useCallback(t=>{p.current=t},[]),w=o.useCallback(t=>{N.current=t},[]),a=o.useCallback(async()=>{try{e.log("[Knock] Registering for push notifications");const t=await d.registerForPushNotifications(h);return t?(e.log(`[Knock] Push token received: ${t}`),T(t),t):null}catch(t){return console.error("[Knock] Error registering for push notifications:",t),null}},[e,h]),f=o.useCallback(async(t,s)=>{var i;const n=(i=t.request.content.data)==null?void 0:i.knock_message_id;if(!n){e.log("[Knock] Skipping status update for non-Knock notification");return}if(!e.isAuthenticated()){e.log("[Knock] Skipping status update; user is not authenticated");return}return e.messages.updateStatus(n,s)},[e]);o.useEffect(()=>{const t=v.getNotificationsModule();if(!t)return;const s=c||(async()=>d.DEFAULT_NOTIFICATION_BEHAVIOR);t.setNotificationHandler({handleNotification:s})},[c]),o.useEffect(()=>{if(!k||!g)return;let t=!0;return(async()=>{try{const n=await a();n&&t&&(await u(n,r),e.log("[Knock] Push token registered with Knock channel"))}catch(n){console.error("[Knock] Error during auto-registration:",n)}})(),()=>{t=!1}},[k,g,r,a,u,e]),o.useEffect(()=>{const t=v.getNotificationsModule();if(!t)return;const s=t.addNotificationReceivedListener(i=>{e.log("[Knock] Notification received in foreground"),f(i,"interacted"),p.current(i)}),n=t.addNotificationResponseReceivedListener(i=>{e.log("[Knock] Notification was tapped"),f(i.notification,"interacted"),N.current(i)});return()=>{s.remove(),n.remove()}},[e,f]);const y={expoPushToken:S,registerForPushNotifications:a,registerPushTokenToChannel:u,unregisterPushTokenFromChannel:R,onNotificationReceived:b,onNotificationTapped:w};return l.jsx(x.Provider,{value:y,children:E})},q=r=>l.jsx(K.KnockPushNotificationProvider,{children:l.jsx(m,{...r})});exports.KnockExpoPushNotificationProvider=q;exports.useExpoPushNotifications=M;
|
|
2
2
|
//# sourceMappingURL=KnockExpoPushNotificationProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnockExpoPushNotificationProvider.js","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import { Message, MessageEngagementStatus } from \"@knocklabs/client\";\nimport { useKnockClient } from \"@knocklabs/react-core\";\nimport {\n KnockPushNotificationProvider,\n usePushNotifications,\n} from \"@knocklabs/react-native\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n type Notification,\n type NotificationResponse,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\nimport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\nimport {\n DEFAULT_NOTIFICATION_BEHAVIOR,\n registerForPushNotifications as registerForPushNotificationsUtil,\n setupDefaultAndroidChannel,\n} from \"./utils\";\n\n// Re-export types for consumers\nexport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\n/**\n * Hook to access push notification functionality within a KnockExpoPushNotificationProvider.\n * @throws Error if used outside of a KnockExpoPushNotificationProvider\n */\nexport function useExpoPushNotifications(): KnockExpoPushNotificationContextType {\n const context = useContext(KnockExpoPushNotificationContext);\n\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider\",\n );\n }\n\n return context;\n}\n\n/**\n * Internal provider component that handles all the Expo push notification logic.\n */\nconst InternalExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n setupAndroidNotificationChannel = setupDefaultAndroidChannel,\n children,\n autoRegister = true,\n}) => {\n const knockClient = useKnockClient();\n const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =\n usePushNotifications();\n\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n\n // Use refs for handlers to avoid re-running effects when handlers change\n const notificationReceivedHandlerRef = useRef<\n (notification: Notification) => void\n >(() => {});\n\n const notificationTappedHandlerRef = useRef<\n (response: NotificationResponse) => void\n >(() => {});\n\n /**\n * Register a handler to be called when a notification is received in the foreground.\n */\n const onNotificationReceived = useCallback(\n (handler: (notification: Notification) => void) => {\n notificationReceivedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Register a handler to be called when a notification is tapped.\n */\n const onNotificationTapped = useCallback(\n (handler: (response: NotificationResponse) => void) => {\n notificationTappedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Manually trigger push notification registration.\n * Returns the push token if successful, or null if registration failed.\n */\n const registerForPushNotifications = useCallback(async (): Promise<\n string | null\n > => {\n try {\n knockClient.log(\"[Knock] Registering for push notifications\");\n\n const token = await registerForPushNotificationsUtil(\n setupAndroidNotificationChannel,\n );\n\n if (token) {\n knockClient.log(`[Knock] Push token received: ${token}`);\n setExpoPushToken(token);\n return token;\n }\n\n return null;\n } catch (error) {\n console.error(\"[Knock] Error registering for push notifications:\", error);\n return null;\n }\n }, [knockClient, setupAndroidNotificationChannel]);\n\n /**\n * Update the Knock message status when a notification is received or interacted with.\n * Only updates status for notifications that originated from Knock (have a knock_message_id).\n */\n const updateMessageStatus = useCallback(\n async (\n notification: Notification,\n status: MessageEngagementStatus,\n ): Promise<Message | void> => {\n const messageId = notification.request.content.data?.[\n \"knock_message_id\"\n ] as string | undefined;\n\n // Skip status update if this isn't a Knock notification\n // Fixes issue: https://github.com/knocklabs/javascript/issues/589\n if (!messageId) {\n knockClient.log(\n \"[Knock] Skipping status update for non-Knock notification\",\n );\n return;\n }\n\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n // Set up the notification handler for foreground notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const handleNotification = customNotificationHandler\n ? customNotificationHandler\n : async () => DEFAULT_NOTIFICATION_BEHAVIOR;\n\n NotificationsModule.setNotificationHandler({ handleNotification });\n }, [customNotificationHandler]);\n\n // Auto-register for push notifications on mount if enabled\n useEffect(() => {\n if (!autoRegister) {\n return;\n }\n\n let isMounted = true;\n\n const register = async () => {\n try {\n const token = await registerForPushNotifications();\n\n if (token && isMounted) {\n await registerPushTokenToChannel(token, knockExpoChannelId);\n knockClient.log(\"[Knock] Push token registered with Knock channel\");\n }\n } catch (error) {\n console.error(\"[Knock] Error during auto-registration:\", error);\n }\n };\n\n register();\n\n return () => {\n isMounted = false;\n };\n }, [\n autoRegister,\n knockExpoChannelId,\n registerForPushNotifications,\n registerPushTokenToChannel,\n knockClient,\n ]);\n\n // Set up notification listeners for received and tapped notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const receivedSubscription =\n NotificationsModule.addNotificationReceivedListener((notification) => {\n knockClient.log(\"[Knock] Notification received in foreground\");\n updateMessageStatus(notification, \"interacted\");\n notificationReceivedHandlerRef.current(notification);\n });\n\n const responseSubscription =\n NotificationsModule.addNotificationResponseReceivedListener(\n (response) => {\n knockClient.log(\"[Knock] Notification was tapped\");\n updateMessageStatus(response.notification, \"interacted\");\n notificationTappedHandlerRef.current(response);\n },\n );\n\n return () => {\n receivedSubscription.remove();\n responseSubscription.remove();\n };\n }, [knockClient, updateMessageStatus]);\n\n const contextValue: KnockExpoPushNotificationContextType = {\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived,\n onNotificationTapped,\n };\n\n return (\n <KnockExpoPushNotificationContext.Provider value={contextValue}>\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\n/**\n * Provider component for Expo push notifications with Knock.\n *\n * Wraps the internal provider with the base KnockPushNotificationProvider\n * to provide full push notification functionality.\n *\n * @example\n * ```tsx\n * <KnockProvider apiKey=\"your-api-key\" userId=\"user-id\">\n * <KnockExpoPushNotificationProvider knockExpoChannelId=\"your-channel-id\">\n * <App />\n * </KnockExpoPushNotificationProvider>\n * </KnockProvider>\n * ```\n */\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = (props) => {\n return (\n <KnockPushNotificationProvider>\n <InternalExpoPushNotificationProvider {...props} />\n </KnockPushNotificationProvider>\n );\n};\n"],"names":["KnockExpoPushNotificationContext","createContext","useExpoPushNotifications","context","useContext","InternalExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","setupAndroidNotificationChannel","setupDefaultAndroidChannel","children","autoRegister","knockClient","useKnockClient","registerPushTokenToChannel","unregisterPushTokenFromChannel","usePushNotifications","expoPushToken","setExpoPushToken","useState","notificationReceivedHandlerRef","useRef","notificationTappedHandlerRef","onNotificationReceived","useCallback","handler","onNotificationTapped","registerForPushNotifications","token","registerForPushNotificationsUtil","error","updateMessageStatus","notification","status","messageId","_a","useEffect","NotificationsModule","getNotificationsModule","handleNotification","DEFAULT_NOTIFICATION_BEHAVIOR","isMounted","receivedSubscription","responseSubscription","response","contextValue","KnockExpoPushNotificationProvider","props","KnockPushNotificationProvider","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.js","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import { Message, MessageEngagementStatus } from \"@knocklabs/client\";\nimport { useKnockAuthState, useKnockClient } from \"@knocklabs/react-core\";\nimport {\n KnockPushNotificationProvider,\n usePushNotifications,\n} from \"@knocklabs/react-native\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n type Notification,\n type NotificationResponse,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\nimport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\nimport {\n DEFAULT_NOTIFICATION_BEHAVIOR,\n registerForPushNotifications as registerForPushNotificationsUtil,\n setupDefaultAndroidChannel,\n} from \"./utils\";\n\n// Re-export types for consumers\nexport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\n/**\n * Hook to access push notification functionality within a KnockExpoPushNotificationProvider.\n * @throws Error if used outside of a KnockExpoPushNotificationProvider\n */\nexport function useExpoPushNotifications(): KnockExpoPushNotificationContextType {\n const context = useContext(KnockExpoPushNotificationContext);\n\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider\",\n );\n }\n\n return context;\n}\n\n/**\n * Internal provider component that handles all the Expo push notification logic.\n */\nconst InternalExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n setupAndroidNotificationChannel = setupDefaultAndroidChannel,\n children,\n autoRegister = true,\n}) => {\n const knockClient = useKnockClient();\n const { status: authStatus } = useKnockAuthState(knockClient);\n const isAuthenticated = authStatus === \"authenticated\";\n const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =\n usePushNotifications();\n\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n\n // Use refs for handlers to avoid re-running effects when handlers change\n const notificationReceivedHandlerRef = useRef<\n (notification: Notification) => void\n >(() => {});\n\n const notificationTappedHandlerRef = useRef<\n (response: NotificationResponse) => void\n >(() => {});\n\n /**\n * Register a handler to be called when a notification is received in the foreground.\n */\n const onNotificationReceived = useCallback(\n (handler: (notification: Notification) => void) => {\n notificationReceivedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Register a handler to be called when a notification is tapped.\n */\n const onNotificationTapped = useCallback(\n (handler: (response: NotificationResponse) => void) => {\n notificationTappedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Manually trigger push notification registration.\n * Returns the push token if successful, or null if registration failed.\n */\n const registerForPushNotifications = useCallback(async (): Promise<\n string | null\n > => {\n try {\n knockClient.log(\"[Knock] Registering for push notifications\");\n\n const token = await registerForPushNotificationsUtil(\n setupAndroidNotificationChannel,\n );\n\n if (token) {\n knockClient.log(`[Knock] Push token received: ${token}`);\n setExpoPushToken(token);\n return token;\n }\n\n return null;\n } catch (error) {\n console.error(\"[Knock] Error registering for push notifications:\", error);\n return null;\n }\n }, [knockClient, setupAndroidNotificationChannel]);\n\n /**\n * Update the Knock message status when a notification is received or interacted with.\n * Only updates status for notifications that originated from Knock (have a knock_message_id).\n */\n const updateMessageStatus = useCallback(\n async (\n notification: Notification,\n status: MessageEngagementStatus,\n ): Promise<Message | void> => {\n const messageId = notification.request.content.data?.[\n \"knock_message_id\"\n ] as string | undefined;\n\n // Skip status update if this isn't a Knock notification\n // Fixes issue: https://github.com/knocklabs/javascript/issues/589\n if (!messageId) {\n knockClient.log(\n \"[Knock] Skipping status update for non-Knock notification\",\n );\n return;\n }\n\n // Skip when there's no authenticated user (e.g. a notification tapped\n // after logout) — otherwise this fires a messages request with no user.\n if (!knockClient.isAuthenticated()) {\n knockClient.log(\n \"[Knock] Skipping status update; user is not authenticated\",\n );\n return;\n }\n\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n // Set up the notification handler for foreground notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const handleNotification = customNotificationHandler\n ? customNotificationHandler\n : async () => DEFAULT_NOTIFICATION_BEHAVIOR;\n\n NotificationsModule.setNotificationHandler({ handleNotification });\n }, [customNotificationHandler]);\n\n // Auto-register for push notifications once there is an authenticated user.\n // Gating on auth defers the OS permission prompt (prompting a logged-out user\n // is hostile) and avoids registering a token against no user. Because\n // `isAuthenticated` is a dependency, enabling auth later re-runs registration.\n useEffect(() => {\n if (!autoRegister || !isAuthenticated) {\n return;\n }\n\n let isMounted = true;\n\n const register = async () => {\n try {\n const token = await registerForPushNotifications();\n\n if (token && isMounted) {\n await registerPushTokenToChannel(token, knockExpoChannelId);\n knockClient.log(\"[Knock] Push token registered with Knock channel\");\n }\n } catch (error) {\n console.error(\"[Knock] Error during auto-registration:\", error);\n }\n };\n\n register();\n\n return () => {\n isMounted = false;\n };\n }, [\n autoRegister,\n isAuthenticated,\n knockExpoChannelId,\n registerForPushNotifications,\n registerPushTokenToChannel,\n knockClient,\n ]);\n\n // Set up notification listeners for received and tapped notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const receivedSubscription =\n NotificationsModule.addNotificationReceivedListener((notification) => {\n knockClient.log(\"[Knock] Notification received in foreground\");\n updateMessageStatus(notification, \"interacted\");\n notificationReceivedHandlerRef.current(notification);\n });\n\n const responseSubscription =\n NotificationsModule.addNotificationResponseReceivedListener(\n (response) => {\n knockClient.log(\"[Knock] Notification was tapped\");\n updateMessageStatus(response.notification, \"interacted\");\n notificationTappedHandlerRef.current(response);\n },\n );\n\n return () => {\n receivedSubscription.remove();\n responseSubscription.remove();\n };\n }, [knockClient, updateMessageStatus]);\n\n const contextValue: KnockExpoPushNotificationContextType = {\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived,\n onNotificationTapped,\n };\n\n return (\n <KnockExpoPushNotificationContext.Provider value={contextValue}>\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\n/**\n * Provider component for Expo push notifications with Knock.\n *\n * Wraps the internal provider with the base KnockPushNotificationProvider\n * to provide full push notification functionality.\n *\n * @example\n * ```tsx\n * <KnockProvider apiKey=\"your-api-key\" userId=\"user-id\">\n * <KnockExpoPushNotificationProvider knockExpoChannelId=\"your-channel-id\">\n * <App />\n * </KnockExpoPushNotificationProvider>\n * </KnockProvider>\n * ```\n */\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = (props) => {\n return (\n <KnockPushNotificationProvider>\n <InternalExpoPushNotificationProvider {...props} />\n </KnockPushNotificationProvider>\n );\n};\n"],"names":["KnockExpoPushNotificationContext","createContext","useExpoPushNotifications","context","useContext","InternalExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","setupAndroidNotificationChannel","setupDefaultAndroidChannel","children","autoRegister","knockClient","useKnockClient","authStatus","useKnockAuthState","isAuthenticated","registerPushTokenToChannel","unregisterPushTokenFromChannel","usePushNotifications","expoPushToken","setExpoPushToken","useState","notificationReceivedHandlerRef","useRef","notificationTappedHandlerRef","onNotificationReceived","useCallback","handler","onNotificationTapped","registerForPushNotifications","token","registerForPushNotificationsUtil","error","updateMessageStatus","notification","status","messageId","_a","useEffect","NotificationsModule","getNotificationsModule","handleNotification","DEFAULT_NOTIFICATION_BEHAVIOR","isMounted","receivedSubscription","responseSubscription","response","contextValue","KnockExpoPushNotificationProvider","props","KnockPushNotificationProvider","jsx"],"mappings":"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"}
|
|
@@ -1,112 +1,119 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { useKnockClient as
|
|
3
|
-
import { KnockPushNotificationProvider as
|
|
4
|
-
import { createContext as
|
|
5
|
-
import { getNotificationsModule as
|
|
6
|
-
import { registerForPushNotifications as
|
|
7
|
-
const m =
|
|
8
|
-
function
|
|
9
|
-
const i =
|
|
1
|
+
import { jsx as h } from "react/jsx-runtime";
|
|
2
|
+
import { useKnockClient as y, useKnockAuthState as A } from "@knocklabs/react-core";
|
|
3
|
+
import { KnockPushNotificationProvider as F, usePushNotifications as I } from "@knocklabs/react-native";
|
|
4
|
+
import { createContext as M, useContext as b, useState as _, useRef as N, useCallback as s, useEffect as d } from "react";
|
|
5
|
+
import { getNotificationsModule as K } from "./getNotificationsModule.mjs";
|
|
6
|
+
import { registerForPushNotifications as L, setupDefaultAndroidChannel as O, DEFAULT_NOTIFICATION_BEHAVIOR as D } from "./utils.mjs";
|
|
7
|
+
const m = M(void 0);
|
|
8
|
+
function z() {
|
|
9
|
+
const i = b(m);
|
|
10
10
|
if (i === void 0)
|
|
11
11
|
throw new Error(
|
|
12
12
|
"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider"
|
|
13
13
|
);
|
|
14
14
|
return i;
|
|
15
15
|
}
|
|
16
|
-
const
|
|
16
|
+
const H = ({
|
|
17
17
|
knockExpoChannelId: i,
|
|
18
18
|
customNotificationHandler: c,
|
|
19
|
-
setupAndroidNotificationChannel:
|
|
19
|
+
setupAndroidNotificationChannel: l = O,
|
|
20
20
|
children: v,
|
|
21
|
-
autoRegister:
|
|
21
|
+
autoRegister: p = !0
|
|
22
22
|
}) => {
|
|
23
|
-
const
|
|
24
|
-
}),
|
|
25
|
-
}),
|
|
26
|
-
(
|
|
27
|
-
|
|
23
|
+
const o = y(), { status: x } = A(o), k = x === "authenticated", { registerPushTokenToChannel: a, unregisterPushTokenFromChannel: E } = I(), [T, R] = _(null), g = N(() => {
|
|
24
|
+
}), P = N(() => {
|
|
25
|
+
}), S = s(
|
|
26
|
+
(t) => {
|
|
27
|
+
g.current = t;
|
|
28
28
|
},
|
|
29
29
|
[]
|
|
30
|
-
),
|
|
31
|
-
(
|
|
32
|
-
|
|
30
|
+
), w = s(
|
|
31
|
+
(t) => {
|
|
32
|
+
P.current = t;
|
|
33
33
|
},
|
|
34
34
|
[]
|
|
35
35
|
), u = s(async () => {
|
|
36
36
|
try {
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
37
|
+
o.log("[Knock] Registering for push notifications");
|
|
38
|
+
const t = await L(
|
|
39
|
+
l
|
|
40
40
|
);
|
|
41
|
-
return
|
|
42
|
-
} catch (
|
|
43
|
-
return console.error("[Knock] Error registering for push notifications:",
|
|
41
|
+
return t ? (o.log(`[Knock] Push token received: ${t}`), R(t), t) : null;
|
|
42
|
+
} catch (t) {
|
|
43
|
+
return console.error("[Knock] Error registering for push notifications:", t), null;
|
|
44
44
|
}
|
|
45
|
-
}, [
|
|
46
|
-
async (
|
|
45
|
+
}, [o, l]), f = s(
|
|
46
|
+
async (t, r) => {
|
|
47
47
|
var n;
|
|
48
|
-
const e = (n =
|
|
48
|
+
const e = (n = t.request.content.data) == null ? void 0 : n.knock_message_id;
|
|
49
49
|
if (!e) {
|
|
50
|
-
|
|
50
|
+
o.log(
|
|
51
51
|
"[Knock] Skipping status update for non-Knock notification"
|
|
52
52
|
);
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
if (!o.isAuthenticated()) {
|
|
56
|
+
o.log(
|
|
57
|
+
"[Knock] Skipping status update; user is not authenticated"
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
return o.messages.updateStatus(e, r);
|
|
56
62
|
},
|
|
57
|
-
[
|
|
63
|
+
[o]
|
|
58
64
|
);
|
|
59
65
|
d(() => {
|
|
60
|
-
const
|
|
61
|
-
if (!
|
|
62
|
-
const r = c || (async () =>
|
|
63
|
-
|
|
66
|
+
const t = K();
|
|
67
|
+
if (!t) return;
|
|
68
|
+
const r = c || (async () => D);
|
|
69
|
+
t.setNotificationHandler({ handleNotification: r });
|
|
64
70
|
}, [c]), d(() => {
|
|
65
|
-
if (!
|
|
71
|
+
if (!p || !k)
|
|
66
72
|
return;
|
|
67
|
-
let
|
|
73
|
+
let t = !0;
|
|
68
74
|
return (async () => {
|
|
69
75
|
try {
|
|
70
76
|
const e = await u();
|
|
71
|
-
e &&
|
|
77
|
+
e && t && (await a(e, i), o.log("[Knock] Push token registered with Knock channel"));
|
|
72
78
|
} catch (e) {
|
|
73
79
|
console.error("[Knock] Error during auto-registration:", e);
|
|
74
80
|
}
|
|
75
81
|
})(), () => {
|
|
76
|
-
|
|
82
|
+
t = !1;
|
|
77
83
|
};
|
|
78
84
|
}, [
|
|
79
|
-
|
|
85
|
+
p,
|
|
86
|
+
k,
|
|
80
87
|
i,
|
|
81
88
|
u,
|
|
82
89
|
a,
|
|
83
|
-
|
|
90
|
+
o
|
|
84
91
|
]), d(() => {
|
|
85
|
-
const
|
|
86
|
-
if (!
|
|
87
|
-
const r =
|
|
88
|
-
|
|
89
|
-
}), e =
|
|
92
|
+
const t = K();
|
|
93
|
+
if (!t) return;
|
|
94
|
+
const r = t.addNotificationReceivedListener((n) => {
|
|
95
|
+
o.log("[Knock] Notification received in foreground"), f(n, "interacted"), g.current(n);
|
|
96
|
+
}), e = t.addNotificationResponseReceivedListener(
|
|
90
97
|
(n) => {
|
|
91
|
-
|
|
98
|
+
o.log("[Knock] Notification was tapped"), f(n.notification, "interacted"), P.current(n);
|
|
92
99
|
}
|
|
93
100
|
);
|
|
94
101
|
return () => {
|
|
95
102
|
r.remove(), e.remove();
|
|
96
103
|
};
|
|
97
|
-
}, [
|
|
98
|
-
const
|
|
99
|
-
expoPushToken:
|
|
104
|
+
}, [o, f]);
|
|
105
|
+
const C = {
|
|
106
|
+
expoPushToken: T,
|
|
100
107
|
registerForPushNotifications: u,
|
|
101
108
|
registerPushTokenToChannel: a,
|
|
102
|
-
unregisterPushTokenFromChannel:
|
|
103
|
-
onNotificationReceived:
|
|
104
|
-
onNotificationTapped:
|
|
109
|
+
unregisterPushTokenFromChannel: E,
|
|
110
|
+
onNotificationReceived: S,
|
|
111
|
+
onNotificationTapped: w
|
|
105
112
|
};
|
|
106
|
-
return /* @__PURE__ */
|
|
107
|
-
},
|
|
113
|
+
return /* @__PURE__ */ h(m.Provider, { value: C, children: v });
|
|
114
|
+
}, G = (i) => /* @__PURE__ */ h(F, { children: /* @__PURE__ */ h(H, { ...i }) });
|
|
108
115
|
export {
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
G as KnockExpoPushNotificationProvider,
|
|
117
|
+
z as useExpoPushNotifications
|
|
111
118
|
};
|
|
112
119
|
//# sourceMappingURL=KnockExpoPushNotificationProvider.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnockExpoPushNotificationProvider.mjs","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import { Message, MessageEngagementStatus } from \"@knocklabs/client\";\nimport { useKnockClient } from \"@knocklabs/react-core\";\nimport {\n KnockPushNotificationProvider,\n usePushNotifications,\n} from \"@knocklabs/react-native\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n type Notification,\n type NotificationResponse,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\nimport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\nimport {\n DEFAULT_NOTIFICATION_BEHAVIOR,\n registerForPushNotifications as registerForPushNotificationsUtil,\n setupDefaultAndroidChannel,\n} from \"./utils\";\n\n// Re-export types for consumers\nexport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\n/**\n * Hook to access push notification functionality within a KnockExpoPushNotificationProvider.\n * @throws Error if used outside of a KnockExpoPushNotificationProvider\n */\nexport function useExpoPushNotifications(): KnockExpoPushNotificationContextType {\n const context = useContext(KnockExpoPushNotificationContext);\n\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider\",\n );\n }\n\n return context;\n}\n\n/**\n * Internal provider component that handles all the Expo push notification logic.\n */\nconst InternalExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n setupAndroidNotificationChannel = setupDefaultAndroidChannel,\n children,\n autoRegister = true,\n}) => {\n const knockClient = useKnockClient();\n const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =\n usePushNotifications();\n\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n\n // Use refs for handlers to avoid re-running effects when handlers change\n const notificationReceivedHandlerRef = useRef<\n (notification: Notification) => void\n >(() => {});\n\n const notificationTappedHandlerRef = useRef<\n (response: NotificationResponse) => void\n >(() => {});\n\n /**\n * Register a handler to be called when a notification is received in the foreground.\n */\n const onNotificationReceived = useCallback(\n (handler: (notification: Notification) => void) => {\n notificationReceivedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Register a handler to be called when a notification is tapped.\n */\n const onNotificationTapped = useCallback(\n (handler: (response: NotificationResponse) => void) => {\n notificationTappedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Manually trigger push notification registration.\n * Returns the push token if successful, or null if registration failed.\n */\n const registerForPushNotifications = useCallback(async (): Promise<\n string | null\n > => {\n try {\n knockClient.log(\"[Knock] Registering for push notifications\");\n\n const token = await registerForPushNotificationsUtil(\n setupAndroidNotificationChannel,\n );\n\n if (token) {\n knockClient.log(`[Knock] Push token received: ${token}`);\n setExpoPushToken(token);\n return token;\n }\n\n return null;\n } catch (error) {\n console.error(\"[Knock] Error registering for push notifications:\", error);\n return null;\n }\n }, [knockClient, setupAndroidNotificationChannel]);\n\n /**\n * Update the Knock message status when a notification is received or interacted with.\n * Only updates status for notifications that originated from Knock (have a knock_message_id).\n */\n const updateMessageStatus = useCallback(\n async (\n notification: Notification,\n status: MessageEngagementStatus,\n ): Promise<Message | void> => {\n const messageId = notification.request.content.data?.[\n \"knock_message_id\"\n ] as string | undefined;\n\n // Skip status update if this isn't a Knock notification\n // Fixes issue: https://github.com/knocklabs/javascript/issues/589\n if (!messageId) {\n knockClient.log(\n \"[Knock] Skipping status update for non-Knock notification\",\n );\n return;\n }\n\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n // Set up the notification handler for foreground notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const handleNotification = customNotificationHandler\n ? customNotificationHandler\n : async () => DEFAULT_NOTIFICATION_BEHAVIOR;\n\n NotificationsModule.setNotificationHandler({ handleNotification });\n }, [customNotificationHandler]);\n\n // Auto-register for push notifications on mount if enabled\n useEffect(() => {\n if (!autoRegister) {\n return;\n }\n\n let isMounted = true;\n\n const register = async () => {\n try {\n const token = await registerForPushNotifications();\n\n if (token && isMounted) {\n await registerPushTokenToChannel(token, knockExpoChannelId);\n knockClient.log(\"[Knock] Push token registered with Knock channel\");\n }\n } catch (error) {\n console.error(\"[Knock] Error during auto-registration:\", error);\n }\n };\n\n register();\n\n return () => {\n isMounted = false;\n };\n }, [\n autoRegister,\n knockExpoChannelId,\n registerForPushNotifications,\n registerPushTokenToChannel,\n knockClient,\n ]);\n\n // Set up notification listeners for received and tapped notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const receivedSubscription =\n NotificationsModule.addNotificationReceivedListener((notification) => {\n knockClient.log(\"[Knock] Notification received in foreground\");\n updateMessageStatus(notification, \"interacted\");\n notificationReceivedHandlerRef.current(notification);\n });\n\n const responseSubscription =\n NotificationsModule.addNotificationResponseReceivedListener(\n (response) => {\n knockClient.log(\"[Knock] Notification was tapped\");\n updateMessageStatus(response.notification, \"interacted\");\n notificationTappedHandlerRef.current(response);\n },\n );\n\n return () => {\n receivedSubscription.remove();\n responseSubscription.remove();\n };\n }, [knockClient, updateMessageStatus]);\n\n const contextValue: KnockExpoPushNotificationContextType = {\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived,\n onNotificationTapped,\n };\n\n return (\n <KnockExpoPushNotificationContext.Provider value={contextValue}>\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\n/**\n * Provider component for Expo push notifications with Knock.\n *\n * Wraps the internal provider with the base KnockPushNotificationProvider\n * to provide full push notification functionality.\n *\n * @example\n * ```tsx\n * <KnockProvider apiKey=\"your-api-key\" userId=\"user-id\">\n * <KnockExpoPushNotificationProvider knockExpoChannelId=\"your-channel-id\">\n * <App />\n * </KnockExpoPushNotificationProvider>\n * </KnockProvider>\n * ```\n */\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = (props) => {\n return (\n <KnockPushNotificationProvider>\n <InternalExpoPushNotificationProvider {...props} />\n </KnockPushNotificationProvider>\n );\n};\n"],"names":["KnockExpoPushNotificationContext","createContext","useExpoPushNotifications","context","useContext","InternalExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","setupAndroidNotificationChannel","setupDefaultAndroidChannel","children","autoRegister","knockClient","useKnockClient","registerPushTokenToChannel","unregisterPushTokenFromChannel","usePushNotifications","expoPushToken","setExpoPushToken","useState","notificationReceivedHandlerRef","useRef","notificationTappedHandlerRef","onNotificationReceived","useCallback","handler","onNotificationTapped","registerForPushNotifications","token","registerForPushNotificationsUtil","error","updateMessageStatus","notification","status","messageId","_a","useEffect","NotificationsModule","getNotificationsModule","handleNotification","DEFAULT_NOTIFICATION_BEHAVIOR","isMounted","receivedSubscription","responseSubscription","response","contextValue","KnockExpoPushNotificationProvider","props","KnockPushNotificationProvider","jsx"],"mappings":";;;;;;AAoCA,MAAMA,IAAmCC,EAEvC,MAAS;AAMJ,SAASC,IAAiE;AACzE,QAAAC,IAAUC,EAAWJ,CAAgC;AAE3D,MAAIG,MAAY;AACd,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAGK,SAAAA;AACT;AAKA,MAAME,IAEF,CAAC;AAAA,EACH,oBAAAC;AAAA,EACA,2BAAAC;AAAA,EACA,iCAAAC,IAAkCC;AAAA,EAClC,UAAAC;AAAA,EACA,cAAAC,IAAe;AACjB,MAAM;AACJ,QAAMC,IAAcC,EAAe,GAC7B,EAAE,4BAAAC,GAA4B,gCAAAC,EAA+B,IACjEC,EAAqB,GAEjB,CAACC,GAAeC,CAAgB,IAAIC,EAAwB,IAAI,GAGhEC,IAAiCC,EAErC,MAAM;AAAA,EAAA,CAAE,GAEJC,IAA+BD,EAEnC,MAAM;AAAA,EAAA,CAAE,GAKJE,IAAyBC;AAAA,IAC7B,CAACC,MAAkD;AACjD,MAAAL,EAA+B,UAAUK;AAAA,IAC3C;AAAA,IACA,CAAA;AAAA,EACF,GAKMC,IAAuBF;AAAA,IAC3B,CAACC,MAAsD;AACrD,MAAAH,EAA6B,UAAUG;AAAA,IACzC;AAAA,IACA,CAAA;AAAA,EACF,GAMME,IAA+BH,EAAY,YAE5C;AACC,QAAA;AACF,MAAAZ,EAAY,IAAI,4CAA4C;AAE5D,YAAMgB,IAAQ,MAAMC;AAAAA,QAClBrB;AAAA,MACF;AAEA,aAAIoB,KACUhB,EAAA,IAAI,gCAAgCgB,CAAK,EAAE,GACvDV,EAAiBU,CAAK,GACfA,KAGF;AAAA,aACAE,GAAO;AACN,qBAAA,MAAM,qDAAqDA,CAAK,GACjE;AAAA,IAAA;AAAA,EACT,GACC,CAAClB,GAAaJ,CAA+B,CAAC,GAM3CuB,IAAsBP;AAAA,IAC1B,OACEQ,GACAC,MAC4B;;AAC5B,YAAMC,KAAYC,IAAAH,EAAa,QAAQ,QAAQ,SAA7B,gBAAAG,EAChB;AAKF,UAAI,CAACD,GAAW;AACF,QAAAtB,EAAA;AAAA,UACV;AAAA,QACF;AACA;AAAA,MAAA;AAGF,aAAOA,EAAY,SAAS,aAAasB,GAAWD,CAAM;AAAA,IAC5D;AAAA,IACA,CAACrB,CAAW;AAAA,EACd;AAGA,EAAAwB,EAAU,MAAM;AACd,UAAMC,IAAsBC,EAAuB;AACnD,QAAI,CAACD,EAAqB;AAEpB,UAAAE,IAAqBhC,MAEvB,YAAYiC;AAEI,IAAAH,EAAA,uBAAuB,EAAE,oBAAAE,GAAoB;AAAA,EAAA,GAChE,CAAChC,CAAyB,CAAC,GAG9B6B,EAAU,MAAM;AACd,QAAI,CAACzB;AACH;AAGF,QAAI8B,IAAY;AAeP,YAbQ,YAAY;AACvB,UAAA;AACI,cAAAb,IAAQ,MAAMD,EAA6B;AAEjD,QAAIC,KAASa,MACL,MAAA3B,EAA2Bc,GAAOtB,CAAkB,GAC1DM,EAAY,IAAI,kDAAkD;AAAA,eAE7DkB,GAAO;AACN,gBAAA,MAAM,2CAA2CA,CAAK;AAAA,MAAA;AAAA,IAElE,GAES,GAEF,MAAM;AACC,MAAAW,IAAA;AAAA,IACd;AAAA,EAAA,GACC;AAAA,IACD9B;AAAA,IACAL;AAAA,IACAqB;AAAAA,IACAb;AAAA,IACAF;AAAA,EAAA,CACD,GAGDwB,EAAU,MAAM;AACd,UAAMC,IAAsBC,EAAuB;AACnD,QAAI,CAACD,EAAqB;AAE1B,UAAMK,IACJL,EAAoB,gCAAgC,CAACL,MAAiB;AACpE,MAAApB,EAAY,IAAI,6CAA6C,GAC7DmB,EAAoBC,GAAc,YAAY,GAC9CZ,EAA+B,QAAQY,CAAY;AAAA,IAAA,CACpD,GAEGW,IACJN,EAAoB;AAAA,MAClB,CAACO,MAAa;AACZ,QAAAhC,EAAY,IAAI,iCAAiC,GAC7BmB,EAAAa,EAAS,cAAc,YAAY,GACvDtB,EAA6B,QAAQsB,CAAQ;AAAA,MAAA;AAAA,IAEjD;AAEF,WAAO,MAAM;AACX,MAAAF,EAAqB,OAAO,GAC5BC,EAAqB,OAAO;AAAA,IAC9B;AAAA,EAAA,GACC,CAAC/B,GAAamB,CAAmB,CAAC;AAErC,QAAMc,IAAqD;AAAA,IACzD,eAAA5B;AAAA,IAAA,8BACAU;AAAAA,IACA,4BAAAb;AAAA,IACA,gCAAAC;AAAA,IACA,wBAAAQ;AAAA,IACA,sBAAAG;AAAA,EACF;AAEA,2BACG1B,EAAiC,UAAjC,EAA0C,OAAO6C,GAC/C,UAAAnC,GACH;AAEJ,GAiBaoC,IAET,CAACC,wBAEAC,GACC,EAAA,UAAA,gBAAAC,EAAC5C,GAAsC,EAAA,GAAG0C,EAAO,CAAA,GACnD;"}
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.mjs","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import { Message, MessageEngagementStatus } from \"@knocklabs/client\";\nimport { useKnockAuthState, useKnockClient } from \"@knocklabs/react-core\";\nimport {\n KnockPushNotificationProvider,\n usePushNotifications,\n} from \"@knocklabs/react-native\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n type Notification,\n type NotificationResponse,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\nimport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\nimport {\n DEFAULT_NOTIFICATION_BEHAVIOR,\n registerForPushNotifications as registerForPushNotificationsUtil,\n setupDefaultAndroidChannel,\n} from \"./utils\";\n\n// Re-export types for consumers\nexport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\n/**\n * Hook to access push notification functionality within a KnockExpoPushNotificationProvider.\n * @throws Error if used outside of a KnockExpoPushNotificationProvider\n */\nexport function useExpoPushNotifications(): KnockExpoPushNotificationContextType {\n const context = useContext(KnockExpoPushNotificationContext);\n\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider\",\n );\n }\n\n return context;\n}\n\n/**\n * Internal provider component that handles all the Expo push notification logic.\n */\nconst InternalExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n setupAndroidNotificationChannel = setupDefaultAndroidChannel,\n children,\n autoRegister = true,\n}) => {\n const knockClient = useKnockClient();\n const { status: authStatus } = useKnockAuthState(knockClient);\n const isAuthenticated = authStatus === \"authenticated\";\n const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =\n usePushNotifications();\n\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n\n // Use refs for handlers to avoid re-running effects when handlers change\n const notificationReceivedHandlerRef = useRef<\n (notification: Notification) => void\n >(() => {});\n\n const notificationTappedHandlerRef = useRef<\n (response: NotificationResponse) => void\n >(() => {});\n\n /**\n * Register a handler to be called when a notification is received in the foreground.\n */\n const onNotificationReceived = useCallback(\n (handler: (notification: Notification) => void) => {\n notificationReceivedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Register a handler to be called when a notification is tapped.\n */\n const onNotificationTapped = useCallback(\n (handler: (response: NotificationResponse) => void) => {\n notificationTappedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Manually trigger push notification registration.\n * Returns the push token if successful, or null if registration failed.\n */\n const registerForPushNotifications = useCallback(async (): Promise<\n string | null\n > => {\n try {\n knockClient.log(\"[Knock] Registering for push notifications\");\n\n const token = await registerForPushNotificationsUtil(\n setupAndroidNotificationChannel,\n );\n\n if (token) {\n knockClient.log(`[Knock] Push token received: ${token}`);\n setExpoPushToken(token);\n return token;\n }\n\n return null;\n } catch (error) {\n console.error(\"[Knock] Error registering for push notifications:\", error);\n return null;\n }\n }, [knockClient, setupAndroidNotificationChannel]);\n\n /**\n * Update the Knock message status when a notification is received or interacted with.\n * Only updates status for notifications that originated from Knock (have a knock_message_id).\n */\n const updateMessageStatus = useCallback(\n async (\n notification: Notification,\n status: MessageEngagementStatus,\n ): Promise<Message | void> => {\n const messageId = notification.request.content.data?.[\n \"knock_message_id\"\n ] as string | undefined;\n\n // Skip status update if this isn't a Knock notification\n // Fixes issue: https://github.com/knocklabs/javascript/issues/589\n if (!messageId) {\n knockClient.log(\n \"[Knock] Skipping status update for non-Knock notification\",\n );\n return;\n }\n\n // Skip when there's no authenticated user (e.g. a notification tapped\n // after logout) — otherwise this fires a messages request with no user.\n if (!knockClient.isAuthenticated()) {\n knockClient.log(\n \"[Knock] Skipping status update; user is not authenticated\",\n );\n return;\n }\n\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n // Set up the notification handler for foreground notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const handleNotification = customNotificationHandler\n ? customNotificationHandler\n : async () => DEFAULT_NOTIFICATION_BEHAVIOR;\n\n NotificationsModule.setNotificationHandler({ handleNotification });\n }, [customNotificationHandler]);\n\n // Auto-register for push notifications once there is an authenticated user.\n // Gating on auth defers the OS permission prompt (prompting a logged-out user\n // is hostile) and avoids registering a token against no user. Because\n // `isAuthenticated` is a dependency, enabling auth later re-runs registration.\n useEffect(() => {\n if (!autoRegister || !isAuthenticated) {\n return;\n }\n\n let isMounted = true;\n\n const register = async () => {\n try {\n const token = await registerForPushNotifications();\n\n if (token && isMounted) {\n await registerPushTokenToChannel(token, knockExpoChannelId);\n knockClient.log(\"[Knock] Push token registered with Knock channel\");\n }\n } catch (error) {\n console.error(\"[Knock] Error during auto-registration:\", error);\n }\n };\n\n register();\n\n return () => {\n isMounted = false;\n };\n }, [\n autoRegister,\n isAuthenticated,\n knockExpoChannelId,\n registerForPushNotifications,\n registerPushTokenToChannel,\n knockClient,\n ]);\n\n // Set up notification listeners for received and tapped notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const receivedSubscription =\n NotificationsModule.addNotificationReceivedListener((notification) => {\n knockClient.log(\"[Knock] Notification received in foreground\");\n updateMessageStatus(notification, \"interacted\");\n notificationReceivedHandlerRef.current(notification);\n });\n\n const responseSubscription =\n NotificationsModule.addNotificationResponseReceivedListener(\n (response) => {\n knockClient.log(\"[Knock] Notification was tapped\");\n updateMessageStatus(response.notification, \"interacted\");\n notificationTappedHandlerRef.current(response);\n },\n );\n\n return () => {\n receivedSubscription.remove();\n responseSubscription.remove();\n };\n }, [knockClient, updateMessageStatus]);\n\n const contextValue: KnockExpoPushNotificationContextType = {\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived,\n onNotificationTapped,\n };\n\n return (\n <KnockExpoPushNotificationContext.Provider value={contextValue}>\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\n/**\n * Provider component for Expo push notifications with Knock.\n *\n * Wraps the internal provider with the base KnockPushNotificationProvider\n * to provide full push notification functionality.\n *\n * @example\n * ```tsx\n * <KnockProvider apiKey=\"your-api-key\" userId=\"user-id\">\n * <KnockExpoPushNotificationProvider knockExpoChannelId=\"your-channel-id\">\n * <App />\n * </KnockExpoPushNotificationProvider>\n * </KnockProvider>\n * ```\n */\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = (props) => {\n return (\n <KnockPushNotificationProvider>\n <InternalExpoPushNotificationProvider {...props} />\n </KnockPushNotificationProvider>\n );\n};\n"],"names":["KnockExpoPushNotificationContext","createContext","useExpoPushNotifications","context","useContext","InternalExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","setupAndroidNotificationChannel","setupDefaultAndroidChannel","children","autoRegister","knockClient","useKnockClient","authStatus","useKnockAuthState","isAuthenticated","registerPushTokenToChannel","unregisterPushTokenFromChannel","usePushNotifications","expoPushToken","setExpoPushToken","useState","notificationReceivedHandlerRef","useRef","notificationTappedHandlerRef","onNotificationReceived","useCallback","handler","onNotificationTapped","registerForPushNotifications","token","registerForPushNotificationsUtil","error","updateMessageStatus","notification","status","messageId","_a","useEffect","NotificationsModule","getNotificationsModule","handleNotification","DEFAULT_NOTIFICATION_BEHAVIOR","isMounted","receivedSubscription","responseSubscription","response","contextValue","KnockExpoPushNotificationProvider","props","KnockPushNotificationProvider","jsx"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnockExpoPushNotificationProvider.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"names":[],"mappings":"AAMA,OAAO,KAON,MAAM,OAAO,CAAC;AAOf,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,MAAM,SAAS,CAAC;AAQjB,YAAY,EACV,oCAAoC,EACpC,sCAAsC,GACvC,MAAM,SAAS,CAAC;AAMjB;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,oCAAoC,CAU/E;
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"names":[],"mappings":"AAMA,OAAO,KAON,MAAM,OAAO,CAAC;AAOf,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,MAAM,SAAS,CAAC;AAQjB,YAAY,EACV,oCAAoC,EACpC,sCAAsC,GACvC,MAAM,SAAS,CAAC;AAMjB;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,oCAAoC,CAU/E;AA+MD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,EAAE,KAAK,CAAC,EAAE,CACtD,sCAAsC,CAOvC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knocklabs/expo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-rc.0",
|
|
4
4
|
"author": "@knocklabs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"typings": "dist/types/index.d.ts",
|
|
10
10
|
"react-native": "./src/index.ts",
|
|
11
11
|
"exports": {
|
|
12
|
+
"./package.json": "./package.json",
|
|
12
13
|
".": {
|
|
13
14
|
"require": "./dist/cjs/index.js",
|
|
14
15
|
"types": "./dist/types/index.d.ts",
|
|
@@ -52,9 +53,9 @@
|
|
|
52
53
|
"react-native-gesture-handler": ">=2.0.0"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
|
-
"@knocklabs/client": "^0.
|
|
56
|
-
"@knocklabs/react-core": "^0.
|
|
57
|
-
"@knocklabs/react-native": "^0.
|
|
56
|
+
"@knocklabs/client": "^0.22.0-rc.0",
|
|
57
|
+
"@knocklabs/react-core": "^0.14.0-rc.0",
|
|
58
|
+
"@knocklabs/react-native": "^0.10.0-rc.0",
|
|
58
59
|
"react-native-render-html": "^6.3.4",
|
|
59
60
|
"react-native-svg": "~15.15.3"
|
|
60
61
|
},
|
|
@@ -62,21 +63,21 @@
|
|
|
62
63
|
"@codecov/vite-plugin": "^2.0.1",
|
|
63
64
|
"@testing-library/jest-dom": "^6.8.0",
|
|
64
65
|
"@testing-library/react": "^16.3.2",
|
|
65
|
-
"@types/react": "^19.2.
|
|
66
|
+
"@types/react": "^19.2.17",
|
|
66
67
|
"@types/react-native-htmlview": "^0.16.6",
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
68
|
-
"@typescript-eslint/parser": "^8.
|
|
68
|
+
"@typescript-eslint/eslint-plugin": "^8.59.4",
|
|
69
|
+
"@typescript-eslint/parser": "^8.59.4",
|
|
69
70
|
"@vitejs/plugin-react": "^4.5.1",
|
|
70
71
|
"eslint": "^8.56.0",
|
|
71
72
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
72
73
|
"eslint-plugin-react-refresh": "^0.5.2",
|
|
73
|
-
"expo": "~
|
|
74
|
-
"expo-constants": "~
|
|
75
|
-
"expo-device": "~
|
|
76
|
-
"expo-notifications": "~
|
|
74
|
+
"expo": "~56.0.5",
|
|
75
|
+
"expo-constants": "~56.0.16",
|
|
76
|
+
"expo-device": "~56.0.4",
|
|
77
|
+
"expo-notifications": "~56.0.14",
|
|
77
78
|
"jsdom": "^29.1.0",
|
|
78
|
-
"react": "^19.2.
|
|
79
|
-
"react-native": "^0.
|
|
79
|
+
"react": "^19.2.7",
|
|
80
|
+
"react-native": "^0.85.3",
|
|
80
81
|
"react-native-gesture-handler": "~2.30.0",
|
|
81
82
|
"rimraf": "^6.0.1",
|
|
82
83
|
"typescript": "^5.8.3",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Message, MessageEngagementStatus } from "@knocklabs/client";
|
|
2
|
-
import { useKnockClient } from "@knocklabs/react-core";
|
|
2
|
+
import { useKnockAuthState, useKnockClient } from "@knocklabs/react-core";
|
|
3
3
|
import {
|
|
4
4
|
KnockPushNotificationProvider,
|
|
5
5
|
usePushNotifications,
|
|
@@ -67,6 +67,8 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
67
67
|
autoRegister = true,
|
|
68
68
|
}) => {
|
|
69
69
|
const knockClient = useKnockClient();
|
|
70
|
+
const { status: authStatus } = useKnockAuthState(knockClient);
|
|
71
|
+
const isAuthenticated = authStatus === "authenticated";
|
|
70
72
|
const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =
|
|
71
73
|
usePushNotifications();
|
|
72
74
|
|
|
@@ -150,6 +152,15 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
150
152
|
return;
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
// Skip when there's no authenticated user (e.g. a notification tapped
|
|
156
|
+
// after logout) — otherwise this fires a messages request with no user.
|
|
157
|
+
if (!knockClient.isAuthenticated()) {
|
|
158
|
+
knockClient.log(
|
|
159
|
+
"[Knock] Skipping status update; user is not authenticated",
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
153
164
|
return knockClient.messages.updateStatus(messageId, status);
|
|
154
165
|
},
|
|
155
166
|
[knockClient],
|
|
@@ -167,9 +178,12 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
167
178
|
NotificationsModule.setNotificationHandler({ handleNotification });
|
|
168
179
|
}, [customNotificationHandler]);
|
|
169
180
|
|
|
170
|
-
// Auto-register for push notifications
|
|
181
|
+
// Auto-register for push notifications once there is an authenticated user.
|
|
182
|
+
// Gating on auth defers the OS permission prompt (prompting a logged-out user
|
|
183
|
+
// is hostile) and avoids registering a token against no user. Because
|
|
184
|
+
// `isAuthenticated` is a dependency, enabling auth later re-runs registration.
|
|
171
185
|
useEffect(() => {
|
|
172
|
-
if (!autoRegister) {
|
|
186
|
+
if (!autoRegister || !isAuthenticated) {
|
|
173
187
|
return;
|
|
174
188
|
}
|
|
175
189
|
|
|
@@ -195,6 +209,7 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
195
209
|
};
|
|
196
210
|
}, [
|
|
197
211
|
autoRegister,
|
|
212
|
+
isAuthenticated,
|
|
198
213
|
knockExpoChannelId,
|
|
199
214
|
registerForPushNotifications,
|
|
200
215
|
registerPushTokenToChannel,
|