Merge pull request #64 from fantasticit/feat/file

文件存储更新
This commit is contained in:
fantasticit 2022-06-04 22:43:45 +08:00 committed by GitHub
commit aaa2b7acb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1284 additions and 65 deletions

View File

@ -5,6 +5,7 @@ lib
**/.next/**
**/dist/**
**/static/**
**/build/**
**/public/**
**/diagram.js

View File

@ -42,12 +42,26 @@ db:
# oss 文件存储服务
oss:
local:
enable: true
# 线上更改为服务端地址https://api.codingit.cn
server: 'http://localhost:5002'
# 以下为各厂商 sdk 配置,不要修改字段,填入值即可
tencent:
enable: false
config:
SecretId: ''
SecretKey: ''
Bucket: ''
Region: ''
aliyun:
accessKeyId: ''
accessKeySecret: ''
bucket: ''
https: true
region: ''
enable: false
config:
accessKeyId: ''
accessKeySecret: ''
bucket: ''
https: true
region: ''
# jwt 配置
jwt:

View File

@ -91,6 +91,7 @@
"requestidlecallback-polyfill": "^1.0.2",
"resize-observer-polyfill": "^1.5.1",
"scroll-into-view-if-needed": "^2.2.29",
"spark-md5": "^3.0.2",
"timeago.js": "^4.0.2",
"tippy.js": "^6.3.7",
"toggle-selection": "^1.0.6",

View File

