2019-04-08 12:35:28 +00:00
|
|
|
import EJSON from 'ejson';
|
2019-09-16 20:26:32 +00:00
|
|
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
|
|
|
import { InteractionManager } from 'react-native';
|
2019-04-08 12:35:28 +00:00
|
|
|
|
2018-05-18 17:55:08 +00:00
|
|
|
import log from '../../../utils/log';
|
2019-04-08 12:35:28 +00:00
|
|
|
import protectedFunction from '../helpers/protectedFunction';
|
|
|
|
import buildMessage from '../helpers/buildMessage';
|
2019-09-16 20:26:32 +00:00
|
|
|
import database from '../../database';
|
|
|
|
import reduxStore from '../../createStore';
|
|
|
|
import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping';
|
2019-10-08 12:36:15 +00:00
|
|
|
import debounce from '../../../utils/debounce';
|
2020-02-05 13:34:53 +00:00
|
|
|
import RocketChat from '../../rocketchat';
|
2020-04-30 17:53:35 +00:00
|
|
|
import { subscribeRoom, unsubscribeRoom } from '../../../actions/room';
|
2020-09-11 14:31:38 +00:00
|
|
|
import { Encryption } from '../../encryption';
|
2020-02-05 13:34:53 +00:00
|
|
|
|
2020-03-02 20:12:41 +00:00
|
|
|
const WINDOW_TIME = 1000;
|
|
|
|
|
2020-02-05 13:34:53 +00:00
|
|
|
export default class RoomSubscription {
|
|
|
|
constructor(rid) {
|
|
|
|
this.rid = rid;
|
|
|
|
this.isAlive = true;
|
2020-03-02 20:12:41 +00:00
|
|
|
this.timer = null;
|
|
|
|
this.queue = {};
|
|
|
|
this.messagesBatch = {};
|
|
|
|
this.threadsBatch = {};
|
|
|
|
this.threadMessagesBatch = {};
|
2020-02-05 13:34:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
subscribe = async() => {
|
|
|
|
console.log(`[RCRN] Subscribing to room ${ this.rid }`);
|
|
|
|
if (this.promises) {
|
|
|
|
await this.unsubscribe();
|
|
|
|
}
|
|
|
|
this.promises = RocketChat.subscribeRoom(this.rid);
|
|
|
|
|
|
|
|
this.connectedListener = RocketChat.onStreamData('connected', this.handleConnection);
|
|
|
|
this.disconnectedListener = RocketChat.onStreamData('close', this.handleConnection);
|
|
|
|
this.notifyRoomListener = RocketChat.onStreamData('stream-notify-room', this.handleNotifyRoomReceived);
|
|
|
|
this.messageReceivedListener = RocketChat.onStreamData('stream-room-messages', this.handleMessageReceived);
|
|
|
|
if (!this.isAlive) {
|
|
|
|
this.unsubscribe();
|
|
|
|
}
|
2020-04-30 17:53:35 +00:00
|
|
|
|
|
|
|
reduxStore.dispatch(subscribeRoom(this.rid));
|
2020-02-05 13:34:53 +00:00
|
|
|
}
|
2018-04-24 19:34:03 +00:00
|
|
|
|
2020-02-05 13:34:53 +00:00
|
|
|
unsubscribe = async() => {
|
|
|
|
console.log(`[RCRN] Unsubscribing from room ${ this.rid }`);
|
|
|
|
this.isAlive = false;
|
2020-08-25 17:44:16 +00:00
|
|
|
reduxStore.dispatch(unsubscribeRoom(this.rid));
|
2020-02-05 13:34:53 +00:00
|
|
|
if (this.promises) {
|
|
|
|
try {
|
|
|
|
const subscriptions = await this.promises || [];
|
2020-02-17 16:07:09 +00:00
|
|
|
subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom')));
|
2020-02-05 13:34:53 +00:00
|
|
|
} catch (e) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
}
|
2020-04-03 18:03:53 +00:00
|
|
|
reduxStore.dispatch(clearUserTyping());
|
2020-02-05 13:34:53 +00:00
|
|
|
this.removeListener(this.connectedListener);
|
|
|
|
this.removeListener(this.disconnectedListener);
|
|
|
|
this.removeListener(this.notifyRoomListener);
|
|
|
|
this.removeListener(this.messageReceivedListener);
|
2020-03-02 20:12:41 +00:00
|
|
|
if (this.timer) {
|
|
|
|
clearTimeout(this.timer);
|
|
|
|
}
|
2020-02-05 13:34:53 +00:00
|
|
|
}
|
2019-12-13 16:23:20 +00:00
|
|
|
|
2020-02-05 13:34:53 +00:00
|
|
|
removeListener = async(promise) => {
|
|
|
|
if (promise) {
|
|
|
|
try {
|
|
|
|
const listener = await promise;
|
|
|
|
listener.stop();
|
|
|
|
} catch (e) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2018-04-24 19:34:03 +00:00
|
|
|
|
2020-02-05 13:34:53 +00:00
|
|
|
handleConnection = () => {
|
2020-04-03 18:03:53 +00:00
|
|
|
reduxStore.dispatch(clearUserTyping());
|
2020-02-05 13:34:53 +00:00
|
|
|
RocketChat.loadMissedMessages({ rid: this.rid }).catch(e => console.log(e));
|
2019-04-08 12:35:28 +00:00
|
|
|
};
|
|
|
|
|
2020-02-05 13:34:53 +00:00
|
|
|
handleNotifyRoomReceived = protectedFunction((ddpMessage) => {
|
2019-04-08 12:35:28 +00:00
|
|
|
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
2020-02-05 13:34:53 +00:00
|
|
|
if (this.rid !== _rid) {
|
2019-04-08 12:35:28 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (ev === 'typing') {
|
2020-03-26 16:59:11 +00:00
|
|
|
const { user } = reduxStore.getState().login;
|
2020-06-26 20:48:40 +00:00
|
|
|
const { UI_Use_Real_Name } = reduxStore.getState().settings;
|
2021-03-15 20:16:34 +00:00
|
|
|
const { rooms } = reduxStore.getState().room;
|
|
|
|
if (rooms[0] !== _rid) {
|
|
|
|
return;
|
|
|
|
}
|
2020-06-26 20:48:40 +00:00
|
|
|
const [name, typing] = ddpMessage.fields.args;
|
|
|
|
const key = UI_Use_Real_Name ? 'name' : 'username';
|
|
|
|
if (name !== user[key]) {
|
2020-03-26 16:59:11 +00:00
|
|
|
if (typing) {
|
2020-06-26 20:48:40 +00:00
|
|
|
reduxStore.dispatch(addUserTyping(name));
|
2020-03-26 16:59:11 +00:00
|
|
|
} else {
|
2020-06-26 20:48:40 +00:00
|
|
|
reduxStore.dispatch(removeUserTyping(name));
|
2020-03-26 16:59:11 +00:00
|
|
|
}
|
2019-04-08 12:35:28 +00:00
|
|
|
}
|
|
|
|
} else if (ev === 'deleteMessage') {
|
2019-09-16 20:26:32 +00:00
|
|
|
InteractionManager.runAfterInteractions(async() => {
|
2019-04-08 12:35:28 +00:00
|
|
|
if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) {
|
2019-09-16 20:26:32 +00:00
|
|
|
try {
|
|
|
|
const { _id } = ddpMessage.fields.args[0];
|
|
|
|
const db = database.active;
|
2021-03-15 20:16:34 +00:00
|
|
|
const msgCollection = db.get('messages');
|
|
|
|
const threadsCollection = db.get('threads');
|
|
|
|
const threadMessagesCollection = db.get('thread_messages');
|
2019-09-16 20:26:32 +00:00
|
|
|
let deleteMessage;
|
|
|
|
let deleteThread;
|
|
|
|
let deleteThreadMessage;
|
|
|
|
|
|
|
|
// Delete message
|
|
|
|
try {
|
|
|
|
const m = await msgCollection.find(_id);
|
|
|
|
deleteMessage = m.prepareDestroyPermanently();
|
|
|
|
} catch (e) {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete thread
|
|
|
|
try {
|
|
|
|
const m = await threadsCollection.find(_id);
|
|
|
|
deleteThread = m.prepareDestroyPermanently();
|
|
|
|
} catch (e) {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete thread message
|
|
|
|
try {
|
|
|
|
const m = await threadMessagesCollection.find(_id);
|
|
|
|
deleteThreadMessage = m.prepareDestroyPermanently();
|
|
|
|
} catch (e) {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
await db.action(async() => {
|
|
|
|
await db.batch(
|
|
|
|
deleteMessage, deleteThread, deleteThreadMessage
|
|
|
|
);
|
2019-04-17 17:01:03 +00:00
|
|
|
});
|
2019-09-16 20:26:32 +00:00
|
|
|
} catch (e) {
|
|
|
|
log(e);
|
2019-04-17 17:01:03 +00:00
|
|
|
}
|
2019-04-08 12:35:28 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-02-05 13:34:53 +00:00
|
|
|
read = debounce((lastOpen) => {
|
2020-02-05 17:31:36 +00:00
|
|
|
RocketChat.readMessages(this.rid, lastOpen);
|
2019-10-08 12:36:15 +00:00
|
|
|
}, 300);
|
|
|
|
|
2020-03-02 20:12:41 +00:00
|
|
|
updateMessage = message => (
|
|
|
|
new Promise(async(resolve) => {
|
|
|
|
if (this.rid !== message.rid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-09-16 20:26:32 +00:00
|
|
|
const db = database.active;
|
2021-03-15 20:16:34 +00:00
|
|
|
const msgCollection = db.get('messages');
|
|
|
|
const threadsCollection = db.get('threads');
|
|
|
|
const threadMessagesCollection = db.get('thread_messages');
|
2019-09-16 20:26:32 +00:00
|
|
|
|
2020-09-11 14:31:38 +00:00
|
|
|
// Decrypt the message if necessary
|
|
|
|
message = await Encryption.decryptMessage(message);
|
|
|
|
|
2019-09-16 20:26:32 +00:00
|
|
|
// Create or update message
|
2019-04-08 12:35:28 +00:00
|
|
|
try {
|
2020-04-30 18:04:39 +00:00
|
|
|
const messageRecord = await msgCollection.find(message._id);
|
|
|
|
if (!messageRecord._hasPendingUpdate) {
|
|
|
|
const update = messageRecord.prepareUpdate(protectedFunction((m) => {
|
|
|
|
Object.assign(m, message);
|
|
|
|
}));
|
|
|
|
this._messagesBatch[message._id] = update;
|
|
|
|
}
|
|
|
|
} catch {
|
2020-03-02 20:12:41 +00:00
|
|
|
const create = msgCollection.prepareCreate(protectedFunction((m) => {
|
|
|
|
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
|
|
|
m.subscription.id = this.rid;
|
|
|
|
Object.assign(m, message);
|
|
|
|
}));
|
|
|
|
this._messagesBatch[message._id] = create;
|
2019-09-16 20:26:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create or update thread
|
|
|
|
if (message.tlm) {
|
|
|
|
try {
|
2020-04-30 18:04:39 +00:00
|
|
|
const threadRecord = await threadsCollection.find(message._id);
|
|
|
|
if (!threadRecord._hasPendingUpdate) {
|
|
|
|
const updateThread = threadRecord.prepareUpdate(protectedFunction((t) => {
|
|
|
|
Object.assign(t, message);
|
|
|
|
}));
|
|
|
|
this._threadsBatch[message._id] = updateThread;
|
|
|
|
}
|
|
|
|
} catch {
|
2020-03-02 20:12:41 +00:00
|
|
|
const createThread = threadsCollection.prepareCreate(protectedFunction((t) => {
|
|
|
|
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
|
|
|
t.subscription.id = this.rid;
|
|
|
|
Object.assign(t, message);
|
|
|
|
}));
|
|
|
|
this._threadsBatch[message._id] = createThread;
|
2019-09-16 20:26:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create or update thread message
|
|
|
|
if (message.tmid) {
|
|
|
|
try {
|
2020-04-30 18:04:39 +00:00
|
|
|
const threadMessageRecord = await threadMessagesCollection.find(message._id);
|
|
|
|
if (!threadMessageRecord._hasPendingUpdate) {
|
|
|
|
const updateThreadMessage = threadMessageRecord.prepareUpdate(protectedFunction((tm) => {
|
|
|
|
Object.assign(tm, message);
|
|
|
|
tm.rid = message.tmid;
|
|
|
|
delete tm.tmid;
|
|
|
|
}));
|
|
|
|
this._threadMessagesBatch[message._id] = updateThreadMessage;
|
|
|
|
}
|
|
|
|
} catch {
|
2020-03-02 20:12:41 +00:00
|
|
|
const createThreadMessage = threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
|
|
|
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
|
|
|
Object.assign(tm, message);
|
|
|
|
tm.subscription.id = this.rid;
|
|
|
|
tm.rid = message.tmid;
|
|
|
|
delete tm.tmid;
|
|
|
|
}));
|
|
|
|
this._threadMessagesBatch[message._id] = createThreadMessage;
|
2019-09-16 20:26:32 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-08 12:35:28 +00:00
|
|
|
|
2020-03-02 20:12:41 +00:00
|
|
|
return resolve();
|
|
|
|
})
|
|
|
|
)
|
2019-09-16 20:26:32 +00:00
|
|
|
|
2020-03-02 20:12:41 +00:00
|
|
|
handleMessageReceived = (ddpMessage) => {
|
|
|
|
if (!this.timer) {
|
|
|
|
this.timer = setTimeout(async() => {
|
|
|
|
// copy variables values to local and clean them
|
|
|
|
const _lastOpen = this.lastOpen;
|
|
|
|
const _queue = Object.keys(this.queue).map(key => this.queue[key]);
|
|
|
|
this._messagesBatch = this.messagesBatch;
|
|
|
|
this._threadsBatch = this.threadsBatch;
|
|
|
|
this._threadMessagesBatch = this.threadMessagesBatch;
|
|
|
|
this.queue = {};
|
|
|
|
this.messagesBatch = {};
|
|
|
|
this.threadsBatch = {};
|
|
|
|
this.threadMessagesBatch = {};
|
|
|
|
this.timer = null;
|
|
|
|
|
|
|
|
for (let i = 0; i < _queue.length; i += 1) {
|
|
|
|
try {
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
await this.updateMessage(_queue[i]);
|
|
|
|
} catch (e) {
|
|
|
|
log(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const db = database.active;
|
|
|
|
await db.action(async() => {
|
|
|
|
await db.batch(
|
|
|
|
...Object.values(this._messagesBatch),
|
|
|
|
...Object.values(this._threadsBatch),
|
|
|
|
...Object.values(this._threadMessagesBatch)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.read(_lastOpen);
|
|
|
|
} catch (e) {
|
|
|
|
log(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clean local variables
|
|
|
|
this._messagesBatch = {};
|
|
|
|
this._threadsBatch = {};
|
|
|
|
this._threadMessagesBatch = {};
|
|
|
|
}, WINDOW_TIME);
|
|
|
|
}
|
|
|
|
this.lastOpen = new Date();
|
|
|
|
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
|
|
|
this.queue[message._id] = message;
|
|
|
|
};
|
2018-04-24 19:34:03 +00:00
|
|
|
}
|