@cldmv/droidsock 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -44
- package/devcheck.mjs +13 -12
- package/dist/api/auth.mjs +17 -0
- package/dist/api/config.mjs +17 -0
- package/dist/api/connection.mjs +17 -0
- package/dist/api/device.mjs +17 -0
- package/dist/api/files.mjs +17 -0
- package/dist/api/log.mjs +17 -0
- package/dist/api/shell.mjs +17 -0
- package/dist/api/stream.mjs +17 -0
- package/dist/api/utils.mjs +17 -0
- package/dist/droidsock.mjs +17 -0
- package/index.cjs +12 -14
- package/index.mjs +15 -30
- package/package.json +33 -23
- package/types/devcheck.d.mts +2 -0
- package/types/dist/api/auth.d.mts +14 -0
- package/types/dist/api/auth.d.mts.map +1 -0
- package/types/dist/api/config.d.mts +52 -0
- package/types/dist/api/config.d.mts.map +1 -0
- package/types/dist/api/connection.d.mts +2 -0
- package/types/dist/api/connection.d.mts.map +1 -0
- package/types/dist/api/device.d.mts +5 -0
- package/types/dist/api/device.d.mts.map +1 -0
- package/types/dist/api/files.d.mts +12 -0
- package/types/dist/api/files.d.mts.map +1 -0
- package/types/dist/api/log.d.mts +21 -0
- package/types/dist/api/log.d.mts.map +1 -0
- package/types/dist/api/shell.d.mts +29 -0
- package/types/dist/api/shell.d.mts.map +1 -0
- package/types/dist/api/stream.d.mts +9 -0
- package/types/dist/api/stream.d.mts.map +1 -0
- package/types/dist/api/utils.d.mts +27 -0
- package/types/dist/api/utils.d.mts.map +1 -0
- package/types/dist/droidsock.d.mts +3 -0
- package/types/dist/droidsock.d.mts.map +1 -0
- package/types/index.d.mts +11 -0
- package/types/index.d.mts.map +1 -0
package/README.md
CHANGED
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
# DroidSock
|
|
2
2
|
|
|
3
|
+
<div align="center">
|
|
4
|
+
<img src="https://github.com/CLDMV/droidsock/raw/HEAD/images/droidsock-banner.jpg" alt="DroidSock Banner">
|
|
5
|
+
</div>
|
|
6
|
+
|
|
3
7
|
A complete, from-scratch implementation of the Android Debug Bridge (ADB) protocol in Node.js. This library provides full ADB functionality including device connection, RSA authentication, shell command execution, and file transfers - eliminating clicking sounds on Android TV devices!
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
[![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] [![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] [![npm last update]][npm_last_update_url] [![coverage]][coverage_url]
|
|
10
|
+
|
|
11
|
+
[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
|
|
12
|
+
|
|
13
|
+
> [!NOTE]
|
|
14
|
+
> **Current status:**
|
|
15
|
+
>
|
|
16
|
+
> - **Shell + streaming**: Stable - command execution, interactive shells, and log/process streaming all work over the real ADB protocol.
|
|
17
|
+
> - **File transfer**: `mkdir`/`remove`/`move`/`copy`/`chmod`/`diskUsage`/`find` work today via shell commands. `push`/`pull`/`list`/`stat` (the SYNC sub-protocol) are not implemented yet.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## ✨ What's New
|
|
22
|
+
|
|
23
|
+
### Latest: v1.0.0 (September 2026)
|
|
24
|
+
|
|
25
|
+
- **First stable release** - a real Vitest test suite with measured coverage, the full CLDMV v4 CI/release pipeline, a real `dist/` build, and an API surface that's been reviewed rather than just grown.
|
|
26
|
+
- **Breaking**: the default export is now itself the callable quick path (`await droidsock()`); the old top-level `connect()`/`listDevices()` exports are gone.
|
|
27
|
+
- [View full v1.0.0 Changelog](./docs/changelog/v1/v1.0.0.md)
|
|
28
|
+
|
|
29
|
+
📚 **For complete version history and detailed release notes, see [docs/changelog/](./docs/changelog/) folder.**
|
|
6
30
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 🚀 Key Features
|
|
34
|
+
|
|
35
|
+
- ✅ **Complete ADB Protocol**: TCP connection, CNXN/AUTH handshake, and stream multiplexing implemented from scratch
|
|
36
|
+
- ✅ **RSA Authentication**: Automatic key generation and ADB-specific signature/public-key formatting
|
|
37
|
+
- ✅ **Stream Multiplexing**: Multiple concurrent operations over a single connection
|
|
10
38
|
- ✅ **Shell Commands**: Execute commands, stream output, interactive sessions
|
|
11
|
-
- ✅ **File
|
|
39
|
+
- ✅ **Shell-Based File Operations**: `mkdir`, `remove`, `move`, `copy`, `chmod`, `diskUsage`, `find`
|
|
12
40
|
- ✅ **Device Discovery**: Support for multiple devices via configuration
|
|
13
41
|
- ✅ **Error Handling**: Robust error handling and connection recovery
|
|
14
42
|
|
|
@@ -21,36 +49,29 @@ npm install @cldmv/droidsock
|
|
|
21
49
|
## Quick Start
|
|
22
50
|
|
|
23
51
|
```javascript
|
|
24
|
-
import
|
|
52
|
+
import droidsock from "@cldmv/droidsock";
|
|
25
53
|
|
|
26
|
-
// Create
|
|
27
|
-
const
|
|
28
|
-
host: "10.6.0.108", // Device IP
|
|
29
|
-
port: 5555 // ADB port
|
|
30
|
-
});
|
|
54
|
+
// Create the API instance
|
|
55
|
+
const api = await droidsock();
|
|
31
56
|
|
|
32
|
-
// Connect
|
|
33
|
-
await
|
|
57
|
+
// Connect to a device
|
|
58
|
+
const device = await api.device.connect("10.6.0.108", 5555);
|
|
34
59
|
|
|
35
|
-
// Execute shell command
|
|
36
|
-
const output = await
|
|
60
|
+
// Execute a shell command
|
|
61
|
+
const output = await device.shell("ls -la");
|
|
37
62
|
console.log(output);
|
|
38
63
|
|
|
39
|
-
//
|
|
40
|
-
const model = await
|
|
41
|
-
const version = await
|
|
42
|
-
|
|
43
|
-
// File operations
|
|
44
|
-
await client.push("./local-file.txt", "/sdcard/remote-file.txt");
|
|
45
|
-
await client.pull("/sdcard/remote-file.txt", "./local-file.txt");
|
|
64
|
+
// Convenience getters
|
|
65
|
+
const model = await device.getModel();
|
|
66
|
+
const version = await device.getAndroidVersion();
|
|
46
67
|
|
|
47
68
|
// Stream commands
|
|
48
|
-
const logcat =
|
|
69
|
+
const logcat = device.logcat({
|
|
49
70
|
onData: (data) => console.log(data)
|
|
50
71
|
});
|
|
51
72
|
|
|
52
73
|
// Clean up
|
|
53
|
-
|
|
74
|
+
device.disconnect();
|
|
54
75
|
```
|
|
55
76
|
|
|
56
77
|
## Device Configuration
|
|
@@ -77,23 +98,22 @@ Use the `references/devices.json` file to configure your devices:
|
|
|
77
98
|
|
|
78
99
|
## API Reference
|
|
79
100
|
|
|
80
|
-
###
|
|
101
|
+
### droidsock(options)
|
|
81
102
|
|
|
82
|
-
|
|
103
|
+
The default export, and the quick path - creates a DroidSock API instance. `options.mode` (`"eager"` or `"lazy"`, default `"eager"`), `options.context`, and `options.config` are all optional. Also available under the explicit name `createDroidSock` (`import { createDroidSock } from "@cldmv/droidsock"`) for callers who prefer it - both names are the exact same function.
|
|
83
104
|
|
|
84
|
-
####
|
|
105
|
+
#### device.connect(host, port, options)
|
|
85
106
|
|
|
86
107
|
- `host`: Device IP address
|
|
87
108
|
- `port`: ADB port (default: 5555)
|
|
88
|
-
- `keyDir`: Directory for RSA keys (default:
|
|
109
|
+
- `options.keyDir`: Directory for RSA keys (default: `~/.adb`)
|
|
89
110
|
|
|
90
|
-
|
|
111
|
+
Returns a device object with the methods below.
|
|
91
112
|
|
|
92
113
|
##### Connection
|
|
93
114
|
|
|
94
|
-
- `connect()`: Connect to device
|
|
95
|
-
- `disconnect()`: Disconnect from device
|
|
96
115
|
- `isConnected()`: Check connection status
|
|
116
|
+
- `disconnect()`: Disconnect from device
|
|
97
117
|
|
|
98
118
|
##### Shell Commands
|
|
99
119
|
|
|
@@ -115,10 +135,8 @@ Main client class for ADB operations.
|
|
|
115
135
|
|
|
116
136
|
##### File Operations
|
|
117
137
|
|
|
118
|
-
- `push(localPath, remotePath, options)`:
|
|
119
|
-
- `pull(remotePath, localPath, options)`:
|
|
120
|
-
- `list(remotePath)`: List directory contents
|
|
121
|
-
- `stat(remotePath)`: Get file/directory stats
|
|
138
|
+
- `push(localPath, remotePath, options)`: **Not yet implemented** - throws
|
|
139
|
+
- `pull(remotePath, localPath, options)`: **Not yet implemented** - throws
|
|
122
140
|
|
|
123
141
|
## Examples
|
|
124
142
|
|
|
@@ -147,15 +165,15 @@ node examples/streaming-example.mjs files
|
|
|
147
165
|
|
|
148
166
|
## Architecture
|
|
149
167
|
|
|
150
|
-
|
|
168
|
+
`src/droidsock.mjs` composes the layers below into a single api tree via [`@cldmv/slothlet`](https://github.com/CLDMV/slothlet):
|
|
151
169
|
|
|
152
|
-
1. **
|
|
153
|
-
2. **Authentication Layer** (`auth.mjs`): RSA key management and ADB
|
|
154
|
-
3. **
|
|
155
|
-
4. **
|
|
156
|
-
5. **
|
|
157
|
-
6. **
|
|
158
|
-
7. **
|
|
170
|
+
1. **Connection Layer** (`src/api/connection.mjs`): TCP socket + CNXN/AUTH handshake
|
|
171
|
+
2. **Authentication Layer** (`src/api/auth.mjs`): RSA key management and ADB signature/public-key formatting
|
|
172
|
+
3. **Stream Layer** (`src/api/stream.mjs`): ADB stream multiplexing (OPEN/WRTE/OKAY/CLSE)
|
|
173
|
+
4. **Shell Layer** (`src/api/shell.mjs`): Command execution, streaming, and interactive shell APIs
|
|
174
|
+
5. **Files Layer** (`src/api/files.mjs`): Shell-based file operations (SYNC-protocol `push`/`pull`/`list`/`stat` not yet implemented)
|
|
175
|
+
6. **Device Layer** (`src/api/device.mjs`): High-level per-device API composing the layers above
|
|
176
|
+
7. **Config / Log Layers** (`src/api/config.mjs`, `src/api/log.mjs`): Shared configuration and logging
|
|
159
177
|
|
|
160
178
|
## Protocol Implementation Details
|
|
161
179
|
|
|
@@ -192,6 +210,7 @@ All ADB packets follow a 24-byte header + optional data format:
|
|
|
192
210
|
- SEND/RECV commands for push/pull
|
|
193
211
|
- DATA packets for file chunks
|
|
194
212
|
- DONE packets signal completion
|
|
213
|
+
- Not yet implemented - see [Current status](#-key-features) above
|
|
195
214
|
|
|
196
215
|
## Troubleshooting
|
|
197
216
|
|
|
@@ -225,8 +244,25 @@ The implementation is based on:
|
|
|
225
244
|
|
|
226
245
|
## License
|
|
227
246
|
|
|
228
|
-
|
|
247
|
+
Apache-2.0 - see [LICENSE](LICENSE) for details.
|
|
229
248
|
|
|
230
249
|
## Contributing
|
|
231
250
|
|
|
232
251
|
This is a complete implementation of the ADB protocol. For improvements or bug fixes, please submit issues or pull requests.
|
|
252
|
+
|
|
253
|
+
[npm version]: https://img.shields.io/npm/v/%40cldmv%2Fdroidsock.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
254
|
+
[npm_version_url]: https://www.npmjs.com/package/@cldmv/droidsock
|
|
255
|
+
[npm downloads]: https://img.shields.io/npm/dm/%40cldmv%2Fdroidsock.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
256
|
+
[npm_downloads_url]: https://www.npmjs.com/package/@cldmv/droidsock
|
|
257
|
+
[github downloads]: https://img.shields.io/github/downloads/CLDMV/droidsock/total?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
258
|
+
[github_downloads_url]: https://github.com/CLDMV/droidsock/releases
|
|
259
|
+
[last commit]: https://img.shields.io/github/last-commit/CLDMV/droidsock?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
260
|
+
[last_commit_url]: https://github.com/CLDMV/droidsock/commits
|
|
261
|
+
[npm last update]: https://img.shields.io/npm/last-update/%40cldmv%2Fdroidsock?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
|
|
262
|
+
[npm_last_update_url]: https://www.npmjs.com/package/@cldmv/droidsock
|
|
263
|
+
[coverage]: https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2FCLDMV%2Fdroidsock%2Fbadges%2Fcoverage.json&style=for-the-badge&logo=vitest&logoColor=white
|
|
264
|
+
[coverage_url]: https://github.com/CLDMV/droidsock/blob/badges/coverage.json
|
|
265
|
+
[contributors]: https://img.shields.io/github/contributors/CLDMV/droidsock.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
|
|
266
|
+
[contributors_url]: https://github.com/CLDMV/droidsock/graphs/contributors
|
|
267
|
+
[sponsor shinrai]: https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor
|
|
268
|
+
[sponsor_url]: https://github.com/sponsors/shinrai
|
package/devcheck.mjs
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
7
|
* -----
|
|
8
8
|
* @Last modified by: Nate Hyson <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
-
* @Last modified time:
|
|
9
|
+
* @Last modified time: 2026-08-30 16:02:20 -07:00 (1788130940)
|
|
10
10
|
* -----
|
|
11
|
-
* @Copyright: Copyright (c) 2013-
|
|
11
|
+
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
@@ -34,27 +34,28 @@ const isCI = !!(
|
|
|
34
34
|
|
|
35
35
|
if (existsSync(srcPath) && !isCI) {
|
|
36
36
|
// if (existsSync(srcPath) && !existsSync(distPath)) {
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// NODE_ENV plays no part in Node's conditional-exports resolution - only the
|
|
38
|
+
// "--conditions=droidsock-dev" condition (via NODE_OPTIONS) actually routes
|
|
39
|
+
// exports["./main"] to src/ instead of dist/. NODE_ENV=development alone
|
|
40
|
+
// would previously pass this check while still resolving to dist/.
|
|
41
|
+
const hasNodeOptions = process.env.NODE_OPTIONS?.includes("--conditions=droidsock-dev");
|
|
39
42
|
|
|
40
|
-
if (!
|
|
43
|
+
if (!hasNodeOptions) {
|
|
41
44
|
console.error("❌ Development environment not properly configured!");
|
|
42
|
-
console.error("📁 Source folder detected but
|
|
45
|
+
console.error("📁 Source folder detected but NODE_OPTIONS is not set for development.");
|
|
43
46
|
console.error("");
|
|
44
47
|
console.error("🔧 To fix this, run one of these commands:");
|
|
45
48
|
console.error(" Windows (cmd):");
|
|
46
|
-
console.error(" set
|
|
47
|
-
console.error(" set NODE_OPTIONS=--conditions=development");
|
|
49
|
+
console.error(" set NODE_OPTIONS=--conditions=droidsock-dev");
|
|
48
50
|
console.error("");
|
|
49
51
|
console.error(" Windows (PowerShell):");
|
|
50
|
-
console.error(" $env:
|
|
51
|
-
console.error(" $env:NODE_OPTIONS='--conditions=development'");
|
|
52
|
+
console.error(" $env:NODE_OPTIONS='--conditions=droidsock-dev'");
|
|
52
53
|
console.error("");
|
|
53
54
|
console.error(" Unix/Linux/macOS:");
|
|
54
|
-
console.error(" export
|
|
55
|
-
console.error(" export NODE_OPTIONS=--conditions=development");
|
|
55
|
+
console.error(" export NODE_OPTIONS=--conditions=droidsock-dev");
|
|
56
56
|
console.error("");
|
|
57
57
|
console.error("💡 This ensures this module loads from src/ instead of dist/ for development.");
|
|
58
|
+
console.error(" (NODE_ENV is not checked here - it has no effect on this resolution.)");
|
|
58
59
|
console.error("🚀 CI environments automatically skip this check.");
|
|
59
60
|
process.exit(1);
|
|
60
61
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import{self}from"@cldmv/slothlet/runtime";import crypto from"node:crypto";import fs from"node:fs";import path from"node:path";import os from"node:os";function getKeys(keyDir){const adbDir=keyDir||path.join(os.homedir(),".adb");const privateKeyPath=path.join(adbDir,"adbkey");const publicKeyPath=path.join(adbDir,"adbkey.pub");if(!fs.existsSync(adbDir)){fs.mkdirSync(adbDir,{recursive:true})}if(fs.existsSync(privateKeyPath)&&fs.existsSync(publicKeyPath)){const privateKey=fs.readFileSync(privateKeyPath,"utf8");const adbPublicKey=fs.readFileSync(publicKeyPath,"utf8").trim();const keyObject=crypto.createPrivateKey(privateKey);const publicKey=crypto.createPublicKey(keyObject).export({type:"spki",format:"pem"});return{privateKey,publicKey,adbPublicKey}}return generateKeys(2048,adbDir)}function generateKeys(keySize=2048,saveDir=null){const{publicKey,privateKey}=crypto.generateKeyPairSync("rsa",{modulusLength:keySize,publicKeyEncoding:{type:"spki",format:"pem"},privateKeyEncoding:{type:"pkcs8",format:"pem"}});const adbPublicKey=formatAdbPublicKey(publicKey);if(saveDir){if(!fs.existsSync(saveDir)){fs.mkdirSync(saveDir,{recursive:true})}const privateKeyPath=path.join(saveDir,"adbkey");const publicKeyPath=path.join(saveDir,"adbkey.pub");fs.writeFileSync(privateKeyPath,privateKey);fs.writeFileSync(publicKeyPath,adbPublicKey)}return{privateKey,publicKey,adbPublicKey}}function sign(token,privateKey){self.log.debug("[DEBUG] AOSP-STYLE ADB SIGNATURE (token as digest):");self.log.debug("[DEBUG] Token:",token.toString("hex"),`(${token.length} bytes)`);if(!Buffer.isBuffer(token)){token=Buffer.from(token)}if(token.length!==20){throw new Error(`Token must be 20 bytes, got ${token.length}`)}const DIGESTINFO_SHA1_PREFIX=Buffer.from([48,33,48,9,6,5,43,14,3,2,26,5,0,4,20]);const digestInfo=Buffer.concat([DIGESTINFO_SHA1_PREFIX,token]);self.log.debug("[DEBUG] DigestInfo:",digestInfo.toString("hex"));const keyObject=crypto.createPrivateKey(privateKey);let keySize=keyObject.asymmetricKeySize;if(keySize===void 0){const keyDetails=keyObject.asymmetricKeyDetails||{};if(keyDetails.modulusLength){keySize=Math.ceil(keyDetails.modulusLength/8)}else{keySize=getKeySizeFromPem(privateKey)}}self.log.debug("[DEBUG] Key size:",keySize,"bytes");const paddedBlock=buildPkcs1v15Block(digestInfo,keySize);self.log.debug("[DEBUG] PKCS#1 v1.5 block length:",paddedBlock.length,"bytes");self.log.debug("[DEBUG] Block starts:",paddedBlock.slice(0,16).toString("hex")+"...");self.log.debug("[DEBUG] Block ends: ..."+paddedBlock.slice(-16).toString("hex"));const signature=crypto.privateEncrypt({key:keyObject,padding:crypto.constants.RSA_NO_PADDING},paddedBlock);self.log.debug("[DEBUG] Signature length:",signature.length,"bytes");self.log.debug("[DEBUG] Signature starts with:",signature.slice(0,8).toString("hex"));return signature}function buildPkcs1v15Block(data,keySize){self.log.debug("[DEBUG] buildPkcs1v15Block - data length:",data.length,"keySize:",keySize,"keySize type:",typeof keySize);if(isNaN(keySize)||keySize<=0){throw new Error(`Invalid key size: ${keySize}`)}const paddingLength=keySize-data.length-3;self.log.debug("[DEBUG] Padding length:",paddingLength);if(paddingLength<8){throw new Error(`Key too small for data - keySize: ${keySize}, dataLength: ${data.length}, paddingLength: ${paddingLength}`)}const padding=Buffer.alloc(paddingLength,255);return Buffer.concat([Buffer.from([0,1]),padding,Buffer.from([0]),data])}function validateAuth(token,privateKey){try{if(!Buffer.isBuffer(token)||token.length!==20){return false}if(typeof privateKey!=="string"||!privateKey.includes("-----BEGIN PRIVATE KEY-----")){return false}sign(token,privateKey);return true}catch{return false}}function formatAdbPublicKey(publicKey){self.log.debug("[DEBUG] Creating ADB public key format...");const pubKey=crypto.createPublicKey(publicKey);const jwk=pubKey.export({format:"jwk"});const n=base64UrlToBuf(jwk.n);const e=base64UrlToBuf(jwk.e);self.log.debug("[DEBUG] Exponent length:",e.length,"bytes, first byte: 0x"+e[0].toString(16).padStart(2,"0"));self.log.debug("[DEBUG] Modulus length:",n.length,"bytes, first byte: 0x"+n[0].toString(16).padStart(2,"0"));const eStripped=e[0]===0?e.slice(1):e;const nStripped=n[0]===0?n.slice(1):n;self.log.debug("[DEBUG] After stripping - Exponent:",eStripped.length,"bytes, Modulus:",nStripped.length,"bytes");const parts=[];writeSshString(parts,Buffer.from("ssh-rsa"));writeSshString(parts,eStripped);writeSshString(parts,nStripped);const sshBlob=Buffer.concat(parts);const base64Key=sshBlob.toString("base64");self.log.debug("[DEBUG] SSH blob length:",sshBlob.length,"bytes");self.log.debug("[DEBUG] Base64 length:",base64Key.length,"chars");self.log.debug("[DEBUG] Base64 preview:",base64Key.substring(0,50)+"...");const hostname=os.hostname()||"unknown";const username=os.userInfo().username||"user";const comment=`${username}@${hostname}`;const result=`${base64Key} ${comment}\0`;self.log.debug("[DEBUG] Final ADB key length:",result.length,"chars");return result}function base64UrlToBuf(b64url){const b64=b64url.replace(/-/g,"+").replace(/_/g,"/");const pad=b64.length%4;const padded=pad?b64+"=".repeat(4-pad):b64;return Buffer.from(padded,"base64")}function writeSshString(bufs,data){const len=Buffer.alloc(4);len.writeUInt32BE(data.length,0);bufs.push(len,data)}function getKeySizeFromPem(privateKey){try{const base64=privateKey.replace(/-----BEGIN PRIVATE KEY-----/,"").replace(/-----END PRIVATE KEY-----/,"").replace(/\s/g,"");const derBuffer=Buffer.from(base64,"base64");if(derBuffer.length>1e3){return 512}else{return 256}}catch(error){self.log.debug("[DEBUG] Failed to parse key size from PEM, defaulting to 256 bytes:",error.message);return 256}}export{formatAdbPublicKey,generateKeys,getKeys,sign,validateAuth};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const DEFAULT_CONFIG={host:"127.0.0.1",port:5555,timeout:1e4,retryAttempts:3,retryDelay:1e3,keyDir:null,autoGenerateKeys:true,keySize:2048,debug:false,verbose:false,silent:false,debugArrowSent:">>>>",debugArrowReceived:"<<<<",streamTimeout:3e4,maxStreams:10,shellTimeout:3e4,shellEncoding:"utf8",fileTimeout:6e4,chunkSize:65536,emitEvents:true,eventPrefix:"adb",bufferSize:1024*1024,keepAlive:true,keepAliveInterval:3e4};let configInstance=null;function init(options={}){if(!configInstance){configInstance={...DEFAULT_CONFIG,...options}}return getApi()}function get(key,defaultValue=void 0){if(!configInstance){init()}const keys=key.split(".");let value=configInstance;for(const k of keys){if(value&&typeof value==="object"&&k in value){value=value[k]}else{return defaultValue}}return value}function set(key,value){if(!configInstance){init()}const keys=key.split(".");let obj=configInstance;for(let i=0;i<keys.length-1;i++){const k=keys[i];if(!(k in obj)||typeof obj[k]!=="object"){obj[k]={}}obj=obj[k]}obj[keys[keys.length-1]]=value}function merge(options){if(!configInstance){init()}const mergeRecursive=(target,source,path="")=>{for(const[key,value]of Object.entries(source)){const fullPath=path?`${path}.${key}`:key;if(typeof value==="object"&&value!==null&&!Array.isArray(value)){if(!(key in target)||typeof target[key]!=="object"){target[key]={}}mergeRecursive(target[key],value,fullPath)}else{target[key]=value}}};mergeRecursive(configInstance,options)}function reset(){configInstance={...DEFAULT_CONFIG}}function all(){return configInstance?{...configInstance}:{...DEFAULT_CONFIG}}function getApi(){return{init,get,set,merge,reset,all}}function getDefaults(){return{...DEFAULT_CONFIG}}function validateConfig(config){const errors=[];if(config.port&&(typeof config.port!=="number"||config.port<1||config.port>65535)){errors.push("port must be a number between 1 and 65535")}if(config.timeout&&(typeof config.timeout!=="number"||config.timeout<0)){errors.push("timeout must be a non-negative number")}const timeouts=["streamTimeout","shellTimeout","fileTimeout"];for(const timeout of timeouts){if(config[timeout]&&(typeof config[timeout]!=="number"||config[timeout]<0)){errors.push(`${timeout} must be a non-negative number`)}}const bufferSizes=["bufferSize","chunkSize"];for(const size of bufferSizes){if(config[size]&&(typeof config[size]!=="number"||config[size]<1024)){errors.push(`${size} must be a number >= 1024`)}}return{valid:errors.length===0,errors}}export{all,get,getApi,getDefaults,init,merge,reset,set,validateConfig};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import{self}from"@cldmv/slothlet/runtime";import net from"node:net";const ADB_PROTOCOL_VERSION=16777216;const ADB_MAX_PAYLOAD=4096;const MSG_CNXN=1314410051;const MSG_AUTH=1213486401;const MSG_OKAY=1497451343;async function create(options){const{host,port,privateKey,adbPublicKey}=options;const socket=new net.Socket;const connection={socket,host,port,authorized:false,connected:false,onUnhandledPacket:null,disconnect:()=>{if(socket&&!socket.destroyed){self.log.debug(`Disconnecting from ${host}:${port}`);socket.destroy()}connection.connected=false;connection.authorized=false}};return new Promise((resolve,reject)=>{const timeout=setTimeout(()=>{socket.destroy();reject(new Error("Connection timeout"))},1e4);socket.connect(port,host,async()=>{try{self.log.debug(`Connected to ${host}:${port}, sending CNXN message...`);const systemIdentity="device::";const cnxnPayload=Buffer.from(systemIdentity);await sendMessage(socket,MSG_CNXN,ADB_PROTOCOL_VERSION,ADB_MAX_PAYLOAD,cnxnPayload);self.log.debug(`${self.config.get("debugArrowSent")} CNXN message sent, waiting for AUTH response...`);const authMessage=await receiveMessage(socket);self.log.debug(`${self.config.get("debugArrowReceived")} Received AUTH message, command:`,authMessage.command.toString(16));if(authMessage.command!==MSG_AUTH){throw new Error(`Expected AUTH message (0x${MSG_AUTH.toString(16)}), got 0x${authMessage.command.toString(16)}`)}self.log.debug(`${self.config.get("debugArrowReceived")} AUTH message received, token length:`,authMessage.data.length);const token=authMessage.data;const signature=await self.auth.sign(token,privateKey);await sendMessage(socket,MSG_AUTH,2,0,signature);const pubKeyBuffer=Buffer.from(adbPublicKey);await sendMessage(socket,MSG_AUTH,3,0,pubKeyBuffer);const okayMessage=await receiveMessage(socket);self.log.debug(`${self.config.get("debugArrowReceived")} After auth, received command:`,okayMessage.command.toString(16));self.log.debug("Expected CNXN:",MSG_CNXN.toString(16),"or OKAY:",MSG_OKAY.toString(16));if(okayMessage.command===MSG_CNXN){const cnxnPayload2=okayMessage.data.toString().replace(/\0/g,"");self.log.debug(`${self.config.get("debugArrowReceived")} Device sent CNXN response - authentication successful!`);self.log.debug(`${self.config.get("debugArrowReceived")} CNXN payload:`,cnxnPayload2);const featuresMatch=cnxnPayload2.match(/features=([^;]+)/);connection.deviceFeatures=featuresMatch?featuresMatch[1].split(","):[];connection.authorized=true;connection.connected=true;self.log.debug("Authentication successful!")}else if(okayMessage.command===MSG_OKAY){self.log.debug(`${self.config.get("debugArrowReceived")} Device sent OKAY response - authentication successful!`);connection.authorized=true;connection.connected=true}else{throw new Error(`Authentication failed - expected CNXN (${MSG_CNXN.toString(16)}) or OKAY (${MSG_OKAY.toString(16)}), got ${okayMessage.command.toString(16)}`)}clearTimeout(timeout);socket.on("data",data=>{if(connection.onUnhandledPacket){connection.onUnhandledPacket(data)}});resolve(connection)}catch(error){clearTimeout(timeout);self.log.debug("Connection error:",error.message);reject(error)}});socket.on("error",error=>{clearTimeout(timeout);self.log.debug("Socket error:",error.message);reject(error)})})}async function sendMessage(socket,command,arg0,arg1,data=Buffer.alloc(0)){const header=Buffer.alloc(24);let offset=0;header.writeUInt32LE(command,offset);offset+=4;header.writeUInt32LE(arg0,offset);offset+=4;header.writeUInt32LE(arg1,offset);offset+=4;header.writeUInt32LE(data.length,offset);offset+=4;header.writeUInt32LE(checksum(data),offset);offset+=4;header.writeUInt32LE((command^4294967295)>>>0,offset);socket.write(header);if(data.length>0){socket.write(data)}}function receiveMessage(socket){return new Promise((resolve,reject)=>{let headerReceived=false;let expectedDataLength=0;let receivedHeader=null;let receivedData=Buffer.alloc(0);const onData=chunk=>{if(!headerReceived){if(chunk.length>=24){receivedHeader=chunk.slice(0,24);self.log.debug("Header bytes:",Array.from(receivedHeader).map(b=>b.toString(16).padStart(2,"0")).join(" "));try{expectedDataLength=receivedHeader.readUInt32LE(12);self.log.debug("Raw data length from header:",expectedDataLength)}catch(readError){reject(new Error(`Failed to read data length from header: ${readError.message}`));return}if(isNaN(expectedDataLength)||expectedDataLength<0||expectedDataLength>1024*1024){reject(new Error(`Invalid data length: ${expectedDataLength} (header: ${receivedHeader.toString("hex")})`));return}headerReceived=true;if(chunk.length>24){receivedData=Buffer.concat([receivedData,chunk.slice(24)])}}}else{receivedData=Buffer.concat([receivedData,chunk])}if(headerReceived&&receivedData.length>=expectedDataLength){socket.removeListener("data",onData);try{const command=receivedHeader.readUInt32LE(0);const arg0=receivedHeader.readUInt32LE(4);const arg1=receivedHeader.readUInt32LE(8);const dataLength=receivedHeader.readUInt32LE(12);const headerChecksum=receivedHeader.readUInt32LE(16);const magic=receivedHeader.readUInt32LE(20);self.log.debug("Parsed header - command:",command.toString(16),"arg0:",arg0,"arg1:",arg1,"length:",dataLength,"checksum:",headerChecksum,"magic:",magic.toString(16));const expectedMagic=(command^4294967295)>>>0;if(magic!==expectedMagic){self.log.debug("Magic mismatch - expected:",expectedMagic.toString(16),"got:",magic.toString(16))}const data=receivedData.slice(0,expectedDataLength);if(data.length>0){const calculatedChecksum=checksum(data);self.log.debug("Data checksum - expected:",headerChecksum,"calculated:",calculatedChecksum)}resolve({command,arg0,arg1,data})}catch(parseError){reject(new Error(`Failed to parse message header: ${parseError.message}`))}}};socket.on("data",onData);socket.on("error",reject)})}function checksum(data){let sum=0;for(let i=0;i<data.length;i++){sum=sum+data[i]>>>0}return sum}export{create};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import{self}from"@cldmv/slothlet/runtime";const connections=new Map;async function connect(host,port=5555,options={}){const deviceId=`${host}:${port}`;if(connections.has(deviceId)){const existing=connections.get(deviceId);if(existing.isConnected()){return existing}else{connections.delete(deviceId)}}const keys=await self.auth.getKeys(options.keyDir);const connection=await self.connection.create({host,port,publicKey:keys.publicKey,privateKey:keys.privateKey,adbPublicKey:keys.adbPublicKey});const streamManager=await self.stream.create(connection.socket);connection.onUnhandledPacket=packet=>streamManager.handlePacket(packet);const device={host,port,deviceId,connection,streamManager,isConnected:()=>connection&&connection.connected,disconnect:()=>{if(connection){connection.disconnect();connections.delete(deviceId)}},shell:async(command,shellOptions={})=>{if(!device.isConnected()){throw new Error("Device not connected")}if(!connection.authorized){throw new Error("Device not authorized. Please accept authorization dialog.")}return await self.shell.execute(connection.socket,streamManager,command,{...shellOptions,deviceFeatures:connection.deviceFeatures||[]})},startStreamingShell:(command,shellOptions={})=>{if(!device.isConnected()){throw new Error("Device not connected")}if(!connection.authorized){throw new Error("Device not authorized. Please accept authorization dialog.")}return self.shell.startStreaming(connection.socket,streamManager,command,shellOptions)},startInteractiveShell:(command,shellOptions={})=>{if(!device.isConnected()){throw new Error("Device not connected")}if(!connection.authorized){throw new Error("Device not authorized. Please accept authorization dialog.")}return self.shell.startInteractive(connection.socket,streamManager,command,shellOptions)},push:async(localPath,remotePath,transferOptions={})=>{if(!device.isConnected()){throw new Error("Device not connected")}if(!connection.authorized){throw new Error("Device not authorized. Please accept authorization dialog.")}return await self.files.push(connection.socket,streamManager,localPath,remotePath,transferOptions)},pull:async(remotePath,localPath,transferOptions={})=>{if(!device.isConnected()){throw new Error("Device not connected")}if(!connection.authorized){throw new Error("Device not authorized. Please accept authorization dialog.")}return await self.files.pull(connection.socket,streamManager,remotePath,localPath,transferOptions)},list:async remotePath=>{if(!device.isConnected()){throw new Error("Device not connected")}if(!connection.authorized){throw new Error("Device not authorized. Please accept authorization dialog.")}return await self.files.list(connection.socket,streamManager,remotePath)},stat:async remotePath=>{if(!device.isConnected()){throw new Error("Device not connected")}if(!connection.authorized){throw new Error("Device not authorized. Please accept authorization dialog.")}return await self.files.stat(connection.socket,streamManager,remotePath)},get ls(){return(path=".")=>device.shell(`ls -la "${path}"`)},get pwd(){return()=>device.shell("pwd")},get getprop(){return(prop=null)=>device.shell(prop?`getprop "${prop}"`:"getprop")},get getModel(){return()=>device.shell("getprop ro.product.model")},get getAndroidVersion(){return()=>device.shell("getprop ro.build.version.release")},get getBattery(){return()=>device.shell("dumpsys battery")},get screenshot(){return(filename="/sdcard/screenshot.png")=>device.shell(`screencap -p "${filename}"`)},get logcat(){return(logOptions={})=>device.startStreamingShell("logcat",logOptions)},get top(){return(topOptions={})=>device.startStreamingShell("top -m 10",topOptions)},get keypress(){return key=>device.shell(`input keyevent ${key}`)},get launchApp(){return(packageName,activity="")=>{const activityArg=activity?`/${activity}`:"";return device.shell(`am start -n ${packageName}${activityArg}`)}}};connections.set(deviceId,device);return device}function list(){return Array.from(connections.values()).filter(device=>device.isConnected())}function disconnect(host,port=5555){const deviceId=`${host}:${port}`;const device=connections.get(deviceId);if(device){device.disconnect();return true}return false}function disconnectAll(){let count=0;for(const device of connections.values()){device.disconnect();count++}connections.clear();return count}export{connect,disconnect,disconnectAll,list};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import{self}from"@cldmv/slothlet/runtime";async function push(___socket,___streamManager,___localPath,___remotePath,___options={}){throw new Error("File push not yet implemented in slothlet structure")}async function pull(___socket,___streamManager,___remotePath,___localPath,___options={}){throw new Error("File pull not yet implemented in slothlet structure")}async function list(___socket,___streamManager,___remotePath){throw new Error("Directory listing not yet implemented in slothlet structure")}async function stat(___socket,___streamManager,___remotePath){throw new Error("File stat not yet implemented in slothlet structure")}async function mkdir(socket,streamManager,remotePath,mode=493){const command=`mkdir -p "${remotePath}" && chmod ${mode.toString(8)} "${remotePath}"`;return await self.shell.execute(socket,streamManager,command)}async function remove(socket,streamManager,remotePath,recursive=false){const flag=recursive?"-rf":"-f";const command=`rm ${flag} "${remotePath}"`;return await self.shell.execute(socket,streamManager,command)}async function move(socket,streamManager,sourcePath,destPath){const command=`mv "${sourcePath}" "${destPath}"`;return await self.shell.execute(socket,streamManager,command)}async function copy(socket,streamManager,sourcePath,destPath,recursive=false){const flag=recursive?"-r":"";const command=`cp ${flag} "${sourcePath}" "${destPath}"`;return await self.shell.execute(socket,streamManager,command)}async function chmod(socket,streamManager,remotePath,mode,recursive=false){const flag=recursive?"-R":"";const command=`chmod ${flag} ${mode.toString(8)} "${remotePath}"`;return await self.shell.execute(socket,streamManager,command)}async function diskUsage(socket,streamManager,path="/"){const command=`df -h "${path}"`;return await self.shell.execute(socket,streamManager,command)}async function find(socket,streamManager,path,pattern,options={}){let command=`find "${path}"`;if(options.maxDepth){command+=` -maxdepth ${options.maxDepth}`}if(options.type){command+=` -type ${options.type}`}command+=` -name "${pattern}"`;return await self.shell.execute(socket,streamManager,command)}export{chmod,copy,diskUsage,find,list,mkdir,move,pull,push,remove,stat};
|
package/dist/api/log.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import{self}from"@cldmv/slothlet/runtime";function getConfig(){return self.config}function getPrefix(){return getConfig().get("eventPrefix","adb")}function debug(...args){if(getConfig().get("silent"))return;if(!getConfig().get("debug"))return;console.log(`[${getPrefix()}][DEBUG]`,...args)}function verbose(...args){if(getConfig().get("silent"))return;if(!getConfig().get("verbose")&&!getConfig().get("debug"))return;console.log(`[${getPrefix()}][VERBOSE]`,...args)}function info(...args){if(getConfig().get("silent"))return;console.log(`[${getPrefix()}][INFO]`,...args)}function warn(...args){if(getConfig().get("silent"))return;console.warn(`[${getPrefix()}][WARN]`,...args)}function error(...args){if(getConfig().get("silent"))return;console.error(`[${getPrefix()}][ERROR]`,...args)}function child(context){const childPrefix=`[${getPrefix()}][${context}]`;const cfg=getConfig();return{debug:(...args)=>{if(cfg.get("silent"))return;if(!cfg.get("debug"))return;console.log(`${childPrefix}[DEBUG]`,...args)},verbose:(...args)=>{if(cfg.get("silent"))return;if(!cfg.get("verbose")&&!cfg.get("debug"))return;console.log(`${childPrefix}[VERBOSE]`,...args)},info:(...args)=>{if(cfg.get("silent"))return;console.log(`${childPrefix}[INFO]`,...args)},warn:(...args)=>{if(cfg.get("silent"))return;console.warn(`${childPrefix}[WARN]`,...args)},error:(...args)=>{if(cfg.get("silent"))return;console.error(`${childPrefix}[ERROR]`,...args)}}}function getApi(){return{debug,verbose,info,warn,error,child}}export{child,debug,error,getApi,info,verbose,warn};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import{self}from"@cldmv/slothlet/runtime";async function execute(socket,streamManager,command,options={}){const{timeout=3e4,encoding="utf8",deviceFeatures=[]}=options;return new Promise((resolve,reject)=>{try{let output=Buffer.alloc(0);let streamId=null;let commandTimeout;let commandCompleted=false;let protocolUsed;const supportsShellV2=deviceFeatures.includes("shell_v2");if(supportsShellV2){protocolUsed=`shell,v2:${command}`}else{protocolUsed=`shell:${command}`}if(self.config.get("debug")){self.log.debug(`Executing shell command: ${protocolUsed}`)}const destBuffer=Buffer.from(protocolUsed);const openPacket=Buffer.alloc(24+destBuffer.length);openPacket.writeUInt32LE(1313165391,0);openPacket.writeUInt32LE(12345,4);openPacket.writeUInt32LE(0,8);openPacket.writeUInt32LE(destBuffer.length,12);let checksum=0;for(let i=0;i<destBuffer.length;i++){checksum+=destBuffer[i]}openPacket.writeUInt32LE(checksum&4294967295,16);openPacket.writeUInt32LE(~1313165391>>>0,20);destBuffer.copy(openPacket,24);socket.write(openPacket);commandTimeout=setTimeout(()=>{if(!commandCompleted){commandCompleted=true;socket.removeAllListeners("data");reject(new Error(`Command timeout: ${command}`))}},timeout);let responseBuffer=Buffer.alloc(0);let originalHandlers=[];const existingHandlers=socket.listeners("data");existingHandlers.forEach(handler=>{socket.removeListener("data",handler);originalHandlers.push(handler)});const dataHandler=chunk=>{if(commandCompleted)return;responseBuffer=Buffer.concat([responseBuffer,chunk]);while(responseBuffer.length>=24){const cmd=responseBuffer.readUInt32LE(0);const arg0=responseBuffer.readUInt32LE(4);const arg1=responseBuffer.readUInt32LE(8);const dataLength=responseBuffer.readUInt32LE(12);if(responseBuffer.length<24+dataLength)break;const packetData=dataLength>0?responseBuffer.slice(24,24+dataLength):null;responseBuffer=responseBuffer.slice(24+dataLength);if(self.config.get("debug")){const commandName=cmd===1497451343?"OKAY":cmd===1163154007?"WRTE":cmd===1163086915?"CLSE":`0x${cmd.toString(16)}`;self.log.debug(`${self.config.get("debugArrowReceived")} ${commandName} arg0:${arg0} arg1:${arg1} len:${dataLength}`)}if(cmd===1497451343&&!streamId){streamId=arg1;if(self.config.get("debug")){self.log.debug(`Shell stream opened with ID: ${streamId}`)}}else if(cmd===1163154007&&arg1===12345){if(packetData){output=Buffer.concat([output,packetData]);if(self.config.get("debug")){self.log.debug(`Received ${packetData.length} bytes of output`)}}}else if(cmd===1163086915&&arg1===12345){if(self.config.get("debug")){self.log.debug("Shell stream closed by device")}commandCompleted=true;clearTimeout(commandTimeout);socket.removeListener("data",dataHandler);originalHandlers.forEach(handler=>{socket.on("data",handler)});resolve(output.toString(encoding));return}}};socket.on("data",dataHandler)}catch(error){reject(error)}})}function startStreaming(socket,streamManager,command,options={}){const{onData,onError,onEnd}=options;let stream=null;const control={stop(){if(stream){stream.close();stream=null}}};(async()=>{try{stream=await streamManager.openStream(`shell:${command}`);stream.on("data",data=>{if(onData)onData(data.toString())});stream.on("close",()=>{if(onEnd)onEnd()});stream.on("error",error=>{if(onError)onError(error)})}catch(error){if(onError)onError(error)}})();return control}function startInteractive(socket,streamManager,command,options={}){const{onData,onError,onEnd}=options;let stream=null;const control={async sendInput(input){if(stream){await stream.write(input)}},stop(){if(stream){stream.close();stream=null}}};(async()=>{try{stream=await streamManager.openStream(`shell:${command}`);stream.on("data",data=>{if(onData)onData(data.toString())});stream.on("close",()=>{if(onEnd)onEnd()});stream.on("error",error=>{if(onError)onError(error)})}catch(error){if(onError)onError(error)}})();return control}const commands={ls:async(socket,streamManager,path=".")=>{return await execute(socket,streamManager,`ls -la "${path}"`)},pwd:async(socket,streamManager)=>{return await execute(socket,streamManager,"pwd")},getprop:async(socket,streamManager,prop=null)=>{const cmd=prop?`getprop "${prop}"`:"getprop";return await execute(socket,streamManager,cmd)},getModel:async(socket,streamManager)=>{return await execute(socket,streamManager,"getprop ro.product.model")},getAndroidVersion:async(socket,streamManager)=>{return await execute(socket,streamManager,"getprop ro.build.version.release")},getBattery:async(socket,streamManager)=>{return await execute(socket,streamManager,"dumpsys battery")},screenshot:async(socket,streamManager,filename="/sdcard/screenshot.png")=>{return await execute(socket,streamManager,`screencap -p "${filename}"`)},logcat:(socket,streamManager,options={})=>{return startStreaming(socket,streamManager,"logcat",options)},top:(socket,streamManager,options={})=>{return startStreaming(socket,streamManager,"top -m 10",options)},keypress:async(socket,streamManager,key)=>{return await execute(socket,streamManager,`input keyevent ${key}`)},launchApp:async(socket,streamManager,packageName,activity="")=>{const activityArg=activity?`/${activity}`:"";return await execute(socket,streamManager,`am start -n ${packageName}${activityArg}`)},killApp:async(socket,streamManager,packageName)=>{return await execute(socket,streamManager,`am force-stop ${packageName}`)},installApk:async(socket,streamManager,apkPath,flags=[])=>{const flagsStr=flags.length>0?` ${flags.join(" ")}`:"";return await execute(socket,streamManager,`pm install${flagsStr} "${apkPath}"`)},uninstallApp:async(socket,streamManager,packageName)=>{return await execute(socket,streamManager,`pm uninstall ${packageName}`)}};export{commands,execute,startInteractive,startStreaming};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import{self}from"@cldmv/slothlet/runtime";import{EventEmitter}from"node:events";const MSG_OPEN=1313165391;const MSG_WRTE=1163154007;const MSG_CLSE=1163086915;const MSG_OKAY=1497451343;function getCommandName(command){switch(command){case MSG_OPEN:return"OPEN";case MSG_WRTE:return"WRTE";case MSG_CLSE:return"CLSE";case MSG_OKAY:return"OKAY";default:return`0x${command.toString(16).toUpperCase()}`}}function create(socket){const streams=new Map;let nextStreamId=1;const manager={socket,streams,nextStreamId,async openStream(destination){const localId=nextStreamId++;const stream=new AdbStream(localId,0,socket,manager);streams.set(localId,stream);const destBuffer=Buffer.from(destination);await sendMessage(socket,MSG_OPEN,localId,0,destBuffer);return new Promise((resolve,reject)=>{stream.once("ready",()=>resolve(stream));stream.once("error",reject);setTimeout(()=>{if(!stream.ready){streams.delete(localId);reject(new Error("Stream open timeout"))}},5e3)})},handlePacket(data){let offset=0;while(offset<data.length){if(data.length-offset<24)break;const command=data.readUInt32LE(offset);const arg0=data.readUInt32LE(offset+4);const arg1=data.readUInt32LE(offset+8);const dataLength=data.readUInt32LE(offset+12);if(data.length-offset<24+dataLength)break;const packetData=data.slice(offset+24,offset+24+dataLength);offset+=24+dataLength;const commandName=getCommandName(command);if(self.config.get("debug")){self.log.debug(`${self.config.get("debugArrowReceived")} ${commandName} arg0:${arg0} arg1:${arg1} len:${dataLength}`)}const stream=streams.get(arg1);if(stream){stream.handleMessage(command,arg0,arg1,packetData)}}},closeStream(streamId){const stream=streams.get(streamId);if(stream){stream.close();streams.delete(streamId)}}};return manager}class AdbStream extends EventEmitter{constructor(localId,remoteId,socket,manager){super();this.localId=localId;this.remoteId=remoteId;this.socket=socket;this.manager=manager;this.ready=false;this.closed=false}handleMessage(command,arg0,arg1,data){switch(command){case MSG_OKAY:if(!this.ready){this.remoteId=arg0;this.ready=true;this.emit("ready")}else{this.emit("ack")}break;case MSG_WRTE:this.emit("data",data);sendMessage(this.socket,MSG_OKAY,this.localId,this.remoteId);break;case MSG_CLSE:this.closed=true;this.emit("close");break}}async write(data){if(this.closed||!this.ready){throw new Error("Stream not ready or closed")}const buffer=Buffer.isBuffer(data)?data:Buffer.from(data);await sendMessage(this.socket,MSG_WRTE,this.remoteId,this.localId,buffer)}close(){if(!this.closed){this.closed=true;sendMessage(this.socket,MSG_CLSE,this.remoteId,this.localId);this.emit("close")}}}async function sendMessage(socket,command,arg0,arg1,data=Buffer.alloc(0)){const header=Buffer.alloc(24);let offset=0;header.writeUInt32LE(command,offset);offset+=4;header.writeUInt32LE(arg0,offset);offset+=4;header.writeUInt32LE(arg1,offset);offset+=4;header.writeUInt32LE(data.length,offset);offset+=4;header.writeUInt32LE(checksum(data),offset);offset+=4;header.writeUInt32LE((command^4294967295)>>>0,offset);const commandName=getCommandName(command);if(self.config.get("debug")){self.log.debug(`${self.config.get("debugArrowSent")} ${commandName} arg0:${arg0} arg1:${arg1} len:${data.length}`)}socket.write(header);if(data.length>0){socket.write(data)}}function checksum(data){let sum=0;for(let i=0;i<data.length;i++){sum+=data[i]}return sum&4294967295}export{create};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function parseProperties(propOutput){const props={};const lines=propOutput.split("\n");for(const line of lines){const match=line.match(/^\[([^\]]+)\]:\s*\[([^\]]*)\]$/);if(match){const[,key,value]=match;props[key]=value}}return props}function parseListing(lsOutput){const entries=[];const lines=lsOutput.split("\n").filter(line=>line.trim());const startIdx=lines[0]?.startsWith("total")?1:0;for(let i=startIdx;i<lines.length;i++){const line=lines[i];const match=line.match(/^([drwx-]+)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(.+?)\s+(.+)$/);if(match){const[,permissions,links,owner,group,size,dateTime,name]=match;entries.push({name,permissions,links:parseInt(links),owner,group,size:parseInt(size),dateTime,isDirectory:permissions.startsWith("d"),isFile:permissions.startsWith("-"),isSymlink:permissions.startsWith("l")})}}return entries}function parseBattery(batteryOutput){const battery={};const lines=batteryOutput.split("\n");for(const line of lines){const match=line.match(/^\s*([^:]+):\s*(.+)$/);if(match){let[,key,value]=match;key=key.trim();value=value.trim();if(/^\d+$/.test(value)){value=parseInt(value)}battery[key]=value}}return battery}function formatBytes(bytes,decimals=2){if(bytes===0)return"0 Bytes";const k=1024;const dm=decimals<0?0:decimals;const sizes=["Bytes","KB","MB","GB","TB"];const i=Math.floor(Math.log(bytes)/Math.log(k));return parseFloat((bytes/Math.pow(k,i)).toFixed(dm))+" "+sizes[i]}function delay(ms){return new Promise(resolve=>setTimeout(resolve,ms))}async function retry(fn,options={}){const{baseDelay=1e3,maxDelay=1e4,shouldRetry=()=>true}=options;let{maxRetries=3}=options;if(maxRetries<0){console.warn(`retry: maxRetries must be >= 0, got ${maxRetries} - clamping to 0`);maxRetries=0}let lastError;for(let attempt=0;attempt<=maxRetries;attempt++){try{return await fn()}catch(error){lastError=error;if(attempt===maxRetries||!shouldRetry(error)){throw error}const delayMs=Math.min(baseDelay*Math.pow(2,attempt),maxDelay);await delay(delayMs)}}throw lastError}function isValidIP(ip){const parts=ip.split(".");if(parts.length!==4)return false;return parts.every(part=>{const num=parseInt(part,10);return num>=0&&num<=255&&part===num.toString()})}function isValidPort(port){return Number.isInteger(port)&&port>=1&&port<=65535}function parseHostPort(hostPort,defaultPort=5555){const colonIndex=hostPort.lastIndexOf(":");if(colonIndex===-1){return{host:hostPort,port:defaultPort}}const host=hostPort.substring(0,colonIndex);const portStr=hostPort.substring(colonIndex+1);const port=parseInt(portStr,10);if(!isValidPort(port)){throw new Error(`Invalid port: ${portStr}`)}return{host,port}}function escapeShell(str){return str.replace(/[\\$`"]/g,"\\$&")}function timeout(ms,message="Operation timed out"){return new Promise((_,reject)=>{setTimeout(()=>reject(new Error(message)),ms)})}function withTimeout(promise,ms,message){return Promise.race([promise,timeout(ms,message)])}export{delay,escapeShell,formatBytes,isValidIP,isValidPort,parseBattery,parseHostPort,parseListing,parseProperties,retry,timeout,withTimeout};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import slothlet from"@cldmv/slothlet";import path from"node:path";import{fileURLToPath}from"node:url";const __filename=fileURLToPath(import.meta.url);const __dirname=path.dirname(__filename);async function createDroidSock(options={}){const{mode="eager",context={},config={},...slothletOptions}=options;const api=await slothlet({base:path.join(__dirname,"api"),mode,runtime:"async",context:{...context},debug:false,sanitize:{lowerFirst:false,rules:{leave:["ADB","TCP","USB","Auth","Sync"],upper:["adb*","tcp*","usb*"]}},...slothletOptions});if(Object.keys(config).length>0){api.config.init(config)}return api}export{createDroidSock as default};
|
package/index.cjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @Project: @cldmv/droidsock
|
|
3
3
|
* @Filename: /index.cjs
|
|
4
|
-
* @Date: 2025-11-
|
|
5
|
-
* @Author:
|
|
4
|
+
* @Date: 2025-11-21T15:41:06-08:00 (1763768466)
|
|
5
|
+
* @Author: Shinrai <CLDMV>
|
|
6
6
|
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
7
|
* -----
|
|
8
|
-
* @Last modified by:
|
|
9
|
-
* @Last modified time:
|
|
8
|
+
* @Last modified by: Shinrai <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-08-30 21:00:34 -07:00 (1788148834)
|
|
10
10
|
* -----
|
|
11
|
-
* @Copyright: Copyright (c) 2013-
|
|
11
|
+
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -23,15 +23,13 @@
|
|
|
23
23
|
const { createRequire } = require("module");
|
|
24
24
|
const requireESM = createRequire(__filename);
|
|
25
25
|
|
|
26
|
-
const { default:
|
|
26
|
+
const { default: droidsock } = requireESM("./index.mjs");
|
|
27
27
|
|
|
28
|
-
// Export main function
|
|
29
|
-
module.exports =
|
|
30
|
-
module.exports.createDroidSock =
|
|
31
|
-
module.exports.connect = connect;
|
|
32
|
-
module.exports.listDevices = listDevices;
|
|
28
|
+
// Export main function - the quick path, also callable with options
|
|
29
|
+
module.exports = droidsock; // Default export
|
|
30
|
+
module.exports.createDroidSock = droidsock;
|
|
33
31
|
|
|
34
32
|
// Common DroidSock aliases
|
|
35
|
-
module.exports.DroidSock =
|
|
36
|
-
module.exports.ADB =
|
|
37
|
-
module.exports.AndroidDebugBridge =
|
|
33
|
+
module.exports.DroidSock = droidsock;
|
|
34
|
+
module.exports.ADB = droidsock;
|
|
35
|
+
module.exports.AndroidDebugBridge = droidsock;
|
package/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @Project: @cldmv/droidsock
|
|
3
3
|
* @Filename: /index.mjs
|
|
4
|
-
* @Date: 2025-11-
|
|
5
|
-
* @Author:
|
|
4
|
+
* @Date: 2025-11-21T15:41:06-08:00 (1763768466)
|
|
5
|
+
* @Author: Shinrai <CLDMV>
|
|
6
6
|
* @Email: <Shinrai@users.noreply.github.com>
|
|
7
7
|
* -----
|
|
8
|
-
* @Last modified by:
|
|
9
|
-
* @Last modified time:
|
|
8
|
+
* @Last modified by: Shinrai <CLDMV> (Shinrai@users.noreply.github.com)
|
|
9
|
+
* @Last modified time: 2026-08-30 21:00:34 -07:00 (1788148834)
|
|
10
10
|
* -----
|
|
11
|
-
* @Copyright: Copyright (c) 2013-
|
|
11
|
+
* @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
// Development environment check (must happen before droidsock imports)
|
|
@@ -21,37 +21,22 @@
|
|
|
21
21
|
})();
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Creates a DroidSock instance for ADB communication
|
|
24
|
+
* Creates a DroidSock instance for ADB communication. This is the default export - the
|
|
25
|
+
* quick path - and also available under the explicit name `createDroidSock` for callers
|
|
26
|
+
* who prefer it.
|
|
25
27
|
* @param {object} [options={}] - Configuration options
|
|
26
28
|
* @returns {Promise<object>} DroidSock instance
|
|
27
29
|
*/
|
|
28
|
-
|
|
30
|
+
async function droidsock(options = {}) {
|
|
29
31
|
// Dynamic import after environment check
|
|
30
32
|
const mod = await import("@cldmv/droidsock/main");
|
|
31
|
-
|
|
32
|
-
return await createDroidSockImpl(options);
|
|
33
|
+
return await mod.default(options);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* @param {string} deviceId - Device ID to connect to
|
|
38
|
-
* @returns {Promise<object>} Connected device instance
|
|
39
|
-
*/
|
|
40
|
-
export async function connect(deviceId) {
|
|
41
|
-
const mod = await import("@cldmv/droidsock/main");
|
|
42
|
-
return mod.connect(deviceId);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* List available devices
|
|
47
|
-
* @returns {Promise<Array>} List of available devices
|
|
48
|
-
*/
|
|
49
|
-
export async function listDevices() {
|
|
50
|
-
const mod = await import("@cldmv/droidsock/main");
|
|
51
|
-
return mod.listDevices();
|
|
52
|
-
}
|
|
36
|
+
export default droidsock;
|
|
37
|
+
export { droidsock as createDroidSock };
|
|
53
38
|
|
|
54
39
|
// Named export aliases
|
|
55
|
-
export {
|
|
56
|
-
export {
|
|
57
|
-
export {
|
|
40
|
+
export { droidsock as DroidSock };
|
|
41
|
+
export { droidsock as ADB };
|
|
42
|
+
export { droidsock as AndroidDebugBridge };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cldmv/droidsock",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Complete Node.js implementation of the Android Debug Bridge (ADB) protocol",
|
|
5
5
|
"main": "./index.cjs",
|
|
6
6
|
"module": "./index.mjs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"import": "./devcheck.mjs"
|
|
17
17
|
},
|
|
18
18
|
"./main": {
|
|
19
|
-
"
|
|
19
|
+
"droidsock-dev": {
|
|
20
20
|
"types": "./types/src/droidsock.d.mts",
|
|
21
21
|
"import": "./src/droidsock.mjs"
|
|
22
22
|
},
|
|
@@ -29,15 +29,21 @@
|
|
|
29
29
|
"node": ">=18.12"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"test": "vitest run --config .configs/vitest.config.mjs",
|
|
33
|
-
"test:run": "vitest run",
|
|
34
|
-
"test:coverage": "vitest run --coverage",
|
|
35
|
-
"test:types": "tsc --noEmit --project .configs/tsconfig.dts.jsonc",
|
|
36
|
-
"lint": "eslint --config .configs/eslint.config.mjs .",
|
|
37
32
|
"build": "node build.mjs",
|
|
38
33
|
"build:types": "tsc --project .configs/tsconfig.dts.jsonc",
|
|
39
|
-
"build:ci": "npm run build
|
|
40
|
-
"
|
|
34
|
+
"build:ci": "npm run build && npm run build:types && npm run test:types",
|
|
35
|
+
"test": "node tests/run-vitest.mjs",
|
|
36
|
+
"test:watch": "vitest --config .configs/vitest.config.mjs",
|
|
37
|
+
"test:types": "tsc --noEmit --project .configs/tsconfig.dts.jsonc",
|
|
38
|
+
"coverage": "node tests/run-vitest.mjs --coverage-quiet",
|
|
39
|
+
"ci:coverage": "npm run coverage",
|
|
40
|
+
"lint": "eslint --config .configs/eslint.config.mjs .",
|
|
41
|
+
"lint:fix": "eslint --config .configs/eslint.config.mjs . --fix",
|
|
42
|
+
"format": "prettier --config .configs/.prettierrc --write .",
|
|
43
|
+
"format:check": "prettier --config .configs/.prettierrc --check .",
|
|
44
|
+
"fix:headers": "node tools/fix-headers.mjs",
|
|
45
|
+
"prepare": "node -e \"import('./.githooks/install.mjs').catch(()=>{})\"",
|
|
46
|
+
"precommit": "npm run build && npm run build:types && npm run test:types && npm run lint && npm run format:check && npm run test"
|
|
41
47
|
},
|
|
42
48
|
"keywords": [
|
|
43
49
|
"adb",
|
|
@@ -98,21 +104,25 @@
|
|
|
98
104
|
],
|
|
99
105
|
"sideEffects": false,
|
|
100
106
|
"devDependencies": {
|
|
101
|
-
"@eslint
|
|
102
|
-
"@
|
|
103
|
-
"@
|
|
104
|
-
"@
|
|
105
|
-
"@
|
|
106
|
-
"@
|
|
107
|
-
"@
|
|
108
|
-
"@
|
|
109
|
-
"eslint": "^
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
107
|
+
"@cldmv/eslint-plugin-jsonv": "^1.0.3",
|
|
108
|
+
"@cldmv/fix-headers": "^1.3.10",
|
|
109
|
+
"@cldmv/jsonv": "^1.0.2",
|
|
110
|
+
"@cldmv/prettier-plugin-jsonv": "^1.0.1",
|
|
111
|
+
"@cldmv/vitest-runner": "^1.2.0",
|
|
112
|
+
"@eslint/css": "^1.4.0",
|
|
113
|
+
"@eslint/js": "^10.0.1",
|
|
114
|
+
"@eslint/json": "^2.0.1",
|
|
115
|
+
"@eslint/markdown": "^8.0.3",
|
|
116
|
+
"@types/node": "^26.4.0",
|
|
117
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
118
|
+
"esbuild": "^0.28.2",
|
|
119
|
+
"eslint": "^10.9.1",
|
|
120
|
+
"globals": "^17.11.0",
|
|
121
|
+
"prettier": "^3.9.6",
|
|
122
|
+
"typescript": "^6.0.3",
|
|
123
|
+
"vitest": "^4.1.11"
|
|
114
124
|
},
|
|
115
125
|
"dependencies": {
|
|
116
|
-
"@cldmv/slothlet": "^
|
|
126
|
+
"@cldmv/slothlet": "^3.15.0"
|
|
117
127
|
}
|
|
118
128
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function formatAdbPublicKey(publicKey: any): string;
|
|
2
|
+
export function generateKeys(keySize?: number, saveDir?: null): {
|
|
3
|
+
privateKey: any;
|
|
4
|
+
publicKey: any;
|
|
5
|
+
adbPublicKey: string;
|
|
6
|
+
};
|
|
7
|
+
export function getKeys(keyDir: any): {
|
|
8
|
+
privateKey: any;
|
|
9
|
+
publicKey: any;
|
|
10
|
+
adbPublicKey: any;
|
|
11
|
+
};
|
|
12
|
+
export function sign(token: any, privateKey: any): any;
|
|
13
|
+
export function validateAuth(token: any, privateKey: any): boolean;
|
|
14
|
+
//# sourceMappingURL=auth.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.mts","sourceRoot":"","sources":["../../../dist/api/auth.mjs"],"names":[],"mappings":"AAgBqmH,2DAAkyC;AAApnI;;;;EAA2kB;AAAxsC;;;;EAA6nB;AAA2kB,uDAAw6C;AAA6lB,mEAAkQ"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function all(): any;
|
|
2
|
+
export function get(key: any, defaultValue?: undefined): any;
|
|
3
|
+
export function getApi(): {
|
|
4
|
+
init: typeof init;
|
|
5
|
+
get: typeof get;
|
|
6
|
+
set: typeof set;
|
|
7
|
+
merge: typeof merge;
|
|
8
|
+
reset: typeof reset;
|
|
9
|
+
all: typeof all;
|
|
10
|
+
};
|
|
11
|
+
export function getDefaults(): {
|
|
12
|
+
host: string;
|
|
13
|
+
port: number;
|
|
14
|
+
timeout: number;
|
|
15
|
+
retryAttempts: number;
|
|
16
|
+
retryDelay: number;
|
|
17
|
+
keyDir: null;
|
|
18
|
+
autoGenerateKeys: boolean;
|
|
19
|
+
keySize: number;
|
|
20
|
+
debug: boolean;
|
|
21
|
+
verbose: boolean;
|
|
22
|
+
silent: boolean;
|
|
23
|
+
debugArrowSent: string;
|
|
24
|
+
debugArrowReceived: string;
|
|
25
|
+
streamTimeout: number;
|
|
26
|
+
maxStreams: number;
|
|
27
|
+
shellTimeout: number;
|
|
28
|
+
shellEncoding: string;
|
|
29
|
+
fileTimeout: number;
|
|
30
|
+
chunkSize: number;
|
|
31
|
+
emitEvents: boolean;
|
|
32
|
+
eventPrefix: string;
|
|
33
|
+
bufferSize: number;
|
|
34
|
+
keepAlive: boolean;
|
|
35
|
+
keepAliveInterval: number;
|
|
36
|
+
};
|
|
37
|
+
export function init(options?: {}): {
|
|
38
|
+
init: typeof init;
|
|
39
|
+
get: typeof get;
|
|
40
|
+
set: typeof set;
|
|
41
|
+
merge: typeof merge;
|
|
42
|
+
reset: typeof reset;
|
|
43
|
+
all: typeof all;
|
|
44
|
+
};
|
|
45
|
+
export function merge(options: any): void;
|
|
46
|
+
export function reset(): void;
|
|
47
|
+
export function set(key: any, value: any): void;
|
|
48
|
+
export function validateConfig(config: any): {
|
|
49
|
+
valid: boolean;
|
|
50
|
+
errors: string[];
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=config.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.mts","sourceRoot":"","sources":["../../../dist/api/config.mjs"],"names":[],"mappings":"AAgBw+C,2BAA6E;AAA5gC,6DAA8O;AAA8xB;;;;;;;EAAuD;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;EAAiD;AAAhuC;;;;;;;EAA4G;AAAie,0CAA0a;AAAA,8BAAoD;AAAjtB,gDAAmP;AAAmpB;;;EAAswB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.mts","sourceRoot":"","sources":["../../../dist/api/connection.mjs"],"names":[],"mappings":"AAgBiN,mDAAg/F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.d.mts","sourceRoot":"","sources":["../../../dist/api/device.mjs"],"names":[],"mappings":"AAgBoE,8EAA4wH;AAA6F,8DAAoK;AAAA,wCAA4I;AAA7Y,8BAA6F"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function chmod(socket: any, streamManager: any, remotePath: any, mode: any, recursive?: boolean): Promise<any>;
|
|
2
|
+
export function copy(socket: any, streamManager: any, sourcePath: any, destPath: any, recursive?: boolean): Promise<any>;
|
|
3
|
+
export function diskUsage(socket: any, streamManager: any, path?: string): Promise<any>;
|
|
4
|
+
export function find(socket: any, streamManager: any, path: any, pattern: any, options?: {}): Promise<any>;
|
|
5
|
+
export function list(___socket: any, ___streamManager: any, ___remotePath: any): Promise<void>;
|
|
6
|
+
export function mkdir(socket: any, streamManager: any, remotePath: any, mode?: number): Promise<any>;
|
|
7
|
+
export function move(socket: any, streamManager: any, sourcePath: any, destPath: any): Promise<any>;
|
|
8
|
+
export function pull(___socket: any, ___streamManager: any, ___remotePath: any, ___localPath: any, ___options?: {}): Promise<void>;
|
|
9
|
+
export function push(___socket: any, ___streamManager: any, ___localPath: any, ___remotePath: any, ___options?: {}): Promise<void>;
|
|
10
|
+
export function remove(socket: any, streamManager: any, remotePath: any, recursive?: boolean): Promise<any>;
|
|
11
|
+
export function stat(___socket: any, ___streamManager: any, ___remotePath: any): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=files.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.mts","sourceRoot":"","sources":["../../../dist/api/files.mjs"],"names":[],"mappings":"AAgB86C,sHAAwO;AAA1c,yHAAkO;AAAwO,wFAAsJ;AAAA,2GAA8S;AAAhvD,+FAA6I;AAAqI,qGAAmN;AAAgN,oGAA6K;AAAlgC,mIAAgK;AAAhU,mIAAgK;AAAqoB,4GAAgN;AAAxiB,+FAAqI"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function child(context: any): {
|
|
2
|
+
debug: (...args: any[]) => void;
|
|
3
|
+
verbose: (...args: any[]) => void;
|
|
4
|
+
info: (...args: any[]) => void;
|
|
5
|
+
warn: (...args: any[]) => void;
|
|
6
|
+
error: (...args: any[]) => void;
|
|
7
|
+
};
|
|
8
|
+
export function debug(...args: any[]): void;
|
|
9
|
+
export function error(...args: any[]): void;
|
|
10
|
+
export function getApi(): {
|
|
11
|
+
debug: typeof debug;
|
|
12
|
+
verbose: typeof verbose;
|
|
13
|
+
info: typeof info;
|
|
14
|
+
warn: typeof warn;
|
|
15
|
+
error: typeof error;
|
|
16
|
+
child: typeof child;
|
|
17
|
+
};
|
|
18
|
+
export function info(...args: any[]): void;
|
|
19
|
+
export function verbose(...args: any[]): void;
|
|
20
|
+
export function warn(...args: any[]): void;
|
|
21
|
+
//# sourceMappingURL=log.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.mts","sourceRoot":"","sources":["../../../dist/api/log.mjs"],"names":[],"mappings":"AAgBkxB;;;;;;EAAmoB;AAAlwC,4CAA+I;AAAmY,4CAA6G;AAAmoB;;;;;;;EAA8D;AAAjgC,2CAAyG;AAAzR,8CAAgL;AAAyG,2CAA0G"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export namespace commands {
|
|
2
|
+
function ls(socket: any, streamManager: any, path?: string): Promise<any>;
|
|
3
|
+
function pwd(socket: any, streamManager: any): Promise<any>;
|
|
4
|
+
function getprop(socket: any, streamManager: any, prop?: null): Promise<any>;
|
|
5
|
+
function getModel(socket: any, streamManager: any): Promise<any>;
|
|
6
|
+
function getAndroidVersion(socket: any, streamManager: any): Promise<any>;
|
|
7
|
+
function getBattery(socket: any, streamManager: any): Promise<any>;
|
|
8
|
+
function screenshot(socket: any, streamManager: any, filename?: string): Promise<any>;
|
|
9
|
+
function logcat(socket: any, streamManager: any, options?: {}): {
|
|
10
|
+
stop(): void;
|
|
11
|
+
};
|
|
12
|
+
function top(socket: any, streamManager: any, options?: {}): {
|
|
13
|
+
stop(): void;
|
|
14
|
+
};
|
|
15
|
+
function keypress(socket: any, streamManager: any, key: any): Promise<any>;
|
|
16
|
+
function launchApp(socket: any, streamManager: any, packageName: any, activity?: string): Promise<any>;
|
|
17
|
+
function killApp(socket: any, streamManager: any, packageName: any): Promise<any>;
|
|
18
|
+
function installApk(socket: any, streamManager: any, apkPath: any, flags?: any[]): Promise<any>;
|
|
19
|
+
function uninstallApp(socket: any, streamManager: any, packageName: any): Promise<any>;
|
|
20
|
+
}
|
|
21
|
+
export function execute(socket: any, streamManager: any, command: any, options?: {}): Promise<any>;
|
|
22
|
+
export function startInteractive(socket: any, streamManager: any, command: any, options?: {}): {
|
|
23
|
+
sendInput(input: any): Promise<void>;
|
|
24
|
+
stop(): void;
|
|
25
|
+
};
|
|
26
|
+
export function startStreaming(socket: any, streamManager: any, command: any, options?: {}): {
|
|
27
|
+
stop(): void;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=shell.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.mts","sourceRoot":"","sources":["../../../dist/api/shell.mjs"],"names":[],"mappings":";IAgBoyH,0EAAqG;IAAK,4DAA+E;IAAS,6EAAoI;IAAU,iEAAoG;IAAmB,0EAA4G;IAAY,mEAA2F;IAAY,sFAAwI;IAAQ;;MAAiG;IAAK;;MAAoG;IAAU,2EAAqG;IAAW,uGAAyL;IAAS,kFAAoH;IAAY,gGAAsL;IAAc,uFAAmH;;AAA7+K,mGAA+vF;AAAod;;;EAAohB;AAAx+B;;EAAod"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.d.mts","sourceRoot":"","sources":["../../../dist/api/stream.mjs"],"names":[],"mappings":"AAgB8Y;;;;;;;EAAs0C"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function delay(ms: any): Promise<any>;
|
|
2
|
+
export function escapeShell(str: any): any;
|
|
3
|
+
export function formatBytes(bytes: any, decimals?: number): string;
|
|
4
|
+
export function isValidIP(ip: any): any;
|
|
5
|
+
export function isValidPort(port: any): boolean;
|
|
6
|
+
export function parseBattery(batteryOutput: any): {};
|
|
7
|
+
export function parseHostPort(hostPort: any, defaultPort?: number): {
|
|
8
|
+
host: any;
|
|
9
|
+
port: number;
|
|
10
|
+
};
|
|
11
|
+
export function parseListing(lsOutput: any): {
|
|
12
|
+
name: any;
|
|
13
|
+
permissions: any;
|
|
14
|
+
links: number;
|
|
15
|
+
owner: any;
|
|
16
|
+
group: any;
|
|
17
|
+
size: number;
|
|
18
|
+
dateTime: any;
|
|
19
|
+
isDirectory: any;
|
|
20
|
+
isFile: any;
|
|
21
|
+
isSymlink: any;
|
|
22
|
+
}[];
|
|
23
|
+
export function parseProperties(propOutput: any): {};
|
|
24
|
+
export function retry(fn: any, options?: {}): Promise<any>;
|
|
25
|
+
export function timeout(ms: any, message?: string): Promise<any>;
|
|
26
|
+
export function withTimeout(promise: any, ms: any, message: any): Promise<any>;
|
|
27
|
+
//# sourceMappingURL=utils.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.mts","sourceRoot":"","sources":["../../../dist/api/utils.mjs"],"names":[],"mappings":"AAgBq4C,6CAAuE;AAA8mC,2CAAgE;AAAjgD,mEAA4Q;AAAqkB,wCAAyL;AAAA,gDAA+E;AAAh5C,qDAAuT;AAAylC;;;EAAwW;AAAh1E;;;;;;;;;;;IAAwlB;AAAl0B,qDAA0O;AAAkuC,2DAA8f;AAAgrB,iEAAmI;AAAA,+EAA4F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"droidsock.d.mts","sourceRoot":"","sources":["../../dist/droidsock.mjs"],"names":[],"mappings":";AAgB+L,6DAAia"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default droidsock;
|
|
2
|
+
/**
|
|
3
|
+
* Creates a DroidSock instance for ADB communication. This is the default export - the
|
|
4
|
+
* quick path - and also available under the explicit name `createDroidSock` for callers
|
|
5
|
+
* who prefer it.
|
|
6
|
+
* @param {object} [options={}] - Configuration options
|
|
7
|
+
* @returns {Promise<object>} DroidSock instance
|
|
8
|
+
*/
|
|
9
|
+
declare function droidsock(options?: object): Promise<object>;
|
|
10
|
+
export { droidsock as createDroidSock, droidsock as DroidSock, droidsock as ADB, droidsock as AndroidDebugBridge };
|
|
11
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../index.mjs"],"names":[],"mappings":";AAsBA;;;;;;GAMG;AACH,qCAHW,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAM3B"}
|