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