diff --git a/lib/types/tunnelbroker/peer-to-peer-message-types.js b/lib/types/tunnelbroker/peer-to-peer-message-types.js --- a/lib/types/tunnelbroker/peer-to-peer-message-types.js +++ b/lib/types/tunnelbroker/peer-to-peer-message-types.js @@ -4,6 +4,10 @@ import t from 'tcomb'; import { tShape, tString } from '../../utils/validation-utils.js'; +import { + signedDeviceListValidator, + type SignedDeviceList, +} from '../identity-service-types.js'; export type SenderInfo = { +userID: string, @@ -19,6 +23,7 @@ ENCRYPTED_MESSAGE: 'EncryptedMessage', REFRESH_KEY_REQUEST: 'RefreshKeyRequest', QR_CODE_AUTH_MESSAGE: 'QRCodeAuthMessage', + DEVICE_LIST_UPDATED: 'DeviceListUpdated', }); export type OutboundSessionCreation = { @@ -67,15 +72,29 @@ encryptedContent: t.String, }); +export type DeviceListUpdated = { + +type: 'DeviceListUpdated', + +userID: string, + +signedDeviceList: SignedDeviceList, +}; +export const deviceListUpdatedValidator: TInterface = + tShape({ + type: tString(peerToPeerMessageTypes.DEVICE_LIST_UPDATED), + userID: t.String, + signedDeviceList: signedDeviceListValidator, + }); + export type PeerToPeerMessage = | OutboundSessionCreation | EncryptedMessage | RefreshKeyRequest - | QRCodeAuthMessage; + | QRCodeAuthMessage + | DeviceListUpdated; export const peerToPeerMessageValidator: TUnion = t.union([ outboundSessionCreationValidator, encryptedMessageValidator, refreshKeysRequestValidator, qrCodeAuthMessageValidator, + deviceListUpdatedValidator, ]); diff --git a/native/profile/secondary-device-qr-code-scanner.react.js b/native/profile/secondary-device-qr-code-scanner.react.js --- a/native/profile/secondary-device-qr-code-scanner.react.js +++ b/native/profile/secondary-device-qr-code-scanner.react.js @@ -51,6 +51,36 @@ const aes256Key = React.useRef(null); const secondaryDeviceID = React.useRef(null); + const broadcastDeviceListUpdate = React.useCallback(async () => { + invariant(identityContext, 'identity context not set'); + const { getAuthMetadata, identityClient } = identityContext; + const { userID } = await getAuthMetadata(); + if (!userID) { + throw new Error('missing auth metadata'); + } + + const deviceLists = + await identityClient.getDeviceListHistoryForUser(userID); + invariant(deviceLists.length > 0, 'received empty device list history'); + + const lastSignedDeviceList = deviceLists[deviceLists.length - 1]; + const deviceList: RawDeviceList = JSON.parse( + lastSignedDeviceList.rawDeviceList, + ); + + const promises = deviceList.devices.map(recipient => + tunnelbrokerContext.sendMessage({ + deviceID: recipient, + payload: JSON.stringify({ + type: peerToPeerMessageTypes.DEVICE_LIST_UPDATED, + userID, + signedDeviceList: lastSignedDeviceList, + }), + }), + ); + await Promise.all(promises); + }, [identityContext, tunnelbrokerContext]); + const addDeviceToList = React.useCallback( async (newDeviceID: string) => { const { getDeviceListHistoryForUser, updateDeviceList } = @@ -125,11 +155,25 @@ ) { return; } + + void broadcastDeviceListUpdate(); + + const backupKeyMessage = createQRAuthTunnelbrokerMessage(encryptionKey, { + type: qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE, + backupID: 'stub', + backupDataKey: 'stub', + backupLogDataKey: 'stub', + }); + await tunnelbrokerContext.sendMessage({ + deviceID: targetDeviceID, + payload: JSON.stringify(backupKeyMessage), + }); + Alert.alert('Device added', 'Device registered successfully', [ { text: 'OK' }, ]); }, - [], + [tunnelbrokerContext, broadcastDeviceListUpdate], ); React.useEffect(() => { diff --git a/native/qr-code/qr-code-screen.react.js b/native/qr-code/qr-code-screen.react.js --- a/native/qr-code/qr-code-screen.react.js +++ b/native/qr-code/qr-code-screen.react.js @@ -108,6 +108,15 @@ qrData.aesKey, innerMessage, ); + + if ( + qrCodeAuthMessage?.type === + qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE + ) { + console.log('Received backup data key:', qrCodeAuthMessage); + return; + } + if ( !qrCodeAuthMessage || qrCodeAuthMessage.type !==