fix: fixed upload and download for new networking

This commit is contained in:
alina 🌸 2023-08-23 22:11:42 +03:00
parent 85c43d804d
commit 4a0d6fbc88
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
30 changed files with 1014 additions and 821 deletions

View file

@ -181,6 +181,10 @@ module.exports = {
], ],
globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly' }, globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly' },
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint'], plugins: ['@typescript-eslint'],
rules: { rules: {
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules

View file

@ -27,15 +27,15 @@
"@types/node": "18.16.0", "@types/node": "18.16.0",
"@types/node-forge": "1.3.2", "@types/node-forge": "1.3.2",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.59.8", "@typescript-eslint/eslint-plugin": "6.4.0",
"@typescript-eslint/parser": "5.59.8", "@typescript-eslint/parser": "6.4.0",
"chai": "4.3.7", "chai": "4.3.7",
"dotenv-flow": "3.2.0", "dotenv-flow": "3.2.0",
"eslint": "8.42.0", "eslint": "8.47.0",
"eslint-config-prettier": "8.8.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-ascii": "1.0.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.28.0",
"eslint-plugin-simple-import-sort": "10.0.0", "eslint-plugin-simple-import-sort": "10.0.0",
"glob": "10.2.6", "glob": "10.2.6",
"husky": "^8.0.3", "husky": "^8.0.3",

View file

@ -64,8 +64,11 @@ async function addSingleMethod(state, fileName) {
if ( if (
!stmt.importClause.namedBindings || !stmt.importClause.namedBindings ||
stmt.importClause.namedBindings.kind !== ts.SyntaxKind.NamedImports stmt.importClause.namedBindings.kind !==
) { throwError(stmt, fileName, 'Only named imports are supported!') } ts.SyntaxKind.NamedImports
) {
throwError(stmt, fileName, 'Only named imports are supported!')
}
let module = stmt.moduleSpecifier.text let module = stmt.moduleSpecifier.text
@ -131,11 +134,7 @@ async function addSingleMethod(state, fileName) {
})() })()
if (!isExported && !isPrivate) { if (!isExported && !isPrivate) {
throwError( throwError(stmt, fileName, 'Public methods MUST be exported.')
stmt,
fileName,
'Public methods MUST be exported.',
)
} }
if (isExported && !checkForFlag(stmt, '@internal')) { if (isExported && !checkForFlag(stmt, '@internal')) {
@ -182,16 +181,20 @@ async function addSingleMethod(state, fileName) {
) )
} }
const returnsExported = (stmt.body ? const returnsExported = (
ts.getLeadingCommentRanges(fileFullText, stmt.body.pos + 2) || stmt.body ?
(stmt.statements && ts.getLeadingCommentRanges(
stmt.statements.length && fileFullText,
ts.getLeadingCommentRanges( stmt.body.pos + 2,
fileFullText, ) ||
stmt.statements[0].pos, (stmt.statements &&
)) || stmt.statements.length &&
[] : ts.getLeadingCommentRanges(
[] fileFullText,
stmt.statements[0].pos,
)) ||
[] :
[]
) )
.map((range) => fileFullText.substring(range.pos, range.end)) .map((range) => fileFullText.substring(range.pos, range.end))
.join('\n') .join('\n')
@ -275,7 +278,9 @@ async function addSingleMethod(state, fileName) {
} }
async function main() { 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 = { const state = {
imports: {}, imports: {},
fields: [], fields: [],
@ -295,7 +300,8 @@ async function main() {
} }
output.write( 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 { BaseTelegramClient, BaseTelegramClientOptions } from '@mtcute/core'\n" +
"import { tl } from '@mtcute/tl'\n", "import { tl } from '@mtcute/tl'\n",
) )
@ -336,7 +342,9 @@ async function main() {
* @param name Event name * @param name Event name
* @param handler ${updates.toSentence(type, 'full')} * @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() const printer = ts.createPrinter()
@ -406,7 +414,9 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this
it.initializer = undefined it.initializer = undefined
const deleteParents = (obj) => { 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 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]) { for (const name of [origName, ...aliases]) {
if (!hasOverloads) { if (!hasOverloads) {
if (!comment.match(/\/\*\*?\s*\*\//)) { if (!comment.match(/\/\*\*?\s*\*\//)) {
// empty comment, no need to write it // empty comment, no need to write it
output.write(comment + '\n') output.write(comment + '\n')
} }
@ -465,18 +475,14 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this
} }
if (!overload) { if (!overload) {
classContents.push( classContents.push(`${name} = ${origName}`)
`${name} = ${origName}`,
)
} }
} }
}, },
) )
output.write('}\n') output.write('}\n')
output.write( output.write('\nexport class TelegramClient extends BaseTelegramClient {\n')
'\nexport class TelegramClient extends BaseTelegramClient {\n',
)
state.fields.forEach(({ code }) => output.write(`protected ${code}\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) await fs.promises.writeFile(targetFile, fullSource)
// fix using eslint // fix using eslint
require('child_process').execSync( require('child_process').execSync(`pnpm exec eslint --fix ${targetFile}`, {
`pnpm exec eslint --fix ${targetFile}`, stdio: 'inherit',
{ stdio: 'inherit' }, })
)
} }
main().catch(console.error) main().catch(console.error)

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
/* THIS FILE WAS AUTO-GENERATED */ /* THIS FILE WAS AUTO-GENERATED */
import { Readable } from 'stream' import { Readable } from 'stream'
@ -8,7 +9,6 @@ import {
Deque, Deque,
MaybeArray, MaybeArray,
MaybeAsync, MaybeAsync,
SessionConnection,
SortedLinkedList, SortedLinkedList,
} from '@mtcute/core' } from '@mtcute/core'
import { ConditionVariable } from '@mtcute/core/src/utils/condition-variable' 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. * 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 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 * File MIME type. By default is automatically inferred from magic number
* If MIME can't be inferred, it defaults to `application/octet-stream` * If MIME can't be inferred, it defaults to `application/octet-stream`
@ -1930,11 +1931,16 @@ export interface TelegramClient extends BaseTelegramClient {
*/ */
partSize?: number 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. * Function that will be called after some part has been uploaded.
* *
* @param uploaded Number of bytes already 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 progressCallback?: (uploaded: number, total: number) => void
}): Promise<UploadedFile> }): Promise<UploadedFile>
@ -4019,7 +4025,6 @@ export class TelegramClient extends BaseTelegramClient {
protected _selfUsername: string | null protected _selfUsername: string | null
protected _pendingConversations: Record<number, Conversation[]> protected _pendingConversations: Record<number, Conversation[]>
protected _hasConversations: boolean protected _hasConversations: boolean
protected _downloadConnections: Record<number, SessionConnection>
protected _parseModes: Record<string, IMessageEntityParser> protected _parseModes: Record<string, IMessageEntityParser>
protected _defaultParseMode: string | null protected _defaultParseMode: string | null
protected _updatesLoopActive: boolean protected _updatesLoopActive: boolean
@ -4054,7 +4059,6 @@ export class TelegramClient extends BaseTelegramClient {
this.log.prefix = '[USER N/A] ' this.log.prefix = '[USER N/A] '
this._pendingConversations = {} this._pendingConversations = {}
this._hasConversations = false this._hasConversations = false
this._downloadConnections = {}
this._parseModes = {} this._parseModes = {}
this._defaultParseMode = null this._defaultParseMode = null
this._updatesLoopActive = false this._updatesLoopActive = false

View file

@ -2,12 +2,7 @@
import { Readable } from 'stream' import { Readable } from 'stream'
// @copy // @copy
import { import { AsyncLock, MaybeArray, MaybeAsync } from '@mtcute/core'
AsyncLock,
MaybeArray,
MaybeAsync,
SessionConnection,
} from '@mtcute/core'
// @copy // @copy
import { Logger } from '@mtcute/core/src/utils/logger' import { Logger } from '@mtcute/core/src/utils/logger'
// @copy // @copy

View file

@ -155,8 +155,9 @@ export async function start(
me.isBot, me.isBot,
) )
// todo where is this._disableUpdates? this.network.setIsPremium(me.isPremium)
if (!false) {
if (!this.network.params.disableUpdates) {
this._catchUpChannels = Boolean(params.catchUp) this._catchUpChannels = Boolean(params.catchUp)
if (!params.catchUp) { if (!params.catchUp) {
@ -176,14 +177,18 @@ export async function start(
if (!(e instanceof tl.errors.AuthKeyUnregisteredError)) throw e 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 let phone = params.phone ? await resolveMaybeDynamic(params.phone) : null
if (phone) { if (phone) {
phone = normalizePhoneNumber(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 { } else {
const botToken = params.botToken ? const botToken = params.botToken ?
await resolveMaybeDynamic(params.botToken) : await resolveMaybeDynamic(params.botToken) :

View file

@ -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 = {}
}

View file

@ -1,3 +1,4 @@
import { ConditionVariable, ConnectionKind } from '@mtcute/core'
import { import {
fileIdToInputFileLocation, fileIdToInputFileLocation,
fileIdToInputWebFileLocation, fileIdToInputWebFileLocation,
@ -10,9 +11,16 @@ import {
FileDownloadParameters, FileDownloadParameters,
FileLocation, FileLocation,
MtArgumentError, MtArgumentError,
MtUnsupportedError,
} from '../../types' } from '../../types'
import { determinePartSize } from '../../utils/file-utils' 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 * 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 * in chunks of a given size. Order of the chunks is guaranteed to be
@ -25,16 +33,6 @@ export async function* downloadAsIterable(
this: TelegramClient, this: TelegramClient,
params: FileDownloadParameters, params: FileDownloadParameters,
): AsyncIterableIterator<Buffer> { ): 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 const offset = params.offset ?? 0
if (offset % 4096 !== 0) { if (offset % 4096 !== 0) {
@ -77,91 +75,151 @@ export async function* downloadAsIterable(
// we will receive a FileMigrateError in case this is invalid // we will receive a FileMigrateError in case this is invalid
if (!dcId) dcId = this._defaultDc.id 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 chunkSize = partSizeKb * 1024
const limit = let limitBytes = params.limit ?? fileSize ?? Infinity
params.limit ?? if (limitBytes === 0) return
// 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)
// fixme let numChunks =
throw new Error('TODO') 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) { const isSmall = fileSize && fileSize <= SMALL_FILE_MAX_SIZE
// connection = await this.createAdditionalConnection(dcId) let connectionKind: ConnectionKind
// this._downloadConnections[dcId] = connection
// } if (isSmall) {
// connectionKind =
// const requestCurrent = async (): Promise<Buffer> => { dcId === this.network.getPrimaryDcId() ? 'main' : 'downloadSmall'
// let result: } else {
// | tl.RpcCallReturn['upload.getFile'] connectionKind = 'download'
// | tl.RpcCallReturn['upload.getWebFile'] }
// const poolSize = this.network.getPoolSize(connectionKind, dcId)
// try {
// result = await this.call( this.log.debug(
// { 'Downloading file of size %d from dc %d using %s connection pool (pool size: %d)',
// _: isWeb ? 'upload.getWebFile' : 'upload.getFile', limitBytes,
// // eslint-disable-next-line @typescript-eslint/no-explicit-any dcId,
// location: location as any, connectionKind,
// offset, poolSize,
// limit: chunkSize, )
// },
// { connection }, const downloadChunk = async (
// ) chunk = nextWorkerChunkIdx++,
// // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise<void> => {
// } catch (e: any) { let result:
// if (e.constructor === tl.errors.FileMigrateXError) { | tl.RpcCallReturn['upload.getFile']
// connection = this._downloadConnections[e.new_dc] | tl.RpcCallReturn['upload.getWebFile']
//
// if (!connection) { try {
// connection = await this.createAdditionalConnection(e.new_dc) result = await this.call(
// this._downloadConnections[e.new_dc] = connection {
// } _: isWeb ? 'upload.getWebFile' : 'upload.getFile',
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// return requestCurrent() location: location as any,
// } else if (e.constructor === tl.errors.FilerefUpgradeNeededError) { offset: chunkSize * chunk,
// // todo: implement someday limit: chunkSize,
// // see: https://github.com/LonamiWebs/Telethon/blob/0e8bd8248cc649637b7c392616887c50986427a0/telethon/client/downloads.py#L99 },
// throw new MtUnsupportedError('File ref expired!') { dcId, kind: connectionKind },
// } else throw e )
// } // eslint-disable-next-line @typescript-eslint/no-explicit-any
// } catch (e: any) {
// if (result._ === 'upload.fileCdnRedirect') { if (e.constructor === tl.errors.FileMigrateXError) {
// // we shouldnt receive them since cdnSupported is not set in the getFile request. dcId = e.new_dc
// // 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: return downloadChunk(chunk)
// throw new MtUnsupportedError( } else if (e.constructor === tl.errors.FilerefUpgradeNeededError) {
// 'Received CDN redirect, which is not supported (yet)', // 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.webFile' &&
// result.size && if (result._ === 'upload.fileCdnRedirect') {
// limit === Infinity // 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
// limit = result.size // i implemented that, i wouldnt be able to test that, so :shrug:
// } throw new MtUnsupportedError(
// 'Received CDN redirect, which is not supported (yet)',
// return result.bytes )
// } }
//
// for (let i = 0; i < limit; i++) { if (
// const buf = await requestCurrent() result._ === 'upload.webFile' &&
// result.size &&
// if (buf.length === 0) { limitBytes === Infinity
// // we've reached the end ) {
// return limitBytes = result.size
// } numChunks = ~~((limitBytes + chunkSize - offset - 1) / chunkSize)
// }
// yield buf
// offset += chunkSize buffer[chunk] = result.bytes
//
// params.progressCallback?.(offset, limit) 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
}
}
}
} }

View file

@ -3,7 +3,7 @@ import { fromBuffer as fileTypeFromBuffer } from 'file-type'
import type { ReadStream } from 'fs' import type { ReadStream } from 'fs'
import { Readable } from 'stream' import { Readable } from 'stream'
import { randomLong } from '@mtcute/core' import { AsyncLock, randomLong } from '@mtcute/core'
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
@ -13,7 +13,6 @@ import {
bufferToStream, bufferToStream,
convertWebStreamToNodeReadable, convertWebStreamToNodeReadable,
readBytesFromStream, readBytesFromStream,
readStreamUntilEnd,
} from '../../utils/stream-utils' } from '../../utils/stream-utils'
let fs: any = null let fs: any = null
@ -29,6 +28,14 @@ const OVERRIDE_MIME: Record<string, string> = {
'audio/opus': 'audio/ogg', '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 * Upload a file to Telegram servers, without actually
* sending a message anywhere. Useful when an `InputFile` is required. * 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. * 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 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 * File MIME type. By default is automatically inferred from magic number
* If MIME can't be inferred, it defaults to `application/octet-stream` * If MIME can't be inferred, it defaults to `application/octet-stream`
@ -82,11 +90,16 @@ export async function uploadFile(
*/ */
partSize?: number 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. * Function that will be called after some part has been uploaded.
* *
* @param uploaded Number of bytes already 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 progressCallback?: (uploaded: number, total: number) => void
}, },
@ -94,7 +107,7 @@ export async function uploadFile(
// normalize params // normalize params
let file = params.file let file = params.file
let fileSize = -1 // unknown let fileSize = -1 // unknown
let fileName = 'unnamed' let fileName = DEFAULT_FILE_NAME
let fileMime = params.fileMime let fileMime = params.fileMime
if (Buffer.isBuffer(file)) { 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 // try to infer from url
const url = new URL(file.url) const url = new URL(file.url)
const name = url.pathname.split('/').pop() const name = url.pathname.split('/').pop()
if (name && name.indexOf('.') > -1) { if (name && name.includes('.')) {
fileName = name fileName = name
} }
} }
@ -192,42 +205,88 @@ export async function uploadFile(
// set file size if not automatically inferred // set file size if not automatically inferred
if (fileSize === -1 && params.fileSize) fileSize = params.fileSize if (fileSize === -1 && params.fileSize) fileSize = params.fileSize
if (fileSize === -1) { let partSizeKb = params.partSize
// load the entire stream into memory
const buffer = await readStreamUntilEnd(file as Readable) if (!partSizeKb) {
fileSize = buffer.length if (fileSize === -1) {
file = bufferToStream(buffer) partSizeKb = params.estimatedSize ?
determinePartSize(params.estimatedSize) :
64
} else {
partSizeKb = determinePartSize(fileSize)
}
} }
if (!(file instanceof Readable)) { if (!(file instanceof Readable)) {
throw new MtArgumentError('Could not convert input `file` to stream!') throw new MtArgumentError('Could not convert input `file` to stream!')
} }
const partSizeKb = params.partSize ?? determinePartSize(fileSize)
if (partSizeKb > 512) { if (partSizeKb > 512) {
throw new MtArgumentError(`Invalid part size: ${partSizeKb}KB`) throw new MtArgumentError(`Invalid part size: ${partSizeKb}KB`)
} }
const partSize = partSizeKb * 1024 const partSize = partSizeKb * 1024
const isBig = fileSize > 10485760 // 10 MB let partCount =
const hash = this._crypto.createMd5() 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( 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, fileSize,
partCount, partCount,
partSize, partSize,
connectionKind,
connectionPoolSize,
) )
// why is the file id generated by the client? // why is the file id generated by the client?
// isn't the server supposed to generate it and handle collisions? // isn't the server supposed to generate it and handle collisions?
const fileId = randomLong() const fileId = randomLong()
let pos = 0 const stream = file
for (let idx = 0; idx < partCount; idx++) { let pos = 0
const part = await readBytesFromStream(file, partSize) 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) { if (!part) {
throw new MtArgumentError( throw new MtArgumentError(
@ -236,15 +295,15 @@ export async function uploadFile(
} }
if (!Buffer.isBuffer(part)) { 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) { if (part.length > partSize) {
throw new MtArgumentError( 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) const fileType = await fileTypeFromBuffer(part)
fileMime = fileType?.mime 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 // why
const request = isBig ? const request = isBig ?
({ ({
_: 'upload.saveBigFilePart', _: 'upload.saveBigFilePart',
fileId, fileId,
filePart: idx, filePart: thisIdx,
fileTotalParts: partCount, fileTotalParts: partCount,
bytes: part, bytes: part,
} as tl.upload.RawSaveBigFilePartRequest) : } satisfies tl.upload.RawSaveBigFilePartRequest) :
({ ({
_: 'upload.saveFilePart', _: 'upload.saveFilePart',
fileId, fileId,
filePart: idx, filePart: thisIdx,
bytes: part, 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}`) if (!result) throw new Error(`Failed to upload part ${idx}`)
pos += part.length
params.progressCallback?.(pos, fileSize) params.progressCallback?.(pos, fileSize)
if (idx === partCount) return
return uploadNextPart()
} }
await Promise.all(
Array.from(
{
length: connectionPoolSize * requestsPerConnection,
},
uploadNextPart,
),
)
let inputFile: tl.TypeInputFile let inputFile: tl.TypeInputFile
if (isBig) { if (isBig) {
@ -306,7 +371,7 @@ export async function uploadFile(
id: fileId, id: fileId,
parts: partCount, parts: partCount,
name: fileName, name: fileName,
md5Checksum: (await hash.digest()).toString('hex'), md5Checksum: '', // tdlib doesn't do this, why should we?
} }
} }

