diff --git a/packages/renderer/assets/join-call.wav b/packages/renderer/assets/join-call.wav new file mode 100644 index 0000000..f306de2 Binary files /dev/null and b/packages/renderer/assets/join-call.wav differ diff --git a/packages/renderer/assets/leave-call.wav b/packages/renderer/assets/leave-call.wav new file mode 100644 index 0000000..98e3b81 Binary files /dev/null and b/packages/renderer/assets/leave-call.wav differ diff --git a/packages/renderer/index.html b/packages/renderer/index.html index d1dbb1a..c40973c 100644 --- a/packages/renderer/index.html +++ b/packages/renderer/index.html @@ -6,7 +6,7 @@ diff --git a/packages/renderer/src/components/ServerConnection.tsx b/packages/renderer/src/components/ServerConnection.tsx index fec52cf..14a18da 100644 --- a/packages/renderer/src/components/ServerConnection.tsx +++ b/packages/renderer/src/components/ServerConnection.tsx @@ -1,5 +1,6 @@ import { createContext, PropsWithChildren, ReactNode, useEffect, useMemo } from "react"; import ClientsListState from "../contexts/EphemeralState/ClientsListState"; +import PeerState from "../contexts/EphemeralState/PeerState"; import { connectApi } from "../lib/api"; interface ServerConnectionProps { @@ -33,7 +34,9 @@ export default function ServerConnection(props: ServerConnectionProps) { return - {props.children} + + {props.children} + } \ No newline at end of file diff --git a/packages/renderer/src/components/Voice.tsx b/packages/renderer/src/components/Voice.tsx index b23c30e..89100d5 100644 --- a/packages/renderer/src/components/Voice.tsx +++ b/packages/renderer/src/components/Voice.tsx @@ -7,17 +7,21 @@ import { useApi } from "../lib/useApi"; export default function Voice(props: any) { const { uid } = props; - const { connected, peerId } = useContext(PeerContext); + const { connected, peerId, join } = useContext(PeerContext); const { channel } = useChannel(); - const { send } = useApi({ - - }); + const { send } = useApi({}); const joinCall = useCallback(() => { - if(peerId === null || connected === false) return; + if(peerId === null || connected === false || channel === null) return; + join(channel); send('voice:join', { peerId, channelId: channel }) - }, [connected, peerId, channel]) + }, [connected, peerId, channel]); + + const leaveCall = useCallback(() => { + if(peerId === null || connected === false) return; + send('voice:leave', { peerId, channelId: channel }) + }, [connected, peerId, channel]); return
Actions +
} \ No newline at end of file diff --git a/packages/renderer/src/contexts/EphemeralState/ClientsListState.tsx b/packages/renderer/src/contexts/EphemeralState/ClientsListState.tsx index d48475d..29b5095 100644 --- a/packages/renderer/src/contexts/EphemeralState/ClientsListState.tsx +++ b/packages/renderer/src/contexts/EphemeralState/ClientsListState.tsx @@ -29,10 +29,6 @@ export default function ClientsListState(props: any) { } }); - useEffect(() => { - console.log(clients); - }, [clients]); - useEffect(() => { send('clients:list') }, []); diff --git a/packages/renderer/src/contexts/EphemeralState/EphemeralState.tsx b/packages/renderer/src/contexts/EphemeralState/EphemeralState.tsx index 1984bfb..300f5d6 100644 --- a/packages/renderer/src/contexts/EphemeralState/EphemeralState.tsx +++ b/packages/renderer/src/contexts/EphemeralState/EphemeralState.tsx @@ -82,9 +82,7 @@ export default function EphemeralState(props: { isSettingsOpen: settings, }}> - - {props.children} - + {props.children} diff --git a/packages/renderer/src/contexts/EphemeralState/PeerState.tsx b/packages/renderer/src/contexts/EphemeralState/PeerState.tsx index 3d52f47..d029e48 100644 --- a/packages/renderer/src/contexts/EphemeralState/PeerState.tsx +++ b/packages/renderer/src/contexts/EphemeralState/PeerState.tsx @@ -1,13 +1,20 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { Peer, MediaConnection } from "peerjs"; import { UserMediaContext } from "./UserMediaState"; +import { useApi } from "/@/lib/useApi"; export const PeerContext = createContext<{ connected: boolean; - peerId: string | null + inCall: boolean; + peerId: string | null; + join: (channelId: string) => void; + leave: () => void; }>({ connected: false, - peerId: null + peerId: null, + inCall: false, + join: () => {}, + leave: () => {} }); function useCurrent(thing: T) { @@ -20,6 +27,13 @@ function useCurrent(thing: T) { return thingRef.current; } +interface Connection { + peerId: string; + clientId: string; + channelID: string; + call: any; +} + export default function PeerState(props: any) { const { mediaStream } = useContext(UserMediaContext); @@ -29,17 +43,30 @@ export default function PeerState(props: any) { const [peer, setPeer] = useState(null); const [peerId, setPeerId] = useState(null); const [incomingCalls, setIncomingCalls] = useState([]); + const [connections, setConnections] = useState([]); + const [channel, setChannel] = useState(null); - const addCall = useCurrent(useCallback((call: MediaConnection) => { - // HACK lookout for possible timing issues here. - // if we get two incomming calls before a re-render - // then our state could be out of date?! - // a possible solution is to cache the - // append to the list, and if the cache and - // state disagree, add to the cache, and set state - // to the cached value. - setIncomingCalls([...incomingCalls, call]); - }, [incomingCalls])) + const addIncomingCall = useCurrent(useCallback((call: MediaConnection) => { + setIncomingCalls(incomingCalls => ([...incomingCalls, call])); + }, [])); + + const { send } = useApi({ + 'voice:join'(data: any) { + if(data.channelId !== channel) return + console.log('PEER STATE CONNECTIONS', data); + }, + 'voice:leave'(data: any) { + if(data.channelId !== channel) return + console.log('PEER STATE CONNECTIONS', data) + }, + 'voice:list'(data: any) { + if(data.uid !== channel) return + setConnections(connections => { + + }) + console.log('PEER STATE CONNECTIONS', data); + } + }, [channel]); useEffect(() => { if(connected) return; @@ -59,7 +86,7 @@ export default function PeerState(props: any) { }); peer.on('call', (call: MediaConnection) => { - addCall(call); + addIncomingCall(call); }); }, [connected]); @@ -69,10 +96,23 @@ export default function PeerState(props: any) { peer.call(id, mediaStream); }, [peer, mediaStream]) + const joinChannel = (channelId: string) => { + setChannel(channelId); + send('voice:list', { channelId }) + } + + useEffect(() => { + if(channel === null) return; + console.log('WE JOINED A CHANNEL') + }, [channel]) + const value = useMemo(() => ({ connected, peerId, - }), [connected, peerId]); + inCall: connections.length === 0, + join: joinChannel, + leave: () => {} + }), [connected, peerId, connections]); return {props.children} diff --git a/packages/renderer/src/lib/sound.ts b/packages/renderer/src/lib/sound.ts new file mode 100644 index 0000000..be1ccaa --- /dev/null +++ b/packages/renderer/src/lib/sound.ts @@ -0,0 +1,14 @@ +import joinCallSound from '../../assets/join-call.wav'; +import leaveCallSound from '../../assets/leave-call.wav'; +const beep = "data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU="; + +function play(sound: string) { + var snd = new Audio(sound); + snd.play(); +} + +export const sfx = { + message: () => play(beep), + joinCall: () => play(joinCallSound), + leaveCall: () => play(leaveCallSound) +} \ No newline at end of file diff --git a/packages/renderer/src/pages/Channel.tsx b/packages/renderer/src/pages/Channel.tsx index 10df928..fd578d8 100644 --- a/packages/renderer/src/pages/Channel.tsx +++ b/packages/renderer/src/pages/Channel.tsx @@ -8,6 +8,7 @@ import { useApi } from "../lib/useApi"; import { useContext, useEffect, useState } from "react"; import { VoiceChannelContext } from "../contexts/EphemeralState/VoiceChannelState"; import { ClientsListContext } from "../contexts/EphemeralState/ClientsListState"; +import { sfx } from "../lib/sound"; interface ChannelProps { unread: number; @@ -16,6 +17,12 @@ interface ChannelProps { type: ChannelType; } +interface Participant { + peerId: string; + channelId: string; + clientId: string; +} + export default function Channel(props: ChannelProps) { const { clientName } = useContext(ClientsListContext); const { channel, setChannel } = useChannel(); @@ -24,7 +31,7 @@ export default function Channel(props: ChannelProps) { const selected = channel === uid; const { voiceChannelId } = useContext(VoiceChannelContext); - const [participants, setParticipants] = useState([]); + const [participants, setParticipants] = useState([]); const { send } = useApi({ 'voice:join'(data: any) { @@ -34,14 +41,20 @@ export default function Channel(props: ChannelProps) { peerId: data.peerId, channelId: data.channelId }]) - console.log('JOIN', data); + sfx.joinCall(); }, 'voice:list'(data: any) { if(type !== 'voice') return; - console.log('CURRENTS', data); + if(uid !== data.uid) return; + setParticipants(data.participants); }, 'voice:leave'(data: any) { - console.log(data); + sfx.leaveCall(); + setParticipants(participants => participants.filter(p => ( + p.channelId !== data.channelId || + p.clientId !== data.clientId || + p.peerId !== data.peerId + ))); }, }, [uid, type, participants]) @@ -53,84 +66,91 @@ export default function Channel(props: ChannelProps) { }, [uid]); return ( -
{ - setChannel(uid, type); - }} - ref={ref} - > - {(type === 'text') ? ( - - ) : ((type === 'voice') ? ( - +
{ + setChannel(uid, type); + }} + ref={ref} + > + {(type === 'text') ? ( + + ) : ((type === 'voice') ? ( + + ) : ( + + ))} +
- ) : ( - - ))} -
- {name.toLowerCase().replaceAll(' ', '-').trim()} -
- {/* {(unread > 0) && ( - ({unread}) - )} - 0 ? 'bold' : '300', - }}> - {name} - - {}}>Delete */} -

- {participants.map(participant => ( -
- {clientName[participant.clientId]} + }}> + {name.toLowerCase().replaceAll(' ', '-').trim()}
- ))} -
+ {/* {(unread > 0) && ( + ({unread}) + )} + 0 ? 'bold' : '300', + }}> + {name} + + {}}>Delete */} +
+ +
+ {participants.map(participant => ( +
+ {clientName[participant.clientId]} +
+ ))} +
+ ) } \ No newline at end of file diff --git a/packages/renderer/src/pages/Channels.tsx b/packages/renderer/src/pages/Channels.tsx index efc5dea..b187eec 100644 --- a/packages/renderer/src/pages/Channels.tsx +++ b/packages/renderer/src/pages/Channels.tsx @@ -11,6 +11,7 @@ import useClientId from '../hooks/useClientId'; import useHomeServer from '../contexts/PersistentState/useHomeServerNative'; import Channel from './Channel'; import { ChannelType } from '../contexts/EphemeralState/EphemeralState'; +import { sfx } from '../lib/sound'; interface IChannel { uid: string; @@ -38,7 +39,9 @@ export default function Channels() { setChannels([...channels, channel]); }, 'message:message'(message: IMessage) { + sfx.message(); if(channel === message.channel) return; + setUnreads({ ...unreads, [message.channel]: (unreads[message.channel] ?? 0) + 1, diff --git a/packages/server/src/lib/WebSocketServer.ts b/packages/server/src/lib/WebSocketServer.ts index b858abd..8e369c9 100644 --- a/packages/server/src/lib/WebSocketServer.ts +++ b/packages/server/src/lib/WebSocketServer.ts @@ -1,4 +1,4 @@ -import { WebSocketServer } from 'ws'; +import { WebSocketServer, WebSocket } from 'ws'; import { inspect } from 'util'; import { validateSessionToken } from '../routers/session'; @@ -30,7 +30,7 @@ export function expose(router: Function, port: number) { throw new Error('action ' + action + ' payload not an object'); } console.log('[IN]', action, data); - const _return = await (router(action, data) as unknown as Promise); + const _return = await (router(action, data, ws, wss) as unknown as Promise); if(_return) { try { switch(_return.type) { @@ -71,8 +71,17 @@ enum ResponseType { REPLY } -function send(client: any, action: string, data?: any) { - client.send(JSON.stringify({action, data})); +// var snd = new Audio("data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU="); +// snd.play(); + +export function send(client: WebSocket | Set, action: string, data?: any) { + if(client instanceof Set) { + for(const c of client) { + c.send(JSON.stringify({action, data})); + } + } else { + client.send(JSON.stringify({action, data})); + } } export function broadcast(data: any) { diff --git a/packages/server/src/lib/router.ts b/packages/server/src/lib/router.ts index 416e602..9678e4f 100644 --- a/packages/server/src/lib/router.ts +++ b/packages/server/src/lib/router.ts @@ -1,14 +1,12 @@ - - export default function router(routes: any) { for(const routeName in routes) { const route = routes[routeName]; if('routes' in route) { for(const suffix of route.routes) { const combinedRouteName = routeName + ':' + suffix; - routes[combinedRouteName] = function(data: any) { + routes[combinedRouteName] = function(...args: any[]) { // console.log(suffix, route, data) - return route(suffix, data); + return route(suffix, ...args); // console.log('INCOMMING', args) }; } @@ -16,9 +14,9 @@ export default function router(routes: any) { } } - const sendFn = function(route: any, data: any) { + const sendFn = function(route: any, ...args: any[]) { if(route in routes) { - return routes[route](data); + return routes[route](...args); } else { console.warn(`route <${route}> not found`); console.trace(); diff --git a/packages/server/src/routers/channel.ts b/packages/server/src/routers/channel.ts index 728f107..907bf6b 100644 --- a/packages/server/src/routers/channel.ts +++ b/packages/server/src/routers/channel.ts @@ -10,6 +10,11 @@ export const mockVoiceChannels = [ uid: v4(), name: 'Voice Test', type: 'voice' + }, + { + uid: v4(), + name: 'Voice Test 2', + type: 'voice' } ] diff --git a/packages/server/src/routers/voice.ts b/packages/server/src/routers/voice.ts index c89cc3a..8e3a0fc 100644 --- a/packages/server/src/routers/voice.ts +++ b/packages/server/src/routers/voice.ts @@ -1,5 +1,6 @@ +import WebSocket from "ws"; import router from "../lib/router"; -import { broadcast, reply } from "../lib/WebSocketServer"; +import { broadcast, reply, send } from "../lib/WebSocketServer"; function filterInPlace(a: T[], condition: (v: T, i: number, a: T[]) => boolean) { let i = 0, j = 0; @@ -27,7 +28,7 @@ interface ClientChannelRelationship { const participants: ClientChannelRelationship[] = []; export default router({ - async join(data: any) { + async join(data: any, ws: WebSocket.WebSocket, wss: WebSocket.Server) { const { $clientId, channelId, peerId } = data; // TODO validate channel exists if(participants @@ -44,12 +45,17 @@ export default router({ participants.push(user_channel); + ws.on('close', () => { + filterInPlace(participants, (p => p !== user_channel)); + send(wss.clients, 'voice:leave', user_channel) + }) + return broadcast(user_channel) }, async list(data: any) { - const { uid } = data; + const uid = data.uid ?? data.channelId return reply({ - uid, + uid: uid, participants: participants.filter(v => uid === v.channelId) }); },