diff --git a/app/irc/server/irc-bridge/index.js b/app/irc/server/irc-bridge/index.js index fe8fd7b770cf..c796720c471d 100644 --- a/app/irc/server/irc-bridge/index.js +++ b/app/irc/server/irc-bridge/index.js @@ -1,9 +1,25 @@ +import { Meteor } from 'meteor/meteor'; import Queue from 'queue-fifo'; +import moment from 'moment'; +import _ from 'underscore'; import * as peerCommandHandlers from './peerHandlers'; import * as localCommandHandlers from './localHandlers'; import { callbacks } from '../../../callbacks'; import * as servers from '../servers'; +import { Settings } from '../../../models/server'; + +let removed = false; +const updateLastPing = _.throttle(Meteor.bindEnvironment(() => { + if (removed) { + return; + } + Settings.upsert({ _id: 'IRC_Bridge_Last_Ping' }, { + $set: { + value: new Date(), + }, + }); +}), 1000 * 10); class Bridge { constructor(config) { @@ -27,7 +43,21 @@ class Bridge { } init() { + this.initTime = new Date(); + removed = false; this.loggedInUsers = []; + + const lastPing = Settings.findOneById('IRC_Bridge_Last_Ping'); + if (lastPing) { + if (Math.abs(moment(lastPing.value).diff()) < 1000 * 30) { + this.log('Not trying to connect.'); + this.remove(); + return; + } + } + + this.log('Connecting.'); + updateLastPing(); this.server.register(); this.server.on('registered', () => { @@ -41,6 +71,13 @@ class Bridge { this.server.disconnect(); } + remove() { + this.log('Removing current connection.'); + removed = true; + this.server = null; + this.removeLocalHandlers(); + } + /** * Log helper */ @@ -64,6 +101,19 @@ class Bridge { } async runQueue() { + if (!this.server) { + return; + } + + const lastResetTime = Settings.findOneById('IRC_Bridge_Reset_Time'); + if (lastResetTime && lastResetTime.value > this.initTime) { + this.stop(); + this.remove(); + return; + } + + updateLastPing(); + // If it is empty, skip and keep the queue going if (this.queue.isEmpty()) { return setTimeout(this.runQueue.bind(this), this.queueTimeout); @@ -75,21 +125,26 @@ class Bridge { this.logQueue(`Processing "${ item.command }" command from "${ item.from }"`); // Handle the command accordingly - switch (item.from) { - case 'local': - if (!localCommandHandlers[item.command]) { - throw new Error(`Could not find handler for local:${ item.command }`); - } - - await localCommandHandlers[item.command].apply(this, item.parameters); - break; - case 'peer': - if (!peerCommandHandlers[item.command]) { - throw new Error(`Could not find handler for peer:${ item.command }`); - } - - await peerCommandHandlers[item.command].apply(this, item.parameters); - break; + try { + // Handle the command accordingly + switch (item.from) { + case 'local': + if (!localCommandHandlers[item.command]) { + throw new Error(`Could not find handler for local:${ item.command }`); + } + + await localCommandHandlers[item.command].apply(this, item.parameters); + break; + case 'peer': + if (!peerCommandHandlers[item.command]) { + throw new Error(`Could not find handler for peer:${ item.command }`); + } + + await peerCommandHandlers[item.command].apply(this, item.parameters); + break; + } + } catch (e) { + this.logQueue(e); } // Keep the queue going @@ -132,6 +187,17 @@ class Bridge { callbacks.add('afterLogoutCleanUp', this.onMessageReceived.bind(this, 'local', 'onLogout'), callbacks.priority.LOW, 'irc-on-logout'); } + removeLocalHandlers() { + callbacks.remove('afterValidateLogin', 'irc-on-login'); + callbacks.remove('afterCreateUser', 'irc-on-create-user'); + callbacks.remove('afterCreateChannel', 'irc-on-create-channel'); + callbacks.remove('afterCreateRoom', 'irc-on-create-room'); + callbacks.remove('afterJoinRoom', 'irc-on-join-room'); + callbacks.remove('afterLeaveRoom', 'irc-on-leave-room'); + callbacks.remove('afterSaveMessage', 'irc-on-save-message'); + callbacks.remove('afterLogoutCleanUp', 'irc-on-logout'); + } + sendCommand(command, parameters) { this.server.emit('onReceiveFromLocal', command, parameters); } diff --git a/app/irc/server/irc-bridge/localHandlers/onCreateRoom.js b/app/irc/server/irc-bridge/localHandlers/onCreateRoom.js index a336238a0349..42e01bd78b9f 100644 --- a/app/irc/server/irc-bridge/localHandlers/onCreateRoom.js +++ b/app/irc/server/irc-bridge/localHandlers/onCreateRoom.js @@ -4,7 +4,7 @@ export default function handleOnCreateRoom(user, room) { const users = Users.findByRoomId(room._id); users.forEach((user) => { - if (user.profile.irc.fromIRC) { + if (user.profile?.irc?.fromIRC) { this.sendCommand('joinChannel', { room, user }); } else { this.sendCommand('joinedChannel', { room, user }); diff --git a/app/irc/server/irc-bridge/localHandlers/onLogin.js b/app/irc/server/irc-bridge/localHandlers/onLogin.js index 972d587084f8..856fef3dc686 100644 --- a/app/irc/server/irc-bridge/localHandlers/onLogin.js +++ b/app/irc/server/irc-bridge/localHandlers/onLogin.js @@ -29,8 +29,13 @@ export default function handleOnLogin(login) { }); this.sendCommand('registerUser', user); - const rooms = Rooms.findBySubscriptionUserId(user._id).fetch(); - rooms.forEach((room) => this.sendCommand('joinedChannel', { room, user })); + rooms.forEach((room) => { + if (room.t === 'd') { + return; + } + + this.sendCommand('joinedChannel', { room, user }); + }); } diff --git a/app/irc/server/irc-bridge/localHandlers/onSaveMessage.js b/app/irc/server/irc-bridge/localHandlers/onSaveMessage.js index 88dc1b5301c0..a5ebf1ddfb82 100644 --- a/app/irc/server/irc-bridge/localHandlers/onSaveMessage.js +++ b/app/irc/server/irc-bridge/localHandlers/onSaveMessage.js @@ -6,7 +6,7 @@ export default function handleOnSaveMessage(message, to) { if (to.t === 'd') { const subscriptions = Subscriptions.findByRoomId(to._id); subscriptions.forEach((subscription) => { - if (subscription.u.username !== to.username) { + if (subscription.u._id !== message.u._id) { const userData = Users.findOne({ username: subscription.u.username }); if (userData) { if (userData.profile && userData.profile.irc && userData.profile.irc.nick) { diff --git a/app/irc/server/irc-bridge/peerHandlers/disconnected.js b/app/irc/server/irc-bridge/peerHandlers/disconnected.js index f4e2efe6d6b0..c0ca0b40b3cf 100644 --- a/app/irc/server/irc-bridge/peerHandlers/disconnected.js +++ b/app/irc/server/irc-bridge/peerHandlers/disconnected.js @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { Users, Rooms } from '../../../../models'; +import { Users } from '../../../../models'; export default function handleQUIT(args) { const user = Users.findOne({ @@ -12,6 +12,4 @@ export default function handleQUIT(args) { status: 'offline', }, }); - - Rooms.removeUsernameFromAll(user.username); } diff --git a/app/irc/server/irc-bridge/peerHandlers/sentMessage.js b/app/irc/server/irc-bridge/peerHandlers/sentMessage.js index 347d1d128ede..7d92daccb5c9 100644 --- a/app/irc/server/irc-bridge/peerHandlers/sentMessage.js +++ b/app/irc/server/irc-bridge/peerHandlers/sentMessage.js @@ -1,5 +1,5 @@ -import { Users, Rooms, Subscriptions } from '../../../../models'; -import { sendMessage } from '../../../../lib'; +import { Users, Rooms } from '../../../../models'; +import { sendMessage, createDirectRoom } from '../../../../lib'; /* * * Get direct chat room helper @@ -7,47 +7,21 @@ import { sendMessage } from '../../../../lib'; * */ const getDirectRoom = (source, target) => { - const rid = [source._id, target._id].sort().join(''); + const uids = [source._id, target._id]; + const { _id, ...extraData } = createDirectRoom([source, target]); - Rooms.upsert({ _id: rid }, { - $setOnInsert: { + const room = Rooms.findOneDirectRoomContainingAllUserIDs(uids); + if (room) { + return { t: 'd', - msgs: 0, - ts: new Date(), - }, - }); - - Subscriptions.upsert({ rid, 'u._id': target._id }, { - $setOnInsert: { - name: source.username, - t: 'd', - open: false, - alert: false, - unread: 0, - u: { - _id: target._id, - username: target.username, - }, - }, - }); - - Subscriptions.upsert({ rid, 'u._id': source._id }, { - $setOnInsert: { - name: target.username, - t: 'd', - open: false, - alert: false, - unread: 0, - u: { - _id: source._id, - username: source.username, - }, - }, - }); + ...room, + }; + } return { - _id: rid, + _id, t: 'd', + ...extraData, }; }; diff --git a/app/irc/server/methods/resetIrcConnection.js b/app/irc/server/methods/resetIrcConnection.js index f29076a347b0..dac76ef7b2ee 100644 --- a/app/irc/server/methods/resetIrcConnection.js +++ b/app/irc/server/methods/resetIrcConnection.js @@ -1,32 +1,31 @@ import { Meteor } from 'meteor/meteor'; +import { Settings } from '../../../models/server'; import { settings } from '../../../settings'; -import { t } from '../../../utils'; import Bridge from '../irc-bridge'; Meteor.methods({ resetIrcConnection() { - const ircEnabled = !!settings.get('IRC_Enabled') === true; - - if (Meteor.ircBridge) { - Meteor.ircBridge.stop(); - if (!ircEnabled) { - return { - message: 'Connection_Closed', - params: [], - }; - } + const ircEnabled = Boolean(settings.get('IRC_Enabled')); + Settings.upsert({ _id: 'IRC_Bridge_Last_Ping' }, { + $set: { + value: new Date(0), + }, + }); + Settings.upsert({ _id: 'IRC_Bridge_Reset_Time' }, { + $set: { + value: new Date(), + }, + }); + + if (!ircEnabled) { + return { + message: 'Connection_Closed', + params: [], + }; } - if (ircEnabled) { - if (Meteor.ircBridge) { - Meteor.ircBridge.init(); - return { - message: 'Connection_Reset', - params: [], - }; - } - + setTimeout(Meteor.bindEnvironment(() => { // Normalize the config values const config = { server: { @@ -44,13 +43,11 @@ Meteor.methods({ Meteor.ircBridge = new Bridge(config); Meteor.ircBridge.init(); + }), 300); - return { - message: 'Connection_Reset', - params: [], - }; - } - - throw new Meteor.Error(t('IRC_Federation_Disabled')); + return { + message: 'Connection_Reset', + params: [], + }; }, }); diff --git a/app/irc/server/servers/RFC2813/index.js b/app/irc/server/servers/RFC2813/index.js index 3b8a2f63cd66..1325da3851f2 100644 --- a/app/irc/server/servers/RFC2813/index.js +++ b/app/irc/server/servers/RFC2813/index.js @@ -169,7 +169,7 @@ class RFC2813 { if (localCommandHandlers[command]) { this.log(`Handling local command: ${ command }`); - localCommandHandlers[command].call(this, parameters); + localCommandHandlers[command].call(this, parameters, this); } else { this.log(`Unhandled local command: ${ JSON.stringify(command) }`); } diff --git a/app/irc/server/servers/RFC2813/localCommandHandlers.js b/app/irc/server/servers/RFC2813/localCommandHandlers.js index ddbb450b14c5..e348aca0b70d 100644 --- a/app/irc/server/servers/RFC2813/localCommandHandlers.js +++ b/app/irc/server/servers/RFC2813/localCommandHandlers.js @@ -23,11 +23,19 @@ function joinChannel(parameters) { }); } -function joinedChannel(parameters) { - const { - room: { name: roomName }, - user: { profile: { irc: { nick } } }, - } = parameters; +function joinedChannel(parameters, handler) { + const roomName = parameters.room?.name; + const nick = parameters.user?.profile?.irc?.nick; + + if (!roomName) { + handler.log('Skipping room with no name.'); + return; + } + + if (!nick) { + handler.log('Skipping user with no irc nick.'); + return; + } this.write({ prefix: nick, @@ -56,12 +64,16 @@ function sentMessage(parameters) { message, } = parameters; - this.write({ - prefix: nick, - command: 'PRIVMSG', - parameters: [to], - trailer: message, - }); + // eslint-disable-next-line no-control-regex + const lines = message.toString().split(/\r\n|\r|\n|\u0007/); + for (const line of lines) { + this.write({ + prefix: nick, + command: 'PRIVMSG', + parameters: [to], + trailer: line, + }); + } } function disconnected(parameters) {