From 3a5b07c2189643f8255febd4bdb6ca8a956a38ee Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Wed, 13 Nov 2019 20:08:10 -0300 Subject: [PATCH] Fix broken ts build --- __mocks__/simple-peer.js | 3 +- src/client/actions/CallActions.test.ts | 9 +-- src/client/actions/CallActions.ts | 9 ++- src/client/actions/NotifyActions.ts | 40 +++++------ src/client/actions/PeerActions.test.ts | 4 +- src/client/actions/PeerActions.ts | 22 +++--- src/client/actions/SocketActions.ts | 14 ++-- src/client/async/index.ts | 3 + src/client/containers/App.test.tsx | 98 +++++++++++++++----------- src/client/reducers/alerts.test.ts | 19 ++--- src/client/reducers/messages.ts | 20 +++++- src/client/reducers/peers.ts | 9 +-- src/client/store.ts | 10 ++- src/{index.js => index.ts} | 1 - 14 files changed, 149 insertions(+), 112 deletions(-) create mode 100644 src/client/async/index.ts rename src/{index.js => index.ts} (97%) diff --git a/__mocks__/simple-peer.js b/__mocks__/simple-peer.js index 00345a1..b274693 100644 --- a/__mocks__/simple-peer.js +++ b/__mocks__/simple-peer.js @@ -1,6 +1,7 @@ +/* eslint-disable */ import EventEmitter from 'events' const Peer = jest.fn().mockImplementation(() => { - let peer = new EventEmitter() + const peer = new EventEmitter() peer.destroy = jest.fn() peer.signal = jest.fn() peer.send = jest.fn() diff --git a/src/client/actions/CallActions.test.ts b/src/client/actions/CallActions.test.ts index 9fcba4a..9b998e9 100644 --- a/src/client/actions/CallActions.test.ts +++ b/src/client/actions/CallActions.test.ts @@ -10,12 +10,14 @@ import socket from '../socket' import storeMock from '../store' import { callId, getUserMedia } from '../window' import { MockStore } from 'redux-mock-store' +import { bindActionCreators } from 'redux' jest.useFakeTimers() describe('reducers/alerts', () => { const store: MockStore = storeMock as any + const callActions = bindActionCreators(CallActions, store.dispatch) beforeEach(() => { store.clearActions(); @@ -31,7 +33,7 @@ describe('reducers/alerts', () => { describe('init', () => { it('calls handshake.init when connected & got camera stream', async () => { - const promise = CallActions.init(store.dispatch, store.getState) + const promise = callActions.init() socket.emit('connect') expect(store.getActions()).toEqual([{ type: constants.INIT_PENDING, @@ -60,8 +62,7 @@ describe('reducers/alerts', () => { }) it('calls dispatches disconnect message on disconnect', async () => { - - const promise = CallActions.init(store.dispatch, store.getState) + const promise = callActions.init() socket.emit('connect') socket.emit('disconnect') expect(store.getActions()).toEqual([{ @@ -102,7 +103,7 @@ describe('reducers/alerts', () => { it('dispatches alert when failed to get media stream', async () => { (getUserMedia as any).fail(true) - const promise = CallActions.init(store.dispatch, store.getState) + const promise = callActions.init() socket.emit('connect') await promise }) diff --git a/src/client/actions/CallActions.ts b/src/client/actions/CallActions.ts index 5279ebc..c746725 100644 --- a/src/client/actions/CallActions.ts +++ b/src/client/actions/CallActions.ts @@ -4,8 +4,7 @@ import * as StreamActions from './StreamActions' import * as constants from '../constants' import socket from '../socket' import { callId, getUserMedia } from '../window' -import { Dispatch } from 'redux' -import { GetState } from '../store' +import { Dispatch, GetState } from '../store' export interface InitAction { type: 'INIT' @@ -20,7 +19,7 @@ const initialize = (): InitializeAction => ({ type: 'INIT', }) -export const init = async (dispatch: Dispatch, getState: GetState) => { +export const init = () => async (dispatch: Dispatch, getState: GetState) => { dispatch(initialize()) const socket = await connect(dispatch) @@ -39,10 +38,10 @@ export async function connect (dispatch: Dispatch) { resolve(socket) }) socket.on('connect', () => { - NotifyActions.warning('Connected to server socket')(dispatch) + dispatch(NotifyActions.warning('Connected to server socket')) }) socket.on('disconnect', () => { - NotifyActions.error('Server socket disconnected')(dispatch) + dispatch(NotifyActions.error('Server socket disconnected')) }) }) } diff --git a/src/client/actions/NotifyActions.ts b/src/client/actions/NotifyActions.ts index 7ce0dad..72c3c16 100644 --- a/src/client/actions/NotifyActions.ts +++ b/src/client/actions/NotifyActions.ts @@ -1,7 +1,7 @@ import * as constants from '../constants' -import * as ChatActions from './ChatActions' -import { Dispatch } from 'redux' +import { Dispatch } from 'redux' import _ from 'underscore' +import { ThunkResult } from '../store' const TIMEOUT = 5000 @@ -13,21 +13,29 @@ function format (string: string, args: string[]) { export type NotifyType = 'info' | 'warning' | 'error' -const _notify = (type: NotifyType, args: string[]) => (dispatch: Dispatch) => { +function notify(dispatch: Dispatch, type: NotifyType, args: string[]) { const string = args[0] || '' const message = format(string, Array.prototype.slice.call(args, 1)) const id = _.uniqueId('notification') const payload: Notification = { id, type, message } - dispatch(addNotification(payload)) - dispatch(ChatActions.addMessage({ - userId: '[PeerCalls]', - message, - timestamp: new Date().toLocaleString(), - image: undefined, - })) + setTimeout(() => { dispatch(dismissNotification(id)) }, TIMEOUT) + + return addNotification(payload) +} + +export const info = (...args: any[]): ThunkResult => { + return dispatch => notify(dispatch, 'info', args) +} + +export const warning = (...args: any[]): ThunkResult => { + return dispatch => notify(dispatch, 'warning', args) +} + +export const error = (...args: any[]): ThunkResult => { + return dispatch => notify(dispatch, 'error', args) } function addNotification(payload: Notification): NotificationAddAction { @@ -60,18 +68,6 @@ export interface NotificationDismissAction { payload: { id: string } } -export function info (...args: any[]) { - return (dispatch: Dispatch) => _notify('info', args)(dispatch) -} - -export function warning (...args: any[]) { - return (dispatch: Dispatch) => _notify('warning', args)(dispatch) -} - -export function error (...args: any[]) { - return (dispatch: Dispatch) => _notify('error', args)(dispatch) -} - export interface NotificationClearAction { type: 'NOTIFY_CLEAR' } diff --git a/src/client/actions/PeerActions.test.ts b/src/client/actions/PeerActions.test.ts index b475acc..233aefb 100644 --- a/src/client/actions/PeerActions.test.ts +++ b/src/client/actions/PeerActions.test.ts @@ -167,9 +167,9 @@ describe('PeerActions', () => { PeerActions.sendMessage({ payload: 'test', type: 'text' })( dispatch, getState) const { peers } = store.getState() - expect(peers['user2'].send.mock.calls) + expect((peers['user2'].send as jest.Mock).mock.calls) .toEqual([[ '{"payload":"test","type":"text"}' ]]) - expect(peers['user3'].send.mock.calls) + expect((peers['user3'].send as jest.Mock).mock.calls) .toEqual([[ '{"payload":"test","type":"text"}' ]]) }) diff --git a/src/client/actions/PeerActions.ts b/src/client/actions/PeerActions.ts index 9879b54..70539ba 100644 --- a/src/client/actions/PeerActions.ts +++ b/src/client/actions/PeerActions.ts @@ -6,7 +6,7 @@ import Peer from 'simple-peer' import _ from 'underscore' import _debug from 'debug' import { play, iceServers } from '../window' -import { Dispatch } from 'redux' +import { Dispatch, GetState } from '../store' const debug = _debug('peercalls') @@ -14,8 +14,6 @@ export interface Peers { [id: string]: Peer.Instance } -export type GetState = () => { peers: Peers } - export interface PeerHandlerOptions { socket: SocketIOClient.Socket user: { id: string } @@ -38,7 +36,7 @@ class PeerHandler { handleError = (err: Error) => { const { dispatch, getState, user } = this debug('peer: %s, error %s', user.id, err.stack) - NotifyActions.error('A peer connection error occurred')(dispatch) + dispatch(NotifyActions.error('A peer connection error occurred')) const peer = getState().peers[user.id] peer && peer.destroy() dispatch(removePeer(user.id)) @@ -53,7 +51,7 @@ class PeerHandler { handleConnect = () => { const { dispatch, user } = this debug('peer: %s, connect', user.id) - NotifyActions.warning('Peer connection established')(dispatch) + dispatch(NotifyActions.warning('Peer connection established')) play() } handleStream = (stream: MediaStream) => { @@ -64,10 +62,10 @@ class PeerHandler { stream, })) } - handleData = (object: any) => { + handleData = (buffer: ArrayBuffer) => { const { dispatch, user } = this - const message = JSON.parse(new window.TextDecoder('utf-8').decode(object)) - debug('peer: %s, message: %o', user.id, object) + const message = JSON.parse(new window.TextDecoder('utf-8').decode(buffer)) + debug('peer: %s, message: %o', user.id, buffer) switch (message.type) { case 'file': dispatch(ChatActions.addMessage({ @@ -89,7 +87,7 @@ class PeerHandler { handleClose = () => { const { dispatch, user } = this debug('peer: %s, close', user.id) - NotifyActions.error('Peer connection closed')(dispatch) + dispatch(NotifyActions.error('Peer connection closed')) dispatch(StreamActions.removeStream(user.id)) dispatch(removePeer(user.id)) } @@ -116,11 +114,11 @@ export function createPeer (options: CreatePeerOptions) { return (dispatch: Dispatch, getState: GetState) => { const userId = user.id debug('create peer: %s, stream:', userId, stream) - NotifyActions.warning('Connecting to peer...')(dispatch) + dispatch(NotifyActions.warning('Connecting to peer...')) const oldPeer = getState().peers[userId] if (oldPeer) { - NotifyActions.info('Cleaning up old connection...')(dispatch) + dispatch(NotifyActions.info('Cleaning up old connection...')) oldPeer.destroy() dispatch(removePeer(userId)) } @@ -244,7 +242,7 @@ export const sendFile = (file: File) => async (dispatch: Dispatch, getState: GetState) => { const { name, size, type } = file if (!window.FileReader) { - NotifyActions.error('File API is not supported by your browser')(dispatch) + dispatch(NotifyActions.error('File API is not supported by your browser')) return } const reader = new window.FileReader() diff --git a/src/client/actions/SocketActions.ts b/src/client/actions/SocketActions.ts index 96c8ef9..7fb67c4 100644 --- a/src/client/actions/SocketActions.ts +++ b/src/client/actions/SocketActions.ts @@ -3,8 +3,8 @@ import * as PeerActions from '../actions/PeerActions' import * as constants from '../constants' import _ from 'underscore' import _debug from 'debug' -import { Dispatch } from 'redux' import { SignalData } from 'simple-peer' +import { Dispatch, GetState } from '../store' const debug = _debug('peercalls') @@ -13,7 +13,7 @@ export interface SocketHandlerOptions { roomName: string stream?: MediaStream dispatch: Dispatch - getState: PeerActions.GetState + getState: GetState } export interface SignalOptions { @@ -31,7 +31,7 @@ class SocketHandler { roomName: string stream?: MediaStream dispatch: Dispatch - getState: PeerActions.GetState + getState: GetState constructor (options: SocketHandlerOptions) { this.socket = options.socket @@ -50,8 +50,8 @@ class SocketHandler { handleUsers = ({ initiator, users }: UsersOptions) => { const { socket, stream, dispatch, getState } = this debug('socket users: %o', users) - NotifyActions.info('Connected users: {0}', users.length)(dispatch) - const { peers } = getState() + this.dispatch(NotifyActions.info('Connected users: {0}', users.length)) + const { peers } = this.getState() users .filter(user => !peers[user.id] && user.id !== socket.id) @@ -78,7 +78,7 @@ export interface HandshakeOptions { export function handshake (options: HandshakeOptions) { const { socket, roomName, stream } = options - return (dispatch: Dispatch, getState: PeerActions.GetState) => { + return (dispatch: Dispatch, getState: GetState) => { const handler = new SocketHandler({ socket, roomName, @@ -92,7 +92,7 @@ export function handshake (options: HandshakeOptions) { debug('socket.id: %s', socket.id) debug('emit ready for room: %s', roomName) - NotifyActions.info('Ready for connections')(dispatch) + dispatch(NotifyActions.info('Ready for connections')) socket.emit('ready', roomName) } } diff --git a/src/client/async/index.ts b/src/client/async/index.ts new file mode 100644 index 0000000..01cf0cf --- /dev/null +++ b/src/client/async/index.ts @@ -0,0 +1,3 @@ +export * from './action' +export * from './middleware' +export * from './reducer' diff --git a/src/client/containers/App.test.tsx b/src/client/containers/App.test.tsx index 775ada6..54a1a1d 100644 --- a/src/client/containers/App.test.tsx +++ b/src/client/containers/App.test.tsx @@ -2,98 +2,114 @@ jest.mock('../actions/CallActions') jest.mock('../socket') jest.mock('../window') -import * as constants from '../constants' -import App from './App' import React from 'react' import ReactDOM from 'react-dom' import TestUtils from 'react-dom/test-utils' -import configureStore from 'redux-mock-store' -import reducers from '../reducers' -import { MediaStream } from '../window' import { Provider } from 'react-redux' +import { AnyAction, applyMiddleware, createStore } from 'redux' import { init } from '../actions/CallActions' -import { middlewares } from '../store' +import { Alert } from '../actions/NotifyActions' +import * as constants from '../constants' +import reducers from '../reducers' +import { middlewares, State, Store } from '../store' +import { MediaStream } from '../window' +import App from './App' describe('App', () => { const initAction = { type: 'INIT' } - let state + let store: Store + let state: Partial + let dispatchSpy: jest.SpyInstance beforeEach(() => { - init.mockReturnValue(initAction) - state = reducers() + state = {}; + (init as jest.Mock).mockReturnValue(initAction) + + dispatchSpy = jest.spyOn(store, 'dispatch') window.HTMLMediaElement.prototype.play = jest.fn() }) - let component, node, store - function render () { - store = configureStore(middlewares)(state) - component = TestUtils.renderIntoDocument( - - - + afterEach(() => { + dispatchSpy.mockReset() + dispatchSpy.mockRestore() + }) + + let node: Element + async function render () { + store = createStore( + reducers, + state, + applyMiddleware(...middlewares), ) - node = ReactDOM.findDOMNode(component) + const div = document.createElement('div') + await new Promise(resolve => { + ReactDOM.render( + +
resolve(app!)}> + +
+
, + div, + ) + }) } describe('render', () => { it('renders without issues', () => { render() expect(node).toBeTruthy() - expect(init.mock.calls.length).toBe(1) + expect((init as jest.Mock).mock.calls.length).toBe(1) }) }) describe('state', () => { - let alert + let alert: Alert beforeEach(() => { state.streams = { test: { mediaStream: new MediaStream(), - url: 'blob://' - } + url: 'blob://', + }, } state.peers = { - test: {} + test: {} as any, } - state.notifications = state.notifications.merge({ + state.notifications = { 'notification1': { id: 'notification1', message: 'test', - type: 'warning' - } - }) - const alerts = state.alerts.asMutable() - alert = { + type: 'warning', + }, + } + state.alerts = [{ dismissable: true, action: 'Dismiss', - message: 'test alert' - } - alerts.push(alert) - state.alerts = alerts + message: 'test alert', + type: 'info', + }] render() - store.clearActions() }) describe('alerts', () => { it('can be dismissed', () => { - const dismiss = node.querySelector('.action-alert-dismiss') + const dismiss = node.querySelector('.action-alert-dismiss')! TestUtils.Simulate.click(dismiss) - expect(store.getActions()).toEqual([{ + expect(dispatchSpy.mock.calls).toEqual([[{ type: constants.ALERT_DISMISS, - payload: alert - }]) + payload: alert, + }]]) }) }) describe('video', () => { it('can be activated', () => { - const video = node.querySelector('video') + const video = node.querySelector('video')! TestUtils.Simulate.click(video) - expect(store.getActions()).toEqual([{ + expect(dispatchSpy.mock.calls).toEqual([[{ type: constants.ACTIVE_TOGGLE, - payload: { userId: constants.ME } - }]) + payload: { userId: constants.ME }, + }]]) }) }) diff --git a/src/client/reducers/alerts.test.ts b/src/client/reducers/alerts.test.ts index 4174a01..16df75f 100644 --- a/src/client/reducers/alerts.test.ts +++ b/src/client/reducers/alerts.test.ts @@ -1,6 +1,6 @@ import * as NotifyActions from '../actions/NotifyActions' import _ from 'underscore' -import { applyMiddleware, createStore, Store } from 'redux' +import { applyMiddleware, createStore, Store, bindActionCreators } from 'redux' import { create } from '../middlewares' import reducers from './index' @@ -9,21 +9,24 @@ jest.useFakeTimers() describe('reducers/alerts', () => { let store: Store + let notifyActions: typeof NotifyActions beforeEach(() => { store = createStore( reducers, applyMiddleware(...create()), ) + notifyActions = bindActionCreators(NotifyActions, store.dispatch) }) + describe('clearAlert', () => { [true, false].forEach(dismissable => { beforeEach(() => { - store.dispatch(NotifyActions.clearAlerts()) + notifyActions.clearAlerts() }) it('adds alert to store', () => { - store.dispatch(NotifyActions.alert('test', dismissable)) + notifyActions.alert('test', dismissable) expect(store.getState().alerts).toEqual([{ action: dismissable ? 'Dismiss' : undefined, dismissable, @@ -69,7 +72,7 @@ describe('reducers/alerts', () => { describe(type, () => { beforeEach(() => { - NotifyActions[type]('Hi {0}!', 'John')(store.dispatch) + notifyActions[type]('Hi {0}!', 'John') }) it('adds a notification', () => { @@ -86,7 +89,7 @@ describe('reducers/alerts', () => { }) it('does not fail when no arguments', () => { - NotifyActions[type]()(store.dispatch) + notifyActions[type]() }) }) @@ -96,9 +99,9 @@ describe('reducers/alerts', () => { describe('clear', () => { it('clears all alerts', () => { - NotifyActions.info('Hi {0}!', 'John')(store.dispatch) - NotifyActions.warning('Hi {0}!', 'John')(store.dispatch) - NotifyActions.error('Hi {0}!', 'John')(store.dispatch) + notifyActions.info('Hi {0}!', 'John') + notifyActions.warning('Hi {0}!', 'John') + notifyActions.error('Hi {0}!', 'John') store.dispatch(NotifyActions.clear()) expect(store.getState().notifications).toEqual({}) }) diff --git a/src/client/reducers/messages.ts b/src/client/reducers/messages.ts index b26481b..e9c296e 100644 --- a/src/client/reducers/messages.ts +++ b/src/client/reducers/messages.ts @@ -1,14 +1,28 @@ import * as constants from '../constants' import { Message, MessageAddAction } from '../actions/ChatActions' +import { NotificationAddAction } from '../actions/NotifyActions' export type MessagesState = Message[] const defaultState: MessagesState = [] +function convertNotificationToMessage(action: NotificationAddAction): Message { + return { + userId: '[PeerCalls]', + message: action.payload.message, + timestamp: new Date().toLocaleString(), + } +} + export default function messages ( - state = defaultState, action: MessageAddAction, -) { - switch (action && action.type) { + state = defaultState, action: MessageAddAction | NotificationAddAction, +): MessagesState { + switch (action.type) { + case constants.NOTIFY: + return [ + ...state, + convertNotificationToMessage(action), + ] case constants.MESSAGE_ADD: return [...state, action.payload] default: diff --git a/src/client/reducers/peers.ts b/src/client/reducers/peers.ts index aab9de4..5eb5e6f 100644 --- a/src/client/reducers/peers.ts +++ b/src/client/reducers/peers.ts @@ -3,13 +3,14 @@ import _ from 'underscore' import Peer from 'simple-peer' import { PeerAction } from '../actions/PeerActions' -export interface PeersState { - [userId: string]: Peer.Instance -} +export type PeersState = Record const defaultState: PeersState = {} -export default function peers (state = defaultState, action: PeerAction) { +export default function peers( + state = defaultState, + action: PeerAction, +): PeersState { switch (action.type) { case constants.PEER_ADD: return { diff --git a/src/client/store.ts b/src/client/store.ts index 6977312..5e069e0 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -1,13 +1,16 @@ +import { Action, applyMiddleware, createStore as _createStore, Store as ReduxStore } from 'redux' +import { ThunkAction, ThunkDispatch } from 'redux-thunk' import { create } from './middlewares' +import { middleware as asyncMiddleware }from './async' import reducers from './reducers' -import { applyMiddleware, createStore as _createStore, Store as ReduxStore } from 'redux' + export const middlewares = create( window.localStorage && window.localStorage.log, ) export const createStore = () => _createStore( reducers, - applyMiddleware(...middlewares), + applyMiddleware(...middlewares, asyncMiddleware), ) export default createStore() @@ -17,3 +20,6 @@ export type Store = ReturnType type TGetState = T extends ReduxStore ? State : never export type State = TGetState export type GetState = () => State + +export type Dispatch = ThunkDispatch +export type ThunkResult = ThunkAction diff --git a/src/index.js b/src/index.ts similarity index 97% rename from src/index.js rename to src/index.ts index 5c7199a..fc00c07 100755 --- a/src/index.js +++ b/src/index.ts @@ -1,5 +1,4 @@ #!/usr/bin/env node -'use strict' if (!process.env.DEBUG) { process.env.DEBUG = 'peercalls' }