frida-overlay-menu 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 +481 -0
- package/lib/api.d.ts +50 -0
- package/lib/api.js +149 -0
- package/lib/component/button.d.ts +11 -0
- package/lib/component/button.js +57 -0
- package/lib/component/category.d.ts +8 -0
- package/lib/component/category.js +37 -0
- package/lib/component/checkBox.d.ts +28 -0
- package/lib/component/checkBox.js +199 -0
- package/lib/component/collapsible.d.ts +19 -0
- package/lib/component/collapsible.js +185 -0
- package/lib/component/image.d.ts +18 -0
- package/lib/component/image.js +87 -0
- package/lib/component/input.d.ts +33 -0
- package/lib/component/input.js +346 -0
- package/lib/component/selector.d.ts +22 -0
- package/lib/component/selector.js +97 -0
- package/lib/component/slider.d.ts +18 -0
- package/lib/component/slider.js +155 -0
- package/lib/component/style/style.d.ts +5 -0
- package/lib/component/style/style.js +261 -0
- package/lib/component/style/theme.d.ts +29 -0
- package/lib/component/style/theme.js +23 -0
- package/lib/component/switch.d.ts +12 -0
- package/lib/component/switch.js +77 -0
- package/lib/component/text.d.ts +9 -0
- package/lib/component/text.js +36 -0
- package/lib/component/ui-components.d.ts +24 -0
- package/lib/component/ui-components.js +49 -0
- package/lib/component/views/log-view.d.ts +25 -0
- package/lib/component/views/log-view.js +231 -0
- package/lib/component/views/tabs-view.d.ts +35 -0
- package/lib/component/views/tabs-view.js +296 -0
- package/lib/event-emitter.d.ts +10 -0
- package/lib/event-emitter.js +52 -0
- package/lib/float-menu.d.ts +63 -0
- package/lib/float-menu.js +568 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +26 -0
- package/lib/logger.d.ts +43 -0
- package/lib/logger.js +183 -0
- package/lib/utils.d.ts +8 -0
- package/lib/utils.js +16 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
# Frida Float Menu - Android 悬浮窗 UI 库
|
|
2
|
+
|
|
3
|
+
一个为 Frida 逆向工程设计的 TypeScript 库,用于在 Android 设备上创建交互式悬浮窗口。支持多种 UI 组件(按钮、开关、文本、选择器等),可以轻松地从 JavaScript 控制。
|
|
4
|
+
|
|
5
|
+
## ✨ 特性
|
|
6
|
+
|
|
7
|
+
- 📱 创建 Android 悬浮窗,使用系统 `WindowManager`
|
|
8
|
+
- 🎨 丰富的 UI 组件:Button、Switch、Text、Selector、Slider、Collapsible、Category、TextInput、NumberInput、CheckBoxGroup、ImageView
|
|
9
|
+
- 🔄 双向数据绑定:JavaScript 变量与 UI 状态同步
|
|
10
|
+
- 📡 完整的事件系统:组件交互、值变更等
|
|
11
|
+
- 💻 通过编程方式更新 UI 组件
|
|
12
|
+
- 📋 可选的日志面板,显示运行时日志(可禁用以提升性能)
|
|
13
|
+
- 🖼️ 支持通过 base64 编码图片设置窗口图标
|
|
14
|
+
- 📑 支持多标签页管理,更好地组织 UI
|
|
15
|
+
- 🎯 模块化、可扩展的架构
|
|
16
|
+
- 📝 完整的 TypeScript 类型定义
|
|
17
|
+
|
|
18
|
+
## 📋 环境要求
|
|
19
|
+
|
|
20
|
+
- Frida(已测试 Android 平台)
|
|
21
|
+
- 目标 Android 应用需要悬浮窗权限(API 26+ 需要权限 `TYPE_APPLICATION_OVERLAY`)
|
|
22
|
+
- frida-compile 用于 TypeScript 编译(已包含在开发依赖中)
|
|
23
|
+
|
|
24
|
+
## 🚀 安装
|
|
25
|
+
|
|
26
|
+
### 在你的 Frida 项目中使用
|
|
27
|
+
|
|
28
|
+
1. 将 `src/` 目录复制到你的项目
|
|
29
|
+
2. 在 TypeScript 文件中导入库:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { FloatMenu, Button, Switch, Text } from './path/to/src/index';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 📖 快速开始
|
|
36
|
+
|
|
37
|
+
### 1. 创建 TypeScript 文件 (`my-script.ts`)
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { FloatMenu, Button, Switch, Text, Selector } from './src/index';
|
|
41
|
+
|
|
42
|
+
Java.perform(() => {
|
|
43
|
+
// 创建悬浮菜单
|
|
44
|
+
const menu = new FloatMenu({
|
|
45
|
+
width: 1200,
|
|
46
|
+
height: 900,
|
|
47
|
+
title: "我的菜单"
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
menu.show();
|
|
51
|
+
|
|
52
|
+
// 添加按钮组件
|
|
53
|
+
const button = new Button('myButton', '点击我', 'primary');
|
|
54
|
+
button.setOnClick(() => {
|
|
55
|
+
console.log('按钮被点击了!');
|
|
56
|
+
menu.toast('按钮被点击!');
|
|
57
|
+
});
|
|
58
|
+
menu.addComponent(button);
|
|
59
|
+
|
|
60
|
+
// 添加开关组件
|
|
61
|
+
const switchComp = new Switch('mySwitch', '启用功能', false);
|
|
62
|
+
switchComp.on('valueChanged', (value: boolean) => {
|
|
63
|
+
console.log('开关状态:', value);
|
|
64
|
+
});
|
|
65
|
+
menu.addComponent(switchComp);
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. 使用 frida-compile 编译
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
frida-compile my-script.ts -o my-script-compiled.js -c
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. 注入到目标应用
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
frida -U -f com.example.app -l my-script-compiled.js
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 📚 组件介绍
|
|
82
|
+
|
|
83
|
+
本项目提供了丰富的 UI 组件,全部在 `example.ts` 中有详细的使用示例。以下是按功能分类的组件说明:
|
|
84
|
+
|
|
85
|
+
### 🎯 基础组件 (Basic Components)
|
|
86
|
+
|
|
87
|
+
#### 1. Text - 文本组件
|
|
88
|
+
显示静态或动态文本,支持 HTML 富文本格式。
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
const text = new Text('myText', '这是普通文本');
|
|
92
|
+
const richText = new Text('richText', 'HTML <b>富文本</b> 支持');
|
|
93
|
+
menu.addComponent(text);
|
|
94
|
+
menu.addComponent(richText);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### 2. Button - 按钮组件
|
|
98
|
+
支持两种样式:主要按钮(primary)和危险按钮(danger)。
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const primaryButton = new Button('btn1', '主要按钮', 'primary');
|
|
102
|
+
const dangerButton = new Button('btn2', '危险操作', 'danger');
|
|
103
|
+
|
|
104
|
+
primaryButton.setOnClick(() => {
|
|
105
|
+
menu.toast('主要按钮被点击!');
|
|
106
|
+
});
|
|
107
|
+
menu.addComponent(primaryButton);
|
|
108
|
+
menu.addComponent(dangerButton);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### 3. Category - 分类标题
|
|
112
|
+
用于组织和分组 UI 组件的分隔标题。
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const category = new Category('category1', '基础组件');
|
|
116
|
+
menu.addComponent(category);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 📝 表单组件 (Form Components)
|
|
120
|
+
|
|
121
|
+
#### 4. TextInput - 文本输入框
|
|
122
|
+
单行文本输入,带有弹窗输入界面。
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const usernameInput = new TextInput(
|
|
126
|
+
'username',
|
|
127
|
+
'', // 初始值
|
|
128
|
+
'用户名:', // 按钮文本
|
|
129
|
+
'请输入用户名', // 提示文本
|
|
130
|
+
'输入用户名' // 对话框标题
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
usernameInput.setOnValueChange((value: string) => {
|
|
134
|
+
console.log('用户名:', value);
|
|
135
|
+
menu.toast(`用户名设置为: ${value}`);
|
|
136
|
+
});
|
|
137
|
+
menu.addComponent(usernameInput);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### 5. NumberInput - 数字输入框
|
|
141
|
+
数值输入,支持最小值、最大值限制。
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const ageInput = new NumberInput(
|
|
145
|
+
'age',
|
|
146
|
+
25, // 初始值
|
|
147
|
+
0, // 最小值
|
|
148
|
+
120, // 最大值
|
|
149
|
+
'年龄:', // 按钮文本
|
|
150
|
+
'请输入年龄', // 提示文本
|
|
151
|
+
'输入年龄' // 对话框标题
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
ageInput.setOnValueChange((value: number) => {
|
|
155
|
+
console.log('年龄:', value);
|
|
156
|
+
// 根据年龄显示不同分类
|
|
157
|
+
let category = value < 18 ? "未成年" : "成年人";
|
|
158
|
+
menu.toast(`年龄段: ${category}`);
|
|
159
|
+
});
|
|
160
|
+
menu.addComponent(ageInput);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### 6. Switch - 开关组件
|
|
164
|
+
简单的二选一开关。
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const soundSwitch = new Switch('sound', '音效', false);
|
|
168
|
+
soundSwitch.on('valueChanged', (value: boolean) => {
|
|
169
|
+
console.log('音效开关:', value);
|
|
170
|
+
menu.toast(`音效已${value ? '开启' : '关闭'}`);
|
|
171
|
+
});
|
|
172
|
+
menu.addComponent(soundSwitch);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### 7. CheckBoxGroup - 多选框组
|
|
176
|
+
支持多选的复选框组件。
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const hobbiesGroup = new CheckBoxGroup(
|
|
180
|
+
'hobbies',
|
|
181
|
+
[
|
|
182
|
+
{ id: 'reading', label: '阅读' },
|
|
183
|
+
{ id: 'music', label: '音乐' },
|
|
184
|
+
{ id: 'sports', label: '运动' },
|
|
185
|
+
{ id: 'travel', label: '旅行' },
|
|
186
|
+
],
|
|
187
|
+
['reading'] // 初始选中的项
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
hobbiesGroup.on('valueChanged', (values: any[]) => {
|
|
191
|
+
console.log('选中的爱好:', values);
|
|
192
|
+
const labels = values.map(v => v.label).join(', ');
|
|
193
|
+
menu.toast(`选中的爱好: ${labels}`);
|
|
194
|
+
});
|
|
195
|
+
menu.addComponent(hobbiesGroup);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 🔘 选择组件 (Selection Components)
|
|
199
|
+
|
|
200
|
+
#### 8. Selector - 下拉选择器
|
|
201
|
+
从多个选项中选择一个,支持自定义数据对象。
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const citySelector = new Selector(
|
|
205
|
+
'city',
|
|
206
|
+
[
|
|
207
|
+
{ lable: '北京', code: 'BJ' },
|
|
208
|
+
{ lable: '上海', code: 'SH' },
|
|
209
|
+
{ lable: '广州', code: 'GZ' },
|
|
210
|
+
{ lable: '深圳', code: 'SZ' },
|
|
211
|
+
],
|
|
212
|
+
0 // 默认选中第一个
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
citySelector.on('valueChanged', (value: any) => {
|
|
216
|
+
console.log('选择的城市:', value.lable, value.code);
|
|
217
|
+
menu.toast(`当前城市: ${value.lable}`);
|
|
218
|
+
});
|
|
219
|
+
menu.addComponent(citySelector);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### 9. Slider - 滑块组件
|
|
223
|
+
数值范围选择,支持自定义步进。
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const volumeSlider = new Slider(
|
|
227
|
+
'volume',
|
|
228
|
+
'音量控制',
|
|
229
|
+
0, // 最小值
|
|
230
|
+
100, // 最大值
|
|
231
|
+
50, // 初始值
|
|
232
|
+
1 // 步进
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
volumeSlider.on('valueChanged', (value: number) => {
|
|
236
|
+
console.log('音量:', value);
|
|
237
|
+
menu.toast(`音量: ${value}%`);
|
|
238
|
+
});
|
|
239
|
+
menu.addComponent(volumeSlider);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 📦 布局组件 (Layout Components)
|
|
243
|
+
|
|
244
|
+
#### 10. Collapsible - 可折叠面板
|
|
245
|
+
可以展开和折叠的面板,支持嵌套子组件。
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const settingsCollapsible = new Collapsible(
|
|
249
|
+
'settings',
|
|
250
|
+
'用户设置',
|
|
251
|
+
true // 初始展开状态
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// 添加子组件到折叠面板
|
|
255
|
+
const usernameInput = new TextInput('username', 'admin', '用户名', '请输入用户名', '修改用户名');
|
|
256
|
+
const roleSelector = new Selector('role', [
|
|
257
|
+
{ lable: '管理员', role: 'admin' },
|
|
258
|
+
{ lable: '普通用户', role: 'user' }
|
|
259
|
+
], 0);
|
|
260
|
+
|
|
261
|
+
settingsCollapsible.addChildren([
|
|
262
|
+
usernameInput,
|
|
263
|
+
roleSelector
|
|
264
|
+
]);
|
|
265
|
+
|
|
266
|
+
menu.addComponent(settingsCollapsible);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### 11. ImageView - 图片组件
|
|
270
|
+
显示 base64 编码的图片。
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { iconBase64 } from './icon';
|
|
274
|
+
|
|
275
|
+
const imageView = new ImageView(
|
|
276
|
+
'logo',
|
|
277
|
+
iconBase64, // base64 编码的图片
|
|
278
|
+
400, // 宽度
|
|
279
|
+
400 // 高度
|
|
280
|
+
);
|
|
281
|
+
menu.addComponent(imageView);
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### 🔄 交互演示 (Interactive Demo)
|
|
285
|
+
|
|
286
|
+
在 `example.ts` 中展示了组件之间的交互和联动效果:
|
|
287
|
+
|
|
288
|
+
1. **用户评分系统**:包含文本输入、滑块选择、多选标签、评论和提交按钮
|
|
289
|
+
2. **购物车功能**:商品选择、数量调整、运费计算、总价自动更新
|
|
290
|
+
|
|
291
|
+
这些示例展示了如何:
|
|
292
|
+
- 组件之间相互影响
|
|
293
|
+
- 获取其他组件的值
|
|
294
|
+
- 动态更新 UI 显示
|
|
295
|
+
- 处理复杂的用户交互
|
|
296
|
+
|
|
297
|
+
## 🎛️ 多标签页支持
|
|
298
|
+
|
|
299
|
+
FloatMenu 支持创建多个标签页来更好地组织 UI 组件:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
const menu = new FloatMenu({
|
|
303
|
+
width: 1200,
|
|
304
|
+
height: 1400,
|
|
305
|
+
title: "综合测试用例",
|
|
306
|
+
tabs: [
|
|
307
|
+
{ id: "basic", label: "基础组件" },
|
|
308
|
+
{ id: "form", label: "表单组件" },
|
|
309
|
+
{ id: "selection", label: "选择组件" },
|
|
310
|
+
{ id: "layout", label: "布局组件" },
|
|
311
|
+
{ id: "interactive", label: "交互演示" },
|
|
312
|
+
],
|
|
313
|
+
activeTab: "basic" // 默认显示的标签页
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
menu.show();
|
|
317
|
+
|
|
318
|
+
// 将组件添加到特定标签页
|
|
319
|
+
const button = new Button("btn1", "点击我");
|
|
320
|
+
menu.addComponent(button, "basic"); // 添加到"基础组件"标签页
|
|
321
|
+
|
|
322
|
+
const input = new TextInput("name", "", "姓名", "请输入姓名", "输入姓名");
|
|
323
|
+
menu.addComponent(input, "form"); // 添加到"表单组件"标签页
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## 📡 API 参考
|
|
327
|
+
|
|
328
|
+
### FloatMenu - 主类
|
|
329
|
+
|
|
330
|
+
创建和管理悬浮窗口的主类。
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
interface TabDefinition {
|
|
334
|
+
id: string; // 标签页唯一标识符
|
|
335
|
+
label: string; // 标签页显示的标题
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
interface FloatMenuOptions {
|
|
339
|
+
width?: number; // 窗口宽度(默认:1000)
|
|
340
|
+
height?: number; // 窗口高度(默认:900)
|
|
341
|
+
x?: number; // X 坐标(默认:0)
|
|
342
|
+
y?: number; // Y 坐标(默认:0)
|
|
343
|
+
iconWidth?: number; // 图标宽度(默认:200)
|
|
344
|
+
iconHeight?: number; // 图标高度(默认:200)
|
|
345
|
+
iconBase64?: string; // Base64 编码的图标图片(可选)
|
|
346
|
+
logMaxLines?: number; // 日志面板最大行数(默认:100)
|
|
347
|
+
theme?: Theme; // 主题设置
|
|
348
|
+
title?: string; // 主标题文本(默认:"Frida Float Menu")
|
|
349
|
+
tabs?: TabDefinition[]; // 标签页定义(可选)
|
|
350
|
+
activeTab?: string; // 初始激活的标签页 ID(默认:第一个标签页或 "default")
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
class FloatMenu {
|
|
354
|
+
constructor(options?: FloatMenuOptions);
|
|
355
|
+
show(): void; // 显示窗口
|
|
356
|
+
hide(): void; // 隐藏窗口
|
|
357
|
+
addComponent(component: UIComponent, tabId?: string): void; // 添加组件
|
|
358
|
+
removeComponent(id: string): void; // 移除组件
|
|
359
|
+
getComponent<T extends UIComponent>(id: string): T | undefined; // 获取组件
|
|
360
|
+
setComponentValue(id: string, value: any): void; // 更新组件值
|
|
361
|
+
on(event: string, callback: (...args: any[]) => void): void; // 监听事件
|
|
362
|
+
off(event: string, callback: (...args: any[]) => void): void; // 取消监听
|
|
363
|
+
toast(msg: string, duration?: 0 | 1): void; // 显示提示信息
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### 事件系统
|
|
368
|
+
|
|
369
|
+
组件会发出事件,可以监听这些事件:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
// 组件级别的事件
|
|
373
|
+
component.on('valueChanged', (value) => {
|
|
374
|
+
// Switch、Selector、Slider 等组件的值改变时触发
|
|
375
|
+
console.log('值改变:', value);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
component.on('click', () => {
|
|
379
|
+
// Button 组件点击时触发
|
|
380
|
+
console.log('按钮被点击');
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Menu 会转发组件事件并带上组件 ID
|
|
384
|
+
menu.on('component:mySwitch:valueChanged', (value) => {
|
|
385
|
+
console.log('ID 为 "mySwitch" 的组件值改变为:', value);
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## 📂 项目结构
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
frida-float-menu/
|
|
393
|
+
├── src/ # 库源代码
|
|
394
|
+
│ ├── index.ts # 主入口(重新导出所有内容)
|
|
395
|
+
│ ├── float-menu.ts # FloatMenu 主类
|
|
396
|
+
│ ├── event-emitter.ts # 事件系统
|
|
397
|
+
│ ├── logger.ts # 日志工具
|
|
398
|
+
│ └── component/ # 组件目录
|
|
399
|
+
│ ├── button.ts # 按钮组件
|
|
400
|
+
│ ├── text.ts # 文本组件
|
|
401
|
+
│ ├── switch.ts # 开关组件
|
|
402
|
+
│ ├── selector.ts # 选择器组件
|
|
403
|
+
│ ├── slider.ts # 滑块组件
|
|
404
|
+
│ ├── input.ts # 输入框组件
|
|
405
|
+
│ ├── checkBox.ts # 多选框组件
|
|
406
|
+
│ ├── category.ts # 分类标题组件
|
|
407
|
+
│ ├── collapsible.ts # 可折叠面板组件
|
|
408
|
+
│ ├── image.ts # 图片组件
|
|
409
|
+
│ ├── ui-components.ts # UI 组件基类
|
|
410
|
+
│ └── style/ # 样式相关
|
|
411
|
+
│ ├── theme.ts # 主题配置
|
|
412
|
+
│ └── style.ts # 样式工具
|
|
413
|
+
├── example.ts # TypeScript 使用示例(综合测试用例)
|
|
414
|
+
├── example.js # 编译后的 JavaScript 示例
|
|
415
|
+
├── icon.ts # 图标 base64 编码
|
|
416
|
+
├── package.json # 包配置文件
|
|
417
|
+
├── tsconfig.json # TypeScript 配置
|
|
418
|
+
└── README.md # 本文件
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## 🛠️ 编译和开发
|
|
422
|
+
|
|
423
|
+
### 对于库使用者
|
|
424
|
+
无需构建 - 直接使用 TypeScript 源代码与 frida-compile 一起使用。
|
|
425
|
+
|
|
426
|
+
### 对于库开发者
|
|
427
|
+
```bash
|
|
428
|
+
npm install # 安装依赖
|
|
429
|
+
npm run check # 类型检查
|
|
430
|
+
npm run build-example # 编译示例脚本
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## 📖 使用示例
|
|
434
|
+
|
|
435
|
+
### 查看完整示例
|
|
436
|
+
|
|
437
|
+
`example.ts` 包含了所有组件的详细使用示例,展示了:
|
|
438
|
+
|
|
439
|
+
1. **基础组件页**:Text、Button、Category 等基础组件的使用
|
|
440
|
+
2. **表单组件页**:TextInput、NumberInput、Switch、CheckBoxGroup 等表单组件
|
|
441
|
+
3. **选择组件页**:Selector、Slider 等选择组件
|
|
442
|
+
4. **布局组件页**:Collapsible、ImageView 等布局组件
|
|
443
|
+
5. **交互演示页**:组件之间的交互和联动效果(用户评分系统、购物车)
|
|
444
|
+
|
|
445
|
+
### 编译和运行示例
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# 编译示例
|
|
449
|
+
frida-compile example.ts -o example-compiled.js -c
|
|
450
|
+
|
|
451
|
+
# 注入到目标应用
|
|
452
|
+
frida -U -f com.example.app -l example-compiled.js
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## ⚠️ 限制
|
|
456
|
+
|
|
457
|
+
- 需要 Android API 26+ 才能使用 `TYPE_APPLICATION_OVERLAY`
|
|
458
|
+
- UI 操作自动在主线程中调度
|
|
459
|
+
- 使用 Android 原生控件(无自定义样式)
|
|
460
|
+
- 仅在 Android 上测试(不支持 iOS)
|
|
461
|
+
|
|
462
|
+
## 📝 许可证
|
|
463
|
+
|
|
464
|
+
MIT
|
|
465
|
+
|
|
466
|
+
## 🤝 贡献
|
|
467
|
+
|
|
468
|
+
欢迎贡献!请提交问题和拉取请求来增强这个库。
|
|
469
|
+
|
|
470
|
+
## 🔗 相关链接
|
|
471
|
+
|
|
472
|
+
- [Frida 官网](https://frida.re/)
|
|
473
|
+
- [frida-compile 文档](https://github.com/nowsecure/frida-compile)
|
|
474
|
+
|
|
475
|
+
## 💡 提示
|
|
476
|
+
|
|
477
|
+
- 使用多标签页可以更好地组织复杂的 UI
|
|
478
|
+
- 组件之间可以通过 `menu.getComponent()` 相互访问
|
|
479
|
+
- 使用 `menu.setComponentValue()` 可以动态更新 UI
|
|
480
|
+
- 日志功能可以帮助调试,但会影响性能
|
|
481
|
+
- 所有示例代码都在 `example.ts` 中,可以参考学习
|
package/lib/api.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const API: {
|
|
2
|
+
readonly ImageView: Java.Wrapper<{}>;
|
|
3
|
+
readonly ImageViewScaleType: Java.Wrapper<{}>;
|
|
4
|
+
readonly FrameLayoutParams: Java.Wrapper<{}>;
|
|
5
|
+
readonly Gravity: Java.Wrapper<{}>;
|
|
6
|
+
readonly DialogMultiChoiceListener: Java.Wrapper<{}>;
|
|
7
|
+
readonly DialogClickListener: Java.Wrapper<{}>;
|
|
8
|
+
readonly HorizontalScrollView: Java.Wrapper<{}>;
|
|
9
|
+
readonly Context: Java.Wrapper<{}>;
|
|
10
|
+
readonly GradientDrawable: Java.Wrapper<{}>;
|
|
11
|
+
readonly BitmapFactory: Java.Wrapper<{}>;
|
|
12
|
+
readonly Base64: Java.Wrapper<{}>;
|
|
13
|
+
readonly Button: Java.Wrapper<{}>;
|
|
14
|
+
readonly Color: Java.Wrapper<{}>;
|
|
15
|
+
readonly ViewManager: Java.Wrapper<{}>;
|
|
16
|
+
readonly JString: Java.Wrapper<{}>;
|
|
17
|
+
readonly View: Java.Wrapper<{}>;
|
|
18
|
+
readonly GridLayout: Java.Wrapper<{}>;
|
|
19
|
+
readonly CheckBox: Java.Wrapper<{}>;
|
|
20
|
+
readonly SeekBar: Java.Wrapper<{}>;
|
|
21
|
+
readonly Html: Java.Wrapper<{}>;
|
|
22
|
+
readonly SeekBarOnSeekBarChangeListener: Java.Wrapper<{}>;
|
|
23
|
+
readonly AdapterViewOnItemSelectedListener: Java.Wrapper<{}>;
|
|
24
|
+
readonly Spinner: Java.Wrapper<{}>;
|
|
25
|
+
readonly ArrayAdapter: Java.Wrapper<{}>;
|
|
26
|
+
readonly R_layout: Java.Wrapper<{}>;
|
|
27
|
+
readonly DialogInterfaceOnClickListener: Java.Wrapper<{}>;
|
|
28
|
+
readonly AlertDialogBuilder: Java.Wrapper<{}>;
|
|
29
|
+
readonly AlertDialog: Java.Wrapper<{}>;
|
|
30
|
+
readonly OnTouchListener: Java.Wrapper<{}>;
|
|
31
|
+
readonly MotionEvent: Java.Wrapper<{}>;
|
|
32
|
+
readonly EditText: Java.Wrapper<{}>;
|
|
33
|
+
readonly Point: Java.Wrapper<{}>;
|
|
34
|
+
readonly DisplayMetrics: Java.Wrapper<{}>;
|
|
35
|
+
readonly TextViewBufferType: Java.Wrapper<{}>;
|
|
36
|
+
readonly InputType: Java.Wrapper<{}>;
|
|
37
|
+
readonly GridLayoutParams: Java.Wrapper<{}>;
|
|
38
|
+
readonly Switch: Java.Wrapper<{}>;
|
|
39
|
+
readonly OnCheckedChangeListener: Java.Wrapper<{}>;
|
|
40
|
+
readonly CompoundButtonOnCheckedChangeListener: Java.Wrapper<{}>;
|
|
41
|
+
readonly TextView: Java.Wrapper<{}>;
|
|
42
|
+
readonly OnClickListener: Java.Wrapper<{}>;
|
|
43
|
+
readonly FrameLayout: Java.Wrapper<{}>;
|
|
44
|
+
readonly ViewGroupLayoutParams: Java.Wrapper<{}>;
|
|
45
|
+
readonly LayoutParams: Java.Wrapper<{}>;
|
|
46
|
+
readonly BuildVERSION: Java.Wrapper<{}>;
|
|
47
|
+
readonly LinearLayout: Java.Wrapper<{}>;
|
|
48
|
+
readonly LinearLayoutParams: Java.Wrapper<{}>;
|
|
49
|
+
readonly ScrollView: Java.Wrapper<{}>;
|
|
50
|
+
};
|
package/lib/api.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.API = void 0;
|
|
4
|
+
exports.API = {
|
|
5
|
+
get ImageView() {
|
|
6
|
+
return Java.use("android.widget.ImageView");
|
|
7
|
+
},
|
|
8
|
+
get ImageViewScaleType() {
|
|
9
|
+
return Java.use("android.widget.ImageView$ScaleType");
|
|
10
|
+
},
|
|
11
|
+
get FrameLayoutParams() {
|
|
12
|
+
return Java.use("android.widget.FrameLayout$LayoutParams");
|
|
13
|
+
},
|
|
14
|
+
get Gravity() {
|
|
15
|
+
return Java.use("android.view.Gravity");
|
|
16
|
+
},
|
|
17
|
+
get DialogMultiChoiceListener() {
|
|
18
|
+
return Java.use("android.content.DialogInterface$OnMultiChoiceClickListener");
|
|
19
|
+
},
|
|
20
|
+
get DialogClickListener() {
|
|
21
|
+
return Java.use("android.content.DialogInterface$OnClickListener");
|
|
22
|
+
},
|
|
23
|
+
get HorizontalScrollView() {
|
|
24
|
+
return Java.use("android.widget.HorizontalScrollView");
|
|
25
|
+
},
|
|
26
|
+
get Context() {
|
|
27
|
+
return Java.use("android.content.Context");
|
|
28
|
+
},
|
|
29
|
+
get GradientDrawable() {
|
|
30
|
+
return Java.use("android.graphics.drawable.GradientDrawable");
|
|
31
|
+
},
|
|
32
|
+
get BitmapFactory() {
|
|
33
|
+
return Java.use("android.graphics.BitmapFactory");
|
|
34
|
+
},
|
|
35
|
+
get Base64() {
|
|
36
|
+
return Java.use("android.util.Base64");
|
|
37
|
+
},
|
|
38
|
+
get Button() {
|
|
39
|
+
return Java.use("android.widget.Button");
|
|
40
|
+
},
|
|
41
|
+
get Color() {
|
|
42
|
+
return Java.use("android.graphics.Color");
|
|
43
|
+
},
|
|
44
|
+
get ViewManager() {
|
|
45
|
+
return Java.use("android.view.ViewManager");
|
|
46
|
+
},
|
|
47
|
+
get JString() {
|
|
48
|
+
return Java.use("java.lang.String");
|
|
49
|
+
},
|
|
50
|
+
get View() {
|
|
51
|
+
return Java.use("android.view.View");
|
|
52
|
+
},
|
|
53
|
+
get GridLayout() {
|
|
54
|
+
return Java.use("android.widget.GridLayout");
|
|
55
|
+
},
|
|
56
|
+
get CheckBox() {
|
|
57
|
+
return Java.use("android.widget.CheckBox");
|
|
58
|
+
},
|
|
59
|
+
get SeekBar() {
|
|
60
|
+
return Java.use("android.widget.SeekBar");
|
|
61
|
+
},
|
|
62
|
+
get Html() {
|
|
63
|
+
return Java.use("android.text.Html");
|
|
64
|
+
},
|
|
65
|
+
get SeekBarOnSeekBarChangeListener() {
|
|
66
|
+
return Java.use("android.widget.SeekBar$OnSeekBarChangeListener");
|
|
67
|
+
},
|
|
68
|
+
get AdapterViewOnItemSelectedListener() {
|
|
69
|
+
return Java.use("android.widget.AdapterView$OnItemSelectedListener");
|
|
70
|
+
},
|
|
71
|
+
get Spinner() {
|
|
72
|
+
return Java.use("android.widget.Spinner");
|
|
73
|
+
},
|
|
74
|
+
get ArrayAdapter() {
|
|
75
|
+
return Java.use("android.widget.ArrayAdapter");
|
|
76
|
+
},
|
|
77
|
+
get R_layout() {
|
|
78
|
+
return Java.use("android.R$layout");
|
|
79
|
+
},
|
|
80
|
+
get DialogInterfaceOnClickListener() {
|
|
81
|
+
return Java.use("android.content.DialogInterface$OnClickListener");
|
|
82
|
+
},
|
|
83
|
+
get AlertDialogBuilder() {
|
|
84
|
+
return Java.use("android.app.AlertDialog$Builder");
|
|
85
|
+
},
|
|
86
|
+
get AlertDialog() {
|
|
87
|
+
return Java.use("android.app.AlertDialog");
|
|
88
|
+
},
|
|
89
|
+
get OnTouchListener() {
|
|
90
|
+
return Java.use("android.view.View$OnTouchListener");
|
|
91
|
+
},
|
|
92
|
+
get MotionEvent() {
|
|
93
|
+
return Java.use("android.view.MotionEvent");
|
|
94
|
+
},
|
|
95
|
+
get EditText() {
|
|
96
|
+
return Java.use("android.widget.EditText");
|
|
97
|
+
},
|
|
98
|
+
get Point() {
|
|
99
|
+
return Java.use("android.graphics.Point");
|
|
100
|
+
},
|
|
101
|
+
get DisplayMetrics() {
|
|
102
|
+
return Java.use("android.util.DisplayMetrics");
|
|
103
|
+
},
|
|
104
|
+
get TextViewBufferType() {
|
|
105
|
+
return Java.use("android.widget.TextView$BufferType");
|
|
106
|
+
},
|
|
107
|
+
get InputType() {
|
|
108
|
+
return Java.use("android.text.InputType");
|
|
109
|
+
},
|
|
110
|
+
get GridLayoutParams() {
|
|
111
|
+
return Java.use("android.widget.GridLayout$LayoutParams");
|
|
112
|
+
},
|
|
113
|
+
get Switch() {
|
|
114
|
+
return Java.use("android.widget.Switch");
|
|
115
|
+
},
|
|
116
|
+
get OnCheckedChangeListener() {
|
|
117
|
+
return Java.use("android.widget.CompoundButton$OnCheckedChangeListener");
|
|
118
|
+
},
|
|
119
|
+
get CompoundButtonOnCheckedChangeListener() {
|
|
120
|
+
return Java.use("android.widget.CompoundButton$OnCheckedChangeListener");
|
|
121
|
+
},
|
|
122
|
+
get TextView() {
|
|
123
|
+
return Java.use("android.widget.TextView");
|
|
124
|
+
},
|
|
125
|
+
get OnClickListener() {
|
|
126
|
+
return Java.use("android.view.View$OnClickListener");
|
|
127
|
+
},
|
|
128
|
+
get FrameLayout() {
|
|
129
|
+
return Java.use("android.widget.FrameLayout");
|
|
130
|
+
},
|
|
131
|
+
get ViewGroupLayoutParams() {
|
|
132
|
+
return Java.use("android.view.ViewGroup$LayoutParams");
|
|
133
|
+
},
|
|
134
|
+
get LayoutParams() {
|
|
135
|
+
return Java.use("android.view.WindowManager$LayoutParams");
|
|
136
|
+
},
|
|
137
|
+
get BuildVERSION() {
|
|
138
|
+
return Java.use("android.os.Build$VERSION");
|
|
139
|
+
},
|
|
140
|
+
get LinearLayout() {
|
|
141
|
+
return Java.use("android.widget.LinearLayout");
|
|
142
|
+
},
|
|
143
|
+
get LinearLayoutParams() {
|
|
144
|
+
return Java.use("android.widget.LinearLayout$LayoutParams");
|
|
145
|
+
},
|
|
146
|
+
get ScrollView() {
|
|
147
|
+
return Java.use("android.widget.ScrollView");
|
|
148
|
+
},
|
|
149
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { UIComponent } from "./ui-components";
|
|
2
|
+
export declare class Button extends UIComponent {
|
|
3
|
+
private label;
|
|
4
|
+
private handler;
|
|
5
|
+
private kind;
|
|
6
|
+
constructor(id: string, label: string, kind?: "primary" | "danger");
|
|
7
|
+
protected createView(context: any): void;
|
|
8
|
+
protected updateView(): void;
|
|
9
|
+
setLabel(label: string): void;
|
|
10
|
+
setOnClick(handler: () => void): void;
|
|
11
|
+
}
|