tests(core): added e2e and fuzzing tests

This commit is contained in:
teidesu 2021-06-15 03:12:22 +03:00
parent 0a45f5e71a
commit 77022e29c9
21 changed files with 625 additions and 80 deletions

View file

@ -43,7 +43,8 @@
"typescript": "^4.1.3",
"nyc": "^15.1.0",
"glob": "^7.1.7",
"rimraf": "^3.0.2"
"rimraf": "^3.0.2",
"dotenv-flow": "^3.2.0"
},
"workspaces": [
"packages/*"

View file

@ -0,0 +1,61 @@
import { describe, it } from 'mocha'
import { expect } from 'chai'
import { isProbablyPlainText } from '../src/utils/file-utils'
describe('isProbablyPlainText', () => {
it('should return true for buffers only containing printable ascii', () => {
expect(
isProbablyPlainText(Buffer.from('hello this is some ascii text'))
).to.be.true
expect(
isProbablyPlainText(
Buffer.from(
'hello this is some ascii text\nwith unix new lines'
)
)
).to.be.true
expect(
isProbablyPlainText(
Buffer.from(
'hello this is some ascii text\r\nwith windows new lines'
)
)
).to.be.true
expect(
isProbablyPlainText(
Buffer.from(
'hello this is some ascii text\n\twith unix new lines and tabs'
)
)
).to.be.true
expect(
isProbablyPlainText(
Buffer.from(
'hello this is some ascii text\r\n\twith windows new lines and tabs'
)
)
).to.be.true
})
it('should return false for buffers containing some binary data', () => {
expect(isProbablyPlainText(Buffer.from('hello this is cedilla: ç'))).to
.be.false
expect(
isProbablyPlainText(
Buffer.from('hello this is some ascii text with emojis 🌸')
)
).to.be.false
// random strings of 16 bytes
expect(
isProbablyPlainText(
Buffer.from('717f80f08eb9d88c3931712c0e2be32f', 'hex')
)
).to.be.false
expect(
isProbablyPlainText(
Buffer.from('20e8e218e54254c813b261432b0330d7', 'hex')
)
).to.be.false
})
})

16
packages/core/.env Normal file
View file

@ -0,0 +1,16 @@
# These env variables are only used in e2e tests
# API ID and Hash (obviously)
API_ID=
API_HASH=
# User (test DC 2) username and session string
USER_USERNAME=
USER_SESSION=
# User (test DC != 2) username and session string
USER_OTHER_DC_USERNAME=
USER_OTHER_DC_SESSION=
# Bot token (from test DC!)
BOT_USERNAME=
BOT_TOKEN=

1
packages/core/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.env.local

View file

@ -0,0 +1,61 @@
import { BaseTelegramClient, defaultDcs } from '../src'
require('dotenv-flow').config({ path: __dirname + '/../' })
require('debug').enable('mtcute:*')
if (!process.env.API_ID || !process.env.API_HASH) {
console.warn('Set API_ID and API_HASH env variables')
process.exit(0)
}
const dcId = process.argv[2] ?? '2'
const tg = new BaseTelegramClient({
apiId: process.env.API_ID,
apiHash: process.env.API_HASH,
primaryDc: defaultDcs.defaultTestDc,
})
;(async () => {
await tg.connect()
let numbers = Math.floor(Math.random() * 9999).toString()
while (numbers.length !== 4) numbers += '0'
const phone = `99966${dcId}${numbers}`
const code = `${dcId}${dcId}${dcId}${dcId}${dcId}${dcId}`
const res = await tg.call({
_: 'auth.sendCode',
phoneNumber: phone,
apiId: tg['_initConnectionParams'].apiId,
apiHash: tg['_apiHash'],
settings: { _: 'codeSettings' },
})
const res1 = await tg.call({
_: 'auth.signIn',
phoneNumber: phone,
phoneCodeHash: res.phoneCodeHash,
phoneCode: code,
})
if (res1._ === 'auth.authorizationSignUpRequired') {
await tg.call({
_: 'auth.signUp',
phoneNumber: phone,
phoneCodeHash: res.phoneCodeHash,
firstName: 'MTCute E2E',
lastName: '',
})
}
const username = `mtcute_e2e_${numbers}`
await tg.call({
_: 'account.updateUsername',
username,
})
console.log('User %s, DC %d: %s', username, dcId, await tg.exportSession())
})().catch(console.error)

View file

@ -185,15 +185,15 @@ export class TelegramConnection extends PersistentConnection {
protected onConnectionUsable(): void {
super.onConnectionUsable()
Object.entries(this._pendingRpcCalls).forEach(([id, it]) =>
this._resend(it, id)
)
const sendOnceUsable = this._sendOnceUsable
// this way in case connection is still invalid (somehow??) messages aren't lost
this._sendOnceUsable = []
sendOnceUsable.forEach((it) => this._resend(it))
Object.entries(this._pendingRpcCalls).forEach(([id, it]) =>
this._resend(it, id)
)
this._pingInterval = setInterval(() => {
if (this._pendingPing === null) {
this._pendingPing = ulongToLong(

View file

@ -64,7 +64,7 @@ export type TransportFactory = () => ICuteTransport
* When receiving a packet, its content is sent to feed(),
* and codec is supposed to emit `packet` or `error` event when packet is parsed.
*/
export interface PacketCodec {
export interface IPacketCodec {
/** Initial tag of the codec. Will be sent immediately once connected. */
tag(): MaybeAsync<Buffer>

View file

@ -1,4 +1,4 @@
import { PacketCodec, TransportError } from './abstract'
import { IPacketCodec, TransportError } from './abstract'
import { StreamedCodec } from './streamed'
import { randomBytes } from '../../utils/buffer-utils'
@ -11,7 +11,7 @@ const PADDED_TAG = Buffer.from([0xdd, 0xdd, 0xdd, 0xdd])
*/
export class IntermediatePacketCodec
extends StreamedCodec
implements PacketCodec {
implements IPacketCodec {
tag(): Buffer {
return TAG
}

View file

@ -1,4 +1,4 @@
import { PacketCodec } from './abstract'
import { IPacketCodec } from './abstract'
import { IEncryptionScheme } from '../../utils/crypto'
import { buffersEqual, randomBytes } from '../../utils/buffer-utils'
import { WrappedCodec } from './wrapped'
@ -19,13 +19,13 @@ interface MtProxyInfo {
media: boolean
}
export class ObfuscatedPacketCodec extends WrappedCodec implements PacketCodec {
export class ObfuscatedPacketCodec extends WrappedCodec implements IPacketCodec {
private _encryptor?: IEncryptionScheme
private _decryptor?: IEncryptionScheme
private _proxy?: MtProxyInfo
constructor(inner: PacketCodec, proxy?: MtProxyInfo) {
constructor(inner: IPacketCodec, proxy?: MtProxyInfo) {
super(inner)
this._proxy = proxy
}

View file

@ -1,4 +1,4 @@
import { ICuteTransport, PacketCodec, TransportState } from './abstract'
import { ICuteTransport, IPacketCodec, TransportState } from './abstract'
import { tl } from '@mtcute/tl'
import { Socket, connect } from 'net'
import EventEmitter from 'events'
@ -18,7 +18,7 @@ export abstract class TcpTransport
protected _state: TransportState = TransportState.Idle
protected _socket: Socket | null = null
abstract _packetCodec: PacketCodec
abstract _packetCodec: IPacketCodec
protected _crypto: ICryptoProvider
packetCodecInitialized = false

View file

@ -1,4 +1,4 @@
import { ICuteTransport, PacketCodec, TransportState } from './abstract'
import { ICuteTransport, IPacketCodec, TransportState } from './abstract'
import { tl } from '@mtcute/tl'
import EventEmitter from 'events'
import { typedArrayToBuffer } from '../../utils/buffer-utils'
@ -42,7 +42,7 @@ export abstract class WebSocketTransport
private _socket: WebSocket | null = null
private _crypto: ICryptoProvider
abstract _packetCodec: PacketCodec
abstract _packetCodec: IPacketCodec
packetCodecInitialized = false
private _baseDomain: string

View file

@ -1,12 +1,12 @@
import EventEmitter from 'events'
import { PacketCodec } from './abstract'
import { IPacketCodec } from './abstract'
import { ICryptoProvider } from '../../utils/crypto'
export abstract class WrappedCodec extends EventEmitter {
protected _crypto: ICryptoProvider
protected _inner: PacketCodec
protected _inner: IPacketCodec
constructor(inner: PacketCodec) {
constructor(inner: IPacketCodec) {
super()
this._inner = inner
this._inner.on('error', (err) => this.emit('error', err))

View file

@ -0,0 +1,62 @@
import { describe, it } from 'mocha'
import { expect } from 'chai'
import { BaseTelegramClient, defaultDcs, TransportState } from '../../src'
import { sleep } from '../../src/utils/misc-utils'
require('dotenv-flow').config()
describe('e2e : idle connection', function () {
if (!process.env.API_ID || !process.env.API_HASH) {
console.warn('Warning: skipping e2e idle connection test (no API_ID or API_HASH)')
return
}
this.timeout(120000)
// 75s is to make sure ping is sent
it('75s idle to test dc', async () => {
const client = new BaseTelegramClient({
apiId: process.env.API_ID!,
apiHash: process.env.API_HASH!,
primaryDc: defaultDcs.defaultTestDc
})
await client.connect()
await sleep(75000)
expect(client.primaryConnection['_transport'].state()).eq(TransportState.Ready)
const config = await client.call({ _: 'help.getConfig' })
expect(config._).eql('config')
await client.close()
expect(client.primaryConnection['_transport'].state()).eq(TransportState.Idle)
})
if (!process.env.USER_SESSION) {
console.warn('Warning: skipping e2e idle connection test with auth (no USER_SESSION)')
return
}
it('75s idle to test dc with auth', async () => {
const client = new BaseTelegramClient({
apiId: process.env.API_ID!,
apiHash: process.env.API_HASH!,
primaryDc: defaultDcs.defaultTestDc
})
client.importSession(process.env.USER_SESSION!)
await client.connect()
await sleep(75000)
expect(client.primaryConnection['_transport'].state()).eq(TransportState.Ready)
const config = await client.call({ _: 'help.getConfig' })
expect(config._).eql('config')
await client.close()
expect(client.primaryConnection['_transport'].state()).eq(TransportState.Idle)
})
})

View file

@ -0,0 +1,173 @@
import { describe, it } from 'mocha'
import { expect } from 'chai'
import { BaseTelegramClient, bufferToBigInt, defaultDcs, randomBytes, tl } from '../../src'
import { sleep } from '../../src/utils/misc-utils'
require('dotenv-flow').config()
async function createClientPair(
session1: string,
session2: string
): Promise<[BaseTelegramClient, BaseTelegramClient]> {
const client1 = new BaseTelegramClient({
apiId: process.env.API_ID!,
apiHash: process.env.API_HASH!,
primaryDc: defaultDcs.defaultTestDc,
})
if (session1.indexOf(':') > -1) {
// bot token
await client1.call({
_: 'auth.importBotAuthorization',
apiId: parseInt(process.env.API_ID!),
apiHash: process.env.API_HASH!,
botAuthToken: session1,
flags: 0
})
} else {
client1.importSession(session1)
}
const client2 = new BaseTelegramClient({
apiId: process.env.API_ID!,
apiHash: process.env.API_HASH!,
primaryDc: defaultDcs.defaultTestDc,
})
if (session2.indexOf(':') > -1) {
// bot token
await client2.call({
_: 'auth.importBotAuthorization',
apiId: parseInt(process.env.API_ID!),
apiHash: process.env.API_HASH!,
botAuthToken: session2,
flags: 0
})
} else {
client2.importSession(session2)
}
return [client1, client2]
}
describe('e2e : receiving updates', function () {
if (!process.env.API_ID || !process.env.API_HASH) {
console.warn('Warning: skipping e2e updates test (no API_ID or API_HASH)')
return
}
if (!process.env.BOT_TOKEN || !process.env.USER_SESSION) {
console.warn('Warning: skipping e2e updates test (no API_ID or API_HASH)')
return
}
this.timeout(60000)
it('receiving from user by bot', async () => {
const [actor, observer] = await createClientPair(
process.env.USER_SESSION!,
process.env.BOT_TOKEN!
)
await observer.connect()
await observer.waitUntilUsable()
let updatesCount = 0
observer['_handleUpdate'] = function() {
updatesCount += 1
}
const bot = await actor.call({
_: 'contacts.resolveUsername',
username: process.env.BOT_USERNAME!
}).then((res) => res.users[0])
expect(bot._).eq('user')
const inputPeer: tl.TypeInputPeer = {
_: 'inputPeerUser',
userId: bot.id,
accessHash: (bot as tl.RawUser).accessHash!
}
await actor.call({
_: 'messages.startBot',
bot: {
_: 'inputUser',
userId: bot.id,
accessHash: (bot as tl.RawUser).accessHash!
},
peer: inputPeer,
randomId: bufferToBigInt(randomBytes(8)),
startParam: '123'
})
for (let i = 0; i < 5; i++) {
await actor.call({
_: 'messages.sendMessage',
peer: inputPeer,
randomId: bufferToBigInt(randomBytes(8)),
message: `Test ${i}`
})
await sleep(1000)
}
// to make sure the updates were delivered
await sleep(5000)
// 5 messages + /start = 6
// it is assumed that there were no gaps
expect(updatesCount).gte(6)
await actor.close()
await observer.close()
})
it('receiving from user by user', async () => {
const [actor, observer] = await createClientPair(
process.env.USER_SESSION!,
process.env.USER_OTHER_DC_SESSION!
)
await observer.connect()
await observer.waitUntilUsable()
let updatesCount = 0
observer['_handleUpdate'] = function() {
updatesCount += 1
}
const user = await actor.call({
_: 'contacts.resolveUsername',
username: process.env.USER_OTHER_DC_USERNAME!
}).then((res) => res.users[0])
expect(user._).eq('user')
const inputPeer: tl.TypeInputPeer = {
_: 'inputPeerUser',
userId: user.id,
accessHash: (user as tl.RawUser).accessHash!
}
for (let i = 0; i < 5; i++) {
await sleep(1000)
await actor.call({
_: 'messages.sendMessage',
peer: inputPeer,
randomId: bufferToBigInt(randomBytes(8)),
message: `Test ${i}`
})
}
// to make sure the updates were delivered
await sleep(5000)
// 5 messages
// it is assumed that there were no gaps
expect(updatesCount).gte(5)
await actor.close()
await observer.close()
})
})

View file

@ -0,0 +1,101 @@
import { describe, after, it } from 'mocha'
import { expect } from 'chai'
import {
BaseTelegramClient, BinaryReader,
BinaryWriter,
defaultDcs,
randomBytes,
} from '../../src'
import { sleep } from '../../src/utils/misc-utils'
import { createAesIgeForMessage } from '../../src/utils/crypto/mtproto'
require('dotenv-flow').config()
describe('fuzz : packet', async function () {
if (!process.env.API_ID || !process.env.API_HASH) {
console.warn(
'Warning: skipping fuzz packet test (no API_ID or API_HASH)'
)
return
}
this.timeout(45000)
it('random packet', async () => {
const client = new BaseTelegramClient({
apiId: process.env.API_ID!,
apiHash: process.env.API_HASH!,
primaryDc: defaultDcs.defaultTestDc,
})
await client.connect()
await client.waitUntilUsable()
const errors: Error[] = []
const errorHandler = (err: Error) => {
errors.push(err)
}
client.onError(errorHandler)
const conn = client.primaryConnection
const mtproto = conn['_mtproto']
const createFakeMtprotoPacket = async (payload: Buffer): Promise<Buffer> => {
// create a fake mtproto packet
const messageId = conn['_getMessageId']().minus(1) // must be odd
const innerWriter = BinaryWriter.alloc(payload.length + 32)
innerWriter.raw(mtproto.serverSalt)
innerWriter.raw(mtproto._sessionId)
innerWriter.long(messageId)
innerWriter.int32(0) // seqno
innerWriter.int32(payload.length)
innerWriter.raw(payload)
const innerData = innerWriter.result()
const messageKey = (
await mtproto._crypto.sha256(
Buffer.concat([mtproto._authKeyServerSalt!, innerData])
)
).slice(8, 24)
const ige = await createAesIgeForMessage(
mtproto._crypto,
mtproto._authKey!,
messageKey,
false
)
const encryptedData = await ige.encrypt(innerData)
const writer = BinaryWriter.alloc(24 + encryptedData.length)
writer.raw(mtproto._authKeyId!)
writer.raw(messageKey)
return Buffer.concat([mtproto._authKeyId!, messageKey, encryptedData])
}
for (let i = 0; i < 100; i++) {
const payload = randomBytes(Math.round(Math.random() * 16) * 16)
await conn['onMessage'](await createFakeMtprotoPacket(payload))
await sleep(100)
}
// similar test, but this time only using object ids that do exist
const objectIds = Object.keys(new BinaryReader(Buffer.alloc(0))._objectsMap)
for (let i = 0; i < 100; i++) {
const payload = randomBytes((Math.round(Math.random() * 16) + 1) * 16)
const objectId = parseInt(objectIds[Math.round(Math.random() * objectIds.length)])
payload.writeUInt32LE(objectId, 0)
await conn['onMessage'](await createFakeMtprotoPacket(payload))
await sleep(100)
}
await client.close()
expect(errors.length).gt(0)
})
})

View file

@ -0,0 +1,113 @@
import { describe, it } from 'mocha'
import { expect } from 'chai'
import {
BaseTelegramClient,
defaultDcs,
ICuteTransport,
randomBytes,
tl,
TransportState,
} from '../../src'
import { EventEmitter } from 'events'
import { sleep } from '../../src/utils/misc-utils'
require('dotenv-flow').config()
class RandomBytesTransport extends EventEmitter implements ICuteTransport {
dc: tl.RawDcOption
interval: NodeJS.Timeout | null
close(): void {
clearInterval(this.interval!)
this.emit('close')
this.interval = null
}
connect(dc: tl.RawDcOption): void {
this.dc = dc
setTimeout(() => this.emit('ready'), 0)
this.interval = setInterval(() => {
this.emit('message', randomBytes(64))
}, 100)
}
currentDc(): tl.RawDcOption | null {
return this.dc
}
send(data: Buffer): Promise<void> {
return Promise.resolve()
}
state(): TransportState {
return this.interval ? TransportState.Ready : TransportState.Idle
}
}
describe('fuzz : transport', function () {
this.timeout(30000)
it('RandomBytesTransport (no auth)', async () => {
const client = new BaseTelegramClient({
transport: () => new RandomBytesTransport(),
apiId: 0,
apiHash: '',
primaryDc: defaultDcs.defaultTestDc,
})
const errors: Error[] = []
client.onError((err) => {
errors.push(err)
})
await client.connect()
await sleep(15000)
await client.close()
expect(errors.length).gt(0)
errors.forEach((err) => {
expect(err.message).match(/unknown object id/i)
})
})
it('RandomBytesTransport (with auth)', async () => {
const client = new BaseTelegramClient({
transport: () => new RandomBytesTransport(),
apiId: 0,
apiHash: '',
primaryDc: defaultDcs.defaultTestDc,
})
// random key just to make it think it already has one
await client.storage.setAuthKeyFor(2, randomBytes(256))
// in this case, there will be no actual errors, only
// warnings like 'received message with unknown authKey'
//
// to test for that, we hook into `decryptMessage` and make
// sure that it returns `null`
await client.connect()
let hadNonNull = false
const decryptMessage =
client.primaryConnection['_mtproto'].decryptMessage
client.primaryConnection[
'_mtproto'
].decryptMessage = async function () {
const res = await decryptMessage.apply(this, arguments)
if (res !== null) hadNonNull = true
return res
}
await sleep(15000)
await client.close()
expect(hadNonNull).false
})
})

View file

@ -1,6 +1,6 @@
import { describe, it } from 'mocha'
import { expect } from 'chai'
import { IntermediatePacketCodec } from '../../src/network/transports/tcp-intermediate'
import { IntermediatePacketCodec } from '../../src/network/transports/intermediate'
import { TransportError } from '../../src/network/transports/abstract'
describe('IntermediatePacketCodec', () => {

View file

@ -2,7 +2,7 @@ import {
bigIntToBuffer,
bufferToBigInt,
ICryptoProvider,
PacketCodec,
IPacketCodec,
WrappedCodec,
randomBytes,
} from '@mtcute/core'
@ -285,7 +285,7 @@ export async function generateFakeTlsHeader(
* Must only be used inside {@link MtProxyTcpTransport}
* @internal
*/
export class FakeTlsPacketCodec extends WrappedCodec implements PacketCodec {
export class FakeTlsPacketCodec extends WrappedCodec implements IPacketCodec {
protected _stream = Buffer.alloc(0)
private _header: Buffer

View file

@ -1,7 +1,7 @@
import {
IntermediatePacketCodec,
ObfuscatedPacketCodec,
PacketCodec,
IPacketCodec,
PaddedIntermediatePacketCodec,
parseUrlSafeBase64,
TcpTransport,
@ -86,7 +86,7 @@ export class MtProxyTcpTransport extends TcpTransport {
}
}
_packetCodec!: PacketCodec
_packetCodec!: IPacketCodec
connect(dc: tl.RawDcOption): void {
if (this._state !== TransportState.Idle)
@ -110,7 +110,7 @@ export class MtProxyTcpTransport extends TcpTransport {
}
if (!this._fakeTlsDomain) {
let inner: PacketCodec
let inner: IPacketCodec
if (this._randomPadding) {
inner = new PaddedIntermediatePacketCodec()
} else {

View file

@ -2161,14 +2161,6 @@ currently-unhandled@^0.4.1:
dependencies:
array-find-index "^1.0.1"
d@1, d@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
dependencies:
es5-ext "^0.10.50"
type "^1.0.1"
dargs@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc"
@ -2384,6 +2376,18 @@ dot-prop@^6.0.1:
dependencies:
is-obj "^2.0.0"
dotenv-flow@^3.2.0:
version "3.2.0"
resolved "http://localhost:4873/dotenv-flow/-/dotenv-flow-3.2.0.tgz#a5d79dd60ddb6843d457a4874aaf122cf659a8b7"
integrity sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg==
dependencies:
dotenv "^8.0.0"
dotenv@^8.0.0:
version "8.6.0"
resolved "http://localhost:4873/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
duplexer@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@ -2501,37 +2505,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1"
is-symbol "^1.0.2"
es5-ext@^0.10.35, es5-ext@^0.10.50:
version "0.10.53"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==
dependencies:
es6-iterator "~2.0.3"
es6-symbol "~3.1.3"
next-tick "~1.0.0"
es6-error@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
es6-iterator@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
dependencies:
d "1"
es5-ext "^0.10.35"
es6-symbol "^3.1.1"
es6-symbol@^3.1.1, es6-symbol@^3.1.3, es6-symbol@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18"
integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==
dependencies:
d "^1.0.1"
ext "^1.1.2"
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@ -2693,13 +2671,6 @@ expand-template@^2.0.3:
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
ext@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==
dependencies:
type "^2.0.0"
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@ -4369,11 +4340,6 @@ neo-async@^2.6.0:
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
next-tick@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
node-abi@^2.21.0:
version "2.26.0"
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.26.0.tgz#355d5d4bc603e856f74197adbf3f5117a396ba40"
@ -6222,16 +6188,6 @@ type-fest@^0.8.0, type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type@^1.0.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0"
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
type@^2.0.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d"
integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"