[FIX] Watermelon throwing "Cannot update a record with pending updates" (#1754)
This commit is contained in:
parent
c360c2ca86
commit
3d535196a5
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import equal from 'deep-equal';
|
||||||
|
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -42,7 +43,24 @@ const Content = React.memo((props) => {
|
||||||
{content}
|
{content}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}, (prevProps, nextProps) => prevProps.isTemp === nextProps.isTemp && prevProps.msg === nextProps.msg && prevProps.theme === nextProps.theme);
|
}, (prevProps, nextProps) => {
|
||||||
|
if (prevProps.isTemp !== nextProps.isTemp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (prevProps.msg !== nextProps.msg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (prevProps.theme !== nextProps.theme) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!equal(prevProps.mentions, nextProps.mentions)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!equal(prevProps.channels, nextProps.channels)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
Content.propTypes = {
|
Content.propTypes = {
|
||||||
isTemp: PropTypes.bool,
|
isTemp: PropTypes.bool,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import random from '../../utils/random';
|
import random from '../../utils/random';
|
||||||
|
|
||||||
const changeMessageStatus = async(id, tmid, status) => {
|
const changeMessageStatus = async(id, tmid, status, message) => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const msgCollection = db.collections.get('messages');
|
const msgCollection = db.collections.get('messages');
|
||||||
const threadMessagesCollection = db.collections.get('thread_messages');
|
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||||
|
@ -14,6 +14,8 @@ const changeMessageStatus = async(id, tmid, status) => {
|
||||||
successBatch.push(
|
successBatch.push(
|
||||||
messageRecord.prepareUpdate((m) => {
|
messageRecord.prepareUpdate((m) => {
|
||||||
m.status = status;
|
m.status = status;
|
||||||
|
m.mentions = message.mentions;
|
||||||
|
m.channels = message.channels;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -22,6 +24,8 @@ const changeMessageStatus = async(id, tmid, status) => {
|
||||||
successBatch.push(
|
successBatch.push(
|
||||||
threadMessageRecord.prepareUpdate((tm) => {
|
threadMessageRecord.prepareUpdate((tm) => {
|
||||||
tm.status = status;
|
tm.status = status;
|
||||||
|
tm.mentions = message.mentions;
|
||||||
|
tm.channels = message.channels;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,12 +46,12 @@ export async function sendMessageCall(message) {
|
||||||
try {
|
try {
|
||||||
const sdk = this.shareSDK || this.sdk;
|
const sdk = this.shareSDK || this.sdk;
|
||||||
// RC 0.60.0
|
// RC 0.60.0
|
||||||
await sdk.post('chat.sendMessage', {
|
const result = await sdk.post('chat.sendMessage', {
|
||||||
message: {
|
message: {
|
||||||
_id, rid, msg, tmid
|
_id, rid, msg, tmid
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await changeMessageStatus(_id, tmid, messagesStatus.SENT);
|
await changeMessageStatus(_id, tmid, messagesStatus.SENT, result.message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await changeMessageStatus(_id, tmid, messagesStatus.ERROR);
|
await changeMessageStatus(_id, tmid, messagesStatus.ERROR);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,17 @@ import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actio
|
||||||
import debounce from '../../../utils/debounce';
|
import debounce from '../../../utils/debounce';
|
||||||
import RocketChat from '../../rocketchat';
|
import RocketChat from '../../rocketchat';
|
||||||
|
|
||||||
|
const WINDOW_TIME = 1000;
|
||||||
|
|
||||||
export default class RoomSubscription {
|
export default class RoomSubscription {
|
||||||
constructor(rid) {
|
constructor(rid) {
|
||||||
this.rid = rid;
|
this.rid = rid;
|
||||||
this.isAlive = true;
|
this.isAlive = true;
|
||||||
|
this.timer = null;
|
||||||
|
this.queue = {};
|
||||||
|
this.messagesBatch = {};
|
||||||
|
this.threadsBatch = {};
|
||||||
|
this.threadMessagesBatch = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe = async() => {
|
subscribe = async() => {
|
||||||
|
@ -49,6 +56,9 @@ export default class RoomSubscription {
|
||||||
this.removeListener(this.notifyRoomListener);
|
this.removeListener(this.notifyRoomListener);
|
||||||
this.removeListener(this.messageReceivedListener);
|
this.removeListener(this.messageReceivedListener);
|
||||||
reduxStore.dispatch(clearUserTyping());
|
reduxStore.dispatch(clearUserTyping());
|
||||||
|
if (this.timer) {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListener = async(promise) => {
|
removeListener = async(promise) => {
|
||||||
|
@ -131,15 +141,13 @@ export default class RoomSubscription {
|
||||||
RocketChat.readMessages(this.rid, lastOpen);
|
RocketChat.readMessages(this.rid, lastOpen);
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
handleMessageReceived = protectedFunction((ddpMessage) => {
|
updateMessage = message => (
|
||||||
const message = buildMessage(EJSON.fromJSONValue(ddpMessage.fields.args[0]));
|
new Promise(async(resolve) => {
|
||||||
const lastOpen = new Date();
|
if (this.rid !== message.rid) {
|
||||||
if (this.rid !== message.rid) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
InteractionManager.runAfterInteractions(async() => {
|
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const batch = [];
|
|
||||||
const msgCollection = db.collections.get('messages');
|
const msgCollection = db.collections.get('messages');
|
||||||
const threadsCollection = db.collections.get('threads');
|
const threadsCollection = db.collections.get('threads');
|
||||||
const threadMessagesCollection = db.collections.get('thread_messages');
|
const threadMessagesCollection = db.collections.get('thread_messages');
|
||||||
|
@ -154,22 +162,17 @@ export default class RoomSubscription {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
if (messageRecord) {
|
if (messageRecord) {
|
||||||
try {
|
const update = messageRecord.prepareUpdate((m) => {
|
||||||
const update = messageRecord.prepareUpdate((m) => {
|
Object.assign(m, message);
|
||||||
Object.assign(m, message);
|
});
|
||||||
});
|
this._messagesBatch[message._id] = update;
|
||||||
batch.push(update);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
batch.push(
|
const create = msgCollection.prepareCreate(protectedFunction((m) => {
|
||||||
msgCollection.prepareCreate(protectedFunction((m) => {
|
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
||||||
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
|
m.subscription.id = this.rid;
|
||||||
m.subscription.id = this.rid;
|
Object.assign(m, message);
|
||||||
Object.assign(m, message);
|
}));
|
||||||
}))
|
this._messagesBatch[message._id] = create;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or update thread
|
// Create or update thread
|
||||||
|
@ -181,19 +184,17 @@ export default class RoomSubscription {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threadRecord) {
|
if (threadRecord) {
|
||||||
batch.push(
|
const updateThread = threadRecord.prepareUpdate(protectedFunction((t) => {
|
||||||
threadRecord.prepareUpdate(protectedFunction((t) => {
|
Object.assign(t, message);
|
||||||
Object.assign(t, message);
|
}));
|
||||||
}))
|
this._threadsBatch[message._id] = updateThread;
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
batch.push(
|
const createThread = threadsCollection.prepareCreate(protectedFunction((t) => {
|
||||||
threadsCollection.prepareCreate(protectedFunction((t) => {
|
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
||||||
t._raw = sanitizedRaw({ id: message._id }, threadsCollection.schema);
|
t.subscription.id = this.rid;
|
||||||
t.subscription.id = this.rid;
|
Object.assign(t, message);
|
||||||
Object.assign(t, message);
|
}));
|
||||||
}))
|
this._threadsBatch[message._id] = createThread;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,35 +207,75 @@ export default class RoomSubscription {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threadMessageRecord) {
|
if (threadMessageRecord) {
|
||||||
batch.push(
|
const updateThreadMessage = threadMessageRecord.prepareUpdate(protectedFunction((tm) => {
|
||||||
threadMessageRecord.prepareUpdate(protectedFunction((tm) => {
|
Object.assign(tm, message);
|
||||||
Object.assign(tm, message);
|
tm.rid = message.tmid;
|
||||||
tm.rid = message.tmid;
|
delete tm.tmid;
|
||||||
delete tm.tmid;
|
}));
|
||||||
}))
|
this._threadMessagesBatch[message._id] = updateThreadMessage;
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
batch.push(
|
const createThreadMessage = threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
||||||
threadMessagesCollection.prepareCreate(protectedFunction((tm) => {
|
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
||||||
tm._raw = sanitizedRaw({ id: message._id }, threadMessagesCollection.schema);
|
Object.assign(tm, message);
|
||||||
Object.assign(tm, message);
|
tm.subscription.id = this.rid;
|
||||||
tm.subscription.id = this.rid;
|
tm.rid = message.tmid;
|
||||||
tm.rid = message.tmid;
|
delete tm.tmid;
|
||||||
delete tm.tmid;
|
}));
|
||||||
}))
|
this._threadMessagesBatch[message._id] = createThreadMessage;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.read(lastOpen);
|
return resolve();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
handleMessageReceived = (ddpMessage) => {
|
||||||
await db.action(async() => {
|
if (!this.timer) {
|
||||||
await db.batch(...batch);
|
this.timer = setTimeout(async() => {
|
||||||
});
|
// copy variables values to local and clean them
|
||||||
} catch (e) {
|
const _lastOpen = this.lastOpen;
|
||||||
log(e);
|
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;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,17 +74,29 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
|
||||||
// Update
|
// Update
|
||||||
msgsToUpdate = msgsToUpdate.map((message) => {
|
msgsToUpdate = msgsToUpdate.map((message) => {
|
||||||
const newMessage = update.find(m => m._id === message.id);
|
const newMessage = update.find(m => m._id === message.id);
|
||||||
|
if (message._hasPendingUpdate) {
|
||||||
|
console.log(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
return message.prepareUpdate(protectedFunction((m) => {
|
return message.prepareUpdate(protectedFunction((m) => {
|
||||||
Object.assign(m, newMessage);
|
Object.assign(m, newMessage);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
threadsToUpdate = threadsToUpdate.map((thread) => {
|
threadsToUpdate = threadsToUpdate.map((thread) => {
|
||||||
|
if (thread._hasPendingUpdate) {
|
||||||
|
console.log(thread);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newThread = allThreads.find(t => t._id === thread.id);
|
const newThread = allThreads.find(t => t._id === thread.id);
|
||||||
return thread.prepareUpdate(protectedFunction((t) => {
|
return thread.prepareUpdate(protectedFunction((t) => {
|
||||||
Object.assign(t, newThread);
|
Object.assign(t, newThread);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => {
|
threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => {
|
||||||
|
if (threadMessage._hasPendingUpdate) {
|
||||||
|
console.log(threadMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id);
|
const newThreadMessage = allThreadMessages.find(t => t._id === threadMessage.id);
|
||||||
return threadMessage.prepareUpdate(protectedFunction((tm) => {
|
return threadMessage.prepareUpdate(protectedFunction((tm) => {
|
||||||
Object.assign(tm, newThreadMessage);
|
Object.assign(tm, newThreadMessage);
|
||||||
|
|
|
@ -5,6 +5,19 @@ import { isIOS } from './deviceInfo';
|
||||||
|
|
||||||
export const animateNextTransition = debounce(() => {
|
export const animateNextTransition = debounce(() => {
|
||||||
if (isIOS) {
|
if (isIOS) {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.configureNext({
|
||||||
|
duration: 200,
|
||||||
|
create: {
|
||||||
|
type: LayoutAnimation.Types.easeInEaseOut,
|
||||||
|
property: LayoutAnimation.Properties.opacity
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
type: LayoutAnimation.Types.easeInEaseOut
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
type: LayoutAnimation.Types.easeInEaseOut,
|
||||||
|
property: LayoutAnimation.Properties.opacity
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, 200, true);
|
}, 200, true);
|
||||||
|
|
Loading…
Reference in New Issue