livedesk 0.1.441 → 0.1.442
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/bin/livedesk.js +40 -13
- package/bootstrap/client-process-identity.js +23 -2
- package/client/bin/livedesk-client.js +6 -0
- package/client/package.json +5 -5
- package/client/src/runtime/agent-process-lifecycle.js +100 -8
- package/hub/package.json +1 -1
- package/hub/src/remote-hub.js +6 -0
- package/hub/src/server.js +3 -0
- package/hub/src/transport/udp-rendezvous.js +402 -78
- package/package.json +6 -6
- package/web/dist/assets/index-D3VhnrP6.js +25 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-BjLnf_H4.js +0 -25
|
@@ -1,78 +1,402 @@
|
|
|
1
|
-
import dgram from 'node:dgram';
|
|
2
|
-
import {
|
|
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
|
-
|
|
1
|
+
import dgram from 'node:dgram';
|
|
2
|
+
import {
|
|
3
|
+
decodeRendezvousMessage,
|
|
4
|
+
encodeRendezvousMessage,
|
|
5
|
+
UDP_MAX_DATAGRAM_BYTES,
|
|
6
|
+
UDP_P2P_PROTOCOL
|
|
7
|
+
} from './udp-protocol.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_LIMITS = Object.freeze({
|
|
10
|
+
roomTtlMs: 60_000,
|
|
11
|
+
sourceTtlMs: 120_000,
|
|
12
|
+
sweepIntervalMs: 10_000,
|
|
13
|
+
statusIntervalMs: 60_000,
|
|
14
|
+
maxRooms: 4_096,
|
|
15
|
+
maxSources: 8_192,
|
|
16
|
+
maxRoomsPerSource: 128,
|
|
17
|
+
perSourceRate: 20,
|
|
18
|
+
perSourceBurst: 40,
|
|
19
|
+
globalRate: 1_000,
|
|
20
|
+
globalBurst: 2_000
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function safeText(value, max = 160) {
|
|
24
|
+
return String(value || '').replace(/[\r\n\t]/g, ' ').trim().slice(0, max);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizePort(value, fallback) {
|
|
28
|
+
const port = Number(value);
|
|
29
|
+
return Number.isInteger(port) && port >= 0 && port <= 65535 ? port : fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeInteger(value, fallback, min = 1, max = Number.MAX_SAFE_INTEGER) {
|
|
33
|
+
const number = Number(value);
|
|
34
|
+
return Number.isInteger(number) && number >= min && number <= max ? number : fallback;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeRate(value, fallback) {
|
|
38
|
+
const number = Number(value);
|
|
39
|
+
return Number.isFinite(number) && number > 0 ? number : fallback;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createTokenBucket(capacity, now) {
|
|
43
|
+
return { tokens: capacity, updatedAt: now };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function consumeToken(bucket, rate, capacity, now) {
|
|
47
|
+
const elapsedMs = Math.max(0, now - bucket.updatedAt);
|
|
48
|
+
bucket.tokens = Math.min(capacity, bucket.tokens + (elapsedMs / 1_000) * rate);
|
|
49
|
+
bucket.updatedAt = Math.max(bucket.updatedAt, now);
|
|
50
|
+
if (bucket.tokens < 1) return false;
|
|
51
|
+
bucket.tokens -= 1;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function createUdpRendezvousServer({
|
|
56
|
+
host = '0.0.0.0',
|
|
57
|
+
port = 5199,
|
|
58
|
+
log = () => {},
|
|
59
|
+
version = 'dev',
|
|
60
|
+
now = () => Date.now(),
|
|
61
|
+
roomTtlMs = DEFAULT_LIMITS.roomTtlMs,
|
|
62
|
+
sourceTtlMs = DEFAULT_LIMITS.sourceTtlMs,
|
|
63
|
+
sweepIntervalMs = DEFAULT_LIMITS.sweepIntervalMs,
|
|
64
|
+
statusIntervalMs = DEFAULT_LIMITS.statusIntervalMs,
|
|
65
|
+
maxRooms = DEFAULT_LIMITS.maxRooms,
|
|
66
|
+
maxSources = DEFAULT_LIMITS.maxSources,
|
|
67
|
+
maxRoomsPerSource = DEFAULT_LIMITS.maxRoomsPerSource,
|
|
68
|
+
perSourceRate = DEFAULT_LIMITS.perSourceRate,
|
|
69
|
+
perSourceBurst = DEFAULT_LIMITS.perSourceBurst,
|
|
70
|
+
globalRate = DEFAULT_LIMITS.globalRate,
|
|
71
|
+
globalBurst = DEFAULT_LIMITS.globalBurst
|
|
72
|
+
} = {}) {
|
|
73
|
+
const socket = dgram.createSocket('udp4');
|
|
74
|
+
const rooms = new Map();
|
|
75
|
+
const unpairedRooms = new Map();
|
|
76
|
+
const sources = new Map();
|
|
77
|
+
const evictableSources = new Map();
|
|
78
|
+
const limits = Object.freeze({
|
|
79
|
+
roomTtlMs: normalizeInteger(roomTtlMs, DEFAULT_LIMITS.roomTtlMs),
|
|
80
|
+
sourceTtlMs: normalizeInteger(sourceTtlMs, DEFAULT_LIMITS.sourceTtlMs),
|
|
81
|
+
sweepIntervalMs: normalizeInteger(sweepIntervalMs, DEFAULT_LIMITS.sweepIntervalMs),
|
|
82
|
+
statusIntervalMs: normalizeInteger(statusIntervalMs, DEFAULT_LIMITS.statusIntervalMs, 0),
|
|
83
|
+
maxRooms: normalizeInteger(maxRooms, DEFAULT_LIMITS.maxRooms),
|
|
84
|
+
maxSources: normalizeInteger(maxSources, DEFAULT_LIMITS.maxSources),
|
|
85
|
+
maxRoomsPerSource: normalizeInteger(maxRoomsPerSource, DEFAULT_LIMITS.maxRoomsPerSource),
|
|
86
|
+
perSourceRate: normalizeRate(perSourceRate, DEFAULT_LIMITS.perSourceRate),
|
|
87
|
+
perSourceBurst: normalizeRate(perSourceBurst, DEFAULT_LIMITS.perSourceBurst),
|
|
88
|
+
globalRate: normalizeRate(globalRate, DEFAULT_LIMITS.globalRate),
|
|
89
|
+
globalBurst: normalizeRate(globalBurst, DEFAULT_LIMITS.globalBurst)
|
|
90
|
+
});
|
|
91
|
+
const counters = {
|
|
92
|
+
receivedDatagrams: 0,
|
|
93
|
+
receivedBytes: 0,
|
|
94
|
+
acceptedRegistrations: 0,
|
|
95
|
+
peerNotifications: 0,
|
|
96
|
+
sentBytes: 0,
|
|
97
|
+
sendErrors: 0,
|
|
98
|
+
invalidDatagrams: 0,
|
|
99
|
+
oversizeDatagrams: 0,
|
|
100
|
+
globalRateDrops: 0,
|
|
101
|
+
sourceRateDrops: 0,
|
|
102
|
+
sourceCapacityDrops: 0,
|
|
103
|
+
sourceRoomLimitDrops: 0,
|
|
104
|
+
roomCapacityDrops: 0,
|
|
105
|
+
tokenMismatchDrops: 0,
|
|
106
|
+
expiredRooms: 0,
|
|
107
|
+
evictedRooms: 0,
|
|
108
|
+
expiredSources: 0,
|
|
109
|
+
evictedSources: 0,
|
|
110
|
+
sweeps: 0,
|
|
111
|
+
socketErrors: 0
|
|
112
|
+
};
|
|
113
|
+
const globalBucket = createTokenBucket(limits.globalBurst, now());
|
|
114
|
+
const serviceVersion = safeText(version, 64) || 'dev';
|
|
115
|
+
let started = false;
|
|
116
|
+
let closing = false;
|
|
117
|
+
let startedAt = 0;
|
|
118
|
+
let boundPort = normalizePort(port, 5199);
|
|
119
|
+
let sweepTimer = null;
|
|
120
|
+
let statusTimer = null;
|
|
121
|
+
let lastError = '';
|
|
122
|
+
|
|
123
|
+
function sourceKey(rinfo) {
|
|
124
|
+
return safeText(rinfo?.address, 128);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function deleteRoom(roomId, reason = '') {
|
|
128
|
+
const room = rooms.get(roomId);
|
|
129
|
+
if (!room) return false;
|
|
130
|
+
rooms.delete(roomId);
|
|
131
|
+
unpairedRooms.delete(roomId);
|
|
132
|
+
const owner = sources.get(room.ownerSource);
|
|
133
|
+
if (owner) {
|
|
134
|
+
owner.activeRooms = Math.max(0, owner.activeRooms - 1);
|
|
135
|
+
if (owner.activeRooms === 0) {
|
|
136
|
+
evictableSources.delete(owner.key);
|
|
137
|
+
evictableSources.set(owner.key, owner);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (reason === 'expired') counters.expiredRooms += 1;
|
|
141
|
+
if (reason === 'capacity') counters.evictedRooms += 1;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function sweep(at = now()) {
|
|
146
|
+
for (const [roomId, room] of rooms) {
|
|
147
|
+
if (at - room.updatedAt >= limits.roomTtlMs) deleteRoom(roomId, 'expired');
|
|
148
|
+
}
|
|
149
|
+
for (const [key, source] of sources) {
|
|
150
|
+
if (source.activeRooms === 0 && at - source.lastSeenAt >= limits.sourceTtlMs) {
|
|
151
|
+
sources.delete(key);
|
|
152
|
+
evictableSources.delete(key);
|
|
153
|
+
counters.expiredSources += 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
counters.sweeps += 1;
|
|
157
|
+
return getStatus();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getOrCreateSource(rinfo, at) {
|
|
161
|
+
const key = sourceKey(rinfo);
|
|
162
|
+
if (!key) return null;
|
|
163
|
+
let source = sources.get(key);
|
|
164
|
+
if (!source) {
|
|
165
|
+
if (sources.size >= limits.maxSources) {
|
|
166
|
+
const oldestEvictableSource = evictableSources.keys().next().value;
|
|
167
|
+
if (!oldestEvictableSource) {
|
|
168
|
+
counters.sourceCapacityDrops += 1;
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
evictableSources.delete(oldestEvictableSource);
|
|
172
|
+
sources.delete(oldestEvictableSource);
|
|
173
|
+
counters.evictedSources += 1;
|
|
174
|
+
}
|
|
175
|
+
source = {
|
|
176
|
+
key,
|
|
177
|
+
bucket: createTokenBucket(limits.perSourceBurst, at),
|
|
178
|
+
lastSeenAt: at,
|
|
179
|
+
activeRooms: 0
|
|
180
|
+
};
|
|
181
|
+
sources.set(key, source);
|
|
182
|
+
evictableSources.set(key, source);
|
|
183
|
+
}
|
|
184
|
+
source.lastSeenAt = at;
|
|
185
|
+
if (source.activeRooms === 0) {
|
|
186
|
+
evictableSources.delete(key);
|
|
187
|
+
evictableSources.set(key, source);
|
|
188
|
+
}
|
|
189
|
+
if (!consumeToken(source.bucket, limits.perSourceRate, limits.perSourceBurst, at)) {
|
|
190
|
+
counters.sourceRateDrops += 1;
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return source;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function send(message, address, targetPort) {
|
|
197
|
+
let payload;
|
|
198
|
+
try {
|
|
199
|
+
payload = encodeRendezvousMessage(message);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
counters.sendErrors += 1;
|
|
202
|
+
lastError = safeText(error?.message || error, 240);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
counters.peerNotifications += 1;
|
|
206
|
+
counters.sentBytes += payload.length;
|
|
207
|
+
socket.send(payload, targetPort, address, error => {
|
|
208
|
+
if (!error) return;
|
|
209
|
+
counters.sendErrors += 1;
|
|
210
|
+
lastError = safeText(error?.message || error, 240);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function createRoom(roomId, token, source, at) {
|
|
215
|
+
if (source.activeRooms >= limits.maxRoomsPerSource) {
|
|
216
|
+
counters.sourceRoomLimitDrops += 1;
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
if (rooms.size >= limits.maxRooms) {
|
|
220
|
+
const oldestUnpairedRoomId = unpairedRooms.keys().next().value;
|
|
221
|
+
if (oldestUnpairedRoomId) {
|
|
222
|
+
deleteRoom(oldestUnpairedRoomId, 'capacity');
|
|
223
|
+
} else {
|
|
224
|
+
counters.roomCapacityDrops += 1;
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const room = {
|
|
229
|
+
token,
|
|
230
|
+
ownerSource: source.key,
|
|
231
|
+
peers: new Map(),
|
|
232
|
+
createdAt: at,
|
|
233
|
+
updatedAt: at
|
|
234
|
+
};
|
|
235
|
+
rooms.set(roomId, room);
|
|
236
|
+
unpairedRooms.set(roomId, room);
|
|
237
|
+
source.activeRooms += 1;
|
|
238
|
+
evictableSources.delete(source.key);
|
|
239
|
+
return room;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function onMessage(payload, rinfo) {
|
|
243
|
+
const at = now();
|
|
244
|
+
counters.receivedDatagrams += 1;
|
|
245
|
+
counters.receivedBytes += Buffer.isBuffer(payload) ? payload.length : 0;
|
|
246
|
+
if (!Buffer.isBuffer(payload)) {
|
|
247
|
+
counters.invalidDatagrams += 1;
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (payload.length > UDP_MAX_DATAGRAM_BYTES) {
|
|
251
|
+
counters.oversizeDatagrams += 1;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (!consumeToken(globalBucket, limits.globalRate, limits.globalBurst, at)) {
|
|
255
|
+
counters.globalRateDrops += 1;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const source = getOrCreateSource(rinfo, at);
|
|
259
|
+
if (!source) return;
|
|
260
|
+
const message = decodeRendezvousMessage(payload);
|
|
261
|
+
if (message?.type !== 'rendezvous.register') {
|
|
262
|
+
counters.invalidDatagrams += 1;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const roomId = safeText(message.roomId, 160);
|
|
266
|
+
const role = safeText(message.role, 16).toLowerCase();
|
|
267
|
+
const token = safeText(message.token, 256);
|
|
268
|
+
if (!roomId || !token || !['hub', 'client'].includes(role)) {
|
|
269
|
+
counters.invalidDatagrams += 1;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
let room = rooms.get(roomId);
|
|
273
|
+
if (room && at - room.updatedAt >= limits.roomTtlMs) {
|
|
274
|
+
deleteRoom(roomId, 'expired');
|
|
275
|
+
room = null;
|
|
276
|
+
}
|
|
277
|
+
if (room && room.token !== token) {
|
|
278
|
+
counters.tokenMismatchDrops += 1;
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (!room) {
|
|
282
|
+
room = createRoom(roomId, token, source, at);
|
|
283
|
+
if (!room) return;
|
|
284
|
+
}
|
|
285
|
+
room.updatedAt = at;
|
|
286
|
+
room.peers.set(role, { address: rinfo.address, port: rinfo.port });
|
|
287
|
+
counters.acceptedRegistrations += 1;
|
|
288
|
+
if (room.peers.size < 2) {
|
|
289
|
+
unpairedRooms.delete(roomId);
|
|
290
|
+
unpairedRooms.set(roomId, room);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
unpairedRooms.delete(roomId);
|
|
294
|
+
const hub = room.peers.get('hub');
|
|
295
|
+
const client = room.peers.get('client');
|
|
296
|
+
if (!hub || !client) return;
|
|
297
|
+
send({
|
|
298
|
+
type: 'rendezvous.peer',
|
|
299
|
+
roomId,
|
|
300
|
+
role: 'client',
|
|
301
|
+
host: client.address,
|
|
302
|
+
port: client.port
|
|
303
|
+
}, hub.address, hub.port);
|
|
304
|
+
send({
|
|
305
|
+
type: 'rendezvous.peer',
|
|
306
|
+
roomId,
|
|
307
|
+
role: 'hub',
|
|
308
|
+
host: hub.address,
|
|
309
|
+
port: hub.port
|
|
310
|
+
}, client.address, client.port);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
socket.on('message', onMessage);
|
|
314
|
+
socket.on('error', error => {
|
|
315
|
+
counters.socketErrors += 1;
|
|
316
|
+
lastError = safeText(error?.message || error, 240);
|
|
317
|
+
log(`UDP rendezvous error: ${lastError}`);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
function getStatus() {
|
|
321
|
+
const current = now();
|
|
322
|
+
return {
|
|
323
|
+
service: 'livedesk-udp-rendezvous',
|
|
324
|
+
version: serviceVersion,
|
|
325
|
+
protocol: UDP_P2P_PROTOCOL,
|
|
326
|
+
healthy: started && !closing,
|
|
327
|
+
started,
|
|
328
|
+
host,
|
|
329
|
+
port: boundPort,
|
|
330
|
+
uptimeMs: startedAt ? Math.max(0, current - startedAt) : 0,
|
|
331
|
+
rooms: rooms.size,
|
|
332
|
+
pairedRooms: rooms.size - unpairedRooms.size,
|
|
333
|
+
unpairedRooms: unpairedRooms.size,
|
|
334
|
+
sources: sources.size,
|
|
335
|
+
evictableSources: evictableSources.size,
|
|
336
|
+
limits: { ...limits },
|
|
337
|
+
counters: { ...counters },
|
|
338
|
+
lastError
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function logStatus(reason) {
|
|
343
|
+
log(`UDP rendezvous status ${JSON.stringify({ reason, ...getStatus() })}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function startTimers() {
|
|
347
|
+
sweepTimer = setInterval(() => sweep(), limits.sweepIntervalMs);
|
|
348
|
+
sweepTimer.unref?.();
|
|
349
|
+
if (limits.statusIntervalMs > 0) {
|
|
350
|
+
statusTimer = setInterval(() => logStatus('periodic'), limits.statusIntervalMs);
|
|
351
|
+
statusTimer.unref?.();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function stopTimers() {
|
|
356
|
+
if (sweepTimer) clearInterval(sweepTimer);
|
|
357
|
+
if (statusTimer) clearInterval(statusTimer);
|
|
358
|
+
sweepTimer = null;
|
|
359
|
+
statusTimer = null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function start() {
|
|
363
|
+
if (started) return { host, port: boundPort };
|
|
364
|
+
await new Promise((resolve, reject) => {
|
|
365
|
+
const onError = error => { socket.off('listening', onListening); reject(error); };
|
|
366
|
+
const onListening = () => { socket.off('error', onError); resolve(); };
|
|
367
|
+
socket.once('error', onError);
|
|
368
|
+
socket.once('listening', onListening);
|
|
369
|
+
socket.bind(boundPort, host);
|
|
370
|
+
});
|
|
371
|
+
started = true;
|
|
372
|
+
closing = false;
|
|
373
|
+
startedAt = now();
|
|
374
|
+
boundPort = socket.address().port;
|
|
375
|
+
startTimers();
|
|
376
|
+
log(`UDP rendezvous listening on udp://${host}:${boundPort} version=${serviceVersion}`);
|
|
377
|
+
log(`UDP rendezvous limits ${JSON.stringify(limits)}`);
|
|
378
|
+
return { host, port: boundPort };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function close() {
|
|
382
|
+
closing = true;
|
|
383
|
+
stopTimers();
|
|
384
|
+
rooms.clear();
|
|
385
|
+
unpairedRooms.clear();
|
|
386
|
+
sources.clear();
|
|
387
|
+
evictableSources.clear();
|
|
388
|
+
if (started) {
|
|
389
|
+
await new Promise(resolve => socket.close(() => resolve()));
|
|
390
|
+
started = false;
|
|
391
|
+
}
|
|
392
|
+
closing = false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
start,
|
|
397
|
+
close,
|
|
398
|
+
getStatus,
|
|
399
|
+
sweepNow: () => sweep(),
|
|
400
|
+
logStatus: (reason = 'requested') => logStatus(safeText(reason, 48) || 'requested')
|
|
401
|
+
};
|
|
402
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "livedesk",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"livedeskClientVersion": "0.1.
|
|
3
|
+
"version": "0.1.442",
|
|
4
|
+
"livedeskClientVersion": "0.1.197",
|
|
5
5
|
"buildFlavor": "production",
|
|
6
6
|
"description": "LiveDesk Hub and client launcher",
|
|
7
7
|
"type": "module",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"ws": "^8.18.3"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
53
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
54
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
55
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
56
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
53
|
+
"@livedesk/fast-linux-x64": "0.1.404",
|
|
54
|
+
"@livedesk/fast-osx-arm64": "0.1.404",
|
|
55
|
+
"@livedesk/fast-osx-x64": "0.1.404",
|
|
56
|
+
"@livedesk/fast-win-x64": "0.1.404"
|
|
57
57
|
},
|
|
58
58
|
"publishConfig": {
|
|
59
59
|
"access": "public"
|