From d5a4ead888a4aa7ef363d200c74caf5aa2f390eb Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 28 Sep 2018 15:57:29 -0300 Subject: [PATCH] [FIX] Handle deleted messages (#466) * [FIX] Handle deleted messages * Fix rest error * Fix some connection issues --- .circleci/config.yml | 2 +- app/lib/ddp.js | 9 ++-- app/lib/methods/canOpenRoom.js | 6 ++- app/lib/methods/getCustomEmojis.js | 5 ++ app/lib/methods/getPermissions.js | 5 ++ app/lib/methods/getRooms.js | 7 +-- app/lib/methods/getSettings.js | 5 ++ app/lib/methods/helpers/rest.js | 2 +- app/lib/methods/loadMessagesForRoom.js | 8 ++-- app/lib/methods/loadMissedMessages.js | 57 +++++++++++------------ app/lib/methods/readMessages.js | 6 ++- app/lib/methods/sendMessage.js | 2 +- app/lib/methods/subscriptions/room.js | 3 +- app/lib/rocketchat.js | 17 ++++--- app/sagas/init.js | 3 +- app/views/RoomView/styles.js | 3 +- app/views/RoomsListView/ServerDropdown.js | 10 +++- e2e/08-roomactions.spec.js | 1 + e2e/09-roominfo.spec.js | 2 +- 19 files changed, 93 insertions(+), 60 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 448e19f60..c829654a0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,7 +59,7 @@ jobs: command: | brew update brew tap wix/brew - brew install applesimutils + brew install wix/brew/applesimutils - run: name: Install NPM modules diff --git a/app/lib/ddp.js b/app/lib/ddp.js index eed422eb4..28c460ee3 100644 --- a/app/lib/ddp.js +++ b/app/lib/ddp.js @@ -185,11 +185,14 @@ export default class Socket extends EventEmitter { if (ignore) { return; } - const cancel = this.ddp.once('disconnected', reject); + const cancel = this.once('disconnected', () => { + this.removeListener('disconnected', cancel); + reject(); + }); this.ddp.once(id, (data) => { // console.log(data); this.lastping = new Date(); - this.ddp.removeListener('disconnected', cancel); + this.removeListener('disconnected', cancel); return (data.error ? reject(data.error) : resolve({ id, ...data })); }); }); @@ -273,7 +276,7 @@ export default class Socket extends EventEmitter { if (this._timer || this.forceDisconnect) { return; } - this._close(); + // this._close(); this._logged = false; this._timer = setTimeout(async() => { diff --git a/app/lib/methods/canOpenRoom.js b/app/lib/methods/canOpenRoom.js index ad0e7ad2d..17736665c 100644 --- a/app/lib/methods/canOpenRoom.js +++ b/app/lib/methods/canOpenRoom.js @@ -1,6 +1,7 @@ import { post } from './helpers/rest'; import database from '../realm'; import log from '../../utils/log'; +import store from '../createStore'; // TODO: api fix const ddpTypes = { @@ -12,7 +13,8 @@ const restTypes = { async function canOpenRoomREST({ type, rid }) { try { - const { token, id } = this.ddp._login; + const { user } = store.getState().login; + const { token, id } = user; const server = this.ddp.url.replace(/^ws/, 'http'); await post({ token, id, server }, `${ restTypes[type] }.open`, { roomId: rid }); return true; @@ -50,7 +52,7 @@ export default async function canOpenRoom({ rid, path }) { try { // eslint-disable-next-line - const data = await (this.ddp && this.ddp.status && false ? canOpenRoomDDP.call(this, { rid, type, name }) : canOpenRoomREST.call(this, { type, rid })); + const data = await (this.ddp && this.ddp.status ? canOpenRoomDDP.call(this, { rid, type, name }) : canOpenRoomREST.call(this, { type, rid })); return data; } catch (e) { log('canOpenRoom', e); diff --git a/app/lib/methods/getCustomEmojis.js b/app/lib/methods/getCustomEmojis.js index 48fea4e4c..bea2fbde9 100644 --- a/app/lib/methods/getCustomEmojis.js +++ b/app/lib/methods/getCustomEmojis.js @@ -15,6 +15,11 @@ const getLastMessage = () => { export default async function() { try { + if (!this.ddp) { + // TODO: should implement loop or get from rest? + return; + } + const lastMessage = getLastMessage(); let emojis = await this.ddp.call('listEmojiCustom'); emojis = emojis.filter(emoji => !lastMessage || emoji._updatedAt > lastMessage); diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index 231054812..7a6758c16 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -11,6 +11,11 @@ const getLastUpdate = () => { export default async function() { try { + if (!this.ddp) { + // TODO: should implement loop or get from rest? + return; + } + const lastUpdate = getLastUpdate(); const result = await (!lastUpdate ? this.ddp.call('permissions/get') : this.ddp.call('permissions/get', new Date(lastUpdate))); const permissions = (result.update || result).filter(permission => defaultPermissions.includes(permission._id)); diff --git a/app/lib/methods/getRooms.js b/app/lib/methods/getRooms.js index 72749e9b1..dc1c0ef25 100644 --- a/app/lib/methods/getRooms.js +++ b/app/lib/methods/getRooms.js @@ -5,6 +5,7 @@ import { get } from './helpers/rest'; import mergeSubscriptionsRooms, { merge } from './helpers/mergeSubscriptionsRooms'; import database from '../realm'; import log from '../../utils/log'; +import store from '../createStore'; const lastMessage = () => { const message = database @@ -14,9 +15,9 @@ const lastMessage = () => { }; const getRoomRest = async function() { - const { ddp } = this; const updatedSince = lastMessage(); - const { token, id } = ddp._login; + const { user } = store.getState().login; + const { token, id } = user; const server = this.ddp.url.replace(/^ws/, 'http'); const [subscriptions, rooms] = await Promise.all([get({ token, id, server }, 'subscriptions.get', { updatedSince }), get({ token, id, server }, 'rooms.get', { updatedSince })]); return mergeSubscriptionsRooms(subscriptions, rooms); @@ -39,7 +40,7 @@ export default async function() { return new Promise(async(resolve, reject) => { try { // eslint-disable-next-line - const { subscriptions, rooms } = await (this.ddp.status && false ? getRoomDpp.apply(this) : getRoomRest.apply(this)); + const { subscriptions, rooms } = await (this.ddp && this.ddp.status ? getRoomDpp.apply(this) : getRoomRest.apply(this)); const data = rooms.map(room => ({ room, sub: database.objects('subscriptions').filtered('rid == $0', room._id) })); diff --git a/app/lib/methods/getSettings.js b/app/lib/methods/getSettings.js index 7bedac0d7..13d7d7f16 100644 --- a/app/lib/methods/getSettings.js +++ b/app/lib/methods/getSettings.js @@ -20,6 +20,11 @@ function updateServer(param) { export default async function() { try { + if (!this.ddp) { + // TODO: should implement loop or get from rest? + return; + } + const lastUpdate = getLastUpdate(); const fetchNewSettings = lastUpdate < settingsUpdatedAt; const result = await ((!lastUpdate || fetchNewSettings) ? this.ddp.call('public-settings/get') : this.ddp.call('public-settings/get', new Date(lastUpdate))); diff --git a/app/lib/methods/helpers/rest.js b/app/lib/methods/helpers/rest.js index 1efe08f54..d155ef3b4 100644 --- a/app/lib/methods/helpers/rest.js +++ b/app/lib/methods/helpers/rest.js @@ -2,7 +2,7 @@ import toQuery from './toQuery'; const handleSuccess = (msg) => { - if (msg.success !== undefined && !msg.success) { + if ((msg.success !== undefined && !msg.success) || (msg.status && msg.status === 'error')) { return Promise.reject(msg); } return msg; diff --git a/app/lib/methods/loadMessagesForRoom.js b/app/lib/methods/loadMessagesForRoom.js index 22e35a516..b1241425c 100644 --- a/app/lib/methods/loadMessagesForRoom.js +++ b/app/lib/methods/loadMessagesForRoom.js @@ -4,6 +4,7 @@ import { get } from './helpers/rest'; import buildMessage from './helpers/buildMessage'; import database from '../realm'; import log from '../../utils/log'; +import store from '../createStore'; // TODO: api fix const types = { @@ -11,7 +12,8 @@ const types = { }; async function loadMessagesForRoomRest({ rid: roomId, latest, t }) { - const { token, id } = this.ddp._login; + const { user } = store.getState().login; + const { token, id } = user; const server = this.ddp.url.replace(/^ws/, 'http'); const data = await get({ token, id, server }, `${ types[t] }.history`, { roomId, latest }); if (!data || data.status === 'error') { @@ -36,11 +38,9 @@ async function loadMessagesForRoomDDP(...args) { export default async function loadMessagesForRoom(...args) { const { database: db } = database; - return new Promise(async(resolve, reject) => { try { - // eslint-disable-next-line - const data = (await (this.ddp.status ? loadMessagesForRoomDDP.call(this, ...args) : loadMessagesForRoomRest.call(this, ...args))).map(buildMessage); + const data = (await (this.ddp && this.ddp.status ? loadMessagesForRoomDDP.call(this, ...args) : loadMessagesForRoomRest.call(this, ...args))).map(buildMessage); if (data && data.length) { InteractionManager.runAfterInteractions(() => { diff --git a/app/lib/methods/loadMissedMessages.js b/app/lib/methods/loadMissedMessages.js index ad8c67a29..92c7b72ee 100644 --- a/app/lib/methods/loadMissedMessages.js +++ b/app/lib/methods/loadMissedMessages.js @@ -4,56 +4,53 @@ import { get } from './helpers/rest'; import buildMessage from './helpers/buildMessage'; import database from '../realm'; import log from '../../utils/log'; +import store from '../createStore'; async function loadMissedMessagesRest({ rid: roomId, lastOpen: lastUpdate }) { - const { token, id } = this.ddp._login; + const { user } = store.getState().login; + const { token, id } = user; const server = this.ddp.url.replace(/^ws/, 'http'); const { result } = await get({ token, id, server }, 'chat.syncMessages', { roomId, lastUpdate }); - // TODO: api fix - if (!result) { - return []; - } - return result.updated || result.messages; + return result; } async function loadMissedMessagesDDP(...args) { const [{ rid, lastOpen: lastUpdate }] = args; try { - const data = await this.ddp.call('messages/get', rid, { lastUpdate: new Date(lastUpdate) }); - return data.updated || data.messages; + const result = await this.ddp.call('messages/get', rid, { lastUpdate: new Date(lastUpdate) }); + return result; } catch (e) { return loadMissedMessagesRest.call(this, ...args); } - - // } - // if (cb) { - // cb({ end: data && data.messages.length < 20 }); - // } - // return data.message; - // }, (err) => { - // if (err) { - // if (cb) { - // cb({ end: true }); - // } - // return Promise.reject(err); - // } - // }); } -export default async function(...args) { +export default async function loadMissedMessages(...args) { const { database: db } = database; return new Promise(async(resolve, reject) => { try { - // eslint-disable-next-line - const data = (await (this.ddp.status ? loadMissedMessagesDDP.call(this, ...args) : loadMissedMessagesRest.call(this, ...args))); + const data = (await (this.ddp && this.ddp.status ? loadMissedMessagesDDP.call(this, ...args) : loadMissedMessagesRest.call(this, ...args))); if (data) { - data.forEach(buildMessage); - return InteractionManager.runAfterInteractions(() => { - db.write(() => data.forEach(message => db.create('messages', message, true))); - resolve(data); - }); + if (data.updated && data.updated.length) { + const { updated } = data; + updated.forEach(buildMessage); + InteractionManager.runAfterInteractions(() => { + db.write(() => updated.forEach(message => db.create('messages', message, true))); + resolve(updated); + }); + } + if (data.deleted && data.deleted.length) { + const { deleted } = data; + InteractionManager.runAfterInteractions(() => { + db.write(() => { + deleted.forEach((m) => { + const message = database.objects('messages').filtered('_id = $0', m._id); + database.delete(message); + }); + }); + }); + } } resolve([]); } catch (e) { diff --git a/app/lib/methods/readMessages.js b/app/lib/methods/readMessages.js index af4644882..89ca9c691 100644 --- a/app/lib/methods/readMessages.js +++ b/app/lib/methods/readMessages.js @@ -1,9 +1,11 @@ import { post } from './helpers/rest'; import database from '../realm'; import log from '../../utils/log'; +import store from '../createStore'; const readMessagesREST = function readMessagesREST(rid) { - const { token, id } = this.ddp._login; + const { user } = store.getState().login; + const { token, id } = user; const server = this.ddp.url.replace(/^ws/, 'http'); return post({ token, id, server }, 'subscriptions.read', { rid }); }; @@ -20,7 +22,7 @@ export default async function readMessages(rid) { const { database: db } = database; try { // eslint-disable-next-line - const data = await (this.ddp.status && false ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid)); + const data = await (this.ddp && this.ddp.status ? readMessagesDDP.call(this, rid) : readMessagesREST.call(this, rid)); const [subscription] = db.objects('subscriptions').filtered('rid = $0', rid); db.write(() => { subscription.open = true; diff --git a/app/lib/methods/sendMessage.js b/app/lib/methods/sendMessage.js index df5d3f33d..e5dc0829d 100644 --- a/app/lib/methods/sendMessage.js +++ b/app/lib/methods/sendMessage.js @@ -46,7 +46,7 @@ function sendMessageByDDP(message) { export async function _sendMessageCall(message) { try { // eslint-disable-next-line - const data = await (this.ddp.status && false ? sendMessageByDDP.call(this, message) : sendMessageByRest.call(this, message)); + const data = await (this.ddp && this.ddp.status ? sendMessageByDDP.call(this, message) : sendMessageByRest.call(this, message)); return data; } catch (e) { database.write(() => { diff --git a/app/lib/methods/subscriptions/room.js b/app/lib/methods/subscriptions/room.js index 6d74bea23..c9d2107b6 100644 --- a/app/lib/methods/subscriptions/room.js +++ b/app/lib/methods/subscriptions/room.js @@ -7,7 +7,8 @@ import log from '../../../utils/log'; const subscribe = (ddp, rid) => Promise.all([ ddp.subscribe('stream-room-messages', rid, false), - ddp.subscribe('stream-notify-room', `${ rid }/typing`, false) + ddp.subscribe('stream-notify-room', `${ rid }/typing`, false), + ddp.subscribe('stream-notify-room', `${ rid }/deleteMessage`, false) ]); const unsubscribe = subscriptions => subscriptions.forEach(sub => sub.unsubscribe().catch((e) => { log('unsubscribeRoom', e); diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 6380148f3..4e653c074 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -171,8 +171,6 @@ const RocketChat = { this.ddp.on('background', () => this.getRooms().catch(e => log('background getRooms', e))); - this.ddp.on('disconnected', () => console.log('disconnected')); - this.ddp.on('logged', protectedFunction((user) => { this.loginSuccess(user); this.getRooms().catch(e => log('logged getRooms', e)); @@ -184,7 +182,7 @@ const RocketChat = { this.ddp.on('disconnected', protectedFunction(() => { reduxStore.dispatch(disconnect()); - console.warn(this.ddp); + console.log('disconnected', this.ddp); })); this.ddp.on('stream-room-messages', (ddpMessage) => { @@ -195,10 +193,17 @@ const RocketChat = { this.ddp.on('stream-notify-room', protectedFunction((ddpMessage) => { const [_rid, ev] = ddpMessage.fields.eventName.split('/'); - if (ev !== 'typing') { - return; + if (ev === 'typing') { + reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] })); + } else if (ev === 'deleteMessage') { + database.write(() => { + if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) { + const { _id } = ddpMessage.fields.args[0]; + const message = database.objects('messages').filtered('_id = $0', _id); + database.delete(message); + } + }); } - return reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] })); })); this.ddp.on('rocketchat_starred_message', protectedFunction((ddpMessage) => { diff --git a/app/sagas/init.js b/app/sagas/init.js index ccdddb332..5bef66d82 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -21,14 +21,13 @@ const restore = function* restore() { const currentServer = yield call([AsyncStorage, 'getItem'], 'currentServer'); if (currentServer) { - yield put(selectServerRequest(currentServer)); - const user = yield call([AsyncStorage, 'getItem'], `${ RocketChat.TOKEN_KEY }-${ currentServer }`); if (user) { const userParsed = JSON.parse(user); if (userParsed.language) { I18n.locale = userParsed.language; } + yield put(selectServerRequest(currentServer)); yield put(setUser(userParsed)); } } diff --git a/app/views/RoomView/styles.js b/app/views/RoomView/styles.js index f1095ecc5..6deca0888 100644 --- a/app/views/RoomView/styles.js +++ b/app/views/RoomView/styles.js @@ -46,8 +46,7 @@ export default StyleSheet.create({ flexDirection: 'column' }, loading: { - flex: 1, - marginVertical: 15 + flex: 1 }, imageBackground: { width: '100%', diff --git a/app/views/RoomsListView/ServerDropdown.js b/app/views/RoomsListView/ServerDropdown.js index 11bb12b40..35df68bd9 100644 --- a/app/views/RoomsListView/ServerDropdown.js +++ b/app/views/RoomsListView/ServerDropdown.js @@ -3,7 +3,8 @@ import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, AsyncStorage } from 'react-native'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; +import { connect, Provider } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms'; import { selectServerRequest as selectServerRequestAction } from '../../actions/server'; @@ -13,10 +14,13 @@ import database from '../../lib/realm'; import Touch from '../../utils/touch'; import RocketChat from '../../lib/rocketchat'; import I18n from '../../i18n'; +import store from '../../lib/createStore'; const ROW_HEIGHT = 68; const ANIMATION_DURATION = 200; +let NewServerView = null; + @connect(state => ({ closeServerDropdown: state.rooms.closeServerDropdown, server: state.server.server @@ -111,6 +115,10 @@ export default class ServerDropdown extends Component { const token = await AsyncStorage.getItem(`${ RocketChat.TOKEN_KEY }-${ server }`); if (!token) { appStart(); + if (NewServerView == null) { + NewServerView = require('../NewServerView').default; + Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); + } setTimeout(() => { navigator.push({ screen: 'NewServerView', diff --git a/e2e/08-roomactions.spec.js b/e2e/08-roomactions.spec.js index 6ed5bea8f..f2f0c0ea3 100644 --- a/e2e/08-roomactions.spec.js +++ b/e2e/08-roomactions.spec.js @@ -14,6 +14,7 @@ async function navigateToRoomActions(type) { } else { room = `private${ data.random }`; } + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); await element(by.id('rooms-list-view-search')).replaceText(room); await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); await element(by.id(`rooms-list-view-item-${ room }`)).tap(); diff --git a/e2e/09-roominfo.spec.js b/e2e/09-roominfo.spec.js index 2185833f5..6da75bf85 100644 --- a/e2e/09-roominfo.spec.js +++ b/e2e/09-roominfo.spec.js @@ -14,7 +14,7 @@ async function navigateToRoomInfo(type) { } await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); await element(by.id('rooms-list-view-search')).replaceText(room); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); await element(by.id(`rooms-list-view-item-${ room }`)).tap(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); await element(by.id('room-view-header-actions')).tap();