@skrillex1224/android-toolkit 0.1.1 → 0.1.7

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.
@@ -14,6 +14,9 @@ function getConfig() {
14
14
  keywords: Array.isArray(CONFIG.keywords)
15
15
  ? CONFIG.keywords.map((item) => String(item || '')).filter(Boolean)
16
16
  : [],
17
+ // 调试开关:捕获所有能解析到的 WebView JS 回调,用于先确认业务事件名。
18
+ // 默认关闭,避免正式任务记录无关事件。
19
+ captureAllEvents: CONFIG.captureAllEvents === true,
17
20
  includeRawJs: CONFIG.includeRawJs === true,
18
21
  maxRawJsLength: Number(CONFIG.maxRawJsLength || 1200),
19
22
  maxPayloadLength: Number(CONFIG.maxPayloadLength || 4 * 1024 * 1024)
@@ -86,6 +89,7 @@ function extractCallPayload(rawSource) {
86
89
 
87
90
  function shouldCapture(config, rawJs, parsed) {
88
91
  const eventName = String(parsed?.eventName || '').trim();
92
+ if (config.captureAllEvents && (eventName || parsed?.payload || config.includeRawJs)) return true;
89
93
  if (eventName && config.eventNames.has(eventName)) return true;
90
94
  if (config.keywords.length === 0) return false;
91
95
  const raw = String(rawJs || '');
package/src/launch.js CHANGED
@@ -6,25 +6,32 @@ import {Device} from './device.js';
6
6
  import {Frida} from './frida-client.js';
7
7
  import {Logger} from './logger.js';
8
8
 
9
+ const DEFAULT_INPUT_PATH = '/apify_storage/input.json';
10
+ const DEFAULT_OUTPUT_PATH = '/apify_storage/output.json';
11
+
9
12
  export const Launch = {
10
13
  /**
11
14
  * Android androider 的统一入口。
12
15
  *
13
16
  * 这层等价于普通 visitor 里的 Actor.init + Actor.getInput + requestHandler 的组合。
14
- * 注意:toolkit 不读取环境变量,input/output 路径必须由具体 androider 的
15
- * main.js 显式传入。这样 toolkit 不会和某个 runner、某个云手机平台绑定。
17
+ * 容器里默认使用 cluster 固定的 input/output 路径;本地调试仍可由
18
+ * main.js 透传 argv 覆盖。
16
19
  *
17
20
  * handler 内建议只写业务流程;输入、ctx、toolkit 模块都从参数里取。
18
21
  */
19
22
  async run(handler, options = {}) {
20
- const input = options.input || JSON.parse(fs.readFileSync(requiredOption(options.inputPath, 'inputPath'), 'utf8'));
23
+ const startedAt = Date.now();
24
+ const inputPath = pathOption(options.inputPath, DEFAULT_INPUT_PATH);
25
+ const outputPath = pathOption(options.outputPath, DEFAULT_OUTPUT_PATH);
26
+ const input = options.input || JSON.parse(fs.readFileSync(inputPath, 'utf8'));
21
27
  const ctx = options.ctx || createAndroidContext(input, options.contextDefaults || {});
28
+ ctx.runId = ctx.runId || input.run_id || input.runId || input.runtime?.run_id || input.runtime?.runId || '';
22
29
  const apifyKit = await ApifyKit.useApifyKit({
23
30
  reset: true,
24
31
  input,
25
32
  ctx,
26
- inputPath: options.inputPath,
27
- outputPath: requiredOption(options.outputPath, 'outputPath')
33
+ inputPath,
34
+ outputPath
28
35
  });
29
36
 
30
37
  const kit = {
@@ -35,18 +42,27 @@ export const Launch = {
35
42
  };
36
43
 
37
44
  try {
45
+ Logger.start('Launch run', {
46
+ inputPath,
47
+ outputPath,
48
+ runId: ctx.runId,
49
+ serial: ctx.serial,
50
+ packageName: ctx.packageName,
51
+ queryChars: ctx.query.length
52
+ });
38
53
  await handler({input, ctx, kit, ApifyKit: apifyKit});
54
+ Logger.success('Launch run', {duration: Logger.duration(startedAt)});
39
55
  } catch (error) {
40
- Logger.fail('Launch handler failed', error);
56
+ Logger.fail(`Launch handler failed duration=${Logger.duration(startedAt)}`, error);
41
57
  if (!apifyKit.hasPushed()) {
42
58
  await apifyKit.pushFailed(error);
43
59
  }
60
+ throw error;
44
61
  }
45
62
  }
46
63
  };
47
64
 
48
- function requiredOption(value, name) {
65
+ function pathOption(value, fallback) {
49
66
  const clean = String(value || '').trim();
50
- if (!clean) throw new Error(`Launch.run requires ${name}`);
51
- return clean;
67
+ return clean || fallback;
52
68
  }
package/src/logger.js CHANGED
@@ -9,8 +9,11 @@ const defaultLogger = {
9
9
  let activeLogger = defaultLogger;
10
10
 
11
11
  function write(level, message, detail = '') {
12
- const line = detail ? `[android-toolkit] ${message} ${detail}` : `[android-toolkit] ${message}`;
13
- const writer = activeLogger[level] || activeLogger.info || console.error;
12
+ const formattedDetail = formatDetail(detail);
13
+ const line = formattedDetail
14
+ ? `[${formatTimestamp()}] [android-toolkit] ${message} ${formattedDetail}`
15
+ : `[${formatTimestamp()}] [android-toolkit] ${message}`;
16
+ const writer = activeLogger[level] || activeLogger.warning || activeLogger.warn || activeLogger.info || console.error;
14
17
  writer.call(activeLogger, line);
15
18
  }
16
19
 
@@ -36,6 +39,23 @@ export const Logger = {
36
39
  info(message, detail = '') {
37
40
  write('info', message, detail);
38
41
  },
42
+ debug(message, detail = '') {
43
+ write('debug', message, detail);
44
+ },
45
+ duration(startedAt) {
46
+ return `${((Date.now() - startedAt) / 1000).toFixed(2)}s`;
47
+ },
48
+ createLogger(scope) {
49
+ const prefix = scope ? `[${scope}] ` : '';
50
+ return {
51
+ start: (message, detail = '') => Logger.start(`${prefix}${message}`, detail),
52
+ success: (message, detail = '') => Logger.success(`${prefix}${message}`, detail),
53
+ fail: (message, error = '') => Logger.fail(`${prefix}${message}`, error),
54
+ warn: (message, detail = '') => Logger.warn(`${prefix}${message}`, detail),
55
+ info: (message, detail = '') => Logger.info(`${prefix}${message}`, detail),
56
+ debug: (message, detail = '') => Logger.debug(`${prefix}${message}`, detail)
57
+ };
58
+ },
39
59
  useTemplate() {
40
60
  return {
41
61
  taskStart: (detail) => Logger.start('任务开始', detail),
@@ -51,3 +71,41 @@ export const Logger = {
51
71
  export function sleep(ms) {
52
72
  return new Promise((resolve) => setTimeout(resolve, ms));
53
73
  }
74
+
75
+ function formatTimestamp(date = new Date()) {
76
+ const pad = (value, size = 2) => String(value).padStart(size, '0');
77
+ return [
78
+ `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`,
79
+ `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}.${pad(date.getMilliseconds(), 3)}`
80
+ ].join(' ');
81
+ }
82
+
83
+ function formatDetail(detail) {
84
+ if (detail == null || detail === '') return '';
85
+ if (typeof detail === 'string') return detail;
86
+ if (detail instanceof Error) return detail.message;
87
+ if (Array.isArray(detail)) {
88
+ return detail.map(formatDetail).filter(Boolean).join(' ');
89
+ }
90
+ if (typeof detail === 'object') {
91
+ const parts = [];
92
+ for (const [key, value] of Object.entries(detail)) {
93
+ if (value == null || value === '') continue;
94
+ if (typeof value === 'object') {
95
+ parts.push(`${key}=${safeJson(value)}`);
96
+ } else {
97
+ parts.push(`${key}=${String(value)}`);
98
+ }
99
+ }
100
+ return parts.join(' ');
101
+ }
102
+ return String(detail);
103
+ }
104
+
105
+ function safeJson(value) {
106
+ try {
107
+ return JSON.stringify(value);
108
+ } catch {
109
+ return String(value);
110
+ }
111
+ }