@ -1,30 +1,139 @@
import { FILE_CHUNK_SIZE, FileApiDefinition } from '@think/domains';
import SparkMD5 from 'spark-md5';
import { HttpClient } from './http-client';
const ONE_MB = 1 * 1024 * 1024;
export const readFileAsDataURL = (file): Promise<string | ArrayBuffer> => {
if (file.size > ONE_MB) {
return Promise.reject(new Error('文件过大,请实现文件上传到存储服务!'));
}
const splitBigFile = (file: File): Promise<{ chunks: File[]; md5: string }> => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener('load', (e) => resolve(e.target.result), { once: true });
reader.readAsDataURL(file);
const chunks = [];
const len = Math.ceil(file.size / FILE_CHUNK_SIZE);
const sparkWorker = new Worker(new URL('./spark-md5.js', import.meta.url));
sparkWorker.onmessage = (evt) => {
resolve({ md5: evt.data.md5, chunks });
};
for (let i = 0; i < len; i++) {
const start = i * FILE_CHUNK_SIZE;
const end = Math.min(start + FILE_CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
chunks.push(chunk);
}
sparkWorker.postMessage({ chunks });
});
};
export const uploadFile = async (file: Blob): Promise<string | ArrayBuffer> => {
if (!process.env.ENABLE_ALIYUN_OSS) {
return readFileAsDataURL(file);
}
const uploadFileToServer = (arg: {
filename: string;
file: File;
md5: string;
isChunk?: boolean;
chunkIndex?: number;
onUploadProgress?: (progress: number) => void;
}) => {
const { filename, file, md5, isChunk, chunkIndex, onUploadProgress } = arg;
const api = isChunk ? 'uploadChunk' : 'upload';
const formData = new FormData();
formData.append('file', file);
return HttpClient.post('/file/upload', formData, {
return HttpClient.request({
method: FileApiDefinition[api].method,
url: FileApiDefinition[api].client(),
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
params: {
filename,
md5,
chunkIndex,
},
onUploadProgress: (progress) => {
const percent = progress.loaded / progress.total;
onUploadProgress && onUploadProgress(percent);
},
});
};
export const uploadFile = async (
file: File,
onUploadProgress?: (progress: number) => void,
onTooLarge?: () => void
) => {
const wraponUploadProgress = (percent) => {
return onUploadProgress && onUploadProgress(Math.ceil(percent * 100));
};
const filename = file.name;
if (file.size > FILE_CHUNK_SIZE * 5) {
onTooLarge && onTooLarge();
}
if (file.size <= FILE_CHUNK_SIZE) {
const spark = new SparkMD5.ArrayBuffer();
spark.append(file);
const md5 = spark.end();
const url = await uploadFileToServer({ filename, file, md5, onUploadProgress: wraponUploadProgress });
return url;
} else {
const { chunks, md5 } = await splitBigFile(file);
const unitPercent = 1 / chunks.length;
const progressMap = {};
/**
*
*/
let url = await uploadFileToServer({
filename,
file: chunks[0],
chunkIndex: 1,
md5,
isChunk: true,
onUploadProgress: (progress) => {
progressMap[1] = progress * unitPercent;
wraponUploadProgress(
Object.keys(progressMap).reduce((a, c) => {
return (a += progressMap[c]);
}, 0)
);
},
});
if (!url) {
await Promise.all(
chunks.slice(1).map((chunk, index) => {
const currentIndex = 1 + index + 1;
return uploadFileToServer({
filename,
file: chunk,
chunkIndex: currentIndex,
md5,
isChunk: true,
onUploadProgress: (progress) => {
progressMap[currentIndex] = progress * unitPercent;
wraponUploadProgress(
Object.keys(progressMap).reduce((a, c) => {
return (a += progressMap[c]);
}, 0)
);
},
});
})
);
url = await HttpClient.request({
method: FileApiDefinition.mergeChunk.method,
url: FileApiDefinition.mergeChunk.client(),
params: {
filename,
md5,
},
});
} else {
wraponUploadProgress(1);
}
return url;
}
};

View File

@ -10,7 +10,7 @@ interface AxiosInstance extends Axios {
export const HttpClient = axios.create({
baseURL: process.env.SERVER_API_URL,
timeout: 10 * 1000,
timeout: 10 * 60 * 1000,
withCredentials: true,
}) as AxiosInstance;

View File

@ -0,0 +1,31 @@
import SparkMD5 from 'spark-md5';
addEventListener('message', (e) => {
const chunks = e.data.chunks || [];
if (!chunks.length) return;
const spark = new SparkMD5.ArrayBuffer();
const reader = new FileReader();
let index = 0;
const load = () => {
const chunk = chunks[index];
reader.readAsArrayBuffer(chunk);
};
reader.onload = (e) => {
spark.append(e.target.result);
if (index === chunks.length - 1) {
const md5 = spark.end();
postMessage({ md5 });
self.close();
} else {
index++;
load();
}
};
load();
});

View File

@ -1,10 +1,11 @@
import { IconClose, IconDownload, IconPlayCircle } from '@douyinfe/semi-icons';
import { Button, Collapsible, Space, Spin, Typography } from '@douyinfe/semi-ui';
import { Button, Collapsible, Progress, Space, Spin, Toast, Typography } from '@douyinfe/semi-ui';
import { FILE_CHUNK_SIZE } from '@think/domains';
import { NodeViewWrapper } from '@tiptap/react';
import cls from 'classnames';
import { Tooltip } from 'components/tooltip';
import { useToggle } from 'hooks/use-toggle';
import { useCallback, useEffect, useRef } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { uploadFile } from 'services/file';
import { download, extractFileExtension, extractFilename, normalizeFileSize } from 'tiptap/prose-utils';
@ -20,6 +21,8 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
const { hasTrigger, fileName, fileSize, fileExt, fileType, url, error } = node.attrs;
const [loading, toggleLoading] = useToggle(false);
const [visible, toggleVisible] = useToggle(false);
const [showProgress, toggleShowProgress] = useToggle(false);
const [uploadProgress, setUploadProgress] = useState(0);
const selectFile = useCallback(() => {
if (!isEditable || url) return;
@ -29,6 +32,7 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
const handleFile = useCallback(
async (e) => {
const file = e.target.files && e.target.files[0];
if (!file) return;
const fileInfo = {
fileName: extractFilename(file.name),
fileSize: file.size,
@ -36,16 +40,28 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
fileExt: extractFileExtension(file.name),
};
toggleLoading(true);
if (file.size > FILE_CHUNK_SIZE) {
toggleShowProgress(true);
}
try {
const url = await uploadFile(file);
const url = await uploadFile(file, setUploadProgress, () => {
Toast.info('文件较大,文件将在后台进行上传处理,您可继续其他操作');
});
updateAttributes({ ...fileInfo, url });
toggleLoading(false);
setUploadProgress(0);
toggleShowProgress(false);
} catch (error) {
updateAttributes({ error: '文件上传失败:' + (error && error.message) || '未知错误' });
toggleLoading(false);
setUploadProgress(0);
toggleShowProgress(false);
$upload.current.value = '';
}
},
[toggleLoading, updateAttributes]
[toggleLoading, toggleShowProgress, updateAttributes]
);
useEffect(() => {
@ -61,7 +77,21 @@ export const AttachmentWrapper = ({ editor, node, updateAttributes }) => {
<div className={cls(styles.wrap, 'render-wrapper')}>
<Spin spinning={loading}>
<Text style={{ cursor: 'pointer' }} onClick={selectFile}>
{loading ? '正在上传中' : '请选择文件'}
{loading ? (
showProgress ? (
<Progress
percent={uploadProgress}
showInfo
style={{
margin: '10px 0',
}}
/>
) : (
'正在上传中'
)
) : (
'请选择文件'
)}
</Text>
<input ref={$upload} type="file" hidden onChange={handleFile} />
</Spin>

View File

@ -31,6 +31,6 @@
"thirtypart/*": ["thirtypart/*"]
}
},
"include": ["next-env.d.ts", "global.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "global.d.ts", "**/*.ts", "**/*.tsx", "src/services/spark-md5.js"],
"exclude": ["node_modules", "next.config.js"]
}

View File

@ -7,4 +7,21 @@ export declare const FileApiDefinition: {
server: "upload";
client: () => string;
};
/**
*
*/
uploadChunk: {
method: "post";
server: "upload/chunk";
client: () => string;
};
/**
*
*/
mergeChunk: {
method: "post";
server: "merge/chunk";
client: () => string;
};
};
export declare const FILE_CHUNK_SIZE: number;

View File

@ -1,6 +1,6 @@
"use strict";
exports.__esModule = true;
exports.FileApiDefinition = void 0;
exports.FILE_CHUNK_SIZE = exports.FileApiDefinition = void 0;
exports.FileApiDefinition = {
/**
*
@ -9,5 +9,22 @@ exports.FileApiDefinition = {
method: 'post',
server: 'upload',
client: function () { return '/file/upload'; }
},
/**
*
*/
uploadChunk: {
method: 'post',
server: 'upload/chunk',
client: function () { return '/file/upload/chunk'; }
},
/**
*
*/
mergeChunk: {
method: 'post',
server: 'merge/chunk',
client: function () { return '/file/merge/chunk'; }
}
};
exports.FILE_CHUNK_SIZE = 2 * 1024 * 1024;

View File

@ -7,4 +7,24 @@ export const FileApiDefinition = {
server: 'upload' as const,
client: () => '/file/upload',
},
/**
*
*/
uploadChunk: {
method: 'post' as const,
server: 'upload/chunk' as const,
client: () => '/file/upload/chunk',
},
/**
*
*/
mergeChunk: {
method: 'post' as const,
server: 'merge/chunk' as const,
client: () => '/file/merge/chunk',
},
};
export const FILE_CHUNK_SIZE = 2 * 1024 * 1024;

View File

@ -33,3 +33,6 @@ lerna-debug.log*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# 静态文件
/static

View File

@ -31,12 +31,14 @@
"@think/config": "workspace:^1.0.0",
"@think/constants": "workspace:^1.0.0",
"@think/domains": "workspace:^1.0.0",
"@types/multer": "^1.4.7",
"ali-oss": "^6.16.0",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"cos-nodejs-sdk-v5": "^2.11.9",
"date-fns": "^2.28.0",
"express": "^4.17.2",
"express-rate-limit": "^6.2.0",

View File

@ -1,27 +1,55 @@
import { JwtGuard } from '@guard/jwt.guard';
import { Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
import { FileQuery } from '@helpers/file.helper/oss.client';
import { Controller, Post, Query, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileService } from '@services/file.service';
import { FileApiDefinition } from '@think/domains';
import { FILE_CHUNK_SIZE, FileApiDefinition } from '@think/domains';
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}
/**
*
*
* @param file
*/
@Post(FileApiDefinition.upload.server)
@UseInterceptors(
FileInterceptor('file', {
limits: {
fieldSize: 50 * 1024 * 1024,
fieldSize: FILE_CHUNK_SIZE,
},
})
)
@UseGuards(JwtGuard)
uploadFile(@UploadedFile() file) {
return this.fileService.uploadFile(file);
uploadFile(@UploadedFile() file: Express.Multer.File, @Query() query: FileQuery) {
return this.fileService.uploadFile(file, query);
}
/**
*
* @param file
*/
@Post(FileApiDefinition.uploadChunk.server)
@UseInterceptors(
FileInterceptor('file', {
limits: {
fieldSize: FILE_CHUNK_SIZE,
},
})
)
@UseGuards(JwtGuard)
uploadChunk(@UploadedFile() file: Express.Multer.File, @Query() query: FileQuery) {
return this.fileService.uploadChunk(file, query);
}
/**
*
* @param file
*/
@Post(FileApiDefinition.mergeChunk.server)
@UseGuards(JwtGuard)
mergeChunk(@Query() query: FileQuery) {
return this.fileService.mergeChunk(query);
}
}

View File

@ -0,0 +1,152 @@
import { FILE_CHUNK_SIZE } from '@think/domains';
import * as AliyunOSS from 'ali-oss';
import * as fs from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { pipeWriteStream } from './local.client';
import { BaseOssClient, FileQuery } from './oss.client';
export class AliyunOssClient extends BaseOssClient {
private client: AliyunOSS | null;
/**
* ali-oss
* @returns
*/
private ensureAliyunOssClient(): AliyunOSS {
if (this.client) {
return this.client;
}
const config = this.configService.get('oss.aliyun.config');
try {
this.client = new AliyunOSS(config);
return this.client;
} catch (err) {
console.log('无法启动阿里云存储服务,请检查阿里云 OSS 配置是否正确', err.message);
}
}
/**
*
* @param md5
* @param filename
* @returns
*/
private getInOssFileName(md5, filename) {
return `/think/${md5}/${filename}`;
}
/**
* oss
* @param md5
* @param filename
* @returns
*/
private async checkIfAlreadyInOss(md5, filename) {
this.ensureAliyunOssClient();
const inOssFileName = this.getInOssFileName(md5, filename);
const ifExist = await this.client.head(inOssFileName).catch(() => false);
if (ifExist) {
return ifExist.res.requestUrls[0];
}
return false;
}
/**
*
* @param md5
* @returns
*/
private getStoreDir(md5: string) {
const tmpdir = os.tmpdir();
const dir = path.join(tmpdir, md5);
fs.ensureDirSync(dir);
return dir;
}
/**
*
* @param file
* @param query
* @returns
*/
async uploadFile(file: Express.Multer.File, query: FileQuery): Promise<string> {
const client = this.ensureAliyunOssClient();
const { filename, md5 } = query;
const maybeOssURL = await this.checkIfAlreadyInOss(md5, filename);
if (maybeOssURL) {
return maybeOssURL;
}
const inOssFileName = this.getInOssFileName(md5, filename);
const res = await client.put(inOssFileName, file.buffer);
return res.url;
}
/**
*
* FIXME: 阿里云的文档没看懂
* @param file
* @param query
* @returns
*/
async uploadChunk(file: Express.Multer.File, query: FileQuery): Promise<string | void> {
const { md5, filename, chunkIndex } = query;
if (!('chunkIndex' in query)) {
throw new Error('请指定 chunkIndex');
}
const maybeOssURL = await this.checkIfAlreadyInOss(md5, filename);
if (maybeOssURL) {
return maybeOssURL;
}
const dir = this.getStoreDir(md5);
const chunksDir = path.join(dir, 'chunks');
fs.ensureDirSync(chunksDir);
fs.writeFileSync(path.join(chunksDir, '' + chunkIndex), file.buffer);
}
/**
*
* FIXME: 阿里云的文档没看懂
* @param query
* @returns
*/
async mergeChunk(query: FileQuery): Promise<string> {
const { filename, md5 } = query;
this.ensureAliyunOssClient();
const inOssFileName = this.getInOssFileName(md5, filename);
const dir = this.getStoreDir(md5);
const absoluteFilepath = path.join(dir, filename);
const chunksDir = path.join(dir, 'chunks');
const chunks = fs.readdirSync(chunksDir);
chunks.sort((a, b) => Number(a) - Number(b));
await Promise.all(
chunks.map((chunk, index) => {
return pipeWriteStream(
path.join(chunksDir, chunk),
fs.createWriteStream(absoluteFilepath, {
start: index * FILE_CHUNK_SIZE,
end: (index + 1) * FILE_CHUNK_SIZE,
})
);
})
);
fs.removeSync(chunksDir);
const ret = await this.client.multipartUpload(inOssFileName, absoluteFilepath);
fs.removeSync(absoluteFilepath);
return ret.res.requestUrls[0];
}
}

View File

@ -0,0 +1,20 @@
import { ConfigService } from '@nestjs/config';
import { AliyunOssClient } from './aliyun.client';
import { LocalOssClient } from './local.client';
import { OssClient } from './oss.client';
import { TencentOssClient } from './tencent.client';
export { OssClient };
export const getOssClient = (configService: ConfigService): OssClient => {
if (configService.get('oss.tencent.enable')) {
return new TencentOssClient(configService);
}
if (configService.get('oss.aliyun.enable')) {
return new AliyunOssClient(configService);
}
return new LocalOssClient(configService);
};

View File

@ -0,0 +1,129 @@
import { FILE_CHUNK_SIZE } from '@think/domains';
import * as fs from 'fs-extra';
import * as path from 'path';
import { BaseOssClient, FileQuery } from './oss.client';
export const FILE_DEST = '/' + 'static';
export const FILE_ROOT_PATH = path.join(__dirname, '../../../', FILE_DEST);
export const pipeWriteStream = (filepath, writeStream): Promise<void> => {
return new Promise((resolve) => {
const readStream = fs.createReadStream(filepath);
readStream.on('end', () => {
fs.unlinkSync(filepath);
resolve();
});
readStream.pipe(writeStream);
});
};
export class LocalOssClient extends BaseOssClient {
/**
*
* @param md5
* @returns
*/
protected getStoreDir(md5: string): {
relative: string;
absolute: string;
} {
const filepath = path.join(FILE_ROOT_PATH, md5);
fs.ensureDirSync(filepath);
return { relative: filepath.replace(FILE_ROOT_PATH, FILE_DEST), absolute: filepath };
}
/**
* 访 URL
* @param serverRoot
* @param relativeFilePath
* @returns
*/
protected serveFilePath(relativeFilePath: string) {
const serverRoot = this.configService.get('oss.local.server');
if (!serverRoot) {
throw new Error(`本地文件存储已启动,但未配置 oss.local.server请在 config 完善!`);
}
return new URL(relativeFilePath, serverRoot).href;
}
/**
*
* @param file
* @param query
* @returns
*/
async uploadFile(file: Express.Multer.File, query: FileQuery): Promise<string> {
const { filename, md5 } = query;
const { absolute, relative } = this.getStoreDir(md5);
const absoluteFilepath = path.join(absolute, filename);
const relativeFilePath = path.join(relative, filename);
if (!fs.existsSync(absoluteFilepath)) {
fs.writeFileSync(absoluteFilepath, file.buffer);
}
return this.serveFilePath(relativeFilePath);
}
/**
*
* @param file
* @param query
*/
async uploadChunk(file: Express.Multer.File, query: FileQuery): Promise<void | string> {
const { filename, md5, chunkIndex } = query;
if (!('chunkIndex' in query)) {
throw new Error('请指定 chunkIndex');
}
const { absolute, relative } = this.getStoreDir(md5);
const absoluteFilepath = path.join(absolute, filename);
if (fs.existsSync(absoluteFilepath)) {
const relativeFilePath = path.join(relative, filename);
return this.serveFilePath(relativeFilePath);
}
const chunksDir = path.join(absolute, 'chunks');
fs.ensureDirSync(chunksDir);
fs.writeFileSync(path.join(chunksDir, '' + chunkIndex), file.buffer);
}
/**
*
* @param query
* @returns
*/
async mergeChunk(query: FileQuery): Promise<string> {
const { filename, md5 } = query;
const { absolute, relative } = this.getStoreDir(md5);
const absoluteFilepath = path.join(absolute, filename);
const relativeFilePath = path.join(relative, filename);
if (!fs.existsSync(absoluteFilepath)) {
const chunksDir = path.join(absolute, 'chunks');
const chunks = fs.readdirSync(chunksDir);
chunks.sort((a, b) => Number(a) - Number(b));
await Promise.all(
chunks.map((chunk, index) => {
return pipeWriteStream(
path.join(chunksDir, chunk),
fs.createWriteStream(absoluteFilepath, {
start: index * FILE_CHUNK_SIZE,
end: (index + 1) * FILE_CHUNK_SIZE,
})
);
})
);
fs.removeSync(chunksDir);
}
return this.serveFilePath(relativeFilePath);
}
}

View File

@ -0,0 +1,36 @@
import { ConfigService } from '@nestjs/config';
export type FileQuery = {
filename: string;
md5: string;
chunkIndex?: number;
};
export abstract class OssClient {
abstract uploadFile(file: Express.Multer.File, query: FileQuery): Promise<string>;
abstract uploadChunk(file: Express.Multer.File, query: FileQuery): Promise<void | string>;
abstract mergeChunk(query: FileQuery): Promise<string>;
}
export class BaseOssClient implements OssClient {
protected configService: ConfigService;
constructor(configService) {
this.configService = configService;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
uploadFile(file: Express.Multer.File, query: FileQuery): Promise<string> {
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
uploadChunk(file: Express.Multer.File, query: FileQuery): Promise<void | string> {
throw new Error('Method not implemented.');
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
mergeChunk(query: FileQuery): Promise<string> {
throw new Error('Method not implemented.');
}
}

View File

@ -0,0 +1,279 @@
import * as TencentCos from 'cos-nodejs-sdk-v5';
import { BaseOssClient, FileQuery } from './oss.client';
export class TencentOssClient extends BaseOssClient {
private client: TencentCos | null;
private uploadIdMap: Map<string, string> = new Map();
private uploadChunkEtagMap: Map<
string,
{
PartNumber: number;
ETag: string;
}[]
> = new Map();
/**
*
* @returns
*/
private ensureOssClient(): TencentCos {
if (this.client) {
return this.client;
}
const config = this.configService.get('oss.tencent.config');
try {
this.client = new TencentCos(config);
return this.client;
} catch (err) {
console.log('无法启动腾讯云存储服务,请检查腾讯云 COS 配置是否正确', err.message);
}
}
/**
*
* @param md5
* @param filename
* @returns
*/
private getInOssFileName(md5, filename) {
return `/think/${md5}/${filename}`;
}
/**
* oss
* @param md5
* @param filename
* @returns
*/
private async checkIfAlreadyInOss(inOssFileName): Promise<boolean | string> {
const params = {
Bucket: this.configService.get('oss.tencent.config.Bucket'),
Region: this.configService.get('oss.tencent.config.Region'),
Key: inOssFileName,
};
return new Promise((resolve, reject) => {
this.ensureOssClient();
this.client.headObject(params, (err) => {
if (err) {
resolve(false);
} else {
this.client.getObjectUrl(params, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.Url);
}
});
}
});
});
}
/**
* oss
* @param inOssFileName
* @param file
* @returns
*/
private putObject(inOssFileName, file): Promise<string> {
return new Promise((resolve, reject) => {
const params = {
Bucket: this.configService.get('oss.tencent.config.Bucket'),
Region: this.configService.get('oss.tencent.config.Region'),
Key: inOssFileName,
};
this.ensureOssClient();
this.client.putObject(
{
...params,
StorageClass: 'STANDARD',
Body: file.buffer, // 上传文件对象
},
(err) => {
if (err) {
reject(err);
}
this.client.getObjectUrl(params, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.Url);
}
});
}
);
});
}
/**
*
* @param inOssFileName
* @returns
*/
private getUploadChunkId(inOssFileName): Promise<string> {
if (this.uploadIdMap.has(inOssFileName)) {
return Promise.resolve(this.uploadIdMap.get(inOssFileName));
}
return new Promise((resolve, reject) => {
const params = {
Bucket: this.configService.get('oss.tencent.config.Bucket'),
Region: this.configService.get('oss.tencent.config.Region'),
Key: inOssFileName,
};
this.ensureOssClient();
this.client.multipartInit(params, (err, data) => {
if (err) {
reject(err);
} else {
const uploadId = data.UploadId;
this.uploadIdMap.set(inOssFileName, uploadId);
resolve(uploadId);
}
});
});
}
/**
*
* @param uploadId
* @param inOssFileName
* @param chunkIndex
* @param file
* @returns
*/
private uploadChunkToCos(uploadId, inOssFileName, chunkIndex, file): Promise<void> {
return new Promise((resolve, reject) => {
const params = {
Bucket: this.configService.get('oss.tencent.config.Bucket'),
Region: this.configService.get('oss.tencent.config.Region'),
Key: inOssFileName,
UploadId: uploadId,
PartNumber: chunkIndex,
Body: file.buffer,
};
this.ensureOssClient();
this.client.multipartUpload(params, (err, data) => {
if (err) {
reject(err);
} else {
if (!this.uploadChunkEtagMap.has(uploadId)) {
this.uploadChunkEtagMap.set(uploadId, []);
}
this.uploadChunkEtagMap.get(uploadId).push({
PartNumber: chunkIndex,
ETag: data.ETag,
});
resolve();
}
});
});
}
/**
*
* @param uploadId
* @param inOssFileName
* @param chunkIndex
* @param file
* @returns
*/
private completeUploadChunkToCos(uploadId, inOssFileName): Promise<string> {
return new Promise((resolve, reject) => {
const params = {
Bucket: this.configService.get('oss.tencent.config.Bucket'),
Region: this.configService.get('oss.tencent.config.Region'),
Key: inOssFileName,
};
this.ensureOssClient();
const parts = this.uploadChunkEtagMap.get(uploadId);
parts.sort((a, b) => a.PartNumber - b.PartNumber);
this.client.multipartComplete(
{
...params,
UploadId: uploadId,
Parts: parts,
},
(err) => {
if (err) {
reject(err);
} else {
this.client.getObjectUrl(params, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.Url);
}
});
}
}
);
});
}
/**
*
* @param file
* @param query
* @returns
*/
async uploadFile(file: Express.Multer.File, query: FileQuery): Promise<string> {
this.ensureOssClient();
const { filename, md5 } = query;
const inOssFileName = this.getInOssFileName(md5, filename);
const maybeOssURL = await this.checkIfAlreadyInOss(inOssFileName);
if (maybeOssURL) {
return maybeOssURL as string;
}
const res = await this.putObject(inOssFileName, file);
return res as string;
}
/**
*
* @param file
* @param query
* @returns
*/
async uploadChunk(file: Express.Multer.File, query: FileQuery): Promise<string | void> {
const { md5, filename, chunkIndex } = query;
if (!('chunkIndex' in query)) {
throw new Error('请指定 chunkIndex');
}
this.ensureOssClient();
const inOssFileName = this.getInOssFileName(md5, filename);
const maybeOssURL = await this.checkIfAlreadyInOss(inOssFileName);
if (maybeOssURL) {
return maybeOssURL as string;
}
const uploadId = await this.getUploadChunkId(inOssFileName);
await this.uploadChunkToCos(uploadId, inOssFileName, chunkIndex, file);
return '';
}
/**
*
* @param query
* @returns
*/
async mergeChunk(query: FileQuery): Promise<string> {
const { filename, md5 } = query;
const inOssFileName = this.getInOssFileName(md5, filename);
const uploadId = await this.getUploadChunkId(inOssFileName);
const data = await this.completeUploadChunkToCos(uploadId, inOssFileName);
this.uploadIdMap.delete(inOssFileName);
this.uploadChunkEtagMap.delete(uploadId);
return data;
}
}

