/**
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* Oracle Mobile Hub JavaScript SDK for Cordova, Release: 18.3.3.0, F13788-01
*/
(function(_module, _exports, _define, _window, _global, _self, _this) {
var cxa;
var MCSGlobal;
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 70);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
Object.defineProperty(exports, "__esModule", { value: true });
var Headers;
(function (Headers) {
Headers["ORACLE_MOBILE_DIAGNOSTIC_SESSION_ID"] = "Oracle-Mobile-DIAGNOSTIC-SESSION-ID";
Headers["ORACLE_MOBILE_DEVICE_ID"] = "Oracle-Mobile-DEVICE-ID";
Headers["ORACLE_MOBILE_CLIENT_REQUEST_TIME"] = "Oracle-Mobile-CLIENT-REQUEST-TIME";
Headers["ORACLE_MOBILE_ANALYTICS_APPLICATION_ID"] = "Oracle-Mobile-Analytics-Application-Id";
Headers["ORACLE_MOBILE_NAME"] = "Oracle-Mobile-Name";
Headers["ORACLE_MOBILE_CREATED_BY"] = "Oracle-Mobile-Created-By";
Headers["ORACLE_MOBILE_CREATED_ON"] = "Oracle-Mobile-Created-On";
Headers["ORACLE_MOBILE_MODIFIED_BY"] = "Oracle-Mobile-Modified-By";
Headers["ORACLE_MOBILE_MODIFIED_ON"] = "Oracle-Mobile-Modified-On";
Headers["ORACLE_MOBILE_SYNC_RESOURCE_TYPE"] = "Oracle-Mobile-Sync-Resource-Type";
Headers["ORACLE_MOBILE_SYNC_AGENT"] = "Oracle-Mobile-Sync-Agent";
Headers["LOCATION"] = "Location";
Headers["ORACLE_MOBILE_BACKEND_ID"] = "Oracle-Mobile-Backend-Id";
Headers["ORACLE_MOBILE_SOCIAL_IDENTITY_PROVIDER"] = "Oracle-Mobile-Social-Identity-Provider";
Headers["ORACLE_MOBILE_SOCIAL_ACCESS_TOKEN"] = "Oracle-Mobile-Social-Access-Token";
Headers["ORACLE_MOBILE_CLIENT_SDK_INFO"] = "Oracle-Mobile-Client-SDK-Info";
Headers["ACCEPT"] = "Accept";
Headers["CONTENT_TYPE"] = "Content-Type";
Headers["E_TAG"] = "ETag";
Headers["IF_MATCH"] = "If-Match";
Headers["AUTHORIZATION"] = "Authorization";
Headers["X_USER_IDENTITY_DOMAIN_NAME"] = "X-USER-IDENTITY-DOMAIN-NAME";
Headers["X_RESOURCE_IDENTITY_DOMAIN_NAME"] = "X-RESOURCE-IDENTITY-DOMAIN-NAME";
Headers["ACCEPT_ENCODING"] = "Accept-Encoding";
Headers["IF_MODIFIED_SINCE"] = "if-modified-since";
Headers["IF_NONE_MATCH"] = "if-none-match";
})(Headers || (Headers = {}));
exports.Headers = Headers;
var ContentTypes;
(function (ContentTypes) {
ContentTypes["APPLICATION_JSON"] = "application/json";
ContentTypes["TEXT_PLAIN"] = "text/plain";
ContentTypes["X_WWW_FORM_FORM_URLENCODED"] = "application/x-www-form-urlencoded; charset=utf-8";
})(ContentTypes || (ContentTypes = {}));
exports.ContentTypes = ContentTypes;
var HttpMethods;
(function (HttpMethods) {
HttpMethods["GET"] = "GET";
HttpMethods["PUT"] = "PUT";
HttpMethods["PATCH"] = "PATCH";
HttpMethods["POST"] = "POST";
HttpMethods["DELETE"] = "DELETE";
HttpMethods["HEAD"] = "HEAD";
})(HttpMethods || (HttpMethods = {}));
exports.HttpMethods = HttpMethods;
var ResourceTypes;
(function (ResourceTypes) {
ResourceTypes["ITEM"] = "item";
ResourceTypes["COLLECTION"] = "collection";
ResourceTypes["FILE"] = "file";
})(ResourceTypes || (ResourceTypes = {}));
exports.ResourceTypes = ResourceTypes;
var AuthenticationTypes;
(function (AuthenticationTypes) {
AuthenticationTypes["basic"] = "basic";
AuthenticationTypes["oauth"] = "oauth";
AuthenticationTypes["facebook"] = "facebook";
AuthenticationTypes["token"] = "token";
})(AuthenticationTypes || (AuthenticationTypes = {}));
exports.AuthenticationTypes = AuthenticationTypes;
var ModuleNames;
(function (ModuleNames) {
ModuleNames["STORAGE"] = "Storage";
ModuleNames["AUTHORIZATION"] = "Authorization";
ModuleNames["USER_MANAGEMENT"] = "UserManagement";
ModuleNames["APP_POLICES"] = "AppPolicies";
ModuleNames["LOCATION"] = "Location";
ModuleNames["SYNC"] = "Sync";
ModuleNames["SYNC_EXPRESS"] = "SyncExpress";
ModuleNames["NOTIFICATIONS"] = "Notifications";
ModuleNames["MCS_ANALYTICS"] = "MCSAnalytics";
ModuleNames["ANALYTICS"] = "Analytics";
ModuleNames["CUSTOM_CODE"] = "CustomCode";
ModuleNames["APP_CONFIG"] = "AppConfig";
})(ModuleNames || (ModuleNames = {}));
exports.ModuleNames = ModuleNames;
var PlatformNames;
(function (PlatformNames) {
PlatformNames["JAVASCRIPT"] = "Javascript";
PlatformNames["CORDOVA"] = "Cordova";
PlatformNames["REACT_NATIVE"] = "ReactNative";
})(PlatformNames || (PlatformNames = {}));
exports.PlatformNames = PlatformNames;
var DevicePlatforms;
(function (DevicePlatforms) {
DevicePlatforms["ANDROID"] = "android";
DevicePlatforms["IOS"] = "ios";
})(DevicePlatforms || (DevicePlatforms = {}));
exports.DevicePlatforms = DevicePlatforms;
var NotificationProviders;
(function (NotificationProviders) {
NotificationProviders["FCM"] = "FCM";
NotificationProviders["APNS"] = "APNS";
NotificationProviders["WNS"] = "WNS";
NotificationProviders["SYNIVERSE"] = "SYNIVERSE";
})(NotificationProviders || (NotificationProviders = {}));
exports.NotificationProviders = NotificationProviders;
var XMLHttpRequestResponseTypes;
(function (XMLHttpRequestResponseTypes) {
XMLHttpRequestResponseTypes["JSON"] = "json";
XMLHttpRequestResponseTypes["BLOB"] = "blob";
XMLHttpRequestResponseTypes["ARRAY_BUFFER"] = "arraybuffer";
XMLHttpRequestResponseTypes["DOCUMENT"] = "document";
XMLHttpRequestResponseTypes["TEXT"] = "text";
})(XMLHttpRequestResponseTypes || (XMLHttpRequestResponseTypes = {}));
exports.XMLHttpRequestResponseTypes = XMLHttpRequestResponseTypes;
var SyncRequestHandlerTypes;
(function (SyncRequestHandlerTypes) {
SyncRequestHandlerTypes["GENERIC"] = "Generic";
SyncRequestHandlerTypes["MCS"] = "MCS";
SyncRequestHandlerTypes["ORACLE_REST"] = "OracleRest";
})(SyncRequestHandlerTypes || (SyncRequestHandlerTypes = {}));
exports.SyncRequestHandlerTypes = SyncRequestHandlerTypes;
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
* @ignore
*/
Object.defineProperty(exports, "__esModule", { value: true });
var Logger = /** @class */ (function () {
function Logger(module) {
this.module = module;
}
Logger.prototype.debug = function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
this.log(Logger.LOG_LEVEL.DEBUG, params);
};
Logger.prototype.error = function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
this.log(Logger.LOG_LEVEL.ERROR, params);
};
Logger.prototype.info = function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
this.log(Logger.LOG_LEVEL.INFO, params);
};
Logger.prototype.warn = function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
this.log(Logger.LOG_LEVEL.WARN, params);
};
Logger.prototype.log = function (level, params) {
if (Logger.logLevel >= level) {
params.unshift('[mcs.' + this.module + ']');
var method = void 0;
switch (Logger.logLevel) {
case Logger.LOG_LEVEL.ERROR:
method = console.error;
break;
case Logger.LOG_LEVEL.WARN:
method = console.warn;
break;
case Logger.LOG_LEVEL.INFO:
method = console.info;
break;
case Logger.LOG_LEVEL.DEBUG:
method = console.debug;
break;
}
if (Logger.historyEnabled) {
Logger.history.push(Object.assign({}, params, { level: level }));
if (Logger.historySize <= Logger.history.length) {
Logger.history.shift();
}
}
method.apply(console, params);
}
};
Logger.LOG_LEVEL = {
NONE: 0,
ERROR: 1,
WARN: 2,
INFO: 3,
DEBUG: 4,
};
Logger.logLevel = Logger.LOG_LEVEL.ERROR;
Logger.historyEnabled = false;
Logger.historySize = 100;
Logger.history = [];
return Logger;
}());
exports.Logger = Logger;
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
* Created by Yuri Panshin on 2016-08-26.
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @classdesc Class that provides network response details.
* @class
* @global
* @hideconstructor
* @mcs
*/
var NetworkResponse = /** @class */ (function () {
function NetworkResponse(statusCode, data, headers) {
/**
* The network status code.
* @type {Number}
* @readonly
* @name NetworkResponse#statusCode
* @mcs
*/
this.statusCode = 0;
/**
* The error data.
* @type {Object}
* @readonly
* @name NetworkResponse#data
* @mcs
*/
this.data = null;
/**
* The response headers in dictionary format with lowercase keys.
* @type {NetworkResponseHeaders}
* @readonly
* @name NetworkResponse#headers
* @mcs
*/
this.headers = null;
this.statusCode = statusCode;
this.data = data;
this.headers = headers;
}
return NetworkResponse;
}());
exports.NetworkResponse = NetworkResponse;
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
Object.defineProperty(exports, "__esModule", { value: true });
var Dictionary = /** @class */ (function () {
function Dictionary(init) {
this._keys = [];
this._values = [];
for (var x = 0; x < init.length; x++) {
this[init[x].key.toString()] = init[x].value;
this._keys.push(init[x].key);
this._values.push(init[x].value);
}
}
Dictionary.prototype.add = function (key, value) {
this[key.toString()] = value;
this._keys.push(key);
this._values.push(value);
};
Dictionary.prototype.remove = function (key) {
var index = this._keys.indexOf(key, 0);
this._keys.splice(index, 1);
this._values.splice(index, 1);
delete this[key.toString()];
};
Dictionary.prototype.keys = function () {
return this._keys;
};
Dictionary.prototype.values = function () {
return this._values;
};
Dictionary.prototype.containsKey = function (key) {
return typeof this[key.toString()] !== "undefined";
};
Dictionary.prototype.toLookup = function () {
return this;
};
return Dictionary;
}());
exports.Dictionary = Dictionary;
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var logger_1 = __webpack_require__(1);
var network_response_1 = __webpack_require__(2);
var types_1 = __webpack_require__(0);
/**
* Authentication response
* @private
*/
var AuthenticationResponse = /** @class */ (function (_super) {
__extends(AuthenticationResponse, _super);
function AuthenticationResponse(response, accessToken) {
var _this = _super.call(this, response.statusCode, response.data, response.headers) || this;
_this.accessToken = accessToken;
return _this;
}
return AuthenticationResponse;
}(network_response_1.NetworkResponse));
exports.AuthenticationResponse = AuthenticationResponse;
/**
* @class
* @global
* @classdesc Class used to authorize a mobile user against Oracle Mobile Hub.
* Callers should use MobileBackend's [authorization]{@link Backend#authorization} property.
* @abstract
* @hideconstructor
* @mcs
*/
var Authorization = /** @class */ (function () {
function Authorization(utils, platform) {
this.utils = utils;
this.platform = platform;
this._onAuthenticationCallbacks = [];
this.logger = new logger_1.Logger('Authorization');
}
Object.defineProperty(Authorization.prototype, "isAuthorized", {
/**
* Is SDK authorized.
* Returns true if a user has been authorized, false otherwise.
* A user can be authorized by calling authenticate() or authenticateAnonymous().
* @type {Boolean}
* @name Authorization#isAuthorized
* @mcs
*/
get: function () {
return this._isAuthorized;
},
enumerable: true,
configurable: true
});
/**
* Get access token.
* Returns the current access token from user credentials.
* @returns {String} current access token from user credentials.
* @function
* @name Authorization#getAccessToken
* @mcs
*/
Authorization.prototype.getAccessToken = function () {
return this._accessToken;
};
Authorization.prototype._setAccessToken = function (token) {
this._accessToken = token;
};
/**
* Get is anonymous
* @return {boolean}
* @private
*/
Authorization.prototype._getIsAnonymous = function () {
return this._isAnonymous;
};
/**
* Get is authorized
* @return {boolean}
* @private
*/
Authorization.prototype._getIsAuthorized = function () {
return this._isAuthorized;
};
/**
* Get anonymous access token
* @return {string}
* @private
*/
Authorization.prototype._getAnonymousAccessToken = function () {
return this._anonymousAccessToken;
};
/**
* Authenticate anonymous
* @param {IDictionary<Headers, string>} headers
* @param {string} url
* @param {HttpMethods} method
* @param {boolean} withCredentials
* @param data
* @return {Promise<AuthenticationResponse>}
* @private
*/
Authorization.prototype._authenticateAnonymousInvoke = function (headers, url, method, withCredentials, data) {
this.logout();
headers = this._getHeaders(headers);
headers = this._getAnonymousAuthorizationHeaders(headers);
return this.platform.invokeService({
url: url,
method: method,
headers: headers,
withCredentials: withCredentials,
data: data,
module: types_1.ModuleNames.AUTHORIZATION,
})
.then(function (response) { return new AuthenticationResponse(response, null); })
.then(this._anonymousTokenResponseConverter.bind(this))
.catch(invokeServiceError.bind(this))
.then(this._authenticateAnonymousSuccess.bind(this));
function invokeServiceError(response) {
this.logger.error('Login failed with error: ' + response.statusCode);
this._clearState();
return Promise.reject(response);
}
};
/**
* Get headers
* @param headers
* @return {any}
* @private
*/
Authorization.prototype._getHeaders = function (headers) {
return headers;
};
/**
* Authenticate anonymous success callback.
* @param {AuthenticationResponse} response
* @return {INetworkResponse}
* @private
*/
Authorization.prototype._authenticateAnonymousSuccess = function (response) {
this.logger.info('User logged in anonymously ' + response.statusCode);
this._setAuthenticateAnonymousSuccess(response.accessToken);
return response;
};
/**
* Set authentication anonymous success callback.
* @param {string} token
* @private
*/
Authorization.prototype._setAuthenticateAnonymousSuccess = function (token) {
this._isAnonymous = true;
this._isAuthorized = true;
this._anonymousAccessToken = token;
for (var i = 0; i < this._onAuthenticationCallbacks.length; i++) {
this._onAuthenticationCallbacks[i](token);
}
};
/**
* Authenticate error callback
* @param {INetworkResponse} response
* @private
*/
Authorization.prototype._authenticateError = function (response) {
this.logger.error('Login failed with error: ' + response.statusCode);
this._clearState();
};
/**
* Clear state after logout
* @private
*/
Authorization.prototype._clearState = function () {
this._accessToken = null;
this._isAnonymous = false;
this._anonymousAccessToken = null;
this._isAuthorized = false;
};
/**
* The authentication callback.
* This callback called when authentication happen.
* @callback Authorization~OnAuthenticationCallback
* @param token {String} The authentication token received from server.
* @mcs
*/
/**
* On authentication event.
* Subscribe for on authentication event
* @param {Authorization~OnAuthenticationCallback} callback The callback that will be called when authentication happen.
* @function
* @name Authorization#onAuthentication
* @mcs
*/
Authorization.prototype.onAuthentication = function (callback) {
this._onAuthenticationCallbacks.push(callback);
};
return Authorization;
}());
exports.Authorization = Authorization;
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var sync_resource_type_1 = __webpack_require__(10);
var mcs_request_handler_1 = __webpack_require__(15);
var types_1 = __webpack_require__(0);
var SyncProcessor = /** @class */ (function () {
function SyncProcessor(backend, apiName, endpointPath, resolvingOfflineUpdate, utils, platform, options, common) {
this.backend = backend;
this.apiName = apiName;
this.endpointPath = endpointPath;
this.resolvingOfflineUpdate = resolvingOfflineUpdate;
this.utils = utils;
this.platform = platform;
this.options = options;
this.common = common;
}
SyncProcessor.prototype.getHttpHeaders = function (requestHeaders) {
var headers = this.backend.getHttpHeaders(requestHeaders);
headers.add(types_1.Headers.ORACLE_MOBILE_SYNC_AGENT, 'true');
return headers;
};
SyncProcessor.prototype.getType = function (responseHeaders, responseData) {
var type = sync_resource_type_1.SyncResourceType.file;
var resourceType = responseHeaders[types_1.Headers.ORACLE_MOBILE_SYNC_RESOURCE_TYPE.toLowerCase()];
if (resourceType != null) {
if (resourceType === types_1.ResourceTypes.ITEM) {
type = sync_resource_type_1.SyncResourceType.item;
}
else if (resourceType === types_1.ResourceTypes.COLLECTION) {
type = sync_resource_type_1.SyncResourceType.collection;
}
}
else {
if (this.common.isString(responseData)) {
try {
var json = JSON.parse(responseData);
if (this.common.isArray(json)) {
type = sync_resource_type_1.SyncResourceType.collection;
}
else {
type = sync_resource_type_1.SyncResourceType.item;
}
}
catch (e) {
type = sync_resource_type_1.SyncResourceType.file;
}
}
}
return type;
};
SyncProcessor.prototype.getUri = function (response, url) {
var location = response && response.headers ?
response.headers[types_1.Headers.LOCATION.toLowerCase()] : null;
if (location != null) {
return '/' + location;
}
var obj = null;
if (response.data) {
if (typeof response.data === 'string') {
obj = response.data !== '' ? JSON.parse(response.data) : null;
}
else {
obj = response.data;
}
}
if (obj && obj[mcs_request_handler_1.McsRequestHandler.URI_KEY]) {
var uri = obj[mcs_request_handler_1.McsRequestHandler.URI_KEY];
delete obj[mcs_request_handler_1.McsRequestHandler.URI_KEY];
return uri;
}
return this.options.parseURL(url).path;
};
SyncProcessor.prototype.createResource = function (method, url, statusCode, requestHeaders, responseHeaders, requestData, responseData, response) {
var location = response.headers[types_1.Headers.LOCATION.toLowerCase()];
if (location != null) {
location = '/' + location;
}
else {
location = url;
}
var type = sync_resource_type_1.SyncResourceType.file;
var resourceType = responseHeaders[types_1.Headers.ORACLE_MOBILE_SYNC_RESOURCE_TYPE.toLowerCase()];
if (resourceType != null) {
if (resourceType === types_1.ResourceTypes.ITEM) {
type = sync_resource_type_1.SyncResourceType.item;
}
else if (resourceType === types_1.ResourceTypes.COLLECTION) {
type = sync_resource_type_1.SyncResourceType.collection;
}
}
else {
if (this.common.isString(responseData)) {
try {
var json = JSON.parse(responseData);
if (this.common.isArray(json)) {
type = sync_resource_type_1.SyncResourceType.collection;
}
else {
type = sync_resource_type_1.SyncResourceType.item;
}
}
catch (e) {
type = sync_resource_type_1.SyncResourceType.file;
}
}
}
return null;
};
return SyncProcessor;
}());
exports.SyncProcessor = SyncProcessor;
/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*/
var db_1 = __webpack_require__(9);
var logger_1 = __webpack_require__(1);
var RequestHandler = /** @class */ (function () {
function RequestHandler(dbName, $options, $common, $utils, dbAdapter) {
var _this = this;
this.dbName = dbName;
this.$options = $options;
this.$common = $common;
this.$utils = $utils;
this.logger = new logger_1.Logger('RequestHandler');
this.prefix = $options.dbPrefix;
this.$db = this._createDatabase(dbAdapter);
$options.onDbPrefixChange = function (oldVal, newVal) {
_this.prefix = newVal;
_this.$db = _this._createDatabase(dbAdapter);
};
}
RequestHandler.prototype.isPersistentRequest = function (request) {
if (!request) {
throw new Error('request cannot be undefined or null value');
}
if (!this.$common.isObject(request)) {
throw new Error('request has to be defined object with properties like: url, data etc.');
}
if (this.$common.isEmpty(request)) {
throw new Error('request cannot be empty object, it request properties like: url, data etc.');
}
if (this.$common.isArray(request) || this.$common.isFunction(request)) {
throw new Error('request cannot be array or function');
}
if (!('url' in request)) {
throw new Error('request.url was not specified');
}
return true;
};
RequestHandler.prototype.isPersistentGetRequest = function (request) {
this.isPersistentRequest(request);
return true;
};
RequestHandler.prototype.isPostRequest = function (request) {
this.isPersistentRequest(request);
if (!('data' in request)) {
throw new Error('request.data was not defined!');
}
if (!this.$common.isObject(request.data)) {
throw new Error('request.data is not a object or array!');
}
if (this.$common.isFunction(request.data)) {
throw new Error('request.data cannot be function');
}
return true;
};
RequestHandler.prototype.isOfflinePersistObj = function (obj) {
return !!('meta' in obj && obj.meta.hasOwnProperty('offline-persist'));
};
RequestHandler.prototype.isLokiDbObj = function (obj) {
return !!('$loki' in obj && typeof (obj.$loki) === 'number' && !isNaN(obj.$loki));
};
RequestHandler.prototype.buildFindQueryBasedOnUrlParams = function (urlQueryParams) {
var dbQuery = {};
if (urlQueryParams.attr.length > 0) {
var key = urlQueryParams.attr[0].name;
dbQuery[key] = (urlQueryParams.attr[0].pattern.indexOf('\d') >= 0)
? parseInt(urlQueryParams.attr[0].value, 10)
: urlQueryParams.attr[0].value + '';
}
return dbQuery;
};
RequestHandler.prototype.buildUniqueIDValue = function (isPersistentUrl, value) {
var isInt = function () {
if ((isPersistentUrl.uri.tokens.length > 1)) {
return isPersistentUrl.uri.tokens[isPersistentUrl.uri.tokens.length - 1].pattern.indexOf('\d') >= 0;
}
else {
return false;
}
};
var parse = function (value) {
return isInt() ? parseInt(value, 10) : value + '';
};
if (this.$common.isEmpty(value)) {
return parse(this.$common.getUID());
}
if (typeof value === 'number' && isInt()) {
return value;
}
return parse(value);
};
/**
* Transform the payload and add it into the $db!
*
* NOTE: Such a transformations could hold a lot of resources!
*
* @param collection
* @param isPersistentUrl
* @param payload
* @returns {*}
*/
RequestHandler.prototype.handleGetRootArrayPayload = function (payload, isPersistentUrl, collection) {
var _this = this;
if (isPersistentUrl.uri.tokens.length > 1) {
var dbArray = collection.find();
if (dbArray.length > 0) {
var keyNameToCompare_1 = isPersistentUrl.uri.tokens[1].name;
collection.removeWhere(function (dbObj) {
var foundObjectIndex = payload.findIndex(function (payloadObj) {
return (payloadObj[keyNameToCompare_1] === dbObj[keyNameToCompare_1]);
});
if (foundObjectIndex > -1) {
try {
if (_this.isOfflinePersistObj(dbObj)) {
dbObj = _this.$common.deepExtend(payload[foundObjectIndex], dbObj);
}
else {
_this.$common.extendOwn(dbObj, payload[foundObjectIndex]);
}
collection.update(dbObj);
payload.splice(foundObjectIndex, 1);
}
catch (e) {
_this.logger.error(e);
}
finally {
return false;
}
}
else {
if (_this.isOfflinePersistObj(dbObj)) {
return false;
}
return true;
}
});
payload.forEach(function (obj) {
if (_this.isLokiDbObj(obj)) {
collection.update(obj);
}
else {
collection.insert(obj);
}
});
return collection.find();
}
return collection.insert(payload);
}
collection.removeWhere(function (obj) {
return (!_this.isOfflinePersistObj(obj));
});
return collection.insert(payload);
};
RequestHandler.prototype.handleGetRootObjectPayload = function (payload, isPersistentUrl, collection) {
var _this = this;
if (isPersistentUrl.uri.tokens.length > 1) {
var keyNameToCompare = isPersistentUrl.uri.tokens[1].name;
if (!payload.hasOwnProperty(keyNameToCompare)) {
this.logger.error('payload does not contain unique key specified in the URL settings');
throw new Error('payload does not contain unique key specified in the URL settings');
}
var findObjByKeyQuery = {};
findObjByKeyQuery[keyNameToCompare] = payload[keyNameToCompare];
var result = collection.findOne(findObjByKeyQuery);
if (result) {
if (this.isOfflinePersistObj(result)) {
result = this.$common.deepExtend(payload, result);
}
else {
this.$common.extendOwn(result, payload);
}
return collection.update(result);
}
return collection.insert(payload);
}
collection.removeWhere(function (obj) { return (!_this.isOfflinePersistObj(obj)); });
return collection.insert(payload);
};
/**
* Build nested property structure to be used in GET calls to setup or edit existing objects!
*
* @param queryParams query parameters properties!
* @returns {string} prop1.prop2[value].prop3....
* @deprecated use buildNestedPropertyArrayParams
*/
RequestHandler.prototype.buildNestedPropertySearchString = function (queryParams) {
var nestedProperty = '';
if (queryParams.attr.length > 1) {
for (var i = 1; i < queryParams.attr.length; i++) {
if (queryParams.attr[i].is) {
if (nestedProperty.length > 0)
nestedProperty += '.' + queryParams.attr[i].name;
else
nestedProperty += queryParams.attr[i].name;
}
else {
if (nestedProperty.length > 0) {
nestedProperty += '.' + queryParams.attr[i].name + '[' + queryParams.attr[i].value + ']';
}
else {
nestedProperty += queryParams.attr[i].value;
}
}
}
}
return nestedProperty;
};
/**
* Has to be build a string of properties which can be used when adding new elements!
*
* @param queryParams
* @param isPersistentUrl
* @param isNotGet
*
* @return {Array<persistenceUtils~Property>} - array of properties with parameters and values
*/
RequestHandler.prototype.buildNestedPropertyArrayParams = function (isPersistentUrl, queryParams, isNotGet) {
if (isNotGet === void 0) { isNotGet = false; }
var nestedProperty = [];
var params = queryParams.attr;
if (Array.isArray(params) && params.length > 0) {
var tokens = isPersistentUrl.uri.tokens.length > 1 ? isPersistentUrl.uri.tokens.slice(1) : [];
for (var i = 0; i < params.length; i++) {
var isLast = (params.length - 1) === i;
if (params[i].is) {
nestedProperty.push({
name: params[i].name,
value: null,
isProperty: true,
isInteger: false,
});
if (isLast && tokens[i + 1] && isNotGet) {
var isInt = this.$utils.isUrlRegexInteger(tokens[i + 1].pattern);
nestedProperty.push({
name: tokens[i + 1].name,
value: isInt ? this.$common.getUID() : this.$common.getUID() + '',
isProperty: false,
isInteger: isInt,
});
}
}
else {
var proValue = params[i].value || this.$common.getUID();
nestedProperty.push({
name: params[i].name,
value: proValue,
isProperty: false,
isInteger: this.$utils.isUrlRegexInteger(tokens[i].pattern),
});
}
}
}
return nestedProperty;
};
/**
* Search for that nested object and add the new payload to it, if any!
*
* @param obj
* @param persistentUrlObj
* @param queryParams
* @param payload
* @param dbPayload {Object} - payload that exists in local database
* @returns {{obj: *, result: *}}
*/
RequestHandler.prototype.createObjFromUrlParamsForExistingForPost = function (obj, persistentUrlObj, queryParams, payload, dbPayload) {
var nestedProperty = this.buildNestedPropertyArrayParams(persistentUrlObj, queryParams);
var result = obj;
if (Array.isArray(nestedProperty) && nestedProperty.length > 0) {
var nestedQuery = void 0;
if (dbPayload) {
var key = this.getKeyForCurrentObject(persistentUrlObj, queryParams);
nestedQuery = {
key: key,
value: dbPayload[key.name],
};
}
result = this.$utils.setNestedProperty2(obj, nestedProperty, payload, nestedQuery);
}
else {
this.$common.extendOwn(obj, payload);
}
return {
obj: obj,
result: result,
};
};
/**
* Returns current nested item key token
* @param persistentUrlObj
* @param queryParams
* @returns {*}
*/
RequestHandler.prototype.getKeyForCurrentObject = function (persistentUrlObj, queryParams) {
var length = queryParams.attr.length;
return persistentUrlObj.uri.tokens[length + 1];
};
/**
* In case of given DB object but we have URL with sub parameters, we have to check if those nested obj exist,
* and create them if not and add the payload inside.
*
* @param obj - object form the offline DB
* @param queryParams - URL parameters, usually what is returned from $utils.extractKeyValuesFromUrl2
* @param payload - from the REST API call
* @return {{obj: *, result: *}}
* @param persistentUrlObj
*/
RequestHandler.prototype.createObjFromUrlParamsForGETAction = function (obj, persistentUrlObj, queryParams, payload) {
var nestedProperty = this.buildNestedPropertyArrayParams(persistentUrlObj, queryParams, false);
var result = obj;
if (Array.isArray(nestedProperty) && nestedProperty.length > 0) {
result = this.$utils.setNestedProperty2(obj, nestedProperty, payload);
}
return {
obj: obj,
result: result,
};
};
/**
* Try to find that nested object when offline or when no payload in GET
* @param obj
* @param persistentUrlObj
* @param queryParams
* @returns {*}
*/
RequestHandler.prototype.getNestedPropertyFromUrlParamsForExisting = function (obj, persistentUrlObj, queryParams) {
var nestedProperty = this.buildNestedPropertyArrayParams(persistentUrlObj, queryParams, false);
if (Array.isArray(nestedProperty) && nestedProperty.length > 0) {
return this.$utils.getNestedProperty(obj, nestedProperty);
}
return obj;
};
/**
* If forces, you could mark the object to be stored in the DB only and not synced!
*
* @param obj
* @param force
* @returns {*}
*/
RequestHandler.prototype.markObjAsOfflineIfForced = function (obj, force) {
if (force) {
if (this.isOfflinePersistObj(obj)) {
delete obj.meta['offline-persist'];
}
return obj;
}
if ('meta' in obj) {
obj.meta['offline-persist'] = true;
return obj;
}
obj['meta'] = {};
obj.meta['offline-persist'] = true;
return obj;
};
/**
* Use only if you have no new data, empty payload, and you want to return everything from the db,
* depending on the GET URL
*
* TODO: should be extended to be able to query sub element!
* @param response{url}
* @returns {*}
*/
RequestHandler.prototype.handleGet = function (response) {
var parsed = this.$options.parseURL(response.url);
var isPersistentUrl = this.$utils.isPersistUrl(parsed.path);
if (!isPersistentUrl) {
throw new Error('Persistence.RequestHandler.get() given URI was not configured for persistence:' + parsed.path);
}
var queryParams = this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
var collection = this.$db.getCollectionByName(queryParams.root);
var keyValueObj = this.buildFindQueryBasedOnUrlParams(queryParams);
if (!this.$common.isEmpty(keyValueObj)) {
var result = collection.findOne(keyValueObj);
if (result) {
var nestedObject = this.getNestedPropertyFromUrlParamsForExisting(result, isPersistentUrl, queryParams);
var cleanObject = this.$common.cleanObject(nestedObject);
return this.buildResponseObject(cleanObject);
}
else {
return null; // TODO: check if generic handler fine with this response (from generic handler: return [];)
}
}
else {
this.logger.debug('return all from db');
var cleanObjects = this.$common.cleanObjects(collection.find());
return this.buildResponseObject(cleanObjects);
}
};
/**
* Response property
* @typedef persistenceRequestHandler~Response
* @property url {String}
* @property data {Object}
*/
/**
* Stores/merges given payload into the offline db!
*
* @param response {persistenceRequestHandler~Response}
* @returns {*}
*/
RequestHandler.prototype.handleGetStore = function (response) {
var parsed = this.$options.parseURL(response.url);
var isPersistentUrl = this.$utils.isPersistUrl(parsed.path);
if (!isPersistentUrl) {
throw new Error('Persistence.RequestHandler.get() given URI was not configured for persistence:' + parsed.path);
}
var queryParams = this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
var collection = this.$db.getCollectionByName(queryParams.root);
var payload = this.getResponsePayload(response);
var keyValueObj = this.buildFindQueryBasedOnUrlParams(queryParams);
if (!this.$common.isEmpty(keyValueObj)) {
var result = collection.findOne(keyValueObj);
var newObj = void 0;
if (result) {
newObj = this.createObjFromUrlParamsForGETAction(result, isPersistentUrl, queryParams, payload);
collection.update(newObj.obj);
}
else {
newObj = this.createObjFromUrlParamsForGETAction(keyValueObj, isPersistentUrl, queryParams, payload);
collection.insert(newObj.obj);
}
return this.buildResponseObject(newObj.result);
}
else {
if (this.$common.isArray(payload) && !this.$common.isFunction(payload) && !this.$common.isEmpty(payload)) {
var items = this.handleGetRootArrayPayload(payload, isPersistentUrl, collection);
return this.buildResponseObject(items);
}
else if (this.$common.isObject(payload)
&& !this.$common.isArray(payload)
&& !this.$common.isFunction(payload)
&& !this.$common.isEmpty(payload)) {
return this.handleGetRootObjectPayload(payload, isPersistentUrl, collection);
}
else {
this.logger.error('handleGetStore', 'unknown or empty object passed for the operation');
throw new Error('RequestHandler.handleGetStore -> unknown or empty object passed for the operation');
}
}
};
/**
* Handle post array payload.
*
* @param payload
* @param isPersistentUrl
* @param collection
* @param force
* @returns {*}
*/
RequestHandler.prototype.handlePostRootArrayPayload = function (payload, isPersistentUrl, collection, force) {
var _this = this;
var keyNameToCompare = (isPersistentUrl.uri.tokens.length > 1) ? isPersistentUrl.uri.tokens[1].name : null;
payload.forEach(function (obj) {
if (_this.isLokiDbObj(obj)) {
_this.markObjAsOfflineIfForced(obj, force);
collection.update(obj);
}
else if (keyNameToCompare && obj.hasOwnProperty(keyNameToCompare)) {
var result = collection.findOne({ keyNameToCompare: obj[keyNameToCompare] });
if (result) {
_this.$common.extendOwn(result, obj);
_this.markObjAsOfflineIfForced(result, force);
collection.update(result);
}
else {
_this.markObjAsOfflineIfForced(obj, force);
collection.insert(obj);
}
}
else {
var objInsertResult = collection.insert(obj);
objInsertResult[keyNameToCompare] = _this.buildUniqueIDValue(isPersistentUrl); // objInsertResult.$loki + "";
_this.markObjAsOfflineIfForced(objInsertResult, force);
return _this.$common.cleanObject(collection.update(objInsertResult));
}
});
return this.$common.cleanObjects(collection.find());
};
/**
* Handle post create object from only simple json object
* @param payload
* @param isPersistentUrl
* @param collection
* @param force {Boolean} mark the object as offline
* @returns {*}
*/
RequestHandler.prototype.handlePostRootObjectPayload = function (payload, isPersistentUrl, collection, force) {
var keyNameToCompare = (isPersistentUrl.uri.tokens.length > 1) ? isPersistentUrl.uri.tokens[1].name : '';
if (keyNameToCompare && !payload.hasOwnProperty(keyNameToCompare)) {
this.logger.info('get payload', 'does not have the key specified in the URL settings');
payload[keyNameToCompare] = '';
var insertResult = collection.insert(payload);
if (insertResult) {
insertResult[keyNameToCompare] = this.buildUniqueIDValue(isPersistentUrl); // insertResult.$loki + "";
this.markObjAsOfflineIfForced(insertResult, force);
return this.$common.cleanObject(collection.update(insertResult));
}
throw new Error('unable to store the payload object');
}
else if (keyNameToCompare && payload.hasOwnProperty(keyNameToCompare)) {
var queryForObject = {};
queryForObject[keyNameToCompare] = payload[keyNameToCompare];
var result = collection.findOne(queryForObject);
if (result) {
this.$common.extendOwn(result, payload);
this.markObjAsOfflineIfForced(result, force);
return this.$common.cleanObject(collection.update(result));
}
payload[keyNameToCompare] = this.buildUniqueIDValue(isPersistentUrl, payload[keyNameToCompare]);
}
this.markObjAsOfflineIfForced(payload, force);
return this.$common.cleanObject(collection.insert(payload));
};
/**
* Handle Post HTTP request!
*
* @param response {persistenceRequestHandler~Response}
* @param force - it means that the meta['offline-persist'] property will be deleted to force update on next GET
* @returns {*}
*/
RequestHandler.prototype.handlePost = function (response, force) {
var parsed = this.$options.parseURL(response.url);
var isPersistentUrl = this.$utils.isPersistUrl(parsed.path);
if (!isPersistentUrl) {
throw new Error('Persistence.RequestHandler.post() given URI not configured for persistence: ' + parsed.path);
}
var queryParams = this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
var collection = this.$db.getCollectionByName(queryParams.root);
var beforeSyncPayload = response.data;
var keyValueObj = this.buildFindQueryBasedOnUrlParams(queryParams);
if (!this.$common.isEmpty(keyValueObj)) {
var result = collection.findOne(keyValueObj);
if (result) {
var newObj = this.createObjFromUrlParamsForExistingForPost(result, isPersistentUrl, queryParams, beforeSyncPayload);
var propertyName = isPersistentUrl.uri.tokens[isPersistentUrl.uri.tokens.length - 1].name;
newObj.result[propertyName] = this.buildUniqueIDValue(isPersistentUrl, newObj.result[propertyName]);
this.markObjAsOfflineIfForced(newObj.obj, force);
collection.update(newObj.obj);
return this.$common.cleanObject(newObj.result);
}
else {
var newObj = this.createObjFromUrlParamsForExistingForPost(keyValueObj, isPersistentUrl, queryParams, beforeSyncPayload);
this.markObjAsOfflineIfForced(newObj.obj, force);
collection.insert(newObj.obj);
return this.$common.cleanObject(newObj.result);
}
}
else {
if (this.$common.isArray(beforeSyncPayload)) {
return this.handlePostRootArrayPayload(beforeSyncPayload, isPersistentUrl, collection, force);
}
else if (this.$common.isObject(beforeSyncPayload)
&& !this.$common.isArray(beforeSyncPayload)
&& !this.$common.isNull(beforeSyncPayload)) {
return this.handlePostRootObjectPayload(beforeSyncPayload, isPersistentUrl, collection, force);
}
}
throw new Error('don\'t know what to do with the payload');
};
/**
* Works like HTTP post
* https://gist.github.com/wookiehangover/877067
*
* @param response
* @param force
* @returns {*}
*/
RequestHandler.prototype.handlePut = function (response, force) {
var parsed = this.$options.parseURL(response.url);
var isPersistentUrl = this.$utils.isPersistUrl(parsed.path);
if (!isPersistentUrl) {
throw new Error('Persistence.RequestHandler.post() given URI not configured for persistence: ' + parsed.path);
}
var queryParams = this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
var collection = this.$db.getCollectionByName(queryParams.root);
var payload = response.data;
var keyValueObj = this.buildFindQueryBasedOnUrlParams(queryParams);
if (!this.$common.isEmpty(keyValueObj)) {
var result = collection.findOne(keyValueObj);
if (result) {
var newObj = this.createObjFromUrlParamsForExistingForPost(result, isPersistentUrl, queryParams, payload);
this.markObjAsOfflineIfForced(newObj.obj, force);
collection.update(newObj.obj);
return newObj.result;
}
else {
var newObj = this.createObjFromUrlParamsForExistingForPost(keyValueObj, isPersistentUrl, queryParams, payload);
this.markObjAsOfflineIfForced(newObj.obj, force);
collection.insert(newObj.obj);
return newObj.result;
}
}
if (this.$common.isArray(payload)) {
return this.handlePostRootArrayPayload(payload, isPersistentUrl, collection, force);
}
else if (this.$common.isObject(payload) && !this.$common.isArray(payload) && !this.$common.isNull(payload)) {
return this.handlePostRootObjectPayload(payload, isPersistentUrl, collection, force);
}
throw new Error('no key specified to recognise obj in the database for editing!');
};
/**
* Works like HTTP post
* https://gist.github.com/wookiehangover/877067
*
* @param response
* @param force
* @returns {*}
*/
RequestHandler.prototype.handlePatch = function (response, force) {
var parsed = this.$options.parseURL(response.url);
var isPersistentUrl = this.$utils.isPersistUrl(parsed.path);
if (!isPersistentUrl) {
throw new Error('Persistence.RequestHandler.post() given URI not configured for persistence: ' + parsed.path);
}
var queryParams = this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
var collection = this.$db.getCollectionByName(queryParams.root);
var payload = response.data;
var keyValueObj = this.buildFindQueryBasedOnUrlParams(queryParams);
if (!this.$common.isEmpty(keyValueObj)) {
var result = collection.findOne(keyValueObj);
if (result) {
var newObj = this.createObjFromUrlParamsForExistingForPost(result, isPersistentUrl, queryParams, payload);
this.markObjAsOfflineIfForced(newObj.obj, force);
collection.update(newObj.obj);
return newObj.result;
}
else {
var newObj = this.createObjFromUrlParamsForExistingForPost(keyValueObj, isPersistentUrl, queryParams, payload);
this.markObjAsOfflineIfForced(newObj.obj, force);
collection.insert(newObj.obj);
return newObj.result;
}
}
if (this.$common.isArray(payload)) {
return this.handlePostRootArrayPayload(payload, isPersistentUrl, collection, force);
}
else if (this.$common.isObject(payload) && !this.$common.isArray(payload) && !this.$common.isNull(payload)) {
return this.handlePostRootObjectPayload(payload, isPersistentUrl, collection, force);
}
throw new Error('no key specified to recognise obj in the database for editing!');
};
/**
* Delete specific element from the offline db
*
* @param request
* @returns {*}
*/
RequestHandler.prototype.handleDelete = function (request) {
var parsed = this.$options.parseURL(request.url);
var isPersistentUrl = this.$utils.isPersistUrl(parsed.path);
if (!isPersistentUrl) {
throw new Error('Persistence.RequestHandler.post() given URI not configured for persistence: ' + parsed.path);
}
var queryParams = this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
var collection = this.$db.getCollectionByName(queryParams.root);
var keyValueObj = this.buildFindQueryBasedOnUrlParams(queryParams);
if (!this.$common.isEmpty(keyValueObj)) {
var findOne = collection.findOne(keyValueObj);
if (findOne) {
return this.$common.cleanObject(collection.remove(findOne));
}
throw new Error('unable to find object with the given ID(%s) in the database');
}
if (request.hasOwnProperty('data')) {
var payload = request.data;
if (!this.$common.isEmpty(payload) && this.$common.isObject(payload) && !this.$common.isArray(payload)) {
var keyNameToCompare = (isPersistentUrl.uri.tokens.length > 1) ? isPersistentUrl.uri.tokens[1].name : null;
if (keyNameToCompare && !request.data.hasOwnProperty(keyNameToCompare)) {
throw new Error('payload does not have the key required to delete the object');
}
var findOne = collection.findOne({ keyNameToCompare: request.data[keyNameToCompare] });
if (findOne) {
return this.$common.cleanObject(collection.remove(findOne));
}
}
}
this.logger.error('payload does not have the key required to delete the object in the payload');
throw new Error('payload does not have the key required to delete the object in the payload');
};
RequestHandler.prototype.postSuccessOperations = function (obj) {
if (obj.syncObj.method === 'POST') {
var cpObj = this.$common.clone(obj);
var parsed = this.$options.parseURL(cpObj.syncObj.url);
var isPersistentUrl = this.$utils.isPersistUrl(parsed.path);
if (!isPersistentUrl) {
this.logger.debug('sync post success operation exist');
return obj;
}
var queryParams = this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
var collection = this.$db.getCollectionByName(queryParams.root);
var beforeSyncPayload = cpObj.syncObj.data;
var response = cpObj.response;
var keyValueObj = this.buildFindQueryBasedOnUrlParams(queryParams);
if (!this.$common.isEmpty(keyValueObj)) {
var parent_1 = collection.findOne(keyValueObj);
if (parent_1) {
var newObj = this.createObjFromUrlParamsForExistingForPost(parent_1, isPersistentUrl, queryParams, response, beforeSyncPayload);
var propertyName = isPersistentUrl.uri.tokens[isPersistentUrl.uri.tokens.length - 1].name;
newObj.result[propertyName] = this.buildUniqueIDValue(isPersistentUrl, newObj.result[propertyName]);
collection.update(newObj.obj);
return this.$common.cleanObject(newObj.result);
}
else {
var newObj = this.createObjFromUrlParamsForExistingForPost(keyValueObj, isPersistentUrl, queryParams, response);
collection.insert(newObj.obj);
return this.$common.cleanObject(newObj.result);
}
}
else {
var keyNameToCompare = (isPersistentUrl.uri.tokens.length > 1) ? isPersistentUrl.uri.tokens[1].name : '';
if (keyNameToCompare && !beforeSyncPayload.hasOwnProperty(keyNameToCompare)) {
response = this.$common.extendOwn(beforeSyncPayload, response);
return this.$common.cleanObject(collection.insert(response));
}
else {
var queryForObject = {};
queryForObject[keyNameToCompare] = beforeSyncPayload[keyNameToCompare];
var result = collection.findOne(queryForObject);
this.$common.extendOwn(result, response);
return this.$common.cleanObject(collection.update(result));
}
}
}
return obj;
};
RequestHandler.prototype.flush = function (path) {
var _this = this;
this.logger.info('Persistence.RequestHandler.flush()', path);
if (this.$common.isEmpty(path)) {
return this.$db.flush();
}
else {
var parsed_1 = this.$options.parseURL(path);
var isPersistentUrl_1 = this.$utils.isPersistUrl(parsed_1.path);
return new Promise(function (resolve, reject) {
if (!isPersistentUrl_1) {
reject(new Error('Persistence.RequestHandler.flush() ' +
("given URI not configured for persistence: " + parsed_1.path)));
}
else {
var queryParams = _this.$utils.extractKeyValuesFromUrl2(isPersistentUrl_1);
resolve(_this.$db.getCollectionByName(queryParams.root).removeDataOnly());
}
});
}
};
RequestHandler.prototype.getDB = function () {
return this.$db;
};
RequestHandler.prototype.get = function (request) {
var _this = this;
var doGet = function (request) {
_this.logger.info('get()');
_this.isPersistentGetRequest(request);
var _request = _this.$common.clone(request);
if (!_request.hasOwnProperty('data') || _this.$common.isEmpty(_request.data)) {
return _this.$common.clone(_this.handleGet(_request));
}
return _this.$common.clone(_this.handleGetStore(_request));
};
return new Promise(function (resolve) {
resolve(doGet(request));
});
};
RequestHandler.prototype.post = function (request, force) {
var _this = this;
var doPost = function (request, force) {
_this.logger.info('post()');
_this.isPostRequest(request);
var _request = _this.$common.clone(request);
return _this.$common.clone(_this.handlePost(_request, force));
};
return new Promise(function (resolve) {
resolve(doPost(request, force));
});
};
RequestHandler.prototype.put = function (request, force) {
var _this = this;
var doPut = function (request, force) {
_this.logger.info('put()');
_this.isPostRequest(request);
var _request = _this.$common.clone(request);
return _this.$common.clone(_this.handlePut(_request, force));
};
return new Promise(function (resolve) {
resolve(doPut(request, force));
});
};
RequestHandler.prototype.patch = function (request, force) {
var _this = this;
var doPatch = function (request, force) {
_this.logger.info('patch()');
_this.isPostRequest(request);
var _request = _this.$common.clone(request);
return _this.$common.clone(_this.handlePatch(_request, force));
};
return new Promise(function (resolve) {
resolve(doPatch(request, force));
});
};
RequestHandler.prototype.delete = function (request) {
var _this = this;
var doDelete = function (request) {
_this.logger.info('delete()');
_this.isPersistentRequest(request);
var _request = _this.$common.clone(request);
return _this.$common.clone(_this.handleDelete(_request));
};
return new Promise(function (resolve) {
resolve(doDelete(request));
});
};
RequestHandler.prototype.data = function (path) {
var _this = this;
var doData = function (path) {
_this.logger.info('data()');
if (path == null) {
throw new Error('Path cannot be empty!');
}
var parsed = _this.$options.parseURL(path);
var isPersistentUrl = _this.$utils.isPersistUrl(parsed.path);
if (!isPersistentUrl) {
throw new Error('post() given URI not configured for persistence: ' + parsed.path);
}
var queryParams = _this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
return _this.$db.getCollectionByName(queryParams.root);
};
return new Promise(function (resolve) {
resolve(doData(path));
});
};
RequestHandler.prototype.router = function (request, force) {
var _this = this;
if (force === void 0) { force = false; }
return new Promise(function (resolve) {
if (!_this.isPersistentGetRequest(request)) {
_this.logger.error('Passed object is not defined request for GET!', request.url);
throw new Error('Passed object is not defined request for GET!');
}
if (!request.hasOwnProperty('method')) {
_this.logger.error('request.method was not provided!', request);
throw new Error('request.method was not provided!');
}
var _request = _this.$common.clone(request);
_request.method = _this.$utils._normalizeMethod(_request.method);
if (!_this[_request.method]) {
_this.logger.error('specified router is not implemented!');
throw new Error('specified router is not implemented!');
}
var result = _this[_request.method](_request, force);
resolve(result);
});
};
RequestHandler.prototype._createDatabase = function (dbAdapter) {
return new db_1.DB(this.prefix + '.' + this.dbName, this.$options, dbAdapter);
};
return RequestHandler;
}());
exports.RequestHandler = RequestHandler;
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var logger_1 = __webpack_require__(1);
var user_1 = __webpack_require__(35);
var network_response_1 = __webpack_require__(2);
var authorization_1 = __webpack_require__(4);
var types_1 = __webpack_require__(0);
var AuthenticationResponse = /** @class */ (function (_super) {
__extends(AuthenticationResponse, _super);
function AuthenticationResponse(response, anonymousAccessToken) {
var _this = _super.call(this, response.statusCode, response.data, response.headers) || this;
_this.accessToken = anonymousAccessToken;
return _this;
}
return AuthenticationResponse;
}(network_response_1.NetworkResponse));
exports.AuthenticationResponse = AuthenticationResponse;
/**
* @class
* @global
* @classdesc Class used to authorize a mobile user against Oracle Mobile Hub.
* Callers should use MobileBackend's [authorization]{@link Backend#authorization} property.
* @abstract
* @extends Authorization
* @hideconstructor
*/
var MCSAuthorization = /** @class */ (function (_super) {
__extends(MCSAuthorization, _super);
function MCSAuthorization(backend, utils, platform) {
var _this = _super.call(this, utils, platform) || this;
_this.backend = backend;
_this.utils = utils;
_this.platform = platform;
_this.logger = new logger_1.Logger('MCSAuthorization');
return _this;
}
/**
* Current user data object.
* Object returned from getCurrentUser().
* @typedef MCSAuthorization~CurrentUserData
* @property statusCode {Number} Any HTTP status code returned from the server, if available.
* @property user {User} The user resource returned by the service.
*/
/**
* Get current user data object.
* Returns the user resource associated with the logged in user.
* @return {Promise<MCSAuthorization~CurrentUserData|NetworkResponse>}
* @function
* @name MCSAuthorization#getCurrentUser
* @example <caption>Example usage of mcs.mobileBackend.authorization.getCurrentUser()</caption>
* mcs.mobileBackend.authorization.getCurrentUser().then(
* function(data){
* },
* function(exception){
* });
* @example // Response example
* {
* "id": "c9a5fdc5-737d-4e93-b292-d258ba334149",
* "username": "DwainDRob",
* "email": "js_sdk@mcs.com",
* "firstName": "Mobile",
* "lastName": "User",
* "properties": {}
* }
*/
MCSAuthorization.prototype.getCurrentUser = function () {
return this.platform.invokeService({
method: types_1.HttpMethods.GET,
url: this.backend.getPlatformUrl('users/~'),
headers: this.backend.getHttpHeaders(),
module: types_1.ModuleNames.AUTHORIZATION,
})
.then(invokeServiceSuccess.bind(this), invokeServiceError.bind(this));
function invokeServiceSuccess(response) {
var user = !!response.data ? new user_1.User(response.data) : null;
return { statusCode: response.statusCode, user: user };
}
function invokeServiceError(response) {
return Promise.reject(response);
}
};
/**
* Authenticate success callback
* @param {INetworkResponse} response
* @param {string} accessToken
* @private
*/
MCSAuthorization.prototype._authenticateSuccess = function (response, accessToken) {
this.logger.info('User logged in ' + response.statusCode);
this._isAnonymous = false;
this._isAuthorized = true;
this._accessToken = accessToken;
for (var i = 0; i < this._onAuthenticationCallbacks.length; i++) {
this._onAuthenticationCallbacks[i](accessToken);
}
};
/**
* Returns the username of the current authorized user if any, null otherwise.
* @return {String}
*/
MCSAuthorization.prototype.getAuthorizedUserName = function () {
throw Error('THis method is not supported by this authorization method.');
};
return MCSAuthorization;
}(authorization_1.Authorization));
exports.MCSAuthorization = MCSAuthorization;
/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {
/* WEBPACK VAR INJECTION */(function(global, process) {var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/**
* LokiJS
* @author Joe Minichino <joe.minichino@gmail.com>
*
* A lightweight document oriented javascript database
*/
(function (root, factory) {
if (true) {
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else {}
}(this, function () {
return (function () {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty;
var Utils = {
copyProperties: function (src, dest) {
var prop;
for (prop in src) {
dest[prop] = src[prop];
}
},
resolveTransformObject: function (subObj, params, depth) {
var prop,
pname;
if (typeof depth !== 'number') {
depth = 0;
}
if (++depth >= 10) return subObj;
for (prop in subObj) {
if (typeof subObj[prop] === 'string' && subObj[prop].indexOf("[%lktxp]") === 0) {
pname = subObj[prop].substring(8);
if (params.hasOwnProperty(pname)) {
subObj[prop] = params[pname];
}
} else if (typeof subObj[prop] === "object") {
subObj[prop] = Utils.resolveTransformObject(subObj[prop], params, depth);
}
}
return subObj;
},
resolveTransformParams: function (transform, params) {
var idx,
clonedStep,
resolvedTransform = [];
if (typeof params === 'undefined') return transform;
for (idx = 0; idx < transform.length; idx++) {
clonedStep = clone(transform[idx], "shallow-recurse-objects");
resolvedTransform.push(Utils.resolveTransformObject(clonedStep, params));
}
return resolvedTransform;
}
};
var Comparators = {
aeq: aeqHelper,
lt: ltHelper,
gt: gtHelper
};
/** Helper function for determining 'loki' abstract equality which is a little more abstract than ==
* aeqHelper(5, '5') === true
* aeqHelper(5.0, '5') === true
* aeqHelper(new Date("1/1/2011"), new Date("1/1/2011")) === true
* aeqHelper({a:1}, {z:4}) === true (all objects sorted equally)
* aeqHelper([1, 2, 3], [1, 3]) === false
* aeqHelper([1, 2, 3], [1, 2, 3]) === true
* aeqHelper(undefined, null) === true
*/
function aeqHelper(prop1, prop2) {
var cv1, cv2, t1, t2;
if (prop1 === prop2) return true;
if (!prop1 || !prop2 || prop1 === true || prop2 === true || prop1 !== prop1 || prop2 !== prop2) {
switch (prop1) {
case undefined: t1 = 1; break;
case null: t1 = 1; break;
case false: t1 = 3; break;
case true: t1 = 4; break;
case "": t1 = 5; break;
default: t1 = (prop1 === prop1)?9:0; break;
}
switch (prop2) {
case undefined: t2 = 1; break;
case null: t2 = 1; break;
case false: t2 = 3; break;
case true: t2 = 4; break;
case "": t2 = 5; break;
default: t2 = (prop2 === prop2)?9:0; break;
}
if (t1 !== 9 || t2 !== 9) {
return (t1===t2);
}
}
cv1 = Number(prop1);
cv2 = Number(prop2);
if (cv1 === cv1 || cv2 === cv2) {
return (cv1 === cv2);
}
cv1 = prop1.toString();
cv2 = prop2.toString();
return (cv1 == cv2);
}
/** Helper function for determining 'less-than' conditions for ops, sorting, and binary indices.
* In the future we might want $lt and $gt ops to use their own functionality/helper.
* Since binary indices on a property might need to index [12, NaN, new Date(), Infinity], we
* need this function (as well as gtHelper) to always ensure one value is LT, GT, or EQ to another.
*/
function ltHelper(prop1, prop2, equal) {
var cv1, cv2, t1, t2;
if (!prop1 || !prop2 || prop1 === true || prop2 === true || prop1 !== prop1 || prop2 !== prop2) {
switch (prop1) {
case undefined: t1 = 1; break;
case null: t1 = 1; break;
case false: t1 = 3; break;
case true: t1 = 4; break;
case "": t1 = 5; break;
default: t1 = (prop1 === prop1)?9:0; break;
}
switch (prop2) {
case undefined: t2 = 1; break;
case null: t2 = 1; break;
case false: t2 = 3; break;
case true: t2 = 4; break;
case "": t2 = 5; break;
default: t2 = (prop2 === prop2)?9:0; break;
}
if (t1 !== 9 || t2 !== 9) {
return (t1===t2)?equal:(t1<t2);
}
}
cv1 = Number(prop1);
cv2 = Number(prop2);
if (cv1 === cv1 && cv2 === cv2) {
if (cv1 < cv2) return true;
if (cv1 > cv2) return false;
return equal;
}
if (cv1 === cv1 && cv2 !== cv2) {
return true;
}
if (cv2 === cv2 && cv1 !== cv1) {
return false;
}
if (prop1 < prop2) return true;
if (prop1 > prop2) return false;
if (prop1 == prop2) return equal;
cv1 = prop1.toString();
cv2 = prop2.toString();
if (cv1 < cv2) {
return true;
}
if (cv1 == cv2) {
return equal;
}
return false;
}
function gtHelper(prop1, prop2, equal) {
var cv1, cv2, t1, t2;
if (!prop1 || !prop2 || prop1 === true || prop2 === true || prop1 !== prop1 || prop2 !== prop2) {
switch (prop1) {
case undefined: t1 = 1; break;
case null: t1 = 1; break;
case false: t1 = 3; break;
case true: t1 = 4; break;
case "": t1 = 5; break;
default: t1 = (prop1 === prop1)?9:0; break;
}
switch (prop2) {
case undefined: t2 = 1; break;
case null: t2 = 1; break;
case false: t2 = 3; break;
case true: t2 = 4; break;
case "": t2 = 5; break;
default: t2 = (prop2 === prop2)?9:0; break;
}
if (t1 !== 9 || t2 !== 9) {
return (t1===t2)?equal:(t1>t2);
}
}
cv1 = Number(prop1);
cv2 = Number(prop2);
if (cv1 === cv1 && cv2 === cv2) {
if (cv1 > cv2) return true;
if (cv1 < cv2) return false;
return equal;
}
if (cv1 === cv1 && cv2 !== cv2) {
return false;
}
if (cv2 === cv2 && cv1 !== cv1) {
return true;
}
if (prop1 > prop2) return true;
if (prop1 < prop2) return false;
if (prop1 == prop2) return equal;
cv1 = prop1.toString();
cv2 = prop2.toString();
if (cv1 > cv2) {
return true;
}
if (cv1 == cv2) {
return equal;
}
return false;
}
function sortHelper(prop1, prop2, desc) {
if (Comparators.aeq(prop1, prop2)) return 0;
if (Comparators.lt(prop1, prop2, false)) {
return (desc) ? (1) : (-1);
}
if (Comparators.gt(prop1, prop2, false)) {
return (desc) ? (-1) : (1);
}
return 0;
}
/**
* compoundeval() - helper function for compoundsort(), performing individual object comparisons
*
* @param {array} properties - array of property names, in order, by which to evaluate sort order
* @param {object} obj1 - first object to compare
* @param {object} obj2 - second object to compare
* @returns {integer} 0, -1, or 1 to designate if identical (sortwise) or which should be first
*/
function compoundeval(properties, obj1, obj2) {
var res = 0;
var prop, field, val1, val2, arr;
for (var i = 0, len = properties.length; i < len; i++) {
prop = properties[i];
field = prop[0];
if (~field.indexOf('.')) {
arr = field.split('.');
val1 = arr.reduce(function(obj, i) { return obj && obj[i] || undefined; }, obj1);
val2 = arr.reduce(function(obj, i) { return obj && obj[i] || undefined; }, obj2);
} else {
val1 = obj1[field];
val2 = obj2[field];
}
res = sortHelper(val1, val2, prop[1]);
if (res !== 0) {
return res;
}
}
return 0;
}
/**
* dotSubScan - helper function used for dot notation queries.
*
* @param {object} root - object to traverse
* @param {array} paths - array of properties to drill into
* @param {function} fun - evaluation function to test with
* @param {any} value - comparative value to also pass to (compare) fun
* @param {number} poffset - index of the item in 'paths' to start the sub-scan from
*/
function dotSubScan(root, paths, fun, value, poffset) {
var pathOffset = poffset || 0;
var path = paths[pathOffset];
if (root === undefined || root === null || !hasOwnProperty.call(root, path)) {
return false;
}
var valueFound = false;
var element = root[path];
if (pathOffset + 1 >= paths.length) {
valueFound = fun(element, value);
} else if (Array.isArray(element)) {
for (var index = 0, len = element.length; index < len; index += 1) {
valueFound = dotSubScan(element[index], paths, fun, value, pathOffset + 1);
if (valueFound === true) {
break;
}
}
} else {
valueFound = dotSubScan(element, paths, fun, value, pathOffset + 1);
}
return valueFound;
}
function containsCheckFn(a) {
if (typeof a === 'string' || Array.isArray(a)) {
return function (b) {
return a.indexOf(b) !== -1;
};
} else if (typeof a === 'object' && a !== null) {
return function (b) {
return hasOwnProperty.call(a, b);
};
}
return null;
}
function doQueryOp(val, op) {
for (var p in op) {
if (hasOwnProperty.call(op, p)) {
return LokiOps[p](val, op[p]);
}
}
return false;
}
var LokiOps = {
$eq: function (a, b) {
return a === b;
},
$aeq: function (a, b) {
return a == b;
},
$ne: function (a, b) {
if (b !== b) {
return (a === a);
}
return a !== b;
},
$dteq: function (a, b) {
return Comparators.aeq(a, b);
},
$gt: function (a, b) {
return Comparators.gt(a, b, false);
},
$gte: function (a, b) {
return Comparators.gt(a, b, true);
},
$lt: function (a, b) {
return Comparators.lt(a, b, false);
},
$lte: function (a, b) {
return Comparators.lt(a, b, true);
},
$jgt: function (a, b) {
return a > b;
},
$jgte: function (a, b) {
return a >= b;
},
$jlt: function (a, b) {
return a < b;
},
$jlte: function (a, b) {
return a <= b;
},
$between: function (a, vals) {
if (a === undefined || a === null) return false;
return (Comparators.gt(a, vals[0], true) && Comparators.lt(a, vals[1], true));
},
$jbetween: function (a, vals) {
if (a === undefined || a === null) return false;
return (a >= vals[0] && a <= vals[1]);
},
$in: function (a, b) {
return b.indexOf(a) !== -1;
},
$nin: function (a, b) {
return b.indexOf(a) === -1;
},
$keyin: function (a, b) {
return a in b;
},
$nkeyin: function (a, b) {
return !(a in b);
},
$definedin: function (a, b) {
return b[a] !== undefined;
},
$undefinedin: function (a, b) {
return b[a] === undefined;
},
$regex: function (a, b) {
return b.test(a);
},
$containsString: function (a, b) {
return (typeof a === 'string') && (a.indexOf(b) !== -1);
},
$containsNone: function (a, b) {
return !LokiOps.$containsAny(a, b);
},
$containsAny: function (a, b) {
var checkFn = containsCheckFn(a);
if (checkFn !== null) {
return (Array.isArray(b)) ? (b.some(checkFn)) : (checkFn(b));
}
return false;
},
$contains: function (a, b) {
var checkFn = containsCheckFn(a);
if (checkFn !== null) {
return (Array.isArray(b)) ? (b.every(checkFn)) : (checkFn(b));
}
return false;
},
$type: function (a, b) {
var type = typeof a;
if (type === 'object') {
if (Array.isArray(a)) {
type = 'array';
} else if (a instanceof Date) {
type = 'date';
}
}
return (typeof b !== 'object') ? (type === b) : doQueryOp(type, b);
},
$finite: function(a, b) {
return (b === isFinite(a));
},
$size: function (a, b) {
if (Array.isArray(a)) {
return (typeof b !== 'object') ? (a.length === b) : doQueryOp(a.length, b);
}
return false;
},
$len: function (a, b) {
if (typeof a === 'string') {
return (typeof b !== 'object') ? (a.length === b) : doQueryOp(a.length, b);
}
return false;
},
$where: function (a, b) {
return b(a) === true;
},
$not: function (a, b) {
return !doQueryOp(a, b);
},
$and: function (a, b) {
for (var idx = 0, len = b.length; idx < len; idx += 1) {
if (!doQueryOp(a, b[idx])) {
return false;
}
}
return true;
},
$or: function (a, b) {
for (var idx = 0, len = b.length; idx < len; idx += 1) {
if (doQueryOp(a, b[idx])) {
return true;
}
}
return false;
}
};
var indexedOps = {
$eq: LokiOps.$eq,
$aeq: true,
$dteq: true,
$gt: true,
$gte: true,
$lt: true,
$lte: true,
$in: true,
$between: true
};
function clone(data, method) {
if (data === null || data === undefined) {
return null;
}
var cloneMethod = method || 'parse-stringify',
cloned;
switch (cloneMethod) {
case "parse-stringify":
cloned = JSON.parse(JSON.stringify(data));
break;
case "jquery-extend-deep":
cloned = jQuery.extend(true, {}, data);
break;
case "shallow":
cloned = Object.create(data.constructor.prototype);
Object.keys(data).map(function (i) {
cloned[i] = data[i];
});
break;
case "shallow-assign":
cloned = Object.create(data.constructor.prototype);
Object.assign(cloned, data);
break;
case "shallow-recurse-objects":
cloned = clone(data, "shallow");
var keys = Object.keys(data);
keys.forEach(function(key) {
if (typeof data[key] === "object" && data[key].constructor.name === "Object") {
cloned[key] = clone(data[key], "shallow-recurse-objects");
}
});
break;
default:
break;
}
return cloned;
}
function cloneObjectArray(objarray, method) {
var i,
result = [];
if (method == "parse-stringify") {
return clone(objarray, method);
}
i = objarray.length - 1;
for (; i <= 0; i--) {
result.push(clone(objarray[i], method));
}
return result;
}
function localStorageAvailable() {
try {
return (window && window.localStorage !== undefined && window.localStorage !== null);
} catch (e) {
return false;
}
}
/**
* LokiEventEmitter is a minimalist version of EventEmitter. It enables any
* constructor that inherits EventEmitter to emit events and trigger
* listeners that have been added to the event through the on(event, callback) method
*
* @constructor LokiEventEmitter
*/
function LokiEventEmitter() {}
/**
* @prop {hashmap} events - a hashmap, with each property being an array of callbacks
* @memberof LokiEventEmitter
*/
LokiEventEmitter.prototype.events = {};
/**
* @prop {boolean} asyncListeners - boolean determines whether or not the callbacks associated with each event
* should happen in an async fashion or not
* Default is false, which means events are synchronous
* @memberof LokiEventEmitter
*/
LokiEventEmitter.prototype.asyncListeners = false;
/**
* on(eventName, listener) - adds a listener to the queue of callbacks associated to an event
* @param {string|string[]} eventName - the name(s) of the event(s) to listen to
* @param {function} listener - callback function of listener to attach
* @returns {int} the index of the callback in the array of listeners for a particular event
* @memberof LokiEventEmitter
*/
LokiEventEmitter.prototype.on = function (eventName, listener) {
var event;
var self = this;
if (Array.isArray(eventName)) {
eventName.forEach(function(currentEventName) {
self.on(currentEventName, listener);
});
return listener;
}
event = this.events[eventName];
if (!event) {
event = this.events[eventName] = [];
}
event.push(listener);
return listener;
};
/**
* emit(eventName, data) - emits a particular event
* with the option of passing optional parameters which are going to be processed by the callback
* provided signatures match (i.e. if passing emit(event, arg0, arg1) the listener should take two parameters)
* @param {string} eventName - the name of the event
* @param {object=} data - optional object passed with the event
* @memberof LokiEventEmitter
*/
LokiEventEmitter.prototype.emit = function (eventName) {
var self = this;
var selfArgs = Array.prototype.slice.call(arguments, 1);
if (eventName && this.events[eventName]) {
this.events[eventName].forEach(function (listener) {
if (self.asyncListeners) {
setTimeout(function () {
listener.apply(self, selfArgs);
}, 1);
} else {
listener.apply(self, selfArgs);
}
});
} else {
throw new Error('No event ' + eventName + ' defined');
}
};
/**
* Alias of LokiEventEmitter.prototype.on
* addListener(eventName, listener) - adds a listener to the queue of callbacks associated to an event
* @param {string|string[]} eventName - the name(s) of the event(s) to listen to
* @param {function} listener - callback function of listener to attach
* @returns {int} the index of the callback in the array of listeners for a particular event
* @memberof LokiEventEmitter
*/
LokiEventEmitter.prototype.addListener = LokiEventEmitter.prototype.on;
/**
* removeListener() - removes the listener at position 'index' from the event 'eventName'
* @param {string|string[]} eventName - the name(s) of the event(s) which the listener is attached to
* @param {function} listener - the listener callback function to remove from emitter
* @memberof LokiEventEmitter
*/
LokiEventEmitter.prototype.removeListener = function (eventName, listener) {
var self = this;
if (Array.isArray(eventName)) {
eventName.forEach(function(currentEventName) {
self.removeListener(currentEventName, listener);
});
return;
}
if (this.events[eventName]) {
var listeners = this.events[eventName];
listeners.splice(listeners.indexOf(listener), 1);
}
};
/**
* Loki: The main database class
* @constructor Loki
* @implements LokiEventEmitter
* @param {string} filename - name of the file to be saved to
* @param {object=} options - (Optional) config options object
* @param {string} options.env - override environment detection as 'NODEJS', 'BROWSER', 'CORDOVA'
* @param {boolean} [options.verbose=false] - enable console output
* @param {boolean} [options.autosave=false] - enables autosave
* @param {int} [options.autosaveInterval=5000] - time interval (in milliseconds) between saves (if dirty)
* @param {boolean} [options.autoload=false] - enables autoload on loki instantiation
* @param {function} options.autoloadCallback - user callback called after database load
* @param {adapter} options.adapter - an instance of a loki persistence adapter
* @param {string} [options.serializationMethod='normal'] - ['normal', 'pretty', 'destructured']
* @param {string} options.destructureDelimiter - string delimiter used for destructured serialization
* @param {boolean} [options.throttledSaves=true] - debounces multiple calls to to saveDatabase reducing number of disk I/O operations
and guaranteeing proper serialization of the calls.
*/
function Loki(filename, options) {
this.filename = filename || 'loki.db';
this.collections = [];
this.databaseVersion = 1.5;
this.engineVersion = 1.5;
this.autosave = false;
this.autosaveInterval = 5000;
this.autosaveHandle = null;
this.throttledSaves = true;
this.options = {};
this.persistenceMethod = null;
this.persistenceAdapter = null;
this.throttledSavePending = false;
this.throttledCallbacks = [];
this.verbose = options && options.hasOwnProperty('verbose') ? options.verbose : false;
this.events = {
'init': [],
'loaded': [],
'flushChanges': [],
'close': [],
'changes': [],
'warning': []
};
var getENV = function () {
if (typeof global !== 'undefined' && (global.android || global.NSObject)) {
return 'NATIVESCRIPT'; //nativescript
}
if (typeof window === 'undefined') {
return 'NODEJS';
}
if (typeof global !== 'undefined' && global.window && process) {
return 'NODEJS'; //node-webkit
}
if (typeof document !== 'undefined') {
if (document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1) {
return 'CORDOVA';
}
return 'BROWSER';
}
return 'CORDOVA';
};
if (options && options.hasOwnProperty('env')) {
this.ENV = options.env;
} else {
this.ENV = getENV();
}
if (this.ENV === 'undefined') {
this.ENV = 'NODEJS';
}
this.configureOptions(options, true);
this.on('init', this.clearChanges);
}
Loki.prototype = new LokiEventEmitter();
Loki.prototype.constructor = Loki;
Loki.prototype.getIndexedAdapter = function () {
var adapter;
if (true) {
adapter = __webpack_require__(12);
}
return adapter;
};
/**
* Allows reconfiguring database options
*
* @param {object} options - configuration options to apply to loki db object
* @param {string} options.env - override environment detection as 'NODEJS', 'BROWSER', 'CORDOVA'
* @param {boolean} options.verbose - enable console output (default is 'false')
* @param {boolean} options.autosave - enables autosave
* @param {int} options.autosaveInterval - time interval (in milliseconds) between saves (if dirty)
* @param {boolean} options.autoload - enables autoload on loki instantiation
* @param {function} options.autoloadCallback - user callback called after database load
* @param {adapter} options.adapter - an instance of a loki persistence adapter
* @param {string} options.serializationMethod - ['normal', 'pretty', 'destructured']
* @param {string} options.destructureDelimiter - string delimiter used for destructured serialization
* @param {boolean} initialConfig - (internal) true is passed when loki ctor is invoking
* @memberof Loki
*/
Loki.prototype.configureOptions = function (options, initialConfig) {
var defaultPersistence = {
'NODEJS': 'fs',
'BROWSER': 'localStorage',
'CORDOVA': 'localStorage',
'MEMORY': 'memory'
},
persistenceMethods = {
'fs': LokiFsAdapter,
'localStorage': LokiLocalStorageAdapter,
'memory': LokiMemoryAdapter
};
this.options = {};
this.persistenceMethod = null;
this.persistenceAdapter = null;
if (typeof (options) !== 'undefined') {
this.options = options;
if (this.options.hasOwnProperty('persistenceMethod')) {
if (typeof (persistenceMethods[options.persistenceMethod]) == 'function') {
this.persistenceMethod = options.persistenceMethod;
this.persistenceAdapter = new persistenceMethods[options.persistenceMethod]();
}
}
if (this.options.hasOwnProperty('adapter')) {
this.persistenceMethod = 'adapter';
this.persistenceAdapter = options.adapter;
this.options.adapter = null;
}
if (options.autoload && initialConfig) {
var self = this;
setTimeout(function () {
self.loadDatabase(options, options.autoloadCallback);
}, 1);
}
if (this.options.hasOwnProperty('autosaveInterval')) {
this.autosaveDisable();
this.autosaveInterval = parseInt(this.options.autosaveInterval, 10);
}
if (this.options.hasOwnProperty('autosave') && this.options.autosave) {
this.autosaveDisable();
this.autosave = true;
if (this.options.hasOwnProperty('autosaveCallback')) {
this.autosaveEnable(options, options.autosaveCallback);
} else {
this.autosaveEnable();
}
}
if (this.options.hasOwnProperty('throttledSaves')) {
this.throttledSaves = this.options.throttledSaves;
}
} // end of options processing
if (!this.options.hasOwnProperty('serializationMethod')) {
this.options.serializationMethod = 'normal';
}
if (!this.options.hasOwnProperty('destructureDelimiter')) {
this.options.destructureDelimiter = '$<\n';
}
if (this.persistenceAdapter === null) {
this.persistenceMethod = defaultPersistence[this.ENV];
if (this.persistenceMethod) {
this.persistenceAdapter = new persistenceMethods[this.persistenceMethod]();
}
}
};
/**
* Copies 'this' database into a new Loki instance. Object references are shared to make lightweight.
*
* @param {object} options - apply or override collection level settings
* @param {bool} options.removeNonSerializable - nulls properties not safe for serialization.
* @memberof Loki
*/
Loki.prototype.copy = function(options) {
var databaseCopy = new Loki(this.filename, { env: "NA" });
var clen, idx;
options = options || {};
databaseCopy.loadJSONObject(this, { retainDirtyFlags: true });
if(options.hasOwnProperty("removeNonSerializable") && options.removeNonSerializable === true) {
databaseCopy.autosaveHandle = null;
databaseCopy.persistenceAdapter = null;
clen = databaseCopy.collections.length;
for (idx=0; idx<clen; idx++) {
databaseCopy.collections[idx].constraints = null;
databaseCopy.collections[idx].ttl = null;
}
}
return databaseCopy;
};
/**
* Adds a collection to the database.
* @param {string} name - name of collection to add
* @param {object=} options - (optional) options to configure collection with.
* @param {array=} [options.unique=[]] - array of property names to define unique constraints for
* @param {array=} [options.exact=[]] - array of property names to define exact constraints for
* @param {array=} [options.indices=[]] - array property names to define binary indexes for
* @param {boolean} [options.asyncListeners=false] - whether listeners are called asynchronously
* @param {boolean} [options.disableMeta=false] - set to true to disable meta property on documents
* @param {boolean} [options.disableChangesApi=true] - set to false to enable Changes Api
* @param {boolean} [options.disableDeltaChangesApi=true] - set to false to enable Delta Changes API (requires Changes API, forces cloning)
* @param {boolean} [options.autoupdate=false] - use Object.observe to update objects automatically
* @param {boolean} [options.clone=false] - specify whether inserts and queries clone to/from user
* @param {string} [options.cloneMethod='parse-stringify'] - 'parse-stringify', 'jquery-extend-deep', 'shallow, 'shallow-assign'
* @param {int=} options.ttl - age of document (in ms.) before document is considered aged/stale.
* @param {int=} options.ttlInterval - time interval for clearing out 'aged' documents; not set by default.
* @returns {Collection} a reference to the collection which was just added
* @memberof Loki
*/
Loki.prototype.addCollection = function (name, options) {
var i,
len = this.collections.length;
if (options && options.disableMeta === true) {
if (options.disableChangesApi === false) {
throw new Error("disableMeta option cannot be passed as true when disableChangesApi is passed as false");
}
if (options.disableDeltaChangesApi === false) {
throw new Error("disableMeta option cannot be passed as true when disableDeltaChangesApi is passed as false");
}
if (typeof options.ttl === "number" && options.ttl > 0) {
throw new Error("disableMeta option cannot be passed as true when ttl is enabled");
}
}
for (i = 0; i < len; i += 1) {
if (this.collections[i].name === name) {
return this.collections[i];
}
}
var collection = new Collection(name, options);
this.collections.push(collection);
if (this.verbose)
collection.console = console;
return collection;
};
Loki.prototype.loadCollection = function (collection) {
if (!collection.name) {
throw new Error('Collection must have a name property to be loaded');
}
this.collections.push(collection);
};
/**
* Retrieves reference to a collection by name.
* @param {string} collectionName - name of collection to look up
* @returns {Collection} Reference to collection in database by that name, or null if not found
* @memberof Loki
*/
Loki.prototype.getCollection = function (collectionName) {
var i,
len = this.collections.length;
for (i = 0; i < len; i += 1) {
if (this.collections[i].name === collectionName) {
return this.collections[i];
}
}
this.emit('warning', 'collection ' + collectionName + ' not found');
return null;
};
/**
* Renames an existing loki collection
* @param {string} oldName - name of collection to rename
* @param {string} newName - new name of collection
* @returns {Collection} reference to the newly renamed collection
* @memberof Loki
*/
Loki.prototype.renameCollection = function (oldName, newName) {
var c = this.getCollection(oldName);
if (c) {
c.name = newName;
}
return c;
};
/**
* Returns a list of collections in the database.
* @returns {object[]} array of objects containing 'name', 'type', and 'count' properties.
* @memberof Loki
*/
Loki.prototype.listCollections = function () {
var i = this.collections.length,
colls = [];
while (i--) {
colls.push({
name: this.collections[i].name,
type: this.collections[i].objType,
count: this.collections[i].data.length
});
}
return colls;
};
/**
* Removes a collection from the database.
* @param {string} collectionName - name of collection to remove
* @memberof Loki
*/
Loki.prototype.removeCollection = function (collectionName) {
var i,
len = this.collections.length;
for (i = 0; i < len; i += 1) {
if (this.collections[i].name === collectionName) {
var tmpcol = new Collection(collectionName, {});
var curcol = this.collections[i];
for (var prop in curcol) {
if (curcol.hasOwnProperty(prop) && tmpcol.hasOwnProperty(prop)) {
curcol[prop] = tmpcol[prop];
}
}
this.collections.splice(i, 1);
return;
}
}
};
Loki.prototype.getName = function () {
return this.name;
};
/**
* serializeReplacer - used to prevent certain properties from being serialized
*
*/
Loki.prototype.serializeReplacer = function (key, value) {
switch (key) {
case 'autosaveHandle':
case 'persistenceAdapter':
case 'constraints':
case 'ttl':
return null;
case 'throttledSavePending':
case 'throttledCallbacks':
return undefined;
default:
return value;
}
};
/**
* Serialize database to a string which can be loaded via {@link Loki#loadJSON}
*
* @returns {string} Stringified representation of the loki database.
* @memberof Loki
*/
Loki.prototype.serialize = function (options) {
options = options || {};
if (!options.hasOwnProperty("serializationMethod")) {
options.serializationMethod = this.options.serializationMethod;
}
switch(options.serializationMethod) {
case "normal": return JSON.stringify(this, this.serializeReplacer);
case "pretty": return JSON.stringify(this, this.serializeReplacer, 2);
case "destructured": return this.serializeDestructured(); // use default options
default: return JSON.stringify(this, this.serializeReplacer);
}
};
Loki.prototype.toJson = Loki.prototype.serialize;
/**
* Database level destructured JSON serialization routine to allow alternate serialization methods.
* Internally, Loki supports destructuring via loki "serializationMethod' option and
* the optional LokiPartitioningAdapter class. It is also available if you wish to do
* your own structured persistence or data exchange.
*
* @param {object=} options - output format options for use externally to loki
* @param {bool=} options.partitioned - (default: false) whether db and each collection are separate
* @param {int=} options.partition - can be used to only output an individual collection or db (-1)
* @param {bool=} options.delimited - (default: true) whether subitems are delimited or subarrays
* @param {string=} options.delimiter - override default delimiter
*
* @returns {string|array} A custom, restructured aggregation of independent serializations.
* @memberof Loki
*/
Loki.prototype.serializeDestructured = function(options) {
var idx, sidx, result, resultlen;
var reconstruct = [];
var dbcopy;
options = options || {};
if (!options.hasOwnProperty("partitioned")) {
options.partitioned = false;
}
if (!options.hasOwnProperty("delimited")) {
options.delimited = true;
}
if (!options.hasOwnProperty("delimiter")) {
options.delimiter = this.options.destructureDelimiter;
}
if (options.partitioned === true && options.hasOwnProperty("partition") && options.partition >= 0) {
return this.serializeCollection({
delimited: options.delimited,
delimiter: options.delimiter,
collectionIndex: options.partition
});
}
dbcopy = new Loki(this.filename);
dbcopy.loadJSONObject(this);
for(idx=0; idx < dbcopy.collections.length; idx++) {
dbcopy.collections[idx].data = [];
}
if (options.partitioned === true && options.partition === -1) {
return dbcopy.serialize({
serializationMethod: "normal"
});
}
reconstruct.push(dbcopy.serialize({
serializationMethod: "normal"
}));
dbcopy = null;
for(idx=0; idx < this.collections.length; idx++) {
result = this.serializeCollection({
delimited: options.delimited,
delimiter: options.delimiter,
collectionIndex: idx
});
if (options.partitioned === false && options.delimited === false) {
if (!Array.isArray(result)) {
throw new Error("a nondelimited, non partitioned collection serialization did not return an expected array");
}
resultlen = result.length;
for (sidx=0; sidx < resultlen; sidx++) {
reconstruct.push(result[sidx]);
result[sidx] = null;
}
reconstruct.push("");
}
else {
reconstruct.push(result);
}
}
if (options.partitioned) {
if (options.delimited) {
return reconstruct;
}
else {
return reconstruct;
}
}
else {
if (options.delimited) {
reconstruct.push("");
return reconstruct.join(options.delimiter);
}
else {
reconstruct.push("");
return reconstruct;
}
}
reconstruct.push("");
return reconstruct.join(delim);
};
/**
* Collection level utility method to serialize a collection in a 'destructured' format
*
* @param {object=} options - used to determine output of method
* @param {int} options.delimited - whether to return single delimited string or an array
* @param {string} options.delimiter - (optional) if delimited, this is delimiter to use
* @param {int} options.collectionIndex - specify which collection to serialize data for
*
* @returns {string|array} A custom, restructured aggregation of independent serializations for a single collection.
* @memberof Loki
*/
Loki.prototype.serializeCollection = function(options) {
var doccount,
docidx,
resultlines = [];
options = options || {};
if (!options.hasOwnProperty("delimited")) {
options.delimited = true;
}
if (!options.hasOwnProperty("collectionIndex")) {
throw new Error("serializeCollection called without 'collectionIndex' option");
}
doccount = this.collections[options.collectionIndex].data.length;
resultlines = [];
for(docidx=0; docidx<doccount; docidx++) {
resultlines.push(JSON.stringify(this.collections[options.collectionIndex].data[docidx]));
}
if (options.delimited) {
resultlines.push("");
return resultlines.join(options.delimiter);
}
else {
return resultlines;
}
};
/**
* Database level destructured JSON deserialization routine to minimize memory overhead.
* Internally, Loki supports destructuring via loki "serializationMethod' option and
* the optional LokiPartitioningAdapter class. It is also available if you wish to do
* your own structured persistence or data exchange.
*
* @param {string|array} destructuredSource - destructured json or array to deserialize from
* @param {object=} options - source format options
* @param {bool=} [options.partitioned=false] - whether db and each collection are separate
* @param {int=} options.partition - can be used to deserialize only a single partition
* @param {bool=} [options.delimited=true] - whether subitems are delimited or subarrays
* @param {string=} options.delimiter - override default delimiter
*
* @returns {object|array} An object representation of the deserialized database, not yet applied to 'this' db or document array
* @memberof Loki
*/
Loki.prototype.deserializeDestructured = function(destructuredSource, options) {
var workarray=[];
var len, cdb;
var idx, collIndex=0, collCount, lineIndex=1, done=false;
var currLine, currObject;
options = options || {};
if (!options.hasOwnProperty("partitioned")) {
options.partitioned = false;
}
if (!options.hasOwnProperty("delimited")) {
options.delimited = true;
}
if (!options.hasOwnProperty("delimiter")) {
options.delimiter = this.options.destructureDelimiter;
}
if (options.partitioned) {
if (options.hasOwnProperty('partition')) {
if (options.partition === -1) {
cdb = JSON.parse(destructuredSource[0]);
return cdb;
}
return this.deserializeCollection(destructuredSource[options.partition+1], options);
}
cdb = JSON.parse(destructuredSource[0]);
collCount = cdb.collections.length;
for(collIndex=0; collIndex<collCount; collIndex++) {
cdb.collections[collIndex].data = this.deserializeCollection(destructuredSource[collIndex+1], options);
}
return cdb;
}
if (options.delimited) {
workarray = destructuredSource.split(options.delimiter);
destructuredSource = null; // lower memory pressure
len = workarray.length;
if (len === 0) {
return null;
}
}
else {
workarray = destructuredSource;
}
cdb = JSON.parse(workarray[0]);
collCount = cdb.collections.length;
workarray[0] = null;
while (!done) {
currLine = workarray[lineIndex];
if (workarray[lineIndex] === "") {
if (++collIndex > collCount) {
done = true;
}
}
else {
currObject = JSON.parse(workarray[lineIndex]);
cdb.collections[collIndex].data.push(currObject);
}
workarray[lineIndex++] = null;
}
return cdb;
};
/**
* Collection level utility function to deserializes a destructured collection.
*
* @param {string|array} destructuredSource - destructured representation of collection to inflate
* @param {object=} options - used to describe format of destructuredSource input
* @param {int=} [options.delimited=false] - whether source is delimited string or an array
* @param {string=} options.delimiter - if delimited, this is delimiter to use (if other than default)
*
* @returns {array} an array of documents to attach to collection.data.
* @memberof Loki
*/
Loki.prototype.deserializeCollection = function(destructuredSource, options) {
var workarray=[];
var idx, len;
options = options || {};
if (!options.hasOwnProperty("partitioned")) {
options.partitioned = false;
}
if (!options.hasOwnProperty("delimited")) {
options.delimited = true;
}
if (!options.hasOwnProperty("delimiter")) {
options.delimiter = this.options.destructureDelimiter;
}
if (options.delimited) {
workarray = destructuredSource.split(options.delimiter);
workarray.pop();
}
else {
workarray = destructuredSource;
}
len = workarray.length;
for (idx=0; idx < len; idx++) {
workarray[idx] = JSON.parse(workarray[idx]);
}
return workarray;
};
/**
* Inflates a loki database from a serialized JSON string
*
* @param {string} serializedDb - a serialized loki database string
* @param {object=} options - apply or override collection level settings
* @param {bool} options.retainDirtyFlags - whether collection dirty flags will be preserved
* @memberof Loki
*/
Loki.prototype.loadJSON = function (serializedDb, options) {
var dbObject;
if (serializedDb.length === 0) {
dbObject = {};
} else {
switch (this.options.serializationMethod) {
case "normal":
case "pretty": dbObject = JSON.parse(serializedDb); break;
case "destructured": dbObject = this.deserializeDestructured(serializedDb); break;
default: dbObject = JSON.parse(serializedDb); break;
}
}
this.loadJSONObject(dbObject, options);
};
/**
* Inflates a loki database from a JS object
*
* @param {object} dbObject - a serialized loki database string
* @param {object=} options - apply or override collection level settings
* @param {bool} options.retainDirtyFlags - whether collection dirty flags will be preserved
* @memberof Loki
*/
Loki.prototype.loadJSONObject = function (dbObject, options) {
var i = 0,
len = dbObject.collections ? dbObject.collections.length : 0,
coll,
copyColl,
clen,
j,
loader,
collObj;
this.name = dbObject.name;
if (dbObject.hasOwnProperty('throttledSaves') && options && !options.hasOwnProperty('throttledSaves')) {
this.throttledSaves = dbObject.throttledSaves;
}
this.collections = [];
function makeLoader(coll) {
var collOptions = options[coll.name];
var inflater;
if(collOptions.proto) {
inflater = collOptions.inflate || Utils.copyProperties;
return function(data) {
var collObj = new(collOptions.proto)();
inflater(data, collObj);
return collObj;
};
}
return collOptions.inflate;
}
for (i; i < len; i += 1) {
coll = dbObject.collections[i];
copyColl = this.addCollection(coll.name, { disableChangesApi: coll.disableChangesApi, disableDeltaChangesApi: coll.disableDeltaChangesApi, disableMeta: coll.disableMeta });
copyColl.adaptiveBinaryIndices = coll.hasOwnProperty('adaptiveBinaryIndices')?(coll.adaptiveBinaryIndices === true): false;
copyColl.transactional = coll.transactional;
copyColl.asyncListeners = coll.asyncListeners;
copyColl.cloneObjects = coll.cloneObjects;
copyColl.cloneMethod = coll.cloneMethod || "parse-stringify";
copyColl.autoupdate = coll.autoupdate;
copyColl.changes = coll.changes;
if (options && options.retainDirtyFlags === true) {
copyColl.dirty = coll.dirty;
}
else {
copyColl.dirty = false;
}
clen = coll.data.length;
j = 0;
if (options && options.hasOwnProperty(coll.name)) {
loader = makeLoader(coll);
for (j; j < clen; j++) {
collObj = loader(coll.data[j]);
copyColl.data[j] = collObj;
copyColl.addAutoUpdateObserver(collObj);
}
} else {
for (j; j < clen; j++) {
copyColl.data[j] = coll.data[j];
copyColl.addAutoUpdateObserver(copyColl.data[j]);
}
}
copyColl.maxId = (typeof coll.maxId === 'undefined') ? 0 : coll.maxId;
copyColl.idIndex = coll.idIndex;
if (typeof (coll.binaryIndices) !== 'undefined') {
copyColl.binaryIndices = coll.binaryIndices;
}
if (typeof coll.transforms !== 'undefined') {
copyColl.transforms = coll.transforms;
}
copyColl.ensureId();
copyColl.uniqueNames = [];
if (coll.hasOwnProperty("uniqueNames")) {
copyColl.uniqueNames = coll.uniqueNames;
for (j = 0; j < copyColl.uniqueNames.length; j++) {
copyColl.ensureUniqueIndex(copyColl.uniqueNames[j]);
}
}
if (typeof (coll.DynamicViews) === 'undefined') continue;
for (var idx = 0; idx < coll.DynamicViews.length; idx++) {
var colldv = coll.DynamicViews[idx];
var dv = copyColl.addDynamicView(colldv.name, colldv.options);
dv.resultdata = colldv.resultdata;
dv.resultsdirty = colldv.resultsdirty;
dv.filterPipeline = colldv.filterPipeline;
dv.sortCriteria = colldv.sortCriteria;
dv.sortFunction = null;
dv.sortDirty = colldv.sortDirty;
dv.resultset.filteredrows = colldv.resultset.filteredrows;
dv.resultset.filterInitialized = colldv.resultset.filterInitialized;
dv.rematerialize({
removeWhereFilters: true
});
}
if (dbObject.databaseVersion < 1.5) {
copyColl.ensureAllIndexes(true);
copyColl.dirty = true;
}
}
};
/**
* Emits the close event. In autosave scenarios, if the database is dirty, this will save and disable timer.
* Does not actually destroy the db.
*
* @param {function=} callback - (Optional) if supplied will be registered with close event before emitting.
* @memberof Loki
*/
Loki.prototype.close = function (callback) {
if (this.autosave) {
this.autosaveDisable();
if (this.autosaveDirty()) {
this.saveDatabase(callback);
callback = undefined;
}
}
if (callback) {
this.on('close', callback);
}
this.emit('close');
};
/**-------------------------+
| Changes API |
+--------------------------*/
/**
* The Changes API enables the tracking the changes occurred in the collections since the beginning of the session,
* so it's possible to create a differential dataset for synchronization purposes (possibly to a remote db)
*/
/**
* (Changes API) : takes all the changes stored in each
* collection and creates a single array for the entire database. If an array of names
* of collections is passed then only the included collections will be tracked.
*
* @param {array=} optional array of collection names. No arg means all collections are processed.
* @returns {array} array of changes
* @see private method createChange() in Collection
* @memberof Loki
*/
Loki.prototype.generateChangesNotification = function (arrayOfCollectionNames) {
function getCollName(coll) {
return coll.name;
}
var changes = [],
selectedCollections = arrayOfCollectionNames || this.collections.map(getCollName);
this.collections.forEach(function (coll) {
if (selectedCollections.indexOf(getCollName(coll)) !== -1) {
changes = changes.concat(coll.getChanges());
}
});
return changes;
};
/**
* (Changes API) - stringify changes for network transmission
* @returns {string} string representation of the changes
* @memberof Loki
*/
Loki.prototype.serializeChanges = function (collectionNamesArray) {
return JSON.stringify(this.generateChangesNotification(collectionNamesArray));
};
/**
* (Changes API) : clears all the changes in all collections.
* @memberof Loki
*/
Loki.prototype.clearChanges = function () {
this.collections.forEach(function (coll) {
if (coll.flushChanges) {
coll.flushChanges();
}
});
};
/*------------------+
| PERSISTENCE |
-------------------*/
/** there are two build in persistence adapters for internal use
* fs for use in Nodejs type environments
* localStorage for use in browser environment
* defined as helper classes here so its easy and clean to use
*/
/**
* In in-memory persistence adapter for an in-memory database.
* This simple 'key/value' adapter is intended for unit testing and diagnostics.
*
* @param {object=} options - memory adapter options
* @param {boolean} [options.asyncResponses=false] - whether callbacks are invoked asynchronously
* @param {int} [options.asyncTimeout=50] - timeout in ms to queue callbacks
* @constructor LokiMemoryAdapter
*/
function LokiMemoryAdapter(options) {
this.hashStore = {};
this.options = options || {};
if (!this.options.hasOwnProperty('asyncResponses')) {
this.options.asyncResponses = false;
}
if (!this.options.hasOwnProperty('asyncTimeout')) {
this.options.asyncTimeout = 50; // 50 ms default
}
}
/**
* Loads a serialized database from its in-memory store.
* (Loki persistence adapter interface function)
*
* @param {string} dbname - name of the database (filename/keyname)
* @param {function} callback - adapter callback to return load result to caller
* @memberof LokiMemoryAdapter
*/
LokiMemoryAdapter.prototype.loadDatabase = function (dbname, callback) {
var self=this;
if (this.options.asyncResponses) {
setTimeout(function() {
if (self.hashStore.hasOwnProperty(dbname)) {
callback(self.hashStore[dbname].value);
}
else {
callback (null);
}
}, this.options.asyncTimeout);
}
else {
if (this.hashStore.hasOwnProperty(dbname)) {
callback(this.hashStore[dbname].value);
}
else {
callback (null);
}
}
};
/**
* Saves a serialized database to its in-memory store.
* (Loki persistence adapter interface function)
*
* @param {string} dbname - name of the database (filename/keyname)
* @param {function} callback - adapter callback to return load result to caller
* @memberof LokiMemoryAdapter
*/
LokiMemoryAdapter.prototype.saveDatabase = function (dbname, dbstring, callback) {
var self=this;
var saveCount;
if (this.options.asyncResponses) {
setTimeout(function() {
saveCount = (self.hashStore.hasOwnProperty(dbname)?self.hashStore[dbname].savecount:0);
self.hashStore[dbname] = {
savecount: saveCount+1,
lastsave: new Date(),
value: dbstring
};
callback();
}, this.options.asyncTimeout);
}
else {
saveCount = (this.hashStore.hasOwnProperty(dbname)?this.hashStore[dbname].savecount:0);
this.hashStore[dbname] = {
savecount: saveCount+1,
lastsave: new Date(),
value: dbstring
};
callback();
}
};
/**
* Deletes a database from its in-memory store.
*
* @param {string} dbname - name of the database (filename/keyname)
* @param {function} callback - function to call when done
* @memberof LokiMemoryAdapter
*/
LokiMemoryAdapter.prototype.deleteDatabase = function(dbname, callback) {
if (this.hashStore.hasOwnProperty(dbname)) {
delete this.hashStore[dbname];
}
if (typeof callback === "function") {
callback();
}
};
/**
* An adapter for adapters. Converts a non reference mode adapter into a reference mode adapter
* which can perform destructuring and partioning. Each collection will be stored in its own key/save and
* only dirty collections will be saved. If you turn on paging with default page size of 25megs and save
* a 75 meg collection it should use up roughly 3 save slots (key/value pairs sent to inner adapter).
* A dirty collection that spans three pages will save all three pages again
* Paging mode was added mainly because Chrome has issues saving 'too large' of a string within a
* single indexeddb row. If a single document update causes the collection to be flagged as dirty, all
* of that collection's pages will be written on next save.
*
* @param {object} adapter - reference to a 'non-reference' mode loki adapter instance.
* @param {object=} options - configuration options for partitioning and paging
* @param {bool} options.paging - (default: false) set to true to enable paging collection data.
* @param {int} options.pageSize - (default : 25MB) you can use this to limit size of strings passed to inner adapter.
* @param {string} options.delimiter - allows you to override the default delimeter
* @constructor LokiPartitioningAdapter
*/
function LokiPartitioningAdapter(adapter, options) {
this.mode = "reference";
this.adapter = null;
this.options = options || {};
this.dbref = null;
this.dbname = "";
this.pageIterator = {};
if (adapter) {
if (adapter.mode === "reference") {
throw new Error("LokiPartitioningAdapter cannot be instantiated with a reference mode adapter");
}
else {
this.adapter = adapter;
}
}
else {
throw new Error("LokiPartitioningAdapter requires a (non-reference mode) adapter on construction");
}
if (!this.options.hasOwnProperty("paging")) {
this.options.paging = false;
}
if (!this.options.hasOwnProperty("pageSize")) {
this.options.pageSize = 25*1024*1024;
}
if (!this.options.hasOwnProperty("delimiter")) {
this.options.delimiter = '$<\n';
}
}
/**
* Loads a database which was partitioned into several key/value saves.
* (Loki persistence adapter interface function)
*
* @param {string} dbname - name of the database (filename/keyname)
* @param {function} callback - adapter callback to return load result to caller
* @memberof LokiPartitioningAdapter
*/
LokiPartitioningAdapter.prototype.loadDatabase = function (dbname, callback) {
var self=this;
this.dbname = dbname;
this.dbref = new Loki(dbname);
this.adapter.loadDatabase(dbname, function(result) {
if (!result) {
callback(result);
return;
}
if (typeof result !== "string") {
callback(new Error("LokiPartitioningAdapter received an unexpected response from inner adapter loadDatabase()"));
}
var db = JSON.parse(result);
self.dbref.loadJSONObject(db);
db = null;
var clen = self.dbref.collections.length;
if (self.dbref.collections.length === 0) {
callback(self.dbref);
return;
}
self.pageIterator = {
collection: 0,
pageIndex: 0
};
self.loadNextPartition(0, function() {
callback(self.dbref);
});
});
};
/**
* Used to sequentially load each collection partition, one at a time.
*
* @param {int} partition - ordinal collection position to load next
* @param {function} callback - adapter callback to return load result to caller
*/
LokiPartitioningAdapter.prototype.loadNextPartition = function(partition, callback) {
var keyname = this.dbname + "." + partition;
var self=this;
if (this.options.paging === true) {
this.pageIterator.pageIndex = 0;
this.loadNextPage(callback);
return;
}
this.adapter.loadDatabase(keyname, function(result) {
var data = self.dbref.deserializeCollection(result, { delimited: true, collectionIndex: partition });
self.dbref.collections[partition].data = data;
if (++partition < self.dbref.collections.length) {
self.loadNextPartition(partition, callback);
}
else {
callback();
}
});
};
/**
* Used to sequentially load the next page of collection partition, one at a time.
*
* @param {function} callback - adapter callback to return load result to caller
*/
LokiPartitioningAdapter.prototype.loadNextPage = function(callback) {
var keyname = this.dbname + "." + this.pageIterator.collection + "." + this.pageIterator.pageIndex;
var self=this;
this.adapter.loadDatabase(keyname, function(result) {
var data = result.split(self.options.delimiter);
result = ""; // free up memory now that we have split it into array
var dlen = data.length;
var idx;
var isLastPage = (data[dlen-1] === "");
if (isLastPage) {
data.pop();
dlen = data.length;
if (data[dlen-1] === "" && dlen === 1) {
data.pop();
dlen = data.length;
}
}
for(idx=0; idx < dlen; idx++) {
self.dbref.collections[self.pageIterator.collection].data.push(JSON.parse(data[idx]));
data[idx] = null;
}
data = [];
if (isLastPage) {
if (++self.pageIterator.collection < self.dbref.collections.length) {
self.loadNextPartition(self.pageIterator.collection, callback);
}
else {
callback();
}
}
else {
self.pageIterator.pageIndex++;
self.loadNextPage(callback);
}
});
};
/**
* Saves a database by partioning into separate key/value saves.
* (Loki 'reference mode' persistence adapter interface function)
*
* @param {string} dbname - name of the database (filename/keyname)
* @param {object} dbref - reference to database which we will partition and save.
* @param {function} callback - adapter callback to return load result to caller
*
* @memberof LokiPartitioningAdapter
*/
LokiPartitioningAdapter.prototype.exportDatabase = function(dbname, dbref, callback) {
var self=this;
var idx, clen = dbref.collections.length;
this.dbref = dbref;
this.dbname = dbname;
this.dirtyPartitions = [-1];
for(idx=0; idx<clen; idx++) {
if (dbref.collections[idx].dirty) {
this.dirtyPartitions.push(idx);
}
}
this.saveNextPartition(function(err) {
callback(err);
});
};
/**
* Helper method used internally to save each dirty collection, one at a time.
*
* @param {function} callback - adapter callback to return load result to caller
*/
LokiPartitioningAdapter.prototype.saveNextPartition = function(callback) {
var self=this;
var partition = this.dirtyPartitions.shift();
var keyname = this.dbname + ((partition===-1)?"":("." + partition));
if (this.options.paging && partition !== -1) {
this.pageIterator = {
collection: partition,
docIndex: 0,
pageIndex: 0
};
this.saveNextPage(function(err) {
if (self.dirtyPartitions.length === 0) {
callback(err);
}
else {
self.saveNextPartition(callback);
}
});
return;
}
var result = this.dbref.serializeDestructured({
partitioned : true,
delimited: true,
partition: partition
});
this.adapter.saveDatabase(keyname, result, function(err) {
if (err) {
callback(err);
return;
}
if (self.dirtyPartitions.length === 0) {
callback(null);
}
else {
self.saveNextPartition(callback);
}
});
};
/**
* Helper method used internally to generate and save the next page of the current (dirty) partition.
*
* @param {function} callback - adapter callback to return load result to caller
*/
LokiPartitioningAdapter.prototype.saveNextPage = function(callback) {
var self=this;
var coll = this.dbref.collections[this.pageIterator.collection];
var keyname = this.dbname + "." + this.pageIterator.collection + "." + this.pageIterator.pageIndex;
var pageLen=0,
cdlen = coll.data.length,
delimlen = this.options.delimiter.length;
var serializedObject = "",
pageBuilder = "";
var doneWithPartition=false,
doneWithPage=false;
var pageSaveCallback = function(err) {
pageBuilder = "";
if (err) {
callback(err);
}
if (doneWithPartition) {
callback(null);
}
else {
self.pageIterator.pageIndex++;
self.saveNextPage(callback);
}
};
if (coll.data.length === 0) {
doneWithPartition = true;
}
while (true) {
if (!doneWithPartition) {
serializedObject = JSON.stringify(coll.data[this.pageIterator.docIndex]);
pageBuilder += serializedObject;
pageLen += serializedObject.length;
if (++this.pageIterator.docIndex >= cdlen) doneWithPartition = true;
}
if (pageLen >= this.options.pageSize) doneWithPage = true;
if (!doneWithPage || doneWithPartition) {
pageBuilder += this.options.delimiter;
pageLen += delimlen;
}
if (doneWithPartition || doneWithPage) {
this.adapter.saveDatabase(keyname, pageBuilder, pageSaveCallback);
return;
}
}
};
/**
* A loki persistence adapter which persists using node fs module
* @constructor LokiFsAdapter
*/
function LokiFsAdapter() {
try {
this.fs = __webpack_require__(11);
}catch(e) {
this.fs = null;
}
}
/**
* loadDatabase() - Load data from file, will throw an error if the file does not exist
* @param {string} dbname - the filename of the database to load
* @param {function} callback - the callback to handle the result
* @memberof LokiFsAdapter
*/
LokiFsAdapter.prototype.loadDatabase = function loadDatabase(dbname, callback) {
var self = this;
this.fs.stat(dbname, function (err, stats) {
if (!err && stats.isFile()) {
self.fs.readFile(dbname, {
encoding: 'utf8'
}, function readFileCallback(err, data) {
if (err) {
callback(new Error(err));
} else {
callback(data);
}
});
}
else {
callback(null);
}
});
};
/**
* saveDatabase() - save data to file, will throw an error if the file can't be saved
* might want to expand this to avoid dataloss on partial save
* @param {string} dbname - the filename of the database to load
* @param {function} callback - the callback to handle the result
* @memberof LokiFsAdapter
*/
LokiFsAdapter.prototype.saveDatabase = function saveDatabase(dbname, dbstring, callback) {
var self = this;
var tmpdbname = dbname + '~';
this.fs.writeFile(tmpdbname, dbstring, function writeFileCallback(err) {
if (err) {
callback(new Error(err));
} else {
self.fs.rename(tmpdbname,dbname,callback);
}
});
};
/**
* deleteDatabase() - delete the database file, will throw an error if the
* file can't be deleted
* @param {string} dbname - the filename of the database to delete
* @param {function} callback - the callback to handle the result
* @memberof LokiFsAdapter
*/
LokiFsAdapter.prototype.deleteDatabase = function deleteDatabase(dbname, callback) {
this.fs.unlink(dbname, function deleteDatabaseCallback(err) {
if (err) {
callback(new Error(err));
} else {
callback();
}
});
};
/**
* A loki persistence adapter which persists to web browser's local storage object
* @constructor LokiLocalStorageAdapter
*/
function LokiLocalStorageAdapter() {}
/**
* loadDatabase() - Load data from localstorage
* @param {string} dbname - the name of the database to load
* @param {function} callback - the callback to handle the result
* @memberof LokiLocalStorageAdapter
*/
LokiLocalStorageAdapter.prototype.loadDatabase = function loadDatabase(dbname, callback) {
if (localStorageAvailable()) {
callback(localStorage.getItem(dbname));
} else {
callback(new Error('localStorage is not available'));
}
};
/**
* saveDatabase() - save data to localstorage, will throw an error if the file can't be saved
* might want to expand this to avoid dataloss on partial save
* @param {string} dbname - the filename of the database to load
* @param {function} callback - the callback to handle the result
* @memberof LokiLocalStorageAdapter
*/
LokiLocalStorageAdapter.prototype.saveDatabase = function saveDatabase(dbname, dbstring, callback) {
if (localStorageAvailable()) {
localStorage.setItem(dbname, dbstring);
callback(null);
} else {
callback(new Error('localStorage is not available'));
}
};
/**
* deleteDatabase() - delete the database from localstorage, will throw an error if it
* can't be deleted
* @param {string} dbname - the filename of the database to delete
* @param {function} callback - the callback to handle the result
* @memberof LokiLocalStorageAdapter
*/
LokiLocalStorageAdapter.prototype.deleteDatabase = function deleteDatabase(dbname, callback) {
if (localStorageAvailable()) {
localStorage.removeItem(dbname);
callback(null);
} else {
callback(new Error('localStorage is not available'));
}
};
/**
* Wait for throttledSaves to complete and invoke your callback when drained or duration is met.
*
* @param {function} callback - callback to fire when save queue is drained, it is passed a sucess parameter value
* @param {object=} options - configuration options
* @param {boolean} options.recursiveWait - (default: true) if after queue is drained, another save was kicked off, wait for it
* @param {bool} options.recursiveWaitLimit - (default: false) limit our recursive waiting to a duration
* @param {int} options.recursiveWaitLimitDelay - (default: 2000) cutoff in ms to stop recursively re-draining
* @memberof Loki
*/
Loki.prototype.throttledSaveDrain = function(callback, options) {
var self = this;
var now = (new Date()).getTime();
if (!this.throttledSaves) {
callback(true);
}
options = options || {};
if (!options.hasOwnProperty('recursiveWait')) {
options.recursiveWait = true;
}
if (!options.hasOwnProperty('recursiveWaitLimit')) {
options.recursiveWaitLimit = false;
}
if (!options.hasOwnProperty('recursiveWaitLimitDuration')) {
options.recursiveWaitLimitDuration = 2000;
}
if (!options.hasOwnProperty('started')) {
options.started = (new Date()).getTime();
}
if (this.throttledSaves && this.throttledSavePending) {
if (options.recursiveWait) {
this.throttledCallbacks.push(function() {
if (self.throttledSavePending) {
if (options.recursiveWaitLimit && (now - options.started > options.recursiveWaitLimitDuration)) {
callback(false);
return;
}
self.throttledSaveDrain(callback, options);
return;
}
else {
callback(true);
return;
}
});
}
else {
this.throttledCallbacks.push(callback);
return;
}
}
else {
callback(true);
}
};
/**
* Internal load logic, decoupled from throttling/contention logic
*
* @param {object} options - not currently used (remove or allow overrides?)
* @param {function=} callback - (Optional) user supplied async callback / error handler
*/
Loki.prototype.loadDatabaseInternal = function (options, callback) {
var cFun = callback || function (err, data) {
if (err) {
throw err;
}
},
self = this;
if (this.persistenceAdapter !== null) {
this.persistenceAdapter.loadDatabase(this.filename, function loadDatabaseCallback(dbString) {
if (typeof (dbString) === 'string') {
var parseSuccess = false;
try {
self.loadJSON(dbString, options || {});
parseSuccess = true;
} catch (err) {
cFun(err);
}
if (parseSuccess) {
cFun(null);
self.emit('loaded', 'database ' + self.filename + ' loaded');
}
} else {
if (!dbString) {
cFun(null);
self.emit('loaded', 'empty database ' + self.filename + ' loaded');
return;
}
if (dbString instanceof Error) {
cFun(dbString);
return;
}
if (typeof (dbString) === "object") {
self.loadJSONObject(dbString, options || {});
cFun(null); // return null on success
self.emit('loaded', 'database ' + self.filename + ' loaded');
return;
}
cFun("unexpected adapter response : " + dbString);
}
});
} else {
cFun(new Error('persistenceAdapter not configured'));
}
};
/**
* Handles manually loading from file system, local storage, or adapter (such as indexeddb)
* This method utilizes loki configuration options (if provided) to determine which
* persistence method to use, or environment detection (if configuration was not provided).
* To avoid contention with any throttledSaves, we will drain the save queue first.
*
* If you are configured with autosave, you do not need to call this method yourself.
*
* @param {object} options - if throttling saves and loads, this controls how we drain save queue before loading
* @param {boolean} options.recursiveWait - (default: true) wait recursively until no saves are queued
* @param {bool} options.recursiveWaitLimit - (default: false) limit our recursive waiting to a duration
* @param {int} options.recursiveWaitLimitDelay - (default: 2000) cutoff in ms to stop recursively re-draining
* @param {function=} callback - (Optional) user supplied async callback / error handler
* @memberof Loki
* @example
* db.loadDatabase({}, function(err) {
* if (err) {
* console.log("error : " + err);
* }
* else {
* console.log("database loaded.");
* }
* });
*/
Loki.prototype.loadDatabase = function (options, callback) {
var self=this;
if (!this.throttledSaves) {
this.loadDatabaseInternal(options, callback);
return;
}
this.throttledSaveDrain(function(success) {
if (success) {
self.throttledSavePending = true;
self.loadDatabaseInternal(options, function(err) {
if (self.throttledCallbacks.length === 0) {
self.throttledSavePending = false;
}
else {
self.saveDatabase();
}
if (typeof callback === 'function') {
callback(err);
}
});
return;
}
else {
if (typeof callback === 'function') {
callback(new Error("Unable to pause save throttling long enough to read database"));
}
}
}, options);
};
/**
* Internal save logic, decoupled from save throttling logic
*/
Loki.prototype.saveDatabaseInternal = function (callback) {
var cFun = callback || function (err) {
if (err) {
throw err;
}
return;
},
self = this;
if (this.persistenceAdapter !== null) {
if (this.persistenceAdapter.mode === "reference" && typeof this.persistenceAdapter.exportDatabase === "function") {
this.persistenceAdapter.exportDatabase(this.filename, this.copy({removeNonSerializable:true}), function exportDatabaseCallback(err) {
self.autosaveClearFlags();
cFun(err);
});
}
else {
self.autosaveClearFlags();
this.persistenceAdapter.saveDatabase(this.filename, self.serialize(), function saveDatabasecallback(err) {
cFun(err);
});
}
} else {
cFun(new Error('persistenceAdapter not configured'));
}
};
/**
* Handles manually saving to file system, local storage, or adapter (such as indexeddb)
* This method utilizes loki configuration options (if provided) to determine which
* persistence method to use, or environment detection (if configuration was not provided).
*
* If you are configured with autosave, you do not need to call this method yourself.
*
* @param {function=} callback - (Optional) user supplied async callback / error handler
* @memberof Loki
* @example
* db.saveDatabase(function(err) {
* if (err) {
* console.log("error : " + err);
* }
* else {
* console.log("database saved.");
* }
* });
*/
Loki.prototype.saveDatabase = function (callback) {
if (!this.throttledSaves) {
this.saveDatabaseInternal(callback);
return;
}
if (this.throttledSavePending) {
this.throttledCallbacks.push(callback);
return;
}
var localCallbacks = this.throttledCallbacks;
this.throttledCallbacks = [];
localCallbacks.unshift(callback);
this.throttledSavePending = true;
var self = this;
this.saveDatabaseInternal(function(err) {
self.throttledSavePending = false;
localCallbacks.forEach(function(pcb) {
if (typeof pcb === 'function') {
setTimeout(function() {
pcb(err);
}, 1);
}
});
if (self.throttledCallbacks.length > 0) {
self.saveDatabase();
}
});
};
Loki.prototype.save = Loki.prototype.saveDatabase;
/**
* Handles deleting a database from file system, local
* storage, or adapter (indexeddb)
* This method utilizes loki configuration options (if provided) to determine which
* persistence method to use, or environment detection (if configuration was not provided).
*
* @param {function=} callback - (Optional) user supplied async callback / error handler
* @memberof Loki
*/
Loki.prototype.deleteDatabase = function (options, callback) {
var cFun = callback || function (err, data) {
if (err) {
throw err;
}
};
if (typeof options === 'function' && !callback) {
cFun = options;
}
if (this.persistenceAdapter !== null) {
this.persistenceAdapter.deleteDatabase(this.filename, function deleteDatabaseCallback(err) {
cFun(err);
});
} else {
cFun(new Error('persistenceAdapter not configured'));
}
};
/**
* autosaveDirty - check whether any collections are 'dirty' meaning we need to save (entire) database
*
* @returns {boolean} - true if database has changed since last autosave, false if not.
*/
Loki.prototype.autosaveDirty = function () {
for (var idx = 0; idx < this.collections.length; idx++) {
if (this.collections[idx].dirty) {
return true;
}
}
return false;
};
/**
* autosaveClearFlags - resets dirty flags on all collections.
* Called from saveDatabase() after db is saved.
*
*/
Loki.prototype.autosaveClearFlags = function () {
for (var idx = 0; idx < this.collections.length; idx++) {
this.collections[idx].dirty = false;
}
};
/**
* autosaveEnable - begin a javascript interval to periodically save the database.
*
* @param {object} options - not currently used (remove or allow overrides?)
* @param {function=} callback - (Optional) user supplied async callback
*/
Loki.prototype.autosaveEnable = function (options, callback) {
this.autosave = true;
var delay = 5000,
self = this;
if (typeof (this.autosaveInterval) !== 'undefined' && this.autosaveInterval !== null) {
delay = this.autosaveInterval;
}
this.autosaveHandle = setInterval(function autosaveHandleInterval() {
if (self.autosaveDirty()) {
self.saveDatabase(callback);
}
}, delay);
};
/**
* autosaveDisable - stop the autosave interval timer.
*
*/
Loki.prototype.autosaveDisable = function () {
if (typeof (this.autosaveHandle) !== 'undefined' && this.autosaveHandle !== null) {
clearInterval(this.autosaveHandle);
this.autosaveHandle = null;
}
};
/**
* Resultset class allowing chainable queries. Intended to be instanced internally.
* Collection.find(), Collection.where(), and Collection.chain() instantiate this.
*
* @example
* mycollection.chain()
* .find({ 'doors' : 4 })
* .where(function(obj) { return obj.name === 'Toyota' })
* .data();
*
* @constructor Resultset
* @param {Collection} collection - The collection which this Resultset will query against.
*/
function Resultset(collection, options) {
options = options || {};
this.collection = collection;
this.filteredrows = [];
this.filterInitialized = false;
return this;
}
/**
* reset() - Reset the resultset to its initial state.
*
* @returns {Resultset} Reference to this resultset, for future chain operations.
*/
Resultset.prototype.reset = function () {
if (this.filteredrows.length > 0) {
this.filteredrows = [];
}
this.filterInitialized = false;
return this;
};
/**
* toJSON() - Override of toJSON to avoid circular references
*
*/
Resultset.prototype.toJSON = function () {
var copy = this.copy();
copy.collection = null;
return copy;
};
/**
* Allows you to limit the number of documents passed to next chain operation.
* A resultset copy() is made to avoid altering original resultset.
*
* @param {int} qty - The number of documents to return.
* @returns {Resultset} Returns a copy of the resultset, limited by qty, for subsequent chain ops.
* @memberof Resultset
* // find the two oldest users
* var result = users.chain().simplesort("age", true).limit(2).data();
*/
Resultset.prototype.limit = function (qty) {
if (!this.filterInitialized && this.filteredrows.length === 0) {
this.filteredrows = this.collection.prepareFullDocIndex();
}
var rscopy = new Resultset(this.collection);
rscopy.filteredrows = this.filteredrows.slice(0, qty);
rscopy.filterInitialized = true;
return rscopy;
};
/**
* Used for skipping 'pos' number of documents in the resultset.
*
* @param {int} pos - Number of documents to skip; all preceding documents are filtered out.
* @returns {Resultset} Returns a copy of the resultset, containing docs starting at 'pos' for subsequent chain ops.
* @memberof Resultset
* // find everyone but the two oldest users
* var result = users.chain().simplesort("age", true).offset(2).data();
*/
Resultset.prototype.offset = function (pos) {
if (!this.filterInitialized && this.filteredrows.length === 0) {
this.filteredrows = this.collection.prepareFullDocIndex();
}
var rscopy = new Resultset(this.collection);
rscopy.filteredrows = this.filteredrows.slice(pos);
rscopy.filterInitialized = true;
return rscopy;
};
/**
* copy() - To support reuse of resultset in branched query situations.
*
* @returns {Resultset} Returns a copy of the resultset (set) but the underlying document references will be the same.
* @memberof Resultset
*/
Resultset.prototype.copy = function () {
var result = new Resultset(this.collection);
if (this.filteredrows.length > 0) {
result.filteredrows = this.filteredrows.slice();
}
result.filterInitialized = this.filterInitialized;
return result;
};
/**
* Alias of copy()
* @memberof Resultset
*/
Resultset.prototype.branch = Resultset.prototype.copy;
/**
* transform() - executes a named collection transform or raw array of transform steps against the resultset.
*
* @param transform {(string|array)} - name of collection transform or raw transform array
* @param parameters {object=} - (Optional) object property hash of parameters, if the transform requires them.
* @returns {Resultset} either (this) resultset or a clone of of this resultset (depending on steps)
* @memberof Resultset
* @example
* users.addTransform('CountryFilter', [
* {
* type: 'find',
* value: {
* 'country': { $eq: '[%lktxp]Country' }
* }
* },
* {
* type: 'simplesort',
* property: 'age',
* options: { desc: false}
* }
* ]);
* var results = users.chain().transform("CountryFilter", { Country: 'fr' }).data();
*/
Resultset.prototype.transform = function (transform, parameters) {
var idx,
step,
rs = this;
if (typeof transform === 'string') {
if (this.collection.transforms.hasOwnProperty(transform)) {
transform = this.collection.transforms[transform];
}
}
if (typeof transform !== 'object' || !Array.isArray(transform)) {
throw new Error("Invalid transform");
}
if (typeof parameters !== 'undefined') {
transform = Utils.resolveTransformParams(transform, parameters);
}
for (idx = 0; idx < transform.length; idx++) {
step = transform[idx];
switch (step.type) {
case "find":
rs.find(step.value);
break;
case "where":
rs.where(step.value);
break;
case "simplesort":
rs.simplesort(step.property, step.desc || step.options);
break;
case "compoundsort":
rs.compoundsort(step.value);
break;
case "sort":
rs.sort(step.value);
break;
case "limit":
rs = rs.limit(step.value);
break; // limit makes copy so update reference
case "offset":
rs = rs.offset(step.value);
break; // offset makes copy so update reference
case "map":
rs = rs.map(step.value, step.dataOptions);
break;
case "eqJoin":
rs = rs.eqJoin(step.joinData, step.leftJoinKey, step.rightJoinKey, step.mapFun, step.dataOptions);
break;
case "mapReduce":
rs = rs.mapReduce(step.mapFunction, step.reduceFunction);
break;
case "update":
rs.update(step.value);
break;
case "remove":
rs.remove();
break;
default:
break;
}
}
return rs;
};
/**
* User supplied compare function is provided two documents to compare. (chainable)
* @example
* rslt.sort(function(obj1, obj2) {
* if (obj1.name === obj2.name) return 0;
* if (obj1.name > obj2.name) return 1;
* if (obj1.name < obj2.name) return -1;
* });
*
* @param {function} comparefun - A javascript compare function used for sorting.
* @returns {Resultset} Reference to this resultset, sorted, for future chain operations.
* @memberof Resultset
*/
Resultset.prototype.sort = function (comparefun) {
if (!this.filterInitialized && this.filteredrows.length === 0) {
this.filteredrows = this.collection.prepareFullDocIndex();
}
var wrappedComparer =
(function (userComparer, data) {
return function (a, b) {
return userComparer(data[a], data[b]);
};
})(comparefun, this.collection.data);
this.filteredrows.sort(wrappedComparer);
return this;
};
/**
* Simpler, loose evaluation for user to sort based on a property name. (chainable).
* Sorting based on the same lt/gt helper functions used for binary indices.
*
* @param {string} propname - name of property to sort by.
* @param {object|bool=} options - boolean to specify if isdescending, or options object
* @param {boolean} [options.desc=false] - whether to sort descending
* @param {boolean} [options.disableIndexIntersect=false] - whether we should explicity not use array intersection.
* @param {boolean} [options.forceIndexIntersect=false] - force array intersection (if binary index exists).
* @param {boolean} [options.useJavascriptSorting=false] - whether results are sorted via basic javascript sort.
* @returns {Resultset} Reference to this resultset, sorted, for future chain operations.
* @memberof Resultset
* @example
* var results = users.chain().simplesort('age').data();
*/
Resultset.prototype.simplesort = function (propname, options) {
var eff,
targetEff = 10,
dc = this.collection.data.length,
frl = this.filteredrows.length,
hasBinaryIndex = this.collection.binaryIndices.hasOwnProperty(propname);
if (typeof (options) === 'undefined' || options === false) {
options = { desc: false };
}
if (options === true) {
options = { desc: true };
}
if (frl === 0) {
if (this.filterInitialized) {
return this;
}
if (this.collection.binaryIndices.hasOwnProperty(propname)) {
this.collection.ensureIndex(propname);
this.filteredrows = this.collection.binaryIndices[propname].values.slice(0);
if (options.desc) {
this.filteredrows.reverse();
}
return this;
}
else {
this.filteredrows = this.collection.prepareFullDocIndex();
}
}
else {
if (!options.disableIndexIntersect && hasBinaryIndex) {
eff = dc/frl;
if (options.useJavascriptSorting) {
targetEff = 6;
}
if (eff <= targetEff || options.forceIndexIntersect) {
var idx, fr=this.filteredrows;
var io = {};
for(idx=0; idx<frl; idx++) {
io[fr[idx]] = true;
}
var pv = this.collection.binaryIndices[propname].values;
this.filteredrows = pv.filter(function(n) { return io[n]; });
if (options.desc) {
this.filteredrows.reverse();
}
return this;
}
}
}
if (options.useJavascriptSorting) {
return this.sort(function(obj1, obj2) {
if (obj1[propname] === obj2[propname]) return 0;
if (obj1[propname] > obj2[propname]) return 1;
if (obj1[propname] < obj2[propname]) return -1;
});
}
var wrappedComparer =
(function (prop, desc, data) {
var val1, val2, arr;
return function (a, b) {
if (~prop.indexOf('.')) {
arr = prop.split('.');
val1 = arr.reduce(function(obj, i) { return obj && obj[i] || undefined; }, data[a]);
val2 = arr.reduce(function(obj, i) { return obj && obj[i] || undefined; }, data[b]);
} else {
val1 = data[a][prop];
val2 = data[b][prop];
}
return sortHelper(val1, val2, desc);
};
})(propname, options.desc, this.collection.data);
this.filteredrows.sort(wrappedComparer);
return this;
};
/**
* Allows sorting a resultset based on multiple columns.
* @example
* // to sort by age and then name (both ascending)
* rs.compoundsort(['age', 'name']);
* // to sort by age (ascending) and then by name (descending)
* rs.compoundsort(['age', ['name', true]);
*
* @param {array} properties - array of property names or subarray of [propertyname, isdesc] used evaluate sort order
* @returns {Resultset} Reference to this resultset, sorted, for future chain operations.
* @memberof Resultset
*/
Resultset.prototype.compoundsort = function (properties) {
if (properties.length === 0) {
throw new Error("Invalid call to compoundsort, need at least one property");
}
var prop;
if (properties.length === 1) {
prop = properties[0];
if (Array.isArray(prop)) {
return this.simplesort(prop[0], prop[1]);
}
return this.simplesort(prop, false);
}
for (var i = 0, len = properties.length; i < len; i += 1) {
prop = properties[i];
if (!Array.isArray(prop)) {
properties[i] = [prop, false];
}
}
if (!this.filterInitialized && this.filteredrows.length === 0) {
this.filteredrows = this.collection.prepareFullDocIndex();
}
var wrappedComparer =
(function (props, data) {
return function (a, b) {
return compoundeval(props, data[a], data[b]);
};
})(properties, this.collection.data);
this.filteredrows.sort(wrappedComparer);
return this;
};
/**
* findOr() - oversee the operation of OR'ed query expressions.
* OR'ed expression evaluation runs each expression individually against the full collection,
* and finally does a set OR on each expression's results.
* Each evaluation can utilize a binary index to prevent multiple linear array scans.
*
* @param {array} expressionArray - array of expressions
* @returns {Resultset} this resultset for further chain ops.
*/
Resultset.prototype.findOr = function (expressionArray) {
var fr = null,
fri = 0,
frlen = 0,
docset = [],
idxset = [],
idx = 0,
origCount = this.count();
for (var ei = 0, elen = expressionArray.length; ei < elen; ei++) {
fr = this.branch().find(expressionArray[ei]).filteredrows;
frlen = fr.length;
if (frlen === origCount) {
return this;
}
for (fri = 0; fri < frlen; fri++) {
idx = fr[fri];
if (idxset[idx] === undefined) {
idxset[idx] = true;
docset.push(idx);
}
}
}
this.filteredrows = docset;
this.filterInitialized = true;
return this;
};
Resultset.prototype.$or = Resultset.prototype.findOr;
/**
* findAnd() - oversee the operation of AND'ed query expressions.
* AND'ed expression evaluation runs each expression progressively against the full collection,
* internally utilizing existing chained resultset functionality.
* Only the first filter can utilize a binary index.
*
* @param {array} expressionArray - array of expressions
* @returns {Resultset} this resultset for further chain ops.
*/
Resultset.prototype.findAnd = function (expressionArray) {
for (var i = 0, len = expressionArray.length; i < len; i++) {
if (this.count() === 0) {
return this;
}
this.find(expressionArray[i]);
}
return this;
};
Resultset.prototype.$and = Resultset.prototype.findAnd;
/**
* Used for querying via a mongo-style query object.
*
* @param {object} query - A mongo-style query object used for filtering current results.
* @param {boolean=} firstOnly - (Optional) Used by collection.findOne()
* @returns {Resultset} this resultset for further chain ops.
* @memberof Resultset
* @example
* var over30 = users.chain().find({ age: { $gte: 30 } }).data();
*/
Resultset.prototype.find = function (query, firstOnly) {
if (this.collection.data.length === 0) {
this.filteredrows = [];
this.filterInitialized = true;
return this;
}
var queryObject = query || 'getAll',
p,
property,
queryObjectOp,
obj,
operator,
value,
key,
searchByIndex = false,
result = [],
filters = [],
index = null;
firstOnly = firstOnly || false;
if (typeof queryObject === 'object') {
for (p in queryObject) {
obj = {};
obj[p] = queryObject[p];
filters.push(obj);
if (hasOwnProperty.call(queryObject, p)) {
property = p;
queryObjectOp = queryObject[p];
}
}
if (filters.length > 1) {
return this.find({ '$and': filters }, firstOnly);
}
}
if (!property || queryObject === 'getAll') {
if (firstOnly) {
this.filteredrows = (this.collection.data.length > 0)?[0]: [];
this.filterInitialized = true;
}
return this;
}
if (property === '$and' || property === '$or') {
this[property](queryObjectOp);
if (firstOnly && this.filteredrows.length > 1) {
this.filteredrows = this.filteredrows.slice(0, 1);
}
return this;
}
if (queryObjectOp === null || (typeof queryObjectOp !== 'object' || queryObjectOp instanceof Date)) {
operator = '$eq';
value = queryObjectOp;
} else if (typeof queryObjectOp === 'object') {
for (key in queryObjectOp) {
if (hasOwnProperty.call(queryObjectOp, key)) {
operator = key;
value = queryObjectOp[key];
break;
}
}
} else {
throw new Error('Do not know what you want to do.');
}
if (operator === '$regex') {
if (Array.isArray(value)) {
value = new RegExp(value[0], value[1]);
} else if (!(value instanceof RegExp)) {
value = new RegExp(value);
}
}
var usingDotNotation = (property.indexOf('.') !== -1);
var doIndexCheck = !usingDotNotation && !this.filterInitialized;
if (doIndexCheck && this.collection.binaryIndices[property] && indexedOps[operator]) {
if (this.collection.adaptiveBinaryIndices !== true) {
this.collection.ensureIndex(property);
}
searchByIndex = true;
index = this.collection.binaryIndices[property];
}
var fun = LokiOps[operator];
var t = this.collection.data;
var i = 0,
len = 0;
var filter, rowIdx = 0;
if (this.filterInitialized) {
filter = this.filteredrows;
len = filter.length;
if (usingDotNotation) {
property = property.split('.');
for(i=0; i<len; i++) {
rowIdx = filter[i];
if (dotSubScan(t[rowIdx], property, fun, value)) {
result.push(rowIdx);
}
}
} else {
for(i=0; i<len; i++) {
rowIdx = filter[i];
if (fun(t[rowIdx][property], value)) {
result.push(rowIdx);
}
}
}
}
else {
if (!searchByIndex) {
len = t.length;
if (usingDotNotation) {
property = property.split('.');
for(i=0; i<len; i++) {
if (dotSubScan(t[i], property, fun, value)) {
result.push(i);
if (firstOnly) {
this.filteredrows = result;
this.filterInitialized = true;
return this;
}
}
}
} else {
for(i=0; i<len; i++) {
if (fun(t[i][property], value)) {
result.push(i);
if (firstOnly) {
this.filteredrows = result;
this.filterInitialized = true;
return this;
}
}
}
}
} else {
var segm = this.collection.calculateRange(operator, property, value);
if (operator !== '$in') {
for (i = segm[0]; i <= segm[1]; i++) {
if (indexedOps[operator] !== true) {
if (indexedOps[operator](t[index.values[i]][property], value)) {
result.push(index.values[i]);
if (firstOnly) {
this.filteredrows = result;
this.filterInitialized = true;
return this;
}
}
}
else {
result.push(index.values[i]);
if (firstOnly) {
this.filteredrows = result;
this.filterInitialized = true;
return this;
}
}
}
} else {
for (i = 0, len = segm.length; i < len; i++) {
result.push(index.values[segm[i]]);
if (firstOnly) {
this.filteredrows = result;
this.filterInitialized = true;
return this;
}
}
}
}
}
this.filteredrows = result;
this.filterInitialized = true; // next time work against filteredrows[]
return this;
};
/**
* where() - Used for filtering via a javascript filter function.
*
* @param {function} fun - A javascript function used for filtering current results by.
* @returns {Resultset} this resultset for further chain ops.
* @memberof Resultset
* @example
* var over30 = users.chain().where(function(obj) { return obj.age >= 30; }.data();
*/
Resultset.prototype.where = function (fun) {
var viewFunction,
result = [];
if ('function' === typeof fun) {
viewFunction = fun;
} else {
throw new TypeError('Argument is not a stored view or a function');
}
try {
if (this.filterInitialized) {
var j = this.filteredrows.length;
while (j--) {
if (viewFunction(this.collection.data[this.filteredrows[j]]) === true) {
result.push(this.filteredrows[j]);
}
}
this.filteredrows = result;
return this;
}
else {
var k = this.collection.data.length;
while (k--) {
if (viewFunction(this.collection.data[k]) === true) {
result.push(k);
}
}
this.filteredrows = result;
this.filterInitialized = true;
return this;
}
} catch (err) {
throw err;
}
};
/**
* count() - returns the number of documents in the resultset.
*
* @returns {number} The number of documents in the resultset.
* @memberof Resultset
* @example
* var over30Count = users.chain().find({ age: { $gte: 30 } }).count();
*/
Resultset.prototype.count = function () {
if (this.filterInitialized) {
return this.filteredrows.length;
}
return this.collection.count();
};
/**
* Terminates the chain and returns array of filtered documents
*
* @param {object=} options - allows specifying 'forceClones' and 'forceCloneMethod' options.
* @param {boolean} options.forceClones - Allows forcing the return of cloned objects even when
* the collection is not configured for clone object.
* @param {string} options.forceCloneMethod - Allows overriding the default or collection specified cloning method.
* Possible values include 'parse-stringify', 'jquery-extend-deep', 'shallow', 'shallow-assign'
* @param {bool} options.removeMeta - Will force clones and strip $loki and meta properties from documents
*
* @returns {array} Array of documents in the resultset
* @memberof Resultset
* @example
* var resutls = users.chain().find({ age: 34 }).data();
*/
Resultset.prototype.data = function (options) {
var result = [],
data = this.collection.data,
obj,
len,
i,
method;
options = options || {};
if (options.removeMeta && !options.forceClones) {
options.forceClones = true;
options.forceCloneMethod = options.forceCloneMethod || 'shallow';
}
if (!this.collection.disableDeltaChangesApi) {
options.forceClones = true;
options.forceCloneMethod = 'parse-stringify';
}
if (!this.filterInitialized) {
if (this.filteredrows.length === 0) {
if (this.collection.cloneObjects || options.forceClones) {
len = data.length;
method = options.forceCloneMethod || this.collection.cloneMethod;
for (i = 0; i < len; i++) {
obj = clone(data[i], method);
if (options.removeMeta) {
delete obj.$loki;
delete obj.meta;
}
result.push(obj);
}
return result;
}
else {
return data.slice();
}
} else {
this.filterInitialized = true;
}
}
var fr = this.filteredrows;
len = fr.length;
if (this.collection.cloneObjects || options.forceClones) {
method = options.forceCloneMethod || this.collection.cloneMethod;
for (i = 0; i < len; i++) {
obj = clone(data[fr[i]], method);
if (options.removeMeta) {
delete obj.$loki;
delete obj.meta;
}
result.push(obj);
}
} else {
for (i = 0; i < len; i++) {
result.push(data[fr[i]]);
}
}
return result;
};
/**
* Used to run an update operation on all documents currently in the resultset.
*
* @param {function} updateFunction - User supplied updateFunction(obj) will be executed for each document object.
* @returns {Resultset} this resultset for further chain ops.
* @memberof Resultset
* @example
* users.chain().find({ country: 'de' }).update(function(user) {
* user.phoneFormat = "+49 AAAA BBBBBB";
* });
*/
Resultset.prototype.update = function (updateFunction) {
if (typeof (updateFunction) !== "function") {
throw new TypeError('Argument is not a function');
}
if (!this.filterInitialized && this.filteredrows.length === 0) {
this.filteredrows = this.collection.prepareFullDocIndex();
}
var obj, len = this.filteredrows.length,
rcd = this.collection.data;
for (var idx = 0; idx < len; idx++) {
if (this.collection.cloneObjects || !this.collection.disableDeltaChangesApi) {
obj = clone(rcd[this.filteredrows[idx]], this.collection.cloneMethod);
updateFunction(obj);
this.collection.update(obj);
}
else {
updateFunction(rcd[this.filteredrows[idx]]);
this.collection.update(rcd[this.filteredrows[idx]]);
}
}
return this;
};
/**
* Removes all document objects which are currently in resultset from collection (as well as resultset)
*
* @returns {Resultset} this (empty) resultset for further chain ops.
* @memberof Resultset
* @example
* // remove users inactive since 1/1/2001
* users.chain().find({ lastActive: { $lte: new Date("1/1/2001").getTime() } }).remove();
*/
Resultset.prototype.remove = function () {
if (!this.filterInitialized && this.filteredrows.length === 0) {
this.filteredrows = this.collection.prepareFullDocIndex();
}
this.collection.removeBatchByPositions(this.filteredrows);
this.filteredrows = [];
return this;
};
/**
* data transformation via user supplied functions
*
* @param {function} mapFunction - this function accepts a single document for you to transform and return
* @param {function} reduceFunction - this function accepts many (array of map outputs) and returns single value
* @returns {value} The output of your reduceFunction
* @memberof Resultset
* @example
* var db = new loki("order.db");
* var orders = db.addCollection("orders");
* orders.insert([{ qty: 4, unitCost: 100.00 }, { qty: 10, unitCost: 999.99 }, { qty: 2, unitCost: 49.99 }]);
*
* function mapfun (obj) { return obj.qty*obj.unitCost };
* function reducefun(array) {
* var grandTotal=0;
* array.forEach(function(orderTotal) { grandTotal += orderTotal; });
* return grandTotal;
* }
* var grandOrderTotal = orders.chain().mapReduce(mapfun, reducefun);
* console.log(grandOrderTotal);
*/
Resultset.prototype.mapReduce = function (mapFunction, reduceFunction) {
try {
return reduceFunction(this.data().map(mapFunction));
} catch (err) {
throw err;
}
};
/**
* eqJoin() - Left joining two sets of data. Join keys can be defined or calculated properties
* eqJoin expects the right join key values to be unique. Otherwise left data will be joined on the last joinData object with that key
* @param {Array|Resultset|Collection} joinData - Data array to join to.
* @param {(string|function)} leftJoinKey - Property name in this result set to join on or a function to produce a value to join on
* @param {(string|function)} rightJoinKey - Property name in the joinData to join on or a function to produce a value to join on
* @param {function=} mapFun - (Optional) A function that receives each matching pair and maps them into output objects - function(left,right){return joinedObject}
* @param {object=} dataOptions - options to data() before input to your map function
* @param {bool} dataOptions.removeMeta - allows removing meta before calling mapFun
* @param {boolean} dataOptions.forceClones - forcing the return of cloned objects to your map object
* @param {string} dataOptions.forceCloneMethod - Allows overriding the default or collection specified cloning method.
* @returns {Resultset} A resultset with data in the format [{left: leftObj, right: rightObj}]
* @memberof Resultset
* @example
* var db = new loki('sandbox.db');
*
* var products = db.addCollection('products');
* var orders = db.addCollection('orders');
*
* products.insert({ productId: "100234", name: "flywheel energy storage", unitCost: 19999.99 });
* products.insert({ productId: "140491", name: "300F super capacitor", unitCost: 129.99 });
* products.insert({ productId: "271941", name: "fuel cell", unitCost: 3999.99 });
* products.insert({ productId: "174592", name: "390V 3AH lithium bank", unitCost: 4999.99 });
*
* orders.insert({ orderDate : new Date("12/1/2017").getTime(), prodId: "174592", qty: 2, customerId: 2 });
* orders.insert({ orderDate : new Date("4/15/2016").getTime(), prodId: "271941", qty: 1, customerId: 1 });
* orders.insert({ orderDate : new Date("3/12/2017").getTime(), prodId: "140491", qty: 4, customerId: 4 });
* orders.insert({ orderDate : new Date("7/31/2017").getTime(), prodId: "100234", qty: 7, customerId: 3 });
* orders.insert({ orderDate : new Date("8/3/2016").getTime(), prodId: "174592", qty: 3, customerId: 5 });
*
* var mapfun = function(left, right) {
* return {
* orderId: left.$loki,
* orderDate: new Date(left.orderDate) + '',
* customerId: left.customerId,
* qty: left.qty,
* productId: left.prodId,
* prodName: right.name,
* prodCost: right.unitCost,
* orderTotal: +((right.unitCost * left.qty).toFixed(2))
* };
* };
*
* // join orders with relevant product info via eqJoin
* var orderSummary = orders.chain().eqJoin(products, "prodId", "productId", mapfun).data();
*
* console.log(orderSummary);
*/
Resultset.prototype.eqJoin = function (joinData, leftJoinKey, rightJoinKey, mapFun, dataOptions) {
var leftData = [],
leftDataLength,
rightData = [],
rightDataLength,
key,
result = [],
leftKeyisFunction = typeof leftJoinKey === 'function',
rightKeyisFunction = typeof rightJoinKey === 'function',
joinMap = {};
leftData = this.data(dataOptions);
leftDataLength = leftData.length;
if (joinData instanceof Collection) {
rightData = joinData.chain().data(dataOptions);
} else if (joinData instanceof Resultset) {
rightData = joinData.data(dataOptions);
} else if (Array.isArray(joinData)) {
rightData = joinData;
} else {
throw new TypeError('joinData needs to be an array or result set');
}
rightDataLength = rightData.length;
for (var i = 0; i < rightDataLength; i++) {
key = rightKeyisFunction ? rightJoinKey(rightData[i]) : rightData[i][rightJoinKey];
joinMap[key] = rightData[i];
}
if (!mapFun) {
mapFun = function (left, right) {
return {
left: left,
right: right
};
};
}
for (var j = 0; j < leftDataLength; j++) {
key = leftKeyisFunction ? leftJoinKey(leftData[j]) : leftData[j][leftJoinKey];
result.push(mapFun(leftData[j], joinMap[key] || {}));
}
this.collection = new Collection('joinData');
this.collection.insert(result);
this.filteredrows = [];
this.filterInitialized = false;
return this;
};
/**
* Applies a map function into a new collection for further chaining.
* @param {function} mapFun - javascript map function
* @param {object=} dataOptions - options to data() before input to your map function
* @param {bool} dataOptions.removeMeta - allows removing meta before calling mapFun
* @param {boolean} dataOptions.forceClones - forcing the return of cloned objects to your map object
* @param {string} dataOptions.forceCloneMethod - Allows overriding the default or collection specified cloning method.
* @memberof Resultset
* @example
* var orders.chain().find({ productId: 32 }).map(function(obj) {
* return {
* orderId: $loki,
* productId: productId,
* quantity: qty
* };
* });
*/
Resultset.prototype.map = function (mapFun, dataOptions) {
var data = this.data(dataOptions).map(mapFun);
this.collection = new Collection('mappedData');
this.collection.insert(data);
this.filteredrows = [];
this.filterInitialized = false;
return this;
};
/**
* DynamicView class is a versatile 'live' view class which can have filters and sorts applied.
* Collection.addDynamicView(name) instantiates this DynamicView object and notifies it
* whenever documents are add/updated/removed so it can remain up-to-date. (chainable)
*
* @example
* var mydv = mycollection.addDynamicView('test'); // default is non-persistent
* mydv.applyFind({ 'doors' : 4 });
* mydv.applyWhere(function(obj) { return obj.name === 'Toyota'; });
* var results = mydv.data();
*
* @constructor DynamicView
* @implements LokiEventEmitter
* @param {Collection} collection - A reference to the collection to work against
* @param {string} name - The name of this dynamic view
* @param {object=} options - (Optional) Pass in object with 'persistent' and/or 'sortPriority' options.
* @param {boolean} [options.persistent=false] - indicates if view is to main internal results array in 'resultdata'
* @param {string} [options.sortPriority='passive'] - 'passive' (sorts performed on call to data) or 'active' (after updates)
* @param {number} options.minRebuildInterval - minimum rebuild interval (need clarification to docs here)
* @see {@link Collection#addDynamicView} to construct instances of DynamicView
*/
function DynamicView(collection, name, options) {
this.collection = collection;
this.name = name;
this.rebuildPending = false;
this.options = options || {};
if (!this.options.hasOwnProperty('persistent')) {
this.options.persistent = false;
}
if (!this.options.hasOwnProperty('sortPriority')) {
this.options.sortPriority = 'passive';
}
if (!this.options.hasOwnProperty('minRebuildInterval')) {
this.options.minRebuildInterval = 1;
}
this.resultset = new Resultset(collection);
this.resultdata = [];
this.resultsdirty = false;
this.cachedresultset = null;
this.filterPipeline = [];
this.sortFunction = null;
this.sortCriteria = null;
this.sortCriteriaSimple = null;
this.sortDirty = false;
this.events = {
'rebuild': []
};
}
DynamicView.prototype = new LokiEventEmitter();
/**
* rematerialize() - internally used immediately after deserialization (loading)
* This will clear out and reapply filterPipeline ops, recreating the view.
* Since where filters do not persist correctly, this method allows
* restoring the view to state where user can re-apply those where filters.
*
* @param {Object=} options - (Optional) allows specification of 'removeWhereFilters' option
* @returns {DynamicView} This dynamic view for further chained ops.
* @memberof DynamicView
* @fires DynamicView.rebuild
*/
DynamicView.prototype.rematerialize = function (options) {
var fpl,
fpi,
idx;
options = options || {};
this.resultdata = [];
this.resultsdirty = true;
this.resultset = new Resultset(this.collection);
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.sortDirty = true;
}
if (options.hasOwnProperty('removeWhereFilters')) {
fpl = this.filterPipeline.length;
fpi = fpl;
while (fpi--) {
if (this.filterPipeline[fpi].type === 'where') {
if (fpi !== this.filterPipeline.length - 1) {
this.filterPipeline[fpi] = this.filterPipeline[this.filterPipeline.length - 1];
}
this.filterPipeline.length--;
}
}
}
var ofp = this.filterPipeline;
this.filterPipeline = [];
fpl = ofp.length;
for (idx = 0; idx < fpl; idx++) {
this.applyFind(ofp[idx].val);
}
this.data();
this.emit('rebuild', this);
return this;
};
/**
* branchResultset() - Makes a copy of the internal resultset for branched queries.
* Unlike this dynamic view, the branched resultset will not be 'live' updated,
* so your branched query should be immediately resolved and not held for future evaluation.
*
* @param {(string|array=)} transform - Optional name of collection transform, or an array of transform steps
* @param {object=} parameters - optional parameters (if optional transform requires them)
* @returns {Resultset} A copy of the internal resultset for branched queries.
* @memberof DynamicView
* @example
* var db = new loki('test');
* var coll = db.addCollection('mydocs');
* var dv = coll.addDynamicView('myview');
* var tx = [
* {
* type: 'offset',
* value: '[%lktxp]pageStart'
* },
* {
* type: 'limit',
* value: '[%lktxp]pageSize'
* }
* ];
* coll.addTransform('viewPaging', tx);
*
* // add some records
*
* var results = dv.branchResultset('viewPaging', { pageStart: 10, pageSize: 10 }).data();
*/
DynamicView.prototype.branchResultset = function (transform, parameters) {
var rs = this.resultset.branch();
if (typeof transform === 'undefined') {
return rs;
}
return rs.transform(transform, parameters);
};
/**
* toJSON() - Override of toJSON to avoid circular references
*
*/
DynamicView.prototype.toJSON = function () {
var copy = new DynamicView(this.collection, this.name, this.options);
copy.resultset = this.resultset;
copy.resultdata = []; // let's not save data (copy) to minimize size
copy.resultsdirty = true;
copy.filterPipeline = this.filterPipeline;
copy.sortFunction = this.sortFunction;
copy.sortCriteria = this.sortCriteria;
copy.sortCriteriaSimple = this.sortCriteriaSimple || null;
copy.sortDirty = this.sortDirty;
copy.collection = null;
return copy;
};
/**
* removeFilters() - Used to clear pipeline and reset dynamic view to initial state.
* Existing options should be retained.
* @param {object=} options - configure removeFilter behavior
* @param {boolean=} options.queueSortPhase - (default: false) if true we will async rebuild view (maybe set default to true in future?)
* @memberof DynamicView
*/
DynamicView.prototype.removeFilters = function (options) {
options = options || {};
this.rebuildPending = false;
this.resultset.reset();
this.resultdata = [];
this.resultsdirty = true;
this.cachedresultset = null;
this.filterPipeline = [];
this.sortFunction = null;
this.sortCriteria = null;
this.sortCriteriaSimple = null;
this.sortDirty = false;
if (options.queueSortPhase === true) {
this.queueSortPhase();
}
};
/**
* applySort() - Used to apply a sort to the dynamic view
* @example
* dv.applySort(function(obj1, obj2) {
* if (obj1.name === obj2.name) return 0;
* if (obj1.name > obj2.name) return 1;
* if (obj1.name < obj2.name) return -1;
* });
*
* @param {function} comparefun - a javascript compare function used for sorting
* @returns {DynamicView} this DynamicView object, for further chain ops.
* @memberof DynamicView
*/
DynamicView.prototype.applySort = function (comparefun) {
this.sortFunction = comparefun;
this.sortCriteria = null;
this.sortCriteriaSimple = null;
this.queueSortPhase();
return this;
};
/**
* applySimpleSort() - Used to specify a property used for view translation.
* @example
* dv.applySimpleSort("name");
*
* @param {string} propname - Name of property by which to sort.
* @param {object|boolean=} options - boolean for sort descending or options object
* @param {boolean} [options.desc=false] - whether we should sort descending.
* @param {boolean} [options.disableIndexIntersect=false] - whether we should explicity not use array intersection.
* @param {boolean} [options.forceIndexIntersect=false] - force array intersection (if binary index exists).
* @param {boolean} [options.useJavascriptSorting=false] - whether results are sorted via basic javascript sort.
* @returns {DynamicView} this DynamicView object, for further chain ops.
* @memberof DynamicView
*/
DynamicView.prototype.applySimpleSort = function (propname, options) {
this.sortCriteriaSimple = { propname: propname, options: options || false };
this.sortCriteria = null;
this.sortFunction = null;
this.queueSortPhase();
return this;
};
/**
* applySortCriteria() - Allows sorting a resultset based on multiple columns.
* @example
* // to sort by age and then name (both ascending)
* dv.applySortCriteria(['age', 'name']);
* // to sort by age (ascending) and then by name (descending)
* dv.applySortCriteria(['age', ['name', true]);
* // to sort by age (descending) and then by name (descending)
* dv.applySortCriteria(['age', true], ['name', true]);
*
* @param {array} properties - array of property names or subarray of [propertyname, isdesc] used evaluate sort order
* @returns {DynamicView} Reference to this DynamicView, sorted, for future chain operations.
* @memberof DynamicView
*/
DynamicView.prototype.applySortCriteria = function (criteria) {
this.sortCriteria = criteria;
this.sortCriteriaSimple = null;
this.sortFunction = null;
this.queueSortPhase();
return this;
};
/**
* startTransaction() - marks the beginning of a transaction.
*
* @returns {DynamicView} this DynamicView object, for further chain ops.
*/
DynamicView.prototype.startTransaction = function () {
this.cachedresultset = this.resultset.copy();
return this;
};
/**
* commit() - commits a transaction.
*
* @returns {DynamicView} this DynamicView object, for further chain ops.
*/
DynamicView.prototype.commit = function () {
this.cachedresultset = null;
return this;
};
/**
* rollback() - rolls back a transaction.
*
* @returns {DynamicView} this DynamicView object, for further chain ops.
*/
DynamicView.prototype.rollback = function () {
this.resultset = this.cachedresultset;
if (this.options.persistent) {
this.resultdata = this.resultset.data();
this.emit('rebuild', this);
}
return this;
};
/**
* Implementation detail.
* _indexOfFilterWithId() - Find the index of a filter in the pipeline, by that filter's ID.
*
* @param {(string|number)} uid - The unique ID of the filter.
* @returns {number}: index of the referenced filter in the pipeline; -1 if not found.
*/
DynamicView.prototype._indexOfFilterWithId = function (uid) {
if (typeof uid === 'string' || typeof uid === 'number') {
for (var idx = 0, len = this.filterPipeline.length; idx < len; idx += 1) {
if (uid === this.filterPipeline[idx].uid) {
return idx;
}
}
}
return -1;
};
/**
* Implementation detail.
* _addFilter() - Add the filter object to the end of view's filter pipeline and apply the filter to the resultset.
*
* @param {object} filter - The filter object. Refer to applyFilter() for extra details.
*/
DynamicView.prototype._addFilter = function (filter) {
this.filterPipeline.push(filter);
this.resultset[filter.type](filter.val);
};
/**
* reapplyFilters() - Reapply all the filters in the current pipeline.
*
* @returns {DynamicView} this DynamicView object, for further chain ops.
*/
DynamicView.prototype.reapplyFilters = function () {
this.resultset.reset();
this.cachedresultset = null;
if (this.options.persistent) {
this.resultdata = [];
this.resultsdirty = true;
}
var filters = this.filterPipeline;
this.filterPipeline = [];
for (var idx = 0, len = filters.length; idx < len; idx += 1) {
this._addFilter(filters[idx]);
}
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.queueSortPhase();
} else {
this.queueRebuildEvent();
}
return this;
};
/**
* applyFilter() - Adds or updates a filter in the DynamicView filter pipeline
*
* @param {object} filter - A filter object to add to the pipeline.
* The object is in the format { 'type': filter_type, 'val', filter_param, 'uid', optional_filter_id }
* @returns {DynamicView} this DynamicView object, for further chain ops.
* @memberof DynamicView
*/
DynamicView.prototype.applyFilter = function (filter) {
var idx = this._indexOfFilterWithId(filter.uid);
if (idx >= 0) {
this.filterPipeline[idx] = filter;
return this.reapplyFilters();
}
this.cachedresultset = null;
if (this.options.persistent) {
this.resultdata = [];
this.resultsdirty = true;
}
this._addFilter(filter);
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.queueSortPhase();
} else {
this.queueRebuildEvent();
}
return this;
};
/**
* applyFind() - Adds or updates a mongo-style query option in the DynamicView filter pipeline
*
* @param {object} query - A mongo-style query object to apply to pipeline
* @param {(string|number)=} uid - Optional: The unique ID of this filter, to reference it in the future.
* @returns {DynamicView} this DynamicView object, for further chain ops.
* @memberof DynamicView
*/
DynamicView.prototype.applyFind = function (query, uid) {
this.applyFilter({
type: 'find',
val: query,
uid: uid
});
return this;
};
/**
* applyWhere() - Adds or updates a javascript filter function in the DynamicView filter pipeline
*
* @param {function} fun - A javascript filter function to apply to pipeline
* @param {(string|number)=} uid - Optional: The unique ID of this filter, to reference it in the future.
* @returns {DynamicView} this DynamicView object, for further chain ops.
* @memberof DynamicView
*/
DynamicView.prototype.applyWhere = function (fun, uid) {
this.applyFilter({
type: 'where',
val: fun,
uid: uid
});
return this;
};
/**
* removeFilter() - Remove the specified filter from the DynamicView filter pipeline
*
* @param {(string|number)} uid - The unique ID of the filter to be removed.
* @returns {DynamicView} this DynamicView object, for further chain ops.
* @memberof DynamicView
*/
DynamicView.prototype.removeFilter = function (uid) {
var idx = this._indexOfFilterWithId(uid);
if (idx < 0) {
throw new Error("Dynamic view does not contain a filter with ID: " + uid);
}
this.filterPipeline.splice(idx, 1);
this.reapplyFilters();
return this;
};
/**
* count() - returns the number of documents representing the current DynamicView contents.
*
* @returns {number} The number of documents representing the current DynamicView contents.
* @memberof DynamicView
*/
DynamicView.prototype.count = function () {
if (this.resultsdirty) {
this.resultdata = this.resultset.data();
}
return this.resultset.count();
};
/**
* data() - resolves and pending filtering and sorting, then returns document array as result.
*
* @param {object=} options - optional parameters to pass to resultset.data() if non-persistent
* @param {boolean} options.forceClones - Allows forcing the return of cloned objects even when
* the collection is not configured for clone object.
* @param {string} options.forceCloneMethod - Allows overriding the default or collection specified cloning method.
* Possible values include 'parse-stringify', 'jquery-extend-deep', 'shallow', 'shallow-assign'
* @param {bool} options.removeMeta - Will force clones and strip $loki and meta properties from documents
* @returns {array} An array of documents representing the current DynamicView contents.
* @memberof DynamicView
*/
DynamicView.prototype.data = function (options) {
if (this.sortDirty || this.resultsdirty) {
this.performSortPhase({
suppressRebuildEvent: true
});
}
return (this.options.persistent) ? (this.resultdata) : (this.resultset.data(options));
};
/**
* queueRebuildEvent() - When the view is not sorted we may still wish to be notified of rebuild events.
* This event will throttle and queue a single rebuild event when batches of updates affect the view.
*/
DynamicView.prototype.queueRebuildEvent = function () {
if (this.rebuildPending) {
return;
}
this.rebuildPending = true;
var self = this;
setTimeout(function () {
if (self.rebuildPending) {
self.rebuildPending = false;
self.emit('rebuild', self);
}
}, this.options.minRebuildInterval);
};
/**
* queueSortPhase : If the view is sorted we will throttle sorting to either :
* (1) passive - when the user calls data(), or
* (2) active - once they stop updating and yield js thread control
*/
DynamicView.prototype.queueSortPhase = function () {
if (this.sortDirty) {
return;
}
this.sortDirty = true;
var self = this;
if (this.options.sortPriority === "active") {
setTimeout(function () {
self.performSortPhase();
}, this.options.minRebuildInterval);
} else {
this.queueRebuildEvent();
}
};
/**
* performSortPhase() - invoked synchronously or asynchronously to perform final sort phase (if needed)
*
*/
DynamicView.prototype.performSortPhase = function (options) {
if (!this.sortDirty && !this.resultsdirty) {
return;
}
options = options || {};
if (this.sortDirty) {
if (this.sortFunction) {
this.resultset.sort(this.sortFunction);
} else if (this.sortCriteria) {
this.resultset.compoundsort(this.sortCriteria);
} else if (this.sortCriteriaSimple) {
this.resultset.simplesort(this.sortCriteriaSimple.propname, this.sortCriteriaSimple.options);
}
this.sortDirty = false;
}
if (this.options.persistent) {
this.resultdata = this.resultset.data();
this.resultsdirty = false;
}
if (!options.suppressRebuildEvent) {
this.emit('rebuild', this);
}
};
/**
* evaluateDocument() - internal method for (re)evaluating document inclusion.
* Called by : collection.insert() and collection.update().
*
* @param {int} objIndex - index of document to (re)run through filter pipeline.
* @param {bool} isNew - true if the document was just added to the collection.
*/
DynamicView.prototype.evaluateDocument = function (objIndex, isNew) {
if (!this.resultset.filterInitialized) {
if (this.options.persistent) {
this.resultdata = this.resultset.data();
}
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.queueSortPhase();
} else {
this.queueRebuildEvent();
}
return;
}
var ofr = this.resultset.filteredrows;
var oldPos = (isNew) ? (-1) : (ofr.indexOf(+objIndex));
var oldlen = ofr.length;
var evalResultset = new Resultset(this.collection);
evalResultset.filteredrows = [objIndex];
evalResultset.filterInitialized = true;
var filter;
for (var idx = 0, len = this.filterPipeline.length; idx < len; idx++) {
filter = this.filterPipeline[idx];
evalResultset[filter.type](filter.val);
}
var newPos = (evalResultset.filteredrows.length === 0) ? -1 : 0;
if (oldPos === -1 && newPos === -1) return;
if (oldPos === -1 && newPos !== -1) {
ofr.push(objIndex);
if (this.options.persistent) {
this.resultdata.push(this.collection.data[objIndex]);
}
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.queueSortPhase();
} else {
this.queueRebuildEvent();
}
return;
}
if (oldPos !== -1 && newPos === -1) {
if (oldPos < oldlen - 1) {
ofr.splice(oldPos, 1);
if (this.options.persistent) {
this.resultdata.splice(oldPos, 1);
}
} else {
ofr.length = oldlen - 1;
if (this.options.persistent) {
this.resultdata.length = oldlen - 1;
}
}
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.queueSortPhase();
} else {
this.queueRebuildEvent();
}
return;
}
if (oldPos !== -1 && newPos !== -1) {
if (this.options.persistent) {
this.resultdata[oldPos] = this.collection.data[objIndex];
}
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.queueSortPhase();
} else {
this.queueRebuildEvent();
}
return;
}
};
/**
* removeDocument() - internal function called on collection.delete()
* @param {number|number[]} objIndex - index of document to (re)run through filter pipeline.
*/
DynamicView.prototype.removeDocument = function (objIndex) {
var idx, rmidx, rmlen, rxo = {}, fxo = {};
var adjels = [];
var drs = this.resultset;
var fr = this.resultset.filteredrows;
var frlen = fr.length;
if (!this.resultset.filterInitialized) {
if (this.options.persistent) {
this.resultdata = this.resultset.data();
}
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.queueSortPhase();
} else {
this.queueRebuildEvent();
}
return;
}
if (!Array.isArray(objIndex)) {
objIndex = [objIndex];
}
rmlen = objIndex.length;
for(rmidx=0;rmidx<rmlen; rmidx++) {
rxo[objIndex[rmidx]] = true;
}
for (idx=0; idx<frlen; idx++) {
if (rxo[fr[idx]]) fxo[idx] = true;
}
if (Object.keys(fxo).length > 0) {
this.resultset.filteredrows = this.resultset.filteredrows.filter(function(di, idx) { return !fxo[idx]; });
if (this.options.persistent) {
this.resultdata = this.resultdata.filter(function(obj, idx) { return !fxo[idx]; });
}
if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {
this.queueSortPhase();
} else {
this.queueRebuildEvent();
}
}
var filt = function(idx) { return function(di) { return di < drs.filteredrows[idx]; }; };
frlen = drs.filteredrows.length;
for (idx = 0; idx < frlen; idx++) {
adjels = objIndex.filter(filt(idx));
drs.filteredrows[idx] -= adjels.length;
}
};
/**
* mapReduce() - data transformation via user supplied functions
*
* @param {function} mapFunction - this function accepts a single document for you to transform and return
* @param {function} reduceFunction - this function accepts many (array of map outputs) and returns single value
* @returns The output of your reduceFunction
* @memberof DynamicView
*/
DynamicView.prototype.mapReduce = function (mapFunction, reduceFunction) {
try {
return reduceFunction(this.data().map(mapFunction));
} catch (err) {
throw err;
}
};
/**
* Collection class that handles documents of same type
* @constructor Collection
* @implements LokiEventEmitter
* @param {string} name - collection name
* @param {(array|object)=} options - (optional) array of property names to be indicized OR a configuration object
* @param {array=} [options.unique=[]] - array of property names to define unique constraints for
* @param {array=} [options.exact=[]] - array of property names to define exact constraints for
* @param {array=} [options.indices=[]] - array property names to define binary indexes for
* @param {boolean} [options.adaptiveBinaryIndices=true] - collection indices will be actively rebuilt rather than lazily
* @param {boolean} [options.asyncListeners=false] - whether listeners are invoked asynchronously
* @param {boolean} [options.disableMeta=false] - set to true to disable meta property on documents
* @param {boolean} [options.disableChangesApi=true] - set to false to enable Changes API
* @param {boolean} [options.disableDeltaChangesApi=true] - set to false to enable Delta Changes API (requires Changes API, forces cloning)
* @param {boolean} [options.autoupdate=false] - use Object.observe to update objects automatically
* @param {boolean} [options.clone=false] - specify whether inserts and queries clone to/from user
* @param {boolean} [options.serializableIndices=true[]] - converts date values on binary indexed properties to epoch time
* @param {string} [options.cloneMethod='parse-stringify'] - 'parse-stringify', 'jquery-extend-deep', 'shallow', 'shallow-assign'
* @param {int=} options.ttl - age of document (in ms.) before document is considered aged/stale.
* @param {int=} options.ttlInterval - time interval for clearing out 'aged' documents; not set by default.
* @see {@link Loki#addCollection} for normal creation of collections
*/
function Collection(name, options) {
this.name = name;
this.data = [];
this.idIndex = []; // index of id
this.binaryIndices = {}; // user defined indexes
this.constraints = {
unique: {},
exact: {}
};
this.uniqueNames = [];
this.transforms = {};
this.objType = name;
this.dirty = true;
this.cachedIndex = null;
this.cachedBinaryIndex = null;
this.cachedData = null;
var self = this;
/* OPTIONS */
options = options || {};
if (options.hasOwnProperty('unique')) {
if (!Array.isArray(options.unique)) {
options.unique = [options.unique];
}
options.unique.forEach(function (prop) {
self.uniqueNames.push(prop); // used to regenerate on subsequent database loads
self.constraints.unique[prop] = new UniqueIndex(prop);
});
}
if (options.hasOwnProperty('exact')) {
options.exact.forEach(function (prop) {
self.constraints.exact[prop] = new ExactIndex(prop);
});
}
this.adaptiveBinaryIndices = options.hasOwnProperty('adaptiveBinaryIndices') ? options.adaptiveBinaryIndices : true;
this.transactional = options.hasOwnProperty('transactional') ? options.transactional : false;
this.cloneObjects = options.hasOwnProperty('clone') ? options.clone : false;
this.cloneMethod = options.hasOwnProperty('cloneMethod') ? options.cloneMethod : "parse-stringify";
this.asyncListeners = options.hasOwnProperty('asyncListeners') ? options.asyncListeners : false;
this.disableMeta = options.hasOwnProperty('disableMeta') ? options.disableMeta : false;
this.disableChangesApi = options.hasOwnProperty('disableChangesApi') ? options.disableChangesApi : true;
this.disableDeltaChangesApi = options.hasOwnProperty('disableDeltaChangesApi') ? options.disableDeltaChangesApi : true;
if (this.disableChangesApi) { this.disableDeltaChangesApi = true; }
this.autoupdate = options.hasOwnProperty('autoupdate') ? options.autoupdate : false;
this.serializableIndices = options.hasOwnProperty('serializableIndices') ? options.serializableIndices : true;
this.ttl = {
age: null,
ttlInterval: null,
daemon: null
};
this.setTTL(options.ttl || -1, options.ttlInterval);
this.maxId = 0;
this.DynamicViews = [];
this.events = {
'insert': [],
'update': [],
'pre-insert': [],
'pre-update': [],
'close': [],
'flushbuffer': [],
'error': [],
'delete': [],
'warning': []
};
this.changes = [];
this.ensureId();
var indices = [];
if (options && options.indices) {
if (Object.prototype.toString.call(options.indices) === '[object Array]') {
indices = options.indices;
} else if (typeof options.indices === 'string') {
indices = [options.indices];
} else {
throw new TypeError('Indices needs to be a string or an array of strings');
}
}
for (var idx = 0; idx < indices.length; idx++) {
this.ensureIndex(indices[idx]);
}
function observerCallback(changes) {
var changedObjects = typeof Set === 'function' ? new Set() : [];
if (!changedObjects.add)
changedObjects.add = function (object) {
if (this.indexOf(object) === -1)
this.push(object);
return this;
};
changes.forEach(function (change) {
changedObjects.add(change.object);
});
changedObjects.forEach(function (object) {
if (!hasOwnProperty.call(object, '$loki'))
return self.removeAutoUpdateObserver(object);
try {
self.update(object);
} catch (err) {}
});
}
this.observerCallback = observerCallback;
function getChangeDelta(obj, old) {
if (old) {
return getObjectDelta(old, obj);
}
else {
return JSON.parse(JSON.stringify(obj));
}
}
this.getChangeDelta = getChangeDelta;
function getObjectDelta(oldObject, newObject) {
var propertyNames = newObject !== null && typeof newObject === 'object' ? Object.keys(newObject) : null;
if (propertyNames && propertyNames.length && ['string', 'boolean', 'number'].indexOf(typeof(newObject)) < 0) {
var delta = {};
for (var i = 0; i < propertyNames.length; i++) {
var propertyName = propertyNames[i];
if (newObject.hasOwnProperty(propertyName)) {
if (!oldObject.hasOwnProperty(propertyName) || self.uniqueNames.indexOf(propertyName) >= 0 || propertyName == '$loki' || propertyName == 'meta') {
delta[propertyName] = newObject[propertyName];
}
else {
var propertyDelta = getObjectDelta(oldObject[propertyName], newObject[propertyName]);
if (typeof propertyDelta !== "undefined" && propertyDelta != {}) {
delta[propertyName] = propertyDelta;
}
}
}
}
return Object.keys(delta).length === 0 ? undefined : delta;
}
else {
return oldObject === newObject ? undefined : newObject;
}
}
this.getObjectDelta = getObjectDelta;
function flushChanges() {
self.changes = [];
}
this.getChanges = function () {
return self.changes;
};
this.flushChanges = flushChanges;
this.setChangesApi = function (enabled) {
self.disableChangesApi = !enabled;
if (!enabled) { self.disableDeltaChangesApi = false; }
};
this.on('delete', function deleteCallback(obj) {
if (!self.disableChangesApi) {
self.createChange(self.name, 'R', obj);
}
});
this.on('warning', function (warning) {
self.console.warn(warning);
});
flushChanges();
}
Collection.prototype = new LokiEventEmitter();
/*
* For ChangeAPI default to clone entire object, for delta changes create object with only differences (+ $loki and meta)
*/
Collection.prototype.createChange = function(name, op, obj, old) {
this.changes.push({
name: name,
operation: op,
obj: op == 'U' && !this.disableDeltaChangesApi ? this.getChangeDelta(obj, old) : JSON.parse(JSON.stringify(obj))
});
};
Collection.prototype.insertMeta = function(obj) {
var len, idx;
if (this.disableMeta || !obj) {
return;
}
if (Array.isArray(obj)) {
len = obj.length;
for(idx=0; idx<len; idx++) {
if (!obj[idx].hasOwnProperty('meta')) {
obj[idx].meta = {};
}
obj[idx].meta.created = (new Date()).getTime();
obj[idx].meta.revision = 0;
}
return;
}
if (!obj.meta) {
obj.meta = {};
}
obj.meta.created = (new Date()).getTime();
obj.meta.revision = 0;
};
Collection.prototype.updateMeta = function(obj) {
if (this.disableMeta || !obj) {
return;
}
obj.meta.updated = (new Date()).getTime();
obj.meta.revision += 1;
};
Collection.prototype.createInsertChange = function(obj) {
this.createChange(this.name, 'I', obj);
};
Collection.prototype.createUpdateChange = function(obj, old) {
this.createChange(this.name, 'U', obj, old);
};
Collection.prototype.insertMetaWithChange = function(obj) {
this.insertMeta(obj);
this.createInsertChange(obj);
};
Collection.prototype.updateMetaWithChange = function(obj, old) {
this.updateMeta(obj);
this.createUpdateChange(obj, old);
};
Collection.prototype.console = {
log: function () {},
warn: function () {},
error: function () {},
};
Collection.prototype.addAutoUpdateObserver = function (object) {
if (!this.autoupdate || typeof Object.observe !== 'function')
return;
Object.observe(object, this.observerCallback, ['add', 'update', 'delete', 'reconfigure', 'setPrototype']);
};
Collection.prototype.removeAutoUpdateObserver = function (object) {
if (!this.autoupdate || typeof Object.observe !== 'function')
return;
Object.unobserve(object, this.observerCallback);
};
/**
* Adds a named collection transform to the collection
* @param {string} name - name to associate with transform
* @param {array} transform - an array of transformation 'step' objects to save into the collection
* @memberof Collection
* @example
* users.addTransform('progeny', [
* {
* type: 'find',
* value: {
* 'age': {'$lte': 40}
* }
* }
* ]);
*
* var results = users.chain('progeny').data();
*/
Collection.prototype.addTransform = function (name, transform) {
if (this.transforms.hasOwnProperty(name)) {
throw new Error("a transform by that name already exists");
}
this.transforms[name] = transform;
};
/**
* Retrieves a named transform from the collection.
* @param {string} name - name of the transform to lookup.
* @memberof Collection
*/
Collection.prototype.getTransform = function (name) {
return this.transforms[name];
};
/**
* Updates a named collection transform to the collection
* @param {string} name - name to associate with transform
* @param {object} transform - a transformation object to save into collection
* @memberof Collection
*/
Collection.prototype.setTransform = function (name, transform) {
this.transforms[name] = transform;
};
/**
* Removes a named collection transform from the collection
* @param {string} name - name of collection transform to remove
* @memberof Collection
*/
Collection.prototype.removeTransform = function (name) {
delete this.transforms[name];
};
Collection.prototype.byExample = function (template) {
var k, obj, query;
query = [];
for (k in template) {
if (!template.hasOwnProperty(k)) continue;
query.push((
obj = {},
obj[k] = template[k],
obj
));
}
return {
'$and': query
};
};
Collection.prototype.findObject = function (template) {
return this.findOne(this.byExample(template));
};
Collection.prototype.findObjects = function (template) {
return this.find(this.byExample(template));
};
/*----------------------------+
| TTL daemon |
+----------------------------*/
Collection.prototype.ttlDaemonFuncGen = function () {
var collection = this;
var age = this.ttl.age;
return function ttlDaemon() {
var now = Date.now();
var toRemove = collection.chain().where(function daemonFilter(member) {
var timestamp = member.meta.updated || member.meta.created;
var diff = now - timestamp;
return age < diff;
});
toRemove.remove();
};
};
/**
* Updates or applies collection TTL settings.
* @param {int} age - age (in ms) to expire document from collection
* @param {int} interval - time (in ms) to clear collection of aged documents.
* @memberof Collection
*/
Collection.prototype.setTTL = function (age, interval) {
if (age < 0) {
clearInterval(this.ttl.daemon);
} else {
this.ttl.age = age;
this.ttl.ttlInterval = interval;
this.ttl.daemon = setInterval(this.ttlDaemonFuncGen(), interval);
}
};
/*----------------------------+
| INDEXING |
+----------------------------*/
/**
* create a row filter that covers all documents in the collection
*/
Collection.prototype.prepareFullDocIndex = function () {
var len = this.data.length;
var indexes = new Array(len);
for (var i = 0; i < len; i += 1) {
indexes[i] = i;
}
return indexes;
};
/**
* Will allow reconfiguring certain collection options.
* @param {boolean} options.adaptiveBinaryIndices - collection indices will be actively rebuilt rather than lazily
* @memberof Collection
*/
Collection.prototype.configureOptions = function (options) {
options = options || {};
if (options.hasOwnProperty('adaptiveBinaryIndices')) {
this.adaptiveBinaryIndices = options.adaptiveBinaryIndices;
if (this.adaptiveBinaryIndices) {
this.ensureAllIndexes();
}
}
};
/**
* Ensure binary index on a certain field
* @param {string} property - name of property to create binary index on
* @param {boolean=} force - (Optional) flag indicating whether to construct index immediately
* @memberof Collection
*/
Collection.prototype.ensureIndex = function (property, force) {
if (typeof (force) === 'undefined') {
force = false;
}
if (property === null || property === undefined) {
throw new Error('Attempting to set index without an associated property');
}
if (this.binaryIndices[property] && !force) {
if (!this.binaryIndices[property].dirty) return;
}
if (this.adaptiveBinaryIndices === true && this.binaryIndices.hasOwnProperty(property) && !force) {
return;
}
var index = {
'name': property,
'dirty': true,
'values': this.prepareFullDocIndex()
};
this.binaryIndices[property] = index;
var wrappedComparer =
(function (prop, data) {
var val1, val2, arr;
return function (a, b) {
if (~prop.indexOf('.')) {
arr = prop.split('.');
val1 = arr.reduce(function(obj, i) { return obj && obj[i] || undefined; }, data[a]);
val2 = arr.reduce(function(obj, i) { return obj && obj[i] || undefined; }, data[b]);
} else {
val1 = data[a][prop];
val2 = data[b][prop];
}
if (val1 !== val2) {
if (Comparators.lt(val1, val2, false)) return -1;
if (Comparators.gt(val1, val2, false)) return 1;
}
return 0;
};
})(property, this.data);
index.values.sort(wrappedComparer);
index.dirty = false;
this.dirty = true; // for autosave scenarios
};
/**
* Perform checks to determine validity/consistency of all binary indices
* @param {object=} options - optional configuration object
* @param {boolean} [options.randomSampling=false] - whether (faster) random sampling should be used
* @param {number} [options.randomSamplingFactor=0.10] - percentage of total rows to randomly sample
* @param {boolean} [options.repair=false] - whether to fix problems if they are encountered
* @returns {string[]} array of index names where problems were found.
* @memberof Collection
* @example
* // check all indices on a collection, returns array of invalid index names
* var result = coll.checkAllIndexes({ repair: true, randomSampling: true, randomSamplingFactor: 0.15 });
* if (result.length > 0) {
* results.forEach(function(name) {
* console.log('problem encountered with index : ' + name);
* });
* }
*/
Collection.prototype.checkAllIndexes = function (options) {
var key, bIndices = this.binaryIndices;
var results = [], result;
for (key in bIndices) {
if (hasOwnProperty.call(bIndices, key)) {
result = this.checkIndex(key, options);
if (!result) {
results.push(key);
}
}
}
return results;
};
/**
* Perform checks to determine validity/consistency of a binary index
* @param {string} property - name of the binary-indexed property to check
* @param {object=} options - optional configuration object
* @param {boolean} [options.randomSampling=false] - whether (faster) random sampling should be used
* @param {number} [options.randomSamplingFactor=0.10] - percentage of total rows to randomly sample
* @param {boolean} [options.repair=false] - whether to fix problems if they are encountered
* @returns {boolean} whether the index was found to be valid (before optional correcting).
* @memberof Collection
* @example
* // full test
* var valid = coll.checkIndex('name');
* // full test with repair (if issues found)
* valid = coll.checkIndex('name', { repair: true });
* // random sampling (default is 10% of total document count)
* valid = coll.checkIndex('name', { randomSampling: true });
* // random sampling (sample 20% of total document count)
* valid = coll.checkIndex('name', { randomSampling: true, randomSamplingFactor: 0.20 });
* // random sampling (implied boolean)
* valid = coll.checkIndex('name', { randomSamplingFactor: 0.20 });
* // random sampling with repair (if issues found)
* valid = coll.checkIndex('name', { repair: true, randomSampling: true });
*/
Collection.prototype.checkIndex = function (property, options) {
options = options || {};
if (options.randomSamplingFactor && options.randomSampling !== false) {
options.randomSampling = true;
}
options.randomSamplingFactor = options.randomSamplingFactor || 0.1;
if (options.randomSamplingFactor < 0 || options.randomSamplingFactor > 1) {
options.randomSamplingFactor = 0.1;
}
var valid=true, idx, iter, pos, len, biv;
if (!this.binaryIndices.hasOwnProperty(property)) {
throw new Error("called checkIndex on property without an index: " + property);
}
if (!this.adaptiveBinaryIndices) {
this.ensureIndex(property);
}
biv = this.binaryIndices[property].values;
len = biv.length;
if (len !== this.data.length) {
if (options.repair) {
this.ensureIndex(property, true);
}
return false;
}
if (len === 0) {
return true;
}
if (len === 1) {
valid = (biv[0] === 0);
}
else {
if (options.randomSampling) {
if (!LokiOps.$lte(this.data[biv[0]][property], this.data[biv[1]][property])) {
valid=false;
}
if (!LokiOps.$lte(this.data[biv[len-2]][property], this.data[biv[len-1]][property])) {
valid=false;
}
if (valid) {
iter = Math.floor((len-1) * options.randomSamplingFactor);
for(idx=0; idx<iter-1; idx++) {
pos = Math.floor(Math.random() * (len-1));
if (!LokiOps.$lte(this.data[biv[pos]][property], this.data[biv[pos+1]][property])) {
valid=false;
break;
}
}
}
}
else {
for(idx=0; idx<len-1; idx++) {
if (!LokiOps.$lte(this.data[biv[idx]][property], this.data[biv[idx+1]][property])) {
valid=false;
break;
}
}
}
}
if (!valid && options.repair) {
this.ensureIndex(property, true);
}
return valid;
};
Collection.prototype.getBinaryIndexValues = function (property) {
var idx, idxvals = this.binaryIndices[property].values;
var result = [];
for (idx = 0; idx < idxvals.length; idx++) {
result.push(this.data[idxvals[idx]][property]);
}
return result;
};
Collection.prototype.ensureUniqueIndex = function (field) {
var index = this.constraints.unique[field];
if (!index) {
if (this.uniqueNames.indexOf(field) == -1) {
this.uniqueNames.push(field);
}
}
this.constraints.unique[field] = index = new UniqueIndex(field);
this.data.forEach(function (obj) {
index.set(obj);
});
return index;
};
/**
* Ensure all binary indices
* @param {boolean} force - whether to force rebuild of existing lazy binary indices
* @memberof Collection
*/
Collection.prototype.ensureAllIndexes = function (force) {
var key, bIndices = this.binaryIndices;
for (key in bIndices) {
if (hasOwnProperty.call(bIndices, key)) {
this.ensureIndex(key, force);
}
}
};
/**
* Internal method used to flag all lazy index as dirty
*/
Collection.prototype.flagBinaryIndexesDirty = function () {
var key, bIndices = this.binaryIndices;
for (key in bIndices) {
if (hasOwnProperty.call(bIndices, key)) {
bIndices[key].dirty = true;
}
}
};
/**
* Internal method used to flag a lazy index as dirty
*/
Collection.prototype.flagBinaryIndexDirty = function (index) {
if (this.binaryIndices[index])
this.binaryIndices[index].dirty = true;
};
/**
* Quickly determine number of documents in collection (or query)
* @param {object=} query - (optional) query object to count results of
* @returns {number} number of documents in the collection
* @memberof Collection
*/
Collection.prototype.count = function (query) {
if (!query) {
return this.data.length;
}
return this.chain().find(query).filteredrows.length;
};
/**
* Rebuild idIndex
*/
Collection.prototype.ensureId = function () {
var len = this.data.length,
i = 0;
this.idIndex = [];
for (i; i < len; i += 1) {
this.idIndex.push(this.data[i].$loki);
}
};
/**
* Rebuild idIndex async with callback - useful for background syncing with a remote server
*/
Collection.prototype.ensureIdAsync = function (callback) {
this.async(function () {
this.ensureId();
}, callback);
};
/**
* Add a dynamic view to the collection
* @param {string} name - name of dynamic view to add
* @param {object=} options - options to configure dynamic view with
* @param {boolean} [options.persistent=false] - indicates if view is to main internal results array in 'resultdata'
* @param {string} [options.sortPriority='passive'] - 'passive' (sorts performed on call to data) or 'active' (after updates)
* @param {number} options.minRebuildInterval - minimum rebuild interval (need clarification to docs here)
* @returns {DynamicView} reference to the dynamic view added
* @memberof Collection
* @example
* var pview = users.addDynamicView('progeny');
* pview.applyFind({'age': {'$lte': 40}});
* pview.applySimpleSort('name');
*
* var results = pview.data();
**/
Collection.prototype.addDynamicView = function (name, options) {
var dv = new DynamicView(this, name, options);
this.DynamicViews.push(dv);
return dv;
};
/**
* Remove a dynamic view from the collection
* @param {string} name - name of dynamic view to remove
* @memberof Collection
**/
Collection.prototype.removeDynamicView = function (name) {
for (var idx = 0; idx < this.DynamicViews.length; idx++) {
if (this.DynamicViews[idx].name === name) {
this.DynamicViews.splice(idx, 1);
}
}
};
/**
* Look up dynamic view reference from within the collection
* @param {string} name - name of dynamic view to retrieve reference of
* @returns {DynamicView} A reference to the dynamic view with that name
* @memberof Collection
**/
Collection.prototype.getDynamicView = function (name) {
for (var idx = 0; idx < this.DynamicViews.length; idx++) {
if (this.DynamicViews[idx].name === name) {
return this.DynamicViews[idx];
}
}
return null;
};
/**
* Applies a 'mongo-like' find query object and passes all results to an update function.
* For filter function querying you should migrate to [updateWhere()]{@link Collection#updateWhere}.
*
* @param {object|function} filterObject - 'mongo-like' query object (or deprecated filterFunction mode)
* @param {function} updateFunction - update function to run against filtered documents
* @memberof Collection
*/
Collection.prototype.findAndUpdate = function (filterObject, updateFunction) {
if (typeof (filterObject) === "function") {
this.updateWhere(filterObject, updateFunction);
}
else {
this.chain().find(filterObject).update(updateFunction);
}
};
/**
* Applies a 'mongo-like' find query object removes all documents which match that filter.
*
* @param {object} filterObject - 'mongo-like' query object
* @memberof Collection
*/
Collection.prototype.findAndRemove = function(filterObject) {
this.chain().find(filterObject).remove();
};
/**
* Adds object(s) to collection, ensure object(s) have meta properties, clone it if necessary, etc.
* @param {(object|array)} doc - the document (or array of documents) to be inserted
* @returns {(object|array)} document or documents inserted
* @memberof Collection
* @example
* users.insert({
* name: 'Odin',
* age: 50,
* address: 'Asgard'
* });
*
* // alternatively, insert array of documents
* users.insert([{ name: 'Thor', age: 35}, { name: 'Loki', age: 30}]);
*/
Collection.prototype.insert = function (doc) {
if (!Array.isArray(doc)) {
return this.insertOne(doc);
}
var obj;
var results = [];
this.emit('pre-insert', doc);
for (var i = 0, len = doc.length; i < len; i++) {
obj = this.insertOne(doc[i], true);
if (!obj) {
return undefined;
}
results.push(obj);
}
this.emit('insert', results);
results = this.cloneObjects ? clone(results, this.cloneMethod) : results;
return results.length === 1 ? results[0] : results;
};
/**
* Adds a single object, ensures it has meta properties, clone it if necessary, etc.
* @param {object} doc - the document to be inserted
* @param {boolean} bulkInsert - quiet pre-insert and insert event emits
* @returns {object} document or 'undefined' if there was a problem inserting it
*/
Collection.prototype.insertOne = function (doc, bulkInsert) {
var err = null;
var returnObj;
if (typeof doc !== 'object') {
err = new TypeError('Document needs to be an object');
} else if (doc === null) {
err = new TypeError('Object cannot be null');
}
if (err !== null) {
this.emit('error', err);
throw err;
}
var obj = this.cloneObjects ? clone(doc, this.cloneMethod) : doc;
if (!this.disableMeta && typeof obj.meta === 'undefined') {
obj.meta = {
revision: 0,
created: 0
};
}
if (!bulkInsert) {
this.emit('pre-insert', obj);
}
if (!this.add(obj)) {
return undefined;
}
if (this.disableChangesApi) {
this.insertMeta(obj);
}
else {
this.insertMetaWithChange(obj);
}
returnObj = this.cloneObjects ? clone(obj, this.cloneMethod) : obj;
if (!bulkInsert) {
this.emit('insert', returnObj);
}
this.addAutoUpdateObserver(returnObj);
return returnObj;
};
/**
* Empties the collection.
* @param {object=} options - configure clear behavior
* @param {bool=} [options.removeIndices=false] - whether to remove indices in addition to data
* @memberof Collection
*/
Collection.prototype.clear = function (options) {
var self = this;
options = options || {};
this.data = [];
this.idIndex = [];
this.cachedIndex = null;
this.cachedBinaryIndex = null;
this.cachedData = null;
this.maxId = 0;
this.DynamicViews = [];
this.dirty = true;
if (options.removeIndices === true) {
this.binaryIndices = {};
this.constraints = {
unique: {},
exact: {}
};
this.uniqueNames = [];
}
else {
var keys = Object.keys(this.binaryIndices);
keys.forEach(function(biname) {
self.binaryIndices[biname].dirty = false;
self.binaryIndices[biname].values = [];
});
this.constraints = {
unique: {},
exact: {}
};
this.uniqueNames.forEach(function(uiname) {
self.ensureUniqueIndex(uiname);
});
}
};
/**
* Updates an object and notifies collection that the document has changed.
* @param {object} doc - document to update within the collection
* @memberof Collection
*/
Collection.prototype.update = function (doc) {
var adaptiveBatchOverride, k, len;
if (Array.isArray(doc)) {
len = doc.length;
adaptiveBatchOverride = !this.cloneObjects &&
this.adaptiveBinaryIndices && Object.keys(this.binaryIndices).length > 0;
if (adaptiveBatchOverride) {
this.adaptiveBinaryIndices = false;
}
try {
for (k=0; k < len; k += 1) {
this.update(doc[k]);
}
}
finally {
if (adaptiveBatchOverride) {
this.ensureAllIndexes();
this.adaptiveBinaryIndices = true;
}
}
return;
}
if (!hasOwnProperty.call(doc, '$loki')) {
throw new Error('Trying to update unsynced document. Please save the document first by using insert() or addMany()');
}
try {
this.startTransaction();
var arr = this.get(doc.$loki, true),
oldInternal, // ref to existing obj
newInternal, // ref to new internal obj
position,
self = this;
if (!arr) {
throw new Error('Trying to update a document not in collection.');
}
oldInternal = arr[0]; // -internal- obj ref
position = arr[1]; // position in data array
newInternal = this.cloneObjects || !this.disableDeltaChangesApi ? clone(doc, this.cloneMethod) : doc;
this.emit('pre-update', doc);
Object.keys(this.constraints.unique).forEach(function (key) {
self.constraints.unique[key].update(oldInternal, newInternal);
});
this.data[position] = newInternal;
if (newInternal !== doc) {
this.addAutoUpdateObserver(doc);
}
for (var idx = 0; idx < this.DynamicViews.length; idx++) {
this.DynamicViews[idx].evaluateDocument(position, false);
}
var key;
if (this.adaptiveBinaryIndices) {
var bIndices = this.binaryIndices;
for (key in bIndices) {
this.adaptiveBinaryIndexUpdate(position, key);
}
}
else {
this.flagBinaryIndexesDirty();
}
this.idIndex[position] = newInternal.$loki;
this.commit();
this.dirty = true; // for autosave scenarios
if (this.disableChangesApi) {
this.updateMeta(newInternal, null);
}
else {
this.updateMetaWithChange(newInternal, oldInternal);
}
var returnObj;
if (this.cloneObjects) {
returnObj = clone(newInternal, this.cloneMethod);
}
else {
returnObj = newInternal;
}
this.emit('update', returnObj, oldInternal);
return returnObj;
} catch (err) {
this.rollback();
this.console.error(err.message);
this.emit('error', err);
throw (err); // re-throw error so user does not think it succeeded
}
};
/**
* Add object to collection
*/
Collection.prototype.add = function (obj) {
if ('object' !== typeof obj) {
throw new TypeError('Object being added needs to be an object');
}
if (typeof (obj.$loki) !== 'undefined') {
throw new Error('Document is already in collection, please use update()');
}
/*
* try adding object to collection
*/
try {
this.startTransaction();
this.maxId++;
if (isNaN(this.maxId)) {
this.maxId = (this.data[this.data.length - 1].$loki + 1);
}
obj.$loki = this.maxId;
if (!this.disableMeta) {
obj.meta.version = 0;
}
var key, constrUnique = this.constraints.unique;
for (key in constrUnique) {
if (hasOwnProperty.call(constrUnique, key)) {
constrUnique[key].set(obj);
}
}
this.idIndex.push(obj.$loki);
this.data.push(obj);
var addedPos = this.data.length - 1;
var dvlen = this.DynamicViews.length;
for (var i = 0; i < dvlen; i++) {
this.DynamicViews[i].evaluateDocument(addedPos, true);
}
if (this.adaptiveBinaryIndices) {
var bIndices = this.binaryIndices;
for (key in bIndices) {
this.adaptiveBinaryIndexInsert(addedPos, key);
}
}
else {
this.flagBinaryIndexesDirty();
}
this.commit();
this.dirty = true; // for autosave scenarios
return (this.cloneObjects) ? (clone(obj, this.cloneMethod)) : (obj);
} catch (err) {
this.rollback();
this.console.error(err.message);
this.emit('error', err);
throw (err); // re-throw error so user does not think it succeeded
}
};
/**
* Applies a filter function and passes all results to an update function.
*
* @param {function} filterFunction - filter function whose results will execute update
* @param {function} updateFunction - update function to run against filtered documents
* @memberof Collection
*/
Collection.prototype.updateWhere = function(filterFunction, updateFunction) {
var results = this.where(filterFunction),
i = 0,
obj;
try {
for (i; i < results.length; i++) {
obj = updateFunction(results[i]);
this.update(obj);
}
} catch (err) {
this.rollback();
this.console.error(err.message);
}
};
/**
* Remove all documents matching supplied filter function.
* For 'mongo-like' querying you should migrate to [findAndRemove()]{@link Collection#findAndRemove}.
* @param {function|object} query - query object to filter on
* @memberof Collection
*/
Collection.prototype.removeWhere = function (query) {
var list;
if (typeof query === 'function') {
list = this.data.filter(query);
this.remove(list);
} else {
this.chain().find(query).remove();
}
};
Collection.prototype.removeDataOnly = function () {
this.remove(this.data.slice());
};
/**
* Internal method to remove a batch of documents from the collection.
* @param {number[]} positions - data/idIndex positions to remove
*/
Collection.prototype.removeBatchByPositions = function(positions) {
var len = positions.length;
var xo = {};
var dlen, didx, idx;
var bic=Object.keys(this.binaryIndices).length;
var adaptiveOverride = this.adaptiveBinaryIndices && Object.keys(this.binaryIndices).length > 0;
try {
this.startTransaction();
for(idx=0; idx < len; idx++) {
xo[this.idIndex[positions[idx]]] = true;
}
dlen = this.DynamicViews.length;
if ((dlen > 0) || (bic > 0)) {
if (dlen > 0) {
for (didx = 0; didx < dlen; didx++) {
this.DynamicViews[didx].removeDocument(positions);
}
}
if (this.adaptiveBinaryIndices && !adaptiveOverride) {
var key, bIndices = this.binaryIndices;
for (key in bIndices) {
this.adaptiveBinaryIndexRemove(positions, key);
}
}
else {
this.flagBinaryIndexesDirty();
}
}
if (!this.disableChangesApi || this.events.delete.length > 1) {
for(idx=0; idx < len; idx++) {
this.emit('delete', this.data[positions[idx]]);
}
}
this.data = this.data.filter(function(obj) {
return !xo[obj.$loki];
});
this.idIndex = this.idIndex.filter(function(id) {
return !xo[id];
});
if (this.adaptiveBinaryIndices && adaptiveOverride) {
this.adaptiveBinaryIndices = false;
this.ensureAllIndexes(true);
this.adaptiveBinaryIndices = true;
}
this.commit();
this.dirty = true;
}
catch (err) {
this.rollback();
if (adaptiveOverride) {
this.adaptiveBinaryIndices = true;
}
this.console.error(err.message);
this.emit('error', err);
return null;
}
};
/**
* Internal method called by remove()
* @param {object[]|number[]} batch - array of documents or $loki ids to remove
*/
Collection.prototype.removeBatch = function(batch) {
var len = batch.length,
dlen=this.data.length,
idx;
var xlt = {};
var posx = [];
for (idx=0; idx < dlen; idx++) {
xlt[this.data[idx].$loki] = idx;
}
for (idx=0; idx < len; idx++) {
if (typeof(batch[idx]) === 'object') {
posx.push(xlt[batch[idx].$loki]);
}
else {
posx.push(xlt[batch[idx]]);
}
}
this.removeBatchByPositions(posx);
};
/**
* Remove a document from the collection
* @param {object} doc - document to remove from collection
* @memberof Collection
*/
Collection.prototype.remove = function (doc) {
if (typeof doc === 'number') {
doc = this.get(doc);
}
if ('object' !== typeof doc) {
throw new Error('Parameter is not an object');
}
if (Array.isArray(doc)) {
this.removeBatch(doc);
return;
}
if (!hasOwnProperty.call(doc, '$loki')) {
throw new Error('Object is not a document stored in the collection');
}
try {
this.startTransaction();
var arr = this.get(doc.$loki, true),
position = arr[1];
var self = this;
Object.keys(this.constraints.unique).forEach(function (key) {
if (doc[key] !== null && typeof doc[key] !== 'undefined') {
self.constraints.unique[key].remove(doc[key]);
}
});
for (var idx = 0; idx < this.DynamicViews.length; idx++) {
this.DynamicViews[idx].removeDocument(position);
}
if (this.adaptiveBinaryIndices) {
var key, bIndices = this.binaryIndices;
for (key in bIndices) {
this.adaptiveBinaryIndexRemove(position, key);
}
}
else {
this.flagBinaryIndexesDirty();
}
this.data.splice(position, 1);
this.removeAutoUpdateObserver(doc);
this.idIndex.splice(position, 1);
this.commit();
this.dirty = true; // for autosave scenarios
this.emit('delete', arr[0]);
delete doc.$loki;
delete doc.meta;
return doc;
} catch (err) {
this.rollback();
this.console.error(err.message);
this.emit('error', err);
return null;
}
};
/*---------------------+
| Finding methods |
+----------------------*/
/**
* Get by Id - faster than other methods because of the searching algorithm
* @param {int} id - $loki id of document you want to retrieve
* @param {boolean} returnPosition - if 'true' we will return [object, position]
* @returns {(object|array|null)} Object reference if document was found, null if not,
* or an array if 'returnPosition' was passed.
* @memberof Collection
*/
Collection.prototype.get = function (id, returnPosition) {
var retpos = returnPosition || false,
data = this.idIndex,
max = data.length - 1,
min = 0,
mid = (min + max) >> 1;
id = typeof id === 'number' ? id : parseInt(id, 10);
if (isNaN(id)) {
throw new TypeError('Passed id is not an integer');
}
while (data[min] < data[max]) {
mid = (min + max) >> 1;
if (data[mid] < id) {
min = mid + 1;
} else {
max = mid;
}
}
if (max === min && data[min] === id) {
if (retpos) {
return [this.data[min], min];
}
return this.data[min];
}
return null;
};
/**
* Perform binary range lookup for the data[dataPosition][binaryIndexName] property value
* Since multiple documents may contain the same value (which the index is sorted on),
* we hone in on range and then linear scan range to find exact index array position.
* @param {int} dataPosition : coll.data array index/position
* @param {string} binaryIndexName : index to search for dataPosition in
*/
Collection.prototype.getBinaryIndexPosition = function(dataPosition, binaryIndexName) {
var val = this.data[dataPosition][binaryIndexName];
var index = this.binaryIndices[binaryIndexName].values;
var range = this.calculateRange("$eq", binaryIndexName, val);
if (range[0] === 0 && range[1] === -1) {
return null;
}
var min = range[0];
var max = range[1];
for(var idx = min; idx <= max; idx++) {
if (index[idx] === dataPosition) return idx;
}
return null;
};
/**
* Adaptively insert a selected item to the index.
* @param {int} dataPosition : coll.data array index/position
* @param {string} binaryIndexName : index to search for dataPosition in
*/
Collection.prototype.adaptiveBinaryIndexInsert = function(dataPosition, binaryIndexName) {
var index = this.binaryIndices[binaryIndexName].values;
var val = this.data[dataPosition][binaryIndexName];
if (this.serializableIndices === true && val instanceof Date) {
this.data[dataPosition][binaryIndexName] = val.getTime();
val = this.data[dataPosition][binaryIndexName];
}
var idxPos = (index.length === 0)?0:this.calculateRangeStart(binaryIndexName, val, true);
this.binaryIndices[binaryIndexName].values.splice(idxPos, 0, dataPosition);
};
/**
* Adaptively update a selected item within an index.
* @param {int} dataPosition : coll.data array index/position
* @param {string} binaryIndexName : index to search for dataPosition in
*/
Collection.prototype.adaptiveBinaryIndexUpdate = function(dataPosition, binaryIndexName) {
var idxPos,
index = this.binaryIndices[binaryIndexName].values,
len=index.length;
for(idxPos=0; idxPos < len; idxPos++) {
if (index[idxPos] === dataPosition) break;
}
this.binaryIndices[binaryIndexName].values.splice(idxPos, 1);
this.adaptiveBinaryIndexInsert(dataPosition, binaryIndexName);
};
/**
* Adaptively remove a selected item from the index.
* @param {number|number[]} dataPosition : coll.data array index/position
* @param {string} binaryIndexName : index to search for dataPosition in
*/
Collection.prototype.adaptiveBinaryIndexRemove = function(dataPosition, binaryIndexName, removedFromIndexOnly) {
var bi = this.binaryIndices[binaryIndexName];
var len, idx, rmidx, rmlen, rxo = {};
var curr, shift, idxPos;
if (Array.isArray(dataPosition)) {
rmlen = dataPosition.length;
if (rmlen === 1) {
dataPosition = dataPosition[0];
}
else {
for(rmidx=0;rmidx<rmlen; rmidx++) {
rxo[dataPosition[rmidx]] = true;
}
bi.values = bi.values.filter(function(di) { return !rxo[di]; });
if (removedFromIndexOnly === true) {
return;
}
var sortedPositions = dataPosition.slice();
sortedPositions.sort(function (a, b) { return a-b; });
len = bi.values.length;
for (idx=0; idx<len; idx++) {
curr=bi.values[idx];
shift=0;
for(rmidx=0; rmidx<rmlen && curr > sortedPositions[rmidx]; rmidx++) {
shift++;
}
bi.values[idx]-=shift;
}
return;
}
}
idxPos = this.getBinaryIndexPosition(dataPosition, binaryIndexName);
if (idxPos === null) {
return null;
}
bi.values.splice(idxPos, 1);
if (removedFromIndexOnly === true) {
return;
}
len = bi.values.length;
for (idx = 0; idx < len; idx++) {
if (bi.values[idx] > dataPosition) {
bi.values[idx]--;
}
}
};
/**
* Internal method used for index maintenance and indexed searching.
* Calculates the beginning of an index range for a given value.
* For index maintainance (adaptive:true), we will return a valid index position to insert to.
* For querying (adaptive:false/undefined), we will :
* return lower bound/index of range of that value (if found)
* return next lower index position if not found (hole)
* If index is empty it is assumed to be handled at higher level, so
* this method assumes there is at least 1 document in index.
*
* @param {string} prop - name of property which has binary index
* @param {any} val - value to find within index
* @param {bool?} adaptive - if true, we will return insert position
*/
Collection.prototype.calculateRangeStart = function (prop, val, adaptive) {
var rcd = this.data;
var index = this.binaryIndices[prop].values;
var min = 0;
var max = index.length - 1;
var mid = 0;
if (index.length === 0) {
return -1;
}
var minVal = rcd[index[min]][prop];
var maxVal = rcd[index[max]][prop];
while (min < max) {
mid = (min + max) >> 1;
if (Comparators.lt(rcd[index[mid]][prop], val, false)) {
min = mid + 1;
} else {
max = mid;
}
}
var lbound = min;
if (Comparators.aeq(val, rcd[index[lbound]][prop])) {
return lbound;
}
if (Comparators.lt(val, rcd[index[lbound]][prop], false)) {
return adaptive?lbound:lbound-1;
}
return adaptive?lbound+1:lbound;
};
/**
* Internal method used for indexed $between. Given a prop (index name), and a value
* (which may or may not yet exist) this will find the final position of that upper range value.
*/
Collection.prototype.calculateRangeEnd = function (prop, val) {
var rcd = this.data;
var index = this.binaryIndices[prop].values;
var min = 0;
var max = index.length - 1;
var mid = 0;
if (index.length === 0) {
return -1;
}
var minVal = rcd[index[min]][prop];
var maxVal = rcd[index[max]][prop];
while (min < max) {
mid = (min + max) >> 1;
if (Comparators.lt(val, rcd[index[mid]][prop], false)) {
max = mid;
} else {
min = mid + 1;
}
}
var ubound = max;
if (Comparators.aeq(val, rcd[index[ubound]][prop])) {
return ubound;
}
if (Comparators.gt(val, rcd[index[ubound]][prop], false)) {
return ubound+1;
}
if (Comparators.aeq(val, rcd[index[ubound-1]][prop])) {
return ubound-1;
}
return ubound;
};
/**
* calculateRange() - Binary Search utility method to find range/segment of values matching criteria.
* this is used for collection.find() and first find filter of resultset/dynview
* slightly different than get() binary search in that get() hones in on 1 value,
* but we have to hone in on many (range)
* @param {string} op - operation, such as $eq
* @param {string} prop - name of property to calculate range for
* @param {object} val - value to use for range calculation.
* @returns {array} [start, end] index array positions
*/
Collection.prototype.calculateRange = function (op, prop, val) {
var rcd = this.data;
var index = this.binaryIndices[prop].values;
var min = 0;
var max = index.length - 1;
var mid = 0;
var lbound, lval;
var ubound, uval;
if (rcd.length === 0) {
return [0, -1];
}
var minVal = rcd[index[min]][prop];
var maxVal = rcd[index[max]][prop];
switch (op) {
case '$eq':
case '$aeq':
if (Comparators.lt(val, minVal, false) || Comparators.gt(val, maxVal, false)) {
return [0, -1];
}
break;
case '$dteq':
if (Comparators.lt(val, minVal, false) || Comparators.gt(val, maxVal, false)) {
return [0, -1];
}
break;
case '$gt':
if (Comparators.gt(val, maxVal, true)) {
return [0, -1];
}
if (Comparators.gt(minVal, val, false)) {
return [min, max];
}
break;
case '$gte':
if (Comparators.gt(val, maxVal, false)) {
return [0, -1];
}
if (Comparators.gt(minVal, val, true)) {
return [min, max];
}
break;
case '$lt':
if (Comparators.lt(val, minVal, true)) {
return [0, -1];
}
if (Comparators.lt(maxVal, val, false)) {
return [min, max];
}
break;
case '$lte':
if (Comparators.lt(val, minVal, false)) {
return [0, -1];
}
if (Comparators.lt(maxVal, val, true)) {
return [min, max];
}
break;
case '$between':
if (Comparators.gt(val[0], maxVal, false)) {
return [0, -1];
}
if (Comparators.lt(val[1], minVal, false)) {
return [0, -1];
}
lbound = this.calculateRangeStart(prop, val[0]);
ubound = this.calculateRangeEnd(prop, val[1]);
if (lbound < 0) lbound++;
if (ubound > max) ubound--;
if (!Comparators.gt(rcd[index[lbound]][prop], val[0], true)) lbound++;
if (!Comparators.lt(rcd[index[ubound]][prop], val[1], true)) ubound--;
if (ubound < lbound) return [0, -1];
return ([lbound, ubound]);
case '$in':
var idxset = [],
segResult = [];
for (var j = 0, len = val.length; j < len; j++) {
var seg = this.calculateRange('$eq', prop, val[j]);
for (var i = seg[0]; i <= seg[1]; i++) {
if (idxset[i] === undefined) {
idxset[i] = true;
segResult.push(i);
}
}
}
return segResult;
}
switch (op) {
case '$eq':
case '$aeq':
case '$dteq':
case '$gte':
case '$lt':
lbound = this.calculateRangeStart(prop, val);
lval = rcd[index[lbound]][prop];
break;
default: break;
}
switch (op) {
case '$eq':
case '$aeq':
case '$dteq':
case '$lte':
case '$gt':
ubound = this.calculateRangeEnd(prop, val);
uval = rcd[index[ubound]][prop];
break;
default: break;
}
switch (op) {
case '$eq':
case '$aeq':
case '$dteq':
if (!Comparators.aeq(lval, val)) {
return [0, -1];
}
return [lbound, ubound];
case '$gt':
if (!Comparators.aeq(rcd[index[ubound]][prop], val)) {
return [ubound, max];
}
return [ubound+1, max];
case '$gte':
if (!Comparators.aeq(rcd[index[lbound]][prop], val)) {
return [lbound+1, max];
}
return [lbound, max];
case '$lt':
if (!Comparators.aeq(rcd[index[lbound]][prop], val)) {
return [min, lbound];
}
return [min, lbound-1];
case '$lte':
if (!Comparators.aeq(rcd[index[ubound]][prop], val)) {
return [min, ubound-1];
}
return [min, ubound];
default:
return [0, rcd.length - 1];
}
};
/**
* Retrieve doc by Unique index
* @param {string} field - name of uniquely indexed property to use when doing lookup
* @param {value} value - unique value to search for
* @returns {object} document matching the value passed
* @memberof Collection
*/
Collection.prototype.by = function (field, value) {
var self;
if (value === undefined) {
self = this;
return function (value) {
return self.by(field, value);
};
}
var result = this.constraints.unique[field].get(value);
if (!this.cloneObjects) {
return result;
} else {
return clone(result, this.cloneMethod);
}
};
/**
* Find one object by index property, by property equal to value
* @param {object} query - query object used to perform search with
* @returns {(object|null)} First matching document, or null if none
* @memberof Collection
*/
Collection.prototype.findOne = function (query) {
query = query || {};
var result = this.chain().find(query,true).data();
if (Array.isArray(result) && result.length === 0) {
return null;
} else {
if (!this.cloneObjects) {
return result[0];
} else {
return clone(result[0], this.cloneMethod);
}
}
};
/**
* Chain method, used for beginning a series of chained find() and/or view() operations
* on a collection.
*
* @param {string|array=} transform - named transform or array of transform steps
* @param {object=} parameters - Object containing properties representing parameters to substitute
* @returns {Resultset} (this) resultset, or data array if any map or join functions where called
* @memberof Collection
*/
Collection.prototype.chain = function (transform, parameters) {
var rs = new Resultset(this);
if (typeof transform === 'undefined') {
return rs;
}
return rs.transform(transform, parameters);
};
/**
* Find method, api is similar to mongodb.
* for more complex queries use [chain()]{@link Collection#chain} or [where()]{@link Collection#where}.
* @example {@tutorial Query Examples}
* @param {object} query - 'mongo-like' query object
* @returns {array} Array of matching documents
* @memberof Collection
*/
Collection.prototype.find = function (query) {
return this.chain().find(query).data();
};
/**
* Find object by unindexed field by property equal to value,
* simply iterates and returns the first element matching the query
*/
Collection.prototype.findOneUnindexed = function (prop, value) {
var i = this.data.length,
doc;
while (i--) {
if (this.data[i][prop] === value) {
doc = this.data[i];
return doc;
}
}
return null;
};
/**
* Transaction methods
*/
/** start the transation */
Collection.prototype.startTransaction = function () {
if (this.transactional) {
this.cachedData = clone(this.data, this.cloneMethod);
this.cachedIndex = this.idIndex;
this.cachedBinaryIndex = this.binaryIndices;
for (var idx = 0; idx < this.DynamicViews.length; idx++) {
this.DynamicViews[idx].startTransaction();
}
}
};
/** commit the transation */
Collection.prototype.commit = function () {
if (this.transactional) {
this.cachedData = null;
this.cachedIndex = null;
this.cachedBinaryIndex = null;
for (var idx = 0; idx < this.DynamicViews.length; idx++) {
this.DynamicViews[idx].commit();
}
}
};
/** roll back the transation */
Collection.prototype.rollback = function () {
if (this.transactional) {
if (this.cachedData !== null && this.cachedIndex !== null) {
this.data = this.cachedData;
this.idIndex = this.cachedIndex;
this.binaryIndices = this.cachedBinaryIndex;
}
for (var idx = 0; idx < this.DynamicViews.length; idx++) {
this.DynamicViews[idx].rollback();
}
}
};
Collection.prototype.async = function (fun, callback) {
setTimeout(function () {
if (typeof fun === 'function') {
fun();
callback();
} else {
throw new TypeError('Argument passed for async execution is not a function');
}
}, 0);
};
/**
* Query the collection by supplying a javascript filter function.
* @example
* var results = coll.where(function(obj) {
* return obj.legs === 8;
* });
*
* @param {function} fun - filter function to run against all collection docs
* @returns {array} all documents which pass your filter function
* @memberof Collection
*/
Collection.prototype.where = function (fun) {
return this.chain().where(fun).data();
};
/**
* Map Reduce operation
*
* @param {function} mapFunction - function to use as map function
* @param {function} reduceFunction - function to use as reduce function
* @returns {data} The result of your mapReduce operation
* @memberof Collection
*/
Collection.prototype.mapReduce = function (mapFunction, reduceFunction) {
try {
return reduceFunction(this.data.map(mapFunction));
} catch (err) {
throw err;
}
};
/**
* Join two collections on specified properties
*
* @param {array|Resultset|Collection} joinData - array of documents to 'join' to this collection
* @param {string} leftJoinProp - property name in collection
* @param {string} rightJoinProp - property name in joinData
* @param {function=} mapFun - (Optional) map function to use
* @param {object=} dataOptions - options to data() before input to your map function
* @param {bool} dataOptions.removeMeta - allows removing meta before calling mapFun
* @param {boolean} dataOptions.forceClones - forcing the return of cloned objects to your map object
* @param {string} dataOptions.forceCloneMethod - Allows overriding the default or collection specified cloning method.
* @returns {Resultset} Result of the mapping operation
* @memberof Collection
*/
Collection.prototype.eqJoin = function (joinData, leftJoinProp, rightJoinProp, mapFun, dataOptions) {
return new Resultset(this).eqJoin(joinData, leftJoinProp, rightJoinProp, mapFun, dataOptions);
};
/* ------ STAGING API -------- */
/**
* stages: a map of uniquely identified 'stages', which hold copies of objects to be
* manipulated without affecting the data in the original collection
*/
Collection.prototype.stages = {};
/**
* (Staging API) create a stage and/or retrieve it
* @memberof Collection
*/
Collection.prototype.getStage = function (name) {
if (!this.stages[name]) {
this.stages[name] = {};
}
return this.stages[name];
};
/**
* a collection of objects recording the changes applied through a commmitStage
*/
Collection.prototype.commitLog = [];
/**
* (Staging API) create a copy of an object and insert it into a stage
* @memberof Collection
*/
Collection.prototype.stage = function (stageName, obj) {
var copy = JSON.parse(JSON.stringify(obj));
this.getStage(stageName)[obj.$loki] = copy;
return copy;
};
/**
* (Staging API) re-attach all objects to the original collection, so indexes and views can be rebuilt
* then create a message to be inserted in the commitlog
* @param {string} stageName - name of stage
* @param {string} message
* @memberof Collection
*/
Collection.prototype.commitStage = function (stageName, message) {
var stage = this.getStage(stageName),
prop,
timestamp = new Date().getTime();
for (prop in stage) {
this.update(stage[prop]);
this.commitLog.push({
timestamp: timestamp,
message: message,
data: JSON.parse(JSON.stringify(stage[prop]))
});
}
this.stages[stageName] = {};
};
Collection.prototype.no_op = function () {
return;
};
/**
* @memberof Collection
*/
Collection.prototype.extract = function (field) {
var i = 0,
len = this.data.length,
isDotNotation = isDeepProperty(field),
result = [];
for (i; i < len; i += 1) {
result.push(deepProperty(this.data[i], field, isDotNotation));
}
return result;
};
/**
* @memberof Collection
*/
Collection.prototype.max = function (field) {
return Math.max.apply(null, this.extract(field));
};
/**
* @memberof Collection
*/
Collection.prototype.min = function (field) {
return Math.min.apply(null, this.extract(field));
};
/**
* @memberof Collection
*/
Collection.prototype.maxRecord = function (field) {
var i = 0,
len = this.data.length,
deep = isDeepProperty(field),
result = {
index: 0,
value: undefined
},
max;
for (i; i < len; i += 1) {
if (max !== undefined) {
if (max < deepProperty(this.data[i], field, deep)) {
max = deepProperty(this.data[i], field, deep);
result.index = this.data[i].$loki;
}
} else {
max = deepProperty(this.data[i], field, deep);
result.index = this.data[i].$loki;
}
}
result.value = max;
return result;
};
/**
* @memberof Collection
*/
Collection.prototype.minRecord = function (field) {
var i = 0,
len = this.data.length,
deep = isDeepProperty(field),
result = {
index: 0,
value: undefined
},
min;
for (i; i < len; i += 1) {
if (min !== undefined) {
if (min > deepProperty(this.data[i], field, deep)) {
min = deepProperty(this.data[i], field, deep);
result.index = this.data[i].$loki;
}
} else {
min = deepProperty(this.data[i], field, deep);
result.index = this.data[i].$loki;
}
}
result.value = min;
return result;
};
/**
* @memberof Collection
*/
Collection.prototype.extractNumerical = function (field) {
return this.extract(field).map(parseBase10).filter(Number).filter(function (n) {
return !(isNaN(n));
});
};
/**
* Calculates the average numerical value of a property
*
* @param {string} field - name of property in docs to average
* @returns {number} average of property in all docs in the collection
* @memberof Collection
*/
Collection.prototype.avg = function (field) {
return average(this.extractNumerical(field));
};
/**
* Calculate standard deviation of a field
* @memberof Collection
* @param {string} field
*/
Collection.prototype.stdDev = function (field) {
return standardDeviation(this.extractNumerical(field));
};
/**
* @memberof Collection
* @param {string} field
*/
Collection.prototype.mode = function (field) {
var dict = {},
data = this.extract(field);
data.forEach(function (obj) {
if (dict[obj]) {
dict[obj] += 1;
} else {
dict[obj] = 1;
}
});
var max,
prop, mode;
for (prop in dict) {
if (max) {
if (max < dict[prop]) {
mode = prop;
}
} else {
mode = prop;
max = dict[prop];
}
}
return mode;
};
/**
* @memberof Collection
* @param {string} field - property name
*/
Collection.prototype.median = function (field) {
var values = this.extractNumerical(field);
values.sort(sub);
var half = Math.floor(values.length / 2);
if (values.length % 2) {
return values[half];
} else {
return (values[half - 1] + values[half]) / 2.0;
}
};
/**
* General utils, including statistical functions
*/
function isDeepProperty(field) {
return field.indexOf('.') !== -1;
}
function parseBase10(num) {
return parseFloat(num, 10);
}
function isNotUndefined(obj) {
return obj !== undefined;
}
function add(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}
function median(values) {
values.sort(sub);
var half = Math.floor(values.length / 2);
return (values.length % 2) ? values[half] : ((values[half - 1] + values[half]) / 2.0);
}
function average(array) {
return (array.reduce(add, 0)) / array.length;
}
function standardDeviation(values) {
var avg = average(values);
var squareDiffs = values.map(function (value) {
var diff = value - avg;
var sqrDiff = diff * diff;
return sqrDiff;
});
var avgSquareDiff = average(squareDiffs);
var stdDev = Math.sqrt(avgSquareDiff);
return stdDev;
}
function deepProperty(obj, property, isDeep) {
if (isDeep === false) {
return obj[property];
}
var pieces = property.split('.'),
root = obj;
while (pieces.length > 0) {
root = root[pieces.shift()];
}
return root;
}
function binarySearch(array, item, fun) {
var lo = 0,
hi = array.length,
compared,
mid;
while (lo < hi) {
mid = (lo + hi) >> 1;
compared = fun.apply(null, [item, array[mid]]);
if (compared === 0) {
return {
found: true,
index: mid
};
} else if (compared < 0) {
hi = mid;
} else {
lo = mid + 1;
}
}
return {
found: false,
index: hi
};
}
function BSonSort(fun) {
return function (array, item) {
return binarySearch(array, item, fun);
};
}
function KeyValueStore() {}
KeyValueStore.prototype = {
keys: [],
values: [],
sort: function (a, b) {
return (a < b) ? -1 : ((a > b) ? 1 : 0);
},
setSort: function (fun) {
this.bs = new BSonSort(fun);
},
bs: function () {
return new BSonSort(this.sort);
},
set: function (key, value) {
var pos = this.bs(this.keys, key);
if (pos.found) {
this.values[pos.index] = value;
} else {
this.keys.splice(pos.index, 0, key);
this.values.splice(pos.index, 0, value);
}
},
get: function (key) {
return this.values[binarySearch(this.keys, key, this.sort).index];
}
};
function UniqueIndex(uniqueField) {
this.field = uniqueField;
this.keyMap = {};
this.lokiMap = {};
}
UniqueIndex.prototype.keyMap = {};
UniqueIndex.prototype.lokiMap = {};
UniqueIndex.prototype.set = function (obj) {
var fieldValue = obj[this.field];
if (fieldValue !== null && typeof (fieldValue) !== 'undefined') {
if (this.keyMap[fieldValue]) {
throw new Error('Duplicate key for property ' + this.field + ': ' + fieldValue);
} else {
this.keyMap[fieldValue] = obj;
this.lokiMap[obj.$loki] = fieldValue;
}
}
};
UniqueIndex.prototype.get = function (key) {
return this.keyMap[key];
};
UniqueIndex.prototype.byId = function (id) {
return this.keyMap[this.lokiMap[id]];
};
/**
* Updates a document's unique index given an updated object.
* @param {Object} obj Original document object
* @param {Object} doc New document object (likely the same as obj)
*/
UniqueIndex.prototype.update = function (obj, doc) {
if (this.lokiMap[obj.$loki] !== doc[this.field]) {
var old = this.lokiMap[obj.$loki];
this.set(doc);
this.keyMap[old] = undefined;
} else {
this.keyMap[obj[this.field]] = doc;
}
};
UniqueIndex.prototype.remove = function (key) {
var obj = this.keyMap[key];
if (obj !== null && typeof obj !== 'undefined') {
this.keyMap[key] = undefined;
this.lokiMap[obj.$loki] = undefined;
} else {
throw new Error('Key is not in unique index: ' + this.field);
}
};
UniqueIndex.prototype.clear = function () {
this.keyMap = {};
this.lokiMap = {};
};
function ExactIndex(exactField) {
this.index = {};
this.field = exactField;
}
ExactIndex.prototype = {
set: function add(key, val) {
if (this.index[key]) {
this.index[key].push(val);
} else {
this.index[key] = [val];
}
},
remove: function remove(key, val) {
var idxSet = this.index[key];
for (var i in idxSet) {
if (idxSet[i] == val) {
idxSet.splice(i, 1);
}
}
if (idxSet.length < 1) {
this.index[key] = undefined;
}
},
get: function get(key) {
return this.index[key];
},
clear: function clear(key) {
this.index = {};
}
};
function SortedIndex(sortedField) {
this.field = sortedField;
}
SortedIndex.prototype = {
keys: [],
values: [],
sort: function (a, b) {
return (a < b) ? -1 : ((a > b) ? 1 : 0);
},
bs: function () {
return new BSonSort(this.sort);
},
setSort: function (fun) {
this.bs = new BSonSort(fun);
},
set: function (key, value) {
var pos = binarySearch(this.keys, key, this.sort);
if (pos.found) {
this.values[pos.index].push(value);
} else {
this.keys.splice(pos.index, 0, key);
this.values.splice(pos.index, 0, [value]);
}
},
get: function (key) {
var bsr = binarySearch(this.keys, key, this.sort);
if (bsr.found) {
return this.values[bsr.index];
} else {
return [];
}
},
getLt: function (key) {
var bsr = binarySearch(this.keys, key, this.sort);
var pos = bsr.index;
if (bsr.found) pos--;
return this.getAll(key, 0, pos);
},
getGt: function (key) {
var bsr = binarySearch(this.keys, key, this.sort);
var pos = bsr.index;
if (bsr.found) pos++;
return this.getAll(key, pos, this.keys.length);
},
getAll: function (key, start, end) {
var results = [];
for (var i = start; i < end; i++) {
results = results.concat(this.values[i]);
}
return results;
},
getPos: function (key) {
return binarySearch(this.keys, key, this.sort);
},
remove: function (key, value) {
var pos = binarySearch(this.keys, key, this.sort).index;
var idxSet = this.values[pos];
for (var i in idxSet) {
if (idxSet[i] == value) idxSet.splice(i, 1);
}
if (idxSet.length < 1) {
this.keys.splice(pos, 1);
this.values.splice(pos, 1);
}
},
clear: function () {
this.keys = [];
this.values = [];
}
};
Loki.LokiOps = LokiOps;
Loki.Collection = Collection;
Loki.KeyValueStore = KeyValueStore;
Loki.LokiMemoryAdapter = LokiMemoryAdapter;
Loki.LokiPartitioningAdapter = LokiPartitioningAdapter;
Loki.LokiLocalStorageAdapter = LokiLocalStorageAdapter;
Loki.LokiFsAdapter = LokiFsAdapter;
Loki.persistenceAdapters = {
fs: LokiFsAdapter,
localStorage: LokiLocalStorageAdapter
};
Loki.aeq = aeqHelper;
Loki.lt = ltHelper;
Loki.gt = gtHelper;
Loki.Comparators = Comparators;
return Loki;
}());
}));
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(14), __webpack_require__(13)))
/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var logger_1 = __webpack_require__(1);
var loki = __webpack_require__(8);
/**
* Creates the offline database and return base API for external usage.
* @ignore
* @type {{getDB, getCollection, getCollectionByName, getCollections, save, close, flush}}
*/
var DB = /** @class */ (function () {
function DB(name, $options, lokiStorageAdapter) {
this.name = name;
this.$options = $options;
this.lokiStorageAdapter = lokiStorageAdapter;
/**
* Create internal JS offline persistence db.
*/
this._db = null;
this.options = {
autosave: true,
autosaveInterval: 500,
autoload: true,
adapter: null,
};
this.logger = new logger_1.Logger('DB');
if (this.$options.off === true || this.$options.isTest === true) {
this.options = {
autosave: false,
autosaveInterval: 60 * 1000 * 60 * 24 * 30 * 12,
autoload: false,
adapter: null,
};
}
this.options.adapter = lokiStorageAdapter;
var dbName = this.name || name || this.$options.offlineDBName || 'offline';
this._db = new loki(dbName, this.options);
this._db.loadDatabase();
}
/**
* Wraps the way collection should be taken from the database. If given collection name does not exist
* it will create it.
*
* @param name - the name of the collection to get, create
* @returns {*}
*/
DB.prototype.getCollectionByName = function (name) {
var col = this._db.getCollection(name);
if (!col) {
var options = {
clone: true,
disableChangesApi: true,
transactional: true,
};
col = this._db.addCollection(name, options);
}
return col;
};
/**
* Return collection directly with promise. If collection does not exist it will be created.
*
* @param name - the name of the database collection
* @returns {*}
* @deprecated
*/
DB.prototype.getCollection = function (name) {
var _this = this;
return new Promise(function (resolve) {
_this._db.loadDatabase({}, function () {
var col = _this._db.getCollection(name);
if (!col) {
var options = {
clone: true,
disableChangesApi: true,
transactional: true,
};
col = _this._db.addCollection(name);
}
resolve(col);
});
});
};
/**
* In case developer explicitly wants to save the database, after db operation.
*/
DB.prototype.saveDatabase = function () {
this._db.save();
};
/**
* Returns all registered collections from the offline database.
*/
DB.prototype.getCollections = function () {
return this._db.listCollections();
};
/**
* Return the object to the internal created database
* @returns {*}
*/
DB.prototype.internalDB = function () {
return this._db;
};
/**
* Emits a close event with an optional callback. Note that this does not destroy the db or collections,
* it's a utility method that can be called before closing the process or 'onbeforeunload' in a browser.
*
* @param callback - optional
*/
DB.prototype.close = function (callback) {
this._db.close(callback);
};
/**
* This will go through all database collections and remove the data from them.
*/
DB.prototype.flush = function () {
var _this = this;
return new Promise(function (resolve) {
var collections = _this.getCollections();
for (var i = 0; i < collections.length; i++) {
_this.getCollectionByName(collections[i].name).removeDataOnly();
}
resolve(true);
});
};
return DB;
}());
exports.DB = DB;
/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
* @ignore
*/
var SyncResourceType = {
item: 0,
collection: 1,
file: 2
};
exports.SyncResourceType = SyncResourceType;
/***/ }),
/* 11 */
/***/ (function(module, exports) {
/***/ }),
/* 12 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*
Loki IndexedDb Adapter (need to include this script to use it)
Console Usage can be used for management/diagnostic, here are a few examples :
adapter.getDatabaseList(); // with no callback passed, this method will log results to console
adapter.saveDatabase('UserDatabase', JSON.stringify(myDb));
adapter.loadDatabase('UserDatabase'); // will log the serialized db to console
adapter.deleteDatabase('UserDatabase');
*/
(function (root, factory) {
if (true) {
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory),
__WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ?
(__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__),
__WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
} else {}
}(this, function () {
return (function() {
/**
* Loki persistence adapter class for indexedDb.
* This class fulfills abstract adapter interface which can be applied to other storage methods.
* Utilizes the included LokiCatalog app/key/value database for actual database persistence.
* Indexeddb is highly async, but this adapter has been made 'console-friendly' as well.
* Anywhere a callback is omitted, it should return results (if applicable) to console.
* IndexedDb storage is provided per-domain, so we implement app/key/value database to
* allow separate contexts for separate apps within a domain.
*
* @example
* var idbAdapter = new LokiIndexedAdapter('finance');
*
* @constructor LokiIndexedAdapter
*
* @param {string} appname - (Optional) Application name context can be used to distinguish subdomains, 'loki' by default
*/
function LokiIndexedAdapter(appname)
{
this.app = 'loki';
if (typeof (appname) !== 'undefined')
{
this.app = appname;
}
this.catalog = null;
if (!this.checkAvailability()) {
throw new Error('indexedDB does not seem to be supported for your environment');
}
}
/**
* Used to check if adapter is available
*
* @returns {boolean} true if indexeddb is available, false if not.
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.checkAvailability = function()
{
if (typeof indexedDB !== 'undefined' && indexedDB) return true;
return false;
};
/**
* Retrieves a serialized db string from the catalog.
*
* @example
* // LOAD
* var idbAdapter = new LokiIndexedAdapter('finance');
* var db = new loki('test', { adapter: idbAdapter });
* db.loadDatabase(function(result) {
* console.log('done');
* });
*
* @param {string} dbname - the name of the database to retrieve.
* @param {function} callback - callback should accept string param containing serialized db string.
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.loadDatabase = function(dbname, callback)
{
var appName = this.app;
var adapter = this;
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
adapter.loadDatabase(dbname, callback);
});
return;
}
this.catalog.getAppKey(appName, dbname, function(result) {
if (typeof (callback) === 'function') {
if (result.id === 0) {
callback(null);
return;
}
callback(result.val);
}
else {
console.log(result.val);
}
});
};
LokiIndexedAdapter.prototype.loadKey = LokiIndexedAdapter.prototype.loadDatabase;
/**
* Saves a serialized db to the catalog.
*
* @example
* // SAVE : will save App/Key/Val as 'finance'/'test'/{serializedDb}
* var idbAdapter = new LokiIndexedAdapter('finance');
* var db = new loki('test', { adapter: idbAdapter });
* var coll = db.addCollection('testColl');
* coll.insert({test: 'val'});
* db.saveDatabase(); // could pass callback if needed for async complete
*
* @param {string} dbname - the name to give the serialized database within the catalog.
* @param {string} dbstring - the serialized db string to save.
* @param {function} callback - (Optional) callback passed obj.success with true or false
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.saveDatabase = function(dbname, dbstring, callback)
{
var appName = this.app;
var adapter = this;
function saveCallback(result) {
if (result && result.success === true) {
callback(null);
}
else {
callback(new Error("Error saving database"));
}
}
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
cat.setAppKey(appName, dbname, dbstring, saveCallback);
});
return;
}
this.catalog.setAppKey(appName, dbname, dbstring, saveCallback);
};
LokiIndexedAdapter.prototype.saveKey = LokiIndexedAdapter.prototype.saveDatabase;
/**
* Deletes a serialized db from the catalog.
*
* @example
* // DELETE DATABASE
* // delete 'finance'/'test' value from catalog
* idbAdapter.deleteDatabase('test', function {
* // database deleted
* });
*
* @param {string} dbname - the name of the database to delete from the catalog.
* @param {function=} callback - (Optional) executed on database delete
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.deleteDatabase = function(dbname, callback)
{
var appName = this.app;
var adapter = this;
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
adapter.deleteDatabase(dbname, callback);
});
return;
}
this.catalog.getAppKey(appName, dbname, function(result) {
var id = result.id;
if (id !== 0) {
adapter.catalog.deleteAppKey(id, callback);
} else if (typeof (callback) === 'function') {
callback({ success: true });
}
});
};
LokiIndexedAdapter.prototype.deleteKey = LokiIndexedAdapter.prototype.deleteDatabase;
/**
* Removes all database partitions and pages with the base filename passed in.
* This utility method does not (yet) guarantee async deletions will be completed before returning
*
* @param {string} dbname - the base filename which container, partitions, or pages are derived
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.deleteDatabasePartitions = function(dbname) {
var self=this;
this.getDatabaseList(function(result) {
result.forEach(function(str) {
if (str.startsWith(dbname)) {
self.deleteDatabase(str);
}
});
});
};
/**
* Retrieves object array of catalog entries for current app.
*
* @example
* idbAdapter.getDatabaseList(function(result) {
* // result is array of string names for that appcontext ('finance')
* result.forEach(function(str) {
* console.log(str);
* });
* });
*
* @param {function} callback - should accept array of database names in the catalog for current app.
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.getDatabaseList = function(callback)
{
var appName = this.app;
var adapter = this;
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
adapter.getDatabaseList(callback);
});
return;
}
this.catalog.getAppKeys(appName, function(results) {
var names = [];
for(var idx = 0; idx < results.length; idx++) {
names.push(results[idx].key);
}
if (typeof (callback) === 'function') {
callback(names);
}
else {
names.forEach(function(obj) {
console.log(obj);
});
}
});
};
LokiIndexedAdapter.prototype.getKeyList = LokiIndexedAdapter.prototype.getDatabaseList;
/**
* Allows retrieval of list of all keys in catalog along with size
*
* @param {function} callback - (Optional) callback to accept result array.
* @memberof LokiIndexedAdapter
*/
LokiIndexedAdapter.prototype.getCatalogSummary = function(callback)
{
var appName = this.app;
var adapter = this;
if (this.catalog === null || this.catalog.db === null) {
this.catalog = new LokiCatalog(function(cat) {
adapter.catalog = cat;
adapter.getCatalogSummary(callback);
});
return;
}
this.catalog.getAllKeys(function(results) {
var entries = [];
var obj,
size,
oapp,
okey,
oval;
for(var idx = 0; idx < results.length; idx++) {
obj = results[idx];
oapp = obj.app || '';
okey = obj.key || '';
oval = obj.val || '';
size = oapp.length * 2 + okey.length * 2 + oval.length + 1;
entries.push({ "app": obj.app, "key": obj.key, "size": size });
}
if (typeof (callback) === 'function') {
callback(entries);
}
else {
entries.forEach(function(obj) {
console.log(obj);
});
}
});
};
/**
* LokiCatalog - underlying App/Key/Value catalog persistence
* This non-interface class implements the actual persistence.
* Used by the IndexedAdapter class.
*/
function LokiCatalog(callback)
{
this.db = null;
this.initializeLokiCatalog(callback);
}
LokiCatalog.prototype.initializeLokiCatalog = function(callback) {
var openRequest = indexedDB.open('LokiCatalog', 1);
var cat = this;
openRequest.onupgradeneeded = function(e) {
var thisDB = e.target.result;
if (thisDB.objectStoreNames.contains('LokiAKV')) {
thisDB.deleteObjectStore('LokiAKV');
}
if(!thisDB.objectStoreNames.contains('LokiAKV')) {
var objectStore = thisDB.createObjectStore('LokiAKV', { keyPath: 'id', autoIncrement:true });
objectStore.createIndex('app', 'app', {unique:false});
objectStore.createIndex('key', 'key', {unique:false});
objectStore.createIndex('appkey', 'appkey', {unique:true});
}
};
openRequest.onsuccess = function(e) {
cat.db = e.target.result;
if (typeof (callback) === 'function') callback(cat);
};
openRequest.onerror = function(e) {
throw e;
};
};
LokiCatalog.prototype.getAppKey = function(app, key, callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readonly');
var store = transaction.objectStore('LokiAKV');
var index = store.index('appkey');
var appkey = app + "," + key;
var request = index.get(appkey);
request.onsuccess = (function(usercallback) {
return function(e) {
var lres = e.target.result;
if (lres === null || typeof(lres) === 'undefined') {
lres = {
id: 0,
success: false
};
}
if (typeof(usercallback) === 'function') {
usercallback(lres);
}
else {
console.log(lres);
}
};
})(callback);
request.onerror = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback({ id: 0, success: false });
}
else {
throw e;
}
};
})(callback);
};
LokiCatalog.prototype.getAppKeyById = function (id, callback, data) {
var transaction = this.db.transaction(['LokiAKV'], 'readonly');
var store = transaction.objectStore('LokiAKV');
var request = store.get(id);
request.onsuccess = (function(data, usercallback){
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback(e.target.result, data);
}
else {
console.log(e.target.result);
}
};
})(data, callback);
};
LokiCatalog.prototype.setAppKey = function (app, key, val, callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readwrite');
var store = transaction.objectStore('LokiAKV');
var index = store.index('appkey');
var appkey = app + "," + key;
var request = index.get(appkey);
request.onsuccess = function(e) {
var res = e.target.result;
if (res === null || res === undefined) {
res = {
app:app,
key:key,
appkey: app + ',' + key,
val:val
};
}
else {
res.val = val;
}
var requestPut = store.put(res);
requestPut.onerror = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback({ success: false });
}
else {
console.error('LokiCatalog.setAppKey (set) onerror');
console.error(request.error);
}
};
})(callback);
requestPut.onsuccess = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback({ success: true });
}
};
})(callback);
};
request.onerror = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback({ success: false });
}
else {
console.error('LokiCatalog.setAppKey (get) onerror');
console.error(request.error);
}
};
})(callback);
};
LokiCatalog.prototype.deleteAppKey = function (id, callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readwrite');
var store = transaction.objectStore('LokiAKV');
var request = store.delete(id);
request.onsuccess = (function(usercallback) {
return function(evt) {
if (typeof(usercallback) === 'function') usercallback({ success: true });
};
})(callback);
request.onerror = (function(usercallback) {
return function(evt) {
if (typeof(usercallback) === 'function') {
usercallback({ success: false });
}
else {
console.error('LokiCatalog.deleteAppKey raised onerror');
console.error(request.error);
}
};
})(callback);
};
LokiCatalog.prototype.getAppKeys = function(app, callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readonly');
var store = transaction.objectStore('LokiAKV');
var index = store.index('app');
var singleKeyRange = IDBKeyRange.only(app);
var cursor = index.openCursor(singleKeyRange);
var localdata = [];
cursor.onsuccess = (function(data, callback) {
return function(e) {
var cursor = e.target.result;
if (cursor) {
var currObject = cursor.value;
data.push(currObject);
cursor.continue();
}
else {
if (typeof(callback) === 'function') {
callback(data);
}
else {
console.log(data);
}
}
};
})(localdata, callback);
cursor.onerror = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') {
usercallback(null);
}
else {
console.error('LokiCatalog.getAppKeys raised onerror');
console.error(e);
}
};
})(callback);
};
LokiCatalog.prototype.getAllKeys = function (callback) {
var transaction = this.db.transaction(['LokiAKV'], 'readonly');
var store = transaction.objectStore('LokiAKV');
var cursor = store.openCursor();
var localdata = [];
cursor.onsuccess = (function(data, callback) {
return function(e) {
var cursor = e.target.result;
if (cursor) {
var currObject = cursor.value;
data.push(currObject);
cursor.continue();
}
else {
if (typeof(callback) === 'function') {
callback(data);
}
else {
console.log(data);
}
}
};
})(localdata, callback);
cursor.onerror = (function(usercallback) {
return function(e) {
if (typeof(usercallback) === 'function') usercallback(null);
};
})(callback);
};
return LokiIndexedAdapter;
}());
}));
/***/ }),
/* 13 */
/***/ (function(module, exports) {
var process = module.exports = {};
var cachedSetTimeout;
var cachedClearTimeout;
function defaultSetTimout() {
throw new Error('setTimeout has not been defined');
}
function defaultClearTimeout () {
throw new Error('clearTimeout has not been defined');
}
(function () {
try {
if (typeof setTimeout === 'function') {
cachedSetTimeout = setTimeout;
} else {
cachedSetTimeout = defaultSetTimout;
}
} catch (e) {
cachedSetTimeout = defaultSetTimout;
}
try {
if (typeof clearTimeout === 'function') {
cachedClearTimeout = clearTimeout;
} else {
cachedClearTimeout = defaultClearTimeout;
}
} catch (e) {
cachedClearTimeout = defaultClearTimeout;
}
} ())
function runTimeout(fun) {
if (cachedSetTimeout === setTimeout) {
return setTimeout(fun, 0);
}
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
cachedSetTimeout = setTimeout;
return setTimeout(fun, 0);
}
try {
return cachedSetTimeout(fun, 0);
} catch(e){
try {
return cachedSetTimeout.call(null, fun, 0);
} catch(e){
return cachedSetTimeout.call(this, fun, 0);
}
}
}
function runClearTimeout(marker) {
if (cachedClearTimeout === clearTimeout) {
return clearTimeout(marker);
}
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
cachedClearTimeout = clearTimeout;
return clearTimeout(marker);
}
try {
return cachedClearTimeout(marker);
} catch (e){
try {
return cachedClearTimeout.call(null, marker);
} catch (e){
return cachedClearTimeout.call(this, marker);
}
}
}
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
if (!draining || !currentQueue) {
return;
}
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = runTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
if (currentQueue) {
currentQueue[queueIndex].run();
}
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
runClearTimeout(timeout);
}
process.nextTick = function (fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
runTimeout(drainQueue);
}
};
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.prependListener = noop;
process.prependOnceListener = noop;
process.listeners = function (name) { return [] }
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
/***/ }),
/* 14 */
/***/ (function(module, exports) {
var g;
g = (function() {
return this;
})();
try {
g = g || Function("return this")() || (1, eval)("this");
} catch (e) {
if (typeof window === "object") g = window;
}
module.exports = g;
/***/ }),
/* 15 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @authors Lyudmil Pelov
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var request_handler_1 = __webpack_require__(6);
/**
* This module provide the MCS Persistent capabilities
* @ignore
* @type {{}}
*/
var McsRequestHandler = /** @class */ (function (_super) {
__extends(McsRequestHandler, _super);
function McsRequestHandler(options, common, utils, lokiStorageAdapter) {
return _super.call(this, 'mcs-request', options, common, utils, lokiStorageAdapter) || this;
}
McsRequestHandler.prototype.buildResponseObject = function (obj) {
return obj;
};
McsRequestHandler.prototype.getResponsePayload = function (response) {
return response.data;
};
McsRequestHandler.prototype.getModuleName = function () {
return 'MCS';
};
/**
* Check to see if this was also MCS payload!
*
* @param request
* @returns {boolean}
*/
McsRequestHandler.prototype.isPersistentGetRequest = function (request) {
_super.prototype.isPersistentGetRequest.call(this, request);
var resourceTypeHeader = McsRequestHandler.HEADER_RESOURCE_TYPE in request ||
request.headers[McsRequestHandler.HEADER_RESOURCE_TYPE];
if (resourceTypeHeader === 'collection') {
if (this.$common.isEmpty(request.data)) {
throw new Error('cannot proceed with empty payload');
}
if (!('items' in request.data)) {
throw new Error('items is not in the payload returned from MCS, probably not Sync Custom Code');
}
if (!('uris' in request.data)) {
throw new Error('url is not in the payload returned from MCS, probably not Sync Custom Code');
}
if (!('etags' in request.data)) {
throw new Error('etags is not in the payload returned from MCS, probably not Sync Custom Code');
}
}
else if (resourceTypeHeader !== 'item') {
throw new Error('oracle-mobile-sync-resource-type is not in the headers returned from MCS, ' +
'probably not Sync Custom Code');
}
return true;
};
/**
* Transform the payload and add it into the $db!
*
* NOTE: Such a transformations could hold a lot of resources!
*
* @param collection
* @param payload
* @returns {*}
*/
McsRequestHandler.prototype.handleMcsGetCollectionPayload = function (payload, collection) {
var items = payload.items;
var now = Date.now();
for (var i = 0; i < items.length; i++) {
var item = items[i];
item[McsRequestHandler.URI_KEY] = this.normalizeURI(payload.uris[i]);
item[McsRequestHandler.ETAG_KEY] = payload.etags[i];
var itemUri = item[McsRequestHandler.URI_KEY];
var result = this.collectionFindOneByUri(collection, itemUri);
if (result) {
this.$common.extendOwn(result, item);
collection.update(result);
}
else {
collection.insert(item);
}
}
collection.removeWhere(function (dbObj) {
return (dbObj.meta.updated || dbObj.meta.created) < now;
});
return collection.find();
};
/**
* Handle item payload from the MCS
*
* @param collection
* @param payload
* @param uri
* @returns {*}
*/
McsRequestHandler.prototype.handleMcsGetItemPayload = function (collection, payload, uri) {
var result = this.collectionFindOneByUri(collection, uri);
if (result) {
var item = payload;
this.$common.extendOwn(result, item);
return collection.update(result);
}
else {
var item = payload;
item[McsRequestHandler.URI_KEY] = uri;
return collection.insert(item);
}
};
/**
* If nothing in the payload check get what is in the offline $db!
* @param response - response is always array of objects!
*/
McsRequestHandler.prototype.handleGet = function (response) {
var persistentPath = this.$options.isPersistentUrl(response.url);
if (persistentPath === false) {
throw new Error('Persistence.handleMcsGet()-> URI was not configured for persistence:' + response.url);
}
var collection = this.$db.getCollectionByName(persistentPath.root);
if (this.$common.isEmpty(persistentPath.params)) {
var result = collection.find();
var data = {
items: [],
uris: [],
etags: [],
};
for (var idx in result) {
if (result[idx].hasOwnProperty(McsRequestHandler.URI_KEY)) {
data.uris.push(result[idx][McsRequestHandler.URI_KEY]);
delete result[idx][McsRequestHandler.URI_KEY];
}
if (result[idx].hasOwnProperty(McsRequestHandler.ETAG_KEY)) {
data.etags.push(result[idx][McsRequestHandler.ETAG_KEY]);
delete result[idx][McsRequestHandler.ETAG_KEY];
}
var cleanObject = this.$common.cleanObject(result[idx]);
data.items.push(cleanObject);
}
return data;
}
var item = this.collectionFindOneByPersistentPath(collection, persistentPath);
return this.$common.cleanObject(item);
};
McsRequestHandler.prototype.handleGetStore = function (response) {
var persistentPath = this.$options.isPersistentUrl(response.url);
if (persistentPath === false) {
throw new Error('Persistence.handleMcsGetStore()-> URI was not configured for persistence:' + response.url);
}
var collection = this.$db.getCollectionByName(persistentPath.root);
var payload = response.data;
var resourceTypeHeader = McsRequestHandler.HEADER_RESOURCE_TYPE in response || response.headers[McsRequestHandler.HEADER_RESOURCE_TYPE];
if (resourceTypeHeader === 'collection') {
return this.handleMcsGetCollectionPayload(payload, collection);
}
else if (resourceTypeHeader === 'item') {
return this.handleMcsGetItemPayload(collection, payload, persistentPath.path);
}
else if (resourceTypeHeader) {
this.logger.error('unknown Oracle-Mobile-Sync-Resource-Type (%s)', resourceTypeHeader);
throw new Error('unknown Oracle-Mobile-Sync-Resource-Type');
}
else {
this.logger.error('this is not MCS response, unable to handle the payload, no MCS header was specified: ', response);
throw new Error('this is not MCS response, unable to handle the payload, no MCS header was specified');
}
};
/**
* Currently posts supports only adding new objects into the root!
*
* @param response
* @param force
*/
McsRequestHandler.prototype.handlePost = function (response, force) {
var persistentPath = this.$options.isPersistentUrl(response.url);
if (persistentPath === false) {
throw new Error('Persistence.handleMcsPost()-> URI was not configured for persistence:' + response.url);
}
var collection = this.$db.getCollectionByName(persistentPath.root);
var payload = response.data;
if (!this.$common.isEmpty(persistentPath.params)) {
throw new Error('you can add new objects only against the root REST resource endpoint');
}
if (this.$common.isArray(payload)) {
throw new Error('the payload cannot be array');
}
else if (this.$common.isObject(payload)
&& !this.$common.isNull(payload)
&& !this.$common.isFunction(payload)) {
if (response.data && response.data[McsRequestHandler.URI_KEY]) {
return this.updatePayloadWithNewUri(collection, response);
}
else {
var uri = null;
if (response && response.headers && response.headers[McsRequestHandler.HEADER_LOCATION]) {
uri = this.$common.stringStartsWith(response.headers[McsRequestHandler.HEADER_LOCATION], '/') ?
response.headers[McsRequestHandler.HEADER_LOCATION] :
'/' + response.headers[McsRequestHandler.HEADER_LOCATION];
}
var result = null;
if (uri) {
result = this.collectionFindOneByUri(collection, uri);
}
if (!result) {
payload[McsRequestHandler.ETAG_KEY] = '"0-1B2M2Y8AsgTpgAmY7PhCfg"'; // empty e-tag
result = collection.insert(payload);
this.markObjAsOfflineIfForced(result, force);
}
else {
this.$common.extendOwn(result, response.data);
}
if (!uri) {
var key = (persistentPath.tokens.length > 0 && /^\w+$/.test(persistentPath.tokens[0].name)) ? persistentPath.tokens[0].name : null;
var value = null;
if (key) {
value = (response.data.hasOwnProperty(key)) ? response.data[key] : this.$common.getUID();
}
uri = (value === null) ? persistentPath.root + '/' + this.$common.getUID() : persistentPath.root + '/' + value;
}
result[McsRequestHandler.URI_KEY] = uri;
return collection.update(result);
}
}
throw new Error('don\'t know what to do with the payload');
};
McsRequestHandler.prototype.updatePayloadWithNewUri = function (collection, response) {
var uri = response.data[McsRequestHandler.URI_KEY];
var result = this.collectionFindOneByUri(collection, uri);
if (result) {
this.$common.extendOwn(result, response.data);
result[McsRequestHandler.URI_KEY] = this.normalizeURI(response.headers[McsRequestHandler.HEADER_LOCATION]);
return collection.update(result);
}
else {
throw new Error("the payload was not found in collection:" + uri);
}
};
/**
* Currently posts supports only adding new objects into the root!
*
* @param response
* @param force
*/
McsRequestHandler.prototype.handlePut = function (response, force) {
var persistentPath = this.$options.isPersistentUrl(response.url);
if (persistentPath === false) {
throw new Error('Persistence.handleMcsPut()-> URI was not configured for persistence:' + response.url);
}
var collection = this.$db.getCollectionByName(persistentPath.root);
var payload = response.data;
if (!this.$common.isEmpty(persistentPath.params)) {
if (this.$common.isArray(payload)) {
throw new Error('the payload cannot be array');
}
else if (this.$common.isObject(payload) &&
!this.$common.isNull(payload) &&
!this.$common.isFunction(payload)) {
var result = this.collectionFindOneByPersistentPath(collection, persistentPath);
if (result) {
this.$common.extendOwn(result, payload);
this.markObjAsOfflineIfForced(result, force);
return collection.update(result);
}
}
}
throw new Error('you can execute update operations only against existing items in the offline database!');
};
/**
* Find item in collection that ends with persistentPath.path
* @param collection
* @param persistentPath
*/
McsRequestHandler.prototype.collectionFindOneByPersistentPath = function (collection, persistentPath) {
var regexStr = persistentPath.path + "$";
var query = {};
query[McsRequestHandler.URI_KEY] = {
$regex: new RegExp(regexStr)
};
return collection.findOne(query);
};
/**
* Find item in collection by itm uri
* @param collection
* @param uri
*/
McsRequestHandler.prototype.collectionFindOneByUri = function (collection, uri) {
var query = {};
query[McsRequestHandler.URI_KEY] = uri;
return collection.findOne(query);
};
/**
*
* @param response
* @returns {*}
*/
McsRequestHandler.prototype.handleDelete = function (response) {
var persistentPath = this.$options.isPersistentUrl(response.url);
if (persistentPath === false) {
throw new Error('Persistence.handleMcsDelete()-> URI was not configured for persistence:' + response.url);
}
var collection = this.$db.getCollectionByName(persistentPath.root);
if (!this.$common.isEmpty(persistentPath.params)) {
var item = this.collectionFindOneByPersistentPath(collection, persistentPath);
if (item) {
return collection.remove(item);
}
throw new Error("unable to find object with the given ID(" + persistentPath.path + ") in the database");
}
};
McsRequestHandler.prototype.postSuccessOperations = function (obj) {
if (obj.syncObj.method === 'POST') {
var cpObj = this.$common.clone(obj);
var parsed = this.$options.parseURL(cpObj.syncObj.url);
var isPersistentUrl = this.$utils.isPersistUrl(parsed.path);
if (isPersistentUrl === false) {
this.logger.debug('sync post success operation exist');
return obj;
}
var queryParams = this.$utils.extractKeyValuesFromUrl2(isPersistentUrl);
var collection = this.$db.getCollectionByName(queryParams.root);
var beforeSyncPayload = cpObj.syncObj.data;
var response = cpObj.response;
if (this.$common.isArray(beforeSyncPayload)) {
this.logger.error('The payload cannot be array');
return obj;
}
else if (this.$common.isObject(beforeSyncPayload)
&& !this.$common.isNull(beforeSyncPayload)
&& !this.$common.isFunction(beforeSyncPayload)) {
if (response.data && response.data[McsRequestHandler.URI_KEY]) {
return this.updatePayloadWithNewUri(collection, response);
}
else {
var uri = null;
if (cpObj.headers) {
var headers = this.$utils.parseResponseHeaders(cpObj.headers);
if (headers[McsRequestHandler.HEADER_LOCATION]) {
var uriHeader = headers[McsRequestHandler.HEADER_LOCATION];
uri = this.normalizeURI(uriHeader);
}
}
var beforeSyncItemUri = beforeSyncPayload[McsRequestHandler.URI_KEY];
var result = this.collectionFindOneByUri(collection, beforeSyncItemUri);
if (!result) {
beforeSyncPayload[McsRequestHandler.ETAG_KEY] = '"0-1B2M2Y8AsgTpgAmY7PhCfg"'; // empty e-tag
result = collection.insert(beforeSyncPayload);
}
else {
this.$common.extendOwn(result, response.data);
}
if (!uri) {
var key = (isPersistentUrl.uri.tokens.length > 0 && /^\w+$/.test(isPersistentUrl.uri.tokens[0].name)) ? isPersistentUrl.uri.tokens[0].name : null;
var value = null;
if (key) {
value = (response.data.hasOwnProperty(key)) ? response.data[key] : this.$common.getUID();
}
uri = (value === null) ? isPersistentUrl.root + '/' + this.$common.getUID() : isPersistentUrl.root + '/' + value;
}
result[McsRequestHandler.URI_KEY] = uri;
return collection.update(result);
}
}
}
return obj;
};
McsRequestHandler.prototype.normalizeURI = function (uri) {
return this.$common.stringStartsWith(uri, '/') || this.$common.stringStartsWith(uri, 'http') ? uri : '/' + uri;
};
McsRequestHandler.prototype.data = function (path) {
function doData(path) {
var persistentPath = this.$options.isPersistentUrl(path);
if (!persistentPath) {
throw new Error('Persistence.BaseModule.data() given URI not configured for persistence: ' + path);
}
return this.$db.getCollectionByName(persistentPath.root);
}
return new Promise(function (resolve) { return resolve(doData(path)); });
};
McsRequestHandler.prototype.router = function (request, force) {
var _this = this;
if (force === void 0) { force = false; }
return new Promise(function (resolve) {
if (!_this.$common.isObject(request)) {
console.error('Passed object is not defined request!', request);
throw new Error('Passed object is not defined request!');
}
if (!request.hasOwnProperty('method')) {
_this.logger.error('request.method was not provided!', request);
throw new Error('request.method was not provided!');
}
var _request = _this.$common.clone(request);
_request.method = _this.$utils._normalizeMethod(_request.method);
if (!_this[_request.method]) {
_this.logger.error('specified router is not implemented!');
throw new Error('specified router is not implemented!');
}
var result = _this[_request.method](_request, force);
resolve(result);
});
};
McsRequestHandler.prototype.get = function (request) {
var _this = this;
var doGet = function (request) {
_this.logger.info('get()');
_this.isPersistentRequest(request);
var _request = _this.$common.clone(request);
if (!_request.hasOwnProperty('data') || _this.$common.isEmpty(_request.data)) {
return _this.$common.clone(_this.handleGet(_request));
}
_this.isPersistentGetRequest(request);
return _this.$common.clone(_this.handleGetStore(_request));
};
return new Promise(function (resolve) {
resolve(doGet(request));
});
};
McsRequestHandler.URI_KEY = '$mcs$mcsPersistenceURI';
McsRequestHandler.ETAG_KEY = '$mcs$etag';
McsRequestHandler.HEADER_LOCATION = 'location';
McsRequestHandler.HEADER_RESOURCE_TYPE = 'oracle-mobile-sync-resource-type';
return McsRequestHandler;
}(request_handler_1.RequestHandler));
exports.McsRequestHandler = McsRequestHandler;
/***/ }),
/* 16 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Base class for MobileObject, MobileCollection and MobileFile.
* @abstract
* @private
*/
var MobileResource = /** @class */ (function () {
function MobileResource(endpoint, uri) {
this.endpoint = endpoint;
this.uri = uri;
}
MobileResource.prototype._getEndpoint = function () {
return this.endpoint;
};
MobileResource.prototype._getMcsId = function () {
return this.uri ? this.uri.substring(this.uri.lastIndexOf('/') + 1, this.uri.length) : null;
};
MobileResource.prototype._getMcsURI = function () {
return this.uri;
};
return MobileResource;
}());
exports.MobileResource = MobileResource;
/***/ }),
/* 17 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var types_1 = __webpack_require__(0);
var StorageObject = /** @class */ (function () {
/**
* @classdesc Class that represents a storage object resource that can be used to store data.
* This class constructor accessible by [mcs.StorageObject]{@link mcs.StorageObject} method.
* @param storageCollection {StorageCollection}
* @param json {Object}
* @class
* @global
* @mcs
*/
function StorageObject(storageCollection, json) {
this.storageCollection = storageCollection;
if (!!json) {
this.id = json.id;
this.name = json.name;
this.contentLength = json.contentLength;
this.contentType = json.contentType;
this._eTag = json.eTag;
this.createdBy = json.createdBy;
this.createdOn = json.createdOn;
this.modifiedBy = json.modifiedBy;
this.modifiedOn = json.modifiedOn;
}
}
/**
* Get payload.
* Returns the current StorageObject payload.
* When contentType is json, this method returns JSON object, otherwise string.
*
* @function
* @name StorageObject#getPayload
* @return Current Storage object payload.
* @mcs
*/
StorageObject.prototype.getPayload = function () {
if (this.contentType === types_1.ContentTypes.APPLICATION_JSON) {
return JSON.parse(this._payload);
}
else {
return this._payload;
}
};
/**
* Set payload.
* Sets the payload for the StorageObject.
*
* @function
* @name StorageObject#setPayload
* @param payload The payload to be associated with StorageObject.
* @mcs
*/
StorageObject.prototype.setPayload = function (payload) {
if (this.contentType === types_1.ContentTypes.APPLICATION_JSON && typeof payload === 'object') {
this._payload = JSON.stringify(payload);
}
else {
this._payload = payload;
}
this.contentLength = this._payload.length;
};
/**
* Get storage collection.
* Returns the current StorageCollection.
*
* @function
* @name StorageObject#getstorageCollection
* @return Current StorageCollection.
* @mcs
*/
StorageObject.prototype.getstorageCollection = function () {
return this.storageCollection;
};
/**
* Get storage.
* Returns the current StorageObject.
*
* @function
* @name StorageObject#getStorage
* @return Current StorageObject.
* @mcs
*/
StorageObject.prototype.getStorage = function () {
return this.storageCollection.getStorage();
};
/**
* Load payload.
* Loads a StorageObject's contents from an object.
* @function
* @name StorageObject#loadPayload
* @param payload {Object} The object to load from.
* @param contentType {String} The media-type to associate with the content.
* @mcs
*/
StorageObject.prototype.loadPayload = function (payload, contentType) {
this.contentType = contentType;
this.setPayload(payload);
};
/**
* Set display name.
* Sets a StorageObject's display name from an object.
* @function
* @name StorageObject#setDisplayName
* @param name {Object} The object's name to be associated with the object.
* @returns The object's name in UTC-8 ASCII format.\
* @mcs
*/
StorageObject.prototype.setDisplayName = function (name) {
this.name = name;
};
/**
* Get Display name.
* Returns a StorageObject's display name from an object.
*
* @function
* @name StorageObject#getDisplayName
* @returns {String} object's name decoded if encoded into the MobileBackend.
* @mcs
*/
StorageObject.prototype.getDisplayName = function () {
return this.name;
};
/**
* Read payload.
* Returns the contents of the StorageObject. May result in a download from the service if the contents were not
* previously downloaded.
* @function
* @name StorageObject#readPayload
* @param {String} objectType responseType for the XMLHttpRequest Object.
* @return {Promise<StorageObject|NetworkResponse>}
* @mcs
*/
StorageObject.prototype.readPayload = function (objectType) {
var _this = this;
var payload = this.getPayload();
if (!payload) {
return this.storageCollection
.loadObjectPayload(this.id, objectType)
.then(function (response) {
_this.contentType = response.headers[types_1.Headers.CONTENT_TYPE.toLowerCase()];
_this.name = decodeURI(response.headers[types_1.Headers.ORACLE_MOBILE_NAME.toLowerCase()]);
_this._eTag = response.headers[types_1.Headers.E_TAG.toLowerCase()];
_this.createdBy = response.headers[types_1.Headers.ORACLE_MOBILE_CREATED_BY.toLowerCase()];
_this.createdOn = response.headers[types_1.Headers.ORACLE_MOBILE_CREATED_ON.toLowerCase()];
_this.modifiedBy = response.headers[types_1.Headers.ORACLE_MOBILE_MODIFIED_BY.toLowerCase()];
_this.modifiedOn = response.headers[types_1.Headers.ORACLE_MOBILE_MODIFIED_ON.toLowerCase()];
_this.setPayload(response.data);
return _this;
});
}
else {
return Promise.resolve(this);
}
};
return StorageObject;
}());
exports.StorageObject = StorageObject;
/***/ }),
/* 18 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var AnalyticsEvent = /** @class */ (function () {
/**
* @classdesc Class that holds an analytics event.
* This class constructor accessible by [mcs.AnalyticsEvent]{@link mcs.AnalyticsEvent} method.
* @param {string} name - event name
* @constructor
* @class
* @global
* @mcs
*/
function AnalyticsEvent(name) {
/**
* The name of the event.
* @type {String}
* @name AnalyticsEvent#name
* @mcs
*/
this.name = null;
/**
* The timestamp of the event. The system will populate with the current time by default.
* @type {String}
* @name AnalyticsEvent#timestamp
* @mcs
*/
this.timestamp = new Date().toISOString();
/**
* The ID of the current session.
* @type {String}
* @name AnalyticsEvent#sessionID
* @mcs
*/
this.sessionID = null;
/**
* Custom caller specifiable properties as key/value strings.
* @type {Object}
* @name AnalyticsEvent#properties
* @mcs
*/
this.properties = {};
this.name = name;
}
return AnalyticsEvent;
}());
exports.AnalyticsEvent = AnalyticsEvent;
/***/ }),
/* 19 */,
/* 20 */,
/* 21 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var types_1 = __webpack_require__(0);
var network_response_1 = __webpack_require__(2);
/**
* Base class for platform-specific capabilities. Users may derive from this class to
* provide implementations specific to their platform.
* @abstract
* @private
*/
var Platform = /** @class */ (function () {
function Platform(config, utils, logger) {
this._logHTTP = false;
this._queryRegex = (/\?/);
this._deviceState = typeof deviceState !== 'undefined' ? deviceState.unrestricted : null;
this._deviceStateChangedCallbacks = [];
this.ANDROID_OS_NAME = '';
this.IOS_OS_NAME = '';
this._logHTTP = typeof config.logHTTP !== 'undefined' ? config.logHTTP : false;
this._config = config;
this._utils = utils;
this._deviceId = utils.uuid();
this._logger = logger;
}
Platform.prototype.initGPSLocation = function () { };
/**
* Returns a device ID used by [Diagnostics]{@link Diagnostics}.
* @returns {String} The device ID.
*/
Platform.prototype.getDeviceId = function () {
return this._deviceId;
};
/**
* Sets the current state of the device. Platform implementations should call this function
* when the state changes. The state is inspected before background operations
* like synchronization are performed.
* @param state {mcs._deviceState} The new state of the device.
*/
Platform.prototype.setDeviceState = function (state) {
if (this._deviceState !== state) {
this._logger.info('Device state changing from ' + this._deviceState + ' to ' + state);
this._deviceState = state;
for (var _i = 0, _a = this._deviceStateChangedCallbacks; _i < _a.length; _i++) {
var callback = _a[_i];
callback(this._deviceState);
}
}
};
/**
* Class that provides the current GPS location of the device.
* @typedef {Object} Platform.GPSLocation
* @property {String} latitude - The device's current latitude.
* @property {String} longitude - The device's current longitude.
*/
/**
* Returns an object that has the current GPS location of the device or null.
* @returns {Platform~GPSLocation} The GPS location is available.
*/
Platform.prototype.getGPSLocation = function () {
return {
latitude: null,
longitude: null,
};
};
/**
* Class that provides information about the device.
* @typedef {Object} Platform.DeviceInformation
* @property {String} model - The device's model.
* @property {String} manufacturer - The device's manufacturer.
* @property {String} osName - The operating system.
* @property {String} osVersion - The operating system's version.
* @property {String} osBuild - The operating system's build number.
* @property {String} carrier - The device's wireless carrier.
*/
/**
* Returns an object with device information used by [Analytics]{@link Analytics}
* @returns {Platform~DeviceInformation} The device specific information.
*/
Platform.prototype.getDeviceInformation = function () {
return {
model: '<unknown>',
manufacturer: '<unknown>',
osName: '<unknown>',
osVersion: '<unknown>',
osBuild: '<unknown>',
carrier: '<unknown>',
};
};
/**
* Performs an HTTP request.
* @param request {Object} The format of the request parameter is identical to the settings parameter in
* [JQuery's ajax]{@link http://api.jquery.com/jQuery.ajax/} method.
* However, only the method, url, headers ,data, success
* and error properties are used.
* @abstract
*/
Platform.prototype.invokeService = function (request) {
var url = request.url;
if (this.isBrowser) {
url = url + (this._queryRegex.test(url) ? '&' : '?') + '_=' + new Date().getTime();
}
return new Promise(invoke.bind(this));
function invoke(resolve, reject) {
var _this = this;
var xhr = new XMLHttpRequest();
xhr.open(request.method, url);
for (var key in request.headers) {
if (request.headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, request.headers[key]);
}
}
this._setRequestHeaders(xhr, request);
xhr.withCredentials = request.hasOwnProperty('withCredentials') && typeof request.withCredentials === 'boolean'
? request.withCredentials
: true;
xhr.responseType = request.responseType || types_1.XMLHttpRequestResponseTypes.JSON;
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var response = xhr.responseType === '' || xhr.responseType === 'text' ? xhr.responseText : xhr.response;
var headers = xhr['responseHeaders'] ?
_this._utils.normalizeHeaderKeys(xhr['responseHeaders']) :
_this._utils.parseHeaders(xhr.getAllResponseHeaders());
var netResponse = new network_response_1.NetworkResponse(xhr.status, response, headers);
if (_this._logHTTP) {
var object = {
headers: headers,
body: response,
};
_this._logger.debug('Received ' + request.method + ' response from ' + request.url, object);
}
if (xhr.status >= 200 && xhr.status <= 299) {
resolve(netResponse);
}
else {
reject(netResponse);
}
}
};
xhr.send(request.data);
if (this._logHTTP) {
var object = {
headers: request.headers,
body: request.data,
};
this._logger.debug('Sent ' + request.method + ' request to ' + request.url, object);
}
}
};
Platform.prototype._setRequestHeaders = function (xhr, request) {
xhr.setRequestHeader(types_1.Headers.ORACLE_MOBILE_CLIENT_SDK_INFO, this.getClientSDKInfoHeader(request.module));
};
Platform.prototype.getClientSDKInfoHeader = function (module) {
var infoHeader = this._getPlatform();
infoHeader += ' ' + (this._config.mcsVersion || 'Unknown');
infoHeader += ' [' + module + ']';
return infoHeader;
};
return Platform;
}());
exports.Platform = Platform;
/***/ }),
/* 22 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
* Created by Yuri Panshin on 2016-08-26.
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @classdesc Class that provides network storage object details.
* @class
* @global
* @hideconstructor
* @mcs
*/
var NetworkStorageObject = /** @class */ (function () {
function NetworkStorageObject(statusCode, storageObject) {
/**
* The network status code.
* @type {Number}
* @readonly
* @name NetworkStorageObject#statusCode
* @mcs
*/
this.statusCode = 0;
/**
* The error data.
* @type {StorageObject}
* @readonly
* @name NetworkStorageObject#storageObject
* @mcs
*/
this.storageObject = null;
this.statusCode = statusCode;
this.storageObject = storageObject;
}
return NetworkStorageObject;
}());
exports.NetworkStorageObject = NetworkStorageObject;
/***/ }),
/* 23 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var storage_object_1 = __webpack_require__(17);
var network_storage_object_1 = __webpack_require__(22);
var types_1 = __webpack_require__(0);
/**
* @classdesc Class that holds the StorageCollection resource. StorageCollections contain Storage objects
* which can be used to persist data in Oracle Mobile Hub.
* @class
* @global
* @hideconstructor
* @mcs
*/
var StorageCollection = /** @class */ (function () {
function StorageCollection(name, userId, userIsolated, backend, utils, logger, platform) {
this.userIsolated = userIsolated;
this.backend = backend;
this.utils = utils;
this.logger = logger;
this.platform = platform;
this.storage = backend.storage;
this.userId = utils.validateConfiguration(userId);
this.id = utils.validateConfiguration(name);
}
/**
* Get description.
* The description of the StorageCollection.
* @return {String}
* @function
* @name StorageCollection#getDescription
* @mcs
*/
StorageCollection.prototype.getDescription = function () {
if (this.data) {
return this.data.description;
}
else {
this.logger.warn('Collection metadata was not loaded yet, ' +
'please use StorageCollection.loadMetadata to load metadata.');
return null;
}
};
StorageCollection.prototype.getUserIsolated = function () {
return this.data ? this.data.userIsolated : this.userIsolated;
};
/**
* Get storage.
* Returns storage object for current storage collection.
* @return {Storage} storage object data for current storage collection.
* @function
* @name StorageCollection#getStorage
* @mcs
*/
StorageCollection.prototype.getStorage = function () {
return this.storage;
};
/**
* Get user id.
* Returns user ID for current storage collection.
* @return {string} user ID for current storage collection.
* @function
* @name StorageCollection#getUserId
*/
StorageCollection.prototype.getUserId = function () {
return this.userId;
};
/**
* Get data.
* Returns data for current storage collection.
* @return {object} storage object data for current storage collection.
* @function
* @name StorageCollection#getData
* @mcs
*/
StorageCollection.prototype.getData = function () {
return this.data;
};
/**
* Convert collection to static object that used by JSON.stringify
* @ignore
*/
StorageCollection.prototype.toJSON = function () {
return {
id: this.id,
description: this.getDescription(),
userId: this.getUserId(),
userIsolated: this.getUserIsolated(),
data: this.getData(),
};
};
/**
* Load metadata.
* Load collection metadata
* @returns {Promise<StorageCollection|NetworkResponse>}
* @function
* @name StorageCollection#loadMetadata
* @mcs
*/
StorageCollection.prototype.loadMetadata = function () {
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.ACCEPT, types_1.ContentTypes.APPLICATION_JSON);
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.GET,
url: this.backend.getPlatformUrl('storage/collections/' + this.id),
module: types_1.ModuleNames.STORAGE,
}).then(invokeServiceSuccess.bind(this));
function invokeServiceSuccess(response) {
this.data = response.data;
return this;
}
};
/**
* Get objects.
* Returns a list of StorageObjects from the collection starting from the offset and up to the limit.
* The service may return fewer objects.<br/>
* 1. If the collection is a shared collection, then it returns all the objects.<br/>
* 2. If the collection is a user-isolated collection and allObjects is false,
* then it returns the objects which belong to the current user.<br/>
* 3. If the collection is user-isolated collection, and allObjects is true,
* then it returns all the objects in the collection.<br/>
* The objects might belong to other users. And the current user MUST have READ_ALL or READ_WRITE_ALL permission.
* @param offset {Number} The offset at which to start. Must be greater than 0.
* @param limit {Number} The max number of StorageObjects to return. Must be non-negative.
* @param allObjects {Boolean} whether to return all the objects in the list.
* @return {Promise<Array<StorageObject>|NetworkResponse>}
* @function
* @name StorageCollection#getObjects
* @mcs
*/
StorageCollection.prototype.getObjects = function (offset, limit, allObjects) {
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.ACCEPT, types_1.ContentTypes.APPLICATION_JSON);
var url = 'storage/collections/' + this.id + '/objects';
if (offset != null) {
url += url.indexOf('?') == -1 ? '?' : '&';
url += 'offset=' + offset;
}
if (limit != null) {
url += url.indexOf('?') == -1 ? '?' : '&';
url += 'limit=' + limit;
}
if (this.getUserIsolated() && allObjects) {
url += url.indexOf('?') == -1 ? '?' : '&';
url += 'user=*';
}
else if (this.getUserIsolated() && this.getUserId()) {
url += url.indexOf('?') == -1 ? '?' : '&';
url += 'user=' + this.getUserId();
}
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.GET,
url: this.backend.getPlatformUrl(url),
module: types_1.ModuleNames.STORAGE,
}).then(invokeServiceSuccess.bind(this));
function invokeServiceSuccess(response) {
var objects = [];
var objectsJson = response.data;
for (var i = 0; i < objectsJson.items.length; i++) {
objects[objects.length] = new storage_object_1.StorageObject(this, objectsJson.items[i]);
}
return objects;
}
};
/**
* Returns a StorageObject given its ID. The contents of the object will be downloaded lazily.
* @function
* @name StorageCollection#getObject
* @param id {string} The ID of the Storage Object to return.
* @param objectType {string} responseType for the XMLHttpRequest Object.
* Default response type if not defined is json.
* Ths parameter can be one of the types: 'json', 'blob', 'arraybuffer', 'document', 'text'.
* @return {Promise<StorageObject|NetworkResponse>}
*
* @example StorageCollection.getObject('00e39862-9652-458b-9a82-d1a66cf1a0c7', mcs.RESPONSE_TYPES.BLOB).then(
* function(storageObject){
* },
* function(networkResponse){
* });
* @mcs
*/
StorageCollection.prototype.getObject = function (id, objectType) {
var storageObject = new storage_object_1.StorageObject(this, this.backend);
storageObject.id = id;
return storageObject
.readPayload(objectType)
.then(readPayloadSuccess);
function readPayloadSuccess() {
return storageObject;
}
};
StorageCollection.prototype.loadObjectPayload = function (objectId, objectType) {
var headers = this.backend.getHttpHeaders();
var url = 'storage/collections/' + this.id + '/objects/' + objectId;
if (this.userId != null && this.getUserIsolated()) {
url += '?user=' + this.userId;
}
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.GET,
url: this.backend.getPlatformUrl(url),
responseType: objectType || types_1.XMLHttpRequestResponseTypes.JSON,
module: types_1.ModuleNames.STORAGE,
});
};
/**
* Post object.
* Creates a new StorageObject in the collection.
* @function
* @name StorageCollection#postObject
* @param storageObject {StorageObject} The StorageObject to create.
* @example storageObject:
* {
* "id": " 213ddbac-ccb2-4a53-ad48-b4588244tc4c", // A service generated ID for the StorageObject.
* The ID is unique in the StorageCollection.
* "name" : "JSText.txt", // A user provided name for the StorageObject.
* A StorageCollection may have multiple StorageObjects with the same name.
* "contentLength": 798", // The length of data content in bytes stored in the StorageObject.
* "contentType" : "text/plain ", // The media-type associated with the StorageObject.
* "createdBy" : "DwainDRob", // The name of the user who created the StorageObject
* "createdOn": "Sat, 17 Oct 2015 10:33:12", // Server generated timestamp when the StorageObject was created.
* "modifiedBy": "DwainDRob", // The name of the user who last updated the StorageObject.
* "modifiedOn": "Sat, 17 Oct 2015 10:33:12" // Server generated timestamp when the StorageObject was last updated.
* }
* @return {Promise<NetworkResponse>}
* @mcs
*/
StorageCollection.prototype.postObject = function (storageObject) {
return this._postOrPutStorageObject(storageObject, true);
};
/**
* Put object.
* Updates an existing StorageObject in the collection.
* @function
* @name StorageCollection#putObject
* @param storageObject {StorageObject} The StorageObject to update.
* @return {Promise<NetworkStorageObject|NetworkResponse>}
* @mcs
*/
StorageCollection.prototype.putObject = function (storageObject) {
return this._postOrPutStorageObject(storageObject, false);
};
StorageCollection.prototype._postOrPutStorageObject = function (storageObject, isPost) {
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.ORACLE_MOBILE_NAME, encodeURI(storageObject.getDisplayName()));
headers.add(types_1.Headers.CONTENT_TYPE, storageObject.contentType);
var url = 'storage/collections/' + this.id + '/objects';
if (!isPost) {
url += '/' + storageObject.id;
if (storageObject._eTag != null) {
headers.add(types_1.Headers.IF_MATCH, storageObject._eTag);
}
}
if (this.getUserIsolated() && this.getUserId()) {
url += '?user=' + this.getUserId();
}
return this.platform.invokeService({
headers: headers,
method: isPost ? types_1.HttpMethods.POST : types_1.HttpMethods.PUT,
url: this.backend.getPlatformUrl(url),
data: storageObject._payload,
module: types_1.ModuleNames.STORAGE,
}).then(invokeServiceSuccess.bind(this));
function invokeServiceSuccess(response) {
var object = new storage_object_1.StorageObject(this, response.data);
return new network_storage_object_1.NetworkStorageObject(response.statusCode, object);
}
};
/**
* Is object exists in the collection.
* Checks the service if a StorageObject with the given ID exists in the collection.
* @function
* @name StorageCollection#contains
* @param id {String} The ID of the StorageObject to check.
* @return {Promise<NetworkResponse>}
* @mcs
*/
StorageCollection.prototype.contains = function (id) {
var headers = this.backend.getHttpHeaders();
var url = 'storage/collections/' + this.id + '/objects/' + id;
if (this.getUserIsolated() && this.getUserId()) {
url += '?user=' + this.getUserId();
}
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.HEAD,
url: this.backend.getPlatformUrl(url),
module: types_1.ModuleNames.STORAGE,
});
};
/**
* Delete object.
* Deletes a StorageObject from a collection.
* @function
* @name StorageCollection#deleteObject
* @param id {String} The ID of the StorageObject to delete.
* @return {Promise<NetworkResponse>}
* @mcs
*/
StorageCollection.prototype.deleteObject = function (id) {
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.IF_MATCH, '*');
var url = 'storage/collections/' + this.id + '/objects/' + id;
if (this.getUserIsolated() && this.getUserId()) {
url += '?user=' + this.getUserId();
}
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.DELETE,
url: this.backend.getPlatformUrl(url),
module: types_1.ModuleNames.STORAGE,
});
};
return StorageCollection;
}());
exports.StorageCollection = StorageCollection;
/***/ }),
/* 24 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var logger_1 = __webpack_require__(1);
var storage_collection_1 = __webpack_require__(23);
/**
* @classdesc Class that provides cloud-based storage capabilities. Callers should use
* MobileBackend's [storage]{@link Backend#storage} property.
* @class
* @global
* @hideconstructor
* @mcs
*/
var Storage = /** @class */ (function () {
function Storage(backend, utils, platform) {
this.backend = backend;
this.utils = utils;
this.platform = platform;
this.logger = new logger_1.Logger('Storage');
}
/**
* Returns a StorageCollection with the given name from the service associated with the user.
* Subsequent accesses to StorageObjects in the
* StorageCollection will only return StorageObjects owned by the user.
* @function
* @name Storage#getCollection
* @param name {String} The name of the StorageCollection.
* @param [userId] {String} Optional, the ID of the user retrieved from the UI.
* @param [userIsolated] {Boolean} - indicate if collection is in isolated mode,
* used in combination with lazyLoad and userId.
* This parameter is not required in case lazyLoad is not provided.
* @param [lazyLoad] {Boolean} - indicate not to load collection metadata
* @return {Promise<StorageCollection|NetworkResponse>}
* @mcs
*/
Storage.prototype.getCollection = function (name, userId, userIsolated, lazyLoad) {
var collection = new storage_collection_1.StorageCollection(name, userId, userIsolated, this.backend, this.utils, this.logger, this.platform);
if (lazyLoad) {
return Promise.resolve(collection);
}
else {
return collection.loadMetadata();
}
};
return Storage;
}());
exports.Storage = Storage;
/***/ }),
/* 25 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
Object.defineProperty(exports, "__esModule", { value: true });
var dictionary_1 = __webpack_require__(3);
/**
* @classdesc Class that exposes fluent APIs for fetching objects from an API endpoint.
* @class
* @global
* @hideconstructor
* @mcs
*/
var FetchCollectionBuilder = /** @class */ (function () {
function FetchCollectionBuilder(endpoint, json) {
this.endpoint = endpoint;
this.json = json;
this._fetchFromService = false;
this._offset = -1;
this._limit = -1;
this._fetchAll = false;
this._withParams = {};
this._withHeaders = new dictionary_1.Dictionary([]);
}
/**
* Execute the query.
* Executes the fetch and returns the results.
* @function
* @name FetchCollectionBuilder#execute
* @return {Promise<MobileObjectCollection|NetworkResponse>}
* @mcs
*/
FetchCollectionBuilder.prototype.execute = function () {
return this.endpoint._executeFetchObjects(this._withHeaders, this._withParams, this._fetchAll, this._offset, this._limit, this._fetchFromService);
};
return FetchCollectionBuilder;
}());
exports.FetchCollectionBuilder = FetchCollectionBuilder;
/***/ }),
/* 26 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var mobile_resource_1 = __webpack_require__(16);
var sync_resource_type_1 = __webpack_require__(10);
/**
* @classdesc Class that represents a collection of MobileObjects returned by a custom code API.
* @class
* @global
* @hideconstructor
* @mcs
*/
var MobileObjectCollection = /** @class */ (function (_super) {
__extends(MobileObjectCollection, _super);
function MobileObjectCollection(endpoint, uri) {
var _this = _super.call(this, endpoint, uri) || this;
_this._type = sync_resource_type_1.SyncResourceType.item;
_this._objects = [];
return _this;
}
MobileObjectCollection.prototype.initialize = function (objects) {
for (var idx in objects) {
if (objects.hasOwnProperty(idx)) {
var object = objects[idx];
this._objects.push(object);
}
}
return this;
};
/**
* Get Length.
* The count of items in the collection
* @function
* @name MobileObjectCollection#getLength
* @returns {number}
* @mcs
*/
MobileObjectCollection.prototype.getLength = function () {
return this._objects.length;
};
;
/**
* Get item.
* Return specific object from collection.
* @function
* @name MobileObjectCollection#getItem
* @param idx {number} item position in collection.
* @return {MobileObject}
* @mcs
*/
MobileObjectCollection.prototype.getItem = function (idx) {
return this._objects[idx];
};
;
/**
* All.
* Return all objects from collection.
* @function
* @name MobileObjectCollection#all
* @return {MobileObject[]}
* @mcs
*/
MobileObjectCollection.prototype.all = function () {
return this._objects;
};
;
/**
* Run this method on every object.
* @callback MobileObjectCollection#forEachCallback
* @param object {MobileObject} the mobile object.
* @mcs
*/
/**
* For each.
* Run method per item
* @function
* @name MobileObjectCollection#forEach
* @param method {MobileObjectCollection#forEachCallback} method to run on item.
* @mcs
*/
MobileObjectCollection.prototype.forEach = function (method) {
this._objects.forEach(method);
};
return MobileObjectCollection;
}(mobile_resource_1.MobileResource));
exports.MobileObjectCollection = MobileObjectCollection;
/***/ }),
/* 27 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var sync_processor_1 = __webpack_require__(5);
var types_1 = __webpack_require__(0);
var dictionary_1 = __webpack_require__(3);
var DeleteProcessor = /** @class */ (function (_super) {
__extends(DeleteProcessor, _super);
function DeleteProcessor(backend, apiName, endpointPath, platform, utils, common, options) {
return _super.call(this, backend, apiName, endpointPath, false, utils, platform, options, common) || this;
}
DeleteProcessor.prototype.performRequest = function (url) {
var headers = this.getHttpHeaders(new dictionary_1.Dictionary([]));
return this.platform.invokeService({
url: url,
headers: headers,
method: types_1.HttpMethods.DELETE,
module: types_1.ModuleNames.SYNC,
});
};
return DeleteProcessor;
}(sync_processor_1.SyncProcessor));
exports.DeleteProcessor = DeleteProcessor;
/***/ }),
/* 28 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
var sync_processor_1 = __webpack_require__(5);
var types_1 = __webpack_require__(0);
var PutProcessor = /** @class */ (function (_super) {
__extends(PutProcessor, _super);
function PutProcessor(backend, apiName, endpointPath, platform, utils, common, options) {
return _super.call(this, backend, apiName, endpointPath, false, utils, platform, options, common) || this;
}
PutProcessor.prototype.performRequest = function (url, requestHeaders, requestData) {
var _this = this;
var headers = this.getHttpHeaders(requestHeaders);
return this.platform.invokeService({
url: url,
headers: headers,
method: types_1.HttpMethods.PUT,
data: requestData,
module: types_1.ModuleNames.SYNC,
}).then(function (response) {
return {
uri: _this.getUri(response, url),
data: response.data,
};
});
};
return PutProcessor;
}(sync_processor_1.SyncProcessor));
exports.PutProcessor = PutProcessor;
/***/ }),
/* 29 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
var sync_processor_1 = __webpack_require__(5);
var types_1 = __webpack_require__(0);
var PostProcessor = /** @class */ (function (_super) {
__extends(PostProcessor, _super);
function PostProcessor(backend, apiName, endpointPath, platform, utils, common, options) {
return _super.call(this, backend, apiName, endpointPath, false, utils, platform, options, common) || this;
}
PostProcessor.prototype.performRequest = function (url, requestHeaders, requestData) {
var _this = this;
var headers = this.getHttpHeaders(requestHeaders);
return this.platform.invokeService({
url: url,
headers: headers,
method: types_1.HttpMethods.POST,
data: requestData,
module: types_1.ModuleNames.SYNC,
}).then(function (response) {
return {
uri: _this.getUri(response, url),
data: response.data,
};
});
};
return PostProcessor;
}(sync_processor_1.SyncProcessor));
exports.PostProcessor = PostProcessor;
/***/ }),
/* 30 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var sync_processor_1 = __webpack_require__(5);
var types_1 = __webpack_require__(0);
var GetProcessor = /** @class */ (function (_super) {
__extends(GetProcessor, _super);
function GetProcessor(backend, apiName, endpointPath, platform, utils, common, options) {
return _super.call(this, backend, apiName, endpointPath, false, utils, platform, options, common) || this;
}
GetProcessor.prototype.performRequest = function (url, requestHeaders, fetchFromService) {
var headers = this.getHttpHeaders(requestHeaders);
return this.platform.invokeService({
url: url,
headers: headers,
method: types_1.HttpMethods.GET,
module: types_1.ModuleNames.SYNC,
}).then(success.bind(this));
function success(response) {
return {
uri: this.getUri(response, url),
data: response.data,
};
}
};
return GetProcessor;
}(sync_processor_1.SyncProcessor));
exports.GetProcessor = GetProcessor;
/***/ }),
/* 31 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var sync_resource_type_1 = __webpack_require__(10);
var mobile_resource_1 = __webpack_require__(16);
/**
* @classdesc Class that represents an object returned by a custom code API.
* @class
* @global
* @hideconstructor
* @mcs
*/
var MobileObject = /** @class */ (function (_super) {
__extends(MobileObject, _super);
function MobileObject(endpoint, uri) {
var _this = _super.call(this, endpoint, uri) || this;
_this._type = sync_resource_type_1.SyncResourceType.item;
return _this;
}
MobileObject.prototype._assign = function (object) {
if (object === void 0) { object = {}; }
object['__proto__'] = this;
return object;
};
/**
* Save the object.
* Saves any changes to the object back to the service.
* @function
* @name MobileObject#save
* @param saveIfOffline {Boolean} If true will cache updates locally and sync them back to the service
* if the device is offline;
* if false will fail if the device is offline.
* @return {Promise<MobileObject>}
* @mcs
*/
MobileObject.prototype.save = function (saveIfOffline) {
return this._getEndpoint()._save(this, saveIfOffline);
};
/**
* Delete the object.
* Saves any changes to the object back to the service.
* @function
* @name MobileObject#delete
* @return {Promise<NetworkResponse>}
* @mcs
*/
MobileObject.prototype.delete = function () {
return this._getEndpoint()._delete(this);
};
return MobileObject;
}(mobile_resource_1.MobileResource));
exports.MobileObject = MobileObject;
/***/ }),
/* 32 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
Object.defineProperty(exports, "__esModule", { value: true });
var mobile_object_1 = __webpack_require__(31);
var get_processor_1 = __webpack_require__(30);
var post_processor_1 = __webpack_require__(29);
var put_processor_1 = __webpack_require__(28);
var delete_processor_1 = __webpack_require__(27);
var mobile_object_collection_1 = __webpack_require__(26);
var fetch_collection_builder_1 = __webpack_require__(25);
var network_response_1 = __webpack_require__(2);
var types_1 = __webpack_require__(0);
var dictionary_1 = __webpack_require__(3);
/**
* @classdesc Class that represents an endpoint in a custom code API. Callers should use
* [Synchronization.openEndpoint()]{@link Synchronization#openEndpoint} to create a new MobileEndpoint.
* @class
* @global
* @hideconstructor
* @mcs
*/
var MobileEndpoint = /** @class */ (function () {
function MobileEndpoint(synchronization, apiName, endpointPath, backend, utils, platform, common, options) {
var _this = this;
this.backend = backend;
this.utils = utils;
this.platform = platform;
this.common = common;
this.options = options;
/**
* Purge cache.
* Deletes all cached resources.
* @function
* @name MobileEndpoint#purge
* @mcs
*/
this.purge = function () {
_this.options.module.flush(_this.backend.getCustomCodeUrl(_this.apiName + '/' + _this.endpointPath));
};
this.synchronization = synchronization;
this.apiName = apiName;
this.endpointPath = endpointPath;
}
/**
* Create MobileObject.
* Creates a new MobileObject.
* The object is not uploaded to the service until [save()]{@link MobileObject#save} is invoked.
* @function
* @name MobileEndpoint#purge
* @returns {MobileObject} A new MobileObject.
* @mcs
*/
MobileEndpoint.prototype.createObject = function (object) {
if (object === void 0) { object = {}; }
return new mobile_object_1.MobileObject(this, null)._assign(object);
};
/**
* Resource processor response.
* @typedef ResourceProcessorResponse
* @property {string} uri
* @property {object} data
* @mcs
*/
/**
* Fetch MobileObject.
* Fetches an object from the API's endpoint.
* @function
* @name MobileEndpoint#fetchObject
* @param id {String} The ID of the object.
* @param fetchFromService {Boolean} If true will download from the service; if false will return any pinned object
* and will trigger a background refresh.
* @return {Promise<MobileObject|ResourceProcessorResponse>}
* @mcs
*/
MobileEndpoint.prototype.fetchObject = function (id, fetchFromService) {
var url = this.apiName;
if (this.endpointPath && this.endpointPath.length > 0) {
url += '/' + this.endpointPath;
}
if (id && id !== '') {
url += '/' + id;
}
url = this.backend.getCustomCodeUrl(url);
var processor = new get_processor_1.GetProcessor(this.backend, this.apiName, this.endpointPath, this.platform, this.utils, this.common, this.options);
var headers = new dictionary_1.Dictionary([]);
headers.add(types_1.Headers.ACCEPT, types_1.ContentTypes.APPLICATION_JSON);
return processor.performRequest(url, headers, fetchFromService).then(performRequestSuccess.bind(this));
function performRequestSuccess(resource) {
if (!resource.data || resource.data === '') {
return Promise.reject(new network_response_1.NetworkResponse(404, 'Object not found in cache.'));
}
else {
var object = resource.data;
if (typeof object === 'string') {
object = object !== '' ? JSON.parse(object) : null;
}
return new mobile_object_1.MobileObject(this, resource.uri)._assign(object);
}
}
};
MobileEndpoint.prototype._save = function (mobileObject, saveIfOffline) {
var _this = this;
var isPost = !mobileObject._getMcsURI();
var url = isPost ?
this.backend.getCustomCodeUrl(this.apiName + '/' + this.endpointPath) :
this._getMcsURI(mobileObject);
var processor = isPost ?
new post_processor_1.PostProcessor(this.backend, this.apiName, this.endpointPath, this.platform, this.utils, this.common, this.options) :
new put_processor_1.PutProcessor(this.backend, this.apiName, this.endpointPath, this.platform, this.utils, this.common, this.options);
var data = JSON.stringify(mobileObject);
var headers = new dictionary_1.Dictionary([]);
headers.add(types_1.Headers.ACCEPT, types_1.ContentTypes.APPLICATION_JSON);
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.APPLICATION_JSON);
return processor.performRequest(url, headers, data)
.then(function (resource) {
var object = resource.data;
if (typeof object === 'string') {
object = object !== '' ? JSON.parse(object) : null;
}
_this._updateMobileObject(mobileObject, object);
mobileObject.__proto__ = new mobile_object_1.MobileObject(_this, resource.uri);
return mobileObject;
});
};
MobileEndpoint.prototype._delete = function (mobileObject) {
var _this = this;
var processor = new delete_processor_1.DeleteProcessor(this.backend, this.apiName, this.endpointPath, this.platform, this.utils, this.common, this.options);
var url = this._getMcsURI(mobileObject);
return processor.performRequest(url)
.then(function (response) {
_this._updateMobileObject(mobileObject._syncResource);
mobileObject._syncResource = null;
return response;
});
};
MobileEndpoint.prototype._getMcsURI = function (mobileObject) {
var uri = mobileObject._getMcsURI();
if (!uri.startsWith('http')) {
return this.backend._baseUrl + uri;
}
else {
return uri;
}
};
MobileEndpoint.prototype._updateMobileObject = function (mobileObject, newObject) {
if (newObject === void 0) { newObject = {}; }
for (var property in mobileObject) {
if (mobileObject.hasOwnProperty(property)) {
delete mobileObject[property];
}
}
for (var property in newObject) {
if (newObject.hasOwnProperty(property)) {
mobileObject[property] = newObject[property];
}
}
};
MobileEndpoint.prototype._executeFetchObjects = function (withHeaders, withParams, fetchAll, offset, limit, fetchFromService) {
var endpoint = this;
var headers = this.backend.getHttpHeaders(withHeaders);
headers.add(types_1.Headers.ORACLE_MOBILE_SYNC_AGENT, 'true');
var request = {
method: types_1.HttpMethods.GET,
url: this.backend.getCustomCodeUrl(this.apiName + '/' + this.endpointPath),
headers: headers,
module: types_1.ModuleNames.SYNC,
};
return this.platform.invokeService(request).then(invokeServiceSuccess.bind(this));
function invokeServiceSuccess(response) {
var data = response.data;
if (typeof data === 'string') {
data = data !== '' ? JSON.parse(data) : null;
}
var objects = [];
for (var idx in data.items) {
if (data.items.hasOwnProperty(idx)) {
var object = data.items[idx];
var uri = '/' + data.uris[idx];
objects.push(new mobile_object_1.MobileObject(endpoint, uri)._assign(object));
}
}
return new mobile_object_collection_1.MobileObjectCollection(endpoint, /*syncResource*/ null).initialize(objects);
}
};
/**
* Fetch mobile objects.
* Method to fetch a collection of objects from the endpoint.
* If the collection exists in the cache, the cached copy is returned; otherwise it is downloaded from the service..
* @function
* @name MobileEndpoint#fetchObjects
* @return {FetchCollectionBuilder}
* @mcs
*/
MobileEndpoint.prototype.fetchObjects = function () {
return new fetch_collection_builder_1.FetchCollectionBuilder(this);
};
return MobileEndpoint;
}());
exports.MobileEndpoint = MobileEndpoint;
/***/ }),
/* 33 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
Object.defineProperty(exports, "__esModule", { value: true });
var mobile_endpoint_1 = __webpack_require__(32);
var logger_1 = __webpack_require__(1);
/**
* @classdesc Class that provides caching and synchronization capabilities. Callers should use
* MobileBackend's [synchronization]{@link Backend#synchronization} property.
* @class
* @global
* @hideconstructor
* @mcs
*/
var Synchronization = /** @class */ (function () {
function Synchronization(backend, utils, platform, common, options, process) {
this.utils = utils;
this.platform = platform;
this.common = common;
this.options = options;
this.process = process;
this._endpoints = {};
this.logger = new logger_1.Logger('Synchronization');
this.backend = backend;
if (!options['_originalIsOnline']) {
options['_originalIsOnline'] = options.isOnline.bind(options);
}
var _isOffline = false;
options.isOnline = function () {
return _isOffline === false ? options['_originalIsOnline']() : !_isOffline;
};
this.setOfflineMode = function (isOffline) {
_isOffline = (typeof isOffline === 'boolean') ? isOffline : true;
};
}
/**
* Is online.
* Gets device network status which is currently being used by Synchronization.
* @function
* @name Synchronization#isOnline
* @returns {Boolean}
* @mcs
*/
Synchronization.prototype.isOnline = function () {
return this.options.isOnline();
};
/**
* Purge cache.
* Deletes all cached resources.
* @function
* @name Synchronization#purge
* @mcs
*/
Synchronization.prototype.purge = function () {
for (var apiName in this._endpoints) {
if (this._endpoints.hasOwnProperty(apiName)) {
var api = this._endpoints[apiName];
for (var path in api) {
if (api.hasOwnProperty(path)) {
api[path].purge();
}
}
}
}
};
/**
* Open endpoint.
* Returns a [MobileEndpoint]{@link MobileEndpoint} that provides access
* to an endpoint in a custom code API.
* @function
* @name Synchronization#openEndpoint
* @param {string} apiName The name of the custom code API
* @param {string} endpointPath The endpoint in the custom code API
* @returns {MobileEndpoint} A MobileEndpoint object.
* @mcs
*/
Synchronization.prototype.openEndpoint = function (apiName, endpointPath) {
this._endpoints[apiName] = this._endpoints[apiName] || {};
this._endpoints[apiName][endpointPath] = this._endpoints[apiName][endpointPath] ||
new mobile_endpoint_1.MobileEndpoint(this, apiName, endpointPath, this.backend, this.utils, this.platform, this.common, this.options);
return this._endpoints[apiName][endpointPath];
};
Synchronization.prototype._run = function (callback) {
return this.process.run(callback);
};
Synchronization.prototype._runWithoutReadInBackground = function (callback) {
return this.process.runWithoutReadInBackground(callback);
};
return Synchronization;
}());
exports.Synchronization = Synchronization;
/***/ }),
/* 34 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var authorization_1 = __webpack_require__(4);
var logger_1 = __webpack_require__(1);
var network_response_1 = __webpack_require__(2);
var mcs_authorization_1 = __webpack_require__(7);
var types_1 = __webpack_require__(0);
var dictionary_1 = __webpack_require__(3);
/**
* @class
* @global
* @classdesc Class used to authorize a mobile user against Oracle Mobile Hub. Callers should use
* MobileBackend's [authorization]{@link Backend#authorization} property.
* @extends Authorization
* @hideconstructor
* @mcs
*/
var OAuthAuthorization = /** @class */ (function (_super) {
__extends(OAuthAuthorization, _super);
function OAuthAuthorization(oAuthTokenEndPoint, config, backend, tenantName, utils, platform) {
var _this = _super.call(this, backend, utils, platform) || this;
_this.oAuthTokenEndPoint = oAuthTokenEndPoint;
_this.config = config;
_this.logger = new logger_1.Logger('OAuthAuthorization');
_this._clientId = utils.validateConfiguration(config.clientId);
_this._clientSecret = utils.validateConfiguration(config.clientSecret);
_this._tenantName = utils.validateConfiguration(tenantName);
return _this;
}
/**
* Get authorized username.
* Returns the username of the current authorized user if any, null otherwise.
* @return {String}
* @function
* @name OAuthAuthorization#getAuthorizedUserName
* @mcs
*/
OAuthAuthorization.prototype.getAuthorizedUserName = function () {
return this._authorizedUserName;
};
/**
* Get client id.
* Returns the client ID for the current backend.
* @return {String}
* @function
* @name OAuthAuthorization#getClientId
* @mcs
*/
OAuthAuthorization.prototype.getClientId = function () {
return this._clientId;
};
/**
* Returns the tenant name for the current backend.
* @private
*/
OAuthAuthorization.prototype.getTenantName = function () {
return this._tenantName;
};
/**
* Get client secret.
* Returns the client secret for the current backend.
* @return {String}
* @function
* @name OAuthAuthorization#getClientSecret
* @mcs
*/
OAuthAuthorization.prototype.getClientSecret = function () {
return this._clientSecret;
};
/**
* Authenticate.
* Authenticates a user with the given credentials against the service.
* The user remains logged in until logout() is called.
* @param username {String} The username of the credentials.
* @param password {String} The password of the credentials.
* @return {Promise<NetworkResponse>}
* @function
* @name OAuthAuthorization#authenticate
* @mcs
*/
OAuthAuthorization.prototype.authenticate = function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
var username = params[0];
var password = params[1];
this.logout();
if (!username || !password) {
this.logger.error('Wrong username or password parameter');
return Promise.reject(new network_response_1.NetworkResponse(400, 'Bad Request'));
}
var authorizationToken = 'Basic ' + this.utils.encodeBase64(this._clientId + ':' + this._clientSecret);
var scope = this.config.scope || this.backend._baseUrl + 'urn:opc:resource:consumer::all';
var requestBody = OAuthAuthorization.urlEncodeComponent(username, password, scope);
var headers = new dictionary_1.Dictionary([]);
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.X_WWW_FORM_FORM_URLENCODED);
headers.add(types_1.Headers.AUTHORIZATION, authorizationToken);
if (typeof this._tenantName !== 'undefined') {
headers.add(types_1.Headers.X_RESOURCE_IDENTITY_DOMAIN_NAME, this._tenantName);
}
return this.platform.invokeService({
headers: headers,
url: this.getOAuthTokenUrl(),
method: types_1.HttpMethods.POST,
data: requestBody,
withCredentials: false,
module: types_1.ModuleNames.AUTHORIZATION,
})
.then(invokeServiceSuccess.bind(this))
.catch(invokeServiceError.bind(this));
function invokeServiceSuccess(response) {
this._authenticateSuccess(response, response.data.access_token);
this._authorizedUserName = username;
return response;
}
function invokeServiceError(response) {
this._authenticateError(response);
return Promise.reject(response);
}
};
/**
* Authenticate anonymous.
* Authenticates an anonymous user against the service. The user remains logged in until logout() is called.
* @return {Promise<NetworkResponse>}
* @function
* @name OAuthAuthorization#authenticateAnonymous
* @mcs
*/
OAuthAuthorization.prototype.authenticateAnonymous = function () {
var headers = new dictionary_1.Dictionary([]);
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.X_WWW_FORM_FORM_URLENCODED);
if (typeof this._tenantName !== 'undefined') {
headers.add(types_1.Headers.X_USER_IDENTITY_DOMAIN_NAME, this._tenantName);
}
var scope = this.config.scope || this.backend._baseUrl + 'urn:opc:resource:consumer::all';
var body = 'grant_type=client_credentials&scope=' + encodeURIComponent(scope);
return this._authenticateAnonymousInvoke(headers, this.getOAuthTokenUrl(), types_1.HttpMethods.POST, false, body)
.then(invokeServiceSuccess.bind(this));
function invokeServiceSuccess(response) {
this._tokenExpiredTime = Date.now() + response.data.expires_in * 1000;
return response;
}
};
OAuthAuthorization.prototype._getAnonymousAuthorizationHeaders = function (headers) {
headers.add(types_1.Headers.AUTHORIZATION, 'Basic ' +
this.utils.encodeBase64(this.getClientId() + ':' + this.getClientSecret()));
return headers;
};
/**
* Is authentication token valid.
* Checks to see if the OAuth token is null, undefined, NaN,an empty string (""), 0,or false.
* It also checks the timestamp
* for when the token was first retrieved to see if it was still valid.
* @returns {Boolean}
* @function
* @name OAuthAuthorization#isTokenValid
* @mcs
*/
OAuthAuthorization.prototype.isTokenValid = function () {
if (this.getAccessToken() || this._getAnonymousAccessToken()) {
this.logger.debug('Token is not null or empty');
var currentTime = Date.now();
if (currentTime >= this._tokenExpiredTime) {
this.logger.info('Token has expired');
return false;
}
else {
this.logger.debug('Token is still valid');
return true;
}
}
else {
return false;
}
};
/**
* Log out.
* Logs out the current user and clears credentials and tokens.
* @function
* @name OAuthAuthorization#logout
* @mcs
*/
OAuthAuthorization.prototype.logout = function () {
this._clearState();
};
/**
* Refresh token
* For OAuth, the SDK can not refresh because it does not persist client credentials.
* This function only exists here because it inherits from the Authorization object,
* which is also used for other types of authentication in which the token can expire.
* @return {Promise<NetworkResponse>}
* @function
* @name OAuthAuthorization#refreshToken
* @mcs
*/
OAuthAuthorization.prototype.refreshToken = function () {
var isTokenValid = this.isTokenValid();
if (isTokenValid && !this.getAccessToken() && this._getIsAnonymous()) {
return Promise.resolve(new network_response_1.NetworkResponse(200, this._getAnonymousAccessToken()));
}
else if (isTokenValid && !this._getAnonymousAccessToken() && !this._getIsAnonymous()) {
return Promise.resolve(new network_response_1.NetworkResponse(200, this._getAnonymousAccessToken()));
}
else {
this.logger.error('Token has expired or user has not been authenticate with the service.');
return Promise.resolve(new network_response_1.NetworkResponse(401, 'Please use the authenticate with username/password ' +
'combination or authenticateAnonymous function before using refreshToken.'));
}
};
OAuthAuthorization.prototype._anonymousTokenResponseConverter = function (response) {
return new authorization_1.AuthenticationResponse(response, response.data.access_token);
};
OAuthAuthorization.prototype._clearState = function () {
_super.prototype._clearState.call(this);
this._authorizedUserName = null;
this._tokenExpiredTime = Date.now() * 1000;
};
OAuthAuthorization.prototype._getHttpHeaders = function (headers) {
if (this.getAccessToken() !== null && typeof this.getAccessToken() === 'string') {
headers.add(types_1.Headers.AUTHORIZATION, 'Bearer ' + this.getAccessToken());
}
return headers;
};
OAuthAuthorization.prototype._getAnonymousHttpHeaders = function (headers) {
if (this._getAnonymousAccessToken() && typeof this._getAnonymousAccessToken() === 'string') {
headers.add(types_1.Headers.AUTHORIZATION, 'Bearer ' + this._getAnonymousAccessToken());
}
return headers;
};
OAuthAuthorization.urlEncodeComponent = function (username, password, scope) {
var _username;
var _password;
if (username.indexOf('@') > -1) {
_username = encodeURIComponent(username).replace(/%20/g, '+');
}
else {
_username = encodeURIComponent(username).replace(/%5B/g, '[').replace(/%5D/g, ']');
}
if (password.indexOf('&') > -1) {
_password = encodeURIComponent(password).replace(/%20/g, '+');
}
else {
_password = encodeURIComponent(password).replace(/%5B/g, '[').replace(/%5D/g, ']');
}
var _scope = encodeURIComponent(scope);
return "grant_type=password&username=" + _username + "&password=" + _password + "&scope=" + _scope;
};
/**
* Get OAuth token url.
* Constructs a full URL, including the prefix, for the OAuth token endpoint.
* @returns {String} The full URL for the OAuth token endpoint.
* @function
* @name OAuthAuthorization#getOAuthTokenUrl
* @mcs
*/
OAuthAuthorization.prototype.getOAuthTokenUrl = function () {
var tokenUri = this.utils.validateConfiguration(this.oAuthTokenEndPoint);
if (!this.utils.strEndsWith(tokenUri, '/oauth2/v1/token')) {
tokenUri += '/oauth2/v1/token';
}
return tokenUri;
};
return OAuthAuthorization;
}(mcs_authorization_1.MCSAuthorization));
exports.OAuthAuthorization = OAuthAuthorization;
/***/ }),
/* 35 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Class that enables you to retrieve information on the current user and manage its properties. Callers should use
* Authorization's [getCurrentUser()]{@link Backend#authorization.getCurrentUser} property.
*/
var User = /** @class */ (function () {
function User(user) {
this._id = user.id;
this._userName = user.username;
this._firstName = user._firstName;
this._lastName = user._lastName;
this._email = user.email;
}
/**
* Returns the current user's name.
*
* @return Current user's name
*/
User.prototype.getId = function () {
return this._id;
};
/**
* Returns first name for current user.
*/
User.prototype.getFirstName = function () {
return this._firstName;
};
;
/**
* Returns last name for current user.
*/
User.prototype.getLastName = function () {
return this._lastName;
};
;
/**
* Returns email address for current user.
*/
User.prototype.getEmail = function () {
return this._email;
};
;
return User;
}());
exports.User = User;
/***/ }),
/* 36 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var authorization_1 = __webpack_require__(4);
var logger_1 = __webpack_require__(1);
var network_response_1 = __webpack_require__(2);
var mcs_authorization_1 = __webpack_require__(7);
var types_1 = __webpack_require__(0);
var dictionary_1 = __webpack_require__(3);
/**
* @class
* @global
* @classdesc Class used to authorize a mobile user against Oracle Mobile Hub
* with the Basic Authentication security schema. Callers should use
* MobileBackend's [authorization]{@link Backend#authorization} property.
* @extends MCSAuthorization
* @hideconstructor
* @mcs
*/
var BasicAuthorization = /** @class */ (function (_super) {
__extends(BasicAuthorization, _super);
function BasicAuthorization(config, backend, utils, platform) {
var _this = _super.call(this, backend, utils, platform) || this;
_this._logger = new logger_1.Logger('BasicAuthorization');
_this._backendId = utils.validateConfiguration(config.mobileBackendId);
_this._anonymousToken = utils.validateConfiguration(config.anonymousKey);
return _this;
}
/**
* Get authorized user name.
* Returns the username of the current authorized user if any, null otherwise.
* @return {String}
* @function
* @name BasicAuthorization#getAuthorizedUserName
* @mcs
*/
BasicAuthorization.prototype.getAuthorizedUserName = function () {
return this._authorizedUserName;
};
/**
* Authenticate.
* Authenticates a user with the given credentials against the service.
* The user remains logged in until logout() is called.
* @param username {String} The username of the credentials.
* @param password {String} The password of the credentials.
* @return {Promise<NetworkResponse>}
* @function
* @name BasicAuthorization#authenticate
* @mcs
*/
BasicAuthorization.prototype.authenticate = function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
var username = params[0];
var password = params[1];
this.logout();
if (!username || !password) {
this._logger.error('Wrong username or password parameter');
return Promise.reject(new network_response_1.NetworkResponse(400, 'Bad Request'));
}
var authorizationToken = 'Basic ' + this.utils.encodeBase64(username + ':' + password);
var headers = new dictionary_1.Dictionary([]);
headers.add(types_1.Headers.AUTHORIZATION, authorizationToken);
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return this.platform.invokeService({
headers: headers,
url: this.backend.getPlatformUrl('users/login'),
method: types_1.HttpMethods.GET,
module: types_1.ModuleNames.AUTHORIZATION,
})
.then(invokeServiceSuccess.bind(this))
.catch(invokeServiceError.bind(this));
function invokeServiceSuccess(response) {
this._authenticateSuccess(response, authorizationToken);
this._authorizedUserName = username;
return response;
}
function invokeServiceError(response) {
this._authenticateError(response);
return Promise.reject(response);
}
};
/**
* Authenticate anonymously.
* Authenticates an anonymous user against the service. The user remains logged in until logout() is called.
* @return {Promise<NetworkResponse>}
* @function
* @name BasicAuthorization#authenticateAnonymous
* @mcs
*/
BasicAuthorization.prototype.authenticateAnonymous = function () {
return this._authenticateAnonymousInvoke(new dictionary_1.Dictionary([]), this.backend.getPlatformUrl('users/login'), types_1.HttpMethods.GET);
};
/**
* Get anonymous authorization headers
* @param {IDictionary<Headers, string>} headers
* @return {IDictionary<Headers, string>}
* @private
*/
BasicAuthorization.prototype._getAnonymousAuthorizationHeaders = function (headers) {
headers.add(types_1.Headers.AUTHORIZATION, 'Basic ' + this._anonymousToken);
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return headers;
};
/**
* Is token valid.
* Checks to see if the authorization token is null, undefined, NaN,an empty string (""), 0, or false.
* @returns {Boolean}
* @function
* @name BasicAuthorization#isTokenValid
* @mcs
*/
BasicAuthorization.prototype.isTokenValid = function () {
if (this.getAccessToken() !== null && typeof this.getAccessToken() == 'string') {
this._logger.info('Authorization token is not null or empty');
return true;
}
else if (this.getAccessToken() == null && typeof this.getAccessToken() !== 'string') {
this._logger.info('Authorization token is null and/or empty');
return false;
}
};
/**
* Refresh token.
* For BasicAuth, there is no need to call this function, because the token never expires.
* This function only exists here because it inherits from the Authorization object, which is also used for other types of authentication in which the token can expire.
* @return {Promise<NetworkResponse>}
* @function
* @name BasicAuthorization#refreshToken
* @mcs
*/
BasicAuthorization.prototype.refreshToken = function () {
if (!this._getIsAuthorized() && !this.isTokenValid()) {
return Promise.reject(new network_response_1.NetworkResponse(401, 'Please use the authenticate with username/password combination or authenticateAnonymous function before using refreshToken.'));
}
else if (this._getIsAuthorized() && this.isTokenValid()) {
this._logger.debug('Authenticated token is valid, you do not need to refresh.');
return Promise.resolve(new network_response_1.NetworkResponse(200, this.getAccessToken()));
}
};
/**
* Log out.
* Logs out the current user and clears credentials and tokens.
* @function
* @name BasicAuthorization#logout
* @mcs
*/
BasicAuthorization.prototype.logout = function () {
this._clearState();
};
BasicAuthorization.prototype._anonymousTokenResponseConverter = function (response) {
return new authorization_1.AuthenticationResponse(response, 'Basic ' + this._anonymousToken);
};
BasicAuthorization.prototype._getHttpHeaders = function (headers) {
if (this.getAccessToken() !== null && typeof this.getAccessToken() == 'string') {
headers.add(types_1.Headers.AUTHORIZATION, this.getAccessToken());
}
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return headers;
};
BasicAuthorization.prototype._getAnonymousHttpHeaders = function (headers) {
if (this._getAnonymousAccessToken() && typeof this._getAnonymousAccessToken() == 'string') {
headers.add(types_1.Headers.AUTHORIZATION, this._getAnonymousAccessToken());
}
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return headers;
};
BasicAuthorization.prototype._clearState = function () {
_super.prototype._clearState.call(this);
this._authorizedUserName = null;
};
return BasicAuthorization;
}(mcs_authorization_1.MCSAuthorization));
exports.BasicAuthorization = BasicAuthorization;
/***/ }),
/* 37 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var analytics_event_1 = __webpack_require__(18);
var logger_1 = __webpack_require__(1);
var network_response_1 = __webpack_require__(2);
var types_1 = __webpack_require__(0);
/**
* @classdesc Class that provides analytics capabilities. Callers should use
* MobileBackend's [analytics]{@link Backend#analytics} property.
* @class
* @global
* @hideconstructor
* @mcs
*/
var Analytics = /** @class */ (function () {
function Analytics(config, backend, platform, utils) {
this.config = config;
this.backend = backend;
this.platform = platform;
this.utils = utils;
this.events = [];
this.logger = new logger_1.Logger('Analytics');
}
/**
* Returns session ID for current session.
* @returns {String}
* @function
* @name Analytics#getSessionId
* @mcs
*/
Analytics.prototype.getSessionId = function () {
return this.sessionId;
};
/**
* Starts a new session. If one is in progress, then a new session will not be created.
* @function
* @name Analytics#startSession
* @mcs
*/
Analytics.prototype.startSession = function () {
if (!this.sessionId) {
if (this._locationEnabled()) {
this.platform.initGPSLocation();
}
this.sessionId = this.utils.uuid();
this.logNamedEvent('sessionStart').type = 'system';
}
};
/**
* Ends a session if one exists.
* @return {Promise<Undefined|NetworkResponse>}
* @function
* @name Analytics#endSession
* @mcs
*/
Analytics.prototype.endSession = function () {
if (!!this.sessionId) {
this.logNamedEvent('sessionEnd').type = 'system';
this.logger.debug('Deactivate a default session');
return this.flush().then(flushSuccess.bind(this));
}
else {
return Promise.reject(new network_response_1.NetworkResponse(500, 'Session ID is null'));
}
function flushSuccess(response) {
this.sessionId = undefined;
return response;
}
};
/**
* Creates a new analytics event with the given name.
* @param name {String} The name of the event.
* @returns {AnalyticsEvent} The [AnalyticsEvent]{@link AnalyticsEvent} instance that was logged.
* @function
* @name Analytics#logNamedEvent
* @mcs
*/
Analytics.prototype.logNamedEvent = function (name) {
var event = new analytics_event_1.AnalyticsEvent(name);
this.logEvent(event);
return event;
};
/**
* Writes out an analytics event. It will implicitly call startSession(),
* which will add a new event to the list of events for Oracle Mobile Hub to consume
* @param event {AnalyticsEvent} The event to log.
* @example event: "GettingStartedJSEvent"
* @returns {AnalyticsEvent} The [AnalyticsEvent]{@link AnalyticsEvent} instance that was logged.
* @function
* @name Analytics#logEvent
* @mcs
*/
Analytics.prototype.logEvent = function (event) {
if (this.events.length === 0) {
this.events[0] = this._createContextEvent();
}
this.startSession();
this.events[this.events.length] = event;
event.sessionID = this.sessionId;
return event;
};
/**
* Uploads all events to the service if the device is online or caches them locally until the device goes online, at
* which point they will be uploaded. If a session is in progress it will end.
* @return {Promise<NetworkResponse>}
* @function
* @name Analytics#flush
* @mcs
*/
Analytics.prototype.flush = function () {
for (var i = 0; i < this.events.length; i++) {
var enableLocation = this._locationEnabled();
if (enableLocation && this.events[i].name == 'context') {
var gpsLocation = this.platform.getGPSLocation();
if (gpsLocation != null && gpsLocation.latitude != null) {
this.events[i].properties.latitude = gpsLocation.latitude;
}
if (gpsLocation != null && gpsLocation.longitude != null) {
this.events[i].properties.longitude = gpsLocation.longitude;
}
}
}
var eventsString = JSON.stringify(this.events);
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.APPLICATION_JSON);
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.POST,
url: this.backend.getPlatformUrl('analytics/events'),
data: eventsString,
module: types_1.ModuleNames.MCS_ANALYTICS,
}).then(invokeServiceSuccess.bind(this), invokeServiceError.bind(this));
function invokeServiceSuccess(response) {
this.logger.debug('Analytics events flushed.');
this.events = [];
return response;
}
function invokeServiceError(response) {
this.logger.error('Failed to flush analytics events.');
return Promise.reject(response);
}
};
Analytics.prototype._createContextEvent = function () {
var contextEvent = new analytics_event_1.AnalyticsEvent('context');
contextEvent.type = 'system';
contextEvent.sessionID = this.sessionId;
contextEvent.properties.timezone = '' + new Date().getTimezoneOffset() * 60;
var deviceInformation = this.platform.getDeviceInformation();
contextEvent.properties.model = deviceInformation.model;
contextEvent.properties.manufacturer = deviceInformation.manufacturer;
contextEvent.properties.osName = deviceInformation.osName;
contextEvent.properties.osVersion = deviceInformation.osVersion;
contextEvent.properties.osBuild = deviceInformation.osBuild;
contextEvent.properties.carrier = deviceInformation.carrier;
return contextEvent;
};
Analytics.prototype._getEvents = function () {
return this.events;
};
Analytics.prototype._locationEnabled = function () {
if (typeof this.config.disableAnalyticsLocation !== 'undefined') {
return this.config.disableAnalyticsLocation;
}
else {
return true;
}
};
return Analytics;
}());
exports.Analytics = Analytics;
/***/ }),
/* 38 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
* Created by ddrobins on 7/28/15.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var network_response_1 = __webpack_require__(2);
var types_1 = __webpack_require__(0);
/**
* @classdesc This class provides a way to invoke custom API endpoints for the
* currently active mobile backend. Callers should use
* MobileBackend's [customCode]{@link Backend#customCode} property.
* @class
* @global
* @hideconstructor
* @mcs
*/
var CustomCode = /** @class */ (function () {
function CustomCode(backend, platform) {
this.backend = backend;
this.platform = platform;
}
/**
* Invoke custom code JSON request.
* Allows the user to call custom Code defined on the UI and assigned to the backend defined by the user
* This custom endpoint should return data only in JSON format.
* @function
* @name CustomCode#invokeCustomCodeJSONRequest
* @param path {String} The path of the endpoint without platform prefix.
* @param method {String} HTTP method that is invoked, this method accepts: GET, POST, PUT, DELETE, PATCH.
* @param data {Object} Data that is inserted into the call on the server for POST and PUT methods.
* Only accepts a JSON object and/or JavaScript array.
* @return {Promise<NetworkResponse>}
* @example <caption>These methods must be defined in the custom API for these methods to work.<br/>
* Example usage of CustomCode.invokeCustomCodeJSONRequest()</caption>
* mcs.mobileBackend.customCode
* .invokeCustomCodeJSONRequest('TaskApi1/tasks/100', 'GET', null)
* .then(invokeSuccess, invokeError);
* function invokeSuccess(response) {
* console.log(response.data);// returns object in JSON format
* }
* function invokeError(response) {
* console.error(response);
* }
* @mcs
*/
CustomCode.prototype.invokeCustomCodeJSONRequest = function (path, method, data) {
if (method in CustomCode.httpMethods) {
if (method === CustomCode.httpMethods.DELETE && data) {
return Promise.reject(new network_response_1.NetworkResponse(500, 'DELETE method content body'));
}
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.APPLICATION_JSON);
var customData = data ? JSON.stringify(data) : null;
return this.platform.invokeService({
method: method,
headers: headers,
url: this.backend.getCustomCodeUrl(path),
data: customData,
module: types_1.ModuleNames.CUSTOM_CODE,
});
}
else {
return Promise.reject(new network_response_1.NetworkResponse(501, 'Method Not Implemented'));
}
};
CustomCode.httpMethods = { GET: 'GET', POST: 'POST', PUT: 'PUT', DELETE: 'DELETE', PATCH: 'PATCH' };
return CustomCode;
}());
exports.CustomCode = CustomCode;
/***/ }),
/* 39 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var logger_1 = __webpack_require__(1);
var dictionary_1 = __webpack_require__(3);
var types_1 = __webpack_require__(0);
/**
* @classdesc Class that provides diagnostics capabilities. Callers should use
* MobileBackend's [diagnostics]{@link Backend#diagnostics} property.
* @class
* @global
* @hideconstructor
* @mcs
*/
var Diagnostics = /** @class */ (function () {
function Diagnostics(platform, utils) {
this.platform = platform;
this.logger = new logger_1.Logger('Diagnostics');
this._sessionId = utils.uuid();
}
Diagnostics.prototype._getHttpHeaders = function (headers) {
if (headers === void 0) { headers = new dictionary_1.Dictionary([]); }
headers.add(types_1.Headers.ORACLE_MOBILE_DIAGNOSTIC_SESSION_ID, this.getSessionId());
headers.add(types_1.Headers.ORACLE_MOBILE_DEVICE_ID, this.platform.getDeviceId());
headers.add(types_1.Headers.ORACLE_MOBILE_CLIENT_REQUEST_TIME, new Date().toISOString());
return headers;
};
/**
* Get diagnostic session id.
* Returns the session ID or process ID of the Diagnostics event.
* @function
* @name Diagnostics#getSessionId
* @return {String} process id for the Diagnostics session.
* @mcs
*/
Diagnostics.prototype.getSessionId = function () {
return this._sessionId;
};
return Diagnostics;
}());
exports.Diagnostics = Diagnostics;
/***/ }),
/* 40 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var logger_1 = __webpack_require__(1);
var diagnostics_1 = __webpack_require__(39);
var custom_code_1 = __webpack_require__(38);
var analytics_1 = __webpack_require__(37);
var basic_authorization_1 = __webpack_require__(36);
var oauth_authorization_1 = __webpack_require__(34);
var synchronization_1 = __webpack_require__(33);
var storage_1 = __webpack_require__(24);
var types_1 = __webpack_require__(0);
var dictionary_1 = __webpack_require__(3);
/**
* @classdesc Represents a mobile backend in Oracle Mobile Hub
* and provides access to all capabilities of the backend.
* Callers should use [omh's mobileBackend]{@link mcs#mobileBackend} property...
* @class
* @global
* @hideconstructor
* @mcs
*/
var Backend = /** @class */ (function () {
/**
* Creates mobile backend object.
* @protected
* @param config {IOracleMobileCloudConfigInternal}
* @param platform {IPlatform}
* @param utils {IUtils}
* @param syncExpress {ISyncExpressInternal}
* @private
*/
function Backend(config, platform, utils, syncExpress) {
this.config = config;
this.platform = platform;
this.utils = utils;
this.syncExpress = syncExpress;
this.PLATFORM_PATH = 'mobile/platform';
this.CUSTOM_CODE_PATH = 'mobile/custom';
this._authenticationType = null;
this._logger = new logger_1.Logger('MobileBackend');
/**
* The name of the MobileBackend as read from the configuration.
* @type {string}
* @name Backend#name
* @readonly
* @mcs
*/
this.name = null;
/**
* Returns the Authorization object used to authorize a mobile user
* against Oracle Mobile Hub.
* Please use {@link mcs#mobileBackend#setAuthenticationType} to initialize this property.<br/>
* @readonly
* @type {Authorization}
* @name Backend#authorization
* @mcs
*/
this.authorization = null;
/**
* Get diagnostic object.
* Returns the Diagnostics object that enables end-end debugging across application and cloud.
* @readonly
* @type {Diagnostics}
* @name Backend#diagnostics
* @mcs
*/
this.diagnostics = null;
/**
* Returns the CustomCode object that enables calls to custom APIs.
* @readonly
* @type {CustomCode}
* @name Backend#customCode
* @mcs
*/
this.customCode = null;
/**
* Returns the Analytics object that enables capture of mobile analytics events.
* @readonly
* @type {Analytics}
* @name Backend#analytics
* @mcs
*/
this.analytics = null;
/**
* Returns the Storage object that provides cloud-based object storage capabilities.
* @readonly
* @type {Storage}
* @name Backend#storage
* @mcs
*/
this.storage = null;
/**
* Returns the Synchronization object that provides caching and synchronization capabilities.
* @readonly
* @type {Synchronization}
* @name Backend#synchronization
* @mcs
*/
this.synchronization = null;
/**
* Returns an instance of the application configuration object.
* Callers can download the configuration from the service by invoking loadAppConfig().
* @readonly
* @type {object}
* @name Backend#appConfig
* @mcs
*/
this.appConfig = {};
this._mbeConfig = config.mobileBackend;
this.name = this._mbeConfig.name;
this.diagnostics = new diagnostics_1.Diagnostics(platform, utils);
this.customCode = new custom_code_1.CustomCode(this, platform);
this.analytics = new analytics_1.Analytics(config, this, platform, utils);
this.storage = new storage_1.Storage(this, utils, platform);
this._baseUrl = utils.validateConfiguration(this._mbeConfig.baseUrl);
this._authenticationTypes = Object.assign({}, types_1.AuthenticationTypes,
{ facebook: undefined, token: undefined });
console.log('constructor', JSON.stringify(this._authenticationTypes));
if (syncExpress) {
this.synchronization = new synchronization_1.Synchronization(this, utils, platform, syncExpress.common, syncExpress.options, syncExpress.process);
}
if (this._mbeConfig.authentication.type) {
this.setAuthenticationType(this._mbeConfig.authentication.type);
}
}
/**
* Constructs a full URL by pre-pending the prefix for platform API REST endpoints
* to the given endpoint path.
* @function
* @name Backend#getPlatformUrl
* @param path {string} The relative path of the endpoint following the platform prefix,
* i.e. /mobile/platform.
* @returns {string} The full URL.
* @mcs
*/
Backend.prototype.getPlatformUrl = function (path) {
var url = this._baseUrl;
if (this._authenticationType === types_1.AuthenticationTypes.oauth &&
this.utils.strEndsWith(this._baseUrl, '1')) {
url = url.substring(0, url.length - 4) + '7777';
}
url = this.utils.validateConfiguration(url) + '/' + this.PLATFORM_PATH;
if (!this.utils.strEndsWith(url, '/')) {
url += '/';
}
return url + path;
};
/**
* Constructs a full URL by prepending the prefix for custom API REST endpoints
* to the given endpoint path.
* @function
* @name Backend#getCustomCodeUrl
* @param path {string} The relative path of the endpoint following the platform prefix,
* i.e. {BaseUrl}/mobile/custom.
* @returns {string} The full URL.
* @mcs
*/
Backend.prototype.getCustomCodeUrl = function (path) {
return this.utils.validateConfiguration(this._baseUrl) + this._getCustomCodeUri(path);
};
/**
* Populates auth and diagnostics HTTP headers for making REST calls to a mobile backend.
* @function
* @name Backend#getHttpHeaders
* @param [headers] {Headers} An optional object with which to populate with the headers.
* @returns {Headers} The headers parameter that is passed in.
* If not provided, a new object with the populated headers
* as properties of that object is created.
* @mcs
*/
Backend.prototype.getHttpHeaders = function (headers) {
if (headers === void 0) { headers = new dictionary_1.Dictionary([]); }
var authorization = this.authorization;
var newHeaders = this.diagnostics._getHttpHeaders(headers);
if (authorization) {
if (authorization._getIsAuthorized() && authorization._getIsAnonymous()) {
newHeaders = authorization._getAnonymousHttpHeaders(newHeaders);
}
else {
newHeaders = authorization._getHttpHeaders(newHeaders);
}
}
return newHeaders;
};
/**
* Use this method to retrieve current authentication type.
* @function
* @name Backend#getAuthenticationType
* @return {string} Authentication type
* @mcs
*/
Backend.prototype.getAuthenticationType = function () {
return this._authenticationType;
};
/**
* Initialize and returns the Authorization object
* that provides authorization capabilities and access to user properties.
* @function
* @name Backend#setAuthenticationType
* @param {string} type
* For [Basic authentication]{@link BasicAuthorization},
* you would specify "basic" to use the Basic Authentication security schema.<br/>
* For [OAuth authentication]{@link OAuthAuthorization},
* you would specify "oauth" to use OAuth Authentication security schema.<br/>
* @return {Authorization}
* @throws When unrecognized authentication type provided,
* this method will throw an Exception stating that the type of Authentication that you provided
* is not supported at this time.
* @example <caption>Example usage of mobileBackend.setAuthenticationType()</caption>
* // Basic Authorization schema
* mcs.mobileBackend.setAuthenticationType('basic');
* @example // OAuth Authorization schema
* mcs.mobileBackend.setAuthenticationType('oauth');
* @mcs:web
*/
Backend.prototype.setAuthenticationType = function (type) {
var authType = this.utils.validateConfiguration(type);
this.authorization = null;
if (!this._authenticationTypes[authType]) {
throw Error("Wrong Authentication type: " + type + ", please use one of: \n" +
this._getAuthenticationTypesMessage(this._authenticationTypes));
}
if (!this._mbeConfig.authentication[authType]) {
throw Error("No Authentication Type called " + type + " is defined in configuration \n" +
'check configuration in authorization object for the following objects:\n' +
this._getAuthenticationTypesMessage(this._authenticationTypes));
}
if (this.authorization && this.authorization._getIsAuthorized()) {
this.authorization.logout();
}
if (authType === types_1.AuthenticationTypes.basic) {
this.authorization = new basic_authorization_1.BasicAuthorization(this._mbeConfig.authentication.basic, this, this.utils, this.platform);
this._logger.info('Your Authentication type: ' + authType);
this._authenticationType = authType;
}
else if (authType === types_1.AuthenticationTypes.oauth) {
var mbeConfigInternal = this._mbeConfig;
this.authorization = new oauth_authorization_1.OAuthAuthorization(this.config.oAuthTokenEndPoint, this._mbeConfig.authentication.oauth, this, mbeConfigInternal.tenantId, this.utils, this.platform);
this._logger.info('Your Authentication type: ' + authType);
this._authenticationType = authType;
}
return this.authorization;
};
/**
* Downloads the configuration from the service.
* The AppConfig property will contain the downloaded configuration.
* @function
* @memberOf Backend
* @name Backend#loadAppConfig
* @return {Promise<NetworkResponse>}
* @mcs
*/
Backend.prototype.loadAppConfig = function () {
var headers = this.getHttpHeaders();
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.APPLICATION_JSON);
var authorizationInternal = this.authorization;
if (!authorizationInternal._getIsAuthorized()) {
headers = authorizationInternal._getAnonymousAuthorizationHeaders(headers);
}
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.GET,
url: this.getPlatformUrl('appconfig/client'),
module: types_1.ModuleNames.APP_CONFIG,
}).catch(invokeServiceFail.bind(this));
function invokeServiceFail(response) {
this.logger.error('App config download failed! with status code: ' + response.statusCode);
return Promise.reject(response);
}
};
Backend.prototype._getCustomCodeUri = function (path) {
var url = '/' + this.CUSTOM_CODE_PATH;
var newPath = path;
if (this.utils.strEndsWith(newPath, '/')) {
newPath = newPath.slice(0, -1);
}
return url + '/' + newPath;
};
Backend.prototype._getAuthenticationTypesMessage = function (types) {
var _this = this;
return Object
.keys(this._authenticationTypes)
.filter(function (key) { return !!_this._authenticationTypes[key]; })
.map(function (key) { return _this._authenticationTypes[key]; })
.join('\n');
};
return Backend;
}());
exports.Backend = Backend;
/***/ }),
/* 41 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
* @ignore
*/
exports.POLICIES_MAP = {
fetchPolicy: {
persistencePropertyName: 'fetchPolicy',
FETCH_FROM_CACHE_SCHEDULE_REFRESH: 'FETCH_FROM_CACHE_SCHEDULE_REFRESH',
FETCH_FROM_SERVICE_IF_ONLINE: 'FETCH_FROM_SERVICE_IF_ONLINE',
FETCH_FROM_CACHE: 'FETCH_FROM_CACHE',
FETCH_FROM_SERVICE: 'FETCH_FROM_SERVICE',
FETCH_FROM_SERVICE_ON_CACHE_MISS: 'FETCH_FROM_SERVICE_ON_CACHE_MISS',
FETCH_FROM_SERVICE_ON_CACHE_MISS_OR_EXPIRY: 'FETCH_FROM_SERVICE_ON_CACHE_MISS_OR_EXPIRY',
FETCH_WITH_REFRESH: 'FETCH_WITH_REFRESH'
},
evictionPolicy: {
persistencePropertyName: 'evictionPolicy',
EVICT_ON_EXPIRY_AT_STARTUP: 'EVICT_ON_EXPIRY_AT_STARTUP',
MANUAL_EVICTION: 'MANUAL_EVICTION'
},
expirationPolicy: {
persistencePropertyName: 'expirationPolicy',
EXPIRE_ON_RESTART: 'EXPIRE_ON_RESTART',
EXPIRE_AFTER: 'EXPIRE_AFTER',
NEVER_EXPIRE: 'NEVER_EXPIRE'
},
updatePolicy: {
persistencePropertyName: 'updatePolicy',
QUEUE_IF_OFFLINE: 'QUEUE_IF_OFFLINE',
UPDATE_IF_ONLINE: 'UPDATE_IF_ONLINE'
},
refreshPolicy: {
persistencePropertyName: 'refreshPolicy',
PeriodicallyRefreshExpiredResource: ''
},
conflictResolutionPolicy: {
persistencePropertyName: 'conflictResolutionPolicy',
SERVER_WINS: 'SERVER_WINS',
PRESERVE_CONFLICT: 'PRESERVE_CONFLICT',
CLIENT_WINS: 'CLIENT_WINS'
},
noCache: {
persistencePropertyName: 'noCache',
'false': false,
'true': true
}
};
/***/ }),
/* 42 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
* @ignore
*/
Object.defineProperty(exports, "__esModule", { value: true });
var Utils = /** @class */ (function () {
function Utils() {
}
Utils.prototype.removeSpace = function (input) {
return input.replace(/ /g, '');
};
Utils.prototype.uuid = function () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
Utils.prototype.validateConfiguration = function (input) {
var prop = input;
if (/\s/.test(prop) && prop) {
prop = this.removeSpace(input);
}
return prop;
};
Utils.prototype.encodeBase64 = function (input) {
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
var i = 0;
do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
}
else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
Utils.KEY_STR.charAt(enc1) +
Utils.KEY_STR.charAt(enc2) +
Utils.KEY_STR.charAt(enc3) +
Utils.KEY_STR.charAt(enc4);
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input.length);
return output;
};
Utils.prototype.decodeBase64 = function (input) {
var output = '';
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
var base64test = /[^A-Za-z0-9\+\/\=]/g;
if (base64test.exec(input)) {
return null;
}
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
do {
enc1 = Utils.KEY_STR.indexOf(input.charAt(i++));
enc2 = Utils.KEY_STR.indexOf(input.charAt(i++));
enc3 = Utils.KEY_STR.indexOf(input.charAt(i++));
enc4 = Utils.KEY_STR.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
} while (i < input.length);
return output;
};
Utils.prototype.clearNonPrintableCharacters = function (str) {
return str.replace(Utils.SPECIAL_CHAR_RGX, '');
};
Utils.prototype.hasValue = function (obj, key, value) {
return obj.hasOwnProperty(key) && obj[key] === value;
};
Utils.prototype.isEquivalentURL = function (url1, url2) {
if (url1.indexOf("https") === 0 && url2.indexOf("https") === 0) {
url1 = this.getPort(url1) === 443 ? url1.replace(':443', '') : url1;
url2 = this.getPort(url2) === 443 ? url2.replace(':443', '') : url2;
}
else if (url1.indexOf("https") === -1 && url2.indexOf("https") === -1) {
url1 = this.getPort(url1) === 80 ? url1.replace(':80', '') : url1;
url2 = this.getPort(url2) === 80 ? url2.replace(':80', '') : url2;
}
return url1.indexOf(url2) === 0;
};
Utils.prototype.getPort = function (url) {
var colonIdx = url.indexOf(':', 7);
var slashIdx = url.indexOf('/', colonIdx);
if (colonIdx > 0 && slashIdx == -1) {
slashIdx = url.length;
}
var port = url.substr(colonIdx + 1, slashIdx - colonIdx - 1);
if (port && !isNaN(port * 1)) {
return port * 1;
}
else {
return -1;
}
};
/**
* Convert headers string to dictionary with lowercase keys.
* @param {string} headerStr
* @return {object}
*/
Utils.prototype.parseHeaders = function (headerStr) {
var headers = {};
if (!headerStr) {
return headers;
}
var headerPairs = headerStr.split('\u000d\u000a');
for (var i = 0, ilen = headerPairs.length; i < ilen; i++) {
var headerPair = headerPairs[i];
var index = headerPair.indexOf('\u003a\u0020');
if (index > 0) {
headers[headerPair.substring(0, index).toLowerCase()] = headerPair.substring(index + 2);
}
}
return headers;
};
Utils.prototype.normalizeHeaderKeys = function (responseHeaders) {
var headers = {};
if (this.isArray(responseHeaders)) {
var array = responseHeaders;
for (var i = 0; i < array.length; i++) {
headers[array[i].name.toLowerCase()] = array[i].value;
}
}
else {
for (var key in responseHeaders) {
if (responseHeaders.hasOwnProperty(key)) {
headers[key.toLowerCase()] = responseHeaders[key];
}
}
}
return headers;
};
;
Utils.prototype.isArray = function (arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
;
Utils.prototype.isCordova = function () {
return typeof window['cordova'] !== 'undefined';
};
/**
* Checks to see if the string ends with a suffix.
* @return {boolean}
*/
Utils.prototype.strEndsWith = function (str, suffix) {
return str.endsWith(suffix);
};
Utils.prototype.hashCode = function (input) {
return input
.split('')
.reduce(function (acc, char) {
acc = ((acc << 5) - acc) + char.charCodeAt(0);
return acc & acc;
}, 0);
};
Utils.KEY_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
Utils.SPECIAL_CHAR_RGX = /[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g;
return Utils;
}());
exports.Utils = Utils;
/***/ }),
/* 43 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*
* @author Yuri Panshin
*/
Object.defineProperty(exports, "__esModule", { value: true });
var utils_1 = __webpack_require__(42);
var logger_1 = __webpack_require__(1);
var analytics_event_1 = __webpack_require__(18);
var storage_object_1 = __webpack_require__(17);
var polices_map_1 = __webpack_require__(41);
var types = __webpack_require__(0);
/**
* Headers dictionary.
* The dictionary has lowercase header as a key and a header value as a value.
* @typedef {Object<string, string>} NetworkResponseHeaders
* @mcs
*/
/**
* Headers dictionary.
* The dictionary has header as a key and a header value as a value.
* @typedef {Object<string, string>} Headers
* @mcs
*/
/**
* Oracle mobile configuration.
* @typedef {object} OracleMobileCloudConfig
* @property [logLevel] {mcs.LOG_LEVEL} Log level for mcs logger.
* @property [logHTTP] {boolean} Include http headers and requests in log
* @property oAuthTokenEndPoint {string} OAuth token endpoint for OAth authentication
* @property [mobileBackend] {MobileBackendConfig} Mobile backend configuration
* @property [sync] {SyncConfig} Configuration for mcs synchronization
* Can't be combined with syncExpress configuration
* @property [syncExpress] {SyncExpressConfig} Configuration for sync express
* Can't be combined with sync configuration
* @property [disableAnalyticsLocation] {boolean} Disable logging location by legacy analytics
* @mcs
*/
/**
* Oracle analytics authentication configuration.
* @typedef {object} AnalyticsAuthentication
* @property oauth {OAuthConfig} Analytics notification profile name for android
* @mcs
*/
/**
* Oracle sync configuration.
* @typedef {object} SyncConfig
* @property periodicRefreshPolicy {string} Synchronization periodic refresh policy
* 'PERIODIC_REFRESH_POLICY_REFRESH_NONE'
* 'PERIODIC_REFRESH_POLICY_REFRESH_EXPIRED_ITEM_ON_STARTUP'
* 'PERIODIC_REFRESH_POLICY_PERIODICALLY_REFRESH_EXPIRED_ITEMS'
* @property policies {PolicesConfig[]} Polices per endpoint
* @mcs
*/
/**
* Oracle sync express configuration.
* @typedef {object} SyncExpressConfig
* @property [handler] {string} Handler type to process requests
* OracleRestHandler - for oracle rest endpoints
* GenericRequestHandler - for simple rest endpoints, default value
* @property policies {PolicesConfig[]} Polices per endpoint
* @mcs
*/
/**
* Oracle sync policies configuration.
* @typedef {object} PolicesConfig
* @property path {string} Path for the endpoint
* @property fetchPolicy {string} Fetch policy for current path
* 'FETCH_FROM_CACHE_SCHEDULE_REFRESH'
* 'FETCH_FROM_SERVICE_IF_ONLINE'
* 'FETCH_FROM_SERVICE'
* @mcs
*/
/**
* Oracle mobile backend configuration.
* @typedef {object} MobileBackendConfig
* @property baseUrl {string} Base url for the backend and analytics
* @property name {string} Mobile backend name
* @property authentication {AuthenticationConfig} Backend authorization's configuration
* @mcs
*/
/**
* Oracle mobile authentication configuration.
* @typedef {object} AuthenticationConfig
* @property [type] {string} Authentication default type: basic, oath, facebook, token
* @property [basic] {BasicAuthConfig} Basic authentication configuration
* @property [oauth] {OAuthConfig} OAuth authentication configuration
* @mcs:web
*/
/**
* Oracle mobile authentication configuration.
* @typedef {object} AuthenticationConfig
* @property [type] {string} Authentication default type: basic, oath, facebook, token
* @property [basic] {BasicAuthConfig} Basic authentication configuration
* @property [oauth] {OAuthConfig} OAuth authentication configuration
* @property [facebook] {FacebookAuthConfig} OAuth authentication configuration
* @property [token] {TokenExchangeAuthConfig} OAuth authentication configuration
* @mcs:cordova
*/
/**
* Oracle mobile basic authentication configuration.
* @typedef {object} BasicAuthConfig
* @property mobileBackendId {string} Mobile backend identifier
* @property anonymousKey {string} Anonymous key for anonymous authentication
* @mcs
*/
/**
* Oracle mobile oauth authentication configuration.
* @typedef {object} OAuthConfig
* @property clientId {string} OAuth client identifier
* @property clientSecret {string} OAuth client secret key
* @mcs
*/
/**
* Oracle mobile facebook authentication configuration.
* @typedef {object} FacebookAuthConfig
* @property appId {string} Facebook application identifier
* @property anonymousKey {string} Anonymous key for anonymous authentication
* @property mobileBackendId {string} Mobile backend identifier
* @property scopes {string} Facebook authentication access types:
* public_profile,user_friends,email,user_location,user_birthday
* @mcs:cordova
*/
/**
* Oracle mobile facebook authentication configuration.
* @typedef {object} TokenExchangeAuthConfig
* @property mobileBackendId {string} Mobile backend identifier
* @property anonymousKey {string} Anonymous key for anonymous authentication
* @property clientId {string} OAuth client identifier
* @property clientSecret {string} OAuth client secret key
* @mcs:cordova
*/
/**
* MCS module.
* @global
* @namespace mcs
* @private
* @mcs
*/
var MCSModule = /** @class */ (function () {
function MCSModule(cxa, _global, sync) {
if (sync === void 0) { sync = null; }
this.cxa = cxa;
this._global = _global;
this._logger = new logger_1.Logger('MCS');
this._utils = new utils_1.Utils();
/**
* Log levels enum.
* @name LOG_LEVEL
* @enum {number}
* @memberof mcs
* @readonly
* @property {number} NONE 0
* @property {number} ERROR 1
* @property {number} WARN 2
* @property {number} INFO 3
* @property {number} DEBUG 4
* @mcs
*/
this.LOG_LEVEL = logger_1.Logger.LOG_LEVEL;
/**
* Authentication types enum.
* @name AUTHENTICATION_TYPES
* @enum {string}
* @memberof mcs
* @readonly
* @instance
* @property {string} basic 'basic'
* @property {string} oauth 'oauth'
* @mcs:web
*/
/**
* Authentication types enum.
* @name AUTHENTICATION_TYPES
* @enum {string}
* @memberof mcs
* @readonly
* @instance
* @property {string} basic 'basic'
* @property {string} oauth 'oauth'
* @property {string} facebook 'facebook'
* @property {string} token 'token'
* @mcs:cordova
*/
this.AUTHENTICATION_TYPES = types.AuthenticationTypes;
/**
* Request response types enum.
* @name RESPONSE_TYPES
* @enum {string}
* @memberof mcs
* @readonly
* @instance
* @property {string} JSON 'json'
* @property {string} BLOB 'blob'
* @property {string} ARRAY_BUFFER 'arraybuffer'
* @property {string} DOCUMENT 'document'
* @property {string} TEXT 'text'
* @mcs:web
*/
this.RESPONSE_TYPES = types.XMLHttpRequestResponseTypes;
/**
* Http methods enum.
* @name HTTP_METHODS
* @enum {string}
* @memberof mcs
* @readonly
* @instance
* @property {string} GET 'GET'
* @property {string} PUT 'PUT'
* @property {string} PATCH 'PATCH'
* @property {string} POST 'POST'
* @property {string} DELETE 'DELETE'
* @property {string} HEAD 'HEAD'
* @mcs:web
*/
this.HTTP_METHODS = types.HttpMethods;
/**
* Notification providers.
* @name NOTIFICATION_PROVIDERS
* @enum {string}
* @memberof mcs
* @readonly
* @instance
* @property {string} FCM 'FCM'
* @property {string} APNS 'APNS'
* @property {string} WNS 'WNS'
* @property {string} SYNIVERSE 'SYNIVERSE'
* @mcs:cordova
* @mcs:react-native
*/
this.NOTIFICATION_PROVIDERS = types.NotificationProviders;
/**
* Get list of latest log entries.
* @return {Array} - last log entries
* @private
* @ignore
*/
this._getLogHistory = function () { return logger_1.Logger.history; };
/**
* Storage object constructor.
* Access point to class that represents a storage object resource that can be used to store data.
* @function
* @name mcs.StorageObject
* @memberOf mcs
* @param storageCollection {StorageCollection}
* @param [json] {Object} Json storage object representation
* @returns {StorageObject}
* @mcs
*/
this.StorageObject = storage_object_1.StorageObject;
/**
* Analytics event constructor.
* Creates analytics event.
* @function
* @name mcs.AnalyticsEvent
* @memberOf mcs
* @param {string} name - event name
* @type {AnalyticsEvent}
* @mcs
*/
this.AnalyticsEvent = analytics_event_1.AnalyticsEvent;
this._syncExpress = sync || (typeof _global.mcs !== 'undefined' ? _global.mcs.sync : null);
}
MCSModule.prototype._init = function (config) {
this._config = Object.assign({
mcsVersion: '18.3.3.0',
}, config);
if (typeof config.logLevel !== 'undefined') {
logger_1.Logger.logLevel = config.logLevel;
}
else {
logger_1.Logger.logLevel = this.LOG_LEVEL.NONE;
}
var internalConfig = this._config;
this._logger.debug('MCS initialization, version', internalConfig.mcsVersion);
if (typeof internalConfig.logHistoryEnabled !== 'undefined') {
logger_1.Logger.historyEnabled = internalConfig.logHistoryEnabled;
}
if (typeof internalConfig.logHistorySize !== 'undefined') {
logger_1.Logger.historySize = internalConfig.logHistorySize;
}
if (typeof internalConfig.mcsGlobal !== 'undefined' && internalConfig.mcsGlobal) {
this._global.mcs = Object.assign({}, this._global.mcs, this);
}
if (!!this._syncExpress) {
this._initPersistenceConfiguration(config);
}
else if (config.sync || config.syncExpress) {
this._logger.warn('Sync script was not included on page, switch caching off');
}
this._platform = this._getPlatform(this._config, this._utils);
};
MCSModule.prototype._initPersistenceConfiguration = function (config) {
var syncExpress = this._syncExpress;
syncExpress._setLogLevel(config.logLevel);
syncExpress._setLogHTTP(config.logHTTP);
var syncConfig = null;
if (config.sync && config.syncExpress) {
throw Error('Configuration contains two types synchronisation, ' +
'please choose one of those types, switch caching off');
}
else if (config.sync) {
syncConfig = config.sync;
syncExpress.setHandler(syncExpress.SYNC_REQUEST_HANDLER_TYPE.MCS);
}
else if (config.syncExpress) {
syncConfig = config.syncExpress;
var isOracleRestHandler = config.syncExpress.handler &&
config.syncExpress.handler === 'OracleRestHandler';
syncExpress.setHandler(isOracleRestHandler ? syncExpress.SYNC_REQUEST_HANDLER_TYPE.ORACLE_REST :
syncExpress.SYNC_REQUEST_HANDLER_TYPE.GENERIC);
}
else {
this._logger.warn('Missing synchronization configuration, switch caching off');
syncExpress.options.switchOff();
return;
}
syncExpress.options.switchOff(false);
var persistenceConfig = {
default: {
conflictResolutionPolicy: 'CLIENT_WINS',
expirationPolicy: 'NEVER_EXPIRE',
expireAfter: 600,
evictionPolicy: 'MANUAL_EVICTION',
fetchPolicy: 'FETCH_FROM_SERVICE_IF_ONLINE',
updatePolicy: 'QUEUE_IF_OFFLINE',
noCache: false,
},
periodicRefreshInterval: syncConfig.backgroundRefreshPolicy || 120,
policies: [],
periodicRefreshPolicy: null,
};
var mcsPolicies = syncConfig.policies;
for (var idx in mcsPolicies) {
if (mcsPolicies.hasOwnProperty(idx)) {
var policy = mcsPolicies[idx];
if (policy) {
persistenceConfig.policies.push(this._getPersistencePolicy(policy));
}
else {
this._logger.error('The ' + policy + 'policy was not found in accepted policies.');
}
}
}
syncExpress.options.setPolicies(persistenceConfig);
syncExpress.options.dbFirst = false;
syncExpress.options.setMaxSyncAttempts(1);
syncExpress.options.setAutoRemoveAfterReachMaxAttempts(true);
};
MCSModule.prototype._getPersistencePolicy = function (mcsPolicy) {
var policy = { path: mcsPolicy.path };
for (var prop in mcsPolicy) {
if (mcsPolicy.hasOwnProperty(prop) && prop !== 'path') {
var persMap = polices_map_1.POLICIES_MAP[prop];
if (!persMap) {
this._logger.error('The ' + prop + ' policy was not found in accepted policies.');
}
else if (persMap[mcsPolicy[prop]] === undefined) {
this._logger.error('The ' +
prop +
' policy value ' +
mcsPolicy[prop] +
' was not found in accepted policy values.');
}
else {
policy[persMap.persistencePropertyName] = persMap[mcsPolicy[prop]];
}
}
}
return policy;
};
return MCSModule;
}());
exports.MCSModule = MCSModule;
/***/ }),
/* 44 */,
/* 45 */,
/* 46 */,
/* 47 */,
/* 48 */,
/* 49 */,
/* 50 */,
/* 51 */,
/* 52 */,
/* 53 */,
/* 54 */,
/* 55 */,
/* 56 */,
/* 57 */,
/* 58 */,
/* 59 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var platform_1 = __webpack_require__(21);
var logger_1 = __webpack_require__(1);
var types_1 = __webpack_require__(0);
/**
* Platform class for browser applications.
* @extends Platform
* @private
*/
var BrowserPlatform = /** @class */ (function (_super) {
__extends(BrowserPlatform, _super);
function BrowserPlatform(config, utils, navigator, logger) {
var _this = _super.call(this, config, utils, logger || new logger_1.Logger('BrowserPlatform')) || this;
_this._navigator = navigator;
_this.isBrowser = true;
_this.isCordova = false;
return _this;
}
BrowserPlatform.prototype.getDevicePlatform = function () {
var userAgent = this._navigator.userAgent;
if (userAgent.match(/Android/i)) {
return types_1.DevicePlatforms.ANDROID;
}
if (userAgent.match(/iPad/i) ||
userAgent.match(/iPod/i) ||
userAgent.match(/iPhone/i)) {
return types_1.DevicePlatforms.IOS;
}
};
BrowserPlatform.prototype._getPlatform = function () {
return types_1.PlatformNames.JAVASCRIPT;
};
return BrowserPlatform;
}(platform_1.Platform));
exports.BrowserPlatform = BrowserPlatform;
/***/ }),
/* 60 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var authorization_1 = __webpack_require__(4);
var logger_1 = __webpack_require__(1);
var network_response_1 = __webpack_require__(2);
var mcs_authorization_1 = __webpack_require__(7);
var types_1 = __webpack_require__(0);
var dictionary_1 = __webpack_require__(3);
/**
* @class
* @global
* @classdesc Class used to authorize a mobile user against Oracle Mobile Hub
* with External Token Authentication security schema. Callers should use
* MobileBackend's [authorization]{@link Backend#authorization} property.
* @extends MCSAuthorization
* @hideconstructor
* @mcs
*/
var ExternalTokenExchangeAuthorization = /** @class */ (function (_super) {
__extends(ExternalTokenExchangeAuthorization, _super);
function ExternalTokenExchangeAuthorization(config, backend, utils, platform) {
var _this = _super.call(this, backend, utils, platform) || this;
_this.logger = new logger_1.Logger('ExternalTokenExchangeAuthorization');
_this._backendId = utils.validateConfiguration(config.mobileBackendId);
_this._anonymousToken = utils.validateConfiguration(config.anonymousKey);
_this._clientId = utils.validateConfiguration(config.clientId);
_this._clientSecret = utils.validateConfiguration(config.clientSecret);
return _this;
}
/**
* Authenticates.
* Authenticates a user with the given external token. The user remains logged in until logout() is called.
* @param token {String} The third party authentication token.
* @return {Promise<NetworkResponse>}
* @function
* @name ExternalTokenExchangeAuthorization#authenticate
* @mcs
*/
ExternalTokenExchangeAuthorization.prototype.authenticate = function () {
var _this = this;
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
var token = params[0];
if (!token || typeof token !== 'string' || token.length === 0) {
this.logger.error('Wrong token parameter');
return Promise.reject(new network_response_1.NetworkResponse(400, 'Bad Request'));
}
this.logout();
this._redToken = token;
var useOldAPI = false, useNewAPI = false;
if (token.indexOf('.') === -1) {
useOldAPI = true;
}
else {
var audiences = this.getAudience(token);
if (!audiences) {
return Promise.reject(new network_response_1.NetworkResponse(400, 'Bad Request'));
}
for (var _a = 0, audiences_1 = audiences; _a < audiences_1.length; _a++) {
var audience = audiences_1[_a];
if (audience === this.backend.getPlatformUrl('sso') ||
audience === this.backend.getPlatformUrl('sso/exchange-token')) {
useOldAPI = true;
}
else if (audience === this.backend.getPlatformUrl('auth') ||
audience === this.backend.getPlatformUrl('auth/token')) {
useNewAPI = true;
}
}
}
console.log(useOldAPI, useNewAPI);
if (useOldAPI && !useNewAPI) {
return this.authenticateOldAPI(token)
.then(invokeServiceSuccess.bind(this))
.catch(invokeServiceError.bind(this));
}
else if (useNewAPI && !useOldAPI) {
return this.authenticateNewAPI(token)
.then(invokeServiceSuccess.bind(this))
.catch(invokeServiceError.bind(this));
}
else {
return this.authenticateOldAPI(token)
.then(invokeServiceSuccess.bind(this))
.catch(function (response) {
if (response.statusCode === 0 || response.statusCode === 401) {
return _this.authenticateNewAPI(token);
}
else {
return Promise.reject(response);
}
})
.then(invokeServiceSuccess.bind(this))
.catch(invokeServiceError.bind(this));
}
function invokeServiceSuccess(response) {
this._tokenExpiredTime = Date.now() + response.data.expires_in * 1000;
this._authenticateSuccess(response, response.data.access_token);
return new network_response_1.NetworkResponse(200, response.data);
}
function invokeServiceError(response) {
this._authenticateError(response);
return Promise.reject(response);
}
};
ExternalTokenExchangeAuthorization.prototype.getAudience = function (token) {
var audience;
try {
var base64BodyPart = token.split('.')[1];
var bodyPart = this.utils.decodeBase64(base64BodyPart);
bodyPart = this.utils.clearNonPrintableCharacters(bodyPart);
var tokenBody = JSON.parse(bodyPart);
audience = tokenBody['aud'];
if (typeof audience === 'string') {
audience = [audience];
}
}
catch (error) {
this.logger.error('Token has wrong format', error);
}
return audience;
};
ExternalTokenExchangeAuthorization.prototype.authenticateOldAPI = function (token) {
console.log('authenticateOldAPI');
var authorizationToken = 'Bearer ' + token;
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.AUTHORIZATION, authorizationToken);
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return this.platform.invokeService({
headers: headers,
url: this.backend.getPlatformUrl('sso/exchange-token?format=json'),
method: types_1.HttpMethods.GET,
module: types_1.ModuleNames.AUTHORIZATION,
});
};
ExternalTokenExchangeAuthorization.prototype.authenticateNewAPI = function (token) {
console.log('authenticateNewAPI');
var authorizationToken = 'Basic ' + this.utils.encodeBase64(this._clientId + ':' + this._clientSecret);
var data = this.getData(token);
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.AUTHORIZATION, authorizationToken);
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.X_WWW_FORM_FORM_URLENCODED);
return this.platform.invokeService({
headers: headers,
data: data,
url: this.backend.getPlatformUrl('auth/token'),
method: types_1.HttpMethods.POST,
module: types_1.ModuleNames.AUTHORIZATION,
});
};
ExternalTokenExchangeAuthorization.prototype.getData = function (token) {
return 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=' + encodeURIComponent(token);
};
/**
* Authenticates anonymous.
* Authenticates an anonymous user against the service. The user remains logged in until logout() is called.
* @return {Promise<NetworkResponse>}
* @function
* @name ExternalTokenExchangeAuthorization#authenticateAnonymous
* @mcs
*/
ExternalTokenExchangeAuthorization.prototype.authenticateAnonymous = function () {
return this._authenticateAnonymousInvoke(new dictionary_1.Dictionary([]), this.backend.getPlatformUrl('users/login'), types_1.HttpMethods.GET);
};
ExternalTokenExchangeAuthorization.prototype._getAnonymousAuthorizationHeaders = function (headers) {
headers.add(types_1.Headers.AUTHORIZATION, 'Basic ' + this._anonymousToken);
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return headers;
};
/**
* Is authentication token valid.
* Checks to see if the OAuth token is null,undefined,NaN,empty string (''),0,false and also checks the timestamp
* of when the token was first retrieved to see if it was still valid.
* @returns {Boolean}
* @function
* @name ExternalTokenExchangeAuthorization#isTokenValid
* @mcs
*/
ExternalTokenExchangeAuthorization.prototype.isTokenValid = function () {
if (this.getAccessToken() || this._getAnonymousAccessToken()) {
this.logger.debug('Token is not null or empty');
var currentTime = Date.now();
if (currentTime >= this._tokenExpiredTime) {
this.logger.info('Token has expired or user has not been authenticate with the service');
return false;
}
else {
this.logger.debug('Token is still valid');
return true;
}
}
else {
return false;
}
};
/**
* Logs out the current user and clears credentials and tokens and cookies.
* @function
* @name ExternalTokenExchangeAuthorization#logout
* @mcs
*/
ExternalTokenExchangeAuthorization.prototype.logout = function () {
this._redToken = null;
this._clearState();
};
/**
* Refreshes the authentication token if it has expired. The authentication scheme should support refresh.
* @return {Promise<String>}
* @function
* @name ExternalTokenExchangeAuthorization#refreshToken
* @mcs
*/
ExternalTokenExchangeAuthorization.prototype.refreshToken = function () {
var boolean = this.isTokenValid();
if (boolean !== false) {
if (this._accessToken == null && this._isAnonymous) {
this.logger.error('Anonymous token is valid, you do not need to refresh.');
return Promise.resolve(this._anonymousAccessToken);
}
if (!this._anonymousAccessToken && !this._isAnonymous) {
this.logger.error('Authenticated token is valid, you do not need to refresh.');
return Promise.resolve(this._accessToken);
}
}
else {
this.logger.error('Token is not valid and has expired, refreshing token from service.', this._redToken);
return this.authenticate(this._redToken).then(function () {
return this._accessToken;
});
}
};
ExternalTokenExchangeAuthorization.prototype._getHttpHeaders = function (headers) {
if (this.getAccessToken() !== null && typeof this.getAccessToken() == 'string') {
headers.add(types_1.Headers.AUTHORIZATION, 'Bearer ' + this.getAccessToken());
}
return headers;
};
ExternalTokenExchangeAuthorization.prototype._getAnonymousHttpHeaders = function (headers) {
if (this._getAnonymousAccessToken() && typeof this._getAnonymousAccessToken() == 'string') {
headers.add(types_1.Headers.AUTHORIZATION, 'Bearer ' + this._getAnonymousAccessToken());
}
return headers;
};
ExternalTokenExchangeAuthorization.prototype._anonymousTokenResponseConverter = function (response) {
return new authorization_1.AuthenticationResponse(response, response.data.access_token);
};
return ExternalTokenExchangeAuthorization;
}(mcs_authorization_1.MCSAuthorization));
exports.ExternalTokenExchangeAuthorization = ExternalTokenExchangeAuthorization;
/***/ }),
/* 61 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var authorization_1 = __webpack_require__(4);
var logger_1 = __webpack_require__(1);
var network_response_1 = __webpack_require__(2);
var mcs_authorization_1 = __webpack_require__(7);
var dictionary_1 = __webpack_require__(3);
var types_1 = __webpack_require__(0);
/**
* @classdesc Class used to authorize a user against Facebook and use the OAuth token from Facebook
* to authenticate against Oracle Mobile Hub. Callers should use
* MobileBackend's [authorization]{@link Backend#authorization} property.
* @class
* @global
* @extends MCSAuthorization
* @hideconstructor
* @mcs:cordova
*/
var FacebookAuthorization = /** @class */ (function (_super) {
__extends(FacebookAuthorization, _super);
function FacebookAuthorization(config, backend, utils, platform) {
var _this = _super.call(this, backend, utils, platform) || this;
_this.expiredTime = null;
_this.logger = new logger_1.Logger('FacebookAuthorization');
_this._backendId = utils.validateConfiguration(config.mobileBackendId);
_this._anonymousToken = utils.validateConfiguration(config.anonymousKey);
_this._facebookAppId = utils.validateConfiguration(config.appId);
_this._scopes = utils.validateConfiguration(config.scopes);
return _this;
}
/**
* Get facebook application id.
* Returns the Facebook Application Id token for the current backend.
* @return {String}
* @function
* @name FacebookAuthorization#getFacebookAppId
* @mcs
*/
FacebookAuthorization.prototype.getFacebookAppId = function () {
return this._facebookAppId;
};
/**
* Authenticates a user against Facebook. The user remains logged in until logout() is called.
* In the Facebook Developer console you must define the domain that the application will use.
* in the Facebook Developer UI, When you add a platform for the application,
* you choose Website and set the site URL to http://localhost/.
* @return {Promise<NetworkResponse>}
* @function
* @name FacebookAuthorization#authenticate
* @mcs
*/
FacebookAuthorization.prototype.authenticate = function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
this.logout();
if (typeof cordova !== 'undefined') {
var metadata = cordova.require('cordova/plugin_list').metadata;
if (this.isInAppBrowserInstalled(metadata) !== true) {
return Promise.reject(new network_response_1.NetworkResponse(100, 'Could not find InAppBrowser plugin, use command "cordova plugin add cordova-plugin-inappbrowser"'));
}
else {
return this._authenticateInvoke();
}
}
else {
return Promise.reject(new network_response_1.NetworkResponse(400, 'Bad Request - This method require Cordova framework'));
}
};
FacebookAuthorization.prototype._authenticateInvoke = function () {
return new Promise(invoke.bind(this))
.then(invokeSuccess.bind(this))
.catch(invokeError.bind(this));
function invoke(resolve, reject) {
var _this = this;
var clientId = this.getFacebookAppId();
var redirectUri = 'http://localhost/callback';
var flowUrl = true ? this.scopes : undefined;
var browserRef = window.open(flowUrl, '_blank', 'location=no,clearsessioncache=yes,clearcache=yes');
browserRef.show();
this.logger.info('Opening InAppBrowser to url: ' + flowUrl);
browserRef.addEventListener('loadstart', function (event) {
if ((event.url).indexOf(redirectUri) === 0) {
browserRef.close();
var callbackResponse = (event.url).split('#')[1];
var responseParameters = (callbackResponse).split('&');
var socialToken = {};
for (var i = 0; i < responseParameters.length; i++) {
socialToken[responseParameters[i].split('=')[0]] = responseParameters[i].split('=')[1];
}
if (socialToken.access_token) {
_this.expiredTime = Date.now() + socialToken.expires_in * 1000;
resolve(new network_response_1.NetworkResponse(200, socialToken));
}
else {
if ((event.url).indexOf('error_code=100') !== 0 && !_this.isAuthorized) {
reject(new network_response_1.NetworkResponse(100, 'Cannot authenticate via a web browser'));
}
}
}
});
browserRef.addEventListener('exit', function () {
if (!_this._getIsAuthorized()) {
reject(new network_response_1.NetworkResponse(100, 'Cannot authenticate via a web browser'));
}
});
}
function invokeSuccess(response) {
this._authenticateSuccess(response, response.data.access_token);
this.expiredTime = Date.now() + response.data.expires_in * 1000;
return response;
}
function invokeError(response) {
this._authenticateError(response);
return Promise.reject(response);
}
};
/**
* Authenticate anonymous.
* Authenticates an anonymous user against the service. The user remains logged in until logout() is called.
* @return {Promise<NetworkResponse>}
* @function
* @name FacebookAuthorization#authenticateAnonymous
* @mcs
*/
FacebookAuthorization.prototype.authenticateAnonymous = function () {
return this._authenticateAnonymousInvoke(new dictionary_1.Dictionary([]), this.backend.getPlatformUrl('users/login'), types_1.HttpMethods.GET);
};
FacebookAuthorization.prototype._getAnonymousAuthorizationHeaders = function (headers) {
headers.add(types_1.Headers.AUTHORIZATION, 'Basic ' + this._anonymousToken);
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return headers;
};
FacebookAuthorization.prototype._anonymousTokenResponseConverter = function (response) {
return new authorization_1.AuthenticationResponse(response, 'Basic ' + this._anonymousToken);
};
/**
* Checks to see if the OAuth token is null, undefined, NaN, AN empty string (''), 0, or false.
* It also checks the timestamp
* for when the token was first retrieved to see if it was still valid.
* @returns {Boolean}
* @function
* @name FacebookAuthorization#isTokenValid
* @mcs
*/
FacebookAuthorization.prototype.isTokenValid = function () {
if (this.getAccessToken() || this._getAnonymousAccessToken()) {
this.logger.debug('Token is not null or empty');
var currentTime = Date.now();
if (currentTime >= this.expiredTime) {
this.logger.info('Token has expired or user has not been authenticate with the service/Facebook');
return false;
}
else {
this.logger.debug('Token is still valid');
return true;
}
}
else {
return false;
}
};
/**
* Refreshes the authentication token if it has expired from Facebook.
* The authentication scheme should support refresh.
* @return {Promise<NetworkResponse>}
* @function
* @name FacebookAuthorization#refreshToken
* @mcs
*/
FacebookAuthorization.prototype.refreshToken = function () {
var isTokenValid = this.isTokenValid();
if (isTokenValid && this.getAccessToken() == null && this._getIsAnonymous()) {
return Promise.resolve(new network_response_1.NetworkResponse(200, this._getAnonymousAccessToken()));
}
else if (isTokenValid && this._getAnonymousAccessToken() && !this._getIsAnonymous()) {
return Promise.resolve(new network_response_1.NetworkResponse(200, this._getAnonymousAccessToken()));
}
else {
this.logger.error('Token is not valid and has expired, refreshing token from Facebook.');
return this.authenticate();
}
};
/**
* Logs out the current user and clears credentials and tokens.
* @function
* @name FacebookAuthorization#logout
* @mcs
*/
FacebookAuthorization.prototype.logout = function () {
this._clearState();
this.expiredTime = Date.now() * 1000;
};
FacebookAuthorization.prototype._getHttpHeaders = function (headers) {
if (this.getAccessToken() != null && typeof this.getAccessToken() == 'string') {
headers.add(types_1.Headers.AUTHORIZATION, 'Basic ' + this._anonymousToken);
headers.add(types_1.Headers.ORACLE_MOBILE_SOCIAL_ACCESS_TOKEN, this.getAccessToken());
headers.add(types_1.Headers.ORACLE_MOBILE_SOCIAL_IDENTITY_PROVIDER, 'facebook');
}
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return headers;
};
FacebookAuthorization.prototype._getAnonymousHttpHeaders = function (headers) {
if (this._getAnonymousAccessToken() && typeof this._getAnonymousAccessToken() == 'string') {
headers.add(types_1.Headers.AUTHORIZATION, this._getAnonymousAccessToken());
}
headers.add(types_1.Headers.ORACLE_MOBILE_BACKEND_ID, this._backendId);
return headers;
};
/**
* Checks to see if the correct plugin is installed into the application.
* @return {boolean}
* @private
*/
FacebookAuthorization.prototype.isInAppBrowserInstalled = function (metadata) {
var inAppBrowserNames = ['cordova-plugin-inappbrowser', 'org.apache.cordova.inappbrowser'];
return inAppBrowserNames.some(function (name) { return metadata.hasOwnProperty(name); });
};
return FacebookAuthorization;
}(mcs_authorization_1.MCSAuthorization));
exports.FacebookAuthorization = FacebookAuthorization;
/***/ }),
/* 62 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
Object.defineProperty(exports, "__esModule", { value: true });
var logger_1 = __webpack_require__(1);
var types_1 = __webpack_require__(0);
/**
* @classdesc Class that provides notification capabilities. Callers should use
* MobileBackend's [notifications]{@link Backend#notifications} property.
* @class
* @hideconstructor
* @global
* @mcs
*/
var Notifications = /** @class */ (function () {
function Notifications(backend, platform) {
this.backend = backend;
this.platform = platform;
this.logger = new logger_1.Logger('Notifications');
}
/**
* Returns a string with device information used by [Notifications]{@link Notifications}
* @function
* @name Notifications#getDevicePlatform
* @returns {String} The device specific information for platform.
* @example : 'IOS', 'ANDROID'
* @mcs
*/
Notifications.prototype.getDevicePlatform = function () {
return this.platform.getDevicePlatform().toUpperCase();
};
/**
* Registers the current Cordova app running on the device for receiving push notifications.
* @function
* @name Notifications#registerForNotifications
* @param deviceToken {String} Platform-specific device token.
* @param packageName {String} Platform-specific application reverse domain identifier.
* @param appVersion {String} Application version.
* @param notificationProvider {String} The provider to register, possible values: 'APNS', 'FCM', 'WNS', 'SYNIVERSE'.
* @return {Promise<NetworkResponse>}
*
* @example <caption>Example usage of mcs.mobileBackend.notifications.registerForNotifications()</caption>
* mcs.mobileBackend
* .notifications
* .registerForNotifications('YOUR_DEVICE_TOKEN', 'com.yourcompany.project', '1.0.0', 'FCM')
* .then(registerSuccess)
* .catch(registerError);
*
* function registerSuccess(response){
* console.log(response);
* }
*
* function registerError(response){
* console.error(response);
* }
* @mcs
*/
Notifications.prototype.registerForNotifications = function (deviceToken, packageName, appVersion, notificationProvider) {
if (!(notificationProvider in types_1.NotificationProviders)) {
throw Error(this._getProviderError(notificationProvider));
}
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.APPLICATION_JSON);
var platform = this.platform.getDevicePlatform().toUpperCase();
var payload = {
notificationProvider: notificationProvider,
notificationToken: deviceToken,
mobileClient: {
platform: platform,
id: packageName,
version: appVersion,
},
};
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.POST,
url: this.backend.getPlatformUrl('devices/register'),
data: JSON.stringify(payload),
module: types_1.ModuleNames.NOTIFICATIONS,
})
.then(invokeServiceSuccess.bind(this))
.catch(invokeServiceError.bind(this));
function invokeServiceSuccess(response) {
this.logger.info('Device registered for push notifications.', response.statusCode);
return response;
}
function invokeServiceError(response) {
this.logger.error('Device registration for push notifications failed!', response);
return Promise.reject(response);
}
};
/**
* Deregisters the current Cordova app running on the device for receiving push notifications.
* @function
* @name Notifications#deregisterForNotifications
* @param deviceToken {String} Platform-specific success callback token.
* @param packageName {String} Platform-specific application reverse domain identifier.
* @param notificationProvider {String} The provider to register, posible values: 'APNS', 'FCM', 'WNS', 'SYNIVERSE'.
* @return {Promise<NetworkResponse>}
*
* @example <caption>Example usage of mcs.mobileBackend.notifications.deregisterForNotifications()</caption>
* mcs.mobileBackend
* .notifications
* .deregisterForNotifications('YOUR_DEVICE_TOKEN', 'com.yourcompany.project', '1.0.0', 'FCM')
* .then(deregisterSuccess)
* .catch(deregisterError);
*
* function deregisterSuccess(response){
* console.log(response);
* }
*
* function deregisterError(response){
* console.log(response);
* }
* @mcs
*/
Notifications.prototype.deregisterForNotifications = function (deviceToken, packageName, notificationProvider) {
if (!(notificationProvider in types_1.NotificationProviders)) {
throw Error(this._getProviderError(notificationProvider));
}
var headers = this.backend.getHttpHeaders();
headers.add(types_1.Headers.CONTENT_TYPE, types_1.ContentTypes.APPLICATION_JSON);
var platform = this.platform.getDevicePlatform().toUpperCase();
var payload = {
notificationProvider: notificationProvider,
notificationToken: deviceToken,
mobileClient: {
platform: platform,
id: packageName,
},
};
return this.platform.invokeService({
headers: headers,
method: types_1.HttpMethods.POST,
url: this.backend.getPlatformUrl('devices/deregister'),
data: JSON.stringify(payload),
module: types_1.ModuleNames.NOTIFICATIONS,
})
.then(invokeServiceSuccess.bind(this))
.catch(invokeServiceError.bind(this));
function invokeServiceSuccess(response) {
this.logger.info('Device deregistered for push notifications succeeded.', response.statusCode);
return response;
}
function invokeServiceError(response) {
this.logger.error('Device deregistration for push notifications failed!', response);
return Promise.reject(response);
}
};
Notifications.prototype._getProviderError = function (notificationProvider) {
return 'No Notification Provider Type called ' + notificationProvider + '\n' +
'please use one of those types\n' +
types_1.NotificationProviders.APNS + '\n' +
types_1.NotificationProviders.FCM + '\n' +
types_1.NotificationProviders.WNS + '\n' +
types_1.NotificationProviders.SYNIVERSE;
};
return Notifications;
}());
exports.Notifications = Notifications;
/***/ }),
/* 63 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var notifications_1 = __webpack_require__(62);
var backend_1 = __webpack_require__(40);
var types_1 = __webpack_require__(0);
var facebook_authorization_1 = __webpack_require__(61);
var external_token_exchange_authorization_1 = __webpack_require__(60);
/**
* @classdesc Represents a mobile backend in Oracle Mobile Hub
* and provides access to all capabilities of the backend.
* Callers should use [mcs's mobileBackend]{@link mcs#mobileBackend} property...
* @class
* @hideconstructor
* @extends Backend
* @global
* @mcs
*/
var MobileBackend = /** @class */ (function (_super) {
__extends(MobileBackend, _super);
/**
* Should not be called by the user.
* @protected
* @param config
* @param platform
* @param utils
* @param syncExpress
* @mcs
*/
function MobileBackend(config, platform, utils, syncExpress) {
var _this = _super.call(this, config, platform, utils, syncExpress) || this;
/**
* Get notifications object.
* Returns the Notifications object that provides notification capabilities.
* @readonly
* @type {Notifications}
* @name MobileBackend#notifications
* @mcs
*/
_this.notifications = null;
_this.notifications = new notifications_1.Notifications(_this, platform);
_this._authenticationTypes = _this.platform._getPlatform() === types_1.PlatformNames.REACT_NATIVE
? Object.assign({}, types_1.AuthenticationTypes, { facebook: undefined })
: types_1.AuthenticationTypes;
return _this;
}
/**
* Set authentication type for mobile backend.
* Initialize and returns the Authorization object that provides
* authorization capabilities and access to user properties.
* @function
* @name MobileBackend#setAuthenticationType
* @param {string} type
* For [Basic authentication]{@link BasicAuthorization}, you would specify "basic"
* to use the Basic Authentication security schema.<br/>
* For [OAuth authentication]{@link OAuthAuthorization}, you would specify "oauth"
* to use OAuth Authentication security schema.<br/>
* For [External token authentication]{@link ExternalTokenExchangeAuthorization},
* you would specify "token" to use External Token Authentication security schema.
* @return {Authorization}
* @throws When unrecognized authentication type provided,
* this method will throw an Exception stating that the type of Authentication that you provided
* is not supported at this time.
* @example <caption>Example usage of mobileBackend.setAuthenticationType()</caption>
* // Basic Authorization schema
* mcs.mobileBackend.setAuthenticationType("basic");
* @example // OAuth Authorization schema
* mcs.mobileBackend.setAuthenticationType("oauth");
* @example // Token Exchange Authorization schema
* mcs.mobileBackend.setAuthenticationType("token");
* @mcs:react-native
*/
/**
* Set authentication type for mobile backend.
* Initialize and returns the Authorization object that provides
* authorization capabilities and access to user properties.
* @function
* @name MobileBackend#setAuthenticationType
* @param {string} type
* For [Basic authentication]{@link BasicAuthorization}, you would specify "basic"
* to use the Basic Authentication security schema.<br/>
* For [OAuth authentication]{@link OAuthAuthorization}, you would specify "oauth"
* to use OAuth Authentication security schema.<br/>
* For [External token authentication]{@link ExternalTokenExchangeAuthorization},
* you would specify "token" to use External Token Authentication security schema.<br/>
* For [Facebook authentication]{@link FacebookAuthorization}, you would specify "facebook"
* to use Facebook Authentication security schema.
* @return {Authorization}
* @throws When unrecognized authentication type provided,
* this method will throw an Exception stating that the type of Authentication that you provided
* is not supported at this time.
* @example <caption>Example usage of mobileBackend.setAuthenticationType()</caption>
* // Basic Authorization schema
* mcs.mobileBackend.setAuthenticationType("basic");
* @example // OAuth Authorization schema
* mcs.mobileBackend.setAuthenticationType("oauth");
* @example // Facebook Authorization schema
* mcs.mobileBackend.setAuthenticationType("facebook");
* @example // Token Exchange Authorization schema
* mcs.mobileBackend.setAuthenticationType("token");
* @mcs:cordova
*/
MobileBackend.prototype.setAuthenticationType = function (type) {
var authType = this.utils.validateConfiguration(type);
this.authorization = _super.prototype.setAuthenticationType.call(this, authType);
if (!this.authorization) {
if (this.authorization && this.authorization._getIsAuthorized()) {
this.authorization.logout();
}
if (authType === types_1.AuthenticationTypes.facebook) {
var config = this._mbeConfig.authentication.facebook;
this.authorization = new facebook_authorization_1.FacebookAuthorization(config, this, this.utils, this.platform);
this._logger.info('Your Authentication type: ' + authType);
this._authenticationType = authType;
}
else if (authType === types_1.AuthenticationTypes.token) {
var config = this._mbeConfig.authentication.token;
this.authorization = new external_token_exchange_authorization_1.ExternalTokenExchangeAuthorization(config, this, this.utils, this.platform);
this._logger.info('Your Authentication type: ' + authType);
this._authenticationType = authType;
}
}
return this.authorization;
};
return MobileBackend;
}(backend_1.Backend));
exports.MobileBackend = MobileBackend;
/***/ }),
/* 64 */,
/* 65 */,
/* 66 */,
/* 67 */,
/* 68 */,
/* 69 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
*/
var browser_platform_1 = __webpack_require__(59);
var logger_1 = __webpack_require__(1);
var types_1 = __webpack_require__(0);
/**
* Platform class for Cordova applications.
* Require console, geolocation, PushPlugin, sqlite-storage and device plugins to be installed.
* @extends BrowserPlatform
* @private
*/
var CordovaPlatform = /** @class */ (function (_super) {
__extends(CordovaPlatform, _super);
function CordovaPlatform(config, utils, navigator, logger) {
var _this = _super.call(this, config, utils, navigator, logger || new logger_1.Logger('CordovaPlatform')) || this;
_this.ANDROID_OS_NAME = 'Android';
_this.IOS_OS_NAME = 'iOS';
_this._latitude = null;
_this._longitude = null;
_this._gpsLocationInitialised = false;
_this.isBrowser = false;
_this.isCordova = true;
return _this;
}
CordovaPlatform.prototype.initGPSLocation = function () {
this._logger.debug('Subscribe to location changes');
if (!this._gpsLocationInitialised) {
if (!!this._navigator &&
!!this._navigator.geolocation &&
this._navigator.geolocation.watchPosition &&
this._navigator.geolocation.getCurrentPosition) {
document.addEventListener('deviceready', deviceReady.bind(this), false);
}
else {
this._logger.warn('This browser does not support geolocation.');
}
this._gpsLocationInitialised = true;
}
function setPosition(position) {
this.latitude = position.coords.latitude;
this.longitude = position.coords.longitude;
}
function onError(error) {
this.logger.error('Error while subscribing for position.', error);
}
function deviceReady() {
this._navigator.geolocation.getCurrentPosition(setPosition, onError);
this._navigator.geolocation.watchPosition(setPosition, onError);
}
};
/**
* Overrides [Platform.getGPSLocation()]{@link Platform#getGPSLocation}
* @override
*/
CordovaPlatform.prototype.getGPSLocation = function () {
return {
latitude: this._latitude,
longitude: this._longitude,
};
};
/**
* Checks the current state of the device. Platform implementations should call this function
* when the state changes. The state is inspected before background operations
* like synchronization are performed.
* Cordova Network Information Plugin MUST be installed for this function to operate.
* `cordova plugin add cordova-plugin-network-information`
*/
CordovaPlatform.prototype.checkConnection = function () {
var networkState = this._navigator['connection'].type;
var states = {};
states[Connection.UNKNOWN] = 'Unknown connection';
states[Connection.ETHERNET] = 'Ethernet connection';
states[Connection.WIFI] = 'WiFi connection';
states[Connection.CELL_2G] = 'Cell 2G connection';
states[Connection.CELL_3G] = 'Cell 3G connection';
states[Connection.CELL_4G] = 'Cell 4G connection';
states[Connection.CELL] = 'Cell generic connection';
states[Connection.NONE] = 'No network connection';
this._logger.info('Connection type: ' + states[networkState]);
return states[networkState];
};
/**
@return Returns an object of variables used to return device specific information like:
@return model: Nexus One returns "Passion", Motorola Droid returns "voles", etc.
*
@return manufacturer: Returns the manufacturer name:
*
* Samsung
* LG
* Motorola
* Micosoft
* Sony
* Apple
*
@return OS Name: Depending on the device, a few examples are:
* "Android"
* "BlackBerry 10"
* Browser: returns "MacIntel" on Mac
* returns "Win32" on Windows
* "iOS"
* "WinCE"
* "Tizen"
*
@return OS Version: Depending on the device, a few examples are:
* Android: Froyo OS would return "2.2"
Eclair OS would return "2.1", "2.0.1", or "2.0"
Version can also return update level "2.1-update1"
* BlackBerry: Torch 9800 using OS 6.0 would return "6.0.0.600"
*
* Browser: Returns version number for the browser
* iPhone: iOS 3.2 returns "3.2"
* Windows Phone 7: returns current OS version number, ex. on Mango returns 7.10.7720
* Tizen: returns "TIZEN_20120425_2"
*
@return OS Build: Get the version of Cordova running on the device.
* Overrides [Platform.getDeviceInformation()]{@link Platform#getDeviceInformation}
* @override
*/
CordovaPlatform.prototype.getDeviceInformation = function () {
return {
model: this._getProperty('model'),
manufacturer: this._getProperty('manufacturer'),
osName: this._getProperty('platform'),
osVersion: this._getProperty('version'),
osBuild: this._getProperty('cordova'),
carrier: '<unknown>',
};
};
CordovaPlatform.prototype._getProperty = function (key) {
var value = typeof device !== 'undefined' ? device[key] : null;
return value || '<unknown>';
};
CordovaPlatform.prototype.getDevicePlatform = function () {
return device.platform.toUpperCase();
};
CordovaPlatform.prototype._getPlatform = function () {
return types_1.PlatformNames.CORDOVA;
};
return CordovaPlatform;
}(browser_platform_1.BrowserPlatform));
exports.CordovaPlatform = CordovaPlatform;
/***/ }),
/* 70 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* Copyright© 2017, Oracle and/or its affiliates. All rights reserved.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var mcs_module_1 = __webpack_require__(43);
var mobile_backend_1 = __webpack_require__(63);
var cordova_platform_1 = __webpack_require__(69);
/**
* MCS module.
* @global
* @namespace mcs
* @mcs
*/
var MCSCordovaModule = /** @class */ (function (_super) {
__extends(MCSCordovaModule, _super);
function MCSCordovaModule(cxa, glob) {
var _this = _super.call(this, cxa, glob) || this;
/**
* Mobile backend object.
* Represents a mobile backend in Oracle Mobile Hub
* and provides access to all capabilities of the backend.
* @name mobileBackend
* @readonly
* @type {MobileBackend}
* @instance
* @memberOf mcs
* @mcs
*/
_this.mobileBackend = null;
return _this;
}
/**
* Init MCS with configuration
* @param {OracleMobileCloudConfig} config - MCS configuration
* The callback will be call when notification received by Cxa Analytics notification provider.
* @alias init
* @memberOf mcs
* @mcs
*/
MCSCordovaModule.prototype.init = function (config) {
this._init(config); // ,cxaAnalyticsTrackerParams TODO: uncomment to return cxa
if (!!config.mobileBackend) {
this.mobileBackend = new mobile_backend_1.MobileBackend(this._config, this._platform, this._utils, this._syncExpress);
}
};
MCSCordovaModule.prototype._getPlatform = function (config, utils) {
this._logger.debug('Create Cordova platform');
return new cordova_platform_1.CordovaPlatform(this._config, this._utils, navigator);
};
return MCSCordovaModule;
}(mcs_module_1.MCSModule));
exports.MCSCordovaModule = MCSCordovaModule;
MCSGlobal = MCSCordovaModule;
/***/ })
/******/ ]);
cxa = function (CXAOAuthAuthorization, NotificationsService, CxaStorage, Logger, mcsPlatform) {
};
// export mcs library
var __global;
if (typeof _window !== 'undefined') {
__global = _window;
} else if (typeof _global !== 'undefined') {
__global = _global;
} else if (typeof _self !== 'undefined') {
__global = _self;
} else {
__global = _this;
}
var mcs = new MCSGlobal(cxa, __global);
if (typeof _exports === 'object' && typeof _module !== 'undefined') {
_module.exports = mcs;
} else if (typeof _define === 'function' && _define.amd) {
_define([], mcs);
} else {
__global.mcs = mcs;
}
})(typeof module === "undefined" ? undefined : module,
typeof exports === "undefined" ? undefined : exports,
typeof define === "undefined" ? undefined : define,
typeof window === "undefined" ? undefined : window,
typeof global === "undefined" ? undefined : global,
typeof self === "undefined" ? undefined : self,
this);