blast-graph-angular2
Version:
 **with** 
1,772 lines (1,758 loc) • 175 kB
JavaScript
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