View File

@ -1,5 +1,6 @@
import { HttpResponseExceptionFilter } from '@exceptions/http-response.exception';
import { IS_PRODUCTION } from '@helpers/env.helper';
import { FILE_DEST, FILE_ROOT_PATH } from '@helpers/file.helper/local.client';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@pipes/validation.pipe';
@ -31,6 +32,7 @@ async function bootstrap() {
max: config.get('server.rateLimitMax'),
})
);
app.use(cookieParser());
app.use(compression());
app.use(helmet());
@ -41,7 +43,16 @@ async function bootstrap() {
app.useGlobalPipes(new ValidationPipe());
app.setGlobalPrefix(config.get('server.prefix') || '/');
if (config.get('oss.local.enable')) {
const serverStatic = express.static(FILE_ROOT_PATH);
app.use(FILE_DEST, (req, res, next) => {
res.header('Cross-Origin-Resource-Policy', 'cross-origin');
return serverStatic(req, res, next);
});
}
await app.listen(port);
console.log(`[think] 主服务启动成功,端口:${port}`);
}

View File

@ -1,25 +1,24 @@
import { AliyunOssClient } from '@helpers/aliyun.helper';
import { dateFormat } from '@helpers/date.helper';
import { uniqueid } from '@helpers/uniqueid.helper';
import { getOssClient, OssClient } from '@helpers/file.helper';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class FileService {
private ossClient: AliyunOssClient;
private ossClient: OssClient;
constructor(private readonly configService: ConfigService) {
this.ossClient = new AliyunOssClient(this.configService);
this.ossClient = getOssClient(this.configService);
}
/**
*
* @param file
*/
async uploadFile(file) {
const { originalname, buffer } = file;
const filename = `/${dateFormat(new Date(), 'yyyy-MM-dd')}/${uniqueid()}/${originalname}`;
const url = await this.ossClient.putFile(filename, buffer);
return url;
async uploadFile(file, query) {
return this.ossClient.uploadFile(file, query);
}
async uploadChunk(file, query) {
return this.ossClient.uploadChunk(file, query);
}
async mergeChunk(query) {
return this.ossClient.mergeChunk(query);
}
}

View File

@ -23,5 +23,8 @@
"@controllers/*": ["src/controllers/*"],
"@modules/*": ["src/modules/*"]
}
},
"watchOptions": {
"excludeFiles": ["static"]
}
}

