src/contacts/Contact.js
import {EventEmitter} from '../eventemitter';
import {
Events as UserManagerEvents,
} from '../users/UserManager';
import {Presence} from './Presence';
import {BaseContact} from './BaseContact';
import {removeContact, updateContact} from './utils';
/**
* Represents a contact. This class must not be directly instantiated. Instead,
* instances of this class are obtained by calling the {@link ContactManager#createContact}
* or {@link ContactManager#getContacts} methods of {ContactManager}
*
* ## Events
*
* - **`update`** is emitted every time the contact is updated.
* - **`delete`** is emitted when the contact is deleted.
*/
export class Contact extends EventEmitter {
/**
* Constructs a new contact
* @protected
* @param {WacStack} stack
* @param {UserManager} userManager
* @param {PresenceManager} presenceManager
* @param {FavoriteContact} favoriteManager
* @param {Object} data
* @param {string} data.id
* @param {string} data.name
* @param {Object[]} data.phones
* @param {Object[]} data.emails
* @param {string} data.source
* @param {boolean} data.favorite
* @param {string} data.deviceId
* @param {string} data.contactDeviceId
* @param {string} data.avatar
* @param {Presence} [data.presence]
*/
constructor(stack, userManager, presenceManager, favoriteManager, data = {}) {
super();
/**
* @type {WacStack}
*/
this._stack = stack;
/**
* @type {UserManager}
*/
this._userManager = userManager;
/**
* @type {PresenceManager}
*/
this._presenceManager = presenceManager;
/**
* @type {FavoriteContact}
*/
this._favoriteManager = favoriteManager;
/**
* @type {BaseContact}
* @private
*/
this._base = BaseContact.create(data);
/**
* @type {Object[]}
*/
this._phones = data.phones || [];
/**
* @type {Object[]}
*/
this._unnormalizedPhones = data.unnormalizedPhones || [];
/**
* @type {Object[]}
*/
this._emails = data.emails || [];
/**
* @type {Presence}
*/
this._presence = false; // Lazy initialization in the getter
/**
* @type {string}
*/
this._deviceId = data.deviceId || '';
/**
* @type {string}
*/
this._contactDeviceId = data.contactDeviceId || '';
/**
- * @type {string}
- */
this._avatar = data.avatar || '';
/**
* @type {object}
*/
this._user = null;
if (data.presence) {
this._presence = data.presence;
}
if (this._userManager) {
this._updateUser();
this._userManager.on(UserManagerEvents.UPDATED, this._updateUser.bind(this));
}
}
/**
* Contact unique identifier
* @type {string}
*/
get id() {
return this._base.getId();
}
/**
* Name associated with this contact
* @type {string}
*/
get name() {
return this._base.getName();
}
/**
* Display name associated with this contact
* @type {string}
*/
set name(name) {
this._base.setName(name);
updateContact(this._base.getId(), this._stack, this.toJSON());
}
/**
* Returns the address associated with the user represented by this contact
* @return {string}
*/
get address() {
if (!this.isWacUser) {
throw Error('Not implemented for contacts without associated user');
}
return this._user.username + '@' + this._user.domain;
}
get phones() {
// Empty phones for anonymous users
if (this.isWacUser && this._user.id === this._user.username) {
return [];
}
return this._phones;
}
set phones(phones) {
this._phones = phones;
this._updateUser();
updateContact(this._base.getId(), this._stack, this.toJSON());
}
get phone() {
return this.phones.length ? this.phones[0].address : undefined;
}
set phone(phone) {
this.phones = [{name: '', address: phone}, ...this.phones.filter(p => p.name !== '')];
}
get unnormalizedPhones() {
return this._unnormalizedPhones;
}
set unnormalizedPhones(unnormalizedPhones) {
this._unnormalizedPhones = unnormalizedPhones;
}
get emails() {
return this._emails;
}
set emails(emails) {
this._emails = emails;
updateContact(this._base.getId(), this._stack, this.toJSON());
}
get email() {
const defaultEmail = this.emails.find(email => email.type === '');
return defaultEmail ? defaultEmail.email : undefined;
}
set email(email) {
this.emails = [{type: '', email}, ...this.emails.filter(p => p.type !== '')];
}
/**
* Presence associated with this contact
* @type {Presence}
*/
get presence() {
if (!this._presence) {
const address = this._user ? this._user.getAddress() : undefined;
this._presence = new Presence(this._presenceManager, address);
}
return this._presence;
}
/**
* Source name of the contact's source
* @type {string}
*/
get source() {
return this._base.getSource();
}
/* Source the contact belongs to
* @type {string}
*/
set source(source) {
this._base.setSource(source);
updateContact(this._base.getId(), this._stack, this.toJSON());
}
/*
* @type {boolean}
*/
get isErasable() {
return this.isEditable;
}
/*
* @type {boolean}
*/
get isEditable() {
return !['static', 'domain', 'users-group'].includes(this.source);
}
/**
* Removes the contact
* @return {Promise<undefined,Error>}
*/
remove() {
return removeContact(this._base.getId(), this._stack)
.then(() => this._remove());
}
/*
* @type {boolean}
*/
get favorite() {
return this._base.isFavorite();
}
/**
* Whether the contact is favorite
* @type {boolean}
*/
set favorite(favorite) {
this._base.setFavorite(favorite);
if (favorite) {
this._favoriteManager.saveFavorite(this.contactDeviceId);
} else {
this._favoriteManager.removeFavorite(this.contactDeviceId);
}
updateContact(this._base.getId(), this._stack, this.toJSON());
}
get avatar() {
return this._avatar || this.presence.avatar;
}
set avatar(value) {
this._avatar = value;
}
get isWacUser() {
return !!this._user;
}
get deviceId() {
return this._deviceId;
}
set deviceId(value) {
this._deviceId = value;
}
get contactDeviceId() {
return this._contactDeviceId;
}
set contactDeviceId(value) {
this._contactDeviceId = value;
}
getWacUserAddress() {
return this._user ? this._user.getAddress() : undefined;
}
/**
* Updates the contact with the new data
* @protected
* @param {Contact} contact
*/
_update(contact) {
this._base.setId(contact.id);
this._base.setName(contact.name);
this._base.setFavorite(contact.favorite);
this._base.setSource(contact.source);
this._phones = contact.phones;
this._unnormalizedPhones = contact.unnormalizedPhones;
this._emails = contact.emails;
this._updateUser();
this._deviceId = contact.deviceId;
this._contactDeviceId = contact.contactDeviceId;
this.emit('update');
}
/**
* @private
*/
_updateUser() {
return Promise.all(this.phones.map((phone) => {
return this._userManager.resolveUser(phone.address).then((user) => {
return {user: user, phone: phone};
});
})).then((items) => {
let user;
let phone;
let phones = [];
items.forEach((item) => {
if (!user && !!item.user) {
user = item.user;
phone = item.phone;
} else {
phones.push(item.phone);
}
});
let emitUpdate = false; // Avoid emitting the event two times
if (user) {
this._phones = [phone].concat(phones);
emitUpdate = true;
}
if (this._user !== user) {
this._user = user;
if (this._presence) {
this._presence.address = this._user ? this._user.getAddress() : null;
}
emitUpdate = true;
}
if (emitUpdate) {
this.emit('update');
}
});
}
/**
* Removes (locally) the contact
* @protected
*/
_remove() {
this._stack = null;
this._presence = null;
this.emit('delete');
}
/**
* Returns a JS object with the JSON serialization representation of this object
*/
toJSON() {
return {
id: this.id,
name: this.name,
source: this.source,
favorite: this.favorite,
phones: this.phones,
unnormalizedPhones: this.unnormalizedPhones,
emails: this.emails,
deviceId: this.deviceId,
contactDeviceId: this.contactDeviceId,
isWacUser: this.isWacUser,
};
}
}