i deadass just dont care
parent
1aead4cc6b
commit
b73ab2c691
Binary file not shown.
Binary file not shown.
|
|
@ -6,7 +6,7 @@
|
|||
<meta http-equiv="Content-Security-Policy" content="
|
||||
default-src 'self' data: https://ssl.gstatic.com https://fonts.gstatic.com;
|
||||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
|
||||
media-src *;
|
||||
media-src * data:;
|
||||
img-src 'self' data: content: http://tinygraphs.com;
|
||||
connect-src *;">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
|
|
|
|||
|
|
@ -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 <ServerConnectionContext.Provider value={serverConnection}>
|
||||
<ClientsListState>
|
||||
{props.children}
|
||||
<PeerState>
|
||||
{props.children}
|
||||
</PeerState>
|
||||
</ClientsListState>
|
||||
</ServerConnectionContext.Provider>
|
||||
}
|
||||
|
|
@ -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 <div style={{
|
||||
width: '100%',
|
||||
|
|
@ -32,6 +36,7 @@ export default function Voice(props: any) {
|
|||
<fieldset>
|
||||
<legend>Actions</legend>
|
||||
<button onClick={joinCall}>Join Call</button>
|
||||
<button onClick={leaveCall}>Leave Call</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -29,10 +29,6 @@ export default function ClientsListState(props: any) {
|
|||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
console.log(clients);
|
||||
}, [clients]);
|
||||
|
||||
useEffect(() => {
|
||||
send('clients:list')
|
||||
}, []);
|
||||
|
|
|
|||
|
|
@ -82,9 +82,7 @@ export default function EphemeralState(props: {
|
|||
isSettingsOpen: settings,
|
||||
}}>
|
||||
<UserMediaState>
|
||||
<PeerState>
|
||||
{props.children}
|
||||
</PeerState>
|
||||
{props.children}
|
||||
</UserMediaState>
|
||||
</SettingsContext.Provider>
|
||||
</TransparencyContext.Provider>
|
||||
|
|
|
|||
|
|
@ -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<T>(thing: T) {
|
||||
|
|
@ -20,6 +27,13 @@ function useCurrent<T>(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<Peer | null>(null);
|
||||
const [peerId, setPeerId] = useState<string | null>(null);
|
||||
const [incomingCalls, setIncomingCalls] = useState<MediaConnection[]>([]);
|
||||
const [connections, setConnections] = useState<Connection[]>([]);
|
||||
const [channel, setChannel] = useState<string | null>(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 <PeerContext.Provider value={value}>
|
||||
{props.children}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<any[]>([]);
|
||||
const [participants, setParticipants] = useState<Participant[]>([]);
|
||||
|
||||
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 (
|
||||
<div
|
||||
style={{
|
||||
margin: '2px 2px',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'min-content 1fr',
|
||||
cursor: 'pointer',
|
||||
background: selected ? 'var(--neutral-5)' :
|
||||
hover ? 'var(--neutral-4)' :
|
||||
'inherit',
|
||||
borderRadius: '8px',
|
||||
transform:'skew(-20deg, 0deg)',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
}}
|
||||
onClick={() => {
|
||||
setChannel(uid, type);
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
{(type === 'text') ? (
|
||||
<CgHashtag color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(-5deg, 0deg)',
|
||||
}}></CgHashtag>
|
||||
) : ((type === 'voice') ? (
|
||||
<MdVolumeUp color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
margin: '2px 2px',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'min-content 1fr',
|
||||
cursor: 'pointer',
|
||||
background: selected ? 'var(--neutral-5)' :
|
||||
hover ? 'var(--neutral-4)' :
|
||||
'inherit',
|
||||
borderRadius: '8px',
|
||||
transform:'skew(-20deg, 0deg)',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
}}
|
||||
onClick={() => {
|
||||
setChannel(uid, type);
|
||||
}}
|
||||
ref={ref}
|
||||
>
|
||||
{(type === 'text') ? (
|
||||
<CgHashtag color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(-5deg, 0deg)',
|
||||
}}></CgHashtag>
|
||||
) : ((type === 'voice') ? (
|
||||
<MdVolumeUp color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(20deg, 0deg)',
|
||||
}}></MdVolumeUp>
|
||||
) : (
|
||||
<BsQuestionLg color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(20deg, 0deg)',
|
||||
}}></BsQuestionLg>
|
||||
))}
|
||||
<div style={{
|
||||
lineHeight: '32px',
|
||||
color: selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-9)' :
|
||||
'var(--neutral-7)',
|
||||
transform:'skew(20deg, 0deg)',
|
||||
}}></MdVolumeUp>
|
||||
) : (
|
||||
<BsQuestionLg color={
|
||||
selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-7)' :
|
||||
'var(--neutral-7)'
|
||||
} size={24} style={{
|
||||
margin: '4px',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
transform:'skew(20deg, 0deg)',
|
||||
}}></BsQuestionLg>
|
||||
))}
|
||||
<div style={{
|
||||
lineHeight: '32px',
|
||||
color: selected ? 'var(--neutral-9)' :
|
||||
hover ? 'var(--neutral-9)' :
|
||||
'var(--neutral-7)',
|
||||
transform:'skew(20deg, 0deg)',
|
||||
transition: 'background 300ms, color 300ms',
|
||||
}}>
|
||||
{name.toLowerCase().replaceAll(' ', '-').trim()}
|
||||
</div>
|
||||
{/* {(unread > 0) && (
|
||||
<span style={{ paddingRight: '8px' }}>({unread})</span>
|
||||
)}
|
||||
<span style={{
|
||||
fontWeight: (unread ?? 0) > 0 ? 'bold' : '300',
|
||||
}}>
|
||||
{name}
|
||||
</span>
|
||||
<a style={{
|
||||
color: 'rgba(0, 100, 200, 1)',
|
||||
marginLeft: '8px',
|
||||
fontSize: '10px',
|
||||
}} href="#" onClick={() => {}}>Delete</a> */}
|
||||
<br></br>
|
||||
{participants.map(participant => (
|
||||
<div key={participant.clientId}>
|
||||
{clientName[participant.clientId]}
|
||||
}}>
|
||||
{name.toLowerCase().replaceAll(' ', '-').trim()}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{/* {(unread > 0) && (
|
||||
<span style={{ paddingRight: '8px' }}>({unread})</span>
|
||||
)}
|
||||
<span style={{
|
||||
fontWeight: (unread ?? 0) > 0 ? 'bold' : '300',
|
||||
}}>
|
||||
{name}
|
||||
</span>
|
||||
<a style={{
|
||||
color: 'rgba(0, 100, 200, 1)',
|
||||
marginLeft: '8px',
|
||||
fontSize: '10px',
|
||||
}} href="#" onClick={() => {}}>Delete</a> */}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
// outline: '1px solid white',
|
||||
paddingLeft: '32px',
|
||||
}}>
|
||||
{participants.map(participant => (
|
||||
<div key={participant.clientId}>
|
||||
{clientName[participant.clientId]}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<any>);
|
||||
const _return = await (router(action, data, ws, wss) as unknown as Promise<any>);
|
||||
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,//uQRAAAAWMSLwUIYAAsYkXgoQwAEaYLWfkWgAI0wWs/ItAAAGDgYtAgAyN+QWaAAihwMWm4G8QQRDiMcCBcH3Cc+CDv/7xA4Tvh9Rz/y8QADBwMWgQAZG/ILNAARQ4GLTcDeIIIhxGOBAuD7hOfBB3/94gcJ3w+o5/5eIAIAAAVwWgQAVQ2ORaIQwEMAJiDg95G4nQL7mQVWI6GwRcfsZAcsKkJvxgxEjzFUgfHoSQ9Qq7KNwqHwuB13MA4a1q/DmBrHgPcmjiGoh//EwC5nGPEmS4RcfkVKOhJf+WOgoxJclFz3kgn//dBA+ya1GhurNn8zb//9NNutNuhz31f////9vt///z+IdAEAAAK4LQIAKobHItEIYCGAExBwe8jcToF9zIKrEdDYIuP2MgOWFSE34wYiR5iqQPj0JIeoVdlG4VD4XA67mAcNa1fhzA1jwHuTRxDUQ//iYBczjHiTJcIuPyKlHQkv/LHQUYkuSi57yQT//uggfZNajQ3Vmz+Zt//+mm3Wm3Q576v////+32///5/EOgAAADVghQAAAAA//uQZAUAB1WI0PZugAAAAAoQwAAAEk3nRd2qAAAAACiDgAAAAAAABCqEEQRLCgwpBGMlJkIz8jKhGvj4k6jzRnqasNKIeoh5gI7BJaC1A1AoNBjJgbyApVS4IDlZgDU5WUAxEKDNmmALHzZp0Fkz1FMTmGFl1FMEyodIavcCAUHDWrKAIA4aa2oCgILEBupZgHvAhEBcZ6joQBxS76AgccrFlczBvKLC0QI2cBoCFvfTDAo7eoOQInqDPBtvrDEZBNYN5xwNwxQRfw8ZQ5wQVLvO8OYU+mHvFLlDh05Mdg7BT6YrRPpCBznMB2r//xKJjyyOh+cImr2/4doscwD6neZjuZR4AgAABYAAAABy1xcdQtxYBYYZdifkUDgzzXaXn98Z0oi9ILU5mBjFANmRwlVJ3/6jYDAmxaiDG3/6xjQQCCKkRb/6kg/wW+kSJ5//rLobkLSiKmqP/0ikJuDaSaSf/6JiLYLEYnW/+kXg1WRVJL/9EmQ1YZIsv/6Qzwy5qk7/+tEU0nkls3/zIUMPKNX/6yZLf+kFgAfgGyLFAUwY//uQZAUABcd5UiNPVXAAAApAAAAAE0VZQKw9ISAAACgAAAAAVQIygIElVrFkBS+Jhi+EAuu+lKAkYUEIsmEAEoMeDmCETMvfSHTGkF5RWH7kz/ESHWPAq/kcCRhqBtMdokPdM7vil7RG98A2sc7zO6ZvTdM7pmOUAZTnJW+NXxqmd41dqJ6mLTXxrPpnV8avaIf5SvL7pndPvPpndJR9Kuu8fePvuiuhorgWjp7Mf/PRjxcFCPDkW31srioCExivv9lcwKEaHsf/7ow2Fl1T/9RkXgEhYElAoCLFtMArxwivDJJ+bR1HTKJdlEoTELCIqgEwVGSQ+hIm0NbK8WXcTEI0UPoa2NbG4y2K00JEWbZavJXkYaqo9CRHS55FcZTjKEk3NKoCYUnSQ0rWxrZbFKbKIhOKPZe1cJKzZSaQrIyULHDZmV5K4xySsDRKWOruanGtjLJXFEmwaIbDLX0hIPBUQPVFVkQkDoUNfSoDgQGKPekoxeGzA4DUvnn4bxzcZrtJyipKfPNy5w+9lnXwgqsiyHNeSVpemw4bWb9psYeq//uQZBoABQt4yMVxYAIAAAkQoAAAHvYpL5m6AAgAACXDAAAAD59jblTirQe9upFsmZbpMudy7Lz1X1DYsxOOSWpfPqNX2WqktK0DMvuGwlbNj44TleLPQ+Gsfb+GOWOKJoIrWb3cIMeeON6lz2umTqMXV8Mj30yWPpjoSa9ujK8SyeJP5y5mOW1D6hvLepeveEAEDo0mgCRClOEgANv3B9a6fikgUSu/DmAMATrGx7nng5p5iimPNZsfQLYB2sDLIkzRKZOHGAaUyDcpFBSLG9MCQALgAIgQs2YunOszLSAyQYPVC2YdGGeHD2dTdJk1pAHGAWDjnkcLKFymS3RQZTInzySoBwMG0QueC3gMsCEYxUqlrcxK6k1LQQcsmyYeQPdC2YfuGPASCBkcVMQQqpVJshui1tkXQJQV0OXGAZMXSOEEBRirXbVRQW7ugq7IM7rPWSZyDlM3IuNEkxzCOJ0ny2ThNkyRai1b6ev//3dzNGzNb//4uAvHT5sURcZCFcuKLhOFs8mLAAEAt4UWAAIABAAAAAB4qbHo0tIjVkUU//uQZAwABfSFz3ZqQAAAAAngwAAAE1HjMp2qAAAAACZDgAAAD5UkTE1UgZEUExqYynN1qZvqIOREEFmBcJQkwdxiFtw0qEOkGYfRDifBui9MQg4QAHAqWtAWHoCxu1Yf4VfWLPIM2mHDFsbQEVGwyqQoQcwnfHeIkNt9YnkiaS1oizycqJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//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<WebSocket>, 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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ export const mockVoiceChannels = [
|
|||
uid: v4(),
|
||||
name: 'Voice Test',
|
||||
type: 'voice'
|
||||
},
|
||||
{
|
||||
uid: v4(),
|
||||
name: 'Voice Test 2',
|
||||
type: 'voice'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T>(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)
|
||||
});
|
||||
},
|
||||
|
|
|
|||
Reference in New Issue