@velor/remote-mouse 6.5.0 → 6.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/client/i18n/locales/de.js +1 -0
- package/client/i18n/locales/en.js +1 -0
- package/client/i18n/locales/es.js +1 -0
- package/client/i18n/locales/fr.js +1 -0
- package/client/i18n/locales/it.js +1 -0
- package/client/i18n/locales/ja.js +1 -0
- package/client/i18n/locales/pt.js +1 -0
- package/client/i18n/locales/ru.js +1 -0
- package/client/i18n/locales/zh.js +1 -0
- package/client/preferences/state.js +1 -0
- package/client/services/app-state/appStateKeys.js +2 -0
- package/client/services/app-state/createAppStateService.js +1 -0
- package/client/services/app-state/resolveEffectiveStateValue.js +4 -0
- package/client/ui/main/applyRemoteVisibilityState.js +7 -0
- package/client/ui/main/init-ui.js +1 -0
- package/client/ui/main/keyboard-panel.js +28 -6
- package/package.json +1 -1
- package/public/server-info.html +53 -85
- package/server/application/shutdown.js +1 -0
- package/server/application/start.js +2 -0
- package/server/application/state.js +1 -0
- package/server/connection/api/admin-remotes.router.js +5 -0
- package/server/connection/api/server-info.router.js +2 -0
- package/server/init/observers/startUpdateManagerLogObserver.js +9 -0
- package/server/remotes/preview/createPreviewStreamer.js +15 -2
- package/server/remotes/samsung/createSamsungCommandService.js +16 -6
- package/server/remotes/samsung/createSamsungTvGetter.js +7 -5
- package/server/remotes/samsung/device-config.js +7 -0
- package/server/services/persistence/createUpdateEventLogDao.js +93 -0
- package/server/services/persistence/index.js +2 -0
- package/server/services/system/createSystemService.js +7 -0
- package/server/term/cli/parseCliArgs.js +3 -0
- package/server/term/cli/runCliCmd.js +1 -0
- package/server/term/srv/commands/helpCommand.js +1 -0
- package/server/term/srv/commands/updateEventsCommand.js +7 -0
- package/server/term/srv/executeCliCommand.js +2 -0
- package/server/utils/network.js +98 -0
package/README.md
CHANGED
|
@@ -312,6 +312,7 @@ Useful CLI commands:
|
|
|
312
312
|
- `service restart` restarts the local daemon/service
|
|
313
313
|
- `tasks` prints the task manager snapshot
|
|
314
314
|
- `task-manager` is an alias of `tasks`
|
|
315
|
+
- `update-events` prints persisted update-manager events
|
|
315
316
|
- `samsung-detect` detects Samsung TVs available on the network
|
|
316
317
|
- `tokens` lists persisted entry tokens
|
|
317
318
|
- `open-qr` opens the QR page on the server
|
|
@@ -170,6 +170,7 @@ export const de = {
|
|
|
170
170
|
'preferences.section.browsers.description': 'Waehlen Sie, welche lokalen Browser in der Browser-Remote angezeigt werden koennen.',
|
|
171
171
|
'preferences.remote.keyboard': 'Tastatur',
|
|
172
172
|
'preferences.remote.browser': 'Browser',
|
|
173
|
+
'preferences.remote.system': 'System',
|
|
173
174
|
'preferences.remote.vlc': 'VLC',
|
|
174
175
|
'preferences.remote.samsung': 'Samsung TV',
|
|
175
176
|
'preferences.remote.preview': 'Cursor-Vorschau',
|
|
@@ -302,6 +302,7 @@ export const en = {
|
|
|
302
302
|
'preferences.section.browsers.description': 'Choose which local browsers can be shown in the browser remote.',
|
|
303
303
|
'preferences.remote.keyboard': 'Keyboard',
|
|
304
304
|
'preferences.remote.browser': 'Browser',
|
|
305
|
+
'preferences.remote.system': 'System',
|
|
305
306
|
'preferences.remote.vlc': 'VLC',
|
|
306
307
|
'preferences.remote.samsung': 'Samsung TV',
|
|
307
308
|
'preferences.remote.preview': 'Cursor preview',
|
|
@@ -173,6 +173,7 @@ export const es = {
|
|
|
173
173
|
'preferences.section.browsers.description': 'Elija que navegadores locales pueden mostrarse en la remote del navegador.',
|
|
174
174
|
'preferences.remote.keyboard': 'Teclado',
|
|
175
175
|
'preferences.remote.browser': 'Navegador',
|
|
176
|
+
'preferences.remote.system': 'Sistema',
|
|
176
177
|
'preferences.remote.vlc': 'VLC',
|
|
177
178
|
'preferences.remote.samsung': 'Samsung TV',
|
|
178
179
|
'preferences.remote.preview': 'Vista previa del cursor',
|
|
@@ -302,6 +302,7 @@ export const fr = {
|
|
|
302
302
|
'preferences.section.browsers.description': 'Choisissez quels navigateurs locaux peuvent etre affiches dans la remote navigateur.',
|
|
303
303
|
'preferences.remote.keyboard': 'Clavier',
|
|
304
304
|
'preferences.remote.browser': 'Navigateur',
|
|
305
|
+
'preferences.remote.system': 'Système',
|
|
305
306
|
'preferences.remote.vlc': 'VLC',
|
|
306
307
|
'preferences.remote.samsung': 'Samsung TV',
|
|
307
308
|
'preferences.remote.preview': 'Preview curseur',
|
|
@@ -170,6 +170,7 @@ export const it = {
|
|
|
170
170
|
'preferences.section.browsers.description': 'Scegli quali browser locali possono essere mostrati nella remote browser.',
|
|
171
171
|
'preferences.remote.keyboard': 'Tastiera',
|
|
172
172
|
'preferences.remote.browser': 'Browser',
|
|
173
|
+
'preferences.remote.system': 'Sistema',
|
|
173
174
|
'preferences.remote.vlc': 'VLC',
|
|
174
175
|
'preferences.remote.samsung': 'Samsung TV',
|
|
175
176
|
'preferences.remote.preview': 'Anteprima cursore',
|
|
@@ -170,6 +170,7 @@ export const ja = {
|
|
|
170
170
|
'preferences.section.browsers.description': 'ブラウザー remote に表示できるローカルブラウザーを選択します。',
|
|
171
171
|
'preferences.remote.keyboard': 'キーボード',
|
|
172
172
|
'preferences.remote.browser': 'ブラウザー',
|
|
173
|
+
'preferences.remote.system': 'システム',
|
|
173
174
|
'preferences.remote.vlc': 'VLC',
|
|
174
175
|
'preferences.remote.samsung': 'Samsung TV',
|
|
175
176
|
'preferences.remote.preview': 'カーソルプレビュー',
|
|
@@ -170,6 +170,7 @@ export const pt = {
|
|
|
170
170
|
'preferences.section.browsers.description': 'Escolha quais navegadores locais podem ser mostrados na remote do navegador.',
|
|
171
171
|
'preferences.remote.keyboard': 'Teclado',
|
|
172
172
|
'preferences.remote.browser': 'Navegador',
|
|
173
|
+
'preferences.remote.system': 'Sistema',
|
|
173
174
|
'preferences.remote.vlc': 'VLC',
|
|
174
175
|
'preferences.remote.samsung': 'Samsung TV',
|
|
175
176
|
'preferences.remote.preview': 'Previa do cursor',
|
|
@@ -154,6 +154,7 @@ export const ru = {
|
|
|
154
154
|
'preferences.section.browsers.description': 'Выберите, какие локальные браузеры можно показывать в пульте браузера.',
|
|
155
155
|
'preferences.remote.keyboard': 'Клавиатура',
|
|
156
156
|
'preferences.remote.browser': 'Браузер',
|
|
157
|
+
'preferences.remote.system': 'Система',
|
|
157
158
|
'preferences.remote.vlc': 'VLC',
|
|
158
159
|
'preferences.remote.samsung': 'Samsung TV',
|
|
159
160
|
'preferences.remote.preview': 'Превью курсора',
|
|
@@ -170,6 +170,7 @@ export const zh = {
|
|
|
170
170
|
'preferences.section.browsers.description': '选择哪些本地浏览器可以显示在浏览器 remote 中。',
|
|
171
171
|
'preferences.remote.keyboard': '键盘',
|
|
172
172
|
'preferences.remote.browser': '浏览器',
|
|
173
|
+
'preferences.remote.system': '系统',
|
|
173
174
|
'preferences.remote.vlc': 'VLC',
|
|
174
175
|
'preferences.remote.samsung': 'Samsung TV',
|
|
175
176
|
'preferences.remote.preview': '光标预览',
|
|
@@ -13,6 +13,7 @@ export const APP_STATE_BROWSER_VISIBILITY = 'preferences.browserVisibility';
|
|
|
13
13
|
|
|
14
14
|
export const APP_STATE_EFFECTIVE_BROWSER_REMOTE_VISIBLE = 'effective.remote.browser.visible';
|
|
15
15
|
export const APP_STATE_EFFECTIVE_KEYBOARD_REMOTE_VISIBLE = 'effective.remote.keyboard.visible';
|
|
16
|
+
export const APP_STATE_EFFECTIVE_SYSTEM_REMOTE_VISIBLE = 'effective.remote.system.visible';
|
|
16
17
|
export const APP_STATE_EFFECTIVE_VLC_REMOTE_VISIBLE = 'effective.remote.vlc.visible';
|
|
17
18
|
export const APP_STATE_EFFECTIVE_SAMSUNG_REMOTE_VISIBLE = 'effective.remote.samsung.visible';
|
|
18
19
|
export const APP_STATE_EFFECTIVE_PREVIEW_REMOTE_VISIBLE = 'effective.remote.preview.visible';
|
|
@@ -20,6 +21,7 @@ export const APP_STATE_EFFECTIVE_PREVIEW_REMOTE_VISIBLE = 'effective.remote.prev
|
|
|
20
21
|
export const EFFECTIVE_APP_STATE_KEYS = [
|
|
21
22
|
APP_STATE_EFFECTIVE_BROWSER_REMOTE_VISIBLE,
|
|
22
23
|
APP_STATE_EFFECTIVE_KEYBOARD_REMOTE_VISIBLE,
|
|
24
|
+
APP_STATE_EFFECTIVE_SYSTEM_REMOTE_VISIBLE,
|
|
23
25
|
APP_STATE_EFFECTIVE_VLC_REMOTE_VISIBLE,
|
|
24
26
|
APP_STATE_EFFECTIVE_SAMSUNG_REMOTE_VISIBLE,
|
|
25
27
|
APP_STATE_EFFECTIVE_PREVIEW_REMOTE_VISIBLE,
|
|
@@ -25,6 +25,7 @@ export {
|
|
|
25
25
|
APP_STATE_EFFECTIVE_KEYBOARD_REMOTE_VISIBLE,
|
|
26
26
|
APP_STATE_EFFECTIVE_PREVIEW_REMOTE_VISIBLE,
|
|
27
27
|
APP_STATE_EFFECTIVE_SAMSUNG_REMOTE_VISIBLE,
|
|
28
|
+
APP_STATE_EFFECTIVE_SYSTEM_REMOTE_VISIBLE,
|
|
28
29
|
APP_STATE_EFFECTIVE_VLC_REMOTE_VISIBLE,
|
|
29
30
|
APP_STATE_HANDEDNESS,
|
|
30
31
|
APP_STATE_KEYBOARD_PREVIEW_ACTIVE,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
APP_STATE_EFFECTIVE_KEYBOARD_REMOTE_VISIBLE,
|
|
5
5
|
APP_STATE_EFFECTIVE_PREVIEW_REMOTE_VISIBLE,
|
|
6
6
|
APP_STATE_EFFECTIVE_SAMSUNG_REMOTE_VISIBLE,
|
|
7
|
+
APP_STATE_EFFECTIVE_SYSTEM_REMOTE_VISIBLE,
|
|
7
8
|
APP_STATE_EFFECTIVE_VLC_REMOTE_VISIBLE,
|
|
8
9
|
APP_STATE_REMOTE_VISIBILITY,
|
|
9
10
|
} from './appStateKeys.js';
|
|
@@ -34,6 +35,9 @@ export function resolveEffectiveStateValue(key, {stateStore, persistStore, clien
|
|
|
34
35
|
return getConfigSection(config, 'keyboard').enabled !== false
|
|
35
36
|
&& getRecordFlag(remoteVisibility, 'keyboard', true);
|
|
36
37
|
|
|
38
|
+
case APP_STATE_EFFECTIVE_SYSTEM_REMOTE_VISIBLE:
|
|
39
|
+
return getRecordFlag(remoteVisibility, 'system', true);
|
|
40
|
+
|
|
37
41
|
case APP_STATE_EFFECTIVE_VLC_REMOTE_VISIBLE:
|
|
38
42
|
return getConfigSection(config, 'vlc').enabled !== false
|
|
39
43
|
&& getRecordFlag(remoteVisibility, 'vlc', true);
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
APP_STATE_EFFECTIVE_KEYBOARD_REMOTE_VISIBLE,
|
|
4
4
|
APP_STATE_EFFECTIVE_PREVIEW_REMOTE_VISIBLE,
|
|
5
5
|
APP_STATE_EFFECTIVE_SAMSUNG_REMOTE_VISIBLE,
|
|
6
|
+
APP_STATE_EFFECTIVE_SYSTEM_REMOTE_VISIBLE,
|
|
6
7
|
APP_STATE_EFFECTIVE_VLC_REMOTE_VISIBLE,
|
|
7
8
|
} from '../../services/app-state/createAppStateService.js';
|
|
8
9
|
|
|
@@ -16,6 +17,8 @@ export function applyRemoteVisibilityState({
|
|
|
16
17
|
const browserVisible = appState.get(APP_STATE_EFFECTIVE_BROWSER_REMOTE_VISIBLE);
|
|
17
18
|
|
|
18
19
|
const keyboardVisible = appState.get(APP_STATE_EFFECTIVE_KEYBOARD_REMOTE_VISIBLE);
|
|
20
|
+
|
|
21
|
+
const systemVisible = appState.get(APP_STATE_EFFECTIVE_SYSTEM_REMOTE_VISIBLE);
|
|
19
22
|
|
|
20
23
|
const vlcVisible = appState.get(APP_STATE_EFFECTIVE_VLC_REMOTE_VISIBLE);
|
|
21
24
|
|
|
@@ -46,6 +49,10 @@ export function applyRemoteVisibilityState({
|
|
|
46
49
|
if (remotes.samsung.root) {
|
|
47
50
|
remotes.samsung.root.hidden = !samsungVisible;
|
|
48
51
|
}
|
|
52
|
+
|
|
53
|
+
if (remotes.system.root) {
|
|
54
|
+
remotes.system.root.hidden = !systemVisible;
|
|
55
|
+
}
|
|
49
56
|
|
|
50
57
|
if (remotes.vlc.root) {
|
|
51
58
|
remotes.vlc.root.hidden = !vlcVisible;
|
|
@@ -39,6 +39,7 @@ export function initUi(services) {
|
|
|
39
39
|
for (const key of [
|
|
40
40
|
'effective.remote.browser.visible',
|
|
41
41
|
'effective.remote.keyboard.visible',
|
|
42
|
+
'effective.remote.system.visible',
|
|
42
43
|
'effective.remote.vlc.visible',
|
|
43
44
|
'effective.remote.samsung.visible',
|
|
44
45
|
'effective.remote.preview.visible',
|
|
@@ -31,6 +31,7 @@ export function bindKeyboardPanel(services, dom) {
|
|
|
31
31
|
shift: false,
|
|
32
32
|
};
|
|
33
33
|
let activeMode = '';
|
|
34
|
+
let compositionFallbackTimer = null;
|
|
34
35
|
|
|
35
36
|
function setPreviewActive(active) {
|
|
36
37
|
appState.set(APP_STATE_KEYBOARD_PREVIEW_ACTIVE, Boolean(active));
|
|
@@ -157,6 +158,17 @@ export function bindKeyboardPanel(services, dom) {
|
|
|
157
158
|
emitWithTimestamp(socket, REMOTE_EVENT_KEYBOARD_KEY, { key, modifiers });
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
function sendLiveText() {
|
|
162
|
+
const text = liveTextInput.value;
|
|
163
|
+
if (!text) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
emitWithTimestamp(socket, REMOTE_EVENT_KEYBOARD_TEXT, { text });
|
|
168
|
+
liveTextInput.value = '';
|
|
169
|
+
setPreviewActive(true);
|
|
170
|
+
}
|
|
171
|
+
|
|
160
172
|
function getActiveModifiers() {
|
|
161
173
|
return Object.entries(modifierState)
|
|
162
174
|
.filter(([, enabled]) => enabled)
|
|
@@ -234,19 +246,29 @@ export function bindKeyboardPanel(services, dom) {
|
|
|
234
246
|
liveTextInput.addEventListener('beforeinput', (event) => {
|
|
235
247
|
const inputType = String(event.inputType || '');
|
|
236
248
|
|
|
237
|
-
if (inputType.startsWith('delete')) {
|
|
249
|
+
if (inputType.startsWith('delete') && !event.isComposing) {
|
|
238
250
|
pressKeyboardAction('backspace');
|
|
239
251
|
setPreviewActive(true);
|
|
240
|
-
return;
|
|
241
252
|
}
|
|
253
|
+
});
|
|
242
254
|
|
|
243
|
-
|
|
244
|
-
if (
|
|
255
|
+
liveTextInput.addEventListener('input', (event) => {
|
|
256
|
+
if (event.isComposing) {
|
|
245
257
|
return;
|
|
246
258
|
}
|
|
247
259
|
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
if (compositionFallbackTimer) {
|
|
261
|
+
window.clearTimeout(compositionFallbackTimer);
|
|
262
|
+
compositionFallbackTimer = null;
|
|
263
|
+
}
|
|
264
|
+
sendLiveText();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
liveTextInput.addEventListener('compositionend', () => {
|
|
268
|
+
compositionFallbackTimer = window.setTimeout(() => {
|
|
269
|
+
compositionFallbackTimer = null;
|
|
270
|
+
sendLiveText();
|
|
271
|
+
}, 0);
|
|
250
272
|
});
|
|
251
273
|
|
|
252
274
|
liveTextInput.addEventListener('keydown', (event) => {
|
package/package.json
CHANGED
package/public/server-info.html
CHANGED
|
@@ -150,6 +150,28 @@
|
|
|
150
150
|
</div>
|
|
151
151
|
</section>
|
|
152
152
|
|
|
153
|
+
<section>
|
|
154
|
+
<h2>Update-manager events</h2>
|
|
155
|
+
<div class="table-wrap">
|
|
156
|
+
<table>
|
|
157
|
+
<thead>
|
|
158
|
+
<tr>
|
|
159
|
+
<th>ID</th>
|
|
160
|
+
<th>Type</th>
|
|
161
|
+
<th>At</th>
|
|
162
|
+
<th>Enabled</th>
|
|
163
|
+
<th>Key</th>
|
|
164
|
+
<th>Checked</th>
|
|
165
|
+
<th>Has update</th>
|
|
166
|
+
<th>Skipped</th>
|
|
167
|
+
<th>Error</th>
|
|
168
|
+
</tr>
|
|
169
|
+
</thead>
|
|
170
|
+
<tbody id="update-events-table"></tbody>
|
|
171
|
+
</table>
|
|
172
|
+
</div>
|
|
173
|
+
</section>
|
|
174
|
+
|
|
153
175
|
<section>
|
|
154
176
|
<h2 data-i18n="serverInfo.section.capabilities">Capabilities</h2>
|
|
155
177
|
<pre id="capabilities"></pre>
|
|
@@ -192,6 +214,7 @@
|
|
|
192
214
|
const tasksTable = document.getElementById('tasks-table');
|
|
193
215
|
const tokensTable = document.getElementById('tokens-table');
|
|
194
216
|
const restartsTable = document.getElementById('restarts-table');
|
|
217
|
+
const updateEventsTable = document.getElementById('update-events-table');
|
|
195
218
|
const capabilitiesEl = document.getElementById('capabilities');
|
|
196
219
|
const configEl = document.getElementById('config');
|
|
197
220
|
const sysConfigEl = document.getElementById('sys-config');
|
|
@@ -202,10 +225,6 @@
|
|
|
202
225
|
timeStyle: 'medium',
|
|
203
226
|
});
|
|
204
227
|
let logsAutoScroll = true;
|
|
205
|
-
let configStream = null;
|
|
206
|
-
let configSubscriptionId = '';
|
|
207
|
-
let pendingConfigPayload = null;
|
|
208
|
-
let applyingConfigPayload = false;
|
|
209
228
|
|
|
210
229
|
function t(key, params) {
|
|
211
230
|
return i18n.t(key, params);
|
|
@@ -349,6 +368,33 @@
|
|
|
349
368
|
}
|
|
350
369
|
}
|
|
351
370
|
|
|
371
|
+
function renderUpdateEvents(updateEvents) {
|
|
372
|
+
updateEventsTable.innerHTML = '';
|
|
373
|
+
if (!Array.isArray(updateEvents) || updateEvents.length === 0) {
|
|
374
|
+
const row = document.createElement('tr');
|
|
375
|
+
row.innerHTML = '<td colspan="9">No update-manager events.</td>';
|
|
376
|
+
updateEventsTable.appendChild(row);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for (const event of updateEvents) {
|
|
381
|
+
const row = document.createElement('tr');
|
|
382
|
+
const result = event.lastResult || {};
|
|
383
|
+
row.innerHTML = `
|
|
384
|
+
<td>${event.id ?? ''}</td>
|
|
385
|
+
<td>${event.type || '-'}</td>
|
|
386
|
+
<td>${formatDateTime(event.eventAt)}</td>
|
|
387
|
+
<td>${event.enabled ? 'yes' : 'no'}</td>
|
|
388
|
+
<td>${event.lastKey || '-'}</td>
|
|
389
|
+
<td>${Object.hasOwn(result, 'checked') ? (result.checked ? 'yes' : 'no') : '-'}</td>
|
|
390
|
+
<td>${Object.hasOwn(result, 'hasUpdate') ? (result.hasUpdate ? 'yes' : 'no') : '-'}</td>
|
|
391
|
+
<td>${Object.hasOwn(result, 'skipped') ? (result.skipped ? 'yes' : 'no') : '-'}</td>
|
|
392
|
+
<td>${result.error || '-'}</td>
|
|
393
|
+
`;
|
|
394
|
+
updateEventsTable.appendChild(row);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
352
398
|
function renderLogs(logs) {
|
|
353
399
|
const previousScrollTop = logsEl.scrollTop;
|
|
354
400
|
const shouldAutoScroll = logsAutoScroll;
|
|
@@ -446,33 +492,6 @@
|
|
|
446
492
|
sysConfigEl.textContent = JSON.stringify(snapshot || {}, null, 2);
|
|
447
493
|
}
|
|
448
494
|
|
|
449
|
-
function flushPendingConfigPayload() {
|
|
450
|
-
if (applyingConfigPayload || !pendingConfigPayload) {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
applyingConfigPayload = true;
|
|
455
|
-
const payload = pendingConfigPayload;
|
|
456
|
-
pendingConfigPayload = null;
|
|
457
|
-
|
|
458
|
-
try {
|
|
459
|
-
renderConfigSnapshot(payload.config);
|
|
460
|
-
renderSystemConfigSnapshot(payload.sysConfig);
|
|
461
|
-
} finally {
|
|
462
|
-
applyingConfigPayload = false;
|
|
463
|
-
if (pendingConfigPayload) {
|
|
464
|
-
flushPendingConfigPayload();
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function scheduleConfigPayload(payload) {
|
|
470
|
-
pendingConfigPayload = payload;
|
|
471
|
-
queueMicrotask(() => {
|
|
472
|
-
flushPendingConfigPayload();
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
|
|
476
495
|
async function refresh() {
|
|
477
496
|
try {
|
|
478
497
|
const response = await fetch('/api/admin/server-info/data', { cache: 'no-store' });
|
|
@@ -498,77 +517,26 @@
|
|
|
498
517
|
renderTasks(data.tasks || []);
|
|
499
518
|
renderTokens(data.tokens || []);
|
|
500
519
|
renderRestarts(data.restarts || []);
|
|
520
|
+
renderUpdateEvents(data.updateEvents || []);
|
|
501
521
|
renderCapabilities(data.system || null);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
sysConfig: data.sysConfig,
|
|
505
|
-
});
|
|
522
|
+
renderConfigSnapshot(data.config);
|
|
523
|
+
renderSystemConfigSnapshot(data.sysConfig);
|
|
506
524
|
renderLogs(data.logs || []);
|
|
507
525
|
} catch (error) {
|
|
508
526
|
logsEl.textContent = t('serverInfo.loadError', {message: error?.message || error});
|
|
509
527
|
}
|
|
510
528
|
}
|
|
511
529
|
|
|
512
|
-
async function subscribeToConfigEvents() {
|
|
513
|
-
if (typeof window.EventSource !== 'function') {
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const response = await fetch('/api/admin/subs/configs', {
|
|
518
|
-
method: 'POST',
|
|
519
|
-
headers: {
|
|
520
|
-
'Content-Type': 'application/json',
|
|
521
|
-
},
|
|
522
|
-
body: JSON.stringify({scope: 'config'}),
|
|
523
|
-
});
|
|
524
|
-
if (!response.ok) {
|
|
525
|
-
throw new Error(t('serverInfo.sseCreateError'));
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
const payload = await response.json();
|
|
529
|
-
configSubscriptionId = String(payload.id || '');
|
|
530
|
-
if (!configSubscriptionId || !payload.eventsUrl) {
|
|
531
|
-
throw new Error(t('serverInfo.sseInvalid'));
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
configStream?.close();
|
|
535
|
-
configStream = new EventSource(payload.eventsUrl);
|
|
536
|
-
configStream.addEventListener('config.changed', (event) => {
|
|
537
|
-
try {
|
|
538
|
-
scheduleConfigPayload(JSON.parse(event.data || '{}'));
|
|
539
|
-
} catch (_error) {
|
|
540
|
-
// Best effort.
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function unsubscribeFromConfigEvents() {
|
|
546
|
-
if (!configSubscriptionId) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
fetch(`/api/admin/subs/${encodeURIComponent(configSubscriptionId)}`, {
|
|
551
|
-
method: 'DELETE',
|
|
552
|
-
keepalive: true,
|
|
553
|
-
}).catch(() => {});
|
|
554
|
-
configSubscriptionId = '';
|
|
555
|
-
}
|
|
556
|
-
|
|
557
530
|
logsEl.addEventListener('scroll', () => {
|
|
558
531
|
logsAutoScroll = isLogScrollAtBottom();
|
|
559
532
|
}, { passive: true });
|
|
560
533
|
|
|
561
534
|
refresh();
|
|
562
535
|
setInterval(refresh, 2000);
|
|
563
|
-
subscribeToConfigEvents().catch(() => {});
|
|
564
536
|
i18n.onChange(() => {
|
|
565
537
|
i18n.translateRoot(document);
|
|
566
538
|
refresh();
|
|
567
539
|
});
|
|
568
|
-
window.addEventListener('beforeunload', () => {
|
|
569
|
-
configStream?.close();
|
|
570
|
-
unsubscribeFromConfigEvents();
|
|
571
|
-
});
|
|
572
540
|
</script>
|
|
573
541
|
</body>
|
|
574
542
|
</html>
|
|
@@ -44,6 +44,7 @@ export function createApplicationShutdown(services) {
|
|
|
44
44
|
runShutdownStep('Erreur a l arret du task manager', () => taskManager.stop()),
|
|
45
45
|
runShutdownStep('Erreur a l arret de l observateur de configuration', () => state.stopConfigObserver()),
|
|
46
46
|
runShutdownStep('Erreur a l arret de l observateur de notifications', () => state.stopNotificationObserver()),
|
|
47
|
+
runShutdownStep('Erreur a l arret de l observateur des evenements update-manager', () => state.stopUpdateManagerLogObserver()),
|
|
47
48
|
runShutdownStep('Erreur a l arret de l observateur de resolution ecran', () => state.stopDisplaySizeObserver()),
|
|
48
49
|
runShutdownStep('Erreur a l arret de l observateur du QR overlay', () => state.stopQrOverlayRefreshObserver()),
|
|
49
50
|
runShutdownStep('Erreur a l arret de l observateur du survol QR overlay', () => state.stopQrOverlayHoverObserver()),
|
|
@@ -8,6 +8,7 @@ import {startNotificationObserver} from '../init/observers/startNotificationObse
|
|
|
8
8
|
import {startDisplaySizeObserver} from '../init/observers/startDisplaySizeObserver.js';
|
|
9
9
|
import {startQrOverlayRefreshObserver} from '../init/observers/startQrOverlayRefreshObserver.js';
|
|
10
10
|
import {startQrOverlayHoverObserver} from '../init/observers/startQrOverlayHoverObserver.js';
|
|
11
|
+
import {startUpdateManagerLogObserver} from '../init/observers/startUpdateManagerLogObserver.js';
|
|
11
12
|
import {notifyIfRestarted} from '../remotes/admin/notifyIfRestarted.js';
|
|
12
13
|
import {ensureApplicationLifecycleState} from './state.js';
|
|
13
14
|
import {createLogger} from './logger.js';
|
|
@@ -41,6 +42,7 @@ export function createApplicationStart(services) {
|
|
|
41
42
|
|
|
42
43
|
state.stopConfigObserver = startConfigObserver(services);
|
|
43
44
|
state.stopNotificationObserver = startNotificationObserver(services);
|
|
45
|
+
state.stopUpdateManagerLogObserver = startUpdateManagerLogObserver(services);
|
|
44
46
|
state.stopDisplaySizeObserver = startDisplaySizeObserver(services);
|
|
45
47
|
state.stopQrOverlayRefreshObserver = startQrOverlayRefreshObserver(services);
|
|
46
48
|
state.stopQrOverlayHoverObserver = startQrOverlayHoverObserver(services);
|
|
@@ -8,6 +8,7 @@ export function ensureApplicationLifecycleState(services) {
|
|
|
8
8
|
shuttingDown: false,
|
|
9
9
|
stopConfigObserver: () => {},
|
|
10
10
|
stopNotificationObserver: () => {},
|
|
11
|
+
stopUpdateManagerLogObserver: () => {},
|
|
11
12
|
stopDisplaySizeObserver: () => {},
|
|
12
13
|
stopQrOverlayRefreshObserver: () => {},
|
|
13
14
|
stopQrOverlayHoverObserver: () => {},
|
|
@@ -32,6 +32,11 @@ export function createAdminRemotesRouter(services) {
|
|
|
32
32
|
labelKey: 'preferences.remote.keyboard',
|
|
33
33
|
enabled: config?.keyboard?.enabled !== false,
|
|
34
34
|
},
|
|
35
|
+
{
|
|
36
|
+
id: 'system',
|
|
37
|
+
labelKey: 'preferences.remote.system',
|
|
38
|
+
enabled: true,
|
|
39
|
+
},
|
|
35
40
|
{
|
|
36
41
|
id: 'preview',
|
|
37
42
|
labelKey: 'preferences.remote.preview',
|
|
@@ -128,6 +128,7 @@ export function createServerInfoRouter(services) {
|
|
|
128
128
|
const tasks = services.getTaskManager().getTasksSnapshot();
|
|
129
129
|
const tokenEntries = services.getPersistence().entryTokenDao.loadEntryTokens();
|
|
130
130
|
const restarts = services.getPersistence().restartLogDao.listRecentRestartRecords(20);
|
|
131
|
+
const updateEvents = services.getPersistence().updateEventLogDao.listRecentEvents(20);
|
|
131
132
|
const currentToken = services.getTokenManager().getToken();
|
|
132
133
|
const entryPathConfig = services.getSystemConfig().entryPath;
|
|
133
134
|
const daemon = await services.getApplicationDaemonService().getInfo();
|
|
@@ -150,6 +151,7 @@ export function createServerInfoRouter(services) {
|
|
|
150
151
|
daemon,
|
|
151
152
|
system,
|
|
152
153
|
restarts,
|
|
154
|
+
updateEvents,
|
|
153
155
|
config,
|
|
154
156
|
sysConfig,
|
|
155
157
|
logs,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {PUBSUB_SERVICE_UPDATE_MANAGER} from '../../services/pubsub/serviceEventConstants.js';
|
|
2
|
+
|
|
3
|
+
export function startUpdateManagerLogObserver(services) {
|
|
4
|
+
return services.getPubSub().subscribe((event) => {
|
|
5
|
+
services.getPersistence().updateEventLogDao.createEvent(event);
|
|
6
|
+
}, {
|
|
7
|
+
service: PUBSUB_SERVICE_UPDATE_MANAGER,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -15,12 +15,22 @@ export function createPreviewStreamer(services) {
|
|
|
15
15
|
let timer = null;
|
|
16
16
|
|
|
17
17
|
function scheduleNextFrame() {
|
|
18
|
+
if (!active || !socket.connected) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
const previewConfig = getPreviewConfig();
|
|
19
23
|
const fps = Number(previewConfig.fps) || DEFAULT_PERSISTED_CONFIG.preview.fps;
|
|
20
24
|
const intervalMs = Math.max(50, Math.round(1000 / fps));
|
|
21
25
|
|
|
22
26
|
timer = setTimeout(async () => {
|
|
23
|
-
|
|
27
|
+
timer = null;
|
|
28
|
+
if (!active || !socket.connected) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!socket.conn.transport.writable) {
|
|
33
|
+
scheduleNextFrame();
|
|
24
34
|
return;
|
|
25
35
|
}
|
|
26
36
|
|
|
@@ -30,6 +40,9 @@ export function createPreviewStreamer(services) {
|
|
|
30
40
|
const frameWidth = Number(currentPreviewConfig.width) || DEFAULT_PERSISTED_CONFIG.preview.width;
|
|
31
41
|
const frameHeight = Number(currentPreviewConfig.height) || DEFAULT_PERSISTED_CONFIG.preview.height;
|
|
32
42
|
const screen = await services.getSystem().getScreenInfo();
|
|
43
|
+
if (!active || !socket.connected) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
33
46
|
if (!screen) {
|
|
34
47
|
throw new Error('Screen size unavailable');
|
|
35
48
|
}
|
|
@@ -43,7 +56,7 @@ export function createPreviewStreamer(services) {
|
|
|
43
56
|
cursorFrameY,
|
|
44
57
|
} = captureAroundCursor(robot, frameWidth, frameHeight, screen);
|
|
45
58
|
const frame = bgraToRgbaBuffer(capture, frameWidth, frameHeight);
|
|
46
|
-
socket.emit(
|
|
59
|
+
socket.volatile.emit(
|
|
47
60
|
'preview:frame',
|
|
48
61
|
{
|
|
49
62
|
width: frameWidth,
|
|
@@ -13,6 +13,7 @@ export function createSamsungCommandService({getConfig, discoverDevices, getSams
|
|
|
13
13
|
checkedAt: 0,
|
|
14
14
|
};
|
|
15
15
|
let powerStatePromise = null;
|
|
16
|
+
let powerStateGeneration = 0;
|
|
16
17
|
|
|
17
18
|
function getEnabledRemote() {
|
|
18
19
|
if (getConfig().enabled) {
|
|
@@ -22,10 +23,12 @@ export function createSamsungCommandService({getConfig, discoverDevices, getSams
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
function invalidatePowerState() {
|
|
26
|
+
powerStateGeneration += 1;
|
|
25
27
|
powerStateCache = {
|
|
26
28
|
value: 'unknown',
|
|
27
29
|
checkedAt: 0,
|
|
28
30
|
};
|
|
31
|
+
powerStatePromise = null;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
async function computePowerState() {
|
|
@@ -51,18 +54,24 @@ export function createSamsungCommandService({getConfig, discoverDevices, getSams
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
if (!powerStatePromise) {
|
|
54
|
-
|
|
57
|
+
const generation = powerStateGeneration;
|
|
58
|
+
const pendingPromise = (async () => {
|
|
55
59
|
try {
|
|
56
60
|
const value = await computePowerState();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
if (generation === powerStateGeneration) {
|
|
62
|
+
powerStateCache = {
|
|
63
|
+
value,
|
|
64
|
+
checkedAt: Date.now(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
61
67
|
return value;
|
|
62
68
|
} finally {
|
|
63
|
-
powerStatePromise
|
|
69
|
+
if (powerStatePromise === pendingPromise) {
|
|
70
|
+
powerStatePromise = null;
|
|
71
|
+
}
|
|
64
72
|
}
|
|
65
73
|
})();
|
|
74
|
+
powerStatePromise = pendingPromise;
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
return powerStatePromise;
|
|
@@ -126,6 +135,7 @@ export function createSamsungCommandService({getConfig, discoverDevices, getSams
|
|
|
126
135
|
return disabledRemote.turnOn();
|
|
127
136
|
}
|
|
128
137
|
try {
|
|
138
|
+
invalidatePowerState();
|
|
129
139
|
const samsungTv = await getSamsungTv();
|
|
130
140
|
await samsungTv.wakeTV();
|
|
131
141
|
invalidatePowerState();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SamsungTvRemote } from 'samsung-tv-remote';
|
|
2
|
-
import {
|
|
2
|
+
import { isTcpPortOpen, wakeHost } from '../../utils/network.js';
|
|
3
3
|
|
|
4
4
|
function isNoAwakeTvError(error) {
|
|
5
5
|
return String(error?.message || error || '').includes('aucune TV Samsung reveillee');
|
|
@@ -12,7 +12,8 @@ function isAmbiguousTvError(error) {
|
|
|
12
12
|
export function createSamsungTvGetter({
|
|
13
13
|
getConfig,
|
|
14
14
|
resolveDeviceConfig,
|
|
15
|
-
|
|
15
|
+
isTcpPortOpenFn = isTcpPortOpen,
|
|
16
|
+
wakeHostFn = wakeHost,
|
|
16
17
|
SamsungTvRemoteClass = SamsungTvRemote,
|
|
17
18
|
}) {
|
|
18
19
|
let tvPromise = null;
|
|
@@ -51,9 +52,10 @@ export function createSamsungTvGetter({
|
|
|
51
52
|
if (!device?.ip) {
|
|
52
53
|
return 'unknown';
|
|
53
54
|
}
|
|
54
|
-
const reachable = await
|
|
55
|
+
const reachable = await isTcpPortOpenFn(
|
|
55
56
|
device.ip,
|
|
56
|
-
|
|
57
|
+
config.port,
|
|
58
|
+
Math.min(2000, Math.max(500, Number(config.timeoutMs) || 1500)),
|
|
57
59
|
);
|
|
58
60
|
return reachable ? 'on' : 'off';
|
|
59
61
|
},
|
|
@@ -64,7 +66,7 @@ export function createSamsungTvGetter({
|
|
|
64
66
|
await remote.sendKeys(keys);
|
|
65
67
|
},
|
|
66
68
|
async wakeTV() {
|
|
67
|
-
await
|
|
69
|
+
await wakeHostFn(device.mac);
|
|
68
70
|
},
|
|
69
71
|
};
|
|
70
72
|
} catch (error) {
|
|
@@ -96,6 +96,13 @@ export function createSamsungDeviceConfigResolver({
|
|
|
96
96
|
const selected = pickSamsungDevice(candidates, selectionConfig);
|
|
97
97
|
|
|
98
98
|
if (!selected) {
|
|
99
|
+
if (config.host && config.mac && candidates.length === 0) {
|
|
100
|
+
return {
|
|
101
|
+
ip: config.host,
|
|
102
|
+
mac: config.mac,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
if (!candidates.length) {
|
|
100
107
|
throw new Error('aucune TV Samsung reveillee detectee sur le reseau');
|
|
101
108
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const UPDATE_EVENT_LOG_TABLE = 'update_manager_event_log';
|
|
2
|
+
|
|
3
|
+
function normalizeRecord(row) {
|
|
4
|
+
if (!row) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let lastResult = null;
|
|
9
|
+
try {
|
|
10
|
+
lastResult = JSON.parse(String(row.last_result_json || 'null'));
|
|
11
|
+
} catch (_error) {
|
|
12
|
+
lastResult = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
id: Number(row.id),
|
|
17
|
+
eventAt: Number(row.event_at),
|
|
18
|
+
type: String(row.event_type || ''),
|
|
19
|
+
enabled: Boolean(row.enabled),
|
|
20
|
+
lastKey: String(row.last_key || ''),
|
|
21
|
+
lastInstallCommand: String(row.last_install_command || ''),
|
|
22
|
+
lastResult,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createUpdateEventLogDao({getDatabase}) {
|
|
27
|
+
let bootstrapped = false;
|
|
28
|
+
|
|
29
|
+
function bootstrapUpdateEventLogTable() {
|
|
30
|
+
if (bootstrapped) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const db = getDatabase();
|
|
35
|
+
db.exec(`
|
|
36
|
+
CREATE TABLE IF NOT EXISTS ${UPDATE_EVENT_LOG_TABLE} (
|
|
37
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
38
|
+
event_at INTEGER NOT NULL,
|
|
39
|
+
event_type TEXT NOT NULL,
|
|
40
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
last_key TEXT NOT NULL DEFAULT '',
|
|
42
|
+
last_install_command TEXT NOT NULL DEFAULT '',
|
|
43
|
+
last_result_json TEXT NOT NULL DEFAULT 'null'
|
|
44
|
+
)
|
|
45
|
+
`);
|
|
46
|
+
|
|
47
|
+
bootstrapped = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createEvent(event = {}) {
|
|
51
|
+
bootstrapUpdateEventLogTable();
|
|
52
|
+
const db = getDatabase();
|
|
53
|
+
const payload = event?.payload || {};
|
|
54
|
+
const eventAtMs = Math.floor(new Date(event.at || Date.now()).getTime());
|
|
55
|
+
|
|
56
|
+
const result = db.prepare(`
|
|
57
|
+
INSERT INTO ${UPDATE_EVENT_LOG_TABLE} (
|
|
58
|
+
event_at,
|
|
59
|
+
event_type,
|
|
60
|
+
enabled,
|
|
61
|
+
last_key,
|
|
62
|
+
last_install_command,
|
|
63
|
+
last_result_json
|
|
64
|
+
)
|
|
65
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
66
|
+
`).run(
|
|
67
|
+
Number.isFinite(eventAtMs) ? eventAtMs : Date.now(),
|
|
68
|
+
String(event.type || 'event'),
|
|
69
|
+
payload?.enabled ? 1 : 0,
|
|
70
|
+
String(payload?.lastKey || ''),
|
|
71
|
+
String(payload?.lastInstallCommand || ''),
|
|
72
|
+
JSON.stringify(payload?.lastResult ?? null),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return Number(result.lastInsertRowid);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function listRecentEvents(limit = 20) {
|
|
79
|
+
bootstrapUpdateEventLogTable();
|
|
80
|
+
const db = getDatabase();
|
|
81
|
+
return db.prepare(`
|
|
82
|
+
SELECT *
|
|
83
|
+
FROM ${UPDATE_EVENT_LOG_TABLE}
|
|
84
|
+
ORDER BY event_at DESC, id DESC
|
|
85
|
+
LIMIT ?
|
|
86
|
+
`).all(Math.max(1, Number(limit) || 20)).map(normalizeRecord);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
createEvent,
|
|
91
|
+
listRecentEvents,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -2,6 +2,7 @@ import {createConfigDao} from './createConfigDao.js';
|
|
|
2
2
|
import {createEntryTokenDao} from './createEntryTokenDao.js';
|
|
3
3
|
import {createDatabaseProvider} from './createDatabaseProvider.js';
|
|
4
4
|
import {createRestartLogDao} from './createRestartLogDao.js';
|
|
5
|
+
import {createUpdateEventLogDao} from './createUpdateEventLogDao.js';
|
|
5
6
|
|
|
6
7
|
export function createPersistence(services) {
|
|
7
8
|
const getDatabase = createDatabaseProvider({
|
|
@@ -15,5 +16,6 @@ export function createPersistence(services) {
|
|
|
15
16
|
}),
|
|
16
17
|
entryTokenDao: createEntryTokenDao({getDatabase}),
|
|
17
18
|
restartLogDao: createRestartLogDao({getDatabase}),
|
|
19
|
+
updateEventLogDao: createUpdateEventLogDao({getDatabase}),
|
|
18
20
|
};
|
|
19
21
|
}
|
|
@@ -74,6 +74,13 @@ function buildRemoteCapabilities({applications, config}) {
|
|
|
74
74
|
available: true,
|
|
75
75
|
enabled: isConfigEnabled(config, 'keyboard'),
|
|
76
76
|
},
|
|
77
|
+
{
|
|
78
|
+
id: 'system',
|
|
79
|
+
labelKey: 'preferences.remote.system',
|
|
80
|
+
applicationIds: [],
|
|
81
|
+
available: true,
|
|
82
|
+
enabled: true,
|
|
83
|
+
},
|
|
77
84
|
{
|
|
78
85
|
id: 'vlc',
|
|
79
86
|
labelKey: 'preferences.remote.vlc',
|
|
@@ -75,6 +75,9 @@ export function parseCliArgs(args) {
|
|
|
75
75
|
.command('task-manager', false, () => {}, () => {
|
|
76
76
|
command = {name: 'task-manager', args: {}};
|
|
77
77
|
})
|
|
78
|
+
.command('update-events', false, () => {}, () => {
|
|
79
|
+
command = {name: 'update-events', args: {}};
|
|
80
|
+
})
|
|
78
81
|
.command('samsung-detect', false, () => {}, () => {
|
|
79
82
|
command = {name: 'samsung-detect', args: {}};
|
|
80
83
|
})
|
|
@@ -30,6 +30,7 @@ export async function runCliCmd(args) {
|
|
|
30
30
|
console.log(' remote-mouse service restart Redemarre le daemon/service local');
|
|
31
31
|
console.log(' remote-mouse tasks Affiche les informations du task manager');
|
|
32
32
|
console.log(' remote-mouse task-manager Alias de tasks');
|
|
33
|
+
console.log(' remote-mouse update-events Affiche les evenements persistés du update-manager');
|
|
33
34
|
console.log(' remote-mouse info --verbosity 2 Affiche les capacites serveur avec logs detailles');
|
|
34
35
|
console.log(' remote-mouse system-info Alias de info');
|
|
35
36
|
console.log(' remote-mouse tokens Liste les tokens en base');
|
|
@@ -14,6 +14,7 @@ export async function executeHelpCommand() {
|
|
|
14
14
|
'remote-mouse service <install|disable|uninstall|restart>',
|
|
15
15
|
'remote-mouse tasks',
|
|
16
16
|
'remote-mouse task-manager',
|
|
17
|
+
'remote-mouse update-events',
|
|
17
18
|
'remote-mouse samsung-detect',
|
|
18
19
|
'remote-mouse tokens',
|
|
19
20
|
'remote-mouse open-qr',
|
|
@@ -7,6 +7,7 @@ import {executeServiceCommand} from './commands/serviceCommand.js';
|
|
|
7
7
|
import {executeSystemConfigCommand} from './commands/systemConfigCommand.js';
|
|
8
8
|
import {executeTasksCommand} from './commands/tasksCommand.js';
|
|
9
9
|
import {executeTokensCommand} from './commands/tokensCommand.js';
|
|
10
|
+
import {executeUpdateEventsCommand} from './commands/updateEventsCommand.js';
|
|
10
11
|
import {formatCliCommand} from '../cli/parseCliArgs.js';
|
|
11
12
|
|
|
12
13
|
const commandHandlers = {
|
|
@@ -21,6 +22,7 @@ const commandHandlers = {
|
|
|
21
22
|
service: executeServiceCommand,
|
|
22
23
|
tasks: executeTasksCommand,
|
|
23
24
|
'task-manager': executeTasksCommand,
|
|
25
|
+
'update-events': executeUpdateEventsCommand,
|
|
24
26
|
'samsung-detect': executeSamsungDetectCommand,
|
|
25
27
|
tokens: executeTokensCommand,
|
|
26
28
|
};
|
package/server/utils/network.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import { createSocket } from 'node:dgram';
|
|
3
|
+
import { createConnection } from 'node:net';
|
|
2
4
|
import { execFileAsync } from './process.js';
|
|
3
5
|
|
|
4
6
|
export function getLanIp(forcedHost = '') {
|
|
@@ -43,3 +45,99 @@ export async function pingHost(host, timeoutMs = 2000) {
|
|
|
43
45
|
|
|
44
46
|
return Boolean(result.ok);
|
|
45
47
|
}
|
|
48
|
+
|
|
49
|
+
export function isTcpPortOpen(host, port, timeoutMs = 1500) {
|
|
50
|
+
const normalizedHost = String(host || '').trim();
|
|
51
|
+
const normalizedPort = Number(port);
|
|
52
|
+
if (!normalizedHost || !Number.isInteger(normalizedPort) || normalizedPort < 1 || normalizedPort > 65535) {
|
|
53
|
+
return Promise.resolve(false);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
const socket = createConnection({host: normalizedHost, port: normalizedPort});
|
|
58
|
+
let settled = false;
|
|
59
|
+
const finish = (isOpen) => {
|
|
60
|
+
if (settled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
settled = true;
|
|
64
|
+
socket.destroy();
|
|
65
|
+
resolve(isOpen);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
socket.setTimeout(Math.max(250, Number(timeoutMs) || 1500));
|
|
69
|
+
socket.once('connect', () => finish(true));
|
|
70
|
+
socket.once('timeout', () => finish(false));
|
|
71
|
+
socket.once('error', () => finish(false));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function wakeHost(mac, {
|
|
76
|
+
address = '255.255.255.255',
|
|
77
|
+
port = 9,
|
|
78
|
+
packetCount = 30,
|
|
79
|
+
intervalMs = 100,
|
|
80
|
+
} = {}) {
|
|
81
|
+
const normalizedMac = String(mac || '').replace(/[^a-f0-9]/gi, '');
|
|
82
|
+
if (!/^[a-f0-9]{12}$/i.test(normalizedMac) || /^0{12}$/.test(normalizedMac)) {
|
|
83
|
+
return Promise.reject(new Error('TV mac address is required'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const macBuffer = Buffer.from(normalizedMac, 'hex');
|
|
87
|
+
const packet = Buffer.alloc(6 + (16 * macBuffer.length), 0xff);
|
|
88
|
+
for (let offset = 6; offset < packet.length; offset += macBuffer.length) {
|
|
89
|
+
macBuffer.copy(packet, offset);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const socket = createSocket('udp4');
|
|
94
|
+
const totalPackets = Math.max(1, Number(packetCount) || 30);
|
|
95
|
+
const delayMs = Math.max(0, Number(intervalMs) || 0);
|
|
96
|
+
let sentPackets = 0;
|
|
97
|
+
let timer = null;
|
|
98
|
+
let settled = false;
|
|
99
|
+
|
|
100
|
+
const finish = (error) => {
|
|
101
|
+
if (settled) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
settled = true;
|
|
105
|
+
if (timer) {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
socket.close();
|
|
110
|
+
} catch (_error) {}
|
|
111
|
+
if (error) {
|
|
112
|
+
reject(error);
|
|
113
|
+
} else {
|
|
114
|
+
resolve();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const sendNext = () => {
|
|
119
|
+
socket.send(packet, Number(port) || 9, address, (error) => {
|
|
120
|
+
if (error) {
|
|
121
|
+
finish(error);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
sentPackets += 1;
|
|
125
|
+
if (sentPackets >= totalPackets) {
|
|
126
|
+
finish();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
timer = setTimeout(sendNext, delayMs);
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
socket.once('error', finish);
|
|
134
|
+
socket.bind(() => {
|
|
135
|
+
try {
|
|
136
|
+
socket.setBroadcast(true);
|
|
137
|
+
sendNext();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
finish(error);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|