src/contacts/Contact.js
import {EventEmitter} from '../eventemitter';
import {Events as UserManagerEvents} from '../users/UserManager';
import {BaseContact} from './BaseContact';
import {Presence} from './Presence';
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 {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.avatar
* @param {Presence} [data.presence]
* @param {string} [data.userId]
*/
constructor(stack, userManager, presenceManager, data = {}) {
super();
/**
* @type {WacStack}
*/
this._stack = stack;
/**
* @type {UserManager}
*/
this._userManager = userManager;
/**
* @type {PresenceManager}
*/
this._presenceManager = presenceManager;
/**
* @type {BaseContact}
*/
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._avatar = data.avatar || '';
this._userId = data.userId;
/**
* @type {object}
*/
this._user = null;
if (data.presence) {
this._presence = data.presence;
}
if (this._userManager) {
this._hydrateUserUsingId();
this._userManager.on(UserManagerEvents.UPDATED, this._hydrateUserUsingId.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() {
const isAnonymousUser = this.isWacUser && this._user.id === this._user.username;
return isAnonymousUser ? [] : this._phones;
}
set phones(phones) {
this._phones = phones;
updateContact(this._base.getId(), this._stack, this.toJSON());
}
get phone() {
return this.phones.length ? this.phones[0].address : undefined;
}
set phone(phone) {
this._addPhone('', phone);
updateContact(this._base.getId(), this._stack, this.toJSON());
}
_addPhone(name, address) {
const phones = this.phones.filter(p => p.name !== name && p.address !== address);
this._phones = [{name, address}, ...phones];
}
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 || [];
return defaultEmail.email;
}
set email(email) {
const emailsRepeatedRemoved = this._emails
? this._emails.filter(e => !!e.type || e.email !== email)
: [];
this.emails = [{email}, ...emailsRepeatedRemoved];
}
/**
* 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);
}
/*
* @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);
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;
}
getWacUserAddress() {
return this._user ? this._user.getAddress() : undefined;
}
set userId(userId) {
if (this._userId !== userId) {
this._userId = userId;
this._hydrateUserUsingId();
}
}
get userId() {
return this._userId;
}
/**
* 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.userId = contact.userId;
this.emit('update');
}
/**
* @private
*/
async _hydrateUserUsingId() {
if (this._userId) {
const user = await this._userManager.resolveUser(this._userId);
this._updateUser(user);
}
}
/*
* @private
*/
async _updateUser(user) {
if (!user && this._isEmptyContact()) {
this.name = this._userId;
this.phone = this._userId;
this._userId = undefined;
this.emit('update');
return;
}
if (user && this._user !== user) {
this._user = user;
this._addPhone('', this.address);
if (this._presence) {
this._presence.address = user.getAddress();
}
this.emit('update');
}
}
_isEmptyContact() {
return !this.name && this.phones.length === 0;
}
/**
* 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,
isWacUser: this.isWacUser,
};
}
}