Commit 234c1d1fae6a7f2c78e456f992f91622ca599060

Authored by vben
1 parent 3a132f3f

feat: the cache can be configured to be encrypted

CHANGELOG.zh_CN.md
1 1 ## Wip
2 2  
  3 +### ✨ Features
  4 +
  5 +- 缓存可以配置是否加密
  6 +- 多语言支持
  7 +
3 8 ### 🎫 Chores
4 9  
5 10 - 移除 messageSetting 配置
... ...
package.json
... ... @@ -37,16 +37,16 @@
37 37 "vditor": "^3.6.3",
38 38 "vue": "^3.0.2",
39 39 "vue-i18n": "^9.0.0-beta.8",
40   - "vue-router": "^4.0.0-rc.3",
  40 + "vue-router": "^4.0.0-rc.5",
41 41 "vuex": "^4.0.0-rc.1",
42 42 "vuex-module-decorators": "^1.0.1",
43   - "xlsx": "^0.16.8",
  43 + "xlsx": "^0.16.9",
44 44 "zxcvbn": "^4.4.2"
45 45 },
46 46 "devDependencies": {
47 47 "@commitlint/cli": "^11.0.0",
48 48 "@commitlint/config-conventional": "^11.0.0",
49   - "@iconify/json": "^1.1.261",
  49 + "@iconify/json": "^1.1.262",
50 50 "@ls-lint/ls-lint": "^1.9.2",
51 51 "@purge-icons/generated": "^0.4.1",
52 52 "@types/echarts": "^4.9.1",
... ... @@ -72,7 +72,7 @@
72 72 "cross-env": "^7.0.2",
73 73 "dot-prop": "^6.0.1",
74 74 "dotenv": "^8.2.0",
75   - "eslint": "^7.13.0",
  75 + "eslint": "^7.14.0",
76 76 "eslint-config-prettier": "^6.15.0",
77 77 "eslint-plugin-prettier": "^3.1.4",
78 78 "eslint-plugin-vue": "^7.1.0",
... ...
src/components/Application/index.ts
1 1 import AppLocalPicker from './src/AppLocalPicker.vue';
  2 +import AppFooterToolbar from './src/AppFooterToolbar.vue';
  3 +import { withInstall } from '../util';
2 4  
3   -export { AppLocalPicker };
  5 +export { AppLocalPicker, AppFooterToolbar };
  6 +
  7 +export default withInstall(AppLocalPicker, AppFooterToolbar);
... ...
src/components/Footer/src/index.vue renamed to src/components/Application/src/AppFooterToolbar.vue
... ... @@ -10,11 +10,14 @@
10 10 </template>
11 11 <script lang="ts">
12 12 import { defineComponent, computed, unref } from 'vue';
  13 +
13 14 import { SIDE_BAR_MINI_WIDTH, SIDE_BAR_SHOW_TIT_MINI_WIDTH } from '/@/enums/appEnum';
  15 +
14 16 import { appStore } from '/@/store/modules/app';
15 17 import { menuStore } from '/@/store/modules/menu';
  18 +
16 19 export default defineComponent({
17   - name: 'AppFooter',
  20 + name: 'AppFooterToolbar',
18 21 setup() {
19 22 const getMiniWidth = computed(() => {
20 23 const {
... ...
src/components/Authority/index.ts
1   -import type { App } from 'vue';
2 1 import Authority from './src/index.vue';
3 2  
4   -export default (app: App): void => {
5   - app.component(Authority.name, Authority);
6   -};
  3 +import { withInstall } from '../util';
  4 +
  5 +export default withInstall(Authority);
7 6  
8 7 export { Authority };
... ...
src/components/Footer/index.ts deleted 100644 → 0
1   -export { default as AppFooter } from './src/index.vue';
src/components/registerGlobComp.ts
1 1 import Icon from './Icon/index';
2 2 import Button from './Button/index.vue';
3   -import { AppFooter } from './Footer';
4 3 import {
5 4 // Need
6 5 Button as AntButton,
... ... @@ -35,7 +34,7 @@ import {
35 34 } from 'ant-design-vue';
36 35 import { getApp } from '/@/setup/App';
37 36  
38   -const compList = [Icon, Button, AntButton.Group, AppFooter];
  37 +const compList = [Icon, Button, AntButton.Group];
39 38  
40 39 // Fix hmr multiple registered components
41 40 let registered = false;
... ...
src/components/util.ts
1 1 import type { VNodeChild } from 'vue';
  2 +import type { App, Component } from 'vue';
  3 +
  4 +export function withInstall(...components: Component[]) {
  5 + return (app: App) => {
  6 + components.forEach((comp) => {
  7 + comp.name && app.component(comp.name, comp);
  8 + });
  9 + };
  10 +}
2 11  
3 12 export function convertToUnit(
4 13 str: string | number | null | undefined,
... ...
src/settings/cipherSetting.ts renamed to src/settings/encryptionSetting.ts
  1 +import { isDevMode } from '/@/utils/env';
  2 +
1 3 // System default cache time, in seconds
2 4 export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
3 5  
4   -/**
5   - * @description:
6   - */
7   -export const storageCipher = {
  6 +// aes encryption key
  7 +export const cacheCipher = {
8 8 key: '_12345678901234@',
9 9 iv: '@12345678901234_',
10 10 };
  11 +
  12 +// Whether the system cache is encrypted using aes
  13 +export const enableStorageEncryption = !isDevMode();
... ...
src/utils/cache/cookie.ts 0 → 100644
  1 +import { DEFAULT_CACHE_TIME } from '../../settings/encryptionSetting';
  2 +import { getStorageShortName } from '/@/utils/helper/envHelper';
  3 +import { cacheCipher } from '/@/settings/encryptionSetting';
  4 +import Encryption from '/@/utils/encryption/aesEncryption';
  5 +
  6 +export default class WebCookie {
  7 + private encryption: Encryption;
  8 + private hasEncrypt: boolean;
  9 +
  10 + constructor(hasEncrypt = true, key = cacheCipher.key, iv = cacheCipher.iv) {
  11 + const encryption = new Encryption({ key, iv });
  12 + this.encryption = encryption;
  13 + this.hasEncrypt = hasEncrypt;
  14 + }
  15 +
  16 + private getKey(key: string) {
  17 + return `${getStorageShortName()}${key}`.toUpperCase();
  18 + }
  19 +
  20 + /**
  21 + * Add cookie
  22 + * @param name cookie key
  23 + * @param value cookie value
  24 + * @param expire
  25 + * If the expiration time is not set, the default management browser will automatically delete
  26 + * e.g:
  27 + * cookieData.set('name','value',)
  28 + */
  29 + setCookie(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
  30 + value = this.hasEncrypt ? this.encryption.encryptByAES(JSON.stringify(value)) : value;
  31 + document.cookie = this.getKey(key) + '=' + value + '; Max-Age=' + expire;
  32 + }
  33 +
  34 + /**
  35 + * Get the cook value according to the key
  36 + * @param key cookie key
  37 + */
  38 + getCookie(key: string) {
  39 + const arr = document.cookie.split('; ');
  40 + for (let i = 0; i < arr.length; i++) {
  41 + const arr2 = arr[i].split('=');
  42 + if (arr2[0] === this.getKey(key)) {
  43 + let message: any = null;
  44 + const str = arr2[1];
  45 + if (this.hasEncrypt && str) {
  46 + message = this.encryption.decryptByAES(str);
  47 + try {
  48 + return JSON.parse(message);
  49 + } catch (e) {
  50 + return str;
  51 + }
  52 + }
  53 + return str;
  54 + }
  55 + }
  56 + return '';
  57 + }
  58 +
  59 + /**
  60 + * Delete cookie based on cookie key
  61 + * @param key cookie key
  62 + */
  63 + removeCookie(key: string) {
  64 + this.setCookie(key, 1, -1);
  65 + }
  66 +
  67 + /**
  68 + * clear cookie
  69 + */
  70 + clearCookie(): void {
  71 + const keys = document.cookie.match(/[^ =;]+(?==)/g);
  72 + if (keys) {
  73 + for (let i = keys.length; i--; ) {
  74 + document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString();
  75 + }
  76 + }
  77 + }
  78 +}
... ...
src/utils/storage/index.ts renamed to src/utils/cache/index.ts
1 1 import { getStorageShortName } from '/@/utils/helper/envHelper';
2   -import { createStorage as create } from './Storage';
3   -
4   -// debug模式下不加密
  2 +import { createStorage as create } from './storageCache';
  3 +import { enableStorageEncryption } from '/@/settings/encryptionSetting';
5 4  
6 5 const createOptions = (storage = sessionStorage) => {
7 6 return {
  7 + // No encryption in debug mode
  8 + hasEncrypt: enableStorageEncryption,
8 9 storage,
9 10 prefixKey: getStorageShortName(),
10 11 };
11 12 };
  13 +
12 14 export const WebStorage = create(createOptions());
13 15  
14 16 export const createStorage = (storage: Storage = sessionStorage) => {
15 17 return create(createOptions(storage))!;
16 18 };
  19 +
17 20 export default WebStorage;
... ...
src/utils/storage/Storage.ts renamed to src/utils/cache/storageCache.ts
1   -import { DEFAULT_CACHE_TIME } from '/@/settings/cipherSetting';
  1 +import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
  2 +import { cacheCipher } from '/@/settings/encryptionSetting';
  3 +import Encryption, { EncryptionParams } from '/@/utils/encryption/aesEncryption';
2 4  
3   -// import { EncryptionParams } from '/@/utils/cipher/aesEncryption';
4   -export interface CreateStorageParams {
  5 +export interface CreateStorageParams extends EncryptionParams {
5 6 storage: Storage;
  7 +
6 8 hasEncrypt: boolean;
7 9 }
8   -export const createStorage = ({ prefixKey = '', storage = sessionStorage } = {}) => {
  10 +export const createStorage = ({
  11 + prefixKey = '',
  12 + storage = sessionStorage,
  13 + key = cacheCipher.key,
  14 + iv = cacheCipher.iv,
  15 + hasEncrypt = true,
  16 +} = {}) => {
  17 + if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) {
  18 + throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!');
  19 + }
  20 +
  21 + const encryption = new Encryption({ key, iv });
  22 +
9 23 /**
10   - *缓存类
11   - *构造参数可以传入 sessionStorage,localStorage,
  24 + *Cache class
  25 + *Construction parameters can be passed into sessionStorage, localStorage,
12 26 * @class Cache
13 27 * @example
14 28 */
15 29 const WebStorage = class WebStorage {
16 30 private storage: Storage;
17 31 private prefixKey?: string;
18   -
  32 + private encryption: Encryption;
  33 + private hasEncrypt: boolean;
19 34 /**
20 35 *
21 36 * @param {*} storage
... ... @@ -23,6 +38,8 @@ export const createStorage = ({ prefixKey = &#39;&#39;, storage = sessionStorage } = {})
23 38 constructor() {
24 39 this.storage = storage;
25 40 this.prefixKey = prefixKey;
  41 + this.encryption = encryption;
  42 + this.hasEncrypt = hasEncrypt;
26 43 }
27 44  
28 45 private getKey(key: string) {
... ... @@ -31,10 +48,10 @@ export const createStorage = ({ prefixKey = &#39;&#39;, storage = sessionStorage } = {})
31 48  
32 49 /**
33 50 *
34   - * 设置缓存
35   - * @param {string} key 缓存键
36   - * @param {*} value 缓存值
37   - * @expire 过期时间 单位秒
  51 + * Set cache
  52 + * @param {string} key
  53 + * @param {*} value
  54 + * @expire Expiration time in seconds
38 55 * @memberof Cache
39 56 */
40 57 set(key: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
... ... @@ -42,21 +59,23 @@ export const createStorage = ({ prefixKey = &#39;&#39;, storage = sessionStorage } = {})
42 59 value,
43 60 expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
44 61 });
45   - this.storage.setItem(this.getKey(key), stringData);
  62 + const stringifyValue = this.hasEncrypt
  63 + ? this.encryption.encryptByAES(stringData)
  64 + : stringData;
  65 + this.storage.setItem(this.getKey(key), stringifyValue);
46 66 }
47 67  
48 68 /**
49   - *
50   - *读取缓存
51   - * @param {string} key 缓存键
52   - * @returns 缓存值
  69 + *Read cache
  70 + * @param {string} key
53 71 * @memberof Cache
54 72 */
55 73 get(key: string, def: any = null): any {
56 74 const item = this.storage.getItem(this.getKey(key));
57 75 if (item) {
58 76 try {
59   - const data = JSON.parse(item);
  77 + const decItem = this.hasEncrypt ? this.encryption.decryptByAES(item) : item;
  78 + const data = JSON.parse(decItem);
60 79 const { value, expire } = data;
61 80 if (expire === null || expire >= new Date().getTime()) {
62 81 return value;
... ... @@ -70,9 +89,8 @@ export const createStorage = ({ prefixKey = &#39;&#39;, storage = sessionStorage } = {})
70 89 }
71 90  
72 91 /**
73   - *
74   - *删除缓存
75   - * @param {string} key 缓存键
  92 + * Delete cache based on key
  93 + * @param {string} key
76 94 * @memberof Cache
77 95 */
78 96 remove(key: string) {
... ... @@ -80,59 +98,11 @@ export const createStorage = ({ prefixKey = &#39;&#39;, storage = sessionStorage } = {})
80 98 }
81 99  
82 100 /**
83   - *
84   - *删除该实例所有缓存
85   - * @memberof Cache
  101 + * Delete all caches of this instance
86 102 */
87 103 clear(): void {
88 104 this.storage.clear();
89 105 }
90   -
91   - /**
92   - * 添加cookie
93   - * @param name cookie名字
94   - * @param value cookie内容
95   - * @param expire
96   - * 如果过期时间未设置,默认管理浏览器自动删除
97   - * 例子:
98   - * cookieData.set('name','value',)
99   - */
100   - setCookie(name: string, value: any, expire: number | null = DEFAULT_CACHE_TIME) {
101   - document.cookie = this.getKey(name) + '=' + value + '; Max-Age=' + expire;
102   - }
103   -
104   - /**
105   - * 根据名字获取cooki值
106   - * @param name cookie名
107   - * @returns {*} cookie值
108   - */
109   - getCookie(name: string) {
110   - const arr = document.cookie.split('; ');
111   - for (let i = 0; i < arr.length; i++) {
112   - const arr2 = arr[i].split('=');
113   - if (arr2[0] === this.getKey(name)) {
114   - return arr2[1];
115   - }
116   - }
117   - return '';
118   - }
119   -
120   - /**
121   - * 根据cookie名字删除cookie
122   - * @param name cookie名字
123   - */
124   - removeCookie(key: string) {
125   - this.setCookie(key, 1, -1);
126   - }
127   -
128   - clearCookie(): void {
129   - const keys = document.cookie.match(/[^ =;]+(?==)/g);
130   - if (keys) {
131   - for (let i = keys.length; i--; ) {
132   - document.cookie = keys[i] + '=0;expires=' + new Date(0).toUTCString();
133   - }
134   - }
135   - }
136 106 };
137 107 return new WebStorage();
138 108 };
... ...
src/utils/encryption/aesEncryption.ts 0 → 100644
  1 +import CryptoES from 'crypto-es';
  2 +export interface EncryptionParams {
  3 + key: string;
  4 + iv: string;
  5 +}
  6 +export class Encryption {
  7 + private key;
  8 +
  9 + private iv;
  10 +
  11 + constructor(opt: EncryptionParams) {
  12 + const { key, iv } = opt;
  13 + this.key = CryptoES.enc.Utf8.parse(key);
  14 + this.iv = CryptoES.enc.Utf8.parse(iv);
  15 + }
  16 +
  17 + get getOpt(): CryptoES.lib.CipherCfg {
  18 + return {
  19 + mode: CryptoES.mode.CBC as any,
  20 + padding: CryptoES.pad.Pkcs7,
  21 + iv: this.iv,
  22 + };
  23 + }
  24 +
  25 + encryptByAES(str: string) {
  26 + const encrypted = CryptoES.AES.encrypt(str, this.key, this.getOpt);
  27 + return encrypted.toString();
  28 + }
  29 +
  30 + decryptByAES(str: string) {
  31 + const decrypted = CryptoES.AES.decrypt(str, this.key, this.getOpt);
  32 + return decrypted.toString(CryptoES.enc.Utf8);
  33 + }
  34 +}
  35 +export default Encryption;
... ...
src/utils/file/base64.ts renamed to src/utils/file/base64Conver.ts
src/utils/file/download.ts
1   -import { dataURLtoBlob, urlToBase64 } from './base64';
  1 +import { dataURLtoBlob, urlToBase64 } from './base64Conver';
2 2  
3 3 /**
4 4 * Download online pictures
... ...
src/utils/helper/persistent.ts
1   -import { createStorage } from '/@/utils/storage';
  1 +import { createStorage } from '/@/utils/cache';
2 2 import { isIeFn } from '/@/utils/browser';
3 3  
4 4 import { BASE_LOCAL_CACHE_KEY, BASE_SESSION_CACHE_KEY } from '/@/enums/cacheEnum';
... ...
src/views/demo/page/form/high/index.vue
... ... @@ -16,21 +16,23 @@
16 16 </a-card>
17 17 </div>
18 18  
19   - <app-footer>
  19 + <AppFooterToolbar>
20 20 <template #right>
21 21 <a-button type="primary" @click="submitAll">提交</a-button>
22 22 </template>
23   - </app-footer>
  23 + </AppFooterToolbar>
24 24 </div>
25 25 </template>
26 26 <script lang="ts">
27 27 import { BasicForm, useForm } from '/@/components/Form';
28 28 import { defineComponent, ref } from 'vue';
29 29 import PersonTable from './PersonTable.vue';
  30 + import { AppFooterToolbar } from '/@/components/Application';
  31 +
30 32 import { schemas, taskSchemas } from './data';
31 33  
32 34 export default defineComponent({
33   - components: { BasicForm, PersonTable },
  35 + components: { BasicForm, PersonTable, AppFooterToolbar },
34 36 setup() {
35 37 const tableRef = ref<{ getDataSource: () => any } | null>(null);
36 38  
... ...
yarn.lock
... ... @@ -1050,10 +1050,10 @@
1050 1050 resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.2.tgz#c4a95ddc06ca9b9496df03604e66fdefb39f4c4b"
1051 1051 integrity sha512-BybEHU5/I9EQ0CcwKAqmreZ2bMnAXrqLCTptAc6vPetHMbrXdZfejP5mt57e/8PNSt/qE7BHniU5PCYA+PGIHw==
1052 1052  
1053   -"@iconify/json@^1.1.261":
1054   - version "1.1.261"
1055   - resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.261.tgz#9a6986b6b36d77ca147c4be149db9a43280a8fb2"
1056   - integrity sha512-lnRk1OBqNxZ593oZyOXEMp/O+cr+lF54xaW6+F3krWdWhzxQgi0W1ffzvdiLySdbTEorQ2NvVU4e0+Af27rXPA==
  1053 +"@iconify/json@^1.1.262":
  1054 + version "1.1.262"
  1055 + resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.262.tgz#a67067bad418d59c729ec514e3aa629d0ab3710b"
  1056 + integrity sha512-PfKUS/Ue9Rn2oO0ez/Yj4Cdodvv6vDHgPjIZNSElu2+149CYaPmWahHI87ZY+r8l3bijPIu6+blyAixdJtPPMg==
1057 1057  
1058 1058 "@koa/cors@^3.1.0":
1059 1059 version "3.1.0"
... ... @@ -3418,10 +3418,10 @@ eslint-visitor-keys@^2.0.0:
3418 3418 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
3419 3419 integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
3420 3420  
3421   -eslint@^7.13.0:
3422   - version "7.13.0"
3423   - resolved "https://registry.npmjs.org/eslint/-/eslint-7.13.0.tgz#7f180126c0dcdef327bfb54b211d7802decc08da"
3424   - integrity sha512-uCORMuOO8tUzJmsdRtrvcGq5qposf7Rw0LwkTJkoDbOycVQtQjmnhZSuLQnozLE4TmAzlMVV45eCHmQ1OpDKUQ==
  3421 +eslint@^7.14.0:
  3422 + version "7.14.0"
  3423 + resolved "https://registry.npmjs.org/eslint/-/eslint-7.14.0.tgz#2d2cac1d28174c510a97b377f122a5507958e344"
  3424 + integrity sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA==
3425 3425 dependencies:
3426 3426 "@babel/code-frame" "^7.0.0"
3427 3427 "@eslint/eslintrc" "^0.2.1"
... ... @@ -3686,6 +3686,11 @@ fastq@^1.6.0:
3686 3686 dependencies:
3687 3687 reusify "^1.0.4"
3688 3688  
  3689 +fflate@^0.3.8:
  3690 + version "0.3.10"
  3691 + resolved "https://registry.npmjs.org/fflate/-/fflate-0.3.10.tgz#0e581839a53203d2eeac7e61ce3652d855e24dcd"
  3692 + integrity sha512-s5j69APkUPPbzdI20Ix4pPtQP+1Qi58YcFRpE7aO/P1kEywUYjbl2RjZRVEMdnySO9pr4MB0BHPbxkiahrtD/Q==
  3693 +
3689 3694 figures@^2.0.0:
3690 3695 version "2.0.0"
3691 3696 resolved "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
... ... @@ -8196,10 +8201,10 @@ vue-i18n@^9.0.0-beta.8:
8196 8201 dependencies:
8197 8202 source-map "0.6.1"
8198 8203  
8199   -vue-router@^4.0.0-rc.3:
8200   - version "4.0.0-rc.3"
8201   - resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.0.0-rc.3.tgz#70d18e90030bc6a25e81a30401d673223998ec6b"
8202   - integrity sha512-NnPqWIfanEhJC4wu8BEFBmnEDIrx9ST0/HtmBiE+oV2MQlhyRk1TmdttWwVqx6Sh7kONsrI10GQV9l3YEkcWXg==
  8204 +vue-router@^4.0.0-rc.5:
  8205 + version "4.0.0-rc.5"
  8206 + resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.0.0-rc.5.tgz#191d32e3d5276641ff21e881d34e33a71dc6e8f0"
  8207 + integrity sha512-Q8Tt6VGwGMN5qASeIdjSydU3uRADK9AUkqnbnzmTz+zZKS0W6GZOAuP235lf3y5/MqEFSKRJGaTWPEY0t+Rjmg==
8203 8208  
8204 8209 vue-types@^3.0.0:
8205 8210 version "3.0.1"
... ... @@ -8480,10 +8485,10 @@ ws@^7.3.1:
8480 8485 resolved "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz#a5dd76a24197940d4a8bb9e0e152bb4503764da7"
8481 8486 integrity sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==
8482 8487  
8483   -xlsx@^0.16.8:
8484   - version "0.16.8"
8485   - resolved "https://registry.npmjs.org/xlsx/-/xlsx-0.16.8.tgz#5546de9b0ba15169b36770d4e43b24790d3ff1b8"
8486   - integrity sha512-qWub4YCn0xLEGHI7WWhk6IJ73MDu7sPSJQImxN6/LiI8wsHi0hUhICEDbyqBT+jgFgORZxrii0HvhNSwBNAPoQ==
  8488 +xlsx@^0.16.9:
  8489 + version "0.16.9"
  8490 + resolved "https://registry.npmjs.org/xlsx/-/xlsx-0.16.9.tgz#dacd5bb46bda6dd3743940c9c3dc1e2171826256"
  8491 + integrity sha512-gxi1I3EasYvgCX1vN9pGyq920Ron4NO8PNfhuoA3Hpq6Y8f0ECXiy4OLrK4QZBnj1jx3QD+8Fq5YZ/3mPZ5iXw==
8487 8492 dependencies:
8488 8493 adler-32 "~1.2.0"
8489 8494 cfb "^1.1.4"
... ... @@ -8491,6 +8496,7 @@ xlsx@^0.16.8:
8491 8496 commander "~2.17.1"
8492 8497 crc-32 "~1.2.0"
8493 8498 exit-on-epipe "~1.0.1"
  8499 + fflate "^0.3.8"
8494 8500 ssf "~0.11.2"
8495 8501 wmf "~1.0.1"
8496 8502 word "~0.3.0"
... ...