From 70da0896580f0c7589d1623aa740642d4b6cb1db Mon Sep 17 00:00:00 2001 From: Titouan Rigoudy Date: Fri, 25 Mar 2016 14:47:58 +0100 Subject: [PATCH] Store socket in state, handle it with reducer. --- TODO.md | 1 - src/actions/RoomActions.js | 22 ++++++++++++++++ src/actions/RoomActionsFactory.js | 9 ++++--- src/actions/SocketActions.js | 29 +++++++++++++++++++++ src/actions/SocketActionsFactory.js | 40 ----------------------------- src/actions/SocketHandlerActions.js | 4 +-- src/components/ConnectForm.js | 8 +++--- src/components/SolsticeApp.js | 3 +-- src/containers/App.js | 15 ++++------- src/index.js | 1 - src/reducers/socket.js | 37 ++++++++++++++++++-------- src/utils/SocketClient.js | 31 ---------------------- 12 files changed, 95 insertions(+), 105 deletions(-) create mode 100644 src/actions/RoomActions.js create mode 100644 src/actions/SocketActions.js delete mode 100644 src/actions/SocketActionsFactory.js delete mode 100644 src/utils/SocketClient.js diff --git a/TODO.md b/TODO.md index 9ade6f6..65ff79e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,4 @@ Things to do: ------------- - - can the socket be stored in a reducer? - actually join rooms, display and send messages diff --git a/src/actions/RoomActions.js b/src/actions/RoomActions.js new file mode 100644 index 0000000..a0e5e0d --- /dev/null +++ b/src/actions/RoomActions.js @@ -0,0 +1,22 @@ +import { + ROOM_SELECT, + ROOM_JOIN +} from "../constants/ActionTypes"; + +import SocketActions from "./SocketActions"; + +import ControlRequest from "../utils/ControlRequest"; + +export default ({ + getRoomList: () => SocketActions.send(ControlRequest.roomList()), + + select: (room) => ({ + type: ROOM_SELECT, + payload: room + }), + + join: (room) => ({ + type: ROOM_JOIN, + paylod: room + }) +}); diff --git a/src/actions/RoomActionsFactory.js b/src/actions/RoomActionsFactory.js index 82b2097..a0e5e0d 100644 --- a/src/actions/RoomActionsFactory.js +++ b/src/actions/RoomActionsFactory.js @@ -2,12 +2,13 @@ import { ROOM_SELECT, ROOM_JOIN } from "../constants/ActionTypes"; + +import SocketActions from "./SocketActions"; + import ControlRequest from "../utils/ControlRequest"; -export default (socketActions) => ({ - getRoomList: () => { - return socketActions.send(ControlRequest.roomList()); - }, +export default ({ + getRoomList: () => SocketActions.send(ControlRequest.roomList()), select: (room) => ({ type: ROOM_SELECT, diff --git a/src/actions/SocketActions.js b/src/actions/SocketActions.js new file mode 100644 index 0000000..cbf7e5e --- /dev/null +++ b/src/actions/SocketActions.js @@ -0,0 +1,29 @@ +import { + SOCKET_RECEIVE_MESSAGE, + SOCKET_SEND_MESSAGE, + SOCKET_SET_CLOSED, + SOCKET_SET_CLOSING, + SOCKET_SET_ERROR, + SOCKET_SET_OPEN, + SOCKET_SET_OPENING +} from "../constants/ActionTypes"; + +export default ({ + open: (url, handlers) => ({ + type: SOCKET_SET_OPENING, + payload: { + url, + onclose: handlers.onclose, + onerror: handlers.onerror, + onopen: handlers.onopen, + onmessage: handlers.onmessage + } + }), + + close: () => ({ type: SOCKET_SET_CLOSING }), + + send: (message) => ({ + type: SOCKET_SEND_MESSAGE, + payload: message + }) +}); diff --git a/src/actions/SocketActionsFactory.js b/src/actions/SocketActionsFactory.js deleted file mode 100644 index 4a7bc83..0000000 --- a/src/actions/SocketActionsFactory.js +++ /dev/null @@ -1,40 +0,0 @@ -import { - SOCKET_SET_OPENING, SOCKET_SET_CLOSING, SOCKET_SEND_MESSAGE -} from "../constants/ActionTypes"; - -export default (socketClient) => ({ - open: url => { - const action = { type: SOCKET_SET_OPENING }; - try { - socketClient.open(url); - action.payload = url; - } catch (err) { - action.error = true; - action.payload = err; - } - return action; - }, - - close: () => { - const action = { type: SOCKET_SET_CLOSING }; - try { - socketClient.close(); - } catch (err) { - action.error = true; - action.payload = err; - } - return action; - }, - - send: message => { - const action = { type: SOCKET_SEND_MESSAGE }; - try { - socketClient.send(JSON.stringify(message)); - action.payload = message; - } catch (err) { - action.error = true; - action.payload = err; - } - return action; - } -}); diff --git a/src/actions/SocketHandlerActions.js b/src/actions/SocketHandlerActions.js index eced24f..ac79b7a 100644 --- a/src/actions/SocketHandlerActions.js +++ b/src/actions/SocketHandlerActions.js @@ -8,9 +8,7 @@ import { export default { onclose: event => ({ type: SOCKET_SET_CLOSED, - payload: { - code: event.code - } + payload: event.code }), onerror: event => ({ type: SOCKET_SET_ERROR }), diff --git a/src/components/ConnectForm.js b/src/components/ConnectForm.js index 3b5d970..98b6d73 100644 --- a/src/components/ConnectForm.js +++ b/src/components/ConnectForm.js @@ -6,9 +6,11 @@ import { STATE_CLOSED } from "../constants/socket"; import ControlRequest from "../utils/ControlRequest"; const ConnectForm = (props) => { - const { fields: { url }, handleSubmit, socket, socketOpen } = props; + const { fields: { url }, handleSubmit, socket, actions } = props; - const onSubmit = handleSubmit((values) => socketOpen(values.url)); + const onSubmit = handleSubmit((values) => { + return actions.socket.open(values.url, actions.socketHandlers); + }); return (
@@ -29,7 +31,7 @@ ConnectForm.propTypes = { fields: PropTypes.object.isRequired, handleSubmit: PropTypes.func.isRequired, socket: PropTypes.object.isRequired, - socketOpen: PropTypes.func.isRequired + actions: PropTypes.object.isRequired }; export default reduxForm({ diff --git a/src/components/SolsticeApp.js b/src/components/SolsticeApp.js index 66401c2..05b484c 100644 --- a/src/components/SolsticeApp.js +++ b/src/components/SolsticeApp.js @@ -15,8 +15,7 @@ const SolsticeApp = (props) => { if (socket.state !== STATE_OPEN ) { return (
- +
); } diff --git a/src/containers/App.js b/src/containers/App.js index 3a31357..42fe554 100644 --- a/src/containers/App.js +++ b/src/containers/App.js @@ -4,14 +4,12 @@ import React, {PropTypes} from "react"; import { bindActionCreators } from "redux"; import { connect } from "react-redux"; -import SocketActionsFactory from "../actions/SocketActionsFactory"; +import RoomActions from "../actions/RoomActions"; +import SocketActions from "../actions/SocketActions"; import SocketHandlerActions from "../actions/SocketHandlerActions"; -import RoomActionsFactory from "../actions/RoomActionsFactory"; import SolsticeApp from "../components/SolsticeApp"; -import SocketClient from "../utils/SocketClient"; - const App = (props) => (); App.propTypes = { @@ -22,14 +20,11 @@ App.propTypes = { const mapStateToProps = ({ socket }) => ({ socket }); function mapDispatchToProps(dispatch) { - const callbacks = bindActionCreators(SocketHandlerActions, dispatch); - const socketClient = new SocketClient(callbacks); - const socketActions = SocketActionsFactory(socketClient); - const roomActions = RoomActionsFactory(socketActions); return { actions: { - room: bindActionCreators(roomActions, dispatch), - socket: bindActionCreators(socketActions, dispatch) + room: bindActionCreators(RoomActions, dispatch), + socket: bindActionCreators(SocketActions, dispatch), + socketHandlers: bindActionCreators(SocketHandlerActions, dispatch) } }; } diff --git a/src/index.js b/src/index.js index e9d7607..bc95648 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,6 @@ import { Provider } from 'react-redux'; import App from './containers/App'; import configureStore from './store/configureStore'; import './styles/styles.scss'; //Yep, that's right. You can import SASS/CSS files too! Webpack will run the associated loader and plug this into the page. -import SocketClient from "./utils/SocketClient"; const store = configureStore(); diff --git a/src/reducers/socket.js b/src/reducers/socket.js index 5fba371..5184c8f 100644 --- a/src/reducers/socket.js +++ b/src/reducers/socket.js @@ -4,33 +4,50 @@ import { } from "../constants/socket.js"; const initialState = { - state: STATE_CLOSED, - url: null + state: STATE_CLOSED }; export default (state = initialState, action) => { switch (action.type) { case types.SOCKET_SET_OPENING: - if (action.error) { - return state; + { + if (state.state !== STATE_CLOSED) { + console.log("Cannot open socket, already open"); } - return { ...state, state: STATE_OPENING, url: action.payload }; + const { url, onopen, onclose, onerror, onmessage } = action.payload; + const socket = new WebSocket(url); + socket.onopen = onopen; + socket.onclose = onclose; + socket.onerror = onerror; + socket.onmessage = onmessage; + return { + ...state, + state: STATE_OPENING, + socket, + url + }; + } case types.SOCKET_SET_OPEN: return { ...state, state: STATE_OPEN }; case types.SOCKET_SET_CLOSING: - if (action.error) { - return state; - } + // Ooh bad stateful reducing... + state.socket.close(); return { ...state, state: STATE_CLOSING }; case types.SOCKET_SET_CLOSED: return { ...state, state: STATE_CLOSED }; case types.SOCKET_SET_ERROR: - if (state.state === STATE_OPENING) { - return { ...state, state: STATE_CLOSED }; + console.log("Socket error"); + return { ...state, state: state.socket.readyState }; + + case types.SOCKET_SEND_MESSAGE: + try { + state.socket.send(JSON.stringify(action.payload)); + } catch (err) { + console.log(`Socket error: failed to send ${action.payload}`); } return state; diff --git a/src/utils/SocketClient.js b/src/utils/SocketClient.js deleted file mode 100644 index df6ea2c..0000000 --- a/src/utils/SocketClient.js +++ /dev/null @@ -1,31 +0,0 @@ -import { bindActionCreators } from "redux"; -import objectAssign from "object-assign"; - -const STATE_CONNECTING = 0; -const STATE_OPEN = 1; -const STATE_CLOSING = 2; -const STATE_CLOSED = 3; - -class SocketClient { - constructor(callbacks) { - this.callbacks = callbacks; - } - - open(url) { - if (this.socket && this.socket.readyState !== STATE_CLOSED) { - throw new Error("SocketClient: socket already open"); - } - this.socket = new WebSocket(url); - objectAssign(this.socket, this.callbacks); - } - - close() { - this.socket.close(); - } - - send(message) { - this.socket.send(message); - } -} - -export default SocketClient;