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