src/Reach.js
import Webcom from 'webcom/webcom';
import browser from './definitions/Browser';
import StreamTypes from './definitions/StreamTypes';
import * as Events from './definitions/Events';
import { audio, video } from './definitions/Codec';
import communicationQuality from './definitions/CommunicationQuality';
import User from './core/User';
import Room from './core/Room';
import Invite from './core/Invite';
import * as DataSync from './core/util/DataSync';
import cache from './core/util/cache';
import * as Log from './core/util/Log';
import Media from './core/util/Media';
/**
* Entry point for Reach SDK
* @public
*/
export default class Reach {
/**
* Create Reach's data structure where the url points to (might not be the root of your namespace)
* @public
* @param {string|Webcom} url The url of your namespace or an existing Webcom reference.
* @param {Config} [cfg] Reach configuration. You can pass constraints here
* @example <caption>Init with the default configuration</caption>
* var myReach = new Reach('https://io.datasync.orange.com/base/<my_namespace>');
* @example <caption>Init and set constraints for SD video and logLevel to 'info'</caption>
* var myReach = new Reach('https://io.datasync.orange.com/base/<my_namespace>', {
* constraints: Reach.media.constraints('SD'),
* logLevel: 'INFO'
* });
*/
constructor(url, cfg = null) {
// Set shared reference
cache.base = url;
// Set shared configuration
cache.config = cfg;
/**
* List of declared callbacks
* @type {{}}
* @private
*/
this._callbacks = {};
}
/**
* Get versions of SDK and DataModel.
* The Schema version can be used to determine compatibility with the Android & iOS SDK.
* @return {{sdk: string, schema: string}}
*/
static get version() {
return { sdk: SDK_VERSION, schema: SCHEMA_VERSION }; // eslint-disable-line no-undef
}
/**
* The supported stream types
* @returns {StreamTypes}
*/
static get types() {
return StreamTypes;
}
/**
* The supported events
* @return {{
room: Events/Room,
reach: Events/Reach,
stream: Events/Stream,
invite: Events/Invite,
local: Events/Local
}}
*/
static get events() {
return {
room: Events.room,
reach: Events.reach,
stream: Events.stream,
invite: Events.invite,
local: Events.local
};
}
/**
* The browser's details
* @return {Browser}
*/
static get browser() {
return browser;
}
/**
* Media utility functions
* @return {Media}
*/
static get media() {
return Media;
}
/**
* The codec presets to use when setting {@link Config#preferredAudioCodec}
* or {@link Config#preferredVideoCodec}
* @return {{audio: Codec/audio, video: Codec/video}}
*/
static get codecs() {
return { audio, video };
}
/**
* The bitrate presets to use when setting {@link Config#communicationQuality}
* @return {communicationQuality}
*/
static get communicationQuality() {
return communicationQuality;
}
/**
static get audioBitrateMax() {
* The audio bitrate maximum {@link Config#audioBitrateMax}
* @return {audioBitrateMax}
*/
static get audioBitrateMax() {
return cache.config.audioBitrateMax;
}
/**
static get videoBitrateMax() {
* The audio bitrate maximum {@link Config#videoBitrateMax}
* @return {videoBitrateMax}
*/
static get videoBitrateMax() {
return cache.config.videoBitrateMax;
}
/**
* DataSync reference
* @type {Webcom}
*/
get base() {
return cache.base;
}
/**
* The configuration
* @type {Config}
*/
get config() {
return cache.config;
}
/**
* The connected User
* @type {User}
*/
get current() {
return cache.user;
}
/**
* Register & Sign-in as a new user
* @param {string} email The email of the user
* @param {string} password The password of the user
* @param {string} [name] The display name of the user (defaults to email)
* @param {boolean} [rememberMe=false] keep user connected ?
* @returns {Promise<User>}
*/
register(email, password, name, rememberMe) {
return cache.base.createUser(email, password)
.then((auth) => {
if (auth) {
return this.login(email, password, name || email, rememberMe);
}
return null;
})
.catch(Log.r('Reach~register'));
}
/**
* Sign-in an existing user
* @param {string} email The email of the user
* @param {string} password The password of the user
* @param {string} [name] The name of the user. Defaults to the value in base.
* @param {boolean} [rememberMe=false] keep user connected ?
* @returns {Promise<User>}
*/
login(email, password, name, rememberMe = false) {
// Force logout to bypass Webcom bug
let p = Promise.resolve();
if (this.current && this.current.email !== email) {
p = this.logout();
}
return p
.then(() => cache.base.authWithPassword({ email, password, rememberMe }))
.then(auth => User.init(auth, name))
.then((u) => {
cache.user = u;
return u;
})
.catch(Log.r('Reach~login'));
}
/**
* Resume previous session
* @returns {Promise<User>}
*/
resume() {
return new Promise((resolve, reject) => {
// Resume session
if (Webcom.INTERNAL.PersistentStorage.get('session')) {
cache.base.resume((error, auth) => {
if (auth && !this.current) {
User.init(auth).then((u) => {
cache.user = u;
resolve(u);
}, reject);
}
});
} else {
reject(new Error('No session to resume'));
}
});
}
/**
* Sign-in an anonymous user
* @param {string} name The display name of the user
* @returns {Promise<User>}
*/
anonymous(name) {
// Force logout to bypass Webcom bug
let p = Promise.resolve();
if (this.current && (!this.current.anonymous || this.current.name !== name)) {
p = this.logout();
}
return p
.then(() => cache.base.authAnonymously())
.then(auth => User.init(auth, name))
.then((u) => {
cache.user = u;
return u;
})
.catch(Log.r('Reach~anonymous'));
}
/**
* Logout current user
* @returns {Promise}
*/
logout() {
return new Promise((resolve, reject) => {
let p = Promise.resolve();
if (this.current != null) {
p = User.disconnect(this.current);
}
p
.then(() => {
Object.keys(this._callbacks).forEach(
event => DataSync.off(Events.reach.toPath(event)(cache.user), event)
);
cache.base.logout(() => {
cache.user = null;
resolve();
});
})
.catch((e) => {
Log.e(e);
reject(e);
});
});
}
/**
* Get the list of registered users
* @ignore If your users base is pretty large, this method is impossible.
* @param {boolean} [include=false] Include current user in user's list
* @return {Promise<User[], Error>}
*/
users(include) {
if (!this.current) {
return Promise.reject(new Error('Only an authenticated user can list Users.'));
}
return DataSync.list('users', User)
.then(users => (!include && users && this.current
? users.filter(user => user.uid !== this.current.uid)
: users))
.catch(Log.r('Reach~users'));
}
/**
* Get the list of rooms
* @ignore If your users base is pretty large, this method is impossible.
* @return {Promise<Room[], Error>}
*/
rooms() {
if (!this.current) {
return Promise.reject(new Error('Only an authenticated user can list Rooms.'));
}
return DataSync.list('rooms', Room)
.catch(Log.r('Reach~rooms'));
}
/**
* Get the list of invites
* @return {Promise<Invite[], Error>}
*/
invites() {
if (!this.current) {
return Promise.reject(new Error('Cannot list invites without a User being logged in.'));
}
return DataSync.list(`_/invites/${this.current.uid}`, Invite)
.catch(Log.r('Reach~invites'));
}
/**
* Register a callback for a specific event
* @param {string} event The event name ({@link Events/Reach}). Can be:
* - USER_ADDED
* - USER_CHANGED
* - USER_REMOVED
* - ROOM_ADDED
* - ROOM_CHANGED
* - ROOM_REMOVED
* - INVITE_ADDED
* - INVITE_CHANGED
* @param {function} callback The callback for the event,
* the arguments depends on the type of event:
* - USER_*: callback({@link User} u)
* - ROOM_*: callback({@link Room} r)
* - INVITE_*: callback({@link Invite} i)
* @param {Webcom/api.Query~cancelCallback} [cancelCallback] The error callback for the event,
* takes an Error as only argument
*/
on(event, callback, cancelCallback) {
const path = Events.reach.toPath(event)(cache.user);
if (path) {
const Cls = Events.reach.toClass(event);
const cb = (snapData) => {
const d = Cls ? new Cls(snapData) : null;
Log.i(`Reach~on(${event})`, d);
callback(d);
};
DataSync.on(path, event, cb, cancelCallback);
if (!this._callbacks[event]) {
this._callbacks[event] = [];
}
this._callbacks[event].push(cb);
}
}
/**
* Create a new room
* @param {string} [name] The room name
* @param {object} [extra] Extra informations
* @param {boolean} [publicRoom=false] Indicates public room
* @returns {Promise<Room>}
*/
createRoom(name, extra, publicRoom = false) {
if (!this.current) {
return Promise.reject(new Error('Cannot create a Room without a User being logged in.'));
}
return Room.create(name, extra, publicRoom);
}
/**
* Get a list of all opened {@link PeerConnection}s
* @return {*}
*/
get peerConnections() {
return cache.peerConnections.stacks;
}
/**
* Get a {@link Room} from its `uid`
* @param {string} uid The room's UID
* @returns {Promise.<Room>}
*/
getRoom(uid) {
return Room.get(uid);
}
/**
* Get a {@link User} from its `uid`
* @param {string} uid The user's UID
* @returns {Promise.<User>}
*/
getUser(uid) {
return User.get(uid);
}
}
module.exports = Reach;