Home Reference Source

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,
		};
	}
}