spy-client
Version:
spy client
753 lines (740 loc) • 28.5 kB
JavaScript
(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;
})));