From 977569cde00d8f006c32956deff4a9ba7d935be2 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 16 Nov 2020 15:32:53 +1100 Subject: [PATCH] move offline network view to react --- background.html | 18 -- background_test.html | 17 -- js/background.js | 6 - js/conversation_controller.js | 3 - js/views/inbox_view.js | 21 --- js/views/network_status_view.js | 133 --------------- stylesheets/_index.scss | 22 --- stylesheets/_session_theme.scss | 11 -- test/index.html | 20 --- test/views/network_status_view_test.js | 160 ------------------ ts/components/LeftPane.tsx | 49 +++--- ts/components/session/SessionInboxView.tsx | 48 ------ .../session/network/SessionOffline.tsx | 35 ++++ ts/components/session/network/useNetwork.ts | 19 +++ 14 files changed, 83 insertions(+), 479 deletions(-) delete mode 100644 js/views/network_status_view.js delete mode 100644 test/views/network_status_view_test.js create mode 100644 ts/components/session/network/SessionOffline.tsx create mode 100644 ts/components/session/network/useNetwork.ts diff --git a/background.html b/background.html index dc6aa0f53..4a26917c6 100644 --- a/background.html +++ b/background.html @@ -78,23 +78,6 @@ - - - diff --git a/background_test.html b/background_test.html index c104bedee..5bf9defd6 100644 --- a/background_test.html +++ b/background_test.html @@ -76,22 +76,6 @@ {{ #summary }}
{{ summary }}
{{ /summary }} - - diff --git a/js/background.js b/js/background.js index 147ed7155..24acc0536 100644 --- a/js/background.js +++ b/js/background.js @@ -589,12 +589,6 @@ Whisper.events.on('showDebugLog', () => { appView.openDebugLog(); }); - Whisper.events.on('unauthorized', () => { - appView.inboxView.networkStatusView.update(); - }); - Whisper.events.on('reconnectTimer', () => { - appView.inboxView.networkStatusView.setSocketReconnectInterval(60000); - }); window.addEventListener('focus', () => Whisper.Notifications.clear()); window.addEventListener('unload', () => Whisper.Notifications.fastClear()); diff --git a/js/conversation_controller.js b/js/conversation_controller.js index 78c7978d3..4141d7894 100644 --- a/js/conversation_controller.js +++ b/js/conversation_controller.js @@ -25,9 +25,6 @@ return messages; }; - window.getConversationByName = name => - conversations.find(d => d.get('name') === name); - window.ConversationController = { get(id) { if (!this._initialFetchComplete) { diff --git a/js/views/inbox_view.js b/js/views/inbox_view.js index 30afb774f..825f7f7ae 100644 --- a/js/views/inbox_view.js +++ b/js/views/inbox_view.js @@ -21,28 +21,7 @@ this.ready = false; this.render(); this.$el.attr('tabindex', '1'); - - if (!options.initialLoadComplete) { - this.appLoadingScreen = new Whisper.AppLoadingScreen(); - this.appLoadingScreen.render(); - this.appLoadingScreen.$el.prependTo(this.el); - this.startConnectionListener(); - } - - // Inbox - const inboxCollection = getInboxCollection(); - // ConversationCollection - this.listenTo(inboxCollection, 'messageError', () => { - if (this.networkStatusView) { - this.networkStatusView.render(); - } - }); - - this.networkStatusView = new Whisper.NetworkStatusView(); - this.$el - .find('.network-status-container') - .append(this.networkStatusView.render().el); extension.expired(expired => { if (expired) { diff --git a/js/views/network_status_view.js b/js/views/network_status_view.js deleted file mode 100644 index a780f87cd..000000000 --- a/js/views/network_status_view.js +++ /dev/null @@ -1,133 +0,0 @@ -/* global Whisper, extension, Backbone, moment, i18n */ - -// eslint-disable-next-line func-names -(function() { - 'use strict'; - - window.Whisper = window.Whisper || {}; - - const DISCONNECTED_DELAY = 30000; - - Whisper.NetworkStatusView = Whisper.View.extend({ - className: 'network-status', - templateName: 'networkStatus', - initialize() { - this.$el.hide(); - - this.renderIntervalHandle = setInterval(this.update.bind(this), 5000); - extension.windows.onClosed(() => { - clearInterval(this.renderIntervalHandle); - }); - - setTimeout(this.finishConnectingGracePeriod.bind(this), 5000); - - this.withinConnectingGracePeriod = true; - this.setSocketReconnectInterval(null); - - window.addEventListener('online', this.update.bind(this)); - window.addEventListener('offline', this.update.bind(this)); - - this.model = new Backbone.Model(); - this.listenTo(this.model, 'change', this.onChange); - this.connectedTimer = null; - }, - onReconnectTimer() { - this.setSocketReconnectInterval(60000); - }, - finishConnectingGracePeriod() { - this.withinConnectingGracePeriod = false; - }, - setSocketReconnectInterval(millis) { - this.socketReconnectWaitDuration = moment.duration(millis); - }, - navigatorOnLine() { - return navigator.onLine; - }, - getSocketStatus() { - return window.getSocketStatus(); - }, - getNetworkStatus(shortCircuit = false) { - let message = ''; - let instructions = ''; - let hasInterruption = false; - - const socketStatus = this.getSocketStatus(); - switch (socketStatus) { - case WebSocket.CONNECTING: - message = i18n('connecting'); - this.setSocketReconnectInterval(null); - window.clearTimeout(this.connectedTimer); - this.connectedTimer = null; - break; - case WebSocket.OPEN: - this.setSocketReconnectInterval(null); - window.clearTimeout(this.connectedTimer); - this.connectedTimer = null; - break; - case WebSocket.CLOSED: - // Intentional fallthrough - case WebSocket.CLOSING: - // Intentional fallthrough - default: { - const markOffline = () => { - message = i18n('offline'); - instructions = i18n('checkNetworkConnection'); - hasInterruption = true; - }; - if (shortCircuit) { - // Used to skip the timer for testing - markOffline(); - break; - } - if (!this.connectedTimer) { - // Mark offline if disconnected for 30 seconds - this.connectedTimer = window.setTimeout(() => { - markOffline(); - }, DISCONNECTED_DELAY); - } - break; - } - } - - if ( - socketStatus === WebSocket.CONNECTING && - !this.withinConnectingGracePeriod - ) { - hasInterruption = true; - } - if (this.socketReconnectWaitDuration.asSeconds() > 0) { - instructions = i18n('attemptingReconnection', [ - this.socketReconnectWaitDuration.asSeconds(), - ]); - } - if (!this.navigatorOnLine()) { - hasInterruption = true; - message = i18n('offline'); - instructions = i18n('checkNetworkConnection'); - } - - return { - message, - instructions, - hasInterruption, - action: null, - buttonClass: null, - }; - }, - update() { - const status = this.getNetworkStatus(); - this.model.set(status); - }, - render_attributes() { - return this.model.attributes; - }, - onChange() { - this.render(); - if (this.model.attributes.hasInterruption) { - this.$el.slideDown(); - } else { - this.$el.hide(); - } - }, - }); -})(); diff --git a/stylesheets/_index.scss b/stylesheets/_index.scss index f801e8894..d060c96a1 100644 --- a/stylesheets/_index.scss +++ b/stylesheets/_index.scss @@ -166,28 +166,6 @@ h4.section-toggle, } } -.network-status-container { - .network-status { - padding: 10px; - padding-inline-start: 2 * $button-height; - display: none; - - .network-status-message { - h3 { - padding: 0px; - margin: 0px; - margin-bottom: 2px; - font-size: 14px; - } - span { - display: inline-block; - font-size: 12px; - padding: 0.5em 0; - } - } - } -} - .left-pane-placeholder { flex-grow: 1; display: flex; diff --git a/stylesheets/_session_theme.scss b/stylesheets/_session_theme.scss index ce0d4dc24..14166b3ce 100644 --- a/stylesheets/_session_theme.scss +++ b/stylesheets/_session_theme.scss @@ -174,14 +174,3 @@ h4 { font-size: 17px; text-align: center; } - -.network-status { - @include themify($themes) { - background: themed('accent'); - } - - h3, - .network-status-message { - color: $black; - } -} diff --git a/test/index.html b/test/index.html index 6ef6df4b9..44eda5088 100644 --- a/test/index.html +++ b/test/index.html @@ -105,24 +105,6 @@ - - - - @@ -260,7 +241,6 @@ - diff --git a/test/views/network_status_view_test.js b/test/views/network_status_view_test.js deleted file mode 100644 index 488ff7101..000000000 --- a/test/views/network_status_view_test.js +++ /dev/null @@ -1,160 +0,0 @@ -/* global _, $, Whisper */ - -describe('NetworkStatusView', () => { - describe('getNetworkStatus', () => { - let networkStatusView; - let socketStatus = WebSocket.OPEN; - - let oldGetSocketStatus; - - /* BEGIN stubbing globals */ - before(() => { - oldGetSocketStatus = window.getSocketStatus; - window.getSocketStatus = () => socketStatus; - }); - - after(() => { - window.getSocketStatus = oldGetSocketStatus; - - // It turns out that continued calls to window.getSocketStatus happen - // because we host NetworkStatusView in three mock interfaces, and the view - // checks every N seconds. That results in infinite errors unless there is - // something to call. - window.getSocketStatus = () => WebSocket.OPEN; - }); - /* END stubbing globals */ - - beforeEach(() => { - networkStatusView = new Whisper.NetworkStatusView(); - $('.network-status-container').append(networkStatusView.el); - }); - afterEach(() => { - // prevents huge number of errors on console after running tests - clearInterval(networkStatusView.renderIntervalHandle); - networkStatusView = null; - }); - - describe('initialization', () => { - it('should have an empty interval', () => { - assert.equal( - networkStatusView.socketReconnectWaitDuration.asSeconds(), - 0 - ); - }); - }); - describe('network status with no connection', () => { - beforeEach(() => { - networkStatusView.navigatorOnLine = () => false; - }); - it('should be interrupted', () => { - networkStatusView.update(); - const status = networkStatusView.getNetworkStatus(); - assert(status.hasInterruption); - assert.equal(status.instructions, 'Check your network connection.'); - }); - it('should display an offline message', () => { - networkStatusView.update(); - assert.match(networkStatusView.$el.text(), /Offline/); - }); - it('should override socket status', () => { - _([ - WebSocket.CONNECTING, - WebSocket.OPEN, - WebSocket.CLOSING, - WebSocket.CLOSED, - ]).forEach(socketStatusVal => { - socketStatus = socketStatusVal; - networkStatusView.update(); - assert.match(networkStatusView.$el.text(), /Offline/); - }); - }); - it('should override registration status', () => { - Whisper.Registration.remove(); - networkStatusView.update(); - assert.match(networkStatusView.$el.text(), /Offline/); - }); - }); - describe('network status when registration is done', () => { - beforeEach(() => { - networkStatusView.navigatorOnLine = () => true; - Whisper.Registration.markDone(); - networkStatusView.update(); - }); - it('should not display an unlinked message', () => { - networkStatusView.update(); - assert.notMatch(networkStatusView.$el.text(), /Relink/); - }); - }); - describe('network status when socket is connecting', () => { - beforeEach(() => { - Whisper.Registration.markDone(); - socketStatus = WebSocket.CONNECTING; - networkStatusView.update(); - }); - it('it should display a connecting string if connecting and not in the connecting grace period', () => { - networkStatusView.withinConnectingGracePeriod = false; - networkStatusView.getNetworkStatus(); - - assert.match(networkStatusView.$el.text(), /Connecting/); - }); - it('it should not be interrupted if in connecting grace period', () => { - assert(networkStatusView.withinConnectingGracePeriod); - const status = networkStatusView.getNetworkStatus(); - - assert.match(networkStatusView.$el.text(), /Connecting/); - assert(!status.hasInterruption); - }); - it('it should be interrupted if connecting grace period is over', () => { - networkStatusView.withinConnectingGracePeriod = false; - const status = networkStatusView.getNetworkStatus(); - - assert(status.hasInterruption); - }); - }); - describe('network status when socket is open', () => { - before(() => { - socketStatus = WebSocket.OPEN; - }); - it('should not be interrupted', () => { - const status = networkStatusView.getNetworkStatus(); - assert(!status.hasInterruption); - assert.match( - networkStatusView.$el - .find('.network-status-message') - .text() - .trim(), - /^$/ - ); - }); - }); - describe('network status when socket is closed or closing', () => { - _([WebSocket.CLOSED, WebSocket.CLOSING]).forEach(socketStatusVal => { - it('should be interrupted', () => { - socketStatus = socketStatusVal; - networkStatusView.update(); - const shortCircuit = true; - const status = networkStatusView.getNetworkStatus(shortCircuit); - assert(status.hasInterruption); - }); - }); - }); - describe('the socket reconnect interval', () => { - beforeEach(() => { - socketStatus = WebSocket.CLOSED; - networkStatusView.setSocketReconnectInterval(61000); - networkStatusView.update(); - }); - it('should format the message based on the socketReconnectWaitDuration property', () => { - assert.equal( - networkStatusView.socketReconnectWaitDuration.asSeconds(), - 61 - ); - assert.match( - networkStatusView.$('.network-status-message:last').text(), - /Attempting reconnect/ - ); - }); - it('should be reset by changing the socketStatus to CONNECTING', () => {}); - }); - }); -}); diff --git a/ts/components/LeftPane.tsx b/ts/components/LeftPane.tsx index 74baa5ace..7a93167b2 100644 --- a/ts/components/LeftPane.tsx +++ b/ts/components/LeftPane.tsx @@ -15,6 +15,7 @@ import { SessionIconType } from './session/icon'; import { SessionTheme } from '../state/ducks/SessionTheme'; import { DefaultTheme } from 'styled-components'; import { SessionSettingCategory } from './session/settings/SessionSettings'; +import { SessionOffline } from './session/network/SessionOffline'; // from https://github.com/bvaughn/react-virtualized/blob/fb3484ed5dcc41bffae8eab029126c0fb8f7abc0/source/List/types.js#L5 export type RowRendererParamsType = { @@ -136,17 +137,20 @@ export class LeftPane extends React.Component { } return ( - + <> + + + ); } @@ -156,10 +160,13 @@ export class LeftPane extends React.Component { const directContacts = this.getDirectContactsOnly(); return ( - + <> + + + ); } @@ -177,11 +184,13 @@ export class LeftPane extends React.Component { const category = settingsCategory || SessionSettingCategory.Appearance; return ( - + <> + + ); } } diff --git a/ts/components/session/SessionInboxView.tsx b/ts/components/session/SessionInboxView.tsx index 7797f8b37..5ef27d9cf 100644 --- a/ts/components/session/SessionInboxView.tsx +++ b/ts/components/session/SessionInboxView.tsx @@ -30,7 +30,6 @@ type State = { export class SessionInboxView extends React.Component { private store: any; - private interval: NodeJS.Timeout | null = null; constructor(props: any) { super(props); @@ -40,7 +39,6 @@ export class SessionInboxView extends React.Component { networkError: false, }; - const conversationModels = window.getConversations(); this.fetchHandleMessageSentData = this.fetchHandleMessageSentData.bind( this ); @@ -55,18 +53,6 @@ export class SessionInboxView extends React.Component { void this.setupLeftPane(); - // ConversationCollection - // this.listenTo(inboxCollection, 'messageError', () => { - // if (this.networkStatusView) { - // this.networkStatusView.render(); - // } - // }); - - // this.networkStatusView = new Whisper.NetworkStatusView(); - // this.$el - // .find('.network-status-container') - // .append(this.networkStatusView.render().el); - // extension.expired(expired => { // if (expired) { // const banner = new Whisper.ExpiredAlertBanner().render(); @@ -144,10 +130,6 @@ export class SessionInboxView extends React.Component { return null; } - // then, find in this conversation the very same message - // const msg = conv.messageCollection.models.find( - // convMsg => convMsg.id === tmpMsg.id - // ); const msg = window.MessageController._get()[m.identifier]; if (!msg || !msg.message) { @@ -256,34 +238,4 @@ export class SessionInboxView extends React.Component { private showSessionViewConversation() { this.setState({ settingsCategory: undefined }); } - - // private startConnectionListener() { - // this.interval = global.setInterval(() => { - // const status = window.getSocketStatus(); - // switch (status) { - // case WebSocket.CONNECTING: - // break; - // case WebSocket.OPEN: - // if (this.interval) { - // clearInterval(this.interval); - // } - // // Default to connected, but lokinet is slow so we pretend empty event - // // this.onEmpty(); - // this.interval = null; - // break; - // case WebSocket.CLOSING: - // case WebSocket.CLOSED: - // if (this.interval) { - // clearInterval(this.interval); - // } - // this.interval = null; - // // if we failed to connect, we pretend we got an empty event - // // this.onEmpty(); - // break; - // default: - // // We also replicate empty here - // // this.onEmpty(); - // } - // }, 1000); - // } } diff --git a/ts/components/session/network/SessionOffline.tsx b/ts/components/session/network/SessionOffline.tsx new file mode 100644 index 000000000..be3a7f8d1 --- /dev/null +++ b/ts/components/session/network/SessionOffline.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useNetwork } from './useNetwork'; + +type ContainerProps = { + show: boolean; +}; + +const OfflineContainer = styled.div` + background: ${props => props.theme.colors.accent}; + color: ${props => props.theme.colors.textColor}; + padding: ${props => (props.show ? props.theme.common.margins.sm : '0px')}; + margin: ${props => (props.show ? props.theme.common.margins.xs : '0px')}; + height: ${props => (props.show ? 'auto' : '0px')}; + overflow: hidden; + transition: ${props => props.theme.common.animations.defaultDuration}; +`; + +const OfflineTitle = styled.h3` + padding-top: 0px; + margin-top: 0px; +`; + +const OfflineMessage = styled.div``; + +export const SessionOffline = () => { + const isOnline = useNetwork(); + + return ( + + {window.i18n('offline')} + {window.i18n('checkNetworkConnection')} + + ); +}; diff --git a/ts/components/session/network/useNetwork.ts b/ts/components/session/network/useNetwork.ts new file mode 100644 index 000000000..4e05f96e5 --- /dev/null +++ b/ts/components/session/network/useNetwork.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; + +export function useNetwork() { + const [isOnline, setNetwork] = useState(window.navigator.onLine); + const updateNetwork = () => { + setNetwork(window.navigator.onLine); + }; + + useEffect(() => { + window.addEventListener('offline', updateNetwork); + window.addEventListener('online', updateNetwork); + + return () => { + window.removeEventListener('offline', updateNetwork); + window.removeEventListener('online', updateNetwork); + }; + }); + return isOnline; +}