UNPKG

spy-client

Version:

spy client

753 lines (740 loc) 28.5 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SpyLocalCache = factory()); }(this, (function () { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } /** * @file Huffman * @author kaivean */ /** * 赫夫曼编码 基本介绍 1.赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式, 属于一种程序算法 赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。 2.赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在20%~90%之间 赫夫曼码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,称之为最佳编码 * * */ var HuffmanNode = /** @class */ (function () { function HuffmanNode(data, weight) { this.data = data; this.weight = weight; } // 前序遍历 HuffmanNode.prototype.preOrder = function (arr) { arr.push(this); if (this.left) { this.left.preOrder(arr); } if (this.right) { this.right.preOrder(arr); } }; return HuffmanNode; }()); /** * * @param {接受字符数组} bytes * @return 返回的就是list形式 */ function getNodes(bytes) { // 创建一个list var list = []; // counts 统计每一个byte出现的次数 var counts = {}; for (var _i = 0, bytes_1 = bytes; _i < bytes_1.length; _i++) { var b = bytes_1[_i]; var count = counts[b]; // map还没有这个字符数据 if (count == null) { counts[b] = 1; } else { counts[b]++; } } for (var _a = 0, _b = Object.entries(counts); _a < _b.length; _a++) { var _c = _b[_a], key = _c[0], value = _c[1]; list.push(new HuffmanNode(key, value)); } return list; } // 通过list创建赫夫曼树 function createHuffmanTree(nodes) { var compareFun = function (a, b) { return a.weight - b.weight; }; while (nodes.length > 1) { // 排序,从小到大 nodes.sort(compareFun); // 取出第一颗最小的二叉树 var leftNode = nodes.shift(); var rightNode = nodes.shift(); if (leftNode && rightNode) { // 创建一个新的二叉树,它的根节点,没有data,只有权值 var parent_1 = new HuffmanNode(null, leftNode.weight + rightNode.weight); parent_1.left = leftNode; parent_1.right = rightNode; // 将新的二叉树,加入到nodes nodes.unshift(parent_1); } } // nodes最后的节点,就是根节点 return nodes.shift(); } // 生成赫夫曼树对应的赫夫曼编码表 function getHuffmanCodes(root) { if (root == null) { return null; } // 生成赫夫曼树对应的赫夫曼编码表 // 思路 // 1.将赫夫曼编码表存放在map里面 // 2.在生成赫夫曼编码表时,需要拼接路径,定义一个数组string,存储某个叶子节点的路径 var huffmanCodes = {}; var strings = []; /** * 将传入的node节点的所有叶子节点的赫夫曼编码得到,并放入到huffmanCodes集合中 * @param {传入节点} node * @param {路径:左子节点是0,右子节点是1} code * @param {用于拼接路径} string */ function getCodes(node, code, strs) { var string2 = [].concat(strs); // 将code加入到string中 string2.push(code); if (node != null) { // 如果node == null不处理 // 判断当前node是叶子节点还是非叶子节点 if (node.data == null) { // 非叶子节点 // 递归处理 // 向左递归 getCodes(node.left, '0', string2); // 向右递归 getCodes(node.right, '1', string2); } else { // 说明是一个叶子节点 // 就表示找到了某个叶子节点的最后 huffmanCodes[node.data] = string2.join(''); } } } getCodes(root, '', strings); return huffmanCodes; } /** * 编写一个方法,将字符串对应的bytes数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩后的byte数组 * @param {原始的字符串对应的bytes数组} bytes * @param {生成的赫夫曼编码表} huffmanCodes * @return 返回的是字符串对应的一个byte数组 */ function zip(bytes, huffmanCodes) { // 1.利用huffmanCodes将bytes转成赫夫曼编码对应的字符串 var string = []; // 遍历数组 for (var _i = 0, bytes_2 = bytes; _i < bytes_2.length; _i++) { var b = bytes_2[_i]; string.push(huffmanCodes[b]); } return string; } function huffStringToByte(strs) { // 计算赫夫曼编码字符串的长度 var str = strs.join(''); var len = Math.ceil(str.length / 8); // 创建存储压缩后的byte数组 var huffmanCodeByte = new Array(len + 1); var index = 0; var strByte = ''; // 记录是第几个byte for (var i = 0; i < str.length; i += 8) { strByte = str.substring(i, i + 8); // 将strByte转成一个byte,放入huffmanCodeByte huffmanCodeByte[index] = parseInt(strByte, 2); index++; } // 记录最后一位二进制码的长度,因为,比如最后一位二进制strByte为00101时, // parseInt(strByte, 2)后等于5,前面的两个00已经丢失,所以必须记录长度,以便解码时补足前面的0 huffmanCodeByte[index] = strByte.length; return huffmanCodeByte; } // 使用一个方法,封装前面的方法,便于调用 /** * * @param {原始的字符串对应的字节数组} bytes * @returns 是经过赫夫曼编码处理后,压缩后的字节数组 * */ function huffmanZip(bytes) { // 1.生成节点数组 var nodes = getNodes(bytes); // 2.根据节点数组创建赫夫曼树 var root = createHuffmanTree(nodes); // 3.根据赫夫曼树生成赫夫曼编码 var hufumanCodes = getHuffmanCodes(root); // 4.根据生成的赫夫曼编码生成压缩后的赫夫曼编码字节数组 var hufumanStrArr = zip(bytes, hufumanCodes); var hufumanByteArr = huffStringToByte(hufumanStrArr); return { result: hufumanByteArr, codes: hufumanCodes }; } // 完成数据的解压 // 思路 // 1.将huffmanBytesArr先转成赫夫曼编码对应的二进制字符串 // 2.将赫夫曼编码对应的二进制的字符串转成赫夫曼编码字符串 /** * * @param {表示是否需要补高位,如果是true,表示需要,如果是false,表示不需要,如果是最后一个字节不需要补高位} flag * @param {传入的byte} byte * @returns 是byte对应的二进制字符串 */ function huffByteToString(flag, byte) { // 如果是 if (flag) { byte |= 256; } var str = Number(byte).toString(2); if (flag) { return str.substring(str.length - 8); } return str; } /** * 编写一份方法,完成对压缩数据的解码 * @param {赫夫曼编码表} huffmanCodes * @param {赫夫曼编码得到的二进制数组} huffmanBytes */ function decode(huffmanCodes, huffmanBytes) { // 1.先得到二进制字符串 形式11001111111011...... var heffmanStrArr = []; for (var i = 0; i < huffmanBytes.length - 1; i++) { // 判断是不是最后一个字节 var flag = (i !== huffmanBytes.length - 2); heffmanStrArr.push(huffByteToString(flag, huffmanBytes[i])); } // 最后一位记录的是最后一位二进制字符串的长度,该长度主要用于补足最后一位丢失的0,所以要单独处理, var lastByteStr = heffmanStrArr[heffmanStrArr.length - 1]; var lastByteLength = huffmanBytes[huffmanBytes.length - 1]; lastByteStr = '00000000'.substring(8 - (lastByteLength - lastByteStr.length)) + lastByteStr; heffmanStrArr[heffmanStrArr.length - 1] = lastByteStr; // 把赫夫曼编码表进行调换 var map = {}; for (var _i = 0, _a = Object.entries(huffmanCodes); _i < _a.length; _i++) { var _b = _a[_i], key = _b[0], value = _b[1]; map[value] = key; } var heffmanStr = heffmanStrArr.join(''); var list = []; var len = heffmanStr.length; for (var i = 0; i < len;) { var count = 1; var flag = true; var b = null; while (flag && i + count <= len) { // 取出一个1或0 // i不动,count移动,直到匹配到一个字符 var key = heffmanStr.substring(i, i + count); if (key === '') { break; } b = map[key]; if (!b) { // 没有匹配到 count++; } else { // 匹配到 flag = false; } } list.push(parseInt(b, 10)); i += count; } // 当for循环结束后,list中就存放了所有的字符 return list; } // js byte[] 和string 相互转换 UTF-8 function stringToByte(str) { var bytes = []; for (var index = 0; index < str.length; index++) { bytes.push(str.charCodeAt(index)); } return bytes; } function byteToString(arr) { var data = ''; for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) { var code = arr_1[_i]; data += String.fromCharCode(code); } return data; } function huffmanEncode(str) { var bytes = stringToByte(str); var _a = huffmanZip(bytes), result = _a.result, codes = _a.codes; return { codes: codes, result: byteToString(result), }; } function huffmanDecode(codes, str) { var bytes = stringToByte(str); var data = decode(codes, bytes); return byteToString(data); } var Storage = /** @class */ (function () { function Storage() { } Storage.prototype.set = function (key, value) { }; Storage.prototype.get = function (key, cb) { }; Storage.prototype.rm = function (key) { }; return Storage; }()); var LS = /** @class */ (function (_super) { __extends(LS, _super); function LS() { return _super !== null && _super.apply(this, arguments) || this; } LS.isSupport = function () { return !!window.localStorage; }; LS.prototype.set = function (key, value) { try { localStorage.setItem(key, value); } catch (e) { console.error(e); } return; }; LS.prototype.get = function (key, cb) { var res = null; try { res = localStorage.getItem(key); } catch (e) { console.error(e); } if (cb) { cb(res); } return; }; LS.prototype.rm = function (key) { try { localStorage.removeItem(key); } catch (e) { console.error(e); } return; }; return LS; }(Storage)); var IndexedDB = /** @class */ (function (_super) { __extends(IndexedDB, _super); function IndexedDB() { var _this = _super.call(this) || this; _this.databaseName = 'spyLC'; _this.db = null; _this.setQueue = []; _this.getQueue = []; var request = window.indexedDB.open(_this.databaseName); request.onupgradeneeded = function (event) { // @ts-ignore ts默认的EventTarget类型有问题,先ignore掉 _this.db = event.target && event.target.result; if (_this.db && !_this.db.objectStoreNames.contains(_this.databaseName)) { _this.db.createObjectStore(_this.databaseName, { keyPath: 'key' }); } _this.runQueueTask(); }; request.onsuccess = function () { _this.db = request.result; _this.runQueueTask(); }; return _this; } IndexedDB.isSupport = function () { return !!window.indexedDB; }; IndexedDB.prototype.set = function (key, value) { // 因为indexDB是异步初始化的,如果set的时候未初始化,先缓存之后再执行set if (!this.db) { this.setQueue.push({ key: key, value: value, }); return; } this.db.transaction([this.databaseName], 'readwrite') .objectStore(this.databaseName) .add({ key: key, value: value, }); }; IndexedDB.prototype.get = function (key, cb) { // 因为indexDB是异步初始化的,如果get的时候未初始化,先缓存之后再执行get if (!this.db) { this.getQueue.push({ key: key, cb: cb, }); return; } var transaction = this.db.transaction([this.databaseName]); var objectStore = transaction.objectStore(this.databaseName); var request = objectStore.get(key); request.onsuccess = function () { var result = request.result ? request.result : { value: '' }; cb(result.value); }; }; IndexedDB.prototype.runQueueTask = function () { var _this = this; this.setQueue.forEach(function (ele) { _this.set(ele.key, ele.value); }); this.getQueue.forEach(function (ele) { _this.get(ele.key, ele.cb); }); }; return IndexedDB; }(Storage)); function assign() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var __assign = Object.assign || function __assign(t) { for (var s = void 0, i = 1, n = arguments.length; i < n; i++) { // eslint-disable-next-line prefer-rest-params s = arguments[i]; for (var p in s) { if (Object.prototype.hasOwnProperty.call(s, p)) { t[p] = s[p]; } } } return t; }; return __assign.apply(this, args); } function utf8Encode(text) { var result = ''; for (var n = 0; n < text.length; n++) { var c = text.charCodeAt(n); if (c < 128) { result += String.fromCharCode(c); } else if (c > 127 && c < 2048) { result += String.fromCharCode((c >> 6) | 192); result += String.fromCharCode((c & 63) | 128); } else { result += String.fromCharCode((c >> 12) | 224); result += String.fromCharCode(((c >> 6) & 63) | 128); result += String.fromCharCode((c & 63) | 128); } } return result; // return window.btoa(result); } function utf8Decode(text) { // text = window.atob(text); var result = ''; var i = 0; var c1 = 0; var c2 = 0; var c3 = 0; while (i < text.length) { c1 = text.charCodeAt(i); if (c1 < 128) { result += String.fromCharCode(c1); i++; } else if (c1 > 191 && c1 < 224) { c2 = text.charCodeAt(i + 1); result += String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); i += 2; } else { c2 = text.charCodeAt(i + 1); c3 = text.charCodeAt(i + 2); result += String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); i += 3; } } return result; } function lzw(text, deCompress) { if (deCompress === void 0) { deCompress = false; } if (!text) { return ''; } if (!deCompress) { text = utf8Encode(text); } var dict = {}; var out = []; var prefix = text.charAt(0); var curChar = prefix; var oldPrefix = curChar; var idx = 256; var i; var c; var d; var g = function () { out.push(prefix.length > 1 ? String.fromCharCode(dict[prefix]) : prefix); }; if (deCompress) { out.push(prefix); } for (i = 1, d; i < text.length; i++) { c = text.charAt(i); if (deCompress) { d = text.charCodeAt(i); prefix = d < 256 ? c : dict[d] || (prefix + curChar); out.push(prefix); curChar = prefix.charAt(0); dict[idx++] = oldPrefix + curChar; oldPrefix = prefix; } else { if (dict.hasOwnProperty(prefix + c)) { prefix += c; } else { g(); dict[prefix + c] = idx++; prefix = c; } } } if (!deCompress) { g(); } var ret = out.join(''); if (deCompress) { ret = utf8Decode(ret); } return ret; } var SpyLocalCache = /** @class */ (function () { function SpyLocalCache(option) { if (option === void 0) { option = {}; } this.tmpList = []; this.option = assign({ defaultTrigger: true, compress: 'lzw', key: 'SpyLocalCache', interval: 500, maxRecordLen: 30, onFlush: function () { }, onSave: function () { }, onAdd: function () { return true; }, storage: IndexedDB.isSupport() ? 'indexedDB' : LS.isSupport() ? 'localstorage' : 'empty', }, option); this.load = this.load.bind(this); this.init(); } SpyLocalCache.prototype.init = function () { if (this.option.storage === 'indexedDB') { this.storage = new IndexedDB(); } else if (this.option.storage === 'localstorage') { this.storage = new LS(); } else { this.storage = new Storage(); } if (document.readyState === 'complete') { this.load(); } else { window.addEventListener('load', this.load); } }; SpyLocalCache.prototype.load = function () { if (location.search.indexOf('_FlushLogLocalCache=1') > -1 && this.option.defaultTrigger) { this.flushLog(); } }; SpyLocalCache.prototype.addLog = function (info) { var _this = this; if (this.option.onAdd) { if (!this.option.onAdd(info)) { return; } } info = JSON.stringify(info); this.tmpList.push(info); // 控制写日志频率 if (this.timer) { clearTimeout(this.timer); } this.timer = setTimeout(function () { _this.save(); }, this.option.interval); }; SpyLocalCache.prototype.getData = function (cb) { var _this = this; try { this.storage.get(this.option.key, function (encodeStr) { if (encodeStr) { _this.storage.get(_this.option.key + 'Codes', function (codes) { var res = []; try { if (codes) { codes = JSON.parse(codes); } var str = _this.unzip(encodeStr, codes); res = str.split('\n'); } catch (e) { console.error(e); } cb(res); }); } else { cb([]); } }); } catch (e) { console.error(e); cb([]); } }; SpyLocalCache.prototype.save = function () { var _this = this; var st = Date.now(); this.getData(function (list) { // 只保留最近的maxRecordLen条日志 var reserveLen = _this.option.maxRecordLen - 1; var originList = list.length > reserveLen ? list.slice(list.length - reserveLen, list.length) : list; var newList = originList.concat(_this.tmpList); var content = newList.join('\n'); var error = null; var len = 0; try { var data = _this.zip(content); var codesStr = ''; if (data.codes) { codesStr = JSON.stringify(data.codes); _this.storage.set(_this.option.key + 'Codes', codesStr); } else { _this.storage.rm(_this.option.key + 'Codes'); } _this.storage.set(_this.option.key, data.result); len = data.result.length; } catch (e) { error = e; console.error(e); } _this.tmpList = []; _this.option.onSave && _this.option.onSave({ cost: Date.now() - st, length: len / 1024, list: newList, error: error, }); }); }; SpyLocalCache.prototype.flushLog = function () { var _this = this; this.getData(function (list) { // 先解析来自存储的数据,若失败,说明格式有问题,清空存储 try { for (var index = 0; index < list.length; index++) { list[index] = JSON.parse(list[index]); } } catch (e) { list = []; _this.storage.rm(_this.option.key); console.error(e); } // 未落盘的数据也加上 for (var index = 0; index < _this.tmpList.length; index++) { list.push(JSON.parse(_this.tmpList[index])); } _this.option.onFlush && _this.option.onFlush(list); }); }; SpyLocalCache.prototype.zip = function (str) { if (this.option.compress === 'lzw') { return { codes: null, result: lzw(str), }; } else if (this.option.compress === 'huffman') { return huffmanEncode(str); } return { codes: null, result: str, }; }; SpyLocalCache.prototype.unzip = function (str, codes) { if (this.option.compress === 'lzw') { return lzw(str, true); } else if (this.option.compress === 'huffman') { return huffmanDecode(codes, str); } return str; }; return SpyLocalCache; }()); // For test // let content = JSON.stringify({ // type: 3, // fm: 'disp', // data: [{"base":{"size":{"doc":{"w":360,"h":4875},"wind":{"w":360,"h":640},"scr":{"w":360,"h":640}},"vsb":"visible","num":16},"t":1629773746698,"path":"/s"}], // qid: 10991431029479106376, // did: '8dd09c47c7bc90c9fd7274f0ad2c581e', // q: '刘德华', // t: 1629773746698, // }); // const compressText = lzw(content); // const deCompressText = lzw(compressText, true); // console.log('compressText', compressText, compressText.length); // console.log('deCompressText', deCompressText, deCompressText.length); return SpyLocalCache; })));