├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── dependabot.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .semaphore ├── publish.yml └── semaphore.yml ├── .vscode ├── launch.json └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bench └── sync │ └── pull.ts ├── bin └── adbkit ├── index.ts ├── package.json ├── src ├── Callback.ts ├── ClientOptions.ts ├── CpuStats.ts ├── Device.ts ├── DeviceWithPath.ts ├── ExtendedPublicKey.ts ├── Features.ts ├── Forward.ts ├── FramebufferMeta.ts ├── FramebufferStreamWithMeta.ts ├── Properties.ts ├── Reverse.ts ├── SocketOptions.ts ├── StartActivityOptions.ts ├── StartServiceOptions.ts ├── TrackerChangeSet.ts ├── WithToString.ts ├── adb.ts ├── adb │ ├── DeviceClient.ts │ ├── auth.ts │ ├── client.ts │ ├── command.ts │ ├── command │ │ ├── host-serial │ │ │ ├── attach.ts │ │ │ ├── detach.ts │ │ │ ├── forward.ts │ │ │ ├── getdevicepath.ts │ │ │ ├── getserialno.ts │ │ │ ├── getstate.ts │ │ │ ├── index.ts │ │ │ ├── listforwards.ts │ │ │ └── waitfordevice.ts │ │ ├── host-transport │ │ │ ├── clear.ts │ │ │ ├── framebuffer.ts │ │ │ ├── getfeatures.ts │ │ │ ├── getpackages.ts │ │ │ ├── getproperties.ts │ │ │ ├── index.ts │ │ │ ├── install.ts │ │ │ ├── isinstalled.ts │ │ │ ├── listreverses.ts │ │ │ ├── local.ts │ │ │ ├── log.ts │ │ │ ├── logcat.ts │ │ │ ├── monkey.ts │ │ │ ├── reboot.ts │ │ │ ├── remount.ts │ │ │ ├── reverse.ts │ │ │ ├── root.ts │ │ │ ├── screencap.ts │ │ │ ├── shell.ts │ │ │ ├── startactivity.ts │ │ │ ├── startservice.ts │ │ │ ├── sync.ts │ │ │ ├── tcp.ts │ │ │ ├── tcpip.ts │ │ │ ├── trackjdwp.ts │ │ │ ├── uninstall.ts │ │ │ ├── usb.ts │ │ │ └── waitbootcomplete.ts │ │ └── host │ │ │ ├── connect.ts │ │ │ ├── devices.ts │ │ │ ├── deviceswithpaths.ts │ │ │ ├── disconnect.ts │ │ │ ├── index.ts │ │ │ ├── kill.ts │ │ │ ├── trackdevices.ts │ │ │ ├── transport.ts │ │ │ └── version.ts │ ├── connection.ts │ ├── dump.ts │ ├── framebuffer │ │ └── rgbtransform.ts │ ├── jdwptracker.ts │ ├── keycode.ts │ ├── linetransform.ts │ ├── parser.ts │ ├── proc │ │ └── stat.ts │ ├── protocol.ts │ ├── sync.ts │ ├── sync │ │ ├── entry.ts │ │ ├── pulltransfer.ts │ │ ├── pushtransfer.ts │ │ └── stats.ts │ ├── tcpusb │ │ ├── packet.ts │ │ ├── packetreader.ts │ │ ├── rollingcounter.ts │ │ ├── server.ts │ │ ├── service.ts │ │ ├── servicemap.ts │ │ └── socket.ts │ ├── tracker.ts │ └── util.ts ├── cli.ts └── index.ts ├── tasks └── keycode.js ├── test ├── adb.ts ├── adb │ ├── command │ │ ├── host-serial │ │ │ └── waitfordevice.ts │ │ ├── host-transport │ │ │ ├── clear.ts │ │ │ ├── framebuffer.ts │ │ │ ├── getfeatures.ts │ │ │ ├── getpackages.ts │ │ │ ├── getproperties.ts │ │ │ ├── install.ts │ │ │ ├── isinstalled.ts │ │ │ ├── local.ts │ │ │ ├── log.ts │ │ │ ├── logcat.ts │ │ │ ├── monkey.ts │ │ │ ├── reboot.ts │ │ │ ├── remount.ts │ │ │ ├── root.ts │ │ │ ├── screencap.ts │ │ │ ├── shell.ts │ │ │ ├── startactivity.ts │ │ │ ├── startservice.ts │ │ │ ├── sync.ts │ │ │ ├── tcp.ts │ │ │ ├── tcpip.ts │ │ │ ├── uninstall.ts │ │ │ ├── usb.ts │ │ │ └── waitbootcomplete.ts │ │ └── host │ │ │ ├── connect.ts │ │ │ ├── disconnect.ts │ │ │ └── version.ts │ ├── framebuffer │ │ └── rgbtransform.ts │ ├── linetransform.ts │ ├── parser.ts │ ├── protocol.ts │ ├── sync.ts │ ├── tracker.ts │ └── util.ts └── mock │ ├── connection.ts │ └── duplex.ts ├── tsconfig-dist.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | # end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.js] 13 | indent_style = space 14 | indent_size = 4 15 | 16 | [*.json] 17 | indent_style = space 18 | indent_size = 4 19 | 20 | [*.md] 21 | indent_style = space 22 | indent_size = 4 23 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/**/* 2 | test/**/*.js 3 | *.js 4 | lib/**/*.d.ts 5 | test/**/*.ts 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@typescript-eslint/recommended", 4 | "plugin:prettier/recommended", 5 | "prettier", 6 | "prettier/@typescript-eslint" 7 | ], 8 | "plugins": ["progress", "@typescript-eslint", "prettier"], 9 | "parserOptions": { 10 | "ecmaVersion": 2020, 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | "progress/activate": 1, 15 | "indent": ["error", 2, {"SwitchCase": 1}] 16 | }, 17 | "overrides": [ 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: "@types/node" 10 | versions: 11 | - 14.14.22 12 | - 14.14.24 13 | - 14.14.25 14 | - 14.14.26 15 | - 14.14.28 16 | - 15.0.0 17 | - dependency-name: sinon 18 | versions: 19 | - 10.0.0 20 | - 9.0.2 21 | - dependency-name: commander 22 | versions: 23 | - 7.0.0 24 | - 7.1.0 25 | - dependency-name: mocha 26 | versions: 27 | - 8.2.1 28 | - 8.3.0 29 | - dependency-name: debug 30 | versions: 31 | - 4.3.1 32 | - dependency-name: "@types/bluebird" 33 | versions: 34 | - 3.5.33 35 | - dependency-name: sinon-chai 36 | versions: 37 | - 2.14.0 38 | - dependency-name: bluebird 39 | versions: 40 | - 3.7.2 41 | - dependency-name: chai 42 | versions: 43 | - 4.2.0 44 | - dependency-name: split 45 | versions: 46 | - 1.0.1 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /temp/ 3 | /index.js 4 | /index.d.ts 5 | /*.tgz 6 | package-lock.json 7 | /dist 8 | *.log 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | index.ts 2 | src/ 3 | test/ 4 | dist/test/ 5 | tasks/ 6 | bench/ 7 | dist/bench/ 8 | .prettierrc 9 | .editorconfig 10 | .semaphore 11 | .vscode 12 | .eslintignore 13 | .eslintrc 14 | tsconfig.json 15 | tsconfig-dist.json 16 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "useTabs": false, 4 | "trailingComma": "all", 5 | "endOfLine": "auto", 6 | "singleQuote": true, 7 | "printWidth": 120, 8 | "tabWidth": 2 9 | } 10 | -------------------------------------------------------------------------------- /.semaphore/publish.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: Publish 3 | blocks: 4 | - name: Publish 5 | task: 6 | jobs: 7 | - name: NPM publish 8 | commands: 9 | - checkout 10 | - sem-version node 16 11 | - cache restore 12 | - npm install 13 | - npm publish --access public 14 | secrets: 15 | - name: npmjs 16 | agent: 17 | machine: 18 | type: e1-standard-2 19 | os_image: ubuntu2004 20 | -------------------------------------------------------------------------------- /.semaphore/semaphore.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | name: NPM Test 3 | agent: 4 | machine: 5 | type: e1-standard-2 6 | os_image: ubuntu2004 7 | blocks: 8 | - name: Test 9 | task: 10 | jobs: 11 | - name: Test 12 | commands: 13 | - checkout 14 | - 'wget https://dl.google.com/android/repository/platform-tools-latest-linux.zip -O ~/platform-tools-latest-linux.zip' 15 | - unzip ~/platform-tools-latest-linux.zip -d ~ 16 | - 'export PATH=$PATH:~/platform-tools' 17 | - sem-version node 16 18 | - cache restore 19 | - npm install 20 | - cache store 21 | - npm run build --if-present 22 | - npm test 23 | promotions: 24 | - name: NPM publish 25 | pipeline_file: publish.yml 26 | auto_promote: 27 | when: (branch = 'master' OR tag =~ '.*') AND result = 'passed' 28 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "cwd": "${workspaceFolder}", 9 | "args": [ 10 | "-r", 11 | "ts-node/register", 12 | "--project", 13 | "tsconfig.json", 14 | "--no-timeouts", 15 | "--colors", 16 | "${workspaceFolder}/test/**/*.ts" 17 | ], 18 | "internalConsoleOptions": "openOnSessionStart", 19 | "name": "Mocha Tests", 20 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 21 | "request": "launch", 22 | "skipFiles": [ 23 | "/**" 24 | ], 25 | "type": "pwa-node" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "lib/**/*.js": true, 4 | "lib/**/*.d.ts": true, 5 | "lib/**/*.map": true 6 | }, 7 | "eslint.format.enable": true 8 | } 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We are happy to accept any contributions that make sense and respect the rules listed below. 4 | 5 | ## How to contribute 6 | 7 | 1. Fork the repo. 8 | 2. Create a feature branch for your contribution out of the `master` branch. Only one contribution per branch is accepted. 9 | 3. Implement your contribution while respecting our rules (see below). 10 | 4. If possible, add tests for your contribution to make sure it actually works. 11 | 5. Don't forget to run `npm test` just right before submitting, it also checks for code styling issues. 12 | 6. Submit a pull request against our `master` branch! 13 | 14 | ## Rules 15 | 16 | - **Do** use feature branches. 17 | - **Do** conform to existing coding style so that your contribution fits in. 18 | - **Do** use [EditorConfig] to enforce our [whitespace rules](.editorconfig). If your editor is not supported, enforce the settings manually. 19 | - **Do** run `npm test` for ESLint and unit test coverage. 20 | - **Do not** touch the `version` field in [package.json](package.json). 21 | - **Do not** commit any generated files, unless already in the repo. If absolutely necessary, explain why. 22 | - **Do not** create any top level files or directories. If absolutely necessary, explain why and update [.npmignore](.npmignore). 23 | 24 | ## License 25 | 26 | By contributing your code, you agree to license your contribution under our [LICENSE](LICENSE). 27 | 28 | [editorconfig]: http://editorconfig.org/ 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2013 CyberAgent, Inc. 2 | Copyright © 2016 The OpenSTF Project 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /bench/sync/pull.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * decaffeinate suggestions: 3 | * DS102: Remove unnecessary code created because of implicit returns 4 | * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md 5 | */ 6 | import Bench from 'bench'; 7 | import { spawn } from 'child_process'; 8 | import Adb from '../../src/adb'; 9 | 10 | const deviceId = process.env.DEVICE_ID; 11 | 12 | module.exports = { 13 | compareCount: 3, 14 | compare: { 15 | 'pull /dev/graphics/fb0 using ADB CLI'(done) { 16 | const proc = spawn('adb', ['-s', deviceId, 'pull', '/dev/graphics/fb0', '/dev/null']); 17 | return proc.stdout.on('end', done); 18 | }, 19 | 'pull /dev/graphics/fb0 using client.pull()'(done) { 20 | const client = Adb.createClient(); 21 | return client 22 | .getDevice(deviceId) 23 | .pull('/dev/graphics/fb0') 24 | .then((stream) => { 25 | stream.resume(); 26 | return stream.on('end', done); 27 | }); 28 | }, 29 | }, 30 | }; 31 | 32 | Bench.runMain(); 33 | -------------------------------------------------------------------------------- /bin/adbkit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../dist/src/cli'); 4 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './src/adb'; 2 | export { default as Adb } from './src/adb'; 3 | 4 | export { default as Parser } from './src/adb/parser'; 5 | export { Callback } from './src/Callback'; 6 | export { ClientOptions } from './src/ClientOptions'; 7 | export { CpuStats, Loads } from './src/CpuStats'; 8 | export { default as Device } from './src/Device'; 9 | export { default as DeviceWithPath } from './src/DeviceWithPath'; 10 | export { default as ExtendedPublicKey } from './src/ExtendedPublicKey'; 11 | export { Features } from './src/Features'; 12 | export { default as Forward } from './src/Forward'; 13 | export { default as FramebufferMeta } from './src/FramebufferMeta'; 14 | export { default as FramebufferStreamWithMeta } from './src/FramebufferStreamWithMeta'; 15 | export { Properties } from './src/Properties'; 16 | export { default as Reverse } from './src/Reverse'; 17 | export { default as SocketOptions } from './src/SocketOptions'; 18 | export { default as StartActivityOptions } from './src/StartActivityOptions'; 19 | export { default as StartServiceOptions, ExtraValue, ExtraObject, Extra } from './src/StartServiceOptions'; 20 | export { default as TrackerChangeSet } from './src/TrackerChangeSet'; 21 | export { default as WithToString } from './src/WithToString'; 22 | 23 | export { default as Client } from './src/adb/client'; 24 | export { default as DeviceClient } from './src/adb/DeviceClient'; 25 | export { default as ShellCommand } from './src/adb/command/host-transport/shell'; 26 | 27 | export { KeyCodes } from './src/adb/keycode'; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devicefarmer/adbkit", 3 | "version": "3.3.8", 4 | "description": "A Typescript client for the Android Debug Bridge.", 5 | "keywords": [ 6 | "adb", 7 | "adbkit", 8 | "android", 9 | "logcat", 10 | "typescript", 11 | "monkey" 12 | ], 13 | "bin": { 14 | "adbkit": "./bin/adbkit" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/devicefarmer/adbkit/issues" 18 | }, 19 | "license": "Apache-2.0", 20 | "author": { 21 | "name": "Device Farmer", 22 | "email": "contact@devicefarmer.com", 23 | "url": "https://devicefarmer.com/" 24 | }, 25 | "contributors": [ 26 | { 27 | "name": "uriel chemouni", 28 | "email": "uchemouni@gmail.com", 29 | "url": "https://urielch.github.io/urielch/" 30 | } 31 | ], 32 | "main": "./dist/index.js", 33 | "types": "./dist/index.d.ts", 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/devicefarmer/adbkit.git" 37 | }, 38 | "scripts": { 39 | "clean": "rimraf dist index.d.ts index.js", 40 | "keycode": "node tasks/keycode.js", 41 | "prepublish": "npm run clean && npm run compile && npm run test", 42 | "compile": "tsc -p .", 43 | "compile2": "tsc -p tsconfig-dist.json", 44 | "lint": "eslint ./ --ext .ts", 45 | "format": "eslint ./ --ext .ts --fix", 46 | "testJS": "mocha --reporter spec --colors dist/test/**/*.js", 47 | "test": "mocha -r ts-node/register --reporter spec --colors test/**/*.ts" 48 | }, 49 | "dependencies": { 50 | "@devicefarmer/adbkit-logcat": "^2.1.2", 51 | "@devicefarmer/adbkit-monkey": "~1.2.1", 52 | "bluebird": "~3.7", 53 | "commander": "^9.1.0", 54 | "debug": "~4.3.1", 55 | "node-forge": "^1.3.1", 56 | "split": "~1.0.1" 57 | }, 58 | "devDependencies": { 59 | "@types/debug": "^4.1.5", 60 | "@types/mocha": "^10.0.0", 61 | "@types/node": "^18.15.11", 62 | "@types/sinon-chai": "^3.2.5", 63 | "@types/bluebird": "^3.5.33", 64 | "@types/node-forge": "^1.0.1", 65 | "@typescript-eslint/eslint-plugin": "^5.44.0", 66 | "@typescript-eslint/parser": "^5.48.0", 67 | "bench": "~0.3.6", 68 | "chai": "~4.3.0", 69 | "eslint": "^8.12.0", 70 | "eslint-config-prettier": "^9.0.0", 71 | "eslint-plugin-prettier": "^5.0.0", 72 | "eslint-plugin-progress": "0.0.1", 73 | "mocha": "~10.2.0", 74 | "prettier": "^3.0.2", 75 | "rimraf": "^3.0.2", 76 | "sinon": "~15.0.1", 77 | "sinon-chai": "~3.7.0", 78 | "ts-node": "^10.4.0", 79 | "typescript": "^5.0.2" 80 | }, 81 | "engines": { 82 | "node": ">= 0.10.4" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Callback.ts: -------------------------------------------------------------------------------- 1 | export type Callback = (err: Error | null, result?: T) => void; 2 | -------------------------------------------------------------------------------- /src/ClientOptions.ts: -------------------------------------------------------------------------------- 1 | import { TcpNetConnectOpts } from 'net'; 2 | 3 | export interface ClientOptions extends TcpNetConnectOpts { 4 | bin?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/CpuStats.ts: -------------------------------------------------------------------------------- 1 | export interface CpuStats { 2 | user: number; 3 | nice: number; 4 | system: number; 5 | idle: number; 6 | iowait: number; 7 | irq: number; 8 | softirq: number; 9 | steal: number; 10 | guest: number; 11 | guestnice: number; 12 | total: number; 13 | } 14 | 15 | export interface Loads { 16 | [index: string]: CpuStats; 17 | } 18 | -------------------------------------------------------------------------------- /src/Device.ts: -------------------------------------------------------------------------------- 1 | export default interface Device { 2 | id: string; 3 | type: 'emulator' | 'device' | 'offline' | 'unauthorized' | 'unknown'; 4 | } 5 | -------------------------------------------------------------------------------- /src/DeviceWithPath.ts: -------------------------------------------------------------------------------- 1 | import Device from './Device'; 2 | 3 | export default interface DeviceWithPath extends Device { 4 | path: string; 5 | product: string; 6 | model: string; 7 | device: string; 8 | transportId: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/ExtendedPublicKey.ts: -------------------------------------------------------------------------------- 1 | import { pki } from 'node-forge'; 2 | import PublicKey = pki.rsa.PublicKey; 3 | 4 | export default interface ExtendedPublicKey extends PublicKey { 5 | fingerprint: string; 6 | comment: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/Features.ts: -------------------------------------------------------------------------------- 1 | export type Features = Record; 2 | -------------------------------------------------------------------------------- /src/Forward.ts: -------------------------------------------------------------------------------- 1 | export default interface Forward { 2 | serial: string; 3 | local: string; 4 | remote: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/FramebufferMeta.ts: -------------------------------------------------------------------------------- 1 | export type ColorFormat = 'bgr' | 'bgra' | 'rgb' | 'rgba'; 2 | export default interface FramebufferMeta { 3 | /** 4 | * The framebuffer version. Useful for patching possible backwards-compatibility issues. 5 | */ 6 | version: number; 7 | /** 8 | * The framebuffer format for convenience. This can be one of `'bgr'`, `'bgra'`, `'rgb'`, `'rgba'`. 9 | */ 10 | format: ColorFormat; 11 | /** 12 | * The horizontal resolution of the framebuffer. This SHOULD always be the same as screen width. We have not encountered any device with incorrect framebuffer metadata, but according to rumors there might be some. 13 | */ 14 | width: number; 15 | /** 16 | * The vertical resolution of the framebuffer. This SHOULD always be the same as screen height. 17 | */ 18 | height: number; 19 | /** 20 | * Bits per pixel (i.e. color depth). 21 | */ 22 | bpp: number; 23 | /** 24 | * The raw byte size of the framebuffer. 25 | */ 26 | size: number; 27 | /** 28 | * The bit offset of the red color in a pixel. 29 | */ 30 | red_offset: number; 31 | /** 32 | * The bit length of the red color in a pixel. 33 | */ 34 | red_length: number; 35 | /** 36 | * The bit offset of the blue color in a pixel. 37 | */ 38 | blue_offset: number; 39 | /** 40 | * The bit length of the blue color in a pixel. 41 | */ 42 | blue_length: number; 43 | /** 44 | * The bit offset of the green color in a pixel. 45 | */ 46 | green_offset: number; 47 | /** 48 | * The bit length of the green color in a pixel. 49 | */ 50 | green_length: number; 51 | /** 52 | * The bit offset of alpha in a pixel. 53 | */ 54 | alpha_offset: number; 55 | /** 56 | * The bit length of alpha in a pixel. `0` when not available. 57 | */ 58 | alpha_length: number; 59 | } 60 | -------------------------------------------------------------------------------- /src/FramebufferStreamWithMeta.ts: -------------------------------------------------------------------------------- 1 | import { Duplex } from 'stream'; 2 | import FramebufferMeta from './FramebufferMeta'; 3 | 4 | export default interface FramebufferStreamWithMeta extends Duplex { 5 | /** 6 | * meta data describing the content 7 | */ 8 | meta: FramebufferMeta; 9 | } 10 | -------------------------------------------------------------------------------- /src/Properties.ts: -------------------------------------------------------------------------------- 1 | export type Properties = Record; 2 | -------------------------------------------------------------------------------- /src/Reverse.ts: -------------------------------------------------------------------------------- 1 | export default interface Reverse { 2 | remote: string; 3 | local: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/SocketOptions.ts: -------------------------------------------------------------------------------- 1 | import ExtendedPublicKey from './ExtendedPublicKey'; 2 | import Bluebird from 'bluebird'; 3 | 4 | export default interface SocketOptions { 5 | auth?: (key: ExtendedPublicKey) => Bluebird; 6 | knownPublicKeys?: ExtendedPublicKey[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/StartActivityOptions.ts: -------------------------------------------------------------------------------- 1 | import StartServiceOptions from './StartServiceOptions'; 2 | 3 | export default interface StartActivityOptions extends StartServiceOptions { 4 | /** 5 | * Set to `true` to enable debugging. 6 | */ 7 | debug?: boolean; 8 | /** 9 | * Set to `true` to wait for the activity to launch. 10 | */ 11 | wait?: boolean; 12 | } 13 | -------------------------------------------------------------------------------- /src/StartServiceOptions.ts: -------------------------------------------------------------------------------- 1 | export interface Extra { 2 | /** 3 | * The key name. 4 | */ 5 | key: string; 6 | /** 7 | * The type, which can be one of `'string'`, `'null'`, `'bool'`, `'int'`, `'long'`, `'float'`, `'uri'`, `'component'`. 8 | */ 9 | type: 'string' | 'null' | 'bool' | 'int' | 'long' | 'float' | 'uri' | 'component'; 10 | /** 11 | * The value. Optional and unused if type is `'null'`. If an `Array`, type is automatically set to be an array of ``. 12 | */ 13 | value?: string | number | boolean | string[] | number[] | boolean[]; 14 | } 15 | 16 | /** 17 | * When an `Object`, each key is treated as the key name. Simple values like `null`, `String`, `Boolean` and `Number` are type-mapped automatically (`Number` maps to `'int'`) and 18 | * can be used as-is. For more complex types, like arrays and URIs, set the value to be an `Object` like in the Array syntax (see above), but leave out the `key` property. 19 | */ 20 | export interface ExtraObject { 21 | [index: string]: ExtraValue; 22 | } 23 | 24 | export type ExtraValue = number | string | boolean | ExtraObject; 25 | 26 | export default interface StartServiceOptions { 27 | /** 28 | * The user to run as. Not set by default. If the option is unsupported by the device, an attempt will be made to run the same command again without the user option. 29 | */ 30 | user?: number; 31 | /** 32 | * The action. 33 | */ 34 | action?: string; 35 | /** 36 | * The data URI, if any. 37 | */ 38 | data?: string; 39 | /** 40 | * The mime type, if any. 41 | */ 42 | mimeType?: string; 43 | /** 44 | * The category. For multiple categories, pass an `Array`. 45 | */ 46 | category?: string | string[]; 47 | /** 48 | * The component. 49 | */ 50 | component?: string; 51 | /** 52 | * Numeric flags. 53 | */ 54 | flags?: number | number[]; 55 | /** 56 | * Any extra data. 57 | */ 58 | extras?: Extra[] | ExtraObject; 59 | } 60 | -------------------------------------------------------------------------------- /src/TrackerChangeSet.ts: -------------------------------------------------------------------------------- 1 | import Device from './Device'; 2 | 3 | export default interface TrackerChangeSet { 4 | removed: Device[]; 5 | changed: Device[]; 6 | added: Device[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/WithToString.ts: -------------------------------------------------------------------------------- 1 | export default interface WithToString { 2 | toString: () => string; 3 | } 4 | -------------------------------------------------------------------------------- /src/adb.ts: -------------------------------------------------------------------------------- 1 | import Client from './adb/client'; 2 | // import { Keycode } from './adb/keycode'; 3 | import util from './adb/util'; 4 | import { ClientOptions } from './ClientOptions'; 5 | 6 | interface Options { 7 | host?: string; 8 | port?: number; 9 | bin?: string; 10 | timeout?: number; 11 | } 12 | 13 | export default class Adb { 14 | // static Keycode = Keycode; 15 | static util = util; 16 | 17 | public static createClient(options: Options = {}): Client { 18 | const opts: ClientOptions = { 19 | bin: options.bin, 20 | host: options.host || process.env.ADB_HOST, 21 | port: options.port || 5037, 22 | timeout: options.timeout || 0 23 | }; 24 | if (!opts.port) { 25 | const port = parseInt(process.env.ADB_PORT || '5037', 10); 26 | if (!isNaN(port)) { 27 | opts.port = port; 28 | } 29 | } 30 | return new Client(opts); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/adb/auth.ts: -------------------------------------------------------------------------------- 1 | /* 2 | The stucture of an ADB RSAPublicKey is as follows: 3 | 4 | #define RSANUMBYTES 256 // 2048 bit key length 5 | #define RSANUMWORDS (RSANUMBYTES / sizeof(uint32_t)) 6 | 7 | typedef struct RSAPublicKey { 8 | int len; // Length of n[] in number of uint32_t 9 | uint32_t n0inv; // -1 / n[0] mod 2^32 10 | uint32_t n[RSANUMWORDS]; // modulus as little endian array 11 | uint32_t rr[RSANUMWORDS]; // R^2 as little endian array 12 | int exponent; // 3 or 65537 13 | } RSAPublicKey; 14 | 15 | */ 16 | 17 | import forge from 'node-forge'; 18 | import ExtendedPublicKey from '../ExtendedPublicKey'; 19 | import Bluebird from 'bluebird'; 20 | 21 | const BigInteger = forge.jsbn.BigInteger; 22 | 23 | export default class Auth { 24 | private static RE = /^((?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?)\0?( .*|)\s*$/; 25 | public static parsePublicKey(buffer: string): Bluebird { 26 | return new Bluebird(function (resolve, reject) { 27 | const match = Auth.RE.exec(buffer); 28 | if (match) { 29 | const struct = Buffer.from(match[1], 'base64'); 30 | const comment = match[2].trim(); 31 | return resolve(Auth.readPublicKeyFromStruct(struct, comment)); 32 | } else { 33 | return reject(new Error('Unrecognizable public key format')); 34 | } 35 | }); 36 | } 37 | 38 | private static readPublicKeyFromStruct(struct: Buffer, comment: string): ExtendedPublicKey { 39 | if (!struct.length) { 40 | throw new Error('Invalid public key'); 41 | } 42 | // Keep track of what we've read already 43 | let offset = 0; 44 | // Get len 45 | const len = struct.readUInt32LE(offset) * 4; 46 | offset += 4; 47 | if (struct.length !== 4 + 4 + len + len + 4) { 48 | throw new Error('Invalid public key'); 49 | } 50 | // Skip n0inv, we don't need it 51 | offset += 4; 52 | // Get n 53 | const n = Buffer.alloc(len); 54 | struct.copy(n, 0, offset, offset + len); 55 | [].reverse.call(n); 56 | offset += len; 57 | // Skip rr, we don't need it 58 | offset += len; 59 | // Get e 60 | const e = struct.readUInt32LE(offset); 61 | if (!(e === 3 || e === 65537)) { 62 | throw new Error(`Invalid exponent ${e}, only 3 and 65537 are supported`); 63 | } 64 | 65 | // FIXME: bug in @types/node-forge 66 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 67 | // @ts-ignore 68 | const modulus = new BigInteger(n.toString('hex'), 16); 69 | 70 | // FIXME: bug in @types/node-forge 71 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 72 | // @ts-ignore 73 | const exponent = new BigInteger(e.toString(), 10); 74 | // Restore the public key 75 | const key = forge.pki.rsa.setPublicKey(modulus, exponent); 76 | // It will be difficult to retrieve the fingerprint later as it's based 77 | // on the complete struct data, so let's just extend the key with it. 78 | const md = forge.md.md5.create(); 79 | md.update(struct.toString('binary')); 80 | const extendedKey: ExtendedPublicKey = key as ExtendedPublicKey; 81 | extendedKey.fingerprint = md.digest().toHex().match(/../g).join(':'); 82 | // Expose comment for the same reason 83 | extendedKey.comment = comment; 84 | return extendedKey; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/adb/client.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import Connection from './connection'; 3 | 4 | import { 5 | HostVersionCommand, 6 | HostConnectCommand, 7 | HostDevicesCommand, 8 | HostDevicesWithPathsCommand, 9 | HostDisconnectCommand, 10 | HostTrackDevicesCommand, 11 | HostKillCommand, 12 | } from './command/host'; 13 | import TcpUsbServer from './tcpusb/server'; 14 | import Device from '../Device'; 15 | import Bluebird from 'bluebird'; 16 | import { ClientOptions } from '../ClientOptions'; 17 | import SocketOptions from '../SocketOptions'; 18 | import Tracker from './tracker'; 19 | import DeviceWithPath from '../DeviceWithPath'; 20 | import DeviceClient from './DeviceClient'; 21 | 22 | export default class Client extends EventEmitter { 23 | public readonly options: ClientOptions; 24 | public readonly host: string; 25 | public readonly port: number | string; 26 | public readonly bin: string; 27 | public readonly timeout: number; 28 | 29 | constructor({ host = '127.0.0.1', port = 5037, bin = 'adb', timeout = 0 }: ClientOptions = { port: 5037 }) { 30 | super(); 31 | this.host = host; 32 | this.port = port; 33 | this.bin = bin; 34 | this.timeout = timeout; 35 | this.options = { host, port, bin, timeout }; 36 | } 37 | 38 | public createTcpUsbBridge(serial: string, options: SocketOptions): TcpUsbServer { 39 | return new TcpUsbServer(this, serial, options); 40 | } 41 | 42 | public connection(): Bluebird { 43 | const connection = new Connection(this.options); 44 | // Reemit unhandled connection errors, so they can be handled externally. 45 | // If not handled at all, these will crash node. 46 | connection.on('error', (err) => this.emit('error', err)); 47 | return connection.connect(); 48 | } 49 | 50 | public version(): Bluebird { 51 | return this.connection().then((conn) => new HostVersionCommand(conn).execute()); 52 | } 53 | 54 | public connect(host: string, port = 5555): Bluebird { 55 | if (host.indexOf(':') !== -1) { 56 | const [h, portString] = host.split(':', 2); 57 | host = h; 58 | const parsed = parseInt(portString, 10); 59 | if (!isNaN(parsed)) { 60 | port = parsed; 61 | } 62 | } 63 | return this.connection().then((conn) => new HostConnectCommand(conn).execute(host, port)); 64 | } 65 | 66 | public disconnect(host: string, port = 5555): Bluebird { 67 | if (host.indexOf(':') !== -1) { 68 | const [h, portString] = host.split(':', 2); 69 | host = h; 70 | const parsed = parseInt(portString, 10); 71 | if (!isNaN(parsed)) { 72 | port = parsed; 73 | } 74 | } 75 | return this.connection() 76 | .then((conn) => new HostDisconnectCommand(conn).execute(host, port)) 77 | .then((deviceId) => new DeviceClient(this, deviceId)); 78 | } 79 | 80 | public listDevices(): Bluebird { 81 | return this.connection().then((conn) => new HostDevicesCommand(conn).execute()); 82 | } 83 | 84 | public listDevicesWithPaths(): Bluebird { 85 | return this.connection().then((conn) => new HostDevicesWithPathsCommand(conn).execute()); 86 | } 87 | 88 | public trackDevices(): Bluebird { 89 | return this.connection().then((conn) => new HostTrackDevicesCommand(conn).execute()); 90 | } 91 | 92 | public kill(): Bluebird { 93 | return this.connection().then((conn) => new HostKillCommand(conn).execute()); 94 | } 95 | 96 | public getDevice(serial: string): DeviceClient { 97 | return new DeviceClient(this, serial); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/adb/command.ts: -------------------------------------------------------------------------------- 1 | import Connection from './connection'; 2 | import Protocol from './protocol'; 3 | import Parser from './parser'; 4 | import d from 'debug'; 5 | import Bluebird from 'bluebird'; 6 | import WithToString from '../WithToString'; 7 | 8 | const debug = d('adb:command'); 9 | const RE_SQUOT = /'/g; 10 | const RE_ESCAPE = /([$`\\!"])/g; 11 | 12 | export default abstract class Command { 13 | public parser: Parser; 14 | public protocol: Protocol; 15 | public connection: Connection; 16 | 17 | constructor(connection: Connection) { 18 | this.connection = connection; 19 | this.parser = this.connection.parser; 20 | this.protocol = Protocol; 21 | } 22 | 23 | // FIXME(intentional any): not "any" will break it all 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types 25 | public abstract execute(...args: any[]): Bluebird; 26 | 27 | public _send(data: string | Buffer): Command { 28 | const encoded = Protocol.encodeData(data); 29 | if (debug.enabled) { 30 | debug(`Send '${encoded}'`); 31 | } 32 | this.connection.write(encoded); 33 | return this; 34 | } 35 | 36 | public _escape(arg: number | WithToString): number | string { 37 | switch (typeof arg) { 38 | case 'number': 39 | return arg; 40 | default: 41 | return "'" + arg.toString().replace(RE_SQUOT, "'\"'\"'") + "'"; 42 | } 43 | } 44 | 45 | public _escapeCompat(arg: number | WithToString): number | string { 46 | switch (typeof arg) { 47 | case 'number': 48 | return arg; 49 | default: 50 | return '"' + arg.toString().replace(RE_ESCAPE, '\\$1') + '"'; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/attach.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class AttachCommand extends Command { 6 | execute(serial: string): Bluebird { 7 | this._send(`host-serial:${serial}:attach`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return true; 12 | case Protocol.FAIL: 13 | return this.parser.readError(); 14 | default: 15 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 16 | } 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/detach.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class DetachCommand extends Command { 6 | execute(serial: string): Bluebird { 7 | this._send(`host-serial:${serial}:detach`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return true; 12 | case Protocol.FAIL: 13 | return this.parser.readError(); 14 | default: 15 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 16 | } 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/forward.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class ForwardCommand extends Command { 6 | execute(serial: string, local: string, remote: string): Bluebird { 7 | this._send(`host-serial:${serial}:forward:${local};${remote}`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser.readAscii(4).then((reply) => { 12 | switch (reply) { 13 | case Protocol.OKAY: 14 | return true; 15 | case Protocol.FAIL: 16 | return this.parser.readError(); 17 | default: 18 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 19 | } 20 | }); 21 | case Protocol.FAIL: 22 | return this.parser.readError(); 23 | default: 24 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/getdevicepath.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class GetDevicePathCommand extends Command { 6 | execute(serial: string): Bluebird { 7 | this._send(`host-serial:${serial}:get-devpath`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser.readValue().then((value) => { 12 | return value.toString(); 13 | }); 14 | case Protocol.FAIL: 15 | return this.parser.readError(); 16 | default: 17 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 18 | } 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/getserialno.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class GetSerialNoCommand extends Command { 6 | execute(serial: string): Bluebird { 7 | this._send(`host-serial:${serial}:get-serialno`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser.readValue().then((value) => value.toString()); 12 | case Protocol.FAIL: 13 | return this.parser.readError(); 14 | default: 15 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 16 | } 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/getstate.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class GetStateCommand extends Command { 6 | execute(serial: string): Bluebird { 7 | this._send(`host-serial:${serial}:get-state`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser.readValue().then((value) => value.toString()); 12 | case Protocol.FAIL: 13 | return this.parser.readError(); 14 | default: 15 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 16 | } 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AttachCommand } from './attach'; 2 | export { default as DetachCommand } from './detach'; 3 | export { default as ForwardCommand } from './forward'; 4 | export { default as GetDevicePathCommand } from './getdevicepath'; 5 | export { default as GetSerialNoCommand } from './getdevicepath'; 6 | export { default as GetStateCommand } from './getstate'; 7 | export { default as ListForwardsCommand } from './listforwards'; 8 | export { default as WaitForDeviceCommand } from './waitfordevice'; 9 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/listforwards.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Forward from '../../../Forward'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class ListForwardsCommand extends Command { 7 | execute(serial: string): Bluebird { 8 | this._send(`host-serial:${serial}:list-forward`); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return this.parser.readValue().then(this._parseForwards); 13 | case Protocol.FAIL: 14 | return this.parser.readError(); 15 | default: 16 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 17 | } 18 | }); 19 | } 20 | 21 | private _parseForwards(value: Buffer): Forward[] { 22 | return value 23 | .toString() 24 | .split('\n') 25 | .filter((e) => e) 26 | .map((forward) => { 27 | const [serial, local, remote] = forward.split(/\s+/); 28 | return { serial, local, remote }; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/waitfordevice.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class WaitForDeviceCommand extends Command { 6 | execute(serial: string): Bluebird { 7 | this._send(`host-serial:${serial}:wait-for-any-device`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser.readAscii(4).then((reply) => { 12 | switch (reply) { 13 | case Protocol.OKAY: 14 | return serial; 15 | case Protocol.FAIL: 16 | return this.parser.readError(); 17 | default: 18 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 19 | } 20 | }); 21 | case Protocol.FAIL: 22 | return this.parser.readError(); 23 | default: 24 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/clear.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class ClearCommand extends Command { 6 | execute(pkg: string): Bluebird { 7 | this._send(`shell:pm clear ${pkg}`); 8 | return this.parser.readAscii(4).then((reply: string) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser 12 | .searchLine(/^(Success|Failed)$/) 13 | .finally(() => { 14 | return this.parser.end(); 15 | }) 16 | .then(function (result) { 17 | switch (result[0]) { 18 | case 'Success': 19 | return true; 20 | case 'Failed': 21 | // Unfortunately, the command may stall at this point and we 22 | // have to kill the connection. 23 | throw new Error(`Package '${pkg}' could not be cleared`); 24 | } 25 | return false; 26 | }); 27 | case Protocol.FAIL: 28 | return this.parser.readError() as Promise; 29 | default: 30 | return this.parser.unexpected(reply, 'OKAY or FAIL') as Promise; 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/framebuffer.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | import d from 'debug'; 3 | import RgbTransform from '../../framebuffer/rgbtransform'; 4 | import Protocol from '../../protocol'; 5 | import Command from '../../command'; 6 | import { Readable } from 'stream'; 7 | import FramebufferMeta, { ColorFormat } from '../../../FramebufferMeta'; 8 | import FramebufferStreamWithMeta from '../../../FramebufferStreamWithMeta'; 9 | import Bluebird from 'bluebird'; 10 | 11 | const debug = d('adb:command:framebuffer'); 12 | 13 | // FIXME(intentional any): not "any" will break it all 14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 15 | export default class FrameBufferCommand extends Command { 16 | execute(format: string): Bluebird { 17 | this._send('framebuffer:'); 18 | return this.parser.readAscii(4).then((reply) => { 19 | switch (reply) { 20 | case Protocol.OKAY: 21 | return this.parser.readBytes(52).then((header) => { 22 | let stream: FramebufferStreamWithMeta; 23 | const meta = this._parseHeader(header); 24 | switch (format) { 25 | case 'raw': 26 | stream = this.parser.raw() as FramebufferStreamWithMeta; 27 | stream.meta = meta; 28 | return stream; 29 | default: 30 | stream = this._convert(meta, format) as FramebufferStreamWithMeta; 31 | stream.meta = meta; 32 | return stream; 33 | } 34 | }); 35 | case Protocol.FAIL: 36 | return this.parser.readError(); 37 | default: 38 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 39 | } 40 | }); 41 | } 42 | 43 | _convert(meta: FramebufferMeta, format: string, raw?: Readable): Readable { 44 | debug(`Converting raw framebuffer stream into ${format.toUpperCase()}`); 45 | switch (meta.format) { 46 | case 'rgb': 47 | case 'rgba': 48 | break; 49 | default: 50 | // Known to be supported by GraphicsMagick 51 | debug(`Silently transforming '${meta.format}' into 'rgb' for \`gm\``); 52 | const transform = new RgbTransform(meta); 53 | meta.format = 'rgb'; 54 | raw = this.parser.raw().pipe(transform); 55 | } 56 | const proc = spawn('gm', ['convert', '-size', `${meta.width}x${meta.height}`, `${meta.format}:-`, `${format}:-`]); 57 | if (raw) { 58 | raw.pipe(proc.stdin); 59 | } 60 | return proc.stdout; 61 | } 62 | 63 | _parseHeader(header: Buffer): FramebufferMeta { 64 | let offset = 0; 65 | const version = header.readUInt32LE(offset); 66 | if (version === 16) { 67 | throw new Error('Old-style raw images are not supported'); 68 | } 69 | offset += 4; 70 | const bpp = header.readUInt32LE(offset); 71 | offset += 4; 72 | const size = header.readUInt32LE(offset); 73 | offset += 4; 74 | const width = header.readUInt32LE(offset); 75 | offset += 4; 76 | const height = header.readUInt32LE(offset); 77 | offset += 4; 78 | const red_offset = header.readUInt32LE(offset); 79 | offset += 4; 80 | const red_length = header.readUInt32LE(offset); 81 | offset += 4; 82 | const blue_offset = header.readUInt32LE(offset); 83 | offset += 4; 84 | const blue_length = header.readUInt32LE(offset); 85 | offset += 4; 86 | const green_offset = header.readUInt32LE(offset); 87 | offset += 4; 88 | const green_length = header.readUInt32LE(offset); 89 | offset += 4; 90 | const alpha_offset = header.readUInt32LE(offset); 91 | offset += 4; 92 | const alpha_length = header.readUInt32LE(offset); 93 | let format: ColorFormat = blue_offset === 0 ? 'bgr' : 'rgb'; 94 | if (bpp === 32 || alpha_length) { 95 | format += 'a'; 96 | } 97 | return { 98 | version, 99 | bpp, 100 | size, 101 | width, 102 | height, 103 | red_offset, 104 | red_length, 105 | blue_offset, 106 | blue_length, 107 | green_offset, 108 | green_length, 109 | alpha_offset, 110 | alpha_length, 111 | format: format as ColorFormat, 112 | }; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/getfeatures.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | import { Features } from '../../../Features'; 5 | 6 | const RE_FEATURE = /^feature:(.*?)(?:=(.*?))?\r?$/gm; 7 | 8 | export default class GetFeaturesCommand extends Command { 9 | execute(): Bluebird { 10 | this._send('shell:pm list features 2>/dev/null'); 11 | return this.parser.readAscii(4).then((reply) => { 12 | switch (reply) { 13 | case Protocol.OKAY: 14 | return this.parser.readAll().then((data) => { 15 | return this._parseFeatures(data.toString()); 16 | }); 17 | case Protocol.FAIL: 18 | return this.parser.readError(); 19 | default: 20 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 21 | } 22 | }); 23 | } 24 | 25 | private _parseFeatures(value: string): Features { 26 | const features = {}; 27 | let match; 28 | while ((match = RE_FEATURE.exec(value))) { 29 | features[match[1]] = match[2] || true; 30 | } 31 | return features; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/getpackages.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class GetPackagesCommand extends Command { 6 | execute(flags?: string): Bluebird { 7 | if (flags) { 8 | this._send(`shell:pm list packages ${flags} 2>/dev/null`); 9 | } else { 10 | this._send('shell:pm list packages 2>/dev/null'); 11 | } 12 | return this.parser.readAscii(4).then((reply) => { 13 | switch (reply) { 14 | case Protocol.OKAY: 15 | return this.parser.readAll().then((data) => { 16 | return this._parsePackages(data.toString()); 17 | }); 18 | case Protocol.FAIL: 19 | return this.parser.readError(); 20 | default: 21 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 22 | } 23 | }); 24 | } 25 | 26 | private _parsePackages(value: string): string[] { 27 | const packages: string[] = []; 28 | const RE_PACKAGE = /^package:(.*?)\r?$/gm; 29 | while (true) { 30 | const match = RE_PACKAGE.exec(value); 31 | if (match) { 32 | packages.push((match as unknown)[1]); 33 | } else { 34 | break; 35 | } 36 | } 37 | return packages; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/getproperties.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | import { Properties } from '../../../Properties'; 5 | 6 | const RE_KEYVAL = /^\[([\s\S]*?)\]: \[([\s\S]*?)\]\r?$/gm; 7 | 8 | // FIXME(intentional any): not "any" will break it all 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | export default class GetPropertiesCommand extends Command { 11 | execute(): Bluebird { 12 | this._send('shell:getprop'); 13 | return this.parser.readAscii(4).then((reply) => { 14 | switch (reply) { 15 | case Protocol.OKAY: 16 | return this.parser.readAll().then((data) => { 17 | return this._parseProperties(data.toString()); 18 | }); 19 | case Protocol.FAIL: 20 | return this.parser.readError(); 21 | default: 22 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 23 | } 24 | }); 25 | } 26 | 27 | private _parseProperties(value: string): Properties { 28 | const properties = {}; 29 | let match; 30 | while ((match = RE_KEYVAL.exec(value))) { 31 | properties[match[1]] = match[2]; 32 | } 33 | return properties; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ClearCommand } from './clear'; 2 | export { default as FrameBufferCommand } from './framebuffer'; 3 | export { default as GetFeaturesCommand } from './getfeatures'; 4 | export { default as GetPackagesCommand } from './getpackages'; 5 | export { default as GetPropertiesCommand } from './getproperties'; 6 | export { default as InstallCommand } from './install'; 7 | export { default as IsInstalledCommand } from './isinstalled'; 8 | export { default as ListReversesCommand } from './listreverses'; 9 | export { default as LocalCommand } from './local'; 10 | export { default as LogCommand } from './log'; 11 | export { default as LogcatCommand } from './logcat'; 12 | export { default as MonkeyCommand } from './monkey'; 13 | export { default as RebootCommand } from './reboot'; 14 | export { default as RemountCommand } from './remount'; 15 | export { default as ReverseCommand } from './reverse'; 16 | export { default as RootCommand } from './root'; 17 | export { default as ScreencapCommand } from './screencap'; 18 | export { default as ShellCommand } from './shell'; 19 | export { default as StartActivityCommand } from './startactivity'; 20 | export { default as StartServiceCommand } from './startservice'; 21 | export { default as SyncCommand } from './sync'; 22 | export { default as TcpCommand } from './tcp'; 23 | export { default as TcpIpCommand } from './tcpip'; 24 | export { default as TrackJdwpCommand } from './trackjdwp'; 25 | export { default as UninstallCommand } from './uninstall'; 26 | export { default as UsbCommand } from './usb'; 27 | export { default as WaitBootCompleteCommand } from './waitbootcomplete'; 28 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/install.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | const OKAY_OUTPUT_REGEXP = /^(Success|Failure \[(.*?)\]|Exception)(.*)$/; 6 | const INSTALL_EXCEPTION_CODE = 'INSTALL_EXCEPTION'; 7 | 8 | class InstallError extends Error { 9 | code: string; 10 | constructor(message: string, code: string) { 11 | super(message); 12 | this.code = code; 13 | } 14 | } 15 | 16 | export default class InstallCommand extends Command { 17 | execute(apk: string): Bluebird { 18 | this._send(`shell:pm install -r ${this._escapeCompat(apk)}`); 19 | return this.parser.readAscii(4).then((reply) => { 20 | switch (reply) { 21 | case Protocol.OKAY: 22 | return this.parser 23 | .searchLine(OKAY_OUTPUT_REGEXP) 24 | .then((match) => { 25 | if (match[1] === 'Success') { 26 | return true; 27 | } else if (match[1] === 'Exception') { 28 | return this.parser.readLine().then((buffer: Buffer) => { 29 | throw new InstallError(buffer.toString(), INSTALL_EXCEPTION_CODE); 30 | }); 31 | } else { 32 | const code = match[2]; 33 | throw new InstallError(`${apk} could not be installed [${code}]`, code); 34 | } 35 | }) 36 | .finally(() => { 37 | return this.parser.readAll(); 38 | }); 39 | case Protocol.FAIL: 40 | return this.parser.readError(); 41 | default: 42 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 43 | } 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/isinstalled.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Parser from '../../parser'; 3 | import Command from '../../command'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class IsInstalledCommand extends Command { 7 | execute(pkg: string): Bluebird { 8 | this._send(`shell:pm path ${pkg} 2>/dev/null`); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return this.parser 13 | .readAscii(8) 14 | .then((reply) => { 15 | switch (reply) { 16 | case 'package:': 17 | return true; 18 | default: 19 | return this.parser.unexpected(reply, "'package:'"); 20 | } 21 | }) 22 | .catch(Parser.PrematureEOFError, function () { 23 | return false; 24 | }); 25 | case Protocol.FAIL: 26 | return this.parser.readError(); 27 | default: 28 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 29 | } 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/listreverses.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Reverse from '../../../Reverse'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class ListReversesCommand extends Command { 7 | execute(): Bluebird { 8 | this._send('reverse:list-forward'); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return this.parser.readValue().then((value) => { 13 | return this._parseReverses(value); 14 | }); 15 | case Protocol.FAIL: 16 | return this.parser.readError(); 17 | default: 18 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 19 | } 20 | }); 21 | } 22 | 23 | private _parseReverses(value: Buffer): Reverse[] { 24 | const reverses: Reverse[] = []; 25 | const ref = value.toString().split('\n'); 26 | for (let i = 0, len = ref.length; i < len; i++) { 27 | const reverse = ref[i]; 28 | if (reverse) { 29 | const [, remote, local] = reverse.split(/\s+/); 30 | reverses.push({ remote, local }); 31 | } 32 | } 33 | return reverses; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/local.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import { Duplex } from 'stream'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class LocalCommand extends Command { 7 | execute(path: string): Bluebird { 8 | this._send(/:/.test(path) ? path : `localfilesystem:${path}`); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return this.parser.raw(); 13 | case Protocol.FAIL: 14 | return this.parser.readError(); 15 | default: 16 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 17 | } 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/log.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import { Duplex } from 'stream'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class LogCommand extends Command { 7 | execute(name: string): Bluebird { 8 | this._send(`log:${name}`); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return this.parser.raw(); 13 | case Protocol.FAIL: 14 | return this.parser.readError(); 15 | default: 16 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 17 | } 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/logcat.ts: -------------------------------------------------------------------------------- 1 | import LineTransform from '../../linetransform'; 2 | import Protocol from '../../protocol'; 3 | import Command from '../../command'; 4 | import Bluebird from 'bluebird'; 5 | 6 | // FIXME(intentional any): not "any" will break it all 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | export default class LogcatCommand extends Command { 9 | execute(options: { clear?: boolean } = {}): Bluebird { 10 | let cmd; 11 | // For some reason, LG G Flex requires a filter spec with the -B option. 12 | // It doesn't actually use it, though. Regardless of the spec we always get 13 | // all events on all devices. 14 | cmd = 'logcat -B *:I 2>/dev/null'; 15 | if (options.clear) { 16 | cmd = `logcat -c 2>/dev/null && ${cmd}`; 17 | } 18 | this._send(`shell:echo && ${cmd}`); 19 | return this.parser.readAscii(4).then((reply) => { 20 | switch (reply) { 21 | case Protocol.OKAY: 22 | return this.parser.raw().pipe( 23 | new LineTransform({ 24 | autoDetect: true, 25 | }), 26 | ); 27 | case Protocol.FAIL: 28 | return this.parser.readError(); 29 | default: 30 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/monkey.ts: -------------------------------------------------------------------------------- 1 | import Bluebird from 'bluebird'; 2 | import Protocol from '../../protocol'; 3 | import Command from '../../command'; 4 | import { Duplex } from 'stream'; 5 | 6 | export default class MonkeyCommand extends Command { 7 | execute(port: number): Bluebird { 8 | // Some devices have broken /sdcard (i.e. /mnt/sdcard), which monkey will 9 | // attempt to use to write log files to. We can cheat and set the location 10 | // with an environment variable, because most logs use 11 | // Environment.getLegacyExternalStorageDirectory() like they should. There 12 | // are some hardcoded logs, though. Anyway, this should enable most things. 13 | // Check https://github.com/android/platform_frameworks_base/blob/master/ 14 | // core/java/android/os/Environment.java for the variables. 15 | this._send(`shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port ${port} -v`); 16 | return this.parser.readAscii(4).then((reply) => { 17 | switch (reply) { 18 | case Protocol.OKAY: 19 | // The monkey command is a bit weird in that it doesn't look like 20 | // it starts in daemon mode, but it actually does. So even though 21 | // the command leaves the terminal "hanging", Ctrl-C (or just 22 | // ending the connection) will not end the daemon. HOWEVER, on 23 | // some devices, such as SO-02C by Sony, it is required to leave 24 | // the command hanging around. In any case, if the command exits 25 | // by itself, it means that something went wrong. 26 | // On some devices (such as F-08D by Fujitsu), the monkey 27 | // command gives no output no matter how many verbose flags you 28 | // give it. So we use a fallback timeout. 29 | return this.parser 30 | .searchLine(/^:Monkey:/) 31 | .timeout(1000) 32 | .then(() => { 33 | return this.parser.raw(); 34 | }) 35 | .catch(Bluebird.TimeoutError, () => { 36 | return this.parser.raw(); 37 | }); 38 | case Protocol.FAIL: 39 | return this.parser.readError(); 40 | default: 41 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 42 | } 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/reboot.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class RebootCommand extends Command { 6 | execute(): Bluebird { 7 | this._send('reboot:'); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser.readAll().return(true); 12 | case Protocol.FAIL: 13 | return this.parser.readError(); 14 | default: 15 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 16 | } 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/remount.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class RemountCommand extends Command { 6 | execute(): Bluebird { 7 | this._send('remount:'); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return true; 12 | case Protocol.FAIL: 13 | return this.parser.readError(); 14 | default: 15 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 16 | } 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/reverse.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class ReverseCommand extends Command { 6 | execute(remote: string, local: string): Bluebird { 7 | this._send(`reverse:forward:${remote};${local}`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser.readAscii(4).then((reply) => { 12 | switch (reply) { 13 | case Protocol.OKAY: 14 | return true; 15 | case Protocol.FAIL: 16 | return this.parser.readError(); 17 | default: 18 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 19 | } 20 | }); 21 | case Protocol.FAIL: 22 | return this.parser.readError(); 23 | default: 24 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/root.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | const RE_OK = /restarting adbd as root/; 6 | 7 | export default class RootCommand extends Command { 8 | execute(): Bluebird { 9 | this._send('root:'); 10 | return this.parser.readAscii(4).then((reply) => { 11 | switch (reply) { 12 | case Protocol.OKAY: 13 | return this.parser.readAll().then(function (value) { 14 | if (RE_OK.test(value.toString())) { 15 | return true; 16 | } else { 17 | throw new Error(value.toString().trim()); 18 | } 19 | }); 20 | case Protocol.FAIL: 21 | return this.parser.readError(); 22 | default: 23 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/screencap.ts: -------------------------------------------------------------------------------- 1 | import LineTransform from '../../linetransform'; 2 | import Protocol from '../../protocol'; 3 | import Parser from '../../parser'; 4 | import Command from '../../command'; 5 | import Bluebird from 'bluebird'; 6 | import { Duplex } from 'stream'; 7 | 8 | export default class ScreencapCommand extends Command { 9 | execute(): Bluebird { 10 | this._send('shell:echo && screencap -p 2>/dev/null'); 11 | return this.parser.readAscii(4).then((reply) => { 12 | switch (reply) { 13 | case Protocol.OKAY: 14 | let transform = new LineTransform(); 15 | return this.parser 16 | .readBytes(1) 17 | .then((chunk) => { 18 | transform = new LineTransform({ autoDetect: true }); 19 | transform.write(chunk); 20 | return this.parser.raw().pipe(transform); 21 | }) 22 | .catch(Parser.PrematureEOFError, () => { 23 | throw Error('No support for the screencap command'); 24 | }); 25 | case Protocol.FAIL: 26 | return this.parser.readError(); 27 | default: 28 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 29 | } 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/shell.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import { Duplex } from 'stream'; 4 | import Bluebird from 'bluebird'; 5 | import WithToString from '../../../WithToString'; 6 | 7 | export default class ShellCommand extends Command { 8 | execute(command: string | ArrayLike): Bluebird { 9 | if (Array.isArray(command)) { 10 | command = command.map(this._escape).join(' '); 11 | } 12 | this._send(`shell:${command}`); 13 | return this.parser.readAscii(4).then((reply) => { 14 | switch (reply) { 15 | case Protocol.OKAY: 16 | return this.parser.raw(); 17 | case Protocol.FAIL: 18 | return this.parser.readError(); 19 | default: 20 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/startactivity.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Parser from '../../parser'; 3 | import Command from '../../command'; 4 | import StartActivityOptions from '../../../StartActivityOptions'; 5 | import Bluebird from 'bluebird'; 6 | import { Extra, ExtraObject, ExtraValue } from '../../../StartServiceOptions'; 7 | 8 | const RE_ERROR = /^Error: (.*)$/; 9 | 10 | const EXTRA_TYPES = { 11 | string: 's', 12 | null: 'sn', 13 | bool: 'z', 14 | int: 'i', 15 | long: 'l', 16 | float: 'f', 17 | uri: 'u', 18 | component: 'cn', 19 | }; 20 | 21 | class StartActivityCommand extends Command { 22 | execute(options: StartActivityOptions): Bluebird { 23 | const args = this._intentArgs(options); 24 | if (options.debug) { 25 | args.push('-D'); 26 | } 27 | if (options.wait) { 28 | args.push('-W'); 29 | } 30 | if (options.user || options.user === 0) { 31 | args.push('--user', this._escape(options.user)); 32 | } 33 | return this._run('start', args); 34 | } 35 | 36 | _run(command: string, args: Array): Bluebird { 37 | this._send(`shell:am ${command} ${args.join(' ')}`); 38 | return this.parser.readAscii(4).then((reply) => { 39 | switch (reply) { 40 | case Protocol.OKAY: 41 | return this.parser 42 | .searchLine(RE_ERROR) 43 | .finally(() => { 44 | return this.parser.end(); 45 | }) 46 | .then(function (match) { 47 | throw new Error(match[1]); 48 | }) 49 | .catch(Parser.PrematureEOFError, function () { 50 | return true; 51 | }); 52 | case Protocol.FAIL: 53 | return this.parser.readError(); 54 | default: 55 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 56 | } 57 | }); 58 | } 59 | 60 | protected _intentArgs(options: StartActivityOptions): Array { 61 | const args: Array = []; 62 | if (options.extras) { 63 | args.push(...this._formatExtras(options.extras)); 64 | } 65 | if (options.action) { 66 | args.push('-a', this._escape(options.action)); 67 | } 68 | if (options.data) { 69 | args.push('-d', this._escape(options.data)); 70 | } 71 | if (options.mimeType) { 72 | args.push('-t', this._escape(options.mimeType)); 73 | } 74 | if (options.category) { 75 | if (Array.isArray(options.category)) { 76 | options.category.forEach((category) => { 77 | return args.push('-c', this._escape(category)); 78 | }); 79 | } else { 80 | args.push('-c', this._escape(options.category)); 81 | } 82 | } 83 | if (options.component) { 84 | args.push('-n', this._escape(options.component)); 85 | } 86 | if (options.flags) { 87 | args.push('-f', this._escape(options.flags)); 88 | } 89 | return args; 90 | } 91 | 92 | // StartActivityOptions['extras'] 93 | private _formatExtras(extras: Extra[] | ExtraObject): Array { 94 | if (!extras) { 95 | return []; 96 | } 97 | if (Array.isArray(extras)) { 98 | return extras.reduce((all, extra) => { 99 | return all.concat(this._formatLongExtra(extra)); 100 | }, []); 101 | } else { 102 | return Object.keys(extras).reduce((all, key) => { 103 | return all.concat(this._formatShortExtra(key, extras[key])); 104 | }, []); 105 | } 106 | } 107 | 108 | private _formatShortExtra(key: string, value: ExtraValue): Array { 109 | let sugared = { 110 | key: key, 111 | type: '' as string, 112 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 113 | value: undefined as any, 114 | }; 115 | if (value === null) { 116 | sugared.type = 'null'; 117 | } else if (Array.isArray(value)) { 118 | throw new Error( 119 | `Refusing to format array value '${key}' using short syntax; empty array would cause unpredictable results due to unknown type. Please use long syntax instead.`, 120 | ); 121 | } else { 122 | switch (typeof value) { 123 | case 'string': 124 | sugared.type = 'string'; 125 | sugared.value = value; 126 | break; 127 | case 'boolean': 128 | sugared.type = 'bool'; 129 | sugared.value = value; 130 | break; 131 | case 'number': 132 | sugared.type = 'int'; 133 | sugared.value = value; 134 | break; 135 | case 'object': 136 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 137 | sugared = value as any; 138 | sugared.key = key; 139 | } 140 | } 141 | return this._formatLongExtra(sugared); 142 | } 143 | 144 | private _formatLongExtra(extra): Array { 145 | const args: Array = []; 146 | if (!extra.type) { 147 | extra.type = 'string'; 148 | } 149 | const type = EXTRA_TYPES[extra.type]; 150 | if (!type) { 151 | throw new Error(`Unsupported type '${extra.type}' for extra '${extra.key}'`); 152 | } 153 | if (extra.type === 'null') { 154 | args.push(`--e${type}`); 155 | args.push(this._escape(extra.key)); 156 | } else if (Array.isArray(extra.value)) { 157 | args.push(`--e${type}a`); 158 | args.push(this._escape(extra.key)); 159 | args.push(this._escape(extra.value.join(','))); 160 | } else { 161 | args.push(`--e${type}`); 162 | args.push(this._escape(extra.key)); 163 | args.push(this._escape(extra.value)); 164 | } 165 | return args; 166 | } 167 | } 168 | 169 | export = StartActivityCommand; 170 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/startservice.ts: -------------------------------------------------------------------------------- 1 | import StartActivityCommand from './startactivity'; 2 | import StartServiceOptions from '../../../StartServiceOptions'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class StartServiceCommand extends StartActivityCommand { 6 | execute(options: StartServiceOptions): Bluebird { 7 | const args = this._intentArgs(options); 8 | if (options.user || options.user === 0) { 9 | args.push('--user', this._escape(options.user)); 10 | } 11 | return this._run('startservice', args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/sync.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Sync from '../../sync'; 3 | import Command from '../../command'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class SyncCommand extends Command { 7 | execute(): Bluebird { 8 | this._send('sync:'); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return new Sync(this.connection); 13 | case Protocol.FAIL: 14 | return this.parser.readError(); 15 | default: 16 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 17 | } 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/tcp.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import { Duplex } from 'stream'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class TcpCommand extends Command { 7 | execute(port: number, host?: string): Bluebird { 8 | this._send(`tcp:${port}` + (host ? `:${host}` : '')); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return this.parser.raw(); 13 | case Protocol.FAIL: 14 | return this.parser.readError(); 15 | default: 16 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 17 | } 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/tcpip.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | const RE_OK = /restarting in/; 6 | 7 | export default class TcpIpCommand extends Command { 8 | execute(port: number): Bluebird { 9 | this._send(`tcpip:${port}`); 10 | return this.parser.readAscii(4).then((reply) => { 11 | switch (reply) { 12 | case Protocol.OKAY: 13 | return this.parser.readAll().then(function (value) { 14 | if (RE_OK.test(value.toString())) { 15 | return port; 16 | } else { 17 | throw new Error(value.toString().trim()); 18 | } 19 | }); 20 | case Protocol.FAIL: 21 | return this.parser.readError(); 22 | default: 23 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/trackjdwp.ts: -------------------------------------------------------------------------------- 1 | import Bluebird from 'bluebird'; 2 | import Protocol from '../../protocol'; 3 | import Command from '../../command'; 4 | import JdwpTracker from '../../jdwptracker'; 5 | 6 | export default class TrackJdwpCommand extends Command { 7 | execute(): Bluebird { 8 | this._send('track-jdwp'); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return new JdwpTracker(this); 13 | case Protocol.FAIL: 14 | return this.parser.readError(); 15 | default: 16 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 17 | } 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/uninstall.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | class UninstallError extends Error { 6 | constructor(message: string) { 7 | super(message); 8 | } 9 | } 10 | export default class UninstallCommand extends Command { 11 | execute(pkg: string): Bluebird { 12 | this._send(`shell:pm uninstall ${pkg}`); 13 | return this.parser.readAscii(4).then((reply) => { 14 | switch (reply) { 15 | case Protocol.OKAY: 16 | return this.parser 17 | .searchLine(/^(Success|Failure.*|.*Unknown package:.*)$/) 18 | .then(function (match) { 19 | if (match[1] === 'Success') { 20 | return true; 21 | } else if (match[1].includes('DELETE_FAILED_DEVICE_POLICY_MANAGER')) { 22 | const reason = match[1]; 23 | throw new UninstallError(`${pkg} could not be uninstalled [${reason}]`); 24 | } else { 25 | // Either way, the package was uninstalled or doesn't exist, 26 | // which is good enough for us. 27 | return true; 28 | } 29 | }) 30 | .finally(() => { 31 | // Consume all remaining content to "naturally" close the 32 | // connection. 33 | return this.parser.readAll(); 34 | }); 35 | case Protocol.FAIL: 36 | return this.parser.readError(); 37 | default: 38 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/usb.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | const RE_OK = /restarting in/; 6 | 7 | export default class UsbCommand extends Command { 8 | execute(): Bluebird { 9 | this._send('usb:'); 10 | return this.parser.readAscii(4).then((reply) => { 11 | switch (reply) { 12 | case Protocol.OKAY: 13 | return this.parser.readAll().then(function (value) { 14 | if (RE_OK.test(value.toString())) { 15 | return true; 16 | } else { 17 | throw new Error(value.toString().trim()); 18 | } 19 | }); 20 | case Protocol.FAIL: 21 | return this.parser.readError(); 22 | default: 23 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/waitbootcomplete.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Command from '../../command'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class WaitBootCompleteCommand extends Command { 6 | execute(): Bluebird { 7 | this._send('shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done'); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser 12 | .searchLine(/^1$/) 13 | .finally(() => this.parser.end()) 14 | .then(() => true); 15 | case Protocol.FAIL: 16 | return this.parser.readError(); 17 | default: 18 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/adb/command/host/connect.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | // Possible replies: 6 | // "unable to connect to 192.168.2.2:5555" 7 | // "connected to 192.168.2.2:5555" 8 | // "already connected to 192.168.2.2:5555" 9 | const RE_OK = /connected to|already connected/; 10 | 11 | export default class HostConnectCommand extends Command { 12 | execute(host: string, port: number): Bluebird { 13 | this._send(`host:connect:${host}:${port}`); 14 | return this.parser.readAscii(4).then((reply) => { 15 | switch (reply) { 16 | case Protocol.OKAY: 17 | return this.parser.readValue().then(function (value) { 18 | if (RE_OK.test(value.toString())) { 19 | return `${host}:${port}`; 20 | } else { 21 | throw new Error(value.toString()); 22 | } 23 | }); 24 | case Protocol.FAIL: 25 | return this.parser.readError(); 26 | default: 27 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/adb/command/host/devices.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Device from '../../../Device'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class HostDevicesCommand extends Command { 7 | execute(): Bluebird { 8 | this._send('host:devices'); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return this._readDevices(); 13 | case Protocol.FAIL: 14 | return this.parser.readError(); 15 | default: 16 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 17 | } 18 | }); 19 | } 20 | 21 | public _readDevices(): Bluebird { 22 | return this.parser.readValue().then(this._parseDevices); 23 | } 24 | 25 | _parseDevices(value: Buffer): Device[] { 26 | return value 27 | .toString('ascii') 28 | .split('\n') 29 | .filter((e) => e) 30 | .map((line) => { 31 | const [id, type] = line.split('\t'); 32 | return { 33 | id, 34 | type: type as 'emulator' | 'device' | 'offline', 35 | }; 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/adb/command/host/deviceswithpaths.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import DeviceWithPath from '../../../DeviceWithPath'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class HostDevicesWithPathsCommand extends Command { 7 | execute(): Bluebird { 8 | this._send('host:devices-l'); 9 | return this.parser.readAscii(4).then((reply) => { 10 | switch (reply) { 11 | case Protocol.OKAY: 12 | return this._readDevices(); 13 | case Protocol.FAIL: 14 | return this.parser.readError(); 15 | default: 16 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 17 | } 18 | }); 19 | } 20 | 21 | public _readDevices(): Bluebird { 22 | return this.parser.readValue().then(this._parseDevices); 23 | } 24 | 25 | private _parseDevices(value: Buffer): DeviceWithPath[] { 26 | return value 27 | .toString('ascii') 28 | .split('\n') 29 | .filter((e) => e) 30 | .map((line) => { 31 | // For some reason, the columns are separated by spaces instead of tabs 32 | const [id, type, path, product, model, device, transportId] = line.split(/\s+/); 33 | return { 34 | id, 35 | type: type as 'emulator' | 'device' | 'offline', 36 | path, 37 | product, 38 | model, 39 | device, 40 | transportId, 41 | }; 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/adb/command/host/disconnect.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | // Possible replies: 6 | // "No such device 192.168.2.2:5555" 7 | // "" 8 | // "disconnected 192.168.2.2:5555" 9 | 10 | const RE_OK = /^$/; 11 | const RE_DISC = /^disconnected.*$/ 12 | 13 | export default class HostDisconnectCommand extends Command { 14 | execute(host: string, port: number): Bluebird { 15 | this._send(`host:disconnect:${host}:${port}`); 16 | return this.parser.readAscii(4).then((reply) => { 17 | switch (reply) { 18 | case Protocol.OKAY: 19 | return this.parser.readValue().then(function (value) { 20 | if (RE_OK.test(value.toString()) || RE_DISC.test(value.toString())) { 21 | return `${host}:${port}`; 22 | } else { 23 | throw new Error(value.toString()); 24 | } 25 | }); 26 | case Protocol.FAIL: 27 | return this.parser.readError(); 28 | default: 29 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 30 | } 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/adb/command/host/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HostConnectCommand } from './connect'; 2 | export { default as HostDevicesCommand } from './devices'; 3 | export { default as HostDevicesWithPathsCommand } from './deviceswithpaths'; 4 | export { default as HostDisconnectCommand } from './disconnect'; 5 | export { default as HostKillCommand } from './kill'; 6 | export { default as HostTrackDevicesCommand } from './trackdevices'; 7 | export { default as HostTransportCommand } from './transport'; 8 | export { default as HostVersionCommand } from './version'; 9 | -------------------------------------------------------------------------------- /src/adb/command/host/kill.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class HostKillCommand extends Command { 6 | execute(): Bluebird { 7 | this._send('host:kill'); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return true; 12 | case Protocol.FAIL: 13 | return this.parser.readError(); 14 | default: 15 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 16 | } 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host/trackdevices.ts: -------------------------------------------------------------------------------- 1 | import Protocol from '../../protocol'; 2 | import Tracker from '../../tracker'; 3 | import HostDevicesCommand from './devices'; 4 | import Bluebird from 'bluebird'; 5 | 6 | export default class HostTrackDevicesCommand extends HostDevicesCommand { 7 | // FIXME(intentional any): correct return value: `Bluebird` 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | execute(): Bluebird { 10 | this._send('host:track-devices'); 11 | return this.parser.readAscii(4).then((reply) => { 12 | switch (reply) { 13 | case Protocol.OKAY: 14 | return new Tracker(this); 15 | case Protocol.FAIL: 16 | return this.parser.readError(); 17 | default: 18 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/adb/command/host/transport.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class HostTransportCommand extends Command { 6 | execute(serial: string): Bluebird { 7 | this._send(`host:transport:${serial}`); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return true; 12 | case Protocol.FAIL: 13 | return this.parser.readError(); 14 | default: 15 | return this.parser.unexpected(reply, 'OKAY or FAIL'); 16 | } 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host/version.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | import Bluebird from 'bluebird'; 4 | 5 | export default class HostVersionCommand extends Command { 6 | execute(): Bluebird { 7 | this._send('host:version'); 8 | return this.parser.readAscii(4).then((reply) => { 9 | switch (reply) { 10 | case Protocol.OKAY: 11 | return this.parser.readValue().then((value) => { 12 | return this._parseVersion(value.toString()); 13 | }); 14 | case Protocol.FAIL: 15 | return this.parser.readError(); 16 | default: 17 | return this._parseVersion(reply); 18 | } 19 | }); 20 | } 21 | 22 | _parseVersion(version: string): number { 23 | return parseInt(version, 16); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/adb/connection.ts: -------------------------------------------------------------------------------- 1 | import * as Net from 'net'; 2 | import { EventEmitter } from 'events'; 3 | import { ChildProcess, execFile, ExecFileOptions } from 'child_process'; 4 | import Parser from './parser'; 5 | import dump from './dump'; 6 | import d from 'debug'; 7 | import { Socket } from 'net'; 8 | import Bluebird from 'bluebird'; 9 | import { ClientOptions } from '../ClientOptions'; 10 | 11 | const debug = d('adb:connection'); 12 | 13 | export default class Connection extends EventEmitter { 14 | public socket!: Socket; 15 | public parser!: Parser; 16 | private triedStarting: boolean; 17 | public options: ClientOptions; 18 | 19 | constructor(options?: ClientOptions) { 20 | super(); 21 | this.options = options || { port: 0 }; 22 | // this.socket = null; 23 | // this.parser = null; 24 | this.triedStarting = false; 25 | } 26 | 27 | public connect(): Bluebird { 28 | this.socket = Net.connect(this.options); 29 | this.socket.setNoDelay(true); 30 | this.parser = new Parser(this.socket); 31 | this.socket.on('connect', () => this.emit('connect')); 32 | this.socket.on('end', () => this.emit('end')); 33 | this.socket.on('drain', () => this.emit('drain')); 34 | this.socket.on('timeout', () => this.emit('timeout')); 35 | this.socket.on('close', (hadError: boolean) => this.emit('close', hadError)); 36 | 37 | return new Bluebird((resolve, reject) => { 38 | this.socket.once('connect', resolve); 39 | this.socket.once('error', reject); 40 | }) 41 | .catch((err) => { 42 | if (err.code === 'ECONNREFUSED' && !this.triedStarting) { 43 | debug("Connection was refused, let's try starting the server once"); 44 | this.triedStarting = true; 45 | return this.startServer().then(() => { 46 | return this.connect(); 47 | }); 48 | } else { 49 | this.end(); 50 | throw err; 51 | } 52 | }) 53 | .then(() => { 54 | // Emit unhandled error events, so that they can be handled on the client. 55 | // Without this, they would just crash node unavoidably. 56 | if (this.socket) { 57 | this.socket.on('error', (err) => { 58 | if (this.socket && this.socket.listenerCount('error') === 1) { 59 | this.emit('error', err); 60 | } 61 | }); 62 | } 63 | return this; 64 | }); 65 | } 66 | 67 | /** 68 | * added for Mock testing 69 | */ 70 | public getSocket(): unknown { 71 | return this.socket; 72 | } 73 | 74 | public end(): this { 75 | if (this.socket) { 76 | this.socket.end(); 77 | } 78 | return this; 79 | } 80 | 81 | public write(data: Buffer, callback?: (err?: Error) => void): this { 82 | this.socket.write(dump(data), callback); 83 | return this; 84 | } 85 | 86 | public startServer(): Bluebird { 87 | let port = 0; 88 | if ('port' in this.options) { 89 | port = this.options.port; 90 | } 91 | const args: string[] = port ? ['-P', String(port), 'start-server'] : ['start-server']; 92 | debug(`Starting ADB server via '${this.options.bin} ${args.join(' ')}'`); 93 | return this._exec(args, {}); 94 | } 95 | 96 | private _exec(args: string[], options): Bluebird { 97 | debug(`CLI: ${this.options.bin} ${args.join(' ')}`); 98 | return Bluebird.promisify< 99 | ChildProcess, 100 | string, 101 | ReadonlyArray, 102 | ({ encoding?: string | null } & ExecFileOptions) | undefined | null 103 | // eslint-disable-next-line indent 104 | >(execFile)(this.options.bin as string, args, options); 105 | } 106 | 107 | // _handleError(err) {} 108 | } 109 | -------------------------------------------------------------------------------- /src/adb/dump.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | let func: (chunk: Buffer) => Buffer; 4 | 5 | if (process.env.ADBKIT_DUMP) { 6 | const out = fs.createWriteStream('adbkit.dump'); 7 | func = function (chunk: Buffer): Buffer { 8 | out.write(chunk); 9 | return chunk; 10 | }; 11 | } else { 12 | func = (chunk: Buffer): Buffer => chunk; 13 | } 14 | 15 | export = func; 16 | -------------------------------------------------------------------------------- /src/adb/framebuffer/rgbtransform.ts: -------------------------------------------------------------------------------- 1 | import Assert from 'assert'; 2 | import { Stream, TransformCallback, TransformOptions } from 'stream'; 3 | import FramebufferMeta from '../../FramebufferMeta'; 4 | 5 | class RgbTransform extends Stream.Transform { 6 | private _buffer = Buffer.from(''); 7 | private readonly _r_pos: number; 8 | private readonly _g_pos: number; 9 | private readonly _b_pos: number; 10 | private readonly _a_pos: number; 11 | private readonly _pixel_bytes: number; 12 | 13 | constructor(private meta: FramebufferMeta, options?: TransformOptions) { 14 | super(options); 15 | Assert.ok( 16 | this.meta.bpp === 24 || this.meta.bpp === 32, 17 | 'Only 24-bit and 32-bit raw images with 8-bits per color are supported', 18 | ); 19 | this._r_pos = this.meta.red_offset / 8; 20 | this._g_pos = this.meta.green_offset / 8; 21 | this._b_pos = this.meta.blue_offset / 8; 22 | this._a_pos = this.meta.alpha_offset / 8; 23 | this._pixel_bytes = this.meta.bpp / 8; 24 | } 25 | 26 | _transform(chunk: Buffer, encoding: string, done: TransformCallback): void { 27 | if (this._buffer.length) { 28 | this._buffer = Buffer.concat([this._buffer, chunk], this._buffer.length + chunk.length); 29 | } else { 30 | this._buffer = chunk; 31 | } 32 | let sourceCursor = 0; 33 | let targetCursor = 0; 34 | const target = 35 | this._pixel_bytes === 3 ? this._buffer : Buffer.alloc(Math.max(4, (chunk.length / this._pixel_bytes) * 3)); 36 | while (this._buffer.length - sourceCursor >= this._pixel_bytes) { 37 | const r = this._buffer[sourceCursor + this._r_pos]; 38 | const g = this._buffer[sourceCursor + this._g_pos]; 39 | const b = this._buffer[sourceCursor + this._b_pos]; 40 | target[targetCursor + 0] = r; 41 | target[targetCursor + 1] = g; 42 | target[targetCursor + 2] = b; 43 | sourceCursor += this._pixel_bytes; 44 | targetCursor += 3; 45 | } 46 | if (targetCursor) { 47 | this.push(target.slice(0, targetCursor)); 48 | this._buffer = this._buffer.slice(sourceCursor); 49 | } 50 | done(); 51 | } 52 | } 53 | 54 | export = RgbTransform; 55 | -------------------------------------------------------------------------------- /src/adb/jdwptracker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { EventEmitter } from 'events'; 3 | import Bluebird from 'bluebird'; 4 | import Parser from './parser'; 5 | import Command from './command'; 6 | 7 | interface JdwpTrackerChangeSet { 8 | /** 9 | * array of pid 10 | */ 11 | removed: string[]; 12 | /** 13 | * array of pid 14 | */ 15 | added: string[]; 16 | } 17 | 18 | export default class JdwpTracker extends EventEmitter { 19 | private pids = []; 20 | private pidMap = Object.create(null); 21 | private reader: Bluebird; 22 | 23 | constructor(private command: Command) { 24 | super(); 25 | this.command = command; 26 | this.pids = []; 27 | this.pidMap = Object.create(null); 28 | this.reader = this.read() 29 | .catch(Parser.PrematureEOFError, () => { 30 | return this.emit('end'); 31 | }) 32 | .catch(Bluebird.CancellationError, () => { 33 | this.command.connection.end(); 34 | return this.emit('end'); 35 | }) 36 | .catch((err) => { 37 | this.command.connection.end(); 38 | this.emit('error', err); 39 | return this.emit('end'); 40 | }); 41 | } 42 | 43 | on(event: 'end', listener: () => void): this; 44 | on(event: 'error', listener: (err: Error) => void): this; 45 | on(event: 'remove', listener: (pid: string) => void): this; 46 | on(event: 'add', listener: (pid: string) => void): this; 47 | on(event: 'changeSet', listener: (changeSet: JdwpTrackerChangeSet, newList: string[]) => void): this; 48 | on(event: string | symbol, listener: (...args: any[]) => void): this { 49 | return super.on(event, listener); 50 | } 51 | 52 | once(event: 'end', listener: () => void): this; 53 | once(event: 'error', listener: (err: Error) => void): this; 54 | once(event: 'remove', listener: (pid: string) => void): this; 55 | once(event: 'add', listener: (pid: string) => void): this; 56 | once(event: 'changeSet', listener: (changeSet: JdwpTrackerChangeSet, newList: string[]) => void): this; 57 | once(event: string | symbol, listener: (...args: any[]) => void): this { 58 | return super.once(event, listener); 59 | } 60 | 61 | emit(event: 'end'): boolean; 62 | emit(event: 'error', err: Error): boolean; 63 | emit(event: 'remove', pid: string): boolean; 64 | emit(event: 'add', pid: string): boolean; 65 | emit(event: 'changeSet', changeSet: JdwpTrackerChangeSet, newList: string[]): boolean; 66 | emit(event: string | symbol, ...args: any[]): boolean { 67 | return super.emit(event, ...args); 68 | } 69 | 70 | read(): Bluebird { 71 | return this.command.parser.readValue().then((list) => { 72 | const pids = list.toString().split('\n'); 73 | const maybeEmpty = pids.pop(); 74 | if (maybeEmpty) { 75 | pids.push(maybeEmpty); 76 | } 77 | return this.update(pids); 78 | }); 79 | } 80 | 81 | update(newList: string[]): JdwpTracker { 82 | const changeSet: JdwpTrackerChangeSet = { 83 | removed: [], 84 | added: [], 85 | }; 86 | const newMap = Object.create(null); 87 | for (let i = 0, len = newList.length; i < len; i++) { 88 | const pid = newList[i]; 89 | if (!this.pidMap[pid]) { 90 | changeSet.added.push(pid); 91 | this.emit('add', pid); 92 | newMap[pid] = pid; 93 | } 94 | } 95 | const ref = this.pids; 96 | for (let j = 0, len1 = ref.length; j < len1; j++) { 97 | const pid = ref[j]; 98 | if (!newMap[pid]) { 99 | changeSet.removed.push(pid); 100 | this.emit('remove', pid); 101 | } 102 | } 103 | this.pids = newList; 104 | this.pidMap = newMap; 105 | this.emit('changeSet', changeSet, newList); 106 | return this; 107 | } 108 | 109 | end(): JdwpTracker { 110 | this.reader.cancel(); 111 | return this; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/adb/linetransform.ts: -------------------------------------------------------------------------------- 1 | import Stream, { TransformCallback, TransformOptions } from 'stream'; 2 | 3 | interface LineTransformOptions extends TransformOptions { 4 | autoDetect?: boolean; 5 | } 6 | 7 | export default class LineTransform extends Stream.Transform { 8 | private savedR?: Buffer; 9 | private autoDetect: boolean; 10 | private transformNeeded: boolean; 11 | private skipBytes: number; 12 | 13 | constructor(options: LineTransformOptions = {}) { 14 | super(options); 15 | // this.savedR = null; 16 | this.autoDetect = options.autoDetect || false; 17 | this.transformNeeded = true; 18 | this.skipBytes = 0; 19 | } 20 | 21 | _nullTransform(chunk: Buffer, encoding: string, done: TransformCallback): void { 22 | this.push(chunk); 23 | done(); 24 | } 25 | 26 | // Sadly, the ADB shell is not very smart. It automatically converts every 27 | // 0x0a ('\n') it can find to 0x0d 0x0a ('\r\n'). This also applies to binary 28 | // content. We could get rid of this behavior by setting `stty raw`, but 29 | // unfortunately it's not available by default (you'd have to install busybox) 30 | // or something similar. On the up side, it really does do this for all line 31 | // feeds, so a simple transform works fine. 32 | _transform(chunk: Buffer, encoding: string, done: TransformCallback): void { 33 | // If auto detection is enabled, check the first byte. The first two 34 | // bytes must be either 0x0a .. or 0x0d 0x0a. This causes a need to skip 35 | // either one or two bytes. The autodetection runs only once. 36 | if (this.autoDetect) { 37 | if (chunk[0] === 0x0a) { 38 | this.transformNeeded = false; 39 | this.skipBytes = 1; 40 | } else { 41 | this.skipBytes = 2; 42 | } 43 | this.autoDetect = false; 44 | } 45 | // It's technically possible that we may receive the first two bytes 46 | // in two separate chunks. That's why the autodetect bytes are skipped 47 | // here, separately. 48 | if (this.skipBytes) { 49 | const skip = Math.min(chunk.length, this.skipBytes); 50 | chunk = chunk.slice(skip); 51 | this.skipBytes -= skip; 52 | } 53 | if (!chunk.length) { 54 | // It's possible that skipping bytes has created an empty chunk. 55 | return done(); 56 | } 57 | if (!this.transformNeeded) { 58 | // At this point all bytes that needed to be skipped should have been 59 | // skipped. If transform is not needed, shortcut to null transform. 60 | return this._nullTransform(chunk, encoding, done); 61 | } 62 | // Ok looks like we're transforming. 63 | let lo = 0; 64 | let hi = 0; 65 | if (this.savedR) { 66 | if (chunk[0] !== 0x0a) { 67 | this.push(this.savedR); 68 | } 69 | this.savedR = undefined; 70 | } 71 | const last = chunk.length - 1; 72 | while (hi <= last) { 73 | if (chunk[hi] === 0x0d) { 74 | if (hi === last) { 75 | this.savedR = chunk.slice(last); 76 | break; // Stop hi from incrementing, we want to skip the last byte. 77 | } else if (chunk[hi + 1] === 0x0a) { 78 | this.push(chunk.slice(lo, hi)); 79 | lo = hi + 1; 80 | } 81 | } 82 | hi += 1; 83 | } 84 | if (hi !== lo) { 85 | this.push(chunk.slice(lo, hi)); 86 | } 87 | done(); 88 | } 89 | 90 | // When the stream ends on an '\r', output it as-is (assume binary data). 91 | _flush(done: TransformCallback): void { 92 | if (this.savedR) { 93 | this.push(this.savedR); 94 | } 95 | done(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/adb/proc/stat.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import Parser from '../parser'; 3 | import Sync from '../sync'; 4 | import { CpuStats, Loads } from '../../CpuStats'; 5 | import Bluebird from 'bluebird'; 6 | 7 | const RE_CPULINE = /^cpu[0-9]+ .*$/gm; 8 | const RE_COLSEP = /\ +/g; 9 | 10 | interface CpuStatsWithLine extends CpuStats { 11 | line: string; 12 | } 13 | 14 | interface LoadsWithLine { 15 | [index: string]: CpuStatsWithLine; 16 | } 17 | 18 | type Stats = { cpus: LoadsWithLine }; 19 | 20 | class ProcStat extends EventEmitter { 21 | public interval = 1000; 22 | public stats: Stats; 23 | private readonly _ignore; 24 | private readonly _timer; 25 | 26 | constructor(private sync?: Sync) { 27 | super(); 28 | 29 | this.stats = this._emptyStats(); 30 | this._ignore = {}; 31 | this._timer = setInterval(() => { 32 | return this.update(); 33 | }, this.interval); 34 | this.update(); 35 | } 36 | 37 | public end(): void { 38 | clearInterval(this._timer); 39 | if (this.sync) { 40 | this.sync.end(); 41 | this.sync = undefined; 42 | } 43 | } 44 | 45 | public update(): Bluebird { 46 | if (!this.sync) { 47 | throw Error('Closed'); 48 | } 49 | return new Parser(this.sync.pull('/proc/stat')) 50 | .readAll() 51 | .then((out) => { 52 | return this._parse(out.toString()); 53 | }) 54 | .catch((err) => { 55 | this._error(err); 56 | return Bluebird.reject(err); 57 | }); 58 | } 59 | 60 | private _parse(out: string): Stats { 61 | let match, val; 62 | const stats = this._emptyStats(); 63 | while ((match = RE_CPULINE.exec(out))) { 64 | const line: string = match[0]; 65 | const cols = line.split(RE_COLSEP); 66 | const type = cols.shift(); 67 | if (this._ignore[type] === line) { 68 | continue; 69 | } 70 | let total = 0; 71 | for (let i = 0, len = cols.length; i < len; i++) { 72 | val = cols[i]; 73 | total += +val; 74 | } 75 | stats.cpus[type] = { 76 | line: line, 77 | user: +cols[0] || 0, 78 | nice: +cols[1] || 0, 79 | system: +cols[2] || 0, 80 | idle: +cols[3] || 0, 81 | iowait: +cols[4] || 0, 82 | irq: +cols[5] || 0, 83 | softirq: +cols[6] || 0, 84 | steal: +cols[7] || 0, 85 | guest: +cols[8] || 0, 86 | guestnice: +cols[9] || 0, 87 | total: total, 88 | }; 89 | } 90 | return this._set(stats); 91 | } 92 | 93 | private _set(stats: Stats): Stats { 94 | const loads: Loads = {}; 95 | let found = false; 96 | const ref = stats.cpus; 97 | for (const id in ref) { 98 | const cur = ref[id]; 99 | const old = this.stats.cpus[id]; 100 | if (!old) { 101 | continue; 102 | } 103 | const ticks = cur.total - old.total; 104 | if (ticks > 0) { 105 | found = true; 106 | // Calculate percentages for everything. For ease of formatting, 107 | // let's do `x / y * 100` as `100 / y * x`. 108 | const m = 100 / ticks; 109 | loads[id] = { 110 | user: Math.floor(m * (cur.user - old.user)), 111 | nice: Math.floor(m * (cur.nice - old.nice)), 112 | system: Math.floor(m * (cur.system - old.system)), 113 | idle: Math.floor(m * (cur.idle - old.idle)), 114 | iowait: Math.floor(m * (cur.iowait - old.iowait)), 115 | irq: Math.floor(m * (cur.irq - old.irq)), 116 | softirq: Math.floor(m * (cur.softirq - old.softirq)), 117 | steal: Math.floor(m * (cur.steal - old.steal)), 118 | guest: Math.floor(m * (cur.guest - old.guest)), 119 | guestnice: Math.floor(m * (cur.guestnice - old.guestnice)), 120 | total: 100, 121 | }; 122 | } else { 123 | // The CPU is either offline (nothing was done) or it mysteriously 124 | // warped back in time (idle stat dropped significantly), causing the 125 | // total tick count to be <0. The latter seems to only happen on 126 | // Galaxy S4 so far. Either way we don't want those anomalies in our 127 | // stats. We'll also ignore the line in the next cycle. This doesn't 128 | // completely eliminate the anomalies, but it helps. 129 | this._ignore[id] = cur.line; 130 | delete stats.cpus[id]; 131 | } 132 | } 133 | if (found) { 134 | this.emit('load', loads); 135 | } 136 | return (this.stats = stats); 137 | } 138 | 139 | private _error(err: Error): boolean { 140 | return this.emit('error', err); 141 | } 142 | 143 | private _emptyStats(): { cpus: LoadsWithLine } { 144 | return { 145 | cpus: {}, 146 | }; 147 | } 148 | } 149 | 150 | export = ProcStat; 151 | -------------------------------------------------------------------------------- /src/adb/protocol.ts: -------------------------------------------------------------------------------- 1 | export default class Protocol { 2 | public static OKAY = 'OKAY'; 3 | public static FAIL = 'FAIL'; 4 | public static STAT = 'STAT'; 5 | public static STA2 = 'STA2'; 6 | public static LIST = 'LIST'; 7 | public static DENT = 'DENT'; 8 | public static RECV = 'RECV'; 9 | public static DATA = 'DATA'; 10 | public static DONE = 'DONE'; 11 | public static SEND = 'SEND'; 12 | public static QUIT = 'QUIT'; 13 | 14 | static decodeLength(length: string): number { 15 | return parseInt(length, 16); 16 | } 17 | 18 | static encodeLength(length: number): string { 19 | return length.toString(16).padStart(4, '0').toUpperCase(); 20 | } 21 | 22 | static encodeData(data: Buffer | string): Buffer { 23 | if (!Buffer.isBuffer(data)) { 24 | data = Buffer.from(data); 25 | } 26 | const len = Protocol.encodeLength(data.length); 27 | return Buffer.concat([Buffer.from(len), data]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/adb/sync/entry.ts: -------------------------------------------------------------------------------- 1 | import Stats from './stats'; 2 | 3 | class Entry extends Stats { 4 | constructor(public name: string, mode: number, size: number, mtime: number) { 5 | super(mode, BigInt(size), mtime); 6 | } 7 | 8 | public toString(): string { 9 | return this.name; 10 | } 11 | } 12 | 13 | export = Entry; 14 | -------------------------------------------------------------------------------- /src/adb/sync/pulltransfer.ts: -------------------------------------------------------------------------------- 1 | import { Stream } from 'stream'; 2 | 3 | export default class PullTransfer extends Stream.PassThrough { 4 | public stats = { 5 | bytesTransferred: 0, 6 | }; 7 | 8 | public cancel(): boolean { 9 | return this.emit('cancel'); 10 | } 11 | 12 | write( 13 | chunk: Buffer, 14 | encoding?: BufferEncoding | typeof callback, 15 | callback?: (error: Error | null | undefined) => void, 16 | ): boolean { 17 | this.stats.bytesTransferred += chunk.length; 18 | this.emit('progress', this.stats); 19 | if (typeof encoding === 'function') { 20 | return super.write(chunk, encoding); 21 | } 22 | return super.write(chunk, encoding, callback); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/adb/sync/pushtransfer.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | export default class PushTransfer extends EventEmitter { 4 | private _stack: number[] = []; 5 | public stats = { 6 | bytesTransferred: 0, 7 | }; 8 | 9 | public cancel(): boolean { 10 | return this.emit('cancel'); 11 | } 12 | 13 | public push(byteCount: number): number { 14 | return this._stack.push(byteCount); 15 | } 16 | 17 | public pop(): boolean { 18 | const byteCount = this._stack.pop(); 19 | if (byteCount) { 20 | this.stats.bytesTransferred += byteCount; 21 | } 22 | return this.emit('progress', this.stats); 23 | } 24 | 25 | public end(): boolean { 26 | return this.emit('end'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/adb/sync/stats.ts: -------------------------------------------------------------------------------- 1 | import * as Fs from 'fs'; 2 | 3 | export default class Stats extends Fs.Stats { 4 | // The following constant were extracted from `man 2 stat` on Ubuntu 12.10. 5 | public static S_IFMT = 0o170000; // bit mask for the file type bit fields 6 | 7 | public static S_IFSOCK = 0o140000; // socket 8 | 9 | public static S_IFLNK = 0o120000; // symbolic link 10 | 11 | public static S_IFREG = 0o100000; // regular file 12 | 13 | public static S_IFBLK = 0o060000; // block device 14 | 15 | public static S_IFDIR = 0o040000; // directory 16 | 17 | public static S_IFCHR = 0o020000; // character device 18 | 19 | public static S_IFIFO = 0o010000; // FIFO 20 | 21 | public static S_ISUID = 0o004000; // set UID bit 22 | 23 | public static S_ISGID = 0o002000; // set-group-ID bit (see below) 24 | 25 | public static S_ISVTX = 0o001000; // sticky bit (see below) 26 | 27 | public static S_IRWXU = 0o0700; // mask for file owner permissions 28 | 29 | public static S_IRUSR = 0o0400; // owner has read permission 30 | 31 | public static S_IWUSR = 0o0200; // owner has write permission 32 | 33 | public static S_IXUSR = 0o0100; // owner has execute permission 34 | 35 | public static S_IRWXG = 0o0070; // mask for group permissions 36 | 37 | public static S_IRGRP = 0o0040; // group has read permission 38 | 39 | sizeBig: bigint; 40 | 41 | constructor(mode: number, sizeBig: bigint, mtime: number) { 42 | super(); 43 | this.mode = Number(mode); 44 | this.size = Number(sizeBig); 45 | this.sizeBig = sizeBig; 46 | this.mtimeMs = mtime * 1000; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/adb/tcpusb/packet.ts: -------------------------------------------------------------------------------- 1 | export default class Packet { 2 | public static A_SYNC = 0x434e5953; 3 | public static A_CNXN = 0x4e584e43; 4 | public static A_OPEN = 0x4e45504f; 5 | public static A_OKAY = 0x59414b4f; 6 | public static A_CLSE = 0x45534c43; 7 | public static A_WRTE = 0x45545257; 8 | public static A_AUTH = 0x48545541; 9 | 10 | public static checksum(data?: Buffer): number { 11 | let sum = 0; 12 | if (data) { 13 | for (let i = 0, len = data.length; i < len; i++) { 14 | const char = data[i]; 15 | sum += char; 16 | } 17 | } 18 | return sum; 19 | } 20 | 21 | public static magic(command: number): number { 22 | // We need the full uint32 range, which ">>> 0" thankfully allows us to use 23 | return (command ^ 0xffffffff) >>> 0; 24 | } 25 | 26 | public static assemble(command: number, arg0: number, arg1: number, data?: Buffer): Buffer { 27 | if (data) { 28 | const chunk = Buffer.alloc(24 + data.length); 29 | chunk.writeUInt32LE(command, 0); 30 | chunk.writeUInt32LE(arg0, 4); 31 | chunk.writeUInt32LE(arg1, 8); 32 | chunk.writeUInt32LE(data.length, 12); 33 | chunk.writeUInt32LE(Packet.checksum(data), 16); 34 | chunk.writeUInt32LE(Packet.magic(command), 20); 35 | data.copy(chunk, 24); 36 | return chunk; 37 | } else { 38 | const chunk = Buffer.alloc(24); 39 | chunk.writeUInt32LE(command, 0); 40 | chunk.writeUInt32LE(arg0, 4); 41 | chunk.writeUInt32LE(arg1, 8); 42 | chunk.writeUInt32LE(0, 12); 43 | chunk.writeUInt32LE(0, 16); 44 | chunk.writeUInt32LE(Packet.magic(command), 20); 45 | return chunk; 46 | } 47 | } 48 | 49 | static swap32(n: number): number { 50 | const buffer = Buffer.alloc(4); 51 | buffer.writeUInt32LE(n, 0); 52 | return buffer.readUInt32BE(0); 53 | } 54 | 55 | constructor( 56 | public readonly command: number, 57 | public readonly arg0: number, 58 | public readonly arg1: number, 59 | readonly length: number, 60 | readonly check: number, 61 | readonly magic: number, 62 | public data?: Buffer, 63 | ) {} 64 | 65 | public verifyChecksum(): boolean { 66 | return this.check === 0 ? true : this.check === Packet.checksum(this.data); 67 | } 68 | 69 | public verifyMagic(): boolean { 70 | return this.magic === Packet.magic(this.command); 71 | } 72 | 73 | private getType(): string { 74 | switch (this.command) { 75 | case Packet.A_SYNC: 76 | return 'SYNC'; 77 | case Packet.A_CNXN: 78 | return 'CNXN'; 79 | case Packet.A_OPEN: 80 | return 'OPEN'; 81 | case Packet.A_OKAY: 82 | return 'OKAY'; 83 | case Packet.A_CLSE: 84 | return 'CLSE'; 85 | case Packet.A_WRTE: 86 | return 'WRTE'; 87 | case Packet.A_AUTH: 88 | return 'AUTH'; 89 | default: 90 | throw new Error('Unknown command {@command}'); 91 | } 92 | } 93 | 94 | toString(): string { 95 | const type = this.getType(); 96 | return `${type} arg0=${this.arg0} arg1=${this.arg1} length=${this.length}`; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/adb/tcpusb/packetreader.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import Packet from './packet'; 3 | import ReadableStream = NodeJS.ReadableStream; 4 | 5 | class ChecksumError extends Error { 6 | constructor(public packet: Packet) { 7 | super(); 8 | Object.setPrototypeOf(this, ChecksumError.prototype); 9 | this.name = 'ChecksumError'; 10 | this.message = 'Checksum mismatch'; 11 | Error.captureStackTrace(this, PacketReader.ChecksumError); 12 | } 13 | } 14 | 15 | class MagicError extends Error { 16 | constructor(public packet: Packet) { 17 | super(); 18 | Object.setPrototypeOf(this, MagicError.prototype); 19 | this.name = 'MagicError'; 20 | this.message = 'Magic value mismatch'; 21 | Error.captureStackTrace(this, PacketReader.MagicError); 22 | } 23 | } 24 | 25 | export default class PacketReader extends EventEmitter { 26 | public static ChecksumError = ChecksumError; 27 | public static MagicError = MagicError; 28 | 29 | private inBody = false; 30 | private buffer?: Buffer; 31 | private packet?: Packet; 32 | 33 | constructor(private stream: ReadableStream) { 34 | super(); 35 | this.stream.on('readable', this._tryRead.bind(this)); 36 | this.stream.on('error', (err) => { 37 | return this.emit('error', err); 38 | }); 39 | this.stream.on('end', () => { 40 | return this.emit('end'); 41 | }); 42 | setImmediate(this._tryRead.bind(this)); 43 | } 44 | 45 | private _tryRead(): void { 46 | while (this._appendChunk()) { 47 | while (this.buffer) { 48 | if (this.inBody) { 49 | if (!(this.buffer.length >= this.packet.length)) { 50 | break; 51 | } 52 | this.packet.data = this._consume(this.packet.length); 53 | if (!this.packet.verifyChecksum()) { 54 | this.emit('error', new PacketReader.ChecksumError(this.packet)); 55 | return; 56 | } 57 | this.emit('packet', this.packet); 58 | this.inBody = false; 59 | } else { 60 | if (!(this.buffer.length >= 24)) { 61 | break; 62 | } 63 | const header = this._consume(24); 64 | this.packet = new Packet( 65 | header.readUInt32LE(0), 66 | header.readUInt32LE(4), 67 | header.readUInt32LE(8), 68 | header.readUInt32LE(12), 69 | header.readUInt32LE(16), 70 | header.readUInt32LE(20), 71 | Buffer.alloc(0), 72 | ); 73 | if (!this.packet.verifyMagic()) { 74 | this.emit('error', new PacketReader.MagicError(this.packet)); 75 | return; 76 | } 77 | if (this.packet.length === 0) { 78 | this.emit('packet', this.packet); 79 | } else { 80 | this.inBody = true; 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | private _appendChunk(): Buffer | null { 88 | const chunk = this.stream.read() as Buffer; 89 | if (chunk) { 90 | if (this.buffer) { 91 | return (this.buffer = Buffer.concat([this.buffer, chunk], this.buffer.length + chunk.length)); 92 | } else { 93 | return (this.buffer = chunk); 94 | } 95 | } else { 96 | return null; 97 | } 98 | } 99 | 100 | private _consume(length): Buffer { 101 | const chunk = this.buffer.slice(0, length); 102 | this.buffer = length === this.buffer.length ? null : this.buffer.slice(length); 103 | return chunk; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/adb/tcpusb/rollingcounter.ts: -------------------------------------------------------------------------------- 1 | export default class RollingCounter { 2 | private now: number; 3 | 4 | constructor(private readonly max: number, private readonly min = 1) { 5 | this.now = this.min; 6 | } 7 | 8 | public next(): number { 9 | if (!(this.now < this.max)) { 10 | this.now = this.min; 11 | } 12 | return ++this.now; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/adb/tcpusb/server.ts: -------------------------------------------------------------------------------- 1 | import * as Net from 'net'; 2 | import Socket from './socket'; 3 | import { EventEmitter } from 'events'; 4 | import Client from '../client'; 5 | import SocketOptions from '../../SocketOptions'; 6 | 7 | type NetServer = Net.Server; 8 | 9 | export default class Server extends EventEmitter { 10 | private readonly server: Net.Server; 11 | private connections: Socket[] = []; 12 | 13 | constructor( 14 | private readonly client: Client, 15 | private readonly serial: string, 16 | private readonly options: SocketOptions, 17 | ) { 18 | super(); 19 | this.server = Net.createServer({ 20 | allowHalfOpen: true, 21 | }); 22 | this.server.on('error', (err) => this.emit('error', err)); 23 | this.server.on('listening', () => this.emit('listening')); 24 | this.server.on('close', () => this.emit('close')); 25 | this.server.on('connection', (conn) => { 26 | const socket = new Socket(this.client, this.serial, conn, this.options); 27 | this.connections.push(socket); 28 | socket.on('error', (err) => { 29 | // 'conn' is guaranteed to get ended 30 | return this.emit('error', err); 31 | }); 32 | socket.once('end', () => { 33 | // 'conn' is guaranteed to get ended 34 | return (this.connections = this.connections.filter(function (val) { 35 | return val !== socket; 36 | })); 37 | }); 38 | return this.emit('connection', socket); 39 | }); 40 | } 41 | 42 | public listen(...args: Parameters): Server { 43 | this.server.listen(...args); 44 | return this; 45 | } 46 | 47 | public close(): Server { 48 | this.server.close(); 49 | return this; 50 | } 51 | 52 | public end(): Server { 53 | const ref = this.connections; 54 | for (let i = 0, len = ref.length; i < len; i++) { 55 | ref[i].end(); 56 | } 57 | return this; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/adb/tcpusb/service.ts: -------------------------------------------------------------------------------- 1 | import d from 'debug'; 2 | import { EventEmitter } from 'events'; 3 | import Packet from './packet'; 4 | import Bluebird from 'bluebird'; 5 | import Protocol from '../protocol'; 6 | import Client from '../client'; 7 | import Socket from './socket'; 8 | import ReadableStream = NodeJS.ReadableStream; 9 | import Connection from '../connection'; 10 | const debug = d('adb:tcpusb:service'); 11 | 12 | class PrematurePacketError extends Error { 13 | constructor(public packet: Packet) { 14 | super(); 15 | Object.setPrototypeOf(this, PrematurePacketError.prototype); 16 | this.name = 'PrematurePacketError'; 17 | this.message = 'Premature packet'; 18 | Error.captureStackTrace(this, Service.PrematurePacketError); 19 | } 20 | } 21 | 22 | class LateTransportError extends Error { 23 | constructor() { 24 | super(); 25 | Object.setPrototypeOf(this, LateTransportError.prototype); 26 | this.name = 'LateTransportError'; 27 | this.message = 'Late transport'; 28 | Error.captureStackTrace(this, Service.LateTransportError); 29 | } 30 | } 31 | 32 | export default class Service extends EventEmitter { 33 | public static PrematurePacketError = PrematurePacketError; 34 | public static LateTransportError = LateTransportError; 35 | 36 | private opened = false; 37 | private ended = false; 38 | private transport?: Connection; 39 | private needAck = false; 40 | 41 | constructor( 42 | private client: Client, 43 | private serial: string, 44 | private localId: number, 45 | private remoteId: number, 46 | private socket: Socket, 47 | ) { 48 | super(); 49 | } 50 | 51 | public end(): Service { 52 | if (this.transport) { 53 | this.transport.end(); 54 | } 55 | if (this.ended) { 56 | return this; 57 | } 58 | debug('O:A_CLSE'); 59 | const localId = this.opened ? this.localId : 0; // Zero can only mean a failed open 60 | try { 61 | // We may or may not have gotten here due to @socket ending, so write 62 | // may fail. 63 | this.socket.write(Packet.assemble(Packet.A_CLSE, localId, this.remoteId, null)); 64 | } catch (error) {} 65 | // Let it go 66 | this.transport = null; 67 | this.ended = true; 68 | this.emit('end'); 69 | return this; 70 | } 71 | 72 | public handle(packet: Packet): Bluebird { 73 | return Bluebird.try(() => { 74 | switch (packet.command) { 75 | case Packet.A_OPEN: 76 | return this._handleOpenPacket(packet); 77 | case Packet.A_OKAY: 78 | return this._handleOkayPacket(packet); 79 | case Packet.A_WRTE: 80 | return this._handleWritePacket(packet); 81 | case Packet.A_CLSE: 82 | return this._handleClosePacket(packet); 83 | default: 84 | throw new Error(`Unexpected packet ${packet.command}`); 85 | } 86 | }).catch((err) => { 87 | this.emit('error', err); 88 | return this.end(); 89 | }); 90 | } 91 | 92 | private _handleOpenPacket(packet): Bluebird { 93 | debug('I:A_OPEN', packet); 94 | return this.client 95 | .getDevice(this.serial) 96 | .transport() 97 | .then((transport) => { 98 | this.transport = transport; 99 | if (this.ended) { 100 | throw new LateTransportError(); 101 | } 102 | this.transport.write(Protocol.encodeData(packet.data.slice(0, -1))); // Discard null byte at end 103 | return this.transport.parser.readAscii(4).then((reply) => { 104 | switch (reply) { 105 | case Protocol.OKAY: 106 | debug('O:A_OKAY'); 107 | this.socket.write(Packet.assemble(Packet.A_OKAY, this.localId, this.remoteId, null)); 108 | return (this.opened = true); 109 | case Protocol.FAIL: 110 | return this.transport.parser.readError(); 111 | default: 112 | return this.transport.parser.unexpected(reply, 'OKAY or FAIL'); 113 | } 114 | }); 115 | }) 116 | .then(() => { 117 | return new Bluebird((resolve, reject) => { 118 | this.transport.socket 119 | .on('readable', () => this._tryPush()) 120 | .on('end', resolve) 121 | .on('error', reject); 122 | return this._tryPush(); 123 | }); 124 | }) 125 | .finally(() => { 126 | return this.end(); 127 | }); 128 | } 129 | 130 | private _handleOkayPacket(packet: Packet): boolean | undefined { 131 | debug('I:A_OKAY', packet); 132 | if (this.ended) { 133 | return; 134 | } 135 | if (!this.transport) { 136 | throw new Service.PrematurePacketError(packet); 137 | } 138 | this.needAck = false; 139 | return this._tryPush(); 140 | } 141 | 142 | private _handleWritePacket(packet: Packet): boolean | undefined { 143 | debug('I:A_WRTE', packet); 144 | if (this.ended) { 145 | return; 146 | } 147 | if (!this.transport) { 148 | throw new Service.PrematurePacketError(packet); 149 | } 150 | if (this.transport && packet.data) { 151 | this.transport.write(packet.data); 152 | } 153 | debug('O:A_OKAY'); 154 | return this.socket.write(Packet.assemble(Packet.A_OKAY, this.localId, this.remoteId, null)); 155 | } 156 | 157 | private _handleClosePacket(packet: Packet): Service | undefined { 158 | debug('I:A_CLSE', packet); 159 | if (this.ended) { 160 | return; 161 | } 162 | if (!this.transport) { 163 | throw new Service.PrematurePacketError(packet); 164 | } 165 | return this.end(); 166 | } 167 | 168 | private _tryPush(): boolean | undefined { 169 | if (this.needAck || this.ended) { 170 | return; 171 | } 172 | const chunk = this._readChunk(this.transport.socket); 173 | if (chunk) { 174 | debug('O:A_WRTE'); 175 | this.socket.write(Packet.assemble(Packet.A_WRTE, this.localId, this.remoteId, chunk)); 176 | return (this.needAck = true); 177 | } 178 | } 179 | 180 | private _readChunk(stream: ReadableStream): Buffer { 181 | return (stream.read(this.socket.maxPayload) || stream.read()) as Buffer; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/adb/tcpusb/servicemap.ts: -------------------------------------------------------------------------------- 1 | import Service from './service'; 2 | 3 | export default class ServiceMap { 4 | private remotes: Record = Object.create(null); 5 | public count = 0; 6 | 7 | public end(): void { 8 | const ref = this.remotes; 9 | for (const remoteId in ref) { 10 | const remote = ref[remoteId]; 11 | remote.end(); 12 | } 13 | this.remotes = Object.create(null); 14 | this.count = 0; 15 | } 16 | 17 | public insert(remoteId: number, socket: Service): Service { 18 | if (this.remotes[remoteId]) { 19 | throw new Error(`Remote ID ${remoteId} is already being used`); 20 | } else { 21 | this.count += 1; 22 | return (this.remotes[remoteId] = socket); 23 | } 24 | } 25 | 26 | public get(remoteId: number): Service | null { 27 | return this.remotes[remoteId] || null; 28 | } 29 | 30 | public remove(remoteId: number): Service | null { 31 | let remote: Service; 32 | if ((remote = this.remotes[remoteId])) { 33 | delete this.remotes[remoteId]; 34 | this.count -= 1; 35 | return remote; 36 | } else { 37 | return null; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/adb/tracker.ts: -------------------------------------------------------------------------------- 1 | import Bluebird from 'bluebird'; 2 | import Parser from './parser'; 3 | import { EventEmitter } from 'events'; 4 | import Device from '../Device'; 5 | import HostDevicesCommand from './command/host/devices'; 6 | import HostDevicesWithPathsCommand from './command/host/deviceswithpaths'; 7 | import TrackerChangeSet from '../TrackerChangeSet'; 8 | 9 | export default class Tracker extends EventEmitter { 10 | private deviceList: Device[] = []; 11 | private deviceMap: Record = {}; 12 | private reader: Bluebird; 13 | 14 | constructor(private readonly command: HostDevicesCommand | HostDevicesWithPathsCommand) { 15 | super(); 16 | this.reader = this.read() 17 | .catch(Bluebird.CancellationError, () => true) 18 | .catch(Parser.PrematureEOFError, () => { 19 | throw new Error('Connection closed'); 20 | }) 21 | .catch((err) => this.emit('error', err)) 22 | .finally(() => { 23 | this.command.parser.end().then(() => this.emit('end')); 24 | }); 25 | } 26 | 27 | public read(): Bluebird { 28 | return this.command._readDevices().then((list) => { 29 | this.update(list); 30 | return this.read(); 31 | }); 32 | } 33 | 34 | public update(newList: Device[]): Tracker { 35 | const changeSet: TrackerChangeSet = { 36 | removed: [], 37 | changed: [], 38 | added: [], 39 | }; 40 | const newMap: Record = {}; 41 | for (let i = 0, len = newList.length; i < len; i++) { 42 | const device = newList[i]; 43 | const oldDevice = this.deviceMap[device.id]; 44 | if (oldDevice) { 45 | if (oldDevice.type !== device.type) { 46 | changeSet.changed.push(device); 47 | this.emit('change', device, oldDevice); 48 | } 49 | } else { 50 | changeSet.added.push(device); 51 | this.emit('add', device); 52 | } 53 | newMap[device.id] = device; 54 | } 55 | const ref = this.deviceList; 56 | for (let i = 0, len = ref.length; i < len; i++) { 57 | const device = ref[i]; 58 | if (!newMap[device.id]) { 59 | changeSet.removed.push(device); 60 | this.emit('remove', device); 61 | } 62 | } 63 | this.emit('changeSet', changeSet); 64 | this.deviceList = newList; 65 | this.deviceMap = newMap; 66 | return this; 67 | } 68 | 69 | public end(): Tracker { 70 | this.reader.cancel(); 71 | return this; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/adb/util.ts: -------------------------------------------------------------------------------- 1 | import Parser from './parser'; 2 | import Auth from './auth'; 3 | import { Callback } from '../Callback'; 4 | import ExtendedPublicKey from '../ExtendedPublicKey'; 5 | import Bluebird from 'bluebird'; 6 | import { Duplex } from 'stream'; 7 | 8 | export default class Util { 9 | public static readAll(stream: Duplex, callback?: Callback): Bluebird { 10 | return new Parser(stream).readAll().nodeify(callback); 11 | } 12 | 13 | public static parsePublicKey(keyString: string, callback?: Callback): Bluebird { 14 | return Auth.parsePublicKey(keyString).nodeify(callback); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { Command } from 'commander'; 3 | import forge from 'node-forge'; 4 | import * as pkg from '../package.json'; 5 | import Adb from './adb'; 6 | import Auth from './adb/auth'; 7 | import PacketReader from './adb/tcpusb/packetreader'; 8 | import Bluebird from 'bluebird'; 9 | 10 | const program = new Command(); 11 | 12 | program.version(pkg.version); 13 | 14 | program 15 | .command('pubkey-convert ') 16 | .option('-f, --format ', 'format (pem or openssh)', String, 'pem') 17 | .description('Converts an ADB-generated public key into PEM format.') 18 | .action(function (file, options) { 19 | return Auth.parsePublicKey(fs.readFileSync(file).toString('utf8')).then((key) => { 20 | switch (options.format.toLowerCase()) { 21 | case 'pem': 22 | return console.log(forge.pki.publicKeyToPem(key).trim()); 23 | case 'openssh': 24 | return console.log(forge.ssh.publicKeyToOpenSSH(key, 'adbkey').trim()); 25 | default: 26 | console.error("Unsupported format '" + options.format + "'"); 27 | return process.exit(1); 28 | } 29 | }); 30 | }); 31 | 32 | program 33 | .command('pubkey-fingerprint ') 34 | .description('Outputs the fingerprint of an ADB-generated public key.') 35 | .action(function (file) { 36 | return Auth.parsePublicKey(fs.readFileSync(file).toString('utf8')).then((key) => { 37 | return console.log('%s %s', key.fingerprint, key.comment); 38 | }); 39 | }); 40 | 41 | program 42 | .command('usb-device-to-tcp ') 43 | .option('-p, --port ', 'port number', (value: string) => String(value), '6174') 44 | .description('Provides an USB device over TCP using a translating proxy.') 45 | .action((serial: string, options) => { 46 | const adb = Adb.createClient(); 47 | const server = adb 48 | .createTcpUsbBridge(serial, { 49 | auth: () => Bluebird.resolve(), 50 | }) 51 | .on('listening', () => console.info('Connect with `adb connect localhost:%d`', options.port)) 52 | .on('error', (err) => console.error('An error occured: ' + err.message)); 53 | server.listen(options.port); 54 | }); 55 | 56 | program 57 | .command('parse-tcp-packets ') 58 | .description('Parses ADB TCP packets from the given file.') 59 | .action((file: string) => { 60 | const reader = new PacketReader(fs.createReadStream(file)); 61 | reader.on('packet', (packet) => console.log(packet.toString())); 62 | }); 63 | 64 | program.parse(process.argv); 65 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // export { default } from './adb'; 2 | 3 | export { default as Adb } from './adb'; 4 | export { Callback } from './Callback'; 5 | export { ClientOptions } from './ClientOptions'; 6 | export { CpuStats, Loads } from './CpuStats'; 7 | export { default as Device } from './Device'; 8 | export { default as DeviceWithPath } from './DeviceWithPath'; 9 | export { default as ExtendedPublicKey } from './ExtendedPublicKey'; 10 | export { Features } from './Features'; 11 | export { default as Forward } from './Forward'; 12 | export { default as FramebufferMeta } from './FramebufferMeta'; 13 | export { default as FramebufferStreamWithMeta } from './FramebufferStreamWithMeta'; 14 | export { Properties } from './Properties'; 15 | export { default as Reverse } from './Reverse'; 16 | export { default as SocketOptions } from './SocketOptions'; 17 | export { default as StartActivityOptions } from './StartActivityOptions'; 18 | export { default as StartServiceOptions, ExtraValue, ExtraObject, Extra } from './StartServiceOptions'; 19 | export { default as TrackerChangeSet } from './TrackerChangeSet'; 20 | export { default as WithToString } from './WithToString'; 21 | 22 | // export { default as Client } from './adb/client'; 23 | // export { default as ShellCommand } from './adb/command/host-transport/shell'; 24 | -------------------------------------------------------------------------------- /tasks/keycode.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const EOL = require('os').EOL; 5 | 6 | const repo_path = '/aosp-mirror/platform_frameworks_base/master'; 7 | const original = { 8 | hostname: 'raw.githubusercontent.com', 9 | path: repo_path + '/core/java/android/view/KeyEvent.java', 10 | method: 'GET', 11 | }; 12 | const regex = /public static final int (KEYCODE_[^\s]+)\s*=\s*([0-9]+);/g; 13 | const file = path.resolve(__dirname, '../src/adb/keycode.ts'); 14 | 15 | const req = https.request(original, function (res) { 16 | if (res.statusCode !== 200) { 17 | console.warn('Unable to retrieve KeyEvent.java (HTTP ' + res.statusCode + ')'); 18 | return; 19 | } 20 | let raw = Buffer.from(''); 21 | res.on('data', (chunk) => { 22 | return (raw = Buffer.concat([raw, chunk])); 23 | }); 24 | return res.on('end', () => { 25 | const code = raw.toString(); 26 | const date = new Date().toUTCString(); 27 | const typescript = []; 28 | typescript.push('// Generated by `npm run keycode` on ' + date); 29 | typescript.push('// KeyEvent.java Copyright (C) 2006 The Android Open Source Project'); 30 | typescript.push(''); 31 | typescript.push('export enum KeyCodes {'); 32 | let match; 33 | while (match = regex.exec(code)) { 34 | typescript.push(` ${match[1]} = ${match[2]},`); 35 | } 36 | typescript.push('}'); 37 | typescript.push(''); 38 | fs.writeFileSync(file, typescript.join(EOL)); 39 | console.log('File ' + file + ' created'); 40 | }); 41 | }); 42 | req.on('error', (e) => { 43 | console.error(e.message); 44 | }); 45 | return req.end(); 46 | -------------------------------------------------------------------------------- /test/adb.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Adb from '../src/adb'; 3 | import Client from '../src/adb/client'; 4 | // import { Keycode } from '../src/adb/keycode'; 5 | import util from '../src/adb/util'; 6 | 7 | describe('Adb', function () { 8 | //it('should expose Keycode', function (done) { 9 | // expect(Adb).to.have.property('Keycode'); 10 | // expect(Adb.Keycode).to.equal(Keycode); 11 | // done(); 12 | //}); 13 | it('should expose util', function (done) { 14 | expect(Adb).to.have.property('util'); 15 | expect(Adb.util).to.equal(util); 16 | done(); 17 | }); 18 | return describe('@createClient(options)', function () { 19 | return it('should return a Client instance', function (done) { 20 | expect(Adb.createClient()).to.be.an.instanceOf(Client); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/adb/command/host-serial/waitfordevice.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import { WaitForDeviceCommand } from '../../../../src/adb/command/host-serial'; 7 | 8 | describe('WaitForDeviceCommand', function () { 9 | it("should send 'host-serial::wait-for-any-device'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new WaitForDeviceCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('host-serial:abba:wait-for-any-device').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute('abba'); 21 | }); 22 | it('should resolve with id when the device is connected', function () { 23 | const conn = new MockConnection(); 24 | const cmd = new WaitForDeviceCommand(conn); 25 | setImmediate(function () { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead(Protocol.OKAY); 28 | return conn.getSocket().causeEnd(); 29 | }); 30 | return cmd.execute('abba').then(function (id) { 31 | expect(id).to.equal('abba'); 32 | }); 33 | }); 34 | return it('should reject with error if unable to connect', function () { 35 | const conn = new MockConnection(); 36 | const cmd = new WaitForDeviceCommand(conn); 37 | setImmediate(function () { 38 | conn.getSocket().causeRead(Protocol.OKAY); 39 | conn.getSocket().causeRead(Protocol.FAIL); 40 | conn.getSocket().causeRead(Protocol.encodeData('not sure how this might happen')); 41 | return conn.getSocket().causeEnd(); 42 | }); 43 | return cmd.execute('abba').catch(function (err) { 44 | expect(err.message).to.contain('not sure how this might happen'); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/clear.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import ClearCommand from '../../../../src/adb/command/host-transport/clear'; 7 | 8 | describe('ClearCommand', function () { 9 | it("should send 'pm clear '", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new ClearCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm clear foo.bar.c').toString()); 14 | conn.getSocket().causeRead(Protocol.OKAY); 15 | conn.getSocket().causeRead('Success\r\n'); 16 | return conn.getSocket().causeEnd(); 17 | }); 18 | return cmd.execute('foo.bar.c'); 19 | }); 20 | it("should succeed on 'Success'", function () { 21 | const conn = new MockConnection(); 22 | const cmd = new ClearCommand(conn); 23 | conn.getSocket().on('write', function (chunk) { 24 | conn.getSocket().causeRead(Protocol.OKAY); 25 | conn.getSocket().causeRead('Success\r\n'); 26 | return conn.getSocket().causeEnd(); 27 | }); 28 | return cmd.execute('foo.bar.c'); 29 | }); 30 | it("should error on 'Failed'", function (done) { 31 | const conn = new MockConnection(); 32 | const cmd = new ClearCommand(conn); 33 | conn.getSocket().on('write', function (chunk) { 34 | conn.getSocket().causeRead(Protocol.OKAY); 35 | conn.getSocket().causeRead('Failed\r\n'); 36 | return conn.getSocket().causeEnd(); 37 | }); 38 | cmd.execute('foo.bar.c').catch(function (err) { 39 | expect(err).to.be.an.instanceof(Error); 40 | done(); 41 | }); 42 | }); 43 | it("should error on 'Failed' even if connection not closed by device", function (done) { 44 | const conn = new MockConnection(); 45 | const cmd = new ClearCommand(conn); 46 | conn.getSocket().on('write', function (chunk) { 47 | conn.getSocket().causeRead(Protocol.OKAY); 48 | return conn.getSocket().causeRead('Failed\r\n'); 49 | }); 50 | cmd.execute('foo.bar.c').catch(function (err) { 51 | expect(err).to.be.an.instanceof(Error); 52 | done(); 53 | }); 54 | }); 55 | return it('should ignore irrelevant lines', function () { 56 | const conn = new MockConnection(); 57 | const cmd = new ClearCommand(conn); 58 | conn.getSocket().on('write', function (chunk) { 59 | conn.getSocket().causeRead(Protocol.OKAY); 60 | conn.getSocket().causeRead('Open: foo error\n\n'); 61 | conn.getSocket().causeRead('Success\r\n'); 62 | return conn.getSocket().causeEnd(); 63 | }); 64 | return cmd.execute('foo.bar.c'); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/framebuffer.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import FrameBufferCommand from '../../../../src/adb/command/host-transport/framebuffer'; 7 | 8 | describe('FrameBufferCommand', function () { 9 | it("should send 'framebuffer:'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new FrameBufferCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('framebuffer:').toString()); 14 | }); 15 | setImmediate(function () { 16 | const meta = Buffer.alloc(52); 17 | meta.fill(0); 18 | conn.getSocket().causeRead(Protocol.OKAY); 19 | conn.getSocket().causeRead(meta); 20 | return conn.getSocket().causeEnd(); 21 | }); 22 | return cmd.execute('raw'); 23 | }); 24 | return it("should parse meta header and return it as the 'meta' property of the stream", function () { 25 | const conn = new MockConnection(); 26 | const cmd = new FrameBufferCommand(conn); 27 | conn.getSocket().on('write', function (chunk) { 28 | return expect(chunk.toString()).to.equal(Protocol.encodeData('framebuffer:').toString()); 29 | }); 30 | setImmediate(function () { 31 | const meta = Buffer.alloc(52); 32 | let offset = 0; 33 | meta.writeUInt32LE(1, offset); 34 | meta.writeUInt32LE(32, (offset += 4)); 35 | meta.writeUInt32LE(819200, (offset += 4)); 36 | meta.writeUInt32LE(640, (offset += 4)); 37 | meta.writeUInt32LE(320, (offset += 4)); 38 | meta.writeUInt32LE(0, (offset += 4)); 39 | meta.writeUInt32LE(8, (offset += 4)); 40 | meta.writeUInt32LE(16, (offset += 4)); 41 | meta.writeUInt32LE(8, (offset += 4)); 42 | meta.writeUInt32LE(8, (offset += 4)); 43 | meta.writeUInt32LE(8, (offset += 4)); 44 | meta.writeUInt32LE(24, (offset += 4)); 45 | meta.writeUInt32LE(8, (offset += 4)); 46 | conn.getSocket().causeRead(Protocol.OKAY); 47 | conn.getSocket().causeRead(meta); 48 | return conn.getSocket().causeEnd(); 49 | }); 50 | return cmd.execute('raw').then(function (stream) { 51 | expect(stream).to.have.property('meta'); 52 | expect(stream.meta).to.eql({ 53 | version: 1, 54 | bpp: 32, 55 | size: 819200, 56 | width: 640, 57 | height: 320, 58 | red_offset: 0, 59 | red_length: 8, 60 | blue_offset: 16, 61 | blue_length: 8, 62 | green_offset: 8, 63 | green_length: 8, 64 | alpha_offset: 24, 65 | alpha_length: 8, 66 | format: 'rgba', 67 | }); 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/getfeatures.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import GetFeaturesCommand from '../../../../src/adb/command/host-transport/getfeatures'; 7 | 8 | describe('GetFeaturesCommand', function () { 9 | it("should send 'pm list features'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new GetFeaturesCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal( 14 | Protocol.encodeData('shell:pm list features 2>/dev/null').toString(), 15 | ); 16 | }); 17 | setImmediate(function () { 18 | conn.getSocket().causeRead(Protocol.OKAY); 19 | return conn.getSocket().causeEnd(); 20 | }); 21 | return cmd.execute(); 22 | }); 23 | it('should return an empty object for an empty feature list', function () { 24 | const conn = new MockConnection(); 25 | const cmd = new GetFeaturesCommand(conn); 26 | setImmediate(function () { 27 | conn.getSocket().causeRead(Protocol.OKAY); 28 | return conn.getSocket().causeEnd(); 29 | }); 30 | return cmd.execute().then(function (features) { 31 | expect(Object.keys(features)).to.be.empty; 32 | }); 33 | }); 34 | return it('should return a map of features', function () { 35 | const conn = new MockConnection(); 36 | const cmd = new GetFeaturesCommand(conn); 37 | setImmediate(function () { 38 | conn.getSocket().causeRead(Protocol.OKAY); 39 | conn.getSocket().causeRead(`feature:reqGlEsVersion=0x20000 40 | feature:foo\r 41 | feature:bar`); 42 | return conn.getSocket().causeEnd(); 43 | }); 44 | return cmd.execute().then(function (features) { 45 | expect(Object.keys(features)).to.have.length(3); 46 | expect(features).to.eql({ 47 | reqGlEsVersion: '0x20000', 48 | foo: true, 49 | bar: true, 50 | }); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/getpackages.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import GetPackagesCommand from '../../../../src/adb/command/host-transport/getpackages'; 7 | 8 | describe('GetPackagesCommand', function () { 9 | it("should send 'pm list packages'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new GetPackagesCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal( 14 | Protocol.encodeData('shell:pm list packages 2>/dev/null').toString(), 15 | ); 16 | }); 17 | setImmediate(function () { 18 | conn.getSocket().causeRead(Protocol.OKAY); 19 | return conn.getSocket().causeEnd(); 20 | }); 21 | return cmd.execute(); 22 | }); 23 | it("should send 'pm list packages' with flag", function() { 24 | const conn = new MockConnection(); 25 | const cmd = new GetPackagesCommand(conn); 26 | conn.getSocket().on('write', function(chunk) { 27 | return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm list packages -3 2>/dev/null').toString()); 28 | }); 29 | setImmediate(function() { 30 | conn.getSocket().causeRead(Protocol.OKAY); 31 | return conn.getSocket().causeEnd(); 32 | }); 33 | return cmd.execute('-3'); 34 | }); 35 | 36 | it('should return an empty array for an empty package list', function () { 37 | const conn = new MockConnection(); 38 | const cmd = new GetPackagesCommand(conn); 39 | setImmediate(function () { 40 | conn.getSocket().causeRead(Protocol.OKAY); 41 | return conn.getSocket().causeEnd(); 42 | }); 43 | return cmd.execute().then(function (packages) { 44 | expect(packages).to.be.empty; 45 | }); 46 | }); 47 | return it('should return an array of packages', function () { 48 | const conn = new MockConnection(); 49 | const cmd = new GetPackagesCommand(conn); 50 | setImmediate(function () { 51 | conn.getSocket().causeRead(Protocol.OKAY); 52 | conn.getSocket().causeRead(`package:com.google.android.gm 53 | package:com.google.android.inputmethod.japanese 54 | package:com.google.android.tag\r 55 | package:com.google.android.GoogleCamera 56 | package:com.google.android.youtube 57 | package:com.google.android.apps.magazines 58 | package:com.google.earth`); 59 | return conn.getSocket().causeEnd(); 60 | }); 61 | return cmd.execute().then(function (packages) { 62 | expect(packages).to.have.length(7); 63 | expect(packages).to.eql([ 64 | 'com.google.android.gm', 65 | 'com.google.android.inputmethod.japanese', 66 | 'com.google.android.tag', 67 | 'com.google.android.GoogleCamera', 68 | 'com.google.android.youtube', 69 | 'com.google.android.apps.magazines', 70 | 'com.google.earth', 71 | ]); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/getproperties.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import GetPropertiesCommand from '../../../../src/adb/command/host-transport/getproperties'; 7 | 8 | describe('GetPropertiesCommand', function () { 9 | it("should send 'getprop'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new GetPropertiesCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:getprop').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | return conn.getSocket().causeEnd(); 18 | }); 19 | return cmd.execute(); 20 | }); 21 | it('should return an empty object for an empty property list', function () { 22 | const conn = new MockConnection(); 23 | const cmd = new GetPropertiesCommand(conn); 24 | setImmediate(function () { 25 | conn.getSocket().causeRead(Protocol.OKAY); 26 | return conn.getSocket().causeEnd(); 27 | }); 28 | return cmd.execute().then(function (properties) { 29 | expect(Object.keys(properties)).to.be.empty; 30 | }); 31 | }); 32 | return it('should return a map of properties', function () { 33 | const conn = new MockConnection(); 34 | const cmd = new GetPropertiesCommand(conn); 35 | setImmediate(function () { 36 | conn.getSocket().causeRead(Protocol.OKAY); 37 | conn.getSocket().causeRead(`[ro.product.locale.region]: [US] 38 | [ro.product.manufacturer]: [samsung]\r 39 | [ro.product.model]: [SC-04E] 40 | [ro.product.name]: [SC-04E]`); 41 | return conn.getSocket().causeEnd(); 42 | }); 43 | return cmd.execute().then(function (properties) { 44 | expect(Object.keys(properties)).to.have.length(4); 45 | expect(properties).to.eql({ 46 | 'ro.product.locale.region': 'US', 47 | 'ro.product.manufacturer': 'samsung', 48 | 'ro.product.model': 'SC-04E', 49 | 'ro.product.name': 'SC-04E', 50 | }); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/install.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import InstallCommand from '../../../../src/adb/command/host-transport/install'; 7 | 8 | describe('InstallCommand', function () { 9 | it("should send 'pm install -r '", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new InstallCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm install -r "foo"').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead('Success\r\n'); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute('foo'); 21 | }); 22 | it("should succeed when command responds with 'Success'", function () { 23 | const conn = new MockConnection(); 24 | const cmd = new InstallCommand(conn); 25 | setImmediate(function () { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead('Success\r\n'); 28 | return conn.getSocket().causeEnd(); 29 | }); 30 | return cmd.execute('foo'); 31 | }); 32 | it("should reject if command responds with 'Failure [REASON]'", function (done) { 33 | const conn = new MockConnection(); 34 | const cmd = new InstallCommand(conn); 35 | setImmediate(function () { 36 | conn.getSocket().causeRead(Protocol.OKAY); 37 | conn.getSocket().causeRead('Failure [BAR]\r\n'); 38 | return conn.getSocket().causeEnd(); 39 | }); 40 | cmd.execute('foo').catch(function (err) { 41 | done(); 42 | }); 43 | }); 44 | it("should give detailed reason in rejection's code property", function (done) { 45 | const conn = new MockConnection(); 46 | const cmd = new InstallCommand(conn); 47 | setImmediate(function () { 48 | conn.getSocket().causeRead(Protocol.OKAY); 49 | conn.getSocket().causeRead('Failure [ALREADY_EXISTS]\r\n'); 50 | return conn.getSocket().causeEnd(); 51 | }); 52 | cmd.execute('foo').catch(function (err) { 53 | expect(err.code).to.equal('ALREADY_EXISTS'); 54 | done(); 55 | }); 56 | }); 57 | return it('should ignore any other data', function () { 58 | const conn = new MockConnection(); 59 | const cmd = new InstallCommand(conn); 60 | setImmediate(function () { 61 | conn.getSocket().causeRead(Protocol.OKAY); 62 | conn.getSocket().causeRead('open: Permission failed\r\n'); 63 | conn.getSocket().causeRead('Success\r\n'); 64 | return conn.getSocket().causeEnd(); 65 | }); 66 | return cmd.execute('foo'); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/isinstalled.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | 7 | import IsInstalledCommand from '../../../../src/adb/command/host-transport/isinstalled'; 8 | 9 | describe('IsInstalledCommand', function () { 10 | it("should send 'pm path '", function () { 11 | const conn = new MockConnection(); 12 | const cmd = new IsInstalledCommand(conn); 13 | conn.getSocket().on('write', function (chunk) { 14 | return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm path foo 2>/dev/null').toString()); 15 | }); 16 | setImmediate(function () { 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | conn.getSocket().causeRead('package:foo\r\n'); 19 | return conn.getSocket().causeEnd(); 20 | }); 21 | return cmd.execute('foo'); 22 | }); 23 | it('should resolve with true if package returned by command', function () { 24 | const conn = new MockConnection(); 25 | const cmd = new IsInstalledCommand(conn); 26 | setImmediate(function () { 27 | conn.getSocket().causeRead(Protocol.OKAY); 28 | conn.getSocket().causeRead('package:bar\r\n'); 29 | return conn.getSocket().causeEnd(); 30 | }); 31 | return cmd.execute('foo').then(function (found) { 32 | expect(found).to.be.true; 33 | }); 34 | }); 35 | it('should resolve with false if no package returned', function () { 36 | const conn = new MockConnection(); 37 | const cmd = new IsInstalledCommand(conn); 38 | setImmediate(function () { 39 | conn.getSocket().causeRead(Protocol.OKAY); 40 | return conn.getSocket().causeEnd(); 41 | }); 42 | return cmd.execute('foo').then(function (found) { 43 | expect(found).to.be.false; 44 | }); 45 | }); 46 | return it('should fail if any other data is received', function (done) { 47 | const conn = new MockConnection(); 48 | const cmd = new IsInstalledCommand(conn); 49 | setImmediate(function () { 50 | conn.getSocket().causeRead(Protocol.OKAY); 51 | conn.getSocket().causeRead('open: Permission failed\r\n'); 52 | return conn.getSocket().causeEnd(); 53 | }); 54 | cmd.execute('foo').catch(function (err) { 55 | expect(err).to.be.an.instanceof(Error); 56 | done(); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/local.ts: -------------------------------------------------------------------------------- 1 | import Stream from 'stream'; 2 | import Chai, { expect } from 'chai'; 3 | import simonChai from 'sinon-chai'; 4 | Chai.use(simonChai); 5 | import MockConnection from '../../../mock/connection'; 6 | import Protocol from '../../../../src/adb/protocol'; 7 | import LocalCommand from '../../../../src/adb/command/host-transport/local'; 8 | 9 | describe('LocalCommand', function () { 10 | it("should send 'localfilesystem:'", function () { 11 | const conn = new MockConnection(); 12 | const cmd = new LocalCommand(conn); 13 | conn.getSocket().on('write', function (chunk) { 14 | return expect(chunk.toString()).to.equal(Protocol.encodeData('localfilesystem:/foo.sock').toString()); 15 | }); 16 | setImmediate(function () { 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute('/foo.sock') 21 | }); 22 | it("should send ':' if prefixed with ':'", function () { 23 | const conn = new MockConnection(); 24 | const cmd = new LocalCommand(conn); 25 | conn.getSocket().on('write', function (chunk) { 26 | return expect(chunk.toString()).to.equal(Protocol.encodeData('localabstract:/foo.sock').toString()); 27 | }); 28 | setImmediate(function () { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | return conn.getSocket().causeEnd(); 31 | }); 32 | return cmd.execute('localabstract:/foo.sock').then(function (stream) { 33 | }); 34 | }); 35 | return it('should resolve with the stream', function () { 36 | const conn = new MockConnection(); 37 | const cmd = new LocalCommand(conn); 38 | setImmediate(function () { 39 | return conn.getSocket().causeRead(Protocol.OKAY); 40 | }); 41 | return cmd.execute('/foo.sock').then(function (stream) { 42 | stream.end(); 43 | expect(stream).to.be.an.instanceof(Stream.Readable); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/log.ts: -------------------------------------------------------------------------------- 1 | import Stream from 'stream'; 2 | import Chai, { expect } from 'chai'; 3 | import simonChai from 'sinon-chai'; 4 | Chai.use(simonChai); 5 | import MockConnection from '../../../mock/connection'; 6 | import Protocol from '../../../../src/adb/protocol'; 7 | import LogCommand from '../../../../src/adb/command/host-transport/log'; 8 | 9 | describe('LogCommand', function () { 10 | it("should send 'log:'", function () { 11 | const conn = new MockConnection(); 12 | const cmd = new LogCommand(conn); 13 | conn.getSocket().on('write', function (chunk) { 14 | return expect(chunk.toString()).to.equal(Protocol.encodeData('log:main').toString()); 15 | }); 16 | setImmediate(function () { 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute('main'); 21 | }); 22 | return it('should resolve with the log stream', function () { 23 | const conn = new MockConnection(); 24 | const cmd = new LogCommand(conn); 25 | setImmediate(function () { 26 | return conn.getSocket().causeRead(Protocol.OKAY); 27 | }); 28 | return cmd.execute('main').then(function (stream) { 29 | stream.end(); 30 | expect(stream).to.be.an.instanceof(Stream.Readable); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/logcat.ts: -------------------------------------------------------------------------------- 1 | import Stream from 'stream'; 2 | import Chai, { expect } from 'chai'; 3 | import simonChai from 'sinon-chai'; 4 | Chai.use(simonChai); 5 | import MockConnection from '../../../mock/connection'; 6 | import Protocol from '../../../../src/adb/protocol'; 7 | import Parser from '../../../../src/adb/parser'; 8 | import LogcatCommand from '../../../../src/adb/command/host-transport/logcat'; 9 | 10 | describe('LogcatCommand', function () { 11 | it("should send 'echo && logcat -B *:I'", function () { 12 | const conn = new MockConnection(); 13 | const cmd = new LogcatCommand(conn); 14 | conn.getSocket().on('write', function (chunk) { 15 | return expect(chunk.toString()).to.equal( 16 | Protocol.encodeData('shell:echo && logcat -B *:I 2>/dev/null').toString(), 17 | ); 18 | }); 19 | setImmediate(function () { 20 | conn.getSocket().causeRead(Protocol.OKAY); 21 | return conn.getSocket().causeEnd(); 22 | }); 23 | return cmd.execute(); 24 | }); 25 | it("should send 'echo && logcat -c && logcat -B *:I' if options.clear is set", function () { 26 | const conn = new MockConnection(); 27 | const cmd = new LogcatCommand(conn); 28 | conn.getSocket().on('write', function (chunk) { 29 | return expect(chunk.toString()).to.equal( 30 | Protocol.encodeData('shell:echo && logcat -c 2>/dev/null && logcat -B *:I 2>/dev/null').toString(), 31 | ); 32 | }); 33 | setImmediate(function () { 34 | conn.getSocket().causeRead(Protocol.OKAY); 35 | return conn.getSocket().causeEnd(); 36 | }); 37 | return cmd 38 | .execute({ 39 | clear: true, 40 | }) 41 | }); 42 | it('should resolve with the logcat stream', function () { 43 | const conn = new MockConnection(); 44 | const cmd = new LogcatCommand(conn); 45 | setImmediate(function () { 46 | return conn.getSocket().causeRead(Protocol.OKAY); 47 | }); 48 | return cmd.execute().then(function (stream) { 49 | stream.end(); 50 | expect(stream).to.be.an.instanceof(Stream.Readable); 51 | }); 52 | }); 53 | it('should perform CRLF transformation by default', function () { 54 | const conn = new MockConnection(); 55 | const cmd = new LogcatCommand(conn); 56 | setImmediate(function () { 57 | conn.getSocket().causeRead(Protocol.OKAY); 58 | conn.getSocket().causeRead('\r\nfoo\r\n'); 59 | return conn.getSocket().causeEnd(); 60 | }); 61 | return cmd 62 | .execute() 63 | .then(function (stream) { 64 | return new Parser(stream).readAll(); 65 | }) 66 | .then(function (out) { 67 | expect(out.toString()).to.equal('foo\n'); 68 | }); 69 | }); 70 | return it('should not perform CRLF transformation if not needed', function () { 71 | const conn = new MockConnection(); 72 | const cmd = new LogcatCommand(conn); 73 | setImmediate(function () { 74 | conn.getSocket().causeRead(Protocol.OKAY); 75 | conn.getSocket().causeRead('\nfoo\r\n'); 76 | return conn.getSocket().causeEnd(); 77 | }); 78 | return cmd 79 | .execute() 80 | .then(function (stream) { 81 | return new Parser(stream).readAll(); 82 | }) 83 | .then(function (out) { 84 | expect(out.toString()).to.equal('foo\r\n'); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/monkey.ts: -------------------------------------------------------------------------------- 1 | import Stream from 'stream'; 2 | import Chai, { expect } from 'chai'; 3 | import simonChai from 'sinon-chai'; 4 | Chai.use(simonChai); 5 | import MockConnection from '../../../mock/connection'; 6 | import Protocol from '../../../../src/adb/protocol'; 7 | import MonkeyCommand from '../../../../src/adb/command/host-transport/monkey'; 8 | 9 | describe('MonkeyCommand', function () { 10 | it("should send 'monkey --port -v'", function () { 11 | const conn = new MockConnection(); 12 | const cmd = new MonkeyCommand(conn); 13 | conn.getSocket().on('write', function (chunk) { 14 | return expect(chunk.toString()).to.equal( 15 | Protocol.encodeData('shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port 1080 -v').toString(), 16 | ); 17 | }); 18 | setImmediate(function () { 19 | conn.getSocket().causeRead(Protocol.OKAY); 20 | return conn.getSocket().causeRead(':Monkey: foo\n'); 21 | }); 22 | return cmd.execute(1080); 23 | }); 24 | it('should resolve with the output stream', function () { 25 | const conn = new MockConnection(); 26 | const cmd = new MonkeyCommand(conn); 27 | setImmediate(function () { 28 | conn.getSocket().causeRead(Protocol.OKAY); 29 | return conn.getSocket().causeRead(':Monkey: foo\n'); 30 | }); 31 | return cmd.execute(1080).then(function (stream) { 32 | stream.end(); 33 | expect(stream).to.be.an.instanceof(Stream.Readable); 34 | }); 35 | }); 36 | return it("should resolve after a timeout if result can't be judged from output", function () { 37 | const conn = new MockConnection(); 38 | const cmd = new MonkeyCommand(conn); 39 | setImmediate(function () { 40 | return conn.getSocket().causeRead(Protocol.OKAY); 41 | }); 42 | return cmd.execute(1080).then(function (stream) { 43 | stream.end(); 44 | expect(stream).to.be.an.instanceof(Stream.Readable); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/reboot.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import RebootCommand from '../../../../src/adb/command/host-transport/reboot'; 7 | 8 | describe('RebootCommand', function () { 9 | it("should send 'reboot:'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new RebootCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('reboot:').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | return conn.getSocket().causeEnd(); 18 | }); 19 | return cmd.execute(); 20 | }); 21 | return it('should send wait for the connection to end', function () { 22 | const conn = new MockConnection(); 23 | const cmd = new RebootCommand(conn); 24 | let ended = false; 25 | conn.getSocket().on('write', function (chunk) { 26 | return expect(chunk.toString()).to.equal(Protocol.encodeData('reboot:').toString()); 27 | }); 28 | setImmediate(function () { 29 | return conn.getSocket().causeRead(Protocol.OKAY); 30 | }); 31 | setImmediate(function () { 32 | ended = true; 33 | return conn.getSocket().causeEnd(); 34 | }); 35 | return cmd.execute().then(function () { 36 | expect(ended).to.be.true; 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/remount.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import RemountCommand from '../../../../src/adb/command/host-transport/remount'; 7 | 8 | describe('RemountCommand', function () { 9 | return it("should send 'remount:'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new RemountCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | expect(chunk.toString()).to.equal(Protocol.encodeData('remount:').toString()); 14 | conn.getSocket().causeRead(Protocol.OKAY); 15 | return conn.getSocket().causeEnd(); 16 | }); 17 | return cmd.execute(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/root.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import RootCommand from '../../../../src/adb/command/host-transport/root'; 7 | 8 | describe('RootCommand', function () { 9 | it("should send 'root:'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new RootCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('root:').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead('restarting adbd as root\n'); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute().then(function (val) { 21 | expect(val).to.be.true; 22 | }); 23 | }); 24 | return it('should reject on unexpected reply', function (done) { 25 | const conn = new MockConnection(); 26 | const cmd = new RootCommand(conn); 27 | setImmediate(function () { 28 | conn.getSocket().causeRead(Protocol.OKAY); 29 | conn.getSocket().causeRead('adbd cannot run as root in production builds\n'); 30 | return conn.getSocket().causeEnd(); 31 | }); 32 | cmd.execute().catch(function (err) { 33 | expect(err.message).to.eql('adbd cannot run as root in production builds'); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/screencap.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import Parser from '../../../../src/adb/parser'; 7 | import ScreencapCommand from '../../../../src/adb/command/host-transport/screencap'; 8 | 9 | describe('ScreencapCommand', function () { 10 | it("should send 'screencap -p'", function () { 11 | const conn = new MockConnection(); 12 | const cmd = new ScreencapCommand(conn); 13 | conn.getSocket().on('write', function (chunk) { 14 | return expect(chunk.toString()).to.equal( 15 | Protocol.encodeData('shell:echo && screencap -p 2>/dev/null').toString(), 16 | ); 17 | }); 18 | setImmediate(function () { 19 | conn.getSocket().causeRead(Protocol.OKAY); 20 | conn.getSocket().causeRead('\r\nlegit image'); 21 | return conn.getSocket().causeEnd(); 22 | }); 23 | return cmd.execute() 24 | }); 25 | it('should resolve with the PNG stream', function () { 26 | const conn = new MockConnection(); 27 | const cmd = new ScreencapCommand(conn); 28 | setImmediate(function () { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | conn.getSocket().causeRead('\r\nlegit image'); 31 | return conn.getSocket().causeEnd(); 32 | }); 33 | return cmd 34 | .execute() 35 | .then(function (stream) { 36 | return new Parser(stream).readAll(); 37 | }) 38 | .then(function (out) { 39 | expect(out.toString()).to.equal('legit image'); 40 | }); 41 | }); 42 | it('should reject if command not supported', function (done) { 43 | const conn = new MockConnection(); 44 | const cmd = new ScreencapCommand(conn); 45 | setImmediate(function () { 46 | conn.getSocket().causeRead(Protocol.OKAY); 47 | return conn.getSocket().causeEnd(); 48 | }); 49 | cmd.execute().catch(function () { 50 | done(); 51 | }); 52 | }); 53 | it('should perform CRLF transformation by default', function () { 54 | const conn = new MockConnection(); 55 | const cmd = new ScreencapCommand(conn); 56 | setImmediate(function () { 57 | conn.getSocket().causeRead(Protocol.OKAY); 58 | conn.getSocket().causeRead('\r\nfoo\r\n'); 59 | return conn.getSocket().causeEnd(); 60 | }); 61 | return cmd 62 | .execute() 63 | .then(function (stream) { 64 | return new Parser(stream).readAll(); 65 | }) 66 | .then(function (out) { 67 | expect(out.toString()).to.equal('foo\n'); 68 | }); 69 | }); 70 | return it('should not perform CRLF transformation if not needed', function () { 71 | const conn = new MockConnection(); 72 | const cmd = new ScreencapCommand(conn); 73 | setImmediate(function () { 74 | conn.getSocket().causeRead(Protocol.OKAY); 75 | conn.getSocket().causeRead('\nfoo\r\n'); 76 | return conn.getSocket().causeEnd(); 77 | }); 78 | return cmd 79 | .execute() 80 | .then(function (stream) { 81 | return new Parser(stream).readAll(); 82 | }) 83 | .then(function (out) { 84 | expect(out.toString()).to.equal('foo\r\n'); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/shell.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import Parser from '../../../../src/adb/parser'; 7 | import ShellCommand from '../../../../src/adb/command/host-transport/shell'; 8 | 9 | describe('ShellCommand', function () { 10 | it('should pass String commands as-is', function () { 11 | const conn = new MockConnection(); 12 | const cmd = new ShellCommand(conn); 13 | conn.getSocket().on('write', function (chunk) { 14 | return expect(chunk.toString()).to.equal(Protocol.encodeData("shell:foo 'bar").toString()); 15 | }); 16 | setImmediate(function () { 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute("foo 'bar"); 21 | }); 22 | it('should escape Array commands', function () { 23 | const conn = new MockConnection(); 24 | const cmd = new ShellCommand(conn); 25 | conn.getSocket().on('write', function (chunk) { 26 | return expect(chunk.toString()).to.equal(Protocol.encodeData(`shell:'foo' ''"'"'bar'"'"'' '"'`).toString()); 27 | }); 28 | setImmediate(function () { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | return conn.getSocket().causeEnd(); 31 | }); 32 | return cmd.execute(['foo', "'bar'", '"']); 33 | }); 34 | it('should not escape numbers in arguments', function () { 35 | const conn = new MockConnection(); 36 | const cmd = new ShellCommand(conn); 37 | conn.getSocket().on('write', function (chunk) { 38 | return expect(chunk.toString()).to.equal(Protocol.encodeData(`shell:'foo' 67`).toString()); 39 | }); 40 | setImmediate(function () { 41 | conn.getSocket().causeRead(Protocol.OKAY); 42 | return conn.getSocket().causeEnd(); 43 | }); 44 | return cmd.execute(['foo', 67]); 45 | }); 46 | return it('should reject with FailError on ADB failure (not command failure)', function (done) { 47 | const conn = new MockConnection(); 48 | const cmd = new ShellCommand(conn); 49 | conn.getSocket().on('write', function (chunk) { 50 | return expect(chunk.toString()).to.equal(Protocol.encodeData(`shell:'foo'`).toString()); 51 | }); 52 | setImmediate(function () { 53 | conn.getSocket().causeRead(Protocol.FAIL); 54 | conn.getSocket().causeRead(Protocol.encodeData('mystery')); 55 | return conn.getSocket().causeEnd(); 56 | }); 57 | cmd.execute(['foo']).catch(Parser.FailError, function () { 58 | done(); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/startservice.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import StartServiceCommand from '../../../../src/adb/command/host-transport/startservice'; 7 | 8 | describe('StartServiceCommand', function () { 9 | it("should succeed when 'Success' returned", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new StartServiceCommand(conn); 12 | setImmediate(function () { 13 | conn.getSocket().causeRead(Protocol.OKAY); 14 | conn.getSocket().causeRead('Success'); 15 | return conn.getSocket().causeEnd(); 16 | }); 17 | const options = { 18 | component: 'com.dummy.component/.Main', 19 | }; 20 | return cmd.execute(options); 21 | }); 22 | it("should fail when 'Error' returned", function (done) { 23 | const conn = new MockConnection(); 24 | const cmd = new StartServiceCommand(conn); 25 | setImmediate(function () { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead('Error: foo\n'); 28 | return conn.getSocket().causeEnd(); 29 | }); 30 | const options = { 31 | component: 'com.dummy.component/.Main', 32 | }; 33 | cmd.execute(options).catch(function (err) { 34 | expect(err).to.be.be.an.instanceOf(Error); 35 | done(); 36 | }); 37 | }); 38 | it("should send 'am startservice --user 0 -n '", function () { 39 | const conn = new MockConnection(); 40 | const cmd = new StartServiceCommand(conn); 41 | conn.getSocket().on('write', function (chunk) { 42 | return expect(chunk.toString()).to.equal( 43 | Protocol.encodeData("shell:am startservice -n 'com.dummy.component/.Main' --user 0").toString(), 44 | ); 45 | }); 46 | setImmediate(function () { 47 | conn.getSocket().causeRead(Protocol.OKAY); 48 | conn.getSocket().causeRead('Success\n'); 49 | return conn.getSocket().causeEnd(); 50 | }); 51 | const options = { 52 | component: 'com.dummy.component/.Main', 53 | user: 0, 54 | }; 55 | return cmd.execute(options); 56 | }); 57 | return it("should not send user option if not set'", function () { 58 | const conn = new MockConnection(); 59 | const cmd = new StartServiceCommand(conn); 60 | conn.getSocket().on('write', function (chunk) { 61 | return expect(chunk.toString()).to.equal( 62 | Protocol.encodeData("shell:am startservice -n 'com.dummy.component/.Main'").toString(), 63 | ); 64 | }); 65 | setImmediate(function () { 66 | conn.getSocket().causeRead(Protocol.OKAY); 67 | conn.getSocket().causeRead('Success\n'); 68 | return conn.getSocket().causeEnd(); 69 | }); 70 | const options = { 71 | component: 'com.dummy.component/.Main', 72 | }; 73 | return cmd.execute(options); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/sync.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import SyncCommand from '../../../../src/adb/command/host-transport/sync'; 7 | 8 | describe('SyncCommand', function () { 9 | return it("should send 'sync:'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new SyncCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | expect(chunk.toString()).to.equal(Protocol.encodeData('sync:').toString()); 14 | conn.getSocket().causeRead(Protocol.OKAY); 15 | return conn.getSocket().causeEnd(); 16 | }); 17 | return cmd.execute(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/tcp.ts: -------------------------------------------------------------------------------- 1 | import Stream from 'stream'; 2 | import Chai, { expect } from 'chai'; 3 | import simonChai from 'sinon-chai'; 4 | Chai.use(simonChai); 5 | import MockConnection from '../../../mock/connection'; 6 | import Protocol from '../../../../src/adb/protocol'; 7 | import TcpCommand from '../../../../src/adb/command/host-transport/tcp'; 8 | 9 | describe('TcpCommand', function () { 10 | it("should send 'tcp:' when no host given", function () { 11 | const conn = new MockConnection(); 12 | const cmd = new TcpCommand(conn); 13 | conn.getSocket().on('write', function (chunk) { 14 | return expect(chunk.toString()).to.equal(Protocol.encodeData('tcp:8080').toString()); 15 | }); 16 | setImmediate(function () { 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute(8080); 21 | }); 22 | it("should send 'tcp::' when host given", function () { 23 | const conn = new MockConnection(); 24 | const cmd = new TcpCommand(conn); 25 | conn.getSocket().on('write', function (chunk) { 26 | return expect(chunk.toString()).to.equal(Protocol.encodeData('tcp:8080:127.0.0.1').toString()); 27 | }); 28 | setImmediate(function () { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | return conn.getSocket().causeEnd(); 31 | }); 32 | return cmd.execute(8080, '127.0.0.1'); 33 | }); 34 | return it('should resolve with the tcp stream', function () { 35 | const conn = new MockConnection(); 36 | const cmd = new TcpCommand(conn); 37 | setImmediate(function () { 38 | return conn.getSocket().causeRead(Protocol.OKAY); 39 | }); 40 | return cmd.execute(8080).then(function (stream) { 41 | stream.end(); 42 | expect(stream).to.be.an.instanceof(Stream.Readable); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/tcpip.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import TcpIpCommand from '../../../../src/adb/command/host-transport/tcpip'; 7 | 8 | describe('TcpIpCommand', function () { 9 | it("should send 'tcp:'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new TcpIpCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('tcpip:5555').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead('restarting in TCP mode port: 5555\n'); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute(5555) 21 | }); 22 | it('should resolve with the port', function () { 23 | const conn = new MockConnection(); 24 | const cmd = new TcpIpCommand(conn); 25 | setImmediate(function () { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead('restarting in TCP mode port: 5555\n'); 28 | return conn.getSocket().causeEnd(); 29 | }); 30 | return cmd.execute(5555).then(function (port) { 31 | expect(port).to.equal(5555); 32 | }); 33 | }); 34 | return it('should reject on unexpected reply', function (done) { 35 | const conn = new MockConnection(); 36 | const cmd = new TcpIpCommand(conn); 37 | setImmediate(function () { 38 | conn.getSocket().causeRead(Protocol.OKAY); 39 | conn.getSocket().causeRead('not sure what this could be\n'); 40 | return conn.getSocket().causeEnd(); 41 | }); 42 | cmd.execute(5555).catch(function (err) { 43 | expect(err.message).to.eql('not sure what this could be'); 44 | done(); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/usb.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import UsbCommand from '../../../../src/adb/command/host-transport/usb'; 7 | 8 | describe('UsbCommand', function () { 9 | it("should send 'usb:'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new UsbCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('usb:').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead('restarting in USB mode\n'); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute().then(function (val) { 21 | expect(val).to.be.true; 22 | }); 23 | }); 24 | return it('should reject on unexpected reply', function (done) { 25 | const conn = new MockConnection(); 26 | const cmd = new UsbCommand(conn); 27 | setImmediate(function () { 28 | conn.getSocket().causeRead(Protocol.OKAY); 29 | conn.getSocket().causeRead('invalid port\n'); 30 | return conn.getSocket().causeEnd(); 31 | }); 32 | cmd.execute().catch(function (err) { 33 | expect(err.message).to.eql('invalid port'); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/waitbootcomplete.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import Parser from '../../../../src/adb/parser'; 7 | import WaitBootCompleteCommand from '../../../../src/adb/command/host-transport/waitbootcomplete'; 8 | 9 | describe('WaitBootCompleteCommand', function () { 10 | it('should send a while loop with boot check', function () { 11 | const conn = new MockConnection(); 12 | const cmd = new WaitBootCompleteCommand(conn); 13 | const want = 'shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done'; 14 | conn.getSocket().on('write', function (chunk) { 15 | return expect(chunk.toString()).to.equal(Protocol.encodeData(want).toString()); 16 | }); 17 | setImmediate(function () { 18 | conn.getSocket().causeRead(Protocol.OKAY); 19 | conn.getSocket().causeRead('1\r\n'); 20 | return conn.getSocket().causeEnd(); 21 | }); 22 | return cmd.execute(); 23 | }); 24 | it('should reject with Parser.PrematureEOFError if connection cuts prematurely', function (done) { 25 | const conn = new MockConnection(); 26 | const cmd = new WaitBootCompleteCommand(conn); 27 | setImmediate(function () { 28 | conn.getSocket().causeRead(Protocol.OKAY); 29 | return conn.getSocket().causeEnd(); 30 | }); 31 | cmd 32 | .execute() 33 | .then(function () { 34 | return done(new Error('Succeeded even though it should not')); 35 | }) 36 | .catch(Parser.PrematureEOFError, function (err) { 37 | done(); 38 | }); 39 | }); 40 | it('should not return until boot is complete', function () { 41 | const conn = new MockConnection(); 42 | const cmd = new WaitBootCompleteCommand(conn); 43 | let sent = false; 44 | setImmediate(function () { 45 | conn.getSocket().causeRead(Protocol.OKAY); 46 | conn.getSocket().causeRead('\r\n'); 47 | conn.getSocket().causeRead('\r\n'); 48 | conn.getSocket().causeRead('\r\n'); 49 | conn.getSocket().causeRead('\r\n'); 50 | conn.getSocket().causeRead('\r\n'); 51 | conn.getSocket().causeRead('\r\n'); 52 | conn.getSocket().causeRead('\r\n'); 53 | conn.getSocket().causeRead('\r\n'); 54 | conn.getSocket().causeRead('\r\n'); 55 | conn.getSocket().causeRead('\r\n'); 56 | return setTimeout(function () { 57 | sent = true; 58 | return conn.getSocket().causeRead('1\r\n'); 59 | }, 50); 60 | }); 61 | return cmd.execute().then(function () { 62 | expect(sent).to.be.true; 63 | }); 64 | }); 65 | return it('should close connection when done', function (done) { 66 | const conn = new MockConnection(); 67 | const cmd = new WaitBootCompleteCommand(conn); 68 | setImmediate(function () { 69 | conn.getSocket().causeRead(Protocol.OKAY); 70 | return conn.getSocket().causeRead('1\r\n'); 71 | }); 72 | conn.getSocket().on('end', function () { 73 | done(); 74 | }); 75 | cmd.execute(); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/adb/command/host/connect.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import ConnectCommand from '../../../../src/adb/command/host/connect'; 7 | 8 | describe('ConnectCommand', function () { 9 | it("should send 'host:connect::'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new ConnectCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('host:connect:192.168.2.2:5555').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead(Protocol.encodeData('connected to 192.168.2.2:5555')); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute('192.168.2.2', 5555); 21 | }); 22 | it('should resolve with the new device id if connected', function () { 23 | const conn = new MockConnection(); 24 | const cmd = new ConnectCommand(conn); 25 | setImmediate(function () { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead(Protocol.encodeData('connected to 192.168.2.2:5555')); 28 | return conn.getSocket().causeEnd(); 29 | }); 30 | return cmd.execute('192.168.2.2', 5555).then(function (val) { 31 | expect(val).to.be.equal('192.168.2.2:5555'); 32 | }); 33 | }); 34 | it('should resolve with the new device id if already connected', function () { 35 | const conn = new MockConnection(); 36 | const cmd = new ConnectCommand(conn); 37 | setImmediate(function () { 38 | conn.getSocket().causeRead(Protocol.OKAY); 39 | conn.getSocket().causeRead(Protocol.encodeData('already connected to 192.168.2.2:5555')); 40 | return conn.getSocket().causeEnd(); 41 | }); 42 | return cmd.execute('192.168.2.2', 5555).then(function (val) { 43 | expect(val).to.be.equal('192.168.2.2:5555'); 44 | }); 45 | }); 46 | return it('should reject with error if unable to connect', function () { 47 | const conn = new MockConnection(); 48 | const cmd = new ConnectCommand(conn); 49 | setImmediate(function () { 50 | conn.getSocket().causeRead(Protocol.OKAY); 51 | conn.getSocket().causeRead(Protocol.encodeData('unable to connect to 192.168.2.2:5555')); 52 | return conn.getSocket().causeEnd(); 53 | }); 54 | return cmd.execute('192.168.2.2', 5555).catch(function (err) { 55 | expect(err.message).to.eql('unable to connect to 192.168.2.2:5555'); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /test/adb/command/host/disconnect.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import DisconnectCommand from '../../../../src/adb/command/host/disconnect'; 7 | import Connection from '../../../../src/adb/connection'; 8 | 9 | describe('DisconnectCommand', function () { 10 | it("should send 'host:disconnect::'", function () { 11 | const conn = new MockConnection(); 12 | const cmd = new DisconnectCommand(conn); 13 | conn.getSocket().on('write', function (chunk) { 14 | return expect(chunk.toString()).to.equal( 15 | Protocol.encodeData('host:disconnect:192.168.2.2:5555').toString(), 16 | ); 17 | }); 18 | setImmediate(function () { 19 | conn.getSocket().causeRead(Protocol.OKAY); 20 | conn.getSocket().causeRead(Protocol.encodeData('')); 21 | return conn.getSocket().causeEnd(); 22 | }); 23 | return cmd.execute('192.168.2.2', 5555); 24 | }); 25 | it('should resolve with the new device id if disconnected', function () { 26 | const conn = new MockConnection(); 27 | const cmd = new DisconnectCommand(conn as Connection); 28 | setImmediate(function () { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | conn.getSocket().causeRead(Protocol.encodeData('')); 31 | return conn.getSocket().causeEnd(); 32 | }); 33 | return cmd.execute('192.168.2.2', 5555).then(function (val) { 34 | expect(val).to.be.equal('192.168.2.2:5555'); 35 | }); 36 | }); 37 | it('should reject with error if unable to disconnect', function () { 38 | const conn = new MockConnection(); 39 | const cmd = new DisconnectCommand(conn as Connection); 40 | setImmediate(function () { 41 | conn.getSocket().causeRead(Protocol.OKAY); 42 | conn.getSocket().causeRead(Protocol.encodeData('No such device 192.168.2.2:5555')); 43 | return conn.getSocket().causeEnd(); 44 | }); 45 | return cmd.execute('192.168.2.2', 5555).catch(function (err) { 46 | expect(err.message).to.eql('No such device 192.168.2.2:5555'); 47 | }); 48 | }); 49 | return it('should resolve with the new device id if disconnected', function () { 50 | const conn = new MockConnection(); 51 | const cmd = new DisconnectCommand(conn as Connection); 52 | setImmediate(function () { 53 | conn.getSocket().causeRead(Protocol.OKAY); 54 | conn.getSocket().causeRead(Protocol.encodeData('disconnected 192.168.2.2:5555')); 55 | return conn.getSocket().causeEnd(); 56 | }); 57 | return cmd.execute('192.168.2.2', 5555).then(function (val) { 58 | expect(val).to.be.equal('192.168.2.2:5555'); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /test/adb/command/host/version.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import MockConnection from '../../../mock/connection'; 5 | import Protocol from '../../../../src/adb/protocol'; 6 | import HostVersionCommand from '../../../../src/adb/command/host/version'; 7 | 8 | describe('HostVersionCommand', function () { 9 | it("should send 'host:version'", function () { 10 | const conn = new MockConnection(); 11 | const cmd = new HostVersionCommand(conn); 12 | conn.getSocket().on('write', function (chunk) { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('host:version').toString()); 14 | }); 15 | setImmediate(function () { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead(Protocol.encodeData('0000')); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute() 21 | }); 22 | it('should resolve with version', function () { 23 | const conn = new MockConnection(); 24 | const cmd = new HostVersionCommand(conn); 25 | setImmediate(function () { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead(Protocol.encodeData((0x1234).toString(16))); 28 | return conn.getSocket().causeEnd(); 29 | }); 30 | return cmd.execute().then(function (version) { 31 | expect(version).to.equal(0x1234); 32 | }); 33 | }); 34 | return it('should handle old-style version', function () { 35 | const conn = new MockConnection(); 36 | const cmd = new HostVersionCommand(conn); 37 | setImmediate(function () { 38 | conn.getSocket().causeRead((0x1234).toString(16)); 39 | return conn.getSocket().causeEnd(); 40 | }); 41 | return cmd.execute().then(function (version) { 42 | expect(version).to.equal(0x1234); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/adb/protocol.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import Protocol from '../../src/adb/protocol'; 4 | 5 | describe('Protocol', function () { 6 | it("should expose a 'FAIL' property", function (done) { 7 | expect(Protocol).to.have.property('FAIL'); 8 | expect(Protocol.FAIL).to.equal('FAIL'); 9 | done(); 10 | }); 11 | it("should expose an 'OKAY' property", function (done) { 12 | expect(Protocol).to.have.property('OKAY'); 13 | expect(Protocol.OKAY).to.equal('OKAY'); 14 | done(); 15 | }); 16 | describe('@decodeLength(length)', function () { 17 | it('should return a Number', function (done) { 18 | expect(Protocol.decodeLength('0x0046')).to.be.a('number'); 19 | done(); 20 | }); 21 | return it('should accept a hexadecimal string', function (done) { 22 | expect(Protocol.decodeLength('0x5887')).to.equal(0x5887); 23 | done(); 24 | }); 25 | }); 26 | describe('@encodeLength(length)', function () { 27 | it('should return a String', function (done) { 28 | expect(Protocol.encodeLength(27)).to.be.a('string'); 29 | done(); 30 | }); 31 | it('should return a valid hexadecimal number', function (done) { 32 | expect(parseInt(Protocol.encodeLength(32), 16)).to.equal(32); 33 | expect(parseInt(Protocol.encodeLength(9999), 16)).to.equal(9999); 34 | done(); 35 | }); 36 | it('should return uppercase hexadecimal digits', function (done) { 37 | expect(Protocol.encodeLength(0x0abc)).to.equal('0ABC'); 38 | done(); 39 | }); 40 | it('should pad short values with zeroes for a 4-byte size', function (done) { 41 | expect(Protocol.encodeLength(1)).to.have.length(4); 42 | expect(Protocol.encodeLength(2)).to.have.length(4); 43 | expect(Protocol.encodeLength(57)).to.have.length(4); 44 | done(); 45 | }); 46 | return it('should return 0000 for 0 length', function (done) { 47 | expect(Protocol.encodeLength(0)).to.equal('0000'); 48 | done(); 49 | }); 50 | }); 51 | return describe('@encodeData(data)', function () { 52 | it('should return a Buffer', function (done) { 53 | expect(Protocol.encodeData(Buffer.from(''))).to.be.an.instanceOf(Buffer); 54 | done(); 55 | }); 56 | it('should accept a string or a Buffer', function (done) { 57 | expect(Protocol.encodeData('')).to.be.an.instanceOf(Buffer); 58 | expect(Protocol.encodeData(Buffer.from(''))).to.be.an.instanceOf(Buffer); 59 | done(); 60 | }); 61 | return it('should prefix data with length', function (done) { 62 | const data = Protocol.encodeData(Buffer.alloc(0x270f)); 63 | expect(data).to.have.length(0x270f + 4); 64 | expect(data.toString('ascii', 0, 4)).to.equal('270F'); 65 | done(); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/adb/util.ts: -------------------------------------------------------------------------------- 1 | import Stream from 'stream'; 2 | import Bluebird from 'bluebird'; 3 | import Chai, { expect } from 'chai'; 4 | import simonChai from 'sinon-chai'; 5 | Chai.use(simonChai); 6 | import util from '../../src/adb/util'; 7 | 8 | describe('util', function () { 9 | return describe('readAll(stream)', function () { 10 | it('should return a cancellable Bluebird Promise', function (done) { 11 | const stream = new Stream.PassThrough(); 12 | const promise = util.readAll(stream); 13 | expect(promise).to.be.an.instanceOf(Bluebird); 14 | // expect(promise.isCancellable()).to.be.true; 15 | // promise.catch(Bluebird.CancellationError, function () { 16 | //}); 17 | promise.cancel(); 18 | expect(promise.isCancelled()).to.be.true; 19 | done(); 20 | }); 21 | return it('should read all remaining content until the stream ends', function (done) { 22 | const stream = new Stream.PassThrough(); 23 | util.readAll(stream).then(function (buf) { 24 | expect(buf.length).to.equal(3); 25 | expect(buf.toString()).to.equal('FOO'); 26 | done(); 27 | }); 28 | stream.write('F'); 29 | stream.write('O'); 30 | stream.write('O'); 31 | return stream.end(); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/mock/connection.ts: -------------------------------------------------------------------------------- 1 | import Connection from '../../src/adb/connection'; 2 | import Parser from '../../src/adb/parser'; 3 | import { Socket } from 'net'; 4 | import MockDuplex from './duplex'; 5 | 6 | export default class MockConnection extends Connection { 7 | _socket = new MockDuplex(); 8 | 9 | constructor() { 10 | super(); 11 | this.parser = new Parser(this._socket); 12 | } 13 | 14 | public getSocket(): MockDuplex { 15 | return this._socket; 16 | } 17 | 18 | end(): this { 19 | this._socket.causeEnd(); 20 | return this; 21 | } 22 | 23 | // public write(data: string | Uint8Array, callback?: (err?: Error) => void): this { 24 | public write(chunk: string | Uint8Array, cb?: (error: Error | null | undefined) => void): this { 25 | this._socket.write(chunk, cb); 26 | return this; 27 | } 28 | 29 | on(): this { 30 | return this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/mock/duplex.ts: -------------------------------------------------------------------------------- 1 | // var MockDuplex; 2 | 3 | import Stream from 'stream'; 4 | 5 | export default class MockDuplex extends Stream.Duplex { 6 | _read(size: number): void { 7 | // empty 8 | } 9 | 10 | _write(chunk, encoding: string, callback: Function): void { 11 | this.emit('write', chunk, encoding, callback); 12 | callback(null); 13 | } 14 | 15 | causeRead(chunk): void { 16 | if (!Buffer.isBuffer(chunk)) { 17 | chunk = Buffer.from(chunk); 18 | } 19 | this.push(chunk); 20 | } 21 | 22 | causeEnd(): void { 23 | this.push(null); 24 | } 25 | 26 | end(cb?: () => void): this { 27 | this.causeEnd(); // In order to better emulate socket streams 28 | return (Stream.Duplex.prototype.end as any).apply(this, cb); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig-dist.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2019" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 6 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true /* Generates corresponding '.d.ts' file. */, 12 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist" /* Redirect output structure to the directory. */, 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, 28 | "strictNullChecks": false /* Enable strict null checks. */, 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, 65 | "resolveJsonModule": true 66 | }, 67 | "include": ["index.ts", "src/**/*.ts", "bench/**/*.ts"], 68 | "exclude": ["**/*.d.ts", "node_modules"] 69 | } 70 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 6 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true /* Generates corresponding '.d.ts' file. */, 12 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist" /* Redirect output structure to the directory. */, 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, 28 | "strictNullChecks": false /* Enable strict null checks. */, 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, 65 | "resolveJsonModule": true 66 | }, 67 | "include": ["index.ts", "src/**/*.ts", "bench/**/*.ts"], 68 | "exclude": ["**/*.d.ts", "node_modules"] 69 | } 70 | --------------------------------------------------------------------------------