Home Manual Reference Source

src/core/User.js

import * as DataSync from './util/DataSync';
import cache from './util/cache';
import * as Log from './util/Log';
import Room from './Room';
import Device from './Device';
import {CONNECTED, NOT_CONNECTED} from './util/constants';
import Webcom from 'webcom/webcom';

let initializing = false;

/**
 * User informations
 * @public
 */
export default class User {
	/**
	 * Create a user
	 * @param {Webcom/api.DataSnapshot|object} snapData The data snapshot
	 * @access protected
	 */
	constructor(snapData) {
		const values = Object.assign({}, snapData.val());
		/**
		 * User's unique id
		 * @type {string}
		 */
		this.uid = snapData.name();
		/**
		 * User's display name
		 * @type {string}
		 */
		this.name = values.name;
		/**
		 * User's status
		 * @type {string}
		 */
		this.status = values.status;
		/**
		 * User's last know connection ts
		 * @type {number}
		 */
		this.lastSeen = values.lastSeen;
		/**
		 * Indicates if the user is an anonymous user
		 * @type {boolean}
		 */
		this.anonymous = /^anonymous/.test(values.provider);
		// TODO #Feat: Add 'extra' property for unrestricted additional information ?
	}

	/**
	 * Invite a user directly. This will create a new Room, log you in it & invite the user.
	 * @param {string} [message] a message to add to the invite
	 * @return {Promise<{room: Room, invite: Invite}, Error>}
	 */
	invite(message) {
		if(!cache.user) {
			return Promise.reject(new Error('Only an authenticated user can invite another User.'));
		}
		return Room.create(`${cache.user.uid}-${this.uid}`)
			.then(room => {
				return room.invite([this], null, message);
			})
			.then(data => {
				return {room: data.room, invite: data.invites[0]};
			})
			.catch(Log.r('User~invite'));
	}

	/**
	 * List Users's devices. Only for current user.
	 * @access protected
	 * @return {Promise<Device[], Error>}
	 */
	devices() {
		return DataSync.list(`_/devices/${this.uid}`, Device);
	}

	/**
	 * Init the current user
	 * @access protected
	 * @param {json} auth The user's identity (webcom JSON structure)
	 * @param {string} [name] The user's display name
	 * @returns {Promise<User, Error>}
	 */
	static init (auth, name) {
		const uid = auth.uid;
		if(!initializing) {
			initializing = true;
			const d = {status: CONNECTED, lastSeen: DataSync.ts(), provider: auth.provider};
			if(name) {
				Object.assign(d, {name});
			}
			let deviceId = Webcom.INTERNAL.PersistentStorage.get(uid);
			return DataSync.update(`users/${uid}`, d)
				// Register current device
				.then(() => {
					const deviceMetadata = {
						status: CONNECTED,
						sdk: {
							reach: SDK_VERSION,
							webcom: Webcom.SDK_VERSION
						},
						userAgent: navigator.userAgent
					};
					cache.userAgent = deviceMetadata.userAgent;
					if(deviceId) {
						return DataSync.update(`_/devices/${uid}/${deviceId}`, deviceMetadata);
					}
					return DataSync.push(`_/devices/${uid}`, deviceMetadata);
				})
				// Save device
				.then(deviceRef => {
					if (!deviceId) {
						deviceId = deviceRef.name();
						Webcom.INTERNAL.PersistentStorage.set(uid, deviceId);
					}
					cache.device = deviceId;
				})
				// Add onDisconnect actions
				.then(() => {
					// Disconnect device
					DataSync.onDisconnect(`_/devices/${uid}/${deviceId}/status`).set(NOT_CONNECTED);
					// Update user status
					DataSync.onDisconnect(`users/${uid}`).update({
						status: NOT_CONNECTED,
						lastSeen: DataSync.ts()
					});
				})
				// Get user
				.then(() => User.get(uid))
				.then(user => {
					initializing = false;
					return user;
				})
				.catch(e => {
					Log.e(e);
					initializing = false;
					return Promise.reject(e);
				});
		}
		return User.get(uid);
	}

	/**
	 * Disconnect the current user
	 * @access protected
	 * @param {User} user The current user
	 * @returns {Promise}
	 */
	static disconnect(user) {
		// Cancel onDisconnect
		DataSync.onDisconnect(`_/devices/${user.uid}/${cache.device}/status`).cancel();
		DataSync.onDisconnect(`users/${user.uid}`).cancel();
		if(user.anonymous) {
			return DataSync.remove(`_/devices/${user.uid}`)
				.then(() => DataSync.get(`_/invites/${user.uid}`))
				.then(invites => {
					const inviteIds = [];
					invites.forEach(invite => {
						inviteIds.push(invite.name());
					});
					return Promise.all(inviteIds.map(inviteId => DataSync.remove(`_/invites/${user.uid}/${inviteId}`)));
				})
				// TODO refactor data model for invites so we can delete _/invites/${user.uid}
				// .then(() => DataSync.remove(`_/invites/${user.uid}`))
				.then(() => DataSync.remove(`users/${user.uid}`))
				.then(() => {
					Webcom.INTERNAL.PersistentStorage.remove(user.uid);
				})
				.catch(Log.r('User#anonymous_disconnect'));
		}
		return DataSync.set(`_/devices/${user.uid}/${cache.device}/status`, NOT_CONNECTED)
			.then(() => DataSync.get(`_/devices/${user.uid}`))
			.then(devices => {
				// Only change user's status if no other device connected
				const hasConnectedDevices = devices.forEach(device => {
					return (new RegExp(`^${CONNECTED}$`)).test(device.val().status);
				});
				if(!hasConnectedDevices) {
					return DataSync.update(`users/${user.uid}`, {status: NOT_CONNECTED});
				}
				return true;
			})
			.catch(Log.r('User#disconnect'));
	}

	/**
	 * Get a user by its uid
	 * @access private
	 * @param {string} uid The user's uid
	 * @returns {Promise<User, Error>}
	 */
	static get(uid) {
		return DataSync.get(`users/${uid}`)
		.then(snapData => snapData ? new User(snapData) : null)
		.catch(Log.r('User#get'));
	}
}