View file

@ -558,11 +558,15 @@ async function _fetchPeersForShort(
if ( if (
msg.replyTo._ === 'messageReplyHeader' && msg.replyTo._ === 'messageReplyHeader' &&
!(await fetchPeer(msg.replyTo.replyToPeerId)) !(await fetchPeer(msg.replyTo.replyToPeerId))
) { return null } ) {
return null
}
if ( if (
msg.replyTo._ === 'messageReplyStoryHeader' && msg.replyTo._ === 'messageReplyStoryHeader' &&
!(await fetchPeer(msg.replyTo.userId)) !(await fetchPeer(msg.replyTo.userId))
) { return null } ) {
return null
}
} }
if (msg._ !== 'messageService') { if (msg._ !== 'messageService') {
@ -791,7 +795,7 @@ async function _fetchChannelDifference(
if (!_pts) _pts = fallbackPts if (!_pts) _pts = fallbackPts
if (!_pts) { if (!_pts) {
this._updsLog.warn( this._updsLog.debug(
'fetchChannelDifference failed for channel %d: base pts not available', 'fetchChannelDifference failed for channel %d: base pts not available',
channelId, channelId,
) )

View file

@ -133,8 +133,7 @@ export class Conversation {
const pending = this.client['_pendingConversations'] const pending = this.client['_pendingConversations']
const idx = const idx = pending[this._chatId].indexOf(this)
pending[this._chatId].indexOf(this)
if (idx > -1) { if (idx > -1) {
// just in case // just in case
@ -143,8 +142,7 @@ export class Conversation {
if (!pending[this._chatId].length) { if (!pending[this._chatId].length) {
delete pending[this._chatId] delete pending[this._chatId]
} }
this.client['_hasConversations'] = this.client['_hasConversations'] = Object.keys(pending).length > 0
Object.keys(pending).length > 0
// reset pending status // reset pending status
this._queuedNewMessage.clear() this._queuedNewMessage.clear()
@ -279,6 +277,7 @@ export class Conversation {
if (timeout !== null) { if (timeout !== null) {
timer = setTimeout(() => { timer = setTimeout(() => {
console.log('timed out')
promise.reject(new tl.errors.TimeoutError()) promise.reject(new tl.errors.TimeoutError())
this._queuedNewMessage.removeBy((it) => it.promise === promise) this._queuedNewMessage.removeBy((it) => it.promise === promise)
}, timeout) }, timeout)
@ -537,7 +536,9 @@ export class Conversation {
it.promise.resolve(msg) it.promise.resolve(msg)
delete this._pendingEditMessage[msg.id] delete this._pendingEditMessage[msg.id]
} }
})().catch((e) => this.client['_emitError'](e)) })().catch((e) => {
this.client['_emitError'](e)
})
} }
private _onHistoryRead(upd: HistoryReadUpdate) { private _onHistoryRead(upd: HistoryReadUpdate) {

View file

@ -4,6 +4,7 @@ import { tl } from '@mtcute/tl'
import { TelegramClient } from '../../client' import { TelegramClient } from '../../client'
import { makeInspectable } from '../utils' import { makeInspectable } from '../utils'
import { FileDownloadParameters } from './utils'
/** /**
* Information about file location. * 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 * in chunks of a given size. Order of the chunks is guaranteed to be
* consecutive. * consecutive.
* *
* Shorthand for `client.downloadAsIterable({ location: this })` * @param params Download parameters
*
* @link TelegramClient.downloadAsIterable * @link TelegramClient.downloadAsIterable
*/ */
downloadIterable(): AsyncIterableIterator<Buffer> { downloadIterable(
return this.client.downloadAsIterable({ location: this }) params?: Partial<FileDownloadParameters>,
): AsyncIterableIterator<Buffer> {
return this.client.downloadAsIterable({
...params,
location: this,
})
} }
/** /**
* Download a file and return it as a Node readable stream, * Download a file and return it as a Node readable stream,
* streaming file contents. * streaming file contents.
* *
* Shorthand for `client.downloadAsStream({ location: this })`
*
* @link TelegramClient.downloadAsStream * @link TelegramClient.downloadAsStream
*/ */
downloadStream(): Readable { downloadStream(params?: Partial<FileDownloadParameters>): Readable {
return this.client.downloadAsStream({ location: this }) return this.client.downloadAsStream({
...params,
location: this,
})
} }
/** /**
* Download a file and return its contents as a Buffer. * Download a file and return its contents as a Buffer.
* *
* Shorthand for `client.downloadAsBuffer({ location: this })` * @param params File download parameters
*
* @link TelegramClient.downloadAsBuffer * @link TelegramClient.downloadAsBuffer
*/ */
downloadBuffer(): Promise<Buffer> { downloadBuffer(params?: Partial<FileDownloadParameters>): Promise<Buffer> {
return this.client.downloadAsBuffer({ location: this }) return this.client.downloadAsBuffer({
...params,
location: this,
})
} }
/** /**
* Download a remote file to a local file (only for NodeJS). * Download a remote file to a local file (only for NodeJS).
* Promise will resolve once the download is complete. * Promise will resolve once the download is complete.
* *
* Shorthand for `client.downloadToFile(filename, { location: this })`
*
* @param filename Local file name * @param filename Local file name
* @param params File download parameters
* @link TelegramClient.downloadToFile * @link TelegramClient.downloadToFile
*/ */
downloadToFile(filename: string): Promise<void> { downloadToFile(
return this.client.downloadToFile(filename, { location: this }) filename: string,
params?: Partial<FileDownloadParameters>,
): Promise<void> {
return this.client.downloadToFile(filename, {
...params,
location: this,
fileSize: this.fileSize,
})
} }
} }

View file

@ -97,7 +97,7 @@ export interface FileDownloadParameters {
offset?: number 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 * By default, downloads the entire file
*/ */
limit?: number limit?: number

View file

@ -5,10 +5,9 @@ import { MtArgumentError } from '../types'
* for upload/download operations. * for upload/download operations.
*/ */
export function determinePartSize(fileSize: number): number { 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 <= 786432000) return 256 // 750 MB
if (fileSize <= 2097152000) return 512 // 2000 MB if (fileSize <= 2097152000) return 512 // 2000 MB
if (fileSize <= 4194304000) return 1024 // 4000 MB
throw new MtArgumentError('File is too large') throw new MtArgumentError('File is too large')
} }

View file

@ -35,7 +35,9 @@ class NodeReadable extends Readable {
return return
} }
if (this.push(res.value)) { if (this.push(res.value)) {
return doRead() doRead()
return
} }
this._reading = false this._reading = false
this._reader.releaseLock() this._reader.releaseLock()
@ -49,7 +51,9 @@ class NodeReadable extends Readable {
const promise = new Promise<void>((resolve) => { const promise = new Promise<void>((resolve) => {
this._doneReading = resolve this._doneReading = resolve
}) })
promise.then(() => this._handleDestroy(err, callback)) promise.then(() => {
this._handleDestroy(err, callback)
})
} else { } else {
this._handleDestroy(err, callback) this._handleDestroy(err, callback)
} }
@ -71,26 +75,6 @@ export function convertWebStreamToNodeReadable(
return new NodeReadable(webStream, opts) 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 { export function bufferToStream(buf: Buffer): Readable {
return new Readable({ return new Readable({
read() { read() {
@ -109,15 +93,17 @@ export async function readBytesFromStream(
let res = stream.read(size) let res = stream.read(size)
if (!res) { if (!res) {
return new Promise((resolve) => { return new Promise((resolve, reject) => {
stream.on('readable', function handler() { stream.on('readable', function handler() {
res = stream.read(size) res = stream.read(size)
if (res) { if (res) {
stream.off('readable', handler) stream.off('readable', handler)
stream.off('error', reject)
resolve(res) resolve(res)
} }
}) })
stream.on('error', reject)
}) })
} }

View file

@ -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',
)
})
})

View file

@ -245,7 +245,7 @@ export class BaseTelegramClient extends EventEmitter {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
protected _handleUpdate(update: tl.TypeUpdates): void {} protected _handleUpdate(update: tl.TypeUpdates): void {}
readonly log = new LogManager() readonly log = new LogManager('client')
readonly network: NetworkManager readonly network: NetworkManager
constructor(opts: BaseTelegramClientOptions) { constructor(opts: BaseTelegramClientOptions) {
@ -451,7 +451,6 @@ export class BaseTelegramClient extends EventEmitter {
* @param factory New transport factory * @param factory New transport factory
*/ */
changeTransport(factory: TransportFactory): void { changeTransport(factory: TransportFactory): void {
// todo
this.network.changeTransport(factory) this.network.changeTransport(factory)
} }

View file

@ -1,4 +1,8 @@
export { NetworkManagerExtraParams, RpcCallOptions } from './network-manager' export {
ConnectionKind,
NetworkManagerExtraParams,
RpcCallOptions,
} from './network-manager'
export * from './reconnection' export * from './reconnection'
export * from './session-connection' export * from './session-connection'
export * from './transports' export * from './transports'

View file

@ -8,7 +8,8 @@ import {
TlWriterMap, TlWriterMap,
} from '@mtcute/tl-runtime' } from '@mtcute/tl-runtime'
import { ControllablePromise, import {
ControllablePromise,
Deque, Deque,
getRandomInt, getRandomInt,
ICryptoProvider, ICryptoProvider,
@ -118,9 +119,18 @@ export class MtprotoSession {
pendingMessages = new LongMap<PendingMessage>() pendingMessages = new LongMap<PendingMessage>()
destroySessionIdToMsgId = new LongMap<Long>() destroySessionIdToMsgId = new LongMap<Long>()
lastPingRtt = NaN
lastPingTime = 0
lastPingMsgId = Long.ZERO
lastSessionCreatedUid = Long.ZERO
initConnectionCalled = false initConnectionCalled = false
authorizationPending = false authorizationPending = false
next429Timeout = 1000
current429Timeout?: NodeJS.Timeout
next429ResetTimeout?: NodeJS.Timeout
constructor( constructor(
readonly _crypto: ICryptoProvider, readonly _crypto: ICryptoProvider,
readonly log: Logger, readonly log: Logger,
@ -130,6 +140,15 @@ export class MtprotoSession {
this.log.prefix = `[SESSION ${this._sessionId.toString(16)}] ` 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 * Reset session by resetting auth key(s) and session state
*/ */
@ -140,7 +159,9 @@ export class MtprotoSession {
this._authKeyTempSecondary.reset() this._authKeyTempSecondary.reset()
} }
clearTimeout(this.current429Timeout)
this.resetState() this.resetState()
this.resetLastPing(true)
} }
/** /**
@ -221,11 +242,11 @@ export class MtprotoSession {
} }
getSeqNo(isContentRelated = true): number { getSeqNo(isContentRelated = true): number {
let seqNo = this._seqNo * 2 let seqNo = this._seqNo
if (isContentRelated) { if (isContentRelated) {
seqNo += 1 seqNo += 1
this._seqNo += 1 this._seqNo += 2
} }
return seqNo return seqNo
@ -293,4 +314,43 @@ export class MtprotoSession {
return messageId 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
}
} }

View file

@ -32,43 +32,48 @@ export class MultiSessionConnection extends EventEmitter {
protected _connections: SessionConnection[] = [] protected _connections: SessionConnection[] = []
setCount(count: number, doUpdate = true): void { setCount(count: number, connect = this.params.isMainConnection): void {
this._count = count this._count = count
if (doUpdate) this._updateConnections(true) this._updateConnections(connect)
} }
private _updateSessions(): void { 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( this._log.debug(
'updating sessions count: %d -> %d', 'updating sessions count: %d -> %d',
this._sessions.length, this._sessions.length,
this._count, 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 // case 1
if (this._sessions.length === this._count) return 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() this._updateSessions()
if (this._connections.length === this._count) return if (this._connections.length === this._count) return
@ -141,9 +146,8 @@ export class MultiSessionConnection extends EventEmitter {
// create new connections // create new connections
for (let i = this._connections.length; i < this._count; i++) { for (let i = this._connections.length; i < this._count; i++) {
const session = this.params.isMainConnection ? const session = this._sessions[i] // this.params.isMainConnection ? // :
this._sessions[i] : // this._sessions[0]
this._sessions[0]
const conn = new SessionConnection( const conn = new SessionConnection(
{ {
...this.params, ...this.params,
@ -185,9 +189,14 @@ export class MultiSessionConnection extends EventEmitter {
}) })
conn.on('usable', () => this.emit('usable', i)) conn.on('usable', () => this.emit('usable', i))
conn.on('request-auth', () => this.emit('request-auth', 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) this._connections.push(conn)
if (active) conn.connect() if (connect) conn.connect()
} }
} }
@ -207,31 +216,32 @@ export class MultiSessionConnection extends EventEmitter {
stack?: string, stack?: string,
timeout?: number, timeout?: number,
): Promise<tl.RpcCallReturn[T['_']]> { ): Promise<tl.RpcCallReturn[T['_']]> {
if (this.params.isMainConnection) { // if (this.params.isMainConnection) {
// find the least loaded connection // find the least loaded connection
let min = Infinity let min = Infinity
let minIdx = 0 let minIdx = 0
for (let i = 0; i < this._connections.length; i++) { for (let i = 0; i < this._connections.length; i++) {
const conn = this._connections[i] const conn = this._connections[i]
const total = const total =
conn._session.queuedRpc.length + conn._session.queuedRpc.length +
conn._session.pendingMessages.size() conn._session.pendingMessages.size()
if (total < min) { if (total < min) {
min = total min = total
minIdx = i minIdx = i
}
} }
return this._connections[minIdx].sendRpc(request, stack, timeout)
} }
return this._connections[minIdx].sendRpc(request, stack, timeout)
// }
// round-robin connections // round-robin connections
// since they all share the same session, it doesn't matter which one we use // since they all share the same session, it doesn't matter which one we use
return this._connections[ // the connection chosen here will only affect the first attempt at sending
this._nextConnection++ % this._connections.length // return this._connections[
].sendRpc(request, stack, timeout) // this._nextConnection++ % this._connections.length
// ].sendRpc(request, stack, timeout)
} }
connect(): void { connect(): void {
@ -308,4 +318,8 @@ export class MultiSessionConnection extends EventEmitter {
changeTransport(factory: TransportFactory): void { changeTransport(factory: TransportFactory): void {
this._connections.forEach((conn) => conn.changeTransport(factory)) this._connections.forEach((conn) => conn.changeTransport(factory))
} }
getPoolSize(): number {
return this._connections.length
}
} }

View file

@ -2,7 +2,12 @@ import { tl } from '@mtcute/tl'
import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime' import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
import { ITelegramStorage } from '../storage' import { ITelegramStorage } from '../storage'
import { ICryptoProvider, Logger, sleep } from '../utils' import {
createControllablePromise,
ICryptoProvider,
Logger,
sleep,
} from '../utils'
import { ConfigManager } from './config-manager' import { ConfigManager } from './config-manager'
import { MultiSessionConnection } from './multi-session-connection' import { MultiSessionConnection } from './multi-session-connection'
import { PersistentConnectionParams } from './persistent-connection' import { PersistentConnectionParams } from './persistent-connection'
@ -18,6 +23,16 @@ import { defaultTransportFactory, TransportFactory } from './transports'
export type ConnectionKind = 'main' | 'upload' | 'download' | 'downloadSmall' 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}. * Params passed into {@link NetworkManager} by {@link TelegramClient}.
* This type is intended for internal usage only. * This type is intended for internal usage only.
@ -62,8 +77,9 @@ const defaultConnectionCountDelegate: ConnectionCountDelegate = (
case 'upload': case 'upload':
return isPremium || (dcId !== 2 && dcId !== 4) ? 8 : 4 return isPremium || (dcId !== 2 && dcId !== 4) ? 8 : 4
case 'download': case 'download':
return 8 // fixme isPremium ? 8 : 2
case 'downloadSmall': case 'downloadSmall':
return isPremium ? 8 : 2 return 2
} }
} }
@ -79,13 +95,14 @@ export interface NetworkManagerExtraParams {
usePfs?: boolean 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: * Defaults to TDLib logic:
* - main: handled internally, **cannot be changed here** * - main: handled internally, **cannot be changed here**
* - upload: if premium or dc id is other than 2 or 4, then 8, otherwise 4 * - upload: if premium or dc id is other than 2 or 4, then 8, otherwise 4
* - download: if premium then 8, otherwise 2 * - download: if premium then 8, otherwise 2
* - downloadSmall: if premium then 8, otherwise 2 * - downloadSmall: 2
*/ */
connectionCount?: ConnectionCountDelegate connectionCount?: ConnectionCountDelegate
@ -174,7 +191,24 @@ export class DcConnectionManager {
) )
download = new MultiSessionConnection( 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( this.manager._connectionCount(
'download', 'download',
this._dc.id, this._dc.id,
@ -313,6 +347,10 @@ export class DcConnectionManager {
connection.on('request-auth', () => { connection.on('request-auth', () => {
this.main.requestAuth() this.main.requestAuth()
}) })
connection.on('error', (err, conn) => {
this.manager.params._emitError(err, conn)
})
} }
setIsPrimary(isPrimary: boolean): void { 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> { async loadKeys(): Promise<boolean> {
const permanent = await this.manager._storage.getAuthKeyFor(this.dcId) const permanent = await this.manager._storage.getAuthKeyFor(this.dcId)
@ -471,9 +525,9 @@ export class NetworkManager {
Promise.resolve(this._storage.getSelf()).then((self) => { Promise.resolve(this._storage.getSelf()).then((self) => {
if (self?.isBot) { if (self?.isBot) {
// bots may receive tmpSessions, which we should respect // bots may receive tmpSessions, which we should respect
this.config this.config.update(true).catch((e) => {
.update(true) this.params._emitError(e)
.catch((e) => this.params._emitError(e)) })
} }
}) })
}) })
@ -485,35 +539,54 @@ export class NetworkManager {
// this._cleanupPrimaryConnection() // this._cleanupPrimaryConnection()
// ) // )
dc.main.on('error', (err, conn) => this.params._emitError(err, conn))
dc.loadKeys() dc.loadKeys()
.catch((e) => this.params._emitError(e)) .catch((e) => {
.then(() => dc.main.ensureConnected()) this.params._emitError(e)
})
.then(() => {
dc.main.ensureConnected()
})
} }
private _dcCreationPromise: Record<number, Promise<void>> = {}
async _getOtherDc(dcId: number): Promise<DcConnectionManager> { async _getOtherDc(dcId: number): Promise<DcConnectionManager> {
if (!this._dcConnections[dcId]) { 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) this._log.debug('creating new DC %d', dcId)
const dcOption = await this.config.findOption({ try {
dcId, const dcOption = await this.config.findOption({
allowIpv6: this.params.useIpv6, dcId,
preferIpv6: this.params.useIpv6, allowIpv6: this.params.useIpv6,
allowMedia: false, preferIpv6: this.params.useIpv6,
cdn: false, allowMedia: true,
}) preferMedia: true,
cdn: false,
})
if (!dcOption) { if (!dcOption) {
throw new Error(`Could not find DC ${dcId}`) 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] return this._dcConnections[dcId]
@ -532,7 +605,7 @@ export class NetworkManager {
const dc = new DcConnectionManager(this, defaultDc.id, defaultDc) const dc = new DcConnectionManager(this, defaultDc.id, defaultDc)
this._dcConnections[defaultDc.id] = dc this._dcConnections[defaultDc.id] = dc
await this._switchPrimaryDc(dc) this._switchPrimaryDc(dc)
} }
private async _exportAuthTo(manager: DcConnectionManager): Promise<void> { 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> { async notifyLoggedIn(auth: tl.auth.TypeAuthorization): Promise<void> {
if ( if (
auth._ === 'auth.authorizationSignUpRequired' || auth._ === 'auth.authorizationSignUpRequired' ||
auth.user._ === 'userEmpty' auth.user._ === 'userEmpty'
) { return } ) {
return
}
if (auth.tmpSessions) { if (auth.tmpSessions) {
this._primaryDc?.main.setCount(auth.tmpSessions) this._primaryDc?.main.setCount(auth.tmpSessions)
} }
this.setIsPremium(auth.user.premium!)
await this.exportAuth() await this.exportAuth()
} }
@ -689,8 +774,12 @@ export class NetworkManager {
} catch (e: any) { } catch (e: any) {
lastError = e lastError = e
if (e instanceof tl.errors.InternalError) { if (e.code && !(e.code in CLIENT_ERRORS)) {
this._log.warn('Telegram is having internal issues: %s', e) this._log.warn(
'Telegram is having internal issues: %d %s, retrying',
e.code,
e.message,
)
if (e.message === 'WORKER_BUSY_TOO_LONG_RETRY') { if (e.message === 'WORKER_BUSY_TOO_LONG_RETRY') {
// according to tdlib, "it is dangerous to resend query without timeout, so use 1" // 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 { destroy(): void {
for (const dc of Object.values(this._dcConnections)) { for (const dc of Object.values(this._dcConnections)) {
dc.main.destroy() dc.main.destroy()

View file

@ -86,11 +86,29 @@ export abstract class PersistentConnection extends EventEmitter {
onTransportReady(): void { onTransportReady(): void {
// transport ready does not mean actual mtproto is ready // transport ready does not mean actual mtproto is ready
if (this._sendOnceConnected.length) { 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() this.onConnected()
} }
@ -125,7 +143,12 @@ export abstract class PersistentConnection extends EventEmitter {
this._consequentFails, this._consequentFails,
this._previousWait, this._previousWait,
) )
if (wait === false) return this.destroy()
if (wait === false) {
this.destroy()
return
}
this.emit('wait', wait) this.emit('wait', wait)

View file

@ -75,19 +75,13 @@ export class SessionConnection extends PersistentConnection {
NodeJS.Timeout 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 _usePfs = this.params.usePfs ?? false
private _isPfsBindingPending = false private _isPfsBindingPending = false
private _isPfsBindingPendingInBackground = false private _isPfsBindingPendingInBackground = false
private _pfsUpdateTimeout?: NodeJS.Timeout private _pfsUpdateTimeout?: NodeJS.Timeout
private _inactivityPendingFlush = false
private _readerMap: TlReaderMap private _readerMap: TlReaderMap
private _writerMap: TlWriterMap private _writerMap: TlWriterMap
@ -149,9 +143,7 @@ export class SessionConnection extends PersistentConnection {
reset(forever = false): void { reset(forever = false): void {
this._session.initConnectionCalled = false this._session.initConnectionCalled = false
this._resetLastPing(true)
this._flushTimer.reset() this._flushTimer.reset()
clearTimeout(this._current429Timeout!)
if (forever) { if (forever) {
this.removeAllListeners() this.removeAllListeners()
@ -242,26 +234,15 @@ export class SessionConnection extends PersistentConnection {
this._onAllFailed(`transport error ${error.code}`) this._onAllFailed(`transport error ${error.code}`)
if (error.code === 429) { if (error.code === 429) {
// all active queries must be resent this._session.onTransportFlood(
const timeout = this._next429Timeout this.emit.bind(this, 'flood-done'),
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,
) )
return return
} }
} }
this.emit('api-error', error) this.emit('error', error)
} }
protected onConnectionUsable() { protected onConnectionUsable() {
@ -622,9 +603,7 @@ export class SessionConnection extends PersistentConnection {
// rpc_result // rpc_result
message.uint() message.uint()
this._sendAck(messageId) return this._onRpcResult(messageId, message)
return this._onRpcResult(message)
} }
// we are safe.. i guess // we are safe.. i guess
@ -648,7 +627,7 @@ export class SessionConnection extends PersistentConnection {
} }
const message = message_ as mtp.TlObject 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) this._session.recentIncomingMsgIds.add(messageId)
switch (message._) { 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) { if (this._usable && this.params.inactivityTimeout) {
this._rescheduleInactivity() this._rescheduleInactivity()
} }
@ -790,9 +769,12 @@ export class SessionConnection extends PersistentConnection {
return return
} }
this._sendAck(messageId)
// special case for auth key binding // special case for auth key binding
if (msg._ !== 'rpc') { if (msg._ !== 'rpc') {
if (msg._ === 'bind') { if (msg._ === 'bind') {
this._sendAck(messageId)
msg.promise.resolve(message.object()) msg.promise.resolve(message.object())
return return
@ -902,7 +884,9 @@ export class SessionConnection extends PersistentConnection {
const msg = this._session.pendingMessages.get(msgId) const msg = this._session.pendingMessages.get(msgId)
if (!msg) { 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 return
} }
@ -969,7 +953,6 @@ export class SessionConnection extends PersistentConnection {
// e.g. when server returns 429 // e.g. when server returns 429
// most service messages can be omitted as stale // most service messages can be omitted as stale
this._resetLastPing(true)
for (const msgId of this._session.pendingMessages.keys()) { for (const msgId of this._session.pendingMessages.keys()) {
const info = this._session.pendingMessages.get(msgId)! const info = this._session.pendingMessages.get(msgId)!
@ -1025,7 +1008,7 @@ export class SessionConnection extends PersistentConnection {
reason, reason,
) )
// restart ping // restart ping
this._resetLastPing(true) this._session.resetLastPing(true)
break break
case 'rpc': { case 'rpc': {
@ -1093,16 +1076,6 @@ export class SessionConnection extends PersistentConnection {
this._session.pendingMessages.delete(msgId) 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 { private _registerOutgoingMsgId(msgId: Long): Long {
this._session.recentOutgoingMsgIds.add(msgId) this._session.recentOutgoingMsgIds.add(msgId)
@ -1142,8 +1115,8 @@ export class SessionConnection extends PersistentConnection {
) )
} }
const rtt = Date.now() - this._lastPingTime const rtt = Date.now() - this._session.lastPingTime
this._lastPingRtt = rtt this._session.lastPingRtt = rtt
if (info.containerId.neq(msgId)) { if (info.containerId.neq(msgId)) {
this._onMessageAcked(info.containerId) this._onMessageAcked(info.containerId)
@ -1155,7 +1128,7 @@ export class SessionConnection extends PersistentConnection {
pingId, pingId,
rtt, rtt,
) )
this._resetLastPing() this._session.resetLastPing()
} }
private _onBadServerSalt(msg: mtp.RawMt_bad_server_salt): void { private _onBadServerSalt(msg: mtp.RawMt_bad_server_salt): void {
@ -1211,7 +1184,7 @@ export class SessionConnection extends PersistentConnection {
serverSalt, serverSalt,
uniqueId, uniqueId,
}: mtp.RawMt_new_session_created): void { }: mtp.RawMt_new_session_created): void {
if (uniqueId.eq(this._lastSessionCreatedUid)) { if (uniqueId.eq(this._session.lastSessionCreatedUid)) {
this.log.debug( this.log.debug(
'received new_session_created with the same uid = %l, ignoring', 'received new_session_created with the same uid = %l, ignoring',
uniqueId, uniqueId,
@ -1221,7 +1194,7 @@ export class SessionConnection extends PersistentConnection {
} }
if ( if (
!this._lastSessionCreatedUid.isZero() && !this._session.lastSessionCreatedUid.isZero() &&
!this.params.disableUpdates !this.params.disableUpdates
) { ) {
// force the client to fetch missed updates // force the client to fetch missed updates
@ -1277,10 +1250,12 @@ export class SessionConnection extends PersistentConnection {
const info = this._session.pendingMessages.get(msgId) const info = this._session.pendingMessages.get(msgId)
if (!info) { if (!info) {
this.log.info( if (!this._session.recentOutgoingMsgIds.has(msgId)) {
'received message info about unknown message %l', this.log.warn(
msgId, 'received message info about unknown message %l',
) msgId,
)
}
return return
} }
@ -1417,7 +1392,7 @@ export class SessionConnection extends PersistentConnection {
this._session.queuedAcks.push(msgId) this._session.queuedAcks.push(msgId)
if (this._session.queuedAcks.length >= 100) { 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() { protected _onInactivityTimeout() {
// we should send all pending acks and other service messages // we should send all pending acks and other service messages
// before dropping the connection // before dropping the connection
if (!this._hasPendingServiceMessages) { if (!this._session.hasPendingMessages) {
this.log.debug('no pending service messages, closing connection') this.log.debug('no pending service messages, closing connection')
super._onInactivityTimeout() super._onInactivityTimeout()
return return
} }
this._flush(() => { this._inactivityPendingFlush = true
if (this._hasPendingServiceMessages) { this._flush()
// 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()
})
} }
private _flush(callback?: () => void): void { flushWhenIdle(): void {
this._flushTimer.emitWhenIdle()
}
private _flush(): void {
if ( if (
!this._session._authKey.ready || !this._session._authKey.ready ||
this._isPfsBindingPending || this._isPfsBindingPending ||
this._current429Timeout this._session.current429Timeout
) { ) {
this.log.debug( this.log.debug(
'skipping flush, connection is not usable (auth key ready = %b, pfs binding pending = %b, 429 timeout = %b)', 'skipping flush, connection is not usable (auth key ready = %b, pfs binding pending = %b, 429 timeout = %b)',
this._session._authKey.ready, this._session._authKey.ready,
this._isPfsBindingPending, this._isPfsBindingPending,
Boolean(this._current429Timeout), Boolean(this._session.current429Timeout),
) )
// it will be flushed once connection is usable // it will be flushed once connection is usable
@ -1636,7 +1598,7 @@ export class SessionConnection extends PersistentConnection {
} }
try { try {
this._doFlush(callback) this._doFlush()
} catch (e: any) { } catch (e: any) {
this.log.error('flush error: %s', e.stack) this.log.error('flush error: %s', e.stack)
// should not happen unless there's a bug in the code // should not happen unless there's a bug in the code
@ -1645,19 +1607,22 @@ export class SessionConnection extends PersistentConnection {
// schedule next flush // schedule next flush
// if there are more queued requests, flush immediately // if there are more queued requests, flush immediately
// (they likely didn't fit into one message) // (they likely didn't fit into one message)
if ( if (this._session.hasPendingMessages) {
this._session.queuedRpc.length || // we schedule it on the next tick, so we can load-balance
this._session.queuedAcks.length || // between multiple connections using the same session
this._session.queuedStateReq.length || this._flushTimer.emitWhenIdle()
this._session.queuedResendReq.length } else if (this._inactivityPendingFlush) {
) { this.log.debug('pending messages sent, closing connection')
this._flush(callback) this._flushTimer.reset()
this._inactivityPendingFlush = false
super._onInactivityTimeout()
} else { } 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( this.log.debug(
'flushing send queue. queued rpc: %d', 'flushing send queue. queued rpc: %d',
this._session.queuedRpc.length, this._session.queuedRpc.length,
@ -1716,13 +1681,15 @@ export class SessionConnection extends PersistentConnection {
const getStateTime = now + 1500 const getStateTime = now + 1500
if (now - this._lastPingTime > 60000) { if (now - this._session.lastPingTime > 60000) {
if (!this._lastPingMsgId.isZero()) { if (!this._session.lastPingMsgId.isZero()) {
this.log.warn( this.log.warn(
"didn't receive pong for previous ping (msg_id = %l)", "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() pingId = randomLong()
@ -1731,7 +1698,7 @@ export class SessionConnection extends PersistentConnection {
pingId, pingId,
} }
this._lastPingTime = Date.now() this._session.lastPingTime = Date.now()
pingRequest = TlBinaryWriter.serializeObject(this._writerMap, obj) pingRequest = TlBinaryWriter.serializeObject(this._writerMap, obj)
containerSize += pingRequest.length + 16 containerSize += pingRequest.length + 16
@ -1836,13 +1803,17 @@ export class SessionConnection extends PersistentConnection {
// if message was already assigned a msg_id, // if message was already assigned a msg_id,
// we must wrap it in a container with a newer msg_id // we must wrap it in a container with a newer msg_id
if (msg.msgId) forceContainer = true 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 packetSize += containerSize
messageCount += containerMessageCount + rpcToSend.length messageCount += containerMessageCount + rpcToSend.length
if (!messageCount) { if (!messageCount) {
this.log.debug('flush failed: nothing to flush') this.log.debug('flush did not happen: nothing to flush')
return return
} }
@ -1875,7 +1846,7 @@ export class SessionConnection extends PersistentConnection {
pingMsgId = this._registerOutgoingMsgId( pingMsgId = this._registerOutgoingMsgId(
this._session.writeMessage(writer, pingRequest), this._session.writeMessage(writer, pingRequest),
) )
this._lastPingMsgId = pingMsgId this._session.lastPingMsgId = pingMsgId
const pingPending: PendingMessage = { const pingPending: PendingMessage = {
_: 'ping', _: 'ping',
pingId: pingId!, pingId: pingId!,
@ -2065,7 +2036,6 @@ export class SessionConnection extends PersistentConnection {
this._session this._session
.encryptMessage(result) .encryptMessage(result)
.then((enc) => this.send(enc)) .then((enc) => this.send(enc))
.then(callback)
.catch((err) => { .catch((err) => {
this.log.error( this.log.error(
'error while sending pending messages (root msg_id = %l): %s', 'error while sending pending messages (root msg_id = %l): %s',

View file

@ -48,7 +48,9 @@ export abstract class BaseTcpTransport
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
connect(dc: tl.RawDcOption, testMode: boolean): void { 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) { if (!this.packetCodecInitialized) {
this._packetCodec.setup?.(this._crypto, this.log) this._packetCodec.setup?.(this._crypto, this.log)
@ -69,7 +71,9 @@ export abstract class BaseTcpTransport
this.handleConnect.bind(this), 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('error', this.handleError.bind(this))
this._socket.on('close', this.close.bind(this)) this._socket.on('close', this.close.bind(this))
} }
@ -87,7 +91,7 @@ export abstract class BaseTcpTransport
this._packetCodec.reset() this._packetCodec.reset()
} }
async handleError(error: Error): Promise<void> { handleError(error: Error): void {
this.log.error('error: %s', error.stack) this.log.error('error: %s', error.stack)
this.emit('error', error) this.emit('error', error)
} }
@ -99,7 +103,11 @@ export abstract class BaseTcpTransport
if (initialMessage.length) { if (initialMessage.length) {
this._socket!.write(initialMessage, (err) => { this._socket!.write(initialMessage, (err) => {
if (err) { if (err) {
this.emit('error', err) this.log.error(
'failed to write initial message: %s',
err.stack,
)
this.emit('error')
this.close() this.close()
} else { } else {
this._state = TransportState.Ready this._state = TransportState.Ready
@ -113,12 +121,20 @@ export abstract class BaseTcpTransport
} }
async send(bytes: Buffer): Promise<void> { 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) const framed = await this._packetCodec.encode(bytes)
return new Promise((res, rej) => { return new Promise((resolve, reject) => {
this._socket!.write(framed, (err) => (err ? rej(err) : res())) this._socket!.write(framed, (error) => {
if (error) {
reject(error)
} else {
resolve()
}
})
}) })
} }
} }

View file

@ -8,12 +8,6 @@ export interface IEncryptionScheme {
decrypt(data: Buffer): MaybeAsync<Buffer> decrypt(data: Buffer): MaybeAsync<Buffer>
} }
export interface IHashMethod {
update(data: Buffer): MaybeAsync<void>
digest(): MaybeAsync<Buffer>
}
export interface ICryptoProvider { export interface ICryptoProvider {
initialize?(): MaybeAsync<void> initialize?(): MaybeAsync<void>
@ -38,8 +32,6 @@ export interface ICryptoProvider {
createAesEcb(key: Buffer): IEncryptionScheme createAesEcb(key: Buffer): IEncryptionScheme
createMd5(): IHashMethod
factorizePQ(pq: Buffer): MaybeAsync<[Buffer, Buffer]> factorizePQ(pq: Buffer): MaybeAsync<[Buffer, Buffer]>
} }

View file

@ -3,7 +3,6 @@ import {
BaseCryptoProvider, BaseCryptoProvider,
ICryptoProvider, ICryptoProvider,
IEncryptionScheme, IEncryptionScheme,
IHashMethod,
} from './abstract' } from './abstract'
// eslint-disable-next-line @typescript-eslint/no-explicit-any // 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> { hmacSha256(data: Buffer, key: Buffer): MaybeAsync<Buffer> {
const hmac = forge.hmac.create() const hmac = forge.hmac.create()
hmac.start('sha256', key.toString('binary')) hmac.start('sha256', key.toString('binary'))

View file

@ -11,7 +11,6 @@ import {
BaseCryptoProvider, BaseCryptoProvider,
ICryptoProvider, ICryptoProvider,
IEncryptionScheme, IEncryptionScheme,
IHashMethod,
} from './abstract' } from './abstract'
export class NodeCryptoProvider export class NodeCryptoProvider
@ -83,10 +82,6 @@ export class NodeCryptoProvider
return createHash('sha256').update(data).digest() return createHash('sha256').update(data).digest()
} }
createMd5(): IHashMethod {
return createHash('md5') as unknown as IHashMethod
}
hmacSha256(data: Buffer, key: Buffer): MaybeAsync<Buffer> { hmacSha256(data: Buffer, key: Buffer): MaybeAsync<Buffer> {
return createHmac('sha256', key).update(data).digest() return createHmac('sha256', key).update(data).digest()
} }

View file

@ -84,9 +84,16 @@ export class Logger {
if (m === '%j') { if (m === '%j') {
return JSON.stringify(val, (k, v) => { 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') 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 return str
} }
@ -143,10 +150,10 @@ export class LogManager extends Logger {
static DEBUG = 4 static DEBUG = 4
static VERBOSE = 5 static VERBOSE = 5
constructor() { constructor(tag = 'base') {
// workaround because we cant pass this to super // workaround because we cant pass this to super
// eslint-disable-next-line @typescript-eslint/no-explicit-any // 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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
;(this as any).mgr = this ;(this as any).mgr = this
} }

View file

@ -164,31 +164,6 @@ export function testCryptoProvider(c: ICryptoProvider): void {
'99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b', '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', () => { describe('NodeCryptoProvider', () => {

View file

@ -34,11 +34,11 @@ importers:
specifier: 8.5.4 specifier: 8.5.4
version: 8.5.4 version: 8.5.4
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: 5.59.8 specifier: 6.4.0
version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.42.0)(typescript@5.0.4) version: 6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.0.4)
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: 5.59.8 specifier: 6.4.0
version: 5.59.8(eslint@8.42.0)(typescript@5.0.4) version: 6.4.0(eslint@8.47.0)(typescript@5.0.4)
chai: chai:
specifier: 4.3.7 specifier: 4.3.7
version: 4.3.7 version: 4.3.7
@ -46,23 +46,23 @@ importers:
specifier: 3.2.0 specifier: 3.2.0
version: 3.2.0 version: 3.2.0
eslint: eslint:
specifier: 8.42.0 specifier: 8.47.0
version: 8.42.0 version: 8.47.0
eslint-config-prettier: eslint-config-prettier:
specifier: 8.8.0 specifier: 8.8.0
version: 8.8.0(eslint@8.42.0) version: 8.8.0(eslint@8.47.0)
eslint-import-resolver-typescript: eslint-import-resolver-typescript:
specifier: 3.5.5 specifier: 3.6.0
version: 3.5.5(@typescript-eslint/parser@5.59.8)(eslint-plugin-import@2.27.5)(eslint@8.42.0) version: 3.6.0(@typescript-eslint/parser@6.4.0)(eslint-plugin-import@2.28.0)(eslint@8.47.0)
eslint-plugin-ascii: eslint-plugin-ascii:
specifier: 1.0.0 specifier: 1.0.0
version: 1.0.0 version: 1.0.0
eslint-plugin-import: eslint-plugin-import:
specifier: 2.27.5 specifier: 2.28.0
version: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.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: eslint-plugin-simple-import-sort:
specifier: 10.0.0 specifier: 10.0.0
version: 10.0.0(eslint@8.42.0) version: 10.0.0(eslint@8.47.0)
glob: glob:
specifier: 10.2.6 specifier: 10.2.6
version: 10.2.6 version: 10.2.6
@ -351,6 +351,11 @@ importers:
packages: 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: /@ampproject/remapping@2.2.0:
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -723,14 +728,14 @@ packages:
'@jridgewell/trace-mapping': 0.3.9 '@jridgewell/trace-mapping': 0.3.9
dev: true 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==} resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
dependencies: dependencies:
eslint: 8.42.0 eslint: 8.47.0
eslint-visitor-keys: 3.4.1 eslint-visitor-keys: 3.4.3
dev: true dev: true
/@eslint-community/regexpp@4.5.1: /@eslint-community/regexpp@4.5.1:
@ -738,13 +743,18 @@ packages:
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
dev: true dev: true
/@eslint/eslintrc@2.0.3: /@eslint-community/regexpp@4.6.2:
resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} 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} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4(supports-color@8.1.1)
espree: 9.5.2 espree: 9.6.1
globals: 13.20.0 globals: 13.20.0
ignore: 5.2.0 ignore: 5.2.0
import-fresh: 3.3.0 import-fresh: 3.3.0
@ -755,8 +765,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@eslint/js@8.42.0: /@eslint/js@8.47.0:
resolution: {integrity: sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==} resolution: {integrity: sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
@ -886,18 +896,6 @@ packages:
dev: true dev: true
optional: 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: /@tokenizer/token@0.3.0:
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
dev: false dev: false
@ -941,8 +939,8 @@ packages:
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
dev: true dev: true
/@types/json-schema@7.0.11: /@types/json-schema@7.0.12:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
dev: true dev: true
/@types/json5@0.0.29: /@types/json5@0.0.29:
@ -984,133 +982,134 @@ packages:
'@types/node': 18.16.0 '@types/node': 18.16.0
dev: true dev: true
/@typescript-eslint/eslint-plugin@5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.42.0)(typescript@5.0.4): /@typescript-eslint/eslint-plugin@6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.0.4):
resolution: {integrity: sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==} resolution: {integrity: sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^5.0.0 '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^7.0.0 || ^8.0.0
typescript: '*' typescript: '*'
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@eslint-community/regexpp': 4.5.1 '@eslint-community/regexpp': 4.5.1
'@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)
'@typescript-eslint/scope-manager': 5.59.8 '@typescript-eslint/scope-manager': 6.4.0
'@typescript-eslint/type-utils': 5.59.8(eslint@8.42.0)(typescript@5.0.4) '@typescript-eslint/type-utils': 6.4.0(eslint@8.47.0)(typescript@5.0.4)
'@typescript-eslint/utils': 5.59.8(eslint@8.42.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) debug: 4.3.4(supports-color@8.1.1)
eslint: 8.42.0 eslint: 8.47.0
grapheme-splitter: 1.0.4 graphemer: 1.4.0
ignore: 5.2.0 ignore: 5.2.4
natural-compare-lite: 1.4.0 natural-compare: 1.4.0
semver: 7.5.1 semver: 7.5.4
tsutils: 3.21.0(typescript@5.0.4) ts-api-utils: 1.0.1(typescript@5.0.4)
typescript: 5.0.4 typescript: 5.0.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@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):
resolution: {integrity: sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==} resolution: {integrity: sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^7.0.0 || ^8.0.0
typescript: '*' typescript: '*'
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/scope-manager': 5.59.8 '@typescript-eslint/scope-manager': 6.4.0
'@typescript-eslint/types': 5.59.8 '@typescript-eslint/types': 6.4.0
'@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) '@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) debug: 4.3.4(supports-color@8.1.1)
eslint: 8.42.0 eslint: 8.47.0
typescript: 5.0.4 typescript: 5.0.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/scope-manager@5.59.8: /@typescript-eslint/scope-manager@6.4.0:
resolution: {integrity: sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==} resolution: {integrity: sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^16.0.0 || >=18.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 5.59.8 '@typescript-eslint/types': 6.4.0
'@typescript-eslint/visitor-keys': 5.59.8 '@typescript-eslint/visitor-keys': 6.4.0
dev: true dev: true
/@typescript-eslint/type-utils@5.59.8(eslint@8.42.0)(typescript@5.0.4): /@typescript-eslint/type-utils@6.4.0(eslint@8.47.0)(typescript@5.0.4):
resolution: {integrity: sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==} resolution: {integrity: sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
eslint: '*' eslint: ^7.0.0 || ^8.0.0
typescript: '*' typescript: '*'
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.0.4)
'@typescript-eslint/utils': 5.59.8(eslint@8.42.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) debug: 4.3.4(supports-color@8.1.1)
eslint: 8.42.0 eslint: 8.47.0
tsutils: 3.21.0(typescript@5.0.4) ts-api-utils: 1.0.1(typescript@5.0.4)
typescript: 5.0.4 typescript: 5.0.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/types@5.59.8: /@typescript-eslint/types@6.4.0:
resolution: {integrity: sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==} resolution: {integrity: sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^16.0.0 || >=18.0.0}
dev: true dev: true
/@typescript-eslint/typescript-estree@5.59.8(typescript@5.0.4): /@typescript-eslint/typescript-estree@6.4.0(typescript@5.0.4):
resolution: {integrity: sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==} resolution: {integrity: sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/types': 5.59.8 '@typescript-eslint/types': 6.4.0
'@typescript-eslint/visitor-keys': 5.59.8 '@typescript-eslint/visitor-keys': 6.4.0
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4(supports-color@8.1.1)
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
semver: 7.5.1 semver: 7.5.4
tsutils: 3.21.0(typescript@5.0.4) ts-api-utils: 1.0.1(typescript@5.0.4)
typescript: 5.0.4 typescript: 5.0.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/utils@5.59.8(eslint@8.42.0)(typescript@5.0.4): /@typescript-eslint/utils@6.4.0(eslint@8.47.0)(typescript@5.0.4):
resolution: {integrity: sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==} resolution: {integrity: sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^7.0.0 || ^8.0.0
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.42.0) '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0)
'@types/json-schema': 7.0.11 '@types/json-schema': 7.0.12
'@types/semver': 7.5.0 '@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 5.59.8 '@typescript-eslint/scope-manager': 6.4.0
'@typescript-eslint/types': 5.59.8 '@typescript-eslint/types': 6.4.0
'@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.0.4)
eslint: 8.42.0 eslint: 8.47.0
eslint-scope: 5.1.1 semver: 7.5.4
semver: 7.5.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
dev: true dev: true
/@typescript-eslint/visitor-keys@5.59.8: /@typescript-eslint/visitor-keys@6.4.0:
resolution: {integrity: sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==} resolution: {integrity: sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^16.0.0 || >=18.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 5.59.8 '@typescript-eslint/types': 6.4.0
eslint-visitor-keys: 3.4.1 eslint-visitor-keys: 3.4.1
dev: true dev: true
@ -1126,12 +1125,12 @@ packages:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: false 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==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
acorn: 8.8.2 acorn: 8.10.0
dev: true dev: true
/acorn-walk@8.2.0: /acorn-walk@8.2.0:
@ -1139,6 +1138,12 @@ packages:
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
dev: true 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: /acorn@8.8.2:
resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
@ -1322,6 +1327,17 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true 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: /array.prototype.flat@1.3.1:
resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -1379,6 +1395,7 @@ packages:
/big-integer@1.6.51: /big-integer@1.6.51:
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
dev: false
/binary-extensions@2.2.0: /binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
@ -1403,13 +1420,6 @@ packages:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
dev: true 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: /brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies: dependencies:
@ -1451,13 +1461,6 @@ packages:
ieee754: 1.2.1 ieee754: 1.2.1
dev: false 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: /cacache@16.0.7:
resolution: {integrity: sha512-a4zfQpp5vm4Ipdvbj+ZrPonikRhm6WBEd4zT1Yc1DXsmAxrPgDwWBLF/u/wTVXSFPIgOJ1U3ghSa2Xm4s3h28w==} resolution: {integrity: sha512-a4zfQpp5vm4Ipdvbj+ZrPonikRhm6WBEd4zT1Yc1DXsmAxrPgDwWBLF/u/wTVXSFPIgOJ1U3ghSa2Xm4s3h28w==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} 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==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true 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: /default-require-extensions@3.0.0:
resolution: {integrity: sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==} resolution: {integrity: sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1930,11 +1915,6 @@ packages:
strip-bom: 4.0.0 strip-bom: 4.0.0
dev: true dev: true
/define-lazy-prop@3.0.0:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
dev: true
/define-properties@1.2.0: /define-properties@1.2.0:
resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -2204,13 +2184,13 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true 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==} resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
eslint: '>=7.0.0' eslint: '>=7.0.0'
dependencies: dependencies:
eslint: 8.42.0 eslint: 8.47.0
dev: true dev: true
/eslint-import-resolver-node@0.3.7: /eslint-import-resolver-node@0.3.7:
@ -2218,13 +2198,13 @@ packages:
dependencies: dependencies:
debug: 3.2.7 debug: 3.2.7
is-core-module: 2.12.1 is-core-module: 2.12.1
resolve: 1.22.2 resolve: 1.22.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true 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): /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-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} resolution: {integrity: sha512-QTHR9ddNnn35RTxlaEnx2gCxqFlF2SEN0SE2d17SqwyM7YOSI2GHWRYp5BiRkObTUNYPupC/3Fq2a0PpT+EKpg==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: '*' eslint: '*'
@ -2232,14 +2212,13 @@ packages:
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4(supports-color@8.1.1)
enhanced-resolve: 5.14.1 enhanced-resolve: 5.14.1
eslint: 8.42.0 eslint: 8.47.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-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.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.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 get-tsconfig: 4.6.0
globby: 13.1.4
is-core-module: 2.12.1 is-core-module: 2.12.1
is-glob: 4.0.3 is-glob: 4.0.3
synckit: 0.8.5
transitivePeerDependencies: transitivePeerDependencies:
- '@typescript-eslint/parser' - '@typescript-eslint/parser'
- eslint-import-resolver-node - eslint-import-resolver-node
@ -2247,7 +2226,7 @@ packages:
- supports-color - supports-color
dev: true 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==} resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
engines: {node: '>=4'} engines: {node: '>=4'}
peerDependencies: peerDependencies:
@ -2268,11 +2247,11 @@ packages:
eslint-import-resolver-webpack: eslint-import-resolver-webpack:
optional: true optional: true
dependencies: 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 debug: 3.2.7
eslint: 8.42.0 eslint: 8.47.0
eslint-import-resolver-node: 0.3.7 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: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -2281,8 +2260,8 @@ packages:
resolution: {integrity: sha512-NT2asS7tLkXMKBk0GuX6eDUZvb5DWTFDCt7R6a8tvWs5P0my2ybxmCFy3Afxgdcam+wQRAn8JrldLmcK0H5Axg==} resolution: {integrity: sha512-NT2asS7tLkXMKBk0GuX6eDUZvb5DWTFDCt7R6a8tvWs5P0my2ybxmCFy3Afxgdcam+wQRAn8JrldLmcK0H5Axg==}
dev: true 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): /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-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} resolution: {integrity: sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==}
engines: {node: '>=4'} engines: {node: '>=4'}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': '*' '@typescript-eslint/parser': '*'
@ -2291,22 +2270,25 @@ packages:
'@typescript-eslint/parser': '@typescript-eslint/parser':
optional: true optional: true
dependencies: 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-includes: 3.1.6
array.prototype.findlastindex: 1.2.2
array.prototype.flat: 1.3.1 array.prototype.flat: 1.3.1
array.prototype.flatmap: 1.3.1 array.prototype.flatmap: 1.3.1
debug: 3.2.7 debug: 3.2.7
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.42.0 eslint: 8.47.0
eslint-import-resolver-node: 0.3.7 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 has: 1.0.3
is-core-module: 2.12.1 is-core-module: 2.12.1
is-glob: 4.0.3 is-glob: 4.0.3
minimatch: 3.1.2 minimatch: 3.1.2
object.fromentries: 2.0.6
object.groupby: 1.0.0
object.values: 1.1.6 object.values: 1.1.6
resolve: 1.22.2 resolve: 1.22.4
semver: 6.3.0 semver: 6.3.1
tsconfig-paths: 3.14.2 tsconfig-paths: 3.14.2
transitivePeerDependencies: transitivePeerDependencies:
- eslint-import-resolver-typescript - eslint-import-resolver-typescript
@ -2314,24 +2296,16 @@ packages:
- supports-color - supports-color
dev: true 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==} resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==}
peerDependencies: peerDependencies:
eslint: '>=5.0.0' eslint: '>=5.0.0'
dependencies: dependencies:
eslint: 8.42.0 eslint: 8.47.0
dev: true dev: true
/eslint-scope@5.1.1: /eslint-scope@7.2.2:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
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==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
esrecurse: 4.3.0 esrecurse: 4.3.0
@ -2343,15 +2317,20 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/eslint@8.42.0: /eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==} 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} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.42.0) '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0)
'@eslint-community/regexpp': 4.5.1 '@eslint-community/regexpp': 4.6.2
'@eslint/eslintrc': 2.0.3 '@eslint/eslintrc': 2.1.2
'@eslint/js': 8.42.0 '@eslint/js': 8.47.0
'@humanwhocodes/config-array': 0.11.10 '@humanwhocodes/config-array': 0.11.10
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8 '@nodelib/fs.walk': 1.2.8
@ -2361,9 +2340,9 @@ packages:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4(supports-color@8.1.1)
doctrine: 3.0.0 doctrine: 3.0.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 7.2.0 eslint-scope: 7.2.2
eslint-visitor-keys: 3.4.1 eslint-visitor-keys: 3.4.3
espree: 9.5.2 espree: 9.6.1
esquery: 1.5.0 esquery: 1.5.0
esutils: 2.0.3 esutils: 2.0.3
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
@ -2373,7 +2352,6 @@ packages:
globals: 13.20.0 globals: 13.20.0
graphemer: 1.4.0 graphemer: 1.4.0
ignore: 5.2.0 ignore: 5.2.0
import-fresh: 3.3.0
imurmurhash: 0.1.4 imurmurhash: 0.1.4
is-glob: 4.0.3 is-glob: 4.0.3
is-path-inside: 3.0.3 is-path-inside: 3.0.3
@ -2383,21 +2361,20 @@ packages:
lodash.merge: 4.6.2 lodash.merge: 4.6.2
minimatch: 3.1.2 minimatch: 3.1.2
natural-compare: 1.4.0 natural-compare: 1.4.0
optionator: 0.9.1 optionator: 0.9.3
strip-ansi: 6.0.1 strip-ansi: 6.0.1
strip-json-comments: 3.1.1
text-table: 0.2.0 text-table: 0.2.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/espree@9.5.2: /espree@9.6.1:
resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
acorn: 8.8.2 acorn: 8.10.0
acorn-jsx: 5.3.2(acorn@8.8.2) acorn-jsx: 5.3.2(acorn@8.10.0)
eslint-visitor-keys: 3.4.1 eslint-visitor-keys: 3.4.3
dev: true dev: true
/esprima@4.0.1: /esprima@4.0.1:
@ -2420,11 +2397,6 @@ packages:
estraverse: 5.3.0 estraverse: 5.3.0
dev: true dev: true
/estraverse@4.3.0:
resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
engines: {node: '>=4.0'}
dev: true
/estraverse@5.3.0: /estraverse@5.3.0:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
@ -2484,8 +2456,8 @@ packages:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true dev: true
/fast-glob@3.2.11: /fast-glob@3.2.12:
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
engines: {node: '>=8.6.0'} engines: {node: '>=8.6.0'}
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@ -2495,8 +2467,8 @@ packages:
micromatch: 4.0.5 micromatch: 4.0.5
dev: true dev: true
/fast-glob@3.2.12: /fast-glob@3.3.1:
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
engines: {node: '>=8.6.0'} engines: {node: '>=8.6.0'}
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@ -2834,23 +2806,12 @@ packages:
dependencies: dependencies:
array-union: 2.1.0 array-union: 2.1.0
dir-glob: 3.0.1 dir-glob: 3.0.1
fast-glob: 3.2.11 fast-glob: 3.2.12
ignore: 5.2.0 ignore: 5.2.0
merge2: 1.4.1 merge2: 1.4.1
slash: 3.0.0 slash: 3.0.0
dev: true 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: /gopd@1.0.1:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
dependencies: dependencies:
@ -2860,10 +2821,6 @@ packages:
/graceful-fs@4.2.10: /graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
/grapheme-splitter@1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true
/graphemer@1.4.0: /graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
dev: true dev: true
@ -3031,6 +2988,11 @@ packages:
engines: {node: '>= 4'} engines: {node: '>= 4'}
dev: true dev: true
/ignore@5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'}
dev: true
/import-fresh@3.3.0: /import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -3125,6 +3087,12 @@ packages:
has: 1.0.3 has: 1.0.3
dev: true 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: /is-date-object@1.0.5:
resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -3132,18 +3100,6 @@ packages:
has-tostringtag: 1.0.0 has-tostringtag: 1.0.0
dev: true 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: /is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -3172,14 +3128,6 @@ packages:
is-extglob: 2.1.1 is-extglob: 2.1.1
dev: true 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: /is-lambda@1.0.1:
resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
dev: false dev: false
@ -3297,13 +3245,6 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true 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: /isarray@1.0.0:
resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
dev: false dev: false
@ -3887,10 +3828,6 @@ packages:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
dev: false dev: false
/natural-compare-lite@1.4.0:
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
dev: true
/natural-compare@1.4.0: /natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true dev: true
@ -4079,6 +4016,24 @@ packages:
object-keys: 1.1.1 object-keys: 1.1.1
dev: true 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: /object.values@1.1.6:
resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -4107,26 +4062,16 @@ packages:
mimic-fn: 4.0.0 mimic-fn: 4.0.0
dev: true dev: true
/open@9.1.0: /optionator@0.9.3:
resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
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==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
dependencies: dependencies:
'@aashutoshrathi/word-wrap': 1.2.6
deep-is: 0.1.4 deep-is: 0.1.4
fast-levenshtein: 2.0.6 fast-levenshtein: 2.0.6
levn: 0.4.1 levn: 0.4.1
prelude-ls: 1.2.1 prelude-ls: 1.2.1
type-check: 0.4.0 type-check: 0.4.0
word-wrap: 1.2.3
dev: true dev: true
/p-limit@2.3.0: /p-limit@2.3.0:
@ -4514,6 +4459,15 @@ packages:
supports-preserve-symlinks-flag: 1.0.0 supports-preserve-symlinks-flag: 1.0.0
dev: true 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: /restore-cursor@3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -4550,13 +4504,6 @@ packages:
glob: 10.2.6 glob: 10.2.6
dev: true 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: /run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
dependencies: dependencies:
@ -4599,6 +4546,11 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
dev: true
/semver@7.5.0: /semver@7.5.0:
resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -4614,6 +4566,14 @@ packages:
dependencies: dependencies:
lru-cache: 6.0.0 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: /serialize-javascript@6.0.0:
resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==}
dependencies: dependencies:
@ -4677,11 +4637,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/slash@4.0.0:
resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==}
engines: {node: '>=12'}
dev: true
/slice-ansi@3.0.0: /slice-ansi@3.0.0:
resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -4950,14 +4905,6 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true 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: /tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -5023,11 +4970,6 @@ packages:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
dev: true dev: true
/titleize@3.0.0:
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
engines: {node: '>=12'}
dev: true
/to-fast-properties@2.0.0: /to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -5053,6 +4995,15 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true 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): /ts-node@10.9.1(@types/node@18.16.0)(typescript@5.0.4):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true hasBin: true
@ -5102,24 +5053,10 @@ packages:
strip-bom: 3.0.0 strip-bom: 3.0.0
dev: true dev: true
/tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
/tslib@2.5.3: /tslib@2.5.3:
resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==}
dev: true 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: /tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
dependencies: dependencies:
@ -5223,11 +5160,6 @@ packages:
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
dev: true dev: true
/untildify@4.0.0:
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
engines: {node: '>=8'}
dev: true
/uri-js@4.4.1: /uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies: dependencies:
@ -5301,11 +5233,6 @@ packages:
string-width: 4.2.3 string-width: 4.2.3
dev: false 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: /workerpool@6.2.1:
resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==}
dev: true dev: true