Line data Source code
1 341 : // SPDX-License-Identifier: LGPL-2.1-or-later
2 :
3 341 : import cockpit from "cockpit";
4 : import { EventEmitter } from "cockpit/event";
5 : import { Channel } from "cockpit/channel";
6 :
7 341 : const _ = cockpit.gettext;
8 :
9 : /* SessionController for handling idle timeouts
10 : */
11 :
12 : interface SessionControllerEvents {
13 : changed: () => void;
14 : }
15 :
16 341 : export class SessionController extends EventEmitter<SessionControllerEvents> {
17 : active: boolean;
18 341 : countdown: number = -1;
19 341 : problem: string | null = null;
20 :
21 341 : #channel: Channel | null = null;
22 341 : #timeout: number = -1;
23 :
24 341 : constructor() {
25 341 : super();
26 63 : this.active = (window.parent === window || window.name.indexOf("cockpit1:") !== 0);
27 341 : if (this.active) {
28 341 : console.debug("session controller active for", window.name);
29 341 : this.add_window(window);
30 341 : this.#open_channel();
31 341 : }
32 341 : }
33 :
34 341 : add_window(win: Window) {
35 341 : if (!this.active)
36 341 : return;
37 :
38 : // NOTE: This function will be called many many times for a
39 : // given window, not just once. Calling addEventListener
40 : // multiple times is ok here, however, since we always pass
41 : // the exact same listener.
42 341 : win.addEventListener("mousemove", this.#record_activity, false);
43 341 : win.addEventListener("mousedown", this.#record_activity, false);
44 341 : win.addEventListener("keypress", this.#record_activity, false);
45 341 : win.addEventListener("touchmove", this.#record_activity, false);
46 341 : win.addEventListener("scroll", this.#record_activity, false);
47 341 : }
48 :
49 341 : #open_channel() {
50 341 : const channel = new Channel({ payload: "session-control" });
51 :
52 338 : channel.on("ready", (data) => {
53 338 : if (typeof data.timeout === "number")
54 338 : this.#timeout = data.timeout;
55 338 : });
56 :
57 0 : channel.on("control", (message) => {
58 0 : if (message.command === "countdown" && typeof message.counter === "number") {
59 0 : this.countdown = message.counter;
60 0 : this.emit("changed");
61 0 : } else if (message.command === "logout") {
62 0 : cockpit.logout(true, _("You have been logged out due to inactivity."));
63 0 : }
64 0 : });
65 :
66 6 : channel.on("close", (options) => {
67 0 : const problem = (typeof options.problem === "string" ? options.problem : "") || "disconnected";
68 6 : this.#channel = null;
69 6 : console.warn("session terminated: " + problem);
70 6 : this.problem = problem;
71 6 : this.emit("changed");
72 6 : });
73 :
74 341 : this.#channel = channel;
75 341 : }
76 :
77 338 : inhibit_activity_reporting(flag: boolean) {
78 338 : this.#inhibit_activity_reporting = flag;
79 338 : }
80 :
81 0 : continue_session() {
82 0 : if (this.countdown != -1) {
83 0 : this.#send_activity_notification();
84 0 : this.countdown = -1;
85 0 : this.emit("changed");
86 0 : }
87 0 : }
88 :
89 341 : #last_activity_report_time: number = 0;
90 341 : #inhibit_activity_reporting: boolean = false;
91 :
92 295 : #record_activity = () => {
93 295 : const now = Date.now();
94 295 : if (!this.#inhibit_activity_reporting && Math.abs(now - this.#last_activity_report_time) > 10 * 1000) {
95 295 : this.#last_activity_report_time = now;
96 295 : this.#send_activity_notification();
97 27 : if (this.countdown !== -1) {
98 27 : this.countdown = -1;
99 27 : this.emit("changed");
100 27 : }
101 295 : }
102 295 : };
103 :
104 295 : #send_activity_notification() {
105 295 : if (this.#channel && this.#timeout > 0)
106 27 : this.#channel.send_control({ command: "active" });
107 295 : }
108 341 : }
109 :
110 341 : let controller : SessionController | undefined;
111 :
112 341 : export function get_session_controller(): SessionController {
113 341 : if (!controller)
114 341 : controller = new SessionController();
115 341 : return controller;
116 341 : }
|