fix: fixed upload and download for new networking
This commit is contained in:
parent
85c43d804d
commit
4a0d6fbc88
30 changed files with 1014 additions and 821 deletions
|
@ -181,6 +181,10 @@ module.exports = {
|
|||
],
|
||||
globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly' },
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules
|
||||
|
|
10
package.json
10
package.json
|
@ -27,15 +27,15 @@
|
|||
"@types/node": "18.16.0",
|
||||
"@types/node-forge": "1.3.2",
|
||||
"@types/ws": "8.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.8",
|
||||
"@typescript-eslint/parser": "5.59.8",
|
||||
"@typescript-eslint/eslint-plugin": "6.4.0",
|
||||
"@typescript-eslint/parser": "6.4.0",
|
||||
"chai": "4.3.7",
|
||||
"dotenv-flow": "3.2.0",
|
||||
"eslint": "8.42.0",
|
||||
"eslint": "8.47.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
"eslint-import-resolver-typescript": "3.5.5",
|
||||
"eslint-import-resolver-typescript": "3.6.0",
|
||||
"eslint-plugin-ascii": "1.0.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-import": "2.28.0",
|
||||
"eslint-plugin-simple-import-sort": "10.0.0",
|
||||
"glob": "10.2.6",
|
||||
"husky": "^8.0.3",
|
||||
|
|
|
@ -64,8 +64,11 @@ async function addSingleMethod(state, fileName) {
|
|||
|
||||
if (
|
||||
!stmt.importClause.namedBindings ||
|
||||
stmt.importClause.namedBindings.kind !== ts.SyntaxKind.NamedImports
|
||||
) { throwError(stmt, fileName, 'Only named imports are supported!') }
|
||||
stmt.importClause.namedBindings.kind !==
|
||||
ts.SyntaxKind.NamedImports
|
||||
) {
|
||||
throwError(stmt, fileName, 'Only named imports are supported!')
|
||||
}
|
||||
|
||||
let module = stmt.moduleSpecifier.text
|
||||
|
||||
|
@ -131,11 +134,7 @@ async function addSingleMethod(state, fileName) {
|
|||
})()
|
||||
|
||||
if (!isExported && !isPrivate) {
|
||||
throwError(
|
||||
stmt,
|
||||
fileName,
|
||||
'Public methods MUST be exported.',
|
||||
)
|
||||
throwError(stmt, fileName, 'Public methods MUST be exported.')
|
||||
}
|
||||
|
||||
if (isExported && !checkForFlag(stmt, '@internal')) {
|
||||
|
@ -182,16 +181,20 @@ async function addSingleMethod(state, fileName) {
|
|||
)
|
||||
}
|
||||
|
||||
const returnsExported = (stmt.body ?
|
||||
ts.getLeadingCommentRanges(fileFullText, stmt.body.pos + 2) ||
|
||||
(stmt.statements &&
|
||||
stmt.statements.length &&
|
||||
ts.getLeadingCommentRanges(
|
||||
fileFullText,
|
||||
stmt.statements[0].pos,
|
||||
)) ||
|
||||
[] :
|
||||
[]
|
||||
const returnsExported = (
|
||||
stmt.body ?
|
||||
ts.getLeadingCommentRanges(
|
||||
fileFullText,
|
||||
stmt.body.pos + 2,
|
||||
) ||
|
||||
(stmt.statements &&
|
||||
stmt.statements.length &&
|
||||
ts.getLeadingCommentRanges(
|
||||
fileFullText,
|
||||
stmt.statements[0].pos,
|
||||
)) ||
|
||||
[] :
|
||||
[]
|
||||
)
|
||||
.map((range) => fileFullText.substring(range.pos, range.end))
|
||||
.join('\n')
|
||||
|
@ -275,7 +278,9 @@ async function addSingleMethod(state, fileName) {
|
|||
}
|
||||
|
||||
async function main() {
|
||||
const output = fs.createWriteStream(path.join(__dirname, '../src/client.ts'))
|
||||
const output = fs.createWriteStream(
|
||||
path.join(__dirname, '../src/client.ts'),
|
||||
)
|
||||
const state = {
|
||||
imports: {},
|
||||
fields: [],
|
||||
|
@ -295,7 +300,8 @@ async function main() {
|
|||
}
|
||||
|
||||
output.write(
|
||||
'/* THIS FILE WAS AUTO-GENERATED */\n' +
|
||||
'/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging, @typescript-eslint/unified-signatures */\n' +
|
||||
'/* THIS FILE WAS AUTO-GENERATED */\n' +
|
||||
"import { BaseTelegramClient, BaseTelegramClientOptions } from '@mtcute/core'\n" +
|
||||
"import { tl } from '@mtcute/tl'\n",
|
||||
)
|
||||
|
@ -336,7 +342,9 @@ async function main() {
|
|||
* @param name Event name
|
||||
* @param handler ${updates.toSentence(type, 'full')}
|
||||
*/
|
||||
on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this\n`)
|
||||
on(name: '${type.typeName}', handler: ((upd: ${
|
||||
type.updateType
|
||||
}) => void)): this\n`)
|
||||
})
|
||||
|
||||
const printer = ts.createPrinter()
|
||||
|
@ -406,7 +414,9 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this
|
|||
it.initializer = undefined
|
||||
|
||||
const deleteParents = (obj) => {
|
||||
if (Array.isArray(obj)) { return obj.forEach((it) => deleteParents(it)) }
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.forEach((it) => deleteParents(it))
|
||||
}
|
||||
|
||||
if (obj.parent) delete obj.parent
|
||||
|
||||
|
@ -455,7 +465,7 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this
|
|||
for (const name of [origName, ...aliases]) {
|
||||
if (!hasOverloads) {
|
||||
if (!comment.match(/\/\*\*?\s*\*\//)) {
|
||||
// empty comment, no need to write it
|
||||
// empty comment, no need to write it
|
||||
output.write(comment + '\n')
|
||||
}
|
||||
|
||||
|
@ -465,18 +475,14 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this
|
|||
}
|
||||
|
||||
if (!overload) {
|
||||
classContents.push(
|
||||
`${name} = ${origName}`,
|
||||
)
|
||||
classContents.push(`${name} = ${origName}`)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
output.write('}\n')
|
||||
|
||||
output.write(
|
||||
'\nexport class TelegramClient extends BaseTelegramClient {\n',
|
||||
)
|
||||
output.write('\nexport class TelegramClient extends BaseTelegramClient {\n')
|
||||
|
||||
state.fields.forEach(({ code }) => output.write(`protected ${code}\n`))
|
||||
|
||||
|
@ -501,10 +507,9 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this
|
|||
await fs.promises.writeFile(targetFile, fullSource)
|
||||
|
||||
// fix using eslint
|
||||
require('child_process').execSync(
|
||||
`pnpm exec eslint --fix ${targetFile}`,
|
||||
{ stdio: 'inherit' },
|
||||
)
|
||||
require('child_process').execSync(`pnpm exec eslint --fix ${targetFile}`, {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
}
|
||||
|
||||
main().catch(console.error)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
|
||||
/* THIS FILE WAS AUTO-GENERATED */
|
||||
import { Readable } from 'stream'
|
||||
|
||||
|
@ -8,7 +9,6 @@ import {
|
|||
Deque,
|
||||
MaybeArray,
|
||||
MaybeAsync,
|
||||
SessionConnection,
|
||||
SortedLinkedList,
|
||||
} from '@mtcute/core'
|
||||
import { ConditionVariable } from '@mtcute/core/src/utils/condition-variable'
|
||||
|
@ -1908,14 +1908,15 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
|
||||
/**
|
||||
* Total file size. Automatically inferred for Buffer, File and local files.
|
||||
*
|
||||
* When using with streams, if `fileSize` is not passed, the entire file is
|
||||
* first loaded into memory to determine file size, and used as a Buffer later.
|
||||
* This might be a major performance bottleneck, so be sure to provide file size
|
||||
* when using streams and file size is known (which often is the case).
|
||||
*/
|
||||
fileSize?: number
|
||||
|
||||
/**
|
||||
* If the file size is unknown, you can provide an estimate,
|
||||
* which will be used to determine appropriate part size.
|
||||
*/
|
||||
estimatedSize?: number
|
||||
|
||||
/**
|
||||
* File MIME type. By default is automatically inferred from magic number
|
||||
* If MIME can't be inferred, it defaults to `application/octet-stream`
|
||||
|
@ -1930,11 +1931,16 @@ export interface TelegramClient extends BaseTelegramClient {
|
|||
*/
|
||||
partSize?: number
|
||||
|
||||
/**
|
||||
* Number of parts to be sent in parallel per connection.
|
||||
*/
|
||||
requestsPerConnection?: number
|
||||
|
||||
/**
|
||||
* Function that will be called after some part has been uploaded.
|
||||
*
|
||||
* @param uploaded Number of bytes already uploaded
|
||||
* @param total Total file size
|
||||
* @param total Total file size, if known
|
||||
*/
|
||||
progressCallback?: (uploaded: number, total: number) => void
|
||||
}): Promise<UploadedFile>
|
||||
|
@ -4019,7 +4025,6 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
protected _selfUsername: string | null
|
||||
protected _pendingConversations: Record<number, Conversation[]>
|
||||
protected _hasConversations: boolean
|
||||
protected _downloadConnections: Record<number, SessionConnection>
|
||||
protected _parseModes: Record<string, IMessageEntityParser>
|
||||
protected _defaultParseMode: string | null
|
||||
protected _updatesLoopActive: boolean
|
||||
|
@ -4054,7 +4059,6 @@ export class TelegramClient extends BaseTelegramClient {
|
|||
this.log.prefix = '[USER N/A] '
|
||||
this._pendingConversations = {}
|
||||
this._hasConversations = false
|
||||
this._downloadConnections = {}
|
||||
this._parseModes = {}
|
||||
this._defaultParseMode = null
|
||||
this._updatesLoopActive = false
|
||||
|
|
|
@ -2,12 +2,7 @@
|
|||
import { Readable } from 'stream'
|
||||
|
||||
// @copy
|
||||
import {
|
||||
AsyncLock,
|
||||
MaybeArray,
|
||||
MaybeAsync,
|
||||
SessionConnection,
|
||||
} from '@mtcute/core'
|
||||
import { AsyncLock, MaybeArray, MaybeAsync } from '@mtcute/core'
|
||||
// @copy
|
||||
import { Logger } from '@mtcute/core/src/utils/logger'
|
||||
// @copy
|
||||
|
|
|
@ -155,8 +155,9 @@ export async function start(
|
|||
me.isBot,
|
||||
)
|
||||
|
||||
// todo where is this._disableUpdates?
|
||||
if (!false) {
|
||||
this.network.setIsPremium(me.isPremium)
|
||||
|
||||
if (!this.network.params.disableUpdates) {
|
||||
this._catchUpChannels = Boolean(params.catchUp)
|
||||
|
||||
if (!params.catchUp) {
|
||||
|
@ -176,14 +177,18 @@ export async function start(
|
|||
if (!(e instanceof tl.errors.AuthKeyUnregisteredError)) throw e
|
||||
}
|
||||
|
||||
if (!params.phone && !params.botToken) { throw new MtArgumentError('Neither phone nor bot token were provided') }
|
||||
if (!params.phone && !params.botToken) {
|
||||
throw new MtArgumentError('Neither phone nor bot token were provided')
|
||||
}
|
||||
|
||||
let phone = params.phone ? await resolveMaybeDynamic(params.phone) : null
|
||||
|
||||
if (phone) {
|
||||
phone = normalizePhoneNumber(phone)
|
||||
|
||||
if (!params.code) { throw new MtArgumentError('You must pass `code` to use `phone`') }
|
||||
if (!params.code) {
|
||||
throw new MtArgumentError('You must pass `code` to use `phone`')
|
||||
}
|
||||
} else {
|
||||
const botToken = params.botToken ?
|
||||
await resolveMaybeDynamic(params.botToken) :
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { SessionConnection } from '@mtcute/core'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
|
||||
// @extension
|
||||
interface FilesExtension {
|
||||
_downloadConnections: Record<number, SessionConnection>
|
||||
}
|
||||
|
||||
// @initialize
|
||||
function _initializeFiles(this: TelegramClient): void {
|
||||
this._downloadConnections = {}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { ConditionVariable, ConnectionKind } from '@mtcute/core'
|
||||
import {
|
||||
fileIdToInputFileLocation,
|
||||
fileIdToInputWebFileLocation,
|
||||
|
@ -10,9 +11,16 @@ import {
|
|||
FileDownloadParameters,
|
||||
FileLocation,
|
||||
MtArgumentError,
|
||||
MtUnsupportedError,
|
||||
} from '../../types'
|
||||
import { determinePartSize } from '../../utils/file-utils'
|
||||
|
||||
// small files (less than 128 kb) are downloaded using the "downloadSmall" pool
|
||||
// furthermore, if the file is small and is located on our main DC, it will be downloaded
|
||||
// using the current main connection
|
||||
const SMALL_FILE_MAX_SIZE = 131072
|
||||
const REQUESTS_PER_CONNECTION = 3 // some arbitrary magic value that seems to work best
|
||||
|
||||
/**
|
||||
* Download a file and return it as an iterable, which yields file contents
|
||||
* in chunks of a given size. Order of the chunks is guaranteed to be
|
||||
|
@ -25,16 +33,6 @@ export async function* downloadAsIterable(
|
|||
this: TelegramClient,
|
||||
params: FileDownloadParameters,
|
||||
): AsyncIterableIterator<Buffer> {
|
||||
const partSizeKb =
|
||||
params.partSize ??
|
||||
(params.fileSize ? determinePartSize(params.fileSize) : 64)
|
||||
|
||||
if (partSizeKb % 4 !== 0) {
|
||||
throw new MtArgumentError(
|
||||
`Invalid part size: ${partSizeKb}. Must be divisible by 4.`,
|
||||
)
|
||||
}
|
||||
|
||||
const offset = params.offset ?? 0
|
||||
|
||||
if (offset % 4096 !== 0) {
|
||||
|
@ -77,91 +75,151 @@ export async function* downloadAsIterable(
|
|||
// we will receive a FileMigrateError in case this is invalid
|
||||
if (!dcId) dcId = this._defaultDc.id
|
||||
|
||||
const partSizeKb =
|
||||
params.partSize ?? (fileSize ? determinePartSize(fileSize) : 64)
|
||||
|
||||
if (partSizeKb % 4 !== 0) {
|
||||
throw new MtArgumentError(
|
||||
`Invalid part size: ${partSizeKb}. Must be divisible by 4.`,
|
||||
)
|
||||
}
|
||||
|
||||
const chunkSize = partSizeKb * 1024
|
||||
|
||||
const limit =
|
||||
params.limit ??
|
||||
// derive limit from chunk size, file size and offset
|
||||
(fileSize ?
|
||||
~~((fileSize + chunkSize - offset - 1) / chunkSize) :
|
||||
// we will receive an error when we have reached the end anyway
|
||||
Infinity)
|
||||
let limitBytes = params.limit ?? fileSize ?? Infinity
|
||||
if (limitBytes === 0) return
|
||||
|
||||
// fixme
|
||||
throw new Error('TODO')
|
||||
let numChunks =
|
||||
limitBytes === Infinity ?
|
||||
Infinity :
|
||||
~~((limitBytes + chunkSize - offset - 1) / chunkSize)
|
||||
|
||||
// let connection = this._downloadConnections[dcId]
|
||||
let nextChunkIdx = 0
|
||||
let nextWorkerChunkIdx = 0
|
||||
const nextChunkCv = new ConditionVariable()
|
||||
const buffer: Record<number, Buffer> = {}
|
||||
|
||||
// if (!connection) {
|
||||
// connection = await this.createAdditionalConnection(dcId)
|
||||
// this._downloadConnections[dcId] = connection
|
||||
// }
|
||||
//
|
||||
// const requestCurrent = async (): Promise<Buffer> => {
|
||||
// let result:
|
||||
// | tl.RpcCallReturn['upload.getFile']
|
||||
// | tl.RpcCallReturn['upload.getWebFile']
|
||||
//
|
||||
// try {
|
||||
// result = await this.call(
|
||||
// {
|
||||
// _: isWeb ? 'upload.getWebFile' : 'upload.getFile',
|
||||
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// location: location as any,
|
||||
// offset,
|
||||
// limit: chunkSize,
|
||||
// },
|
||||
// { connection },
|
||||
// )
|
||||
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// } catch (e: any) {
|
||||
// if (e.constructor === tl.errors.FileMigrateXError) {
|
||||
// connection = this._downloadConnections[e.new_dc]
|
||||
//
|
||||
// if (!connection) {
|
||||
// connection = await this.createAdditionalConnection(e.new_dc)
|
||||
// this._downloadConnections[e.new_dc] = connection
|
||||
// }
|
||||
//
|
||||
// return requestCurrent()
|
||||
// } else if (e.constructor === tl.errors.FilerefUpgradeNeededError) {
|
||||
// // todo: implement someday
|
||||
// // see: https://github.com/LonamiWebs/Telethon/blob/0e8bd8248cc649637b7c392616887c50986427a0/telethon/client/downloads.py#L99
|
||||
// throw new MtUnsupportedError('File ref expired!')
|
||||
// } else throw e
|
||||
// }
|
||||
//
|
||||
// if (result._ === 'upload.fileCdnRedirect') {
|
||||
// // we shouldnt receive them since cdnSupported is not set in the getFile request.
|
||||
// // also, i couldnt find any media that would be downloaded from cdn, so even if
|
||||
// // i implemented that, i wouldnt be able to test that, so :shrug:
|
||||
// throw new MtUnsupportedError(
|
||||
// 'Received CDN redirect, which is not supported (yet)',
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// if (
|
||||
// result._ === 'upload.webFile' &&
|
||||
// result.size &&
|
||||
// limit === Infinity
|
||||
// ) {
|
||||
// limit = result.size
|
||||
// }
|
||||
//
|
||||
// return result.bytes
|
||||
// }
|
||||
//
|
||||
// for (let i = 0; i < limit; i++) {
|
||||
// const buf = await requestCurrent()
|
||||
//
|
||||
// if (buf.length === 0) {
|
||||
// // we've reached the end
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// yield buf
|
||||
// offset += chunkSize
|
||||
//
|
||||
// params.progressCallback?.(offset, limit)
|
||||
// }
|
||||
const isSmall = fileSize && fileSize <= SMALL_FILE_MAX_SIZE
|
||||
let connectionKind: ConnectionKind
|
||||
|
||||
if (isSmall) {
|
||||
connectionKind =
|
||||
dcId === this.network.getPrimaryDcId() ? 'main' : 'downloadSmall'
|
||||
} else {
|
||||
connectionKind = 'download'
|
||||
}
|
||||
const poolSize = this.network.getPoolSize(connectionKind, dcId)
|
||||
|
||||
this.log.debug(
|
||||
'Downloading file of size %d from dc %d using %s connection pool (pool size: %d)',
|
||||
limitBytes,
|
||||
dcId,
|
||||
connectionKind,
|
||||
poolSize,
|
||||
)
|
||||
|
||||
const downloadChunk = async (
|
||||
chunk = nextWorkerChunkIdx++,
|
||||
): Promise<void> => {
|
||||
let result:
|
||||
| tl.RpcCallReturn['upload.getFile']
|
||||
| tl.RpcCallReturn['upload.getWebFile']
|
||||
|
||||
try {
|
||||
result = await this.call(
|
||||
{
|
||||
_: isWeb ? 'upload.getWebFile' : 'upload.getFile',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
location: location as any,
|
||||
offset: chunkSize * chunk,
|
||||
limit: chunkSize,
|
||||
},
|
||||
{ dcId, kind: connectionKind },
|
||||
)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
if (e.constructor === tl.errors.FileMigrateXError) {
|
||||
dcId = e.new_dc
|
||||
|
||||
return downloadChunk(chunk)
|
||||
} else if (e.constructor === tl.errors.FilerefUpgradeNeededError) {
|
||||
// todo: implement someday
|
||||
// see: https://github.com/LonamiWebs/Telethon/blob/0e8bd8248cc649637b7c392616887c50986427a0/telethon/client/downloads.py#L99
|
||||
throw new MtUnsupportedError('File ref expired!')
|
||||
} else throw e
|
||||
}
|
||||
|
||||
if (result._ === 'upload.fileCdnRedirect') {
|
||||
// we shouldnt receive them since cdnSupported is not set in the getFile request.
|
||||
// also, i couldnt find any media that would be downloaded from cdn, so even if
|
||||
// i implemented that, i wouldnt be able to test that, so :shrug:
|
||||
throw new MtUnsupportedError(
|
||||
'Received CDN redirect, which is not supported (yet)',
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
result._ === 'upload.webFile' &&
|
||||
result.size &&
|
||||
limitBytes === Infinity
|
||||
) {
|
||||
limitBytes = result.size
|
||||
numChunks = ~~((limitBytes + chunkSize - offset - 1) / chunkSize)
|
||||
}
|
||||
|
||||
buffer[chunk] = result.bytes
|
||||
|
||||
if (chunk === nextChunkIdx) {
|
||||
nextChunkCv.notify()
|
||||
}
|
||||
|
||||
if (
|
||||
nextWorkerChunkIdx < numChunks &&
|
||||
result.bytes.length === chunkSize
|
||||
) {
|
||||
return downloadChunk()
|
||||
}
|
||||
}
|
||||
|
||||
let error: unknown = undefined
|
||||
Promise.all(
|
||||
Array.from(
|
||||
{ length: Math.min(poolSize * REQUESTS_PER_CONNECTION, numChunks) },
|
||||
downloadChunk,
|
||||
),
|
||||
)
|
||||
.catch((e) => {
|
||||
this.log.debug('download workers errored: %s', e.message)
|
||||
error = e
|
||||
nextChunkCv.notify()
|
||||
})
|
||||
.then(() => {
|
||||
this.log.debug('download workers finished')
|
||||
})
|
||||
|
||||
let position = offset
|
||||
|
||||
while (position < limitBytes) {
|
||||
await nextChunkCv.wait()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
while (nextChunkIdx in buffer) {
|
||||
const buf = buffer[nextChunkIdx]
|
||||
delete buffer[nextChunkIdx]
|
||||
|
||||
position += buf.length
|
||||
|
||||
params.progressCallback?.(position, limitBytes)
|
||||
|
||||
yield buf
|
||||
|
||||
nextChunkIdx++
|
||||
|
||||
if (buf.length < chunkSize) {
|
||||
// we received the last chunk
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { fromBuffer as fileTypeFromBuffer } from 'file-type'
|
|||
import type { ReadStream } from 'fs'
|
||||
import { Readable } from 'stream'
|
||||
|
||||
import { randomLong } from '@mtcute/core'
|
||||
import { AsyncLock, randomLong } from '@mtcute/core'
|
||||
import { tl } from '@mtcute/tl'
|
||||
|
||||
import { TelegramClient } from '../../client'
|
||||
|
@ -13,7 +13,6 @@ import {
|
|||
bufferToStream,
|
||||
convertWebStreamToNodeReadable,
|
||||
readBytesFromStream,
|
||||
readStreamUntilEnd,
|
||||
} from '../../utils/stream-utils'
|
||||
|
||||
let fs: any = null
|
||||
|
@ -29,6 +28,14 @@ const OVERRIDE_MIME: Record<string, string> = {
|
|||
'audio/opus': 'audio/ogg',
|
||||
}
|
||||
|
||||
// small files (less than 128 kb) are uploaded using the current connection and not the "upload" pool
|
||||
const SMALL_FILE_MAX_SIZE = 131072
|
||||
const BIG_FILE_MIN_SIZE = 10485760 // files >10 MB are considered "big"
|
||||
const DEFAULT_FILE_NAME = 'unnamed'
|
||||
const REQUESTS_PER_CONNECTION = 3
|
||||
const MAX_PART_COUNT = 4000 // 512 kb * 4000 = 2000 MiB
|
||||
const MAX_PART_COUNT_PREMIUM = 8000 // 512 kb * 8000 = 4000 MiB
|
||||
|
||||
/**
|
||||
* Upload a file to Telegram servers, without actually
|
||||
* sending a message anywhere. Useful when an `InputFile` is required.
|
||||
|
@ -60,14 +67,15 @@ export async function uploadFile(
|
|||
|
||||
/**
|
||||
* Total file size. Automatically inferred for Buffer, File and local files.
|
||||
*
|
||||
* When using with streams, if `fileSize` is not passed, the entire file is
|
||||
* first loaded into memory to determine file size, and used as a Buffer later.
|
||||
* This might be a major performance bottleneck, so be sure to provide file size
|
||||
* when using streams and file size is known (which often is the case).
|
||||
*/
|
||||
fileSize?: number
|
||||
|
||||
/**
|
||||
* If the file size is unknown, you can provide an estimate,
|
||||
* which will be used to determine appropriate part size.
|
||||
*/
|
||||
estimatedSize?: number
|
||||
|
||||
/**
|
||||
* File MIME type. By default is automatically inferred from magic number
|
||||
* If MIME can't be inferred, it defaults to `application/octet-stream`
|
||||
|
@ -82,11 +90,16 @@ export async function uploadFile(
|
|||
*/
|
||||
partSize?: number
|
||||
|
||||
/**
|
||||
* Number of parts to be sent in parallel per connection.
|
||||
*/
|
||||
requestsPerConnection?: number
|
||||
|
||||
/**
|
||||
* Function that will be called after some part has been uploaded.
|
||||
*
|
||||
* @param uploaded Number of bytes already uploaded
|
||||
* @param total Total file size
|
||||
* @param total Total file size, if known
|
||||
*/
|
||||
progressCallback?: (uploaded: number, total: number) => void
|
||||
},
|
||||
|
@ -94,7 +107,7 @@ export async function uploadFile(
|
|||
// normalize params
|
||||
let file = params.file
|
||||
let fileSize = -1 // unknown
|
||||
let fileName = 'unnamed'
|
||||
let fileName = DEFAULT_FILE_NAME
|
||||
let fileMime = params.fileMime
|
||||
|
||||
if (Buffer.isBuffer(file)) {
|
||||
|
@ -162,12 +175,12 @@ export async function uploadFile(
|
|||
}
|
||||
}
|
||||
|
||||
if (fileName === 'unnamed') {
|
||||
if (fileName === DEFAULT_FILE_NAME) {
|
||||
// try to infer from url
|
||||
const url = new URL(file.url)
|
||||
const name = url.pathname.split('/').pop()
|
||||
|
||||
if (name && name.indexOf('.') > -1) {
|
||||
if (name && name.includes('.')) {
|
||||
fileName = name
|
||||
}
|
||||
}
|
||||
|
@ -192,42 +205,88 @@ export async function uploadFile(
|
|||
// set file size if not automatically inferred
|
||||
if (fileSize === -1 && params.fileSize) fileSize = params.fileSize
|
||||
|
||||
if (fileSize === -1) {
|
||||
// load the entire stream into memory
|
||||
const buffer = await readStreamUntilEnd(file as Readable)
|
||||
fileSize = buffer.length
|
||||
file = bufferToStream(buffer)
|
||||
let partSizeKb = params.partSize
|
||||
|
||||
if (!partSizeKb) {
|
||||
if (fileSize === -1) {
|
||||
partSizeKb = params.estimatedSize ?
|
||||
determinePartSize(params.estimatedSize) :
|
||||
64
|
||||
} else {
|
||||
partSizeKb = determinePartSize(fileSize)
|
||||
}
|
||||
}
|
||||
|
||||
if (!(file instanceof Readable)) {
|
||||
throw new MtArgumentError('Could not convert input `file` to stream!')
|
||||
}
|
||||
|
||||
const partSizeKb = params.partSize ?? determinePartSize(fileSize)
|
||||
|
||||
if (partSizeKb > 512) {
|
||||
throw new MtArgumentError(`Invalid part size: ${partSizeKb}KB`)
|
||||
}
|
||||
const partSize = partSizeKb * 1024
|
||||
|
||||
const isBig = fileSize > 10485760 // 10 MB
|
||||
const hash = this._crypto.createMd5()
|
||||
let partCount =
|
||||
fileSize === -1 ? -1 : ~~((fileSize + partSize - 1) / partSize)
|
||||
const maxPartCount = this.network.params.isPremium ?
|
||||
MAX_PART_COUNT_PREMIUM :
|
||||
MAX_PART_COUNT
|
||||
|
||||
if (partCount > maxPartCount) {
|
||||
throw new MtArgumentError(
|
||||
`File is too large (max ${maxPartCount} parts, got ${partCount})`,
|
||||
)
|
||||
}
|
||||
|
||||
const isBig = fileSize === -1 || fileSize > BIG_FILE_MIN_SIZE
|
||||
const isSmall = fileSize !== -1 && fileSize < SMALL_FILE_MAX_SIZE
|
||||
const connectionKind = isSmall ? 'main' : 'upload'
|
||||
const connectionPoolSize = Math.min(
|
||||
this.network.getPoolSize(connectionKind),
|
||||
partCount,
|
||||
)
|
||||
const requestsPerConnection =
|
||||
params.requestsPerConnection ?? REQUESTS_PER_CONNECTION
|
||||
|
||||
const partCount = ~~((fileSize + partSize - 1) / partSize)
|
||||
this.log.debug(
|
||||
'uploading %d bytes file in %d chunks, each %d bytes',
|
||||
'uploading %d bytes file in %d chunks, each %d bytes in %s connection pool of size %d',
|
||||
fileSize,
|
||||
partCount,
|
||||
partSize,
|
||||
connectionKind,
|
||||
connectionPoolSize,
|
||||
)
|
||||
|
||||
// why is the file id generated by the client?
|
||||
// isn't the server supposed to generate it and handle collisions?
|
||||
const fileId = randomLong()
|
||||
let pos = 0
|
||||
const stream = file
|
||||
|
||||
for (let idx = 0; idx < partCount; idx++) {
|
||||
const part = await readBytesFromStream(file, partSize)
|
||||
let pos = 0
|
||||
let idx = 0
|
||||
const lock = new AsyncLock()
|
||||
|
||||
const uploadNextPart = async (): Promise<void> => {
|
||||
const thisIdx = idx++
|
||||
|
||||
let part
|
||||
|
||||
try {
|
||||
await lock.acquire()
|
||||
part = await readBytesFromStream(stream, partSize)
|
||||
} finally {
|
||||
lock.release()
|
||||
}
|
||||
|
||||
if (fileSize === -1 && stream.readableEnded) {
|
||||
fileSize = pos + (part?.length ?? 0)
|
||||
partCount = ~~((fileSize + partSize - 1) / partSize)
|
||||
this.log.debug(
|
||||
'readable ended, file size = %d, part count = %d',
|
||||
fileSize,
|
||||
partCount,
|
||||
)
|
||||
}
|
||||
|
||||
if (!part) {
|
||||
throw new MtArgumentError(
|
||||
|
@ -236,15 +295,15 @@ export async function uploadFile(
|
|||
}
|
||||
|
||||
if (!Buffer.isBuffer(part)) {
|
||||
throw new MtArgumentError(`Part ${idx} was not a Buffer!`)
|
||||
throw new MtArgumentError(`Part ${thisIdx} was not a Buffer!`)
|
||||
}
|
||||
if (part.length > partSize) {
|
||||
throw new MtArgumentError(
|
||||
`Part ${idx} had invalid size (expected ${partSize}, got ${part.length})`,
|
||||
`Part ${thisIdx} had invalid size (expected ${partSize}, got ${part.length})`,
|
||||
)
|
||||
}
|
||||
|
||||
if (idx === 0 && fileMime === undefined) {
|
||||
if (thisIdx === 0 && fileMime === undefined) {
|
||||
const fileType = await fileTypeFromBuffer(part)
|
||||
fileMime = fileType?.mime
|
||||
|
||||
|
@ -260,37 +319,43 @@ export async function uploadFile(
|
|||
}
|
||||
}
|
||||
|
||||
if (!isBig) {
|
||||
// why md5 only small files?
|
||||
// big files have more chance of corruption, but whatever
|
||||
// also isn't integrity guaranteed by mtproto?
|
||||
await hash.update(part)
|
||||
}
|
||||
|
||||
pos += part.length
|
||||
|
||||
// why
|
||||
const request = isBig ?
|
||||
({
|
||||
_: 'upload.saveBigFilePart',
|
||||
fileId,
|
||||
filePart: idx,
|
||||
filePart: thisIdx,
|
||||
fileTotalParts: partCount,
|
||||
bytes: part,
|
||||
} as tl.upload.RawSaveBigFilePartRequest) :
|
||||
} satisfies tl.upload.RawSaveBigFilePartRequest) :
|
||||
({
|
||||
_: 'upload.saveFilePart',
|
||||
fileId,
|
||||
filePart: idx,
|
||||
filePart: thisIdx,
|
||||
bytes: part,
|
||||
} as tl.upload.RawSaveFilePartRequest)
|
||||
} satisfies tl.upload.RawSaveFilePartRequest)
|
||||
|
||||
const result = await this.call(request)
|
||||
const result = await this.call(request, { kind: connectionKind })
|
||||
if (!result) throw new Error(`Failed to upload part ${idx}`)
|
||||
|
||||
pos += part.length
|
||||
|
||||
params.progressCallback?.(pos, fileSize)
|
||||
|
||||
if (idx === partCount) return
|
||||
|
||||
return uploadNextPart()
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Array.from(
|
||||
{
|
||||
length: connectionPoolSize * requestsPerConnection,
|
||||
},
|
||||
uploadNextPart,
|
||||
),
|
||||
)
|
||||
|
||||
let inputFile: tl.TypeInputFile
|
||||
|
||||
if (isBig) {
|
||||
|
@ -306,7 +371,7 @@ export async function uploadFile(
|
|||
id: fileId,
|
||||
parts: partCount,
|
||||
name: fileName,
|
||||
md5Checksum: (await hash.digest()).toString('hex'),
|
||||
md5Checksum: '', // tdlib doesn't do this, why should we?
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -558,11 +558,15 @@ async function _fetchPeersForShort(
|
|||
if (
|
||||
msg.replyTo._ === 'messageReplyHeader' &&
|
||||
!(await fetchPeer(msg.replyTo.replyToPeerId))
|
||||
) { return null }
|
||||
) {
|
||||
return null
|
||||
}
|
||||
if (
|
||||
msg.replyTo._ === 'messageReplyStoryHeader' &&
|
||||
!(await fetchPeer(msg.replyTo.userId))
|
||||
) { return null }
|
||||
) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (msg._ !== 'messageService') {
|
||||
|
@ -791,7 +795,7 @@ async function _fetchChannelDifference(
|
|||
if (!_pts) _pts = fallbackPts
|
||||
|
||||
if (!_pts) {
|
||||
this._updsLog.warn(
|
||||
this._updsLog.debug(
|
||||
'fetchChannelDifference failed for channel %d: base pts not available',
|
||||
channelId,
|
||||
)
|
||||
|
|
|
@ -133,8 +133,7 @@ export class Conversation {
|
|||
|
||||
const pending = this.client['_pendingConversations']
|
||||
|
||||
const idx =
|
||||
pending[this._chatId].indexOf(this)
|
||||
const idx = pending[this._chatId].indexOf(this)
|
||||
|
||||
if (idx > -1) {
|
||||
// just in case
|
||||
|
@ -143,8 +142,7 @@ export class Conversation {
|
|||
if (!pending[this._chatId].length) {
|
||||
delete pending[this._chatId]
|
||||
}
|
||||
this.client['_hasConversations'] =
|
||||
Object.keys(pending).length > 0
|
||||
this.client['_hasConversations'] = Object.keys(pending).length > 0
|
||||
|
||||
// reset pending status
|
||||
this._queuedNewMessage.clear()
|
||||
|
@ -279,6 +277,7 @@ export class Conversation {
|
|||
|
||||
if (timeout !== null) {
|
||||
timer = setTimeout(() => {
|
||||
console.log('timed out')
|
||||
promise.reject(new tl.errors.TimeoutError())
|
||||
this._queuedNewMessage.removeBy((it) => it.promise === promise)
|
||||
}, timeout)
|
||||
|
@ -537,7 +536,9 @@ export class Conversation {
|
|||
it.promise.resolve(msg)
|
||||
delete this._pendingEditMessage[msg.id]
|
||||
}
|
||||
})().catch((e) => this.client['_emitError'](e))
|
||||
})().catch((e) => {
|
||||
this.client['_emitError'](e)
|
||||
})
|
||||
}
|
||||
|
||||
private _onHistoryRead(upd: HistoryReadUpdate) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { tl } from '@mtcute/tl'
|
|||
|
||||
import { TelegramClient } from '../../client'
|
||||
import { makeInspectable } from '../utils'
|
||||
import { FileDownloadParameters } from './utils'
|
||||
|
||||
/**
|
||||
* Information about file location.
|
||||
|
@ -50,48 +51,61 @@ export class FileLocation {
|
|||
* in chunks of a given size. Order of the chunks is guaranteed to be
|
||||
* consecutive.
|
||||
*
|
||||
* Shorthand for `client.downloadAsIterable({ location: this })`
|
||||
*
|
||||
* @param params Download parameters
|
||||
* @link TelegramClient.downloadAsIterable
|
||||
*/
|
||||
downloadIterable(): AsyncIterableIterator<Buffer> {
|
||||
return this.client.downloadAsIterable({ location: this })
|
||||
downloadIterable(
|
||||
params?: Partial<FileDownloadParameters>,
|
||||
): AsyncIterableIterator<Buffer> {
|
||||
return this.client.downloadAsIterable({
|
||||
...params,
|
||||
location: this,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file and return it as a Node readable stream,
|
||||
* streaming file contents.
|
||||
*
|
||||
* Shorthand for `client.downloadAsStream({ location: this })`
|
||||
*
|
||||
* @link TelegramClient.downloadAsStream
|
||||
*/
|
||||
downloadStream(): Readable {
|
||||
return this.client.downloadAsStream({ location: this })
|
||||
downloadStream(params?: Partial<FileDownloadParameters>): Readable {
|
||||
return this.client.downloadAsStream({
|
||||
...params,
|
||||
location: this,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file and return its contents as a Buffer.
|
||||
*
|
||||
* Shorthand for `client.downloadAsBuffer({ location: this })`
|
||||
*
|
||||
* @param params File download parameters
|
||||
* @link TelegramClient.downloadAsBuffer
|
||||
*/
|
||||
downloadBuffer(): Promise<Buffer> {
|
||||
return this.client.downloadAsBuffer({ location: this })
|
||||
downloadBuffer(params?: Partial<FileDownloadParameters>): Promise<Buffer> {
|
||||
return this.client.downloadAsBuffer({
|
||||
...params,
|
||||
location: this,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a remote file to a local file (only for NodeJS).
|
||||
* Promise will resolve once the download is complete.
|
||||
*
|
||||
* Shorthand for `client.downloadToFile(filename, { location: this })`
|
||||
*
|
||||
* @param filename Local file name
|
||||
* @param params File download parameters
|
||||
* @link TelegramClient.downloadToFile
|
||||
*/
|
||||
downloadToFile(filename: string): Promise<void> {
|
||||
return this.client.downloadToFile(filename, { location: this })
|
||||
downloadToFile(
|
||||
filename: string,
|
||||
params?: Partial<FileDownloadParameters>,
|
||||
): Promise<void> {
|
||||
return this.client.downloadToFile(filename, {
|
||||
...params,
|
||||
location: this,
|
||||
fileSize: this.fileSize,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ export interface FileDownloadParameters {
|
|||
offset?: number
|
||||
|
||||
/**
|
||||
* Number of chunks (!) of that given size that will be downloaded.
|
||||
* Number of bytes to be downloaded.
|
||||
* By default, downloads the entire file
|
||||
*/
|
||||
limit?: number
|
||||
|
|
|
@ -5,10 +5,9 @@ import { MtArgumentError } from '../types'
|
|||
* for upload/download operations.
|
||||
*/
|
||||
export function determinePartSize(fileSize: number): number {
|
||||
if (fileSize <= 104857600) return 128 // 100 MB
|
||||
if (fileSize <= 262078465) return 128 // 200 MB
|
||||
if (fileSize <= 786432000) return 256 // 750 MB
|
||||
if (fileSize <= 2097152000) return 512 // 2000 MB
|
||||
if (fileSize <= 4194304000) return 1024 // 4000 MB
|
||||
|
||||
throw new MtArgumentError('File is too large')
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ class NodeReadable extends Readable {
|
|||
return
|
||||
}
|
||||
if (this.push(res.value)) {
|
||||
return doRead()
|
||||
doRead()
|
||||
|
||||
return
|
||||
}
|
||||
this._reading = false
|
||||
this._reader.releaseLock()
|
||||
|
@ -49,7 +51,9 @@ class NodeReadable extends Readable {
|
|||
const promise = new Promise<void>((resolve) => {
|
||||
this._doneReading = resolve
|
||||
})
|
||||
promise.then(() => this._handleDestroy(err, callback))
|
||||
promise.then(() => {
|
||||
this._handleDestroy(err, callback)
|
||||
})
|
||||
} else {
|
||||
this._handleDestroy(err, callback)
|
||||
}
|
||||
|
@ -71,26 +75,6 @@ export function convertWebStreamToNodeReadable(
|
|||
return new NodeReadable(webStream, opts)
|
||||
}
|
||||
|
||||
export async function readStreamUntilEnd(stream: Readable): Promise<Buffer> {
|
||||
const chunks = []
|
||||
let length = 0
|
||||
|
||||
while (stream.readable) {
|
||||
const c = await stream.read()
|
||||
if (c === null) break
|
||||
|
||||
length += c.length
|
||||
|
||||
if (length > 2097152000) {
|
||||
throw new Error('File is too big')
|
||||
}
|
||||
|
||||
chunks.push(c)
|
||||
}
|
||||
|
||||
return Buffer.concat(chunks)
|
||||
}
|
||||
|
||||
export function bufferToStream(buf: Buffer): Readable {
|
||||
return new Readable({
|
||||
read() {
|
||||
|
@ -109,15 +93,17 @@ export async function readBytesFromStream(
|
|||
let res = stream.read(size)
|
||||
|
||||
if (!res) {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('readable', function handler() {
|
||||
res = stream.read(size)
|
||||
|
||||
if (res) {
|
||||
stream.off('readable', handler)
|
||||
stream.off('error', reject)
|
||||
resolve(res)
|
||||
}
|
||||
})
|
||||
stream.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { expect } from 'chai'
|
||||
import { describe, it } from 'mocha'
|
||||
import { Readable } from 'stream'
|
||||
|
||||
import { readStreamUntilEnd } from '../src/utils/stream-utils'
|
||||
|
||||
describe('readStreamUntilEnd', () => {
|
||||
it('should read stream until end', async () => {
|
||||
const stream = new Readable({
|
||||
read() {
|
||||
this.push(Buffer.from('aaeeff', 'hex'))
|
||||
this.push(Buffer.from('ff33ee', 'hex'))
|
||||
this.push(null)
|
||||
},
|
||||
})
|
||||
|
||||
expect((await readStreamUntilEnd(stream)).toString('hex')).eq(
|
||||
'aaeeffff33ee',
|
||||
)
|
||||
})
|
||||
})
|
|
@ -245,7 +245,7 @@ export class BaseTelegramClient extends EventEmitter {
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected _handleUpdate(update: tl.TypeUpdates): void {}
|
||||
|
||||
readonly log = new LogManager()
|
||||
readonly log = new LogManager('client')
|
||||
readonly network: NetworkManager
|
||||
|
||||
constructor(opts: BaseTelegramClientOptions) {
|
||||
|
@ -451,7 +451,6 @@ export class BaseTelegramClient extends EventEmitter {
|
|||
* @param factory New transport factory
|
||||
*/
|
||||
changeTransport(factory: TransportFactory): void {
|
||||
// todo
|
||||
this.network.changeTransport(factory)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
export { NetworkManagerExtraParams, RpcCallOptions } from './network-manager'
|
||||
export {
|
||||
ConnectionKind,
|
||||
NetworkManagerExtraParams,
|
||||
RpcCallOptions,
|
||||
} from './network-manager'
|
||||
export * from './reconnection'
|
||||
export * from './session-connection'
|
||||
export * from './transports'
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
TlWriterMap,
|
||||
} from '@mtcute/tl-runtime'
|
||||
|
||||
import { ControllablePromise,
|
||||
import {
|
||||
ControllablePromise,
|
||||
Deque,
|
||||
getRandomInt,
|
||||
ICryptoProvider,
|
||||
|
@ -118,9 +119,18 @@ export class MtprotoSession {
|
|||
pendingMessages = new LongMap<PendingMessage>()
|
||||
destroySessionIdToMsgId = new LongMap<Long>()
|
||||
|
||||
lastPingRtt = NaN
|
||||
lastPingTime = 0
|
||||
lastPingMsgId = Long.ZERO
|
||||
lastSessionCreatedUid = Long.ZERO
|
||||
|
||||
initConnectionCalled = false
|
||||
authorizationPending = false
|
||||
|
||||
next429Timeout = 1000
|
||||
current429Timeout?: NodeJS.Timeout
|
||||
next429ResetTimeout?: NodeJS.Timeout
|
||||
|
||||
constructor(
|
||||
readonly _crypto: ICryptoProvider,
|
||||
readonly log: Logger,
|
||||
|
@ -130,6 +140,15 @@ export class MtprotoSession {
|
|||
this.log.prefix = `[SESSION ${this._sessionId.toString(16)}] `
|
||||
}
|
||||
|
||||
get hasPendingMessages(): boolean {
|
||||
return Boolean(
|
||||
this.queuedRpc.length ||
|
||||
this.queuedAcks.length ||
|
||||
this.queuedStateReq.length ||
|
||||
this.queuedResendReq.length,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset session by resetting auth key(s) and session state
|
||||
*/
|
||||
|
@ -140,7 +159,9 @@ export class MtprotoSession {
|
|||
this._authKeyTempSecondary.reset()
|
||||
}
|
||||
|
||||
clearTimeout(this.current429Timeout)
|
||||
this.resetState()
|
||||
this.resetLastPing(true)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,11 +242,11 @@ export class MtprotoSession {
|
|||
}
|
||||
|
||||
getSeqNo(isContentRelated = true): number {
|
||||
let seqNo = this._seqNo * 2
|
||||
let seqNo = this._seqNo
|
||||
|
||||
if (isContentRelated) {
|
||||
seqNo += 1
|
||||
this._seqNo += 1
|
||||
this._seqNo += 2
|
||||
}
|
||||
|
||||
return seqNo
|
||||
|
@ -293,4 +314,43 @@ export class MtprotoSession {
|
|||
|
||||
return messageId
|
||||
}
|
||||
|
||||
onTransportFlood(callback: () => void) {
|
||||
if (this.current429Timeout) return // already waiting
|
||||
|
||||
// all active queries must be resent after a timeout
|
||||
this.resetLastPing(true)
|
||||
|
||||
const timeout = this.next429Timeout
|
||||
|
||||
this.next429Timeout = Math.min(this.next429Timeout * 2, 32000)
|
||||
clearTimeout(this.current429Timeout)
|
||||
clearTimeout(this.next429ResetTimeout)
|
||||
|
||||
this.current429Timeout = setTimeout(() => {
|
||||
this.current429Timeout = undefined
|
||||
callback()
|
||||
}, timeout)
|
||||
this.next429ResetTimeout = setTimeout(() => {
|
||||
this.next429ResetTimeout = undefined
|
||||
this.next429Timeout = 1000
|
||||
}, 60000)
|
||||
|
||||
this.log.debug(
|
||||
'transport flood, waiting for %d ms before proceeding',
|
||||
timeout,
|
||||
)
|
||||
|
||||
return Date.now() + timeout
|
||||
}
|
||||
|
||||
resetLastPing(withTime = false): void {
|
||||
if (withTime) this.lastPingTime = 0
|
||||
|
||||
if (!this.lastPingMsgId.isZero()) {
|
||||
this.pendingMessages.delete(this.lastPingMsgId)
|
||||
}
|
||||
|
||||
this.lastPingMsgId = Long.ZERO
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,43 +32,48 @@ export class MultiSessionConnection extends EventEmitter {
|
|||
|
||||
protected _connections: SessionConnection[] = []
|
||||
|
||||
setCount(count: number, doUpdate = true): void {
|
||||
setCount(count: number, connect = this.params.isMainConnection): void {
|
||||
this._count = count
|
||||
|
||||
if (doUpdate) this._updateConnections(true)
|
||||
this._updateConnections(connect)
|
||||
}
|
||||
|
||||
private _updateSessions(): void {
|
||||
// there are two cases
|
||||
// 1. this msc is main, in which case every connection should have its own session
|
||||
// 2. this msc is not main, in which case all connections should share the same session
|
||||
// if (!this.params.isMainConnection) {
|
||||
// // case 2
|
||||
// this._log.debug(
|
||||
// 'updating sessions count: %d -> 1',
|
||||
// this._sessions.length,
|
||||
// )
|
||||
//
|
||||
// if (this._sessions.length === 0) {
|
||||
// this._sessions.push(
|
||||
// new MtprotoSession(
|
||||
// this.params.crypto,
|
||||
// this._log.create('session'),
|
||||
// this.params.readerMap,
|
||||
// this.params.writerMap,
|
||||
// ),
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // shouldn't happen, but just in case
|
||||
// while (this._sessions.length > 1) {
|
||||
// this._sessions.pop()!.reset()
|
||||
// }
|
||||
//
|
||||
// return
|
||||
// }
|
||||
|
||||
this._log.debug(
|
||||
'updating sessions count: %d -> %d',
|
||||
this._sessions.length,
|
||||
this._count,
|
||||
)
|
||||
|
||||
// there are two cases
|
||||
// 1. this msc is main, in which case every connection should have its own session
|
||||
// 2. this msc is not main, in which case all connections should share the same session
|
||||
if (!this.params.isMainConnection) {
|
||||
// case 2
|
||||
if (this._sessions.length === 0) {
|
||||
this._sessions.push(
|
||||
new MtprotoSession(
|
||||
this.params.crypto,
|
||||
this._log.create('session'),
|
||||
this.params.readerMap,
|
||||
this.params.writerMap,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// shouldn't happen, but just in case
|
||||
while (this._sessions.length > 1) {
|
||||
this._sessions.pop()!.reset()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// case 1
|
||||
if (this._sessions.length === this._count) return
|
||||
|
||||
|
@ -99,7 +104,7 @@ export class MultiSessionConnection extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
private _updateConnections(active = false): void {
|
||||
private _updateConnections(connect = false): void {
|
||||
this._updateSessions()
|
||||
if (this._connections.length === this._count) return
|
||||
|
||||
|
@ -141,9 +146,8 @@ export class MultiSessionConnection extends EventEmitter {
|
|||
|
||||
// create new connections
|
||||
for (let i = this._connections.length; i < this._count; i++) {
|
||||
const session = this.params.isMainConnection ?
|
||||
this._sessions[i] :
|
||||
this._sessions[0]
|
||||
const session = this._sessions[i] // this.params.isMainConnection ? // :
|
||||
// this._sessions[0]
|
||||
const conn = new SessionConnection(
|
||||
{
|
||||
...this.params,
|
||||
|
@ -185,9 +189,14 @@ export class MultiSessionConnection extends EventEmitter {
|
|||
})
|
||||
conn.on('usable', () => this.emit('usable', i))
|
||||
conn.on('request-auth', () => this.emit('request-auth', i))
|
||||
conn.on('flood-done', () => {
|
||||
this._log.debug('received flood-done from connection %d', i)
|
||||
|
||||
this._connections.forEach((it) => it.flushWhenIdle())
|
||||
})
|
||||
|
||||
this._connections.push(conn)
|
||||
if (active) conn.connect()
|
||||
if (connect) conn.connect()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,31 +216,32 @@ export class MultiSessionConnection extends EventEmitter {
|
|||
stack?: string,
|
||||
timeout?: number,
|
||||
): Promise<tl.RpcCallReturn[T['_']]> {
|
||||
if (this.params.isMainConnection) {
|
||||
// find the least loaded connection
|
||||
let min = Infinity
|
||||
let minIdx = 0
|
||||
// if (this.params.isMainConnection) {
|
||||
// find the least loaded connection
|
||||
let min = Infinity
|
||||
let minIdx = 0
|
||||
|
||||
for (let i = 0; i < this._connections.length; i++) {
|
||||
const conn = this._connections[i]
|
||||
const total =
|
||||
conn._session.queuedRpc.length +
|
||||
conn._session.pendingMessages.size()
|
||||
for (let i = 0; i < this._connections.length; i++) {
|
||||
const conn = this._connections[i]
|
||||
const total =
|
||||
conn._session.queuedRpc.length +
|
||||
conn._session.pendingMessages.size()
|
||||
|
||||
if (total < min) {
|
||||
min = total
|
||||
minIdx = i
|
||||
}
|
||||
if (total < min) {
|
||||
min = total
|
||||
minIdx = i
|
||||
}
|
||||
|
||||
return this._connections[minIdx].sendRpc(request, stack, timeout)
|
||||
}
|
||||
|
||||
return this._connections[minIdx].sendRpc(request, stack, timeout)
|
||||
// }
|
||||
|
||||
// round-robin connections
|
||||
// since they all share the same session, it doesn't matter which one we use
|
||||
return this._connections[
|
||||
this._nextConnection++ % this._connections.length
|
||||
].sendRpc(request, stack, timeout)
|
||||
// the connection chosen here will only affect the first attempt at sending
|
||||
// return this._connections[
|
||||
// this._nextConnection++ % this._connections.length
|
||||
// ].sendRpc(request, stack, timeout)
|
||||
}
|
||||
|
||||
connect(): void {
|
||||
|
@ -308,4 +318,8 @@ export class MultiSessionConnection extends EventEmitter {
|
|||
changeTransport(factory: TransportFactory): void {
|
||||
this._connections.forEach((conn) => conn.changeTransport(factory))
|
||||
}
|
||||
|
||||
getPoolSize(): number {
|
||||
return this._connections.length
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,12 @@ import { tl } from '@mtcute/tl'
|
|||
import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||
|
||||
import { ITelegramStorage } from '../storage'
|
||||
import { ICryptoProvider, Logger, sleep } from '../utils'
|
||||
import {
|
||||
createControllablePromise,
|
||||
ICryptoProvider,
|
||||
Logger,
|
||||
sleep,
|
||||
} from '../utils'
|
||||
import { ConfigManager } from './config-manager'
|
||||
import { MultiSessionConnection } from './multi-session-connection'
|
||||
import { PersistentConnectionParams } from './persistent-connection'
|
||||
|
@ -18,6 +23,16 @@ import { defaultTransportFactory, TransportFactory } from './transports'
|
|||
|
||||
export type ConnectionKind = 'main' | 'upload' | 'download' | 'downloadSmall'
|
||||
|
||||
const CLIENT_ERRORS = {
|
||||
'303': 1,
|
||||
'400': 1,
|
||||
'401': 1,
|
||||
'403': 1,
|
||||
'404': 1,
|
||||
'406': 1,
|
||||
'420': 1,
|
||||
}
|
||||
|
||||
/**
|
||||
* Params passed into {@link NetworkManager} by {@link TelegramClient}.
|
||||
* This type is intended for internal usage only.
|
||||
|
@ -62,8 +77,9 @@ const defaultConnectionCountDelegate: ConnectionCountDelegate = (
|
|||
case 'upload':
|
||||
return isPremium || (dcId !== 2 && dcId !== 4) ? 8 : 4
|
||||
case 'download':
|
||||
return 8 // fixme isPremium ? 8 : 2
|
||||
case 'downloadSmall':
|
||||
return isPremium ? 8 : 2
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,13 +95,14 @@ export interface NetworkManagerExtraParams {
|
|||
usePfs?: boolean
|
||||
|
||||
/**
|
||||
* Connection count for each connection kind
|
||||
* Connection count for each connection kind.
|
||||
* The function should be pure to avoid unexpected behavior.
|
||||
*
|
||||
* Defaults to TDLib logic:
|
||||
* - main: handled internally, **cannot be changed here**
|
||||
* - upload: if premium or dc id is other than 2 or 4, then 8, otherwise 4
|
||||
* - download: if premium then 8, otherwise 2
|
||||
* - downloadSmall: if premium then 8, otherwise 2
|
||||
* - downloadSmall: 2
|
||||
*/
|
||||
connectionCount?: ConnectionCountDelegate
|
||||
|
||||
|
@ -174,7 +191,24 @@ export class DcConnectionManager {
|
|||
)
|
||||
|
||||
download = new MultiSessionConnection(
|
||||
this.__baseConnectionParams(),
|
||||
// this.__baseConnectionParams(),
|
||||
// fixme
|
||||
{
|
||||
...this.__baseConnectionParams(),
|
||||
dc: {
|
||||
_: 'dcOption',
|
||||
ipv6: false,
|
||||
mediaOnly: true,
|
||||
tcpoOnly: false,
|
||||
cdn: false,
|
||||
static: false,
|
||||
thisPortOnly: false,
|
||||
id: 2,
|
||||
ipAddress: '149.154.167.222',
|
||||
port: 443,
|
||||
secret: undefined,
|
||||
},
|
||||
},
|
||||
this.manager._connectionCount(
|
||||
'download',
|
||||
this._dc.id,
|
||||
|
@ -313,6 +347,10 @@ export class DcConnectionManager {
|
|||
connection.on('request-auth', () => {
|
||||
this.main.requestAuth()
|
||||
})
|
||||
|
||||
connection.on('error', (err, conn) => {
|
||||
this.manager.params._emitError(err, conn)
|
||||
})
|
||||
}
|
||||
|
||||
setIsPrimary(isPrimary: boolean): void {
|
||||
|
@ -328,6 +366,22 @@ export class DcConnectionManager {
|
|||
}
|
||||
}
|
||||
|
||||
setIsPremium(isPremium: boolean): void {
|
||||
this.upload.setCount(
|
||||
this.manager._connectionCount('upload', this._dc.id, isPremium),
|
||||
)
|
||||
this.download.setCount(
|
||||
this.manager._connectionCount('download', this._dc.id, isPremium),
|
||||
)
|
||||
this.downloadSmall.setCount(
|
||||
this.manager._connectionCount(
|
||||
'downloadSmall',
|
||||
this._dc.id,
|
||||
isPremium,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
async loadKeys(): Promise<boolean> {
|
||||
const permanent = await this.manager._storage.getAuthKeyFor(this.dcId)
|
||||
|
||||
|
@ -471,9 +525,9 @@ export class NetworkManager {
|
|||
Promise.resolve(this._storage.getSelf()).then((self) => {
|
||||
if (self?.isBot) {
|
||||
// bots may receive tmpSessions, which we should respect
|
||||
this.config
|
||||
.update(true)
|
||||
.catch((e) => this.params._emitError(e))
|
||||
this.config.update(true).catch((e) => {
|
||||
this.params._emitError(e)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -485,35 +539,54 @@ export class NetworkManager {
|
|||
// this._cleanupPrimaryConnection()
|
||||
// )
|
||||
|
||||
dc.main.on('error', (err, conn) => this.params._emitError(err, conn))
|
||||
|
||||
dc.loadKeys()
|
||||
.catch((e) => this.params._emitError(e))
|
||||
.then(() => dc.main.ensureConnected())
|
||||
.catch((e) => {
|
||||
this.params._emitError(e)
|
||||
})
|
||||
.then(() => {
|
||||
dc.main.ensureConnected()
|
||||
})
|
||||
}
|
||||
|
||||
private _dcCreationPromise: Record<number, Promise<void>> = {}
|
||||
async _getOtherDc(dcId: number): Promise<DcConnectionManager> {
|
||||
if (!this._dcConnections[dcId]) {
|
||||
if (dcId in this._dcCreationPromise) {
|
||||
this._log.debug('waiting for DC %d to be created', dcId)
|
||||
await this._dcCreationPromise[dcId]
|
||||
|
||||
return this._dcConnections[dcId]
|
||||
}
|
||||
|
||||
const promise = createControllablePromise<void>()
|
||||
this._dcCreationPromise[dcId] = promise
|
||||
|
||||
this._log.debug('creating new DC %d', dcId)
|
||||
|
||||
const dcOption = await this.config.findOption({
|
||||
dcId,
|
||||
allowIpv6: this.params.useIpv6,
|
||||
preferIpv6: this.params.useIpv6,
|
||||
allowMedia: false,
|
||||
cdn: false,
|
||||
})
|
||||
try {
|
||||
const dcOption = await this.config.findOption({
|
||||
dcId,
|
||||
allowIpv6: this.params.useIpv6,
|
||||
preferIpv6: this.params.useIpv6,
|
||||
allowMedia: true,
|
||||
preferMedia: true,
|
||||
cdn: false,
|
||||
})
|
||||
|
||||
if (!dcOption) {
|
||||
throw new Error(`Could not find DC ${dcId}`)
|
||||
if (!dcOption) {
|
||||
throw new Error(`Could not find DC ${dcId}`)
|
||||
}
|
||||
const dc = new DcConnectionManager(this, dcId, dcOption)
|
||||
|
||||
if (!(await dc.loadKeys())) {
|
||||
dc.main.requestAuth()
|
||||
}
|
||||
|
||||
this._dcConnections[dcId] = dc
|
||||
promise.resolve()
|
||||
} catch (e) {
|
||||
promise.reject(e)
|
||||
}
|
||||
const dc = new DcConnectionManager(this, dcId, dcOption)
|
||||
|
||||
if (!(await dc.loadKeys())) {
|
||||
dc.main.requestAuth()
|
||||
}
|
||||
|
||||
this._dcConnections[dcId] = dc
|
||||
}
|
||||
|
||||
return this._dcConnections[dcId]
|
||||
|
@ -532,7 +605,7 @@ export class NetworkManager {
|
|||
|
||||
const dc = new DcConnectionManager(this, defaultDc.id, defaultDc)
|
||||
this._dcConnections[defaultDc.id] = dc
|
||||
await this._switchPrimaryDc(dc)
|
||||
this._switchPrimaryDc(dc)
|
||||
}
|
||||
|
||||
private async _exportAuthTo(manager: DcConnectionManager): Promise<void> {
|
||||
|
@ -575,16 +648,28 @@ export class NetworkManager {
|
|||
}
|
||||
}
|
||||
|
||||
setIsPremium(isPremium: boolean): void {
|
||||
this._log.debug('setting isPremium to %s', isPremium)
|
||||
this.params.isPremium = isPremium
|
||||
Object.values(this._dcConnections).forEach((dc) => {
|
||||
dc.setIsPremium(isPremium)
|
||||
})
|
||||
}
|
||||
|
||||
async notifyLoggedIn(auth: tl.auth.TypeAuthorization): Promise<void> {
|
||||
if (
|
||||
auth._ === 'auth.authorizationSignUpRequired' ||
|
||||
auth.user._ === 'userEmpty'
|
||||
) { return }
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (auth.tmpSessions) {
|
||||
this._primaryDc?.main.setCount(auth.tmpSessions)
|
||||
}
|
||||
|
||||
this.setIsPremium(auth.user.premium!)
|
||||
|
||||
await this.exportAuth()
|
||||
}
|
||||
|
||||
|
@ -689,8 +774,12 @@ export class NetworkManager {
|
|||
} catch (e: any) {
|
||||
lastError = e
|
||||
|
||||
if (e instanceof tl.errors.InternalError) {
|
||||
this._log.warn('Telegram is having internal issues: %s', e)
|
||||
if (e.code && !(e.code in CLIENT_ERRORS)) {
|
||||
this._log.warn(
|
||||
'Telegram is having internal issues: %d %s, retrying',
|
||||
e.code,
|
||||
e.message,
|
||||
)
|
||||
|
||||
if (e.message === 'WORKER_BUSY_TOO_LONG_RETRY') {
|
||||
// according to tdlib, "it is dangerous to resend query without timeout, so use 1"
|
||||
|
@ -769,6 +858,32 @@ export class NetworkManager {
|
|||
})
|
||||
}
|
||||
|
||||
getPoolSize(kind: ConnectionKind, dcId?: number) {
|
||||
const dc = dcId ? this._dcConnections[dcId] : this._primaryDc
|
||||
|
||||
if (!dc) {
|
||||
if (!this._primaryDc) {
|
||||
throw new Error('Not connected to any DC')
|
||||
}
|
||||
|
||||
// guess based on the provided delegate. it is most likely correct,
|
||||
// but we should give actual values if possible
|
||||
return this._connectionCount(
|
||||
kind,
|
||||
dcId ?? this._primaryDc.dcId,
|
||||
this.params.isPremium,
|
||||
)
|
||||
}
|
||||
|
||||
return dc[kind].getPoolSize()
|
||||
}
|
||||
|
||||
getPrimaryDcId() {
|
||||
if (!this._primaryDc) throw new Error('Not connected to any DC')
|
||||
|
||||
return this._primaryDc.dcId
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
for (const dc of Object.values(this._dcConnections)) {
|
||||
dc.main.destroy()
|
||||
|
|
|
@ -86,11 +86,29 @@ export abstract class PersistentConnection extends EventEmitter {
|
|||
|
||||
onTransportReady(): void {
|
||||
// transport ready does not mean actual mtproto is ready
|
||||
|
||||
if (this._sendOnceConnected.length) {
|
||||
this._transport.send(Buffer.concat(this._sendOnceConnected))
|
||||
const sendNext = () => {
|
||||
if (!this._sendOnceConnected.length) {
|
||||
this.onConnected()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const data = this._sendOnceConnected.shift()!
|
||||
this._transport
|
||||
.send(data)
|
||||
.then(sendNext)
|
||||
.catch((err) => {
|
||||
this.log.error('error sending queued data: %s', err)
|
||||
this._sendOnceConnected.unshift(data)
|
||||
})
|
||||
}
|
||||
|
||||
sendNext()
|
||||
|
||||
return
|
||||
}
|
||||
this._sendOnceConnected = []
|
||||
|
||||
this.onConnected()
|
||||
}
|
||||
|
||||
|
@ -125,7 +143,12 @@ export abstract class PersistentConnection extends EventEmitter {
|
|||
this._consequentFails,
|
||||
this._previousWait,
|
||||
)
|
||||
if (wait === false) return this.destroy()
|
||||
|
||||
if (wait === false) {
|
||||
this.destroy()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.emit('wait', wait)
|
||||
|
||||
|
|
|
@ -75,19 +75,13 @@ export class SessionConnection extends PersistentConnection {
|
|||
NodeJS.Timeout
|
||||
][] = []
|
||||
|
||||
private _next429Timeout = 1000
|
||||
private _current429Timeout?: NodeJS.Timeout
|
||||
|
||||
private _lastPingRtt = NaN
|
||||
private _lastPingTime = 0
|
||||
private _lastPingMsgId = Long.ZERO
|
||||
private _lastSessionCreatedUid = Long.ZERO
|
||||
|
||||
private _usePfs = this.params.usePfs ?? false
|
||||
private _isPfsBindingPending = false
|
||||
private _isPfsBindingPendingInBackground = false
|
||||
private _pfsUpdateTimeout?: NodeJS.Timeout
|
||||
|
||||
private _inactivityPendingFlush = false
|
||||
|
||||
private _readerMap: TlReaderMap
|
||||
private _writerMap: TlWriterMap
|
||||
|
||||
|
@ -149,9 +143,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
|
||||
reset(forever = false): void {
|
||||
this._session.initConnectionCalled = false
|
||||
this._resetLastPing(true)
|
||||
this._flushTimer.reset()
|
||||
clearTimeout(this._current429Timeout!)
|
||||
|
||||
if (forever) {
|
||||
this.removeAllListeners()
|
||||
|
@ -242,26 +234,15 @@ export class SessionConnection extends PersistentConnection {
|
|||
this._onAllFailed(`transport error ${error.code}`)
|
||||
|
||||
if (error.code === 429) {
|
||||
// all active queries must be resent
|
||||
const timeout = this._next429Timeout
|
||||
|
||||
this._next429Timeout = Math.min(this._next429Timeout * 2, 16000)
|
||||
clearTimeout(this._current429Timeout!)
|
||||
this._current429Timeout = setTimeout(() => {
|
||||
this._current429Timeout = undefined
|
||||
this._flushTimer.emitNow()
|
||||
}, timeout)
|
||||
|
||||
this.log.debug(
|
||||
'transport flood, waiting for %d ms before proceeding',
|
||||
timeout,
|
||||
this._session.onTransportFlood(
|
||||
this.emit.bind(this, 'flood-done'),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('api-error', error)
|
||||
this.emit('error', error)
|
||||
}
|
||||
|
||||
protected onConnectionUsable() {
|
||||
|
@ -622,9 +603,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
// rpc_result
|
||||
message.uint()
|
||||
|
||||
this._sendAck(messageId)
|
||||
|
||||
return this._onRpcResult(message)
|
||||
return this._onRpcResult(messageId, message)
|
||||
}
|
||||
|
||||
// we are safe.. i guess
|
||||
|
@ -648,7 +627,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
}
|
||||
const message = message_ as mtp.TlObject
|
||||
|
||||
this.log.verbose('received %s (msg_id: %l)', message._, messageId)
|
||||
this.log.debug('received %s (msg_id: %l)', message._, messageId)
|
||||
this._session.recentIncomingMsgIds.add(messageId)
|
||||
|
||||
switch (message._) {
|
||||
|
@ -738,7 +717,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
}
|
||||
}
|
||||
|
||||
private _onRpcResult(message: TlBinaryReader): void {
|
||||
private _onRpcResult(messageId: Long, message: TlBinaryReader): void {
|
||||
if (this._usable && this.params.inactivityTimeout) {
|
||||
this._rescheduleInactivity()
|
||||
}
|
||||
|
@ -790,9 +769,12 @@ export class SessionConnection extends PersistentConnection {
|
|||
return
|
||||
}
|
||||
|
||||
this._sendAck(messageId)
|
||||
|
||||
// special case for auth key binding
|
||||
if (msg._ !== 'rpc') {
|
||||
if (msg._ === 'bind') {
|
||||
this._sendAck(messageId)
|
||||
msg.promise.resolve(message.object())
|
||||
|
||||
return
|
||||
|
@ -902,7 +884,9 @@ export class SessionConnection extends PersistentConnection {
|
|||
const msg = this._session.pendingMessages.get(msgId)
|
||||
|
||||
if (!msg) {
|
||||
this.log.warn('received ack for unknown message %l', msgId)
|
||||
if (!this._session.recentOutgoingMsgIds.has(msgId)) {
|
||||
this.log.warn('received ack for unknown message %l', msgId)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -969,7 +953,6 @@ export class SessionConnection extends PersistentConnection {
|
|||
// e.g. when server returns 429
|
||||
|
||||
// most service messages can be omitted as stale
|
||||
this._resetLastPing(true)
|
||||
|
||||
for (const msgId of this._session.pendingMessages.keys()) {
|
||||
const info = this._session.pendingMessages.get(msgId)!
|
||||
|
@ -1025,7 +1008,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
reason,
|
||||
)
|
||||
// restart ping
|
||||
this._resetLastPing(true)
|
||||
this._session.resetLastPing(true)
|
||||
break
|
||||
|
||||
case 'rpc': {
|
||||
|
@ -1093,16 +1076,6 @@ export class SessionConnection extends PersistentConnection {
|
|||
this._session.pendingMessages.delete(msgId)
|
||||
}
|
||||
|
||||
private _resetLastPing(withTime = false): void {
|
||||
if (withTime) this._lastPingTime = 0
|
||||
|
||||
if (!this._lastPingMsgId.isZero()) {
|
||||
this._session.pendingMessages.delete(this._lastPingMsgId)
|
||||
}
|
||||
|
||||
this._lastPingMsgId = Long.ZERO
|
||||
}
|
||||
|
||||
private _registerOutgoingMsgId(msgId: Long): Long {
|
||||
this._session.recentOutgoingMsgIds.add(msgId)
|
||||
|
||||
|
@ -1142,8 +1115,8 @@ export class SessionConnection extends PersistentConnection {
|
|||
)
|
||||
}
|
||||
|
||||
const rtt = Date.now() - this._lastPingTime
|
||||
this._lastPingRtt = rtt
|
||||
const rtt = Date.now() - this._session.lastPingTime
|
||||
this._session.lastPingRtt = rtt
|
||||
|
||||
if (info.containerId.neq(msgId)) {
|
||||
this._onMessageAcked(info.containerId)
|
||||
|
@ -1155,7 +1128,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
pingId,
|
||||
rtt,
|
||||
)
|
||||
this._resetLastPing()
|
||||
this._session.resetLastPing()
|
||||
}
|
||||
|
||||
private _onBadServerSalt(msg: mtp.RawMt_bad_server_salt): void {
|
||||
|
@ -1211,7 +1184,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
serverSalt,
|
||||
uniqueId,
|
||||
}: mtp.RawMt_new_session_created): void {
|
||||
if (uniqueId.eq(this._lastSessionCreatedUid)) {
|
||||
if (uniqueId.eq(this._session.lastSessionCreatedUid)) {
|
||||
this.log.debug(
|
||||
'received new_session_created with the same uid = %l, ignoring',
|
||||
uniqueId,
|
||||
|
@ -1221,7 +1194,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
}
|
||||
|
||||
if (
|
||||
!this._lastSessionCreatedUid.isZero() &&
|
||||
!this._session.lastSessionCreatedUid.isZero() &&
|
||||
!this.params.disableUpdates
|
||||
) {
|
||||
// force the client to fetch missed updates
|
||||
|
@ -1277,10 +1250,12 @@ export class SessionConnection extends PersistentConnection {
|
|||
const info = this._session.pendingMessages.get(msgId)
|
||||
|
||||
if (!info) {
|
||||
this.log.info(
|
||||
'received message info about unknown message %l',
|
||||
msgId,
|
||||
)
|
||||
if (!this._session.recentOutgoingMsgIds.has(msgId)) {
|
||||
this.log.warn(
|
||||
'received message info about unknown message %l',
|
||||
msgId,
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -1417,7 +1392,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
this._session.queuedAcks.push(msgId)
|
||||
|
||||
if (this._session.queuedAcks.length >= 100) {
|
||||
this._flushTimer.emitNow()
|
||||
this._flushTimer.emitWhenIdle()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1586,49 +1561,36 @@ export class SessionConnection extends PersistentConnection {
|
|||
}
|
||||
}
|
||||
|
||||
private get _hasPendingServiceMessages(): boolean {
|
||||
return Boolean(
|
||||
this._session.queuedRpc.length ||
|
||||
this._session.queuedAcks.length ||
|
||||
this._session.queuedStateReq.length ||
|
||||
this._session.queuedResendReq.length,
|
||||
)
|
||||
}
|
||||
|
||||
protected _onInactivityTimeout() {
|
||||
// we should send all pending acks and other service messages
|
||||
// before dropping the connection
|
||||
|
||||
if (!this._hasPendingServiceMessages) {
|
||||
if (!this._session.hasPendingMessages) {
|
||||
this.log.debug('no pending service messages, closing connection')
|
||||
super._onInactivityTimeout()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this._flush(() => {
|
||||
if (this._hasPendingServiceMessages) {
|
||||
// the callback will be called again once all pending messages are sent
|
||||
return
|
||||
}
|
||||
|
||||
this.log.debug('pending service messages sent, closing connection')
|
||||
this._flushTimer.reset()
|
||||
super._onInactivityTimeout()
|
||||
})
|
||||
this._inactivityPendingFlush = true
|
||||
this._flush()
|
||||
}
|
||||
|
||||
private _flush(callback?: () => void): void {
|
||||
flushWhenIdle(): void {
|
||||
this._flushTimer.emitWhenIdle()
|
||||
}
|
||||
|
||||
private _flush(): void {
|
||||
if (
|
||||
!this._session._authKey.ready ||
|
||||
this._isPfsBindingPending ||
|
||||
this._current429Timeout
|
||||
this._session.current429Timeout
|
||||
) {
|
||||
this.log.debug(
|
||||
'skipping flush, connection is not usable (auth key ready = %b, pfs binding pending = %b, 429 timeout = %b)',
|
||||
this._session._authKey.ready,
|
||||
this._isPfsBindingPending,
|
||||
Boolean(this._current429Timeout),
|
||||
Boolean(this._session.current429Timeout),
|
||||
)
|
||||
|
||||
// it will be flushed once connection is usable
|
||||
|
@ -1636,7 +1598,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
}
|
||||
|
||||
try {
|
||||
this._doFlush(callback)
|
||||
this._doFlush()
|
||||
} catch (e: any) {
|
||||
this.log.error('flush error: %s', e.stack)
|
||||
// should not happen unless there's a bug in the code
|
||||
|
@ -1645,19 +1607,22 @@ export class SessionConnection extends PersistentConnection {
|
|||
// schedule next flush
|
||||
// if there are more queued requests, flush immediately
|
||||
// (they likely didn't fit into one message)
|
||||
if (
|
||||
this._session.queuedRpc.length ||
|
||||
this._session.queuedAcks.length ||
|
||||
this._session.queuedStateReq.length ||
|
||||
this._session.queuedResendReq.length
|
||||
) {
|
||||
this._flush(callback)
|
||||
if (this._session.hasPendingMessages) {
|
||||
// we schedule it on the next tick, so we can load-balance
|
||||
// between multiple connections using the same session
|
||||
this._flushTimer.emitWhenIdle()
|
||||
} else if (this._inactivityPendingFlush) {
|
||||
this.log.debug('pending messages sent, closing connection')
|
||||
this._flushTimer.reset()
|
||||
this._inactivityPendingFlush = false
|
||||
|
||||
super._onInactivityTimeout()
|
||||
} else {
|
||||
this._flushTimer.emitBefore(this._lastPingTime + 60000)
|
||||
this._flushTimer.emitBefore(this._session.lastPingTime + 60000)
|
||||
}
|
||||
}
|
||||
|
||||
private _doFlush(callback?: () => void): void {
|
||||
private _doFlush(): void {
|
||||
this.log.debug(
|
||||
'flushing send queue. queued rpc: %d',
|
||||
this._session.queuedRpc.length,
|
||||
|
@ -1716,13 +1681,15 @@ export class SessionConnection extends PersistentConnection {
|
|||
|
||||
const getStateTime = now + 1500
|
||||
|
||||
if (now - this._lastPingTime > 60000) {
|
||||
if (!this._lastPingMsgId.isZero()) {
|
||||
if (now - this._session.lastPingTime > 60000) {
|
||||
if (!this._session.lastPingMsgId.isZero()) {
|
||||
this.log.warn(
|
||||
"didn't receive pong for previous ping (msg_id = %l)",
|
||||
this._lastPingMsgId,
|
||||
this._session.lastPingMsgId,
|
||||
)
|
||||
this._session.pendingMessages.delete(
|
||||
this._session.lastPingMsgId,
|
||||
)
|
||||
this._session.pendingMessages.delete(this._lastPingMsgId)
|
||||
}
|
||||
|
||||
pingId = randomLong()
|
||||
|
@ -1731,7 +1698,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
pingId,
|
||||
}
|
||||
|
||||
this._lastPingTime = Date.now()
|
||||
this._session.lastPingTime = Date.now()
|
||||
|
||||
pingRequest = TlBinaryWriter.serializeObject(this._writerMap, obj)
|
||||
containerSize += pingRequest.length + 16
|
||||
|
@ -1836,13 +1803,17 @@ export class SessionConnection extends PersistentConnection {
|
|||
// if message was already assigned a msg_id,
|
||||
// we must wrap it in a container with a newer msg_id
|
||||
if (msg.msgId) forceContainer = true
|
||||
|
||||
// having >1 upload.getFile within a container seems to cause flood_wait errors
|
||||
// also a crutch for load-balancing
|
||||
if (msg.method === 'upload.getFile') break
|
||||
}
|
||||
|
||||
packetSize += containerSize
|
||||
messageCount += containerMessageCount + rpcToSend.length
|
||||
|
||||
if (!messageCount) {
|
||||
this.log.debug('flush failed: nothing to flush')
|
||||
this.log.debug('flush did not happen: nothing to flush')
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -1875,7 +1846,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
pingMsgId = this._registerOutgoingMsgId(
|
||||
this._session.writeMessage(writer, pingRequest),
|
||||
)
|
||||
this._lastPingMsgId = pingMsgId
|
||||
this._session.lastPingMsgId = pingMsgId
|
||||
const pingPending: PendingMessage = {
|
||||
_: 'ping',
|
||||
pingId: pingId!,
|
||||
|
@ -2065,7 +2036,6 @@ export class SessionConnection extends PersistentConnection {
|
|||
this._session
|
||||
.encryptMessage(result)
|
||||
.then((enc) => this.send(enc))
|
||||
.then(callback)
|
||||
.catch((err) => {
|
||||
this.log.error(
|
||||
'error while sending pending messages (root msg_id = %l): %s',
|
||||
|
|
|
@ -48,7 +48,9 @@ export abstract class BaseTcpTransport
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
connect(dc: tl.RawDcOption, testMode: boolean): void {
|
||||
if (this._state !== TransportState.Idle) { throw new Error('Transport is not IDLE') }
|
||||
if (this._state !== TransportState.Idle) {
|
||||
throw new Error('Transport is not IDLE')
|
||||
}
|
||||
|
||||
if (!this.packetCodecInitialized) {
|
||||
this._packetCodec.setup?.(this._crypto, this.log)
|
||||
|
@ -69,7 +71,9 @@ export abstract class BaseTcpTransport
|
|||
this.handleConnect.bind(this),
|
||||
)
|
||||
|
||||
this._socket.on('data', (data) => this._packetCodec.feed(data))
|
||||
this._socket.on('data', (data) => {
|
||||
this._packetCodec.feed(data)
|
||||
})
|
||||
this._socket.on('error', this.handleError.bind(this))
|
||||
this._socket.on('close', this.close.bind(this))
|
||||
}
|
||||
|
@ -87,7 +91,7 @@ export abstract class BaseTcpTransport
|
|||
this._packetCodec.reset()
|
||||
}
|
||||
|
||||
async handleError(error: Error): Promise<void> {
|
||||
handleError(error: Error): void {
|
||||
this.log.error('error: %s', error.stack)
|
||||
this.emit('error', error)
|
||||
}
|
||||
|
@ -99,7 +103,11 @@ export abstract class BaseTcpTransport
|
|||
if (initialMessage.length) {
|
||||
this._socket!.write(initialMessage, (err) => {
|
||||
if (err) {
|
||||
this.emit('error', err)
|
||||
this.log.error(
|
||||
'failed to write initial message: %s',
|
||||
err.stack,
|
||||
)
|
||||
this.emit('error')
|
||||
this.close()
|
||||
} else {
|
||||
this._state = TransportState.Ready
|
||||
|
@ -113,12 +121,20 @@ export abstract class BaseTcpTransport
|
|||
}
|
||||
|
||||
async send(bytes: Buffer): Promise<void> {
|
||||
if (this._state !== TransportState.Ready) { throw new Error('Transport is not READY') }
|
||||
if (this._state !== TransportState.Ready) {
|
||||
throw new Error('Transport is not READY')
|
||||
}
|
||||
|
||||
const framed = await this._packetCodec.encode(bytes)
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
this._socket!.write(framed, (err) => (err ? rej(err) : res()))
|
||||
return new Promise((resolve, reject) => {
|
||||
this._socket!.write(framed, (error) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,6 @@ export interface IEncryptionScheme {
|
|||
decrypt(data: Buffer): MaybeAsync<Buffer>
|
||||
}
|
||||
|
||||
export interface IHashMethod {
|
||||
update(data: Buffer): MaybeAsync<void>
|
||||
|
||||
digest(): MaybeAsync<Buffer>
|
||||
}
|
||||
|
||||
export interface ICryptoProvider {
|
||||
initialize?(): MaybeAsync<void>
|
||||
|
||||
|
@ -38,8 +32,6 @@ export interface ICryptoProvider {
|
|||
|
||||
createAesEcb(key: Buffer): IEncryptionScheme
|
||||
|
||||
createMd5(): IHashMethod
|
||||
|
||||
factorizePQ(pq: Buffer): MaybeAsync<[Buffer, Buffer]>
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import {
|
|||
BaseCryptoProvider,
|
||||
ICryptoProvider,
|
||||
IEncryptionScheme,
|
||||
IHashMethod,
|
||||
} from './abstract'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -108,15 +107,6 @@ export class ForgeCryptoProvider
|
|||
)
|
||||
}
|
||||
|
||||
createMd5(): IHashMethod {
|
||||
const hash = forge.md.md5.create()
|
||||
|
||||
return {
|
||||
update: (data) => hash.update(data.toString('binary')),
|
||||
digest: () => Buffer.from(hash.digest().data, 'binary'),
|
||||
}
|
||||
}
|
||||
|
||||
hmacSha256(data: Buffer, key: Buffer): MaybeAsync<Buffer> {
|
||||
const hmac = forge.hmac.create()
|
||||
hmac.start('sha256', key.toString('binary'))
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
BaseCryptoProvider,
|
||||
ICryptoProvider,
|
||||
IEncryptionScheme,
|
||||
IHashMethod,
|
||||
} from './abstract'
|
||||
|
||||
export class NodeCryptoProvider
|
||||
|
@ -83,10 +82,6 @@ export class NodeCryptoProvider
|
|||
return createHash('sha256').update(data).digest()
|
||||
}
|
||||
|
||||
createMd5(): IHashMethod {
|
||||
return createHash('md5') as unknown as IHashMethod
|
||||
}
|
||||
|
||||
hmacSha256(data: Buffer, key: Buffer): MaybeAsync<Buffer> {
|
||||
return createHmac('sha256', key).update(data).digest()
|
||||
}
|
||||
|
|
|
@ -84,9 +84,16 @@ export class Logger {
|
|||
|
||||
if (m === '%j') {
|
||||
return JSON.stringify(val, (k, v) => {
|
||||
if (typeof v === 'object' && v.type === 'Buffer' && Array.isArray(v.data)) {
|
||||
if (
|
||||
typeof v === 'object' &&
|
||||
v.type === 'Buffer' &&
|
||||
Array.isArray(v.data)
|
||||
) {
|
||||
let str = Buffer.from(v.data).toString('base64')
|
||||
if (str.length > 300) str = str.slice(0, 300) + '...'
|
||||
|
||||
if (str.length > 300) {
|
||||
str = str.slice(0, 300) + '...'
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
@ -143,10 +150,10 @@ export class LogManager extends Logger {
|
|||
static DEBUG = 4
|
||||
static VERBOSE = 5
|
||||
|
||||
constructor() {
|
||||
constructor(tag = 'base') {
|
||||
// workaround because we cant pass this to super
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
super(null as any, 'base')
|
||||
super(null as any, tag)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
;(this as any).mgr = this
|
||||
}
|
||||
|
|
|
@ -164,31 +164,6 @@ export function testCryptoProvider(c: ICryptoProvider): void {
|
|||
'99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b',
|
||||
)
|
||||
})
|
||||
|
||||
it('should calculate md5', async () => {
|
||||
const test = async (...parts: string[]): Promise<Buffer> => {
|
||||
const md5 = c.createMd5()
|
||||
for (const p of parts) await md5.update(Buffer.from(p, 'hex'))
|
||||
|
||||
return md5.digest()
|
||||
}
|
||||
|
||||
expect((await test()).toString('hex')).eq(
|
||||
'd41d8cd98f00b204e9800998ecf8427e',
|
||||
)
|
||||
expect((await test('aaeeff')).toString('hex')).eq(
|
||||
'9c20ec5e212b4fcfa4666a8b165c6d5d',
|
||||
)
|
||||
expect((await test('aaeeffffeeaa')).toString('hex')).eq(
|
||||
'cf216071768a7b610d079e5eb7b68b74',
|
||||
)
|
||||
expect((await test('aaeeff', 'ffeeaa')).toString('hex')).eq(
|
||||
'cf216071768a7b610d079e5eb7b68b74',
|
||||
)
|
||||
expect((await test('aa', 'ee', 'ff', 'ffeeaa')).toString('hex')).eq(
|
||||
'cf216071768a7b610d079e5eb7b68b74',
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
describe('NodeCryptoProvider', () => {
|
||||
|
|
533
pnpm-lock.yaml
533
pnpm-lock.yaml
|
@ -34,11 +34,11 @@ importers:
|
|||
specifier: 8.5.4
|
||||
version: 8.5.4
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: 5.59.8
|
||||
version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.42.0)(typescript@5.0.4)
|
||||
specifier: 6.4.0
|
||||
version: 6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: 5.59.8
|
||||
version: 5.59.8(eslint@8.42.0)(typescript@5.0.4)
|
||||
specifier: 6.4.0
|
||||
version: 6.4.0(eslint@8.47.0)(typescript@5.0.4)
|
||||
chai:
|
||||
specifier: 4.3.7
|
||||
version: 4.3.7
|
||||
|
@ -46,23 +46,23 @@ importers:
|
|||
specifier: 3.2.0
|
||||
version: 3.2.0
|
||||
eslint:
|
||||
specifier: 8.42.0
|
||||
version: 8.42.0
|
||||
specifier: 8.47.0
|
||||
version: 8.47.0
|
||||
eslint-config-prettier:
|
||||
specifier: 8.8.0
|
||||
version: 8.8.0(eslint@8.42.0)
|
||||
version: 8.8.0(eslint@8.47.0)
|
||||
eslint-import-resolver-typescript:
|
||||
specifier: 3.5.5
|
||||
version: 3.5.5(@typescript-eslint/parser@5.59.8)(eslint-plugin-import@2.27.5)(eslint@8.42.0)
|
||||
specifier: 3.6.0
|
||||
version: 3.6.0(@typescript-eslint/parser@6.4.0)(eslint-plugin-import@2.28.0)(eslint@8.47.0)
|
||||
eslint-plugin-ascii:
|
||||
specifier: 1.0.0
|
||||
version: 1.0.0
|
||||
eslint-plugin-import:
|
||||
specifier: 2.27.5
|
||||
version: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0)
|
||||
specifier: 2.28.0
|
||||
version: 2.28.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0)
|
||||
eslint-plugin-simple-import-sort:
|
||||
specifier: 10.0.0
|
||||
version: 10.0.0(eslint@8.42.0)
|
||||
version: 10.0.0(eslint@8.47.0)
|
||||
glob:
|
||||
specifier: 10.2.6
|
||||
version: 10.2.6
|
||||
|
@ -351,6 +351,11 @@ importers:
|
|||
|
||||
packages:
|
||||
|
||||
/@aashutoshrathi/word-wrap@1.2.6:
|
||||
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/@ampproject/remapping@2.2.0:
|
||||
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
@ -723,14 +728,14 @@ packages:
|
|||
'@jridgewell/trace-mapping': 0.3.9
|
||||
dev: true
|
||||
|
||||
/@eslint-community/eslint-utils@4.4.0(eslint@8.42.0):
|
||||
/@eslint-community/eslint-utils@4.4.0(eslint@8.47.0):
|
||||
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
dependencies:
|
||||
eslint: 8.42.0
|
||||
eslint-visitor-keys: 3.4.1
|
||||
eslint: 8.47.0
|
||||
eslint-visitor-keys: 3.4.3
|
||||
dev: true
|
||||
|
||||
/@eslint-community/regexpp@4.5.1:
|
||||
|
@ -738,13 +743,18 @@ packages:
|
|||
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/@eslint/eslintrc@2.0.3:
|
||||
resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==}
|
||||
/@eslint-community/regexpp@4.6.2:
|
||||
resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==}
|
||||
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/@eslint/eslintrc@2.1.2:
|
||||
resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
espree: 9.5.2
|
||||
espree: 9.6.1
|
||||
globals: 13.20.0
|
||||
ignore: 5.2.0
|
||||
import-fresh: 3.3.0
|
||||
|
@ -755,8 +765,8 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@eslint/js@8.42.0:
|
||||
resolution: {integrity: sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==}
|
||||
/@eslint/js@8.47.0:
|
||||
resolution: {integrity: sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
|
@ -886,18 +896,6 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/@pkgr/utils@2.4.1:
|
||||
resolution: {integrity: sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
dependencies:
|
||||
cross-spawn: 7.0.3
|
||||
fast-glob: 3.2.12
|
||||
is-glob: 4.0.3
|
||||
open: 9.1.0
|
||||
picocolors: 1.0.0
|
||||
tslib: 2.5.3
|
||||
dev: true
|
||||
|
||||
/@tokenizer/token@0.3.0:
|
||||
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
|
||||
dev: false
|
||||
|
@ -941,8 +939,8 @@ packages:
|
|||
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
|
||||
dev: true
|
||||
|
||||
/@types/json-schema@7.0.11:
|
||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||
/@types/json-schema@7.0.12:
|
||||
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
|
||||
dev: true
|
||||
|
||||
/@types/json5@0.0.29:
|
||||
|
@ -984,133 +982,134 @@ packages:
|
|||
'@types/node': 18.16.0
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/eslint-plugin@5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.42.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
/@typescript-eslint/eslint-plugin@6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': ^5.0.0
|
||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
'@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
|
||||
eslint: ^7.0.0 || ^8.0.0
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.5.1
|
||||
'@typescript-eslint/parser': 5.59.8(eslint@8.42.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/scope-manager': 5.59.8
|
||||
'@typescript-eslint/type-utils': 5.59.8(eslint@8.42.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/utils': 5.59.8(eslint@8.42.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/scope-manager': 6.4.0
|
||||
'@typescript-eslint/type-utils': 6.4.0(eslint@8.47.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/utils': 6.4.0(eslint@8.47.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/visitor-keys': 6.4.0
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.42.0
|
||||
grapheme-splitter: 1.0.4
|
||||
ignore: 5.2.0
|
||||
natural-compare-lite: 1.4.0
|
||||
semver: 7.5.1
|
||||
tsutils: 3.21.0(typescript@5.0.4)
|
||||
eslint: 8.47.0
|
||||
graphemer: 1.4.0
|
||||
ignore: 5.2.4
|
||||
natural-compare: 1.4.0
|
||||
semver: 7.5.4
|
||||
ts-api-utils: 1.0.1(typescript@5.0.4)
|
||||
typescript: 5.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/parser@5.59.8(eslint@8.42.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
/@typescript-eslint/parser@6.4.0(eslint@8.47.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
eslint: ^7.0.0 || ^8.0.0
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 5.59.8
|
||||
'@typescript-eslint/types': 5.59.8
|
||||
'@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4)
|
||||
'@typescript-eslint/scope-manager': 6.4.0
|
||||
'@typescript-eslint/types': 6.4.0
|
||||
'@typescript-eslint/typescript-estree': 6.4.0(typescript@5.0.4)
|
||||
'@typescript-eslint/visitor-keys': 6.4.0
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.42.0
|
||||
eslint: 8.47.0
|
||||
typescript: 5.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/scope-manager@5.59.8:
|
||||
resolution: {integrity: sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
/@typescript-eslint/scope-manager@6.4.0:
|
||||
resolution: {integrity: sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 5.59.8
|
||||
'@typescript-eslint/visitor-keys': 5.59.8
|
||||
'@typescript-eslint/types': 6.4.0
|
||||
'@typescript-eslint/visitor-keys': 6.4.0
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/type-utils@5.59.8(eslint@8.42.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
/@typescript-eslint/type-utils@6.4.0(eslint@8.47.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
eslint: '*'
|
||||
eslint: ^7.0.0 || ^8.0.0
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4)
|
||||
'@typescript-eslint/utils': 5.59.8(eslint@8.42.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/typescript-estree': 6.4.0(typescript@5.0.4)
|
||||
'@typescript-eslint/utils': 6.4.0(eslint@8.47.0)(typescript@5.0.4)
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
eslint: 8.42.0
|
||||
tsutils: 3.21.0(typescript@5.0.4)
|
||||
eslint: 8.47.0
|
||||
ts-api-utils: 1.0.1(typescript@5.0.4)
|
||||
typescript: 5.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/types@5.59.8:
|
||||
resolution: {integrity: sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
/@typescript-eslint/types@6.4.0:
|
||||
resolution: {integrity: sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/typescript-estree@5.59.8(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
/@typescript-eslint/typescript-estree@6.4.0(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 5.59.8
|
||||
'@typescript-eslint/visitor-keys': 5.59.8
|
||||
'@typescript-eslint/types': 6.4.0
|
||||
'@typescript-eslint/visitor-keys': 6.4.0
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
semver: 7.5.1
|
||||
tsutils: 3.21.0(typescript@5.0.4)
|
||||
semver: 7.5.4
|
||||
ts-api-utils: 1.0.1(typescript@5.0.4)
|
||||
typescript: 5.0.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/utils@5.59.8(eslint@8.42.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
/@typescript-eslint/utils@6.4.0(eslint@8.47.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
eslint: ^7.0.0 || ^8.0.0
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.4.0(eslint@8.42.0)
|
||||
'@types/json-schema': 7.0.11
|
||||
'@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0)
|
||||
'@types/json-schema': 7.0.12
|
||||
'@types/semver': 7.5.0
|
||||
'@typescript-eslint/scope-manager': 5.59.8
|
||||
'@typescript-eslint/types': 5.59.8
|
||||
'@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4)
|
||||
eslint: 8.42.0
|
||||
eslint-scope: 5.1.1
|
||||
semver: 7.5.1
|
||||
'@typescript-eslint/scope-manager': 6.4.0
|
||||
'@typescript-eslint/types': 6.4.0
|
||||
'@typescript-eslint/typescript-estree': 6.4.0(typescript@5.0.4)
|
||||
eslint: 8.47.0
|
||||
semver: 7.5.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@typescript-eslint/visitor-keys@5.59.8:
|
||||
resolution: {integrity: sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
/@typescript-eslint/visitor-keys@6.4.0:
|
||||
resolution: {integrity: sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 5.59.8
|
||||
'@typescript-eslint/types': 6.4.0
|
||||
eslint-visitor-keys: 3.4.1
|
||||
dev: true
|
||||
|
||||
|
@ -1126,12 +1125,12 @@ packages:
|
|||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
dev: false
|
||||
|
||||
/acorn-jsx@5.3.2(acorn@8.8.2):
|
||||
/acorn-jsx@5.3.2(acorn@8.10.0):
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
dependencies:
|
||||
acorn: 8.8.2
|
||||
acorn: 8.10.0
|
||||
dev: true
|
||||
|
||||
/acorn-walk@8.2.0:
|
||||
|
@ -1139,6 +1138,12 @@ packages:
|
|||
engines: {node: '>=0.4.0'}
|
||||
dev: true
|
||||
|
||||
/acorn@8.10.0:
|
||||
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/acorn@8.8.2:
|
||||
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
@ -1322,6 +1327,17 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/array.prototype.findlastindex@1.2.2:
|
||||
resolution: {integrity: sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.2.0
|
||||
es-abstract: 1.21.2
|
||||
es-shim-unscopables: 1.0.0
|
||||
get-intrinsic: 1.2.1
|
||||
dev: true
|
||||
|
||||
/array.prototype.flat@1.3.1:
|
||||
resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -1379,6 +1395,7 @@ packages:
|
|||
/big-integer@1.6.51:
|
||||
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/binary-extensions@2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
|
@ -1403,13 +1420,6 @@ packages:
|
|||
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
|
||||
dev: true
|
||||
|
||||
/bplist-parser@0.2.0:
|
||||
resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==}
|
||||
engines: {node: '>= 5.10.0'}
|
||||
dependencies:
|
||||
big-integer: 1.6.51
|
||||
dev: true
|
||||
|
||||
/brace-expansion@1.1.11:
|
||||
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
|
||||
dependencies:
|
||||
|
@ -1451,13 +1461,6 @@ packages:
|
|||
ieee754: 1.2.1
|
||||
dev: false
|
||||
|
||||
/bundle-name@3.0.0:
|
||||
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
run-applescript: 5.0.0
|
||||
dev: true
|
||||
|
||||
/cacache@16.0.7:
|
||||
resolution: {integrity: sha512-a4zfQpp5vm4Ipdvbj+ZrPonikRhm6WBEd4zT1Yc1DXsmAxrPgDwWBLF/u/wTVXSFPIgOJ1U3ghSa2Xm4s3h28w==}
|
||||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||
|
@ -1905,24 +1908,6 @@ packages:
|
|||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
dev: true
|
||||
|
||||
/default-browser-id@3.0.0:
|
||||
resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
bplist-parser: 0.2.0
|
||||
untildify: 4.0.0
|
||||
dev: true
|
||||
|
||||
/default-browser@4.0.0:
|
||||
resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==}
|
||||
engines: {node: '>=14.16'}
|
||||
dependencies:
|
||||
bundle-name: 3.0.0
|
||||
default-browser-id: 3.0.0
|
||||
execa: 7.1.1
|
||||
titleize: 3.0.0
|
||||
dev: true
|
||||
|
||||
/default-require-extensions@3.0.0:
|
||||
resolution: {integrity: sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -1930,11 +1915,6 @@ packages:
|
|||
strip-bom: 4.0.0
|
||||
dev: true
|
||||
|
||||
/define-lazy-prop@3.0.0:
|
||||
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/define-properties@1.2.0:
|
||||
resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -2204,13 +2184,13 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/eslint-config-prettier@8.8.0(eslint@8.42.0):
|
||||
/eslint-config-prettier@8.8.0(eslint@8.47.0):
|
||||
resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
eslint: '>=7.0.0'
|
||||
dependencies:
|
||||
eslint: 8.42.0
|
||||
eslint: 8.47.0
|
||||
dev: true
|
||||
|
||||
/eslint-import-resolver-node@0.3.7:
|
||||
|
@ -2218,13 +2198,13 @@ packages:
|
|||
dependencies:
|
||||
debug: 3.2.7
|
||||
is-core-module: 2.12.1
|
||||
resolve: 1.22.2
|
||||
resolve: 1.22.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.59.8)(eslint-plugin-import@2.27.5)(eslint@8.42.0):
|
||||
resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==}
|
||||
/eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.4.0)(eslint-plugin-import@2.28.0)(eslint@8.47.0):
|
||||
resolution: {integrity: sha512-QTHR9ddNnn35RTxlaEnx2gCxqFlF2SEN0SE2d17SqwyM7YOSI2GHWRYp5BiRkObTUNYPupC/3Fq2a0PpT+EKpg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
eslint: '*'
|
||||
|
@ -2232,14 +2212,13 @@ packages:
|
|||
dependencies:
|
||||
debug: 4.3.4(supports-color@8.1.1)
|
||||
enhanced-resolve: 5.14.1
|
||||
eslint: 8.42.0
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0)
|
||||
eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0)
|
||||
eslint: 8.47.0
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0)
|
||||
eslint-plugin-import: 2.28.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0)
|
||||
fast-glob: 3.3.1
|
||||
get-tsconfig: 4.6.0
|
||||
globby: 13.1.4
|
||||
is-core-module: 2.12.1
|
||||
is-glob: 4.0.3
|
||||
synckit: 0.8.5
|
||||
transitivePeerDependencies:
|
||||
- '@typescript-eslint/parser'
|
||||
- eslint-import-resolver-node
|
||||
|
@ -2247,7 +2226,7 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-module-utils@2.8.0(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0):
|
||||
/eslint-module-utils@2.8.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0):
|
||||
resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
|
@ -2268,11 +2247,11 @@ packages:
|
|||
eslint-import-resolver-webpack:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.59.8(eslint@8.42.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.0.4)
|
||||
debug: 3.2.7
|
||||
eslint: 8.42.0
|
||||
eslint: 8.47.0
|
||||
eslint-import-resolver-node: 0.3.7
|
||||
eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.8)(eslint-plugin-import@2.27.5)(eslint@8.42.0)
|
||||
eslint-import-resolver-typescript: 3.6.0(@typescript-eslint/parser@6.4.0)(eslint-plugin-import@2.28.0)(eslint@8.47.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -2281,8 +2260,8 @@ packages:
|
|||
resolution: {integrity: sha512-NT2asS7tLkXMKBk0GuX6eDUZvb5DWTFDCt7R6a8tvWs5P0my2ybxmCFy3Afxgdcam+wQRAn8JrldLmcK0H5Axg==}
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0):
|
||||
resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==}
|
||||
/eslint-plugin-import@2.28.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0):
|
||||
resolution: {integrity: sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==}
|
||||
engines: {node: '>=4'}
|
||||
peerDependencies:
|
||||
'@typescript-eslint/parser': '*'
|
||||
|
@ -2291,22 +2270,25 @@ packages:
|
|||
'@typescript-eslint/parser':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@typescript-eslint/parser': 5.59.8(eslint@8.42.0)(typescript@5.0.4)
|
||||
'@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.0.4)
|
||||
array-includes: 3.1.6
|
||||
array.prototype.findlastindex: 1.2.2
|
||||
array.prototype.flat: 1.3.1
|
||||
array.prototype.flatmap: 1.3.1
|
||||
debug: 3.2.7
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.42.0
|
||||
eslint: 8.47.0
|
||||
eslint-import-resolver-node: 0.3.7
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0)
|
||||
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0)
|
||||
has: 1.0.3
|
||||
is-core-module: 2.12.1
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.1.2
|
||||
object.fromentries: 2.0.6
|
||||
object.groupby: 1.0.0
|
||||
object.values: 1.1.6
|
||||
resolve: 1.22.2
|
||||
semver: 6.3.0
|
||||
resolve: 1.22.4
|
||||
semver: 6.3.1
|
||||
tsconfig-paths: 3.14.2
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
|
@ -2314,24 +2296,16 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-simple-import-sort@10.0.0(eslint@8.42.0):
|
||||
/eslint-plugin-simple-import-sort@10.0.0(eslint@8.47.0):
|
||||
resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==}
|
||||
peerDependencies:
|
||||
eslint: '>=5.0.0'
|
||||
dependencies:
|
||||
eslint: 8.42.0
|
||||
eslint: 8.47.0
|
||||
dev: true
|
||||
|
||||
/eslint-scope@5.1.1:
|
||||
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
esrecurse: 4.3.0
|
||||
estraverse: 4.3.0
|
||||
dev: true
|
||||
|
||||
/eslint-scope@7.2.0:
|
||||
resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==}
|
||||
/eslint-scope@7.2.2:
|
||||
resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
esrecurse: 4.3.0
|
||||
|
@ -2343,15 +2317,20 @@ packages:
|
|||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/eslint@8.42.0:
|
||||
resolution: {integrity: sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==}
|
||||
/eslint-visitor-keys@3.4.3:
|
||||
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dev: true
|
||||
|
||||
/eslint@8.47.0:
|
||||
resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.4.0(eslint@8.42.0)
|
||||
'@eslint-community/regexpp': 4.5.1
|
||||
'@eslint/eslintrc': 2.0.3
|
||||
'@eslint/js': 8.42.0
|
||||
'@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0)
|
||||
'@eslint-community/regexpp': 4.6.2
|
||||
'@eslint/eslintrc': 2.1.2
|
||||
'@eslint/js': 8.47.0
|
||||
'@humanwhocodes/config-array': 0.11.10
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
|
@ -2361,9 +2340,9 @@ packages:
|
|||
debug: 4.3.4(supports-color@8.1.1)
|
||||
doctrine: 3.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 7.2.0
|
||||
eslint-visitor-keys: 3.4.1
|
||||
espree: 9.5.2
|
||||
eslint-scope: 7.2.2
|
||||
eslint-visitor-keys: 3.4.3
|
||||
espree: 9.6.1
|
||||
esquery: 1.5.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
|
@ -2373,7 +2352,6 @@ packages:
|
|||
globals: 13.20.0
|
||||
graphemer: 1.4.0
|
||||
ignore: 5.2.0
|
||||
import-fresh: 3.3.0
|
||||
imurmurhash: 0.1.4
|
||||
is-glob: 4.0.3
|
||||
is-path-inside: 3.0.3
|
||||
|
@ -2383,21 +2361,20 @@ packages:
|
|||
lodash.merge: 4.6.2
|
||||
minimatch: 3.1.2
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.1
|
||||
optionator: 0.9.3
|
||||
strip-ansi: 6.0.1
|
||||
strip-json-comments: 3.1.1
|
||||
text-table: 0.2.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/espree@9.5.2:
|
||||
resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==}
|
||||
/espree@9.6.1:
|
||||
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
dependencies:
|
||||
acorn: 8.8.2
|
||||
acorn-jsx: 5.3.2(acorn@8.8.2)
|
||||
eslint-visitor-keys: 3.4.1
|
||||
acorn: 8.10.0
|
||||
acorn-jsx: 5.3.2(acorn@8.10.0)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
dev: true
|
||||
|
||||
/esprima@4.0.1:
|
||||
|
@ -2420,11 +2397,6 @@ packages:
|
|||
estraverse: 5.3.0
|
||||
dev: true
|
||||
|
||||
/estraverse@4.3.0:
|
||||
resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
|
||||
engines: {node: '>=4.0'}
|
||||
dev: true
|
||||
|
||||
/estraverse@5.3.0:
|
||||
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||
engines: {node: '>=4.0'}
|
||||
|
@ -2484,8 +2456,8 @@ packages:
|
|||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
|
||||
/fast-glob@3.2.11:
|
||||
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
|
||||
/fast-glob@3.2.12:
|
||||
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
|
@ -2495,8 +2467,8 @@ packages:
|
|||
micromatch: 4.0.5
|
||||
dev: true
|
||||
|
||||
/fast-glob@3.2.12:
|
||||
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
|
||||
/fast-glob@3.3.1:
|
||||
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
|
@ -2834,23 +2806,12 @@ packages:
|
|||
dependencies:
|
||||
array-union: 2.1.0
|
||||
dir-glob: 3.0.1
|
||||
fast-glob: 3.2.11
|
||||
fast-glob: 3.2.12
|
||||
ignore: 5.2.0
|
||||
merge2: 1.4.1
|
||||
slash: 3.0.0
|
||||
dev: true
|
||||
|
||||
/globby@13.1.4:
|
||||
resolution: {integrity: sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
dependencies:
|
||||
dir-glob: 3.0.1
|
||||
fast-glob: 3.2.11
|
||||
ignore: 5.2.0
|
||||
merge2: 1.4.1
|
||||
slash: 4.0.0
|
||||
dev: true
|
||||
|
||||
/gopd@1.0.1:
|
||||
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
|
||||
dependencies:
|
||||
|
@ -2860,10 +2821,6 @@ packages:
|
|||
/graceful-fs@4.2.10:
|
||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||
|
||||
/grapheme-splitter@1.0.4:
|
||||
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
|
||||
dev: true
|
||||
|
||||
/graphemer@1.4.0:
|
||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||
dev: true
|
||||
|
@ -3031,6 +2988,11 @@ packages:
|
|||
engines: {node: '>= 4'}
|
||||
dev: true
|
||||
|
||||
/ignore@5.2.4:
|
||||
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
|
||||
engines: {node: '>= 4'}
|
||||
dev: true
|
||||
|
||||
/import-fresh@3.3.0:
|
||||
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -3125,6 +3087,12 @@ packages:
|
|||
has: 1.0.3
|
||||
dev: true
|
||||
|
||||
/is-core-module@2.13.0:
|
||||
resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==}
|
||||
dependencies:
|
||||
has: 1.0.3
|
||||
dev: true
|
||||
|
||||
/is-date-object@1.0.5:
|
||||
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -3132,18 +3100,6 @@ packages:
|
|||
has-tostringtag: 1.0.0
|
||||
dev: true
|
||||
|
||||
/is-docker@2.2.1:
|
||||
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/is-docker@3.0.0:
|
||||
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/is-extglob@2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -3172,14 +3128,6 @@ packages:
|
|||
is-extglob: 2.1.1
|
||||
dev: true
|
||||
|
||||
/is-inside-container@1.0.0:
|
||||
resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
|
||||
engines: {node: '>=14.16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
is-docker: 3.0.0
|
||||
dev: true
|
||||
|
||||
/is-lambda@1.0.1:
|
||||
resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
|
||||
dev: false
|
||||
|
@ -3297,13 +3245,6 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/is-wsl@2.2.0:
|
||||
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
is-docker: 2.2.1
|
||||
dev: true
|
||||
|
||||
/isarray@1.0.0:
|
||||
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
|
||||
dev: false
|
||||
|
@ -3887,10 +3828,6 @@ packages:
|
|||
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
||||
dev: false
|
||||
|
||||
/natural-compare-lite@1.4.0:
|
||||
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
|
||||
dev: true
|
||||
|
||||
/natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
dev: true
|
||||
|
@ -4079,6 +4016,24 @@ packages:
|
|||
object-keys: 1.1.1
|
||||
dev: true
|
||||
|
||||
/object.fromentries@2.0.6:
|
||||
resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.2.0
|
||||
es-abstract: 1.21.2
|
||||
dev: true
|
||||
|
||||
/object.groupby@1.0.0:
|
||||
resolution: {integrity: sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==}
|
||||
dependencies:
|
||||
call-bind: 1.0.2
|
||||
define-properties: 1.2.0
|
||||
es-abstract: 1.21.2
|
||||
get-intrinsic: 1.2.1
|
||||
dev: true
|
||||
|
||||
/object.values@1.1.6:
|
||||
resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -4107,26 +4062,16 @@ packages:
|
|||
mimic-fn: 4.0.0
|
||||
dev: true
|
||||
|
||||
/open@9.1.0:
|
||||
resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==}
|
||||
engines: {node: '>=14.16'}
|
||||
dependencies:
|
||||
default-browser: 4.0.0
|
||||
define-lazy-prop: 3.0.0
|
||||
is-inside-container: 1.0.0
|
||||
is-wsl: 2.2.0
|
||||
dev: true
|
||||
|
||||
/optionator@0.9.1:
|
||||
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
|
||||
/optionator@0.9.3:
|
||||
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
'@aashutoshrathi/word-wrap': 1.2.6
|
||||
deep-is: 0.1.4
|
||||
fast-levenshtein: 2.0.6
|
||||
levn: 0.4.1
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
word-wrap: 1.2.3
|
||||
dev: true
|
||||
|
||||
/p-limit@2.3.0:
|
||||
|
@ -4514,6 +4459,15 @@ packages:
|
|||
supports-preserve-symlinks-flag: 1.0.0
|
||||
dev: true
|
||||
|
||||
/resolve@1.22.4:
|
||||
resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
is-core-module: 2.13.0
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
dev: true
|
||||
|
||||
/restore-cursor@3.1.0:
|
||||
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -4550,13 +4504,6 @@ packages:
|
|||
glob: 10.2.6
|
||||
dev: true
|
||||
|
||||
/run-applescript@5.0.0:
|
||||
resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
execa: 5.1.1
|
||||
dev: true
|
||||
|
||||
/run-parallel@1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
dependencies:
|
||||
|
@ -4599,6 +4546,11 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/semver@7.5.0:
|
||||
resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -4614,6 +4566,14 @@ packages:
|
|||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
|
||||
/semver@7.5.4:
|
||||
resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/serialize-javascript@6.0.0:
|
||||
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
|
||||
dependencies:
|
||||
|
@ -4677,11 +4637,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/slash@4.0.0:
|
||||
resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/slice-ansi@3.0.0:
|
||||
resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -4950,14 +4905,6 @@ packages:
|
|||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/synckit@0.8.5:
|
||||
resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
dependencies:
|
||||
'@pkgr/utils': 2.4.1
|
||||
tslib: 2.5.3
|
||||
dev: true
|
||||
|
||||
/tapable@2.2.1:
|
||||
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -5023,11 +4970,6 @@ packages:
|
|||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: true
|
||||
|
||||
/titleize@3.0.0:
|
||||
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/to-fast-properties@2.0.0:
|
||||
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -5053,6 +4995,15 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/ts-api-utils@1.0.1(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==}
|
||||
engines: {node: '>=16.13.0'}
|
||||
peerDependencies:
|
||||
typescript: '>=4.2.0'
|
||||
dependencies:
|
||||
typescript: 5.0.4
|
||||
dev: true
|
||||
|
||||
/ts-node@10.9.1(@types/node@18.16.0)(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
|
||||
hasBin: true
|
||||
|
@ -5102,24 +5053,10 @@ packages:
|
|||
strip-bom: 3.0.0
|
||||
dev: true
|
||||
|
||||
/tslib@1.14.1:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: true
|
||||
|
||||
/tslib@2.5.3:
|
||||
resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==}
|
||||
dev: true
|
||||
|
||||
/tsutils@3.21.0(typescript@5.0.4):
|
||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||
engines: {node: '>= 6'}
|
||||
peerDependencies:
|
||||
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
|
||||
dependencies:
|
||||
tslib: 1.14.1
|
||||
typescript: 5.0.4
|
||||
dev: true
|
||||
|
||||
/tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
dependencies:
|
||||
|
@ -5223,11 +5160,6 @@ packages:
|
|||
engines: {node: '>= 10.0.0'}
|
||||
dev: true
|
||||
|
||||
/untildify@4.0.0:
|
||||
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/uri-js@4.4.1:
|
||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||
dependencies:
|
||||
|
@ -5301,11 +5233,6 @@ packages:
|
|||
string-width: 4.2.3
|
||||
dev: false
|
||||
|
||||
/word-wrap@1.2.3:
|
||||
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/workerpool@6.2.1:
|
||||
resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
|
||||
dev: true
|
||||
|
|
Loading…
Reference in a new issue