UNPKG

@synerty/vortexjs

Version:

Custom observable data serialisation and routing based on Angular 2+

1 lines 411 kB
{"version":3,"file":"synerty-vortexjs.mjs","sources":["../../src/vortex/UtilMisc.ts","../../src/vortex/UtilArray.ts","../../src/vortex/payload/PayloadDelegateABC.ts","../../src/vortex/payload/PayloadDelegateInMainWeb.ts","../../src/vortex/SerialiseUtil.ts","../../src/vortex/UtilString.ts","../../src/vortex/Jsonable.ts","../../src/vortex/Tuple.ts","../../src/vortex/PayloadEnvelope.ts","../../src/vortex/Payload.ts","../../src/vortex/PayloadIO.ts","../../src/vortex/PayloadEndpoint.ts","../../src/vortex/PayloadFilterKeys.ts","../../src/vortex/VortexClientABC.ts","../../src/vortex/TupleLoader.ts","../../src/vortex/VortexStatusService.ts","../../src/vortex/VortexClientHttp.ts","../../src/vortex/VortexClientWebsocket.ts","../../src/vortex/VortexService.ts","../../src/util/NgLifeCycleEvents.ts","../../src/vortex/PayloadResponse.ts","../../src/vortex/storage-factory/TupleStorageFactoryService.ts","../../src/vortex/storage/TupleStorageServiceABC.ts","../../src/vortex/storage-api/sql-api-abc.ts","../../src/vortex/storage-api/cap-sql-api.ts","../../src/vortex/storage-api/web-sql-api.ts","../../src/vortex/storage-api/sql-api-factory.ts","../../src/vortex/storage/TupleOfflineStorageNameService.ts","../../src/vortex/storage/TupleOfflineStorageService.ts","../../src/vortex/TupleSelector.ts","../../src/vortex/observable-service/TupleDataOfflineObserverService.ts","../../src/vortex/observable-service/TupleDataObserverService.ts","../../src/vortex/TupleAction.ts","../../src/vortex/action-service/TupleActionPushService.ts","../../src/vortex/action-service/TupleActionPushOfflineSingletonService.ts","../../src/vortex/action-service/TupleActionPushOfflineService.ts","../../src/vortex/action-service/TupleActionProcessorService.ts","../../src/vortex/action-service/TupleActionProcessorDelegate.ts","../../src/vortex/UtilSort.ts","../../src/vortex/payload/PayloadDelegateWeb.ts","../../src/vortex/storage-api/indexeddb-api.ts","../../src/vortex/storage/TupleStorageIndexedDbService.ts","../../src/vortex/storage/TupleStorageNullService.ts","../../src/vortex/action-storage/TupleActionStorageServiceABC.ts","../../src/vortex/action-storage/TupleActionStorageIndexedDbService.ts","../../src/vortex/storage/TupleStorageWebSqlService.ts","../../src/vortex/action-storage/tuple-action-storage-sql.service.ts","../../src/vortex/storage-factory/TupleStorageFactoryServiceWeb.ts","../../src/vortex/data-loader/TupleDataLoaderTuples.ts","../../src/vortex/data-loader/TupleDataLoader.ts","../../src/vortex/data-loader/TupleDataLoaderDelegate.ts","../../src/vortex/data-loader/TupleDataLoaderTupleABC.ts","../../synerty-vortexjs.ts"],"sourcesContent":["/**\n * Created by Jarrod Chesney / Synerty on 22/11/16.\n */\n// ----------------------------------------------------------------------------\n/**\n * Keys from Object\n *\n * Extract an array of keys from a json object.\n * This will not include keys starting with an underscore.\n *\n * @param obj: The object to get the keys from.\n * @param includeUnderscore: Should keys with underscores be included?\n * @return A list of keys from the object.\n */\nexport function dictKeysFromObject(\n obj: {},\n includeUnderscore = false\n): string[] {\n let keys = []\n for (let k in obj) {\n if ((!k.startsWith(\"_\") || includeUnderscore)\n && obj.hasOwnProperty(k) && typeof k !== \"function\") {\n keys.push(k)\n }\n }\n return keys\n}\n\n// ----------------------------------------------------------------------------\n\nexport class AssertException {\n message: string\n \n constructor(message: string) {\n let self = this\n self.message = message\n }\n \n toString(): string {\n let self = this\n return \"AssertException: \" + self.message\n }\n}\n\n/**\n * A simple assert statement\n * @param exp : The boolean to assert\n * @param message : The message to log when the assertion fails.\n */\nexport function assert(\n exp: boolean,\n message: string | null = null\n): null {\n if (exp)\n return\n \n console.trace()\n throw new AssertException(message)\n}\n\n// ----------------------------------------------------------------------------\n\n/**\n * Create url encoded arguments\n *\n * @param filter : The object containing the key:value pairs to convert into a url\n *\n */\nexport function getFiltStr(filter: {}): string {\n let filtStr = \"\"\n \n for (let key in filter) {\n if (!filter.hasOwnProperty(key))\n continue\n \n filtStr += (filtStr.length ? \"&\" : \"?\") + key + \"=\" + filter[key]\n }\n \n return filtStr\n}\n\n// ----------------------------------------------------------------------------\n\n/**\n * Date String\n *\n * @return A date and time formatted to a string for log messages.\n */\nexport function dateStr(): string {\n let d = new Date()\n return d.toTimeString()\n .split(\" \")[0] + \".\" + d.getUTCMilliseconds() + \": \"\n}\n\n// ----------------------------------------------------------------------------\n\n/**\n * Bind a function\n * @param obj : The object to bind the function for.\n * @param method : The method to bind onto to the object.\n *\n * @return A callable function that will call the method correctly bound to \"this\"\n */\nexport function bind(\n obj: any,\n method: any\n): any {\n return function () {\n return method.apply(obj, arguments)\n }\n}\n\n// ----------------------------------------------------------------------------\n\n/**\n * Bind a function\n * @param err : The err object to convert to a string.\n *\n * @return A callable function that will call the method correctly bound to \"this\"\n */\nexport function errToStr(err: any): string {\n \n if (err.message != null)\n return err.message\n \n try {\n let jsonStr = JSON.stringify(err)\n if (jsonStr != \"{}\")\n return jsonStr\n \n }\n catch (ignore) {\n }\n \n return err.toString()\n}\n\n// ----------------------------------------------------------------------------\n\ninterface ignoreDictType {\n [name: string]: any\n}\n\n/** Deep Clone\n * @param data: Deep Clone an entire JSON data structure\n * @param ignoreFieldNames: An array of field names not to copy.\n *\n * @return A clone of the data\n */\n\nexport function deepCopy(\n data,\n ignoreFieldNames: string[] | null = null\n) {\n const dict: ignoreDictType = {}\n if (ignoreFieldNames != null\n && Object.prototype.toString.call(ignoreFieldNames)\n .slice(8, -1) == \"Array\") {\n for (const fieldName of ignoreFieldNames)\n dict[fieldName] = true\n }\n return _deepCopy(data, dict)\n \n}\n\nfunction _deepCopy(\n data,\n ignoreFieldNames: ignoreDictType\n) {\n \n // If the data is null or undefined then we return undefined\n if (data === null || data === undefined)\n return undefined\n \n // Get the data type and store it\n const dataType = Object.prototype.toString.call(data)\n .slice(8, -1)\n \n // DATE\n if (dataType == \"Date\") {\n const clonedDate = new Date()\n clonedDate.setTime(data.getTime())\n return clonedDate\n }\n \n // OBJECT\n if (dataType == \"Object\") {\n let copiedObject = {}\n \n for (const key of Object.keys(data)) {\n if (ignoreFieldNames != null && ignoreFieldNames[key] === true)\n continue\n copiedObject[key] = _deepCopy(data[key], ignoreFieldNames)\n }\n \n return copiedObject\n }\n \n // ARRAY\n if (dataType == \"Array\") {\n let copiedArray = []\n \n for (const item of data)\n copiedArray.push(_deepCopy(item, ignoreFieldNames))\n \n return copiedArray\n }\n \n return data\n}\n\n// ----------------------------------------------------------------------------\n\n/* Add a imports for these requires */\n","// Declare the TypeScript for Declaration Merging\n// https://www.typescriptlang.org/docs/handbook/declaration-merging.html\n\ninterface Array<T> {\n diff(a: Array<T>): Array<T>;\n intersect(a: Array<T>): Array<T>;\n remove(a: Array<T> | T): Array<T>;\n add(a: Array<T> | T): Array<T>;\n equals(array: Array<T> | null): boolean;\n bubbleSort(compFunc: (\n left: T,\n right: T\n ) => number): Array<T>;\n indexOf(item: T): number;\n}\n\n// Start the javascript type patching\n\nif (Array.prototype.diff == null) {\n Array.prototype.diff = function (a: Array<any>): Array<any> {\n return this.filter(function (i: any) {\n return !(a.indexOf(i) > -1)\n })\n }\n}\n\nif (Array.prototype.intersect == null) {\n Array.prototype.intersect = function (a: Array<any>): Array<any> {\n return this.filter(function (i) {\n return (a.indexOf(i) > -1)\n })\n }\n}\n\nif (Array.prototype.remove == null) {\n Array.prototype.remove = function (objectOrArray: Array<any> | any): Array<any> {\n if (objectOrArray == null)\n return this\n \n if (objectOrArray instanceof Array) {\n return this.diff(objectOrArray)\n \n }\n else {\n let index = this.indexOf(objectOrArray)\n if (index !== -1)\n this.splice(index, 1)\n return this\n }\n }\n}\n\nif (Array.prototype.add == null) {\n Array.prototype.add = function (objectOrArray: Array<any> | any): Array<any> {\n if (objectOrArray == null)\n return this\n \n // If for some reasons they are trying to add us to our self, throw an exception.\n if (objectOrArray === this)\n throw new Error(\"Array.add, I was passed myself, i can't add my self to myself.\")\n \n if (objectOrArray instanceof Array) {\n for (let i = 0; i < objectOrArray.length; ++i)\n this.push(objectOrArray[i])\n return this\n }\n \n this.push(objectOrArray)\n return this\n }\n}\n\nif (Array.prototype.equals == null) {\n Array.prototype.equals = function (array: Array<any> | null): boolean {\n // if the other array is a false value, return\n if (array == null)\n return false\n \n // compare object instances\n if (this === array)\n return true\n \n // compare lengths - can save a lot of time\n if (this.length !== array.length)\n return false\n \n for (let i = 0; i < this.length; i++) {\n // Check if we have nested arrays\n if (this[i] instanceof Array && array[i] instanceof Array) {\n // recurse into the nested arrays\n if (!this[i].compare(array[i]))\n return false\n \n }\n else if (this[i] !== array[i]) {\n // Warning - two different object instances will never be equal: {x:20} !=\n // {x:20}\n return false\n \n }\n }\n return true\n }\n}\n\nif (Array.prototype.bubbleSort == null) {\n Array.prototype.bubbleSort = function (compFunc: (\n left: any,\n right: any\n ) => number): Array<any> {\n let self = this\n \n function merge(\n left,\n right\n ) {\n let result = []\n \n while (left.length && right.length) {\n if (compFunc(left[0], right[0]) <= 0) {\n result.push(left.shift())\n }\n else {\n result.push(right.shift())\n }\n }\n \n while (left.length)\n result.push(left.shift())\n \n while (right.length)\n result.push(right.shift())\n \n return result\n }\n \n if (self.length < 2)\n return self.slice(0, self.length)\n \n let middle = parseInt((self.length / 2).toString())\n let left = self.slice(0, middle)\n let right = self.slice(middle, self.length)\n \n return merge(left.bubbleSort(compFunc), right.bubbleSort(compFunc))\n }\n}\n\n// ============================================================================\n// Array.indexOf prptotype function\n// Add if this browser (STUPID IE) doesn't support it\n\nif (Array.prototype.indexOf == null) {\n Array.prototype.indexOf = function (item: any): number {\n let len = this.length >>> 0\n \n let from = Number(arguments[1]) || 0\n from = (from < 0) ? Math.ceil(from) : Math.floor(from)\n if (from < 0)\n from += len\n \n for (; from < len; from++) {\n if (from in this && this[from] === item)\n return from\n }\n return -1\n }\n}\n","// ----------------------------------------------------------------------------\n// Typescript date - date fooler\nexport function now(): any {\n return new Date()\n}\n\nexport function logLong(\n message: string,\n start: any,\n payload: any | null = null\n) {\n let duration = now() - start\n let desc = \"\"\n \n // You get 5ms to do what you need before i call the performance cops.\n if (duration < 10)\n return\n \n if (payload != null) {\n desc = \", \" + JSON.stringify(payload.filt)\n }\n \n // console.log(`${message}, took ${duration}${desc}`);\n}\n\n// ----------------------------------------------------------------------------\nexport abstract class PayloadDelegateABC {\n \n abstract deflateAndEncode(payloadJson: string): Promise<string> ;\n \n abstract encodeEnvelope(payloadJson: string): Promise<string> ;\n \n abstract decodeAndInflate(vortexStr: string): Promise<string> ;\n \n abstract decodeEnvelope(vortexStr: string): Promise<string> ;\n \n}\n","import * as pako from \"pako\";\nimport * as base64 from \"base-64\";\nimport { PayloadDelegateABC } from \"./PayloadDelegateABC\";\n\nfunction btoa(data) {\n try {\n return window[\"btoa\"](data);\n } catch (e) {\n return base64.encode(data);\n }\n}\n\nfunction atob(data) {\n try {\n return window[\"atob\"](data);\n } catch (e) {\n return base64.decode(data);\n }\n}\n\nexport class PayloadDelegateInMainWeb extends PayloadDelegateABC {\n deflateAndEncode(payloadJson: string): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const compressedData: Uint8Array = pako.deflate(payloadJson);\n const compressedDataStr = new Uint8Array(compressedData).reduce(\n (acc: string, curr: number, i: number) =>\n acc + String.fromCharCode(curr),\n \"\",\n );\n const encodedData = btoa(compressedDataStr);\n resolve(encodedData);\n });\n }\n\n encodeEnvelope(payloadEnvelopeJson: string): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const vortexMsg = btoa(payloadEnvelopeJson);\n resolve(vortexMsg);\n });\n }\n\n decodeAndInflate(vortexStr: string): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n const compressedData = Uint8Array.from(\n atob(vortexStr),\n (v: string) => v.charCodeAt(0),\n );\n const payloadJson = pako.inflate(compressedData, { to: \"string\" });\n resolve(payloadJson);\n });\n }\n\n decodeEnvelope(vortexStr: string): Promise<string> {\n return new Promise<string>((resolve, reject) => {\n let payloadJson = atob(vortexStr);\n resolve(payloadJson);\n });\n }\n}\n","/*\n * ###############################################################################\n * Common Serialisation functions\n * ###############################################################################\n */\n\nimport {dictKeysFromObject} from \"./UtilMisc\";\nimport * as base64 from \"base-64\";\n\nexport class SerialiseUtil {\n public static readonly T_RAPUI_TUPLE = \"rt\";\n public static readonly T_RAPUI_PAYLOAD = \"rp\";\n public static readonly T_RAPUI_PAYLOAD_ENVELOPE = \"rpe\";\n public static readonly T_GENERIC_CLASS = \"gen\"; // NOT SUPPORTED\n public static readonly T_FLOAT = \"float\";\n public static readonly T_INT = \"int\";\n public static readonly T_STR = \"str\";\n public static readonly T_BYTES = \"bytes\";\n public static readonly T_BOOL = \"bool\";\n public static readonly T_DATETIME = \"datetime\";\n public static readonly T_DICT = \"dict\";\n public static readonly T_LIST = \"list\";\n public static readonly V_NULL = \"null\";\n public static readonly V_TRUE = \"1\";\n public static readonly V_FALSE = \"0\";\n public static readonly ISO8601_PY = \"%Y-%m-%d %H:%M:%S.%f%z\";\n public static readonly ISO8601 = \"YYYY-MM-DD HH:mm:ss.SSSSSSZZ\";\n\n // Rapui Serialised Type - Shortened for memory constraints.\n protected __rst: string;\n\n toStr(obj: any): string {\n let self = this;\n\n if (obj[\"toISOString\"] != null)\n // instanceof Date or moment\n return obj.toISOString().replace(\"Z\", \"000+0000\").replace(\"T\", \" \");\n\n if (typeof obj.constructor === \"boolean\")\n return obj ? SerialiseUtil.V_TRUE : SerialiseUtil.V_FALSE;\n\n if (typeof obj.constructor === \"string\") return obj;\n\n return obj.toString();\n }\n\n fromStr(val: string, typeName: string): any {\n let self = this;\n\n if (typeName === SerialiseUtil.T_STR) return val;\n\n if (typeName === SerialiseUtil.T_BYTES)\n return base64.decode(encodeURI(val));\n\n if (typeName === SerialiseUtil.T_BOOL)\n return val === SerialiseUtil.V_TRUE;\n\n if (\n typeName === SerialiseUtil.T_FLOAT ||\n typeName === SerialiseUtil.T_INT\n )\n return parseFloat(val);\n\n if (typeName === SerialiseUtil.T_DATETIME) return new Date(val);\n\n alert(\"fromStr - UNKNOWN TYPE\");\n }\n\n toRapuiType(value: any): string {\n let self = this;\n\n if (value == null) return SerialiseUtil.V_NULL;\n\n if (value.__rst === SerialiseUtil.T_RAPUI_TUPLE)\n return SerialiseUtil.T_RAPUI_TUPLE;\n\n if (value.__rst === SerialiseUtil.T_RAPUI_PAYLOAD)\n return SerialiseUtil.T_RAPUI_PAYLOAD;\n\n if (value.__rst === SerialiseUtil.T_RAPUI_PAYLOAD_ENVELOPE)\n return SerialiseUtil.T_RAPUI_PAYLOAD_ENVELOPE;\n\n if (value instanceof Date) return SerialiseUtil.T_DATETIME;\n\n if (value.constructor === Number) return SerialiseUtil.T_FLOAT;\n\n if (value.constructor === String) return SerialiseUtil.T_STR;\n\n if (value.constructor === Boolean) return SerialiseUtil.T_BOOL;\n\n if (value.constructor === Array) return SerialiseUtil.T_LIST;\n\n if (value.constructor === Object) return SerialiseUtil.T_DICT;\n\n alert(\"toRapuiType - UNKNOWN TYPE\");\n }\n\n rapuiEquals(\n obj1: any,\n obj2: any,\n obj1FieldNames: string[],\n obj2FieldNames: string[],\n ): boolean {\n let self = this;\n\n let fieldNames1: string[] = obj1FieldNames;\n fieldNames1.sort();\n\n let fieldNames2: string[] = obj2FieldNames;\n fieldNames2.sort();\n\n if (!fieldNames1.equals(fieldNames2)) return false;\n\n // Create the <items> base element\n for (\n let fieldIndex = 0;\n fieldIndex < fieldNames1.length;\n ++fieldIndex\n ) {\n let name = fieldNames1[fieldIndex];\n let field1 = obj1[name];\n let field2 = obj2[name];\n\n if (field1 === undefined && field2 === undefined) continue;\n else if (field1 === undefined || field2 === undefined) return false;\n\n let type1 = self.toRapuiType(field1);\n let type2 = self.toRapuiType(field2);\n\n if (type1 !== type2) return false;\n\n if (\n type1 === SerialiseUtil.T_RAPUI_TUPLE ||\n type1 === SerialiseUtil.T_RAPUI_PAYLOAD ||\n type1 === SerialiseUtil.T_RAPUI_PAYLOAD_ENVELOPE\n ) {\n if (!field1.equals(field2)) return false;\n } else if (type1 === SerialiseUtil.T_LIST) {\n let indexes = [];\n for (let index = 0; index < field1.length; index++) {\n indexes.push(index);\n }\n\n let isEqual = self.rapuiEquals(\n field1,\n field2,\n indexes,\n indexes,\n );\n if (!isEqual) return false;\n } else if (type1 === SerialiseUtil.T_DICT) {\n let isEqual = self.rapuiEquals(\n field1,\n field2,\n dictKeysFromObject(field1),\n dictKeysFromObject(field2),\n );\n if (!isEqual) return false;\n } else if (type1 === SerialiseUtil.T_DATETIME) {\n if (field1.getTime() !== field2.getTime()) return false;\n } else {\n if (field1 !== field2) return false;\n }\n }\n return true;\n }\n}\n","// Declare the TypeScript for Declaration Merging\n// https://www.typescriptlang.org/docs/handbook/declaration-merging.html\n\ninterface String {\n replaceAll(\n stringToFind: string,\n stringToReplace: string\n ): string;\n format(...args: any[]): string;\n startsWith(str: string): boolean;\n endsWith(str: string): boolean;\n isPrintable(): boolean;\n}\n\nif (String.prototype.replaceAll == null) {\n String.prototype.replaceAll = function (\n stringToFind: string,\n stringToReplace: string\n ): string {\n let temp = this\n while (temp.indexOf(stringToFind) !== -1)\n temp = temp.replace(stringToFind, stringToReplace)\n return temp\n }\n}\n\nif (String.prototype.format == null) {\n String.prototype.format = function () {\n let args = arguments\n return this.replace(/{(\\d+)}/g, function (\n match,\n num\n ) {\n return typeof args[num] !== \"undefined\" ? args[num] : match\n })\n }\n}\n\nif (String.prototype.trim == null) {\n String.prototype.trim = function () {\n return String(this)\n .replace(/^\\s+|\\s+$/g, \"\")\n }\n}\n\nif (String.prototype.startsWith == null) {\n // see below for better implementation!\n String.prototype.startsWith = function (str: string): boolean {\n return this.slice(0, str.length) === str\n }\n}\n\nif (String.prototype.endsWith == null) {\n String.prototype.endsWith = function (pattern: string): boolean {\n let d = this.length - pattern.length\n return d >= 0 && this.lastIndexOf(pattern) === d\n }\n}\n\nif (String.prototype.isPrintable == null) {\n String.prototype.isPrintable = function (): boolean {\n let re = /^[\\x20-\\x7e]*$/\n return re.test(this)\n }\n}\n","import { SerialiseUtil } from \"./exports\";\nimport { deepCopy } from \"./UtilMisc\";\nimport \"./UtilString\";\nimport { PayloadEnvelope } from \"./PayloadEnvelope\";\nimport { Payload } from \"./Payload\";\n\nconst JSONABLE_TYPES = {};\n\nexport function addJsonableType(jsonableType: string) {\n return function (_Class: any) {\n JSONABLE_TYPES[jsonableType] = _Class;\n return _Class;\n };\n}\n\n/**\n * ############################################################################### #\n * JSON Serialisation functions\n * ###############################################################################\n */\nexport class Jsonable extends SerialiseUtil {\n public _tupleType: string;\n protected _rawJonableFields = null;\n\n public static readonly JSON_CLASS_TYPE = \"_ct\";\n // private static readonly JSON_CLASS = \"_c\";\n private static readonly JSON_TUPLE_TYPE = \"_c\";\n private static readonly JSON_FIELD_TYPE = \"_ft\";\n private static readonly JSON_FIELD_DATA = \"_fd\";\n\n constructor() {\n super();\n /*\n * Jsonable This class gives simple objects suport for serialising to/from json.\n * It handles Number, String, Array and Date. It doesn't handle more complex\n * structures (hence why Payloads have their own functions to do this)\n */\n let self: Jsonable = this;\n\n self.__rst = SerialiseUtil.T_GENERIC_CLASS;\n }\n\n private _isRawJsonableField(name: string): boolean {\n if (name == null || name.length == 0) {\n return false;\n }\n if (this._rawJonableFields == null) {\n return false;\n }\n return this._rawJonableFields.indexOf(name) != -1;\n }\n\n _fieldNames() {\n let self = this;\n\n let keys = [];\n for (let k in self) {\n if (!k.startsWith(\"_\") && self.hasOwnProperty(k)) {\n keys.push(k);\n }\n }\n return keys;\n }\n\n equals(other) {\n let self = this;\n\n return self.rapuiEquals(\n self,\n other,\n self._fieldNames(),\n other._fieldNames(),\n );\n }\n\n toRestfulJsonDict() {\n return this._tupleToJsonDict(true);\n }\n\n toJsonDict() {\n return this._tupleToJsonDict(false);\n }\n\n private _tupleToJsonDict(useShortNames: boolean) {\n let self = this;\n\n let jsonDict = {};\n if (!useShortNames) {\n jsonDict[Jsonable.JSON_CLASS_TYPE] = self.__rst;\n }\n\n if (!useShortNames && self._tupleType != null) {\n jsonDict[Jsonable.JSON_TUPLE_TYPE] = self._tupleType;\n }\n\n /* This is in the PY version\n else\n jsonDict[JSON_CLASS] = className(self)\n */\n\n let fieldNames = self._fieldNames();\n // fieldNames.sort(); // Why?\n\n // Create the <items> base element\n for (let i = 0; i < fieldNames.length; ++i) {\n let name = fieldNames[i];\n self.toJsonField(self[name], jsonDict, name, useShortNames);\n }\n\n return jsonDict;\n }\n\n fromJsonDict(jsonDict: {}): any {\n /*\n * From Json Returns and instance of this object populated with data from the\n * json dict\n *\n */\n for (const name of Object.keys(jsonDict)) {\n if (name.startsWith(\"_\")) {\n continue;\n }\n\n if (this._isRawJsonableField(name)) {\n this[name] = jsonDict[name];\n } else {\n this[name] = this.fromJsonField(jsonDict[name]);\n }\n }\n\n // This is only required for unit tests new Tuple().fromJsonDict(..)\n if (jsonDict[Jsonable.JSON_CLASS_TYPE] == SerialiseUtil.T_RAPUI_TUPLE) {\n this._tupleType = jsonDict[Jsonable.JSON_TUPLE_TYPE];\n }\n\n return this;\n }\n\n toJsonField(\n value: any,\n jsonDict: {} | null = null,\n name: string | null = null,\n useShortNames: boolean = false,\n ): any {\n let self = this;\n\n let convertedValue = null;\n let valueType =\n value == null ? SerialiseUtil.V_NULL : self.toRapuiType(value);\n\n if (this._isRawJsonableField(name)) {\n convertedValue = deepCopy(value);\n } else if (\n valueType === SerialiseUtil.T_RAPUI_TUPLE ||\n valueType === SerialiseUtil.T_RAPUI_PAYLOAD ||\n valueType === SerialiseUtil.T_RAPUI_PAYLOAD_ENVELOPE\n ) {\n if (useShortNames) {\n convertedValue = value.toRestfulJsonDict();\n } else {\n convertedValue = value.toJsonDict();\n }\n } else if (valueType === SerialiseUtil.T_DICT) {\n // Treat these like dicts\n convertedValue = {};\n for (const keyName of Object.keys(value)) {\n if (useShortNames) {\n self.toJsonField(\n value[keyName],\n convertedValue,\n keyName,\n true,\n );\n } else {\n self.toJsonField(value[keyName], convertedValue, keyName);\n }\n }\n } else if (valueType === SerialiseUtil.T_LIST) {\n convertedValue = [];\n // List\n for (let i = 0; i < value.length; ++i) {\n let element;\n if (useShortNames) {\n element = self.toJsonField(value[i], null, null, true);\n } else {\n element = self.toJsonField(value[i]);\n }\n convertedValue.push(element);\n }\n } else if (\n valueType === SerialiseUtil.T_FLOAT ||\n valueType === SerialiseUtil.T_INT ||\n valueType === SerialiseUtil.T_BOOL ||\n valueType === SerialiseUtil.T_STR\n ) {\n convertedValue = value;\n } else if (valueType === SerialiseUtil.V_NULL) {\n convertedValue = null;\n } else {\n convertedValue = self.toStr(value);\n }\n\n // Non standard values need a dict to store their value type attributes\n // Create a sub dict that contains the value and type\n let jsonStandardTypes = [\n SerialiseUtil.T_FLOAT,\n SerialiseUtil.T_STR,\n SerialiseUtil.T_INT,\n SerialiseUtil.V_NULL,\n SerialiseUtil.T_BOOL,\n SerialiseUtil.T_LIST,\n SerialiseUtil.T_DICT,\n ];\n\n if (\n jsonStandardTypes.indexOf(valueType) === -1 &&\n !(value instanceof Jsonable)\n ) {\n let typedData = {};\n typedData[Jsonable.JSON_FIELD_TYPE] = valueType;\n typedData[Jsonable.JSON_FIELD_DATA] = convertedValue;\n convertedValue = typedData;\n }\n\n /* Now assign the value and it's value type if applicable */\n if (name != null && jsonDict != null) {\n jsonDict[name] = convertedValue;\n }\n\n return convertedValue;\n }\n\n // ----------------------------------------------------------------------------\n fromJsonField(value: any, valueType: string = null) {\n let self = this;\n if (valueType === SerialiseUtil.V_NULL || value == null) {\n return null;\n }\n\n if (valueType === SerialiseUtil.T_INT) {\n return value;\n }\n\n if (value[Jsonable.JSON_CLASS_TYPE] != null) {\n valueType = value[Jsonable.JSON_CLASS_TYPE];\n }\n\n // JSON handles these types natively,\n // if there is no type then these are the right types\n if (valueType == null) {\n valueType = self.toRapuiType(value);\n if (\n [\n SerialiseUtil.T_BOOL,\n SerialiseUtil.T_FLOAT,\n SerialiseUtil.T_INT,\n SerialiseUtil.T_STR,\n ].indexOf(valueType) !== -1\n ) {\n return value;\n }\n }\n\n if (value[Jsonable.JSON_FIELD_TYPE] != null) {\n return self.fromJsonField(\n value[Jsonable.JSON_FIELD_DATA],\n value[Jsonable.JSON_FIELD_TYPE],\n );\n }\n\n // Tuple\n if (valueType === SerialiseUtil.T_RAPUI_TUPLE) {\n let Tuple = JSONABLE_TYPES[SerialiseUtil.T_RAPUI_TUPLE];\n let tupleType = value[Jsonable.JSON_TUPLE_TYPE];\n let newTuple = Tuple.create(tupleType);\n\n return newTuple.fromJsonDict(value);\n }\n\n // Handle the case of payloads within payloads\n if (valueType === SerialiseUtil.T_RAPUI_PAYLOAD) {\n return new Payload().fromJsonDict(value);\n }\n\n // Payload Endpoint\n if (valueType === SerialiseUtil.T_RAPUI_PAYLOAD_ENVELOPE) {\n return new PayloadEnvelope().fromJsonDict(value);\n }\n\n /* SKIP T_GENERIC_CLASS */\n if (valueType === SerialiseUtil.T_DICT) {\n let restoredDict = {};\n for (const subName of Object.keys(value)) {\n restoredDict[subName] = self.fromJsonField(value[subName]);\n }\n\n return restoredDict;\n }\n\n if (valueType === SerialiseUtil.T_LIST) {\n let restoredList = [];\n for (let i = 0; i < value.length; ++i) {\n restoredList.push(self.fromJsonField(value[i]));\n }\n\n return restoredList;\n }\n\n // Handle single value\n return self.fromStr(value, valueType);\n }\n}\n","import deepEqual from \"deep-equal\";\nimport { dictKeysFromObject } from \"./UtilMisc\";\nimport { addJsonableType, Jsonable, SerialiseUtil } from \"./exports\";\n\nexport interface TupleChangeI {\n fieldName: string;\n oldValue: any;\n newValue: any;\n}\n\n/** Tuples implementation details.\n *\n * We're not going to have fully fledged tuples in the browser. As far as the\n * browser is concerned, it will recieve tuples which will have all the fields\n * and then it will create tuples to send back, populating the fields it needs.\n *\n * There should be some checks when it gets back to the server to ensure the\n * populated fields exist in the tuples when it deserialises it.\n *\n */\n@addJsonableType(SerialiseUtil.T_RAPUI_TUPLE)\nexport class Tuple extends Jsonable {\n // Change Tracking Enabled - Shortened for memory conservation\n private _ct: boolean = false;\n\n // Change Tracking Reference State - Shortened for memory conservation\n private _ctrs: Tuple | null = null;\n\n constructor(tupleType: string | null = null) {\n super();\n let self = this;\n self.__rst = SerialiseUtil.T_RAPUI_TUPLE;\n self._tupleType = tupleType;\n }\n\n static create(tupleType: string) {\n if (TUPLE_TYPES[tupleType] == null) {\n return new Tuple(tupleType);\n } else {\n // Tuples set their own types, don't pass anything to the constructor\n return new TUPLE_TYPES[tupleType]();\n }\n }\n\n _tupleName(): string {\n return this._tupleType;\n }\n\n // ---------------\n // Start change detection code\n\n _setChangeTracking(on: boolean = true) {\n this._ctrs = new Tuple();\n this._ctrs.fromJsonDict(this.toJsonDict());\n this._ct = on;\n }\n\n _detectedChanges(reset: boolean = true): TupleChangeI[] {\n let changes = [];\n for (let key of dictKeysFromObject(this)) {\n let old_ = this._ctrs[key];\n let new_ = this[key];\n if (deepEqual(old_, new_)) {\n continue;\n }\n\n changes.push({\n fieldName: key,\n oldValue: old_,\n newValue: new_,\n });\n }\n\n if (reset) {\n this._setChangeTracking(true);\n }\n\n return changes;\n }\n}\n\ninterface ITuple {\n new (name: string | null): Tuple;\n}\n\nexport let TUPLE_TYPES = {};\n\nexport function addTupleType(_Class: Function) {\n let inst = new (<any>_Class)();\n TUPLE_TYPES[inst._tupleType] = _Class;\n}\n","import { Jsonable, SerialiseUtil } from \"./exports\";\nimport { assert } from \"./UtilMisc\";\nimport \"./UtilArray\";\nimport { PayloadDelegateABC } from \"./payload/PayloadDelegateABC\";\nimport { PayloadDelegateInMainWeb } from \"./payload/PayloadDelegateInMainWeb\";\nimport { Payload } from \"./Payload\";\n\n// ----------------------------------------------------------------------------\n// Payload class\n\n/**\n *\n * This class is serialised and transferred over the vortex to the server.\n */\nexport class PayloadEnvelope extends Jsonable {\n private static workerDelegate = new PayloadDelegateInMainWeb();\n\n static readonly vortexUuidKey = \"__vortexUuid__\";\n static readonly vortexNameKey = \"__vortexName__\";\n\n filt: {};\n data: any | string | null;\n result: string | {} | null = null;\n date: Date | null = null;\n\n /**\n * Payload Envelope\n * This class is serialised and tranferred over the vortex to the server.\n * @param filt The filter that the server handler is listening for\n * @param data: The encoded payload to go into this envelope\n * different location @depreciated\n * @param date The date for this envelope, it should match the payload.\n */\n constructor(\n filt: {} = {},\n data: any | string | null = null,\n date: Date | null = null\n ) {\n super();\n\n this.__rst = SerialiseUtil.T_RAPUI_PAYLOAD_ENVELOPE;\n\n this.filt = filt;\n this.data = data;\n\n this.date = date == null ? new Date() : this.date;\n }\n\n static setWorkerDelegate(delegate: PayloadDelegateABC) {\n PayloadEnvelope.workerDelegate = delegate;\n }\n\n get encodedPayload(): string | null {\n if (!this.data?.length) {\n return null;\n }\n\n if (typeof this.data !== \"string\") {\n throw new Error(\"PayloadEnvelope: encodedPayload is not an array\");\n }\n\n // noinspection UnnecessaryLocalVariableJS\n const str: any = this.data;\n return str;\n }\n\n set encodedPayload(val: string | null) {\n if (!val?.length || typeof val !== \"string\") {\n throw new Error(\"PayloadEnvelope: val is not null or string\");\n }\n\n this.data = val;\n }\n\n // -------------------------------------------\n // Envelope method\n\n isEmpty() {\n // Ignore the connection start vortexUuid value\n // It's sent as the first response when we connect.\n for (let property in this.filt) {\n if (property === PayloadEnvelope.vortexUuidKey) continue;\n // Anything else, return false\n return false;\n }\n\n return (\n (this.encodedPayload == null || this.encodedPayload.length === 0) &&\n this.result == null\n );\n }\n\n async decodePayload(): Promise<Payload> {\n if (this.encodedPayload == null) {\n throw new Error(\"PayloadEnvelope: decodePayload, data is null\");\n }\n return await Payload.fromEncodedPayload(this.encodedPayload);\n }\n\n // -------------------------------------------\n // JSON Related method\n\n private _fromJson(jsonStr: string): Promise<PayloadEnvelope> {\n return Promise.resolve(JSON.parse(jsonStr)) //\n .then((jsonDict) => {\n assert(jsonDict[Jsonable.JSON_CLASS_TYPE] === this.__rst);\n return this.fromJsonDict(jsonDict);\n });\n }\n\n private _toJson(): Promise<string> {\n return Promise.resolve(this.toJsonDict()) //\n .then((jsonDict) => JSON.stringify(jsonDict));\n }\n\n static fromVortexMsg(vortexStr: string): Promise<PayloadEnvelope> {\n // Websockets do not require base64 encoding\n if (vortexStr[0] === \"{\") {\n // noinspection UnnecessaryLocalVariableJS\n const jsonStr = vortexStr;\n return new PayloadEnvelope()._fromJson(jsonStr);\n }\n\n // noinspection UnnecessaryLocalVariableJS\n const result = PayloadEnvelope.workerDelegate\n .decodeEnvelope(vortexStr)\n .then((jsonStr) => new PayloadEnvelope()._fromJson(jsonStr));\n return result;\n }\n\n toVortexMsg(): Promise<string> {\n return this._toJson() //\n .then((jsonStr) =>\n PayloadEnvelope.workerDelegate.encodeEnvelope(jsonStr)\n );\n }\n}\n","import { assert } from \"./UtilMisc\";\nimport \"./UtilArray\";\nimport { PayloadDelegateInMainWeb } from \"./payload/PayloadDelegateInMainWeb\";\nimport { PayloadDelegateABC } from \"./payload/PayloadDelegateABC\";\nimport { PayloadEnvelope } from \"./PayloadEnvelope\";\nimport { Jsonable, SerialiseUtil, Tuple } from \"./exports\";\n\n// ----------------------------------------------------------------------------\n// Types\n\n/**\n * IPayloadFilt\n * This interface defines the structure for a valid payload filter.\n */\nexport interface IPayloadFilt {\n key: string;\n\n [more: string]: any;\n}\n\n// ----------------------------------------------------------------------------\n// Payload class\n\n/**\n *\n * This class is serialised and transferred over the vortex to the server.\n */\nexport class Payload extends Jsonable {\n private static workerDelegate = new PayloadDelegateInMainWeb();\n\n filt: {};\n tuples: Array<Tuple | any>;\n date: Date | null = null;\n\n /**\n * Payload\n * This class is serialised and tranferred over the vortex to the server.\n * @param filt The filter that the server handler is listening for\n * @param tuples: The tuples to init the Payload with\n * different location @depreciated\n * @param date The date for this envelope, it should match the payload.\n */\n constructor(\n filt: {} = {},\n tuples: Array<Tuple | any> = [],\n date: Date | null = null,\n ) {\n super();\n\n this.__rst = SerialiseUtil.T_RAPUI_PAYLOAD;\n\n this.filt = filt;\n this.tuples = tuples;\n this.date = date == null ? new Date() : this.date;\n }\n\n static setWorkerDelegate(delegate: PayloadDelegateABC) {\n Payload.workerDelegate = delegate;\n }\n\n // -------------------------------------------\n // JSON Related method\n\n private _fromJson(jsonStr: string): Promise<Payload> {\n return Promise.resolve(JSON.parse(jsonStr)).then((jsonDict) => {\n assert(jsonDict[Jsonable.JSON_CLASS_TYPE] === this.__rst);\n return this.fromJsonDict(jsonDict);\n });\n }\n\n private async _toJson(): Promise<string> {\n const jsonDict = await this.toJsonDict();\n return await JSON.stringify(jsonDict);\n }\n\n static async fromEncodedPayload(\n encodedPayloadStr: string,\n ): Promise<Payload> {\n const jsonStr =\n await Payload.workerDelegate.decodeAndInflate(encodedPayloadStr);\n return await new Payload()._fromJson(jsonStr);\n }\n\n async toEncodedPayload(): Promise<string> {\n const jsonStr = await this._toJson();\n return await Payload.workerDelegate.deflateAndEncode(jsonStr);\n }\n\n async makePayloadEnvelope(): Promise<any> {\n const encodedThis = await this.toEncodedPayload();\n return new PayloadEnvelope(this.filt, encodedThis, this.date);\n }\n}\n","import { PayloadEndpoint } from \"./PayloadEndpoint\"\nimport { PayloadEnvelope } from \"./PayloadEnvelope\"\n\nexport let STOP_PROCESSING = \"STOP_PROCESSING\"\n\nexport class PayloadIO {\n private _endpoints: PayloadEndpoint[]\n \n constructor() {\n let self = this\n self._endpoints = []\n }\n \n add(endpoint) {\n let self = this\n self._endpoints.add(endpoint)\n }\n \n remove(endpoint) {\n let self = this\n self._endpoints.remove(endpoint)\n }\n \n process(payloadEnvelope: PayloadEnvelope) {\n let self = this\n // Make a copy of the endpoints array, it may change endpoints\n // can remove them selves during iteration.\n let endpoints = self._endpoints.slice(0)\n for (let i = 0; i < endpoints.length; ++i) {\n if (endpoints[i].process(payloadEnvelope) === STOP_PROCESSING)\n break\n }\n }\n \n}\n\nexport let payloadIO = new PayloadIO()\n","import { payloadIO } from \"./PayloadIO\";\nimport { IPayloadFilt } from \"./Payload\";\nimport { assert, dateStr, dictKeysFromObject } from \"./UtilMisc\";\nimport \"./UtilArray\";\nimport { NgLifeCycleEvents } from \"../util/NgLifeCycleEvents\";\nimport { Observable, Subject } from \"rxjs\";\nimport { PayloadEnvelope } from \"./PayloadEnvelope\";\n\nexport class PayloadEndpoint {\n private _filt: { key: string };\n private _lastPayloadDate: Date | null;\n private _processLatestOnly: boolean;\n\n constructor(\n component: NgLifeCycleEvents,\n filter: IPayloadFilt,\n processLatestOnly: boolean = false\n ) {\n let self = this;\n\n self._filt = filter;\n self._lastPayloadDate = null;\n self._processLatestOnly = processLatestOnly === true;\n\n assert(self._filt != null, \"Payload filter is null\");\n\n if (self._filt.key == null) {\n let e = new Error(`There is no 'key' in the payload filt \\\n , There must be one for routing - ${JSON.stringify(\n self._filt\n )}`);\n console.log(e);\n throw e;\n }\n\n payloadIO.add(self);\n\n // Add auto tear downs for angular scopes\n let subscription = component.onDestroyEvent.subscribe(() => {\n this.shutdown();\n subscription.unsubscribe();\n });\n\n this._observable = new Subject<PayloadEnvelope>();\n }\n\n private _observable: Subject<PayloadEnvelope>;\n\n get observable(): Observable<PayloadEnvelope> {\n return this._observable;\n }\n\n /**\n * Process Payload\n * Check if the payload is meant for us then process it.\n *\n * @return null, or if the function is overloaded, you could return STOP_PROCESSING\n * from PayloadIO, which will tell it to stop processing further endpoints.\n */\n process(payloadEnvelope: PayloadEnvelope): null | string {\n if (!this.checkFilt(this._filt, payloadEnvelope.filt)) return null;\n\n if (!this.checkDate(payloadEnvelope)) return null;\n\n try {\n this._observable.next(payloadEnvelope);\n } catch (e) {\n // NOTE: Observables automatically remove observers when the raise exceptions.\n console.log(`${dateStr()} ERROR: PayloadEndpoint.process, observable has been removed\n ${e.toString()}\n ${JSON.stringify(payloadEnvelope.filt)}`);\n }\n\n return null;\n }\n\n shutdown() {\n let self = this;\n payloadIO.remove(self);\n if (this._observable[\"observers\"] != null) {\n for (let observer of this._observable[\"observers\"]) {\n observer[\"unsubscribe\"]();\n }\n }\n }\n\n private checkFilt(leftFilt, rightFilt): boolean {\n for (let key of dictKeysFromObject(leftFilt, true)) {\n if (!rightFilt.hasOwnProperty(key)) return false;\n\n let left = leftFilt[key];\n let right = rightFilt[key];\n\n // Handle the case of null !== undefined\n if (left == null && right == null) return true;\n\n if (typeof left !== typeof right) return false;\n\n // Handle special case for Arrays using our equals method in ArrayUtil\n if (left instanceof Array) {\n if (left.sort().equals(right.sort())) continue;\n else return false;\n }\n\n // Handle special case for Arrays using our equals method in ArrayUtil\n if (left instanceof Object) {\n if (this.checkFilt(left, right)) continue;\n else return false;\n }\n\n if (left !== right) return false;\n }\n\n return true;\n }\n\n private checkDate(payload): boolean {\n if (this._processLatestOnly) {\n if (\n this._lastPayloadDate == null ||\n this._lastPayloadDate < payload.date\n )\n this._lastPayloadDate = payload.date;\n else return false;\n }\n\n return true;\n }\n}\n","/**\n * The file defines some commonly used filter keys\n */\n\nexport let rapuiServerEcho = \"rapuiServerEcho\"\nexport let rapuiClientEcho = \"rapuiClientEcho\"\nexport let rapuiVortexUuid = \"rapuiVortexUuid\"\n\nexport let plIdKey = \"id\"\nexport let plDeleteKey = \"delete\"\n\n","import { Payload } from \"./Payload\";\nimport { dateStr } from \"./UtilMisc\";\nimport { rapuiClientEcho } from \"./PayloadFilterKeys\";\nimport { payloadIO } from \"./PayloadIO\";\nimport { VortexStatusService } from \"./VortexStatusService\";\nimport { PayloadEnvelope } from \"./PayloadEnvelope\";\nimport { Network } from \"@capacitor/network\";\n\n/**\n * Server response timeout in milliseconds\n * @type {number}\n */\nexport let SERVER_RESPONSE_TIMEOUT_SECONDS = 20.0;\n\nexport enum VortexClientStateE {\n Idle,\n Connecting,\n Online,\n Closing,\n Closed,\n Shutdown,\n}\n\nexport abstract class VortexClientABC {\n readonly HEART_BEAT_PERIOD_SECONDS = 10.0;\n readonly HEART_BEAT_TIMEOUT_SECONDS = 180.0;\n readonly RECONNECT_BACKOFF_SECONDS =