livedesk 0.1.418 → 0.1.419
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/hub/src/filesystem/transfer-jobs.js +90 -5
- package/hub/src/server.js +10 -7
- package/package.json +1 -1
- package/web/dist/assets/{index-C2aVLw-n.css → index-Bt3n0Lrm.css} +1 -1
- package/web/dist/assets/index-CRb9c7S3.js +25 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-Dl8JUwo9.js +0 -25
|
@@ -4,6 +4,7 @@ import { setImmediate as yieldToEventLoop } from 'node:timers/promises';
|
|
|
4
4
|
|
|
5
5
|
const CHUNK_BYTES = 512 * 1024;
|
|
6
6
|
const JOB_RETENTION_MS = 10 * 60 * 1000;
|
|
7
|
+
const COMMAND_RESULT_TIMEOUT_MS = 30 * 1000;
|
|
7
8
|
|
|
8
9
|
function snapshot(job) {
|
|
9
10
|
return {
|
|
@@ -28,13 +29,15 @@ function markUpdated(job) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export class HubTransferJobs {
|
|
31
|
-
constructor({ filesystem, remoteHub, maxConcurrent = 2 } = {}) {
|
|
32
|
+
constructor({ filesystem, remoteHub, maxConcurrent = 2, commandResultTimeoutMs = COMMAND_RESULT_TIMEOUT_MS } = {}) {
|
|
32
33
|
this.filesystem = filesystem;
|
|
33
34
|
this.remoteHub = remoteHub;
|
|
34
35
|
this.maxConcurrent = Math.max(1, Number(maxConcurrent) || 2);
|
|
36
|
+
this.commandResultTimeoutMs = Math.max(1_000, Number(commandResultTimeoutMs) || COMMAND_RESULT_TIMEOUT_MS);
|
|
35
37
|
this.jobs = new Map();
|
|
36
38
|
this.queue = [];
|
|
37
39
|
this.running = 0;
|
|
40
|
+
this.pendingCommands = new Map();
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
create({ itemIds = [], deviceIds = [], remoteDirectory = '', files = null, onComplete = null } = {}) {
|
|
@@ -90,6 +93,7 @@ export class HubTransferJobs {
|
|
|
90
93
|
job.state = 'cancelled';
|
|
91
94
|
job.error = 'transfer-cancelled';
|
|
92
95
|
if (job.currentStream) job.currentStream.destroy();
|
|
96
|
+
this.finishPendingCommands(job.jobId, 'transfer-cancelled');
|
|
93
97
|
markUpdated(job);
|
|
94
98
|
return snapshot(job);
|
|
95
99
|
}
|
|
@@ -181,10 +185,13 @@ export class HubTransferJobs {
|
|
|
181
185
|
}
|
|
182
186
|
|
|
183
187
|
async sendChunk(job, file, offset, buffer, final, targets) {
|
|
184
|
-
|
|
185
|
-
if (job.cancelled) return;
|
|
188
|
+
const outcomes = await Promise.all(targets.map(async deviceId => {
|
|
189
|
+
if (job.cancelled) return { deviceId, ok: false, error: 'transfer-cancelled' };
|
|
190
|
+
const commandId = crypto.randomUUID();
|
|
191
|
+
const pending = this.waitForCommandResult(job.jobId, deviceId, commandId);
|
|
186
192
|
const result = this.remoteHub.sendCommand(deviceId, {
|
|
187
193
|
command: 'file.transfer.chunk',
|
|
194
|
+
commandId,
|
|
188
195
|
payload: {
|
|
189
196
|
transferId: job.jobId,
|
|
190
197
|
remoteDirectory: job.remoteDirectory,
|
|
@@ -199,9 +206,87 @@ export class HubTransferJobs {
|
|
|
199
206
|
}
|
|
200
207
|
});
|
|
201
208
|
if (!result?.ok) {
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
this.settlePendingCommand(commandId, {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: String(result?.error || 'device-not-connected')
|
|
212
|
+
});
|
|
204
213
|
}
|
|
214
|
+
return { deviceId, ...await pending };
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
for (const outcome of outcomes) {
|
|
218
|
+
if (outcome.ok || outcome.error === 'transfer-cancelled') continue;
|
|
219
|
+
job.failedTargets.add(outcome.deviceId);
|
|
220
|
+
job.targetErrors.set(outcome.deviceId, outcome.error || 'transfer-failed');
|
|
221
|
+
}
|
|
222
|
+
if (!job.cancelled && outcomes.length > 0 && outcomes.every(outcome => !outcome.ok)) {
|
|
223
|
+
throw new Error('all-targets-failed');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
waitForCommandResult(jobId, deviceId, commandId) {
|
|
228
|
+
return new Promise(resolve => {
|
|
229
|
+
const timer = setTimeout(() => {
|
|
230
|
+
this.settlePendingCommand(commandId, {
|
|
231
|
+
ok: false,
|
|
232
|
+
error: 'file-transfer-ack-timeout'
|
|
233
|
+
});
|
|
234
|
+
}, this.commandResultTimeoutMs);
|
|
235
|
+
timer.unref?.();
|
|
236
|
+
this.pendingCommands.set(commandId, {
|
|
237
|
+
jobId,
|
|
238
|
+
deviceId,
|
|
239
|
+
timer,
|
|
240
|
+
resolve
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
settlePendingCommand(commandId, outcome) {
|
|
246
|
+
const pending = this.pendingCommands.get(String(commandId || ''));
|
|
247
|
+
if (!pending) return false;
|
|
248
|
+
this.pendingCommands.delete(commandId);
|
|
249
|
+
clearTimeout(pending.timer);
|
|
250
|
+
pending.resolve(outcome);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
finishPendingCommands(jobId, error) {
|
|
255
|
+
for (const [commandId, pending] of this.pendingCommands) {
|
|
256
|
+
if (pending.jobId !== jobId) continue;
|
|
257
|
+
this.settlePendingCommand(commandId, { ok: false, error });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
handleRemoteEvent(type, event) {
|
|
262
|
+
if (type === 'RemoteCommandResult') {
|
|
263
|
+
const commandId = String(event?.commandId || '');
|
|
264
|
+
const pending = this.pendingCommands.get(commandId);
|
|
265
|
+
if (!pending) return;
|
|
266
|
+
const eventDeviceId = String(event?.deviceId || event?.device?.deviceId || '');
|
|
267
|
+
if (eventDeviceId && eventDeviceId !== pending.deviceId) return;
|
|
268
|
+
const result = event?.result;
|
|
269
|
+
const error = String(
|
|
270
|
+
event?.error
|
|
271
|
+
|| (result?.ok === false || result?.status === 'failed' || result?.status === 'rejected'
|
|
272
|
+
? result?.error || 'file-transfer-rejected'
|
|
273
|
+
: '')
|
|
274
|
+
);
|
|
275
|
+
this.settlePendingCommand(commandId, error
|
|
276
|
+
? { ok: false, error }
|
|
277
|
+
: { ok: true, result });
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (type !== 'RemoteDeviceDisconnected') return;
|
|
282
|
+
const deviceId = String(event?.deviceId || event?.device?.deviceId || '');
|
|
283
|
+
if (!deviceId) return;
|
|
284
|
+
for (const [commandId, pending] of this.pendingCommands) {
|
|
285
|
+
if (pending.deviceId !== deviceId) continue;
|
|
286
|
+
this.settlePendingCommand(commandId, {
|
|
287
|
+
ok: false,
|
|
288
|
+
error: 'device-disconnected'
|
|
289
|
+
});
|
|
205
290
|
}
|
|
206
291
|
}
|
|
207
292
|
|
package/hub/src/server.js
CHANGED
|
@@ -128,9 +128,10 @@ let verifiedLicense = {
|
|
|
128
128
|
verifiedAt: 0
|
|
129
129
|
};
|
|
130
130
|
let frameClientSeq = 0;
|
|
131
|
-
let inputClientSeq = 0;
|
|
132
|
-
let audioClientSeq = 0;
|
|
131
|
+
let inputClientSeq = 0;
|
|
132
|
+
let audioClientSeq = 0;
|
|
133
133
|
let liveDeskUpdateManager = null;
|
|
134
|
+
let hubTransferJobs = null;
|
|
134
135
|
const HUB_HOST_TARGET_LEASE_MS = Math.max(5000, readPositiveIntegerEnv('LIVEDESK_HOST_TARGET_LEASE_MS', 60_000));
|
|
135
136
|
const HUB_HOST_TARGET_RENEW_MS = Math.max(1000, Math.min(
|
|
136
137
|
readPositiveIntegerEnv('LIVEDESK_HOST_TARGET_RENEW_MS', 20_000),
|
|
@@ -168,6 +169,7 @@ function readPositiveIntegerEnv(name, fallback) {
|
|
|
168
169
|
|
|
169
170
|
function handleRemoteHubEvent(type, event) {
|
|
170
171
|
liveDeskUpdateManager?.handleRemoteEvent(type, event);
|
|
172
|
+
hubTransferJobs?.handleRemoteEvent(type, event);
|
|
171
173
|
if (type === 'RemoteInputApplied') {
|
|
172
174
|
for (const ws of inputClients) {
|
|
173
175
|
sendJson(ws, {
|
|
@@ -1208,11 +1210,12 @@ async function synchronizeAgentEnablement() {
|
|
|
1208
1210
|
}
|
|
1209
1211
|
|
|
1210
1212
|
const hubFilesystem = createHubFilesystem();
|
|
1211
|
-
|
|
1212
|
-
filesystem: hubFilesystem,
|
|
1213
|
-
remoteHub,
|
|
1214
|
-
maxConcurrent: Number(process.env.LIVEDESK_MAX_FILE_JOBS || 2)
|
|
1215
|
-
|
|
1213
|
+
hubTransferJobs = createHubTransferJobs({
|
|
1214
|
+
filesystem: hubFilesystem,
|
|
1215
|
+
remoteHub,
|
|
1216
|
+
maxConcurrent: Number(process.env.LIVEDESK_MAX_FILE_JOBS || 2),
|
|
1217
|
+
commandResultTimeoutMs: readPositiveIntegerEnv('LIVEDESK_FILE_CHUNK_ACK_TIMEOUT_MS', 30_000)
|
|
1218
|
+
});
|
|
1216
1219
|
const hubSharedFolders = createHubSharedFolders({
|
|
1217
1220
|
filesystem: hubFilesystem,
|
|
1218
1221
|
transferJobs: hubTransferJobs,
|