diff --git a/src/client/actions/CallActions.test.ts b/src/client/actions/CallActions.test.ts index 8da26eb..8d03a08 100644 --- a/src/client/actions/CallActions.test.ts +++ b/src/client/actions/CallActions.test.ts @@ -46,7 +46,7 @@ describe('CallActions', () => { it('calls handshake.init when connected & got camera stream', async () => { const promise = callActions.init() - socket.emit('connect') + socket.emit('connect', undefined) await promise expect(store.getState().allActions.slice(1)).toEqual([{ type: constants.NOTIFY, @@ -73,8 +73,8 @@ describe('CallActions', () => { it('calls dispatches disconnect message on disconnect', async () => { const promise = callActions.init() - socket.emit('connect') - socket.emit('disconnect') + socket.emit('connect', undefined) + socket.emit('disconnect', undefined) expect(store.getState().allActions.slice(1)).toEqual([{ type: constants.NOTIFY, payload: { @@ -96,7 +96,7 @@ describe('CallActions', () => { it('dispatches alert when failed to get media stream', async () => { (getUserMedia as any).fail(true) const promise = callActions.init() - socket.emit('connect') + socket.emit('connect', undefined) await promise }) diff --git a/src/client/actions/CallActions.ts b/src/client/actions/CallActions.ts index 3201db0..8b8ba61 100644 --- a/src/client/actions/CallActions.ts +++ b/src/client/actions/CallActions.ts @@ -2,6 +2,7 @@ import * as constants from '../constants' import socket from '../socket' import { Dispatch, ThunkResult } from '../store' import { callId, getUserMedia } from '../window' +import { ClientSocket } from '../socket' import * as NotifyActions from './NotifyActions' import * as SocketActions from './SocketActions' import * as StreamActions from './StreamActions' @@ -34,7 +35,7 @@ async (dispatch, getState) => { } export const connect = () => (dispatch: Dispatch) => { - return new Promise(resolve => { + return new Promise(resolve => { socket.once('connect', () => { resolve(socket) }) diff --git a/src/client/actions/PeerActions.test.ts b/src/client/actions/PeerActions.test.ts index b41895f..91441d7 100644 --- a/src/client/actions/PeerActions.test.ts +++ b/src/client/actions/PeerActions.test.ts @@ -7,15 +7,16 @@ import { EventEmitter } from 'events' import { createStore, Store, GetState } from '../store' import { play } from '../window' import { Dispatch } from 'redux' +import { ClientSocket } from '../socket' describe('PeerActions', () => { function createSocket () { - const socket = new EventEmitter() as unknown as SocketIOClient.Socket + const socket = new EventEmitter() as unknown as ClientSocket socket.id = 'user1' return socket } - let socket: SocketIOClient.Socket + let socket: ClientSocket let stream: MediaStream let user: { id: string } let store: Store diff --git a/src/client/actions/PeerActions.ts b/src/client/actions/PeerActions.ts index 502dcbb..59ff633 100644 --- a/src/client/actions/PeerActions.ts +++ b/src/client/actions/PeerActions.ts @@ -2,11 +2,12 @@ import * as ChatActions from '../actions/ChatActions' import * as NotifyActions from '../actions/NotifyActions' import * as StreamActions from '../actions/StreamActions' import * as constants from '../constants' -import Peer from 'simple-peer' +import Peer, { SignalData } from 'simple-peer' import forEach from 'lodash/forEach' import _debug from 'debug' import { play, iceServers } from '../window' import { Dispatch, GetState } from '../store' +import { ClientSocket } from '../socket' const debug = _debug('peercalls') @@ -15,14 +16,14 @@ export interface Peers { } export interface PeerHandlerOptions { - socket: SocketIOClient.Socket + socket: ClientSocket user: { id: string } dispatch: Dispatch getState: GetState } class PeerHandler { - socket: SocketIOClient.Socket + socket: ClientSocket user: { id: string } dispatch: Dispatch getState: GetState @@ -41,7 +42,7 @@ class PeerHandler { peer && peer.destroy() dispatch(removePeer(user.id)) } - handleSignal = (signal: unknown) => { + handleSignal = (signal: SignalData) => { const { socket, user } = this debug('peer: %s, signal: %o', user.id, signal) @@ -94,7 +95,7 @@ class PeerHandler { } export interface CreatePeerOptions { - socket: SocketIOClient.Socket + socket: ClientSocket user: { id: string } initiator: string stream?: MediaStream diff --git a/src/client/actions/SocketActions.test.ts b/src/client/actions/SocketActions.test.ts index 735a5b6..6adaee4 100644 --- a/src/client/actions/SocketActions.test.ts +++ b/src/client/actions/SocketActions.test.ts @@ -6,12 +6,13 @@ import * as constants from '../constants' import Peer from 'simple-peer' import { EventEmitter } from 'events' import { createStore, Store, GetState } from '../store' +import { ClientSocket } from '../socket' import { Dispatch } from 'redux' describe('SocketActions', () => { const roomName = 'bla' - let socket: SocketIOClient.Socket + let socket: ClientSocket let store: Store let dispatch: Dispatch let getState: GetState @@ -67,7 +68,7 @@ describe('SocketActions', () => { it('should forward signal to peer', () => { socket.emit('signal', { userId: 'b', - data, + signal: data, }) expect(instances.length).toBe(1) @@ -77,7 +78,7 @@ describe('SocketActions', () => { it('does nothing if no peer', () => { socket.emit('signal', { userId: 'a', - data, + signal: data, }) expect(instances.length).toBe(1) diff --git a/src/client/actions/SocketActions.ts b/src/client/actions/SocketActions.ts index f164465..27913a1 100644 --- a/src/client/actions/SocketActions.ts +++ b/src/client/actions/SocketActions.ts @@ -5,11 +5,12 @@ import keyBy from 'lodash/keyBy' import _debug from 'debug' import { SignalData } from 'simple-peer' import { Dispatch, GetState } from '../store' +import { ClientSocket } from '../socket' const debug = _debug('peercalls') export interface SocketHandlerOptions { - socket: SocketIOClient.Socket + socket: ClientSocket roomName: string stream?: MediaStream dispatch: Dispatch @@ -27,7 +28,7 @@ export interface UsersOptions { } class SocketHandler { - socket: SocketIOClient.Socket + socket: ClientSocket roomName: string stream?: MediaStream dispatch: Dispatch @@ -70,7 +71,7 @@ class SocketHandler { } export interface HandshakeOptions { - socket: SocketIOClient.Socket + socket: ClientSocket roomName: string stream?: MediaStream } diff --git a/src/client/socket.ts b/src/client/socket.ts index 3d6a3e0..4b3a92d 100644 --- a/src/client/socket.ts +++ b/src/client/socket.ts @@ -1,3 +1,7 @@ import SocketIOClient from 'socket.io-client' import { baseUrl } from './window' -export default SocketIOClient('', { path: baseUrl + '/ws' }) +import { TypedEmitterKeys, SocketEvent, TypedEmitter } from '../shared' +export type ClientSocket = Omit & + TypedEmitter +const socket: ClientSocket = SocketIOClient('', { path: baseUrl + '/ws' }) +export default socket diff --git a/src/server/app.ts b/src/server/app.ts index f3cba76..7ba1f32 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -33,5 +33,6 @@ router.use('/', index) app.use(BASE_URL, router) io.on('connection', socket => handleSocket(socket, io)) +io.sockets.adapter.rooms['test'].sockets export default server diff --git a/src/server/socket.test.ts b/src/server/socket.test.ts index 78c7333..9736e74 100644 --- a/src/server/socket.test.ts +++ b/src/server/socket.test.ts @@ -1,6 +1,7 @@ import { EventEmitter } from 'events' +import { Socket } from 'socket.io' +import { TypedIO } from '../shared' import handleSocket from './socket' -import { Socket, Server } from 'socket.io' describe('server/socket', () => { type SocketMock = Socket & { @@ -12,7 +13,7 @@ describe('server/socket', () => { } let socket: SocketMock - let io: Server & { + let io: TypedIO & { in: jest.Mock<(room: string) => SocketMock> to: jest.Mock<(room: string) => SocketMock> } diff --git a/src/server/socket.ts b/src/server/socket.ts index 64d7d0e..9305bc4 100644 --- a/src/server/socket.ts +++ b/src/server/socket.ts @@ -1,13 +1,11 @@ 'use strict' import _debug from 'debug' import map from 'lodash/map' -import { Socket, Server } from 'socket.io' +import { ServerSocket, TypedIO } from '../shared' const debug = _debug('peercalls:socket') -type SocketWithRoom = Socket & { room?: string } - -export default function handleSocket(socket: SocketWithRoom, io: Server) { +export default function handleSocket(socket: ServerSocket, io: TypedIO) { socket.on('signal', payload => { // debug('signal: %s, payload: %o', socket.id, payload) io.to(payload.userId).emit('signal', { diff --git a/src/shared/SocketEvent.ts b/src/shared/SocketEvent.ts new file mode 100644 index 0000000..a0ae5bb --- /dev/null +++ b/src/shared/SocketEvent.ts @@ -0,0 +1,30 @@ +import { TypedEmitter, TypedEmitterKeys } from './TypedEmitter' +import { SignalData } from 'simple-peer' + +export interface User { + id: string +} + +export interface SocketEvent { + users: { + initiator: string + users: User[] + } + signal: { + userId: string + // eslint-disable-next-line + signal: SignalData + } + connect: undefined + disconnect: undefined + ready: string +} + +export type ServerSocket = + Omit & + TypedEmitter & + { room?: string } + +export type TypedIO = SocketIO.Server & { + to(roomName: string): TypedEmitter +} diff --git a/src/shared/TypedEmitter.test.ts b/src/shared/TypedEmitter.test.ts index 11b93c8..4fe0eb6 100644 --- a/src/shared/TypedEmitter.test.ts +++ b/src/shared/TypedEmitter.test.ts @@ -38,12 +38,12 @@ describe('TypedEmitter', () => { let emitter: TypedEmitter beforeEach(() => { emitter = new EventEmitter() - emitter.addListener('test1', listener1) + emitter.on('test1', listener1) emitter.on('test2', listener2) emitter.once('test3', listener3) }) - describe('addListener & on', () => { + describe('on & on', () => { it('adds an event emitter', () => { emitter.emit('test1', 'value') emitter.emit('test2', 3) diff --git a/src/shared/TypedEmitter.ts b/src/shared/TypedEmitter.ts index 6dd7acb..c0add9d 100644 --- a/src/shared/TypedEmitter.ts +++ b/src/shared/TypedEmitter.ts @@ -1,13 +1,19 @@ -import { EventEmitter } from 'events' - type Callback = (a: A) => void // eslint-disable-next-line type Events = Record -export interface TypedEmitter -extends EventEmitter { - addListener(t: K, callback: Callback): this +export type TypedEmitterKeys = + 'removeListener' | + 'on' | + 'once' | + 'off' | + 'emit' + +// Some methods might be missing and we do not extend EventEmitter because +// SocketIOClient.Socket does not inherit from EventEmitter, and the method +// signatures differ slightly. +export interface TypedEmitter { removeListener(t: K, callback: Callback): this on(t: K, callback: Callback): this @@ -15,5 +21,5 @@ extends EventEmitter { off(t: K, callback: Callback): this - emit(t: K, value: E[K]): boolean + emit(t: K, value: E[K]): void } diff --git a/src/shared/index.ts b/src/shared/index.ts new file mode 100644 index 0000000..231f83e --- /dev/null +++ b/src/shared/index.ts @@ -0,0 +1,2 @@ +export * from './SocketEvent' +export * from './TypedEmitter'