Home Reference Source

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