@livedesk/client 0.1.90 → 0.1.92
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-client-node.js +88 -0
- package/fast/linux-x64/livedesk-client-fast.dll +0 -0
- package/fast/linux-x64/livedesk-client-fast.pdb +0 -0
- package/fast/osx-arm64/livedesk-client-fast.dll +0 -0
- package/fast/osx-arm64/livedesk-client-fast.pdb +0 -0
- package/fast/osx-x64/livedesk-client-fast.dll +0 -0
- package/fast/osx-x64/livedesk-client-fast.pdb +0 -0
- package/fast/win-x64/livedesk-client-fast.dll +0 -0
- package/fast/win-x64/livedesk-client-fast.pdb +0 -0
- package/package.json +1 -1
|
@@ -466,6 +466,76 @@ async function handleFileTransferCommand(options, payload = {}) {
|
|
|
466
466
|
};
|
|
467
467
|
}
|
|
468
468
|
|
|
469
|
+
async function handleFileTransferChunkCommand(options, payload = {}) {
|
|
470
|
+
const baseDir = normalizeDirectoryPath(payload.remoteDirectory || options.filesDir);
|
|
471
|
+
const name = sanitizePathSegment(payload.name, 'file');
|
|
472
|
+
const relativePath = sanitizeRelativeFilePath(payload.relativePath || name, name);
|
|
473
|
+
const targetPath = path.resolve(baseDir, relativePath);
|
|
474
|
+
const relativeFromBase = path.relative(baseDir, targetPath);
|
|
475
|
+
if (relativeFromBase.startsWith('..') || path.isAbsolute(relativeFromBase)) {
|
|
476
|
+
throw new Error(`Unsafe file path: ${relativePath}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const transferId = String(payload.transferId || '').replace(/[^a-zA-Z0-9_.:-]/g, '-').slice(0, 128);
|
|
480
|
+
const offset = Math.max(0, Math.floor(Number(payload.offset) || 0));
|
|
481
|
+
const totalBytes = Math.max(0, Math.floor(Number(payload.totalBytes) || 0));
|
|
482
|
+
const final = payload.final === true;
|
|
483
|
+
const buffer = payload.dataBase64 ? Buffer.from(String(payload.dataBase64), 'base64') : Buffer.alloc(0);
|
|
484
|
+
if (!transferId || offset > totalBytes || offset + buffer.length > totalBytes || buffer.length > 512 * 1024) {
|
|
485
|
+
throw new Error('Invalid file transfer chunk.');
|
|
486
|
+
}
|
|
487
|
+
if (buffer.length === 0 && !(final && totalBytes === 0 && offset === 0)) {
|
|
488
|
+
throw new Error('Empty file chunks are not allowed.');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
492
|
+
const tempPath = `${targetPath}.livedesk-${transferId}.part`;
|
|
493
|
+
const handle = await fs.open(tempPath, offset === 0 ? 'w+' : 'r+');
|
|
494
|
+
let completedSize = 0;
|
|
495
|
+
try {
|
|
496
|
+
const before = await handle.stat();
|
|
497
|
+
if (offset > before.size) {
|
|
498
|
+
throw new Error(`File chunk gap detected at ${offset}; current length is ${before.size}.`);
|
|
499
|
+
}
|
|
500
|
+
if (buffer.length > 0) {
|
|
501
|
+
await handle.write(buffer, 0, buffer.length, offset);
|
|
502
|
+
}
|
|
503
|
+
const after = await handle.stat();
|
|
504
|
+
completedSize = after.size;
|
|
505
|
+
if (final) {
|
|
506
|
+
await handle.sync();
|
|
507
|
+
if (after.size !== totalBytes) {
|
|
508
|
+
throw new Error(`File transfer is incomplete (${after.size}/${totalBytes}).`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
} finally {
|
|
512
|
+
await handle.close();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (final) {
|
|
516
|
+
await fs.rm(targetPath, { force: true });
|
|
517
|
+
await fs.rename(tempPath, targetPath);
|
|
518
|
+
const lastModified = Number(payload.lastModified || 0);
|
|
519
|
+
if (lastModified > 0) {
|
|
520
|
+
const modifiedAt = new Date(lastModified);
|
|
521
|
+
await fs.utimes(targetPath, modifiedAt, modifiedAt).catch(() => undefined);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
kind: 'file.transfer.chunk',
|
|
527
|
+
transferId,
|
|
528
|
+
status: final ? 'completed' : 'receiving',
|
|
529
|
+
relativePath: relativePath.split(path.sep).join('/'),
|
|
530
|
+
offset,
|
|
531
|
+
bytes: buffer.length,
|
|
532
|
+
totalBytes,
|
|
533
|
+
completedSize,
|
|
534
|
+
final,
|
|
535
|
+
completedAt: final ? new Date().toISOString() : null
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
469
539
|
function clampNumber(value, min, max, fallback) {
|
|
470
540
|
const number = Number(value);
|
|
471
541
|
if (!Number.isFinite(number)) {
|
|
@@ -996,6 +1066,24 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
|
|
|
996
1066
|
return;
|
|
997
1067
|
}
|
|
998
1068
|
|
|
1069
|
+
if (command === 'file.transfer.chunk') {
|
|
1070
|
+
try {
|
|
1071
|
+
const result = await handleFileTransferChunkCommand(options, message.payload || {});
|
|
1072
|
+
writeJsonLine(socket, {
|
|
1073
|
+
type: 'command.result',
|
|
1074
|
+
commandId: message.commandId,
|
|
1075
|
+
result
|
|
1076
|
+
});
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
writeJsonLine(socket, {
|
|
1079
|
+
type: 'command.result',
|
|
1080
|
+
commandId: message.commandId,
|
|
1081
|
+
error: error?.message || String(error)
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
999
1087
|
if (command === 'audio.start' || command === 'audio.stop') {
|
|
1000
1088
|
writeJsonLine(socket, {
|
|
1001
1089
|
type: 'command.result',
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|