Fix race condition with NICK and QUIT when multiple dispatch users are in the same channel
This commit is contained in:
parent
3393b1b706
commit
18651c1a10
File diff suppressed because one or more lines are too long
@ -1,14 +1,14 @@
|
|||||||
export default function createCommandMiddleware(type, handlers) {
|
export default function createCommandMiddleware(type, handlers) {
|
||||||
return store => next => action => {
|
return ({ dispatch, getState }) => next => action => {
|
||||||
if (action.type === type) {
|
if (action.type === type) {
|
||||||
const words = action.command.slice(1).split(' ');
|
const words = action.command.slice(1).split(' ');
|
||||||
const command = words[0];
|
const command = words[0];
|
||||||
const params = words.slice(1);
|
const params = words.slice(1);
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(handlers, command)) {
|
if (command in handlers) {
|
||||||
handlers[command]({
|
handlers[command]({
|
||||||
dispatch: store.dispatch,
|
dispatch,
|
||||||
getState: store.getState,
|
getState,
|
||||||
server: action.server,
|
server: action.server,
|
||||||
channel: action.channel
|
channel: action.channel
|
||||||
}, ...params);
|
}, ...params);
|
||||||
|
@ -104,15 +104,15 @@ export default createReducer(Map(), {
|
|||||||
},
|
},
|
||||||
|
|
||||||
[actions.SOCKET_NICK](state, action) {
|
[actions.SOCKET_NICK](state, action) {
|
||||||
const { server, channels } = action;
|
const { server } = action;
|
||||||
return state.withMutations(s => {
|
return state.withMutations(s => {
|
||||||
channels.forEach(channel => {
|
s.get(server).forEach((v, channel) => {
|
||||||
s.updateIn([server, channel, 'users'], users => {
|
s.updateIn([server, channel, 'users'], users =>
|
||||||
const i = users.findIndex(user => user.nick === action.old);
|
users.update(
|
||||||
return users.update(i, user =>
|
users.findIndex(user => user.nick === action.old),
|
||||||
updateRenderName(user.set('nick', action.new))
|
user => updateRenderName(user.set('nick', action.new))
|
||||||
).sort(compareUsers);
|
).sort(compareUsers)
|
||||||
});
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -7,20 +7,29 @@ function withReason(message, reason) {
|
|||||||
return message + (reason ? ` (${reason})` : '');
|
return message + (reason ? ` (${reason})` : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function handleSocket(socket, { dispatch, getState }) {
|
function findChannels(state, server, user) {
|
||||||
socket.onAny((event, data) => {
|
const channels = [];
|
||||||
const type = `SOCKET_${event.toUpperCase()}`;
|
|
||||||
if (Array.isArray(data)) {
|
state.channels.get(server).forEach((channel, channelName) => {
|
||||||
dispatch({ type, data });
|
if (channel.get('users').find(u => u.nick === user)) {
|
||||||
} else {
|
channels.push(channelName);
|
||||||
dispatch({ type, ...data });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('message', message => dispatch(addMessage(message)));
|
return channels;
|
||||||
socket.on('pm', message => dispatch(addMessage(message)));
|
}
|
||||||
|
|
||||||
socket.on('join', data => {
|
export default function handleSocket(socket, { dispatch, getState }) {
|
||||||
|
const handlers = {
|
||||||
|
message(message) {
|
||||||
|
dispatch(addMessage(message));
|
||||||
|
},
|
||||||
|
|
||||||
|
pm(message) {
|
||||||
|
dispatch(addMessage(message));
|
||||||
|
},
|
||||||
|
|
||||||
|
join(data) {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { server, channel } = state.tab.selected;
|
const { server, channel } = state.tab.selected;
|
||||||
if (server && channel) {
|
if (server && channel) {
|
||||||
@ -33,39 +42,39 @@ export default function handleSocket(socket, { dispatch, getState }) {
|
|||||||
dispatch(select(server, joinedChannel));
|
dispatch(select(server, joinedChannel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('servers', data => {
|
dispatch(inform(`${data.user} joined the channel`, data.server, data.channels[0]));
|
||||||
|
},
|
||||||
|
|
||||||
|
servers(data) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
dispatch(routeActions.replace('/connect'));
|
dispatch(routeActions.replace('/connect'));
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
|
||||||
socket.on('join', ({ user, server, channels }) =>
|
part({ user, server, channel, reason }) {
|
||||||
dispatch(inform(`${user} joined the channel`, server, channels[0]))
|
dispatch(inform(withReason(`${user} left the channel`, reason), server, channel));
|
||||||
);
|
},
|
||||||
|
|
||||||
socket.on('part', ({ user, server, channel, reason }) =>
|
quit({ user, server, reason }) {
|
||||||
dispatch(inform(withReason(`${user} left the channel`, reason), server, channel))
|
const channels = findChannels(getState(), server, user);
|
||||||
);
|
dispatch(broadcast(withReason(`${user} quit`, reason), server, channels));
|
||||||
|
},
|
||||||
|
|
||||||
socket.on('quit', ({ user, server, reason, channels }) =>
|
nick(data) {
|
||||||
dispatch(broadcast(withReason(`${user} quit`, reason), server, channels))
|
const channels = findChannels(getState(), data.server, data.old);
|
||||||
);
|
dispatch(broadcast(`${data.old} changed nick to ${data.new}`, data.server, channels));
|
||||||
|
},
|
||||||
|
|
||||||
socket.on('nick', data =>
|
motd({ content, server }) {
|
||||||
dispatch(broadcast(`${data.old} changed nick to ${data.new}`, data.server, data.channels))
|
|
||||||
);
|
|
||||||
|
|
||||||
socket.on('motd', ({ content, server }) =>
|
|
||||||
dispatch(addMessages(content.map(line => ({
|
dispatch(addMessages(content.map(line => ({
|
||||||
server,
|
server,
|
||||||
to: server,
|
to: server,
|
||||||
message: line
|
message: line
|
||||||
}))))
|
}))));
|
||||||
);
|
},
|
||||||
|
|
||||||
socket.on('whois', data => {
|
whois(data) {
|
||||||
const tab = getState().tab.selected;
|
const tab = getState().tab.selected;
|
||||||
|
|
||||||
dispatch(inform([
|
dispatch(inform([
|
||||||
@ -76,7 +85,23 @@ export default function handleSocket(socket, { dispatch, getState }) {
|
|||||||
`Server: ${data.server}`,
|
`Server: ${data.server}`,
|
||||||
`Channels: ${data.channels}`
|
`Channels: ${data.channels}`
|
||||||
], tab.server, tab.channel));
|
], tab.server, tab.channel));
|
||||||
});
|
},
|
||||||
|
|
||||||
socket.on('print', ({ server, message }) => dispatch(inform(message, server)));
|
print({ server, message }) {
|
||||||
|
dispatch(inform(message, server));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onMessage((type, data) => {
|
||||||
|
if (type in handlers) {
|
||||||
|
handlers[type](data);
|
||||||
|
}
|
||||||
|
|
||||||
|
type = `SOCKET_${type.toUpperCase()}`;
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
dispatch({ type, data });
|
||||||
|
} else {
|
||||||
|
dispatch({ type, ...data });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import EventEmitter2 from 'eventemitter2';
|
|
||||||
import Backoff from 'backo';
|
import Backoff from 'backo';
|
||||||
|
|
||||||
export default class Socket extends EventEmitter2 {
|
export default class Socket {
|
||||||
constructor(host) {
|
constructor(host) {
|
||||||
super();
|
|
||||||
|
|
||||||
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
this.url = `${protocol}://${host}/ws`;
|
this.url = `${protocol}://${host}/ws`;
|
||||||
|
|
||||||
@ -15,6 +12,7 @@ export default class Socket extends EventEmitter2 {
|
|||||||
max: 5000,
|
max: 5000,
|
||||||
jitter: 0.25
|
jitter: 0.25
|
||||||
});
|
});
|
||||||
|
this.handlers = [];
|
||||||
|
|
||||||
this.connect();
|
this.connect();
|
||||||
}
|
}
|
||||||
@ -30,7 +28,6 @@ export default class Socket extends EventEmitter2 {
|
|||||||
this.ws.onopen = () => {
|
this.ws.onopen = () => {
|
||||||
clearTimeout(this.timeoutConnect);
|
clearTimeout(this.timeoutConnect);
|
||||||
this.backoff.reset();
|
this.backoff.reset();
|
||||||
this.emit('connect');
|
|
||||||
this.setTimeoutPing();
|
this.setTimeoutPing();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,7 +35,6 @@ export default class Socket extends EventEmitter2 {
|
|||||||
clearTimeout(this.timeoutConnect);
|
clearTimeout(this.timeoutConnect);
|
||||||
clearTimeout(this.timeoutPing);
|
clearTimeout(this.timeoutPing);
|
||||||
if (!this.closing) {
|
if (!this.closing) {
|
||||||
this.emit('disconnect');
|
|
||||||
this.retry();
|
this.retry();
|
||||||
}
|
}
|
||||||
this.closing = false;
|
this.closing = false;
|
||||||
@ -76,10 +72,19 @@ export default class Socket extends EventEmitter2 {
|
|||||||
setTimeoutPing() {
|
setTimeoutPing() {
|
||||||
clearTimeout(this.timeoutPing);
|
clearTimeout(this.timeoutPing);
|
||||||
this.timeoutPing = setTimeout(() => {
|
this.timeoutPing = setTimeout(() => {
|
||||||
this.emit('disconnect');
|
|
||||||
this.closing = true;
|
this.closing = true;
|
||||||
this.ws.close();
|
this.ws.close();
|
||||||
this.connect();
|
this.connect();
|
||||||
}, this.pingTimeout);
|
}, this.pingTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMessage(handler) {
|
||||||
|
this.handlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(type, data) {
|
||||||
|
for (let i = 0; i < this.handlers.length; i++) {
|
||||||
|
this.handlers[i](type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,6 @@ func (i *ircHandler) nick(msg *irc.Message) {
|
|||||||
Server: i.client.Host,
|
Server: i.client.Host,
|
||||||
Old: msg.Nick,
|
Old: msg.Nick,
|
||||||
New: msg.LastParam(),
|
New: msg.LastParam(),
|
||||||
Channels: channelStore.FindUserChannels(msg.Nick, i.client.Host),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
channelStore.RenameUser(msg.Nick, msg.LastParam(), i.client.Host)
|
channelStore.RenameUser(msg.Nick, msg.LastParam(), i.client.Host)
|
||||||
@ -141,7 +140,6 @@ func (i *ircHandler) quit(msg *irc.Message) {
|
|||||||
Server: i.client.Host,
|
Server: i.client.Host,
|
||||||
User: msg.Nick,
|
User: msg.Nick,
|
||||||
Reason: msg.LastParam(),
|
Reason: msg.LastParam(),
|
||||||
Channels: channelStore.FindUserChannels(msg.Nick, i.client.Host),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
channelStore.RemoveUserAll(msg.Nick, i.client.Host)
|
channelStore.RemoveUserAll(msg.Nick, i.client.Host)
|
||||||
|
@ -30,7 +30,6 @@ type Nick struct {
|
|||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
Old string `json:"old"`
|
Old string `json:"old"`
|
||||||
New string `json:"new"`
|
New string `json:"new"`
|
||||||
Channels []string `json:"channels"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Join struct {
|
type Join struct {
|
||||||
@ -59,7 +58,6 @@ type Quit struct {
|
|||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
Channels []string `json:"channels"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Chat struct {
|
type Chat struct {
|
||||||
|
@ -101,25 +101,6 @@ func (c *ChannelStore) SetMode(server, channel, user, add, remove string) {
|
|||||||
c.userLock.Unlock()
|
c.userLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ChannelStore) FindUserChannels(user, server string) []string {
|
|
||||||
var channels []string
|
|
||||||
|
|
||||||
c.userLock.Lock()
|
|
||||||
|
|
||||||
for channel, users := range c.users[server] {
|
|
||||||
for _, nick := range users {
|
|
||||||
if strings.TrimLeft(nick, "@+") == user {
|
|
||||||
channels = append(channels, channel)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.userLock.Unlock()
|
|
||||||
|
|
||||||
return channels
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ChannelStore) GetTopic(server, channel string) string {
|
func (c *ChannelStore) GetTopic(server, channel string) string {
|
||||||
c.topicLock.Lock()
|
c.topicLock.Lock()
|
||||||
defer c.topicLock.Unlock()
|
defer c.topicLock.Unlock()
|
||||||
|
Loading…
Reference in New Issue
Block a user