diff --git a/lib/tunnelbroker/tunnelbroker-context.js b/lib/tunnelbroker/tunnelbroker-context.js --- a/lib/tunnelbroker/tunnelbroker-context.js +++ b/lib/tunnelbroker/tunnelbroker-context.js @@ -15,6 +15,10 @@ tunnelbrokerMessageTypes, tunnelbrokerMessageValidator, } from '../types/tunnelbroker/messages.js'; +import { + type PeerToPeerMessage, + peerToPeerMessageValidator, +} from '../types/tunnelbroker/peer-to-peer-message-types.js'; import type { ConnectionInitializationMessage } from '../types/tunnelbroker/session-types.js'; export type ClientMessageToDevice = { @@ -45,10 +49,11 @@ type Props = { +children: React.Node, +initMessage: ?ConnectionInitializationMessage, + +peerToPeerMessageHandler?: (message: PeerToPeerMessage) => mixed, }; function TunnelbrokerProvider(props: Props): React.Node { - const { children, initMessage } = props; + const { children, initMessage, peerToPeerMessageHandler } = props; const [connected, setConnected] = React.useState(false); const listeners = React.useRef>(new Set()); const socket = React.useRef(null); @@ -137,6 +142,28 @@ messageIDs: [message.messageID], }; socket.current?.send(JSON.stringify(confirmation)); + + if (!peerToPeerMessageHandler) { + return; + } + + let rawPeerToPeerMessage; + try { + rawPeerToPeerMessage = JSON.parse(message.payload); + } catch (e) { + console.log( + 'error while parsing Tunnelbroker peer-to-peer message:', + e.message, + ); + return; + } + + if (!peerToPeerMessageValidator.is(rawPeerToPeerMessage)) { + console.log('invalid Tunnelbroker PeerToPeerMessage'); + return; + } + const peerToPeerMessage: PeerToPeerMessage = rawPeerToPeerMessage; + peerToPeerMessageHandler(peerToPeerMessage); } else if ( message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST_STATUS @@ -163,7 +190,13 @@ }; socket.current = tunnelbrokerSocket; - }, [connected, initMessage, resetHeartbeatTimeout, stopHeartbeatTimeout]); + }, [ + connected, + initMessage, + resetHeartbeatTimeout, + stopHeartbeatTimeout, + peerToPeerMessageHandler, + ]); const sendMessage: (message: ClientMessageToDevice) => Promise = React.useCallback( diff --git a/lib/types/tunnelbroker/peer-to-peer-message-types.js b/lib/types/tunnelbroker/peer-to-peer-message-types.js new file mode 100644 --- /dev/null +++ b/lib/types/tunnelbroker/peer-to-peer-message-types.js @@ -0,0 +1,68 @@ +// @flow + +import type { TInterface, TUnion } from 'tcomb'; +import t from 'tcomb'; + +import { tShape, tString } from '../../utils/validation-utils.js'; + +export type SenderInfo = { + +userID: string, + +deviceID: string, +}; +const senderInfoValidator: TInterface = tShape({ + userID: t.String, + deviceID: t.String, +}); + +export const peerToPeerMessageTypes = Object.freeze({ + OUTBOUND_SESSION_CREATION: 'OutboundSessionCreation', + ENCRYPTED_MESSAGE: 'EncryptedMessage', + REFRESH_KEY_REQUEST: 'RefreshKeyRequest', +}); + +export type OutboundSessionCreation = { + +type: 'OutboundSessionCreation', + +senderInfo: SenderInfo, + +encryptedContent: string, +}; +export const outboundSessionCreationValidator: TInterface = + tShape({ + type: tString(peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION), + senderInfo: senderInfoValidator, + encryptedContent: t.String, + }); + +export type EncryptedMessage = { + +type: 'EncryptedMessage', + +senderInfo: SenderInfo, + +encryptedContent: string, +}; +export const encryptedMessageValidator: TInterface = + tShape({ + type: tString(peerToPeerMessageTypes.ENCRYPTED_MESSAGE), + senderInfo: senderInfoValidator, + encryptedContent: t.String, + }); + +export type RefreshKeyRequest = { + +type: 'RefreshKeyRequest', + +deviceID: string, + +numberOfKeys: number, +}; +export const refreshKeysRequestValidator: TInterface = + tShape({ + type: tString(peerToPeerMessageTypes.REFRESH_KEY_REQUEST), + deviceID: t.String, + numberOfKeys: t.Number, + }); + +export type PeerToPeerMessage = + | OutboundSessionCreation + | EncryptedMessage + | RefreshKeyRequest; + +export const peerToPeerMessageValidator: TUnion = t.union([ + outboundSessionCreationValidator, + encryptedMessageValidator, + refreshKeysRequestValidator, +]); diff --git a/native/handlers/peer-to-peer-message-handler.js b/native/handlers/peer-to-peer-message-handler.js new file mode 100644 --- /dev/null +++ b/native/handlers/peer-to-peer-message-handler.js @@ -0,0 +1,18 @@ +// @flow + +import { + type PeerToPeerMessage, + peerToPeerMessageTypes, +} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; + +async function peerToPeerMessageHandler( + message: PeerToPeerMessage, +): Promise { + if (message.type === peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION) { + console.log('Received session creation request'); + } else if (message.type === peerToPeerMessageTypes.ENCRYPTED_MESSAGE) { + console.log('Received encrypted message'); + } +} + +export { peerToPeerMessageHandler }; diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -44,6 +44,7 @@ import ConnectedStatusBar from './connected-status-bar.react.js'; import { SQLiteDataHandler } from './data/sqlite-data-handler.js'; import ErrorBoundary from './error-boundary.react.js'; +import { peerToPeerMessageHandler } from './handlers/peer-to-peer-message-handler.js'; import InputStateContainer from './input/input-state-container.react.js'; import LifecycleHandler from './lifecycle/lifecycle-handler.react.js'; import MarkdownContextProvider from './markdown/markdown-context-provider.react.js'; @@ -295,7 +296,10 @@ return ( - +