Home Reference Source

src/users/UserManager.js

import {EventEmitter} from '../eventemitter';
import {bindMethods} from '../utils';
import {ResolveQueue} from './ResolveQueue';
import {
	UserCollection,
	USER_ADDRESS_PATTERN,
	USER_ADDRESS_PREFIX,
} from './UserCollection';

/** @private */
export const Events = {
	/** Resolved users changed event */
	UPDATED: 'updated',
};

/**
 * Element that enables user resolution.
 * Previously resolved users are stored in a local collection that is updated
 * when some of the requested addresses change.
 * @private
 */
export class UserManager extends EventEmitter {

	/**
	 * Constructs an users manager
	 * @protected
	 */
	constructor(wacProxy) {
		super();

		/**
		 * Property used to determine if UserManager has been intialized
		 * and wait until initialization success.
		 * @private
		 * @type {Promise}
		 */
		this._init = null;

		/**
		 * WacProxy instance.
		 * @private
		 * @type {WacProxy}
		 */
		this._stack = wacProxy;

		/**
		 * Collection of already resolved Users.
		 * @private
		 * @type {UserCollection}
		 */
		this._collection = null;

		/**
		 * Queue that resolve addresses through WacProxy.
		 * @private
		 * @type {ResolveQueue}
		 */
		this._resolveQueue = null;

		this.setMaxListeners(0);

		bindMethods(this, [
			'onResolveUpdated',
		]);
	}

	/**
	 * Initialize UsersManager.
	 * It retrieves already resolved Users and subscribed addresses from WacProxy
	 * and subscribes to resolution updates.
	 * @return {Promise} resolved after intialization.
	 */
	init() {
		if (!this._init) {
			this._stack.getResolver().emitter.on('update', this.onResolveUpdated);
			this._init = (async () => {
				const items = await this._stack.getResolver().getResolvedUsers();
				this._collection = new UserCollection(this._stack.getCurrentSession().domain);
				this._collection.updateUsers(items.resolved, items.subscribed);
				this._resolveQueue = new ResolveQueue(this._stack, this._collection);
			})();
		}
		return this._init;
	}

	/**
	 * Deinitialize UsersManager.
	 */
	uninit() {
		this._stack.getResolver().emitter.off('update', this.onResolveUpdated);
		this._init = null;
		this._collection = null;
		this._resolveQueue = null;
	}

	/**
	 * Get the address associated with given user Id.
	 * @param {String} userId The user identifier used to build the user address.
	 * @return {String} The user address.
	 */
	getAddress(userId) {
		return `${USER_ADDRESS_PREFIX}${userId}`;
	}

	/**
	 * Get the userId associated with given user address.
	 * @param {String} address A user address.
	 * @return {String} The user ID of that user address.
	 */
	getUserId(address) {
		return address.replace(USER_ADDRESS_PATTERN, '$2');
	}

	/**
	 * Checks if given address is a WAC user address
	 * @param {String} address the address to check.
	 * @return {Boolean} true if given address is a WAC user address.
	 */
	isAddress(address) {
		return USER_ADDRESS_PATTERN.test(address);
	}

	/**
	 * Obtains own user WAC address
	 * @return {String} address of current user
	 */
	getOwnAddress() {
		return this.getAddress(this._stack.getCurrentSession().user);
	}

	/**
	 * Checks if given address is own WAC user address
	 * @param {String} address the address to check.
	 * @return {Boolean} true if given address is own WAC user address.
	 */
	isOwnAddress(address) {
		return this.getUserId(address) === this._stack.getCurrentSession().user;
	}

	/**
	 * Request an address resolution.
	 * When there is an user in local collection that resolves given address, such user
	 * will be returned.
	 * Otherwise a resolution will be requested through resolve queue to WacStack. If resolution success
	 * local collection will be updated.
	 * @param {String} address The address to be resolved.
	 * @return {Promise<User|undefined>} resolved user or undefined if there is no user that
	 * conforms to given address.
	 */
	async resolveUser(address) {
		if (!this._init) {
			throw new Error('not initialized');
		}
		await this._init;
		return this._collection.resolve(address) || this._resolveQueue.resolve(address);
	}

	/**
	 * Returns an user username given a gateway username.
	 * @param {String} gatewayUsername The gateway username to search.
	 * @param {Boolean} [ignoreErrors=true] When true, if no user with such gateway
	 * username is found, gatewayUsername value will be returned instead an error.
	 * @return {Promise<String>} User username on success.
	 */
	getUsername(gatewayUsername, ignoreErrors = true) {
		let localUser = this._collection.resolve(gatewayUsername);
		if (localUser && localUser.gatewayUsername === gatewayUsername) {
			return Promise.resolve(localUser.username);
		}
		return this._stack.getResolver().getUserByGatewayUsername(gatewayUsername)
			.then(userId => this.resolveUser(userId))
			.then(user => user.username)
			.catch((error) => {
				if (ignoreErrors) {
					return gatewayUsername;
				}
				throw error;
			});
	}

	/**
	 * Returns the gateway username associated with an user username.
	 * @param {String} username The user's username.
	 * @param {Boolean} [ignoreErrors=true] When true, if no user is found,
	 * username value will be returned instead an error.
	 * @return {Promise<String>} The gateway username on success.
	 */
	getGatewayUsername(username, ignoreErrors = true) {
		return this.resolveUser(username)
			.then(user => user.gatewayUsername ? user.gatewayUsername : this._stack.getResolver().getGatewayUsernameByUser(user.id))
			.catch((error) => {
				if (ignoreErrors) {
					return username;
				}
				throw error;
			});
	}

	getGatewayUsernames(usernames, ignoreErrors = true) {
		return Promise.all(usernames.map(username => this.getGatewayUsername(username, ignoreErrors)));
	}

	/**
	 * Callback called when WacProxy notifies that resolved and subscribed items
	 * have changed. This can happen:
	 *  - When a resolved user changes some of their fields or ceases to exist.
	 *  - When a new user is created and satisfies one of the addresses to which we have subscribed
	 * @private
	 * @param {Object} items object that contains resolved users and subscribed addresses.
	 */
	onResolveUpdated(items) {
		this._collection.updateUsers(items.resolved, items.subscribed);
		this.emit(Events.UPDATED);
	}
}