From 703c13f296acc10edcebf54a98957484d39c5a42 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Fri, 15 Nov 2019 20:53:43 -0300 Subject: [PATCH 1/3] Add TypedSocket to server --- src/server/socket.ts | 6 ++---- src/shared/SocketEvent.ts | 28 ++++++++++++++++++++++++++++ src/shared/TypedEmitter.ts | 8 ++++++++ src/shared/index.ts | 2 ++ 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/shared/SocketEvent.ts create mode 100644 src/shared/index.ts 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..1014233 --- /dev/null +++ b/src/shared/SocketEvent.ts @@ -0,0 +1,28 @@ +import { TypedEmitter, TypedEmitterKeys } from './TypedEmitter' + +export interface User { + id: string +} + +export interface SocketEvent { + users: { + initiator: string + users: User[] + } + signal: { + userId: string + signal: unknown + } + connect: void + disconnect: void + ready: string +} + +export type ServerSocket = + Omit & + TypedEmitter & + { room?: string } + +export type TypedIO = SocketIO.Server & { + to(roomName: string): TypedEmitter +} diff --git a/src/shared/TypedEmitter.ts b/src/shared/TypedEmitter.ts index 6dd7acb..f761220 100644 --- a/src/shared/TypedEmitter.ts +++ b/src/shared/TypedEmitter.ts @@ -5,6 +5,14 @@ type Callback = (a: A) => void // eslint-disable-next-line type Events = Record +export type TypedEmitterKeys = + 'addListener' | + 'removeListener' | + 'on' | + 'once' | + 'off' | + 'emit' + export interface TypedEmitter extends EventEmitter { addListener(t: K, callback: Callback): this 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' From 7fa09fa6b826ca44f856b2a8cddfc50fb298a9c8 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Fri, 15 Nov 2019 23:38:19 -0300 Subject: [PATCH 2/3] Use TypedEmitter for client socket connections --- src/client/actions/CallActions.test.ts | 8 ++++---- src/client/actions/CallActions.ts | 3 ++- src/client/actions/PeerActions.test.ts | 5 +++-- src/client/actions/PeerActions.ts | 7 ++++--- src/client/actions/SocketActions.test.ts | 7 ++++--- src/client/actions/SocketActions.ts | 7 ++++--- src/client/socket.ts | 6 +++++- src/shared/SocketEvent.ts | 7 ++++--- src/shared/TypedEmitter.test.ts | 4 ++-- src/shared/TypedEmitter.ts | 12 +++++------- 10 files changed, 37 insertions(+), 29 deletions(-) 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..65dab1c 100644 --- a/src/client/actions/PeerActions.ts +++ b/src/client/actions/PeerActions.ts @@ -7,6 +7,7 @@ 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 @@ -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/shared/SocketEvent.ts b/src/shared/SocketEvent.ts index 1014233..9e7b5ce 100644 --- a/src/shared/SocketEvent.ts +++ b/src/shared/SocketEvent.ts @@ -11,10 +11,11 @@ export interface SocketEvent { } signal: { userId: string - signal: unknown + // eslint-disable-next-line + signal: any } - connect: void - disconnect: void + connect: undefined + disconnect: undefined ready: string } 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 f761220..c0add9d 100644 --- a/src/shared/TypedEmitter.ts +++ b/src/shared/TypedEmitter.ts @@ -1,21 +1,19 @@ -import { EventEmitter } from 'events' - type Callback = (a: A) => void // eslint-disable-next-line type Events = Record export type TypedEmitterKeys = - 'addListener' | 'removeListener' | 'on' | 'once' | 'off' | 'emit' -export interface TypedEmitter -extends EventEmitter { - addListener(t: K, callback: Callback): this +// 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 @@ -23,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 } From 92a5f2063c5b29d05febf09b7d7aff78705331d9 Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Fri, 15 Nov 2019 23:46:54 -0300 Subject: [PATCH 3/3] Use SignalData on server-side This should not cause compile issues when simple-peer is not installed (because client-side JS will be precompiled after pkg is published in NPM repository) because types are not used in runtime. --- src/client/actions/PeerActions.ts | 4 ++-- src/server/app.ts | 1 + src/server/socket.test.ts | 5 +++-- src/shared/SocketEvent.ts | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/client/actions/PeerActions.ts b/src/client/actions/PeerActions.ts index 65dab1c..59ff633 100644 --- a/src/client/actions/PeerActions.ts +++ b/src/client/actions/PeerActions.ts @@ -2,7 +2,7 @@ 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' @@ -42,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) 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/shared/SocketEvent.ts b/src/shared/SocketEvent.ts index 9e7b5ce..a0ae5bb 100644 --- a/src/shared/SocketEvent.ts +++ b/src/shared/SocketEvent.ts @@ -1,4 +1,5 @@ import { TypedEmitter, TypedEmitterKeys } from './TypedEmitter' +import { SignalData } from 'simple-peer' export interface User { id: string @@ -12,7 +13,7 @@ export interface SocketEvent { signal: { userId: string // eslint-disable-next-line - signal: any + signal: SignalData } connect: undefined disconnect: undefined