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="
|
<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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
<PeerState>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
</PeerState>
|
||||||
</ClientsListState>
|
</ClientsListState>
|
||||||
</ServerConnectionContext.Provider>
|
</ServerConnectionContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
@ -29,10 +29,6 @@ export default function ClientsListState(props: any) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(clients);
|
|
||||||
}, [clients]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
send('clients:list')
|
send('clients:list')
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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 { 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,6 +66,7 @@ export default function Channel(props: ChannelProps) {
|
||||||
}, [uid]);
|
}, [uid]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
margin: '2px 2px',
|
margin: '2px 2px',
|
||||||
|
|
@ -125,12 +139,18 @@ export default function Channel(props: ChannelProps) {
|
||||||
marginLeft: '8px',
|
marginLeft: '8px',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
}} href="#" onClick={() => {}}>Delete</a> */}
|
}} href="#" onClick={() => {}}>Delete</a> */}
|
||||||
<br></br>
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
// outline: '1px solid white',
|
||||||
|
paddingLeft: '32px',
|
||||||
|
}}>
|
||||||
{participants.map(participant => (
|
{participants.map(participant => (
|
||||||
<div key={participant.clientId}>
|
<div key={participant.clientId}>
|
||||||
{clientName[participant.clientId]}
|
{clientName[participant.clientId]}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,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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<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}));
|
client.send(JSON.stringify({action, data}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function broadcast(data: any) {
|
export function broadcast(data: any) {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Reference in New Issue