@knocklabs/expo 0.5.7 → 0.6.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 +13 -0
- package/dist/cjs/modules/push/KnockExpoPushNotificationProvider.js +1 -1
- package/dist/cjs/modules/push/KnockExpoPushNotificationProvider.js.map +1 -1
- package/dist/cjs/modules/push/getNotificationsModule.js +4 -0
- package/dist/cjs/modules/push/getNotificationsModule.js.map +1 -0
- package/dist/cjs/modules/push/utils.js +2 -2
- package/dist/cjs/modules/push/utils.js.map +1 -1
- package/dist/esm/modules/push/KnockExpoPushNotificationProvider.mjs +57 -53
- package/dist/esm/modules/push/KnockExpoPushNotificationProvider.mjs.map +1 -1
- package/dist/esm/modules/push/getNotificationsModule.mjs +32 -0
- package/dist/esm/modules/push/getNotificationsModule.mjs.map +1 -0
- package/dist/esm/modules/push/utils.mjs +32 -23
- package/dist/esm/modules/push/utils.mjs.map +1 -1
- package/dist/types/modules/push/KnockExpoPushNotificationProvider.d.ts.map +1 -1
- package/dist/types/modules/push/getNotificationsModule.d.ts +15 -0
- package/dist/types/modules/push/getNotificationsModule.d.ts.map +1 -0
- package/dist/types/modules/push/types.d.ts +4 -4
- package/dist/types/modules/push/types.d.ts.map +1 -1
- package/dist/types/modules/push/utils.d.ts +10 -4
- package/dist/types/modules/push/utils.d.ts.map +1 -1
- package/package.json +11 -10
- package/src/modules/push/KnockExpoPushNotificationProvider.tsx +27 -16
- package/src/modules/push/getNotificationsModule.ts +97 -0
- package/src/modules/push/types.ts +10 -5
- package/src/modules/push/utils.ts +53 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @knocklabs/expo
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ecbfed1: chore: upgrade expo and react native packages to their latest version, ensure expo go builds on android get the proper warning about deprecated notification support from expo, ensure expo-example app works as expected.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- b932f46: Move `react-native-gesture-handler` from dependencies to peer dependencies so consumer apps do not install duplicate native module copies.
|
|
12
|
+
- Updated dependencies [b932f46]
|
|
13
|
+
- Updated dependencies [ecbfed1]
|
|
14
|
+
- @knocklabs/react-native@0.9.0
|
|
15
|
+
|
|
3
16
|
## 0.5.7
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime"),w=require("@knocklabs/react-core"),P=require("@knocklabs/react-native"),t=require("react"),N=require("./getNotificationsModule.js"),d=require("./utils.js"),v=t.createContext(void 0);function y(){const r=t.useContext(v);if(r===void 0)throw new Error("[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider");return r}const M=({knockExpoChannelId:r,customNotificationHandler:c,setupAndroidNotificationChannel:k=d.setupDefaultAndroidChannel,children:x,autoRegister:g=!0})=>{const o=w.useKnockClient(),{registerPushTokenToChannel:u,unregisterPushTokenFromChannel:K}=P.usePushNotifications(),[E,C]=t.useState(null),h=t.useRef(()=>{}),p=t.useRef(()=>{}),R=t.useCallback(e=>{h.current=e},[]),T=t.useCallback(e=>{p.current=e},[]),a=t.useCallback(async()=>{try{o.log("[Knock] Registering for push notifications");const e=await d.registerForPushNotifications(k);return e?(o.log(`[Knock] Push token received: ${e}`),C(e),e):null}catch(e){return console.error("[Knock] Error registering for push notifications:",e),null}},[o,k]),f=t.useCallback(async(e,s)=>{var i;const n=(i=e.request.content.data)==null?void 0:i.knock_message_id;if(!n){o.log("[Knock] Skipping status update for non-Knock notification");return}return o.messages.updateStatus(n,s)},[o]);t.useEffect(()=>{const e=N.getNotificationsModule();if(!e)return;const s=c||(async()=>d.DEFAULT_NOTIFICATION_BEHAVIOR);e.setNotificationHandler({handleNotification:s})},[c]),t.useEffect(()=>{if(!g)return;let e=!0;return(async()=>{try{const n=await a();n&&e&&(await u(n,r),o.log("[Knock] Push token registered with Knock channel"))}catch(n){console.error("[Knock] Error during auto-registration:",n)}})(),()=>{e=!1}},[g,r,a,u,o]),t.useEffect(()=>{const e=N.getNotificationsModule();if(!e)return;const s=e.addNotificationReceivedListener(i=>{o.log("[Knock] Notification received in foreground"),f(i,"interacted"),h.current(i)}),n=e.addNotificationResponseReceivedListener(i=>{o.log("[Knock] Notification was tapped"),f(i.notification,"interacted"),p.current(i)});return()=>{s.remove(),n.remove()}},[o,f]);const b={expoPushToken:E,registerForPushNotifications:a,registerPushTokenToChannel:u,unregisterPushTokenFromChannel:K,onNotificationReceived:R,onNotificationTapped:T};return l.jsx(v.Provider,{value:b,children:x})},S=r=>l.jsx(P.KnockPushNotificationProvider,{children:l.jsx(M,{...r})});exports.KnockExpoPushNotificationProvider=S;exports.useExpoPushNotifications=y;
|
|
2
2
|
//# sourceMappingURL=KnockExpoPushNotificationProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnockExpoPushNotificationProvider.js","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import { Message, MessageEngagementStatus } from \"@knocklabs/client\";\nimport { useKnockClient } from \"@knocklabs/react-core\";\nimport {\n KnockPushNotificationProvider,\n usePushNotifications,\n} from \"@knocklabs/react-native\";\nimport * as Notifications from \"expo-notifications\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\nimport {\n DEFAULT_NOTIFICATION_BEHAVIOR,\n registerForPushNotifications as registerForPushNotificationsUtil,\n setupDefaultAndroidChannel,\n} from \"./utils\";\n\n// Re-export types for consumers\nexport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\n/**\n * Hook to access push notification functionality within a KnockExpoPushNotificationProvider.\n * @throws Error if used outside of a KnockExpoPushNotificationProvider\n */\nexport function useExpoPushNotifications(): KnockExpoPushNotificationContextType {\n const context = useContext(KnockExpoPushNotificationContext);\n\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider\",\n );\n }\n\n return context;\n}\n\n/**\n * Internal provider component that handles all the Expo push notification logic.\n */\nconst InternalExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n setupAndroidNotificationChannel = setupDefaultAndroidChannel,\n children,\n autoRegister = true,\n}) => {\n const knockClient = useKnockClient();\n const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =\n usePushNotifications();\n\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n\n // Use refs for handlers to avoid re-running effects when handlers change\n const notificationReceivedHandlerRef = useRef<\n (notification: Notifications.Notification) => void\n >(() => {});\n\n const notificationTappedHandlerRef = useRef<\n (response: Notifications.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: Notifications.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: Notifications.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: Notifications.Notification,\n status: MessageEngagementStatus,\n ): Promise<Message | void> => {\n const messageId = notification.request.content.data?.[\n \"knock_message_id\"\n ] as string | undefined;\n\n // Skip status update if this isn't a Knock notification\n // Fixes issue: https://github.com/knocklabs/javascript/issues/589\n if (!messageId) {\n knockClient.log(\n \"[Knock] Skipping status update for non-Knock notification\",\n );\n return;\n }\n\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n // Set up the notification handler for foreground notifications\n useEffect(() => {\n const handleNotification = customNotificationHandler\n ? customNotificationHandler\n : async () => DEFAULT_NOTIFICATION_BEHAVIOR;\n\n Notifications.setNotificationHandler({ handleNotification });\n }, [customNotificationHandler]);\n\n // Auto-register for push notifications on mount if enabled\n useEffect(() => {\n if (!autoRegister) {\n return;\n }\n\n let isMounted = true;\n\n const register = async () => {\n try {\n const token = await registerForPushNotifications();\n\n if (token && isMounted) {\n await registerPushTokenToChannel(token, knockExpoChannelId);\n knockClient.log(\"[Knock] Push token registered with Knock channel\");\n }\n } catch (error) {\n console.error(\"[Knock] Error during auto-registration:\", error);\n }\n };\n\n register();\n\n return () => {\n isMounted = false;\n };\n }, [\n autoRegister,\n knockExpoChannelId,\n registerForPushNotifications,\n registerPushTokenToChannel,\n knockClient,\n ]);\n\n // Set up notification listeners for received and tapped notifications\n useEffect(() => {\n const receivedSubscription = Notifications.addNotificationReceivedListener(\n (notification) => {\n knockClient.log(\"[Knock] Notification received in foreground\");\n updateMessageStatus(notification, \"interacted\");\n notificationReceivedHandlerRef.current(notification);\n },\n );\n\n const responseSubscription =\n Notifications.addNotificationResponseReceivedListener((response) => {\n knockClient.log(\"[Knock] Notification was tapped\");\n updateMessageStatus(response.notification, \"interacted\");\n notificationTappedHandlerRef.current(response);\n });\n\n return () => {\n receivedSubscription.remove();\n responseSubscription.remove();\n };\n }, [knockClient, updateMessageStatus]);\n\n const contextValue: KnockExpoPushNotificationContextType = {\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived,\n onNotificationTapped,\n };\n\n return (\n <KnockExpoPushNotificationContext.Provider value={contextValue}>\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\n/**\n * Provider component for Expo push notifications with Knock.\n *\n * Wraps the internal provider with the base KnockPushNotificationProvider\n * to provide full push notification functionality.\n *\n * @example\n * ```tsx\n * <KnockProvider apiKey=\"your-api-key\" userId=\"user-id\">\n * <KnockExpoPushNotificationProvider knockExpoChannelId=\"your-channel-id\">\n * <App />\n * </KnockExpoPushNotificationProvider>\n * </KnockProvider>\n * ```\n */\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = (props) => {\n return (\n <KnockPushNotificationProvider>\n <InternalExpoPushNotificationProvider {...props} />\n </KnockPushNotificationProvider>\n );\n};\n"],"names":["KnockExpoPushNotificationContext","createContext","useExpoPushNotifications","context","useContext","InternalExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","setupAndroidNotificationChannel","setupDefaultAndroidChannel","children","autoRegister","knockClient","useKnockClient","registerPushTokenToChannel","unregisterPushTokenFromChannel","usePushNotifications","expoPushToken","setExpoPushToken","useState","notificationReceivedHandlerRef","useRef","notificationTappedHandlerRef","onNotificationReceived","useCallback","handler","onNotificationTapped","registerForPushNotifications","token","registerForPushNotificationsUtil","error","updateMessageStatus","notification","status","messageId","_a","useEffect","handleNotification","DEFAULT_NOTIFICATION_BEHAVIOR","Notifications","isMounted","receivedSubscription","responseSubscription","response","contextValue","KnockExpoPushNotificationProvider","props","KnockPushNotificationProvider","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.js","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import { Message, MessageEngagementStatus } from \"@knocklabs/client\";\nimport { useKnockClient } from \"@knocklabs/react-core\";\nimport {\n KnockPushNotificationProvider,\n usePushNotifications,\n} from \"@knocklabs/react-native\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n type Notification,\n type NotificationResponse,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\nimport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\nimport {\n DEFAULT_NOTIFICATION_BEHAVIOR,\n registerForPushNotifications as registerForPushNotificationsUtil,\n setupDefaultAndroidChannel,\n} from \"./utils\";\n\n// Re-export types for consumers\nexport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\n/**\n * Hook to access push notification functionality within a KnockExpoPushNotificationProvider.\n * @throws Error if used outside of a KnockExpoPushNotificationProvider\n */\nexport function useExpoPushNotifications(): KnockExpoPushNotificationContextType {\n const context = useContext(KnockExpoPushNotificationContext);\n\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider\",\n );\n }\n\n return context;\n}\n\n/**\n * Internal provider component that handles all the Expo push notification logic.\n */\nconst InternalExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n setupAndroidNotificationChannel = setupDefaultAndroidChannel,\n children,\n autoRegister = true,\n}) => {\n const knockClient = useKnockClient();\n const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =\n usePushNotifications();\n\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n\n // Use refs for handlers to avoid re-running effects when handlers change\n const notificationReceivedHandlerRef = useRef<\n (notification: Notification) => void\n >(() => {});\n\n const notificationTappedHandlerRef = useRef<\n (response: NotificationResponse) => void\n >(() => {});\n\n /**\n * Register a handler to be called when a notification is received in the foreground.\n */\n const onNotificationReceived = useCallback(\n (handler: (notification: Notification) => void) => {\n notificationReceivedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Register a handler to be called when a notification is tapped.\n */\n const onNotificationTapped = useCallback(\n (handler: (response: NotificationResponse) => void) => {\n notificationTappedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Manually trigger push notification registration.\n * Returns the push token if successful, or null if registration failed.\n */\n const registerForPushNotifications = useCallback(async (): Promise<\n string | null\n > => {\n try {\n knockClient.log(\"[Knock] Registering for push notifications\");\n\n const token = await registerForPushNotificationsUtil(\n setupAndroidNotificationChannel,\n );\n\n if (token) {\n knockClient.log(`[Knock] Push token received: ${token}`);\n setExpoPushToken(token);\n return token;\n }\n\n return null;\n } catch (error) {\n console.error(\"[Knock] Error registering for push notifications:\", error);\n return null;\n }\n }, [knockClient, setupAndroidNotificationChannel]);\n\n /**\n * Update the Knock message status when a notification is received or interacted with.\n * Only updates status for notifications that originated from Knock (have a knock_message_id).\n */\n const updateMessageStatus = useCallback(\n async (\n notification: Notification,\n status: MessageEngagementStatus,\n ): Promise<Message | void> => {\n const messageId = notification.request.content.data?.[\n \"knock_message_id\"\n ] as string | undefined;\n\n // Skip status update if this isn't a Knock notification\n // Fixes issue: https://github.com/knocklabs/javascript/issues/589\n if (!messageId) {\n knockClient.log(\n \"[Knock] Skipping status update for non-Knock notification\",\n );\n return;\n }\n\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n // Set up the notification handler for foreground notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const handleNotification = customNotificationHandler\n ? customNotificationHandler\n : async () => DEFAULT_NOTIFICATION_BEHAVIOR;\n\n NotificationsModule.setNotificationHandler({ handleNotification });\n }, [customNotificationHandler]);\n\n // Auto-register for push notifications on mount if enabled\n useEffect(() => {\n if (!autoRegister) {\n return;\n }\n\n let isMounted = true;\n\n const register = async () => {\n try {\n const token = await registerForPushNotifications();\n\n if (token && isMounted) {\n await registerPushTokenToChannel(token, knockExpoChannelId);\n knockClient.log(\"[Knock] Push token registered with Knock channel\");\n }\n } catch (error) {\n console.error(\"[Knock] Error during auto-registration:\", error);\n }\n };\n\n register();\n\n return () => {\n isMounted = false;\n };\n }, [\n autoRegister,\n knockExpoChannelId,\n registerForPushNotifications,\n registerPushTokenToChannel,\n knockClient,\n ]);\n\n // Set up notification listeners for received and tapped notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const receivedSubscription =\n NotificationsModule.addNotificationReceivedListener((notification) => {\n knockClient.log(\"[Knock] Notification received in foreground\");\n updateMessageStatus(notification, \"interacted\");\n notificationReceivedHandlerRef.current(notification);\n });\n\n const responseSubscription =\n NotificationsModule.addNotificationResponseReceivedListener(\n (response) => {\n knockClient.log(\"[Knock] Notification was tapped\");\n updateMessageStatus(response.notification, \"interacted\");\n notificationTappedHandlerRef.current(response);\n },\n );\n\n return () => {\n receivedSubscription.remove();\n responseSubscription.remove();\n };\n }, [knockClient, updateMessageStatus]);\n\n const contextValue: KnockExpoPushNotificationContextType = {\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived,\n onNotificationTapped,\n };\n\n return (\n <KnockExpoPushNotificationContext.Provider value={contextValue}>\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\n/**\n * Provider component for Expo push notifications with Knock.\n *\n * Wraps the internal provider with the base KnockPushNotificationProvider\n * to provide full push notification functionality.\n *\n * @example\n * ```tsx\n * <KnockProvider apiKey=\"your-api-key\" userId=\"user-id\">\n * <KnockExpoPushNotificationProvider knockExpoChannelId=\"your-channel-id\">\n * <App />\n * </KnockExpoPushNotificationProvider>\n * </KnockProvider>\n * ```\n */\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = (props) => {\n return (\n <KnockPushNotificationProvider>\n <InternalExpoPushNotificationProvider {...props} />\n </KnockPushNotificationProvider>\n );\n};\n"],"names":["KnockExpoPushNotificationContext","createContext","useExpoPushNotifications","context","useContext","InternalExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","setupAndroidNotificationChannel","setupDefaultAndroidChannel","children","autoRegister","knockClient","useKnockClient","registerPushTokenToChannel","unregisterPushTokenFromChannel","usePushNotifications","expoPushToken","setExpoPushToken","useState","notificationReceivedHandlerRef","useRef","notificationTappedHandlerRef","onNotificationReceived","useCallback","handler","onNotificationTapped","registerForPushNotifications","token","registerForPushNotificationsUtil","error","updateMessageStatus","notification","status","messageId","_a","useEffect","NotificationsModule","getNotificationsModule","handleNotification","DEFAULT_NOTIFICATION_BEHAVIOR","isMounted","receivedSubscription","responseSubscription","response","contextValue","KnockExpoPushNotificationProvider","props","KnockPushNotificationProvider","jsx"],"mappings":"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"}
|
|
@@ -0,0 +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.
|
|
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
|
|
@@ -0,0 +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,6 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
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:
|
|
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=
|
|
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
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
|
|
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,108 +1,112 @@
|
|
|
1
|
-
import { jsx as
|
|
1
|
+
import { jsx as l } from "react/jsx-runtime";
|
|
2
2
|
import { useKnockClient as C } from "@knocklabs/react-core";
|
|
3
3
|
import { KnockPushNotificationProvider as y, usePushNotifications as F } from "@knocklabs/react-native";
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import { registerForPushNotifications as
|
|
4
|
+
import { createContext as S, useContext as I, useState as M, useRef as P, useCallback as s, useEffect as d } from "react";
|
|
5
|
+
import { getNotificationsModule as N } from "./getNotificationsModule.mjs";
|
|
6
|
+
import { registerForPushNotifications as b, setupDefaultAndroidChannel as _, DEFAULT_NOTIFICATION_BEHAVIOR as A } from "./utils.mjs";
|
|
7
7
|
const m = S(void 0);
|
|
8
|
-
function
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
8
|
+
function q() {
|
|
9
|
+
const i = I(m);
|
|
10
|
+
if (i === void 0)
|
|
11
11
|
throw new Error(
|
|
12
12
|
"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider"
|
|
13
13
|
);
|
|
14
|
-
return
|
|
14
|
+
return i;
|
|
15
15
|
}
|
|
16
|
-
const
|
|
17
|
-
knockExpoChannelId:
|
|
18
|
-
customNotificationHandler:
|
|
19
|
-
setupAndroidNotificationChannel:
|
|
16
|
+
const L = ({
|
|
17
|
+
knockExpoChannelId: i,
|
|
18
|
+
customNotificationHandler: c,
|
|
19
|
+
setupAndroidNotificationChannel: p = _,
|
|
20
20
|
children: v,
|
|
21
21
|
autoRegister: h = !0
|
|
22
22
|
}) => {
|
|
23
|
-
const
|
|
24
|
-
}), g =
|
|
25
|
-
}), T =
|
|
23
|
+
const t = C(), { registerPushTokenToChannel: a, unregisterPushTokenFromChannel: K } = F(), [x, E] = M(null), k = P(() => {
|
|
24
|
+
}), g = P(() => {
|
|
25
|
+
}), T = s(
|
|
26
26
|
(o) => {
|
|
27
27
|
k.current = o;
|
|
28
28
|
},
|
|
29
29
|
[]
|
|
30
|
-
), R =
|
|
30
|
+
), R = s(
|
|
31
31
|
(o) => {
|
|
32
32
|
g.current = o;
|
|
33
33
|
},
|
|
34
34
|
[]
|
|
35
|
-
),
|
|
35
|
+
), u = s(async () => {
|
|
36
36
|
try {
|
|
37
|
-
|
|
38
|
-
const o = await
|
|
39
|
-
|
|
37
|
+
t.log("[Knock] Registering for push notifications");
|
|
38
|
+
const o = await b(
|
|
39
|
+
p
|
|
40
40
|
);
|
|
41
|
-
return o ? (
|
|
41
|
+
return o ? (t.log(`[Knock] Push token received: ${o}`), E(o), o) : null;
|
|
42
42
|
} catch (o) {
|
|
43
43
|
return console.error("[Knock] Error registering for push notifications:", o), null;
|
|
44
44
|
}
|
|
45
|
-
}, [
|
|
46
|
-
async (o,
|
|
47
|
-
var
|
|
48
|
-
const
|
|
49
|
-
if (!
|
|
50
|
-
|
|
45
|
+
}, [t, p]), f = s(
|
|
46
|
+
async (o, r) => {
|
|
47
|
+
var n;
|
|
48
|
+
const e = (n = o.request.content.data) == null ? void 0 : n.knock_message_id;
|
|
49
|
+
if (!e) {
|
|
50
|
+
t.log(
|
|
51
51
|
"[Knock] Skipping status update for non-Knock notification"
|
|
52
52
|
);
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
|
-
return
|
|
55
|
+
return t.messages.updateStatus(e, r);
|
|
56
56
|
},
|
|
57
|
-
[
|
|
57
|
+
[t]
|
|
58
58
|
);
|
|
59
59
|
d(() => {
|
|
60
|
-
const o =
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const o = N();
|
|
61
|
+
if (!o) return;
|
|
62
|
+
const r = c || (async () => A);
|
|
63
|
+
o.setNotificationHandler({ handleNotification: r });
|
|
64
|
+
}, [c]), d(() => {
|
|
63
65
|
if (!h)
|
|
64
66
|
return;
|
|
65
67
|
let o = !0;
|
|
66
68
|
return (async () => {
|
|
67
69
|
try {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
} catch (
|
|
71
|
-
console.error("[Knock] Error during auto-registration:",
|
|
70
|
+
const e = await u();
|
|
71
|
+
e && o && (await a(e, i), t.log("[Knock] Push token registered with Knock channel"));
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error("[Knock] Error during auto-registration:", e);
|
|
72
74
|
}
|
|
73
75
|
})(), () => {
|
|
74
76
|
o = !1;
|
|
75
77
|
};
|
|
76
78
|
}, [
|
|
77
79
|
h,
|
|
78
|
-
|
|
80
|
+
i,
|
|
81
|
+
u,
|
|
79
82
|
a,
|
|
80
|
-
|
|
81
|
-
e
|
|
83
|
+
t
|
|
82
84
|
]), d(() => {
|
|
83
|
-
const o =
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
const o = N();
|
|
86
|
+
if (!o) return;
|
|
87
|
+
const r = o.addNotificationReceivedListener((n) => {
|
|
88
|
+
t.log("[Knock] Notification received in foreground"), f(n, "interacted"), k.current(n);
|
|
89
|
+
}), e = o.addNotificationResponseReceivedListener(
|
|
90
|
+
(n) => {
|
|
91
|
+
t.log("[Knock] Notification was tapped"), f(n.notification, "interacted"), g.current(n);
|
|
86
92
|
}
|
|
87
|
-
)
|
|
88
|
-
e.log("[Knock] Notification was tapped"), u(t.notification, "interacted"), g.current(t);
|
|
89
|
-
});
|
|
93
|
+
);
|
|
90
94
|
return () => {
|
|
91
|
-
|
|
95
|
+
r.remove(), e.remove();
|
|
92
96
|
};
|
|
93
|
-
}, [
|
|
97
|
+
}, [t, f]);
|
|
94
98
|
const w = {
|
|
95
99
|
expoPushToken: x,
|
|
96
|
-
registerForPushNotifications:
|
|
97
|
-
registerPushTokenToChannel:
|
|
100
|
+
registerForPushNotifications: u,
|
|
101
|
+
registerPushTokenToChannel: a,
|
|
98
102
|
unregisterPushTokenFromChannel: K,
|
|
99
103
|
onNotificationReceived: T,
|
|
100
104
|
onNotificationTapped: R
|
|
101
105
|
};
|
|
102
|
-
return /* @__PURE__ */
|
|
103
|
-
},
|
|
106
|
+
return /* @__PURE__ */ l(m.Provider, { value: w, children: v });
|
|
107
|
+
}, B = (i) => /* @__PURE__ */ l(y, { children: /* @__PURE__ */ l(L, { ...i }) });
|
|
104
108
|
export {
|
|
105
|
-
|
|
106
|
-
|
|
109
|
+
B as KnockExpoPushNotificationProvider,
|
|
110
|
+
q as useExpoPushNotifications
|
|
107
111
|
};
|
|
108
112
|
//# sourceMappingURL=KnockExpoPushNotificationProvider.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnockExpoPushNotificationProvider.mjs","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import { Message, MessageEngagementStatus } from \"@knocklabs/client\";\nimport { useKnockClient } from \"@knocklabs/react-core\";\nimport {\n KnockPushNotificationProvider,\n usePushNotifications,\n} from \"@knocklabs/react-native\";\nimport * as Notifications from \"expo-notifications\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\nimport {\n DEFAULT_NOTIFICATION_BEHAVIOR,\n registerForPushNotifications as registerForPushNotificationsUtil,\n setupDefaultAndroidChannel,\n} from \"./utils\";\n\n// Re-export types for consumers\nexport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\n/**\n * Hook to access push notification functionality within a KnockExpoPushNotificationProvider.\n * @throws Error if used outside of a KnockExpoPushNotificationProvider\n */\nexport function useExpoPushNotifications(): KnockExpoPushNotificationContextType {\n const context = useContext(KnockExpoPushNotificationContext);\n\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider\",\n );\n }\n\n return context;\n}\n\n/**\n * Internal provider component that handles all the Expo push notification logic.\n */\nconst InternalExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n setupAndroidNotificationChannel = setupDefaultAndroidChannel,\n children,\n autoRegister = true,\n}) => {\n const knockClient = useKnockClient();\n const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =\n usePushNotifications();\n\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n\n // Use refs for handlers to avoid re-running effects when handlers change\n const notificationReceivedHandlerRef = useRef<\n (notification: Notifications.Notification) => void\n >(() => {});\n\n const notificationTappedHandlerRef = useRef<\n (response: Notifications.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: Notifications.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: Notifications.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: Notifications.Notification,\n status: MessageEngagementStatus,\n ): Promise<Message | void> => {\n const messageId = notification.request.content.data?.[\n \"knock_message_id\"\n ] as string | undefined;\n\n // Skip status update if this isn't a Knock notification\n // Fixes issue: https://github.com/knocklabs/javascript/issues/589\n if (!messageId) {\n knockClient.log(\n \"[Knock] Skipping status update for non-Knock notification\",\n );\n return;\n }\n\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n // Set up the notification handler for foreground notifications\n useEffect(() => {\n const handleNotification = customNotificationHandler\n ? customNotificationHandler\n : async () => DEFAULT_NOTIFICATION_BEHAVIOR;\n\n Notifications.setNotificationHandler({ handleNotification });\n }, [customNotificationHandler]);\n\n // Auto-register for push notifications on mount if enabled\n useEffect(() => {\n if (!autoRegister) {\n return;\n }\n\n let isMounted = true;\n\n const register = async () => {\n try {\n const token = await registerForPushNotifications();\n\n if (token && isMounted) {\n await registerPushTokenToChannel(token, knockExpoChannelId);\n knockClient.log(\"[Knock] Push token registered with Knock channel\");\n }\n } catch (error) {\n console.error(\"[Knock] Error during auto-registration:\", error);\n }\n };\n\n register();\n\n return () => {\n isMounted = false;\n };\n }, [\n autoRegister,\n knockExpoChannelId,\n registerForPushNotifications,\n registerPushTokenToChannel,\n knockClient,\n ]);\n\n // Set up notification listeners for received and tapped notifications\n useEffect(() => {\n const receivedSubscription = Notifications.addNotificationReceivedListener(\n (notification) => {\n knockClient.log(\"[Knock] Notification received in foreground\");\n updateMessageStatus(notification, \"interacted\");\n notificationReceivedHandlerRef.current(notification);\n },\n );\n\n const responseSubscription =\n Notifications.addNotificationResponseReceivedListener((response) => {\n knockClient.log(\"[Knock] Notification was tapped\");\n updateMessageStatus(response.notification, \"interacted\");\n notificationTappedHandlerRef.current(response);\n });\n\n return () => {\n receivedSubscription.remove();\n responseSubscription.remove();\n };\n }, [knockClient, updateMessageStatus]);\n\n const contextValue: KnockExpoPushNotificationContextType = {\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived,\n onNotificationTapped,\n };\n\n return (\n <KnockExpoPushNotificationContext.Provider value={contextValue}>\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\n/**\n * Provider component for Expo push notifications with Knock.\n *\n * Wraps the internal provider with the base KnockPushNotificationProvider\n * to provide full push notification functionality.\n *\n * @example\n * ```tsx\n * <KnockProvider apiKey=\"your-api-key\" userId=\"user-id\">\n * <KnockExpoPushNotificationProvider knockExpoChannelId=\"your-channel-id\">\n * <App />\n * </KnockExpoPushNotificationProvider>\n * </KnockProvider>\n * ```\n */\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = (props) => {\n return (\n <KnockPushNotificationProvider>\n <InternalExpoPushNotificationProvider {...props} />\n </KnockPushNotificationProvider>\n );\n};\n"],"names":["KnockExpoPushNotificationContext","createContext","useExpoPushNotifications","context","useContext","InternalExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","setupAndroidNotificationChannel","setupDefaultAndroidChannel","children","autoRegister","knockClient","useKnockClient","registerPushTokenToChannel","unregisterPushTokenFromChannel","usePushNotifications","expoPushToken","setExpoPushToken","useState","notificationReceivedHandlerRef","useRef","notificationTappedHandlerRef","onNotificationReceived","useCallback","handler","onNotificationTapped","registerForPushNotifications","token","registerForPushNotificationsUtil","error","updateMessageStatus","notification","status","messageId","_a","useEffect","handleNotification","DEFAULT_NOTIFICATION_BEHAVIOR","Notifications","isMounted","receivedSubscription","responseSubscription","response","contextValue","KnockExpoPushNotificationProvider","props","KnockPushNotificationProvider","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.mjs","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"sourcesContent":["import { Message, MessageEngagementStatus } from \"@knocklabs/client\";\nimport { useKnockClient } from \"@knocklabs/react-core\";\nimport {\n KnockPushNotificationProvider,\n usePushNotifications,\n} from \"@knocklabs/react-native\";\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n type Notification,\n type NotificationResponse,\n getNotificationsModule,\n} from \"./getNotificationsModule\";\nimport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\nimport {\n DEFAULT_NOTIFICATION_BEHAVIOR,\n registerForPushNotifications as registerForPushNotificationsUtil,\n setupDefaultAndroidChannel,\n} from \"./utils\";\n\n// Re-export types for consumers\nexport type {\n KnockExpoPushNotificationContextType,\n KnockExpoPushNotificationProviderProps,\n} from \"./types\";\n\nconst KnockExpoPushNotificationContext = createContext<\n KnockExpoPushNotificationContextType | undefined\n>(undefined);\n\n/**\n * Hook to access push notification functionality within a KnockExpoPushNotificationProvider.\n * @throws Error if used outside of a KnockExpoPushNotificationProvider\n */\nexport function useExpoPushNotifications(): KnockExpoPushNotificationContextType {\n const context = useContext(KnockExpoPushNotificationContext);\n\n if (context === undefined) {\n throw new Error(\n \"[Knock] useExpoPushNotifications must be used within a KnockExpoPushNotificationProvider\",\n );\n }\n\n return context;\n}\n\n/**\n * Internal provider component that handles all the Expo push notification logic.\n */\nconst InternalExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = ({\n knockExpoChannelId,\n customNotificationHandler,\n setupAndroidNotificationChannel = setupDefaultAndroidChannel,\n children,\n autoRegister = true,\n}) => {\n const knockClient = useKnockClient();\n const { registerPushTokenToChannel, unregisterPushTokenFromChannel } =\n usePushNotifications();\n\n const [expoPushToken, setExpoPushToken] = useState<string | null>(null);\n\n // Use refs for handlers to avoid re-running effects when handlers change\n const notificationReceivedHandlerRef = useRef<\n (notification: Notification) => void\n >(() => {});\n\n const notificationTappedHandlerRef = useRef<\n (response: NotificationResponse) => void\n >(() => {});\n\n /**\n * Register a handler to be called when a notification is received in the foreground.\n */\n const onNotificationReceived = useCallback(\n (handler: (notification: Notification) => void) => {\n notificationReceivedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Register a handler to be called when a notification is tapped.\n */\n const onNotificationTapped = useCallback(\n (handler: (response: NotificationResponse) => void) => {\n notificationTappedHandlerRef.current = handler;\n },\n [],\n );\n\n /**\n * Manually trigger push notification registration.\n * Returns the push token if successful, or null if registration failed.\n */\n const registerForPushNotifications = useCallback(async (): Promise<\n string | null\n > => {\n try {\n knockClient.log(\"[Knock] Registering for push notifications\");\n\n const token = await registerForPushNotificationsUtil(\n setupAndroidNotificationChannel,\n );\n\n if (token) {\n knockClient.log(`[Knock] Push token received: ${token}`);\n setExpoPushToken(token);\n return token;\n }\n\n return null;\n } catch (error) {\n console.error(\"[Knock] Error registering for push notifications:\", error);\n return null;\n }\n }, [knockClient, setupAndroidNotificationChannel]);\n\n /**\n * Update the Knock message status when a notification is received or interacted with.\n * Only updates status for notifications that originated from Knock (have a knock_message_id).\n */\n const updateMessageStatus = useCallback(\n async (\n notification: Notification,\n status: MessageEngagementStatus,\n ): Promise<Message | void> => {\n const messageId = notification.request.content.data?.[\n \"knock_message_id\"\n ] as string | undefined;\n\n // Skip status update if this isn't a Knock notification\n // Fixes issue: https://github.com/knocklabs/javascript/issues/589\n if (!messageId) {\n knockClient.log(\n \"[Knock] Skipping status update for non-Knock notification\",\n );\n return;\n }\n\n return knockClient.messages.updateStatus(messageId, status);\n },\n [knockClient],\n );\n\n // Set up the notification handler for foreground notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const handleNotification = customNotificationHandler\n ? customNotificationHandler\n : async () => DEFAULT_NOTIFICATION_BEHAVIOR;\n\n NotificationsModule.setNotificationHandler({ handleNotification });\n }, [customNotificationHandler]);\n\n // Auto-register for push notifications on mount if enabled\n useEffect(() => {\n if (!autoRegister) {\n return;\n }\n\n let isMounted = true;\n\n const register = async () => {\n try {\n const token = await registerForPushNotifications();\n\n if (token && isMounted) {\n await registerPushTokenToChannel(token, knockExpoChannelId);\n knockClient.log(\"[Knock] Push token registered with Knock channel\");\n }\n } catch (error) {\n console.error(\"[Knock] Error during auto-registration:\", error);\n }\n };\n\n register();\n\n return () => {\n isMounted = false;\n };\n }, [\n autoRegister,\n knockExpoChannelId,\n registerForPushNotifications,\n registerPushTokenToChannel,\n knockClient,\n ]);\n\n // Set up notification listeners for received and tapped notifications\n useEffect(() => {\n const NotificationsModule = getNotificationsModule();\n if (!NotificationsModule) return;\n\n const receivedSubscription =\n NotificationsModule.addNotificationReceivedListener((notification) => {\n knockClient.log(\"[Knock] Notification received in foreground\");\n updateMessageStatus(notification, \"interacted\");\n notificationReceivedHandlerRef.current(notification);\n });\n\n const responseSubscription =\n NotificationsModule.addNotificationResponseReceivedListener(\n (response) => {\n knockClient.log(\"[Knock] Notification was tapped\");\n updateMessageStatus(response.notification, \"interacted\");\n notificationTappedHandlerRef.current(response);\n },\n );\n\n return () => {\n receivedSubscription.remove();\n responseSubscription.remove();\n };\n }, [knockClient, updateMessageStatus]);\n\n const contextValue: KnockExpoPushNotificationContextType = {\n expoPushToken,\n registerForPushNotifications,\n registerPushTokenToChannel,\n unregisterPushTokenFromChannel,\n onNotificationReceived,\n onNotificationTapped,\n };\n\n return (\n <KnockExpoPushNotificationContext.Provider value={contextValue}>\n {children}\n </KnockExpoPushNotificationContext.Provider>\n );\n};\n\n/**\n * Provider component for Expo push notifications with Knock.\n *\n * Wraps the internal provider with the base KnockPushNotificationProvider\n * to provide full push notification functionality.\n *\n * @example\n * ```tsx\n * <KnockProvider apiKey=\"your-api-key\" userId=\"user-id\">\n * <KnockExpoPushNotificationProvider knockExpoChannelId=\"your-channel-id\">\n * <App />\n * </KnockExpoPushNotificationProvider>\n * </KnockProvider>\n * ```\n */\nexport const KnockExpoPushNotificationProvider: React.FC<\n KnockExpoPushNotificationProviderProps\n> = (props) => {\n return (\n <KnockPushNotificationProvider>\n <InternalExpoPushNotificationProvider {...props} />\n </KnockPushNotificationProvider>\n );\n};\n"],"names":["KnockExpoPushNotificationContext","createContext","useExpoPushNotifications","context","useContext","InternalExpoPushNotificationProvider","knockExpoChannelId","customNotificationHandler","setupAndroidNotificationChannel","setupDefaultAndroidChannel","children","autoRegister","knockClient","useKnockClient","registerPushTokenToChannel","unregisterPushTokenFromChannel","usePushNotifications","expoPushToken","setExpoPushToken","useState","notificationReceivedHandlerRef","useRef","notificationTappedHandlerRef","onNotificationReceived","useCallback","handler","onNotificationTapped","registerForPushNotifications","token","registerForPushNotificationsUtil","error","updateMessageStatus","notification","status","messageId","_a","useEffect","NotificationsModule","getNotificationsModule","handleNotification","DEFAULT_NOTIFICATION_BEHAVIOR","isMounted","receivedSubscription","responseSubscription","response","contextValue","KnockExpoPushNotificationProvider","props","KnockPushNotificationProvider","jsx"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
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;
|
|
6
|
+
}
|
|
7
|
+
let r = () => (
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
9
|
+
require("expo-notifications")
|
|
10
|
+
);
|
|
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;
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
s as getNotificationsModule
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=getNotificationsModule.mjs.map
|
|
@@ -0,0 +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,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import p from "expo-constants";
|
|
2
2
|
import * as h from "expo-device";
|
|
3
|
-
import * as o from "expo-notifications";
|
|
4
3
|
import { Platform as m } from "react-native";
|
|
5
|
-
|
|
4
|
+
import { getNotificationsModule as e } from "./getNotificationsModule.mjs";
|
|
5
|
+
const C = {
|
|
6
6
|
shouldShowAlert: !0,
|
|
7
7
|
shouldPlaySound: !0,
|
|
8
8
|
shouldSetBadge: !0,
|
|
@@ -10,16 +10,19 @@ const A = {
|
|
|
10
10
|
shouldShowList: !0
|
|
11
11
|
};
|
|
12
12
|
function g() {
|
|
13
|
-
var n,
|
|
14
|
-
const t =
|
|
15
|
-
return (r = (
|
|
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;
|
|
16
16
|
}
|
|
17
17
|
async function j() {
|
|
18
|
-
const
|
|
19
|
-
if (t
|
|
20
|
-
return
|
|
21
|
-
const { status: n } = await
|
|
22
|
-
|
|
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;
|
|
23
26
|
}
|
|
24
27
|
async function I() {
|
|
25
28
|
const t = g();
|
|
@@ -27,13 +30,17 @@ async function I() {
|
|
|
27
30
|
return console.error(
|
|
28
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."
|
|
29
32
|
), null;
|
|
30
|
-
const n =
|
|
31
|
-
|
|
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;
|
|
32
38
|
}
|
|
33
39
|
async function x() {
|
|
34
|
-
|
|
40
|
+
const t = e();
|
|
41
|
+
t && await t.setNotificationChannelAsync("default", {
|
|
35
42
|
name: "Default",
|
|
36
|
-
importance:
|
|
43
|
+
importance: t.AndroidImportance.MAX,
|
|
37
44
|
vibrationPattern: [0, 250, 250, 250],
|
|
38
45
|
lightColor: "#FF231F7C"
|
|
39
46
|
});
|
|
@@ -41,24 +48,26 @@ async function x() {
|
|
|
41
48
|
function y() {
|
|
42
49
|
return h.isDevice;
|
|
43
50
|
}
|
|
44
|
-
function
|
|
51
|
+
function d() {
|
|
45
52
|
return m.OS === "android";
|
|
46
53
|
}
|
|
47
|
-
async function
|
|
54
|
+
async function k(t = x) {
|
|
48
55
|
if (!y())
|
|
49
56
|
return console.warn(
|
|
50
57
|
"[Knock] Must use physical device for Push Notifications. Push notifications are not supported on emulators/simulators."
|
|
51
58
|
), null;
|
|
52
|
-
|
|
59
|
+
d() && await t();
|
|
53
60
|
const n = await j();
|
|
61
|
+
if (n === "unavailable")
|
|
62
|
+
return null;
|
|
54
63
|
if (n !== "granted")
|
|
55
64
|
return console.warn(
|
|
56
65
|
`[Knock] Push notification permission not granted. Status: ${n}. User may have denied the permission or the system blocked it.`
|
|
57
66
|
), null;
|
|
58
67
|
try {
|
|
59
68
|
return await I();
|
|
60
|
-
} catch (
|
|
61
|
-
return console.error("[Knock] Error getting Expo push token:",
|
|
69
|
+
} catch (o) {
|
|
70
|
+
return console.error("[Knock] Error getting Expo push token:", o), d() && console.error(
|
|
62
71
|
`[Knock] Android push token registration failed. Common causes:
|
|
63
72
|
1. FCM is not configured (google-services.json missing)
|
|
64
73
|
2. Running on an emulator
|
|
@@ -68,12 +77,12 @@ async function C(t = x) {
|
|
|
68
77
|
}
|
|
69
78
|
}
|
|
70
79
|
export {
|
|
71
|
-
|
|
80
|
+
C as DEFAULT_NOTIFICATION_BEHAVIOR,
|
|
72
81
|
I as getExpoPushToken,
|
|
73
82
|
g as getProjectId,
|
|
74
|
-
|
|
83
|
+
d as isAndroid,
|
|
75
84
|
y as isPushNotificationSupported,
|
|
76
|
-
|
|
85
|
+
k as registerForPushNotifications,
|
|
77
86
|
j as requestPushPermission,
|
|
78
87
|
x as setupDefaultAndroidChannel
|
|
79
88
|
};
|
|
@@ -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
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KnockExpoPushNotificationProvider.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KnockExpoPushNotificationProvider.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/KnockExpoPushNotificationProvider.tsx"],"names":[],"mappings":"AAMA,OAAO,KAON,MAAM,OAAO,CAAC;AAOf,OAAO,KAAK,EACV,oCAAoC,EACpC,sCAAsC,EACvC,MAAM,SAAS,CAAC;AAQjB,YAAY,EACV,oCAAoC,EACpC,sCAAsC,GACvC,MAAM,SAAS,CAAC;AAMjB;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,oCAAoC,CAU/E;AAgMD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iCAAiC,EAAE,KAAK,CAAC,EAAE,CACtD,sCAAsC,CAOvC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type * as Notifications from "expo-notifications";
|
|
2
|
+
/**
|
|
3
|
+
* The type of the expo-notifications module when successfully loaded.
|
|
4
|
+
*/
|
|
5
|
+
export type NotificationsModule = typeof Notifications;
|
|
6
|
+
export type Notification = Notifications.Notification;
|
|
7
|
+
export type NotificationResponse = Notifications.NotificationResponse;
|
|
8
|
+
export type NotificationBehavior = Notifications.NotificationBehavior;
|
|
9
|
+
export declare function getNotificationsModule(): NotificationsModule | null;
|
|
10
|
+
/**
|
|
11
|
+
* @internal Test-only — reset the cached module and optionally override
|
|
12
|
+
* the require function used to load expo-notifications.
|
|
13
|
+
*/
|
|
14
|
+
export declare function _resetForTesting(overrideRequire?: () => NotificationsModule): void;
|
|
15
|
+
//# sourceMappingURL=getNotificationsModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getNotificationsModule.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/getNotificationsModule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,aAAa,MAAM,oBAAoB,CAAC;AAGzD;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,aAAa,CAAC;AAKvD,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;AACtD,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAC,oBAAoB,CAAC;AACtE,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAC,oBAAoB,CAAC;AAuCtE,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,IAAI,CA8BnE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,eAAe,CAAC,EAAE,MAAM,mBAAmB,GAC1C,IAAI,CAKN"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KnockPushNotificationContextType } from '@knocklabs/react-native';
|
|
2
2
|
import { default as React } from 'react';
|
|
3
|
-
import
|
|
3
|
+
import { Notification, NotificationBehavior, NotificationResponse } from './getNotificationsModule';
|
|
4
4
|
/**
|
|
5
5
|
* Context type for the Expo push notification provider.
|
|
6
6
|
* Extends the base push notification context with Expo-specific functionality.
|
|
@@ -11,9 +11,9 @@ export interface KnockExpoPushNotificationContextType extends KnockPushNotificat
|
|
|
11
11
|
/** Manually trigger push notification registration */
|
|
12
12
|
registerForPushNotifications: () => Promise<string | null>;
|
|
13
13
|
/** Register a handler for when a notification is received in the foreground */
|
|
14
|
-
onNotificationReceived: (handler: (notification:
|
|
14
|
+
onNotificationReceived: (handler: (notification: Notification) => void) => void;
|
|
15
15
|
/** Register a handler for when a notification is tapped */
|
|
16
|
-
onNotificationTapped: (handler: (response:
|
|
16
|
+
onNotificationTapped: (handler: (response: NotificationResponse) => void) => void;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Props for the KnockExpoPushNotificationProvider component.
|
|
@@ -25,7 +25,7 @@ export interface KnockExpoPushNotificationProviderProps {
|
|
|
25
25
|
* Custom handler for determining how notifications should be displayed.
|
|
26
26
|
* If not provided, notifications will show alerts, play sounds, and set badges.
|
|
27
27
|
*/
|
|
28
|
-
customNotificationHandler?: (notification:
|
|
28
|
+
customNotificationHandler?: (notification: Notification) => Promise<NotificationBehavior>;
|
|
29
29
|
/**
|
|
30
30
|
* Custom function to set up the Android notification channel.
|
|
31
31
|
* If not provided, a default channel will be created.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,KAAK,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAChF,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAElC;;;GAGG;AACH,MAAM,WAAW,oCACf,SAAQ,gCAAgC;IACxC,yDAAyD;IACzD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAE7B,sDAAsD;IACtD,4BAA4B,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE3D,+EAA+E;IAC/E,sBAAsB,EAAE,CACtB,OAAO,EAAE,CAAC,YAAY,EAAE,YAAY,KAAK,IAAI,KAC1C,IAAI,CAAC;IAEV,2DAA2D;IAC3D,oBAAoB,EAAE,CACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,KAC9C,IAAI,CAAC;CACX;AAED;;GAEG;AACH,MAAM,WAAW,sCAAsC;IACrD,uDAAuD;IACvD,kBAAkB,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,yBAAyB,CAAC,EAAE,CAC1B,YAAY,EAAE,YAAY,KACvB,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEnC;;;OAGG;IACH,+BAA+B,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtD,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC;IAE9B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB"}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { NotificationBehavior } from './getNotificationsModule';
|
|
2
|
+
/**
|
|
3
|
+
* Permission status values returned by expo-notifications.
|
|
4
|
+
* "unavailable" is returned when the notifications module could not be loaded
|
|
5
|
+
* (e.g. Android Expo Go where push support was removed in SDK 53).
|
|
6
|
+
*/
|
|
7
|
+
export type PushPermissionStatus = "granted" | "denied" | "undetermined" | "unavailable";
|
|
2
8
|
/**
|
|
3
9
|
* Default notification behavior when a notification is received.
|
|
4
10
|
*/
|
|
5
|
-
export declare const DEFAULT_NOTIFICATION_BEHAVIOR:
|
|
11
|
+
export declare const DEFAULT_NOTIFICATION_BEHAVIOR: NotificationBehavior;
|
|
6
12
|
/**
|
|
7
13
|
* Get the Expo project ID from various possible sources.
|
|
8
14
|
* Different Expo SDK versions and configurations store this differently.
|
|
@@ -10,9 +16,9 @@ export declare const DEFAULT_NOTIFICATION_BEHAVIOR: Notifications.NotificationBe
|
|
|
10
16
|
export declare function getProjectId(): string | null;
|
|
11
17
|
/**
|
|
12
18
|
* Request push notification permissions if not already granted.
|
|
13
|
-
* @returns The permission status
|
|
19
|
+
* @returns The permission status
|
|
14
20
|
*/
|
|
15
|
-
export declare function requestPushPermission(): Promise<
|
|
21
|
+
export declare function requestPushPermission(): Promise<PushPermissionStatus>;
|
|
16
22
|
/**
|
|
17
23
|
* Get the Expo push token for this device.
|
|
18
24
|
* @returns The push token or null if unable to get one
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/modules/push/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,0BAA0B,CAAC;AAKlC;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,QAAQ,GACR,cAAc,GACd,aAAa,CAAC;AAElB;;GAEG;AACH,eAAO,MAAM,6BAA6B,EAAE,oBAM3C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAwB5C;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAe3E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmB/D;AAED;;GAEG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC,CAYhE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,mBAAmB,GAAE,MAAM,OAAO,CAAC,IAAI,CAA8B,GACpE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiDxB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knocklabs/expo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"author": "@knocklabs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"expo-device": "*",
|
|
49
49
|
"expo-notifications": "*",
|
|
50
50
|
"react": "*",
|
|
51
|
-
"react-native": "*"
|
|
51
|
+
"react-native": "*",
|
|
52
|
+
"react-native-gesture-handler": ">=2.0.0"
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
55
|
"@knocklabs/client": "^0.21.7",
|
|
55
56
|
"@knocklabs/react-core": "^0.13.7",
|
|
56
|
-
"@knocklabs/react-native": "^0.
|
|
57
|
-
"react-native-gesture-handler": "^2.27.1",
|
|
57
|
+
"@knocklabs/react-native": "^0.9.0",
|
|
58
58
|
"react-native-render-html": "^6.3.4",
|
|
59
|
-
"react-native-svg": "
|
|
59
|
+
"react-native-svg": "~15.15.3"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@codecov/vite-plugin": "^1.9.1",
|
|
@@ -70,13 +70,14 @@
|
|
|
70
70
|
"eslint": "^8.56.0",
|
|
71
71
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
72
72
|
"eslint-plugin-react-refresh": "^0.5.2",
|
|
73
|
-
"expo": "~
|
|
74
|
-
"expo-constants": "~
|
|
75
|
-
"expo-device": "
|
|
76
|
-
"expo-notifications": "
|
|
73
|
+
"expo": "~55.0.9",
|
|
74
|
+
"expo-constants": "~55.0.9",
|
|
75
|
+
"expo-device": "~55.0.10",
|
|
76
|
+
"expo-notifications": "~55.0.14",
|
|
77
77
|
"jsdom": "^27.1.0",
|
|
78
78
|
"react": "^19.0.0",
|
|
79
|
-
"react-native": "^0.
|
|
79
|
+
"react-native": "^0.83.4",
|
|
80
|
+
"react-native-gesture-handler": "~2.30.0",
|
|
80
81
|
"rimraf": "^6.0.1",
|
|
81
82
|
"typescript": "^5.8.3",
|
|
82
83
|
"vite": "^5.4.19",
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
KnockPushNotificationProvider,
|
|
5
5
|
usePushNotifications,
|
|
6
6
|
} from "@knocklabs/react-native";
|
|
7
|
-
import * as Notifications from "expo-notifications";
|
|
8
7
|
import React, {
|
|
9
8
|
createContext,
|
|
10
9
|
useCallback,
|
|
@@ -14,6 +13,11 @@ import React, {
|
|
|
14
13
|
useState,
|
|
15
14
|
} from "react";
|
|
16
15
|
|
|
16
|
+
import {
|
|
17
|
+
type Notification,
|
|
18
|
+
type NotificationResponse,
|
|
19
|
+
getNotificationsModule,
|
|
20
|
+
} from "./getNotificationsModule";
|
|
17
21
|
import type {
|
|
18
22
|
KnockExpoPushNotificationContextType,
|
|
19
23
|
KnockExpoPushNotificationProviderProps,
|
|
@@ -70,18 +74,18 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
70
74
|
|
|
71
75
|
// Use refs for handlers to avoid re-running effects when handlers change
|
|
72
76
|
const notificationReceivedHandlerRef = useRef<
|
|
73
|
-
(notification:
|
|
77
|
+
(notification: Notification) => void
|
|
74
78
|
>(() => {});
|
|
75
79
|
|
|
76
80
|
const notificationTappedHandlerRef = useRef<
|
|
77
|
-
(response:
|
|
81
|
+
(response: NotificationResponse) => void
|
|
78
82
|
>(() => {});
|
|
79
83
|
|
|
80
84
|
/**
|
|
81
85
|
* Register a handler to be called when a notification is received in the foreground.
|
|
82
86
|
*/
|
|
83
87
|
const onNotificationReceived = useCallback(
|
|
84
|
-
(handler: (notification:
|
|
88
|
+
(handler: (notification: Notification) => void) => {
|
|
85
89
|
notificationReceivedHandlerRef.current = handler;
|
|
86
90
|
},
|
|
87
91
|
[],
|
|
@@ -91,7 +95,7 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
91
95
|
* Register a handler to be called when a notification is tapped.
|
|
92
96
|
*/
|
|
93
97
|
const onNotificationTapped = useCallback(
|
|
94
|
-
(handler: (response:
|
|
98
|
+
(handler: (response: NotificationResponse) => void) => {
|
|
95
99
|
notificationTappedHandlerRef.current = handler;
|
|
96
100
|
},
|
|
97
101
|
[],
|
|
@@ -130,7 +134,7 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
130
134
|
*/
|
|
131
135
|
const updateMessageStatus = useCallback(
|
|
132
136
|
async (
|
|
133
|
-
notification:
|
|
137
|
+
notification: Notification,
|
|
134
138
|
status: MessageEngagementStatus,
|
|
135
139
|
): Promise<Message | void> => {
|
|
136
140
|
const messageId = notification.request.content.data?.[
|
|
@@ -153,11 +157,14 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
153
157
|
|
|
154
158
|
// Set up the notification handler for foreground notifications
|
|
155
159
|
useEffect(() => {
|
|
160
|
+
const NotificationsModule = getNotificationsModule();
|
|
161
|
+
if (!NotificationsModule) return;
|
|
162
|
+
|
|
156
163
|
const handleNotification = customNotificationHandler
|
|
157
164
|
? customNotificationHandler
|
|
158
165
|
: async () => DEFAULT_NOTIFICATION_BEHAVIOR;
|
|
159
166
|
|
|
160
|
-
|
|
167
|
+
NotificationsModule.setNotificationHandler({ handleNotification });
|
|
161
168
|
}, [customNotificationHandler]);
|
|
162
169
|
|
|
163
170
|
// Auto-register for push notifications on mount if enabled
|
|
@@ -196,20 +203,24 @@ const InternalExpoPushNotificationProvider: React.FC<
|
|
|
196
203
|
|
|
197
204
|
// Set up notification listeners for received and tapped notifications
|
|
198
205
|
useEffect(() => {
|
|
199
|
-
const
|
|
200
|
-
|
|
206
|
+
const NotificationsModule = getNotificationsModule();
|
|
207
|
+
if (!NotificationsModule) return;
|
|
208
|
+
|
|
209
|
+
const receivedSubscription =
|
|
210
|
+
NotificationsModule.addNotificationReceivedListener((notification) => {
|
|
201
211
|
knockClient.log("[Knock] Notification received in foreground");
|
|
202
212
|
updateMessageStatus(notification, "interacted");
|
|
203
213
|
notificationReceivedHandlerRef.current(notification);
|
|
204
|
-
}
|
|
205
|
-
);
|
|
214
|
+
});
|
|
206
215
|
|
|
207
216
|
const responseSubscription =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
NotificationsModule.addNotificationResponseReceivedListener(
|
|
218
|
+
(response) => {
|
|
219
|
+
knockClient.log("[Knock] Notification was tapped");
|
|
220
|
+
updateMessageStatus(response.notification, "interacted");
|
|
221
|
+
notificationTappedHandlerRef.current(response);
|
|
222
|
+
},
|
|
223
|
+
);
|
|
213
224
|
|
|
214
225
|
return () => {
|
|
215
226
|
receivedSubscription.remove();
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import Constants, { ExecutionEnvironment } from "expo-constants";
|
|
2
|
+
import type * as Notifications from "expo-notifications";
|
|
3
|
+
import { Platform } from "react-native";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The type of the expo-notifications module when successfully loaded.
|
|
7
|
+
*/
|
|
8
|
+
export type NotificationsModule = typeof Notifications;
|
|
9
|
+
|
|
10
|
+
// Type aliases derived from the expo-notifications namespace so that consumers
|
|
11
|
+
// access all expo-notifications types through this module rather than importing
|
|
12
|
+
// from the package directly (which can trigger runtime side-effects).
|
|
13
|
+
export type Notification = Notifications.Notification;
|
|
14
|
+
export type NotificationResponse = Notifications.NotificationResponse;
|
|
15
|
+
export type NotificationBehavior = Notifications.NotificationBehavior;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Lazily load the expo-notifications module.
|
|
19
|
+
*
|
|
20
|
+
* In Expo SDK 55+, `import * as Notifications from "expo-notifications"` triggers
|
|
21
|
+
* a top-level side-effect (DevicePushTokenAutoRegistration.fx.ts) that calls
|
|
22
|
+
* `addPushTokenListener()`, which throws on Android Expo Go where push notification
|
|
23
|
+
* functionality has been removed (since SDK 53).
|
|
24
|
+
*
|
|
25
|
+
* We detect Android Expo Go before attempting the require() and skip it entirely,
|
|
26
|
+
* since the throw from expo-notifications bypasses JavaScript try/catch via
|
|
27
|
+
* React Native's global error handler.
|
|
28
|
+
*
|
|
29
|
+
* On all other environments (iOS Expo Go, development builds, production),
|
|
30
|
+
* expo-notifications loads normally.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// Cache the module after the first load to avoid repeated require() calls and
|
|
34
|
+
// environment detection checks on every access. The three states are:
|
|
35
|
+
// undefined = not yet loaded (initial)
|
|
36
|
+
// null = unavailable (Android Expo Go or load failure)
|
|
37
|
+
// module = successfully loaded
|
|
38
|
+
let cachedModule: NotificationsModule | null | undefined = undefined;
|
|
39
|
+
|
|
40
|
+
function isAndroidExpoGo(): boolean {
|
|
41
|
+
return (
|
|
42
|
+
Platform.OS === "android" &&
|
|
43
|
+
Constants.executionEnvironment === ExecutionEnvironment.StoreClient
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Abstracted for testability — Vitest cannot intercept require() calls
|
|
48
|
+
// inside dynamically imported modules after vi.resetModules().
|
|
49
|
+
/* v8 ignore next 3 -- default require is replaced in tests via _resetForTesting */
|
|
50
|
+
let requireNotifications: () => NotificationsModule = () =>
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
52
|
+
require("expo-notifications") as NotificationsModule;
|
|
53
|
+
|
|
54
|
+
export function getNotificationsModule(): NotificationsModule | null {
|
|
55
|
+
if (cachedModule !== undefined) {
|
|
56
|
+
return cachedModule;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isAndroidExpoGo()) {
|
|
60
|
+
console.warn(
|
|
61
|
+
"[Knock] Push notifications (remote notifications) are not available in Expo Go " +
|
|
62
|
+
"on Android. This is an Expo platform limitation — push notification support was " +
|
|
63
|
+
"removed from Expo Go on Android in SDK 53. Push features (token registration, " +
|
|
64
|
+
"notification listeners) will be disabled, but all other Knock features will " +
|
|
65
|
+
"continue to work.\n\n" +
|
|
66
|
+
"To use push notifications on Android, use a development build instead of Expo Go: " +
|
|
67
|
+
"https://docs.expo.dev/develop/development-builds/introduction/",
|
|
68
|
+
);
|
|
69
|
+
cachedModule = null;
|
|
70
|
+
return cachedModule;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
cachedModule = requireNotifications();
|
|
75
|
+
} catch {
|
|
76
|
+
console.warn(
|
|
77
|
+
"[Knock] expo-notifications could not be loaded. " +
|
|
78
|
+
"Push notification features will be disabled.",
|
|
79
|
+
);
|
|
80
|
+
cachedModule = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return cachedModule;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @internal Test-only — reset the cached module and optionally override
|
|
88
|
+
* the require function used to load expo-notifications.
|
|
89
|
+
*/
|
|
90
|
+
export function _resetForTesting(
|
|
91
|
+
overrideRequire?: () => NotificationsModule,
|
|
92
|
+
): void {
|
|
93
|
+
cachedModule = undefined;
|
|
94
|
+
if (overrideRequire) {
|
|
95
|
+
requireNotifications = overrideRequire;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { KnockPushNotificationContextType } from "@knocklabs/react-native";
|
|
2
|
-
import type * as Notifications from "expo-notifications";
|
|
3
2
|
import type React from "react";
|
|
4
3
|
|
|
4
|
+
import type {
|
|
5
|
+
Notification,
|
|
6
|
+
NotificationBehavior,
|
|
7
|
+
NotificationResponse,
|
|
8
|
+
} from "./getNotificationsModule";
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* Context type for the Expo push notification provider.
|
|
7
12
|
* Extends the base push notification context with Expo-specific functionality.
|
|
@@ -16,12 +21,12 @@ export interface KnockExpoPushNotificationContextType
|
|
|
16
21
|
|
|
17
22
|
/** Register a handler for when a notification is received in the foreground */
|
|
18
23
|
onNotificationReceived: (
|
|
19
|
-
handler: (notification:
|
|
24
|
+
handler: (notification: Notification) => void,
|
|
20
25
|
) => void;
|
|
21
26
|
|
|
22
27
|
/** Register a handler for when a notification is tapped */
|
|
23
28
|
onNotificationTapped: (
|
|
24
|
-
handler: (response:
|
|
29
|
+
handler: (response: NotificationResponse) => void,
|
|
25
30
|
) => void;
|
|
26
31
|
}
|
|
27
32
|
|
|
@@ -37,8 +42,8 @@ export interface KnockExpoPushNotificationProviderProps {
|
|
|
37
42
|
* If not provided, notifications will show alerts, play sounds, and set badges.
|
|
38
43
|
*/
|
|
39
44
|
customNotificationHandler?: (
|
|
40
|
-
notification:
|
|
41
|
-
) => Promise<
|
|
45
|
+
notification: Notification,
|
|
46
|
+
) => Promise<NotificationBehavior>;
|
|
42
47
|
|
|
43
48
|
/**
|
|
44
49
|
* Custom function to set up the Android notification channel.
|
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
import Constants from "expo-constants";
|
|
2
2
|
import * as Device from "expo-device";
|
|
3
|
-
import * as Notifications from "expo-notifications";
|
|
4
3
|
import { Platform } from "react-native";
|
|
5
4
|
|
|
5
|
+
import {
|
|
6
|
+
type NotificationBehavior,
|
|
7
|
+
getNotificationsModule,
|
|
8
|
+
} from "./getNotificationsModule";
|
|
9
|
+
|
|
6
10
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
11
|
type ExpoConstants = typeof Constants & Record<string, any>;
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Permission status values returned by expo-notifications.
|
|
15
|
+
* "unavailable" is returned when the notifications module could not be loaded
|
|
16
|
+
* (e.g. Android Expo Go where push support was removed in SDK 53).
|
|
17
|
+
*/
|
|
18
|
+
export type PushPermissionStatus =
|
|
19
|
+
| "granted"
|
|
20
|
+
| "denied"
|
|
21
|
+
| "undetermined"
|
|
22
|
+
| "unavailable";
|
|
23
|
+
|
|
9
24
|
/**
|
|
10
25
|
* Default notification behavior when a notification is received.
|
|
11
26
|
*/
|
|
12
|
-
export const DEFAULT_NOTIFICATION_BEHAVIOR:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
27
|
+
export const DEFAULT_NOTIFICATION_BEHAVIOR: NotificationBehavior = {
|
|
28
|
+
shouldShowAlert: true,
|
|
29
|
+
shouldPlaySound: true,
|
|
30
|
+
shouldSetBadge: true,
|
|
31
|
+
shouldShowBanner: true,
|
|
32
|
+
shouldShowList: true,
|
|
33
|
+
};
|
|
20
34
|
|
|
21
35
|
/**
|
|
22
36
|
* Get the Expo project ID from various possible sources.
|
|
@@ -50,17 +64,23 @@ export function getProjectId(): string | null {
|
|
|
50
64
|
|
|
51
65
|
/**
|
|
52
66
|
* Request push notification permissions if not already granted.
|
|
53
|
-
* @returns The permission status
|
|
67
|
+
* @returns The permission status
|
|
54
68
|
*/
|
|
55
|
-
export async function requestPushPermission(): Promise<
|
|
56
|
-
const
|
|
69
|
+
export async function requestPushPermission(): Promise<PushPermissionStatus> {
|
|
70
|
+
const NotificationsModule = getNotificationsModule();
|
|
71
|
+
if (!NotificationsModule) {
|
|
72
|
+
return "unavailable";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const { status: existingStatus } =
|
|
76
|
+
await NotificationsModule.getPermissionsAsync();
|
|
57
77
|
|
|
58
78
|
if (existingStatus === "granted") {
|
|
59
79
|
return existingStatus;
|
|
60
80
|
}
|
|
61
81
|
|
|
62
|
-
const { status } = await
|
|
63
|
-
return status;
|
|
82
|
+
const { status } = await NotificationsModule.requestPermissionsAsync();
|
|
83
|
+
return status as PushPermissionStatus;
|
|
64
84
|
}
|
|
65
85
|
|
|
66
86
|
/**
|
|
@@ -79,7 +99,12 @@ export async function getExpoPushToken(): Promise<string | null> {
|
|
|
79
99
|
return null;
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
const
|
|
102
|
+
const NotificationsModule = getNotificationsModule();
|
|
103
|
+
if (!NotificationsModule) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const token = await NotificationsModule.getExpoPushTokenAsync({ projectId });
|
|
83
108
|
return token?.data ?? null;
|
|
84
109
|
}
|
|
85
110
|
|
|
@@ -87,9 +112,14 @@ export async function getExpoPushToken(): Promise<string | null> {
|
|
|
87
112
|
* Set up the default Android notification channel.
|
|
88
113
|
*/
|
|
89
114
|
export async function setupDefaultAndroidChannel(): Promise<void> {
|
|
90
|
-
|
|
115
|
+
const NotificationsModule = getNotificationsModule();
|
|
116
|
+
if (!NotificationsModule) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await NotificationsModule.setNotificationChannelAsync("default", {
|
|
91
121
|
name: "Default",
|
|
92
|
-
importance:
|
|
122
|
+
importance: NotificationsModule.AndroidImportance.MAX,
|
|
93
123
|
vibrationPattern: [0, 250, 250, 250],
|
|
94
124
|
lightColor: "#FF231F7C",
|
|
95
125
|
});
|
|
@@ -137,6 +167,12 @@ export async function registerForPushNotifications(
|
|
|
137
167
|
|
|
138
168
|
const permissionStatus = await requestPushPermission();
|
|
139
169
|
|
|
170
|
+
if (permissionStatus === "unavailable") {
|
|
171
|
+
// Module couldn't be loaded (e.g. Android Expo Go) — the warning is
|
|
172
|
+
// already emitted by getNotificationsModule, so just bail silently.
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
140
176
|
if (permissionStatus !== "granted") {
|
|
141
177
|
console.warn(
|
|
142
178
|
`[Knock] Push notification permission not granted. Status: ${permissionStatus}. ` +
|