Line data Source code
1 338 : // SPDX-License-Identifier: LGPL-2.1-or-later
2 :
3 338 : import cockpit from "cockpit";
4 : import { EventEmitter } from "cockpit/event";
5 : import { Channel } from "cockpit/channel";
6 :
7 338 : const _ = cockpit.gettext;
8 :
9 : /* SessionController for handling idle timeouts
10 : */
11 :
12 : interface SessionControllerEvents {
13 : changed: () => void;
14 : }
15 :
16 338 : export class SessionController extends EventEmitter<SessionControllerEvents> {
17 : active: boolean;
18 338 : countdown: number = -1;
19 338 : problem: string | null = null;
20 :
21 338 : #channel: Channel | null = null;
22 338 : #timeout: number = -1;
23 :
24 338 : constructor() {
25 338 : super();
26 61 : this.active = (window.parent === window || window.name.indexOf("cockpit1:") !== 0);
27 338 : if (this.active) {
28 338 : console.debug("session controller active for", window.name);
29 338 : this.add_window(window);
30 338 : this.#open_channel();
31 338 : }
32 338 : }
33 :
34 338 : add_window(win: Window) {
35 338 : if (!this.active)
36 338 : 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 338 : win.addEventListener("mousemove", this.#record_activity, false);
43 338 : win.addEventListener("mousedown", this.#record_activity, false);
44 338 : win.addEventListener("keypress", this.#record_activity, false);
45 338 : win.addEventListener("touchmove", this.#record_activity, false);
46 338 : win.addEventListener("scroll", this.#record_activity, false);
47 338 : }
48 :
49 338 : #open_channel() {
50 338 : const channel = new Channel({ payload: "session-control" });
51 :
52 335 : channel.on("ready", (data) => {
53 335 : if (typeof data.timeout === "number")
54 335 : this.#timeout = data.timeout;
55 335 : });
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 338 : this.#channel = channel;
75 338 : }
76 :
77 335 : inhibit_activity_reporting(flag: boolean) {
78 335 : this.#inhibit_activity_reporting = flag;
79 335 : }
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 338 : #last_activity_report_time: number = 0;
90 338 : #inhibit_activity_reporting: boolean = false;
91 :
92 292 : #record_activity = () => {
93 292 : const now = Date.now();
94 292 : if (!this.#inhibit_activity_reporting && Math.abs(now - this.#last_activity_report_time) > 10 * 1000) {
95 292 : this.#last_activity_report_time = now;
96 292 : this.#send_activity_notification();
97 25 : if (this.countdown !== -1) {
98 25 : this.countdown = -1;
99 25 : this.emit("changed");
100 25 : }
101 292 : }
102 292 : };
103 :
104 292 : #send_activity_notification() {
105 292 : if (this.#channel && this.#timeout > 0)
106 25 : this.#channel.send_control({ command: "active" });
107 292 : }
108 338 : }
109 :
110 338 : let controller : SessionController | undefined;
111 :
112 338 : export function get_session_controller(): SessionController {
113 338 : if (!controller)
114 338 : controller = new SessionController();
115 338 : return controller;
116 338 : }
|