diff --git a/src/appState.ts b/src/appState.ts index 0dec0b1df..afeddc04f 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -74,6 +74,7 @@ export const getDefaultAppState = (): Omit< zoom: { value: 1 as NormalizedZoomValue, translation: { x: 0, y: 0 } }, viewModeEnabled: false, networkSpeed: 0, + ping: "…", }; }; @@ -155,6 +156,7 @@ const APP_STATE_STORAGE_CONF = (< zoom: { browser: true, export: false }, viewModeEnabled: { browser: false, export: false }, networkSpeed: { browser: false, export: false }, + ping: { browser: false, export: false }, }); const _clearAppStateForStorage = ( diff --git a/src/components/App.tsx b/src/components/App.tsx index 4215370a2..1868aba6d 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -184,7 +184,7 @@ import LayerUI from "./LayerUI"; import { Stats } from "./Stats"; import { Toast } from "./Toast"; import { actionToggleViewMode } from "../actions/actionToggleViewMode"; -import { getNetworkSpeed } from "../networkStats"; +import { getNetworkSpeed, simulatePing } from "../networkStats"; const { history } = createHistory(); @@ -291,7 +291,8 @@ class App extends React.Component { height: window.innerHeight, }; private scene: Scene; - private netStatsIntervalId?: any; + private networkSpeedIntervalId?: any; + private pingIntervalId?: any; constructor(props: ExcalidrawProps) { super(props); const defaultAppState = getDefaultAppState(); @@ -752,7 +753,7 @@ class App extends React.Component { this.removeEventListeners(); this.scene.destroy(); clearTimeout(touchTimeout); - clearTimeout(this.netStatsIntervalId); + clearTimeout(this.networkSpeedIntervalId); touchTimeout = 0; } @@ -897,7 +898,7 @@ class App extends React.Component { "change", this.calculateNetStats, ); - clearTimeout(this.netStatsIntervalId); + clearTimeout(this.networkSpeedIntervalId); } } @@ -1026,23 +1027,39 @@ class App extends React.Component { } } - private calculateNetStats = async () => { + private calculateNetStats = () => { + this.checkPing(); + this.checkNetworkSpeed(); + }; + + private checkPing = async () => { if (!this.state.showStats || !this.props.isCollaborating) { - clearTimeout(this.netStatsIntervalId); + clearTimeout(this.pingIntervalId); return; } - const speed = await getNetworkSpeed(); - const networkSpeed = speed; - this.setState({ networkSpeed }); - if (this.netStatsIntervalId) { - clearTimeout(this.netStatsIntervalId); + const ping = await simulatePing(); + this.setState({ ping }); + if (this.pingIntervalId) { + clearTimeout(this.pingIntervalId); } - this.netStatsIntervalId = setTimeout( - this.calculateNetStats, + this.pingIntervalId = setTimeout(this.checkPing, NETWORK_SPEED_TIMEOUT_MS); + }; + + private checkNetworkSpeed = async () => { + if (!this.state.showStats || !this.props.isCollaborating) { + clearTimeout(this.networkSpeedIntervalId); + return; + } + const networkSpeed = await getNetworkSpeed(); + this.setState({ networkSpeed }); + if (this.networkSpeedIntervalId) { + clearTimeout(this.networkSpeedIntervalId); + } + this.networkSpeedIntervalId = setTimeout( + this.checkNetworkSpeed, NETWORK_SPEED_TIMEOUT_MS, ); }; - // Copy/paste private onCut = withBatchedUpdates((event: ClipboardEvent) => { diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx index b441e3ffc..58c81545e 100644 --- a/src/components/Stats.tsx +++ b/src/components/Stats.tsx @@ -180,6 +180,10 @@ export const Stats = (props: { {t("stats.collaborators")} {props.appState.collaborators.size} + + {t("stats.ping")} + {props.appState.ping} + {t("stats.speed")} diff --git a/src/locales/en.json b/src/locales/en.json index e7a688b73..2bf4c633c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -233,6 +233,7 @@ "elements": "Elements", "error": "Error", "height": "Height", + "ping": "Ping", "scene": "Scene", "selected": "Selected", "speed": "Speed", diff --git a/src/networkStats.ts b/src/networkStats.ts index ff1ea3093..bf2189f0e 100644 --- a/src/networkStats.ts +++ b/src/networkStats.ts @@ -50,3 +50,18 @@ const processImage = (): Promise => { export const getNetworkSpeed = async (): Promise => { return await processImage(); }; + +export const simulatePing = async () => { + const startTime = new Date().getTime(); + try { + await fetch(process.env.REACT_APP_SOCKET_SERVER_URL, { + mode: "no-cors", + method: "HEAD", + }); + const endTime = new Date().getTime(); + const delay = endTime - startTime; + return delay < 1000 ? `${delay} ms` : `${(delay / 1000).toFixed(1)} s`; + } catch (e) { + return "Error!"; + } +}; diff --git a/src/types.ts b/src/types.ts index 50b83a314..7cfef7b6f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -89,6 +89,7 @@ export type AppState = { gridSize: number | null; viewModeEnabled: boolean; networkSpeed: number; + ping: string; /** top-most selected groups (i.e. does not include nested groups) */ selectedGroupIds: { [groupId: string]: boolean };