@velor/remote-mouse 6.6.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.
@@ -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': '光标预览',
@@ -1,5 +1,6 @@
1
1
  export const LOCAL_REMOTE_DEFINITIONS = [
2
2
  {id: 'keyboard', labelKey: 'preferences.remote.keyboard'},
3
+ {id: 'system', labelKey: 'preferences.remote.system'},
3
4
  ];
4
5
 
5
6
  export function createPreferencesState() {
@@ -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
- const text = typeof event.data === 'string' ? event.data : '';
244
- if (!text) {
255
+ liveTextInput.addEventListener('input', (event) => {
256
+ if (event.isComposing) {
245
257
  return;
246
258
  }
247
259
 
248
- emitWithTimestamp(socket, REMOTE_EVENT_KEYBOARD_TEXT, { text });
249
- setPreviewActive(true);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velor/remote-mouse",
3
- "version": "6.6.0",
3
+ "version": "6.6.1",
4
4
  "type": "module",
5
5
  "description": "Remote mouse and keyboard with mobile client",
6
6
  "homepage": "https://github.com/glennerichall/node-mouse#readme",
@@ -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',
@@ -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
- if (!active) {
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
- powerStatePromise = (async () => {
57
+ const generation = powerStateGeneration;
58
+ const pendingPromise = (async () => {
55
59
  try {
56
60
  const value = await computePowerState();
57
- powerStateCache = {
58
- value,
59
- checkedAt: Date.now(),
60
- };
61
+ if (generation === powerStateGeneration) {
62
+ powerStateCache = {
63
+ value,
64
+ checkedAt: Date.now(),
65
+ };
66
+ }
61
67
  return value;
62
68
  } finally {
63
- powerStatePromise = null;
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 { pingHost } from '../../utils/network.js';
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
- pingHostFn = pingHost,
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 pingHostFn(
55
+ const reachable = await isTcpPortOpenFn(
55
56
  device.ip,
56
- Math.max(500, Number(config.timeoutMs) || 2000),
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 remote.wakeTV();
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
  }
@@ -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',
@@ -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
+ }