mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-07-18 10:49:02 +00:00
Improve reconnect handling
This commit is contained in:
parent
f4eb1ae4b0
commit
409376daf0
@ -4,15 +4,7 @@ import {
|
||||
process as processMessage,
|
||||
prepare as prepareMessage,
|
||||
} from 'utils/message'
|
||||
import { getIO } from 'utils/socket'
|
||||
|
||||
export const createRoom = id => async dispatch => fetch({
|
||||
resourceName: 'handshake',
|
||||
method: 'POST',
|
||||
body: {
|
||||
roomId: id,
|
||||
},
|
||||
}, dispatch, 'handshake')
|
||||
import { getSocket } from 'utils/socket'
|
||||
|
||||
export const receiveSocketMessage = payload => async (dispatch, getState) => {
|
||||
const state = getState()
|
||||
@ -26,7 +18,7 @@ export const createUser = payload => async (dispatch) => {
|
||||
}
|
||||
|
||||
export const sendUserEnter = payload => async () => {
|
||||
getIO().emit('USER_ENTER', {
|
||||
getSocket().emit('USER_ENTER', {
|
||||
publicKey: payload.publicKey,
|
||||
})
|
||||
}
|
||||
@ -34,6 +26,11 @@ export const sendUserEnter = payload => async () => {
|
||||
export const receiveUserExit = payload => async (dispatch, getState) => {
|
||||
const state = getState()
|
||||
const exitingUser = state.room.members.find(m => !payload.map(p => JSON.stringify(p.publicKey)).includes(JSON.stringify(m.publicKey)))
|
||||
|
||||
if (!exitingUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exitingUserId = exitingUser.id
|
||||
const exitingUsername = exitingUser.username
|
||||
|
||||
@ -59,12 +56,12 @@ export const sendSocketMessage = payload => async (dispatch, getState) => {
|
||||
const state = getState()
|
||||
const msg = await prepareMessage(payload, state)
|
||||
dispatch({ type: `SEND_SOCKET_MESSAGE_${msg.original.type}`, payload: msg.original.payload })
|
||||
getIO().emit('PAYLOAD', msg.toSend)
|
||||
getSocket().emit('PAYLOAD', msg.toSend)
|
||||
}
|
||||
|
||||
export const toggleLockRoom = () => async (dispatch, getState) => {
|
||||
const state = getState()
|
||||
getIO().emit('TOGGLE_LOCK_ROOM', null, (res) => {
|
||||
getSocket().emit('TOGGLE_LOCK_ROOM', null, (res) => {
|
||||
dispatch({
|
||||
type: 'TOGGLE_LOCK_ROOM',
|
||||
payload: {
|
||||
@ -82,6 +79,7 @@ export const receiveToggleLockRoom = payload => async (dispatch, getState) => {
|
||||
const lockedByUser = state.room.members.find(m => isEqual(m.publicKey, payload.publicKey))
|
||||
const lockedByUsername = lockedByUser.username
|
||||
const lockedByUserId = lockedByUser.id
|
||||
console.log('locked by', lockedByUserId);
|
||||
|
||||
dispatch({
|
||||
type: 'RECEIVE_TOGGLE_LOCK_ROOM',
|
||||
@ -96,3 +94,12 @@ export const receiveToggleLockRoom = payload => async (dispatch, getState) => {
|
||||
export const clearActivities = () => async (dispatch) => {
|
||||
dispatch({ type: 'CLEAR_ACTIVITIES' })
|
||||
}
|
||||
|
||||
export const onConnected = payload => async (dispatch) => {
|
||||
dispatch({ type: 'CONNECTED', payload })
|
||||
}
|
||||
|
||||
export const sendUserDisconnect = () => async () => {
|
||||
getSocket().emit('USER_DISCONNECT')
|
||||
}
|
||||
|
||||
|
@ -43,56 +43,32 @@ export default class Home extends Component {
|
||||
async componentWillMount() {
|
||||
const roomId = encodeURI(this.props.match.params.roomId)
|
||||
|
||||
const res = await this.props.createRoom(roomId)
|
||||
|
||||
if (res.json.isLocked) {
|
||||
this.props.openModal('Room Locked')
|
||||
return
|
||||
}
|
||||
|
||||
if (res.json.size === 1) {
|
||||
this.props.openModal('Welcome')
|
||||
}
|
||||
|
||||
const user = await this.createUser()
|
||||
|
||||
const io = connect(roomId)
|
||||
const socket = connect(roomId)
|
||||
|
||||
const disconnectEvents = [
|
||||
'reconnect_failed',
|
||||
'connect_timeout',
|
||||
'connect_error',
|
||||
'disconnect',
|
||||
'reconnect',
|
||||
'reconnect_error',
|
||||
'reconnecting',
|
||||
'reconnect_attempt',
|
||||
]
|
||||
|
||||
disconnectEvents.forEach((evt) => {
|
||||
io.on(evt, () => {
|
||||
socket.on(evt, () => {
|
||||
this.props.toggleSocketConnected(false)
|
||||
})
|
||||
})
|
||||
|
||||
const connectEvents = [
|
||||
'connect',
|
||||
'reconnect',
|
||||
]
|
||||
|
||||
connectEvents.forEach((evt) => {
|
||||
io.on(evt, () => {
|
||||
if (evt === 'connect') {
|
||||
if (!this.hasConnected) {
|
||||
this.initApp(user)
|
||||
this.hasConnected = true
|
||||
}
|
||||
}
|
||||
socket.on(evt, () => {
|
||||
this.initApp(user)
|
||||
this.props.toggleSocketConnected(true)
|
||||
})
|
||||
})
|
||||
|
||||
io.on('USER_ENTER', (payload) => {
|
||||
socket.on('USER_ENTER', (payload) => {
|
||||
this.props.receiveUserEnter(payload)
|
||||
this.props.sendSocketMessage({
|
||||
type: 'ADD_USER',
|
||||
@ -105,17 +81,29 @@ export default class Home extends Component {
|
||||
})
|
||||
})
|
||||
|
||||
io.on('USER_EXIT', (payload) => {
|
||||
socket.on('USER_EXIT', (payload) => {
|
||||
this.props.receiveUserExit(payload)
|
||||
})
|
||||
|
||||
io.on('PAYLOAD', (payload) => {
|
||||
socket.on('PAYLOAD', (payload) => {
|
||||
this.props.receiveSocketMessage(payload)
|
||||
})
|
||||
|
||||
io.on('TOGGLE_LOCK_ROOM', (payload) => {
|
||||
socket.on('TOGGLE_LOCK_ROOM', (payload) => {
|
||||
this.props.receiveToggleLockRoom(payload)
|
||||
})
|
||||
|
||||
socket.on('CONNECTED', (payload) => {
|
||||
this.props.onConnected(payload);
|
||||
});
|
||||
|
||||
socket.on('ROOM_LOCKED', (payload) => {
|
||||
this.props.openModal('Room Locked')
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', (evt) => {
|
||||
this.props.sendUserDisconnect();
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -135,7 +123,7 @@ export default class Home extends Component {
|
||||
|
||||
Tinycon.setBubble(nextProps.faviconCount)
|
||||
|
||||
if (nextProps.faviconCount !== 0 && this.props.soundIsEnabled) {
|
||||
if (nextProps.faviconCount !== 0 && nextProps.faviconCount !== this.props.faviconCount && this.props.soundIsEnabled) {
|
||||
this.beep.play()
|
||||
}
|
||||
}
|
||||
@ -423,7 +411,6 @@ Home.defaultProps = {
|
||||
}
|
||||
|
||||
Home.propTypes = {
|
||||
createRoom: PropTypes.func.isRequired,
|
||||
receiveSocketMessage: PropTypes.func.isRequired,
|
||||
sendSocketMessage: PropTypes.func.isRequired,
|
||||
createUser: PropTypes.func.isRequired,
|
||||
@ -445,6 +432,7 @@ Home.propTypes = {
|
||||
scrolledToBottom: PropTypes.bool.isRequired,
|
||||
iAmOwner: PropTypes.bool.isRequired,
|
||||
sendUserEnter: PropTypes.func.isRequired,
|
||||
sendUserDisconnect: PropTypes.func.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
joining: PropTypes.bool.isRequired,
|
||||
toggleWindowFocus: PropTypes.func.isRequired,
|
||||
@ -453,4 +441,5 @@ Home.propTypes = {
|
||||
toggleSoundEnabled: PropTypes.func.isRequired,
|
||||
toggleSocketConnected: PropTypes.func.isRequired,
|
||||
socketConnected: PropTypes.bool.isRequired,
|
||||
onConnected: PropTypes.func.isRequired
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { connect } from 'react-redux'
|
||||
import Home from 'components/Home'
|
||||
import {
|
||||
createRoom,
|
||||
receiveSocketMessage,
|
||||
sendSocketMessage,
|
||||
createUser,
|
||||
@ -16,6 +15,8 @@ import {
|
||||
toggleWindowFocus,
|
||||
toggleSoundEnabled,
|
||||
toggleSocketConnected,
|
||||
onConnected,
|
||||
sendUserDisconnect
|
||||
} from 'actions'
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
@ -41,7 +42,6 @@ const mapStateToProps = (state) => {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
createRoom,
|
||||
receiveSocketMessage,
|
||||
sendSocketMessage,
|
||||
receiveUserExit,
|
||||
@ -56,6 +56,8 @@ const mapDispatchToProps = {
|
||||
toggleWindowFocus,
|
||||
toggleSoundEnabled,
|
||||
toggleSocketConnected,
|
||||
onConnected,
|
||||
sendUserDisconnect
|
||||
}
|
||||
|
||||
export default connect(
|
||||
|
@ -90,13 +90,6 @@ const activities = (state = initialState, action) => {
|
||||
return state
|
||||
}
|
||||
|
||||
// Duplicate "user entered" can happen when >2 users join
|
||||
// in quick succession
|
||||
const alreadyEntered = state.items.find(act => act.type === 'USER_ENTER' && act.userId === newUserId)
|
||||
if (alreadyEntered) {
|
||||
return state
|
||||
}
|
||||
|
||||
if (action.payload.state.room.joining) {
|
||||
return state
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ const initialState = {
|
||||
|
||||
const app = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'FETCH_CREATE_HANDSHAKE_SUCCESS':
|
||||
return state;
|
||||
case 'OPEN_MODAL':
|
||||
return {
|
||||
...state,
|
||||
|
@ -15,15 +15,14 @@ const initialState = {
|
||||
|
||||
const room = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case 'FETCH_CREATE_HANDSHAKE_SUCCESS':
|
||||
const isLocked = action.payload.json.isLocked
|
||||
// Handle "room is locked" message for new members here
|
||||
case 'CONNECTED':
|
||||
const size = action.payload.users ? action.payload.users.length : 1;
|
||||
return {
|
||||
...state,
|
||||
id: action.payload.json.id,
|
||||
isLocked,
|
||||
size: action.payload.json.size,
|
||||
joining: !(action.payload.json.size === 1),
|
||||
id: action.payload.id,
|
||||
isLocked: Boolean(action.payload.isLocked),
|
||||
size,
|
||||
joining: false
|
||||
}
|
||||
case 'USER_EXIT':
|
||||
const memberPubKeys = action.payload.members.map(m => JSON.stringify(m.publicKey))
|
||||
@ -41,7 +40,7 @@ const room = (state = initialState, action) => {
|
||||
}
|
||||
case 'HANDLE_SOCKET_MESSAGE_ADD_USER':
|
||||
const membersWithId = state.members.filter(m => m.id)
|
||||
const joining = state.joining ? membersWithId.length + 1 < state.size : false
|
||||
const joining = false
|
||||
|
||||
return {
|
||||
...state,
|
||||
@ -79,24 +78,29 @@ const room = (state = initialState, action) => {
|
||||
will receive a USER_ENTER event that doesn't contain itself. In that case we
|
||||
want to prepend "me" to the members payload
|
||||
*/
|
||||
const diff = _.differenceBy(state.members, action.payload, m => m.publicKey.n)
|
||||
const members = diff.length ? state.members.concat(action.payload) : action.payload
|
||||
const members = _.uniqBy(action.payload, member => member.publicKey.n);
|
||||
return {
|
||||
...state,
|
||||
members: members.map((user) => {
|
||||
const exists = state.members.find(m => _.isEqual(m.publicKey, user.publicKey))
|
||||
members: members.reduce((acc, user) => {
|
||||
const exists = state.members.find(m => m.publicKey.n === user.publicKey.n)
|
||||
if (exists) {
|
||||
return {
|
||||
...user,
|
||||
...exists,
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
...user,
|
||||
...exists,
|
||||
}
|
||||
]
|
||||
}
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
publicKey: user.publicKey,
|
||||
isOwner: user.isOwner,
|
||||
id: user.id,
|
||||
}
|
||||
}
|
||||
return {
|
||||
publicKey: user.publicKey,
|
||||
isOwner: user.isOwner,
|
||||
id: user.id,
|
||||
}
|
||||
}),
|
||||
]
|
||||
}, []),
|
||||
}
|
||||
case 'TOGGLE_LOCK_ROOM':
|
||||
return {
|
||||
|
@ -13,4 +13,4 @@ export const connect = (roomId) => {
|
||||
return socket
|
||||
}
|
||||
|
||||
export const getIO = () => socket
|
||||
export const getSocket = () => socket
|
||||
|
@ -42,25 +42,6 @@ if ((siteURL || env === 'development') && !isReviewApp) {
|
||||
}));
|
||||
}
|
||||
|
||||
router.post('/handshake', koaBody, async (ctx) => {
|
||||
const { body } = ctx.request;
|
||||
const { roomId } = body;
|
||||
|
||||
const roomIdHash = getRoomIdHash(roomId)
|
||||
|
||||
let roomExists = await redis.hgetAsync('rooms', roomIdHash)
|
||||
if (roomExists) {
|
||||
roomExists = JSON.parse(roomExists)
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
id: roomId,
|
||||
ready: true,
|
||||
isLocked: Boolean(roomExists && roomExists.isLocked),
|
||||
size: ((roomExists && roomExists.users.length) || 0) + 1,
|
||||
};
|
||||
});
|
||||
|
||||
router.post('/abuse/:roomId', koaBody, async (ctx) => {
|
||||
let { roomId } = ctx.params;
|
||||
|
||||
@ -120,7 +101,10 @@ if (clientDistDirectory) {
|
||||
const protocol = (process.env.PROTOCOL || 'http') === 'http' ? http : https;
|
||||
|
||||
const server = protocol.createServer(app.callback());
|
||||
const io = Io(server);
|
||||
const io = Io(server, {
|
||||
pingInterval: 5000,
|
||||
pingTimeout: 3000
|
||||
});
|
||||
io.adapter(socketRedis(process.env.REDIS_URL));
|
||||
|
||||
const roomHashSecret = process.env.ROOM_HASH_SECRET;
|
||||
@ -151,6 +135,7 @@ io.on('connection', async (socket) => {
|
||||
room = JSON.parse(room || '{}')
|
||||
|
||||
new Socket({
|
||||
roomIdOriginal: roomId,
|
||||
roomId: roomIdHash,
|
||||
socket,
|
||||
room,
|
||||
|
@ -4,10 +4,14 @@ import { getIO, getRedis } from './index'
|
||||
|
||||
export default class Socket {
|
||||
constructor(opts) {
|
||||
const { roomId, socket, room } = opts
|
||||
const { roomId, socket, room, roomIdOriginal } = opts
|
||||
|
||||
this._roomId = roomId
|
||||
this.socket = socket;
|
||||
this.roomIdOriginal = roomIdOriginal;
|
||||
this.room = room;
|
||||
if (room.isLocked) {
|
||||
this.sendRoomLocked();
|
||||
return
|
||||
}
|
||||
|
||||
@ -17,9 +21,28 @@ export default class Socket {
|
||||
async init(opts) {
|
||||
const { roomId, socket, room } = opts
|
||||
await this.joinRoom(roomId, socket.id)
|
||||
this.sendRoomInfo();
|
||||
this.handleSocket(socket)
|
||||
}
|
||||
|
||||
sendRoomInfo() {
|
||||
let room;
|
||||
if (_.isEmpty(this.room)) {
|
||||
room = {
|
||||
id: this.roomIdOriginal,
|
||||
users: [],
|
||||
isLocked: false,
|
||||
}
|
||||
} else {
|
||||
room = this.room;
|
||||
}
|
||||
this.socket.emit('CONNECTED', room);
|
||||
}
|
||||
|
||||
sendRoomLocked() {
|
||||
this.socket.emit('ROOM_LOCKED');
|
||||
}
|
||||
|
||||
async saveRoom(room) {
|
||||
const json = {
|
||||
...room,
|
||||
@ -109,6 +132,8 @@ export default class Socket {
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => this.handleDisconnect(socket));
|
||||
|
||||
socket.on('USER_DISCONNECT', () => this.handleDisconnect(socket));
|
||||
}
|
||||
|
||||
async handleDisconnect(socket) {
|
||||
@ -129,6 +154,8 @@ export default class Socket {
|
||||
if (newRoom.users && newRoom.users.length === 0) {
|
||||
await this.destroyRoom()
|
||||
}
|
||||
|
||||
socket.disconnect(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user