i deadass just dont care

main
Valerie 2022-08-11 14:29:24 -04:00
parent 1aead4cc6b
commit b73ab2c691
15 changed files with 219 additions and 122 deletions

Binary file not shown.

Binary file not shown.

View File

@ -6,7 +6,7 @@
<meta http-equiv="Content-Security-Policy" content=" <meta http-equiv="Content-Security-Policy" content="
default-src 'self' data: https://ssl.gstatic.com https://fonts.gstatic.com; default-src 'self' data: https://ssl.gstatic.com https://fonts.gstatic.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
media-src *; media-src * data:;
img-src 'self' data: content: http://tinygraphs.com; img-src 'self' data: content: http://tinygraphs.com;
connect-src *;"> connect-src *;">
<meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="width=device-width, initial-scale=1.0" name="viewport">

View File

@ -1,5 +1,6 @@
import { createContext, PropsWithChildren, ReactNode, useEffect, useMemo } from "react"; import { createContext, PropsWithChildren, ReactNode, useEffect, useMemo } from "react";
import ClientsListState from "../contexts/EphemeralState/ClientsListState"; import ClientsListState from "../contexts/EphemeralState/ClientsListState";
import PeerState from "../contexts/EphemeralState/PeerState";
import { connectApi } from "../lib/api"; import { connectApi } from "../lib/api";
interface ServerConnectionProps { interface ServerConnectionProps {
@ -33,7 +34,9 @@ export default function ServerConnection(props: ServerConnectionProps) {
return <ServerConnectionContext.Provider value={serverConnection}> return <ServerConnectionContext.Provider value={serverConnection}>
<ClientsListState> <ClientsListState>
{props.children} <PeerState>
{props.children}
</PeerState>
</ClientsListState> </ClientsListState>
</ServerConnectionContext.Provider> </ServerConnectionContext.Provider>
} }

View File

@ -7,17 +7,21 @@ import { useApi } from "../lib/useApi";
export default function Voice(props: any) { export default function Voice(props: any) {
const { uid } = props; const { uid } = props;
const { connected, peerId } = useContext(PeerContext); const { connected, peerId, join } = useContext(PeerContext);
const { channel } = useChannel(); const { channel } = useChannel();
const { send } = useApi({ const { send } = useApi({});
});
const joinCall = useCallback(() => { 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 }) 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={{ return <div style={{
width: '100%', width: '100%',
@ -32,6 +36,7 @@ export default function Voice(props: any) {
<fieldset> <fieldset>
<legend>Actions</legend> <legend>Actions</legend>
<button onClick={joinCall}>Join Call</button> <button onClick={joinCall}>Join Call</button>
<button onClick={leaveCall}>Leave Call</button>
</fieldset> </fieldset>
</div> </div>
} }

View File

@ -29,10 +29,6 @@ export default function ClientsListState(props: any) {
} }
}); });
useEffect(() => {
console.log(clients);
}, [clients]);
useEffect(() => { useEffect(() => {
send('clients:list') send('clients:list')
}, []); }, []);

View File

@ -82,9 +82,7 @@ export default function EphemeralState(props: {
isSettingsOpen: settings, isSettingsOpen: settings,
}}> }}>
<UserMediaState> <UserMediaState>
<PeerState> {props.children}
{props.children}
</PeerState>
</UserMediaState> </UserMediaState>
</SettingsContext.Provider> </SettingsContext.Provider>
</TransparencyContext.Provider> </TransparencyContext.Provider>

View File

