Files
BetterDesk/web-nodejs/services/rustdeskAddressBookSync.js
T
2026-05-18 02:34:38 +02:00

185 lines
5.1 KiB
JavaScript

/**
* RustDesk address book sync helpers.
*
* The web panel stores device tags and folders separately. RustDesk address
* books only own explicit peer tags; BetterDesk folders are exposed through the
* RustDesk device-group API. Folder names must not be used to infer or remove
* address book tags because users can intentionally use the same label for both.
*/
'use strict';
const MAX_TAG_LENGTH = 50;
function sanitizeTag(value) {
if (typeof value !== 'string') return '';
return value
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '')
.trim()
.slice(0, MAX_TAG_LENGTH);
}
function uniquePush(list, seen, value) {
const tag = sanitizeTag(value);
if (!tag || seen.has(tag)) return;
seen.add(tag);
list.push(tag);
}
function normalizeTags(value) {
if (!value) return [];
let raw = value;
if (typeof raw === 'string') {
const trimmed = raw.trim();
if (!trimmed) return [];
if (trimmed.startsWith('[')) {
try {
raw = JSON.parse(trimmed);
} catch (_) {
raw = trimmed.split(',');
}
} else {
raw = trimmed.split(',');
}
}
if (!Array.isArray(raw)) return [];
const tags = [];
const seen = new Set();
for (const item of raw) {
uniquePush(tags, seen, String(item || ''));
}
return tags;
}
function filterFolderTags(tags) {
return normalizeTags(tags);
}
function parseAddressBookData(data) {
let parsed = {};
if (data && typeof data === 'object' && !Buffer.isBuffer(data)) {
parsed = { ...data };
} else if (typeof data === 'string' && data.trim()) {
try {
parsed = JSON.parse(data);
} catch (_) {
parsed = {};
}
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
parsed = {};
}
parsed.peers = Array.isArray(parsed.peers) ? parsed.peers : [];
parsed.tags = normalizeTags(parsed.tags);
return parsed;
}
function getDeviceTags(device) {
return normalizeTags(device && device.tags);
}
function mergePeerFields(existing, device, tags) {
const peer = existing && typeof existing === 'object' && !Array.isArray(existing)
? existing
: {};
peer.id = String(peer.id || device.id || '');
if (!peer.username && (device.username || device.user)) peer.username = String(device.username || device.user);
if (!peer.hostname && device.hostname) peer.hostname = String(device.hostname);
if (!peer.alias && (device.display_name || device.note)) peer.alias = String(device.display_name || device.note);
if (!peer.platform && (device.platform || device.os)) peer.platform = String(device.platform || device.os);
peer.tags = tags;
return peer;
}
function mergeAddressBookData(data, options = {}) {
const ab = parseAddressBookData(data);
const devices = Array.isArray(options.devices) ? options.devices : [];
const includeDevices = options.includeDevices !== false;
ab.tags = normalizeTags(ab.tags);
const globalSeen = new Set(ab.tags);
const peerById = new Map();
for (const peer of ab.peers) {
if (!peer || typeof peer !== 'object') continue;
const id = String(peer.id || '').trim();
if (!id) continue;
peer.tags = normalizeTags(peer.tags);
peerById.set(id, peer);
for (const tag of peer.tags) {
uniquePush(ab.tags, globalSeen, tag);
}
}
for (const device of devices) {
const id = String(device && device.id || '').trim();
if (!id) continue;
const existing = peerById.get(id);
if (!existing && !includeDevices) continue;
const mergedTags = normalizeTags(existing && existing.tags);
const tagSeen = new Set(mergedTags);
for (const tag of getDeviceTags(device)) {
uniquePush(mergedTags, tagSeen, tag);
uniquePush(ab.tags, globalSeen, tag);
}
const peer = mergePeerFields(existing, device, mergedTags);
if (!existing) {
ab.peers.push(peer);
peerById.set(id, peer);
}
}
return JSON.stringify(ab);
}
function collectVisibleTags(devices, folders, assignments) {
const tags = [];
const seen = new Set();
for (const device of Array.isArray(devices) ? devices : []) {
for (const tag of getDeviceTags(device)) {
uniquePush(tags, seen, tag);
}
}
return tags.sort((a, b) => a.localeCompare(b));
}
function collectPeerTagUpdates(data, options = {}) {
const ab = parseAddressBookData(data);
const updates = [];
const seen = new Set();
for (const peer of ab.peers) {
if (!peer || typeof peer !== 'object') continue;
const id = String(peer.id || '').trim();
if (!id || seen.has(id)) continue;
if (!Object.prototype.hasOwnProperty.call(peer, 'tags')) continue;
seen.add(id);
updates.push({ id, tags: normalizeTags(peer.tags) });
}
return updates;
}
module.exports = {
normalizeTags,
parseAddressBookData,
filterFolderTags,
mergeAddressBookData,
collectVisibleTags,
collectPeerTagUpdates
};