From ec55299c8b2c8e0fd5bb3004406b71d3fdc1178e Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 20 Aug 2020 20:37:54 -0700 Subject: [PATCH] fix: Improve websocket reliability (#1470) * check connection on page visibility change * fix: SocketPresence account for socket changing --- app/components/SocketProvider.js | 40 ++++++++++++++----- .../Document/components/SocketPresence.js | 2 +- app/utils/pageVisibility.js | 25 ++++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 app/utils/pageVisibility.js diff --git a/app/components/SocketProvider.js b/app/components/SocketProvider.js index 3538a7da7..373617628 100644 --- a/app/components/SocketProvider.js +++ b/app/components/SocketProvider.js @@ -3,7 +3,7 @@ import { find } from "lodash"; import { observable } from "mobx"; import { inject, observer } from "mobx-react"; import * as React from "react"; -import io from "socket.io-client"; +import io, { Socket } from "socket.io-client"; import AuthStore from "stores/AuthStore"; import CollectionsStore from "stores/CollectionsStore"; import DocumentPresenceStore from "stores/DocumentPresenceStore"; @@ -13,6 +13,7 @@ import MembershipsStore from "stores/MembershipsStore"; import PoliciesStore from "stores/PoliciesStore"; import UiStore from "stores/UiStore"; import ViewsStore from "stores/ViewsStore"; +import { getVisibilityListener, getPageVisible } from "utils/pageVisibility"; export const SocketContext: any = React.createContext(); @@ -31,9 +32,35 @@ type Props = { @observer class SocketProvider extends React.Component { - @observable socket; + @observable socket: Socket; componentDidMount() { + this.createConnection(); + + document.addEventListener(getVisibilityListener(), this.checkConnection); + } + + componentWillUnmount() { + if (this.socket) { + this.socket.authenticated = false; + this.socket.disconnect(); + } + + document.removeEventListener(getVisibilityListener(), this.checkConnection); + } + + checkConnection = () => { + if (this.socket && this.socket.disconnected && getPageVisible()) { + // null-ifying this reference is important, do not remove. Without it + // references to old sockets are potentially held in context + this.socket.close(); + this.socket = null; + + this.createConnection(); + } + }; + + createConnection = () => { this.socket = io(window.location.origin, { path: "/realtime", transports: ["websocket"], @@ -264,14 +291,7 @@ class SocketProvider extends React.Component { this.socket.on("user.presence", (event) => { presence.touch(event.documentId, event.userId, event.isEditing); }); - } - - componentWillUnmount() { - if (this.socket) { - this.socket.disconnect(); - this.socket.authenticated = false; - } - } + }; render() { return ( diff --git a/app/scenes/Document/components/SocketPresence.js b/app/scenes/Document/components/SocketPresence.js index fe077cb30..d919e26be 100644 --- a/app/scenes/Document/components/SocketPresence.js +++ b/app/scenes/Document/components/SocketPresence.js @@ -41,7 +41,7 @@ export default class SocketPresence extends React.Component { } setupOnce = () => { - if (this.context && !this.previousContext) { + if (this.context && this.context !== this.previousContext) { this.previousContext = this.context; if (this.context.authenticated) { diff --git a/app/utils/pageVisibility.js b/app/utils/pageVisibility.js new file mode 100644 index 000000000..512b3eb38 --- /dev/null +++ b/app/utils/pageVisibility.js @@ -0,0 +1,25 @@ +// @flow +let hidden = "hidden"; +let visibilityChange = "visibilitychange"; + +if ("hidden" in document) { + hidden = "hidden"; + visibilityChange = "visibilitychange"; +} else if ("mozHidden" in document) { + // Firefox up to v17 + hidden = "mozHidden"; + visibilityChange = "mozvisibilitychange"; +} else if ("webkitHidden" in document) { + // Chrome up to v32, Android up to v4.4, Blackberry up to v10 + hidden = "webkitHidden"; + visibilityChange = "webkitvisibilitychange"; +} + +export function getVisibilityListener(): string { + return visibilityChange; +} + +export function getPageVisible(): boolean { + // $FlowFixMe + return !document[hidden]; +}