@ -1,13 +1,20 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { Peer, MediaConnection } from "peerjs"; import { Peer, MediaConnection } from "peerjs";
import { UserMediaContext } from "./UserMediaState"; import { UserMediaContext } from "./UserMediaState";
import { useApi } from "/@/lib/useApi";
export const PeerContext = createContext<{ export const PeerContext = createContext<{
connected: boolean; connected: boolean;
peerId: string | null inCall: boolean;
peerId: string | null;
join: (channelId: string) => void;
leave: () => void;
}>({ }>({
connected: false, connected: false,
peerId: null peerId: null,
inCall: false,
join: () => {},
leave: () => {}
}); });
function useCurrent<T>(thing: T) { function useCurrent<T>(thing: T) {
@ -20,6 +27,13 @@ function useCurrent<T>(thing: T) {
return thingRef.current; return thingRef.current;
} }
interface Connection {
peerId: string;
clientId: string;
channelID: string;
call: any;
}
export default function PeerState(props: any) { export default function PeerState(props: any) {
const { mediaStream } = useContext(UserMediaContext); const { mediaStream } = useContext(UserMediaContext);
@ -29,17 +43,30 @@ export default function PeerState(props: any) {
const [peer, setPeer] = useState<Peer | null>(null); const [peer, setPeer] = useState<Peer | null>(null);
const [peerId, setPeerId] = useState<string | null>(null); const [peerId, setPeerId] = useState<string | null>(null);
const [incomingCalls, setIncomingCalls] = useState<MediaConnection[]>([]); const [incomingCalls, setIncomingCalls] = useState<MediaConnection[]>([]);
const [connections, setConnections] = useState<Connection[]>([]);
const [channel, setChannel] = useState<string | null>(null);
const addCall = useCurrent(useCallback((call: MediaConnection) => { const addIncomingCall = useCurrent(useCallback((call: MediaConnection) => {
// HACK lookout for possible timing issues here. setIncomingCalls(incomingCalls => ([...incomingCalls, call]));
// 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 const { send } = useApi({
// append to the list, and if the cache and 'voice:join'(data: any) {
// state disagree, add to the cache, and set state if(data.channelId !== channel) return
// to the cached value. console.log('PEER STATE CONNECTIONS', data);
setIncomingCalls([...incomingCalls, call]); },
}, [incomingCalls])) '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(() => { useEffect(() => {
if(connected) return; if(connected) return;
@ -59,7 +86,7 @@ export default function PeerState(props: any) {
}); });
peer.on('call', (call: MediaConnection) => { peer.on('call', (call: MediaConnection) => {
addCall(call); addIncomingCall(call);
}); });
}, [connected]); }, [connected]);
@ -69,10 +96,23 @@ export default function PeerState(props: any) {
peer.call(id, mediaStream); peer.call(id, mediaStream);
}, [peer, 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(() => ({ const value = useMemo(() => ({
connected, connected,
peerId, peerId,
}), [connected, peerId]); inCall: connections.length === 0,
join: joinChannel,
leave: () => {}
}), [connected, peerId, connections]);
return <PeerContext.Provider value={value}> return <PeerContext.Provider value={value}>
{props.children} {props.children}

View File

@ -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)
}

View File

