@knocklabs/expo 0.7.0-rc.1 → 0.8.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 CHANGED
@@ -1,5 +1,69 @@
1
1
  # @knocklabs/expo
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e52c4ef: chore(deps): bump react-native-get-random-values from 1.11.0 to 2.0.0
8
+
9
+ v2 of the polyfill is new architecture only and requires React Native >= 0.81
10
+ (Expo SDK >= 54). `@knocklabs/react-native` imports it for its side effects from
11
+ `src/index.ts`, so that floor now applies to every consumer of the package, and
12
+ to `@knocklabs/expo` by way of its dependency on `@knocklabs/react-native`.
13
+
14
+ The `react-native` peer range on both packages has been narrowed from `*` to
15
+ `>=0.81`, so installs on unsupported versions surface a peer warning instead of
16
+ failing at runtime when the native module is missing. Expo SDK 54 is the release
17
+ that ships React Native 0.81, so the `expo` peer stays at `*` and the
18
+ `react-native` constraint covers both.
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [e52c4ef]
23
+ - Updated dependencies [7e3ee26]
24
+ - @knocklabs/react-native@0.11.0
25
+ - @knocklabs/react-core@0.15.0
26
+
27
+ ## 0.7.0
28
+
29
+ ### Minor Changes
30
+
31
+ - e8567eb: Add an `enabled` prop to `KnockProvider` (and an `enabled` option to `useAuthenticatedKnockClient`).
32
+
33
+ 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.
34
+
35
+ Use this instead of conditionally mounting `KnockProvider`, for example to wait for a user token that loads asynchronously:
36
+
37
+ ```tsx
38
+ <KnockProvider
39
+ apiKey={apiKey}
40
+ user={{ id: userId }}
41
+ userToken={userToken}
42
+ enabled={Boolean(userId && userToken)}
43
+ />
44
+ ```
45
+
46
+ Also fixed:
47
+
48
+ - `useFeedSettings` no longer calls `GET /v1/users/undefined/feeds/.../settings` when there's no user.
49
+ - `KnockProvider` now disconnects its client (websocket, token-refresh timer, listener) when it unmounts, instead of leaving them running.
50
+
51
+ - e8567eb: Add `useKnockAuthState()` and make Slack, MS Teams, and Expo respond to sign-in changes.
52
+
53
+ - New `useKnockAuthState(knock)` hook re-renders when the user signs in, signs out, or switches.
54
+ - Slack and MS Teams connection status now re-checks when the user changes, instead of checking once and sticking with that result.
55
+ - 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.
56
+
57
+ ### Patch Changes
58
+
59
+ - e8567eb: Fix Expo push auto-registration not re-running when the signed-in user changes in place. The auto-register effect now depends on the authenticated `userId`, so switching users on the same `KnockProvider` (where `isAuthenticated` never flips) re-registers the device token against the new user's channel data.
60
+ - Updated dependencies [e8567eb]
61
+ - Updated dependencies [e8567eb]
62
+ - Updated dependencies [e8567eb]
63
+ - @knocklabs/client@0.22.0
64
+ - @knocklabs/react-core@0.14.0
65
+ - @knocklabs/react-native@0.10.0
66
+
3
67
  ## 0.7.0-rc.1
4
68
 
5
69
  ### Patch Changes
