Show last IRC connection error in status tab, log IRC connection errors
This commit is contained in:
parent
786d8013b9
commit
18aff3ded6
File diff suppressed because one or more lines are too long
@ -39,6 +39,14 @@ i[class^="icon-"]:before, i[class*=" icon-"]:before {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: #6BB758 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #F6546A !important;
|
||||||
|
}
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -12,13 +12,14 @@ export default class TabList extends PureComponent {
|
|||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
||||||
channels.forEach((server, address) => {
|
channels.forEach((server, address) => {
|
||||||
|
const srv = servers.get(address);
|
||||||
tabs.push(
|
tabs.push(
|
||||||
<TabListItem
|
<TabListItem
|
||||||
key={address}
|
key={address}
|
||||||
server={address}
|
server={address}
|
||||||
content={servers.getIn([address, 'name'])}
|
content={srv.name}
|
||||||
selected={tab.server === address && tab.name === null}
|
selected={tab.server === address && tab.name === null}
|
||||||
connected={servers.getIn([address, 'connected'])}
|
connected={srv.status.connected}
|
||||||
onClick={this.handleTabClick}
|
onClick={this.handleTabClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -15,9 +15,9 @@ export default class TabListItem extends PureComponent {
|
|||||||
classes.push('tab-server');
|
classes.push('tab-server');
|
||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
style.color = '#6BB758';
|
classes.push('success');
|
||||||
} else {
|
} else {
|
||||||
style.color = '#F6546A';
|
classes.push('error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ export default class Chat extends Component {
|
|||||||
nick,
|
nick,
|
||||||
search,
|
search,
|
||||||
showUserList,
|
showUserList,
|
||||||
|
status,
|
||||||
tab,
|
tab,
|
||||||
title,
|
title,
|
||||||
users,
|
users,
|
||||||
@ -81,6 +82,7 @@ export default class Chat extends Component {
|
|||||||
<div className={chatClass}>
|
<div className={chatClass}>
|
||||||
<ChatTitle
|
<ChatTitle
|
||||||
channel={channel}
|
channel={channel}
|
||||||
|
status={status}
|
||||||
tab={tab}
|
tab={tab}
|
||||||
title={title}
|
title={title}
|
||||||
onCloseClick={this.handleCloseClick}
|
onCloseClick={this.handleCloseClick}
|
||||||
|
@ -7,7 +7,7 @@ import { linkify } from 'util';
|
|||||||
|
|
||||||
export default class ChatTitle extends PureComponent {
|
export default class ChatTitle extends PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { title, tab, channel, onTitleChange,
|
const { status, title, tab, channel, onTitleChange,
|
||||||
onToggleSearch, onToggleUserList, onCloseClick } = this.props;
|
onToggleSearch, onToggleUserList, onCloseClick } = this.props;
|
||||||
|
|
||||||
let closeTitle;
|
let closeTitle;
|
||||||
@ -19,6 +19,11 @@ export default class ChatTitle extends PureComponent {
|
|||||||
closeTitle = 'Disconnect';
|
closeTitle = 'Disconnect';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let serverError = null;
|
||||||
|
if (!tab.name && status.error) {
|
||||||
|
serverError = <span className="error">Error! {status.error}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="chat-title-bar">
|
<div className="chat-title-bar">
|
||||||
@ -34,6 +39,7 @@ export default class ChatTitle extends PureComponent {
|
|||||||
</Editable>
|
</Editable>
|
||||||
<div className="chat-topic-wrap">
|
<div className="chat-topic-wrap">
|
||||||
<span className="chat-topic">{linkify(channel.get('topic')) || null}</span>
|
<span className="chat-topic">{linkify(channel.get('topic')) || null}</span>
|
||||||
|
{serverError}
|
||||||
</div>
|
</div>
|
||||||
<i className="icon-search" title="Search" onClick={onToggleSearch} />
|
<i className="icon-search" title="Search" onClick={onToggleSearch} />
|
||||||
<i
|
<i
|
||||||
|
@ -10,7 +10,7 @@ import { getSelectedMessages, getHasMoreMessages,
|
|||||||
runCommand, sendMessage, fetchMessages, addFetchedMessages } from 'state/messages';
|
runCommand, sendMessage, fetchMessages, addFetchedMessages } from 'state/messages';
|
||||||
import { openPrivateChat, closePrivateChat } from 'state/privateChats';
|
import { openPrivateChat, closePrivateChat } from 'state/privateChats';
|
||||||
import { getSearch, searchMessages, toggleSearch } from 'state/search';
|
import { getSearch, searchMessages, toggleSearch } from 'state/search';
|
||||||
import { getCurrentNick, disconnect, setNick, setServerName } from 'state/servers';
|
import { getCurrentNick, getCurrentServerStatus, disconnect, setNick, setServerName } from 'state/servers';
|
||||||
import { getSelectedTab, select } from 'state/tab';
|
import { getSelectedTab, select } from 'state/tab';
|
||||||
import { getShowUserList, toggleUserList } from 'state/ui';
|
import { getShowUserList, toggleUserList } from 'state/ui';
|
||||||
|
|
||||||
@ -22,6 +22,7 @@ const mapState = createStructuredSelector({
|
|||||||
nick: getCurrentNick,
|
nick: getCurrentNick,
|
||||||
search: getSearch,
|
search: getSearch,
|
||||||
showUserList: getShowUserList,
|
showUserList: getShowUserList,
|
||||||
|
status: getCurrentServerStatus,
|
||||||
tab: getSelectedTab,
|
tab: getSelectedTab,
|
||||||
title: getSelectedTabTitle,
|
title: getSelectedTabTitle,
|
||||||
users: getSelectedChannelUsers
|
users: getSelectedChannelUsers
|
||||||
|
@ -1,4 +1,26 @@
|
|||||||
import { setServerName } from '../servers';
|
import { connect, setServerName } from '../servers';
|
||||||
|
|
||||||
|
describe('connect()', () => {
|
||||||
|
it('sets host and port correctly', () => {
|
||||||
|
expect(connect('cake.com:1881', '', {})).toMatchObject({
|
||||||
|
socket: {
|
||||||
|
data: {
|
||||||
|
host: 'cake.com',
|
||||||
|
port: '1881'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(connect('cake.com', '', {})).toMatchObject({
|
||||||
|
socket: {
|
||||||
|
data: {
|
||||||
|
host: 'cake.com',
|
||||||
|
port: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('setServerName()', () => {
|
describe('setServerName()', () => {
|
||||||
it('passes valid names to the server', () => {
|
it('passes valid names to the server', () => {
|
||||||
|
@ -8,10 +8,13 @@ describe('server reducer', () => {
|
|||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
connected: false,
|
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null
|
editedNick: null,
|
||||||
|
status: {
|
||||||
|
connected: false,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -19,10 +22,13 @@ describe('server reducer', () => {
|
|||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
connected: false,
|
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null
|
editedNick: null,
|
||||||
|
status: {
|
||||||
|
connected: false,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,16 +38,22 @@ describe('server reducer', () => {
|
|||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toEqual({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
connected: false,
|
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null
|
editedNick: null,
|
||||||
|
status: {
|
||||||
|
connected: false,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'127.0.0.2': {
|
'127.0.0.2': {
|
||||||
connected: false,
|
|
||||||
name: 'srv',
|
name: 'srv',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null
|
editedNick: null,
|
||||||
|
status: {
|
||||||
|
connected: false,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -87,9 +99,8 @@ describe('server reducer', () => {
|
|||||||
editing: true
|
editing: true
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toMatchObject({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
connected: false,
|
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: 'nick2'
|
editedNick: 'nick2'
|
||||||
@ -111,9 +122,8 @@ describe('server reducer', () => {
|
|||||||
nick: ''
|
nick: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toMatchObject({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
connected: false,
|
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null
|
editedNick: null
|
||||||
@ -130,9 +140,8 @@ describe('server reducer', () => {
|
|||||||
newNick: 'nick2'
|
newNick: 'nick2'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toMatchObject({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
connected: false,
|
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick2',
|
nick: 'nick2',
|
||||||
editedNick: null
|
editedNick: null
|
||||||
@ -153,9 +162,8 @@ describe('server reducer', () => {
|
|||||||
server: '127.0.0.1'
|
server: '127.0.0.1'
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toMatchObject({
|
||||||
'127.0.0.1': {
|
'127.0.0.1': {
|
||||||
connected: false,
|
|
||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null
|
editedNick: null
|
||||||
@ -171,13 +179,17 @@ describe('server reducer', () => {
|
|||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
name: 'stuff',
|
name: 'stuff',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
|
status: {
|
||||||
connected: true
|
connected: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
host: '127.0.0.2',
|
host: '127.0.0.2',
|
||||||
name: 'stuffz',
|
name: 'stuffz',
|
||||||
nick: 'nick2',
|
nick: 'nick2',
|
||||||
|
status: {
|
||||||
connected: false
|
connected: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@ -187,13 +199,19 @@ describe('server reducer', () => {
|
|||||||
name: 'stuff',
|
name: 'stuff',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null,
|
editedNick: null,
|
||||||
connected: true
|
status: {
|
||||||
|
connected: true,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'127.0.0.2': {
|
'127.0.0.2': {
|
||||||
name: 'stuffz',
|
name: 'stuffz',
|
||||||
nick: 'nick2',
|
nick: 'nick2',
|
||||||
editedNick: null,
|
editedNick: null,
|
||||||
connected: false
|
status: {
|
||||||
|
connected: false,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -202,7 +220,8 @@ describe('server reducer', () => {
|
|||||||
let state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
let state = reducer(undefined, connect('127.0.0.1:1337', 'nick', {}));
|
||||||
state = reducer(state, {
|
state = reducer(state, {
|
||||||
type: actions.socket.CONNECTION_UPDATE,
|
type: actions.socket.CONNECTION_UPDATE,
|
||||||
'127.0.0.1': true
|
server: '127.0.0.1',
|
||||||
|
connected: true
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(state.toJS()).toEqual({
|
expect(state.toJS()).toEqual({
|
||||||
@ -210,7 +229,29 @@ describe('server reducer', () => {
|
|||||||
name: '127.0.0.1',
|
name: '127.0.0.1',
|
||||||
nick: 'nick',
|
nick: 'nick',
|
||||||
editedNick: null,
|
editedNick: null,
|
||||||
connected: true
|
status: {
|
||||||
|
connected: true,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
state = reducer(state, {
|
||||||
|
type: actions.socket.CONNECTION_UPDATE,
|
||||||
|
server: '127.0.0.1',
|
||||||
|
connected: false,
|
||||||
|
error: 'Bad stuff happened'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(state.toJS()).toEqual({
|
||||||
|
'127.0.0.1': {
|
||||||
|
name: '127.0.0.1',
|
||||||
|
nick: 'nick',
|
||||||
|
editedNick: null,
|
||||||
|
status: {
|
||||||
|
connected: false,
|
||||||
|
error: 'Bad stuff happened'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,11 +4,16 @@ import createReducer from 'util/createReducer';
|
|||||||
import { getSelectedTab, updateSelection } from './tab';
|
import { getSelectedTab, updateSelection } from './tab';
|
||||||
import * as actions from './actions';
|
import * as actions from './actions';
|
||||||
|
|
||||||
|
const Status = Record({
|
||||||
|
connected: false,
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
|
||||||
const Server = Record({
|
const Server = Record({
|
||||||
nick: '',
|
nick: '',
|
||||||
editedNick: null,
|
editedNick: null,
|
||||||
name: '',
|
name: '',
|
||||||
connected: false
|
status: new Status()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getServers = state => state.servers;
|
export const getServers = state => state.servers;
|
||||||
@ -31,6 +36,12 @@ export const getCurrentServerName = createSelector(
|
|||||||
(servers, tab) => servers.getIn([tab.server, 'name'])
|
(servers, tab) => servers.getIn([tab.server, 'name'])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getCurrentServerStatus = createSelector(
|
||||||
|
getServers,
|
||||||
|
getSelectedTab,
|
||||||
|
(servers, tab) => servers.getIn([tab.server, 'status'])
|
||||||
|
);
|
||||||
|
|
||||||
export default createReducer(Map(), {
|
export default createReducer(Map(), {
|
||||||
[actions.CONNECT](state, { host, nick, options }) {
|
[actions.CONNECT](state, { host, nick, options }) {
|
||||||
if (!state.has(host)) {
|
if (!state.has(host)) {
|
||||||
@ -81,27 +92,27 @@ export default createReducer(Map(), {
|
|||||||
|
|
||||||
return state.withMutations(s => {
|
return state.withMutations(s => {
|
||||||
data.forEach(server => {
|
data.forEach(server => {
|
||||||
|
server.status = new Status(server.status);
|
||||||
s.set(server.host, new Server(server));
|
s.set(server.host, new Server(server));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
[actions.socket.CONNECTION_UPDATE](state, action) {
|
[actions.socket.CONNECTION_UPDATE](state, action) {
|
||||||
return state.withMutations(s =>
|
if (state.has(action.server)) {
|
||||||
Object.keys(action).forEach(server => {
|
return state.setIn([action.server, 'status'], new Status(action));
|
||||||
if (s.has(server)) {
|
|
||||||
s.setIn([server, 'connected'], action[server]);
|
|
||||||
}
|
}
|
||||||
})
|
return state;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export function connect(server, nick, options) {
|
export function connect(server, nick, options) {
|
||||||
let host = server;
|
let host = server;
|
||||||
|
let port;
|
||||||
const i = server.indexOf(':');
|
const i = server.indexOf(':');
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
host = server.slice(0, i);
|
host = server.slice(0, i);
|
||||||
|
port = server.slice(i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -112,7 +123,8 @@ export function connect(server, nick, options) {
|
|||||||
socket: {
|
socket: {
|
||||||
type: 'connect',
|
type: 'connect',
|
||||||
data: {
|
data: {
|
||||||
server,
|
host,
|
||||||
|
port,
|
||||||
nick,
|
nick,
|
||||||
username: options.username || nick,
|
username: options.username || nick,
|
||||||
password: options.password,
|
password: options.password,
|
||||||
|
@ -20,7 +20,7 @@ type Client struct {
|
|||||||
Username string
|
Username string
|
||||||
Realname string
|
Realname string
|
||||||
Messages chan *Message
|
Messages chan *Message
|
||||||
ConnectionChanged chan bool
|
ConnectionChanged chan ConnectionState
|
||||||
HandleNickInUse func(string) string
|
HandleNickInUse func(string) string
|
||||||
|
|
||||||
nick string
|
nick string
|
||||||
@ -47,7 +47,7 @@ func NewClient(nick, username string) *Client {
|
|||||||
Username: username,
|
Username: username,
|
||||||
Realname: nick,
|
Realname: nick,
|
||||||
Messages: make(chan *Message, 32),
|
Messages: make(chan *Message, 32),
|
||||||
ConnectionChanged: make(chan bool, 16),
|
ConnectionChanged: make(chan ConnectionState, 16),
|
||||||
out: make(chan string, 32),
|
out: make(chan string, 32),
|
||||||
quit: make(chan struct{}),
|
quit: make(chan struct{}),
|
||||||
reconnect: make(chan struct{}),
|
reconnect: make(chan struct{}),
|
||||||
|
23
irc/conn.go
23
irc/conn.go
@ -10,8 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) Connect(address string) {
|
func (c *Client) Connect(address string) {
|
||||||
c.ConnectionChanged <- false
|
|
||||||
|
|
||||||
if idx := strings.Index(address, ":"); idx < 0 {
|
if idx := strings.Index(address, ":"); idx < 0 {
|
||||||
c.Host = address
|
c.Host = address
|
||||||
|
|
||||||
@ -26,6 +24,7 @@ func (c *Client) Connect(address string) {
|
|||||||
c.Server = address
|
c.Server = address
|
||||||
c.dialer = &net.Dialer{Timeout: 10 * time.Second}
|
c.dialer = &net.Dialer{Timeout: 10 * time.Second}
|
||||||
|
|
||||||
|
c.connChange(false, nil)
|
||||||
go c.run()
|
go c.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +70,20 @@ func (c *Client) run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectionState struct {
|
||||||
|
Connected bool
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) connChange(connected bool, err error) {
|
||||||
|
c.ConnectionChanged <- ConnectionState{
|
||||||
|
Connected: connected,
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) disconnect() {
|
func (c *Client) disconnect() {
|
||||||
c.ConnectionChanged <- false
|
c.connChange(false, nil)
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
c.connected = false
|
c.connected = false
|
||||||
c.lock.Unlock()
|
c.lock.Unlock()
|
||||||
@ -91,7 +102,9 @@ func (c *Client) tryConnect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := c.connect()
|
err := c.connect()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
|
c.connChange(false, err)
|
||||||
|
} else {
|
||||||
c.backoff.Reset()
|
c.backoff.Reset()
|
||||||
|
|
||||||
c.flushChannels()
|
c.flushChannels()
|
||||||
@ -123,7 +136,7 @@ func (c *Client) connect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.connected = true
|
c.connected = true
|
||||||
c.ConnectionChanged <- true
|
c.connChange(true, nil)
|
||||||
c.reader = bufio.NewReader(c.conn)
|
c.reader = bufio.NewReader(c.conn)
|
||||||
|
|
||||||
c.register()
|
c.register()
|
||||||
|
@ -20,7 +20,7 @@ type connectDefaults struct {
|
|||||||
|
|
||||||
type indexData struct {
|
type indexData struct {
|
||||||
Defaults connectDefaults `json:"defaults"`
|
Defaults connectDefaults `json:"defaults"`
|
||||||
Servers []storage.Server `json:"servers,omitempty"`
|
Servers []Server `json:"servers,omitempty"`
|
||||||
Channels []storage.Channel `json:"channels,omitempty"`
|
Channels []storage.Channel `json:"channels,omitempty"`
|
||||||
|
|
||||||
// Users in the selected channel
|
// Users in the selected channel
|
||||||
@ -57,32 +57,32 @@ func (d *indexData) addUsersAndMessages(server, channel string, session *Session
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getIndexData(r *http.Request, session *Session) *indexData {
|
func getIndexData(r *http.Request, session *Session) *indexData {
|
||||||
|
data := indexData{}
|
||||||
servers := session.user.GetServers()
|
servers := session.user.GetServers()
|
||||||
connections := session.getConnectionStates()
|
connections := session.getConnectionStates()
|
||||||
for i, server := range servers {
|
for _, server := range servers {
|
||||||
servers[i].Connected = connections[server.Host]
|
server.Password = ""
|
||||||
servers[i].Port = ""
|
server.Username = ""
|
||||||
servers[i].TLS = false
|
server.Realname = ""
|
||||||
servers[i].Password = ""
|
|
||||||
servers[i].Username = ""
|
data.Servers = append(data.Servers, Server{
|
||||||
servers[i].Realname = ""
|
Server: server,
|
||||||
|
Status: newConnectionUpdate(server.Host, connections[server.Host]),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
channels := session.user.GetChannels()
|
channels := session.user.GetChannels()
|
||||||
for i, channel := range channels {
|
for i, channel := range channels {
|
||||||
channels[i].Topic = channelStore.GetTopic(channel.Server, channel.Name)
|
channels[i].Topic = channelStore.GetTopic(channel.Server, channel.Name)
|
||||||
}
|
}
|
||||||
|
data.Channels = channels
|
||||||
|
|
||||||
data := indexData{
|
data.Defaults = connectDefaults{
|
||||||
Defaults: connectDefaults{
|
|
||||||
Name: viper.GetString("defaults.name"),
|
Name: viper.GetString("defaults.name"),
|
||||||
Address: viper.GetString("defaults.address"),
|
Address: viper.GetString("defaults.address"),
|
||||||
Channels: viper.GetStringSlice("defaults.channels"),
|
Channels: viper.GetStringSlice("defaults.channels"),
|
||||||
Password: viper.GetString("defaults.password") != "",
|
Password: viper.GetString("defaults.password") != "",
|
||||||
SSL: viper.GetBool("defaults.ssl"),
|
SSL: viper.GetBool("defaults.ssl"),
|
||||||
},
|
|
||||||
Servers: servers,
|
|
||||||
Channels: channels,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server, channel := getTabFromPath(r.URL.EscapedPath())
|
server, channel := getTabFromPath(r.URL.EscapedPath())
|
||||||
|
@ -27,31 +27,7 @@ func reconnectIRC() {
|
|||||||
channels := user.GetChannels()
|
channels := user.GetChannels()
|
||||||
|
|
||||||
for _, server := range user.GetServers() {
|
for _, server := range user.GetServers() {
|
||||||
i := irc.NewClient(server.Nick, server.Username)
|
i := connectIRC(server, session)
|
||||||
i.TLS = server.TLS
|
|
||||||
i.Password = server.Password
|
|
||||||
i.Realname = server.Realname
|
|
||||||
i.HandleNickInUse = createNickInUseHandler(i, session)
|
|
||||||
|
|
||||||
if i.TLS {
|
|
||||||
i.TLSConfig = &tls.Config{
|
|
||||||
InsecureSkipVerify: !viper.GetBool("verify_certificates"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert := user.GetCertificate(); cert != nil {
|
|
||||||
i.TLSConfig.Certificates = []tls.Certificate{*cert}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session.setIRC(server.Host, i)
|
|
||||||
|
|
||||||
if server.Port != "" {
|
|
||||||
i.Connect(net.JoinHostPort(server.Host, server.Port))
|
|
||||||
} else {
|
|
||||||
i.Connect(server.Host)
|
|
||||||
}
|
|
||||||
|
|
||||||
go newIRCHandler(i, session).run()
|
|
||||||
|
|
||||||
var joining []string
|
var joining []string
|
||||||
for _, channel := range channels {
|
for _, channel := range channels {
|
||||||
@ -63,3 +39,39 @@ func reconnectIRC() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func connectIRC(server storage.Server, session *Session) *irc.Client {
|
||||||
|
i := irc.NewClient(server.Nick, server.Username)
|
||||||
|
i.TLS = server.TLS
|
||||||
|
i.Realname = server.Realname
|
||||||
|
i.HandleNickInUse = createNickInUseHandler(i, session)
|
||||||
|
|
||||||
|
address := server.Host
|
||||||
|
if server.Port != "" {
|
||||||
|
address = net.JoinHostPort(server.Host, server.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if server.Password == "" &&
|
||||||
|
viper.GetString("defaults.password") != "" &&
|
||||||
|
address == viper.GetString("defaults.address") {
|
||||||
|
i.Password = viper.GetString("defaults.password")
|
||||||
|
} else {
|
||||||
|
i.Password = server.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.TLS {
|
||||||
|
i.TLSConfig = &tls.Config{
|
||||||
|
InsecureSkipVerify: !viper.GetBool("verify_certificates"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert := session.user.GetCertificate(); cert != nil {
|
||||||
|
i.TLSConfig.Certificates = []tls.Certificate{*cert}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session.setIRC(server.Host, i)
|
||||||
|
i.Connect(address)
|
||||||
|
go newIRCHandler(i, session).run()
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
@ -37,6 +38,7 @@ func newIRCHandler(client *irc.Client, session *Session) *ircHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *ircHandler) run() {
|
func (i *ircHandler) run() {
|
||||||
|
var lastConnErr error
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg, ok := <-i.client.Messages:
|
case msg, ok := <-i.client.Messages:
|
||||||
@ -47,11 +49,17 @@ func (i *ircHandler) run() {
|
|||||||
|
|
||||||
i.dispatchMessage(msg)
|
i.dispatchMessage(msg)
|
||||||
|
|
||||||
case connected := <-i.client.ConnectionChanged:
|
case state := <-i.client.ConnectionChanged:
|
||||||
i.session.sendJSON("connection_update", map[string]bool{
|
i.session.sendJSON("connection_update", newConnectionUpdate(i.client.Host, state))
|
||||||
i.client.Host: connected,
|
i.session.setConnectionState(i.client.Host, state)
|
||||||
})
|
|
||||||
i.session.setConnectionState(i.client.Host, connected)
|
if state.Error != nil && (lastConnErr == nil ||
|
||||||
|
state.Error.Error() != lastConnErr.Error()) {
|
||||||
|
lastConnErr = state.Error
|
||||||
|
i.log("Connection error:", state.Error)
|
||||||
|
} else if state.Connected {
|
||||||
|
i.log("Connected")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,6 +320,11 @@ func (i *ircHandler) initHandlers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *ircHandler) log(v ...interface{}) {
|
||||||
|
s := fmt.Sprintln(v...)
|
||||||
|
log.Println("[IRC]", i.session.user.ID, i.client.Host, s[:len(s)-1])
|
||||||
|
}
|
||||||
|
|
||||||
func parseMode(mode string) *Mode {
|
func parseMode(mode string) *Mode {
|
||||||
m := Mode{}
|
m := Mode{}
|
||||||
add := false
|
add := false
|
||||||
|
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/khlieng/dispatch/irc"
|
||||||
"github.com/khlieng/dispatch/storage"
|
"github.com/khlieng/dispatch/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,14 +17,31 @@ type WSResponse struct {
|
|||||||
Data interface{} `json:"data"`
|
Data interface{} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Connect struct {
|
type Server struct {
|
||||||
Name string `json:"name"`
|
storage.Server
|
||||||
|
Status ConnectionUpdate `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerName struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
TLS bool `json:"tls"`
|
Name string `json:"name"`
|
||||||
Password string `json:"password"`
|
}
|
||||||
Nick string `json:"nick"`
|
|
||||||
Username string `json:"username"`
|
type ConnectionUpdate struct {
|
||||||
Realname string `json:"realname"`
|
Server string `json:"server"`
|
||||||
|
Connected bool `json:"connected"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnectionUpdate(server string, state irc.ConnectionState) ConnectionUpdate {
|
||||||
|
status := ConnectionUpdate{
|
||||||
|
Server: server,
|
||||||
|
Connected: state.Connected,
|
||||||
|
}
|
||||||
|
if state.Error != nil {
|
||||||
|
status.Error = state.Error.Error()
|
||||||
|
}
|
||||||
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
type Nick struct {
|
type Nick struct {
|
||||||
|
@ -16,7 +16,7 @@ const (
|
|||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
irc map[string]*irc.Client
|
irc map[string]*irc.Client
|
||||||
connectionState map[string]bool
|
connectionState map[string]irc.ConnectionState
|
||||||
ircLock sync.Mutex
|
ircLock sync.Mutex
|
||||||
|
|
||||||
ws map[string]*wsConn
|
ws map[string]*wsConn
|
||||||
@ -31,7 +31,7 @@ type Session struct {
|
|||||||
func NewSession(user *storage.User) *Session {
|
func NewSession(user *storage.User) *Session {
|
||||||
return &Session{
|
return &Session{
|
||||||
irc: make(map[string]*irc.Client),
|
irc: make(map[string]*irc.Client),
|
||||||
connectionState: make(map[string]bool),
|
connectionState: make(map[string]irc.ConnectionState),
|
||||||
ws: make(map[string]*wsConn),
|
ws: make(map[string]*wsConn),
|
||||||
broadcast: make(chan WSResponse, 32),
|
broadcast: make(chan WSResponse, 32),
|
||||||
user: user,
|
user: user,
|
||||||
@ -51,7 +51,9 @@ func (s *Session) getIRC(server string) (*irc.Client, bool) {
|
|||||||
func (s *Session) setIRC(server string, i *irc.Client) {
|
func (s *Session) setIRC(server string, i *irc.Client) {
|
||||||
s.ircLock.Lock()
|
s.ircLock.Lock()
|
||||||
s.irc[server] = i
|
s.irc[server] = i
|
||||||
s.connectionState[server] = false
|
s.connectionState[server] = irc.ConnectionState{
|
||||||
|
Connected: false,
|
||||||
|
}
|
||||||
s.ircLock.Unlock()
|
s.ircLock.Unlock()
|
||||||
|
|
||||||
s.reset <- 0
|
s.reset <- 0
|
||||||
@ -74,9 +76,9 @@ func (s *Session) numIRC() int {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) getConnectionStates() map[string]bool {
|
func (s *Session) getConnectionStates() map[string]irc.ConnectionState {
|
||||||
s.ircLock.Lock()
|
s.ircLock.Lock()
|
||||||
state := make(map[string]bool, len(s.connectionState))
|
state := make(map[string]irc.ConnectionState, len(s.connectionState))
|
||||||
|
|
||||||
for k, v := range s.connectionState {
|
for k, v := range s.connectionState {
|
||||||
state[k] = v
|
state[k] = v
|
||||||
@ -86,9 +88,9 @@ func (s *Session) getConnectionStates() map[string]bool {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) setConnectionState(server string, connected bool) {
|
func (s *Session) setConnectionState(server string, state irc.ConnectionState) {
|
||||||
s.ircLock.Lock()
|
s.ircLock.Lock()
|
||||||
s.connectionState[server] = connected
|
s.connectionState[server] = state
|
||||||
s.ircLock.Unlock()
|
s.ircLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/kjk/betterguid"
|
"github.com/kjk/betterguid"
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"github.com/khlieng/dispatch/irc"
|
|
||||||
"github.com/khlieng/dispatch/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type wsHandler struct {
|
type wsHandler struct {
|
||||||
@ -87,56 +81,17 @@ func (h *wsHandler) init(r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *wsHandler) connect(b []byte) {
|
func (h *wsHandler) connect(b []byte) {
|
||||||
var data Connect
|
var data Server
|
||||||
json.Unmarshal(b, &data)
|
json.Unmarshal(b, &data)
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(data.Server)
|
if _, ok := h.session.getIRC(data.Host); !ok {
|
||||||
if err != nil {
|
|
||||||
host = data.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := h.session.getIRC(host); !ok {
|
|
||||||
log.Println(h.addr, "[IRC] Add server", data.Server)
|
log.Println(h.addr, "[IRC] Add server", data.Server)
|
||||||
|
|
||||||
i := irc.NewClient(data.Nick, data.Username)
|
connectIRC(data.Server, h.session)
|
||||||
i.TLS = data.TLS
|
|
||||||
i.Realname = data.Realname
|
|
||||||
i.HandleNickInUse = createNickInUseHandler(i, h.session)
|
|
||||||
|
|
||||||
if data.Password == "" &&
|
go h.session.user.AddServer(data.Server)
|
||||||
viper.GetString("defaults.password") != "" &&
|
|
||||||
data.Server == viper.GetString("defaults.address") {
|
|
||||||
i.Password = viper.GetString("defaults.password")
|
|
||||||
} else {
|
} else {
|
||||||
i.Password = data.Password
|
log.Println(h.addr, "[IRC]", data.Host, "already added")
|
||||||
}
|
|
||||||
|
|
||||||
if i.TLS {
|
|
||||||
i.TLSConfig = &tls.Config{
|
|
||||||
InsecureSkipVerify: !viper.GetBool("verify_certificates"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert := h.session.user.GetCertificate(); cert != nil {
|
|
||||||
i.TLSConfig.Certificates = []tls.Certificate{*cert}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h.session.setIRC(host, i)
|
|
||||||
i.Connect(data.Server)
|
|
||||||
go newIRCHandler(i, h.session).run()
|
|
||||||
|
|
||||||
go h.session.user.AddServer(storage.Server{
|
|
||||||
Name: data.Name,
|
|
||||||
Host: host,
|
|
||||||
Port: port,
|
|
||||||
TLS: data.TLS,
|
|
||||||
Password: data.Password,
|
|
||||||
Nick: data.Nick,
|
|
||||||
Username: data.Username,
|
|
||||||
Realname: data.Realname,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
log.Println(h.addr, "[IRC]", data.Server, "already added")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,7 +242,7 @@ func (h *wsHandler) fetchMessages(b []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *wsHandler) setServerName(b []byte) {
|
func (h *wsHandler) setServerName(b []byte) {
|
||||||
var data Connect
|
var data ServerName
|
||||||
json.Unmarshal(b, &data)
|
json.Unmarshal(b, &data)
|
||||||
|
|
||||||
if isValidServerName(data.Name) {
|
if isValidServerName(data.Name) {
|
||||||
|
@ -25,13 +25,12 @@ type User struct {
|
|||||||
type Server struct {
|
type Server struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
Port string `json:"port,omitempty"`
|
Port string `json:"port"`
|
||||||
TLS bool `json:"tls,omitempty"`
|
TLS bool `json:"tls,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
Nick string `json:"nick"`
|
Nick string `json:"nick"`
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Realname string `json:"realname,omitempty"`
|
Realname string `json:"realname,omitempty"`
|
||||||
Connected bool `json:"connected"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user