@skrillex1224/android-toolkit 0.1.9 → 1.0.1
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 +51 -64
- package/browser.d.ts +31 -0
- package/dist/browser.js +107 -0
- package/dist/browser.js.map +7 -0
- package/dist/index.cjs +1910 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.js +1880 -0
- package/dist/index.js.map +7 -0
- package/index.d.ts +157 -184
- package/package.json +30 -10
- package/entrys/node.js +0 -26
- package/index.js +0 -1
- package/scripts/postinstall.js +0 -72
- package/src/apify-kit.js +0 -217
- package/src/constants.js +0 -75
- package/src/context.js +0 -89
- package/src/device.js +0 -761
- package/src/errors.js +0 -37
- package/src/frida-client.js +0 -1074
- package/src/internals/compression.js +0 -188
- package/src/internals/frida/webview_event_agent.js +0 -227
- package/src/launch.js +0 -70
- package/src/logger.js +0 -111
- package/src/share.js +0 -644
package/README.md
CHANGED
|
@@ -1,87 +1,74 @@
|
|
|
1
1
|
# Android Toolkit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
`@skrillex1224/android-toolkit` is the native Android counterpart to
|
|
4
|
+
`@skrillex1224/playwright-toolkit`.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Actor code should import only `useAndroidToolKit()` and then use the returned modules.
|
|
7
|
+
The public API is intentionally small and destructive from v1 onward.
|
|
7
8
|
|
|
8
9
|
```js
|
|
9
|
-
import { useAndroidToolKit } from '
|
|
10
|
+
import { useAndroidToolKit } from '@skrillex1224/android-toolkit';
|
|
10
11
|
|
|
11
12
|
const { Launch } = useAndroidToolKit();
|
|
12
13
|
|
|
13
|
-
await Launch.run(async ({
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
await
|
|
17
|
-
|
|
14
|
+
await Launch.run(async ({ ctx, kit }) => {
|
|
15
|
+
const { ApifyKit, DeviceInput, Mutation, Share } = kit;
|
|
16
|
+
await ApifyKit.runStep('输入并发送', ctx, async () => {
|
|
17
|
+
await DeviceInput.click(ctx, { id: 'input_text' });
|
|
18
|
+
await DeviceInput.fill(ctx, { id: 'input_text' }, ctx.query);
|
|
19
|
+
await DeviceInput.pressEnter(ctx);
|
|
18
20
|
});
|
|
19
|
-
|
|
20
|
-
await
|
|
21
|
-
|
|
22
|
-
inputPath,
|
|
23
|
-
outputPath
|
|
21
|
+
await Mutation.waitForStable(ctx, { id: 'message_list' });
|
|
22
|
+
const screenshotBase64 = await Share.captureScreen(ctx);
|
|
23
|
+
await ApifyKit.pushArtifact({ screenshotBase64 });
|
|
24
24
|
});
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
不把包名放进 `Constants.ActorInfo`,也不在业务包里维护包名真源。
|
|
29
|
-
|
|
30
|
-
## 保留模块
|
|
27
|
+
## Public Modules
|
|
31
28
|
|
|
32
|
-
|
|
|
29
|
+
| Module | Responsibility |
|
|
33
30
|
| --- | --- |
|
|
34
|
-
| `Launch` |
|
|
35
|
-
| `ApifyKit` |
|
|
36
|
-
| `Device` | ADB
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
`Share.captureScreen(ctx, options)` 用于采集 Android 页面长图。实现策略是先用
|
|
45
|
-
Frida 扫描当前 Activity 的 View 树,读取所有可滚动 View 的
|
|
46
|
-
`computeVerticalScrollRange()`,取最大所需高度后临时执行 `wm size WxH`,再用
|
|
47
|
-
`screencap` 一次性截图。它不裁剪状态栏和导航栏;如果 View 树无法读取,会直接报错,
|
|
48
|
-
不会退回普通视口截图。默认压缩阈值与 `playwright-toolkit` 一致:base64 最大
|
|
49
|
-
5MiB,JPEG,质量 `0.72`,最低质量 `0.38`,最低缩放 `0.25`。
|
|
31
|
+
| `Launch` | Native actor entrypoint with explicit input/output paths and lifecycle hooks. |
|
|
32
|
+
| `ApifyKit` | `runStep`, `runStepLoose`, `pushSuccess`, `pushFailed`, `pushArtifact`. |
|
|
33
|
+
| `Device` | Low-level ADB primitives only. |
|
|
34
|
+
| `DeviceView` | Native View-tree snapshot and selector lookup over `uiautomator dump`. |
|
|
35
|
+
| `DeviceInput` | View-tree-backed click/fill/press/scroll APIs using ADB input. |
|
|
36
|
+
| `Mutation` | Playwright-like stable wait semantics over matched ViewNode subtrees. |
|
|
37
|
+
| `Share` | Sprite screenshot and clipboard share-link polling. |
|
|
38
|
+
| `Frida` | Public `querySQLite()` and `health()` only. |
|
|
39
|
+
| `Constants` | Native-useful actor metadata and codes. |
|
|
40
|
+
| `Errors` | `CrawlerError` and error serialization. |
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
const screenshotBase64 = await Share.captureScreen(ctx, {
|
|
53
|
-
Frida,
|
|
54
|
-
actorInfo: Constants.ActorInfo['doubao.android'].key,
|
|
55
|
-
maxHeight: 8000
|
|
56
|
-
});
|
|
57
|
-
```
|
|
42
|
+
## Runtime Contract
|
|
58
43
|
|
|
59
|
-
|
|
60
|
-
校验分享链接。Android 当前稳定模式是 `clipboard`:业务层执行点击分享、复制链接,
|
|
61
|
-
toolkit 通过 `Frida.runScript` 轮询系统剪贴板候选内容并提取匹配 prefix 的 URL。
|
|
44
|
+
The toolkit does not install or discover runtime tools.
|
|
62
45
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
46
|
+
Required environment:
|
|
47
|
+
|
|
48
|
+
- `ANDROID_TOOLKIT_ADB_PATH`
|
|
49
|
+
- `ANDROID_TOOLKIT_FRIDA_PATH`
|
|
50
|
+
|
|
51
|
+
Device initialization, Appium Settings, UnicodeIME, and frida-server provisioning belong
|
|
52
|
+
to manager/cluster runtime. Runtime checks fail clearly when prerequisites are missing.
|
|
53
|
+
|
|
54
|
+
## Boundaries
|
|
70
55
|
|
|
71
|
-
|
|
56
|
+
Included:
|
|
72
57
|
|
|
73
|
-
|
|
58
|
+
- ADB command execution, app launch, key input, screenshot, activity checks.
|
|
59
|
+
- View-tree parsing with a real XML parser.
|
|
60
|
+
- View selector operations: `id`, `text`, `textContains`, `contentDesc`.
|
|
61
|
+
- Internal Frida script execution only where the toolkit owns the behavior, such as
|
|
62
|
+
SQLite queries or clipboard probing inside `Share`.
|
|
74
63
|
|
|
75
|
-
|
|
76
|
-
- Frida attach 目标 App pid,并启动 WebView JS 注入事件 recorder。
|
|
77
|
-
- App 进程内 SQLite 查询:业务方提供 db 路径规则、SQL 和参数,toolkit 只返回 cursor rows。
|
|
78
|
-
- `runStep` / `runStepLoose` / `pushSuccess` / `pushFailed`。
|
|
79
|
-
- 通用错误码和失败 dataset 兜底结构。
|
|
64
|
+
Excluded:
|
|
80
65
|
|
|
81
|
-
|
|
66
|
+
- Public generic Frida script execution.
|
|
67
|
+
- WebView event recorder.
|
|
68
|
+
- Frida UI traversal/click helpers.
|
|
69
|
+
- Package name and activity truth. Backend/runtime input owns those values.
|
|
70
|
+
- Actor-specific DB schemas, message parsing, product parsing, and success criteria.
|
|
82
71
|
|
|
83
|
-
|
|
84
|
-
- 任意平台的事件名、正文边界、引用来源解析规则。
|
|
85
|
-
- 任意平台的成功标准和业务错误码。
|
|
72
|
+
## Browser Entry
|
|
86
73
|
|
|
87
|
-
|
|
74
|
+
`@skrillex1224/android-toolkit/browser` exports only `Constants` and `Errors`.
|
package/browser.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export namespace Constants {
|
|
2
|
+
export const Code: Record<string, number>;
|
|
3
|
+
export const Status: Record<string, string>;
|
|
4
|
+
export const ActorInfo: Record<string, {
|
|
5
|
+
key: string;
|
|
6
|
+
name: string;
|
|
7
|
+
icon: string;
|
|
8
|
+
share: {
|
|
9
|
+
mode: 'clipboard';
|
|
10
|
+
prefix: string;
|
|
11
|
+
xurl: unknown[];
|
|
12
|
+
};
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export namespace Errors {
|
|
17
|
+
export class CrawlerError extends Error {
|
|
18
|
+
code: number;
|
|
19
|
+
context: Record<string, unknown>;
|
|
20
|
+
constructor(input?: string | {
|
|
21
|
+
message?: string;
|
|
22
|
+
code?: number;
|
|
23
|
+
context?: Record<string, unknown>;
|
|
24
|
+
});
|
|
25
|
+
static isCrawlerError(error: unknown): boolean;
|
|
26
|
+
static from(error: unknown, fallback?: {
|
|
27
|
+
code?: number;
|
|
28
|
+
context?: Record<string, unknown>;
|
|
29
|
+
}): CrawlerError;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/constants.js
|
|
8
|
+
var constants_exports = {};
|
|
9
|
+
__export(constants_exports, {
|
|
10
|
+
ActorInfo: () => ActorInfo,
|
|
11
|
+
Code: () => Code,
|
|
12
|
+
Status: () => Status,
|
|
13
|
+
UnicodeIme: () => UnicodeIme
|
|
14
|
+
});
|
|
15
|
+
var Code = Object.freeze({
|
|
16
|
+
Success: 0,
|
|
17
|
+
UnknownError: -1,
|
|
18
|
+
NotLogin: 30000001,
|
|
19
|
+
Timeout: 30000003,
|
|
20
|
+
InitialTimeout: 30000004,
|
|
21
|
+
OverallTimeout: 30000005,
|
|
22
|
+
InvalidRequest: 30010001,
|
|
23
|
+
AdbUnavailable: 30010002,
|
|
24
|
+
ContentUnavailable: 30010003,
|
|
25
|
+
SourceExtractionFailed: 30010004,
|
|
26
|
+
AutomationFailed: 30010005,
|
|
27
|
+
FridaUnavailable: 30010008,
|
|
28
|
+
AppNotInstalled: 30010009
|
|
29
|
+
});
|
|
30
|
+
var Status = Object.freeze({
|
|
31
|
+
Success: "SUCCESS",
|
|
32
|
+
Failed: "FAILED"
|
|
33
|
+
});
|
|
34
|
+
var UnicodeIme = Object.freeze({
|
|
35
|
+
packageName: "io.appium.settings",
|
|
36
|
+
component: "io.appium.settings/.UnicodeIME",
|
|
37
|
+
inputTextAction: "ADB_INPUT_TEXT"
|
|
38
|
+
});
|
|
39
|
+
var normalizeShare = (share) => {
|
|
40
|
+
const source = share && typeof share === "object" ? share : {};
|
|
41
|
+
return {
|
|
42
|
+
mode: "clipboard",
|
|
43
|
+
prefix: String(source.prefix || "").trim(),
|
|
44
|
+
xurl: Array.isArray(source.xurl) ? source.xurl : []
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
var createActorInfo = (info) => {
|
|
48
|
+
const key = String(info.key || "").trim();
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
key,
|
|
51
|
+
name: String(info.name || key),
|
|
52
|
+
icon: String(info.icon || `https://static.heartbitai.com/general/actors/${key}.png`),
|
|
53
|
+
share: Object.freeze(normalizeShare(info.share))
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
var ActorInfo = Object.freeze({
|
|
57
|
+
"doubao.android": createActorInfo({
|
|
58
|
+
key: "doubao.android",
|
|
59
|
+
name: "\u8C46\u5305 Android",
|
|
60
|
+
share: {
|
|
61
|
+
prefix: "https://www.doubao.com/thread/",
|
|
62
|
+
xurl: []
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// src/errors.js
|
|
68
|
+
var errors_exports = {};
|
|
69
|
+
__export(errors_exports, {
|
|
70
|
+
CrawlerError: () => CrawlerError,
|
|
71
|
+
serializeError: () => serializeError
|
|
72
|
+
});
|
|
73
|
+
var CrawlerError = class _CrawlerError extends Error {
|
|
74
|
+
constructor(input = {}, options = {}) {
|
|
75
|
+
const payload = typeof input === "string" ? { message: input } : input;
|
|
76
|
+
super(payload.message || "Android crawler error", options);
|
|
77
|
+
this.name = "CrawlerError";
|
|
78
|
+
this.code = payload.code || Code.UnknownError;
|
|
79
|
+
this.context = payload.context || {};
|
|
80
|
+
}
|
|
81
|
+
static isCrawlerError(error) {
|
|
82
|
+
return error instanceof _CrawlerError || (error == null ? void 0 : error.name) === "CrawlerError";
|
|
83
|
+
}
|
|
84
|
+
static from(error, fallback = {}) {
|
|
85
|
+
if (_CrawlerError.isCrawlerError(error)) return error;
|
|
86
|
+
return new _CrawlerError({
|
|
87
|
+
message: (error == null ? void 0 : error.message) || String(error),
|
|
88
|
+
code: fallback.code || Code.UnknownError,
|
|
89
|
+
context: fallback.context || {}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
function serializeError(error) {
|
|
94
|
+
if (!error) return { message: "" };
|
|
95
|
+
return {
|
|
96
|
+
name: error.name || "Error",
|
|
97
|
+
message: error.message || String(error),
|
|
98
|
+
stack: error.stack || "",
|
|
99
|
+
code: error.code || "",
|
|
100
|
+
context: error.context || void 0
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
constants_exports as Constants,
|
|
105
|
+
errors_exports as Errors
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/constants.js", "../src/errors.js"],
|
|
4
|
+
"sourcesContent": ["export const Code = Object.freeze({\n Success: 0,\n UnknownError: -1,\n NotLogin: 30000001,\n Timeout: 30000003,\n InitialTimeout: 30000004,\n OverallTimeout: 30000005,\n InvalidRequest: 30010001,\n AdbUnavailable: 30010002,\n ContentUnavailable: 30010003,\n SourceExtractionFailed: 30010004,\n AutomationFailed: 30010005,\n FridaUnavailable: 30010008,\n AppNotInstalled: 30010009,\n});\n\nexport const Status = Object.freeze({\n Success: 'SUCCESS',\n Failed: 'FAILED',\n});\n\nexport const UnicodeIme = Object.freeze({\n packageName: 'io.appium.settings',\n component: 'io.appium.settings/.UnicodeIME',\n inputTextAction: 'ADB_INPUT_TEXT',\n});\n\nconst normalizeShare = (share) => {\n const source = share && typeof share === 'object' ? share : {};\n return {\n mode: 'clipboard',\n prefix: String(source.prefix || '').trim(),\n xurl: Array.isArray(source.xurl) ? source.xurl : [],\n };\n};\n\nconst createActorInfo = (info) => {\n const key = String(info.key || '').trim();\n return Object.freeze({\n key,\n name: String(info.name || key),\n icon: String(info.icon || `https://static.heartbitai.com/general/actors/${key}.png`),\n share: Object.freeze(normalizeShare(info.share)),\n });\n};\n\nexport const ActorInfo = Object.freeze({\n 'doubao.android': createActorInfo({\n key: 'doubao.android',\n name: '\u8C46\u5305 Android',\n share: {\n prefix: 'https://www.doubao.com/thread/',\n xurl: [],\n },\n }),\n});\n", "import { Code } from './constants.js';\n\nexport class CrawlerError extends Error {\n constructor(input = {}, options = {}) {\n const payload = typeof input === 'string' ? { message: input } : input;\n super(payload.message || 'Android crawler error', options);\n this.name = 'CrawlerError';\n this.code = payload.code || Code.UnknownError;\n this.context = payload.context || {};\n }\n\n static isCrawlerError(error) {\n return error instanceof CrawlerError || error?.name === 'CrawlerError';\n }\n\n static from(error, fallback = {}) {\n if (CrawlerError.isCrawlerError(error)) return error;\n return new CrawlerError({\n message: error?.message || String(error),\n code: fallback.code || Code.UnknownError,\n context: fallback.context || {},\n });\n }\n}\n\nexport function serializeError(error) {\n if (!error) return { message: '' };\n return {\n name: error.name || 'Error',\n message: error.message || String(error),\n stack: error.stack || '',\n code: error.code || '',\n context: error.context || undefined,\n };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,IAAM,OAAO,OAAO,OAAO;AAAA,EAC9B,SAAS;AAAA,EACT,cAAc;AAAA,EACd,UAAU;AAAA,EACV,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,iBAAiB;AACrB,CAAC;AAEM,IAAM,SAAS,OAAO,OAAO;AAAA,EAChC,SAAS;AAAA,EACT,QAAQ;AACZ,CAAC;AAEM,IAAM,aAAa,OAAO,OAAO;AAAA,EACpC,aAAa;AAAA,EACb,WAAW;AAAA,EACX,iBAAiB;AACrB,CAAC;AAED,IAAM,iBAAiB,CAAC,UAAU;AAC9B,QAAM,SAAS,SAAS,OAAO,UAAU,WAAW,QAAQ,CAAC;AAC7D,SAAO;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,OAAO,OAAO,UAAU,EAAE,EAAE,KAAK;AAAA,IACzC,MAAM,MAAM,QAAQ,OAAO,IAAI,IAAI,OAAO,OAAO,CAAC;AAAA,EACtD;AACJ;AAEA,IAAM,kBAAkB,CAAC,SAAS;AAC9B,QAAM,MAAM,OAAO,KAAK,OAAO,EAAE,EAAE,KAAK;AACxC,SAAO,OAAO,OAAO;AAAA,IACjB;AAAA,IACA,MAAM,OAAO,KAAK,QAAQ,GAAG;AAAA,IAC7B,MAAM,OAAO,KAAK,QAAQ,gDAAgD,GAAG,MAAM;AAAA,IACnF,OAAO,OAAO,OAAO,eAAe,KAAK,KAAK,CAAC;AAAA,EACnD,CAAC;AACL;AAEO,IAAM,YAAY,OAAO,OAAO;AAAA,EACnC,kBAAkB,gBAAgB;AAAA,IAC9B,KAAK;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACH,QAAQ;AAAA,MACR,MAAM,CAAC;AAAA,IACX;AAAA,EACJ,CAAC;AACL,CAAC;;;ACvDD;AAAA;AAAA;AAAA;AAAA;AAEO,IAAM,eAAN,MAAM,sBAAqB,MAAM;AAAA,EACpC,YAAY,QAAQ,CAAC,GAAG,UAAU,CAAC,GAAG;AAClC,UAAM,UAAU,OAAO,UAAU,WAAW,EAAE,SAAS,MAAM,IAAI;AACjE,UAAM,QAAQ,WAAW,yBAAyB,OAAO;AACzD,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ,QAAQ,KAAK;AACjC,SAAK,UAAU,QAAQ,WAAW,CAAC;AAAA,EACvC;AAAA,EAEA,OAAO,eAAe,OAAO;AACzB,WAAO,iBAAiB,kBAAgB,+BAAO,UAAS;AAAA,EAC5D;AAAA,EAEA,OAAO,KAAK,OAAO,WAAW,CAAC,GAAG;AAC9B,QAAI,cAAa,eAAe,KAAK,EAAG,QAAO;AAC/C,WAAO,IAAI,cAAa;AAAA,MACpB,UAAS,+BAAO,YAAW,OAAO,KAAK;AAAA,MACvC,MAAM,SAAS,QAAQ,KAAK;AAAA,MAC5B,SAAS,SAAS,WAAW,CAAC;AAAA,IAClC,CAAC;AAAA,EACL;AACJ;AAEO,SAAS,eAAe,OAAO;AAClC,MAAI,CAAC,MAAO,QAAO,EAAE,SAAS,GAAG;AACjC,SAAO;AAAA,IACH,MAAM,MAAM,QAAQ;AAAA,IACpB,SAAS,MAAM,WAAW,OAAO,KAAK;AAAA,IACtC,OAAO,MAAM,SAAS;AAAA,IACtB,MAAM,MAAM,QAAQ;AAAA,IACpB,SAAS,MAAM,WAAW;AAAA,EAC9B;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|