UNPKG

blast-graph-angular2

Version:

![Alt text](./resources/images/b-circle-trans-100.png) **with** ![Alt text](./resources/images/angular.png)

1,772 lines (1,758 loc) 175 kB
import { Subject, BehaviorSubject } from 'rxjs/index'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class BlastHelpers { /** * @param {?} obj * @return {?} */ static isPresent(obj) { return obj !== undefined && obj !== null; } /** * @param {?} obj * @return {?} */ static isString(obj) { return typeof obj === 'string'; } /** * @param {?} obj * @return {?} */ static isArray(obj) { return Array.isArray(obj); } /** * @param {?} obj * @return {?} */ static isFunction(obj) { return typeof obj === 'function'; } /** * @param {?} obj * @return {?} */ static isJson(obj) { try { JSON.parse(obj); return true; } catch (exception) { return false; } } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** @type {?} */ const BLAST_VERSION = '0.0.1'; /** @type {?} */ const LOG_LEVEL = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 }; class BlastService { /** * @param {?} url * @param {?=} connectNow * @param {?=} protocols * @param {?=} config */ constructor(url, connectNow, protocols, config) { this.url = url; this.protocols = protocols; this.config = config; this.reconnectAttempts = 0; this.sendQueue = []; this.onMessageCallbacks = []; this.onOpenCallbacks = []; this.onErrorCallbacks = []; this.onCloseCallbacks = []; this.readyStateConstants = { 'CONNECTING': 0, 'OPEN': 1, 'CLOSING': 2, 'CLOSED': 3, 'RECONNECT_ABORTED': 4 }; this.normalCloseCode = 1000; this.reconnectableStatusCodes = [4000]; this.logLevel = LOG_LEVEL.ERROR; /** @type {?} */ const match = new RegExp('wss?:\/\/').test(url); if (!match) { throw new Error('Invalid url provided'); } this.config = config || { initialTimeout: 500, maxTimeout: 300000, reconnectIfNotNormalClose: true }; this.dataStream = new Subject(); if (connectNow === undefined || connectNow) { this.connect(true); } } /** * @param {?=} force * @return {?} */ connect(force = false) { /** @type {?} */ const self = this; if (force || !this.socket || this.socket.readyState !== this.readyStateConstants.OPEN) { self.socket = this.protocols ? new WebSocket(this.url, this.protocols) : new WebSocket(this.url); self.socket.onopen = (ev) => { this.onOpenHandler(ev); }; self.socket.onmessage = (ev) => { if (BlastHelpers.isJson(ev.data)) { /** @type {?} */ const message = JSON.parse(ev.data); this.debug('BlastService', 'jsonMessage', 'passing to handlers', ev.data); // call a json message handler - if true, then message handled mustn't carry on if (self.handleJsonMessage(message) === true) { return; } } self.onMessageHandler(ev.data); this.dataStream.next(ev.data); }; this.socket.onclose = (ev) => { self.onCloseHandler(ev); }; this.socket.onerror = (ev) => { self.onErrorHandler(ev); this.dataStream.error(ev); }; } } /** * Run in Block Mode * Return true when can send and false in socket closed * @param {?} data * @param {?=} binary * @return {?} */ sendMessage(data, binary) { /** @type {?} */ const self = this; if (this.getReadyState() !== this.readyStateConstants.OPEN && this.getReadyState() !== this.readyStateConstants.CONNECTING) { this.connect(); } this.debug('BlastService', 'sendMessage', data); self.sendQueue.push({ message: data, binary: binary }); if (self.socket.readyState === self.readyStateConstants.OPEN) { self.fireQueue(); return true; } else { return false; } } /** * Use {mode} mode to send {data} data * If no specify, Default SendMode is Observable mode * @param {?} data * @param {?=} binary * @return {?} */ send(data, binary) { return this.sendMessage(data, binary); } /** * @return {?} */ getDataStream() { return this.dataStream; } /** * @param {?} event * @return {?} */ notifyOpenCallbacks(event) { for (let i = 0; i < this.onOpenCallbacks.length; i++) { this.onOpenCallbacks[i].call(this, event); } } /** * @return {?} */ fireQueue() { while (this.sendQueue.length && this.socket.readyState === this.readyStateConstants.OPEN) { /** @type {?} */ const data = this.sendQueue.shift(); if (data.binary) { this.socket.send(data.message); } else { this.socket.send(BlastHelpers.isString(data.message) ? data.message : JSON.stringify(data.message)); } } } /** * @param {?} event * @return {?} */ notifyCloseCallbacks(event) { for (let i = 0; i < this.onCloseCallbacks.length; i++) { this.onCloseCallbacks[i].call(this, event); } } /** * @param {?} event * @return {?} */ notifyErrorCallbacks(event) { for (let i = 0; i < this.onErrorCallbacks.length; i++) { this.onErrorCallbacks[i].call(this, event); } } /** * @param {?} cb * @return {?} */ onOpen(cb) { this.onOpenCallbacks.push(cb); return this; } ; /** * @param {?} cb * @return {?} */ onClose(cb) { this.onCloseCallbacks.push(cb); return this; } /** * @param {?} cb * @return {?} */ onError(cb) { this.onErrorCallbacks.push(cb); return this; } ; /** * @param {?} callback * @param {?=} options * @return {?} */ onMessage(callback, options) { if (!BlastHelpers.isFunction(callback)) { throw new Error('Callback must be a function'); } this.onMessageCallbacks.push({ fn: callback, pattern: options ? options.filter : undefined, autoApply: options ? options.autoApply : true }); return this; } /** * @param {?} message * @return {?} */ handleJsonMessage(message) { // as a default return false i.e. don't change message flow // enables extended classes to override this function return false; } /** * @param {?} message * @return {?} */ onMessageHandler(message) { this.debug('BlastService', 'onMessageHandler', message.data); /** @type {?} */ const self = this; /** @type {?} */ let currentCallback; for (let i = 0; i < self.onMessageCallbacks.length; i++) { currentCallback = self.onMessageCallbacks[i]; currentCallback.fn.apply(self, [message]); } } ; /** * @param {?} event * @return {?} */ onOpenHandler(event) { this.debug('BlastService', 'connected'); this.reconnectAttempts = 0; this.notifyOpenCallbacks(event); this.fireQueue(); } /** * @param {?} event * @return {?} */ onCloseHandler(event) { this.debug('BlastService', 'closed'); this.notifyCloseCallbacks(event); if ((this.config.reconnectIfNotNormalClose && event.code !== this.normalCloseCode) || this.reconnectableStatusCodes.indexOf(event.code) > -1) { this.reconnect(); } else { this.sendQueue = []; this.dataStream.complete(); } } ; /** * @param {?} event * @return {?} */ onErrorHandler(event) { this.debug('BlastService', 'onErrorHandler', event); this.notifyErrorCallbacks(event); } ; /** * @return {?} */ reconnect() { this.close(true); /** @type {?} */ const backoffDelay = this.getBackoffDelay(++this.reconnectAttempts); // let backoffDelaySeconds = backoffDelay / 1000; // // console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds'); this.debug('BlastService', 'reconnectDelay', backoffDelay); setTimeout(() => this.connect(), backoffDelay); return this; } /** * @param {?=} force * @return {?} */ close(force = false) { if (force || !this.socket.bufferedAmount) { this.socket.close(this.normalCloseCode); } return this; } ; /** * @param {?} attempt * @return {?} */ getBackoffDelay(attempt) { /** @type {?} */ const R = Math.random() + 1; /** @type {?} */ const T = this.config.initialTimeout; /** @type {?} */ const F = 2; /** @type {?} */ const N = attempt; /** @type {?} */ const M = this.config.maxTimeout; return Math.floor(Math.min(R * T * Math.pow(F, N), M)); } ; /** * @param {?} state * @return {?} */ setInternalState(state) { if (Math.floor(state) !== state || state < 0 || state > 4) { throw new Error('state must be an integer between 0 and 4, got: ' + state); } this.internalConnectionState = state; } /** * Could be -1 if not initzialized yet * @return {?} */ getReadyState() { if (this.socket == null) { return -1; } return this.internalConnectionState || this.socket.readyState; } /** * @return {?} */ getVersion() { return BLAST_VERSION; } /** * @return {?} */ hasConsole() { if (console === undefined) { return false; } return true; } /** * @param {...?} args * @return {?} */ debug(...args) { if (this.hasConsole() && this.logLevel < 1) { console.debug.apply(console, args); } } /** * @param {...?} args * @return {?} */ info(...args) { if (this.hasConsole() && this.logLevel < 2) { console.debug.apply(console, args); } } /** * @param {...?} args * @return {?} */ warn(...args) { if (this.hasConsole() && this.logLevel < 4) { console.debug.apply(console, args); } } /** * @param {...?} args * @return {?} */ error(...args) { console.error.apply(console, args); } /** * @param {?} level * @return {?} */ setLogLevel(level) { this.logLevel = level; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class BlastException { /** * @param {?} message */ constructor(message) { this.message = message; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class PathDetails { /** * @param {?} key * @return {?} */ static splitPath(key) { /** @type {?} */ const details = []; if (key === null || key.trim().length === 0 || key === 'root') { /** @type {?} */ const tmpPathDetails = new PathDetails(); tmpPathDetails.setRoot(); tmpPathDetails.setNotCollectionOnly(); details.push(tmpPathDetails); return details; } if (key.startsWith('/')) { throw new BlastException('key cannot start with / - must specify collection e.g. collection/keyname:value'); } /** @type {?} */ const splits = key.split('/'); if (splits[0].includes(':')) { throw new BlastException('must specify collection e.g. collection/keyname:value'); } // check if it is collection only if (splits.length === 1) { /** @type {?} */ const tmpPathDetails = new PathDetails(); tmpPathDetails.setNotRoot(); tmpPathDetails.setCollection(key); tmpPathDetails.setCollectionOnly(); details.push(tmpPathDetails); return details; } /** @type {?} */ let pathDetails = new PathDetails(); pathDetails.setNotRoot(); pathDetails.setCollection(splits[0]); details.push(pathDetails); for (let x = 1; x < splits.length; x++) { if (splits[x].includes(':')) { /** @type {?} */ const keySplit = splits[x].split(':'); if (keySplit.length !== 2) { throw new BlastException('look up must be in form keyname:value - found: ' + splits[x]); } pathDetails.setKeyField(keySplit[0]); pathDetails.setKeyValue(keySplit[1]); pathDetails.setNotCollectionOnly(); } else { if (splits[x].startsWith('?')) { pathDetails.setQueryParams(splits[x].substring(1).split(',')); } else { pathDetails = new PathDetails(); pathDetails.setNotRoot(); pathDetails.setCollectionOnly(); pathDetails.setCollection(splits[x]); details.push(pathDetails); } } } return details; } /** * @return {?} */ setRoot() { this._isRoot = true; this._collection = 'root'; } /** * @return {?} */ setNotRoot() { this._isRoot = false; } /** * @param {?} collection * @return {?} */ setCollection(collection) { this._collection = collection; } /** * @return {?} */ setCollectionOnly() { this._isCollectionOnly = true; } /** * @return {?} */ setNotCollectionOnly() { this._isCollectionOnly = false; } /** * @param {?} keyField * @return {?} */ setKeyField(keyField) { this._keyField = keyField; } /** * @param {?} keyValue * @return {?} */ setKeyValue(keyValue) { this._keyValue = keyValue; } /** * @param {?} params * @return {?} */ setQueryParams(params) { this._queryParams = params; } /** * @return {?} */ getCollection() { return this._collection; } /** * @return {?} */ getKeyField() { return this._keyField; } /** * @return {?} */ getKeyValue() { return this._keyValue; } /** * @return {?} */ getQueryParams() { return this._queryParams; } /** * @return {?} */ isRoot() { return this._isRoot; } /** * @return {?} */ isCollectionOnly() { return this._isCollectionOnly; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class GraphRequest { /** * @param {?} command * @param {?=} key * @param {?=} data * @param {?=} pathParameters * @param {?=} attachmentId */ constructor(command, key, data, pathParameters, attachmentId) { if (!key) { PathDetails.splitPath(key); } this._command = command; this._key = key; this._data = data; this._pathParameters = pathParameters; this._attachmentId = attachmentId; } /** * @param {?} command * @return {?} */ setCommand(command) { this._command = command; } /** * @param {?} key * @return {?} */ setKey(key) { this._key = key; } /** * @param {?} pathParameters * @return {?} */ setParameters(pathParameters) { this._pathParameters = pathParameters; } /** * @param {?} data * @return {?} */ setData(data) { this._data = data; } /** * @param {?} collection * @return {?} */ setCollection(collection) { this._collection = collection; } /** * @param {?} collectionList * @return {?} */ setCollectionList(collectionList) { this._collectionList = collectionList; } /** * @param {?} correlationId * @param {?} onFulFilled * @param {?} onError * @return {?} */ setCorrelationInfo(correlationId, onFulFilled, onError) { this._correlationId = correlationId; this._onFulFilled = onFulFilled; this._onError = onError; this._requestTime = new Date().getTime(); } /** * @return {?} */ getMessage() { /** @type {?} */ const message = { cmd: this._command, correlationId: this._correlationId }; if (this._key) { message['key'] = this._key; } if (this._pathParameters) { message['parameters'] = this._pathParameters.getParameters(); } if (this._attachmentId) { message['attachmentId'] = this._attachmentId; } if (this._data) { message['data'] = this._data; } return message; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class CollectionTraversor { /** * @param {?} path * @param {?} data * @return {?} */ static findList(path, data) { // console.log('path',path,'data',data); if (Object.prototype.toString.call(data) === '[object Array]') { return this.traverseUntilFinalList(path, data); } else { /** @type {?} */ const pathDetails = PathDetails.splitPath(path); if (pathDetails.length === 1 && pathDetails[0].getKeyField() === undefined) { return data[pathDetails[0].getCollection()]; } /** @type {?} */ let resultList = []; for (let index = 0; index < pathDetails.length; index++) { if (pathDetails[index].getKeyField() === undefined) { resultList = data[pathDetails[index].getCollection()]; } else { data = this.findARecord(index, pathDetails, data); } } return resultList === undefined ? [] : resultList; } } /** * @param {?} path * @param {?} data * @return {?} */ static findRecord(path, data) { if (Object.prototype.toString.call(data) === '[object Array]') { return this.traverseUntilFinalRecord(path, data); } else { /** @type {?} */ const pathDetails = PathDetails.splitPath(path); for (let index = 0; index < pathDetails.length; index++) { data = this.findARecord(index, pathDetails, data); } return data; } } /** * @param {?} keyField * @param {?} keyValue * @param {?} list * @return {?} */ static findRecordInList(keyField, keyValue, list) { if (list !== undefined) { for (let index = 0; index < list.length; index++) { /** @type {?} */ const id = list[index][keyField]; if (id !== undefined) { if (this.matches(id, keyValue)) { return list[index]; } } } } throw new BlastException('[findRecordInList] failed to find record - field [' + keyField + ' ] value [' + keyValue + ']'); } /** * @param {?} keyField * @param {?} keyValue * @param {?} list * @return {?} */ static findRecordIndexInList(keyField, keyValue, list) { if (list !== undefined) { for (let index = 0; index < list.length; index++) { /** @type {?} */ const id = list[index][keyField]; if (id !== undefined) { if (this.matches(id, keyValue)) { return index; } } } } throw new BlastException('[findRecordIndexInList] failed to find record - field [' + keyField + ' ] value [' + keyValue + ']'); } /** * @param {?} index * @param {?} pathDetails * @param {?} dataSubset * @return {?} */ static findARecord(index, pathDetails, dataSubset) { /** @type {?} */ const list = dataSubset[pathDetails[index].getCollection()]; return this.findRecordInList(pathDetails[index].getKeyField(), pathDetails[index].getKeyValue(), list); } /** * @param {?} path * @param {?} list * @return {?} */ static traverseUntilFinalRecord(path, list) { /** @type {?} */ const pathDetails = PathDetails.splitPath(path); /** @type {?} */ let data = this.findRecordInList(pathDetails[0].getKeyField(), pathDetails[0].getKeyValue(), list); for (let index = 1; index < pathDetails.length; index++) { data = this.findARecord(index, pathDetails, data); } return data; } /** * @param {?} path * @param {?} list * @return {?} */ static traverseUntilFinalList(path, list) { /** @type {?} */ const pathDetails = PathDetails.splitPath(path); // asking for a list with no details so return original list if (pathDetails[0].getKeyField() === undefined) { return list; } /** @type {?} */ let resultList = []; /** @type {?} */ let data = this.findRecordInList(pathDetails[0].getKeyField(), pathDetails[0].getKeyValue(), list); for (let index = 1; index < pathDetails.length; index++) { // there is no key field or we are on last path element if (pathDetails[index].getKeyField() === undefined || index === pathDetails.length - 1) { resultList = data[pathDetails[index].getCollection()]; } else { data = this.findARecord(index, pathDetails, data); } } return resultList === undefined ? [] : resultList; } /** * @param {?} one * @param {?} two * @return {?} */ static matches(one, two) { if (typeof (one) === 'number') { if (typeof (two) === 'number') { return one === two; } else { return one === Number(two); } } else { if (typeof (two) === 'string') { return one === two; } else { return one === String(two); } } } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** @type {?} */ const Operation = { ADD: 'ADD', UPDATE: 'UPDATE', REMOVE: 'REMOVE' }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class Instruction { /** * @param {?} jsonObject */ constructor(jsonObject) { this._operation = jsonObject['operation']; this._path = jsonObject['path']; this._collection = jsonObject['collection']; this._changes = jsonObject['changes']; this._record = jsonObject['record']; } /** * @return {?} */ getOperation() { return this._operation; } /** * @return {?} */ getPath() { return this._path; } /** * @return {?} */ getCollection() { return this._collection; } /** * @return {?} */ getChanges() { return this._changes; } /** * @return {?} */ getRecord() { return this._record; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** @type {?} */ const GraphMessageType = { GRAPH_ADD_RESPONSE: 'graph-add', GRAPH_UPDATE_RESPONSE: 'graph-update', GRAPH_REMOVE_RESPONSE: 'graph-remove', GRAPH_INITIAL_LOAD_RESPONSE: 'graph-attach', GRAPH_FETCH_RESPONSE: 'graph-fetch', GRAPH_SCHEMA_RESPONSE: 'graph-schema', GRAPH_CLIENT_ATTACHMENTS_RESPONSE: 'graph-current_attachments', GRAPH_OK_RESPONSE: 'ok', GRAPH_FAIL_RESPONSE: 'fail', GRAPH_DETACH_RESPONSE: 'graph-detach', GRAPH_DETACH_ALL_RESPONSE: 'graph-detach_all' }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class GraphResponseMessage { /** * @param {?} jsonObject */ constructor(jsonObject) { if (jsonObject['graphMessageType'] === undefined) { throw new BlastException('not a graph response message'); } this._graphMessageType = jsonObject['graphMessageType']; if (GraphMessageType[this._graphMessageType] === undefined) { throw new BlastException('not a valid graph response message type'); } this._correlationId = jsonObject['correlationId']; this._key = jsonObject['key']; if (jsonObject['instruction']) { this._instruction = new Instruction(jsonObject['instruction']); } this._data = jsonObject['data']; this._status = jsonObject['status']; this._cmd = jsonObject['cmd']; this._attachmentId = jsonObject['attachmentId']; this._graphMessageType = jsonObject['graphMessageType']; } /** * @return {?} */ getCorrelationId() { return this._correlationId; } /** * @return {?} */ getKey() { return this._key; } /** * @return {?} */ getInstruction() { return this._instruction; } /** * @return {?} */ getData() { return this._data; } /** * @return {?} */ getStatus() { return this._status; } /** * @return {?} */ getGraphMessageType() { return this._graphMessageType; } /** * @return {?} */ getCommand() { return this._cmd; } /** * @return {?} */ getAttachmentId() { return this._attachmentId; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class Attachment { /** * @param {?} attachmentId * @param {?} key * @param {?} logService */ constructor(attachmentId, key, logService) { this._key = key; this._attachmentId = attachmentId; this._loadStream = new Subject(); this._addStream = new Subject(); this._changeStream = new Subject(); this._removeStream = new Subject(); this._tickStream = new Subject(); this._logService = logService; } /** * @return {?} */ isList() { return this._isList; } /** * @return {?} */ getAttachmentId() { return this._attachmentId; } /** * @return {?} */ getKey() { return this._key; } /** * @return {?} */ getData() { return this._data; } /** * @return {?} */ getListData() { return this._listData; } /** * @param {?} data * @return {?} */ setData(data) { if (data === undefined) { this._data = {}; } else { this._data = data; } this._isList = false; } /** * @param {?} data * @return {?} */ setListData(data) { if (data === undefined) { this._listData = []; } else { this._listData = data; } this._isList = true; } /** * @param {?} data * @return {?} */ load(data) { this._logService.log('attachment ' + this._key + ' loaded', data); if (data !== undefined) { this._loadStream.next(data); } else { // we need to send back empty data if collection had nothing if (this._isList) { this._loadStream.next(undefined); } else { this._loadStream.next(undefined); } } this._tickStream.next(data); } /** * @param {?} data * @return {?} */ added(data) { this._logService.log('attachment ' + this._key + ' added', data); this._addStream.next(data); this._tickStream.next(data); } /** * @param {?} data * @return {?} */ changed(data) { this._logService.log('attachment ' + this._key + ' changed', data); this._changeStream.next(data); this._tickStream.next(data); } /** * @param {?} data * @return {?} */ removed(data) { this._logService.log('attachment ' + this._key + ' removed', data); this._removeStream.next(data); this._tickStream.next(data); } /** * @return {?} */ loadStream() { return this._loadStream; } /** * @return {?} */ addedStream() { return this._addStream; } /** * @return {?} */ changedStream() { return this._changeStream; } /** * @return {?} */ removedStream() { return this._removeStream; } /** * @return {?} */ onTick() { return this._tickStream; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** * @param {?} val * @return {?} */ function camelToKebabCase(val) { /** @type {?} */ let result = val; // Convert camelCase capitals to kebab-case. result = result.replace(/([a-z][A-Z])/g, function (match) { return match.substr(0, 1) + '-' + match.substr(1, 1).toLowerCase(); }); // Convert non-camelCase capitals to lowercase. result = result.toLowerCase(); // Convert non-alphanumeric characters to hyphens result = result.replace(/[^-a-z0-9]+/g, '-'); // Remove hyphens from both ends result = result.replace(/^-+/, '').replace(/-$/, ''); return result; } /** * @param {?} val * @return {?} */ function kebabCaseToCamel(val) { return val.replace(/-([a-z])/gi, function ($0, $1) { return $1.toUpperCase(); }); } /** * @param {?} o * @return {?} */ function encodeRequest(o) { if (typeof o === 'string') { return camelToKebabCase(o); } /** @type {?} */ let newO; /** @type {?} */ let newKey; /** @type {?} */ let value; if (o instanceof Array) { newO = []; for (const origKey in o) { if (o.hasOwnProperty(origKey)) { value = o[origKey]; if (typeof value === 'object') { value = encodeRequest(value); } newO.push(value); } } } else { newO = {}; for (const origKey in o) { if (o.hasOwnProperty(origKey)) { newKey = camelToKebabCase(origKey); value = o[origKey]; if (value !== null && value !== undefined && value.constructor === Object) { value = encodeRequest(value); } newO[newKey] = value; } } } return newO; } /** * @param {?} o * @return {?} */ function decodeResponse(o) { if (typeof o === 'string') { return kebabCaseToCamel(o); } /** @type {?} */ let newO; /** @type {?} */ let newKey; /** @type {?} */ let value; if (o instanceof Array) { newO = []; for (const origKey in o) { if (o.hasOwnProperty(origKey)) { value = o[origKey]; if (typeof value === 'object') { value = decodeResponse(value); } newO.push(value); } } } else { newO = {}; for (const origKey in o) { if (o.hasOwnProperty(origKey)) { newKey = kebabCaseToCamel(origKey); value = o[origKey]; if (value !== null && value.constructor === Object) { value = decodeResponse(value); } else if (value instanceof Array) { value = decodeResponse(value); } newO[newKey] = value; } } } return newO; } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ class LogService { constructor() { this.logging = new BehaviorSubject(false); } /** * @param {?} value * @return {?} */ setLog(value) { this.logging.next(value); } /** * @param {?} message * @param {?=} data * @return {?} */ log(message, data) { /** @type {?} */ const date = new Date(); if (this.logging.getValue()) { if (data === undefined) { console.log('Blast [' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() + '.' + date.getMilliseconds() + '] ' + message); } else { console.log('Blast [' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() + '.' + date.getMilliseconds() + '] ' + message, data); } } } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** @type {?} */ const useKebab = true; class GraphBlastService extends BlastService { /** * @param {?} _url * @param {?} _logging * @param {?=} _connectNow * @param {?=} _protocols * @param {?=} _config */ constructor(_url, _logging, _connectNow, _protocols, _config) { super(_url, _connectNow, _protocols, _config); this._url = _url; this._logging = _logging; this._connectNow = _connectNow; this._protocols = _protocols; this._config = _config; this._correlatedGraphRequestMap = []; this._collectionMap = {}; this._correlationId = 0; this._logService = new LogService(); this._logService.setLog(_logging); } /** * @param {?} collection * @param {?} entity * @return {?} */ add(collection, entity) { return this.sendGraphRequest(new GraphRequest('add', collection, entity)); } /** * @param {?} key * @param {?} entity * @return {?} */ update(key, entity) { return this.sendGraphRequest(new GraphRequest('update', key, entity)); } /** * @param {?} key * @return {?} */ remove(key) { return this.sendGraphRequest(new GraphRequest('remove', key)); } /** * @return {?} */ randomId() { /** * @return {?} */ function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } /** * @param {?} key * @param {?} data * @param {?=} parameters * @return {?} */ attach(key, data, parameters) { // attach(key: string, data: any, parameters?: PathParameters): Promise<any> { if (key.trim().length === 0) { key = 'root'; } /** @type {?} */ let isArray = false; if (Object.prototype.toString.call(data) === '[object Array]') { isArray = true; } /** @type {?} */ const pathDetails = PathDetails.splitPath(key); if (pathDetails[pathDetails.length - 1].getKeyField() !== undefined && isArray) { this.handlePromiseError(new BlastException('Can only attach to a type of {} when keyField:keyValue is last element of path')); return; } if (pathDetails[pathDetails.length - 1].getKeyField() === undefined && !isArray) { this.handlePromiseError(new BlastException('Can only attach to a type of [] when last element of path is a collection')); return; } /** @type {?} */ const attachmentId = this.randomId(); /** @type {?} */ const attachment = new Attachment(attachmentId, key, this._logService); if (isArray) { attachment.setListData(data); } else { attachment.setData(data); } this._collectionMap[attachmentId] = attachment; // console.log('added to collection', key, clientCollection, this._collectionMap); // return this.sendGraphRequest(new GraphRequest('attach', key, parameters, attachmentId)); this.sendGraphRequest(new GraphRequest('attach', key, null, parameters, attachmentId)); return attachment; } /** * @param {?} blastException * @return {?} */ handlePromiseError(blastException) { return new Promise((resolve, reject) => { reject(blastException.message); }); // console.error('Exception:', blastException.message); // const promise: Promise<any> = new Promise((onFulfilled, onRejected) => { // onRejected(blastException.message); // }); // return promise; } /** * @param {?} key * @return {?} */ detach(key) { return this.sendGraphRequest(new GraphRequest('detach', key)); } /** * @return {?} */ detachAll() { return this.sendGraphRequest(new GraphRequest('detachAll')); } /** * @return {?} */ getAttachments() { return this.sendGraphRequest(new GraphRequest('attachments')); } /** * @param {?} key * @param {?=} parameters * @return {?} */ fetch(key, parameters) { return this.sendGraphRequest(new GraphRequest('fetch', key, parameters)); } /** * @return {?} */ fetchRoot() { return this.sendGraphRequest(new GraphRequest('fetch', '')); } /** * @return {?} */ getSchema() { return this.sendGraphRequest(new GraphRequest('schema')); } /** * @return {?} */ loggingOn() { this._logService.setLog(true); } /** * @return {?} */ loggingOff() { this._logService.setLog(false); } /** * @param {?} request * @return {?} */ sendGraphRequest(request) { /** @type {?} */ let complete; /** @type {?} */ let err; /** @type {?} */ const promise = new Promise((onFulfilled, onRejected) => { complete = onFulfilled; err = onRejected; }); request.setCorrelationInfo(++this._correlationId, complete, err); // we add the requestDO to a map - used to marry response with request this._correlatedGraphRequestMap[request._correlationId] = request; // console.log('request.getMessage()', request.getMessage()); // send the message { this.send(encodeRequest(request.getMessage())); } // return the promise - will be fulfilled when we get a response from the server return promise; } /** * @param {?} startIndex * @param {?} pathDetails * @param {?} addLastKey * @return {?} */ buildPath(startIndex, pathDetails, addLastKey) { /** @type {?} */ let builder = ''; for (let x = startIndex; x < pathDetails.length; x++) { if (builder.length > 0) { builder = builder + '/'; } builder = builder + pathDetails[x].getCollection(); if (x < pathDetails.length - 1 || addLastKey) { builder = builder + '/'; if (pathDetails[x].getKeyField() != null) { builder = builder + pathDetails[x].getKeyField() + ':' + pathDetails[x].getKeyValue(); } } } return builder; } /** * @param {?} key * @param {?} path * @param {?} operation * @return {?} */ calculateTruePath(key, path, operation) { /** @type {?} */ const keyDetails = PathDetails.splitPath(key); /** @type {?} */ const pathDetails = PathDetails.splitPath(path); /** @type {?} */ let truePath = ''; if (keyDetails[keyDetails.length - 1].isRoot()) { truePath = this.buildPath(0, pathDetails, operation === Operation.UPDATE); } else if (keyDetails[keyDetails.length - 1].getKeyField() == null) { // collection is an array for (let x = 0; x < pathDetails.length; x++) { // loop through until collection in path matches last collection in key if (pathDetails[x].getCollection() === keyDetails[keyDetails.length - 1].getCollection()) { truePath = this.buildPath(x, pathDetails, operation === Operation.UPDATE); break; } } } else { // collection is a map if (keyDetails.length === pathDetails.length) { truePath = this.buildPath(pathDetails.length - 1, pathDetails, operation === Operation.UPDATE); } else { truePath = this.buildPath(keyDetails.length, pathDetails, operation === Operation.UPDATE); } } return truePath; } /** * @param {?} collection * @param {?} instruction * @return {?} */ applyChangeToRecord(collection, instruction) { for (let x = 0; x < instruction.getChanges().length; x++) { // name = name of field, value = new value collection[instruction.getChanges()[x]['name']] = instruction.getChanges()[x]['value']; } } /** * @param {?} value * @return {?} */ getGraphMessage(value) { { return new GraphResponseMessage(JSON.parse(decodeResponse(value))); } } /** * @param {?} graphRequest * @param {?} graphMessage * @return {?} */ handleInitialLoad(graphRequest, graphMessage) { /** @type {?} */ const attachment = this._collectionMap[graphMessage.getAttachmentId()]; // console.log('retrieve [initial load] from collection', graphMessage.getAttachmentId(), attachment, this._collectionMap); if (attachment == null) { console.error('Failed to find collection for key: ', graphMessage.getAttachmentId()); graphRequest._onError('Failed to find collection for key: ' + graphMessage.getAttachmentId()); return; } if (attachment.isList()) { // as its initial load we can just add to collection if (graphMessage.getData() !== null && graphMessage.getData() !== undefined) { Array.prototype.push.apply(attachment.getListData(), graphMessage.getData()); attachment.load(graphMessage.getData()); graphRequest._onFulFilled(attachment.getListData()); } else { attachment.load(undefined); graphRequest._onFulFilled(attachment.getListData()); } } else { this.mergeMap(attachment.getData(), graphMessage.getData()); graphRequest._onFulFilled(attachment.getData()); } } /** * @param {?} message * @return {?} */ handleJsonMessage(message) { try { this._logService.log('raw message ', message); /** @type {?} */ const graphMessage = new GraphResponseMessage(decodeResponse(message)); this._logService.log('graph message ', graphMessage); this.handleCommand(graphMessage); return true; } catch (blastException) { // not a valid graph message, so return false so can be handled by normal flow console.error(blastException); return false; } } /** * @param {?} graphMessage * @return {?} */ handleCommand(graphMessage) { this.handleTheCommand(graphMessage); } /** * @param {?} graphMessage * @return {?} */ handleTheCommand(graphMessage) { // console.log('handle command', graphMessage); try { // 1st pass - handle responses that don't have a future attached switch (graphMessage.getCommand()) { case GraphMessageType.GRAPH_ADD_RESPONSE: case GraphMessageType.GRAPH_UPDATE_RESPONSE: case GraphMessageType.GRAPH_REMOVE_RESPONSE: this.handleGraphModify(graphMessage); return; } /** @type {?} */ const graphRequest = this._correlatedGraphRequestMap[graphMessage.getCorrelationId()]; if (graphRequest === undefined) { throw new BlastException('Failed to find correlation id: ' + graphMessage.getCorrelationId()); } switch (graphMessage.getCommand()) { case GraphMessageType.GRAPH_DETACH_RESPONSE: case GraphMessageType.GRAPH_DETACH_ALL_RESPONSE: case GraphMessageType.GRAPH_OK_RESPONSE: graphRequest._onFulFilled(graphMessage); break; case GraphMessageType.GRAPH_FAIL_RESPONSE: graphRequest._onFulFilled(graphMessage); break; case GraphMessageType.GRAPH_INITIAL_LOAD_RESPONSE: this.handleInitialLoad(graphRequest, graphMessage); break; case GraphMessageType.GRAPH_CLIENT_ATTACHMENTS_RESPONSE: case GraphMessageType.GRAPH_SCHEMA_RESPONSE: case GraphMessageType.GRAPH_FETCH_RESPONSE: graphRequest._onFulFilled(graphMessage.getData()); break; case GraphMessageType.GRAPH_ADD_RESPONSE: case GraphMessageType.GRAPH_UPDATE_RESPONSE: case GraphMessageType.GRAPH_REMOVE_RESPONSE: break; } } catch (blastException) { // console.log('Exception', blastException); // eventHandler.onError(new WebSocketException(ex.getMessage(), ex)); } } /** * @param {?} graphMessage * @return {?} */ handleGraphModify(graphMessage) { // console.log('Handling ...', graphMessage); if (this.shouldAbandonProcessing(graphMessage)) { // 'No Instruction or no changes - doing nothing' return; } /** @type {?} */ const attachment = this._collectionMap[graphMessage.getAttachmentId()]; // console.log('retrieve [modify] from collection', graphMessage.getAttachmentId(), clientCollection, this._collectionMap); if (attachment === undefined) { // 'Cannot find collection for {}', graphMessage.getKey()); return; } /** @type {?} */ const path = this.calculateTruePath(graphMessage.getKey(), graphMessage.getInstruction().getPath(), graphMessage.getInstruction().getOperation()); /** @type {?} */ let parentList = []; /** @type {?} */ let record = {}; // console.log('Ready to ...', graphMessage.getInstruction().getOperation()); switch (graphMessage.getInstruction().getOperation()) { case Operation.ADD: // console.log('Okay Im Adding'); if (attachment.isList()) { CollectionTraversor.findList(path, attachment.getListData()).push(graphMessage.getInstruction().getRecord()); } else { CollectionTraversor.findList(path, attachment.getData()).push(graphMessage.getInstruction().getRecord()); } attachment.added(graphMessage.getInstruction().getRecord()); break; case Operation.UPDATE: // console.log('Okay Im Updating'); if (attachment.isList()) { record = CollectionTraversor.findRecord(path, attachment.getListData()); this.applyChangeToRecord(record, graphMessage.getInstruction()); attachment.change