We don't want to depend on: 1) socket.io generated IDs because they change on server reconnect 2) simple-peer generated IDs because they change for every peer connection We generate a single ID when the call web page is refreshed and use that throughout the session (until page refresh). We keep relations of user-id to socket-id on the server side in memory and use that to get to the right socket. In the future this might be replaced with Redis to allow multiple nodes. If the server is restarted, but people have active calls, we want them to keep using the active peer connections and only connect to new peers. Ideally, we do not want to disturb the active peer connections, but peer connections might be restarted because the in-memory store will not have the information on for any peers in the room upon restart.
107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
import * as NotifyActions from '../actions/NotifyActions'
|
|
import * as PeerActions from '../actions/PeerActions'
|
|
import * as constants from '../constants'
|
|
import keyBy from 'lodash/keyBy'
|
|
import _debug from 'debug'
|
|
import { Dispatch, GetState } from '../store'
|
|
import { ClientSocket } from '../socket'
|
|
import { SocketEvent } from '../../shared'
|
|
|
|
const debug = _debug('peercalls')
|
|
|
|
export interface SocketHandlerOptions {
|
|
socket: ClientSocket
|
|
roomName: string
|
|
stream?: MediaStream
|
|
dispatch: Dispatch
|
|
getState: GetState
|
|
userId: string
|
|
}
|
|
|
|
class SocketHandler {
|
|
socket: ClientSocket
|
|
roomName: string
|
|
stream?: MediaStream
|
|
dispatch: Dispatch
|
|
getState: GetState
|
|
userId: string
|
|
|
|
constructor (options: SocketHandlerOptions) {
|
|
this.socket = options.socket
|
|
this.roomName = options.roomName
|
|
this.stream = options.stream
|
|
this.dispatch = options.dispatch
|
|
this.getState = options.getState
|
|
this.userId = options.userId
|
|
}
|
|
handleSignal = ({ userId, signal }: SocketEvent['signal']) => {
|
|
const { getState } = this
|
|
const peer = getState().peers[userId]
|
|
// debug('socket signal, userId: %s, signal: %o', userId, signal);
|
|
if (!peer) return debug('user: %s, no peer found', userId)
|
|
peer.signal(signal)
|
|
}
|
|
handleUsers = ({ initiator, users }: SocketEvent['users']) => {
|
|
const { socket, stream, dispatch, getState } = this
|
|
debug('socket users: %o', users)
|
|
this.dispatch(NotifyActions.info('Connected users: {0}', users.length))
|
|
const { peers } = this.getState()
|
|
|
|
users
|
|
.filter(
|
|
user =>
|
|
user.userId && !peers[user.userId] && user.userId !== this.userId)
|
|
.forEach(user => PeerActions.createPeer({
|
|
socket,
|
|
user: {
|
|
// users without id should be filtered out
|
|
id: user.userId!,
|
|
},
|
|
initiator,
|
|
stream,
|
|
})(dispatch, getState))
|
|
|
|
const newUsersMap = keyBy(users, 'userId')
|
|
Object.keys(peers)
|
|
.filter(id => !newUsersMap[id])
|
|
.forEach(id => peers[id].destroy())
|
|
}
|
|
}
|
|
|
|
export interface HandshakeOptions {
|
|
socket: ClientSocket
|
|
roomName: string
|
|
userId: string
|
|
stream?: MediaStream
|
|
}
|
|
|
|
export function handshake (options: HandshakeOptions) {
|
|
const { socket, roomName, stream, userId } = options
|
|
|
|
return (dispatch: Dispatch, getState: GetState) => {
|
|
const handler = new SocketHandler({
|
|
socket,
|
|
roomName,
|
|
stream,
|
|
dispatch,
|
|
getState,
|
|
userId,
|
|
})
|
|
|
|
// remove listeneres to make seocket reusable
|
|
socket.removeListener(constants.SOCKET_EVENT_SIGNAL, handler.handleSignal)
|
|
socket.removeListener(constants.SOCKET_EVENT_USERS, handler.handleUsers)
|
|
|
|
socket.on(constants.SOCKET_EVENT_SIGNAL, handler.handleSignal)
|
|
socket.on(constants.SOCKET_EVENT_USERS, handler.handleUsers)
|
|
|
|
debug('userId: %s', userId)
|
|
debug('emit ready for room: %s', roomName)
|
|
dispatch(NotifyActions.info('Ready for connections'))
|
|
socket.emit('ready', {
|
|
room: roomName,
|
|
userId,
|
|
})
|
|
}
|
|
}
|