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