fix missing read notifications
This commit is contained in:
@@ -16,11 +16,14 @@ import LogoView from './logo-view.jsx';
|
||||
import SendMessage from '../widgets/send-message.jsx';
|
||||
|
||||
import { DEFAULT_P2P_ACCESS_MODE, KEYPRESS_DELAY, MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE,
|
||||
MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
|
||||
MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE, READ_DELAY } from '../config.js';
|
||||
import { SUPPORTED_IMAGE_FORMATS, filePasted, fileToBase64, imageFileToBase64,
|
||||
imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { bytesToHumanSize, shortDateFormat } from '../lib/strformat.js';
|
||||
|
||||
// Run timer with this frequency (ms) for checking notification queue.
|
||||
const NOTIFICATION_EXEC_INTERVAL = 300;
|
||||
|
||||
const messages = defineMessages({
|
||||
online_now: {
|
||||
id: 'online_now',
|
||||
@@ -62,7 +65,6 @@ class MessagesView extends React.Component {
|
||||
|
||||
this.state = MessagesView.getDerivedStateFromProps(props, {});
|
||||
|
||||
// this.propsChange = this.propsChange.bind(this);
|
||||
this.leave = this.leave.bind(this);
|
||||
this.sendImageAttachment = this.sendImageAttachment.bind(this);
|
||||
this.sendFileAttachment = this.sendFileAttachment.bind(this);
|
||||
@@ -83,6 +85,11 @@ class MessagesView extends React.Component {
|
||||
this.handleEnablePeer = this.handleEnablePeer.bind(this);
|
||||
this.handleAttachFile = this.handleAttachFile.bind(this);
|
||||
this.handleAttachImage = this.handleAttachImage.bind(this);
|
||||
this.postReadNotification = this.postReadNotification.bind(this);
|
||||
this.clearNotificationQueue = this.clearNotificationQueue.bind(this);
|
||||
|
||||
this.readNotificationQueue = [];
|
||||
this.readNotificationTimer = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -96,6 +103,9 @@ class MessagesView extends React.Component {
|
||||
if (this.messagesScroller) {
|
||||
this.messagesScroller.removeEventListener('scroll', this.handleScrollEvent);
|
||||
}
|
||||
|
||||
// Flush all notifications.
|
||||
this.clearNotificationQueue();
|
||||
}
|
||||
|
||||
// Scroll last message into view on component update e.g. on message received
|
||||
@@ -113,7 +123,6 @@ class MessagesView extends React.Component {
|
||||
if (this.state.topic != prevState.topic) {
|
||||
if (prevState.topic && !Tinode.isNewGroupTopicName(prevState.topic)) {
|
||||
this.leave(prevState.topic);
|
||||
this.props.readTimerHandler(null);
|
||||
}
|
||||
|
||||
if (topic) {
|
||||
@@ -126,6 +135,14 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.props.applicationVisible) {
|
||||
// If application is not visible, flush all unsent 'read' notifications.
|
||||
this.clearNotificationQueue();
|
||||
} else {
|
||||
// Otherwise assume there are unread messages.
|
||||
this.postReadNotification(0);
|
||||
}
|
||||
|
||||
if (topic && !topic.isSubscribed() && this.props.ready &&
|
||||
((this.state.topic != prevState.topic) || !prevProps.ready)) {
|
||||
// Is this a new topic?
|
||||
@@ -362,6 +379,68 @@ class MessagesView extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
postReadNotification(seq) {
|
||||
// Ignore notifications if the app is invisible.
|
||||
if (!this.props.applicationVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up the timer if it's not running already.
|
||||
if (!this.readNotificationTimer) {
|
||||
this.readNotificationTimer = setInterval(() => {
|
||||
if (this.readNotificationQueue.length == 0) {
|
||||
// Shut down the timer if the queue is empty.
|
||||
clearInterval(this.readNotificationTimer);
|
||||
this.readNotificationTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
let seq = -1;
|
||||
while (this.readNotificationQueue.length > 0) {
|
||||
const n = this.readNotificationQueue[0];
|
||||
if (n.topicName != this.state.topic) {
|
||||
// Topic has changed. Drop the notification.
|
||||
this.readNotificationQueue.shift();
|
||||
continue;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
if (n.sendAt <= now) {
|
||||
// Remove expired notification from queue.
|
||||
this.readNotificationQueue.shift();
|
||||
seq = Math.max(seq, n.seq);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send only one notification for the whole batch of messages.
|
||||
if (seq >= 0) {
|
||||
const topic = this.props.tinode.getTopic(this.state.topic);
|
||||
if (topic) {
|
||||
topic.noteRead(seq);
|
||||
}
|
||||
}
|
||||
}, NOTIFICATION_EXEC_INTERVAL);
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
this.readNotificationQueue.push({
|
||||
topicName: this.state.topic,
|
||||
seq: seq,
|
||||
sendAt: now.setMilliseconds(now.getMilliseconds() + READ_DELAY)
|
||||
});
|
||||
}
|
||||
|
||||
// Clear notification queue and timer.
|
||||
clearNotificationQueue() {
|
||||
this.readNotificationQueue = [];
|
||||
if (this.readNotificationTimer) {
|
||||
clearInterval(this.readNotificationTimer);
|
||||
this.readNotificationTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleSubsUpdated() {
|
||||
if (this.state.topic) {
|
||||
const subs = [];
|
||||
@@ -408,9 +487,7 @@ class MessagesView extends React.Component {
|
||||
// automatically assumed to be read and recived.
|
||||
const status = topic.msgStatus(msg);
|
||||
if (status >= Tinode.MESSAGE_STATUS_SENT && msg.from != this.props.myUserId) {
|
||||
this.props.readTimerHandler(() => {
|
||||
topic.noteRead(msg.seq);
|
||||
});
|
||||
this.postReadNotification(msg.seq);
|
||||
}
|
||||
this.props.onData(msg);
|
||||
}
|
||||
@@ -419,6 +496,8 @@ class MessagesView extends React.Component {
|
||||
|
||||
handleAllMessagesReceived(count) {
|
||||
this.setState({fetchingMessages: false});
|
||||
// 0 means "latest".
|
||||
this.postReadNotification(0);
|
||||
}
|
||||
|
||||
handleInfoReceipt(info) {
|
||||
|
||||
@@ -15,8 +15,8 @@ import InfoView from './info-view.jsx';
|
||||
import MessagesView from './messages-view.jsx';
|
||||
import SidepanelView from './sidepanel-view.jsx';
|
||||
|
||||
import { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, LOGGING_ENABLED, MEDIA_BREAKPOINT,
|
||||
READ_DELAY, RECEIVED_DELAY } from '../config.js';
|
||||
import { API_KEY, APP_NAME, DEFAULT_P2P_ACCESS_MODE, LOGGING_ENABLED,
|
||||
MEDIA_BREAKPOINT, RECEIVED_DELAY } from '../config.js';
|
||||
import { base64ReEncode, makeImageUrl } from '../lib/blob-helpers.js';
|
||||
import { detectServerAddress, isLocalHost, isSecureConnection } from '../lib/host-name.js';
|
||||
import LocalStorageUtil from '../lib/local-storage.js';
|
||||
@@ -55,8 +55,6 @@ class TinodeWeb extends React.Component {
|
||||
this.handleHashRoute = this.handleHashRoute.bind(this);
|
||||
this.handleOnline = this.handleOnline.bind(this);
|
||||
this.checkForAppUpdate = this.checkForAppUpdate.bind(this);
|
||||
this.handleAppVisibility = this.handleAppVisibility.bind(this);
|
||||
this.handleReadTimer = this.handleReadTimer.bind(this);
|
||||
this.handleVisibilityEvent = this.handleVisibilityEvent.bind(this);
|
||||
this.handleError = this.handleError.bind(this);
|
||||
this.handleLoginRequest = this.handleLoginRequest.bind(this);
|
||||
@@ -135,6 +133,8 @@ class TinodeWeb extends React.Component {
|
||||
(typeof FIREBASE_INIT != 'undefined'),
|
||||
firebaseToken: LocalStorageUtil.getObject('firebase-token'),
|
||||
|
||||
applicationVisible: !document.hidden,
|
||||
|
||||
errorText: '',
|
||||
errorLevel: null,
|
||||
errorAction: undefined,
|
||||
@@ -334,24 +334,8 @@ class TinodeWeb extends React.Component {
|
||||
this.setState({liveConnection: online});
|
||||
}
|
||||
|
||||
// Handling read notifications here to be able to pause
|
||||
// them then the window/tab is not visible.
|
||||
handleAppVisibility(visible, callback) {
|
||||
clearTimeout(this.readTimer);
|
||||
this.readTimerCallback = callback;
|
||||
if (visible && callback) {
|
||||
this.readTimer = setTimeout(callback, READ_DELAY);
|
||||
} else {
|
||||
this.readTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleReadTimer(callback) {
|
||||
this.handleAppVisibility(!document.hidden, callback);
|
||||
}
|
||||
|
||||
handleVisibilityEvent() {
|
||||
this.handleAppVisibility(!document.hidden, this.readTimerCallback);
|
||||
this.setState({applicationVisible: !document.hidden});
|
||||
}
|
||||
|
||||
handleError(err, level, action, actionText) {
|
||||
@@ -1417,6 +1401,7 @@ class TinodeWeb extends React.Component {
|
||||
myUserId={this.state.myUserId}
|
||||
serverVersion={this.state.serverVersion}
|
||||
serverAddress={this.state.serverAddress}
|
||||
applicationVisible={this.state.applicationVisible}
|
||||
|
||||
errorText={this.state.errorText}
|
||||
errorLevel={this.state.errorLevel}
|
||||
@@ -1429,7 +1414,6 @@ class TinodeWeb extends React.Component {
|
||||
onData={this.tnData}
|
||||
onError={this.handleError}
|
||||
onNewTopicCreated={this.handleNewTopicCreated}
|
||||
readTimerHandler={this.handleReadTimer}
|
||||
showContextMenu={this.handleShowContextMenu}
|
||||
onChangePermissions={this.handleChangePermissions}
|
||||
onNewChat={this.handleNewChatInvitation}
|
||||
|
||||
Reference in New Issue
Block a user