View File

@ -136,6 +136,7 @@ importers:
requestidlecallback-polyfill: ^1.0.2
resize-observer-polyfill: ^1.5.1
scroll-into-view-if-needed: ^2.2.29
spark-md5: ^3.0.2
timeago.js: ^4.0.2
tippy.js: ^6.3.7
toggle-selection: ^1.0.6
@ -226,6 +227,7 @@ importers:
requestidlecallback-polyfill: 1.0.2
resize-observer-polyfill: 1.5.1
scroll-into-view-if-needed: 2.2.29
spark-md5: 3.0.2
timeago.js: 4.0.2
tippy.js: 6.3.7
toggle-selection: 1.0.6
@ -289,6 +291,7 @@ importers:
'@types/express': ^4.17.13
'@types/jest': 27.0.2
'@types/lodash': ^4.14.182
'@types/multer': ^1.4.7
'@types/node': ^16.0.0
'@types/supertest': ^2.0.11
'@typescript-eslint/eslint-plugin': ^5.21.0
@ -299,6 +302,7 @@ importers:
class-validator: ^0.13.2
compression: ^1.7.4
cookie-parser: ^1.4.6
cos-nodejs-sdk-v5: ^2.11.9
date-fns: ^2.28.0
eslint: ^8.14.0
eslint-config-prettier: ^8.5.0
@ -350,12 +354,14 @@ importers:
'@think/config': link:../config
'@think/constants': link:../constants
'@think/domains': link:../domains
'@types/multer': 1.4.7
ali-oss: 6.16.0
bcryptjs: 2.4.3
class-transformer: 0.5.1
class-validator: 0.13.2
compression: 1.7.4
cookie-parser: 1.4.6
cos-nodejs-sdk-v5: 2.11.9
date-fns: 2.28.0
express: 4.17.2
express-rate-limit: 6.2.0_express@4.17.2
@ -3103,13 +3109,11 @@ packages:
dependencies:
'@types/connect': 3.4.35
'@types/node': 16.11.21
dev: true
/@types/connect/3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies:
'@types/node': 16.11.21
dev: true
/@types/cookie-parser/1.4.3:
resolution: {integrity: sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==}
@ -3156,7 +3160,6 @@ packages:
'@types/node': 16.11.21
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
dev: true
/@types/express/4.17.13:
resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==}
@ -3165,7 +3168,6 @@ packages:
'@types/express-serve-static-core': 4.17.28
'@types/qs': 6.9.7
'@types/serve-static': 1.13.10
dev: true
/@types/glob/7.2.0:
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
@ -3236,7 +3238,6 @@ packages:
/@types/mime/1.3.2:
resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==}
dev: true
/@types/minimatch/3.0.5:
resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==}
@ -3246,6 +3247,12 @@ packages:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
dev: true
/@types/multer/1.4.7:
resolution: {integrity: sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==}
dependencies:
'@types/express': 4.17.13
dev: false
/@types/node/16.11.21:
resolution: {integrity: sha512-Pf8M1XD9i1ksZEcCP8vuSNwooJ/bZapNmIzpmsMaL+jMI+8mEYU3PKvs+xDNuQcJWF/x24WzY4qxLtB0zNow9A==}
@ -3349,11 +3356,9 @@ packages:
/@types/qs/6.9.7:
resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
dev: true
/@types/range-parser/1.2.4:
resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==}
dev: true
/@types/react-window/1.8.5:
resolution: {integrity: sha512-V9q3CvhC9Jk9bWBOysPGaWy/Z0lxYcTXLtLipkt2cnRj1JOSFNF7wqGpkScSXMgBwC+fnVRg/7shwgddBG5ICw==}
@ -3382,7 +3387,6 @@ packages:
dependencies:
'@types/mime': 1.3.2
'@types/node': 16.11.21
dev: true
/@types/stack-utils/2.0.1:
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
@ -3758,6 +3762,15 @@ packages:
indent-string: 4.0.0
dev: true
/ajv-formats/1.6.1:
resolution: {integrity: sha512-4CjkH20If1lhR5CGtqkrVg3bbOtFEG80X9v6jDOIUhbzzbB+UzPBGy8GQhUNVZ0yvMHdMpawCOcy5ydGMsagGQ==}
peerDependenciesMeta:
ajv:
optional: true
dependencies:
ajv: 7.2.4
dev: false
/ajv-formats/2.1.1:
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependenciesMeta:
@ -3791,6 +3804,15 @@ packages:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
/ajv/7.2.4:
resolution: {integrity: sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==}
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
uri-js: 4.4.1
dev: false
/ajv/8.6.3:
resolution: {integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==}
dependencies:
@ -3988,6 +4010,17 @@ packages:
resolution: {integrity: sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=}
dev: true
/asn1/0.2.6:
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
dependencies:
safer-buffer: 2.1.2
dev: false
/assert-plus/1.0.0:
resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
engines: {node: '>=0.8'}
dev: false
/ast-types/0.13.4:
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
engines: {node: '>=4'}
@ -4014,7 +4047,6 @@ packages:
/asynckit/0.4.0:
resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=}
dev: true
/at-least-node/1.0.0:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
@ -4025,11 +4057,24 @@ packages:
engines: {node: '>=8.0.0'}
dev: false
/atomically/1.7.0:
resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
engines: {node: '>=10.12.0'}
dev: false
/available-typed-arrays/1.0.5:
resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
engines: {node: '>= 0.4'}
dev: false
/aws-sign2/0.7.0:
resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==}
dev: false
/aws4/1.11.0:
resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}
dev: false
/axios/0.24.0:
resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
dependencies:
@ -4184,6 +4229,12 @@ packages:
/base64-js/1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
/bcrypt-pbkdf/1.0.2:
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
dependencies:
tweetnacl: 0.14.5
dev: false
/bcryptjs/2.4.3:
resolution: {integrity: sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=}
dev: false
@ -4393,6 +4444,10 @@ packages:
resolution: {integrity: sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==}
dev: false
/caseless/0.12.0:
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
dev: false
/chalk/2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@ -4622,7 +4677,6 @@ packages:
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: true
/commander/2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@ -4701,6 +4755,23 @@ packages:
yargs: 16.2.0
dev: false
/conf/9.0.2:
resolution: {integrity: sha512-rLSiilO85qHgaTBIIHQpsv8z+NnVfZq3cKuYNCXN1AOqPzced0GWZEe/A517VldRLyQYXUMyV+vszavE2jSAqw==}
engines: {node: '>=10'}
dependencies:
ajv: 7.2.4
ajv-formats: 1.6.1
atomically: 1.7.0
debounce-fn: 4.0.0
dot-prop: 6.0.1
env-paths: 2.2.1
json-schema-typed: 7.0.3
make-dir: 3.1.0
onetime: 5.1.2
pkg-up: 3.1.0
semver: 7.3.5
dev: false
/consola/2.15.3:
resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
dev: false
@ -4778,6 +4849,10 @@ packages:
requiresBuild: true
dev: false
/core-util-is/1.0.2:
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
dev: false
/core-util-is/1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: false
@ -4790,6 +4865,16 @@ packages:
vary: 1.1.2
dev: false
/cos-nodejs-sdk-v5/2.11.9:
resolution: {integrity: sha512-szsUw/8hx1RWUfMNwgErzYcdPM3EwcmgbylqQf82HPZALMCAcaa7qCeAxVQHNvCumWYeQLy7EEloZjMUyjg7Ug==}
engines: {node: '>= 6'}
dependencies:
conf: 9.0.2
mime-types: 2.1.34
request: 2.88.2
xml2js: 0.4.23
dev: false
/cosmiconfig/6.0.0:
resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==}
engines: {node: '>=8'}
@ -4891,6 +4976,13 @@ packages:
/csstype/3.0.10:
resolution: {integrity: sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==}
/dashdash/1.14.1:
resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==}
engines: {node: '>=0.10'}
dependencies:
assert-plus: 1.0.0
dev: false
/data-uri-to-buffer/3.0.1:
resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==}
engines: {node: '>= 6'}
@ -4926,6 +5018,13 @@ packages:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
dev: false
/debounce-fn/4.0.0:
resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
engines: {node: '>=10'}
dependencies:
mimic-fn: 3.1.0
dev: false
/debug/2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
dependencies:
@ -5065,7 +5164,6 @@ packages:
/delayed-stream/1.0.0:
resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=}
engines: {node: '>=0.4.0'}
dev: true
/denque/2.0.1:
resolution: {integrity: sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==}
@ -5153,6 +5251,13 @@ packages:
resolution: {integrity: sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ==}
dev: false
/dot-prop/6.0.1:
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
engines: {node: '>=10'}
dependencies:
is-obj: 2.0.0
dev: false
/dotenv-expand/5.1.0:
resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==}
dev: false
@ -5180,6 +5285,13 @@ packages:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
/ecc-jsbn/0.1.2:
resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==}
dependencies:
jsbn: 0.1.1
safer-buffer: 2.1.2
dev: false
/ecdsa-sig-formatter/1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
dependencies:
@ -5248,6 +5360,11 @@ packages:
resolution: {integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==}
dev: false
/env-paths/2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
dev: false
/error-ex/1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
dependencies:
@ -5726,6 +5843,10 @@ packages:
is-extendable: 0.1.1
dev: false
/extend/3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
dev: false
/external-editor/3.1.0:
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
engines: {node: '>=4'}
@ -5735,6 +5856,11 @@ packages:
tmp: 0.0.33
dev: true
/extsprintf/1.3.0:
resolution: {integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=}
engines: {'0': node >=0.6.0}
dev: false
/fast-deep-equal/3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@ -5869,6 +5995,13 @@ packages:
locate-path: 2.0.0
dev: true
/find-up/3.0.0:
resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
engines: {node: '>=6'}
dependencies:
locate-path: 3.0.0
dev: false
/find-up/4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@ -5902,6 +6035,10 @@ packages:
resolution: {integrity: sha1-C+4AUBiusmDQo6865ljdATbsG5k=}
dev: false
/forever-agent/0.6.1:
resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==}
dev: false
/fork-ts-checker-webpack-plugin/6.5.0_787dd39517260957bc59f00cd4915d0b:
resolution: {integrity: sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw==}
engines: {node: '>=10', yarn: '>=1.0.0'}
@ -5934,6 +6071,15 @@ packages:
webpack: 5.66.0
dev: true
/form-data/2.3.3:
resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
engines: {node: '>= 0.12'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.34
dev: false
/form-data/3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
@ -6111,6 +6257,12 @@ packages:
- supports-color
dev: false
/getpass/0.1.7:
resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==}
dependencies:
assert-plus: 1.0.0
dev: false
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@ -6205,6 +6357,20 @@ packages:
/graceful-fs/4.2.9:
resolution: {integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==}
/har-schema/2.0.0:
resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==}
engines: {node: '>=4'}
dev: false
/har-validator/5.1.5:
resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==}
engines: {node: '>=6'}
deprecated: this library is no longer supported
dependencies:
ajv: 6.12.6
har-schema: 2.0.0
dev: false
/hard-rejection/2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
engines: {node: '>=6'}
@ -6308,6 +6474,15 @@ packages:
transitivePeerDependencies:
- supports-color
/http-signature/1.2.0:
resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
engines: {node: '>=0.8', npm: '>=1.3.7'}
dependencies:
assert-plus: 1.0.0
jsprim: 1.4.2
sshpk: 1.17.0
dev: false
/https-proxy-agent/5.0.0:
resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==}
engines: {node: '>= 6'}
@ -6615,6 +6790,11 @@ packages:
engines: {node: '>=0.10.0'}
dev: false
/is-obj/2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
dev: false
/is-path-cwd/2.2.0:
resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
engines: {node: '>=6'}
@ -6719,7 +6899,6 @@ packages:
/is-typedarray/1.0.0:
resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=}
dev: true
/is-unicode-supported/0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
@ -7331,6 +7510,10 @@ packages:
dependencies:
argparse: 2.0.1
/jsbn/0.1.1:
resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==}
dev: false
/jsdom/16.7.0:
resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==}
engines: {node: '>=10'}
@ -7397,6 +7580,10 @@ packages:
/json-schema-traverse/1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
/json-schema-typed/7.0.3:
resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==}
dev: false
/json-schema/0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
dev: false
@ -7405,6 +7592,10 @@ packages:
resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=}
dev: true
/json-stringify-safe/5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
dev: false
/json5/1.0.1:
resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==}
hasBin: true
@ -7457,6 +7648,16 @@ packages:
semver: 5.7.1
dev: false
/jsprim/1.4.2:
resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
engines: {node: '>=0.6.0'}
dependencies:
assert-plus: 1.0.0
extsprintf: 1.3.0
json-schema: 0.4.0
verror: 1.10.0
dev: false
/jstoxml/0.2.4:
resolution: {integrity: sha1-/z+2eFaIOgMpU8fOjOdIYhD0hEc=}
engines: {node: '>=0.2.0'}
@ -7651,6 +7852,14 @@ packages:
path-exists: 3.0.0
dev: true
/locate-path/3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
dependencies:
p-locate: 3.0.0
path-exists: 3.0.0
dev: false
/locate-path/5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@ -7973,7 +8182,11 @@ packages:
/mimic-fn/2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
dev: true
/mimic-fn/3.1.0:
resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==}
engines: {node: '>=8'}
dev: false
/min-indent/1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
@ -8274,6 +8487,10 @@ packages:
resolution: {integrity: sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==}
dev: true
/oauth-sign/0.9.0:
resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==}
dev: false
/object-assign/4.1.1:
resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}
engines: {node: '>=0.10.0'}
@ -8371,7 +8588,6 @@ packages:
engines: {node: '>=6'}
dependencies:
mimic-fn: 2.1.0
dev: true
/optional/0.1.4:
resolution: {integrity: sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==}
@ -8469,6 +8685,13 @@ packages:
p-limit: 1.3.0
dev: true
/p-locate/3.0.0:
resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
engines: {node: '>=6'}
dependencies:
p-limit: 2.3.0
dev: false
/p-locate/4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
@ -8580,7 +8803,6 @@ packages:
/path-exists/3.0.0:
resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=}
engines: {node: '>=4'}
dev: true
/path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
@ -8632,6 +8854,10 @@ packages:
optional: true
dev: false
/performance-now/2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
dev: false
/picocolors/0.2.1:
resolution: {integrity: sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==}
dev: false
@ -8742,6 +8968,13 @@ packages:
dependencies:
find-up: 4.1.0
/pkg-up/3.1.0:
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
engines: {node: '>=8'}
dependencies:
find-up: 3.0.0
dev: false
/platform/1.3.6:
resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
dev: false
@ -9047,7 +9280,6 @@ packages:
/psl/1.8.0:
resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==}
dev: true
/pump/3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
@ -9069,6 +9301,11 @@ packages:
dependencies:
side-channel: 1.0.4
/qs/6.5.3:
resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
engines: {node: '>=0.6'}
dev: false
/qs/6.9.3:
resolution: {integrity: sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==}
engines: {node: '>=0.6'}
@ -9449,6 +9686,33 @@ packages:
resolution: {integrity: sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=}
dev: false
/request/2.88.2:
resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==}
engines: {node: '>= 6'}
deprecated: request has been deprecated, see https://github.com/request/request/issues/3142
dependencies:
aws-sign2: 0.7.0
aws4: 1.11.0
caseless: 0.12.0
combined-stream: 1.0.8
extend: 3.0.2
forever-agent: 0.6.1
form-data: 2.3.3
har-validator: 5.1.5
http-signature: 1.2.0
is-typedarray: 1.0.0
isstream: 0.1.2
json-stringify-safe: 5.0.1
mime-types: 2.1.34
oauth-sign: 0.9.0
performance-now: 2.1.0
qs: 6.5.3
safe-buffer: 5.2.1
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
uuid: 3.4.0
dev: false
/requestidlecallback-polyfill/1.0.2:
resolution: {integrity: sha512-zzkRzvMe7UdV0M7AIU70vl2fh4rFnNYDL8U0ISwWiOX/5MowBV1ESYCWSQP/KsgJNUOC/AS6X3DApOmxoyE6MA==}
dev: false
@ -9920,6 +10184,10 @@ packages:
/sourcemap-codec/1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
/spark-md5/3.0.2:
resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==}
dev: false
/spawn-command/0.0.2-1:
resolution: {integrity: sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=}
dev: false
@ -9965,6 +10233,22 @@ packages:
engines: {node: '>= 0.6'}
dev: false
/sshpk/1.17.0:
resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==}
engines: {node: '>=0.10.0'}
hasBin: true
dependencies:
asn1: 0.2.6
assert-plus: 1.0.0
bcrypt-pbkdf: 1.0.2
dashdash: 1.14.1
ecc-jsbn: 0.1.2
getpass: 0.1.7
jsbn: 0.1.1
safer-buffer: 2.1.2
tweetnacl: 0.14.5
dev: false
/stack-utils/2.0.5:
resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==}
engines: {node: '>=10'}
@ -10614,6 +10898,14 @@ packages:
engines: {node: '>=0.6'}
dev: false
/tough-cookie/2.5.0:
resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
engines: {node: '>=0.8'}
dependencies:
psl: 1.8.0
punycode: 2.1.1
dev: false
/tough-cookie/4.0.0:
resolution: {integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==}
engines: {node: '>=6'}
@ -10769,6 +11061,16 @@ packages:
typescript: 4.5.5
dev: true
/tunnel-agent/0.6.0:
resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=}
dependencies:
safe-buffer: 5.2.1
dev: false
/tweetnacl/0.14.5:
resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=}
dev: false
/type-check/0.3.2:
resolution: {integrity: sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=}
engines: {node: '>= 0.8.0'}
@ -11066,6 +11368,12 @@ packages:
engines: {node: '>= 0.4.0'}
dev: false
/uuid/3.4.0:
resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
hasBin: true
dev: false
/uuid/8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@ -11101,6 +11409,15 @@ packages:
engines: {node: '>= 0.8'}
dev: false
/verror/1.10.0:
resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=}
engines: {'0': node >=0.6.0}
dependencies:
assert-plus: 1.0.0
core-util-is: 1.0.2
extsprintf: 1.3.0
dev: false
/viewerjs/1.10.4:
resolution: {integrity: sha512-CjMt64yC9D+XUx2t3F0TPbh/Yt5+/ke8/s3IizXa6NtksdJUFDoCcNxi/KRZ9eiZPR/D77pHnnQzAtCoLDaGIw==}
dev: false