src/Clinical6.js
import Client from './Client';
import Device from './helpers/Device';
import Storage from './helpers/Storage';
import UserService from './services/UserService';
import SystemService from './services/SystemService';
import { isEmail } from './utilities/FormatUtility';
import { serviceFactory } from './utilities/factories/ServiceFactory';
import { validate, isRequired, hasAttribute } from './utilities/ValidationUtility';
/**
* Client to the Clinical6 instance
*
* @example
* Clinical6 client = new Clinical6("https://mysite.captivereach.com");
* await client.guestSignIn('thisismydeviceid', 'ios', 'thisismypushid');
* console.log(client.authToken);
*/
class Clinical6 {
/**
* Constructs the class with baseUrl provided
*
* @param {String} [apiBaseUrl = 'http://localhost'] - Base URL the (sub)domain of the Captive Reach server. Example: 'https://dhsadobe.captivereach.com'
*/
constructor() {
this.apiBaseUrl = 'http://localhost';
/** @type {Class} */
this.Client = Client.instance;
// Storage
/** @type {StorageUtility} */
// this.storageUtility = Client.instance.storageUtility;
/** @type {Storage} */
this.storage = new Storage();
}
/**
* Gets the apiBaseUrl
*
* @type {String}
* @return {String} Current apiBaseUrl
*/
get apiBaseUrl() {
/** @type {String} */
this._apiBaseUrl = Client.instance.apiBaseUrl;
return this._apiBaseUrl;
}
/**
* Sets the apiBaseUrl
*
* @type {String}
* @return {String} Current apiBaseUrl
*/
set apiBaseUrl(apiBaseUrl) {
this._apiBaseUrl = apiBaseUrl;
Client.instance.apiBaseUrl = this._apiBaseUrl;
}
/**
* Gets the authToken from Client.instance.authToken
*
* @type {String}
* @param {String} authToken
*/
get authToken() {
/** @type {String} */
this._authToken = Client.instance.authToken;
return this._authToken;
}
/**
* Sets the authToken for Client.instance.authToken
*
* @type {String}
* @param {String} authToken
*/
set authToken(authToken) {
this._authToken = authToken;
Client.instance.authToken = this._authToken;
}
/**
* Gets the configuration information
*
* @type {Object}
* @return {Object} data - the information in a key value object
*/
get config() {
return Client.instance.config;
}
/**
* Sets the configuration information
*
* @type {Object}
* @param {Object} data - the information in a key value object
*/
set config(data) {
Client.instance.config = data;
}
/**
* Gets the device from Client.instance.device
*
* @type {Device}
*/
get device() {
return Client.instance.device;
}
/**
* Sets the device for Client.instance.device
*
* @type {Device}
* @param {Device} device
*/
set device(device) {
Client.instance.device = device;
}
/**
* Gets the mobileApplicationKey from Client.instance.mobileApplicationKey
*
* @type {String}
*/
set mobileApplicationKey(mobileApplicationKey) {
Client.instance.mobileApplicationKey = mobileApplicationKey;
}
/**
* Sets the mobileApplicationKey for Client.instance.mobileApplicationKey
*
* @type {String}
* @param {String} device
*/
get mobileApplicationKey() {
return Client.instance.mobileApplicationKey;
}
/**
* Gets the user from Client.instance.user
*
* @type {User}
*/
get user() {
return Client.instance.user;
}
/**
* Sets the user for Client.instance.user
*
* @type {User}
* @param {MobileUser} user
*/
set user(user) {
Client.instance.user = user;
}
/**
* Deletes an object
* @param {Object} [obj] - Object used for this action
* @param {String} [cacheMode] - Override the caching method for this call
* @return {Promise} - Returns a promise via ajax call.
*
* @example
* import { clinical6, Site, Client } from 'clinical6';
*
* // Removes obj from server and local storage
* const obj = new Site({...}); // Site for example
* clinical6.delete(obj);
*
* // No longer in storage
* Client.instance.storageUtility.has('sites', { id: obj.id });
*
* // Can also be done using a json description
* clinical6.delete({ id: 5, type: '23', options: { cascading: true }});
*/
async delete(obj, cacheMode = 'networkOnly') {
validate('Clinical6.delete',
isRequired({ obj }),
hasAttribute({ obj }, 'type'));
const service = serviceFactory.get(obj.type);
return service.delete(obj, { cacheMode });
}
/**
* Prepare and Fetch HTTP request to API
*
* @param {String} url - Path to the endpoint starting with '/'
* @param {String} [method] - HTTP Method (DELETE|GET|POST|PUT)
* @param {Object} [data] - JSON payload, required on POST and PUT calls.
* @return {Promise} - Resolves on HTTP 200. Rejects on all else.
*/
fetch(url, method = 'get', data) {
return Client.instance.fetch(url, method, data);
}
/**
* Updates an object
* @throws {Clinical6Error} - If missing token or missing required parameters
* @param {Object} [params] - Parameters used to get information from server
* @param {Number} [params.id] - Id to get data from the server
* @param {String} [params.type] - Type to be used for storage
* @param {Object} [params.filters] - Filters to be used for get
* @param {Object} [options] - Options to modify or change behavior/results of the call
* @param {String} [options.cacheMode] - Override the caching method for this call
* @param {String} [options.meta] - Pass through and update by reference meta data
* @return {Promise} - Returns a promise via ajax call.
*
* @example
* import { clinical6, Setting, Site, Client } from 'clinical6';
*
* // You can get sites using an existing instantiated object
* const obj = new Site({...}); // Site for example
* clinical6.get(obj).then(sites => console.log(sites)); // will get all sites
*
* // If id exists, it will return only one site
* obj.id = 5;
* clinical6.get(obj).then(site => console.log(site)); // will get site where id = 5
*
* // Using the string of the type
* clinical6.get('mobile_users').then(users => console.log(users)); // will return all mobile users
*
* // Using the class name directly
* clinical6.get(Setting).then(settings => console.log(settings)); // will return all settings
*
* // Adding filters and cache mode
* clinical6.get({ type: 'some_object_type', filters: {}}, 'networkOnly').then(obj => console.log(obj));
*/
async get(params, options = { cacheMode: undefined, meta: {} }) {
let type = '';
if (typeof params === 'string') {
type = params;
} else if (params && params.type) {
({ type } = params);
}
const service = serviceFactory.get(type);
let _params = { type };
if (typeof params === 'object') {
_params = Object.assign({}, _params, params);
}
if (typeof options === 'string') {
options.cacheMode = options;
}
return service.get(_params, options);
}
/**
* Updates an object
* @throws {Clinical6Error} - If missing token or missing required parameters
* @param {Object} parent - Parameters used to get information from server
* @param {Number} parent.id - Id to get data from the server
* @param {String} parent.type - Type to be used for storage
* @param {Object} [child] - Parameters used to get information from server
* @param {Number} [child.id] - Id to get data from the server
* @param {String} [child.type] - Type to be used for storage
* @param {Object} [child.filters] - Filters to be used for get
* @param {Object} [options] - Options to modify or change behavior/results of the call
* @param {String} [options.cacheMode] - Override the caching method for this call
* @param {String} [options.meta] - Pass through and update by reference meta data
* @return {Promise} - Returns a promise via ajax call.
*
* @example
* import { clinical6, SiteMember } from 'clinical6';
*
* clinical6.getChildren(site, { type: 'trials/site_members' });
* clinical6.getChildren(site, new SiteMember());
* clinical6.getChildren(user, { type: 'ediary/entries' }); // generates /v3/mobile_users/:id/ediary/entries
*/
async getChildren(parent, child = {}, options = { cacheMode: undefined, meta: {} }) {
const type = (parent && parent.type) ? parent.type : '';
const service = serviceFactory.get(type);
return service.getChildren(parent, child, options);
}
/**
* Retrieves the profile of the current, authenticated mobile user.
*
* @return {Promise} Promise object that returns success or failure
*/
getProfile() {
return new UserService().getProfile();
}
/**
* Inserts an object
* @param {Object} [obj] - Object used for this action
* @param {String} [cacheMode] - Override the caching method for this call
* @return {Promise} - Returns a promise via ajax call.
*
* @example
* import { clinical6, Site, Client } from 'clinical6';
*
* // Removes obj from server and local storage
* const obj = new Site({...}); // Site for example
* clinical6.insert(obj);
*
* // Now in storage
* Client.instance.storageUtility.has('sites', { id: obj.id });
*/
async insert(obj, cacheMode = 'networkOnly') {
validate('Clinical6.insert',
isRequired({ obj }),
hasAttribute({ obj }, 'type'));
const service = serviceFactory.get(obj.type);
return service.insert(obj, { cacheMode });
}
/**
* Tracks a new event in the log.
*
* @param {Object} [logEntry] - The new log entry to track. (optional)
* @param {String} [logEntry.message] - The message of the log entry. (optional)
* @param {String} [logEntry.tag] - The tag value of the log entry. (optional)
* @param {String} logEntry.level - Indicates the level of importance of the log entry. (required)
* @param {Object} [requestInformation] - The request information of the API call. (optional)
* @param {String} [requestInformation.method] - The method of the request. (optional)
* @param {String} [requestInformation.params] - The body or parameters that were sent on
* the failing/debugging request. (optional)
* @param {String} [requestInformationtry.status_code] - The response of the request. (optional)
* @param {String} [requestInformationtry.url] - The URL of the request. (optional)
* @param {Object} [deviceInformation] - The device information of the device that
* made the request. (optional)
* @param {String} [deviceInformation.app_version] - The version of the app that
* was running on the device. (optional)
* @param {String} [deviceInformation.brand] - The brand of the device. (optional)
* @param {String} [deviceInformation.carrier] - The carrier of the device . (optional)
* @param {String} [deviceInformation.gps_status] - The gps status of the device. (optional)
* @param {Number} [deviceInformation.latitude] - The latitude value where the device was.
* (optional)
* @param {Number} [deviceInformation.longitude] - The longitude value where the device was.
* (optional)
* @param {String} [deviceInformation.manufacturer] - The manufacturer of the device. (optional)
* @param {String} [deviceInformation.network_status] - The network status of the device.
* (optional)
* @param {String} [deviceInformation.os_version] - The OS version of the device. (optional)
* @param {String} [deviceInformation.push_id] - The push ID value of the device. (optional)
* @param {String} [deviceInformation.token] - The token value of the device. (optional)
* @param {String} [deviceInformation.udid] - The UDID value of the device. (optional)
* @param {String} [deviceInformation.technology] - The technology of the device. (optional)
*
* @return {Promise} Promise object that returns success or failure
*
* @example <caption>Log</caption>
* import { systemService } from 'Clinical6';
* systemService.log({
* "message": "MyText",
* "tag": "MyString",
* "level": "Error"
* }, {
* "url": "MyString",
* "method": "MyString",
* "params": "MyText",
* "status_code": "MyString"
* }, {
* "udid": "MyString",
* "token": "MyString",
* "push_id": "MyString",
* "app_version": "MyString",
* "os_version": "MyString",
* "network_status": "MyString",
* "gps_status": "MyString",
* "latitude": "1.5",
* "longitude": "1.5",
* "brand": "MyString",
* "manufacturer": "MyString",
* "carrier": "MyString"
* });
*
* @example <caption>Success Response</caption>
* {
* "status": "ok",
* "message": "Debug Log was successfully created"
* }
*/
log(logEntry, requestInformation, deviceInformation) {
return new SystemService().log(logEntry, requestInformation, deviceInformation);
}
/**
* Register a device for guest, a username, or email
*
* @param {String} [account] - MobileUser object in which to insert
* @param {String} [password] - Password must exist if guest is false
* @param {Device} [device] - Device with id to register
* @return {Promise<MobileUser>} - Registered Mobile User
*
* @example
* import { clinical6 } from 'clinical6';
* const mobileUser = new MobileUser({...});
*
* // you can register a guest using the `register` method.
* clinical6.register()
* .then(mobileUser => console.log(mobileUser));
*
* // Register using an account name
* clinical6.register('abc', 'asdf', device)
* .then(mobileUser => console.log(mobileUser));
*
* // Register using an email address
* clinical6.register('test@test.com', 'asdf', device)
* .then(mobileUser => console.log(mobileUser));
*/
async register(account = undefined, password = undefined, device = undefined) {
const attributes = {};
if (!account && !password) {
attributes.guest = true;
} else {
attributes.password = password;
if (isEmail(account)) {
attributes.email = account;
} else {
attributes.account_name = account;
}
}
let d = device || Client.instance.device;
if (d) {
if (d.id && d.select) {
// if exists, just use device information
d.select();
} else {
// if doesn't exist, insert new one
d = await d.save();
}
d.select();
}
return new UserService().register(attributes, Client.instance.device);
// return prepareDevice.then(() => new UserService().register(attributes));
}
/**
* Starts the reset password process and sends an email to the user with instructions.
*
* @throws {Clinical6Error} - If missing required parameters
* @param {String} account - Either the account name or email address
* @return {Promise} - Promise object that returns success or failure
*
* @example
* import { clinical6 } from 'clinical6';
* clinical6.requestPasswordReset('test@test.com'); // email
* clinical6.requestPasswordReset('jsmith'); // account
*/
requestPasswordReset(account) {
const attributes = {};
if (isEmail(account)) {
attributes.email = account;
} else {
attributes.account_name = account;
}
return new UserService().requestPasswordReset(attributes);
}
/**
* Sends user's account name via email. This method does not require an access token.
*
* @param {String} token - Token from email.
* @param {String} password - New password for the account.
* @param {Device} [device] - Reset with this device (Default to saved device).
*
* @return {Promise} - Promise object that returns success or failure
*/
resetPassword(token, password, device = undefined) {
return new UserService().resetPassword(token, password, device || Client.instance.device);
}
/**
* Sign in with device info
*
* @param {String} account - The username associated with the user account
* @param {String} password - The password for the user
* @return {Promise} - Promise object that returns success or failure
*/
signIn(account = undefined, password = undefined) {
const attributes = {};
if (!account && !password) {
attributes.guest = true;
} else {
attributes.password = password;
if (isEmail(account)) {
attributes.email = account;
} else {
attributes.account_name = account;
}
}
let promise;
let prepareDevice;
switch (this.user.type) {
case 'users':
promise = (new UserService()).signIn(attributes);
break;
case 'mobile_users':
default:
prepareDevice = new Promise((resolve) => {
const d = Client.instance.device;
if (d.id && d.select) {
resolve(d.select());
} else {
d.save().then(_d => resolve(_d.select()));
}
});
promise = prepareDevice.then(() => new UserService().signIn(attributes));
break;
}
return promise;
}
/**
* Sign in with device info
*
* @return {Promise} Promise object that returns success or failure
*/
async signInGuest() {
const deviceJson = Client.instance.device.toJSON();
if (Client.instance.authToken) {
await Client.instance.device.delete();
}
deviceJson.id = undefined;
deviceJson.accessToken = undefined;
Client.instance.device = new Device(deviceJson);
const d = await Client.instance.device.save();
d.select();
return new UserService().register({ guest: true }, Client.instance.device);
}
/**
* Detaches the device from the mobile_user. If the current mobile_user is a guest, his
* information will be destroyed. After this method is executed the device won't have any
* access_token associated and won't be able to make other requests until it signs in.
*
* This method does not require access token.
*
* @return {Promise} Promise object that returns success or failure
*/
signOut() {
return new UserService().signOut();
}
/**
* Updates an object
* @param {Object} [obj] - Object used for this action
* @param {Object} [options] - Options to override the call
* @param {String} [options.cacheMode] - Override the caching method for this call
* @param {Object} [options.meta] - Apply meta data to be passed to this call
* @return {Promise} - Returns a promise via ajax call.
*
* @example
* import { clinical6, Site, Client } from 'clinical6';
*
* // Updates a site
* const obj = new Site({...}); // Site for example
* obj.data = `Updated Data`;
* clinical6.update(obj);
*
* // Updates a site and applies meta data
* const obj = new Site({...}); // Site for example
* obj.data = `Updated Data`;
* clinical6.update(obj, { cacheMode: 'networkOnly', meta: { something: 23 }});
*/
async update(obj, options = { cacheMode: 'networkOnly', meta: undefined }) {
validate('Clinical6.update',
isRequired({ obj }),
hasAttribute({ obj }, 'type'));
const service = serviceFactory.get(obj.type);
return service.update(obj, options);
}
/**
* Submits the provided JSON to the server.
*
* @param {Object} json - The JSON value with the attributes to validate.
*
* @return {Promise} Promise object that returns success or failure
*/
xAuth(json) {
return new SystemService().xAuth(json);
}
}
export default Clinical6;