├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ ├── npm-publish.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bench └── sync │ └── pull.ts ├── bin ├── STFService_2.4.9.apk ├── adbkit ├── scrcpy-server-v1.20.jar ├── scrcpy-server-v1.24.jar └── wireService.proto ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── main.js │ ├── search.js │ └── style.css ├── classes │ ├── AdbError.html │ ├── AdbFailError.html │ ├── AdbPrematureEOFError.html │ ├── AdbUnexpectedDataError.html │ ├── AuthError.html │ ├── ChecksumError.html │ ├── Client.html │ ├── Connection.html │ ├── DeviceClient.html │ ├── DeviceClientExtra.html │ ├── DevicePackage.html │ ├── Entry.html │ ├── Entry64.html │ ├── IpRouteEntry.html │ ├── IpRuleEntry.html │ ├── JdwpTracker.html │ ├── LateTransportError.html │ ├── MagicError.html │ ├── Minicap.html │ ├── Packet.html │ ├── PacketReader.html │ ├── ParcelReader.html │ ├── Parser.html │ ├── PrematurePacketError.html │ ├── ProcStat.html │ ├── PullTransfer.html │ ├── Pushtransfer.html │ ├── RollingCounter.html │ ├── STFService.html │ ├── STFServiceBuf.html │ ├── Scrcpy.html │ ├── Service.html │ ├── ServiceCallCommand.html │ ├── ServiceMap.html │ ├── ShellCommand.html │ ├── Socket.html │ ├── Stats.html │ ├── Stats64.html │ ├── Sync.html │ ├── TcpUsbServer.html │ ├── Tracker.html │ ├── UnauthorizedError.html │ └── Utils.html ├── enums │ ├── DeviceMessageType.html │ ├── KeyCodes.html │ ├── KeyEvent.html │ ├── KeyEventMeta.html │ ├── MotionEvent.html │ ├── Orientation.html │ ├── STFServiceModel.ClipboardType.html │ ├── STFServiceModel.MessageType.html │ ├── STFServiceModel.RingerMode.html │ └── SurfaceControl.html ├── index.html ├── interfaces │ ├── AdbOptions.html │ ├── AdbServiceInfo.html │ ├── ClientOptions.html │ ├── CpuStats.html │ ├── CpuStatsWithLine.html │ ├── Device.html │ ├── DeviceClientOptions.html │ ├── DeviceWithPath.html │ ├── ExtendedPublicKey.html │ ├── Extra.html │ ├── ExtraObject.html │ ├── Forward.html │ ├── FramebufferMeta.html │ ├── FramebufferStreamWithMeta.html │ ├── H264Configuration.html │ ├── KeyEventRequest.html │ ├── Loads.html │ ├── LoadsWithLine.html │ ├── MinicapOptions.html │ ├── PackageInfo.html │ ├── Point.html │ ├── PsEntry.html │ ├── Reverse.html │ ├── STFServiceModel.AirplaneModeEvent.html │ ├── STFServiceModel.BatteryEvent.html │ ├── STFServiceModel.BrowserApp.html │ ├── STFServiceModel.BrowserPackageEvent.html │ ├── STFServiceModel.ConnectivityEvent.html │ ├── STFServiceModel.DoAddAccountMenuRequest.html │ ├── STFServiceModel.DoAddAccountMenuResponse.html │ ├── STFServiceModel.DoIdentifyRequest.html │ ├── STFServiceModel.DoIdentifyResponse.html │ ├── STFServiceModel.DoRemoveAccountRequest.html │ ├── STFServiceModel.DoRemoveAccountResponse.html │ ├── STFServiceModel.DoTypeRequest.html │ ├── STFServiceModel.DoWakeRequest.html │ ├── STFServiceModel.Envelope.html │ ├── STFServiceModel.GetAccountsRequest.html │ ├── STFServiceModel.GetAccountsResponse.html │ ├── STFServiceModel.GetBluetoothStatusRequest.html │ ├── STFServiceModel.GetBluetoothStatusResponse.html │ ├── STFServiceModel.GetBrowsersRequest.html │ ├── STFServiceModel.GetBrowsersResponse.html │ ├── STFServiceModel.GetClipboardRequest.html │ ├── STFServiceModel.GetClipboardResponse.html │ ├── STFServiceModel.GetDisplayRequest.html │ ├── STFServiceModel.GetDisplayResponse.html │ ├── STFServiceModel.GetPropertiesRequest.html │ ├── STFServiceModel.GetPropertiesResponse.html │ ├── STFServiceModel.GetRingerModeRequest.html │ ├── STFServiceModel.GetRingerModeResponse.html │ ├── STFServiceModel.GetRootStatusRequest.html │ ├── STFServiceModel.GetRootStatusResponse.html │ ├── STFServiceModel.GetSdStatusRequest.html │ ├── STFServiceModel.GetSdStatusResponse.html │ ├── STFServiceModel.GetVersionRequest.html │ ├── STFServiceModel.GetVersionResponse.html │ ├── STFServiceModel.GetWifiStatusRequest.html │ ├── STFServiceModel.GetWifiStatusResponse.html │ ├── STFServiceModel.PhoneStateEvent.html │ ├── STFServiceModel.Property.html │ ├── STFServiceModel.RotationEvent.html │ ├── STFServiceModel.SetBluetoothEnabledRequest.html │ ├── STFServiceModel.SetBluetoothEnabledResponse.html │ ├── STFServiceModel.SetClipboardRequest.html │ ├── STFServiceModel.SetClipboardResponse.html │ ├── STFServiceModel.SetKeyguardStateRequest.html │ ├── STFServiceModel.SetKeyguardStateResponse.html │ ├── STFServiceModel.SetMasterMuteRequest.html │ ├── STFServiceModel.SetMasterMuteResponse.html │ ├── STFServiceModel.SetRingerModeRequest.html │ ├── STFServiceModel.SetRingerModeResponse.html │ ├── STFServiceModel.SetRotationRequest.html │ ├── STFServiceModel.SetWakeLockRequest.html │ ├── STFServiceModel.SetWakeLockResponse.html │ ├── STFServiceModel.SetWifiEnabledRequest.html │ ├── STFServiceModel.SetWifiEnabledResponse.html │ ├── STFServiceOptions.html │ ├── ScrcpyOptions.html │ ├── ServiceCallArgNull.html │ ├── ServiceCallArgNumber.html │ ├── ServiceCallArgString.html │ ├── SocketOptions.html │ ├── StartActivityOptions.html │ ├── StartServiceOptions.html │ ├── TrackerChangeSet.html │ ├── VideoStreamFramePacket.html │ └── WithToString.html ├── modules.html ├── modules │ └── STFServiceModel.html ├── types │ ├── ColorFormat.html │ ├── DeviceType.html │ ├── ExtraValue.html │ ├── Features.html │ ├── MyMessage.html │ ├── ProcStats.html │ ├── ProcessState.html │ ├── Properties.html │ ├── RebootType.html │ └── ServiceCallArg.html └── variables │ └── Adb.html ├── misc └── wireshark_scrcpy_1.20.lua ├── package.json ├── pnpm-lock.yaml ├── src ├── adb.ts ├── adb │ ├── DeviceClient.ts │ ├── DeviceClientExtra.ts │ ├── DevicePackage.ts │ ├── auth.ts │ ├── client.ts │ ├── command.ts │ ├── command │ │ ├── host-serial │ │ │ ├── forward.ts │ │ │ ├── getdevicepath.ts │ │ │ ├── getserialno.ts │ │ │ ├── getstate.ts │ │ │ ├── index.ts │ │ │ ├── killForward.ts │ │ │ ├── listforwards.ts │ │ │ └── waitfordevice.ts │ │ ├── host-transport │ │ │ ├── Parcel.ts │ │ │ ├── ShellExecError.ts │ │ │ ├── clear.ts │ │ │ ├── exec.ts │ │ │ ├── framebuffer.ts │ │ │ ├── getfeatures.ts │ │ │ ├── getpackages.ts │ │ │ ├── getproperties.ts │ │ │ ├── index.ts │ │ │ ├── install.ts │ │ │ ├── ipRoute.ts │ │ │ ├── ipRule.ts │ │ │ ├── isinstalled.ts │ │ │ ├── listreverses.ts │ │ │ ├── local.ts │ │ │ ├── log.ts │ │ │ ├── logcat.ts │ │ │ ├── monkey.ts │ │ │ ├── ps.ts │ │ │ ├── reboot.ts │ │ │ ├── remount.ts │ │ │ ├── reverse.ts │ │ │ ├── root.ts │ │ │ ├── screencap.ts │ │ │ ├── serviceCall.ts │ │ │ ├── serviceCheck.ts │ │ │ ├── servicesList.ts │ │ │ ├── shell.ts │ │ │ ├── startactivity.ts │ │ │ ├── startservice.ts │ │ │ ├── sync.ts │ │ │ ├── tcp.ts │ │ │ ├── tcpip.ts │ │ │ ├── trackjdwp.ts │ │ │ ├── uninstall.ts │ │ │ ├── usb.ts │ │ │ └── waitbootcomplete.ts │ │ └── host │ │ │ ├── HostConnectCommand.ts │ │ │ ├── HostDevicesCommand.ts │ │ │ ├── HostDevicesWithPathsCommand.ts │ │ │ ├── HostDisconnectCommand.ts │ │ │ ├── HostKillCommand.ts │ │ │ ├── HostTrackDevicesCommand.ts │ │ │ ├── HostTransportCommand.ts │ │ │ ├── HostVersionCommand.ts │ │ │ └── index.ts │ ├── connection.ts │ ├── dump.ts │ ├── errors.ts │ ├── framebuffer │ │ └── rgbtransform.ts │ ├── jdwptracker.ts │ ├── keycode.ts │ ├── linetransform.ts │ ├── parser.ts │ ├── proc │ │ └── stat.ts │ ├── protocol.ts │ ├── sync.ts │ ├── sync │ │ ├── entry.ts │ │ ├── entry64.ts │ │ ├── pulltransfer.ts │ │ ├── pushtransfer.ts │ │ ├── stats.ts │ │ └── stats64.ts │ ├── tcpusb │ │ ├── packet.ts │ │ ├── packetreader.ts │ │ ├── rollingcounter.ts │ │ ├── server.ts │ │ ├── service.ts │ │ ├── servicemap.ts │ │ └── socket.ts │ ├── thirdparty │ │ ├── STFService │ │ │ ├── STFService.ts │ │ │ ├── STFServiceBuf.ts │ │ │ └── STFServiceModel.ts │ │ ├── ThirdUtils.ts │ │ ├── minicap │ │ │ ├── BufWrite.ts │ │ │ └── Minicap.ts │ │ └── scrcpy │ │ │ ├── Scrcpy.ts │ │ │ ├── ScrcpyConst.ts │ │ │ ├── ScrcpyModels.ts │ │ │ └── sps.ts │ ├── tracker.ts │ └── utils.ts ├── cli-airplane.ts ├── cli-boatware.ts ├── cli-common.ts ├── cli-connectivity.ts ├── cli-tethering.ts ├── cli.ts ├── index.ts └── models │ ├── ClientOptions.ts │ ├── CpuStats.ts │ ├── Device.ts │ ├── DeviceClientOptions.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 ├── tasks ├── GitSource.ts ├── genProto.ts ├── keycode.ts ├── realTest.ts └── servicemap.ts ├── test ├── adb.ts ├── adb │ ├── command │ │ ├── host-serial │ │ │ └── waitfordevice.ts │ │ ├── host-transport │ │ │ ├── Tester.ts │ │ │ ├── clear.ts │ │ │ ├── framebuffer.ts │ │ │ ├── getfeatures.ts │ │ │ ├── getpackages.ts │ │ │ ├── getproperties.ts │ │ │ ├── install.ts │ │ │ ├── ipRoute.ts │ │ │ ├── ipRules.ts │ │ │ ├── isinstalled.ts │ │ │ ├── local.ts │ │ │ ├── log.ts │ │ │ ├── logcat.ts │ │ │ ├── monkey.ts │ │ │ ├── ps.ts │ │ │ ├── reboot.ts │ │ │ ├── remount.ts │ │ │ ├── root.ts │ │ │ ├── screencap.ts │ │ │ ├── serviceCall.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 │ ├── thirdparty │ │ └── ptotobuf.ts │ ├── tracker.ts │ └── util.ts ├── mock │ ├── client.ts │ ├── connection.ts │ └── duplex.ts └── test.ts └── 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 | indent_size = 2 12 | 13 | [*.js] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [*.json] 18 | indent_style = space 19 | indent_size = 4 20 | 21 | [*.md] 22 | indent_style = space 23 | indent_size = 4 24 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/**/* 2 | test/**/*.js 3 | *.js 4 | lib/**/*.d.ts 5 | test/**/*.ts 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint"], 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/recommended" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 2020, 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | "indent": ["error", 2, {"SwitchCase": 1}], 15 | "no-case-declarations": "off" 16 | }, 17 | "overrides": [ 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: urielch -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: publish on npmjs 2 | 3 | on: 4 | release: 5 | types: [created] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - run: sudo apt-get install -y android-tools-adb 15 | - uses: actions/checkout@v3 16 | - uses: pnpm/action-setup@v2 17 | with: 18 | version: 9.10.0 19 | - name: Use Node.js 20 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 20 23 | registry-url: https://registry.npmjs.org/ 24 | cache: 'pnpm' 25 | - name: run pnpm install 26 | run: pnpm install --frozen-lockfile 27 | - run: pnpm run build 28 | - run: pnpm publish --no-git-checks 29 | env: 30 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | paths: 7 | - "src/**" 8 | - "test/**" 9 | - "package.json" 10 | - "pnpm-lock.yaml" 11 | - "tsconfig.json" 12 | - ".github/workflows/test.yml" 13 | pull_request: 14 | branches: [ "master" ] 15 | paths: 16 | - "src/**" 17 | - "test/**" 18 | - "package.json" 19 | - "pnpm-lock.yaml" 20 | - "tsconfig.json" 21 | - ".github/workflows/test.yml" 22 | 23 | jobs: 24 | build: 25 | 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | matrix: 30 | node-version: [ 18, 20 , 22] 31 | 32 | steps: 33 | - run: sudo apt-get install -y android-tools-adb 34 | - uses: actions/checkout@v3 35 | - uses: pnpm/action-setup@v2 36 | with: 37 | version: 9.10.0 38 | - name: Use Node.js ${{ matrix.node-version }} 39 | uses: actions/setup-node@v3 40 | with: 41 | node-version: ${{ matrix.node-version }} 42 | cache: 'pnpm' 43 | - name: run pnpm install 44 | run: pnpm install --frozen-lockfile 45 | - run: pnpm run test 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /temp/ 3 | /index.js 4 | /index.d.ts 5 | /*.tgz 6 | package-lock.json 7 | /dist 8 | *.log 9 | parcel.txt 10 | tasks/*encoder*/ 11 | bench/.DS_Store 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | index.ts 2 | src/ 3 | test/ 4 | tasks/ 5 | bench/ 6 | .prettierrc 7 | .editorconfig 8 | .semaphore 9 | .vscode 10 | .eslintignore 11 | .eslintrc 12 | tsconfig.json 13 | tsconfig-dist.json 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | 9 | { 10 | "name": "cli boatware", 11 | "type": "node", 12 | "request": "launch", 13 | "runtimeArgs": [ "--trace-warnings", "--unhandled-rejections", "warn", "--trace-sync-io", "--nolazy", "-r", "ts-node/register"], 14 | "args": ["./src/cli.ts", "--transpile-only"], 15 | "cwd": "${workspaceFolder}", 16 | "internalConsoleOptions": "openOnSessionStart", 17 | // "skipFiles": ["/**", "node_modules/**"], 18 | "skipFiles": ["/**" ], 19 | "env": { 20 | "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", 21 | "DEBUG": "adb:minicap, adb:scrcpy", 22 | "DEBUG_COLORS": "1" 23 | } 24 | }, 25 | 26 | 27 | 28 | { 29 | "name": "realTest", 30 | "type": "node", 31 | "request": "launch", 32 | "runtimeArgs": [ "--trace-warnings", "--unhandled-rejections", "warn", "--trace-sync-io", "--nolazy", "-r", "ts-node/register"], 33 | "args": ["./tasks/realTest.ts", "--transpile-only"], 34 | "cwd": "${workspaceFolder}", 35 | "internalConsoleOptions": "openOnSessionStart", 36 | // "skipFiles": ["/**", "node_modules/**"], 37 | "skipFiles": ["/**" ], 38 | "env": { 39 | "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", 40 | "DEBUG": "adb:minicap, adb:scrcpy", 41 | "DEBUG_COLORS": "1" 42 | } 43 | }, 44 | { 45 | "name": "Mocha Tests", 46 | "cwd": "${workspaceFolder}", 47 | "args": [ 48 | "-r", 49 | "ts-node/register", 50 | "--project", 51 | "tsconfig.json", 52 | "--no-timeouts", 53 | "--colors", 54 | "${workspaceFolder}/test/**/*.ts" 55 | ], 56 | "internalConsoleOptions": "openOnSessionStart", 57 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 58 | "request": "launch", 59 | "skipFiles": [ 60 | "/**" 61 | ], 62 | "type": "node" 63 | }, 64 | { 65 | "name": "Mocha Tests serviceCall", 66 | "cwd": "${workspaceFolder}", 67 | "args": [ 68 | "-r", 69 | "ts-node/register", 70 | "--project", 71 | "tsconfig.json", 72 | "--no-timeouts", 73 | "--colors", 74 | "${workspaceFolder}/test/adb/command/host-transport/serviceCall.ts" 75 | ], 76 | "internalConsoleOptions": "openOnSessionStart", 77 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 78 | "request": "launch", 79 | "skipFiles": [ 80 | "/**" 81 | ], 82 | "type": "node" 83 | }, 84 | 85 | ] 86 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # @u4/adbkit Code of Conduct 2 | 3 | ## 1. Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## 2. Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## 3. Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## 4. Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 34 | 35 | ## 5. Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at uchemoui@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. 38 | 39 | ## 6. Pull Requests and Testing 40 | 41 | We encourage all community members to actively participate in the development process by submitting pull requests. All non-trivial fixes should include comprehensive testing to ensure stability and prevent regression. We aim to process pull requests in a timely manner and appreciate your patience. 42 | 43 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 46 | 47 | For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /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-2016 CyberAgent, Inc. 2 | Copyright © 2016-2020 The OpenSTF Project 3 | Copyright © 2020-2022 CHEMOUNI Uriel 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /bench/sync/pull.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | import { createClient } from '../../src/adb'; 3 | import { performance, PerformanceObserver } from 'perf_hooks'; 4 | 5 | const deviceId = process.env.DEVICE_ID || ''; 6 | const deviceParams = deviceId ? ['-s', deviceId] : []; 7 | 8 | // Number of iterations for each benchmark 9 | const ITERATIONS = 3; 10 | 11 | interface BenchmarkResult { 12 | name: string; 13 | duration: number; 14 | opsPerSecond: number; 15 | } 16 | 17 | async function runBenchmark(name: string, fn: () => Promise): Promise { 18 | const results: number[] = []; 19 | 20 | // Run warm-up iteration 21 | await fn(); 22 | 23 | // Run actual benchmark iterations 24 | for (let i = 0; i < ITERATIONS; i++) { 25 | const start = performance.now(); 26 | await fn(); 27 | const end = performance.now(); 28 | results.push(end - start); 29 | } 30 | 31 | const totalDuration = results.reduce((sum, time) => sum + time, 0); 32 | const avgDuration = totalDuration / results.length; 33 | const opsPerSecond = 1000 / avgDuration; 34 | 35 | return { 36 | name, 37 | duration: avgDuration, 38 | opsPerSecond 39 | }; 40 | } 41 | 42 | async function main() { 43 | // Setup performance observer 44 | const obs = new PerformanceObserver((list) => { 45 | const entries = list.getEntries(); 46 | entries.forEach((entry) => { 47 | console.log(`${entry.name}: ${entry.duration}ms`); 48 | }); 49 | }); 50 | obs.observe({ entryTypes: ['measure'], buffered: true }); 51 | 52 | const benchmarks = [ 53 | { 54 | name: 'pull /dev/graphics/fb0 using ADB CLI', 55 | fn: () => new Promise((resolve) => { 56 | const proc = spawn('adb', [...deviceParams, 'pull', '/dev/graphics/fb0', '/dev/null']); 57 | proc.stdout.on('end', () => resolve()); 58 | }) 59 | }, 60 | { 61 | name: 'pull /dev/graphics/fb0 using client.pull()', 62 | fn: async () => { 63 | const client = createClient(); 64 | const stream = await client 65 | .getDevice(deviceId) 66 | .pull('/dev/graphics/fb0'); 67 | stream.resume(); 68 | return new Promise((resolve) => stream.on('end', () => resolve())); 69 | } 70 | } 71 | ]; 72 | 73 | console.log(`Running ${ITERATIONS} iterations for each benchmark...\n`); 74 | 75 | const results: BenchmarkResult[] = []; 76 | for (const benchmark of benchmarks) { 77 | try { 78 | const result = await runBenchmark(benchmark.name, benchmark.fn); 79 | results.push(result); 80 | } catch (error) { 81 | console.error(`Error running benchmark "${benchmark.name}":`, error); 82 | } 83 | } 84 | 85 | // Print results 86 | console.log('\nResults:'); 87 | console.log('----------------------------------------'); 88 | results.forEach((result) => { 89 | console.log(`\n${result.name}`); 90 | console.log(` Average duration: ${result.duration.toFixed(2)}ms`); 91 | console.log(` Operations/second: ${result.opsPerSecond.toFixed(2)}`); 92 | }); 93 | 94 | // Determine the faster method 95 | if (results.length === 2) { 96 | const [first, second] = results; 97 | const diff = Math.abs(first.opsPerSecond - second.opsPerSecond); 98 | const faster = first.opsPerSecond > second.opsPerSecond ? first : second; 99 | const percentage = ((diff / Math.min(first.opsPerSecond, second.opsPerSecond)) * 100).toFixed(2); 100 | 101 | console.log('\nComparison:'); 102 | console.log(`"${faster.name}" is ${percentage}% faster`); 103 | } 104 | } 105 | 106 | // Run benchmarks 107 | main().catch(console.error); -------------------------------------------------------------------------------- /bin/STFService_2.4.9.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UrielCh/adbkit/d8a31d4a916195787cb590a526e847d547cb59a6/bin/STFService_2.4.9.apk -------------------------------------------------------------------------------- /bin/adbkit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-undef */ 3 | require('../dist/cli'); 4 | -------------------------------------------------------------------------------- /bin/scrcpy-server-v1.20.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UrielCh/adbkit/d8a31d4a916195787cb590a526e847d547cb59a6/bin/scrcpy-server-v1.20.jar -------------------------------------------------------------------------------- /bin/scrcpy-server-v1.24.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UrielCh/adbkit/d8a31d4a916195787cb590a526e847d547cb59a6/bin/scrcpy-server-v1.24.jar -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #000000; 3 | --dark-hl-0: #D4D4D4; 4 | --light-hl-1: #AF00DB; 5 | --dark-hl-1: #C586C0; 6 | --light-hl-2: #001080; 7 | --dark-hl-2: #9CDCFE; 8 | --light-hl-3: #A31515; 9 | --dark-hl-3: #CE9178; 10 | --light-hl-4: #0000FF; 11 | --dark-hl-4: #569CD6; 12 | --light-hl-5: #795E26; 13 | --dark-hl-5: #DCDCAA; 14 | --light-hl-6: #0070C1; 15 | --dark-hl-6: #4FC1FF; 16 | --light-hl-7: #008000; 17 | --dark-hl-7: #6A9955; 18 | --light-hl-8: #098658; 19 | --dark-hl-8: #B5CEA8; 20 | --light-hl-9: #EE0000; 21 | --dark-hl-9: #D7BA7D; 22 | --light-hl-10: #267F99; 23 | --dark-hl-10: #4EC9B0; 24 | --light-hl-11: #000000FF; 25 | --dark-hl-11: #D4D4D4; 26 | --light-hl-12: #811F3F; 27 | --dark-hl-12: #D16969; 28 | --light-code-background: #FFFFFF; 29 | --dark-code-background: #1E1E1E; 30 | } 31 | 32 | @media (prefers-color-scheme: light) { :root { 33 | --hl-0: var(--light-hl-0); 34 | --hl-1: var(--light-hl-1); 35 | --hl-2: var(--light-hl-2); 36 | --hl-3: var(--light-hl-3); 37 | --hl-4: var(--light-hl-4); 38 | --hl-5: var(--light-hl-5); 39 | --hl-6: var(--light-hl-6); 40 | --hl-7: var(--light-hl-7); 41 | --hl-8: var(--light-hl-8); 42 | --hl-9: var(--light-hl-9); 43 | --hl-10: var(--light-hl-10); 44 | --hl-11: var(--light-hl-11); 45 | --hl-12: var(--light-hl-12); 46 | --code-background: var(--light-code-background); 47 | } } 48 | 49 | @media (prefers-color-scheme: dark) { :root { 50 | --hl-0: var(--dark-hl-0); 51 | --hl-1: var(--dark-hl-1); 52 | --hl-2: var(--dark-hl-2); 53 | --hl-3: var(--dark-hl-3); 54 | --hl-4: var(--dark-hl-4); 55 | --hl-5: var(--dark-hl-5); 56 | --hl-6: var(--dark-hl-6); 57 | --hl-7: var(--dark-hl-7); 58 | --hl-8: var(--dark-hl-8); 59 | --hl-9: var(--dark-hl-9); 60 | --hl-10: var(--dark-hl-10); 61 | --hl-11: var(--dark-hl-11); 62 | --hl-12: var(--dark-hl-12); 63 | --code-background: var(--dark-code-background); 64 | } } 65 | 66 | :root[data-theme='light'] { 67 | --hl-0: var(--light-hl-0); 68 | --hl-1: var(--light-hl-1); 69 | --hl-2: var(--light-hl-2); 70 | --hl-3: var(--light-hl-3); 71 | --hl-4: var(--light-hl-4); 72 | --hl-5: var(--light-hl-5); 73 | --hl-6: var(--light-hl-6); 74 | --hl-7: var(--light-hl-7); 75 | --hl-8: var(--light-hl-8); 76 | --hl-9: var(--light-hl-9); 77 | --hl-10: var(--light-hl-10); 78 | --hl-11: var(--light-hl-11); 79 | --hl-12: var(--light-hl-12); 80 | --code-background: var(--light-code-background); 81 | } 82 | 83 | :root[data-theme='dark'] { 84 | --hl-0: var(--dark-hl-0); 85 | --hl-1: var(--dark-hl-1); 86 | --hl-2: var(--dark-hl-2); 87 | --hl-3: var(--dark-hl-3); 88 | --hl-4: var(--dark-hl-4); 89 | --hl-5: var(--dark-hl-5); 90 | --hl-6: var(--dark-hl-6); 91 | --hl-7: var(--dark-hl-7); 92 | --hl-8: var(--dark-hl-8); 93 | --hl-9: var(--dark-hl-9); 94 | --hl-10: var(--dark-hl-10); 95 | --hl-11: var(--dark-hl-11); 96 | --hl-12: var(--dark-hl-12); 97 | --code-background: var(--dark-code-background); 98 | } 99 | 100 | .hl-0 { color: var(--hl-0); } 101 | .hl-1 { color: var(--hl-1); } 102 | .hl-2 { color: var(--hl-2); } 103 | .hl-3 { color: var(--hl-3); } 104 | .hl-4 { color: var(--hl-4); } 105 | .hl-5 { color: var(--hl-5); } 106 | .hl-6 { color: var(--hl-6); } 107 | .hl-7 { color: var(--hl-7); } 108 | .hl-8 { color: var(--hl-8); } 109 | .hl-9 { color: var(--hl-9); } 110 | .hl-10 { color: var(--hl-10); } 111 | .hl-11 { color: var(--hl-11); } 112 | .hl-12 { color: var(--hl-12); } 113 | pre, code { background: var(--code-background); } 114 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@u4/adbkit", 3 | "version": "4.1.20", 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 | "scrcpy" 13 | ], 14 | "bin": { 15 | "adbkit": "./bin/adbkit" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/UrielCh/adbkit/issues" 19 | }, 20 | "license": "Apache-2.0", 21 | "funding": "https://github.com/sponsors/urielch", 22 | "author": { 23 | "name": "openstf", 24 | "email": "contact@openstf.io", 25 | "url": "https://urielch.github.io/" 26 | }, 27 | "contributors": [ 28 | { 29 | "name": "uriel chemouni", 30 | "email": "uchemouni@gmail.com", 31 | "url": "https://urielch.github.io/" 32 | } 33 | ], 34 | "main": "./dist/index.js", 35 | "types": "./dist/index.d.ts", 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/UrielCh/adbkit.git" 39 | }, 40 | "scripts": { 41 | "clean": "rimraf dist", 42 | "keycode": "ts-node tasks/keycode.ts", 43 | "servicemap": "ts-node tasks/servicemap.ts", 44 | "prepublish": "npm run clean && npm run compile", 45 | "docs": "typedoc --entryPointStrategy expand", 46 | "compile": "tsc -p .", 47 | "build": "tsc -p .", 48 | "lint": "eslint ./ --ext .ts", 49 | "format": "eslint ./ --ext .ts --fix", 50 | "test": "mocha -r tsx --reporter spec --colors test/**/*.ts", 51 | "test2": "mocha -r tsx --reporter spec --colors test/adb/thirdparty/**/*.ts", 52 | "test3": "mocha -r tsx --reporter spec --colors test/adb/command/host-transport/serviceCall.ts", 53 | "ncu": "npx npm-check-updates -i" 54 | }, 55 | "dependencies": { 56 | "@u4/adbkit-logcat": "2.1.2", 57 | "@u4/adbkit-monkey": "^1.0.5", 58 | "@u4/minicap-prebuilt": "^1.0.0", 59 | "@xmldom/xmldom": "^0.8.10", 60 | "commander": "12.1.0", 61 | "debug": "~4.4.0", 62 | "get-port": "7.1.0", 63 | "node-forge": "^1.3.1", 64 | "promise-duplex": "^8.0.0", 65 | "promise-readable": "^8.0.1", 66 | "protobufjs": "^7.4.0", 67 | "xpath": "^0.0.34" 68 | }, 69 | "devDependencies": { 70 | "@types/debug": "^4.1.12", 71 | "@types/mocha": "^10.0.10", 72 | "@types/node": "^20.11.7", 73 | "@types/node-forge": "^1.3.11", 74 | "@types/sinon": "^17.0.3", 75 | "@types/sinon-chai": "^4.0.0", 76 | "@typescript-eslint/eslint-plugin": "^8.18.1", 77 | "@typescript-eslint/parser": "^8.18.1", 78 | "chai": "~5.1.2", 79 | "eslint": "^8.56.0", 80 | "mocha": "~10.8.2", 81 | "picocolors": "^1.1.1", 82 | "prettier": "^3.4.2", 83 | "rimraf": "^6.0.1", 84 | "sinon": "~19.0.2", 85 | "sinon-chai": "~4.0.0", 86 | "tsx": "^4.19.2", 87 | "typedoc": "^0.27.5", 88 | "typedoc-plugin-rename-defaults": "^0.7.2", 89 | "typescript": "5.6.3", 90 | "why-is-node-running": "^3.2.1" 91 | }, 92 | "engines": { 93 | "node": ">= 12.20.0" 94 | }, 95 | "files": [ 96 | "dist", 97 | "bin" 98 | ], 99 | "optionalDependencies": { 100 | "@devicefarmer/minicap-prebuilt": "^2.7.2" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/adb.ts: -------------------------------------------------------------------------------- 1 | import Client from './adb/client'; 2 | import { ClientOptions } from './models/ClientOptions'; 3 | 4 | export interface AdbOptions { 5 | /** 6 | * host to connect default is 127.0.0.1 7 | */ 8 | host?: string; 9 | /** 10 | * The port where the ADB server is listening. Defaults to `5037`. 11 | */ 12 | port?: number; 13 | /** 14 | * As the sole exception, this option provides the path to the `adb` binary, used for starting the server locally if initial connection fails. Defaults to `'adb'`. 15 | */ 16 | bin?: string; 17 | } 18 | 19 | /** 20 | * Creates a client instance with the provided options. Note that this will not automatically establish a connection, it will only be done when necessary. 21 | * @param options An object compatible with [Net.connect][net-connect]'s options: 22 | * **port** The port where the ADB server is listening. Defaults to `5037`. 23 | * **host** The host of the ADB server. Defaults to `'127.0.0.1'`. 24 | * **bin** As the sole exception, this option provides the path to the `adb` binary, used for starting the server locally if initial connection fails. Defaults to `'adb'`. 25 | * 26 | * @returns The client instance. 27 | * @example 28 | * Function you should import and call first 29 | * ```ts 30 | * import { createClient } fronm @u4/adbkit 31 | * 32 | * const client = createClient(); 33 | * ``` 34 | 35 | */ 36 | export function createClient(options: AdbOptions = { port: 5037 }): Client { 37 | const opts: ClientOptions = { 38 | bin: options.bin, 39 | host: options.host || process.env.ADB_HOST, 40 | port: options.port || 5037, 41 | }; 42 | if (!opts.port) { 43 | const port = parseInt(process.env.ADB_PORT || '5037', 10); 44 | if (!isNaN(port)) { 45 | opts.port = port; 46 | } 47 | } 48 | return new Client(opts); 49 | } 50 | -------------------------------------------------------------------------------- /src/adb/DevicePackage.ts: -------------------------------------------------------------------------------- 1 | import DeviceClient from './DeviceClient'; 2 | 3 | // export interface DevicePackageDump { 4 | // 'Activity Resolver Table': { 5 | // 'Full MIME Types': {[mime: string]: any}; 6 | // 'Base MIME Types': {[mime: string]: any}; 7 | // 'Schemes': any; 8 | // 'Non-Data Actions': {[action: string]: any}; 9 | // 'MIME Typed Actions': {[action: string]: any}; 10 | // }, 11 | // 'Receiver Resolver Table': { 12 | // 'Non-Data Actions': {[action: string]: any}; 13 | // } 14 | // 'Service Resolver Table': { 15 | // 'Non-Data Actions': {[action: string]: any}; 16 | // }, 17 | // 'Provider Resolver Table': { 18 | // 'Non-Data Actions': {[action: string]: any}; 19 | // }, 20 | // 'Preferred Activities User 0': { 21 | // 'Non-Data Actions': {[action: string]: any}; 22 | // }, 23 | // Permissions: Array; 24 | // 'Registered ContentProviders': {[action: string]: any}; 25 | // 'ContentProvider Authorities': {[action: string]: any}; 26 | // 'Key Set Manager': {[action: string]: any}; 27 | // Packages: { 28 | // } 29 | // } 30 | 31 | 32 | export interface PackageInfo { 33 | userId: number 34 | pkg: string; 35 | codePath: string; 36 | resourcePath: string; 37 | legacyNativeLibraryDir: string; 38 | primaryCpuAbi: string; 39 | secondaryCpuAbi: string; 40 | versionCode: string; 41 | minSdk: string; 42 | targetSdk: string; 43 | versionName: string; 44 | apkSigningVersion: string; 45 | dataDir: string; 46 | timeStamp: string; 47 | firstInstallTime: string; 48 | lastUpdateTime: string; 49 | installPermissionsFixed: string;// true / false 50 | } 51 | 52 | export default class DevicePackage { 53 | #name: string; 54 | constructor(private client: DeviceClient, name: string) { 55 | this.#name = name; 56 | } 57 | 58 | get name(): string { 59 | return this.#name; 60 | } 61 | 62 | async dump(): Promise { 63 | return this.client.execOut(`dumpsys package ${this.#name}`, 'utf-8'); 64 | } 65 | 66 | async getInfo(): Promise { 67 | let raw = await this.dump(); 68 | const indexStart = raw.indexOf('\nPackages:'); 69 | if (indexStart === -1) 70 | throw Error('invalid dumpsys package input'); 71 | raw = raw.substring(indexStart+1); 72 | const end = raw.search(/\n\w/m); 73 | if (end >= 0) raw = raw.substring(0, end); 74 | const result = {} as {[key: string]: string}; 75 | for (const m of raw.matchAll(/(\w+)=(.+)/g)) { 76 | result[m[1]] = m[2]; 77 | } 78 | return result as unknown as PackageInfo; 79 | } 80 | } -------------------------------------------------------------------------------- /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 '../models/ExtendedPublicKey'; 19 | 20 | const BigInteger = forge.jsbn.BigInteger; 21 | 22 | export default class Auth { 23 | private static RE = /^((?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?)\0?( .*|)\s*$/; 24 | public static parsePublicKey(buffer: string): Promise { 25 | return new Promise((resolve, reject) => { 26 | const match = Auth.RE.exec(buffer); 27 | if (match) { 28 | const struct = Buffer.from(match[1], 'base64'); 29 | const comment = match[2].trim(); 30 | return resolve(Auth.readPublicKeyFromStruct(struct, comment)); 31 | } else { 32 | return reject(new Error('Unrecognizable public key format')); 33 | } 34 | }); 35 | } 36 | 37 | private static readPublicKeyFromStruct(struct: Buffer, comment: string): ExtendedPublicKey { 38 | if (!struct.length) { 39 | throw new Error('Invalid public key'); 40 | } 41 | // Keep track of what we've read already 42 | let offset = 0; 43 | // Get len 44 | const len = struct.readUInt32LE(offset) * 4; 45 | offset += 4; 46 | if (struct.length !== 4 + 4 + len + len + 4) { 47 | throw new Error('Invalid public key'); 48 | } 49 | // Skip n0inv, we don't need it 50 | offset += 4; 51 | // Get n 52 | const n = Buffer.alloc(len); 53 | struct.copy(n, 0, offset, offset + len); 54 | [].reverse.call(n); 55 | offset += len; 56 | // Skip rr, we don't need it 57 | offset += len; 58 | // Get e 59 | const e = struct.readUInt32LE(offset); 60 | if (!(e === 3 || e === 65537)) { 61 | throw new Error(`Invalid exponent ${e}, only 3 and 65537 are supported`); 62 | } 63 | 64 | // FIXME: bug in @types/node-forge 65 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 66 | // @ts-ignore 67 | const modulus = new BigInteger(n.toString('hex'), 16); 68 | 69 | // FIXME: bug in @types/node-forge 70 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 71 | // @ts-ignore 72 | const exponent = new BigInteger(e.toString(), 10); 73 | // Restore the public key 74 | const key = forge.pki.rsa.setPublicKey(modulus, exponent); 75 | // It will be difficult to retrieve the fingerprint later as it's based 76 | // on the complete struct data, so let's just extend the key with it. 77 | const md = forge.md.md5.create(); 78 | md.update(struct.toString('binary')); 79 | const extendedKey: ExtendedPublicKey = key as ExtendedPublicKey; 80 | extendedKey.fingerprint = (md.digest().toHex().match(/../g) || []).join(':'); 81 | // Expose comment for the same reason 82 | extendedKey.comment = comment; 83 | return extendedKey; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/adb/command.ts: -------------------------------------------------------------------------------- 1 | import Connection from './connection'; 2 | import Protocol from './protocol'; 3 | import Parser from './parser'; 4 | import WithToString from '../models/WithToString'; 5 | import { DeviceClientOptions } from '../models/DeviceClientOptions'; 6 | import Utils from './utils'; 7 | 8 | const debug = Utils.debug('adb:command'); 9 | const RE_SQUOT = /'/g; 10 | const RE_ESCAPE = /([$`\\!"])/g; 11 | 12 | export default abstract class Command { 13 | public readonly options: Partial; 14 | private lastCmd = ''; 15 | 16 | get lastCommand(): string { 17 | return this.lastCmd || ''; 18 | } 19 | 20 | constructor(public connection: Connection, options = {} as Partial) { 21 | this.options = { sudo: false, ...options }; 22 | } 23 | 24 | public get parser(): Parser { 25 | return this.connection.parser; 26 | } 27 | 28 | // FIXME(intentional any): not "any" will break it all 29 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types 30 | public abstract execute(...args: any[]): Promise; 31 | 32 | /** 33 | * encode message and send it to ADB socket 34 | * @returns byte write count 35 | */ 36 | public _send(data: string): Promise { 37 | this.parser.lastMessage = data; 38 | const encoded = Protocol.encodeData(data); 39 | if (debug.enabled) { 40 | debug(`Send '${encoded}'`); 41 | } 42 | return this.connection.write(encoded); 43 | } 44 | 45 | public escape(arg: number | WithToString): number | string { 46 | switch (typeof arg) { 47 | case 'number': 48 | return arg; 49 | default: 50 | return `'${arg.toString().replace(RE_SQUOT, "'\"'\"'")}'`; 51 | } 52 | } 53 | 54 | public escapeCompat(arg: number | WithToString): number | string { 55 | switch (typeof arg) { 56 | case 'number': 57 | return arg; 58 | default: 59 | return `"${arg.toString().replace(RE_ESCAPE, '\\$1')}"`; 60 | } 61 | } 62 | 63 | /** 64 | * called once per command, only affect shell based command. 65 | * @returns sent data 66 | */ 67 | protected async sendCommand(data: string): Promise { 68 | if (this.options.sudo) { 69 | if (data.startsWith('shell:')) { 70 | data = 'shell:su -c \'' + data.substring(6).replace(/'/g, "\\'") + '\''; 71 | } 72 | else if (data.startsWith('exec:')) { 73 | data = 'exec:su -c \'' + data.substring(5).replace(/'/g, "\\'") + '\''; 74 | } 75 | } 76 | this.lastCmd = data; 77 | await this._send(data); 78 | return data; 79 | } 80 | 81 | /** 82 | * most common action: read for Okey 83 | */ 84 | protected async readOKAY(): Promise { 85 | await this.parser.readCode(Protocol.OKAY); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/forward.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class ForwardCommand extends Command { 4 | async execute(serial: string, local: string, remote: string): Promise { 5 | await this._send(`host-serial:${serial}:forward:${local};${remote}`); 6 | await this.readOKAY(); 7 | await this.readOKAY(); 8 | return true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/getdevicepath.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class GetDevicePathCommand extends Command { 4 | async execute(serial: string): Promise { 5 | await this._send(`host-serial:${serial}:get-devpath`); 6 | await this.readOKAY(); 7 | return this.parser.readValue('utf8'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/getserialno.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class GetSerialNoCommand extends Command { 4 | async execute(serial: string): Promise { 5 | await this._send(`host-serial:${serial}:get-serialno`); 6 | await this.readOKAY(); 7 | return this.parser.readValue('utf8'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/getstate.ts: -------------------------------------------------------------------------------- 1 | import { DeviceType } from '../../../models/Device'; 2 | import Command from '../../command'; 3 | 4 | export default class GetStateCommand extends Command { 5 | async execute(serial: string): Promise { 6 | await this._send(`host-serial:${serial}:get-state`); 7 | await this.readOKAY(); 8 | return this.parser.readValue('utf8') as Promise; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ForwardCommand } from './forward'; 2 | export { default as KillForwardCommand } from './killForward'; 3 | export { default as GetDevicePathCommand } from './getdevicepath'; 4 | export { default as GetSerialNoCommand } from './getdevicepath'; 5 | export { default as GetStateCommand } from './getstate'; 6 | export { default as ListForwardsCommand } from './listforwards'; 7 | export { default as WaitForDeviceCommand } from './waitfordevice'; 8 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/killForward.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class KillForwardCommand extends Command { 4 | async execute(serial: string, local: string): Promise { 5 | await this._send(`host-serial:${serial}:killforward:${local}`); 6 | await this.readOKAY(); 7 | await this.readOKAY(); 8 | return true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/listforwards.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Forward from '../../../models/Forward'; 3 | 4 | export default class ListForwardsCommand extends Command { 5 | async execute(serial: string): Promise { 6 | await this._send(`host-serial:${serial}:list-forward`); 7 | await this.readOKAY(); 8 | const value = await this.parser.readValue('utf8') 9 | return this._parseForwards(value); 10 | } 11 | 12 | private _parseForwards(value: string): Forward[] { 13 | return value 14 | .split('\n') 15 | .filter((e) => e) 16 | .map((forward) => { 17 | const [serial, local, remote] = forward.split(/\s+/); 18 | return { serial, local, remote }; 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/adb/command/host-serial/waitfordevice.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class WaitForDeviceCommand extends Command { 4 | async execute(serial: string): Promise { 5 | await this._send(`host-serial:${serial}:wait-for-any-device`); 6 | await this.readOKAY(); 7 | await this.readOKAY(); 8 | return serial; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/Parcel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://android.googlesource.com/platform/frameworks/native/+/master/libs/binder/ParcelValTypes.h 3 | */ 4 | export enum ParcelVal { 5 | VAL_NULL = -1, 6 | VAL_STRING = 0, 7 | VAL_INTEGER = 1, 8 | VAL_MAP = 2, 9 | VAL_BUNDLE = 3, 10 | VAL_PARCELABLE = 4, 11 | VAL_SHORT = 5, 12 | VAL_LONG = 6, 13 | VAL_FLOAT = 7, 14 | VAL_DOUBLE = 8, 15 | VAL_BOOLEAN = 9, 16 | VAL_CHARSEQUENCE = 10, 17 | VAL_LIST = 11, 18 | VAL_SPARSEARRAY = 12, 19 | VAL_BYTEARRAY = 13, 20 | VAL_STRINGARRAY = 14, 21 | VAL_IBINDER = 15, 22 | VAL_PARCELABLEARRAY = 16, 23 | VAL_OBJECTARRAY = 17, 24 | VAL_INTARRAY = 18, 25 | VAL_LONGARRAY = 19, 26 | VAL_BYTE = 20, 27 | VAL_SERIALIZABLE = 21, 28 | VAL_SPARSEBOOLEANARRAY = 22, 29 | VAL_BOOLEANARRAY = 23, 30 | VAL_CHARSEQUENCEARRAY = 24, 31 | VAL_PERSISTABLEBUNDLE = 25, 32 | VAL_SIZE = 26, 33 | VAL_SIZEF = 27, 34 | VAL_DOUBLEARRAY = 28, 35 | VAL_CHAR = 29, 36 | VAL_SHORTARRAY = 30, 37 | VAL_CHARARRAY = 31, 38 | VAL_FLOATARRAY = 32, 39 | } -------------------------------------------------------------------------------- /src/adb/command/host-transport/ShellExecError.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class ShellExecError extends Error { 3 | constructor(message: string) { 4 | super(`Failure: '${message}'`); 5 | Object.setPrototypeOf(this, ShellExecError.prototype); 6 | this.name = 'ShellExecError'; 7 | Error.captureStackTrace(this, ShellExecError); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/clear.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class ClearCommand extends Command { 4 | async execute(pkg: string): Promise { 5 | this.sendCommand(`shell:pm clear ${pkg}`); 6 | await this.readOKAY(); 7 | try { 8 | const result = await this.parser.searchLine(/^(Success|Failed)$/) 9 | switch (result[0]) { 10 | case 'Success': 11 | return true; 12 | case 'Failed': 13 | // Unfortunately, the command may stall at this point and we 14 | // have to kill the connection. 15 | throw new Error(`Package '${pkg}' could not be cleared`); 16 | } 17 | return false; 18 | } finally { 19 | this.parser.end() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/exec.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import { Duplex } from 'stream'; 3 | import WithToString from '../../../models/WithToString'; 4 | 5 | export default class ExecCommand extends Command { 6 | async execute(command: string | ArrayLike): Promise { 7 | if (Array.isArray(command)) { 8 | command = command.map(this.escape).join(' '); 9 | } 10 | this.sendCommand(`exec:${command}`); 11 | await this.readOKAY(); 12 | return this.parser.raw(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/framebuffer.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | import RgbTransform from '../../framebuffer/rgbtransform'; 3 | import Command from '../../command'; 4 | import { Readable } from 'stream'; 5 | import FramebufferMeta, { ColorFormat } from '../../../models/FramebufferMeta'; 6 | import FramebufferStreamWithMeta from '../../../models/FramebufferStreamWithMeta'; 7 | import Utils from '../../utils'; 8 | 9 | const debug = Utils.debug('adb:command:framebuffer'); 10 | 11 | export default class FrameBufferCommand extends Command { 12 | async execute(format: string): Promise { 13 | await this._send('framebuffer:'); 14 | await this.readOKAY(); 15 | const header = await this.parser.readBytes(52) 16 | let stream: FramebufferStreamWithMeta; 17 | const meta = this._parseHeader(header); 18 | switch (format) { 19 | case 'raw': 20 | stream = this.parser.raw() as FramebufferStreamWithMeta; 21 | stream.meta = meta; 22 | return stream; 23 | default: 24 | stream = this._convert(meta, format) as FramebufferStreamWithMeta; 25 | stream.meta = meta; 26 | return stream; 27 | } 28 | } 29 | 30 | _convert(meta: FramebufferMeta, format: string, raw?: Readable): Readable { 31 | debug(`Converting raw framebuffer stream into ${format.toUpperCase()}`); 32 | switch (meta.format) { 33 | case 'rgb': 34 | case 'rgba': 35 | break; 36 | default: 37 | // Known to be supported by GraphicsMagick 38 | debug(`Silently transforming '${meta.format}' into 'rgb' for \`gm\``); 39 | const transform = new RgbTransform(meta); 40 | meta.format = 'rgb'; 41 | raw = this.parser.raw().pipe(transform); 42 | } 43 | const proc = spawn('gm', ['convert', '-size', `${meta.width}x${meta.height}`, `${meta.format}:-`, `${format}:-`]); 44 | if (raw) { 45 | raw.pipe(proc.stdin); 46 | } 47 | return proc.stdout; 48 | } 49 | 50 | _parseHeader(header: Buffer): FramebufferMeta { 51 | let offset = 0; 52 | const version = header.readUInt32LE(offset); 53 | if (version === 16) { 54 | throw new Error('Old-style raw images are not supported'); 55 | } 56 | offset += 4; 57 | const bpp = header.readUInt32LE(offset); 58 | offset += 4; 59 | const size = header.readUInt32LE(offset); 60 | offset += 4; 61 | const width = header.readUInt32LE(offset); 62 | offset += 4; 63 | const height = header.readUInt32LE(offset); 64 | offset += 4; 65 | const red_offset = header.readUInt32LE(offset); 66 | offset += 4; 67 | const red_length = header.readUInt32LE(offset); 68 | offset += 4; 69 | const blue_offset = header.readUInt32LE(offset); 70 | offset += 4; 71 | const blue_length = header.readUInt32LE(offset); 72 | offset += 4; 73 | const green_offset = header.readUInt32LE(offset); 74 | offset += 4; 75 | const green_length = header.readUInt32LE(offset); 76 | offset += 4; 77 | const alpha_offset = header.readUInt32LE(offset); 78 | offset += 4; 79 | const alpha_length = header.readUInt32LE(offset); 80 | let format: ColorFormat = blue_offset === 0 ? 'bgr' : 'rgb'; 81 | if (bpp === 32 || alpha_length) { 82 | format += 'a'; 83 | } 84 | return { 85 | version, 86 | bpp, 87 | size, 88 | width, 89 | height, 90 | red_offset, 91 | red_length, 92 | blue_offset, 93 | blue_length, 94 | green_offset, 95 | green_length, 96 | alpha_offset, 97 | alpha_length, 98 | format: format as ColorFormat, 99 | }; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/getfeatures.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import { Features } from '../../../models/Features'; 3 | 4 | const RE_FEATURE = /^feature:(.*?)(?:=(.*?))?\r?$/gm; 5 | 6 | export default class GetFeaturesCommand extends Command { 7 | async execute(): Promise { 8 | this.sendCommand('shell:pm list features 2>/dev/null'); 9 | await this.readOKAY(); 10 | const data = await this.parser.readAll(); 11 | return this._parseFeatures(data.toString()); 12 | } 13 | 14 | private _parseFeatures(value: string): Features { 15 | const features: Features = {}; 16 | let match: RegExpExecArray | null; 17 | while ((match = RE_FEATURE.exec(value))) { 18 | features[match[1]] = match[2] || true; 19 | } 20 | return features; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/getpackages.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class GetPackagesCommand extends Command { 4 | async execute(flags?: string): Promise { 5 | if (flags) { 6 | this.sendCommand(`shell:pm list packages ${flags} 2>/dev/null`); 7 | } else { 8 | this.sendCommand('shell:pm list packages 2>/dev/null'); 9 | } 10 | await this.readOKAY(); 11 | const data = await this.parser.readAll(); 12 | return this._parsePackages(data.toString()); 13 | } 14 | 15 | private _parsePackages(value: string): string[] { 16 | const packages: string[] = []; 17 | const RE_PACKAGE = /^package:(.*?)\r?$/gm; 18 | for (;;) { 19 | const match = RE_PACKAGE.exec(value); 20 | if (match) { 21 | packages.push(match[1]); 22 | } else { 23 | break; 24 | } 25 | } 26 | return packages; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/getproperties.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import { Properties } from '../../../models/Properties'; 3 | 4 | const RE_KEYVAL = /^\[([\s\S]*?)\]: \[([\s\S]*?)\]\r?$/gm; 5 | 6 | export default class GetPropertiesCommand extends Command { 7 | async execute(): Promise { 8 | this.sendCommand('shell:getprop'); 9 | await this.readOKAY(); 10 | const data = await this.parser.readAll() 11 | return this._parseProperties(data.toString()); 12 | } 13 | 14 | private _parseProperties(value: string): Properties { 15 | const properties: Properties = {}; 16 | let match: RegExpExecArray | null; 17 | while ((match = RE_KEYVAL.exec(value))) { 18 | properties[match[1]] = match[2]; 19 | } 20 | return properties; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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, RebootType } 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 ExecCommand } from './exec'; 20 | export { default as StartActivityCommand } from './startactivity'; 21 | export { default as StartServiceCommand } from './startservice'; 22 | export { default as SyncCommand } from './sync'; 23 | export { default as TcpCommand } from './tcp'; 24 | export { default as TcpIpCommand } from './tcpip'; 25 | export { default as TrackJdwpCommand } from './trackjdwp'; 26 | export { default as UninstallCommand, type UninstallCommandOptions } from './uninstall'; 27 | export { default as UsbCommand } from './usb'; 28 | export { default as WaitBootCompleteCommand } from './waitbootcomplete'; 29 | export { default as PsCommand, PsEntry, ProcessState } from './ps'; 30 | 31 | export { default as ServicesListCommand, type AdbServiceInfo, type KnownServices } from './servicesList'; 32 | export { default as ServiceCheckCommand } from './serviceCheck'; 33 | // stop re-export for internal usage 34 | // export { default as ServiceCallCommand, ServiceCallArg, ServiceCallArgNumber, ServiceCallArgNull, ServiceCallArgString, ParcelReader } from './serviceCall'; 35 | 36 | export { default as IpRouteCommand, IpRouteEntry } from './ipRoute'; 37 | export { default as IpRuleCommand, IpRuleEntry } from './ipRule'; 38 | 39 | // not a command 40 | export { default as ShellExecError } from './ShellExecError'; 41 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/install.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | /** 4 | * install [-rtfdgw] [-i PACKAGE] [--user USER_ID|all|current] 5 | * [-p INHERIT_PACKAGE] [--install-location 0/1/2] 6 | * [--install-reason 0/1/2/3/4] [--originating-uri URI] 7 | * [--referrer URI] [--abi ABI_NAME] [--force-sdk] 8 | * [--preload] [--instant] [--full] [--dont-kill] 9 | * [--enable-rollback] 10 | * [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] 11 | * [--apex] [--wait TIMEOUT] 12 | * [PATH [SPLIT...]|-] 13 | * Install an application. Must provide the apk data to install, either as 14 | * file path(s) or '-' to read from stdin. Options are: 15 | * -R: disallow replacement of existing application 16 | * -t: allow test packages 17 | * -i: specify package name of installer owning the app 18 | * -f: install application on internal flash 19 | * -d: allow version code downgrade (debuggable packages only) 20 | * -p: partial application install (new split on top of existing pkg) 21 | * -g: grant all runtime permissions 22 | * -S: size in bytes of package, required for stdin 23 | * --user: install under the given user. 24 | * --dont-kill: installing a new feature split, don't kill running app 25 | * --restrict-permissions: don't whitelist restricted permissions at install 26 | * --originating-uri: set URI where app was downloaded from 27 | * --referrer: set URI that instigated the install of the app 28 | * --pkg: specify expected package name of app being installed 29 | * --abi: override the default ABI of the platform 30 | * --instant: cause the app to be installed as an ephemeral install app 31 | * --full: cause the app to be installed as a non-ephemeral full app 32 | * --install-location: force the install location: 33 | * 0=auto, 1=internal only, 2=prefer external 34 | * --install-reason: indicates why the app is being installed: 35 | * 0=unknown, 1=admin policy, 2=device restore, 36 | * 3=device setup, 4=user request 37 | * --force-uuid: force install on to disk volume with given UUID 38 | * --apex: install an .apex file, not an .apk 39 | * --wait: when performing staged install, wait TIMEOUT milliseconds 40 | * for pre-reboot verification to complete. If TIMEOUT is not 41 | * specified it will wait for 60000 milliseconds. 42 | */ 43 | export default class InstallCommand extends Command { 44 | async execute(apk: string): Promise { 45 | this.sendCommand(`shell:pm install -r ${this.escapeCompat(apk)}`); 46 | await this.readOKAY(); 47 | try { 48 | const match = await this.parser.searchLine(/^(Success|Failure \[(.*?)\])$/); 49 | if (match[1] === 'Success') { 50 | return true; 51 | } else { 52 | const code = match[2]; 53 | const err = new Error(`${apk} could not be installed [${code}]`); 54 | (err as Error & {code: string}).code = code; 55 | throw err; 56 | } 57 | } finally { 58 | // Consume all remaining content to "naturally" close the 59 | // connection. 60 | this.parser.readAll(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/ipRule.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | /** 4 | * Usage: ip rule { add | del } SELECTOR ACTION 5 | * ip rule { flush | save | restore } 6 | * ip rule [ list [ SELECTOR ]] 7 | * SELECTOR := [ not ] [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK[/MASK] ] 8 | * [ iif STRING ] [ oif STRING ] [ pref NUMBER ] [ l3mdev ] 9 | * [ uidrange NUMBER-NUMBER ] 10 | * ACTION := [ table TABLE_ID ] 11 | * [ nat ADDRESS ] 12 | * [ realms [SRCREALM/]DSTREALM ] 13 | * [ goto NUMBER ] 14 | * SUPPRESSOR 15 | * SUPPRESSOR := [ suppress_prefixlength NUMBER ] 16 | * [ suppress_ifgroup DEVGROUP ] 17 | * TABLE_ID := [ local | main | default | NUMBER ] 18 | */ 19 | export default class IpRuleCommand extends Command> { 20 | async execute(...args: string[]): Promise> { 21 | this.sendCommand(['shell:ip', 'rule', ...args].join(' ')); 22 | await this.readOKAY(); 23 | const data = await this.parser.readAll() 24 | return this.parseIpRule(data.toString()); 25 | } 26 | 27 | private parseIpRule(value: string): Array { 28 | const lines: string[] = value.split(/[\r\n]+/g).filter(a => a); 29 | const result: IpRuleEntry[] = []; 30 | for (const line of lines) { 31 | result.push(new IpRuleEntry(line)); 32 | } 33 | return result; 34 | } 35 | } 36 | 37 | /** 38 | * unix route model 39 | * ROUTE := NODE_SPEC [ INFO_SPEC ] 40 | * 41 | * NODE_SPEC := [ TYPE ] PREFIX [ tos TOS ] [ table TABLE_ID ] [ proto RTPROTO ] [ scope SCOPE ] [ metric METRIC ] 42 | */ 43 | export class IpRuleEntry { 44 | constructor(line: string) { 45 | const words = line.split(/\s+/g).filter(a => a); 46 | const id = words.shift(); 47 | if (!id) 48 | throw Error(`Failed to parse line:\n ${line}\n line should start with an id, Fix me in ipRule.ts`); 49 | this.id = Number(id.replace(':', '')) 50 | while (words.length) { 51 | const next = words.shift(); 52 | switch (next) { 53 | case 'from': 54 | case 'fwmark': 55 | case 'lookup': 56 | case 'iif': 57 | case 'oif': 58 | case 'uidrange': 59 | this[next] = words.shift(); 60 | break; 61 | case 'unreachable': 62 | this[next] = true; 63 | break; 64 | default: 65 | throw Error(`Failed to parse line:\n ${line}\n token: ${next} in ip route response, Fix me in ipRule.ts`); 66 | } 67 | } 68 | } 69 | 70 | // route priority 71 | id: number; 72 | 73 | from?: string; 74 | fwmark?: string; 75 | iif?: string; 76 | oif?: string; 77 | uidrange?: string; 78 | lookup?: string; 79 | unreachable?: boolean; 80 | 81 | toStirng(): string { 82 | const opt: string[] = []; 83 | for (const field of ['from', 'fwmark', 'iif', 'oif', 'uidrange', 'lookup'] as const) { 84 | const value = this[field]; 85 | if (value) { 86 | opt.push(field); 87 | opt.push(value); 88 | } 89 | } 90 | if (this.unreachable) 91 | opt.push('unreachable'); 92 | return `${this.id}:\t${opt.join(' ')}`; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/isinstalled.ts: -------------------------------------------------------------------------------- 1 | import { AdbPrematureEOFError } from '../../errors'; 2 | import Command from '../../command'; 3 | 4 | export default class IsInstalledCommand extends Command { 5 | async execute(pkg: string): Promise { 6 | await this._send(`shell:pm path ${pkg} 2>/dev/null`); 7 | await this.readOKAY(); 8 | try { 9 | const reply = await this.parser.readAscii(8); 10 | switch (reply) { 11 | case 'package:': 12 | return true; 13 | default: 14 | throw this.parser.unexpected(reply, "'package:'"); 15 | } 16 | } catch (err) { 17 | if (err instanceof AdbPrematureEOFError) { 18 | return false; 19 | } 20 | throw err; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/listreverses.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Reverse from '../../../models/Reverse'; 3 | 4 | export default class ListReversesCommand extends Command { 5 | async execute(): Promise { 6 | await this._send('reverse:list-forward'); 7 | await this.readOKAY(); 8 | const value = await this.parser.readValue('utf8') 9 | return this._parseReverses(value); 10 | } 11 | 12 | private _parseReverses(value: string): Reverse[] { 13 | const reverses: Reverse[] = []; 14 | const ref = value.split('\n'); 15 | for (let i = 0, len = ref.length; i < len; i++) { 16 | const reverse = ref[i]; 17 | if (reverse) { 18 | const [, remote, local] = reverse.split(/\s+/); 19 | reverses.push({ remote, local }); 20 | } 21 | } 22 | return reverses; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/local.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import { Duplex } from 'stream'; 3 | 4 | export default class LocalCommand extends Command { 5 | async execute(path: string): Promise { 6 | await this._send(/:/.test(path) ? path : `localfilesystem:${path}`); 7 | await this.readOKAY(); 8 | return this.parser.raw(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/log.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import { Duplex } from 'stream'; 3 | 4 | export default class LogCommand extends Command { 5 | async execute(name: string): Promise { 6 | await this._send(`log:${name}`); 7 | await this.readOKAY(); 8 | return this.parser.raw(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/logcat.ts: -------------------------------------------------------------------------------- 1 | import LineTransform from '../../linetransform'; 2 | import Command from '../../command'; 3 | 4 | export default class LogcatCommand extends Command { 5 | async execute(options: { clear?: boolean } = {}): Promise { 6 | // For some reason, LG G Flex requires a filter spec with the -B option. 7 | // It doesn't actually use it, though. Regardless of the spec we always get 8 | // all events on all devices. 9 | let cmd = 'logcat -B *:I 2>/dev/null'; 10 | if (options.clear) { 11 | cmd = `logcat -c 2>/dev/null && ${cmd}`; 12 | } 13 | this.sendCommand(`shell:echo && ${cmd}`); 14 | await this.readOKAY(); 15 | return this.parser.raw().pipe( 16 | new LineTransform({ 17 | autoDetect: true, 18 | })); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/monkey.ts: -------------------------------------------------------------------------------- 1 | import Connection from '../../connection'; 2 | import Command from '../../command'; 3 | import { Duplex } from 'stream'; 4 | import Utils from '../../utils'; 5 | import { DeviceClientOptions } from '../../../models/DeviceClientOptions'; 6 | 7 | export default class MonkeyCommand extends Command { 8 | constructor(connection: Connection, private timeout = 1000, options?: Partial) { 9 | super(connection, options); 10 | } 11 | 12 | async execute(port: number): Promise { 13 | // Some devices have broken /sdcard (i.e. /mnt/sdcard), which monkey will 14 | // attempt to use to write log files to. We can cheat and set the location 15 | // with an environment variable, because most logs use 16 | // Environment.getLegacyExternalStorageDirectory() like they should. There 17 | // are some hardcoded logs, though. Anyway, this should enable most things. 18 | // Check https://github.com/android/platform_frameworks_base/blob/master/ 19 | // core/java/android/os/Environment.java for the variables. 20 | this.sendCommand(`shell:EXTERNAL_STORAGE=/data/local/tmp monkey --port ${port} -v`); 21 | await this.readOKAY(); 22 | // The monkey command is a bit weird in that it doesn't look like 23 | // it starts in daemon mode, but it actually does. So even though 24 | // the command leaves the terminal "hanging", Ctrl-C (or just 25 | // ending the connection) will not end the daemon. HOWEVER, on 26 | // some devices, such as SO-02C by Sony, it is required to leave 27 | // the command hanging around. In any case, if the command exits 28 | // by itself, it means that something went wrong. 29 | // On some devices (such as F-08D by Fujitsu), the monkey 30 | // command gives no output no matter how many verbose flags you 31 | // give it. So we use a fallback timeout. 32 | const pTimeout = Utils.delay(this.timeout) 33 | const parse = this.parser.searchLine(/^:Monkey:/); 34 | await Promise.race([pTimeout, parse]) 35 | return this.parser.raw(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/reboot.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export type RebootType = 'bootloader' | 'recovery' | 'sideload' | 'fastboot' | 'sideload-auto-reboot'; 4 | 5 | export default class RebootCommand extends Command { 6 | async execute(type?: RebootType): Promise { 7 | if (type) 8 | await this._send(`reboot:${type}`); 9 | else 10 | await this._send('reboot:'); 11 | await this.readOKAY(); 12 | await this.parser.readAll(); 13 | return true; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/remount.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class RemountCommand extends Command { 4 | async execute(): Promise { 5 | await this._send('remount:'); 6 | await this.readOKAY(); 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/reverse.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class ReverseCommand extends Command { 4 | async execute(remote: string, local: string): Promise { 5 | await this._send(`reverse:forward:${remote};${local}`); 6 | await this.readOKAY(); 7 | await this.readOKAY(); 8 | return true; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/root.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | const RE_OK = /restarting adbd as root/; 4 | 5 | export default class RootCommand extends Command { 6 | async execute(): Promise { 7 | await this._send('root:'); 8 | await this.readOKAY(); 9 | const value = await this.parser.readAll(); 10 | if (RE_OK.test(value.toString())) { 11 | return true; 12 | } else { 13 | throw new Error(value.toString().trim()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/screencap.ts: -------------------------------------------------------------------------------- 1 | import LineTransform from '../../linetransform'; 2 | import { AdbPrematureEOFError } from '../../errors'; 3 | import Command from '../../command'; 4 | import { Duplex } from 'stream'; 5 | 6 | export default class ScreencapCommand extends Command { 7 | async execute(): Promise { 8 | this.sendCommand('shell:echo && screencap -p 2>/dev/null'); 9 | await this.readOKAY(); 10 | let transform = new LineTransform(); 11 | try { 12 | const chunk = await this.parser.readBytes(1); 13 | transform = new LineTransform({ autoDetect: true }); 14 | transform.write(chunk); 15 | return this.parser.raw().pipe(transform); 16 | } catch (err) { 17 | if (err instanceof AdbPrematureEOFError) { 18 | throw Error('No support for the screencap command'); 19 | } 20 | throw err; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/serviceCheck.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import { KnownServices } from './servicesList'; 3 | 4 | export default class ServiceCheckCommand extends Command { 5 | async execute(serviceName: KnownServices | string): Promise { 6 | this.sendCommand(`exec:service check ${serviceName} 2>/dev/null`); 7 | await this.readOKAY(); 8 | const data = await this.parser.readAll() 9 | return this._parse(data.toString()); 10 | } 11 | 12 | private _parse(value: string): boolean { 13 | if (value.includes('not found')) 14 | return false; 15 | if (value.includes('found')) 16 | return true; 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/shell.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import { Duplex } from 'stream'; 3 | import WithToString from '../../../models/WithToString'; 4 | 5 | export default class ShellCommand extends Command { 6 | async execute(command: string | ArrayLike): Promise { 7 | if (Array.isArray(command)) { 8 | command = command.map(this.escape).join(' '); 9 | } 10 | await this._send(`shell:${command}`); 11 | await this.readOKAY(); 12 | return this.parser.raw(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/startservice.ts: -------------------------------------------------------------------------------- 1 | import StartActivityCommand from './startactivity'; 2 | import StartServiceOptions from '../../../models/StartServiceOptions'; 3 | 4 | export default class StartServiceCommand extends StartActivityCommand { 5 | execute(options: StartServiceOptions): Promise { 6 | const args = this._intentArgs(options); 7 | if (options.user || options.user === 0) { 8 | args.push('--user', this.escape(options.user)); 9 | } 10 | return this._run('startservice', args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/sync.ts: -------------------------------------------------------------------------------- 1 | import Sync from '../../sync'; 2 | import Command from '../../command'; 3 | 4 | export default class SyncCommand extends Command { 5 | async execute(): Promise { 6 | await this._send('sync:'); 7 | await this.readOKAY(); 8 | return new Sync(this.connection); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/tcp.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import { Duplex } from 'stream'; 3 | 4 | export default class TcpCommand extends Command { 5 | async execute(port: number, host?: string): Promise { 6 | await this._send(`tcp:${port}` + (host ? `:${host}` : '')); 7 | await this.readOKAY(); 8 | return this.parser.raw(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/tcpip.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | const RE_OK = /restarting in/; 4 | 5 | export default class TcpIpCommand extends Command { 6 | async execute(port: number): Promise { 7 | await this._send(`tcpip:${port}`); 8 | await this.readOKAY(); 9 | const value = await this.parser.readAll() 10 | if (RE_OK.test(value.toString())) { 11 | return port; 12 | } else { 13 | throw new Error(value.toString().trim()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/trackjdwp.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import JdwpTracker from '../../jdwptracker'; 3 | 4 | export default class TrackJdwpCommand extends Command { 5 | async execute(): Promise { 6 | await this._send('track-jdwp'); 7 | await this.readOKAY(); 8 | return new JdwpTracker(this); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/uninstall.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export interface UninstallCommandOptions { 4 | /** 5 | * keep the data and cache directories around after package removal. 6 | */ 7 | keep?: boolean; 8 | /** 9 | * remove the app from the given user. 10 | */ 11 | user?: string | number; 12 | /** 13 | * only uninstall if the app has the given version code. 14 | */ 15 | versionCode?: string; 16 | } 17 | 18 | class UninstallError extends Error { 19 | constructor(message: string) { 20 | super(message); 21 | } 22 | } 23 | 24 | export default class UninstallCommand extends Command { 25 | async execute(pkg: string, opts?: UninstallCommandOptions): Promise { 26 | let cmd = 'shell:pm uninstall'; 27 | if (opts) { 28 | if (opts.keep) cmd += ' -k'; 29 | if (opts.user) cmd += ` --user ${opts.user}`; 30 | if (opts.versionCode) cmd += ` --versionCode ${opts.versionCode}`; 31 | } 32 | cmd += ` ${pkg}` 33 | this.sendCommand(cmd); 34 | await this.readOKAY(); 35 | try { 36 | const match = await this.parser.searchLine(/^(Success|Failure.*|.*Unknown package:.*)$/); 37 | if (match[1] === 'Success') { 38 | return true; 39 | } else if (match[1].includes('DELETE_FAILED_DEVICE_POLICY_MANAGER')) { 40 | // @see https://github.com/DeviceFarmer/adbkit/pull/513 41 | const reason = match[1]; 42 | throw new UninstallError(`${pkg} could not be uninstalled [${reason}]`); 43 | } else { 44 | // Either way, the package was uninstalled or doesn't exist, 45 | // which is good enough for us. 46 | return true; 47 | } 48 | } finally { 49 | this.parser.readAll(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/usb.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | const RE_OK = /restarting in/; 4 | 5 | export default class UsbCommand extends Command { 6 | async execute(): Promise { 7 | await this._send('usb:'); 8 | await this.readOKAY(); 9 | const value = await this.parser.readAll(); 10 | if (RE_OK.test(value.toString())) { 11 | return true; 12 | } else { 13 | throw new Error(value.toString().trim()); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/adb/command/host-transport/waitbootcomplete.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class WaitBootCompleteCommand extends Command { 4 | async execute(): Promise { 5 | this.sendCommand('shell:while getprop sys.boot_completed 2>/dev/null; do sleep 1; done'); 6 | await this.readOKAY(); 7 | try { 8 | await this.parser.searchLine(/^1$/); 9 | } finally { 10 | this.parser.end() 11 | } 12 | return true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/adb/command/host/HostConnectCommand.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | // Possible replies: 4 | // "unable to connect to 192.168.2.2:5555" 5 | // "connected to 192.168.2.2:5555" 6 | // "already connected to 192.168.2.2:5555" 7 | const RE_OK = /connected to|already connected/; 8 | 9 | export default class HostConnectCommand extends Command { 10 | async execute(host: string, port: number): Promise { 11 | await this._send(`host:connect:${host}:${port}`); 12 | await this.readOKAY(); 13 | const value = await this.parser.readValue('utf8'); 14 | if (RE_OK.test(value)) { 15 | if (value.includes("already connected")) 16 | return false; 17 | else 18 | return true; 19 | } else { 20 | throw new Error(value); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/adb/command/host/HostDevicesCommand.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Device, { DeviceType } from '../../../models/Device'; 3 | import DeviceClient from '../../DeviceClient'; 4 | 5 | export default class HostDevicesCommand extends Command { 6 | async execute(): Promise { 7 | await this._send('host:devices'); 8 | await this.readOKAY(); 9 | return this._readDevices(); 10 | } 11 | 12 | // copyed to HostTrackDevicesCommand.ts 13 | public async _readDevices(): Promise { 14 | const value = await this.parser.readValue('ascii'); 15 | return this._parseDevices(value); 16 | } 17 | 18 | _parseDevices(value: string): Device[] { 19 | return value 20 | .split('\n') 21 | .filter((e) => e) 22 | .map((line: string) => { 23 | const [id, type] = line.split(/\s+/); 24 | return { 25 | id, 26 | type: type as DeviceType, 27 | getClient: () => new DeviceClient(this.connection.parent, id), 28 | }; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/adb/command/host/HostDevicesWithPathsCommand.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import DeviceWithPath from '../../../models/DeviceWithPath'; 3 | import DeviceClient from '../../DeviceClient'; 4 | import { isDeviceType } from '../../../models/Device'; 5 | 6 | 7 | export default class HostDevicesWithPathsCommand extends Command { 8 | async execute(): Promise { 9 | await this._send('host:devices-l'); 10 | await this.readOKAY(); 11 | return this._readDevices(); 12 | } 13 | 14 | 15 | public async _readDevices(): Promise { 16 | const value = await this.parser.readValue('ascii'); 17 | return this._parseDevices(value); 18 | } 19 | 20 | private _parseDevices(value: string): DeviceWithPath[] { 21 | const regexp = /^([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+product:([^\s]+)\s+model:([^\s]+)\s+device:([^\s]+)\s+transport_id:([^\s]+)$/gm; 22 | const devices: DeviceWithPath[] = []; 23 | let match; 24 | while ((match = regexp.exec(value)) !== null) { 25 | const [, id, type, path, product, model, device, transportId] = match; 26 | if (!isDeviceType(type)) { 27 | continue; 28 | } 29 | devices.push({ 30 | id, 31 | type, 32 | path, 33 | product, 34 | model, 35 | device, 36 | transportId, 37 | getClient: () => new DeviceClient(this.connection.parent, id), 38 | }) 39 | } 40 | return devices; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/adb/command/host/HostDisconnectCommand.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | // Possible replies: 4 | // "No such device 192.168.2.2:5555" 5 | // "" 6 | // "disconnected 192.168.2.2:5555" 7 | 8 | const RE_OK = /^$/; 9 | const RE_DISC = /^disconnected.*$/ 10 | 11 | export default class HostDisconnectCommand extends Command { 12 | async execute(host: string, port: number): Promise { 13 | await this._send(`host:disconnect:${host}:${port}`); 14 | await this.readOKAY(); 15 | const value = await this.parser.readValue("ascii"); 16 | if (RE_OK.test(value) || RE_DISC.test(value)) { 17 | return `${host}:${port}`; 18 | } else { 19 | throw new Error(value); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/adb/command/host/HostKillCommand.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class HostKillCommand extends Command { 4 | async execute(): Promise { 5 | await this._send('host:kill'); 6 | await this.readOKAY(); 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/adb/command/host/HostTrackDevicesCommand.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Device, { DeviceType } from '../../../models/Device'; 3 | import DeviceClient from '../../DeviceClient'; 4 | import Tracker from '../../tracker'; 5 | 6 | export default class HostTrackDevicesCommand extends Command { 7 | async execute(): Promise { 8 | await this._send('host:track-devices'); 9 | await this.readOKAY(); 10 | return new Tracker(this); 11 | } 12 | 13 | // copy from HostDevicesCommand.ts 14 | public async _readDevices(): Promise { 15 | const value = await this.parser.readValue('ascii'); 16 | return this._parseDevices(value); 17 | } 18 | 19 | _parseDevices(value: string): Device[] { 20 | return value 21 | .split('\n') 22 | .filter((e) => e) 23 | .map((line: string) => { 24 | const [id, type] = line.split(/\s+/); 25 | return { 26 | id, 27 | type: type as DeviceType, 28 | getClient: () => new DeviceClient(this.connection.parent, id), 29 | }; 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/adb/command/host/HostTransportCommand.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | 3 | export default class HostTransportCommand extends Command { 4 | async execute(serial: string): Promise { 5 | await this._send(`host:transport:${serial}`); 6 | await this.readOKAY(); 7 | return true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/adb/command/host/HostVersionCommand.ts: -------------------------------------------------------------------------------- 1 | import Command from '../../command'; 2 | import Protocol from '../../protocol'; 3 | 4 | export default class HostVersionCommand extends Command { 5 | async execute(): Promise { 6 | await this._send('host:version'); 7 | const reply = await this.parser.readAscii(4); 8 | switch (reply) { 9 | case Protocol.OKAY: 10 | const value = await this.parser.readValue('utf8'); 11 | return this._parseVersion(value); 12 | case Protocol.FAIL: 13 | throw await this.parser.readError(); 14 | default: 15 | return this._parseVersion(reply); 16 | } 17 | } 18 | 19 | _parseVersion(version: string): number { 20 | return parseInt(version, 16); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/adb/command/host/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HostConnectCommand } from './HostConnectCommand'; 2 | export { default as HostDevicesCommand } from './HostDevicesCommand'; 3 | export { default as HostDevicesWithPathsCommand } from './HostDevicesWithPathsCommand'; 4 | export { default as HostDisconnectCommand } from './HostDisconnectCommand'; 5 | export { default as HostKillCommand } from './HostKillCommand'; 6 | export { default as HostTrackDevicesCommand } from './HostTrackDevicesCommand'; 7 | export { default as HostTransportCommand } from './HostTransportCommand'; 8 | export { default as HostVersionCommand } from './HostVersionCommand'; 9 | -------------------------------------------------------------------------------- /src/adb/dump.ts: -------------------------------------------------------------------------------- 1 | import 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 = (chunk: Buffer): Buffer => { 8 | out.write(chunk); 9 | return chunk; 10 | }; 11 | } else { 12 | func = (chunk: Buffer): Buffer => chunk; 13 | } 14 | 15 | export default func; 16 | -------------------------------------------------------------------------------- /src/adb/errors.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * AdbError is the common parent class for all ADB Exceptions: 4 | * AdbFailError AdbPrematureEOFError and AdbUnexpectedDataError 5 | */ 6 | export class AdbError extends Error { 7 | constructor(message: string, public lastMessage: string) { 8 | super(message + ' lastMessage:' + lastMessage); 9 | } 10 | } 11 | 12 | /** 13 | * a command call respond an Error 14 | */ 15 | export class AdbFailError extends AdbError { 16 | constructor(message: string, lastMessage: string) { 17 | super(`Failure: '${message}'`, lastMessage); 18 | Object.setPrototypeOf(this, AdbFailError.prototype); 19 | this.name = 'AdbFailError'; 20 | Error.captureStackTrace(this, AdbFailError); 21 | } 22 | } 23 | 24 | /** 25 | * the connection get interupt before the end of expected response 26 | */ 27 | export class AdbPrematureEOFError extends AdbError { 28 | constructor(public missingBytes: number, lastMessage: string) { 29 | super(`Premature end of stream, needed ${missingBytes} more bytes`, lastMessage); 30 | Object.setPrototypeOf(this, AdbPrematureEOFError.prototype); 31 | this.name = 'AdbPrematureEOFError'; 32 | Error.captureStackTrace(this, AdbPrematureEOFError); 33 | } 34 | } 35 | 36 | export class AdbUnexpectedDataError extends AdbError { 37 | constructor(public unexpected: string, public expected: string, lastMessage: string) { 38 | super(`Unexpected '${unexpected}', was expecting ${expected}`, lastMessage); 39 | Object.setPrototypeOf(this, AdbUnexpectedDataError.prototype); 40 | this.name = 'AdbUnexpectedDataError'; 41 | Error.captureStackTrace(this, AdbUnexpectedDataError); 42 | } 43 | } -------------------------------------------------------------------------------- /src/adb/framebuffer/rgbtransform.ts: -------------------------------------------------------------------------------- 1 | import Assert from 'assert'; 2 | import { Stream, TransformCallback, TransformOptions } from 'stream'; 3 | import FramebufferMeta from '../../models/FramebufferMeta'; 4 | 5 | export default 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 | -------------------------------------------------------------------------------- /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/protocol.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * adb Protocol is a 4 byte prefixed message. 3 | * the 4 fisrt byte can be on of the 10 predefined Code, of a 4-hexa string indicating the message len. 4 | */ 5 | export default class Protocol { 6 | public static OKAY = 'OKAY'; 7 | public static FAIL = 'FAIL'; 8 | public static STAT = 'STAT'; 9 | public static STA2 = 'STA2'; 10 | public static LIST = 'LIST'; 11 | public static LIS2 = 'LIS2'; 12 | public static DENT = 'DENT'; 13 | public static DNT2 = 'DNT2'; 14 | public static RECV = 'RECV'; 15 | public static DATA = 'DATA'; 16 | public static DONE = 'DONE'; 17 | public static SEND = 'SEND'; 18 | public static QUIT = 'QUIT'; 19 | 20 | public static bOKAY = Buffer.from(Protocol.OKAY); 21 | public static bFAIL = Buffer.from(Protocol.FAIL); 22 | public static bSTAT = Buffer.from(Protocol.STAT); 23 | public static bSTA2 = Buffer.from(Protocol.STA2); 24 | public static bLIST = Buffer.from(Protocol.LIST); 25 | public static bLIS2 = Buffer.from(Protocol.LIS2); 26 | public static bDENT = Buffer.from(Protocol.DENT); 27 | public static bRECV = Buffer.from(Protocol.RECV); 28 | public static bDATA = Buffer.from(Protocol.DATA); 29 | public static bDONE = Buffer.from(Protocol.DONE); 30 | public static bSEND = Buffer.from(Protocol.SEND); 31 | public static bQUIT = Buffer.from(Protocol.QUIT); 32 | 33 | /** 34 | * parse a 4 char string 35 | */ 36 | static decodeLength(length: string): number { 37 | return parseInt(length, 16); 38 | } 39 | /** 40 | * 41 | * @param length message len 42 | * @returns message len as a 4 char string 43 | */ 44 | static encodeLength(length: number): string { 45 | return length.toString(16).padStart(4, '0').toUpperCase(); 46 | } 47 | /** 48 | * prefix a chunk with it's len stored in a 4 char hexa string, so data len can not exceed 0Xffff 49 | * @param data string or buffer to send. 50 | * @returns data as a Buffer prefixed by a 4 char Base16 length chunk 51 | */ 52 | static encodeData(data: Buffer | string): Buffer { 53 | if (!Buffer.isBuffer(data)) { 54 | data = Buffer.from(data); 55 | } 56 | const len = Protocol.encodeLength(data.length); 57 | return Buffer.concat([Buffer.from(len), data]); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/adb/sync/entry.ts: -------------------------------------------------------------------------------- 1 | import Stats from './stats'; 2 | 3 | export default class Entry extends Stats { 4 | constructor(public name: string, mode: number, size: number, mtime: number) { 5 | super(mode, size, mtime); 6 | } 7 | 8 | public toString(): string { 9 | return this.name; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/adb/sync/entry64.ts: -------------------------------------------------------------------------------- 1 | import Stats64 from './stats64'; 2 | 3 | export default class Entry64 extends Stats64 { 4 | constructor(public name: string, error: number, dev: bigint, ino: bigint, 5 | mode: bigint, nlink: bigint, uid: bigint, gid: bigint, size: bigint, 6 | atimeNs: bigint, mtimeNs: bigint, ctimeNs: bigint) { 7 | super(error, dev, ino, mode, nlink, uid, gid, size, atimeNs, mtimeNs, ctimeNs); 8 | } 9 | 10 | public toString(): string { 11 | let out = ''; 12 | 13 | if (this.isDirectory()) { 14 | out += 'd'; 15 | } else if (this.isSymbolicLink()) { 16 | out += 'l'; 17 | } else if (this.isBlockDevice()) { 18 | out += 'd'; 19 | } else if (this.isFile()) { 20 | out += '-'; 21 | } else if (this.isCharacterDevice()) { 22 | out += 'c'; 23 | } else if (this.isFIFO()) { 24 | out += 'p'; 25 | } else if (this.isSocket()) { 26 | out += 's'; 27 | } else { 28 | out += '?'; 29 | } 30 | const mode = Number(this.mode); 31 | out += (mode & 256) ? 'r':'-' 32 | out += (mode & 128) ? 'w':'-' 33 | out += (mode & 64) ? 'x':'-' 34 | out += (mode & 32) ? 'r':'-' 35 | out += (mode & 16) ? 'w':'-' 36 | out += (mode & 8) ? 'x':'-' 37 | out += (mode & 4) ? 'r':'-' 38 | out += (mode & 2) ? 'w':'-' 39 | out += (mode & 1) ? 'x':'-' 40 | out += ' ' 41 | out += this.gid.toString().padStart(5, ' '); 42 | out += ' ' 43 | out += this.uid.toString().padStart(5, ' '); 44 | out += ' ' 45 | out += this.size.toString().padStart(10, ' '); 46 | out += ' '; 47 | out += this.ctime.toISOString().substring(0, 10); 48 | out += ' '; 49 | out += this.name; 50 | out += ' '; 51 | out = out.padEnd(60, ' '); 52 | return out; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/adb/sync/pulltransfer.ts: -------------------------------------------------------------------------------- 1 | import { Stream } from 'stream'; 2 | 3 | /** 4 | * `PullTransfer` is a [`Stream`][node-stream]. Use [`fs.createWriteStream()`][node-fs] to pipe the stream to a file if necessary. 5 | */ 6 | export default class PullTransfer extends Stream.PassThrough { 7 | public stats = { 8 | bytesTransferred: 0, 9 | }; 10 | 11 | /** 12 | * Cancels the transfer by ending the connection. Can be useful for reading endless streams of data, such as `/dev/urandom` or `/dev/zero`, perhaps for benchmarking use. Note that you must create a new sync connection if you wish to continue using the sync service. 13 | * @returns The pullTransfer instance. 14 | */ 15 | public cancel(): boolean { 16 | return this.emit('cancel'); 17 | } 18 | 19 | write( 20 | chunk: Buffer, 21 | encoding?: BufferEncoding | typeof callback, 22 | callback?: (error: Error | null | undefined) => void, 23 | ): boolean { 24 | this.stats.bytesTransferred += chunk.length; 25 | this.emit('progress', this.stats); 26 | if (typeof encoding === 'function') { 27 | return super.write(chunk, encoding); 28 | } 29 | return super.write(chunk, encoding, callback); 30 | } 31 | 32 | promiseWrite( 33 | chunk: Buffer, 34 | encoding?: BufferEncoding 35 | ): Promise { 36 | this.stats.bytesTransferred += chunk.length; 37 | this.emit('progress', this.stats); 38 | return new Promise((accept, reject) => { 39 | super.write(chunk, encoding, (err) => { 40 | if (err) reject(err); 41 | else accept(); 42 | }); 43 | }) 44 | } 45 | 46 | private waitForEndPromise?: Promise; 47 | /** 48 | * get end notification using Promise 49 | */ 50 | public waitForEnd(): Promise { 51 | if (!this.waitForEndPromise) { 52 | if (this.closed) { 53 | this.waitForEndPromise = Promise.resolve(); 54 | } else { 55 | this.waitForEndPromise = new Promise((resolve, reject) => { 56 | const unReg = () => { 57 | this.off('end', onEnd); 58 | this.off('error', onError); 59 | } 60 | const onError = (e: Error) => { unReg(); reject(e); }; 61 | const onEnd = () => (unReg(), resolve()); 62 | this.on('end', onEnd); 63 | this.on('error', onError); 64 | }) 65 | } 66 | } 67 | return this.waitForEndPromise; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/adb/sync/pushtransfer.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'events'; 2 | 3 | /** 4 | * enforce EventEmitter typing 5 | */ 6 | interface IEmissions { 7 | /** 8 | * Emitted on error. 9 | */ 10 | end: () => void 11 | cancel: () => void 12 | /** 13 | * **(stats)** Emitted when a chunk has been flushed to the ADB connection. 14 | * @param stats An object with the following stats about the transfer: 15 | */ 16 | progress: (stats: { /** The number of bytes transferred so far. */ bytesTransferred: number }) => void 17 | /** 18 | * Emitted when the transfer has successfully completed. 19 | */ 20 | error: (data: Error) => void 21 | } 22 | /** 23 | * A simple EventEmitter, mainly for keeping track of the progress. 24 | */ 25 | export default class PushTransfer extends EventEmitter { 26 | private stack: number[] = []; 27 | public stats = { 28 | bytesTransferred: 0, 29 | }; 30 | 31 | public on = (event: K, listener: IEmissions[K]): this => super.on(event, listener) 32 | public off = (event: K, listener: IEmissions[K]): this => super.off(event, listener) 33 | public once = (event: K, listener: IEmissions[K]): this => super.once(event, listener) 34 | public emit = (event: K, ...args: Parameters): boolean => super.emit(event, ...args) 35 | 36 | /** 37 | * Cancels the transfer by ending both the stream that is being pushed and the sync connection. This will most likely end up creating a broken file on your device. **Use at your own risk.** Also note that you must create a new sync connection if you wish to continue using the sync service. 38 | * @returns The pushTransfer instance. 39 | */ 40 | public cancel(): boolean { 41 | return this.emit('cancel'); 42 | } 43 | 44 | public push(byteCount: number): number { 45 | return this.stack.push(byteCount); 46 | } 47 | 48 | public pop(): boolean { 49 | const byteCount = this.stack.pop(); 50 | if (byteCount) { 51 | this.stats.bytesTransferred += byteCount; 52 | } 53 | return this.emit('progress', this.stats); 54 | } 55 | 56 | private done = false; 57 | public end(): boolean { 58 | this.done = true; 59 | return this.emit('end'); 60 | } 61 | 62 | private waitForEndPromise?: Promise; 63 | /** 64 | * get end notification using Promise 65 | */ 66 | public waitForEnd(): Promise { 67 | if (!this.waitForEndPromise) { 68 | if (this.done) { 69 | this.waitForEndPromise = Promise.resolve(); 70 | } else { 71 | this.waitForEndPromise = new Promise((resolve, reject) => { 72 | const unReg = () => { 73 | this.off('end', onEnd); 74 | this.off('error', onError); 75 | } 76 | const onError = (e: Error) => { unReg(); reject(e); }; 77 | const onEnd = () => (unReg(), resolve()); 78 | this.on('end', onEnd); 79 | this.on('error', onError); 80 | }) 81 | } 82 | } 83 | return this.waitForEndPromise; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/adb/sync/stats.ts: -------------------------------------------------------------------------------- 1 | import 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 | constructor(mode: number, size: number, mtime: number) { 40 | super(); 41 | this.mode = mode; 42 | this.size = size; 43 | this.mtime = new Date(mtime * 1000); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/adb/sync/stats64.ts: -------------------------------------------------------------------------------- 1 | import Stats from './stats'; 2 | import fs from 'fs'; 3 | 4 | const b1k = BigInt(1000); 5 | 6 | export default class Stats64 implements fs.BigIntStats { 7 | isFile(): boolean { 8 | return !!(Number(this.mode) & Stats.S_IFREG); 9 | } 10 | isDirectory(): boolean { 11 | return !!(Number(this.mode) & Stats.S_IFDIR); 12 | } 13 | isBlockDevice(): boolean { 14 | return !!(Number(this.mode) & Stats.S_IFBLK); 15 | } 16 | isCharacterDevice(): boolean { 17 | return !!(Number(this.mode) & Stats.S_IFCHR); 18 | } 19 | isSymbolicLink(): boolean { 20 | return !!(Number(this.mode) & Stats.S_IFLNK); 21 | } 22 | isFIFO(): boolean { 23 | return !!(Number(this.mode) & Stats.S_IFIFO); 24 | } 25 | isSocket(): boolean { 26 | return !!(Number(this.mode) & Stats.S_IFSOCK); 27 | } 28 | 29 | get atimeMs(): bigint { 30 | return this.atimeNs / b1k; 31 | } 32 | get mtimeMs(): bigint { 33 | return this.mtimeNs / b1k; 34 | } 35 | get ctimeMs(): bigint { 36 | return this.ctimeNs / b1k; 37 | } 38 | get birthtimeMs(): bigint { 39 | return this.birthtimeMs / b1k; 40 | } 41 | 42 | rdev = BigInt(0); 43 | blksize = BigInt(0); 44 | blocks = BigInt(0); 45 | 46 | get atime(): Date { 47 | return new Date(Number(this.atimeMs)) 48 | } 49 | 50 | get mtime(): Date { 51 | return new Date(Number(this.mtimeMs)) 52 | } 53 | 54 | get ctime(): Date { 55 | return new Date(Number(this.ctimeMs)) 56 | } 57 | 58 | get birthtimeNs(): bigint { 59 | return this.ctimeNs; 60 | } 61 | get birthtime(): Date { 62 | return this.ctime; 63 | } 64 | 65 | constructor( 66 | public readonly error: number, 67 | public readonly dev: bigint, 68 | public readonly ino: bigint, 69 | public readonly mode: bigint, 70 | public readonly nlink: bigint, 71 | public readonly uid: bigint, 72 | public readonly gid: bigint, 73 | public readonly size: bigint, 74 | public readonly atimeNs: bigint, 75 | public readonly mtimeNs: bigint, 76 | public readonly ctimeNs: bigint) { } 77 | } 78 | -------------------------------------------------------------------------------- /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 | // see https://github.com/DeviceFarmer/adbkit/issues/42 67 | return this.check === 0 || this.check === Packet.checksum(this.data); 68 | } 69 | 70 | public verifyMagic(): boolean { 71 | return this.magic === Packet.magic(this.command); 72 | } 73 | 74 | private getType(): string { 75 | switch (this.command) { 76 | case Packet.A_SYNC: 77 | return 'SYNC'; 78 | case Packet.A_CNXN: 79 | return 'CNXN'; 80 | case Packet.A_OPEN: 81 | return 'OPEN'; 82 | case Packet.A_OKAY: 83 | return 'OKAY'; 84 | case Packet.A_CLSE: 85 | return 'CLSE'; 86 | case Packet.A_WRTE: 87 | return 'WRTE'; 88 | case Packet.A_AUTH: 89 | return 'AUTH'; 90 | default: 91 | throw new Error('Unknown command {@command}'); 92 | } 93 | } 94 | 95 | toString(): string { 96 | const type = this.getType(); 97 | return `${type} arg0=${this.arg0} arg1=${this.arg1} length=${this.length}`; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /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 Net from 'net'; 2 | import Socket from './socket'; 3 | import EventEmitter from 'events'; 4 | import Client from '../client'; 5 | import SocketOptions from '../../models/SocketOptions'; 6 | 7 | /** 8 | * enforce EventEmitter typing 9 | */ 10 | interface IEmissions { 11 | listening: () => void 12 | close: () => void 13 | connection: (socket: Socket) => void 14 | error: (data: Error) => void 15 | } 16 | 17 | export default class Server extends EventEmitter { 18 | private readonly server: Net.Server; 19 | private connections: Socket[] = []; 20 | 21 | constructor( 22 | private readonly client: Client, 23 | private readonly serial: string, 24 | private readonly options: SocketOptions, 25 | ) { 26 | super(); 27 | this.server = Net.createServer({ 28 | allowHalfOpen: true, 29 | }); 30 | this.server.on('error', (err) => this.emit('error', err)); 31 | this.server.on('listening', () => this.emit('listening')); 32 | this.server.on('close', () => this.emit('close')); 33 | this.server.on('connection', (conn) => { 34 | const socket = new Socket(this.client, this.serial, conn, this.options); 35 | this.connections.push(socket); 36 | socket.on('error', (err) => { 37 | // 'conn' is guaranteed to get ended 38 | return this.emit('error', err); 39 | }); 40 | socket.once('end', () => { 41 | // 'conn' is guaranteed to get ended 42 | return (this.connections = this.connections.filter((val) => val !== socket)); 43 | }); 44 | return this.emit('connection', socket); 45 | }); 46 | } 47 | 48 | public on = (event: K, listener: IEmissions[K]): this => super.on(event, listener) 49 | public off = (event: K, listener: IEmissions[K]): this => super.off(event, listener) 50 | public once = (event: K, listener: IEmissions[K]): this => super.once(event, listener) 51 | public emit = (event: K, ...args: Parameters): boolean => super.emit(event, ...args) 52 | 53 | public listen(...args: Parameters): Server { 54 | this.server.listen(...args); 55 | return this; 56 | } 57 | 58 | public close(): Server { 59 | this.server.close(); 60 | return this; 61 | } 62 | 63 | public end(): Server { 64 | const ref = this.connections; 65 | for (let i = 0, len = ref.length; i < len; i++) { 66 | ref[i].end(); 67 | } 68 | return this; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /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/thirdparty/ThirdUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * utils only used by third party 3 | */ 4 | 5 | import PromiseDuplex from 'promise-duplex'; 6 | import { Duplex } from 'stream'; 7 | import { Utils } from '../..'; 8 | import path from 'path'; 9 | import DeviceClient from '../DeviceClient'; 10 | 11 | export default class ThirdUtils { 12 | /** 13 | * use to debug external apk output 14 | * @param duplex process IO 15 | * @param name for display only 16 | * @returns resolve on stream closed 17 | */ 18 | static async dumpReadable(duplex: PromiseDuplex, name: string): Promise { 19 | try { 20 | const prefix = name + ':'; 21 | for (; ;) { 22 | await Utils.waitforReadable(duplex); 23 | const data = await duplex.read(); 24 | if (data) { 25 | const msg = data.toString(); 26 | console.log(prefix, msg.trim()); 27 | } 28 | } 29 | } catch (e) { 30 | // End 31 | return; 32 | } 33 | } 34 | 35 | static get resourceDir() { 36 | return path.join(__dirname, '..', '..', '..', 'bin'); 37 | } 38 | 39 | /** 40 | * get fullpath from a ressource file. 41 | * @param fileName filename 42 | * @returns full path within the ressource folder 43 | */ 44 | static getResourcePath(fileName: string): string { 45 | const fullPath = path.join(ThirdUtils.resourceDir, fileName); 46 | return fullPath; 47 | } 48 | 49 | static async getScreenSize(client: DeviceClient): Promise<{x: number, y: number}>{ 50 | const str = await client.execOut('wm size', 'utf8'); 51 | const m = str.match(/(\d+)x(\d+)/); 52 | if (!m) throw Error('can not get device size info'); 53 | return {x: Number(m[1]), y: Number(m[2])} 54 | } 55 | } -------------------------------------------------------------------------------- /src/adb/thirdparty/minicap/BufWrite.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * help writing message 3 | */ 4 | export class BufWrite { 5 | public buffer: Buffer; 6 | public pos = 0; 7 | 8 | constructor(len: number) { 9 | this.buffer = Buffer.allocUnsafe(len); 10 | } 11 | 12 | writeBigUint64BE(val: bigint) { 13 | this.buffer.writeBigUint64BE(val, this.pos); 14 | this.pos += 8; 15 | } 16 | 17 | writeUint32BE(val: number) { 18 | this.buffer.writeUint32BE(val, this.pos); 19 | this.pos += 4; 20 | } 21 | writeInt32BE(val: number) { 22 | this.buffer.writeInt32BE(val, this.pos); 23 | this.pos += 4; 24 | } 25 | 26 | writeUint16BE(val: number) { 27 | this.buffer.writeUint16BE(val, this.pos); 28 | this.pos += 2; 29 | } 30 | 31 | writeInt16BE(val: number) { 32 | this.buffer.writeInt16BE(val, this.pos); 33 | this.pos += 2; 34 | } 35 | 36 | writeUint8(val: number) { 37 | this.buffer.writeUint8(val, this.pos); 38 | this.pos += 1; 39 | } 40 | 41 | writeString(text: string) { 42 | const textData = Buffer.from(text, 'utf8'); 43 | this.writeUint32BE(textData.length); 44 | this.append(textData); 45 | this.pos += textData.length; 46 | } 47 | 48 | writeInt8(val: number) { 49 | this.buffer.writeInt8(val, this.pos); 50 | this.pos += 1; 51 | } 52 | 53 | append(buf: Buffer) { 54 | this.buffer = Buffer.concat([this.buffer, buf], this.buffer.length + buf.length); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/adb/thirdparty/scrcpy/ScrcpyConst.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Action from android.view.MotionEvent 4 | // https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/view/MotionEvent.java 5 | export const enum MotionEvent { 6 | ACTION_DOWN = 0, 7 | ACTION_UP = 1, 8 | ACTION_MOVE = 2, 9 | ACTION_CANCEL = 3, 10 | ACTION_OUTSIDE = 4, 11 | ACTION_POINTER_DOWN = 5, 12 | ACTION_POINTER_UP = 6, 13 | ACTION_HOVER_MOVE = 7, 14 | ACTION_SCROLL = 8, 15 | ACTION_HOVER_ENTER = 9, 16 | ACTION_HOVER_EXIT = 10, 17 | ACTION_BUTTON_PRESS = 11, 18 | ACTION_BUTTON_RELEASE = 12, 19 | 20 | BUTTON_PRIMARY = 1 << 0, 21 | BUTTON_SECONDARY = 1 << 1, 22 | BUTTON_TERTIARY = 1 << 2, 23 | BUTTON_BACK = 1 << 3, 24 | BUTTON_FORWARD = 1 << 4, 25 | BUTTON_STYLUS_PRIMARY = 1 << 5, 26 | BUTTON_STYLUS_SECONDARY = 1 << 6, 27 | } 28 | 29 | // fro m DeviceMessage.java 30 | export const enum DeviceMessageType { 31 | TYPE_CLIPBOARD = 0, 32 | } 33 | 34 | // Screen power mode from Device.java 35 | export const enum SurfaceControl { 36 | POWER_MODE_OFF = 0, 37 | POWER_MODE_NORMAL = 2, 38 | } 39 | 40 | // types from Device.java 41 | export const enum Orientation { 42 | LOCK_VIDEO_ORIENTATION_UNLOCKED = -1, 43 | LOCK_VIDEO_ORIENTATION_INITIAL = -2, 44 | // from android source 45 | LOCK_SCREEN_ORIENTATION_0 = 0, 46 | LOCK_SCREEN_ORIENTATION_1 = 1, 47 | LOCK_SCREEN_ORIENTATION_2 = 2, 48 | LOCK_SCREEN_ORIENTATION_3 = 3, 49 | } 50 | 51 | // Lock screen orientation 52 | export const enum ControlMessage { 53 | TYPE_INJECT_KEYCODE = 0, 54 | TYPE_INJECT_TEXT = 1, 55 | TYPE_INJECT_TOUCH_EVENT = 2, 56 | TYPE_INJECT_SCROLL_EVENT = 3, 57 | TYPE_BACK_OR_SCREEN_ON = 4, 58 | TYPE_EXPAND_NOTIFICATION_PANEL = 5, 59 | TYPE_EXPAND_SETTINGS_PANEL = 6, 60 | TYPE_COLLAPSE_PANELS = 7, 61 | TYPE_GET_CLIPBOARD = 8, 62 | TYPE_SET_CLIPBOARD = 9, 63 | TYPE_SET_SCREEN_POWER_MODE = 10, 64 | TYPE_ROTATE_DEVICE = 11, 65 | } 66 | 67 | 68 | export const enum KeyEventMeta { 69 | META_CTRL_LEFT_ON = 0x00002000, 70 | META_CTRL_ON = 0x00007000, 71 | META_META_MASK = 0x00070000, 72 | META_CAPS_LOCK_ON = 0x00100000, 73 | META_CTRL_RIGHT_ON = 0x00004000, 74 | META_META_LEFT_ON = 0x00020000, 75 | // .... 76 | } 77 | -------------------------------------------------------------------------------- /src/adb/thirdparty/scrcpy/ScrcpyModels.ts: -------------------------------------------------------------------------------- 1 | export interface Point { 2 | x: number; 3 | y: number; 4 | } 5 | 6 | export interface ScrcpyOptions { 7 | version: 20 | 24; 8 | /** 9 | * maxSize (integer, multiple of 8) 0 10 | * Max width 11 | */ 12 | maxSize: number, 13 | /** 14 | * maximum fps, 0 means not limited (supported after android 10) 15 | */ 16 | maxFps: number; 17 | /** 18 | * flip the video 19 | */ 20 | flip: boolean; 21 | /** 22 | * 23 | */ 24 | bitrate: number, 25 | /** 26 | * lock screen orientation, LOCK_VIDEO_ORIENTATION_* 27 | */ 28 | lockedVideoOrientation: number; 29 | 30 | /** 31 | * use "adb forward" instead of "adb tunnel" 32 | */ 33 | tunnelForward: boolean, 34 | tunnelDelay: number, 35 | /** 36 | * "width:height:x:y" or '-' 37 | * Crop the device screen on the server. 38 | * The values are expressed in the device natural orientation (typically, 39 | * portrait for a phone, landscape for a tablet). Any --max-size value is 40 | * cmoputed on the cropped size. 41 | */ 42 | crop: string, 43 | sendFrameMeta: boolean, 44 | /** 45 | * set Control added in scrcpy 1.9 46 | */ 47 | control: boolean; 48 | displayId: number, 49 | showTouches: boolean; 50 | stayAwake: boolean; 51 | codecOptions: string; 52 | encoderName: 'OMX.qcom.video.encoder.avc' | 'c2.android.avc.encoder' | 'OMX.google.h264.encoder' | string; 53 | powerOffScreenOnClose: boolean; 54 | /** 55 | * since scrcpy 1.21 56 | */ 57 | clipboardAutosync?: boolean; 58 | /** 59 | * since scrcpy 1.22 60 | */ 61 | downsizeOnError?: boolean; 62 | sendDeviceMeta?: boolean; 63 | sendDummyByte?: boolean; 64 | rawVideoStream?: boolean; 65 | /** 66 | * since scrcpy 1.23 67 | */ 68 | cleanup?: boolean; 69 | } 70 | 71 | export interface H264Configuration { 72 | profileIndex: number; 73 | constraintSet: number; 74 | levelIndex: number; 75 | 76 | encodedWidth: number; 77 | encodedHeight: number; 78 | 79 | cropLeft: number; 80 | cropRight: number; 81 | 82 | cropTop: number; 83 | cropBottom: number; 84 | 85 | croppedWidth: number; 86 | croppedHeight: number; 87 | 88 | data: Uint8Array; 89 | } 90 | 91 | 92 | export interface VideoStreamFramePacket { 93 | // type: 'frame'; 94 | keyframe?: boolean | undefined; 95 | pts?: bigint | undefined; 96 | data: Uint8Array; 97 | config?: H264Configuration; 98 | } 99 | -------------------------------------------------------------------------------- /src/cli-airplane.ts: -------------------------------------------------------------------------------- 1 | import { program } from 'commander'; 2 | import DeviceClient from './adb/DeviceClient'; 3 | import Utils from './adb/utils'; 4 | import { getClientDevice } from './cli-common'; 5 | 6 | program 7 | .command('airplane-mode-off [serials...]') 8 | .description('Disable airplane.') 9 | .action(async (serials: string[]) => { 10 | const devices = await getClientDevice(serials); 11 | const process = async (device: DeviceClient) => { 12 | if (await device.extra.airPlainMode(false)) { 13 | console.log(`[${device.serial}] airplane disabled`); 14 | await Utils.delay(100) 15 | await device.extra.back(); 16 | } else { 17 | console.log(`[${device.serial}] airplane or already enabled`); 18 | } 19 | } 20 | await Promise.all(devices.map(process)); 21 | }); 22 | 23 | program 24 | .command('airplane-mode-on-off [serials...]') 25 | .description('Enable then Disable airplane.') 26 | .action(async (serials: string[]) => { 27 | const devices = await getClientDevice(serials); 28 | const process = async (device: DeviceClient) => { 29 | if (await device.extra.airPlainMode(false, 200)) { 30 | console.log(`[${device.serial}] airplane on-off`); 31 | await Utils.delay(100) 32 | await device.extra.back(); 33 | } else { 34 | console.log(`[${device.serial}] airplane or already enabled`); 35 | } 36 | } 37 | await Promise.all(devices.map(process)); 38 | }); 39 | 40 | program 41 | .command('airplane-mode-on [serials...]') 42 | .description('Enable airplane.') 43 | .action(async (serials: string[]) => { 44 | const devices = await getClientDevice(serials); 45 | const process = async (device: DeviceClient) => { 46 | if (await device.extra.airPlainMode(true)) { 47 | console.log(`[${device.serial}] airplane enabled`); 48 | await Utils.delay(100) 49 | await device.extra.back(); 50 | } else { 51 | console.log(`[${device.serial}] airplane or already enabled`); 52 | } 53 | } 54 | await Promise.all(devices.map(process)); 55 | }); 56 | 57 | -------------------------------------------------------------------------------- /src/cli-boatware.ts: -------------------------------------------------------------------------------- 1 | import { program } from 'commander'; 2 | import DeviceClient from './adb/DeviceClient'; 3 | import { getClientDevice } from './cli-common'; 4 | import { stdin, stdout } from 'node:process'; 5 | import readlinePromises from 'node:readline/promises'; 6 | 7 | const BixbySet = new Set(['com.samsung.android.app.spage', 'com.samsung.android.app.routines', 'com.samsung.android.visionintelligence']); 8 | 9 | program 10 | .command(`boatware [serials...]`) 11 | .description('remove common boatware.') 12 | .action(async (serials: string[]) => { 13 | 14 | const rl = readlinePromises.createInterface({ 15 | input: stdin, 16 | output: stdout 17 | }); 18 | 19 | const devices = await getClientDevice(serials); 20 | const process = async (device: DeviceClient) => { 21 | const pkgs = await device.listPackages(); 22 | console.log(`${pkgs.length} Packages availible`); 23 | // const samsung = pkgs.filter(a => a.name.startsWith('com.samsung')) 24 | // samsung.forEach(a => console.log(a.name)); 25 | const bixby = pkgs.filter(a => a.name.includes('bixby') || BixbySet.has(a.name)) 26 | if (bixby.length) { 27 | console.log(`${bixby.length} bixby Packages`); 28 | for (const pkg of bixby) { 29 | console.log(`- ${pkg.name}`) 30 | } 31 | const r = await rl.question(`do you want to remove them ? [y/N]`); 32 | if (r.toLowerCase() == 'y' || r.toLowerCase() == 'yes') { 33 | for (const p of bixby) { 34 | console.log(`uninstalling ${p.name}`) 35 | const r = await device.uninstall(p.name, {keep: true, user: 0}); 36 | console.log(r ? 'Success': 'Failed') 37 | } 38 | } else { 39 | console.log(`response: "${r}", Skip`); 40 | } 41 | } 42 | } 43 | for (const device of devices) { 44 | await process(device); 45 | } 46 | rl.close(); 47 | }); -------------------------------------------------------------------------------- /src/cli-common.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from './adb'; 2 | import Device from './models/Device'; 3 | import DeviceClient from './adb/DeviceClient'; 4 | 5 | export async function getClientDevice(serials: string[]): Promise { 6 | const adb = createClient(); 7 | if (!serials || !serials.length) { 8 | const devices: Device[] = await adb.listDevices(); 9 | if (devices.length == 0) { 10 | console.error('no Android devices found'); 11 | process.exit(1); 12 | return []; 13 | } 14 | if (devices.length == 1) { 15 | return [devices[0].getClient()]; 16 | } 17 | console.error('Multiple devices avaliable provide a serial number to choose one'); 18 | console.error(devices.map(d => `- ${d.id}`).join('\n')); 19 | process.exit(1); 20 | } 21 | if (serials.length === 1 && (serials[0] === 'all' || serials[0] === '*')) { 22 | serials = (await adb.listDevices()).map((d: Device) => d.id); 23 | } 24 | return serials.map(serial => adb.getDevice(serial)) 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/cli-connectivity.ts: -------------------------------------------------------------------------------- 1 | import { program } from 'commander'; 2 | import DeviceClient from './adb/DeviceClient'; 3 | import { getClientDevice } from './cli-common'; 4 | 5 | for (const type of ['bluetooth', 'data', 'wifi'] as const) { 6 | program 7 | .command(`${type}-off [serials...]`) 8 | .description('Disable bluetooth.') 9 | .action(async (serials: string[]) => { 10 | const devices = await getClientDevice(serials); 11 | const process = async (device: DeviceClient) => { 12 | if (await device.extra.setSvc(type, false)) { 13 | console.log(`[${device.serial}] ${type} disabled`); 14 | } else { 15 | console.log(`[${device.serial}] failed ${type} already disabled`); 16 | } 17 | } 18 | await Promise.all(devices.map(process)); 19 | }); 20 | 21 | program 22 | .command(`${type}-on [serials...]`) 23 | .description('Enable bluetooth.') 24 | .action(async (serials: string[]) => { 25 | const devices = await getClientDevice(serials); 26 | const process = async (device: DeviceClient) => { 27 | if (await device.extra.setSvc(type, true)) { 28 | console.log(`[${device.serial}] ${type} enabled`); 29 | } else { 30 | console.log(`[${device.serial}] failed ${type} already enabled`); 31 | } 32 | } 33 | await Promise.all(devices.map(process)); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/cli-tethering.ts: -------------------------------------------------------------------------------- 1 | import { program } from 'commander'; 2 | import DeviceClient from './adb/DeviceClient'; 3 | import Utils from './adb/utils'; 4 | import { getClientDevice } from './cli-common'; 5 | 6 | program 7 | .command('usb-tethering-off [serials...]') 8 | .description('Disable USB tethering.') 9 | .action(async (serials: string[]) => { 10 | const devices = await getClientDevice(serials); 11 | const process = async (device: DeviceClient) => { 12 | if (await device.extra.usbTethering(false)) { 13 | console.log(`[${device.serial}] tethering enabled`); 14 | await Utils.delay(100) 15 | await device.extra.back(); 16 | } else { 17 | console.log(`[${device.serial}] failed or already enabled`); 18 | } 19 | } 20 | await Promise.all(devices.map(process)); 21 | }); 22 | 23 | program 24 | .command('usb-tethering-on [serials...]') 25 | .description('Enable USB tethering.') 26 | .action(async (serials: string[]) => { 27 | const devices = await getClientDevice(serials); 28 | const process = async (device: DeviceClient) => { 29 | if (await device.extra.usbTethering(true)) { 30 | console.log(`[${device.serial}] tethering enabled`); 31 | await Utils.delay(100) 32 | await device.extra.back(); 33 | } else { 34 | console.log(`[${device.serial}] failed or already enabled`); 35 | } 36 | } 37 | await Promise.all(devices.map(process)); 38 | }); 39 | 40 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { program } from 'commander'; 3 | import forge from 'node-forge'; 4 | import { createClient } from './adb'; 5 | import Auth from './adb/auth'; 6 | import PacketReader from './adb/tcpusb/packetreader'; 7 | import path from 'path'; 8 | import Utils from './adb/utils'; 9 | import { getClientDevice } from './cli-common'; 10 | 11 | import './cli-airplane'; 12 | import './cli-connectivity'; 13 | import './cli-tethering'; 14 | import './cli-boatware' 15 | 16 | const pkg: { version: string } = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), { encoding: 'utf-8' })); 17 | 18 | program.version(pkg.version); 19 | 20 | program 21 | .command('pubkey-convert ') 22 | .option('-f, --format ', 'format (pem or openssh)', String, 'pem') 23 | .description('Converts an ADB-generated public key into PEM format.') 24 | .action(async (file, options): Promise => { 25 | const key = await Auth.parsePublicKey(fs.readFileSync(file).toString('utf8')); 26 | switch (options.format.toLowerCase()) { 27 | case 'pem': 28 | return console.log(forge.pki.publicKeyToPem(key).trim()); 29 | case 'openssh': 30 | return console.log(forge.ssh.publicKeyToOpenSSH(key, 'adbkey').trim()); 31 | default: 32 | console.error("Unsupported format '" + options.format + "'"); 33 | return process.exit(1); 34 | } 35 | }); 36 | 37 | program 38 | .command('pubkey-fingerprint ') 39 | .description('Outputs the fingerprint of an ADB-generated public key.') 40 | .action(async (file: string) => { 41 | const key = await Auth.parsePublicKey(fs.readFileSync(file).toString('utf8')); 42 | return console.log('%s %s', key.fingerprint, key.comment); 43 | }); 44 | 45 | program 46 | .command('usb-device-to-tcp ') 47 | .option('-p, --port ', 'port number', (value: string) => String(value), '6174') 48 | .description('Provides an USB device over TCP using a translating proxy.') 49 | .action((serial: string, options: { port: string }) => { 50 | const adb = createClient(); 51 | const server = adb 52 | .createTcpUsbBridge(serial, { 53 | auth: () => Promise.resolve(), 54 | }) 55 | .on('listening', () => console.info('Connect with `adb connect localhost:%d`', options.port)) 56 | .on('error', (err) => console.error('An error occured: ' + err.message)); 57 | server.listen(options.port); 58 | }); 59 | 60 | program 61 | .command('parse-tcp-packets ') 62 | .description('Parses ADB TCP packets from the given file.') 63 | .action((file: string) => { 64 | const reader = new PacketReader(fs.createReadStream(file)); 65 | reader.on('packet', (packet) => console.log(packet.toString())); 66 | }); 67 | 68 | 69 | program 70 | .command('capture dest [serials...]') 71 | .description('capture screen of one or many device as png.') 72 | .action(async (dest: string, serials: string[]) => { 73 | const devices = await getClientDevice(serials); 74 | for (let i = 0; i < devices.length; i++) { 75 | const device = devices[i]; 76 | let out = dest; 77 | if (devices.length > 1) { 78 | out = path.join(path.dirname(out), device.serial + '-' + path.basename(out)); 79 | } 80 | const stream = await device.screencap(); 81 | const capture = await Utils.readAll(stream); 82 | fs.writeFileSync(dest, capture); 83 | } 84 | }); 85 | 86 | program.parse(process.argv); 87 | -------------------------------------------------------------------------------- /src/models/ClientOptions.ts: -------------------------------------------------------------------------------- 1 | import { TcpNetConnectOpts } from 'net'; 2 | 3 | export interface ClientOptions extends TcpNetConnectOpts { 4 | /** 5 | * As the sole exception, this option provides the path to the `adb` binary, used for starting the server locally if initial connection fails. Defaults to `'adb'`. 6 | */ 7 | bin?: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/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/models/Device.ts: -------------------------------------------------------------------------------- 1 | import { DeviceClient } from ".."; 2 | 3 | const deviceTypes = ['emulator', 'device', 'offline', 'unauthorized', 'recovery'] as const; 4 | /** 5 | * adb device starts 6 | */ 7 | export type DeviceType = typeof deviceTypes[number]; 8 | export function isDeviceType(value: string): value is DeviceType { 9 | return deviceTypes.includes(value as DeviceType); 10 | } 11 | 12 | export default interface Device { 13 | /** 14 | * The ID of the device. For real devices, this is usually the USB identifier. 15 | */ 16 | id: string; 17 | /** 18 | * The device type. Values include `'emulator'` for emulators, `'device'` for devices, and `'offline'` for offline devices. `'offline'` can occur for example during boot, in low-battery conditions or when the ADB connection has not yet been approved on the device. 19 | */ 20 | type: DeviceType; 21 | /** 22 | * return a DeviceClient attached to this devices 23 | */ 24 | getClient: () => DeviceClient; 25 | } 26 | -------------------------------------------------------------------------------- /src/models/DeviceClientOptions.ts: -------------------------------------------------------------------------------- 1 | export interface DeviceClientOptions { 2 | sudo: boolean; 3 | } 4 | -------------------------------------------------------------------------------- /src/models/DeviceWithPath.ts: -------------------------------------------------------------------------------- 1 | import Device from './Device'; 2 | 3 | export default interface DeviceWithPath extends Device { 4 | /** 5 | * The device path. This can be something like `usb:FD120000` for real devices. 6 | */ 7 | path: string; 8 | /** 9 | * The product name of the device 10 | */ 11 | product: string; 12 | /** 13 | * The model of the device 14 | */ 15 | model: string; 16 | /** 17 | * The device 18 | */ 19 | device: string; 20 | /** 21 | * The transport id for the device 22 | */ 23 | transportId: string; 24 | } 25 | -------------------------------------------------------------------------------- /src/models/ExtendedPublicKey.ts: -------------------------------------------------------------------------------- 1 | import { pki } from 'node-forge'; 2 | import PublicKey = pki.rsa.PublicKey; 3 | 4 | export default interface ExtendedPublicKey extends PublicKey { 5 | /** 6 | * The key fingerprint, like it would display on a device. Note that this is different than the one you'd get from `forge.ssh.getPublicKeyFingerprint(key)`, because the device fingerprint is based on the original format. 7 | */ 8 | fingerprint: string; 9 | /** 10 | * The key comment, if any. 11 | */ 12 | comment: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/models/Features.ts: -------------------------------------------------------------------------------- 1 | export type Features = Record; 2 | -------------------------------------------------------------------------------- /src/models/Forward.ts: -------------------------------------------------------------------------------- 1 | export default interface Forward { 2 | serial: string; 3 | local: string; 4 | remote: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/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/models/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/models/Properties.ts: -------------------------------------------------------------------------------- 1 | export type Properties = Record; 2 | -------------------------------------------------------------------------------- /src/models/Reverse.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An array of reverse objects 3 | */ 4 | export default interface Reverse { 5 | /** 6 | * The remote endpoint on the device. Same format as `client.reverse()`'s `remote` argument. 7 | */ 8 | remote: string; 9 | /** 10 | * The local endpoint on the host. Same format as `client.reverse()`'s `local` argument. 11 | */ 12 | local: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/models/SocketOptions.ts: -------------------------------------------------------------------------------- 1 | import ExtendedPublicKey from './ExtendedPublicKey'; 2 | 3 | export default interface SocketOptions { 4 | auth?: (key: ExtendedPublicKey) => Promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/models/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/models/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, 29 | * an attempt will be made to run the same command again without the user option. 30 | */ 31 | user?: number; 32 | /** 33 | * The action. (the -a parameter) 34 | */ 35 | action?: string; 36 | /** 37 | * The data URI, if any. (the -d parameter) 38 | */ 39 | data?: string; 40 | /** 41 | * The mime type, if any. (the -rt parameter) 42 | */ 43 | mimeType?: string; 44 | /** 45 | * The category. For multiple categories, pass an `Array`. (the -c parameter) 46 | */ 47 | category?: string | string[]; 48 | /** 49 | * The component. (the -n parameter) 50 | */ 51 | component?: string; 52 | /** 53 | * Numeric flags. (the -f parameter) 54 | */ 55 | flags?: number | number[]; 56 | /** 57 | * Any extra data. (the --e parameter) 58 | */ 59 | extras?: Extra[] | ExtraObject; 60 | /** 61 | * args to append at the end 62 | */ 63 | args?: string | string[]; 64 | } 65 | -------------------------------------------------------------------------------- /src/models/TrackerChangeSet.ts: -------------------------------------------------------------------------------- 1 | import Device from './Device'; 2 | 3 | /** 4 | * An object with the following properties always present: 5 | */ 6 | export default interface TrackerChangeSet { 7 | /** 8 | * An array of removed device objects, each one as in the `remove` event. Empty if none. 9 | */ 10 | removed: Device[]; 11 | /** 12 | * An array of changed device objects, each one as in the `change` event. Empty if none. 13 | */ 14 | changed: Device[]; 15 | /** 16 | * An array of added device objects, each one as in the `add` event. Empty if none. 17 | */ 18 | added: Device[]; 19 | } 20 | -------------------------------------------------------------------------------- /src/models/WithToString.ts: -------------------------------------------------------------------------------- 1 | export default interface WithToString { 2 | toString: () => string; 3 | } 4 | -------------------------------------------------------------------------------- /tasks/GitSource.ts: -------------------------------------------------------------------------------- 1 | import { exec, ExecOptions, ExecException } from 'child_process'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import { ChildProcess } from 'child_process'; 5 | 6 | function execPromise(command: string, options: { encoding: BufferEncoding } & ExecOptions): Promise<{ stdout: string, stderr: string, exitCode: number }> { 7 | return new Promise((resolve, reject) => { 8 | const cp: ChildProcess = exec(command, options, (error: ExecException, stdout: string, stderr: string) => { 9 | if (error) 10 | return reject(error); 11 | resolve({ stdout, stderr, exitCode: cp.exitCode }); 12 | }) 13 | }) 14 | } 15 | 16 | export default class GitSource { 17 | workDir: string; 18 | gitDir: string; 19 | constructor(public name: string, public url: string) { 20 | this.workDir = path.resolve(path.join(__dirname, '..', '..')); 21 | this.gitDir = path.resolve(path.join(this.workDir, name)); 22 | } 23 | 24 | async clone() { 25 | if (!fs.existsSync(this.gitDir)) { 26 | console.log('cloning git...'); 27 | await execPromise(`git clone ${this.url} ${this.gitDir}`, { encoding: 'utf8', cwd: this.workDir }); 28 | console.log('clone done'); 29 | } 30 | } 31 | 32 | async update() { 33 | console.log('git pull'); 34 | let log = await execPromise(`git pull`, { encoding: 'utf8', cwd: this.gitDir }); 35 | console.log('pull Done'); 36 | console.log(log); 37 | 38 | console.log('git fetch origin'); 39 | log = await execPromise(`git fetch origin`, { encoding: 'utf8', cwd: this.gitDir }); 40 | console.log('Done'); 41 | console.log(log); 42 | } 43 | 44 | async listBranch(): Promise { 45 | console.log(`listing git branches`); 46 | const log = await execPromise(`git branch -r --list --no-color`, { encoding: 'utf8', cwd: this.gitDir }); 47 | let branches = log.stdout.split(/[\r\n ]+/g); 48 | branches = branches.filter(a=>a.startsWith('origin/')).map(a=> a.replace('origin/', '')); 49 | return branches; 50 | } 51 | 52 | async listTag(): Promise { 53 | console.log(`listing git tag`); 54 | const log = await execPromise(`git tag`, { encoding: 'utf8', cwd: this.gitDir }); 55 | let tags = log.stdout.split(/[\r\n ]+/g); 56 | tags = tags.filter(a=>a.match(/android-(\d+\.){1,2}\d+_r\d+/)) 57 | // .map(a=> a.replace('origin/', '')); 58 | return tags; 59 | } 60 | 61 | async checkoutTag(tag: string): Promise { 62 | console.log(`switch to tag ${tag}`); 63 | const log = await execPromise(`git checkout tags/${tag}`, { encoding: 'utf8', cwd: this.gitDir }); 64 | console.log(log); 65 | } 66 | 67 | async listAidl(tag: string): Promise { 68 | console.log(`switch to tag ${tag}`); 69 | const log = await execPromise(`git checkout tags/${tag}`, { encoding: 'utf8', cwd: this.gitDir }); 70 | console.log(log); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /tasks/keycode.ts: -------------------------------------------------------------------------------- 1 | import https from 'https'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import { EOL } from 'os'; 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, (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: RegExpExecArray; 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 | req.end(); 46 | -------------------------------------------------------------------------------- /test/adb.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Adb, { Utils } from '../'; 3 | // import Client from '../src/adb/client'; 4 | // import { Keycode } from '../src/adb/keycode'; 5 | 6 | describe('Adb', () => { 7 | //it('should expose Keycode', (done) => { 8 | // expect(Adb).to.have.property('Keycode'); 9 | // expect(Adb.Keycode).to.equal(Keycode); 10 | // done(); 11 | //}); 12 | it('should expose utisl', (done) => { 13 | expect(Adb).to.have.property('util'); 14 | expect(Adb.util).to.equal(Utils); 15 | done(); 16 | }); 17 | // return describe('@createClient(options)', () => { 18 | // it('should return a Client instance', (done) => { 19 | // expect(Adb.createClient()).to.be.an.instanceOf(Client); 20 | // done(); 21 | // }); 22 | // }); 23 | }); 24 | -------------------------------------------------------------------------------- /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 | import Connection from '../../../../src/adb/connection'; 8 | 9 | describe('WaitForDeviceCommand', () => { 10 | it("should send 'host-serial::wait-for-any-device'", () => { 11 | const conn = new MockConnection(); 12 | const cmd = new WaitForDeviceCommand(conn as any as Connection); 13 | conn.getSocket().on('write', (chunk) => { 14 | return expect(chunk.toString()).to.equal(Protocol.encodeData('host-serial:abba:wait-for-any-device').toString()); 15 | }); 16 | setImmediate(() => { 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | conn.getSocket().causeRead(Protocol.OKAY); 19 | return conn.getSocket().causeEnd(); 20 | }); 21 | return cmd.execute('abba'); 22 | }); 23 | it('should resolve with id when the device is connected', async () => { 24 | const conn = new MockConnection(); 25 | const cmd = new WaitForDeviceCommand(conn as any as Connection); 26 | setImmediate(() => { 27 | conn.getSocket().causeRead(Protocol.OKAY); 28 | conn.getSocket().causeRead(Protocol.OKAY); 29 | return conn.getSocket().causeEnd(); 30 | }); 31 | const id = await cmd.execute('abba') 32 | expect(id).to.equal('abba'); 33 | return true; 34 | }); 35 | it('should reject with error if unable to connect', async () => { 36 | const conn = new MockConnection(); 37 | const cmd = new WaitForDeviceCommand(conn as any as Connection); 38 | setImmediate(() => { 39 | conn.getSocket().causeRead(Protocol.OKAY); 40 | conn.getSocket().causeRead(Protocol.FAIL); 41 | conn.getSocket().causeRead(Protocol.encodeData('not sure how this might happen')); 42 | return conn.getSocket().causeEnd(); 43 | }); 44 | try { 45 | return await cmd.execute('abba'); 46 | } catch (err) { 47 | expect((err as Error).message).to.contain('not sure how this might happen'); 48 | } 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /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 | import Tester from './Tester'; 8 | const t = new Tester(ClearCommand); 9 | 10 | describe('ClearCommand', () => { 11 | it("should send 'pm clear '", () => { 12 | const conn = new MockConnection(); 13 | const cmd = new ClearCommand(conn); 14 | conn.getSocket().on('write', (chunk) => { 15 | expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm clear foo.bar.c').toString()); 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead('Success\r\n'); 18 | conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute('foo.bar.c'); 21 | }); 22 | it("should succeed on 'Success'", () => { 23 | const conn = new MockConnection(); 24 | const cmd = new ClearCommand(conn); 25 | conn.getSocket().on('write', (chunk) => { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead('Success\r\n'); 28 | conn.getSocket().causeEnd(); 29 | }); 30 | return cmd.execute('foo.bar.c'); 31 | }); 32 | it("should error on 'Failed'", (done) => { 33 | const conn = new MockConnection(); 34 | const cmd = new ClearCommand(conn); 35 | conn.getSocket().on('write', (chunk) => { 36 | conn.getSocket().causeRead(Protocol.OKAY); 37 | conn.getSocket().causeRead('Failed\r\n'); 38 | conn.getSocket().causeEnd(); 39 | }); 40 | cmd.execute('foo.bar.c').catch((err) => { 41 | expect(err).to.be.an.instanceof(Error); 42 | done(); 43 | }); 44 | }); 45 | it("should error on 'Failed' even if connection not closed by device", (done) => { 46 | const conn = new MockConnection(); 47 | const cmd = new ClearCommand(conn); 48 | conn.getSocket().on('write', (chunk) => { 49 | conn.getSocket().causeRead(Protocol.OKAY); 50 | conn.getSocket().causeRead('Failed\r\n'); 51 | }); 52 | cmd.execute('foo.bar.c').catch((err) => { 53 | expect(err).to.be.an.instanceof(Error); 54 | done(); 55 | }); 56 | }); 57 | it('should ignore irrelevant lines', async () => { 58 | const result = await t.testPr(['Open: foo error\n\n', 'Success\r\n'], 'foo.bar.c') 59 | return expect(result).to.be.eq(true); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /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', () => { 9 | it("should send 'framebuffer:'", () => { 10 | const conn = new MockConnection(); 11 | const cmd = new FrameBufferCommand(conn); 12 | conn.getSocket().on('write', (chunk) => { 13 | expect(chunk.toString()).to.equal(Protocol.encodeData('framebuffer:').toString()); 14 | }); 15 | setImmediate(() => { 16 | const meta = Buffer.alloc(52); 17 | meta.fill(0); 18 | conn.getSocket().causeRead(Protocol.OKAY); 19 | conn.getSocket().causeRead(meta); 20 | conn.getSocket().causeEnd(); 21 | }); 22 | return cmd.execute('raw'); 23 | }); 24 | it("should parse meta header and return it as the 'meta' property of the stream", async () => { 25 | const conn = new MockConnection(); 26 | const cmd = new FrameBufferCommand(conn); 27 | conn.getSocket().on('write', (chunk) => { 28 | return expect(chunk.toString()).to.equal(Protocol.encodeData('framebuffer:').toString()); 29 | }); 30 | setImmediate(() => { 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 | conn.getSocket().causeEnd(); 49 | }); 50 | const stream = await cmd.execute('raw'); 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 | -------------------------------------------------------------------------------- /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', () => { 9 | it("should send 'pm list features'", () => { 10 | const conn = new MockConnection(); 11 | const cmd = new GetFeaturesCommand(conn); 12 | conn.getSocket().on('write', (chunk) => { 13 | return expect(chunk.toString()).to.equal( 14 | Protocol.encodeData('shell:pm list features 2>/dev/null').toString(), 15 | ); 16 | }); 17 | setImmediate(() => { 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', async () => { 24 | const conn = new MockConnection(); 25 | const cmd = new GetFeaturesCommand(conn); 26 | setImmediate(() => { 27 | conn.getSocket().causeRead(Protocol.OKAY); 28 | conn.getSocket().causeEnd(); 29 | }); 30 | const features = await cmd.execute(); 31 | expect(Object.keys(features)).to.be.empty; 32 | return true; 33 | }); 34 | it('should return a map of features', async () => { 35 | const conn = new MockConnection(); 36 | const cmd = new GetFeaturesCommand(conn); 37 | setImmediate(() => { 38 | const socket = conn.getSocket(); 39 | socket.causeRead(Protocol.OKAY); 40 | socket.causeRead(`feature:reqGlEsVersion=0x20000 41 | feature:foo\r 42 | feature:bar`); 43 | socket.causeEnd(); 44 | }); 45 | const features = await cmd.execute(); 46 | expect(Object.keys(features)).to.have.length(3); 47 | expect(features).to.eql({ 48 | reqGlEsVersion: '0x20000', 49 | foo: true, 50 | bar: true, 51 | }); 52 | return true; 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /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 GetPackagesCommand from '../../../../src/adb/command/host-transport/getpackages'; 5 | import Tester from './Tester'; 6 | const t = new Tester(GetPackagesCommand); 7 | 8 | describe('GetPackagesCommand', () => { 9 | it("should send 'pm list packages'", () => t.testTr('shell:pm list packages 2>/dev/null')); 10 | it("should send 'pm list packages' with flag", () => t.testTr('shell:pm list packages -3 2>/dev/null', '-3')); 11 | 12 | it('should return an empty array for an empty package list', async () => { 13 | const packages = await t.testTr(''); 14 | return expect(packages).to.be.empty; 15 | }); 16 | 17 | it('should return an array of packages', async () => { 18 | const packages = await t.testPr(`package:com.google.android.gm 19 | package:com.google.android.inputmethod.japanese 20 | package:com.google.android.tag\r 21 | package:com.google.android.GoogleCamera 22 | package:com.google.android.youtube 23 | package:com.google.android.apps.magazines 24 | package:com.google.earth`); 25 | expect(packages).to.have.length(7); 26 | return expect(packages).to.eql([ 27 | 'com.google.android.gm', 28 | 'com.google.android.inputmethod.japanese', 29 | 'com.google.android.tag', 30 | 'com.google.android.GoogleCamera', 31 | 'com.google.android.youtube', 32 | 'com.google.android.apps.magazines', 33 | 'com.google.earth', 34 | ]); 35 | }) 36 | }); 37 | -------------------------------------------------------------------------------- /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 GetPropertiesCommand from '../../../../src/adb/command/host-transport/getproperties'; 5 | import Tester from './Tester'; 6 | const t = new Tester(GetPropertiesCommand); 7 | 8 | describe('GetPropertiesCommand', () => { 9 | it("should send 'getprop'", () => t.testTr('shell:getprop')); 10 | it('should return an empty object for an empty property list', async () => { 11 | const properties = await t.testPr(); 12 | expect(Object.keys(properties)).to.be.empty; 13 | }); 14 | it('should return a map of properties', async () => { 15 | const properties = await t.testPr(`[ro.product.locale.region]: [US] 16 | [ro.product.manufacturer]: [samsung]\r 17 | [ro.product.model]: [SC-04E] 18 | [ro.product.name]: [SC-04E]`); 19 | expect(Object.keys(properties)).to.have.length(4); 20 | expect(properties).to.eql({ 21 | 'ro.product.locale.region': 'US', 22 | 'ro.product.manufacturer': 'samsung', 23 | 'ro.product.model': 'SC-04E', 24 | 'ro.product.name': 'SC-04E', 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /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 | import Tester from './Tester'; 8 | const t = new Tester(InstallCommand); 9 | 10 | describe('InstallCommand', () => { 11 | it("should send 'pm install -r '", () => { 12 | const conn = new MockConnection(); 13 | const cmd = new InstallCommand(conn); 14 | conn.getSocket().on('write', (chunk) => { 15 | return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm install -r "foo"').toString()); 16 | }); 17 | setImmediate(() => { 18 | conn.getSocket().causeRead(Protocol.OKAY); 19 | conn.getSocket().causeRead('Success\r\n'); 20 | return conn.getSocket().causeEnd(); 21 | }); 22 | return cmd.execute('foo'); 23 | }); 24 | it("should succeed when command responds with 'Success'", () => { 25 | const conn = new MockConnection(); 26 | const cmd = new InstallCommand(conn); 27 | setImmediate(() => { 28 | conn.getSocket().causeRead(Protocol.OKAY); 29 | conn.getSocket().causeRead('Success\r\n'); 30 | return conn.getSocket().causeEnd(); 31 | }); 32 | return cmd.execute('foo'); 33 | }); 34 | it("should reject if command responds with 'Failure [REASON]'", (done) => { 35 | const conn = new MockConnection(); 36 | const cmd = new InstallCommand(conn); 37 | setImmediate(() => { 38 | conn.getSocket().causeRead(Protocol.OKAY); 39 | conn.getSocket().causeRead('Failure [BAR]\r\n'); 40 | return conn.getSocket().causeEnd(); 41 | }); 42 | cmd.execute('foo').catch((err) => { 43 | done(); 44 | }); 45 | }); 46 | it("should give detailed reason in rejection's code property", (done) => { 47 | const conn = new MockConnection(); 48 | const cmd = new InstallCommand(conn); 49 | setImmediate(() => { 50 | conn.getSocket().causeRead(Protocol.OKAY); 51 | conn.getSocket().causeRead('Failure [ALREADY_EXISTS]\r\n'); 52 | return conn.getSocket().causeEnd(); 53 | }); 54 | cmd.execute('foo').catch((err) => { 55 | expect(err.code).to.equal('ALREADY_EXISTS'); 56 | done(); 57 | }); 58 | }); 59 | it('should ignore any other data', () => { 60 | const conn = new MockConnection(); 61 | const cmd = new InstallCommand(conn); 62 | setImmediate(() => { 63 | conn.getSocket().causeRead(Protocol.OKAY); 64 | conn.getSocket().causeRead('open: Permission failed\r\n'); 65 | conn.getSocket().causeRead('Success\r\n'); 66 | return conn.getSocket().causeEnd(); 67 | }); 68 | return cmd.execute('foo'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/ipRoute.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import { IpRouteCommand } from '../../../../src/adb/command/host-transport'; 5 | import Tester from './Tester'; 6 | 7 | const t = new Tester(IpRouteCommand); 8 | 9 | describe('ipRouteCommand', () => { 10 | it("should send 'ip route'", () => t.testTr('shell:ip route')); 11 | 12 | it("should send 'su -c p iroute'", () => t.sudo().testTr('shell:ip route')); 13 | 14 | it("should send 'ip route list table all'", () => t.testTr('shell:ip route list table all', 'list table all')); 15 | 16 | it("should send 'ip route list table all' split", () => t.testTr('shell:ip route list table all', 'list', 'table', 'all')); 17 | 18 | it('should return a list of routes', async() => { 19 | const result = await t.testPr( 20 | `192.168.1.0/24 dev wlan0 proto kernel scope link src 192.168.1.2\n`); 21 | return expect(result).to.eql( [{ dest: '192.168.1.0/24', dev: 'wlan0', proto: 'kernel', scope: 'link', src: '192.168.1.2' }]); 22 | }); 23 | 24 | it('should return convert number string to numbers', async () => { 25 | const result = await t.testPr(`fe80::/64 dev wlan0 table 1021 proto kernel metric 256 pref medium\n`); 26 | return expect(result).to.eql( [{ dest: 'fe80::/64', dev: 'wlan0', table: 1021, proto: 'kernel', metric: 256, pref: 'medium' }]); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/ipRules.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import { IpRuleCommand } from '../../../../src/adb/command/host-transport'; 5 | import Tester from './Tester'; 6 | 7 | const t = new Tester(IpRuleCommand); 8 | 9 | describe('IpRuleCommand', () => { 10 | it("should send 'ip rule'", () => t.testTr('shell:ip rule')); 11 | 12 | it("should send 'ip rule list'", () => t.testTr('shell:ip rule list', 'list')); 13 | 14 | it('should return a list of rule', async() => { 15 | const result = await t.testPr(`0: from all lookup local 16 | 10000: from all fwmark 0xc0000/0xd0000 lookup 99 17 | 10500: from all iif lo oif dummy0 uidrange 0-0 lookup 1003`); 18 | expect(result).to.have.length(3); 19 | expect(result[0].toStirng()).to.eq('0:\tfrom all lookup local'); 20 | expect(result[1].toStirng()).to.eq('10000:\tfrom all fwmark 0xc0000/0xd0000 lookup 99'); 21 | expect(result[2].toStirng()).to.eq('10500:\tfrom all iif lo oif dummy0 uidrange 0-0 lookup 1003'); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /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', () => { 10 | it("should send 'pm path '", () => { 11 | const conn = new MockConnection(); 12 | const cmd = new IsInstalledCommand(conn); 13 | conn.getSocket().on('write', (chunk) => { 14 | return expect(chunk.toString()).to.equal(Protocol.encodeData('shell:pm path foo 2>/dev/null').toString()); 15 | }); 16 | setImmediate(() => { 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', async () => { 24 | const conn = new MockConnection(); 25 | const cmd = new IsInstalledCommand(conn); 26 | setImmediate(() => { 27 | conn.getSocket().causeRead(Protocol.OKAY); 28 | conn.getSocket().causeRead('package:bar\r\n'); 29 | return conn.getSocket().causeEnd(); 30 | }); 31 | const found = await cmd.execute('foo'); 32 | expect(found).to.be.true; 33 | }); 34 | it('should resolve with false if no package returned', async () => { 35 | const conn = new MockConnection(); 36 | const cmd = new IsInstalledCommand(conn); 37 | setImmediate(() => { 38 | conn.getSocket().causeRead(Protocol.OKAY); 39 | return conn.getSocket().causeEnd(); 40 | }); 41 | const found = await cmd.execute('foo'); 42 | expect(found).to.be.false; 43 | }); 44 | it('should fail if any other data is received', (done) => { 45 | const conn = new MockConnection(); 46 | const cmd = new IsInstalledCommand(conn); 47 | setImmediate(() => { 48 | conn.getSocket().causeRead(Protocol.OKAY); 49 | conn.getSocket().causeRead('open: Permission failed\r\n'); 50 | return conn.getSocket().causeEnd(); 51 | }); 52 | cmd.execute('foo').catch((err) => { 53 | expect(err).to.be.an.instanceof(Error); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /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', () => { 10 | it("should send 'localfilesystem:'", () => { 11 | const conn = new MockConnection(); 12 | const cmd = new LocalCommand(conn); 13 | conn.getSocket().on('write', (chunk) => { 14 | return expect(chunk.toString()).to.equal(Protocol.encodeData('localfilesystem:/foo.sock').toString()); 15 | }); 16 | setImmediate(() => { 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 ':'", async () => { 23 | const conn = new MockConnection(); 24 | const cmd = new LocalCommand(conn); 25 | conn.getSocket().on('write', (chunk) => { 26 | return expect(chunk.toString()).to.equal(Protocol.encodeData('localabstract:/foo.sock').toString()); 27 | }); 28 | setImmediate(() => { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | return conn.getSocket().causeEnd(); 31 | }); 32 | const stream = await cmd.execute('localabstract:/foo.sock'); 33 | }); 34 | it('should resolve with the stream', async () => { 35 | const conn = new MockConnection(); 36 | const cmd = new LocalCommand(conn); 37 | setImmediate(() => { 38 | return conn.getSocket().causeRead(Protocol.OKAY); 39 | }); 40 | const stream = await cmd.execute('/foo.sock'); 41 | stream.end(); 42 | expect(stream).to.be.an.instanceof(Stream.Readable); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /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 | import Tester from './Tester'; 9 | const t = new Tester(LogCommand); 10 | 11 | describe('LogCommand', () => { 12 | it("should send 'log:'", async () => { 13 | await t.testTr('log:main', 'main'); 14 | // return a Duplex; 15 | return true; 16 | }); 17 | it('should resolve with the log stream', async () => { 18 | const conn = new MockConnection(); 19 | const cmd = new LogCommand(conn); 20 | setImmediate(() => { 21 | conn.getSocket().causeRead(Protocol.OKAY); 22 | }); 23 | const stream = await cmd.execute('main'); 24 | stream.end(); 25 | expect(stream).to.be.an.instanceof(Stream.Readable); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /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', () => { 11 | it("should send 'echo && logcat -B *:I'", () => { 12 | const conn = new MockConnection(); 13 | const cmd = new LogcatCommand(conn); 14 | conn.getSocket().on('write', (chunk: Buffer) => { 15 | return expect(chunk.toString()).to.equal( 16 | Protocol.encodeData('shell:echo && logcat -B *:I 2>/dev/null').toString(), 17 | ); 18 | }); 19 | setImmediate(() => { 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", () => { 26 | const conn = new MockConnection(); 27 | const cmd = new LogcatCommand(conn); 28 | conn.getSocket().on('write', (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(() => { 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', async () => { 43 | const conn = new MockConnection(); 44 | const cmd = new LogcatCommand(conn); 45 | setImmediate(() => { 46 | return conn.getSocket().causeRead(Protocol.OKAY); 47 | }); 48 | const stream = await cmd.execute(); 49 | stream.end(); 50 | expect(stream).to.be.an.instanceof(Stream.Readable); 51 | }); 52 | it('should perform CRLF transformation by default', async () => { 53 | const conn = new MockConnection(); 54 | const cmd = new LogcatCommand(conn); 55 | setImmediate(() => { 56 | conn.getSocket().causeRead(Protocol.OKAY); 57 | conn.getSocket().causeRead('\r\nfoo\r\n'); 58 | return conn.getSocket().causeEnd(); 59 | }); 60 | const stream = await cmd.execute(); 61 | const out = await new Parser(stream).readAll(); 62 | expect(out.toString()).to.equal('foo\n'); 63 | }); 64 | it('should not perform CRLF transformation if not needed', async () => { 65 | const conn = new MockConnection(); 66 | const cmd = new LogcatCommand(conn); 67 | setImmediate(() => { 68 | conn.getSocket().causeRead(Protocol.OKAY); 69 | conn.getSocket().causeRead('\nfoo\r\n'); 70 | return conn.getSocket().causeEnd(); 71 | }); 72 | const stream = await cmd.execute(); 73 | const out = await new Parser(stream).readAll(); 74 | expect(out.toString()).to.equal('foo\r\n'); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /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', () => { 10 | it("should send 'monkey --port -v'", () => { 11 | const conn = new MockConnection(); 12 | const cmd = new MonkeyCommand(conn); 13 | conn.getSocket().on('write', (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(() => { 19 | conn.getSocket().causeRead(Protocol.OKAY); 20 | conn.getSocket().causeRead(':Monkey: foo\n'); 21 | }); 22 | return cmd.execute(1080); 23 | }); 24 | it('should resolve with the output stream', async () => { 25 | const conn = new MockConnection(); 26 | const cmd = new MonkeyCommand(conn); 27 | setImmediate(() => { 28 | conn.getSocket().causeRead(Protocol.OKAY); 29 | conn.getSocket().causeRead(':Monkey: foo\n'); 30 | }); 31 | const stream = await cmd.execute(1080); 32 | stream.end(); 33 | expect(stream).to.be.an.instanceof(Stream.Readable); 34 | return true; 35 | }); 36 | it("should resolve after a timeout if result can't be judged from output", async () => { 37 | const conn = new MockConnection(); 38 | const cmd = new MonkeyCommand(conn, 10); 39 | setImmediate(() => { 40 | return conn.getSocket().causeRead(Protocol.OKAY); 41 | }); 42 | const stream = await cmd.execute(1080); 43 | stream.end(); 44 | expect(stream).to.be.an.instanceof(Stream.Readable); 45 | return true; 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/ps.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | Chai.use(simonChai); 4 | import PsCommand from '../../../../src/adb/command/host-transport/ps'; 5 | import Tester from './Tester'; 6 | const t = new Tester(PsCommand); 7 | 8 | describe('psCommand', () => { 9 | it("should send 'ps'", () => t.testTr('shell:ps')); 10 | 11 | it("should send 'ps -f'", () => t.testTr('shell:ps -f', '-f')); 12 | 13 | it('should return a list of PsEntry', async () => { 14 | const result = await t.testPr(`UID PID PPID C STIME TTY TIME CMD 15 | shell 30941 3021 0 15:35:50 pts/3 00:00:00 sh 16 | shell 31350 30941 21 16:18:23 pts/3 00:00:00 ps -f 17 | `); 18 | return expect(result).to.eql([ 19 | { UID: 'shell', PID: '30941', PPID: '3021', C: '0', STIME: '15:35:50', TTY: 'pts/3', TIME: '00:00:00', CMD: 'sh' }, 20 | { UID: 'shell', PID: '31350', PPID: '30941', C: '21', STIME: '16:18:23', TTY: 'pts/3', TIME: '00:00:00', CMD: 'ps -f' }, 21 | ]); 22 | }); 23 | 24 | it('should return a list of PsEntry -A', async () => { 25 | const result = await t.testPr(`USER PID PPID VSZ RSS WCHAN ADDR S NAME 26 | root 1 0 10826728 3728 0 0 S init 27 | root 2 0 0 0 0 0 S [kthreadd]`); 28 | return expect(result).to.eql([ 29 | { USER: 'root', PID: '1', PPID: '0', VSZ: '10826728', RSS: '3728', WCHAN: '0', ADDR: '0', S: 'S', NAME: 'init' }, 30 | { USER: 'root', PID: '2', PPID: '0', VSZ: '0', RSS: '0', WCHAN: '0', ADDR: '0', S: 'S', NAME: '[kthreadd]' }, 31 | ]) 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /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 | import Tester from './Tester'; 8 | 9 | const t = new Tester(RebootCommand); 10 | 11 | describe('RebootCommand', () => { 12 | it("should send 'reboot:'", () => t.testTr('reboot:')); 13 | 14 | it("should send 'reboot:recovery'", () => t.testTr('reboot:recovery', 'recovery')); 15 | 16 | it("should send 'reboot:sideload'", () => t.testTr('reboot:sideload', 'sideload')); 17 | 18 | it('should send wait for the connection to end', async () => { 19 | const conn = new MockConnection(); 20 | const cmd = new RebootCommand(conn); 21 | let ended = false; 22 | conn.getSocket().on('write', (chunk) => { 23 | return expect(chunk.toString()).to.equal(Protocol.encodeData('reboot:').toString()); 24 | }); 25 | setImmediate(() => { 26 | return conn.getSocket().causeRead(Protocol.OKAY); 27 | }); 28 | setImmediate(() => { 29 | ended = true; 30 | return conn.getSocket().causeEnd(); 31 | }); 32 | await cmd.execute(); 33 | expect(ended).to.be.true; 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /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', () => { 9 | it("should send 'remount:'", () => { 10 | const conn = new MockConnection(); 11 | const cmd = new RemountCommand(conn); 12 | conn.getSocket().on('write', (chunk) => { 13 | expect(chunk.toString()).to.equal(Protocol.encodeData('remount:').toString()); 14 | conn.getSocket().causeRead(Protocol.OKAY); 15 | 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 | // import Tester from './Tester'; 8 | 9 | // const t = new Tester(RootCommand); 10 | 11 | describe('RootCommand', () => { 12 | // it("should send 'root:'", () => t.testTr('root:')); 13 | it("should send 'root:'", async () => { 14 | const conn = new MockConnection(); 15 | const cmd = new RootCommand(conn); 16 | conn.getSocket().on('write', (chunk) => { 17 | expect(chunk.toString()).to.equal(Protocol.encodeData('root:').toString()); 18 | }); 19 | setImmediate(() => { 20 | conn.getSocket().causeRead(Protocol.OKAY); 21 | conn.getSocket().causeRead('restarting adbd as root\n'); 22 | conn.getSocket().causeEnd(); 23 | }); 24 | const val = await cmd.execute(); 25 | expect(val).to.be.true; 26 | }); 27 | it('should reject on unexpected reply', (done) => { 28 | const conn = new MockConnection(); 29 | const cmd = new RootCommand(conn); 30 | setImmediate(() => { 31 | conn.getSocket().causeRead(Protocol.OKAY); 32 | conn.getSocket().causeRead('adbd cannot run as root in production builds\n'); 33 | conn.getSocket().causeEnd(); 34 | }); 35 | cmd.execute().catch((err) => { 36 | expect(err.message).to.eql('adbd cannot run as root in production builds'); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /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', () => { 10 | it("should send 'screencap -p'", () => { 11 | const conn = new MockConnection(); 12 | const cmd = new ScreencapCommand(conn); 13 | conn.getSocket().on('write', (chunk) => { 14 | expect(chunk.toString()).to.equal( 15 | Protocol.encodeData('shell:echo && screencap -p 2>/dev/null').toString(), 16 | ); 17 | }); 18 | setImmediate(() => { 19 | conn.getSocket().causeRead(Protocol.OKAY); 20 | conn.getSocket().causeRead('\r\nlegit image'); 21 | conn.getSocket().causeEnd(); 22 | }); 23 | return cmd.execute() 24 | }); 25 | it('should resolve with the PNG stream', async () => { 26 | const conn = new MockConnection(); 27 | const cmd = new ScreencapCommand(conn); 28 | setImmediate(() => { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | conn.getSocket().causeRead('\r\nlegit image'); 31 | conn.getSocket().causeEnd(); 32 | }); 33 | const stream = await cmd 34 | .execute(); 35 | const out = await new Parser(stream).readAll(); 36 | expect(out.toString()).to.equal('legit image'); 37 | }); 38 | it('should reject if command not supported', (done) => { 39 | const conn = new MockConnection(); 40 | const cmd = new ScreencapCommand(conn); 41 | setImmediate(() => { 42 | conn.getSocket().causeRead(Protocol.OKAY); 43 | conn.getSocket().causeEnd(); 44 | }); 45 | cmd.execute().catch(() => { 46 | done(); 47 | }); 48 | }); 49 | it('should perform CRLF transformation by default', async () => { 50 | const conn = new MockConnection(); 51 | const cmd = new ScreencapCommand(conn); 52 | setImmediate(() => { 53 | conn.getSocket().causeRead(Protocol.OKAY); 54 | conn.getSocket().causeRead('\r\nfoo\r\n'); 55 | conn.getSocket().causeEnd(); 56 | }); 57 | const stream = await cmd 58 | .execute(); 59 | const out = await new Parser(stream).readAll(); 60 | expect(out.toString()).to.equal('foo\n'); 61 | }); 62 | it('should not perform CRLF transformation if not needed', async () => { 63 | const conn = new MockConnection(); 64 | const cmd = new ScreencapCommand(conn); 65 | setImmediate(() => { 66 | conn.getSocket().causeRead(Protocol.OKAY); 67 | conn.getSocket().causeRead('\nfoo\r\n'); 68 | conn.getSocket().causeEnd(); 69 | }); 70 | const stream = await cmd 71 | .execute(); 72 | const out = await new Parser(stream).readAll(); 73 | expect(out.toString()).to.equal('foo\r\n'); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/adb/command/host-transport/serviceCall.ts: -------------------------------------------------------------------------------- 1 | import Chai, { expect } from 'chai'; 2 | import simonChai from 'sinon-chai'; 3 | import { ServiceCallCommand, ParcelReader } from '../../../../src/'; 4 | Chai.use(simonChai); 5 | import Tester from './Tester'; 6 | const t = new Tester(ServiceCallCommand); 7 | 8 | describe('serviceCall', () => { 9 | // it("should send ''", () => t.testTr('shell:ps')); 10 | it('should parse single line Parcel', async () => { 11 | const result: ParcelReader = await t.testPr(`Result: Parcel(00000000 00000002 00340032 00000000 '........2.4.....')`); 12 | expect(result.readType()).to.eql(0); 13 | expect(result.readString()).to.eql('24'); 14 | return true; 15 | }); 16 | 17 | it('should parse multi lines Parcel', async () => { 18 | const result: ParcelReader = await t.testPr(`Result: Parcel( 19 | 0x00000000: 00000000 0000000f 00360038 00390037 '........1.3.7.9.' 20 | 0x00000010: 00380035 00350030 00330031 00370031 '5.8.0.5.8.6.1.7.' 21 | 0x00000020: 00330035 00000036 '5.3.6... ')`) ; 22 | expect(result.readType()).to.eql(0); 23 | expect(result.readString()).to.eql('867958051317536'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /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 { AdbFailError } from '../../../../src/index'; 7 | import ShellCommand from '../../../../src/adb/command/host-transport/shell'; 8 | 9 | describe('ShellCommand', () => { 10 | it('should pass String commands as-is', () => { 11 | const conn = new MockConnection(); 12 | const cmd = new ShellCommand(conn); 13 | conn.getSocket().on('write', (chunk) => { 14 | expect(chunk.toString()).to.equal(Protocol.encodeData("shell:foo 'bar").toString()); 15 | }); 16 | setImmediate(() => { 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute("foo 'bar"); 21 | }); 22 | it('should escape Array commands', () => { 23 | const conn = new MockConnection(); 24 | const cmd = new ShellCommand(conn); 25 | conn.getSocket().on('write', (chunk) => { 26 | expect(chunk.toString()).to.equal(Protocol.encodeData(`shell:'foo' ''"'"'bar'"'"'' '"'`).toString()); 27 | }); 28 | setImmediate(() => { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | conn.getSocket().causeEnd(); 31 | }); 32 | return cmd.execute(['foo', "'bar'", '"']); 33 | }); 34 | it('should not escape numbers in arguments', () => { 35 | const conn = new MockConnection(); 36 | const cmd = new ShellCommand(conn); 37 | conn.getSocket().on('write', (chunk) => { 38 | expect(chunk.toString()).to.equal(Protocol.encodeData(`shell:'foo' 67`).toString()); 39 | }); 40 | setImmediate(() => { 41 | conn.getSocket().causeRead(Protocol.OKAY); 42 | conn.getSocket().causeEnd(); 43 | }); 44 | return cmd.execute(['foo', 67]); 45 | }); 46 | it('should reject with FailError on ADB failure (not command failure)', async () => { 47 | const conn = new MockConnection(); 48 | const cmd = new ShellCommand(conn); 49 | conn.getSocket().on('write', (chunk) => { 50 | expect(chunk.toString()).to.equal(Protocol.encodeData(`shell:'foo'`).toString()); 51 | }); 52 | setImmediate(() => { 53 | conn.getSocket().causeRead(Protocol.FAIL); 54 | conn.getSocket().causeRead(Protocol.encodeData('mystery')); 55 | conn.getSocket().causeEnd(); 56 | }); 57 | try { 58 | await cmd.execute(['foo']); 59 | throw Error('should throw AdbFailError'); 60 | } catch(err) { 61 | expect(err).to.be.instanceOf(AdbFailError); 62 | expect(Object.prototype.toString.call(err)).to.be.eq('[object Error]') 63 | } 64 | return true; 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /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', () => { 9 | it("should succeed when 'Success' returned", () => { 10 | const conn = new MockConnection(); 11 | const cmd = new StartServiceCommand(conn); 12 | setImmediate(() => { 13 | conn.getSocket().causeRead(Protocol.OKAY); 14 | conn.getSocket().causeRead('Success'); 15 | 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", (done) => { 23 | const conn = new MockConnection(); 24 | const cmd = new StartServiceCommand(conn); 25 | setImmediate(() => { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead('Error: foo\n'); 28 | conn.getSocket().causeEnd(); 29 | }); 30 | const options = { 31 | component: 'com.dummy.component/.Main', 32 | }; 33 | cmd.execute(options).catch((err) => { 34 | expect(err).to.be.be.an.instanceOf(Error); 35 | done(); 36 | }); 37 | }); 38 | it("should send 'am startservice --user 0 -n '", () => { 39 | const conn = new MockConnection(); 40 | const cmd = new StartServiceCommand(conn); 41 | conn.getSocket().on('write', (chunk) => { 42 | expect(chunk.toString()).to.equal( 43 | Protocol.encodeData("shell:am startservice -n 'com.dummy.component/.Main' --user 0").toString(), 44 | ); 45 | }); 46 | setImmediate(() => { 47 | conn.getSocket().causeRead(Protocol.OKAY); 48 | conn.getSocket().causeRead('Success\n'); 49 | conn.getSocket().causeEnd(); 50 | }); 51 | const options = { 52 | component: 'com.dummy.component/.Main', 53 | user: 0, 54 | }; 55 | return cmd.execute(options); 56 | }); 57 | it("should not send user option if not set'", () => { 58 | const conn = new MockConnection(); 59 | const cmd = new StartServiceCommand(conn); 60 | conn.getSocket().on('write', (chunk) => { 61 | expect(chunk.toString()).to.equal( 62 | Protocol.encodeData("shell:am startservice -n 'com.dummy.component/.Main'").toString(), 63 | ); 64 | }); 65 | setImmediate(() => { 66 | conn.getSocket().causeRead(Protocol.OKAY); 67 | conn.getSocket().causeRead('Success\n'); 68 | 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', () => { 9 | it("should send 'sync:'", () => { 10 | const conn = new MockConnection(); 11 | const cmd = new SyncCommand(conn); 12 | conn.getSocket().on('write', (chunk) => { 13 | expect(chunk.toString()).to.equal(Protocol.encodeData('sync:').toString()); 14 | conn.getSocket().causeRead(Protocol.OKAY); 15 | 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', () => { 10 | it("should send 'tcp:' when no host given", () => { 11 | const conn = new MockConnection(); 12 | const cmd = new TcpCommand(conn); 13 | conn.getSocket().on('write', (chunk) => { 14 | expect(chunk.toString()).to.equal(Protocol.encodeData('tcp:8080').toString()); 15 | }); 16 | setImmediate(() => { 17 | conn.getSocket().causeRead(Protocol.OKAY); 18 | conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute(8080); 21 | }); 22 | it("should send 'tcp::' when host given", () => { 23 | const conn = new MockConnection(); 24 | const cmd = new TcpCommand(conn); 25 | conn.getSocket().on('write', (chunk) => { 26 | expect(chunk.toString()).to.equal(Protocol.encodeData('tcp:8080:127.0.0.1').toString()); 27 | }); 28 | setImmediate(() => { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | conn.getSocket().causeEnd(); 31 | }); 32 | return cmd.execute(8080, '127.0.0.1'); 33 | }); 34 | it('should resolve with the tcp stream', async () => { 35 | const conn = new MockConnection(); 36 | const cmd = new TcpCommand(conn); 37 | setImmediate(() => { 38 | conn.getSocket().causeRead(Protocol.OKAY); 39 | }); 40 | const stream = await cmd.execute(8080); 41 | stream.end(); 42 | expect(stream).to.be.an.instanceof(Stream.Readable); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /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', () => { 9 | it("should send 'tcp:'", () => { 10 | const conn = new MockConnection(); 11 | const cmd = new TcpIpCommand(conn); 12 | conn.getSocket().on('write', (chunk) => { 13 | expect(chunk.toString()).to.equal(Protocol.encodeData('tcpip:5555').toString()); 14 | }); 15 | setImmediate(() => { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead('restarting in TCP mode port: 5555\n'); 18 | conn.getSocket().causeEnd(); 19 | }); 20 | return cmd.execute(5555) 21 | }); 22 | it('should resolve with the port', async () => { 23 | const conn = new MockConnection(); 24 | const cmd = new TcpIpCommand(conn); 25 | setImmediate(() => { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead('restarting in TCP mode port: 5555\n'); 28 | conn.getSocket().causeEnd(); 29 | }); 30 | const port = await cmd.execute(5555); 31 | expect(port).to.equal(5555); 32 | }); 33 | it('should reject on unexpected reply', (done) => { 34 | const conn = new MockConnection(); 35 | const cmd = new TcpIpCommand(conn); 36 | setImmediate(() => { 37 | conn.getSocket().causeRead(Protocol.OKAY); 38 | conn.getSocket().causeRead('not sure what this could be\n'); 39 | conn.getSocket().causeEnd(); 40 | }); 41 | cmd.execute(5555).catch((err) => { 42 | expect(err.message).to.eql('not sure what this could be'); 43 | done(); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /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', () => { 9 | it("should send 'usb:'", async () => { 10 | const conn = new MockConnection(); 11 | const cmd = new UsbCommand(conn); 12 | conn.getSocket().on('write', (chunk) => { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('usb:').toString()); 14 | }); 15 | setImmediate(() => { 16 | conn.getSocket().causeRead(Protocol.OKAY); 17 | conn.getSocket().causeRead('restarting in USB mode\n'); 18 | return conn.getSocket().causeEnd(); 19 | }); 20 | const val = await cmd.execute(); 21 | expect(val).to.be.true; 22 | }); 23 | it('should reject on unexpected reply', (done) => { 24 | const conn = new MockConnection(); 25 | const cmd = new UsbCommand(conn); 26 | setImmediate(() => { 27 | conn.getSocket().causeRead(Protocol.OKAY); 28 | conn.getSocket().causeRead('invalid port\n'); 29 | return conn.getSocket().causeEnd(); 30 | }); 31 | 32 | cmd.execute().catch((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 { AdbPrematureEOFError } from '../../../../src/index'; 7 | import WaitBootCompleteCommand from '../../../../src/adb/command/host-transport/waitbootcomplete'; 8 | 9 | describe('WaitBootCompleteCommand', () => { 10 | it('should send a while loop with boot check', () => { 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', (chunk) => { 15 | return expect(chunk.toString()).to.equal(Protocol.encodeData(want).toString()); 16 | }); 17 | setImmediate(() => { 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 AdbPrematureEOFError if connection cuts prematurely', async () => { 25 | const conn = new MockConnection(); 26 | const cmd = new WaitBootCompleteCommand(conn); 27 | setImmediate(() => { 28 | conn.getSocket().causeRead(Protocol.OKAY); 29 | return conn.getSocket().causeEnd(); 30 | }); 31 | try { 32 | await cmd.execute() 33 | throw new Error('Succeeded even though it should not'); 34 | } catch (err) { 35 | expect(err).to.be.an.instanceof(AdbPrematureEOFError); 36 | return true; 37 | } 38 | }); 39 | it('should not return until boot is complete', async () => { 40 | const conn = new MockConnection(); 41 | const cmd = new WaitBootCompleteCommand(conn); 42 | let sent = false; 43 | setImmediate(() => { 44 | conn.getSocket().causeRead(Protocol.OKAY); 45 | conn.getSocket().causeRead('\r\n'); 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 | return setTimeout(() => { 56 | sent = true; 57 | return conn.getSocket().causeRead('1\r\n'); 58 | }, 50); 59 | }); 60 | await cmd.execute(); 61 | expect(sent).to.be.true; 62 | }); 63 | it('should close connection when done', (done) => { 64 | const conn = new MockConnection(); 65 | const cmd = new WaitBootCompleteCommand(conn); 66 | setImmediate(() => { 67 | conn.getSocket().causeRead(Protocol.OKAY); 68 | return conn.getSocket().causeRead('1\r\n'); 69 | }); 70 | conn.getSocket().once('end', () => { 71 | done(); 72 | }); 73 | cmd.execute(); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /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 HostConnectCommand from '../../../../src/adb/command/host/HostConnectCommand'; 7 | 8 | describe('ConnectCommand', () => { 9 | it("should send 'host:connect::'", () => { 10 | const conn = new MockConnection(); 11 | const cmd = new HostConnectCommand(conn); 12 | conn.getSocket().on('write', (chunk) => { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('host:connect:192.168.2.2:5555').toString()); 14 | }); 15 | setImmediate(() => { 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', async () => { 23 | const conn = new MockConnection(); 24 | const cmd = new HostConnectCommand(conn); 25 | setImmediate(() => { 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 | const val = await cmd.execute('192.168.2.2', 5555); 31 | expect(val).to.be.equal(true); 32 | }); 33 | it('should resolve with the new device id if already connected', async () => { 34 | const conn = new MockConnection(); 35 | const cmd = new HostConnectCommand(conn); 36 | setImmediate(() => { 37 | conn.getSocket().causeRead(Protocol.OKAY); 38 | conn.getSocket().causeRead(Protocol.encodeData('already connected to 192.168.2.2:5555')); 39 | return conn.getSocket().causeEnd(); 40 | }); 41 | const val = await cmd.execute('192.168.2.2', 5555); 42 | expect(val).to.be.equal(false); 43 | }); 44 | it('should reject with error if unable to connect', async () => { 45 | const conn = new MockConnection(); 46 | const cmd = new HostConnectCommand(conn); 47 | setImmediate(() => { 48 | conn.getSocket().causeRead(Protocol.OKAY); 49 | conn.getSocket().causeRead(Protocol.encodeData('unable to connect to 192.168.2.2:5555')); 50 | return conn.getSocket().causeEnd(); 51 | }); 52 | try { 53 | return await cmd.execute('192.168.2.2', 5555); 54 | } catch (err) { 55 | expect((err as Error).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 HostDisconnectCommand from '../../../../src/adb/command/host/HostDisconnectCommand'; 7 | import Connection from '../../../../src/adb/connection'; 8 | 9 | describe('DisconnectCommand', () => { 10 | it("should send 'host:disconnect::'", () => { 11 | const conn = new MockConnection(); 12 | const cmd = new HostDisconnectCommand(conn); 13 | conn.getSocket().on('write', (chunk) => { 14 | return expect(chunk.toString()).to.equal( 15 | Protocol.encodeData('host:disconnect:192.168.2.2:5555').toString(), 16 | ); 17 | }); 18 | setImmediate(() => { 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', async () => { 26 | const conn = new MockConnection(); 27 | const cmd = new HostDisconnectCommand(conn as Connection); 28 | setImmediate(() => { 29 | conn.getSocket().causeRead(Protocol.OKAY); 30 | conn.getSocket().causeRead(Protocol.encodeData('')); 31 | return conn.getSocket().causeEnd(); 32 | }); 33 | const val = await cmd.execute('192.168.2.2', 5555); 34 | expect(val).to.be.equal('192.168.2.2:5555'); 35 | }); 36 | it('should reject with error if unable to disconnect', async () => { 37 | const conn = new MockConnection(); 38 | const cmd = new HostDisconnectCommand(conn as Connection); 39 | setImmediate(() => { 40 | conn.getSocket().causeRead(Protocol.OKAY); 41 | conn.getSocket().causeRead(Protocol.encodeData('No such device 192.168.2.2:5555')); 42 | return conn.getSocket().causeEnd(); 43 | }); 44 | try { 45 | return await cmd.execute('192.168.2.2', 5555); 46 | } catch (err) { 47 | expect((err as Error).message).to.eql('No such device 192.168.2.2:5555'); 48 | } 49 | }); 50 | it('should resolve with the new device id if disconnected', async () => { 51 | const conn = new MockConnection(); 52 | const cmd = new HostDisconnectCommand(conn as Connection); 53 | setImmediate(function () { 54 | conn.getSocket().causeRead(Protocol.OKAY); 55 | conn.getSocket().causeRead(Protocol.encodeData('disconnected 192.168.2.2:5555')); 56 | return conn.getSocket().causeEnd(); 57 | }); 58 | 59 | const val = await cmd.execute('192.168.2.2', 5555) 60 | expect(val).to.be.equal('192.168.2.2:5555'); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /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/HostVersionCommand'; 7 | 8 | describe('HostVersionCommand', () => { 9 | it("should send 'host:version'", () => { 10 | const conn = new MockConnection(); 11 | const cmd = new HostVersionCommand(conn); 12 | conn.getSocket().on('write', (chunk) => { 13 | return expect(chunk.toString()).to.equal(Protocol.encodeData('host:version').toString()); 14 | }); 15 | setImmediate(() => { 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', async () => { 23 | const conn = new MockConnection(); 24 | const cmd = new HostVersionCommand(conn); 25 | setImmediate(() => { 26 | conn.getSocket().causeRead(Protocol.OKAY); 27 | conn.getSocket().causeRead(Protocol.encodeData((0x1234).toString(16))); 28 | return conn.getSocket().causeEnd(); 29 | }); 30 | const version = await cmd.execute() 31 | expect(version).to.equal(0x1234); 32 | return true; 33 | }); 34 | it('should handle old-style version', async () => { 35 | const conn = new MockConnection(); 36 | const cmd = new HostVersionCommand(conn); 37 | setImmediate(() => { 38 | conn.getSocket().causeRead((0x1234).toString(16)); 39 | return conn.getSocket().causeEnd(); 40 | }); 41 | const version = await cmd.execute() 42 | expect(version).to.equal(0x1234); 43 | return true; 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/adb/protocol.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import Protocol from '../../src/adb/protocol'; 4 | 5 | describe('Protocol', () => { 6 | it("should expose a 'FAIL' property", (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", (done) => { 12 | expect(Protocol).to.have.property('OKAY'); 13 | expect(Protocol.OKAY).to.equal('OKAY'); 14 | done(); 15 | }); 16 | describe('@decodeLength(length)', () => { 17 | it('should return a Number', (done) => { 18 | expect(Protocol.decodeLength('0x0046')).to.be.a('number'); 19 | done(); 20 | }); 21 | it('should accept a hexadecimal string', (done) => { 22 | expect(Protocol.decodeLength('0x5887')).to.equal(0x5887); 23 | done(); 24 | }); 25 | }); 26 | describe('@encodeLength(length)', () => { 27 | it('should return a String', (done) => { 28 | expect(Protocol.encodeLength(27)).to.be.a('string'); 29 | done(); 30 | }); 31 | it('should return a valid hexadecimal number', (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', (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', (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 | it('should return 0000 for 0 length', (done) => { 47 | expect(Protocol.encodeLength(0)).to.equal('0000'); 48 | done(); 49 | }); 50 | }); 51 | return describe('@encodeData(data)', () => { 52 | it('should return a Buffer', (done) => { 53 | expect(Protocol.encodeData(Buffer.from(''))).to.be.an.instanceOf(Buffer); 54 | done(); 55 | }); 56 | it('should accept a string or a Buffer', (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 | it('should prefix data with length', (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/thirdparty/ptotobuf.ts: -------------------------------------------------------------------------------- 1 | import STFServiceBuf from "../../../src/adb/thirdparty/STFService/STFServiceBuf"; 2 | 3 | describe('Protobuff', () => { 4 | it("test Protobuff encode / decode", async () => { 5 | // const encoded = '0610111a020800'; 6 | // const proto = await STFProtoBuf.get(); 7 | // const tert: Envelope = { type: MessageType.EVENT_ROTATION, message: Buffer.from('CAA=', 'base64')}; 8 | // const buf = proto.write.Envelope(tert); 9 | // expect(buf.toString('hex')).to.be.eq(encoded); 10 | // const envelop = proto.readEnvelope(buf); 11 | // expect(envelop.toJSON().message).to.be.eq('CAA='); 12 | // const data2 = proto.read.RotationEvent(envelop.message); 13 | // expect(data2.rotation).to.be.eq(0); 14 | }); 15 | 16 | it("parse GetVersionResponse enveloppe", async () => { 17 | // const encoded = '0908011205322e342e39'; 18 | // const proto = await STFServiceBuf.get(); 19 | // const env = proto.readEnvelopeDelimited(Buffer.from(encoded, 'hex')); 20 | // console.log(env); 21 | //const proto = await STFProtoBuf.get(); 22 | 23 | 24 | // const tert: Envelope = { type: MessageType.EVENT_ROTATION, message: Buffer.from('CAA=', 'base64')}; 25 | // const buf = proto.write.Envelope(tert); 26 | // expect(buf.toString('hex')).to.be.eq(encoded); 27 | // const envelop = proto.readEnvelope(buf); 28 | // expect(envelop.toJSON().message).to.be.eq('CAA='); 29 | // const data2 = proto.read.RotationEvent(envelop.message); 30 | // expect(data2.rotation).to.be.eq(0); 31 | }); 32 | 33 | 34 | }) -------------------------------------------------------------------------------- /test/adb/util.ts: -------------------------------------------------------------------------------- 1 | import Stream from 'stream'; 2 | import { use, expect } from 'chai'; 3 | import simonChai from 'sinon-chai'; 4 | import { Utils } from '../../src/'; 5 | use(simonChai); 6 | 7 | describe('util', () => { 8 | return describe('readAll(stream)', () => { 9 | it('should return a cancellable Bluebird Promise', (done) => { 10 | const stream = new Stream.PassThrough(); 11 | const promise = Utils.readAll(stream); 12 | expect(promise).to.be.an.instanceOf(Promise); 13 | stream.end(); 14 | done(); 15 | }); 16 | it('should read all remaining content until the stream ends', async () => { 17 | const stream = new Stream.PassThrough(); 18 | stream.write('F'); 19 | stream.write('O'); 20 | stream.write('O'); 21 | stream.end(); 22 | const buf = await Utils.readAll(stream) 23 | expect(buf.length).to.equal(3); 24 | expect(buf.toString()).to.equal('FOO'); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/mock/client.ts: -------------------------------------------------------------------------------- 1 | import Client from '../../src/adb/client'; 2 | 3 | export default class MockClient extends Client { 4 | constructor() { 5 | super(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/mock/connection.ts: -------------------------------------------------------------------------------- 1 | import Connection from '../../src/adb/connection'; 2 | import Parser from '../../src/adb/parser'; 3 | import MockDuplex from './duplex'; 4 | import MockClient from './client'; 5 | 6 | export default class MockConnection extends Connection { 7 | _socket = new MockDuplex(); 8 | 9 | constructor() { 10 | super(new MockClient()); 11 | this.parser = new Parser(this._socket); 12 | } 13 | 14 | public getSocket(): MockDuplex { 15 | return this._socket; 16 | } 17 | 18 | public end(): this { 19 | this._socket.causeEnd(); 20 | return this; 21 | } 22 | 23 | public write(chunk: string | Uint8Array): Promise { 24 | return new Promise((accept, reject) => { 25 | this._socket.write(chunk, (err) => { 26 | if (err) reject(err); 27 | else accept(chunk.length); 28 | }); 29 | }) 30 | } 31 | 32 | // @ts-ignore 33 | public on(): this { 34 | return this; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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: Buffer, encoding: string, callback: Function): void { 11 | this.emit('write', chunk, encoding, callback); 12 | callback(null); 13 | } 14 | 15 | causeRead(chunk: string | Buffer): 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(...args: any[]): this { 27 | this.causeEnd(); // In order to better emulate socket streams 28 | return (Stream.Duplex.prototype.end as any).apply(this, args); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | // import Adb from "../src/adb"; 2 | 3 | //async function main() { 4 | // const client = Adb.createClient(); 5 | // const devices = await client.listDevices(); 6 | // const device = client.getDevice(devices[0].id); 7 | // // const services = await device.getServices(); 8 | // // console.log (services); 9 | // // const services = await device.checkService('location'); 10 | // // console.log (services); 11 | // // service call serial 1 12 | // // 1 => imei 13 | // // 19 => phone number 14 | // // for (let i = 0; i < 32; i++) { 15 | // // console.log('iphonesubinfo', i); 16 | // // try { 17 | // // const services = await device.callServiceRaw('iphonesubinfo', i); 18 | // // // console.log(services.length); 19 | // // console.log(services.length, services.toString('utf-8')); 20 | // // console.log(services.length, services.swap16().toString('ucs2')); 21 | // // } catch (e) { 22 | // // console.log(e); 23 | // // } 24 | // // } 25 | //} 26 | //main() --------------------------------------------------------------------------------