From 8ccb89310be34b85cad8950e5d1e65a7a4ad270d Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 19 Sep 2019 15:16:46 -0700 Subject: [PATCH] New Idle timer; messages not marked read if user is idle --- background.html | 1 - js/background.js | 39 ++++++++++++++++++++++++- js/focus_listener.js | 14 --------- js/models/conversations.js | 2 +- js/notifications.js | 3 +- js/views/app_view.js | 2 +- js/views/conversation_view.js | 10 +++---- preload.js | 8 ----- test/index.html | 1 - ts/components/conversation/Timeline.tsx | 15 ++++------ ts/state/ducks/conversations.ts | 15 ++++------ 11 files changed, 55 insertions(+), 55 deletions(-) delete mode 100644 js/focus_listener.js diff --git a/background.html b/background.html index dd6ae604e..c0da6a528 100644 --- a/background.html +++ b/background.html @@ -449,7 +449,6 @@ - diff --git a/js/background.js b/js/background.js index 6eda29047..3e52cd598 100644 --- a/js/background.js +++ b/js/background.js @@ -40,6 +40,43 @@ false ); + // Idle timer - you're active for ACTIVE_TIMEOUT after one of these events + const ACTIVE_TIMEOUT = 15 * 1000; + const ACTIVE_EVENTS = [ + 'click', + 'keypress', + 'mousedown', + 'mousemove', + // 'scroll', // this is triggered by Timeline re-renders, can't use + 'touchstart', + 'wheel', + ]; + + const LISTENER_DEBOUNCE = 5 * 1000; + let activeHandlers = []; + let activeTimestamp = Date.now(); + + window.resetActiveTimer = _.throttle(() => { + const previouslyActive = window.isActive(); + activeTimestamp = Date.now(); + if (!previouslyActive) { + activeHandlers.forEach(handler => handler()); + } + }, LISTENER_DEBOUNCE); + + ACTIVE_EVENTS.forEach(name => { + document.addEventListener(name, window.resetActiveTimer, true); + }); + + window.isActive = () => { + const now = Date.now(); + return now <= activeTimestamp + ACTIVE_TIMEOUT; + }; + window.registerForActive = handler => activeHandlers.push(handler); + window.unregisterForActive = handler => { + activeHandlers = activeHandlers.filter(item => item !== handler); + }; + // Load these images now to ensure that they don't flicker on first use window.Signal.EmojiLib.preloadImages(); const images = []; @@ -712,7 +749,7 @@ } }); - window.addEventListener('focus', () => Whisper.Notifications.clear()); + window.registerForActive(() => Whisper.Notifications.clear()); window.addEventListener('unload', () => Whisper.Notifications.fastClear()); Whisper.events.on('showConversation', (id, messageId) => { diff --git a/js/focus_listener.js b/js/focus_listener.js deleted file mode 100644 index c9a728233..000000000 --- a/js/focus_listener.js +++ /dev/null @@ -1,14 +0,0 @@ -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - let windowFocused = false; - window.addEventListener('blur', () => { - windowFocused = false; - }); - window.addEventListener('focus', () => { - windowFocused = true; - }); - - window.isFocused = () => windowFocused; -})(); diff --git a/js/models/conversations.js b/js/models/conversations.js index edaa94ac1..e9bc61d42 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -334,7 +334,7 @@ this.id, [model.getReduxData()], isNewMessage, - document.hasFocus() + window.isActive() ); } diff --git a/js/notifications.js b/js/notifications.js index 7c0fb39ae..8df33aa42 100644 --- a/js/notifications.js +++ b/js/notifications.js @@ -3,7 +3,6 @@ /* global drawAttention: false */ /* global i18n: false */ -/* global isFocused: false */ /* global Signal: false */ /* global storage: false */ /* global Whisper: false */ @@ -54,7 +53,7 @@ } const { isEnabled } = this; - const isAppFocused = isFocused(); + const isAppFocused = window.isActive(); const isAudioNotificationEnabled = storage.get('audio-notification') || false; const isAudioNotificationSupported = Settings.isAudioNotificationSupported(); diff --git a/js/views/app_view.js b/js/views/app_view.js index 162f0b69f..9321cfd3a 100644 --- a/js/views/app_view.js +++ b/js/views/app_view.js @@ -166,7 +166,7 @@ if (!$.contains(this.el, this.inboxView.el)) { this.openView(this.inboxView); } - window.focus(); // FIXME + return Promise.resolve(); }, onEmpty() { diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index d30dd663b..bd8115c77 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -442,7 +442,7 @@ id, models.map(model => model.getReduxData()), isNewMessage, - document.hasFocus() + window.isActive() ); } catch (error) { setMessagesLoading(conversationId, true); @@ -493,7 +493,7 @@ id, models.map(model => model.getReduxData()), isNewMessage, - document.hasFocus() + window.isActive() ); } catch (error) { setMessagesLoading(conversationId, false); @@ -502,10 +502,8 @@ finish(); } }; - const markMessageRead = async (messageId, forceFocus) => { - // We need a forceFocus parameter because the BrowserWindow focus event fires - // before the document realizes that it has focus. - if (!document.hasFocus() && !forceFocus) { + const markMessageRead = async messageId => { + if (!window.isActive()) { return; } diff --git a/preload.js b/preload.js index 369fb10ca..8e350791b 100644 --- a/preload.js +++ b/preload.js @@ -10,14 +10,6 @@ const { remote } = electron; const { app } = remote; const { systemPreferences } = remote.require('electron'); -const browserWindow = remote.getCurrentWindow(); -let focusHandlers = []; -browserWindow.on('focus', () => focusHandlers.forEach(handler => handler())); -window.registerForFocus = handler => focusHandlers.push(handler); -window.unregisterForFocus = handler => { - focusHandlers = focusHandlers.filter(item => item !== handler); -}; - // Waiting for clients to implement changes on receive side window.ENABLE_STICKER_SEND = true; window.TIMESTAMP_VALIDATION = false; diff --git a/test/index.html b/test/index.html index e51e22adc..9c013d1a6 100644 --- a/test/index.html +++ b/test/index.html @@ -476,7 +476,6 @@ - diff --git a/ts/components/conversation/Timeline.tsx b/ts/components/conversation/Timeline.tsx index 697a68fd2..5220dd487 100644 --- a/ts/components/conversation/Timeline.tsx +++ b/ts/components/conversation/Timeline.tsx @@ -61,7 +61,7 @@ type PropsActionsType = { loadOlderMessages: (messageId: string) => unknown; loadNewerMessages: (messageId: string) => unknown; loadNewestMessages: (messageId: string) => unknown; - markMessageRead: (messageId: string, forceFocus?: boolean) => unknown; + markMessageRead: (messageId: string) => unknown; } & MessageActionsType & SafetyNumberActionsType; @@ -402,7 +402,7 @@ export class Timeline extends React.PureComponent { // tslint:disable-next-line member-ordering cyclomatic-complexity public updateWithVisibleRows = debounce( - (forceFocus?: boolean) => { + () => { const { unreadCount, haveNewest, @@ -426,7 +426,7 @@ export class Timeline extends React.PureComponent { return; } - markMessageRead(newest.id, forceFocus); + markMessageRead(newest.id); const rowCount = this.getRowCount(); @@ -710,19 +710,14 @@ export class Timeline extends React.PureComponent { public componentDidMount() { this.updateWithVisibleRows(); // @ts-ignore - window.registerForFocus(this.forceFocusVisibleRowUpdate); + window.registerForActive(this.updateWithVisibleRows); } public componentWillUnmount() { // @ts-ignore - window.unregisterForFocus(this.forceFocusVisibleRowUpdate); + window.unregisterForActive(this.updateWithVisibleRows); } - public forceFocusVisibleRowUpdate = () => { - const forceFocus = true; - this.updateWithVisibleRows(forceFocus); - }; - // tslint:disable-next-line cyclomatic-complexity max-func-body-length public componentDidUpdate(prevProps: Props) { const { diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index a39eb86b4..264777c72 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -160,7 +160,7 @@ export type MessagesAddedActionType = { conversationId: string; messages: Array; isNewMessage: boolean; - isFocused: boolean; + isActive: boolean; }; }; export type MessagesResetActionType = { @@ -357,7 +357,7 @@ function messagesAdded( conversationId: string, messages: Array, isNewMessage: boolean, - isFocused: boolean + isActive: boolean ): MessagesAddedActionType { return { type: 'MESSAGES_ADDED', @@ -365,7 +365,7 @@ function messagesAdded( conversationId, messages, isNewMessage, - isFocused, + isActive, }, }; } @@ -870,12 +870,7 @@ export function reducer( }; } if (action.type === 'MESSAGES_ADDED') { - const { - conversationId, - isFocused, - isNewMessage, - messages, - } = action.payload; + const { conversationId, isActive, isNewMessage, messages } = action.payload; const { messagesByConversation, messagesLookup } = state; const existingConversation = messagesByConversation[conversationId]; @@ -937,7 +932,7 @@ export function reducer( const newMessageIds = difference(newIds, existingConversation.messageIds); const { isNearBottom } = existingConversation; - if ((!isNearBottom || !isFocused) && !oldestUnread) { + if ((!isNearBottom || !isActive) && !oldestUnread) { const oldestId = newMessageIds.find(messageId => { const message = lookup[messageId];