src/chat/AckCounter.js
/** @private */
export class AckCounter {
constructor() {
this.limit = 2;
this.data = [];
this.lastAckedBy = [];
this.olderAcked = false;
}
/**
* Sets the number of times an element needs to be incremented to be removed
* @param {number} limit
*/
setLimit(limit) {
this.limit = limit;
}
/**
* Adds a new element at the end of the collection.
* @param {string} element
* @return {boolean} true if the element is correctly added, false otherwise
*/
append(element) {
if (this.data.find(d => d.key === element)) {
return false;
}
this.data.push({key: element, value: new window.Set()});
return true;
}
/**
* Adds a new element at the start of the collection.
* @param {string} element
*/
prepend(element) {
if (this.olderAcked) {
return;
}
// If element has received no acks, copy last added element
if (!this.data.find(d => d.key === element)) {
for (let i = this.data.length - 1; i >= 0; i--) {
if (this.data[i].isAdded) {
const value = new window.Set(this.data[i].value);
this.data.splice(i, 0, {key: element, value});
break;
}
}
}
// If there was no last added element, add new element as the newest element
if (!this.data.find(d => d.key === element)) {
this.data.push({key: element, value: new window.Set()});
}
const index = this.data.findIndex(({key}) => key === element);
this.data[index].isAdded = true;
const {value} = this.data[index];
this.olderAcked = value.size >= this.limit;
}
/**
* Allows knowing if the specified element is currently in the collection
* @param {string} element
* @return {boolean}
*/
hasElement(element) {
return !!this.data.find(d => d.key === element);
}
/**
* Adds a new ack to the specified element from the specified user
* @param {string} element
* @param {string} user
* @returns {boolean} true if the element reached the limit, false otherwise
*/
appendAck(element, user) {
if (!this.data.find(d => d.key === element)) {
throw new Error(`${element} doesn't exist`);
}
// Add user until key is found
for (const {key, value} of this.data) {
value.add(user);
if (key === element) {
break;
}
}
// When limit is reached, remove every element until the one that reach the limit
let keyAcked = false;
for (let i = this.data.length - 1; i >= 0; i--) {
const value = this.data[i].value;
const key = this.data[i].key;
if (!keyAcked && value.size === this.limit) {
keyAcked = key;
this.olderAcked = true; // We can assure any older message is already acked
}
if (keyAcked) {
this.data.splice(i, 1);
}
}
this.lastAckedBy[user] = element;
return keyAcked;
}
prependAck(element, user) {
if (this.olderAcked) {
return;
}
// If it is the first ACK for an element, copy oldest one
if (!this.data.find(d => d.key === element)) {
const prevValue = new window.Set(this.data[0] && this.data[0].value);
this.data.unshift({key: element, value: prevValue});
}
// Add user until key is found
for (const {key, value} of this.data) {
value.add(user);
if (key === element) {
break;
}
}
if (!this.lastAckedBy[user]) {
this.lastAckedBy[user] = element;
}
}
isOlderAcked() {
return this.olderAcked;
}
getLastAckedBy(user) {
return this.lastAckedBy[user];
}
}