Files
Power BI Dev 88c7c653c2 first init
2026-08-17 10:52:09 +07:00

90 lines
3.0 KiB
JavaScript

export class LockManager {
// Map of lockKey (`${ticketId}:${field}`) -> FieldLockInfo
static locks = new Map();
// Auto-expire locks after 15 seconds of inactivity to prevent ghost locks
static LOCK_EXPIRATION_MS = 15000;
static getLockKey(ticketId, field) {
return `${ticketId}:${field}`;
}
/**
* Attempt to acquire or refresh a lock.
* Returns { success: boolean, lock?: FieldLockInfo, currentLock?: FieldLockInfo }
*/
static acquireLock(ticketId, field, socketId, userId, userName) {
const lockKey = this.getLockKey(ticketId, field);
const existing = this.locks.get(lockKey);
const now = Date.now();
// If lock exists and hasn't expired
if (existing && now - existing.lockedAt < this.LOCK_EXPIRATION_MS) {
// If the current socket or user holds it, refresh the timestamp
if (existing.socketId === socketId || existing.userId === userId) {
existing.lockedAt = now;
existing.userName = userName;
return { success: true, lock: existing };
}
// Held by another user
return { success: false, currentHolder: existing };
}
// Lock is either free or expired, acquire it
const newLock = {
ticketId,
field,
socketId,
userId,
userName,
lockedAt: now,
};
this.locks.set(lockKey, newLock);
return { success: true, lock: newLock };
}
/**
* Release a lock by ticketId and field.
*/
static releaseLock(ticketId, field, socketId) {
const lockKey = this.getLockKey(ticketId, field);
const existing = this.locks.get(lockKey);
if (!existing)
return null;
// If socketId specified, verify ownership
if (socketId && existing.socketId !== socketId) {
// If it has expired, allow release
if (Date.now() - existing.lockedAt >= this.LOCK_EXPIRATION_MS) {
this.locks.delete(lockKey);
return existing;
}
return null;
}
this.locks.delete(lockKey);
return existing;
}
/**
* Release all locks held by a disconnected socket.
*/
static releaseSocketLocks(socketId) {
const released = [];
for (const [key, lock] of this.locks.entries()) {
if (lock.socketId === socketId) {
released.push(lock);
this.locks.delete(key);
}
}
return released;
}
/**
* Get all active (non-expired) locks for a board/tickets.
*/
static getActiveLocks() {
const now = Date.now();
const active = [];
for (const [key, lock] of this.locks.entries()) {
if (now - lock.lockedAt < this.LOCK_EXPIRATION_MS) {
active.push(lock);
}
else {
this.locks.delete(key);
}
}
return active;
}
}