Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/client/idle.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45declare var $: any;6import { throttle } from "lodash";7import { delay } from "awaiting";8import { redux } from "../app-framework";9import { IS_TOUCH } from "../feature";10import { WebappClient } from "./client";11import { disconnect_from_all_projects } from "../project/websocket/connect";1213export class IdleClient {14private notification_is_visible: boolean = false;15private client: WebappClient;16private idle_timeout: number = 5 * 60 * 1000; // default -- 5 minutes17private idle_time: number = 0;18private delayed_disconnect?;1920constructor(client: WebappClient) {21this.client = client;22this.init_idle();23}2425public reset(): void {}2627private async init_idle(): Promise<void> {28// Do not bother on touch devices, since they already automatically tend to29// disconnect themselves very aggressively to save battery life, and it's30// sketchy trying to ensure that banner will dismiss properly.31if (IS_TOUCH) {32return;33}3435// Wait a little before setting this stuff up.36await delay(15 * 1000);3738this.idle_time = Date.now() + this.idle_timeout;3940/*41The this.init_time is a Date in the future.42It is pushed forward each time this.idle_reset is called.43The setInterval timer checks every minute, if the current44time is past this this.init_time.45If so, the user is 'idle'.46To keep 'active', call webapp_client.idle_reset as often as you like:47A document.body event listener here and one for each48jupyter iframe.body (see jupyter.coffee).49*/5051this.idle_reset();5253// There is no need to worry about cleaning this up, since the client survives54// for the lifetime of the page.55setInterval(this.idle_check.bind(this), 60 * 1000);5657// Call this idle_reset like a function58// throttled, so will reset timer on *first* call and59// then every 15secs while being called60this.idle_reset = throttle(this.idle_reset.bind(this), 15 * 1000);6162// activate a listener on our global body (universal sink for63// bubbling events, unless stopped!)64$(document).on(65"click mousemove keydown focusin touchstart",66this.idle_reset67);68$("#smc-idle-notification").on(69"click mousemove keydown focusin touchstart",70this.idle_reset71);7273// Every 30s, if the document is visible right now, then we74// reset the idle timeout., just as if the mouse moved. This means75// that users never get the standby timeout if their current browser76// tab is considered visible according to the Page Visibility API77// https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API78// See also https://github.com/sagemathinc/cocalc/issues/637179setInterval(() => {80if (!document.hidden) {81this.idle_reset();82}83}, 30 * 1000);84}8586private idle_check(): void {87if (!this.idle_time) return;88const now = Date.now();89if (this.idle_time >= now) return;90this.show_notification();91if (!this.delayed_disconnect) {92// We actually disconnect 15s after appearing to93// so that if the user sees the idle banner and immediately94// dismisses it, then the experience is less disruptive.95this.delayed_disconnect = setTimeout(() => {96this.client.hub_client.disconnect();97disconnect_from_all_projects();98}, 15 * 1000);99}100}101102// We set this.idle_time to the **moment in in the future** at103// which the user will be considered idle, and also emit event104// indicating that user is currently active.105public idle_reset(): void {106this.hide_notification();107this.idle_time = Date.now() + this.idle_timeout + 1000;108if (this.delayed_disconnect) {109clearTimeout(this.delayed_disconnect);110this.delayed_disconnect = undefined;111}112this.client.hub_client.reconnect();113}114115// Change the standby timeout to a particular time in minutes.116// This gets called when the user configuration settings are set/loaded.117public set_standby_timeout_m(time_m: number): void {118this.idle_timeout = time_m * 60 * 1000;119this.idle_reset();120}121122private notification_html(): string {123const customize = redux.getStore("customize");124const site_name = customize.get("site_name");125const description = customize.get("site_description");126const logo_rect = customize.get("logo_rectangular");127const logo_square = customize.get("logo_square");128129// we either have just a customized square logo or square + rectangular -- or just the baked in default130let html: string = "<div>";131if (logo_square != "") {132if (logo_rect != "") {133html += `<img class="logo-square" src="${logo_square}"><img class="logo-rectangular" src="${logo_rect}">`;134} else {135html += `<img class="logo-square" src="${logo_square}"><h3>${site_name}</h3>`;136}137html += `<h4>${description}</h4>`;138} else {139// We have to import this here since art can *ONLY* be imported140// when this is loaded in webpack.141const { APP_LOGO_WHITE } = require("../art");142html += `<img class="logo-square" src="${APP_LOGO_WHITE}"><h3>${description}</h3>`;143}144145return html + "— click to reconnect —</div>";146}147148public show_notification(): void {149if (this.notification_is_visible) return;150const idle = $("#cocalc-idle-notification");151if (idle.length === 0) {152const content = this.notification_html();153const box = $("<div/>", { id: "cocalc-idle-notification" }).html(content);154$("body").append(box);155// quick slide up, just to properly slide down the fist time156box.slideUp(0, () => box.slideDown("slow"));157} else {158idle.slideDown("slow");159}160this.notification_is_visible = true;161}162163public hide_notification(): void {164if (!this.notification_is_visible) return;165$("#cocalc-idle-notification").slideUp("slow");166this.notification_is_visible = false;167}168}169170171