From e90e5cce7f07cc1e42fad3c484f58da810a93e79 Mon Sep 17 00:00:00 2001 From: Anthony Mittaz Date: Mon, 8 Dec 2025 10:14:31 +1100 Subject: [PATCH 1/4] Add possibility to provide a locale --- .../src/modules/push/KnockPushNotificationProvider.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/react-native/src/modules/push/KnockPushNotificationProvider.tsx b/packages/react-native/src/modules/push/KnockPushNotificationProvider.tsx index 625fd9ff..787119c2 100644 --- a/packages/react-native/src/modules/push/KnockPushNotificationProvider.tsx +++ b/packages/react-native/src/modules/push/KnockPushNotificationProvider.tsx @@ -12,6 +12,7 @@ export interface KnockPushNotificationContextType { registerPushTokenToChannel( token: string, channelId: string, + locale?: string, ): Promise; unregisterPushTokenFromChannel( token: string, @@ -44,10 +45,14 @@ export const KnockPushNotificationProvider: React.FC< // Acts like an upsert. Inserts or updates const registerPushTokenToChannel = useCallback( - async (token: string, channelId: string): Promise => { + async ( + token: string, + channelId: string, + locale: string = Intl.DateTimeFormat().resolvedOptions().locale, + ): Promise => { const newDevice: Device = { token, - locale: Intl.DateTimeFormat().resolvedOptions().locale, + locale, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, }; From 60499efe86ac0f4bc0940b7b564d277b95d12b00 Mon Sep 17 00:00:00 2001 From: Anthony Mittaz Date: Mon, 8 Dec 2025 10:17:08 +1100 Subject: [PATCH 2/4] Make sure to also fetch last notification response If not tap might be missed --- .../modules/push/KnockExpoPushNotificationProvider.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx b/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx index d1c5ff58..2d838b05 100644 --- a/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx +++ b/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx @@ -227,6 +227,16 @@ const InternalKnockExpoPushNotificationProvider: React.FC< notificationReceivedHandler(notification); }); + const response = Notifications.getLastNotificationResponse(); + if (response?.notification) { + knockClient.log("[Knock] Expo Push Notification was interacted with"); + updateKnockMessageStatusFromNotification( + response.notification, + "interacted", + ); + notificationTappedHandler(response); + } + const notificationResponseSubscription = Notifications.addNotificationResponseReceivedListener((response) => { knockClient.log("[Knock] Expo Push Notification was interacted with"); From f2a14de3769e7b4ca51d92f673ee40b02af733e2 Mon Sep 17 00:00:00 2001 From: Anthony Mittaz Date: Mon, 8 Dec 2025 10:32:11 +1100 Subject: [PATCH 3/4] Restore expo token on boot if has permissions --- packages/expo/package.json | 3 ++- .../KnockExpoPushNotificationProvider.tsx | 27 +++++++++++++++++++ yarn.lock | 11 ++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/expo/package.json b/packages/expo/package.json index b5348bcf..6a6a71d1 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -1,6 +1,6 @@ { "name": "@knocklabs/expo", - "version": "0.4.0", + "version": "0.5.0", "author": "@knocklabs", "license": "MIT", "main": "dist/cjs/index.js", @@ -73,6 +73,7 @@ "expo": "~53.0.22", "expo-constants": "~17.1.7", "expo-device": "^7.1.4", + "expo-network": "^8.0.0", "expo-notifications": "^0.31.4", "jsdom": "^27.1.0", "react": "^19.0.0", diff --git a/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx b/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx index 2d838b05..6cf02544 100644 --- a/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx +++ b/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx @@ -7,6 +7,7 @@ import { } from "@knocklabs/react-native"; import Constants from "expo-constants"; import * as Device from "expo-device"; +import * as Network from "expo-network"; import * as Notifications from "expo-notifications"; import React, { createContext, @@ -127,6 +128,7 @@ const InternalKnockExpoPushNotificationProvider: React.FC< usePushNotifications(); const [expoPushToken, setExpoPushToken] = useState(null); const knockClient = useKnockClient(); + const { isInternetReachable } = Network.useNetworkState(); const [notificationReceivedHandler, setNotificationReceivedHandler] = useState<(notification: Notifications.Notification) => void>( @@ -188,6 +190,31 @@ const InternalKnockExpoPushNotificationProvider: React.FC< [knockClient], ); + useEffect(() => { + if (expoPushToken) { + return; + } + + const fetchExpoTokenIfNeeded = async () => { + const { status: existingStatus } = + await Notifications.getPermissionsAsync(); + if ( + existingStatus === "granted" && + Device.isDevice && + isInternetReachable + ) { + try { + const token = await getExpoPushToken(); + setExpoPushToken(token ? token.data : null); + } catch (error) { + console.error(`[Knock] Error getting expo push token:`, error); + } + } + }; + + fetchExpoTokenIfNeeded(); + }, [isInternetReachable, expoPushToken]); + useEffect(() => { Notifications.setNotificationHandler({ handleNotification: diff --git a/yarn.lock b/yarn.lock index 093f85b0..ac0041d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4071,6 +4071,7 @@ __metadata: expo: "npm:~53.0.22" expo-constants: "npm:~17.1.7" expo-device: "npm:^7.1.4" + expo-network: "npm:^8.0.0" expo-notifications: "npm:^0.31.4" jsdom: "npm:^27.1.0" react: "npm:^19.0.0" @@ -12061,6 +12062,16 @@ __metadata: languageName: node linkType: hard +"expo-network@npm:^8.0.0": + version: 8.0.8 + resolution: "expo-network@npm:8.0.8" + peerDependencies: + expo: "*" + react: "*" + checksum: 10c0/e12e4d136dae02e22ceaf0344ed7ee053e5aed126a71e69718c91bf7161c8bc97f745eec6e794f66e60cfba44d341b4462ce2987c958671a80fc5bea85dd124d + languageName: node + linkType: hard + "expo-notifications@npm:^0.31.4": version: 0.31.4 resolution: "expo-notifications@npm:0.31.4" From 4ed8dea7c0f399c498988b4bdbf504bdb505dd04 Mon Sep 17 00:00:00 2001 From: Anthony Mittaz Date: Mon, 8 Dec 2025 11:41:06 +1100 Subject: [PATCH 4/4] Cleanup effects --- .../KnockExpoPushNotificationProvider.tsx | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx b/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx index 6cf02544..0532e78b 100644 --- a/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx +++ b/packages/expo/src/modules/push/KnockExpoPushNotificationProvider.tsx @@ -221,6 +221,25 @@ const InternalKnockExpoPushNotificationProvider: React.FC< customNotificationHandler ?? defaultNotificationHandler, }); + const response = Notifications.getLastNotificationResponse(); + if (response?.notification) { + knockClient.log( + "[Knock] Expo Push Notification was interacted with (initial)", + ); + updateKnockMessageStatusFromNotification( + response.notification, + "interacted", + ); + notificationTappedHandler(response); + } + }, [ + customNotificationHandler, + knockClient, + notificationTappedHandler, + updateKnockMessageStatusFromNotification, + ]); + + useEffect(() => { if (autoRegister) { registerForPushNotifications() .then(() => { @@ -254,16 +273,6 @@ const InternalKnockExpoPushNotificationProvider: React.FC< notificationReceivedHandler(notification); }); - const response = Notifications.getLastNotificationResponse(); - if (response?.notification) { - knockClient.log("[Knock] Expo Push Notification was interacted with"); - updateKnockMessageStatusFromNotification( - response.notification, - "interacted", - ); - notificationTappedHandler(response); - } - const notificationResponseSubscription = Notifications.addNotificationResponseReceivedListener((response) => { knockClient.log("[Knock] Expo Push Notification was interacted with"); @@ -278,18 +287,16 @@ const InternalKnockExpoPushNotificationProvider: React.FC< notificationReceivedSubscription.remove(); notificationResponseSubscription.remove(); }; - - // TODO: Remove when possible and ensure dependency array is correct - // eslint-disable-next-line react-hooks/exhaustive-deps }, [ registerForPushNotifications, notificationReceivedHandler, notificationTappedHandler, - customNotificationHandler, autoRegister, expoPushToken, knockExpoChannelId, knockClient, + registerPushTokenToChannel, + updateKnockMessageStatusFromNotification, ]); return (