@@ -0,0 +1 @@
1
+ "use strict";var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));exports.__toESM=s;
package/dist/cjs/index.js CHANGED
@@ -1,2 +1 @@
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
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./modules/push/KnockExpoPushNotificationProvider.js");require("./modules/push/index.js"),exports.KnockExpoPushNotificationProvider=e.KnockExpoPushNotificationProvider,exports.useExpoPushNotifications=e.useExpoPushNotifications;var t=require("@knocklabs/react-native");Object.keys(t).forEach(function(e){e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:function(){return t[e]}})});
@@ -1,2 +1,2 @@
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 q=({knockExpoChannelId:r,customNotificationHandler:c,setupAndroidNotificationChannel:h=d.setupDefaultAndroidChannel,children:E,autoRegister:k=!0})=>{const e=P.useKnockClient(),{status:C,userId:R}=P.useKnockAuthState(e),g=C==="authenticated",{registerPushTokenToChannel:u,unregisterPushTokenFromChannel:S}=K.usePushNotifications(),[T,b]=o.useState(null),p=o.useRef(()=>{}),N=o.useRef(()=>{}),w=o.useCallback(t=>{p.current=t},[]),y=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}`),b(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,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 M={expoPushToken:T,registerForPushNotifications:a,registerPushTokenToChannel:u,unregisterPushTokenFromChannel:S,onNotificationReceived:w,onNotificationTapped:y};return l.jsx(x.Provider,{value:M,children:E})},A=r=>l.jsx(K.KnockPushNotificationProvider,{children:l.jsx(q,{...r})});exports.KnockExpoPushNotificationProvider=A;exports.useExpoPushNotifications=m;
2
- //# sourceMappingURL=KnockExpoPushNotificationProvider.js.map
1
+ "use strict";const e=require("../../_virtual/_rolldown/runtime.js"),t=require("./getNotificationsModule.js"),n=require("./utils.js");let r=require("@knocklabs/react-core"),i=require("@knocklabs/react-native"),a=require("react");a=e.__toESM(a);let o=require("react/jsx-runtime");var s=(0,a.createContext)(void 0);function c(){let e=(0,a.useContext)(s);if(e===void 0)throw Error(`[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider`);return e}var l=({knockExpoChannelId:e,customNotificationHandler:c,setupAndroidNotificationChannel:l=n.setupDefaultAndroidChannel,children:u,autoRegister:d=!0})=>{let f=(0,r.useKnockClient)(),{status:p,userId:m}=(0,r.useKnockAuthState)(f),h=p===`authenticated`,{registerPushTokenToChannel:g,unregisterPushTokenFromChannel:_}=(0,i.usePushNotifications)(),[v,y]=(0,a.useState)(null),b=(0,a.useRef)(()=>{}),x=(0,a.useRef)(()=>{}),S=(0,a.useCallback)(e=>{b.current=e},[]),C=(0,a.useCallback)(e=>{x.current=e},[]),w=(0,a.useCallback)(async()=>{try{f.log(`[Knock] Registering for push notifications`);let e=await n.registerForPushNotifications(l);return e?(f.log(`[Knock] Push token received: ${e}`),y(e),e):null}catch(e){return console.error(`[Knock] Error registering for push notifications:`,e),null}},[f,l]),T=(0,a.useCallback)(async(e,t)=>{let n=e.request.content.data?.knock_message_id;if(!n){f.log(`[Knock] Skipping status update for non-Knock notification`);return}if(!f.isAuthenticated()){f.log(`[Knock] Skipping status update; user is not authenticated`);return}return f.messages.updateStatus(n,t)},[f]);(0,a.useEffect)(()=>{let e=t.getNotificationsModule();if(!e)return;let r=c||(async()=>n.DEFAULT_NOTIFICATION_BEHAVIOR);e.setNotificationHandler({handleNotification:r})},[c]),(0,a.useEffect)(()=>{if(!d||!h)return;let t=!0;return(async()=>{try{let n=await w();n&&t&&(await g(n,e),f.log(`[Knock] Push token registered with Knock channel`))}catch(e){console.error(`[Knock] Error during auto-registration:`,e)}})(),()=>{t=!1}},[d,h,m,e,w,g,f]),(0,a.useEffect)(()=>{let e=t.getNotificationsModule();if(!e)return;let n=e.addNotificationReceivedListener(e=>{f.log(`[Knock] Notification received in foreground`),T(e,`interacted`),b.current(e)}),r=e.addNotificationResponseReceivedListener(e=>{f.log(`[Knock] Notification was tapped`),T(e.notification,`interacted`),x.current(e)});return()=>{n.remove(),r.remove()}},[f,T]);let E={expoPushToken:v,registerForPushNotifications:w,registerPushTokenToChannel:g,unregisterPushTokenFromChannel:_,onNotificationReceived:S,onNotificationTapped:C};return(0,o.jsx)(s.Provider,{value:E,children:u})},u=e=>(0,o.jsx)(i.KnockPushNotificationProvider,{children:(0,o.jsx)(l,{...e})});exports.KnockExpoPushNotificationProvider=u,exports.useExpoPushNotifications=c;
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 { 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, userId } = 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 // `userId` is also a dependency so that an in-place user switch (same Knock\n // instance re-authenticated as a different user, where `isAuthenticated` never\n // flips) re-registers the device token against the new user's channel data.\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 userId,\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","userId","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
+ {"version":3,"file":"KnockExpoPushNotificationProvider.js","names":[],"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, userId } = 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 // `userId` is also a dependency so that an in-place user switch (same Knock\n // instance re-authenticated as a different user, where `isAuthenticated` never\n // flips) re-registers the device token against the new user's channel data.\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 userId,\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"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("expo-constants"),e=require("react-native"),i=t=>t&&typeof t=="object"&&"default"in t?t:{default:t},r=i(n);let o;function a(){return e.Platform.OS==="android"&&r.default.executionEnvironment===n.ExecutionEnvironment.StoreClient}let l=()=>require("expo-notifications");function s(){if(o!==void 0)return o;if(a())return console.warn(`[Knock] Push notifications (remote notifications) are not available in Expo Go on Android. This is an Expo platform limitation — push notification support was removed from Expo Go on Android in SDK 53. Push features (token registration, notification listeners) will be disabled, but all other Knock features will continue to work.
1
+ "use strict";const e=require("../../_virtual/_rolldown/runtime.js");let t=require("expo-constants");t=e.__toESM(t);let n=require("react-native");var r=void 0;function i(){return n.Platform.OS===`android`&&t.default.executionEnvironment===t.ExecutionEnvironment.StoreClient}var a=()=>require("expo-notifications");function o(){if(r!==void 0)return r;if(i())return console.warn(`[Knock] Push notifications (remote notifications) are not available in Expo Go on Android. This is an Expo platform limitation — push notification support was removed from Expo Go on Android in SDK 53. Push features (token registration, notification listeners) will be disabled, but all other Knock features will continue to work.
2
2
 
3
- To use push notifications on Android, use a development build instead of Expo Go: https://docs.expo.dev/develop/development-builds/introduction/`),o=null,o;try{o=l()}catch{console.warn("[Knock] expo-notifications could not be loaded. Push notification features will be disabled."),o=null}return o}exports.getNotificationsModule=s;
4
- //# sourceMappingURL=getNotificationsModule.js.map
3
+ To use push notifications on Android, use a development build instead of Expo Go: https://docs.expo.dev/develop/development-builds/introduction/`),r=null,r;try{r=a()}catch{console.warn(`[Knock] expo-notifications could not be loaded. Push notification features will be disabled.`),r=null}return r}exports.getNotificationsModule=o;
4
+ //# sourceMappingURL=getNotificationsModule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"getNotificationsModule.js","sources":["../../../../src/modules/push/getNotificationsModule.ts"],"sourcesContent":["import Constants, { ExecutionEnvironment } from \"expo-constants\";\nimport type * as Notifications from \"expo-notifications\";\nimport { Platform } from \"react-native\";\n\n/**\n * The type of the expo-notifications module when successfully loaded.\n */\nexport type NotificationsModule = typeof Notifications;\n\n// Type aliases derived from the expo-notifications namespace so that consumers\n// access all expo-notifications types through this module rather than importing\n// from the package directly (which can trigger runtime side-effects).\nexport type Notification = Notifications.Notification;\nexport type NotificationResponse = Notifications.NotificationResponse;\nexport type NotificationBehavior = Notifications.NotificationBehavior;\n\n/**\n * Lazily load the expo-notifications module.\n *\n * In Expo SDK 55+, `import * as Notifications from \"expo-notifications\"` triggers\n * a top-level side-effect (DevicePushTokenAutoRegistration.fx.ts) that calls\n * `addPushTokenListener()`, which throws on Android Expo Go where push notification\n * functionality has been removed (since SDK 53).\n *\n * We detect Android Expo Go before attempting the require() and skip it entirely,\n * since the throw from expo-notifications bypasses JavaScript try/catch via\n * React Native's global error handler.\n *\n * On all other environments (iOS Expo Go, development builds, production),\n * expo-notifications loads normally.\n */\n\n// Cache the module after the first load to avoid repeated require() calls and\n// environment detection checks on every access. The three states are:\n// undefined = not yet loaded (initial)\n// null = unavailable (Android Expo Go or load failure)\n// module = successfully loaded\nlet cachedModule: NotificationsModule | null | undefined = undefined;\n\nfunction isAndroidExpoGo(): boolean {\n return (\n Platform.OS === \"android\" &&\n Constants.executionEnvironment === ExecutionEnvironment.StoreClient\n );\n}\n\n// Abstracted for testability — Vitest cannot intercept require() calls\n// inside dynamically imported modules after vi.resetModules().\n/* v8 ignore next 3 -- default require is replaced in tests via _resetForTesting */\nlet requireNotifications: () => NotificationsModule = () =>\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require(\"expo-notifications\") as NotificationsModule;\n\nexport function getNotificationsModule(): NotificationsModule | null {\n if (cachedModule !== undefined) {\n return cachedModule;\n }\n\n if (isAndroidExpoGo()) {\n console.warn(\n \"[Knock] Push notifications (remote notifications) are not available in Expo Go \" +\n \"on Android. This is an Expo platform limitation — push notification support was \" +\n \"removed from Expo Go on Android in SDK 53. Push features (token registration, \" +\n \"notification listeners) will be disabled, but all other Knock features will \" +\n \"continue to work.\\n\\n\" +\n \"To use push notifications on Android, use a development build instead of Expo Go: \" +\n \"https://docs.expo.dev/develop/development-builds/introduction/\",\n );\n cachedModule = null;\n return cachedModule;\n }\n\n try {\n cachedModule = requireNotifications();\n } catch {\n console.warn(\n \"[Knock] expo-notifications could not be loaded. \" +\n \"Push notification features will be disabled.\",\n );\n cachedModule = null;\n }\n\n return cachedModule;\n}\n\n/**\n * @internal Test-only — reset the cached module and optionally override\n * the require function used to load expo-notifications.\n */\nexport function _resetForTesting(\n overrideRequire?: () => NotificationsModule,\n): void {\n cachedModule = undefined;\n if (overrideRequire) {\n requireNotifications = overrideRequire;\n }\n}\n"],"names":["cachedModule","isAndroidExpoGo","Platform","Constants","ExecutionEnvironment","requireNotifications","getNotificationsModule"],"mappings":"2MAqCA,IAAIA,EAEJ,SAASC,GAA2B,CAClC,OACEC,EAAAA,SAAS,KAAO,WAChBC,EAAAA,QAAU,uBAAyBC,EAAAA,qBAAqB,WAE5D,CAKA,IAAIC,EAAkD,IAEpD,QAAQ,oBAAoB,EAEvB,SAASC,GAAqD,CACnE,GAAIN,IAAiB,OACZ,OAAAA,EAGT,GAAIC,IACM,eAAA,KACN;AAAA;AAAA,iJAOF,EACeD,EAAA,KACRA,EAGL,GAAA,CACFA,EAAeK,EAAqB,CAAA,MAC9B,CACE,QAAA,KACN,8FAEF,EACeL,EAAA,IAAA,CAGV,OAAAA,CACT"}
1
+ {"version":3,"file":"getNotificationsModule.js","names":[],"sources":["../../../../src/modules/push/getNotificationsModule.ts"],"sourcesContent":["import Constants, { ExecutionEnvironment } from \"expo-constants\";\nimport type * as Notifications from \"expo-notifications\";\nimport { Platform } from \"react-native\";\n\n/**\n * The type of the expo-notifications module when successfully loaded.\n */\nexport type NotificationsModule = typeof Notifications;\n\n// Type aliases derived from the expo-notifications namespace so that consumers\n// access all expo-notifications types through this module rather than importing\n// from the package directly (which can trigger runtime side-effects).\nexport type Notification = Notifications.Notification;\nexport type NotificationResponse = Notifications.NotificationResponse;\nexport type NotificationBehavior = Notifications.NotificationBehavior;\n\n/**\n * Lazily load the expo-notifications module.\n *\n * In Expo SDK 55+, `import * as Notifications from \"expo-notifications\"` triggers\n * a top-level side-effect (DevicePushTokenAutoRegistration.fx.ts) that calls\n * `addPushTokenListener()`, which throws on Android Expo Go where push notification\n * functionality has been removed (since SDK 53).\n *\n * We detect Android Expo Go before attempting the require() and skip it entirely,\n * since the throw from expo-notifications bypasses JavaScript try/catch via\n * React Native's global error handler.\n *\n * On all other environments (iOS Expo Go, development builds, production),\n * expo-notifications loads normally.\n */\n\n// Cache the module after the first load to avoid repeated require() calls and\n// environment detection checks on every access. The three states are:\n// undefined = not yet loaded (initial)\n// null = unavailable (Android Expo Go or load failure)\n// module = successfully loaded\nlet cachedModule: NotificationsModule | null | undefined = undefined;\n\nfunction isAndroidExpoGo(): boolean {\n return (\n Platform.OS === \"android\" &&\n Constants.executionEnvironment === ExecutionEnvironment.StoreClient\n );\n}\n\n// Abstracted for testability — Vitest cannot intercept require() calls\n// inside dynamically imported modules after vi.resetModules().\n/* v8 ignore next 3 -- default require is replaced in tests via _resetForTesting */\nlet requireNotifications: () => NotificationsModule = () =>\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require(\"expo-notifications\") as NotificationsModule;\n\nexport function getNotificationsModule(): NotificationsModule | null {\n if (cachedModule !== undefined) {\n return cachedModule;\n }\n\n if (isAndroidExpoGo()) {\n console.warn(\n \"[Knock] Push notifications (remote notifications) are not available in Expo Go \" +\n \"on Android. This is an Expo platform limitation — push notification support was \" +\n \"removed from Expo Go on Android in SDK 53. Push features (token registration, \" +\n \"notification listeners) will be disabled, but all other Knock features will \" +\n \"continue to work.\\n\\n\" +\n \"To use push notifications on Android, use a development build instead of Expo Go: \" +\n \"https://docs.expo.dev/develop/development-builds/introduction/\",\n );\n cachedModule = null;\n return cachedModule;\n }\n\n try {\n cachedModule = requireNotifications();\n } catch {\n console.warn(\n \"[Knock] expo-notifications could not be loaded. \" +\n \"Push notification features will be disabled.\",\n );\n cachedModule = null;\n }\n\n return cachedModule;\n}\n\n/**\n * @internal Test-only — reset the cached module and optionally override\n * the require function used to load expo-notifications.\n */\nexport function _resetForTesting(\n overrideRequire?: () => NotificationsModule,\n): void {\n cachedModule = undefined;\n if (overrideRequire) {\n requireNotifications = overrideRequire;\n }\n}\n"],"mappings":"iJAqCA,IAAI,EAAuD,IAAA,GAE3D,SAAS,GAA2B,CAClC,OACE,EAAA,SAAS,KAAO,WAChB,EAAA,QAAU,uBAAyB,EAAA,qBAAqB,WAE5D,CAKA,IAAI,MAEF,QAAQ,oBAAoB,EAE9B,SAAgB,GAAqD,CACnE,GAAI,IAAiB,IAAA,GACnB,OAAO,EAGT,GAAI,EAAgB,EAWlB,OAVA,QAAQ,KACN;;iJAOF,EACA,EAAe,KACR,EAGT,GAAI,CACF,EAAe,EAAqB,CACtC,MAAQ,CACN,QAAQ,KACN,8FAEF,EACA,EAAe,IACjB,CAEA,OAAO,CACT"}
@@ -0,0 +1 @@
1
+ "use strict";const e=require("./KnockExpoPushNotificationProvider.js");exports.KnockExpoPushNotificationProvider=e.KnockExpoPushNotificationProvider,exports.useExpoPushNotifications=e.useExpoPushNotifications;
@@ -1,6 +1,6 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("expo-constants"),y=require("expo-device"),I=require("react-native"),r=require("./getNotificationsModule.js"),N=t=>t&&typeof t=="object"&&"default"in t?t:{default:t};function A(t){if(t&&typeof t=="object"&&"default"in t)return t;const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const n in t)if(n!=="default"){const o=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,o.get?o:{enumerable:!0,get:()=>t[n]})}}return e.default=t,Object.freeze(e)}const C=N(P),x=A(y),S={shouldShowAlert:!0,shouldPlaySound:!0,shouldSetBadge:!0,shouldShowBanner:!0,shouldShowList:!0};function p(){var e,n,o,s,a,u,c,f,l,d;const t=C.default;return(o=(n=(e=t.expoConfig)==null?void 0:e.extra)==null?void 0:n.eas)!=null&&o.projectId?t.expoConfig.extra.eas.projectId:(s=t.easConfig)!=null&&s.projectId?t.easConfig.projectId:(c=(u=(a=t.manifest)==null?void 0:a.extra)==null?void 0:u.eas)!=null&&c.projectId?t.manifest.extra.eas.projectId:(d=(l=(f=t.manifest2)==null?void 0:f.extra)==null?void 0:l.eas)!=null&&d.projectId?t.manifest2.extra.eas.projectId:null}async function g(){const t=r.getNotificationsModule();if(!t)return"unavailable";const{status:e}=await t.getPermissionsAsync();if(e==="granted")return e;const{status:n}=await t.requestPermissionsAsync();return n}async function h(){const t=p();if(!t)return console.error("[Knock] Expo Project ID is not defined in the app configuration. Make sure you have configured your project with EAS. The projectId should be in app.json/app.config.js at extra.eas.projectId."),null;const e=r.getNotificationsModule();if(!e)return null;const n=await e.getExpoPushTokenAsync({projectId:t});return(n==null?void 0:n.data)??null}async function m(){const t=r.getNotificationsModule();t&&await t.setNotificationChannelAsync("default",{name:"Default",importance:t.AndroidImportance.MAX,vibrationPattern:[0,250,250,250],lightColor:"#FF231F7C"})}function j(){return x.isDevice}function i(){return I.Platform.OS==="android"}async function b(t=m){if(!j())return console.warn("[Knock] Must use physical device for Push Notifications. Push notifications are not supported on emulators/simulators."),null;i()&&await t();const e=await g();if(e==="unavailable")return null;if(e!=="granted")return console.warn(`[Knock] Push notification permission not granted. Status: ${e}. User may have denied the permission or the system blocked it.`),null;try{return await h()}catch(n){return console.error("[Knock] Error getting Expo push token:",n),i()&&console.error(`[Knock] Android push token registration failed. Common causes:
1
+ "use strict";const e=require("../../_virtual/_rolldown/runtime.js"),t=require("./getNotificationsModule.js");let n=require("expo-constants");n=e.__toESM(n);let r=require("react-native"),i=require("expo-device");i=e.__toESM(i);var a={shouldShowAlert:!0,shouldPlaySound:!0,shouldSetBadge:!0,shouldShowBanner:!0,shouldShowList:!0};function o(){let e=n.default;return e.expoConfig?.extra?.eas?.projectId?e.expoConfig.extra.eas.projectId:e.easConfig?.projectId?e.easConfig.projectId:e.manifest?.extra?.eas?.projectId?e.manifest.extra.eas.projectId:e.manifest2?.extra?.eas?.projectId?e.manifest2.extra.eas.projectId:null}async function s(){let e=t.getNotificationsModule();if(!e)return`unavailable`;let{status:n}=await e.getPermissionsAsync();if(n===`granted`)return n;let{status:r}=await e.requestPermissionsAsync();return r}async function c(){let e=o();if(!e)return console.error(`[Knock] Expo Project ID is not defined in the app configuration. Make sure you have configured your project with EAS. The projectId should be in app.json/app.config.js at extra.eas.projectId.`),null;let n=t.getNotificationsModule();return n?(await n.getExpoPushTokenAsync({projectId:e}))?.data??null:null}async function l(){let e=t.getNotificationsModule();e&&await e.setNotificationChannelAsync(`default`,{name:`Default`,importance:e.AndroidImportance.MAX,vibrationPattern:[0,250,250,250],lightColor:`#FF231F7C`})}function u(){return i.isDevice}function d(){return r.Platform.OS===`android`}async function f(e=l){if(!u())return console.warn(`[Knock] Must use physical device for Push Notifications. Push notifications are not supported on emulators/simulators.`),null;d()&&await e();let t=await s();if(t===`unavailable`)return null;if(t!==`granted`)return console.warn(`[Knock] Push notification permission not granted. Status: ${t}. User may have denied the permission or the system blocked it.`),null;try{return await c()}catch(e){return console.error(`[Knock] Error getting Expo push token:`,e),d()&&console.error(`[Knock] Android push token registration failed. Common causes:
2
2
  1. FCM is not configured (google-services.json missing)
3
3
  2. Running on an emulator
4
4
  3. Network connectivity issues
5
- 4. expo-notifications plugin not configured in app.json`),null}}exports.DEFAULT_NOTIFICATION_BEHAVIOR=S;exports.getExpoPushToken=h;exports.getProjectId=p;exports.isAndroid=i;exports.isPushNotificationSupported=j;exports.registerForPushNotifications=b;exports.requestPushPermission=g;exports.setupDefaultAndroidChannel=m;
6
- //# sourceMappingURL=utils.js.map
5
+ 4. expo-notifications plugin not configured in app.json`),null}}exports.DEFAULT_NOTIFICATION_BEHAVIOR=a,exports.getExpoPushToken=c,exports.getProjectId=o,exports.isAndroid=d,exports.isPushNotificationSupported=u,exports.registerForPushNotifications=f,exports.requestPushPermission=s,exports.setupDefaultAndroidChannel=l;
6
+ //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../../../src/modules/push/utils.ts"],"sourcesContent":["import Constants from \"expo-constants\";\nimport * as Device from \"expo-device\";\nimport { Platform } from \"react-native\";\n\nimport {\n type NotificationBehavior,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ExpoConstants = typeof Constants & Record<string, any>;\n\n/**\n * Permission status values returned by expo-notifications.\n * \"unavailable\" is returned when the notifications module could not be loaded\n * (e.g. Android Expo Go where push support was removed in SDK 53).\n */\nexport type PushPermissionStatus =\n | \"granted\"\n | \"denied\"\n | \"undetermined\"\n | \"unavailable\";\n\n/**\n * Default notification behavior when a notification is received.\n */\nexport const DEFAULT_NOTIFICATION_BEHAVIOR: NotificationBehavior = {\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: true,\n shouldShowBanner: true,\n shouldShowList: true,\n};\n\n/**\n * Get the Expo project ID from various possible sources.\n * Different Expo SDK versions and configurations store this differently.\n */\nexport function getProjectId(): string | null {\n const constants = Constants as ExpoConstants;\n\n // Try Constants.expoConfig.extra.eas.projectId (common in EAS builds)\n if (constants.expoConfig?.extra?.eas?.projectId) {\n return constants.expoConfig.extra.eas.projectId;\n }\n\n // Try Constants.easConfig?.projectId (available in newer SDK versions)\n if (constants.easConfig?.projectId) {\n return constants.easConfig.projectId;\n }\n\n // Try Constants.manifest?.extra?.eas?.projectId (older SDK versions)\n if (constants.manifest?.extra?.eas?.projectId) {\n return constants.manifest.extra.eas.projectId;\n }\n\n // Try Constants.manifest2?.extra?.eas?.projectId (Expo SDK 46+)\n if (constants.manifest2?.extra?.eas?.projectId) {\n return constants.manifest2.extra.eas.projectId;\n }\n\n return null;\n}\n\n/**\n * Request push notification permissions if not already granted.\n * @returns The permission status\n */\nexport async function requestPushPermission(): Promise<PushPermissionStatus> {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return \"unavailable\";\n }\n\n const { status: existingStatus } =\n await NotificationsModule.getPermissionsAsync();\n\n if (existingStatus === \"granted\") {\n return existingStatus;\n }\n\n const { status } = await NotificationsModule.requestPermissionsAsync();\n return status as PushPermissionStatus;\n}\n\n/**\n * Get the Expo push token for this device.\n * @returns The push token or null if unable to get one\n */\nexport async function getExpoPushToken(): Promise<string | null> {\n const projectId = getProjectId();\n\n if (!projectId) {\n console.error(\n \"[Knock] Expo Project ID is not defined in the app configuration. \" +\n \"Make sure you have configured your project with EAS. \" +\n \"The projectId should be in app.json/app.config.js at extra.eas.projectId.\",\n );\n return null;\n }\n\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return null;\n }\n\n const token = await NotificationsModule.getExpoPushTokenAsync({ projectId });\n return token?.data ?? null;\n}\n\n/**\n * Set up the default Android notification channel.\n */\nexport async function setupDefaultAndroidChannel(): Promise<void> {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return;\n }\n\n await NotificationsModule.setNotificationChannelAsync(\"default\", {\n name: \"Default\",\n importance: NotificationsModule.AndroidImportance.MAX,\n vibrationPattern: [0, 250, 250, 250],\n lightColor: \"#FF231F7C\",\n });\n}\n\n/**\n * Check if the current environment supports push notifications.\n * @returns true if push notifications are supported\n */\nexport function isPushNotificationSupported(): boolean {\n return Device.isDevice;\n}\n\n/**\n * Check if the current platform is Android.\n */\nexport function isAndroid(): boolean {\n return Platform.OS === \"android\";\n}\n\n/**\n * Request permissions and get a push token.\n * Handles Android-specific channel setup and provides appropriate error messaging.\n *\n * @param setupAndroidChannel - Function to set up the Android notification channel\n * @returns The push token string or null if registration failed\n */\nexport async function registerForPushNotifications(\n setupAndroidChannel: () => Promise<void> = setupDefaultAndroidChannel,\n): Promise<string | null> {\n // Check for device support\n if (!isPushNotificationSupported()) {\n console.warn(\n \"[Knock] Must use physical device for Push Notifications. \" +\n \"Push notifications are not supported on emulators/simulators.\",\n );\n return null;\n }\n\n // Setup Android notification channel before requesting permissions\n // This is REQUIRED for Android 13+ to show the permission prompt\n if (isAndroid()) {\n await setupAndroidChannel();\n }\n\n const permissionStatus = await requestPushPermission();\n\n if (permissionStatus === \"unavailable\") {\n // Module couldn't be loaded (e.g. Android Expo Go) — the warning is\n // already emitted by getNotificationsModule, so just bail silently.\n return null;\n }\n\n if (permissionStatus !== \"granted\") {\n console.warn(\n `[Knock] Push notification permission not granted. Status: ${permissionStatus}. ` +\n \"User may have denied the permission or the system blocked it.\",\n );\n return null;\n }\n\n try {\n return await getExpoPushToken();\n } catch (error) {\n console.error(\"[Knock] Error getting Expo push token:\", error);\n\n if (isAndroid()) {\n console.error(\n \"[Knock] Android push token registration failed. Common causes:\\n\" +\n \"1. FCM is not configured (google-services.json missing)\\n\" +\n \"2. Running on an emulator\\n\" +\n \"3. Network connectivity issues\\n\" +\n \"4. expo-notifications plugin not configured in app.json\",\n );\n }\n\n return null;\n }\n}\n"],"names":["DEFAULT_NOTIFICATION_BEHAVIOR","getProjectId","constants","Constants","_c","_b","_a","_d","_g","_f","_e","_j","_i","_h","requestPushPermission","NotificationsModule","getNotificationsModule","existingStatus","status","getExpoPushToken","projectId","token","setupDefaultAndroidChannel","isPushNotificationSupported","Device","isAndroid","Platform","registerForPushNotifications","setupAndroidChannel","permissionStatus","error"],"mappings":"wlBA0BaA,EAAsD,CACjE,gBAAiB,GACjB,gBAAiB,GACjB,eAAgB,GAChB,iBAAkB,GAClB,eAAgB,EAClB,EAMO,SAASC,GAA8B,yBAC5C,MAAMC,EAAYC,EAAA,QAGlB,OAAIC,GAAAC,GAAAC,EAAAJ,EAAU,aAAV,YAAAI,EAAsB,QAAtB,YAAAD,EAA6B,MAA7B,MAAAD,EAAkC,UAC7BF,EAAU,WAAW,MAAM,IAAI,WAIpCK,EAAAL,EAAU,YAAV,MAAAK,EAAqB,UAChBL,EAAU,UAAU,WAIzBM,GAAAC,GAAAC,EAAAR,EAAU,WAAV,YAAAQ,EAAoB,QAApB,YAAAD,EAA2B,MAA3B,MAAAD,EAAgC,UAC3BN,EAAU,SAAS,MAAM,IAAI,WAIlCS,GAAAC,GAAAC,EAAAX,EAAU,YAAV,YAAAW,EAAqB,QAArB,YAAAD,EAA4B,MAA5B,MAAAD,EAAiC,UAC5BT,EAAU,UAAU,MAAM,IAAI,UAGhC,IACT,CAMA,eAAsBY,GAAuD,CAC3E,MAAMC,EAAsBC,EAAAA,uBAAuB,EACnD,GAAI,CAACD,EACI,MAAA,cAGT,KAAM,CAAE,OAAQE,CACd,EAAA,MAAMF,EAAoB,oBAAoB,EAEhD,GAAIE,IAAmB,UACd,OAAAA,EAGT,KAAM,CAAE,OAAAC,CAAA,EAAW,MAAMH,EAAoB,wBAAwB,EAC9D,OAAAG,CACT,CAMA,eAAsBC,GAA2C,CAC/D,MAAMC,EAAYnB,EAAa,EAE/B,GAAI,CAACmB,EACK,eAAA,MACN,iMAGF,EACO,KAGT,MAAML,EAAsBC,EAAAA,uBAAuB,EACnD,GAAI,CAACD,EACI,OAAA,KAGT,MAAMM,EAAQ,MAAMN,EAAoB,sBAAsB,CAAE,UAAAK,EAAW,EAC3E,OAAOC,GAAA,YAAAA,EAAO,OAAQ,IACxB,CAKA,eAAsBC,GAA4C,CAChE,MAAMP,EAAsBC,EAAAA,uBAAuB,EAC9CD,GAIC,MAAAA,EAAoB,4BAA4B,UAAW,CAC/D,KAAM,UACN,WAAYA,EAAoB,kBAAkB,IAClD,iBAAkB,CAAC,EAAG,IAAK,IAAK,GAAG,EACnC,WAAY,WAAA,CACb,CACH,CAMO,SAASQ,GAAuC,CACrD,OAAOC,EAAO,QAChB,CAKO,SAASC,GAAqB,CACnC,OAAOC,EAAAA,SAAS,KAAO,SACzB,CASsB,eAAAC,EACpBC,EAA2CN,EACnB,CAEpB,GAAA,CAACC,IACK,eAAA,KACN,wHAEF,EACO,KAKLE,KACF,MAAMG,EAAoB,EAGtB,MAAAC,EAAmB,MAAMf,EAAsB,EAErD,GAAIe,IAAqB,cAGhB,OAAA,KAGT,GAAIA,IAAqB,UACf,eAAA,KACN,6DAA6DA,CAAgB,iEAE/E,EACO,KAGL,GAAA,CACF,OAAO,MAAMV,EAAiB,QACvBW,EAAO,CACN,eAAA,MAAM,yCAA0CA,CAAK,EAEzDL,KACM,QAAA,MACN;AAAA;AAAA;AAAA;AAAA,wDAKF,EAGK,IAAA,CAEX"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../../src/modules/push/utils.ts"],"sourcesContent":["import Constants from \"expo-constants\";\nimport * as Device from \"expo-device\";\nimport { Platform } from \"react-native\";\n\nimport {\n type NotificationBehavior,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ExpoConstants = typeof Constants & Record<string, any>;\n\n/**\n * Permission status values returned by expo-notifications.\n * \"unavailable\" is returned when the notifications module could not be loaded\n * (e.g. Android Expo Go where push support was removed in SDK 53).\n */\nexport type PushPermissionStatus =\n | \"granted\"\n | \"denied\"\n | \"undetermined\"\n | \"unavailable\";\n\n/**\n * Default notification behavior when a notification is received.\n */\nexport const DEFAULT_NOTIFICATION_BEHAVIOR: NotificationBehavior = {\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: true,\n shouldShowBanner: true,\n shouldShowList: true,\n};\n\n/**\n * Get the Expo project ID from various possible sources.\n * Different Expo SDK versions and configurations store this differently.\n */\nexport function getProjectId(): string | null {\n const constants = Constants as ExpoConstants;\n\n // Try Constants.expoConfig.extra.eas.projectId (common in EAS builds)\n if (constants.expoConfig?.extra?.eas?.projectId) {\n return constants.expoConfig.extra.eas.projectId;\n }\n\n // Try Constants.easConfig?.projectId (available in newer SDK versions)\n if (constants.easConfig?.projectId) {\n return constants.easConfig.projectId;\n }\n\n // Try Constants.manifest?.extra?.eas?.projectId (older SDK versions)\n if (constants.manifest?.extra?.eas?.projectId) {\n return constants.manifest.extra.eas.projectId;\n }\n\n // Try Constants.manifest2?.extra?.eas?.projectId (Expo SDK 46+)\n if (constants.manifest2?.extra?.eas?.projectId) {\n return constants.manifest2.extra.eas.projectId;\n }\n\n return null;\n}\n\n/**\n * Request push notification permissions if not already granted.\n * @returns The permission status\n */\nexport async function requestPushPermission(): Promise<PushPermissionStatus> {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return \"unavailable\";\n }\n\n const { status: existingStatus } =\n await NotificationsModule.getPermissionsAsync();\n\n if (existingStatus === \"granted\") {\n return existingStatus;\n }\n\n const { status } = await NotificationsModule.requestPermissionsAsync();\n return status as PushPermissionStatus;\n}\n\n/**\n * Get the Expo push token for this device.\n * @returns The push token or null if unable to get one\n */\nexport async function getExpoPushToken(): Promise<string | null> {\n const projectId = getProjectId();\n\n if (!projectId) {\n console.error(\n \"[Knock] Expo Project ID is not defined in the app configuration. \" +\n \"Make sure you have configured your project with EAS. \" +\n \"The projectId should be in app.json/app.config.js at extra.eas.projectId.\",\n );\n return null;\n }\n\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return null;\n }\n\n const token = await NotificationsModule.getExpoPushTokenAsync({ projectId });\n return token?.data ?? null;\n}\n\n/**\n * Set up the default Android notification channel.\n */\nexport async function setupDefaultAndroidChannel(): Promise<void> {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return;\n }\n\n await NotificationsModule.setNotificationChannelAsync(\"default\", {\n name: \"Default\",\n importance: NotificationsModule.AndroidImportance.MAX,\n vibrationPattern: [0, 250, 250, 250],\n lightColor: \"#FF231F7C\",\n });\n}\n\n/**\n * Check if the current environment supports push notifications.\n * @returns true if push notifications are supported\n */\nexport function isPushNotificationSupported(): boolean {\n return Device.isDevice;\n}\n\n/**\n * Check if the current platform is Android.\n */\nexport function isAndroid(): boolean {\n return Platform.OS === \"android\";\n}\n\n/**\n * Request permissions and get a push token.\n * Handles Android-specific channel setup and provides appropriate error messaging.\n *\n * @param setupAndroidChannel - Function to set up the Android notification channel\n * @returns The push token string or null if registration failed\n */\nexport async function registerForPushNotifications(\n setupAndroidChannel: () => Promise<void> = setupDefaultAndroidChannel,\n): Promise<string | null> {\n // Check for device support\n if (!isPushNotificationSupported()) {\n console.warn(\n \"[Knock] Must use physical device for Push Notifications. \" +\n \"Push notifications are not supported on emulators/simulators.\",\n );\n return null;\n }\n\n // Setup Android notification channel before requesting permissions\n // This is REQUIRED for Android 13+ to show the permission prompt\n if (isAndroid()) {\n await setupAndroidChannel();\n }\n\n const permissionStatus = await requestPushPermission();\n\n if (permissionStatus === \"unavailable\") {\n // Module couldn't be loaded (e.g. Android Expo Go) — the warning is\n // already emitted by getNotificationsModule, so just bail silently.\n return null;\n }\n\n if (permissionStatus !== \"granted\") {\n console.warn(\n `[Knock] Push notification permission not granted. Status: ${permissionStatus}. ` +\n \"User may have denied the permission or the system blocked it.\",\n );\n return null;\n }\n\n try {\n return await getExpoPushToken();\n } catch (error) {\n console.error(\"[Knock] Error getting Expo push token:\", error);\n\n if (isAndroid()) {\n console.error(\n \"[Knock] Android push token registration failed. Common causes:\\n\" +\n \"1. FCM is not configured (google-services.json missing)\\n\" +\n \"2. Running on an emulator\\n\" +\n \"3. Network connectivity issues\\n\" +\n \"4. expo-notifications plugin not configured in app.json\",\n );\n }\n\n return null;\n }\n}\n"],"mappings":"kOA0BA,IAAa,EAAsD,CACjE,gBAAiB,GACjB,gBAAiB,GACjB,eAAgB,GAChB,iBAAkB,GAClB,eAAgB,EAClB,EAMA,SAAgB,GAA8B,CAC5C,IAAM,EAAY,EAAA,QAsBlB,OAnBI,EAAU,YAAY,OAAO,KAAK,UAC7B,EAAU,WAAW,MAAM,IAAI,UAIpC,EAAU,WAAW,UAChB,EAAU,UAAU,UAIzB,EAAU,UAAU,OAAO,KAAK,UAC3B,EAAU,SAAS,MAAM,IAAI,UAIlC,EAAU,WAAW,OAAO,KAAK,UAC5B,EAAU,UAAU,MAAM,IAAI,UAGhC,IACT,CAMA,eAAsB,GAAuD,CAC3E,IAAM,EAAsB,EAAA,uBAAuB,EACnD,GAAI,CAAC,EACH,MAAO,cAGT,GAAM,CAAE,OAAQ,GACd,MAAM,EAAoB,oBAAoB,EAEhD,GAAI,IAAmB,UACrB,OAAO,EAGT,GAAM,CAAE,UAAW,MAAM,EAAoB,wBAAwB,EACrE,OAAO,CACT,CAMA,eAAsB,GAA2C,CAC/D,IAAM,EAAY,EAAa,EAE/B,GAAI,CAAC,EAMH,OALA,QAAQ,MACN,iMAGF,EACO,KAGT,IAAM,EAAsB,EAAA,uBAAuB,EAMnD,OALK,GAKE,MADa,EAAoB,sBAAsB,CAAE,WAAU,CAAC,EAAA,EAC7D,MAAQ,KAJb,IAKX,CAKA,eAAsB,GAA4C,CAChE,IAAM,EAAsB,EAAA,uBAAuB,EAC9C,GAIL,MAAM,EAAoB,4BAA4B,UAAW,CAC/D,KAAM,UACN,WAAY,EAAoB,kBAAkB,IAClD,iBAAkB,CAAC,EAAG,IAAK,IAAK,GAAG,EACnC,WAAY,WACd,CAAC,CACH,CAMA,SAAgB,GAAuC,CACrD,OAAO,EAAO,QAChB,CAKA,SAAgB,GAAqB,CACnC,OAAO,EAAA,SAAS,KAAO,SACzB,CASA,eAAsB,EACpB,EAA2C,EACnB,CAExB,GAAI,CAAC,EAA4B,EAK/B,OAJA,QAAQ,KACN,wHAEF,EACO,KAKL,EAAU,GACZ,MAAM,EAAoB,EAG5B,IAAM,EAAmB,MAAM,EAAsB,EAErD,GAAI,IAAqB,cAGvB,OAAO,KAGT,GAAI,IAAqB,UAKvB,OAJA,QAAQ,KACN,6DAA6D,EAAiB,gEAEhF,EACO,KAGT,GAAI,CACF,OAAO,MAAM,EAAiB,CAChC,OAAS,EAAO,CAad,OAZA,QAAQ,MAAM,yCAA0C,CAAK,EAEzD,EAAU,GACZ,QAAQ,MACN;;;;wDAKF,EAGK,IACT,CACF"}
@@ -0,0 +1,7 @@
1
+ //#region \0rolldown/runtime.js
2
+ var e = /* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
3
+ if (typeof require < "u") return require.apply(this, arguments);
4
+ throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
5
+ });
6
+ //#endregion
7
+ export { e as __require };
@@ -1,7 +1,4 @@
1
- import { KnockExpoPushNotificationProvider as r, useExpoPushNotifications as t } from "./modules/push/KnockExpoPushNotificationProvider.mjs";
1
+ import { KnockExpoPushNotificationProvider as e, useExpoPushNotifications as t } from "./modules/push/KnockExpoPushNotificationProvider.mjs";
2
+ import "./modules/push/index.mjs";
2
3
  export * from "@knocklabs/react-native";
3
- export {
4
- r as KnockExpoPushNotificationProvider,
5
- t as useExpoPushNotifications
6
- };
7
- //# sourceMappingURL=index.mjs.map
4
+ export { e as KnockExpoPushNotificationProvider, t as useExpoPushNotifications };
@@ -1,120 +1,93 @@
1
- import { jsx as h } from "react/jsx-runtime";
2
- import { useKnockClient as A, useKnockAuthState as F } from "@knocklabs/react-core";
3
- import { KnockPushNotificationProvider as I, usePushNotifications as M } from "@knocklabs/react-native";
4
- import { createContext as b, useContext as _, useState as L, useRef as N, useCallback as s, useEffect as d } from "react";
5
- import { getNotificationsModule as K } from "./getNotificationsModule.mjs";
6
- import { registerForPushNotifications as O, setupDefaultAndroidChannel as D, DEFAULT_NOTIFICATION_BEHAVIOR as H } from "./utils.mjs";
7
- const m = b(void 0);
8
- function G() {
9
- const i = _(m);
10
- if (i === void 0)
11
- throw new Error(
12
- "[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider"
13
- );
14
- return i;
1
+ import { getNotificationsModule as e } from "./getNotificationsModule.mjs";
2
+ import { DEFAULT_NOTIFICATION_BEHAVIOR as t, registerForPushNotifications as n, setupDefaultAndroidChannel as r } from "./utils.mjs";
3
+ import { useKnockAuthState as i, useKnockClient as a } from "@knocklabs/react-core";
4
+ import { KnockPushNotificationProvider as o, usePushNotifications as s } from "@knocklabs/react-native";
5
+ import { createContext as c, useCallback as l, useContext as u, useEffect as d, useRef as f, useState as p } from "react";
6
+ import { jsx as m } from "react/jsx-runtime";
7
+ //#region src/modules/push/KnockExpoPushNotificationProvider.tsx
8
+ var h = c(void 0);
9
+ function g() {
10
+ let e = u(h);
11
+ if (e === void 0) throw Error("[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider");
12
+ return e;
15
13
  }
16
- const V = ({
17
- knockExpoChannelId: i,
18
- customNotificationHandler: c,
19
- setupAndroidNotificationChannel: l = D,
20
- children: v,
21
- autoRegister: p = !0
22
- }) => {
23
- const o = A(), { status: x, userId: E } = F(o), k = x === "authenticated", { registerPushTokenToChannel: u, unregisterPushTokenFromChannel: T } = M(), [R, S] = L(null), g = N(() => {
24
- }), P = N(() => {
25
- }), w = s(
26
- (t) => {
27
- g.current = t;
28
- },
29
- []
30
- ), C = s(
31
- (t) => {
32
- P.current = t;
33
- },
34
- []
35
- ), a = s(async () => {
36
- try {
37
- o.log("[Knock] Registering for push notifications");
38
- const t = await O(
39
- l
40
- );
41
- return t ? (o.log(`[Knock] Push token received: ${t}`), S(t), t) : null;
42
- } catch (t) {
43
- return console.error("[Knock] Error registering for push notifications:", t), null;
44
- }
45
- }, [o, l]), f = s(
46
- async (t, r) => {
47
- var n;
48
- const e = (n = t.request.content.data) == null ? void 0 : n.knock_message_id;
49
- if (!e) {
50
- o.log(
51
- "[Knock] Skipping status update for non-Knock notification"
52
- );
53
- return;
54
- }
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);
62
- },
63
- [o]
64
- );
65
- d(() => {
66
- const t = K();
67
- if (!t) return;
68
- const r = c || (async () => H);
69
- t.setNotificationHandler({ handleNotification: r });
70
- }, [c]), d(() => {
71
- if (!p || !k)
72
- return;
73
- let t = !0;
74
- return (async () => {
75
- try {
76
- const e = await a();
77
- e && t && (await u(e, i), o.log("[Knock] Push token registered with Knock channel"));
78
- } catch (e) {
79
- console.error("[Knock] Error during auto-registration:", e);
80
- }
81
- })(), () => {
82
- t = !1;
83
- };
84
- }, [
85
- p,
86
- k,
87
- E,
88
- i,
89
- a,
90
- u,
91
- o
92
- ]), d(() => {
93
- const t = K();
94
- if (!t) return;
95
- const r = t.addNotificationReceivedListener((n) => {
96
- o.log("[Knock] Notification received in foreground"), f(n, "interacted"), g.current(n);
97
- }), e = t.addNotificationResponseReceivedListener(
98
- (n) => {
99
- o.log("[Knock] Notification was tapped"), f(n.notification, "interacted"), P.current(n);
100
- }
101
- );
102
- return () => {
103
- r.remove(), e.remove();
104
- };
105
- }, [o, f]);
106
- const y = {
107
- expoPushToken: R,
108
- registerForPushNotifications: a,
109
- registerPushTokenToChannel: u,
110
- unregisterPushTokenFromChannel: T,
111
- onNotificationReceived: w,
112
- onNotificationTapped: C
113
- };
114
- return /* @__PURE__ */ h(m.Provider, { value: y, children: v });
115
- }, J = (i) => /* @__PURE__ */ h(I, { children: /* @__PURE__ */ h(V, { ...i }) });
116
- export {
117
- J as KnockExpoPushNotificationProvider,
118
- G as useExpoPushNotifications
119
- };
120
- //# sourceMappingURL=KnockExpoPushNotificationProvider.mjs.map
14
+ var _ = ({ knockExpoChannelId: o, customNotificationHandler: c, setupAndroidNotificationChannel: u = r, children: g, autoRegister: _ = !0 }) => {
15
+ let v = a(), { status: y, userId: b } = i(v), x = y === "authenticated", { registerPushTokenToChannel: S, unregisterPushTokenFromChannel: C } = s(), [w, T] = p(null), E = f(() => {}), D = f(() => {}), O = l((e) => {
16
+ E.current = e;
17
+ }, []), k = l((e) => {
18
+ D.current = e;
19
+ }, []), A = l(async () => {
20
+ try {
21
+ v.log("[Knock] Registering for push notifications");
22
+ let e = await n(u);
23
+ return e ? (v.log(`[Knock] Push token received: ${e}`), T(e), e) : null;
24
+ } catch (e) {
25
+ return console.error("[Knock] Error registering for push notifications:", e), null;
26
+ }
27
+ }, [v, u]), j = l(async (e, t) => {
28
+ let n = e.request.content.data?.knock_message_id;
29
+ if (!n) {
30
+ v.log("[Knock] Skipping status update for non-Knock notification");
31
+ return;
32
+ }
33
+ if (!v.isAuthenticated()) {
34
+ v.log("[Knock] Skipping status update; user is not authenticated");
35
+ return;
36
+ }
37
+ return v.messages.updateStatus(n, t);
38
+ }, [v]);
39
+ d(() => {
40
+ let n = e();
41
+ if (!n) return;
42
+ let r = c || (async () => t);
43
+ n.setNotificationHandler({ handleNotification: r });
44
+ }, [c]), d(() => {
45
+ if (!_ || !x) return;
46
+ let e = !0;
47
+ return (async () => {
48
+ try {
49
+ let t = await A();
50
+ t && e && (await S(t, o), v.log("[Knock] Push token registered with Knock channel"));
51
+ } catch (e) {
52
+ console.error("[Knock] Error during auto-registration:", e);
53
+ }
54
+ })(), () => {
55
+ e = !1;
56
+ };
57
+ }, [
58
+ _,
59
+ x,
60
+ b,
61
+ o,
62
+ A,
63
+ S,
64
+ v
65
+ ]), d(() => {
66
+ let t = e();
67
+ if (!t) return;
68
+ let n = t.addNotificationReceivedListener((e) => {
69
+ v.log("[Knock] Notification received in foreground"), j(e, "interacted"), E.current(e);
70
+ }), r = t.addNotificationResponseReceivedListener((e) => {
71
+ v.log("[Knock] Notification was tapped"), j(e.notification, "interacted"), D.current(e);
72
+ });
73
+ return () => {
74
+ n.remove(), r.remove();
75
+ };
76
+ }, [v, j]);
77
+ let M = {
78
+ expoPushToken: w,
79
+ registerForPushNotifications: A,
80
+ registerPushTokenToChannel: S,
81
+ unregisterPushTokenFromChannel: C,
82
+ onNotificationReceived: O,
83
+ onNotificationTapped: k
84
+ };
85
+ return /* @__PURE__ */ m(h.Provider, {
86
+ value: M,
87
+ children: g
88
+ });
89
+ }, v = (e) => /* @__PURE__ */ m(o, { children: /* @__PURE__ */ m(_, { ...e }) });
90
+ //#endregion
91
+ export { v as KnockExpoPushNotificationProvider, g as useExpoPushNotifications };
92
+
93
+ //# 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 { 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, userId } = 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 // `userId` is also a dependency so that an in-place user switch (same Knock\n // instance re-authenticated as a different user, where `isAuthenticated` never\n // flips) re-registers the device token against the new user's channel data.\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 userId,\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","userId","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
+ {"version":3,"file":"KnockExpoPushNotificationProvider.mjs","names":[],"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, userId } = 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 // `userId` is also a dependency so that an in-place user switch (same Knock\n // instance re-authenticated as a different user, where `isAuthenticated` never\n // flips) re-registers the device token against the new user's channel data.\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 userId,\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"],"mappings":"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"}
@@ -1,32 +1,24 @@
1
- import i, { ExecutionEnvironment as n } from "expo-constants";
2
- import { Platform as t } from "react-native";
3
- let o;
4
- function e() {
5
- return t.OS === "android" && i.executionEnvironment === n.StoreClient;
1
+ import { __require as e } from "../../_virtual/_rolldown/runtime.mjs";
2
+ import t, { ExecutionEnvironment as n } from "expo-constants";
3
+ import { Platform as r } from "react-native";
4
+ //#region src/modules/push/getNotificationsModule.ts
5
+ var i = void 0;
6
+ function a() {
7
+ return r.OS === "android" && t.executionEnvironment === n.StoreClient;
6
8
  }
7
- let r = () => (
8
- // eslint-disable-next-line @typescript-eslint/no-require-imports
9
- require("expo-notifications")
10
- );
9
+ /* v8 ignore next 3 -- default require is replaced in tests via _resetForTesting */
10
+ var o = () => e("expo-notifications");
11
11
  function s() {
12
- if (o !== void 0)
13
- return o;
14
- if (e())
15
- return console.warn(
16
- `[Knock] Push notifications (remote notifications) are not available in Expo Go on Android. This is an Expo platform limitation — push notification support was removed from Expo Go on Android in SDK 53. Push features (token registration, notification listeners) will be disabled, but all other Knock features will continue to work.
17
-
18
- To use push notifications on Android, use a development build instead of Expo Go: https://docs.expo.dev/develop/development-builds/introduction/`
19
- ), o = null, o;
20
- try {
21
- o = r();
22
- } catch {
23
- console.warn(
24
- "[Knock] expo-notifications could not be loaded. Push notification features will be disabled."
25
- ), o = null;
26
- }
27
- return o;
12
+ if (i !== void 0) return i;
13
+ if (a()) return console.warn("[Knock] Push notifications (remote notifications) are not available in Expo Go on Android. This is an Expo platform limitation — push notification support was removed from Expo Go on Android in SDK 53. Push features (token registration, notification listeners) will be disabled, but all other Knock features will continue to work.\n\nTo use push notifications on Android, use a development build instead of Expo Go: https://docs.expo.dev/develop/development-builds/introduction/"), i = null, i;
14
+ try {
15
+ i = o();
16
+ } catch {
17
+ console.warn("[Knock] expo-notifications could not be loaded. Push notification features will be disabled."), i = null;
18
+ }
19
+ return i;
28
20
  }
29
- export {
30
- s as getNotificationsModule
31
- };
32
- //# sourceMappingURL=getNotificationsModule.mjs.map
21
+ //#endregion
22
+ export { s as getNotificationsModule };
23
+
24
+ //# sourceMappingURL=getNotificationsModule.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"getNotificationsModule.mjs","sources":["../../../../src/modules/push/getNotificationsModule.ts"],"sourcesContent":["import Constants, { ExecutionEnvironment } from \"expo-constants\";\nimport type * as Notifications from \"expo-notifications\";\nimport { Platform } from \"react-native\";\n\n/**\n * The type of the expo-notifications module when successfully loaded.\n */\nexport type NotificationsModule = typeof Notifications;\n\n// Type aliases derived from the expo-notifications namespace so that consumers\n// access all expo-notifications types through this module rather than importing\n// from the package directly (which can trigger runtime side-effects).\nexport type Notification = Notifications.Notification;\nexport type NotificationResponse = Notifications.NotificationResponse;\nexport type NotificationBehavior = Notifications.NotificationBehavior;\n\n/**\n * Lazily load the expo-notifications module.\n *\n * In Expo SDK 55+, `import * as Notifications from \"expo-notifications\"` triggers\n * a top-level side-effect (DevicePushTokenAutoRegistration.fx.ts) that calls\n * `addPushTokenListener()`, which throws on Android Expo Go where push notification\n * functionality has been removed (since SDK 53).\n *\n * We detect Android Expo Go before attempting the require() and skip it entirely,\n * since the throw from expo-notifications bypasses JavaScript try/catch via\n * React Native's global error handler.\n *\n * On all other environments (iOS Expo Go, development builds, production),\n * expo-notifications loads normally.\n */\n\n// Cache the module after the first load to avoid repeated require() calls and\n// environment detection checks on every access. The three states are:\n// undefined = not yet loaded (initial)\n// null = unavailable (Android Expo Go or load failure)\n// module = successfully loaded\nlet cachedModule: NotificationsModule | null | undefined = undefined;\n\nfunction isAndroidExpoGo(): boolean {\n return (\n Platform.OS === \"android\" &&\n Constants.executionEnvironment === ExecutionEnvironment.StoreClient\n );\n}\n\n// Abstracted for testability — Vitest cannot intercept require() calls\n// inside dynamically imported modules after vi.resetModules().\n/* v8 ignore next 3 -- default require is replaced in tests via _resetForTesting */\nlet requireNotifications: () => NotificationsModule = () =>\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require(\"expo-notifications\") as NotificationsModule;\n\nexport function getNotificationsModule(): NotificationsModule | null {\n if (cachedModule !== undefined) {\n return cachedModule;\n }\n\n if (isAndroidExpoGo()) {\n console.warn(\n \"[Knock] Push notifications (remote notifications) are not available in Expo Go \" +\n \"on Android. This is an Expo platform limitation — push notification support was \" +\n \"removed from Expo Go on Android in SDK 53. Push features (token registration, \" +\n \"notification listeners) will be disabled, but all other Knock features will \" +\n \"continue to work.\\n\\n\" +\n \"To use push notifications on Android, use a development build instead of Expo Go: \" +\n \"https://docs.expo.dev/develop/development-builds/introduction/\",\n );\n cachedModule = null;\n return cachedModule;\n }\n\n try {\n cachedModule = requireNotifications();\n } catch {\n console.warn(\n \"[Knock] expo-notifications could not be loaded. \" +\n \"Push notification features will be disabled.\",\n );\n cachedModule = null;\n }\n\n return cachedModule;\n}\n\n/**\n * @internal Test-only — reset the cached module and optionally override\n * the require function used to load expo-notifications.\n */\nexport function _resetForTesting(\n overrideRequire?: () => NotificationsModule,\n): void {\n cachedModule = undefined;\n if (overrideRequire) {\n requireNotifications = overrideRequire;\n }\n}\n"],"names":["cachedModule","isAndroidExpoGo","Platform","Constants","ExecutionEnvironment","requireNotifications","getNotificationsModule"],"mappings":";;AAqCA,IAAIA;AAEJ,SAASC,IAA2B;AAClC,SACEC,EAAS,OAAO,aAChBC,EAAU,yBAAyBC,EAAqB;AAE5D;AAKA,IAAIC,IAAkD;AAAA;AAAA,EAEpD,QAAQ,oBAAoB;AAAA;AAEvB,SAASC,IAAqD;AACnE,MAAIN,MAAiB;AACZ,WAAAA;AAGT,MAAIC;AACM,mBAAA;AAAA,MACN;AAAA;AAAA;AAAA,IAOF,GACeD,IAAA,MACRA;AAGL,MAAA;AACF,IAAAA,IAAeK,EAAqB;AAAA,EAAA,QAC9B;AACE,YAAA;AAAA,MACN;AAAA,IAEF,GACeL,IAAA;AAAA,EAAA;AAGV,SAAAA;AACT;"}
1
+ {"version":3,"file":"getNotificationsModule.mjs","names":[],"sources":["../../../../src/modules/push/getNotificationsModule.ts"],"sourcesContent":["import Constants, { ExecutionEnvironment } from \"expo-constants\";\nimport type * as Notifications from \"expo-notifications\";\nimport { Platform } from \"react-native\";\n\n/**\n * The type of the expo-notifications module when successfully loaded.\n */\nexport type NotificationsModule = typeof Notifications;\n\n// Type aliases derived from the expo-notifications namespace so that consumers\n// access all expo-notifications types through this module rather than importing\n// from the package directly (which can trigger runtime side-effects).\nexport type Notification = Notifications.Notification;\nexport type NotificationResponse = Notifications.NotificationResponse;\nexport type NotificationBehavior = Notifications.NotificationBehavior;\n\n/**\n * Lazily load the expo-notifications module.\n *\n * In Expo SDK 55+, `import * as Notifications from \"expo-notifications\"` triggers\n * a top-level side-effect (DevicePushTokenAutoRegistration.fx.ts) that calls\n * `addPushTokenListener()`, which throws on Android Expo Go where push notification\n * functionality has been removed (since SDK 53).\n *\n * We detect Android Expo Go before attempting the require() and skip it entirely,\n * since the throw from expo-notifications bypasses JavaScript try/catch via\n * React Native's global error handler.\n *\n * On all other environments (iOS Expo Go, development builds, production),\n * expo-notifications loads normally.\n */\n\n// Cache the module after the first load to avoid repeated require() calls and\n// environment detection checks on every access. The three states are:\n// undefined = not yet loaded (initial)\n// null = unavailable (Android Expo Go or load failure)\n// module = successfully loaded\nlet cachedModule: NotificationsModule | null | undefined = undefined;\n\nfunction isAndroidExpoGo(): boolean {\n return (\n Platform.OS === \"android\" &&\n Constants.executionEnvironment === ExecutionEnvironment.StoreClient\n );\n}\n\n// Abstracted for testability — Vitest cannot intercept require() calls\n// inside dynamically imported modules after vi.resetModules().\n/* v8 ignore next 3 -- default require is replaced in tests via _resetForTesting */\nlet requireNotifications: () => NotificationsModule = () =>\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n require(\"expo-notifications\") as NotificationsModule;\n\nexport function getNotificationsModule(): NotificationsModule | null {\n if (cachedModule !== undefined) {\n return cachedModule;\n }\n\n if (isAndroidExpoGo()) {\n console.warn(\n \"[Knock] Push notifications (remote notifications) are not available in Expo Go \" +\n \"on Android. This is an Expo platform limitation — push notification support was \" +\n \"removed from Expo Go on Android in SDK 53. Push features (token registration, \" +\n \"notification listeners) will be disabled, but all other Knock features will \" +\n \"continue to work.\\n\\n\" +\n \"To use push notifications on Android, use a development build instead of Expo Go: \" +\n \"https://docs.expo.dev/develop/development-builds/introduction/\",\n );\n cachedModule = null;\n return cachedModule;\n }\n\n try {\n cachedModule = requireNotifications();\n } catch {\n console.warn(\n \"[Knock] expo-notifications could not be loaded. \" +\n \"Push notification features will be disabled.\",\n );\n cachedModule = null;\n }\n\n return cachedModule;\n}\n\n/**\n * @internal Test-only — reset the cached module and optionally override\n * the require function used to load expo-notifications.\n */\nexport function _resetForTesting(\n overrideRequire?: () => NotificationsModule,\n): void {\n cachedModule = undefined;\n if (overrideRequire) {\n requireNotifications = overrideRequire;\n }\n}\n"],"mappings":";;;;AAqCA,IAAI,IAAuD,KAAA;AAE3D,SAAS,IAA2B;CAClC,OACE,EAAS,OAAO,aAChB,EAAU,yBAAyB,EAAqB;AAE5D;;AAKA,IAAI,UAAA,EAEM,oBAAoB;AAE9B,SAAgB,IAAqD;CACnE,IAAI,MAAiB,KAAA,GACnB,OAAO;CAGT,IAAI,EAAgB,GAWlB,OAVA,QAAQ,KACN,geAOF,GACA,IAAe,MACR;CAGT,IAAI;EACF,IAAe,EAAqB;CACtC,QAAQ;EAKN,AAJA,QAAQ,KACN,8FAEF,GACA,IAAe;CACjB;CAEA,OAAO;AACT"}
@@ -0,0 +1,2 @@
1
+ import { KnockExpoPushNotificationProvider as e, useExpoPushNotifications as t } from "./KnockExpoPushNotificationProvider.mjs";
2
+ export { e as KnockExpoPushNotificationProvider, t as useExpoPushNotifications };
@@ -1,89 +1,66 @@
1
- import p from "expo-constants";
2
- import * as h from "expo-device";
3
- import { Platform as m } from "react-native";
4
1
  import { getNotificationsModule as e } from "./getNotificationsModule.mjs";
5
- const C = {
6
- shouldShowAlert: !0,
7
- shouldPlaySound: !0,
8
- shouldSetBadge: !0,
9
- shouldShowBanner: !0,
10
- shouldShowList: !0
2
+ import t from "expo-constants";
3
+ import { Platform as n } from "react-native";
4
+ import * as r from "expo-device";
5
+ //#region src/modules/push/utils.ts
6
+ var i = {
7
+ shouldShowAlert: !0,
8
+ shouldPlaySound: !0,
9
+ shouldSetBadge: !0,
10
+ shouldShowBanner: !0,
11
+ shouldShowList: !0
11
12
  };
12
- function g() {
13
- var n, o, r, i, s, a, u, c, f, l;
14
- const t = p;
15
- return (r = (o = (n = t.expoConfig) == null ? void 0 : n.extra) == null ? void 0 : o.eas) != null && r.projectId ? t.expoConfig.extra.eas.projectId : (i = t.easConfig) != null && i.projectId ? t.easConfig.projectId : (u = (a = (s = t.manifest) == null ? void 0 : s.extra) == null ? void 0 : a.eas) != null && u.projectId ? t.manifest.extra.eas.projectId : (l = (f = (c = t.manifest2) == null ? void 0 : c.extra) == null ? void 0 : f.eas) != null && l.projectId ? t.manifest2.extra.eas.projectId : null;
13
+ function a() {
14
+ let e = t;
15
+ return e.expoConfig?.extra?.eas?.projectId ? e.expoConfig.extra.eas.projectId : e.easConfig?.projectId ? e.easConfig.projectId : e.manifest?.extra?.eas?.projectId ? e.manifest.extra.eas.projectId : e.manifest2?.extra?.eas?.projectId ? e.manifest2.extra.eas.projectId : null;
16
16
  }
17
- async function j() {
18
- const t = e();
19
- if (!t)
20
- return "unavailable";
21
- const { status: n } = await t.getPermissionsAsync();
22
- if (n === "granted")
23
- return n;
24
- const { status: o } = await t.requestPermissionsAsync();
25
- return o;
17
+ async function o() {
18
+ let t = e();
19
+ if (!t) return "unavailable";
20
+ let { status: n } = await t.getPermissionsAsync();
21
+ if (n === "granted") return n;
22
+ let { status: r } = await t.requestPermissionsAsync();
23
+ return r;
26
24
  }
27
- async function I() {
28
- const t = g();
29
- if (!t)
30
- return console.error(
31
- "[Knock] Expo Project ID is not defined in the app configuration. Make sure you have configured your project with EAS. The projectId should be in app.json/app.config.js at extra.eas.projectId."
32
- ), null;
33
- const n = e();
34
- if (!n)
35
- return null;
36
- const o = await n.getExpoPushTokenAsync({ projectId: t });
37
- return (o == null ? void 0 : o.data) ?? null;
25
+ async function s() {
26
+ let t = a();
27
+ if (!t) return console.error("[Knock] Expo Project ID is not defined in the app configuration. Make sure you have configured your project with EAS. The projectId should be in app.json/app.config.js at extra.eas.projectId."), null;
28
+ let n = e();
29
+ return n ? (await n.getExpoPushTokenAsync({ projectId: t }))?.data ?? null : null;
38
30
  }
39
- async function x() {
40
- const t = e();
41
- t && await t.setNotificationChannelAsync("default", {
42
- name: "Default",
43
- importance: t.AndroidImportance.MAX,
44
- vibrationPattern: [0, 250, 250, 250],
45
- lightColor: "#FF231F7C"
46
- });
31
+ async function c() {
32
+ let t = e();
33
+ t && await t.setNotificationChannelAsync("default", {
34
+ name: "Default",
35
+ importance: t.AndroidImportance.MAX,
36
+ vibrationPattern: [
37
+ 0,
38
+ 250,
39
+ 250,
40
+ 250
41
+ ],
42
+ lightColor: "#FF231F7C"
43
+ });
47
44
  }
48
- function y() {
49
- return h.isDevice;
45
+ function l() {
46
+ return r.isDevice;
50
47
  }
51
- function d() {
52
- return m.OS === "android";
48
+ function u() {
49
+ return n.OS === "android";
53
50
  }
54
- async function k(t = x) {
55
- if (!y())
56
- return console.warn(
57
- "[Knock] Must use physical device for Push Notifications. Push notifications are not supported on emulators/simulators."
58
- ), null;
59
- d() && await t();
60
- const n = await j();
61
- if (n === "unavailable")
62
- return null;
63
- if (n !== "granted")
64
- return console.warn(
65
- `[Knock] Push notification permission not granted. Status: ${n}. User may have denied the permission or the system blocked it.`
66
- ), null;
67
- try {
68
- return await I();
69
- } catch (o) {
70
- return console.error("[Knock] Error getting Expo push token:", o), d() && console.error(
71
- `[Knock] Android push token registration failed. Common causes:
72
- 1. FCM is not configured (google-services.json missing)
73
- 2. Running on an emulator
74
- 3. Network connectivity issues
75
- 4. expo-notifications plugin not configured in app.json`
76
- ), null;
77
- }
51
+ async function d(e = c) {
52
+ if (!l()) return console.warn("[Knock] Must use physical device for Push Notifications. Push notifications are not supported on emulators/simulators."), null;
53
+ u() && await e();
54
+ let t = await o();
55
+ if (t === "unavailable") return null;
56
+ if (t !== "granted") return console.warn(`[Knock] Push notification permission not granted. Status: ${t}. User may have denied the permission or the system blocked it.`), null;
57
+ try {
58
+ return await s();
59
+ } catch (e) {
60
+ return console.error("[Knock] Error getting Expo push token:", e), u() && console.error("[Knock] Android push token registration failed. Common causes:\n1. FCM is not configured (google-services.json missing)\n2. Running on an emulator\n3. Network connectivity issues\n4. expo-notifications plugin not configured in app.json"), null;
61
+ }
78
62
  }
79
- export {
80
- C as DEFAULT_NOTIFICATION_BEHAVIOR,
81
- I as getExpoPushToken,
82
- g as getProjectId,
83
- d as isAndroid,
84
- y as isPushNotificationSupported,
85
- k as registerForPushNotifications,
86
- j as requestPushPermission,
87
- x as setupDefaultAndroidChannel
88
- };
89
- //# sourceMappingURL=utils.mjs.map
63
+ //#endregion
64
+ export { i as DEFAULT_NOTIFICATION_BEHAVIOR, s as getExpoPushToken, a as getProjectId, u as isAndroid, l as isPushNotificationSupported, d as registerForPushNotifications, o as requestPushPermission, c as setupDefaultAndroidChannel };
65
+
66
+ //# sourceMappingURL=utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","sources":["../../../../src/modules/push/utils.ts"],"sourcesContent":["import Constants from \"expo-constants\";\nimport * as Device from \"expo-device\";\nimport { Platform } from \"react-native\";\n\nimport {\n type NotificationBehavior,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ExpoConstants = typeof Constants & Record<string, any>;\n\n/**\n * Permission status values returned by expo-notifications.\n * \"unavailable\" is returned when the notifications module could not be loaded\n * (e.g. Android Expo Go where push support was removed in SDK 53).\n */\nexport type PushPermissionStatus =\n | \"granted\"\n | \"denied\"\n | \"undetermined\"\n | \"unavailable\";\n\n/**\n * Default notification behavior when a notification is received.\n */\nexport const DEFAULT_NOTIFICATION_BEHAVIOR: NotificationBehavior = {\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: true,\n shouldShowBanner: true,\n shouldShowList: true,\n};\n\n/**\n * Get the Expo project ID from various possible sources.\n * Different Expo SDK versions and configurations store this differently.\n */\nexport function getProjectId(): string | null {\n const constants = Constants as ExpoConstants;\n\n // Try Constants.expoConfig.extra.eas.projectId (common in EAS builds)\n if (constants.expoConfig?.extra?.eas?.projectId) {\n return constants.expoConfig.extra.eas.projectId;\n }\n\n // Try Constants.easConfig?.projectId (available in newer SDK versions)\n if (constants.easConfig?.projectId) {\n return constants.easConfig.projectId;\n }\n\n // Try Constants.manifest?.extra?.eas?.projectId (older SDK versions)\n if (constants.manifest?.extra?.eas?.projectId) {\n return constants.manifest.extra.eas.projectId;\n }\n\n // Try Constants.manifest2?.extra?.eas?.projectId (Expo SDK 46+)\n if (constants.manifest2?.extra?.eas?.projectId) {\n return constants.manifest2.extra.eas.projectId;\n }\n\n return null;\n}\n\n/**\n * Request push notification permissions if not already granted.\n * @returns The permission status\n */\nexport async function requestPushPermission(): Promise<PushPermissionStatus> {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return \"unavailable\";\n }\n\n const { status: existingStatus } =\n await NotificationsModule.getPermissionsAsync();\n\n if (existingStatus === \"granted\") {\n return existingStatus;\n }\n\n const { status } = await NotificationsModule.requestPermissionsAsync();\n return status as PushPermissionStatus;\n}\n\n/**\n * Get the Expo push token for this device.\n * @returns The push token or null if unable to get one\n */\nexport async function getExpoPushToken(): Promise<string | null> {\n const projectId = getProjectId();\n\n if (!projectId) {\n console.error(\n \"[Knock] Expo Project ID is not defined in the app configuration. \" +\n \"Make sure you have configured your project with EAS. \" +\n \"The projectId should be in app.json/app.config.js at extra.eas.projectId.\",\n );\n return null;\n }\n\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return null;\n }\n\n const token = await NotificationsModule.getExpoPushTokenAsync({ projectId });\n return token?.data ?? null;\n}\n\n/**\n * Set up the default Android notification channel.\n */\nexport async function setupDefaultAndroidChannel(): Promise<void> {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return;\n }\n\n await NotificationsModule.setNotificationChannelAsync(\"default\", {\n name: \"Default\",\n importance: NotificationsModule.AndroidImportance.MAX,\n vibrationPattern: [0, 250, 250, 250],\n lightColor: \"#FF231F7C\",\n });\n}\n\n/**\n * Check if the current environment supports push notifications.\n * @returns true if push notifications are supported\n */\nexport function isPushNotificationSupported(): boolean {\n return Device.isDevice;\n}\n\n/**\n * Check if the current platform is Android.\n */\nexport function isAndroid(): boolean {\n return Platform.OS === \"android\";\n}\n\n/**\n * Request permissions and get a push token.\n * Handles Android-specific channel setup and provides appropriate error messaging.\n *\n * @param setupAndroidChannel - Function to set up the Android notification channel\n * @returns The push token string or null if registration failed\n */\nexport async function registerForPushNotifications(\n setupAndroidChannel: () => Promise<void> = setupDefaultAndroidChannel,\n): Promise<string | null> {\n // Check for device support\n if (!isPushNotificationSupported()) {\n console.warn(\n \"[Knock] Must use physical device for Push Notifications. \" +\n \"Push notifications are not supported on emulators/simulators.\",\n );\n return null;\n }\n\n // Setup Android notification channel before requesting permissions\n // This is REQUIRED for Android 13+ to show the permission prompt\n if (isAndroid()) {\n await setupAndroidChannel();\n }\n\n const permissionStatus = await requestPushPermission();\n\n if (permissionStatus === \"unavailable\") {\n // Module couldn't be loaded (e.g. Android Expo Go) — the warning is\n // already emitted by getNotificationsModule, so just bail silently.\n return null;\n }\n\n if (permissionStatus !== \"granted\") {\n console.warn(\n `[Knock] Push notification permission not granted. Status: ${permissionStatus}. ` +\n \"User may have denied the permission or the system blocked it.\",\n );\n return null;\n }\n\n try {\n return await getExpoPushToken();\n } catch (error) {\n console.error(\"[Knock] Error getting Expo push token:\", error);\n\n if (isAndroid()) {\n console.error(\n \"[Knock] Android push token registration failed. Common causes:\\n\" +\n \"1. FCM is not configured (google-services.json missing)\\n\" +\n \"2. Running on an emulator\\n\" +\n \"3. Network connectivity issues\\n\" +\n \"4. expo-notifications plugin not configured in app.json\",\n );\n }\n\n return null;\n }\n}\n"],"names":["DEFAULT_NOTIFICATION_BEHAVIOR","getProjectId","constants","Constants","_c","_b","_a","_d","_g","_f","_e","_j","_i","_h","requestPushPermission","NotificationsModule","getNotificationsModule","existingStatus","status","getExpoPushToken","projectId","token","setupDefaultAndroidChannel","isPushNotificationSupported","Device","isAndroid","Platform","registerForPushNotifications","setupAndroidChannel","permissionStatus","error"],"mappings":";;;;AA0BO,MAAMA,IAAsD;AAAA,EACjE,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,gBAAgB;AAClB;AAMO,SAASC,IAA8B;;AAC5C,QAAMC,IAAYC;AAGlB,UAAIC,KAAAC,KAAAC,IAAAJ,EAAU,eAAV,gBAAAI,EAAsB,UAAtB,gBAAAD,EAA6B,QAA7B,QAAAD,EAAkC,YAC7BF,EAAU,WAAW,MAAM,IAAI,aAIpCK,IAAAL,EAAU,cAAV,QAAAK,EAAqB,YAChBL,EAAU,UAAU,aAIzBM,KAAAC,KAAAC,IAAAR,EAAU,aAAV,gBAAAQ,EAAoB,UAApB,gBAAAD,EAA2B,QAA3B,QAAAD,EAAgC,YAC3BN,EAAU,SAAS,MAAM,IAAI,aAIlCS,KAAAC,KAAAC,IAAAX,EAAU,cAAV,gBAAAW,EAAqB,UAArB,gBAAAD,EAA4B,QAA5B,QAAAD,EAAiC,YAC5BT,EAAU,UAAU,MAAM,IAAI,YAGhC;AACT;AAMA,eAAsBY,IAAuD;AAC3E,QAAMC,IAAsBC,EAAuB;AACnD,MAAI,CAACD;AACI,WAAA;AAGT,QAAM,EAAE,QAAQE,EACd,IAAA,MAAMF,EAAoB,oBAAoB;AAEhD,MAAIE,MAAmB;AACd,WAAAA;AAGT,QAAM,EAAE,QAAAC,EAAA,IAAW,MAAMH,EAAoB,wBAAwB;AAC9D,SAAAG;AACT;AAMA,eAAsBC,IAA2C;AAC/D,QAAMC,IAAYnB,EAAa;AAE/B,MAAI,CAACmB;AACK,mBAAA;AAAA,MACN;AAAA,IAGF,GACO;AAGT,QAAML,IAAsBC,EAAuB;AACnD,MAAI,CAACD;AACI,WAAA;AAGT,QAAMM,IAAQ,MAAMN,EAAoB,sBAAsB,EAAE,WAAAK,GAAW;AAC3E,UAAOC,KAAA,gBAAAA,EAAO,SAAQ;AACxB;AAKA,eAAsBC,IAA4C;AAChE,QAAMP,IAAsBC,EAAuB;AACnD,EAAKD,KAIC,MAAAA,EAAoB,4BAA4B,WAAW;AAAA,IAC/D,MAAM;AAAA,IACN,YAAYA,EAAoB,kBAAkB;AAAA,IAClD,kBAAkB,CAAC,GAAG,KAAK,KAAK,GAAG;AAAA,IACnC,YAAY;AAAA,EAAA,CACb;AACH;AAMO,SAASQ,IAAuC;AACrD,SAAOC,EAAO;AAChB;AAKO,SAASC,IAAqB;AACnC,SAAOC,EAAS,OAAO;AACzB;AASsB,eAAAC,EACpBC,IAA2CN,GACnB;AAEpB,MAAA,CAACC;AACK,mBAAA;AAAA,MACN;AAAA,IAEF,GACO;AAKT,EAAIE,OACF,MAAMG,EAAoB;AAGtB,QAAAC,IAAmB,MAAMf,EAAsB;AAErD,MAAIe,MAAqB;AAGhB,WAAA;AAGT,MAAIA,MAAqB;AACf,mBAAA;AAAA,MACN,6DAA6DA,CAAgB;AAAA,IAE/E,GACO;AAGL,MAAA;AACF,WAAO,MAAMV,EAAiB;AAAA,WACvBW,GAAO;AACN,mBAAA,MAAM,0CAA0CA,CAAK,GAEzDL,OACM,QAAA;AAAA,MACN;AAAA;AAAA;AAAA;AAAA;AAAA,IAKF,GAGK;AAAA,EAAA;AAEX;"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../../../../src/modules/push/utils.ts"],"sourcesContent":["import Constants from \"expo-constants\";\nimport * as Device from \"expo-device\";\nimport { Platform } from \"react-native\";\n\nimport {\n type NotificationBehavior,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ExpoConstants = typeof Constants & Record<string, any>;\n\n/**\n * Permission status values returned by expo-notifications.\n * \"unavailable\" is returned when the notifications module could not be loaded\n * (e.g. Android Expo Go where push support was removed in SDK 53).\n */\nexport type PushPermissionStatus =\n | \"granted\"\n | \"denied\"\n | \"undetermined\"\n | \"unavailable\";\n\n/**\n * Default notification behavior when a notification is received.\n */\nexport const DEFAULT_NOTIFICATION_BEHAVIOR: NotificationBehavior = {\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: true,\n shouldShowBanner: true,\n shouldShowList: true,\n};\n\n/**\n * Get the Expo project ID from various possible sources.\n * Different Expo SDK versions and configurations store this differently.\n */\nexport function getProjectId(): string | null {\n const constants = Constants as ExpoConstants;\n\n // Try Constants.expoConfig.extra.eas.projectId (common in EAS builds)\n if (constants.expoConfig?.extra?.eas?.projectId) {\n return constants.expoConfig.extra.eas.projectId;\n }\n\n // Try Constants.easConfig?.projectId (available in newer SDK versions)\n if (constants.easConfig?.projectId) {\n return constants.easConfig.projectId;\n }\n\n // Try Constants.manifest?.extra?.eas?.projectId (older SDK versions)\n if (constants.manifest?.extra?.eas?.projectId) {\n return constants.manifest.extra.eas.projectId;\n }\n\n // Try Constants.manifest2?.extra?.eas?.projectId (Expo SDK 46+)\n if (constants.manifest2?.extra?.eas?.projectId) {\n return constants.manifest2.extra.eas.projectId;\n }\n\n return null;\n}\n\n/**\n * Request push notification permissions if not already granted.\n * @returns The permission status\n */\nexport async function requestPushPermission(): Promise<PushPermissionStatus> {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return \"unavailable\";\n }\n\n const { status: existingStatus } =\n await NotificationsModule.getPermissionsAsync();\n\n if (existingStatus === \"granted\") {\n return existingStatus;\n }\n\n const { status } = await NotificationsModule.requestPermissionsAsync();\n return status as PushPermissionStatus;\n}\n\n/**\n * Get the Expo push token for this device.\n * @returns The push token or null if unable to get one\n */\nexport async function getExpoPushToken(): Promise<string | null> {\n const projectId = getProjectId();\n\n if (!projectId) {\n console.error(\n \"[Knock] Expo Project ID is not defined in the app configuration. \" +\n \"Make sure you have configured your project with EAS. \" +\n \"The projectId should be in app.json/app.config.js at extra.eas.projectId.\",\n );\n return null;\n }\n\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return null;\n }\n\n const token = await NotificationsModule.getExpoPushTokenAsync({ projectId });\n return token?.data ?? null;\n}\n\n/**\n * Set up the default Android notification channel.\n */\nexport async function setupDefaultAndroidChannel(): Promise<void> {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) {\n return;\n }\n\n await NotificationsModule.setNotificationChannelAsync(\"default\", {\n name: \"Default\",\n importance: NotificationsModule.AndroidImportance.MAX,\n vibrationPattern: [0, 250, 250, 250],\n lightColor: \"#FF231F7C\",\n });\n}\n\n/**\n * Check if the current environment supports push notifications.\n * @returns true if push notifications are supported\n */\nexport function isPushNotificationSupported(): boolean {\n return Device.isDevice;\n}\n\n/**\n * Check if the current platform is Android.\n */\nexport function isAndroid(): boolean {\n return Platform.OS === \"android\";\n}\n\n/**\n * Request permissions and get a push token.\n * Handles Android-specific channel setup and provides appropriate error messaging.\n *\n * @param setupAndroidChannel - Function to set up the Android notification channel\n * @returns The push token string or null if registration failed\n */\nexport async function registerForPushNotifications(\n setupAndroidChannel: () => Promise<void> = setupDefaultAndroidChannel,\n): Promise<string | null> {\n // Check for device support\n if (!isPushNotificationSupported()) {\n console.warn(\n \"[Knock] Must use physical device for Push Notifications. \" +\n \"Push notifications are not supported on emulators/simulators.\",\n );\n return null;\n }\n\n // Setup Android notification channel before requesting permissions\n // This is REQUIRED for Android 13+ to show the permission prompt\n if (isAndroid()) {\n await setupAndroidChannel();\n }\n\n const permissionStatus = await requestPushPermission();\n\n if (permissionStatus === \"unavailable\") {\n // Module couldn't be loaded (e.g. Android Expo Go) — the warning is\n // already emitted by getNotificationsModule, so just bail silently.\n return null;\n }\n\n if (permissionStatus !== \"granted\") {\n console.warn(\n `[Knock] Push notification permission not granted. Status: ${permissionStatus}. ` +\n \"User may have denied the permission or the system blocked it.\",\n );\n return null;\n }\n\n try {\n return await getExpoPushToken();\n } catch (error) {\n console.error(\"[Knock] Error getting Expo push token:\", error);\n\n if (isAndroid()) {\n console.error(\n \"[Knock] Android push token registration failed. Common causes:\\n\" +\n \"1. FCM is not configured (google-services.json missing)\\n\" +\n \"2. Running on an emulator\\n\" +\n \"3. Network connectivity issues\\n\" +\n \"4. expo-notifications plugin not configured in app.json\",\n );\n }\n\n return null;\n }\n}\n"],"mappings":";;;;;AA0BA,IAAa,IAAsD;CACjE,iBAAiB;CACjB,iBAAiB;CACjB,gBAAgB;CAChB,kBAAkB;CAClB,gBAAgB;AAClB;AAMA,SAAgB,IAA8B;CAC5C,IAAM,IAAY;CAsBlB,OAnBI,EAAU,YAAY,OAAO,KAAK,YAC7B,EAAU,WAAW,MAAM,IAAI,YAIpC,EAAU,WAAW,YAChB,EAAU,UAAU,YAIzB,EAAU,UAAU,OAAO,KAAK,YAC3B,EAAU,SAAS,MAAM,IAAI,YAIlC,EAAU,WAAW,OAAO,KAAK,YAC5B,EAAU,UAAU,MAAM,IAAI,YAGhC;AACT;AAMA,eAAsB,IAAuD;CAC3E,IAAM,IAAsB,EAAuB;CACnD,IAAI,CAAC,GACH,OAAO;CAGT,IAAM,EAAE,QAAQ,MACd,MAAM,EAAoB,oBAAoB;CAEhD,IAAI,MAAmB,WACrB,OAAO;CAGT,IAAM,EAAE,cAAW,MAAM,EAAoB,wBAAwB;CACrE,OAAO;AACT;AAMA,eAAsB,IAA2C;CAC/D,IAAM,IAAY,EAAa;CAE/B,IAAI,CAAC,GAMH,OALA,QAAQ,MACN,iMAGF,GACO;CAGT,IAAM,IAAsB,EAAuB;CAMnD,OALK,KAKE,MADa,EAAoB,sBAAsB,EAAE,aAAU,CAAC,EAAA,EAC7D,QAAQ,OAJb;AAKX;AAKA,eAAsB,IAA4C;CAChE,IAAM,IAAsB,EAAuB;CAC9C,KAIL,MAAM,EAAoB,4BAA4B,WAAW;EAC/D,MAAM;EACN,YAAY,EAAoB,kBAAkB;EAClD,kBAAkB;GAAC;GAAG;GAAK;GAAK;EAAG;EACnC,YAAY;CACd,CAAC;AACH;AAMA,SAAgB,IAAuC;CACrD,OAAO,EAAO;AAChB;AAKA,SAAgB,IAAqB;CACnC,OAAO,EAAS,OAAO;AACzB;AASA,eAAsB,EACpB,IAA2C,GACnB;CAExB,IAAI,CAAC,EAA4B,GAK/B,OAJA,QAAQ,KACN,wHAEF,GACO;CAKT,AAAI,EAAU,KACZ,MAAM,EAAoB;CAG5B,IAAM,IAAmB,MAAM,EAAsB;CAErD,IAAI,MAAqB,eAGvB,OAAO;CAGT,IAAI,MAAqB,WAKvB,OAJA,QAAQ,KACN,6DAA6D,EAAiB,gEAEhF,GACO;CAGT,IAAI;EACF,OAAO,MAAM,EAAiB;CAChC,SAAS,GAAO;EAad,OAZA,QAAQ,MAAM,0CAA0C,CAAK,GAEzD,EAAU,KACZ,QAAQ,MACN,6OAKF,GAGK;CACT;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knocklabs/expo",
3
- "version": "0.7.0-rc.1",
3
+ "version": "0.8.0",
4
4
  "author": "@knocklabs",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",
@@ -49,13 +49,13 @@
49
49
  "expo-device": "*",
50
50
  "expo-notifications": "*",
51
51
  "react": "*",
52
- "react-native": "*",
52
+ "react-native": ">=0.81",
53
53
  "react-native-gesture-handler": ">=2.0.0"
54
54
  },
55
55
  "dependencies": {
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",
56
+ "@knocklabs/client": "^0.22.0",
57
+ "@knocklabs/react-core": "^0.15.0",
58
+ "@knocklabs/react-native": "^0.11.0",
59
59
  "react-native-render-html": "^6.3.4",
60
60
  "react-native-svg": "~15.15.3"
61
61
  },
@@ -67,7 +67,7 @@
67
67
  "@types/react-native-htmlview": "^0.16.6",
68
68
  "@typescript-eslint/eslint-plugin": "^8.59.4",
69
69
  "@typescript-eslint/parser": "^8.59.4",
70
- "@vitejs/plugin-react": "^4.5.1",
70
+ "@vitejs/plugin-react": "^6.0.4",
71
71
  "eslint": "^8.56.0",
72
72
  "eslint-plugin-react-hooks": "^5.2.0",
73
73
  "eslint-plugin-react-refresh": "^0.5.2",
@@ -81,9 +81,9 @@
81
81
  "react-native-gesture-handler": "~2.30.0",
82
82
  "rimraf": "^6.0.1",
83
83
  "typescript": "^5.8.3",
84
- "vite": "^5.4.19",
84
+ "vite": "^8.1.5",
85
85
  "vite-plugin-dts": "^4.5.0",
86
86
  "vite-plugin-no-bundle": "^4.0.0",
87
- "vitest": "^3.2.4"
87
+ "vitest": "^4.1.10"
88
88
  }
89
89
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}