core/client.js
import MemoryCache from 'fast-memory-cache';
import url from 'url';
import isString from 'lodash/isString';
import isNumber from 'lodash/isNumber';
import isNaN from 'lodash/isNaN';
import { KinveyError } from './errors';
import { Log } from './log';
import { isDefined, uuidv4, isValidStorageProviderValue } from './utils';
import { StorageProvider } from './datastore';
const DEFAULT_TIMEOUT = 60000;
const ACTIVE_USER_KEY = 'active_user';
let sharedInstance = null;
/**
* @private
*/
class ActiveUserStorage {
constructor() {
this.memory = new MemoryCache();
}
get(key) {
if (!isString(key)) {
throw new KinveyError('ActiveUserStorage key must be a string.');
}
try {
return JSON.parse(this.memory.get(key));
} catch (e) {
return null;
}
}
set(key, value) {
if (!isString(key)) {
throw new KinveyError('ActiveUserStorage key must be a string.');
}
if (isDefined(value)) {
this.memory.set(key, JSON.stringify(value));
} else {
this.memory.delete(key);
}
return value;
}
}
/**
* @private
* The Client class stores information about your application on the Kinvey platform. You can create mutiple clients
* to send requests to different environments on the Kinvey platform.
*/
export class Client {
/**
* Creates a new instance of the Client class.
*
* @param {Object} options Options
* @param {string} [options.instanceId='<my-subdomain>'] Custom subdomain for Kinvey API and MIC requests.
* @param {string} [options.apiHostname='https://baas.kinvey.com'] Deprecated: Use the instanceID property instead. Host name used for Kinvey API requests
* @param {string} [options.micHostname='https://auth.kinvey.com'] Deprecated: Use the instanceID property instead. Host name used for Kinvey MIC requests
* @param {string} [options.appKey] App Key
* @param {string} [options.appSecret] App Secret
* @param {string} [options.masterSecret] App Master Secret
* @param {string} [options.encryptionKey] App Encryption Key
* @param {string} [options.appVersion] App Version
* @return {Client} An instance of the Client class.
*/
constructor(config = {}) {
let apiHostname = 'https://baas.kinvey.com';
let micHostname = 'https://auth.kinvey.com';
if (config.instanceId) {
const { instanceId } = config;
if (!isString(instanceId)) {
throw new KinveyError('Instance ID must be a string.');
}
apiHostname = `https://${instanceId}-baas.kinvey.com`;
micHostname = `https://${instanceId}-auth.kinvey.com`;
} else {
if (isString(config.apiHostname)) {
apiHostname = /^https?:\/\//i.test(config.apiHostname) ? config.apiHostname : `https://${config.apiHostname}`;
}
if (isString(config.micHostname)) {
micHostname = /^https?:\/\//i.test(config.micHostname) ? config.micHostname : `https://${config.micHostname}`;
}
}
const apiHostnameParsed = url.parse(apiHostname);
const micHostnameParsed = url.parse(micHostname);
/**
* @type {string}
*/
this.deviceId = uuidv4();
/**
* @type {string}
*/
this.apiProtocol = apiHostnameParsed.protocol;
/**
* @type {string}
*/
this.apiHost = apiHostnameParsed.host;
/**
* @type {string}
*/
this.micProtocol = micHostnameParsed.protocol;
/**
* @type {string}
*/
this.micHost = micHostnameParsed.host;
/**
* @type {?string}
*/
this.appKey = config.appKey;
/**
* @type {?string}
*/
this.appSecret = config.appSecret;
/**
* @type {?string}
*/
this.masterSecret = config.masterSecret;
/**
* @type {?string}
*/
this.encryptionKey = config.encryptionKey;
/**
* @type {?string}
*/
this.appVersion = config.appVersion;
/**
* @type {?number}
*/
this.defaultTimeout = isNumber(config.defaultTimeout) && config.defaultTimeout >= 0 ? config.defaultTimeout : DEFAULT_TIMEOUT;
/**
* @private
*/
this.activeUserStorage = new ActiveUserStorage();
this.storage = config.storage || StorageProvider.Memory;
}
/**
* Get the active user.
*/
getActiveUser() {
return this.activeUserStorage.get(`${this.appKey}.${ACTIVE_USER_KEY}`);
}
/**
* Set the active user
*/
setActiveUser(activeUser) {
return this.activeUserStorage.set(`${this.appKey}.${ACTIVE_USER_KEY}`, activeUser);
}
/**
* API host name used for Kinvey API requests.
*/
get apiHostname() {
return url.format({
protocol: this.apiProtocol,
host: this.apiHost
});
}
/**
* Mobile Identity Connect host name used for MIC requests.
*/
get micHostname() {
return url.format({
protocol: this.micProtocol,
host: this.micHost
});
}
/**
* The version of your app. It will sent with Kinvey API requests
* using the X-Kinvey-Api-Version header.
*/
get appVersion() {
return this._appVersion;
}
/**
* Set the version of your app. It will sent with Kinvey API requests
* using the X-Kinvey-Api-Version header.
*
* @param {String} appVersion App version.
*/
set appVersion(appVersion) {
if (appVersion && !isString(appVersion)) {
appVersion = String(appVersion);
}
this._appVersion = appVersion;
}
get defaultTimeout() {
return this._defaultTimeout;
}
set defaultTimeout(timeout) {
timeout = parseInt(timeout, 10);
if (isNumber(timeout) === false || isNaN(timeout)) {
throw new KinveyError('Invalid timeout. Timeout must be a number.');
}
if (timeout < 0) {
Log.info(`Default timeout is less than 0. Setting default timeout to ${this.defaultTimeout}ms.`);
timeout = this.defaultTimeout;
}
this._defaultTimeout = timeout;
}
get storage() {
return this._storage;
}
set storage(value) {
if (!isValidStorageProviderValue(value)) {
throw new KinveyError('Please provide a valid set of supported storage providers for this platform');
}
this._storage = value;
}
/**
* Returns an object containing all the information for this Client.
*
* @return {Object} Object
*/
toPlainObject() {
return {
deviceId: this.deviceId,
apiHostname: this.apiHostname,
apiProtocol: this.apiProtocol,
apiHost: this.apiHost,
micHostname: this.micHostname,
micProtocol: this.micProtocol,
micHost: this.micHost,
appKey: this.appKey,
appSecret: this.appSecret,
masterSecret: this.masterSecret,
encryptionKey: this.encryptionKey,
appVersion: this.appVersion,
storage: this.storage
};
}
/**
* Initializes the Client class by creating a new instance of the
* Client class and storing it as a shared instance. The returned promise
* resolves with the shared instance of the Client class.
*
* @param {Object} options Options
* @param {string} [options.apiHostname='https://baas.kinvey.com'] Host name used for Kinvey API requests
* @param {string} [options.micHostname='https://auth.kinvey.com'] Host name used for Kinvey MIC requests
* @param {string} [options.appKey] App Key
* @param {string} [options.appSecret] App Secret
* @param {string} [options.masterSecret] App Master Secret
* @param {string} [options.encryptionKey] App Encryption Key
* @param {string} [options.appVersion] App Version
* @return {Promise} A promise.
*/
static initialize() {
throw new KinveyError('Please use Client.init().');
}
/**
* Initializes the Client class by creating a new instance of the
* Client class and storing it as a shared instance. The returned promise
* resolves with the shared instance of the Client class.
*
* @param {Object} options Options
* @param {string} [options.apiHostname='https://baas.kinvey.com'] Host name used for Kinvey API requests
* @param {string} [options.micHostname='https://auth.kinvey.com'] Host name used for Kinvey MIC requests
* @param {string} [options.appKey] App Key
* @param {string} [options.appSecret] App Secret
* @param {string} [options.masterSecret] App Master Secret
* @param {string} [options.encryptionKey] App Encryption Key
* @param {string} [options.appVersion] App Version
* @return {Client} A promise.
*/
static init(config) {
sharedInstance = new Client(config);
return sharedInstance;
}
/**
* Returns the shared instance of the Client class used by the SDK.
*
* @throws {KinveyError} If a shared instance does not exist.
*
* @return {Client} The shared instance.
*
* @example
* var client = Kinvey.Client.sharedInstance();
*/
static sharedInstance() {
if (isDefined(sharedInstance) === false) {
throw new KinveyError('You have not initialized the library. ' +
'Please call Kinvey.init() to initialize the library.');
}
return sharedInstance;
}
}