feat(activate): allow retireDeviceId to free a device slot on migration

Supports client migration from per-user UUID to machine fingerprint without consuming an extra maxDevices slot.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-23 10:39:46 +08:00
parent ab86790feb
commit f19b50b2ad
3 changed files with 58 additions and 5 deletions
+13 -4
View File
@@ -175,6 +175,8 @@ export function createServer(options = {}) {
const body = JSON.parse(raw || '{}');
const productKey = body.productKey;
const deviceId = body.deviceId;
const retireDeviceId =
typeof body.retireDeviceId === 'string' ? body.retireDeviceId.trim() : '';
if (!productKey || !deviceId) return json(res, 400, { error: 'productKey_and_deviceId_required' });
const data = readData();
@@ -184,8 +186,13 @@ export function createServer(options = {}) {
data.activations ??= {};
const list = data.activations[pk.sub] ?? [];
const already = list.includes(deviceId);
if (!already && list.length >= pk.maxDevices) {
// Миграция со старого per-user UUID: освобождаем слот retireDeviceId перед проверкой лимита.
let next = [...list];
if (retireDeviceId && retireDeviceId !== deviceId) {
next = next.filter((d) => d !== retireDeviceId);
}
const already = next.includes(deviceId);
if (!already && next.length >= pk.maxDevices) {
return json(res, 403, { error: 'too_many_devices' });
}
const now = Math.floor(Date.now() / 1000);
@@ -199,8 +206,10 @@ export function createServer(options = {}) {
}
if (!already) {
list.push(deviceId);
data.activations[pk.sub] = list;
next.push(deviceId);
}
if (next.length !== list.length || next.some((d, i) => d !== list[i])) {
data.activations[pk.sub] = next;
dataChanged = true;
}