Repository navigation
Expand file tree
/
Copy pathkeepalive.js
More file actions
129 lines (111 loc) · 3.5 KB
/
Copy pathkeepalive.js
File metadata and controls
129 lines (111 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// ── Keep-alive ──
// Two independent halves, because a session dies two different ways:
// 1. The Windows box sleeps or locks the screen out from under it. We hold a
// powerSaveBlocker, which is the same thing a video player does while it
// is playing — Windows sees the machine as busy and never idles out.
// 2. The far end drops an idle connection. SSH protocol keepalives cover
// servers that count any traffic; anti-idle writes a harmless byte into
// the session itself for servers that only count channel data.
const { app, ipcMain, powerSaveBlocker } = require('electron');
const path = require('path');
const fs = require('fs');
const DEFAULTS = {
enabled: true,
keepAwake: true,
// 'display' keeps the screen on too (video-player behaviour); 'system' lets
// the monitor sleep but keeps the machine from suspending.
awakeMode: 'display',
sshInterval: 20, // seconds between SSH keepalives; 0 disables
antiIdle: false,
antiIdleSeconds: 120,
};
// NUL: every shell ignores it and nothing echoes, so it is safe to inject
// into whatever happens to be running in the session.
const ANTI_IDLE_BYTE = '\u0000';
let settings = { ...DEFAULTS };
let blockerId = null;
let blockerType = null;
const listeners = new Set();
function settingsFile() {
return path.join(app.getPath('userData'), 'keepalive.json');
}
function load() {
try {
settings = { ...DEFAULTS, ...JSON.parse(fs.readFileSync(settingsFile(), 'utf-8')) };
} catch {
settings = { ...DEFAULTS };
}
return settings;
}
function persist() {
try {
fs.writeFileSync(settingsFile(), JSON.stringify(settings, null, 2));
} catch {}
}
function blockerActive() {
return blockerId !== null && powerSaveBlocker.isStarted(blockerId);
}
function syncBlocker() {
const want = settings.enabled && settings.keepAwake;
const type = settings.awakeMode === 'system' ? 'prevent-app-suspension' : 'prevent-display-sleep';
// The blocker type is fixed at start(), so a mode change means restart it.
if (blockerActive() && (!want || blockerType !== type)) {
powerSaveBlocker.stop(blockerId);
blockerId = null;
blockerType = null;
}
if (want && !blockerActive()) {
blockerId = powerSaveBlocker.start(type);
blockerType = type;
}
}
function get() {
return { ...settings, awake: blockerActive() };
}
function set(patch) {
settings = { ...settings, ...patch };
persist();
syncBlocker();
for (const fn of listeners) {
try { fn(settings); } catch {}
}
return get();
}
// sshmanager subscribes so live sessions pick up an anti-idle change without
// having to be reconnected.
function subscribe(fn) {
listeners.add(fn);
return () => listeners.delete(fn);
}
// Effective SSH keepalive interval in ms, for ssh2's connect options.
function sshKeepaliveMs() {
if (!settings.enabled) return 0;
const secs = Number(settings.sshInterval) || 0;
return secs > 0 ? secs * 1000 : 0;
}
function antiIdleMs() {
if (!settings.enabled || !settings.antiIdle) return 0;
const secs = Number(settings.antiIdleSeconds) || 0;
return secs > 0 ? secs * 1000 : 0;
}
function register() {
load();
syncBlocker();
ipcMain.handle('keepalive:get', () => get());
ipcMain.handle('keepalive:set', (event, patch) => set(patch || {}));
app.on('will-quit', () => {
if (blockerActive()) powerSaveBlocker.stop(blockerId);
blockerId = null;
blockerType = null;
});
}
module.exports = {
register,
get,
set,
subscribe,
sshKeepaliveMs,
antiIdleMs,
ANTI_IDLE_BYTE,
DEFAULTS,
};