Updates to potentially resolve devicesclient.
All checks were successful
Deploy Frontend / deploy (push) Successful in 24s

This commit is contained in:
Bailey Taylor
2025-10-29 12:49:27 +08:00
parent f7ecef695c
commit 10d65a89b4

View File

@@ -33,13 +33,86 @@ interface InstalledApp {
type RawRecord = Record<string, unknown>; type RawRecord = Record<string, unknown>;
const pick = (source: RawRecord | null | undefined, keys: string[]): unknown => { const normalizeKey = (key: string): string =>
key.toLowerCase().replace(/[^a-z0-9]/g, '');
const getDirectValue = (source: RawRecord | null | undefined, keys: string[]): unknown => {
if (!source) return undefined; if (!source) return undefined;
for (const key of keys) { for (const key of keys) {
if (Object.prototype.hasOwnProperty.call(source, key)) { if (Object.prototype.hasOwnProperty.call(source, key)) {
return source[key]; const value = source[key];
if (value !== undefined && value !== null) {
return value;
} }
} }
}
return undefined;
};
const findValueDeep = (
source: unknown,
normalizedTargets: string[],
seen = new WeakSet<object>()
): unknown => {
if (!source || typeof source !== 'object') return undefined;
const stack: unknown[] = [source];
while (stack.length > 0) {
const current = stack.pop();
if (!current || typeof current !== 'object') continue;
if (seen.has(current as object)) continue;
seen.add(current as object);
if (Array.isArray(current)) {
for (const item of current) {
if (item && (typeof item === 'object' || typeof item === 'string')) {
stack.push(item);
}
}
continue;
}
for (const [key, value] of Object.entries(current as RawRecord)) {
const normalizedKey = normalizeKey(key);
if (normalizedTargets.includes(normalizedKey) && value !== undefined && value !== null) {
return value;
}
if (value && typeof value === 'object') {
stack.push(value);
}
}
}
return undefined;
};
const resolveValue = (
source: RawRecord | null | undefined,
keys: string[],
fallbackKeys: string[] = keys
): unknown => {
if (!source) return undefined;
const direct = getDirectValue(source, keys);
if (direct !== undefined && direct !== null) {
return direct;
}
const normalizedTargets = Array.from(new Set(fallbackKeys.map(normalizeKey))).filter(Boolean);
return findValueDeep(source, normalizedTargets);
};
const resolveArray = <T = unknown>(
source: RawRecord | null | undefined,
keys: string[],
fallbackKeys?: string[]
): T[] | undefined => {
const value = resolveValue(source, keys, fallbackKeys ?? keys);
if (Array.isArray(value)) {
return value as T[];
}
return undefined; return undefined;
}; };
@@ -94,12 +167,36 @@ const normalizeDrive = (driveInput: unknown, index: number): DriveInfo | null =>
const drive = driveInput as RawRecord; const drive = driveInput as RawRecord;
const name = toNonEmptyString( const name = toNonEmptyString(
pick(drive, ['name', 'driveName', 'label', 'volume']) ?? `Drive ${index + 1}`, resolveValue(drive, ['name', 'driveName', 'label', 'volume'], [
'name',
'driveName',
'label',
'volume',
'drive_letter',
'driveletter',
'mount_point',
'mountpoint',
'logicaldisk',
'deviceid',
]) ?? `Drive ${index + 1}`,
`Drive ${index + 1}` `Drive ${index + 1}`
); );
const driveType = toNonEmptyString(pick(drive, ['driveType', 'drive_type', 'type']), 'Unknown'); const driveType = toNonEmptyString(
resolveValue(drive, ['driveType', 'drive_type', 'type'], [
'driveType',
'drive_type',
'type',
'drivetype',
'mediatype',
'busType',
]),
'Unknown'
);
const totalSizeGB = parseSizeToGB( const totalSizeGB = parseSizeToGB(
pick(drive, [ resolveValue(
drive,
['totalSizeGB', 'total_size_gb', 'totalSize', 'total_size', 'size', 'capacity', 'totalBytes', 'total_bytes'],
[
'totalSizeGB', 'totalSizeGB',
'total_size_gb', 'total_size_gb',
'totalSize', 'totalSize',
@@ -108,10 +205,19 @@ const normalizeDrive = (driveInput: unknown, index: number): DriveInfo | null =>
'capacity', 'capacity',
'totalBytes', 'totalBytes',
'total_bytes', 'total_bytes',
]) 'size_bytes',
'capacity_bytes',
'totalsize',
'totalspace',
'totalspacebytes',
]
)
); );
const freeSpaceGB = parseSizeToGB( const freeSpaceGB = parseSizeToGB(
pick(drive, [ resolveValue(
drive,
['freeSpaceGB', 'free_space_gb', 'freeSpace', 'free_space', 'free', 'freeBytes', 'free_bytes', 'availableBytes'],
[
'freeSpaceGB', 'freeSpaceGB',
'free_space_gb', 'free_space_gb',
'freeSpace', 'freeSpace',
@@ -120,7 +226,12 @@ const normalizeDrive = (driveInput: unknown, index: number): DriveInfo | null =>
'freeBytes', 'freeBytes',
'free_bytes', 'free_bytes',
'availableBytes', 'availableBytes',
]) 'available_bytes',
'freespace',
'freespacebytes',
'remaining',
]
)
); );
if (!name && !totalSizeGB && !freeSpaceGB) return null; if (!name && !totalSizeGB && !freeSpaceGB) return null;
@@ -138,12 +249,17 @@ const extractDrives = (rawInput: unknown): DriveInfo[] => {
const raw = rawInput as RawRecord; const raw = rawInput as RawRecord;
const potentialArrays = [ const potentialArrays = [
pick(raw, ['drives']), resolveArray(raw, ['drives']),
pick(raw, ['driveInfo']), resolveArray(raw, ['driveInfo']),
pick(raw, ['drive_info']), resolveArray(raw, ['drive_info']),
pick(raw, ['storageDevices']), resolveArray(raw, ['storageDevices']),
pick(raw, ['drive_details']), resolveArray(raw, ['drive_details']),
pick(raw, ['storage']), resolveArray(raw, ['storage']),
resolveArray(raw, ['logicalDrives']),
resolveArray(raw, ['logical_drives']),
resolveArray(raw, ['volumes']),
resolveArray(raw, ['volume_info']),
resolveArray(raw, ['disks']),
]; ];
for (const candidate of potentialArrays) { for (const candidate of potentialArrays) {
@@ -155,8 +271,26 @@ const extractDrives = (rawInput: unknown): DriveInfo[] => {
} }
const aggregatedDrive = { const aggregatedDrive = {
totalSize: pick(raw, ['total_disk_space', 'totalDiskSpace', 'total_storage']), totalSize: resolveValue(raw, ['total_disk_space', 'totalDiskSpace', 'total_storage'], [
freeSpace: pick(raw, ['free_disk_space', 'freeDiskSpace', 'available_storage']), 'total_disk_space',
'totalDiskSpace',
'total_storage',
'totalSpace',
'total_space',
'storage_total',
'disk_total',
]),
freeSpace: resolveValue(raw, ['free_disk_space', 'freeDiskSpace', 'available_storage'], [
'free_disk_space',
'freeDiskSpace',
'available_storage',
'free_storage',
'freeSpace',
'free_space',
'availableSpace',
'available_space',
'storage_free',
]),
}; };
if (aggregatedDrive.totalSize || aggregatedDrive.freeSpace) { if (aggregatedDrive.totalSize || aggregatedDrive.freeSpace) {
@@ -181,19 +315,45 @@ const normalizeIpAddress = (entryInput: unknown, index: number): IpAddress | nul
const entry = entryInput as RawRecord; const entry = entryInput as RawRecord;
const ipAddress = toNonEmptyString( const ipAddress = toNonEmptyString(
pick(entry, ['ipAddress', 'ip_address', 'address', 'ip', 'ipv4', 'ipv6']) ?? '', resolveValue(entry, ['ipAddress', 'ip_address', 'address', 'ip', 'ipv4', 'ipv6'], [
'ipAddress',
'ip_address',
'address',
'ip',
'ipv4',
'ipv6',
'primary_ip',
'primaryIp',
'lan_ip',
]) ?? '',
'' ''
); );
const interfaceName = toNonEmptyString( const interfaceName = toNonEmptyString(
pick(entry, ['interfaceName', 'interface_name', 'adapter', 'name', 'nic']) ?? '', resolveValue(entry, ['interfaceName', 'interface_name', 'adapter', 'name', 'nic'], [
'interfaceName',
'interface_name',
'adapter',
'name',
'nic',
'network_adapter',
'interface',
'description',
]) ?? '',
ipAddress ? `Interface ${index + 1}` : '' ipAddress ? `Interface ${index + 1}` : ''
); );
if (!ipAddress && !interfaceName) return null; if (!ipAddress && !interfaceName) return null;
const macAddress = toOptionalString( const macAddress = toOptionalString(
pick(entry, ['macAddress', 'mac_address', 'mac', 'physicalAddress']) resolveValue(entry, ['macAddress', 'mac_address', 'mac', 'physicalAddress'], [
'macAddress',
'mac_address',
'mac',
'physicalAddress',
'physical_address',
'hw_address',
])
); );
return { return {
@@ -208,11 +368,14 @@ const extractIpAddresses = (rawInput: unknown): IpAddress[] => {
const raw = rawInput as RawRecord; const raw = rawInput as RawRecord;
const potentialArrays = [ const potentialArrays = [
pick(raw, ['ipAddresses']), resolveArray(raw, ['ipAddresses']),
pick(raw, ['ip_addresses']), resolveArray(raw, ['ip_addresses']),
pick(raw, ['networkInterfaces']), resolveArray(raw, ['networkInterfaces']),
pick(raw, ['network_interfaces']), resolveArray(raw, ['network_interfaces']),
pick(raw, ['interfaces']), resolveArray(raw, ['interfaces']),
resolveArray(raw, ['networkAdapters']),
resolveArray(raw, ['network_adapters']),
resolveArray(raw, ['network']),
]; ];
for (const candidate of potentialArrays) { for (const candidate of potentialArrays) {
@@ -223,14 +386,14 @@ const extractIpAddresses = (rawInput: unknown): IpAddress[] => {
} }
} }
const singleIp = pick(raw, ['ipAddress', 'ip_address']); const singleIp = resolveValue(raw, ['ipAddress', 'ip_address', 'primary_ip', 'primaryIp']);
if (singleIp) { if (singleIp) {
return [ return [
normalizeIpAddress( normalizeIpAddress(
{ {
ipAddress: singleIp, ipAddress: singleIp,
interfaceName: pick(raw, ['interfaceName', 'interface_name', 'adapter']), interfaceName: resolveValue(raw, ['interfaceName', 'interface_name', 'adapter']),
macAddress: pick(raw, ['macAddress', 'mac_address']), macAddress: resolveValue(raw, ['macAddress', 'mac_address']),
}, },
0 0
) as IpAddress, ) as IpAddress,
@@ -244,13 +407,34 @@ const normalizeInstalledApp = (appInput: unknown): InstalledApp | null => {
if (!appInput || typeof appInput !== 'object') return null; if (!appInput || typeof appInput !== 'object') return null;
const app = appInput as RawRecord; const app = appInput as RawRecord;
const appName = toNonEmptyString(pick(app, ['app_name', 'name', 'title']) ?? '', ''); const appName = toNonEmptyString(
resolveValue(app, ['app_name', 'name', 'title'], [
'app_name',
'name',
'title',
'display_name',
'product_name',
]) ?? '',
''
);
if (!appName) return null; if (!appName) return null;
return { return {
app_name: appName, app_name: appName,
app_version: toNonEmptyString(pick(app, ['app_version', 'version']) ?? '', ''), app_version: toNonEmptyString(
publisher: toNonEmptyString(pick(app, ['publisher', 'manufacturer', 'vendor']) ?? '', ''), resolveValue(app, ['app_version', 'version'], ['app_version', 'version', 'display_version', 'product_version']) ?? '',
''
),
publisher: toNonEmptyString(
resolveValue(app, ['publisher', 'manufacturer', 'vendor'], [
'publisher',
'manufacturer',
'vendor',
'company',
'publisher_name',
]) ?? '',
''
),
}; };
}; };
@@ -259,11 +443,14 @@ const extractInstalledApps = (rawInput: unknown): InstalledApp[] => {
const raw = rawInput as RawRecord; const raw = rawInput as RawRecord;
const potentialArrays = [ const potentialArrays = [
pick(raw, ['installedApplications']), resolveArray(raw, ['installedApplications']),
pick(raw, ['installed_applications']), resolveArray(raw, ['installed_applications']),
pick(raw, ['software']), resolveArray(raw, ['software']),
pick(raw, ['applications']), resolveArray(raw, ['applications']),
pick(raw, ['apps']), resolveArray(raw, ['apps']),
resolveArray(raw, ['installedPrograms']),
resolveArray(raw, ['installed_programs']),
resolveArray(raw, ['programs']),
]; ];
for (const candidate of potentialArrays) { for (const candidate of potentialArrays) {
@@ -279,18 +466,32 @@ const extractInstalledApps = (rawInput: unknown): InstalledApp[] => {
const extractMacAddresses = (rawInput: unknown, ipAddresses: IpAddress[]): MacAddress[] => { const extractMacAddresses = (rawInput: unknown, ipAddresses: IpAddress[]): MacAddress[] => {
if (!rawInput || typeof rawInput !== 'object') return []; if (!rawInput || typeof rawInput !== 'object') return [];
const raw = rawInput as RawRecord; const raw = rawInput as RawRecord;
const potentialArrays = pick(raw, ['macAddresses', 'mac_addresses']); const potentialArrays = resolveArray(raw, ['macAddresses', 'mac_addresses', 'macs', 'mac_list']);
if (Array.isArray(potentialArrays)) { if (Array.isArray(potentialArrays)) {
return potentialArrays return potentialArrays
.map((entry, idx: number) => { .map((entry, idx: number) => {
if (!entry || typeof entry !== 'object') return null; if (!entry || typeof entry !== 'object') return null;
const record = entry as RawRecord; const record = entry as RawRecord;
const macAddress = toOptionalString(pick(record, ['macAddress', 'mac_address', 'mac'])); const macAddress = toOptionalString(
resolveValue(record, ['macAddress', 'mac_address', 'mac'], [
'macAddress',
'mac_address',
'mac',
'physicalAddress',
'hw_address',
])
);
if (!macAddress) return null; if (!macAddress) return null;
return { return {
interfaceName: toNonEmptyString( interfaceName: toNonEmptyString(
pick(record, ['interfaceName', 'interface_name', 'adapter']) ?? `Interface ${idx + 1}`, resolveValue(record, ['interfaceName', 'interface_name', 'adapter'], [
'interfaceName',
'interface_name',
'adapter',
'network_adapter',
'name',
]) ?? `Interface ${idx + 1}`,
`Interface ${idx + 1}` `Interface ${idx + 1}`
), ),
macAddress: macAddress.trim(), macAddress: macAddress.trim(),
@@ -329,47 +530,159 @@ const normalizeDevice = (rawInput: unknown): DetailedDevice => {
rawInput && typeof rawInput === 'object' ? (rawInput as RawRecord) : {}; rawInput && typeof rawInput === 'object' ? (rawInput as RawRecord) : {};
const deviceId = Number( const deviceId = Number(
pick(raw, ['deviceId', 'device_id', 'id', 'deviceID', 'device_id_pk']) ?? 0 resolveValue(raw, ['deviceId', 'device_id', 'id', 'deviceID', 'device_id_pk'], [
'deviceId',
'device_id',
'id',
'deviceID',
'device_id_pk',
]) ?? 0
); );
const drives = extractDrives(raw); const drives = extractDrives(raw);
const ipAddresses = extractIpAddresses(raw); const ipAddresses = extractIpAddresses(raw);
const macAddresses = extractMacAddresses(raw, ipAddresses); const macAddresses = extractMacAddresses(raw, ipAddresses);
const clientEntry = pick(raw, ['client']); const clientEntry = resolveValue(raw, ['client'], ['client', 'clientInfo', 'customer']);
const clientNameFromNested = const clientNameFromNested =
clientEntry && typeof clientEntry === 'object' clientEntry && typeof clientEntry === 'object'
? pick(clientEntry as RawRecord, ['name']) ? resolveValue(clientEntry as RawRecord, ['name', 'clientName', 'client_name'])
: undefined; : undefined;
const hostname = toNonEmptyString( const hostname = toNonEmptyString(
pick(raw, ['hostname', 'hostName', 'computerName', 'deviceName']) ?? `Device ${deviceId}`, resolveValue(raw, ['hostname', 'hostName', 'computerName', 'deviceName'], [
'hostname',
'hostName',
'computerName',
'deviceName',
'host',
'machineName',
]) ?? `Device ${deviceId}`,
deviceId ? `Device ${deviceId}` : 'Unknown Device' deviceId ? `Device ${deviceId}` : 'Unknown Device'
); );
return { return {
deviceId, deviceId,
hostname, hostname,
osName: toNonEmptyString(pick(raw, ['osName', 'os_name', 'operatingSystem', 'os']) ?? '', ''), osName: toNonEmptyString(
osVersion: toNonEmptyString(pick(raw, ['osVersion', 'os_version', 'osVersionString']) ?? '', ''), resolveValue(
windowsVersion: toNonEmptyString(pick(raw, ['windowsVersion', 'windows_version', 'osRelease']) ?? '', ''), raw,
windowsBuild: toNonEmptyString(pick(raw, ['windowsBuild', 'windows_build', 'buildNumber']) ?? '', ''), ['osName', 'os_name', 'operatingSystem', 'os'],
osArchitecture: toNonEmptyString(pick(raw, ['osArchitecture', 'os_architecture', 'architecture']) ?? '', ''), [
processorName: toNonEmptyString(pick(raw, ['processorName', 'processor_name', 'cpuName']) ?? '', ''), 'osName',
processorArchitecture: toNonEmptyString( 'os_name',
pick(raw, ['processorArchitecture', 'processor_architecture', 'cpuArchitecture']) ?? '', 'operatingSystem',
'operating_system',
'operatingSystemName',
'operating_system_name',
'osFriendlyName',
'os_caption',
'oscaption',
'os',
]
) ?? '',
'' ''
), ),
gpuNames: splitGpuNames(pick(raw, ['gpuNames', 'gpu_names', 'gpu_name', 'gpus'])), osVersion: toNonEmptyString(
resolveValue(raw, ['osVersion', 'os_version', 'osVersionString'], [
'osVersion',
'os_version',
'osVersionString',
'operatingSystemVersion',
'osversion',
]) ?? '',
''
),
windowsVersion: toNonEmptyString(
resolveValue(raw, ['windowsVersion', 'windows_version', 'osRelease'], [
'windowsVersion',
'windows_version',
'osRelease',
'windowsRelease',
'windows_release',
]) ?? '',
''
),
windowsBuild: toNonEmptyString(
resolveValue(raw, ['windowsBuild', 'windows_build', 'buildNumber'], [
'windowsBuild',
'windows_build',
'buildNumber',
'build_number',
'windowsBuildNumber',
]) ?? '',
''
),
osArchitecture: toNonEmptyString(
resolveValue(raw, ['osArchitecture', 'os_architecture', 'architecture'], [
'osArchitecture',
'os_architecture',
'architecture',
'platform',
'osarch',
]) ?? '',
''
),
processorName: toNonEmptyString(
resolveValue(raw, ['processorName', 'processor_name', 'cpuName'], [
'processorName',
'processor_name',
'cpuName',
'cpu_name',
'processor',
]) ?? '',
''
),
processorArchitecture: toNonEmptyString(
resolveValue(raw, ['processorArchitecture', 'processor_architecture', 'cpuArchitecture'], [
'processorArchitecture',
'processor_architecture',
'cpuArchitecture',
'cpu_architecture',
'cpuArch',
]) ?? '',
''
),
gpuNames: splitGpuNames(
resolveValue(raw, ['gpuNames', 'gpu_names', 'gpu_name', 'gpus', 'graphics']) ?? []
),
totalMemory: toNonEmptyString( totalMemory: toNonEmptyString(
pick(raw, ['totalMemory', 'total_memory', 'memory', 'totalMemoryMb', 'totalMemoryMB']) ?? '', resolveValue(
raw,
['totalMemory', 'total_memory', 'memory', 'totalMemoryMb', 'totalMemoryMB'],
[
'totalMemory',
'total_memory',
'memory',
'totalMemoryMb',
'totalMemoryMB',
'memory_total',
'ram_total',
'physicalMemory',
]
) ?? '',
'' ''
), ),
lastBootTime: toNonEmptyString( lastBootTime: toNonEmptyString(
pick(raw, ['lastBootTime', 'last_boot_time', 'bootTime', 'lastBoot']) ?? '', resolveValue(raw, ['lastBootTime', 'last_boot_time', 'bootTime', 'lastBoot', 'lastBootUpTime'], [
'lastBootTime',
'last_boot_time',
'bootTime',
'lastBoot',
'lastBootUpTime',
'last_boot',
'boot_time',
]) ?? '',
'' ''
), ),
lastCheckedIn: toNonEmptyString( lastCheckedIn: toNonEmptyString(
pick(raw, ['lastCheckedIn', 'last_checked_in', 'lastSeen', 'checked_in_at']) ?? '', resolveValue(raw, ['lastCheckedIn', 'last_checked_in', 'lastSeen', 'checked_in_at'], [
'lastCheckedIn',
'last_checked_in',
'lastSeen',
'checked_in_at',
'lastSeenAt',
'last_seen',
]) ?? '',
'' ''
), ),
drives, drives,
@@ -378,7 +691,9 @@ const normalizeDevice = (rawInput: unknown): DetailedDevice => {
installedApplications: extractInstalledApps(raw), installedApplications: extractInstalledApps(raw),
clientName: clientName:
toOptionalString( toOptionalString(
pick(raw, ['clientName', 'client_name']) ?? clientNameFromNested ?? pick(raw, ['clientIdentifier']) resolveValue(raw, ['clientName', 'client_name'], ['clientName', 'client_name', 'client']) ??
clientNameFromNested ??
resolveValue(raw, ['clientIdentifier', 'client_identifier'])
) ?? undefined, ) ?? undefined,
}; };
}; };