Super fast message receiving

pull/1279/head
Vincent 5 years ago
parent 28a0d82ea2
commit efd5b20a6e

@ -571,6 +571,8 @@
const model = this.addSingleMessage(message); const model = this.addSingleMessage(message);
MessageController.register(model.id, model); MessageController.register(model.id, model);
console.log(`[vince] Changed triggered from model`);
this.trigger('change'); this.trigger('change');
}, },
addSingleMessage(message, setToExpire = true) { addSingleMessage(message, setToExpire = true) {

@ -20,6 +20,8 @@ import { Constants, getMessageQueue } from '../../../session';
import { MessageQueue } from '../../../session/sending'; import { MessageQueue } from '../../../session/sending';
import { SessionKeyVerification } from '../SessionKeyVerification'; import { SessionKeyVerification } from '../SessionKeyVerification';
import _ from 'lodash'; import _ from 'lodash';
import { UserUtil } from '../../../util';
import { MultiDeviceProtocol } from '../../../session/protocols';
interface State { interface State {
conversationKey: string; conversationKey: string;
@ -35,6 +37,7 @@ interface State {
sendingProgressStatus: -1 | 0 | 1 | 2; sendingProgressStatus: -1 | 0 | 1 | 2;
unreadCount: number; unreadCount: number;
initialFetchComplete: boolean;
messages: Array<any>; messages: Array<any>;
selectedMessages: Array<string>; selectedMessages: Array<string>;
isScrolledToBottom: boolean; isScrolledToBottom: boolean;
@ -75,6 +78,7 @@ export class SessionConversation extends React.Component<any, State> {
sendingProgressStatus: 0, sendingProgressStatus: 0,
conversationKey, conversationKey,
unreadCount, unreadCount,
initialFetchComplete: false,
messages: [], messages: [],
selectedMessages: [], selectedMessages: [],
isScrolledToBottom: !unreadCount, isScrolledToBottom: !unreadCount,
@ -107,6 +111,7 @@ export class SessionConversation extends React.Component<any, State> {
this.onExitVoiceNoteView = this.onExitVoiceNoteView.bind(this); this.onExitVoiceNoteView = this.onExitVoiceNoteView.bind(this);
// Messages // Messages
this.loadInitialMessages = this.loadInitialMessages.bind(this);
this.selectMessage = this.selectMessage.bind(this); this.selectMessage = this.selectMessage.bind(this);
this.resetSelection = this.resetSelection.bind(this); this.resetSelection = this.resetSelection.bind(this);
this.updateSendingProgress = this.updateSendingProgress.bind(this); this.updateSendingProgress = this.updateSendingProgress.bind(this);
@ -121,26 +126,32 @@ export class SessionConversation extends React.Component<any, State> {
// Keyboard navigation // Keyboard navigation
this.onKeyDown = this.onKeyDown.bind(this); this.onKeyDown = this.onKeyDown.bind(this);
const conversationModel = window.ConversationController.get(this.state.conversationKey);
conversationModel.on('change', () => {
this.setState({
messages: conversationModel.messageCollection.models,
});
});
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~ LIFECYCLES ~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~ LIFECYCLES ~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public async componentWillMount() {
await this.loadInitialMessages();
this.setState({initialFetchComplete: true});
}
public componentDidMount() { public componentDidMount() {
this.loadInitialMessages() // Pause thread to wait for rendering to complete
.then(() => { setTimeout(this.scrollToUnread, 0);
// Pause thread to wait for rendering to complete setTimeout(() => {
setTimeout(() => { this.setState({
this.scrollToUnread(); doneInitialScroll: true,
}, 0); });
setTimeout(() => { }, 100);
this.setState({
doneInitialScroll: true,
});
}, 100);
})
.catch();
this.updateReadMessages(); this.updateReadMessages();
} }
@ -150,24 +161,16 @@ export class SessionConversation extends React.Component<any, State> {
if (this.state.isScrolledToBottom) { if (this.state.isScrolledToBottom) {
this.scrollToBottom(); this.scrollToBottom();
} }
}
public async componentWillReceiveProps() {
const timestamp = getTimestamp();
// If we have pulled messages in the last second, don't bother rescanning // New messages get from message collection.
// This avoids getting messages on every re-render. const messageCollection = window.ConversationController.get(this.state.conversationKey).messageCollection;
if (timestamp > this.state.messageFetchTimestamp) { console.log('[vince] messageCollection:', messageCollection);
await this.getMessages(); console.log('[vince] this.state.messages:', this.state.messages);
} }
// console.log('[vince] this.props.conversations:', this.props.conversations); public async componentWillReceiveProps(nextProps: any) {
console.log(`[vince] Conversation changed from redux`);
const conversationModel = window.ConversationController.get(this.state.conversationKey);
const messages = conversationModel.messageCollection;
console.log('[vince] messages:', messages);
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -244,7 +247,7 @@ export class SessionConversation extends React.Component<any, State> {
onScroll={this.handleScroll} onScroll={this.handleScroll}
ref={this.messageContainerRef} ref={this.messageContainerRef}
> >
{this.renderMessages()} {this.renderMessages(messages)}
<div ref={this.messagesEndRef} /> <div ref={this.messagesEndRef} />
</div> </div>
@ -284,8 +287,7 @@ export class SessionConversation extends React.Component<any, State> {
); );
} }
public renderMessages() { public renderMessages(messages: any) {
const { messages } = this.state;
const multiSelectMode = Boolean(this.state.selectedMessages.length); const multiSelectMode = Boolean(this.state.selectedMessages.length);
// FIXME VINCE: IF MESSAGE IS THE TOP OF UNREAD, THEN INSERT AN UNREAD BANNER // FIXME VINCE: IF MESSAGE IS THE TOP OF UNREAD, THEN INSERT AN UNREAD BANNER
@ -378,25 +380,29 @@ export class SessionConversation extends React.Component<any, State> {
// After the inital fetch, all new messages are automatically added from onNewMessage // After the inital fetch, all new messages are automatically added from onNewMessage
// in the conversation model. // in the conversation model.
// The only time we need to call getMessages() is to grab more messages on scroll. // The only time we need to call getMessages() is to grab more messages on scroll.
const { conversationKey } = this.state; const { conversationKey, initialFetchComplete } = this.state;
const conversationModel = window.ConversationController.get(conversationKey); const conversationModel = window.ConversationController.get(conversationKey);
if (initialFetchComplete) {
return;
}
const messageSet = await window.Signal.Data.getMessagesByConversation( const messageSet = await window.Signal.Data.getMessagesByConversation(
conversationKey, conversationKey,
{ limit: Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT, MessageCollection: window.Whisper.MessageCollection } { limit: Constants.CONVERSATION.DEFAULT_MESSAGE_FETCH_COUNT, MessageCollection: window.Whisper.MessageCollection }
); );
const messageModels = messageSet.models; const messages = messageSet.models;
const messages = messageModels.map((message: any) => message.id); const messageFetchTimestamp = Date.now();
this.setState({ messages }, () => { this.setState({ messages, messageFetchTimestamp }, () => {
if (this.state.isScrolledToBottom) { if (this.state.isScrolledToBottom) {
this.updateReadMessages(); this.updateReadMessages();
} }
});
// Add new messages to conversation collection // Add new messages to conversation collection
conversationModel.messageCollection = messageSet; conversationModel.messageCollection = messageSet;
});
} }
public async getMessages( public async getMessages(
@ -429,19 +435,19 @@ export class SessionConversation extends React.Component<any, State> {
); );
// Set first member of series here. // Set first member of series here.
// const messageModels = messageSet.models; const messageModels = messageSet.models;
// const messages = []; const messages = [];
// let previousSender; let previousSender;
// for (let i = 0; i < messageModels.length; i++) { for (let i = 0; i < messageModels.length; i++) {
// // Handle firstMessageOfSeries for conditional avatar rendering // Handle firstMessageOfSeries for conditional avatar rendering
// let firstMessageOfSeries = true; let firstMessageOfSeries = true;
// if (i > 0 && previousSender === messageModels[i].authorPhoneNumber) { if (i > 0 && previousSender === messageModels[i].authorPhoneNumber) {
// firstMessageOfSeries = false; firstMessageOfSeries = false;
// } }
// messages.push({ ...messageModels[i], firstMessageOfSeries }); messages.push({ ...messageModels[i], firstMessageOfSeries });
// previousSender = messageModels[i].authorPhoneNumber; previousSender = messageModels[i].authorPhoneNumber;
// } }
const previousTopMessage = this.state.messages[0]?.id; const previousTopMessage = this.state.messages[0]?.id;
const newTopMessage = messages[0]?.id; const newTopMessage = messages[0]?.id;
@ -762,20 +768,18 @@ export class SessionConversation extends React.Component<any, State> {
return null; return null;
} }
public async deleteSelectedMessages(onSuccess?: any) { public async deleteSelectedMessages() {
// Get message objects // Get message objects
const messageObjects = this.state.messages.filter(message => this.state.selectedMessages.find( const selectedMessages = this.state.messages.filter(message => this.state.selectedMessages.find(
selectedMessage => selectedMessage === message.id selectedMessage => selectedMessage === message.id
)); ));
// Get message model for each message
const messages = messageObjects.map(message => message?.collection?.models[0]);
const { conversationKey } = this.state; const { conversationKey } = this.state;
const conversationModel = window.ConversationController.get( const conversationModel = window.ConversationController.get(
conversationKey conversationKey
); );
const multiple = messages.length > 1; const multiple = selectedMessages.length > 1;
const isPublic = conversationModel.isPublic(); const isPublic = conversationModel.isPublic();
// In future, we may be able to unsend private messages also // In future, we may be able to unsend private messages also
@ -795,21 +799,39 @@ export class SessionConversation extends React.Component<any, State> {
const doDelete = async () => { const doDelete = async () => {
let toDeleteLocally; let toDeleteLocally;
console.log('[vince] conversationKey:', conversationKey);
console.log('[vince] conversationModel:', conversationModel);
console.log('[vince] messages:', messages);
// VINCE TOOD: MARK TO-DELETE MESSAGES AS READ // VINCE TOOD: MARK TO-DELETE MESSAGES AS READ
if (isPublic) { if (isPublic) {
toDeleteLocally = await conversationModel.deletePublicMessages(messages); // Get our Moderator status
const ourDevicePubkey = await UserUtil.getCurrentDevicePubKey();
if (!ourDevicePubkey) {
return;
}
const ourPrimaryPubkey = (await MultiDeviceProtocol.getPrimaryDevice(ourDevicePubkey)).key;
const isModerator = conversationModel.isModerator(ourPrimaryPubkey);
const isAllOurs = selectedMessages.every(
message =>
message.propsForMessage.authorPhoneNumber === message.OUR_NUMBER
);
if (!isAllOurs && !isModerator) {
window.pushToast({
title: window.i18n('messageDeletionForbidden'),
type: 'error',
id: 'messageDeletionForbidden',
});
return;
}
toDeleteLocally = await conversationModel.deletePublicMessages(selectedMessages);
if (toDeleteLocally.length === 0) { if (toDeleteLocally.length === 0) {
// Message failed to delete from server, show error? // Message failed to delete from server, show error?
return; return;
} }
} else { } else {
messages.forEach(m => conversationModel.messageCollection.remove(m.id)); selectedMessages.forEach(m => conversationModel.messageCollection.remove(m.id));
toDeleteLocally = messages; toDeleteLocally = selectedMessages;
} }
await Promise.all( await Promise.all(
@ -821,14 +843,15 @@ export class SessionConversation extends React.Component<any, State> {
}) })
); );
if (onSuccess) { // Update view and trigger update
onSuccess(); this.setState({selectedMessages: [] }, () => {
} conversationModel.trigger('change', conversationModel);
});
}; };
// Only show a warning when at least one messages was successfully // Only show a warning when at least one messages was successfully
// saved in on the server // saved in on the server
if (!messages.some(m => !m.hasErrors())) { if (!selectedMessages.some(m => !m.hasErrors())) {
await doDelete(); await doDelete();
return; return;
} }

Loading…
Cancel
Save