src/core/User.js
import Webcom from 'webcom/webcom';
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';
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, userId) {
const values = Object.assign({}, snapData.val());
/**
* User's unique id
* @type {string}
*/
// this.uid = snapData.name();
this.uid = userId;
/**
* 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 => room.invite([this], null, message))
.then(data => ({ 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 id1 = Math.floor(Math.random() * 1000);
const id2 = Math.floor(Math.random() * 1000);
const uid = `${id1}/${id2}/${auth.uid}`;
const userUid = `${id1}:${id2}:${auth.uid}`;
// 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, // eslint-disable-line no-undef
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.get(userUid))
.then((user) => {
initializing = false;
return user;
})
.catch((e) => {
Log.e(e);
initializing = false;
return Promise.reject(e);
});
}
// return User.get(uid);
return User.get(userUid);
}
/**
* Disconnect the current user
* @access protected
* @param {User} user The current user
* @returns {Promise}
*/
static disconnect(user) {
// Cancel onDisconnect
// due to the problem of long list, uids have a : instead of /
const userUid = user.uid.replace(/:/g, '/');
// DataSync.onDisconnect(`_/devices/${user.uid}/${cache.device}/status`).cancel();
// DataSync.onDisconnect(`users/${user.uid}`).cancel();
DataSync.onDisconnect(`_/devices/${userUid}/${cache.device}/status`).cancel();
DataSync.onDisconnect(`users/${userUid}`).cancel();
if (user.anonymous) {
return DataSync.remove(`_/devices/${userUid}`)
.then(() => DataSync.get(`_/invites/${userUid}`))
.then((invites) => {
const inviteIds = [];
invites.forEach((invite) => {
inviteIds.push(invite.name());
});
return Promise.all(inviteIds.map(inviteId => DataSync.remove(`_/invites/${userUid}/${inviteId}`)));
})
// TODO refactor data model for invites so we can delete _/invites/${user.uid}
// .then(() => DataSync.remove(`_/invites/${user.uid}`))
.then(() => DataSync.remove(`users/${userUid}`))
.then(() => {
Webcom.INTERNAL.PersistentStorage.remove(userUid);
})
.catch(Log.r('User#anonymous_disconnect'));
}
return DataSync.set(`_/devices/${userUid}/${cache.device}/status`, NOT_CONNECTED)
.then(() => DataSync.get(`_/devices/${userUid}`))
.then((devices) => {
// Only change user's status if no other device connected
const hasConnectedDevices = devices.forEach(device => (
new RegExp(`^${CONNECTED}$`)
).test(device.val().status));
if (!hasConnectedDevices) {
return DataSync.update(`users/${userUid}`, { 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) {
// due to the problem of long list, uids have a : instead of /
const userUid = uid.replace(/:/g, '/');
return DataSync.get(`users/${userUid}`)
// .then(snapData => snapData ? new User(snapData, newUid) : null)
.then(snapData => (snapData ? new User(snapData, uid) : null))
.catch(Log.r('User#get'));
}
}