@ -8,6 +8,7 @@ import { useApi } from "../lib/useApi";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { VoiceChannelContext } from "../contexts/EphemeralState/VoiceChannelState"; import { VoiceChannelContext } from "../contexts/EphemeralState/VoiceChannelState";
import { ClientsListContext } from "../contexts/EphemeralState/ClientsListState"; import { ClientsListContext } from "../contexts/EphemeralState/ClientsListState";
import { sfx } from "../lib/sound";
interface ChannelProps { interface ChannelProps {
unread: number; unread: number;
@ -16,6 +17,12 @@ interface ChannelProps {
type: ChannelType; type: ChannelType;
} }
interface Participant {
peerId: string;
channelId: string;
clientId: string;
}
export default function Channel(props: ChannelProps) { export default function Channel(props: ChannelProps) {
const { clientName } = useContext(ClientsListContext); const { clientName } = useContext(ClientsListContext);
const { channel, setChannel } = useChannel(); const { channel, setChannel } = useChannel();
@ -24,7 +31,7 @@ export default function Channel(props: ChannelProps) {
const selected = channel === uid; const selected = channel === uid;
const { voiceChannelId } = useContext(VoiceChannelContext); const { voiceChannelId } = useContext(VoiceChannelContext);
const [participants, setParticipants] = useState<any[]>([]); const [participants, setParticipants] = useState<Participant[]>([]);
const { send } = useApi({ const { send } = useApi({
'voice:join'(data: any) { 'voice:join'(data: any) {
@ -34,14 +41,20 @@ export default function Channel(props: ChannelProps) {
peerId: data.peerId, peerId: data.peerId,
channelId: data.channelId channelId: data.channelId
}]) }])
console.log('JOIN', data); sfx.joinCall();
}, },
'voice:list'(data: any) { 'voice:list'(data: any) {
if(type !== 'voice') return; if(type !== 'voice') return;
console.log('CURRENTS', data); if(uid !== data.uid) return;
setParticipants(data.participants);
}, },
'voice:leave'(data: any) { '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]) }, [uid, type, participants])
@ -53,84 +66,91 @@ export default function Channel(props: ChannelProps) {
}, [uid]); }, [uid]);
return ( return (
<div <>
style={{ <div
margin: '2px 2px', style={{
display: 'grid', margin: '2px 2px',
gridTemplateColumns: 'min-content 1fr', display: 'grid',
cursor: 'pointer', gridTemplateColumns: 'min-content 1fr',
background: selected ? 'var(--neutral-5)' : cursor: 'pointer',
hover ? 'var(--neutral-4)' : background: selected ? 'var(--neutral-5)' :
'inherit', hover ? 'var(--neutral-4)' :
borderRadius: '8px', 'inherit',
transform:'skew(-20deg, 0deg)', borderRadius: '8px',
transition: 'background 300ms, color 300ms', transform:'skew(-20deg, 0deg)',
}}
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', 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)', 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', transition: 'background 300ms, color 300ms',
transform:'skew(20deg, 0deg)', }}>
}}></BsQuestionLg> {name.toLowerCase().replaceAll(' ', '-').trim()}
))}
<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]}
</div> </div>
))} {/* {(unread > 0) && (
</div> <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>
</>
) )
} }

View File

@ -11,6 +11,7 @@ import useClientId from '../hooks/useClientId';
import useHomeServer from '../contexts/PersistentState/useHomeServerNative'; import useHomeServer from '../contexts/PersistentState/useHomeServerNative';
import Channel from './Channel'; import Channel from './Channel';
import { ChannelType } from '../contexts/EphemeralState/EphemeralState'; import { ChannelType } from '../contexts/EphemeralState/EphemeralState';
import { sfx } from '../lib/sound';
interface IChannel { interface IChannel {
uid: string; uid: string;
@ -38,7 +39,9 @@ export default function Channels() {
setChannels([...channels, channel]); setChannels([...channels, channel]);
}, },
'message:message'(message: IMessage) { 'message:message'(message: IMessage) {
sfx.message();
if(channel === message.channel) return; if(channel === message.channel) return;
setUnreads({ setUnreads({
...unreads, ...unreads,
[message.channel]: (unreads[message.channel] ?? 0) + 1, [message.channel]: (unreads[message.channel] ?? 0) + 1,

View File

@ -1,4 +1,4 @@
import { WebSocketServer } from 'ws'; import { WebSocketServer, WebSocket } from 'ws';
import { inspect } from 'util'; import { inspect } from 'util';
import { validateSessionToken } from '../routers/session'; 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'); throw new Error('action ' + action + ' payload not an object');
} }
console.log('[IN]', action, data); 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) { if(_return) {
try { try {
switch(_return.type) { switch(_return.type) {
@ -71,8 +71,17 @@ enum ResponseType {
REPLY REPLY
} }
function send(client: any, action: string, data?: any) { // 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=");
client.send(JSON.stringify({action, data})); // 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) { export function broadcast(data: any) {

View File

@ -1,14 +1,12 @@
export default function router(routes: any) { export default function router(routes: any) {
for(const routeName in routes) { for(const routeName in routes) {
const route = routes[routeName]; const route = routes[routeName];
if('routes' in route) { if('routes' in route) {
for(const suffix of route.routes) { for(const suffix of route.routes) {
const combinedRouteName = routeName + ':' + suffix; const combinedRouteName = routeName + ':' + suffix;
routes[combinedRouteName] = function(data: any) { routes[combinedRouteName] = function(...args: any[]) {
// console.log(suffix, route, data) // console.log(suffix, route, data)
return route(suffix, data); return route(suffix, ...args);
// console.log('INCOMMING', 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) { if(route in routes) {
return routes[route](data); return routes[route](...args);
} else { } else {
console.warn(`route <${route}> not found`); console.warn(`route <${route}> not found`);
console.trace(); console.trace();

View File

@ -10,6 +10,11 @@ export const mockVoiceChannels = [
uid: v4(), uid: v4(),
name: 'Voice Test', name: 'Voice Test',
type: 'voice' type: 'voice'
},
{
uid: v4(),
name: 'Voice Test 2',
type: 'voice'
} }
] ]

View File

@ -1,5 +1,6 @@
import WebSocket from "ws";
import router from "../lib/router"; 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) { function filterInPlace<T>(a: T[], condition: (v: T, i: number, a: T[]) => boolean) {
let i = 0, j = 0; let i = 0, j = 0;
@ -27,7 +28,7 @@ interface ClientChannelRelationship {
const participants: ClientChannelRelationship[] = []; const participants: ClientChannelRelationship[] = [];
export default router({ export default router({
async join(data: any) { async join(data: any, ws: WebSocket.WebSocket, wss: WebSocket.Server) {
const { $clientId, channelId, peerId } = data; const { $clientId, channelId, peerId } = data;
// TODO validate channel exists // TODO validate channel exists
if(participants if(participants
@ -44,12 +45,17 @@ export default router({
participants.push(user_channel); participants.push(user_channel);
ws.on('close', () => {
filterInPlace(participants, (p => p !== user_channel));
send(wss.clients, 'voice:leave', user_channel)
})
return broadcast(user_channel) return broadcast(user_channel)
}, },
async list(data: any) { async list(data: any) {
const { uid } = data; const uid = data.uid ?? data.channelId
return reply({ return reply({
uid, uid: uid,
participants: participants.filter(v => uid === v.channelId) participants: participants.filter(v => uid === v.channelId)
}); });
}, },