diff --git a/desktop/flipper-server-core/package.json b/desktop/flipper-server-core/package.json index a37668370..9f276ee7e 100644 --- a/desktop/flipper-server-core/package.json +++ b/desktop/flipper-server-core/package.json @@ -11,8 +11,8 @@ "dependencies": { "@iarna/toml": "^2.2.5", "JSONStream": "^1.3.1", - "adbkit": "^2.11.1", - "adbkit-logcat": "^2.0.1", + "@u4/adbkit": "^4.1.18", + "@u4/adbkit-logcat": "^2.1.2", "archiver": "^5.3.1", "async-mutex": "^0.3.2", "axios": "^0.26.0", diff --git a/desktop/flipper-server-core/src/devices/android/AndroidCertificateProvider.tsx b/desktop/flipper-server-core/src/devices/android/AndroidCertificateProvider.tsx index 18fc654c2..29ccada9b 100644 --- a/desktop/flipper-server-core/src/devices/android/AndroidCertificateProvider.tsx +++ b/desktop/flipper-server-core/src/devices/android/AndroidCertificateProvider.tsx @@ -8,7 +8,7 @@ */ import CertificateProvider from '../../utils/CertificateProvider'; -import {Client} from 'adbkit'; +import {Client} from '@u4/adbkit'; import * as androidUtil from './androidContainerUtility'; import {csrFileName, extractAppNameFromCSR} from '../../utils/certificateUtils'; @@ -98,7 +98,7 @@ export default class AndroidCertificateProvider extends CertificateProvider { const appName = await extractAppNameFromCSR(csr); const deviceId = await this.getTargetDeviceId(appName, destination, csr); await androidUtil.push( - this.adb, + this.adb.getDevice(deviceId), deviceId, appName, destination + filename, @@ -113,7 +113,7 @@ export default class AndroidCertificateProvider extends CertificateProvider { csr: string, ): Promise<{isMatch: boolean; foundCsr: string}> { const deviceCsr = await androidUtil.pull( - this.adb, + this.adb.getDevice(deviceId), deviceId, processName, directory + csrFileName, diff --git a/desktop/flipper-server-core/src/devices/android/AndroidDevice.tsx b/desktop/flipper-server-core/src/devices/android/AndroidDevice.tsx index 92c20214f..afcad069d 100644 --- a/desktop/flipper-server-core/src/devices/android/AndroidDevice.tsx +++ b/desktop/flipper-server-core/src/devices/android/AndroidDevice.tsx @@ -7,8 +7,7 @@ * @format */ -import adb, {util, Client as ADBClient, PullTransfer} from 'adbkit'; -import {Reader} from 'adbkit-logcat'; +import adb, {DeviceClient as ADBDeviceClient, PullTransfer} from '@u4/adbkit'; import {createWriteStream} from 'fs'; import type {DeviceDebugData, DeviceType} from 'flipper-common'; import {spawn} from 'child_process'; @@ -27,10 +26,8 @@ export default class AndroidDevice extends ServerDevice implements DebuggableDevice { - adb: ADBClient; - pidAppMapping: {[key: number]: string} = {}; + private adbClient: ADBDeviceClient; private recordingProcess?: Promise; - reader?: Reader; readonly logListener: AndroidLogListener; readonly crashWatcher: AndroidCrashWatcher; @@ -39,7 +36,7 @@ export default class AndroidDevice serial: string, deviceType: DeviceType, title: string, - adb: ADBClient, + adb: ADBDeviceClient, abiList: Array, sdkVersion: string, specs: DeviceSpec[] = [], @@ -58,13 +55,12 @@ export default class AndroidDevice screenshotAvailable: false, }, }); - this.adb = adb; + this.adbClient = adb; this.logListener = new AndroidLogListener( () => this.connected, (logEntry) => this.addLogEntry(logEntry), - this.adb, - this.serial, + this.adbClient, ); // It is OK not to await the start of the log listener. We just spawn it and handle errors internally. this.logListener @@ -87,9 +83,7 @@ export default class AndroidDevice reverse(ports: number[]): Promise { return Promise.all( - ports.map((port) => - this.adb.reverse(this.serial, `tcp:${port}`, `tcp:${port}`), - ), + ports.map((port) => this.adbClient.reverse(`tcp:${port}`, `tcp:${port}`)), ).then(() => { return; }); @@ -103,13 +97,13 @@ export default class AndroidDevice async navigateToLocation(location: string) { const shellCommand = `am start ${encodeURI(location)}`; - this.adb.shell(this.serial, shellCommand); + this.adbClient.shell(shellCommand); } async screenshot(): Promise { return new Promise((resolve, reject) => { - this.adb - .screencap(this.serial) + this.adbClient + .screencap() .then((stream) => { const chunks: Array = []; stream @@ -129,7 +123,7 @@ export default class AndroidDevice console.debug('AndroidDevice.setIntoPermissiveMode', this.serial); try { try { - await this.adb.root(this.serial); + await this.adbClient.root(); } catch (e) { if ( !(e instanceof Error) || @@ -179,15 +173,15 @@ export default class AndroidDevice } async executeShell(command: string): Promise { - return await this.adb - .shell(this.serial, command) + return await this.adbClient + .shell(command) .then(adb.util.readAll) .then((output: Buffer) => output.toString().trim()); } private async executeShellOrDie(command: string | string[]): Promise { - const output = await this.adb - .shell(this.serial, command) + const output = await this.adbClient + .shell(command) .then(adb.util.readAll) .then((output: Buffer) => output.toString().trim()); if (output) { @@ -196,16 +190,16 @@ export default class AndroidDevice } private async getSdkVersion(): Promise { - return await this.adb - .shell(this.serial, 'getprop ro.build.version.sdk') + return await this.adbClient + .shell('getprop ro.build.version.sdk') .then(adb.util.readAll) .then((output) => Number(output.toString().trim())); } private async isValidFile(filePath: string): Promise { const sdkVersion = await this.getSdkVersion(); - const fileSize = await this.adb - .shell(this.serial, `ls -l "${filePath}"`) + const fileSize = await this.adbClient + .shell(`ls -l "${filePath}"`) .then(adb.util.readAll) .then((output: Buffer) => output.toString().trim().split(' ')) .then((x) => x.filter(Boolean)) @@ -222,7 +216,7 @@ export default class AndroidDevice let newSize: string | undefined; try { const sizeString = ( - await adb.util.readAll(await this.adb.shell(this.serial, 'wm size')) + await adb.util.readAll(await this.adbClient.shell('wm size')) ).toString(); const size = sizeString.split(' ').slice(-1).pop()?.split('x'); if (size && size.length === 2) { @@ -239,8 +233,8 @@ export default class AndroidDevice } const sizeArg = newSize ? `--size ${newSize}` : ''; const cmd = `screenrecord ${sizeArg} "${recordingLocation}"`; - this.recordingProcess = this.adb - .shell(this.serial, cmd) + this.recordingProcess = this.adbClient + .shell(cmd) .then(adb.util.readAll) .then(async (output) => { const isValid = await this.isValidFile(recordingLocation); @@ -254,8 +248,7 @@ export default class AndroidDevice .then( (_) => new Promise(async (resolve, reject) => { - const stream: PullTransfer = await this.adb.pull( - this.serial, + const stream: PullTransfer = await this.adbClient.pull( recordingLocation, ); stream.on('end', resolve as () => void); @@ -274,14 +267,14 @@ export default class AndroidDevice if (!recordingProcess) { return Promise.reject(new Error('Recording was not properly started')); } - await this.adb.shell(this.serial, `pkill -l2 screenrecord`); + await this.adbClient.shell(`pkill -l2 screenrecord`); const destination = await recordingProcess; this.recordingProcess = undefined; return destination; } async forwardPort(local: string, remote: string): Promise { - return this.adb.forward(this.serial, local, remote); + return this.adbClient.forward(local, remote); } disconnect() { @@ -293,7 +286,7 @@ export default class AndroidDevice async installApp(apkPath: string) { console.log(`Installing app with adb ${apkPath}`); - await this.adb.install(this.serial, apkPath); + await this.adbClient.install(apkPath); } async readFlipperFolderForAllApps(): Promise { @@ -301,9 +294,9 @@ export default class AndroidDevice 'AndroidDevice.readFlipperFolderForAllApps', this.info.serial, ); - const output = await this.adb - .shell(this.info.serial, 'pm list packages -3 -e') - .then(util.readAll) + const output = await this.adbClient + .shell('pm list packages -3 -e') + .then(adb.util.readAll) .then((buffer) => buffer.toString()); const appIds = output @@ -322,7 +315,7 @@ export default class AndroidDevice const appsCommandsResults = await Promise.all( appIds.map(async (appId): Promise => { const sonarDirFilePaths = await executeCommandAsApp( - this.adb, + this.adbClient, this.info.serial, appId, `find /data/data/${appId}/files/sonar -type f`, @@ -369,7 +362,7 @@ export default class AndroidDevice return { path: filePath, data: await pull( - this.adb, + this.adbClient, this.info.serial, appId, filePath, @@ -379,7 +372,7 @@ export default class AndroidDevice ); const sonarDirContentWithStatsCommandPromise = executeCommandAsApp( - this.adb, + this.adbClient, this.info.serial, appId, `ls -al /data/data/${appId}/files/sonar`, diff --git a/desktop/flipper-server-core/src/devices/android/AndroidLogListener.tsx b/desktop/flipper-server-core/src/devices/android/AndroidLogListener.tsx index aa312059d..3ce18a368 100644 --- a/desktop/flipper-server-core/src/devices/android/AndroidLogListener.tsx +++ b/desktop/flipper-server-core/src/devices/android/AndroidLogListener.tsx @@ -7,8 +7,8 @@ * @format */ -import type {Client as ADBClient} from 'adbkit'; -import {Priority} from 'adbkit-logcat'; +import type {DeviceClient as ADBClient} from '@u4/adbkit'; +import {Priority, Reader} from '@u4/adbkit-logcat'; import {DeviceLogEntry, DeviceLogLevel} from 'flipper-common'; import {DeviceListener} from '../../utils/DeviceListener'; @@ -17,14 +17,13 @@ export class AndroidLogListener extends DeviceListener { isDeviceConnected: () => boolean, private onNewLogEntry: (logEntry: DeviceLogEntry) => void, private readonly adb: ADBClient, - private readonly serial: string, ) { super(isDeviceConnected); } protected async startListener() { - const reader = await this.adb.openLogcat(this.serial, { + const reader = (await this.adb.openLogcat({ clear: true, - }); + })) as Reader; let gracefulShutdown = false; let lastKnownError: Error | undefined; diff --git a/desktop/flipper-server-core/src/devices/android/KaiOSDevice.tsx b/desktop/flipper-server-core/src/devices/android/KaiOSDevice.tsx index b74a03392..68cc6827d 100644 --- a/desktop/flipper-server-core/src/devices/android/KaiOSDevice.tsx +++ b/desktop/flipper-server-core/src/devices/android/KaiOSDevice.tsx @@ -9,7 +9,7 @@ import {DeviceType} from 'flipper-common'; import AndroidDevice from './AndroidDevice'; -import {Client as ADBClient} from 'adbkit'; +import {DeviceClient} from '@u4/adbkit'; import {FlipperServerImpl} from '../../FlipperServerImpl'; export default class KaiOSDevice extends AndroidDevice { @@ -18,7 +18,7 @@ export default class KaiOSDevice extends AndroidDevice { serial: string, deviceType: DeviceType, title: string, - adb: ADBClient, + adb: DeviceClient, abiList: Array, sdkVersion: string, ) { diff --git a/desktop/flipper-server-core/src/devices/android/adbClient.tsx b/desktop/flipper-server-core/src/devices/android/adbClient.tsx index b9c0b96ba..4c72468be 100644 --- a/desktop/flipper-server-core/src/devices/android/adbClient.tsx +++ b/desktop/flipper-server-core/src/devices/android/adbClient.tsx @@ -9,8 +9,7 @@ import {reportPlatformFailures} from 'flipper-common'; import {execFile} from 'promisify-child-process'; -import adbConfig from './adbConfig'; -import adbkit, {Client} from 'adbkit'; +import adbkit, {Client as ADBClient} from '@u4/adbkit'; import path from 'path'; type Config = { @@ -23,7 +22,7 @@ type Config = { export async function initializeAdbClient( config: Config, -): Promise { +): Promise { const adbClient = await reportPlatformFailures( createClient(config), 'createADBClient', @@ -39,10 +38,10 @@ export async function initializeAdbClient( /* Adbkit will attempt to start the adb server if it's not already running, however, it sometimes fails with ENOENT errors. So instead, we start it manually before requesting a client. */ -async function createClient(config: Config): Promise { - return reportPlatformFailures( +async function createClient(config: Config): Promise { + return reportPlatformFailures( startAdbServer(config.androidHome).then(() => - adbkit.createClient(adbConfig(config.adbKitSettings)), + adbkit.createClient(config.adbKitSettings), ), 'createADBClient.shell', ); diff --git a/desktop/flipper-server-core/src/devices/android/androidContainerUtility.tsx b/desktop/flipper-server-core/src/devices/android/androidContainerUtility.tsx index ae5464c10..ad1bd50ff 100644 --- a/desktop/flipper-server-core/src/devices/android/androidContainerUtility.tsx +++ b/desktop/flipper-server-core/src/devices/android/androidContainerUtility.tsx @@ -8,7 +8,7 @@ */ import {UnsupportedError} from 'flipper-common'; -import adbkit, {Client} from 'adbkit'; +import adbkit, {DeviceClient} from '@u4/adbkit'; const allowedAppNameRegex = /^[\w.-]+$/; const appNotApplicationRegex = /not an application/; @@ -23,7 +23,7 @@ export type FilePath = string; export type FileContent = string; export async function push( - client: Client, + client: DeviceClient, deviceId: string, app: string, filepath: string, @@ -36,7 +36,7 @@ export async function push( } export async function pull( - client: Client, + client: DeviceClient, deviceId: string, app: string, path: string, @@ -81,7 +81,7 @@ class RunAsError extends Error { } function _push( - client: Client, + deviceClient: DeviceClient, deviceId: string, app: AppName, filename: FilePath, @@ -91,12 +91,12 @@ function _push( // TODO: this is sensitive to escaping issues, can we leverage client.push instead? // https://www.npmjs.com/package/adbkit#pushing-a-file-to-all-connected-devices const command = `echo "${contents}" > '${filename}' && chmod 644 '${filename}'`; - return executeCommandAsApp(client, deviceId, app, command) + return executeCommandAsApp(deviceClient, deviceId, app, command) .then((_) => undefined) .catch((error) => { if (error instanceof RunAsError) { // Fall back to running the command directly. This will work if adb is running as root. - executeCommandWithSu(client, deviceId, app, command, error); + executeCommandWithSu(deviceClient, deviceId, app, command, error); return undefined; } throw error; @@ -104,24 +104,32 @@ function _push( } function _pull( - client: Client, + deviceClient: DeviceClient, deviceId: string, app: AppName, path: FilePath, ): Promise { const command = `cat '${path}'`; - return executeCommandAsApp(client, deviceId, app, command).catch((error) => { - if (error instanceof RunAsError) { - // Fall back to running the command directly. This will work if adb is running as root. - return executeCommandWithSu(client, deviceId, app, command, error); - } - throw error; - }); + return executeCommandAsApp(deviceClient, deviceId, app, command).catch( + (error) => { + if (error instanceof RunAsError) { + // Fall back to running the command directly. This will work if adb is running as root. + return executeCommandWithSu( + deviceClient, + deviceId, + app, + command, + error, + ); + } + throw error; + }, + ); } // Keep this method private since it relies on pre-validated arguments export function executeCommandAsApp( - client: Client, + client: DeviceClient, deviceId: string, app: string, command: string, @@ -136,7 +144,7 @@ export function executeCommandAsApp( } async function executeCommandWithSu( - client: Client, + client: DeviceClient, deviceId: string, app: string, command: string, @@ -151,14 +159,14 @@ async function executeCommandWithSu( } function _executeCommandWithRunner( - client: Client, + client: DeviceClient, deviceId: string, app: string, command: string, runner: string, ): Promise { return client - .shell(deviceId, `echo '${command}' | ${runner}`) + .shell(`echo '${command}' | ${runner}`) .then(adbkit.util.readAll) .then((buffer) => buffer.toString()) .then((output) => { diff --git a/desktop/flipper-server-core/src/devices/android/androidDeviceManager.tsx b/desktop/flipper-server-core/src/devices/android/androidDeviceManager.tsx index 1589f3ffa..3106c9d6b 100644 --- a/desktop/flipper-server-core/src/devices/android/androidDeviceManager.tsx +++ b/desktop/flipper-server-core/src/devices/android/androidDeviceManager.tsx @@ -10,7 +10,7 @@ import AndroidDevice from './AndroidDevice'; import KaiOSDevice from './KaiOSDevice'; import child_process from 'child_process'; -import {Client as ADBClient, Device} from 'adbkit'; +import {Client as ADBClient, Device} from '@u4/adbkit'; import {join} from 'path'; import {FlipperServerImpl} from '../../FlipperServerImpl'; import {notNull} from '../../utils/typeUtils'; @@ -26,10 +26,7 @@ export class AndroidDeviceManager { this.certificateProvider = new AndroidCertificateProvider(this.adbClient); } - private createDevice( - adbClient: ADBClient, - device: Device, - ): Promise { + private createDevice(device: Device): Promise { return new Promise(async (resolve, reject) => { const type = device.type !== 'device' || device.id.startsWith('emulator') @@ -37,7 +34,9 @@ export class AndroidDeviceManager { : 'physical'; try { - const props = await adbClient.getProperties(device.id); + const deviceClient = device.getClient(); + + const props = await deviceClient.getProperties(); try { let name = props['ro.product.model']; const abiString = props['ro.product.cpu.abilist'] || ''; @@ -56,7 +55,7 @@ export class AndroidDeviceManager { device.id, type, name, - adbClient, + deviceClient, abiList, sdkVersion, ); @@ -195,7 +194,7 @@ export class AndroidDeviceManager { const devices = await this.adbClient.listDevices(); for (const device of devices) { if (device.type !== 'offline') { - this.registerDevice(this.adbClient, device); + this.registerDevice(device); } else { this.handleOfflineDevice(device); } @@ -237,7 +236,7 @@ export class AndroidDeviceManager { return; } if (device.type !== 'offline') { - this.registerDevice(this.adbClient, device); + this.registerDevice(device); } else { this.handleOfflineDevice(device); } @@ -247,7 +246,7 @@ export class AndroidDeviceManager { if (device.type === 'offline') { this.flipperServer.unregisterDevice(device.id); } else { - this.registerDevice(this.adbClient, device); + this.registerDevice(device); } }); @@ -273,8 +272,8 @@ export class AndroidDeviceManager { ); } - private async registerDevice(adbClient: ADBClient, deviceData: Device) { - const androidDevice = await this.createDevice(adbClient, deviceData); + private async registerDevice(deviceData: Device) { + const androidDevice = await this.createDevice(deviceData); if (!androidDevice) { return; } diff --git a/desktop/package.json b/desktop/package.json index e473c9550..17c7ee5e4 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -116,7 +116,6 @@ "productName": "Flipper", "resolutions": { "@jest-runner/electron/electron": "18.2.0", - "adbkit-logcat": "^2.0.1", "minimist": "1.2.6", "node-forge": "^1.0.6" }, diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 3af082415..27f897089 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -2502,6 +2502,11 @@ ajv "^6.12.0" ajv-keywords "^3.4.1" +"@devicefarmer/minicap-prebuilt@^2.7.1": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@devicefarmer/minicap-prebuilt/-/minicap-prebuilt-2.7.1.tgz#338cf797735213135af728c103b7de463d04906d" + integrity sha512-XM+mPIA9hgwSF4eOq0RnKSXycj2rLNm3Xsl7MM0kG3CtTpX3/J0AA8P+v8zWiGNCMxyhl2VwY52nmQE/QwN3zg== + "@electron/get@^1.13.0": version "1.14.1" resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40" @@ -3459,6 +3464,59 @@ resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-3.0.3.tgz#e679ad10535e31d333f809f7a71335cc9aef1e55" integrity sha512-KX8gMYA9ujBPOd1HFsV9e0iEx7Uoj8AG/3YsW4TtWQTg4lJvr82qNm7o/cFQfYRIt+jw7Ew/4oL4A22zOT+IRA== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@reach/observe-rect@^1.1.0", "@reach/observe-rect@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2" @@ -3813,6 +3871,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + "@types/mdast@^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" @@ -3904,6 +3967,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.31.tgz#a5bb84ecfa27eec5e1c802c6bbf8139bdb163a5d" integrity sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q== +"@types/node@>=13.7.0": + version "18.16.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01" + integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== + "@types/node@^16.11.26": version "16.11.36" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.36.tgz#9ab9f8276987132ed2b225cace2218ba794fc751" @@ -4344,6 +4412,46 @@ "@typescript-eslint/types" "5.55.0" eslint-visitor-keys "^3.3.0" +"@u4/adbkit-logcat@2.1.2", "@u4/adbkit-logcat@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@u4/adbkit-logcat/-/adbkit-logcat-2.1.2.tgz#09bf408d6f07063e9b6ea9bf1d08f466076ee389" + integrity sha512-mCAuqwWCA2MXYP8nfPJe9IJQ/aTkiFCVvF+qnsBXQ5ceaQOnw2JKNQxjz5Gw7dhYZ9iazvcxg6FNhyec5pfHeQ== + +"@u4/adbkit-monkey@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@u4/adbkit-monkey/-/adbkit-monkey-1.0.5.tgz#b43217c76a82195551bd56ce8fc61a3b3a3e8756" + integrity sha512-6Hoz8EeKVK3eEJAGR44yzclADhA/5Q4KDlTtegMY4Dl5VMGq+tm1HtrmBvT++kftfrG64/H7xnrOwQ7DwQ2TlA== + +"@u4/adbkit@^4.1.18": + version "4.1.18" + resolved "https://registry.yarnpkg.com/@u4/adbkit/-/adbkit-4.1.18.tgz#6e65227b476909f454de68444a7f815ba7bbdb2b" + integrity sha512-30nwR9p7QXPf1uqGwA6rnjjv7Qogbp7VnfF6tM8SL58evxMwNQdpAKVLD4BqIpui8k9mZfMwuWCR8T7RgDibRw== + dependencies: + "@u4/adbkit-logcat" "2.1.2" + "@u4/adbkit-monkey" "^1.0.5" + "@u4/minicap-prebuilt" "^1.0.0" + "@xmldom/xmldom" "^0.8.6" + commander "9.4.1" + debug "~4.3.4" + get-port "5.1.1" + node-forge "^1.3.1" + promise-duplex "^6.0.0" + promise-readable "^6.0.0" + protobufjs "^6.11.3" + xpath "^0.0.32" + optionalDependencies: + "@devicefarmer/minicap-prebuilt" "^2.7.1" + +"@u4/minicap-prebuilt@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@u4/minicap-prebuilt/-/minicap-prebuilt-1.0.0.tgz#9075d248f0a0cefc9a81224cc564490a7bbb47dd" + integrity sha512-z+hfnXEZmlCjNUfk4EioIWOLGoMg44a63FWcLqym2xSJuGP9WGS3qJ6ZXjT6ErYsuzSLPR0LGzCqfhXkQs/NBA== + +"@xmldom/xmldom@^0.8.6": + version "0.8.7" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.7.tgz#8b1e39c547013941974d83ad5e9cf5042071a9a0" + integrity sha512-sI1Ly2cODlWStkINzqGrZ8K6n+MTSbAeQnAipGyL+KZCXuHaRlj2gyyy8B/9MvsFFqN7XHryQnB2QwhzvJXovg== + "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" @@ -4443,31 +4551,6 @@ acorn@^8.5.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== -adbkit-logcat@^1.1.0, adbkit-logcat@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/adbkit-logcat/-/adbkit-logcat-2.0.1.tgz#d4986b9fc7cfda42733389d46a52124abef43ca5" - integrity sha512-MznVzzEzcrWhIaIyblll+a0AL1TICJe/yuaia7HDYTAtiNabR/9amJkAnLt30U8/W7MVBc3mvU1jB/6MJ/TYHw== - -adbkit-monkey@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/adbkit-monkey/-/adbkit-monkey-1.0.1.tgz#f291be701a2efc567a63fc7aa6afcded31430be1" - integrity sha1-8pG+cBou/FZ6Y/x6pq/N7TFDC+E= - dependencies: - async "~0.2.9" - -adbkit@^2.11.1: - version "2.11.1" - resolved "https://registry.yarnpkg.com/adbkit/-/adbkit-2.11.1.tgz#7da847fe561254f3121088947bc1907ef053e894" - integrity sha512-hDTiRg9NX3HQt7WoDAPCplUpvzr4ZzQa2lq7BdTTJ/iOZ6O7YNAs6UYD8sFAiBEcYHDRIyq3cm9sZP6uZnhvXw== - dependencies: - adbkit-logcat "^1.1.0" - adbkit-monkey "~1.0.1" - bluebird "~2.9.24" - commander "^2.3.0" - debug "~2.6.3" - node-forge "^0.7.1" - split "~0.3.3" - address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -4952,11 +5035,6 @@ async@^3.2.2, async@^3.2.3: resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== -async@~0.2.9: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -5346,11 +5424,6 @@ bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== -bluebird@~2.9.24: - version "2.9.34" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.9.34.tgz#2f7b4ec80216328a9fddebdf69c8d4942feff7d8" - integrity sha1-L3tOyAIWMoqf3evfacjUlC/v99g= - body-parser@1.19.2: version "1.19.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e" @@ -6060,7 +6133,12 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" -commander@^2.20.0, commander@^2.3.0: +commander@9.4.1: + version "9.4.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" + integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== + +commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -6244,6 +6322,11 @@ core-js-pure@^3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== +core-js@^3.6.5: + version "3.30.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.1.tgz#fc9c5adcc541d8e9fa3e381179433cbf795628ba" + integrity sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -6432,14 +6515,14 @@ dayjs@1.x: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9, debug@~2.6.3: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -8252,6 +8335,11 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-port@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -10733,6 +10821,11 @@ lolex@^5.0.0: dependencies: "@sinonjs/commons" "^1.7.0" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -11421,7 +11514,7 @@ node-fetch@2.6.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -node-forge@^0.10.0, node-forge@^0.7.1, node-forge@^1.0.6: +node-forge@^0.10.0, node-forge@^1.0.6, node-forge@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== @@ -12199,11 +12292,32 @@ progress@^2.0.0, progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-duplex@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/promise-duplex/-/promise-duplex-6.0.0.tgz#9d9d13383ac0f70701b9f095ffd486f106c2232e" + integrity sha512-ZL7rquzjTFzInDBeWYcsT+qddolNvzigahk6MI6qLSbQvlyRRCJkU3JztgaVunzvkH28smRa2Qu/cY9RXtSkgA== + dependencies: + core-js "^3.6.5" + promise-readable "^6.0.0" + promise-writable "^6.0.0" + promise-polyfill@^8.1.3: version "8.1.3" resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" integrity sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g== +promise-readable@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/promise-readable/-/promise-readable-6.0.0.tgz#ab1ec1a3daa53ed8764b61902fb7f3de3242025a" + integrity sha512-5NxtmUswijvX5cAM0zPSy6yiCXH/eKBpiiBq6JfAUrmngMquMbzcBhF2qA+ocs4rYYKdvAfv3cOvZxADLtL1CA== + dependencies: + core-js "^3.6.5" + +promise-writable@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/promise-writable/-/promise-writable-6.0.0.tgz#4d703e75873a4e755a39bdb926a37f41538e507f" + integrity sha512-b81zre/itgJFS7dwWzIdKNVVqvLiUxYRS/wolUB0H1YY/tAaS146XGKa4Q/5wCbsnXLyn0MCeV6f8HHe4iUHLg== + promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -12245,6 +12359,25 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= +protobufjs@^6.11.3: + version "6.11.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -13983,13 +14116,6 @@ split2@^4.1.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== -split@~0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" - integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8= - dependencies: - through "2" - sprintf-js@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" @@ -14395,7 +14521,7 @@ through2@^2.0.1: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8: +"through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -15298,6 +15424,11 @@ xmlchars@^2.1.1, xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xpath@^0.0.32: + version "0.0.32" + resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" + integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== + xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"