diff --git a/lib/hooks/peer-list-hooks.js b/lib/hooks/peer-list-hooks.js --- a/lib/hooks/peer-list-hooks.js +++ b/lib/hooks/peer-list-hooks.js @@ -35,6 +35,12 @@ import { useDispatch, useSelector } from '../utils/redux-utils.js'; import sleep from '../utils/sleep.js'; +export type PrimaryDeviceChange = { + +userID: string, + +prevPrimaryDeviceID: string, + +newPrimaryDeviceID: string, +}; + function useGetDeviceListsForUsers(): ( userIDs: $ReadOnlyArray, ) => Promise<{ @@ -67,6 +73,9 @@ function useGetAndUpdateDeviceListsForUsers(): ( userIDs: $ReadOnlyArray, broadcastUpdates: ?boolean, + handlePrimaryDeviceChanges?: ( + changes: $ReadOnlyArray, + ) => Promise, ) => Promise { const getDeviceListsForUsers = useGetDeviceListsForUsers(); const dispatch = useDispatch(); @@ -76,7 +85,11 @@ const peerPrimaryDevices = useSelector(getPeersPrimaryDeviceIDs); return React.useCallback( - async (userIDs: $ReadOnlyArray, broadcastUpdates: ?boolean) => { + async ( + userIDs: $ReadOnlyArray, + broadcastUpdates: ?boolean, + handlePrimaryDeviceChanges, + ) => { const result = await Promise.race([ getDeviceListsForUsers(userIDs), throwOnTimeout(userIDs), @@ -90,22 +103,28 @@ return {}; } - const primaryDeviceChangedUserIDs = userIDs.filter(userID => { - const prevPrimaryDeviceID = peerPrimaryDevices[userID]; - const newPrimaryDeviceID = result.deviceLists[userID]?.devices[0]; - return ( - !!prevPrimaryDeviceID && - !!newPrimaryDeviceID && - newPrimaryDeviceID !== prevPrimaryDeviceID - ); - }); + const primaryDeviceChanges = userIDs + .map(userID => { + const prevPrimaryDeviceID = peerPrimaryDevices[userID]; + const newPrimaryDeviceID = result.deviceLists[userID]?.devices[0]; + if ( + !prevPrimaryDeviceID || + !newPrimaryDeviceID || + newPrimaryDeviceID === prevPrimaryDeviceID + ) { + return null; + } - if (primaryDeviceChangedUserIDs.length > 0) { - console.log( - 'Primary device ID changed for users', - primaryDeviceChangedUserIDs, - ); - // TODO: implement + return { userID, prevPrimaryDeviceID, newPrimaryDeviceID }; + }) + .filter(Boolean); + + if (primaryDeviceChanges.length > 0) { + try { + await handlePrimaryDeviceChanges?.(primaryDeviceChanges); + } catch (err) { + console.warn('Failed to handle primary device changes:', err); + } } dispatch({ diff --git a/lib/tunnelbroker/use-peer-to-peer-message-handler.js b/lib/tunnelbroker/use-peer-to-peer-message-handler.js --- a/lib/tunnelbroker/use-peer-to-peer-message-handler.js +++ b/lib/tunnelbroker/use-peer-to-peer-message-handler.js @@ -6,7 +6,10 @@ import * as React from 'react'; import uuid from 'uuid'; -import { useResendPeerToPeerMessages } from './use-resend-peer-to-peer-messages.js'; +import { + useResendPeerToPeerMessages, + useResendP2PMessagesToNewPrimaryDevices, +} from './use-resend-peer-to-peer-messages.js'; import { removePeerUsersActionType } from '../actions/aux-user-actions.js'; import { invalidateTunnelbrokerDeviceTokenActionType } from '../actions/tunnelbroker-actions.js'; import { logOutActionTypes, useBaseLogOut } from '../actions/user-actions.js'; @@ -247,6 +250,7 @@ const handleOlmMessageToDevice = useHandleOlmMessageToDevice(); const resendPeerToPeerMessages = useResendPeerToPeerMessages(); + const handlePrimaryDeviceChanges = useResendP2PMessagesToNewPrimaryDevices(); const { createOlmSessionsWithUser } = usePeerOlmSessionsCreatorContext(); const { addLog } = useDebugLogs(); @@ -517,7 +521,11 @@ `Received valid device list update for user ${message.userID}`, ); } - await getAndUpdateDeviceListsForUsers([message.userID]); + await getAndUpdateDeviceListsForUsers( + [message.userID], + false, + handlePrimaryDeviceChanges, + ); if (result.valid && message?.signedDeviceList?.rawDeviceList) { const receivedRawList = JSON.parse( @@ -549,7 +557,11 @@ await Promise.all([ broadcastDeviceListUpdates(foreignPeerDevices), - getAndUpdateDeviceListsForUsers([userID]), + getAndUpdateDeviceListsForUsers( + [userID], + false, + handlePrimaryDeviceChanges, + ), ]); } catch (e) { console.log( @@ -594,6 +606,7 @@ olmAPI, olmDebugLog, resendPeerToPeerMessages, + handlePrimaryDeviceChanges, sqliteAPI, ], ); diff --git a/lib/tunnelbroker/use-resend-peer-to-peer-messages.js b/lib/tunnelbroker/use-resend-peer-to-peer-messages.js --- a/lib/tunnelbroker/use-resend-peer-to-peer-messages.js +++ b/lib/tunnelbroker/use-resend-peer-to-peer-messages.js @@ -3,20 +3,43 @@ import * as React from 'react'; import { usePeerToPeerCommunication } from './peer-to-peer-context.js'; +import type { PrimaryDeviceChange } from '../hooks/peer-list-hooks.js'; import { getConfig } from '../utils/config.js'; -function useResendPeerToPeerMessages(): (deviceID: string) => Promise { +function useResendPeerToPeerMessages(): ( + deviceID: string, + newDeviceID?: ?string, +) => Promise { const { sqliteAPI } = getConfig(); const { processOutboundMessages } = usePeerToPeerCommunication(); return React.useCallback( - async (deviceID: string) => { - const messageIDs = - await sqliteAPI.resetOutboundP2PMessagesForDevice(deviceID); + async (deviceID: string, newDeviceID?: ?string) => { + const messageIDs = await sqliteAPI.resetOutboundP2PMessagesForDevice( + deviceID, + newDeviceID, + ); processOutboundMessages(messageIDs); }, [processOutboundMessages, sqliteAPI], ); } -export { useResendPeerToPeerMessages }; +function useResendP2PMessagesToNewPrimaryDevices(): ( + primaryDeviceChanges: $ReadOnlyArray, +) => Promise { + const resendPeerToPeerMessages = useResendPeerToPeerMessages(); + + return React.useCallback( + async (primaryDeviceChanges: $ReadOnlyArray) => { + const promises = primaryDeviceChanges.map( + ({ prevPrimaryDeviceID, newPrimaryDeviceID }) => + resendPeerToPeerMessages(prevPrimaryDeviceID, newPrimaryDeviceID), + ); + await Promise.all(promises); + }, + [resendPeerToPeerMessages], + ); +} + +export { useResendPeerToPeerMessages, useResendP2PMessagesToNewPrimaryDevices };