From abd37e416dba4e36bcf9768ab6bbf1e82a206dd9 Mon Sep 17 00:00:00 2001 From: Nick Berardi Date: Sat, 29 Aug 2026 18:06:49 -0400 Subject: [PATCH] fix(unifi-protect): stop keying device identity on DHCP host/IP (#2082) getNativeId matched cameras on host (IP) as a fallback identity key. IPs are not stable: when DHCP recycles an address, a newly provisioned camera resolves to an unrelated camera's nativeId. cleanDict() then overwrites the original camera's mac/anonymousDeviceId entries, collapsing two cameras onto one Scrypted device and dropping the other from the device list. Match only on stable hardware identifiers (mac, anonymousDeviceId, id) and stop maintaining the host map. --- plugins/unifi-protect/src/main.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/unifi-protect/src/main.ts b/plugins/unifi-protect/src/main.ts index bcc4f5455..0dc3f25b3 100644 --- a/plugins/unifi-protect/src/main.ts +++ b/plugins/unifi-protect/src/main.ts @@ -859,14 +859,18 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device // for now fall back to old behavior which will be removed at a later date. } - const { id, mac, anonymousDeviceId, host } = device; + const { id, mac, anonymousDeviceId } = device; const idMaps = this.storageSettings.values.idMaps; - // try to find an existing nativeId given the mac and anonymous device id + // Try to find an existing nativeId using stable hardware identifiers. + // Do NOT match on host/IP: DHCP reassigns addresses, so a recycled IP + // would bind a newly provisioned camera to an unrelated camera's + // nativeId. The subsequent cleanDict() then clobbers the original + // camera's mac/anonymousDeviceId entries, collapsing two cameras onto + // a single device (and losing the other from the device list). const found = (mac && idMaps.mac?.[mac]) || (anonymousDeviceId && idMaps.anonymousDeviceId?.[anonymousDeviceId]) || (id && idMaps.id?.[id]) - || (host && idMaps.host?.[host]) ; // use the found id if one exists (device got provisioned a new id), otherwise use the id provided by the device. @@ -889,25 +893,20 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device // Clean existing mappings before adding new ones idMaps.mac ||= {}; idMaps.anonymousDeviceId ||= {}; - idMaps.host ||= {}; idMaps.id ||= {}; idMaps.nativeId ||= {}; cleanDict(idMaps.mac); cleanDict(idMaps.anonymousDeviceId); - cleanDict(idMaps.host); cleanDict(idMaps.id); - // map the mac, host, and anonymous device id to the native id. + // map the mac and anonymous device id to the native id. if (mac) { idMaps.mac[mac] = nativeId; } if (anonymousDeviceId) { idMaps.anonymousDeviceId[anonymousDeviceId] = nativeId; } - if (host) { - idMaps.host[host] = nativeId; - } // map the id and native id to each other. idMaps.id[id] = nativeId;