frida-ui-runtime 2.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 +181 -0
- package/lib/android-listener.d.ts +20 -0
- package/lib/android-listener.js +92 -0
- package/lib/android-runtime.d.ts +25 -0
- package/lib/android-runtime.js +310 -0
- package/lib/api.d.ts +1 -0
- package/lib/api.js +156 -0
- package/lib/component/button.d.ts +11 -0
- package/lib/component/button.js +61 -0
- package/lib/component/category.d.ts +9 -0
- package/lib/component/category.js +57 -0
- package/lib/component/checkBox.d.ts +33 -0
- package/lib/component/checkBox.js +276 -0
- package/lib/component/collapsible.d.ts +19 -0
- package/lib/component/collapsible.js +197 -0
- package/lib/component/image.d.ts +18 -0
- package/lib/component/image.js +85 -0
- package/lib/component/infoCardBox.d.ts +13 -0
- package/lib/component/infoCardBox.js +68 -0
- package/lib/component/input.d.ts +41 -0
- package/lib/component/input.js +342 -0
- package/lib/component/selector.d.ts +31 -0
- package/lib/component/selector.js +246 -0
- package/lib/component/slider.d.ts +18 -0
- package/lib/component/slider.js +156 -0
- package/lib/component/style/style.d.ts +4 -0
- package/lib/component/style/style.js +325 -0
- package/lib/component/style/theme.d.ts +33 -0
- package/lib/component/style/theme.js +24 -0
- package/lib/component/switch.d.ts +12 -0
- package/lib/component/switch.js +117 -0
- package/lib/component/text.d.ts +9 -0
- package/lib/component/text.js +36 -0
- package/lib/component/ui-components.d.ts +34 -0
- package/lib/component/ui-components.js +108 -0
- package/lib/component/views/header-view.d.ts +20 -0
- package/lib/component/views/header-view.js +198 -0
- package/lib/component/views/log-view.d.ts +41 -0
- package/lib/component/views/log-view.js +511 -0
- package/lib/component/views/tabs-view.d.ts +41 -0
- package/lib/component/views/tabs-view.js +334 -0
- package/lib/constant-config.d.ts +5 -0
- package/lib/constant-config.js +3 -0
- package/lib/event-emitter.d.ts +10 -0
- package/lib/event-emitter.js +52 -0
- package/lib/float-menu.d.ts +103 -0
- package/lib/float-menu.js +648 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +16 -0
- package/lib/java-runtime.d.ts +2 -0
- package/lib/java-runtime.js +2 -0
- package/lib/logger.d.ts +43 -0
- package/lib/logger.js +200 -0
- package/lib/runtime/component-registry.d.ts +24 -0
- package/lib/runtime/component-registry.js +48 -0
- package/lib/runtime/launch-sequence.d.ts +5 -0
- package/lib/runtime/launch-sequence.js +20 -0
- package/lib/runtime/lifecycle-controller.d.ts +9 -0
- package/lib/runtime/lifecycle-controller.js +31 -0
- package/lib/runtime/listener-registry.d.ts +7 -0
- package/lib/runtime/listener-registry.js +40 -0
- package/lib/runtime/overlay-options.d.ts +9 -0
- package/lib/runtime/overlay-options.js +42 -0
- package/lib/runtime/safe-runtime.d.ts +3 -0
- package/lib/runtime/safe-runtime.js +20 -0
- package/lib/utils.d.ts +8 -0
- package/lib/utils.js +13 -0
- package/package.json +43 -0
package/lib/logger.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare function log(message: string): void;
|
|
2
|
+
export type LogLevel = "debug" | "info" | "warn" | "error" | "none";
|
|
3
|
+
type LogItem = {
|
|
4
|
+
ts: number;
|
|
5
|
+
level: LogLevel;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
type LogListener = (items: LogItem[]) => void;
|
|
9
|
+
export declare class Logger {
|
|
10
|
+
on(event: string, listener: (level: LogLevel, message: string) => void): () => void;
|
|
11
|
+
private static _instance;
|
|
12
|
+
static get instance(): Logger;
|
|
13
|
+
private levelPriority;
|
|
14
|
+
private currentLevel;
|
|
15
|
+
private maxBuffer;
|
|
16
|
+
private buffer;
|
|
17
|
+
private head;
|
|
18
|
+
private size;
|
|
19
|
+
private flushIntervalMs;
|
|
20
|
+
private pending;
|
|
21
|
+
private flushTimer;
|
|
22
|
+
private listeners;
|
|
23
|
+
constructor(level?: LogLevel, options?: {
|
|
24
|
+
maxBuffer?: number;
|
|
25
|
+
flushIntervalMs?: number;
|
|
26
|
+
});
|
|
27
|
+
setLevel(level: LogLevel): void;
|
|
28
|
+
setMaxBuffer(max: number): void;
|
|
29
|
+
debug(...args: any[]): void;
|
|
30
|
+
info(...args: any[]): void;
|
|
31
|
+
warn(...args: any[]): void;
|
|
32
|
+
error(...args: any[]): void;
|
|
33
|
+
onLog(listener: LogListener, replay?: boolean): () => void;
|
|
34
|
+
getRecent(limit?: number): LogItem[];
|
|
35
|
+
clear(): void;
|
|
36
|
+
private safeStringify;
|
|
37
|
+
private formatArgs;
|
|
38
|
+
private _log;
|
|
39
|
+
private pushToBuffer;
|
|
40
|
+
private scheduleFlush;
|
|
41
|
+
private emitBatch;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { ConstantConfig } from "./constant-config.js";
|
|
2
|
+
export function log(message) {
|
|
3
|
+
console.log(message);
|
|
4
|
+
}
|
|
5
|
+
export class Logger {
|
|
6
|
+
on(event, listener) {
|
|
7
|
+
if (event !== "log")
|
|
8
|
+
throw new Error(`Unsupported logger event: ${event}`);
|
|
9
|
+
return this.onLog((items) => {
|
|
10
|
+
for (const item of items)
|
|
11
|
+
listener(item.level, item.message);
|
|
12
|
+
}, false);
|
|
13
|
+
}
|
|
14
|
+
static get instance() {
|
|
15
|
+
if (!Logger._instance)
|
|
16
|
+
Logger._instance = new Logger(ConstantConfig.isDev ? "debug" : "info");
|
|
17
|
+
return Logger._instance;
|
|
18
|
+
}
|
|
19
|
+
constructor(level = "info", options) {
|
|
20
|
+
this.levelPriority = {
|
|
21
|
+
debug: 0,
|
|
22
|
+
info: 1,
|
|
23
|
+
warn: 2,
|
|
24
|
+
error: 3,
|
|
25
|
+
none: 4,
|
|
26
|
+
};
|
|
27
|
+
this.head = 0;
|
|
28
|
+
this.size = 0;
|
|
29
|
+
this.pending = [];
|
|
30
|
+
this.flushTimer = null;
|
|
31
|
+
this.listeners = [];
|
|
32
|
+
this.currentLevel = level;
|
|
33
|
+
this.maxBuffer = Math.max(50, options?.maxBuffer ?? 300);
|
|
34
|
+
this.flushIntervalMs = Math.max(8, options?.flushIntervalMs ?? 16);
|
|
35
|
+
this.buffer = new Array(this.maxBuffer);
|
|
36
|
+
}
|
|
37
|
+
setLevel(level) {
|
|
38
|
+
this.currentLevel = level;
|
|
39
|
+
}
|
|
40
|
+
setMaxBuffer(max) {
|
|
41
|
+
const newMax = Math.max(50, max | 0);
|
|
42
|
+
if (newMax === this.maxBuffer)
|
|
43
|
+
return;
|
|
44
|
+
const items = this.getRecent(newMax);
|
|
45
|
+
this.maxBuffer = newMax;
|
|
46
|
+
this.buffer = new Array(this.maxBuffer);
|
|
47
|
+
this.head = 0;
|
|
48
|
+
this.size = 0;
|
|
49
|
+
for (const it of items)
|
|
50
|
+
this.pushToBuffer(it);
|
|
51
|
+
}
|
|
52
|
+
debug(...args) {
|
|
53
|
+
this._log("debug", ...args);
|
|
54
|
+
}
|
|
55
|
+
info(...args) {
|
|
56
|
+
this._log("info", ...args);
|
|
57
|
+
}
|
|
58
|
+
warn(...args) {
|
|
59
|
+
this._log("warn", ...args);
|
|
60
|
+
}
|
|
61
|
+
error(...args) {
|
|
62
|
+
this._log("error", ...args);
|
|
63
|
+
}
|
|
64
|
+
onLog(listener, replay = true) {
|
|
65
|
+
this.listeners.push(listener);
|
|
66
|
+
if (replay) {
|
|
67
|
+
const recent = this.getRecent(this.maxBuffer);
|
|
68
|
+
if (recent.length)
|
|
69
|
+
listener(recent);
|
|
70
|
+
}
|
|
71
|
+
return () => {
|
|
72
|
+
const idx = this.listeners.indexOf(listener);
|
|
73
|
+
if (idx >= 0)
|
|
74
|
+
this.listeners.splice(idx, 1);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
getRecent(limit = 200) {
|
|
78
|
+
const n = Math.min(Math.max(1, limit | 0), this.size);
|
|
79
|
+
const out = [];
|
|
80
|
+
if (n <= 0)
|
|
81
|
+
return out;
|
|
82
|
+
const start = (this.head - this.size + this.maxBuffer) % this.maxBuffer;
|
|
83
|
+
const begin = (this.head - n + this.maxBuffer) % this.maxBuffer;
|
|
84
|
+
let i = begin;
|
|
85
|
+
for (let k = 0; k < n; k++) {
|
|
86
|
+
const it = this.buffer[i];
|
|
87
|
+
if (it)
|
|
88
|
+
out.push(it);
|
|
89
|
+
i = (i + 1) % this.maxBuffer;
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
clear() {
|
|
94
|
+
this.buffer = new Array(this.maxBuffer);
|
|
95
|
+
this.head = 0;
|
|
96
|
+
this.size = 0;
|
|
97
|
+
this.pending.length = 0;
|
|
98
|
+
this.emitBatch([]);
|
|
99
|
+
}
|
|
100
|
+
safeStringify(v) {
|
|
101
|
+
try {
|
|
102
|
+
return JSON.stringify(v);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
try {
|
|
106
|
+
return String(v);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return "[Unstringifiable]";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
formatArgs(args) {
|
|
114
|
+
if (!args || args.length === 0)
|
|
115
|
+
return "";
|
|
116
|
+
if (args.length === 1 && typeof args[0] === "string")
|
|
117
|
+
return args[0];
|
|
118
|
+
let out = "";
|
|
119
|
+
for (let i = 0; i < args.length; i++) {
|
|
120
|
+
const a = args[i];
|
|
121
|
+
let s;
|
|
122
|
+
if (typeof a === "string")
|
|
123
|
+
s = a;
|
|
124
|
+
else if (a == null)
|
|
125
|
+
s = String(a);
|
|
126
|
+
else if (typeof a === "number" ||
|
|
127
|
+
typeof a === "boolean" ||
|
|
128
|
+
typeof a === "bigint")
|
|
129
|
+
s = String(a);
|
|
130
|
+
else if (a instanceof Error)
|
|
131
|
+
s = a.stack || a.message || String(a);
|
|
132
|
+
else
|
|
133
|
+
s = this.safeStringify(a);
|
|
134
|
+
if (i === 0)
|
|
135
|
+
out = s;
|
|
136
|
+
else
|
|
137
|
+
out += " " + s;
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
_log(level, ...args) {
|
|
142
|
+
if (this.levelPriority[level] < this.levelPriority[this.currentLevel])
|
|
143
|
+
return;
|
|
144
|
+
const msg = this.formatArgs(args);
|
|
145
|
+
const item = {
|
|
146
|
+
ts: Date.now(),
|
|
147
|
+
level,
|
|
148
|
+
message: msg,
|
|
149
|
+
};
|
|
150
|
+
switch (level) {
|
|
151
|
+
case "debug":
|
|
152
|
+
console.debug(msg);
|
|
153
|
+
break;
|
|
154
|
+
case "info":
|
|
155
|
+
console.info(msg);
|
|
156
|
+
break;
|
|
157
|
+
case "warn":
|
|
158
|
+
console.warn(msg);
|
|
159
|
+
break;
|
|
160
|
+
case "error":
|
|
161
|
+
console.error(msg);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
this.pushToBuffer(item);
|
|
165
|
+
this.pending.push(item);
|
|
166
|
+
const pendingLimit = this.maxBuffer * 2;
|
|
167
|
+
if (this.pending.length > pendingLimit) {
|
|
168
|
+
this.pending.splice(0, this.pending.length - pendingLimit);
|
|
169
|
+
}
|
|
170
|
+
this.scheduleFlush();
|
|
171
|
+
}
|
|
172
|
+
pushToBuffer(item) {
|
|
173
|
+
this.buffer[this.head] = item;
|
|
174
|
+
this.head = (this.head + 1) % this.maxBuffer;
|
|
175
|
+
if (this.size < this.maxBuffer)
|
|
176
|
+
this.size++;
|
|
177
|
+
}
|
|
178
|
+
scheduleFlush() {
|
|
179
|
+
if (this.flushTimer)
|
|
180
|
+
return;
|
|
181
|
+
this.flushTimer = setTimeout(() => {
|
|
182
|
+
this.flushTimer = null;
|
|
183
|
+
const batch = this.pending;
|
|
184
|
+
this.pending = [];
|
|
185
|
+
this.emitBatch(batch);
|
|
186
|
+
}, this.flushIntervalMs);
|
|
187
|
+
}
|
|
188
|
+
emitBatch(items) {
|
|
189
|
+
if (!this.listeners.length)
|
|
190
|
+
return;
|
|
191
|
+
for (let i = 0; i < this.listeners.length; i++) {
|
|
192
|
+
const fn = this.listeners[i];
|
|
193
|
+
try {
|
|
194
|
+
fn(items);
|
|
195
|
+
}
|
|
196
|
+
catch { }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
Logger._instance = null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface RegisteredComponent {
|
|
2
|
+
getId(): string;
|
|
3
|
+
getValue?(): unknown;
|
|
4
|
+
captureState?(): unknown;
|
|
5
|
+
dispose?(): void | Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface ComponentEntry<T extends RegisteredComponent> {
|
|
8
|
+
component: T;
|
|
9
|
+
tabId: string;
|
|
10
|
+
ownerId?: string;
|
|
11
|
+
container?: unknown;
|
|
12
|
+
}
|
|
13
|
+
export declare class ComponentRegistry<T extends RegisteredComponent = RegisteredComponent> {
|
|
14
|
+
private readonly components;
|
|
15
|
+
add(component: T, tabId: string, ownerId?: string): void;
|
|
16
|
+
get(id: string): T | undefined;
|
|
17
|
+
getEntry(id: string): ComponentEntry<T> | undefined;
|
|
18
|
+
remove(id: string): ComponentEntry<T> | undefined;
|
|
19
|
+
attach(id: string, container: unknown): void;
|
|
20
|
+
childrenOf(ownerId: string): ComponentEntry<T>[];
|
|
21
|
+
values(): IterableIterator<ComponentEntry<T>>;
|
|
22
|
+
captureState(): Record<string, unknown>;
|
|
23
|
+
clear(): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export class ComponentRegistry {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.components = new Map();
|
|
4
|
+
}
|
|
5
|
+
add(component, tabId, ownerId) {
|
|
6
|
+
const id = component.getId();
|
|
7
|
+
if (!id)
|
|
8
|
+
throw new Error("Component identifier cannot be empty");
|
|
9
|
+
if (this.components.has(id)) {
|
|
10
|
+
throw new Error(`Component ${id} is already registered`);
|
|
11
|
+
}
|
|
12
|
+
this.components.set(id, { component, tabId, ownerId });
|
|
13
|
+
}
|
|
14
|
+
get(id) {
|
|
15
|
+
return this.components.get(id)?.component;
|
|
16
|
+
}
|
|
17
|
+
getEntry(id) {
|
|
18
|
+
return this.components.get(id);
|
|
19
|
+
}
|
|
20
|
+
remove(id) {
|
|
21
|
+
const entry = this.components.get(id);
|
|
22
|
+
if (entry)
|
|
23
|
+
this.components.delete(id);
|
|
24
|
+
return entry;
|
|
25
|
+
}
|
|
26
|
+
attach(id, container) {
|
|
27
|
+
const entry = this.components.get(id);
|
|
28
|
+
if (!entry)
|
|
29
|
+
throw new Error(`Component ${id} is not registered`);
|
|
30
|
+
entry.container = container;
|
|
31
|
+
}
|
|
32
|
+
childrenOf(ownerId) {
|
|
33
|
+
return Array.from(this.components.values()).filter(entry => entry.ownerId === ownerId);
|
|
34
|
+
}
|
|
35
|
+
values() {
|
|
36
|
+
return this.components.values();
|
|
37
|
+
}
|
|
38
|
+
captureState() {
|
|
39
|
+
const state = {};
|
|
40
|
+
for (const [id, { component }] of this.components) {
|
|
41
|
+
state[id] = component.captureState?.() ?? component.getValue?.();
|
|
42
|
+
}
|
|
43
|
+
return state;
|
|
44
|
+
}
|
|
45
|
+
clear() {
|
|
46
|
+
this.components.clear();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface LaunchableOverlay {
|
|
2
|
+
present(mode: "icon"): Promise<void>;
|
|
3
|
+
dispose(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export declare function runLaunchSequence<T extends LaunchableOverlay>(create: () => Promise<T>, setup: (overlay: T) => void | Promise<void>, onCleanupError?: (error: unknown) => void): Promise<T>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export async function runLaunchSequence(create, setup, onCleanupError) {
|
|
2
|
+
let overlay;
|
|
3
|
+
try {
|
|
4
|
+
overlay = await create();
|
|
5
|
+
await setup(overlay);
|
|
6
|
+
await overlay.present("icon");
|
|
7
|
+
return overlay;
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (overlay) {
|
|
11
|
+
try {
|
|
12
|
+
await overlay.dispose();
|
|
13
|
+
}
|
|
14
|
+
catch (cleanupError) {
|
|
15
|
+
onCleanupError?.(cleanupError);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type LifecycleState = "created" | "mounted" | "icon-visible" | "menu-visible" | "hidden" | "disposing" | "disposed";
|
|
2
|
+
export declare class LifecycleController {
|
|
3
|
+
private queue;
|
|
4
|
+
private currentState;
|
|
5
|
+
get state(): LifecycleState;
|
|
6
|
+
get disposed(): boolean;
|
|
7
|
+
transition(next: LifecycleState): void;
|
|
8
|
+
run<T>(name: string, operation: () => T | Promise<T>): Promise<T>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class LifecycleController {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.queue = Promise.resolve();
|
|
4
|
+
this.currentState = "created";
|
|
5
|
+
}
|
|
6
|
+
get state() {
|
|
7
|
+
return this.currentState;
|
|
8
|
+
}
|
|
9
|
+
get disposed() {
|
|
10
|
+
return this.currentState === "disposed";
|
|
11
|
+
}
|
|
12
|
+
transition(next) {
|
|
13
|
+
if (this.currentState === "disposed" && next !== "disposed") {
|
|
14
|
+
throw new Error(`Cannot transition disposed lifecycle to ${next}`);
|
|
15
|
+
}
|
|
16
|
+
if (this.currentState === "disposing" && next !== "disposed") {
|
|
17
|
+
throw new Error(`Cannot transition disposing lifecycle to ${next}`);
|
|
18
|
+
}
|
|
19
|
+
this.currentState = next;
|
|
20
|
+
}
|
|
21
|
+
run(name, operation) {
|
|
22
|
+
const result = this.queue.then(async () => {
|
|
23
|
+
if (this.disposed && name !== "dispose") {
|
|
24
|
+
throw new Error(`Cannot run ${name}: lifecycle is disposed`);
|
|
25
|
+
}
|
|
26
|
+
return operation();
|
|
27
|
+
});
|
|
28
|
+
this.queue = result.then(() => undefined, () => undefined);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class ListenerRegistry {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.listeners = new Map();
|
|
4
|
+
}
|
|
5
|
+
add(owner, unsubscribe) {
|
|
6
|
+
let owned = this.listeners.get(owner);
|
|
7
|
+
if (!owned) {
|
|
8
|
+
owned = new Set();
|
|
9
|
+
this.listeners.set(owner, owned);
|
|
10
|
+
}
|
|
11
|
+
owned.add(unsubscribe);
|
|
12
|
+
return () => {
|
|
13
|
+
if (!owned.delete(unsubscribe))
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
unsubscribe();
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
if (owned.size === 0)
|
|
20
|
+
this.listeners.delete(owner);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
clear(owner) {
|
|
25
|
+
const owned = this.listeners.get(owner);
|
|
26
|
+
if (!owned)
|
|
27
|
+
return;
|
|
28
|
+
this.listeners.delete(owner);
|
|
29
|
+
for (const unsubscribe of owned) {
|
|
30
|
+
try {
|
|
31
|
+
unsubscribe();
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
clearAll() {
|
|
37
|
+
for (const owner of Array.from(this.listeners.keys()))
|
|
38
|
+
this.clear(owner);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const DEFAULT_PERMISSION_DENIED_MESSAGE = "\u8BF7\u5148\u6388\u4E88\u60AC\u6D6E\u7A97\u6743\u9650";
|
|
2
|
+
export declare class OverlayPermissionError extends Error {
|
|
3
|
+
constructor(message?: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class InvalidOverlayIconError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export declare function normalizeIconBase64(input: string): string;
|
|
9
|
+
export declare function requireOverlayPermission(granted: boolean, message: string, showToast: (message: string) => void, onToastError?: (error: unknown) => void): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const DEFAULT_PERMISSION_DENIED_MESSAGE = "请先授予悬浮窗权限";
|
|
2
|
+
export class OverlayPermissionError extends Error {
|
|
3
|
+
constructor(message = DEFAULT_PERMISSION_DENIED_MESSAGE) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "OverlayPermissionError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class InvalidOverlayIconError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "InvalidOverlayIconError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function normalizeIconBase64(input) {
|
|
15
|
+
if (typeof input !== "string") {
|
|
16
|
+
throw new InvalidOverlayIconError("Overlay icon Base64 must be a string");
|
|
17
|
+
}
|
|
18
|
+
const trimmed = input.trim();
|
|
19
|
+
const marker = ";base64,";
|
|
20
|
+
const markerIndex = trimmed.toLowerCase().indexOf(marker);
|
|
21
|
+
const payload = markerIndex >= 0
|
|
22
|
+
? trimmed.slice(markerIndex + marker.length)
|
|
23
|
+
: trimmed;
|
|
24
|
+
const normalized = payload.replace(/\s+/g, "");
|
|
25
|
+
if (normalized.length === 0
|
|
26
|
+
|| normalized.length % 4 === 1
|
|
27
|
+
|| !/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) {
|
|
28
|
+
throw new InvalidOverlayIconError("Overlay icon contains invalid Base64 data");
|
|
29
|
+
}
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
export function requireOverlayPermission(granted, message, showToast, onToastError) {
|
|
33
|
+
if (granted)
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
showToast(message);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
onToastError?.(error);
|
|
40
|
+
}
|
|
41
|
+
throw new OverlayPermissionError(message);
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Logger } from "../logger.js";
|
|
2
|
+
function contextualError(label, cause) {
|
|
3
|
+
const detail = cause instanceof Error ? cause.stack || cause.message : String(cause);
|
|
4
|
+
return new Error(`${label}: ${detail}`);
|
|
5
|
+
}
|
|
6
|
+
function defaultReporter(error) {
|
|
7
|
+
Logger.instance.error(error);
|
|
8
|
+
}
|
|
9
|
+
export function safeInvoke(label, callback, report = defaultReporter) {
|
|
10
|
+
try {
|
|
11
|
+
return callback();
|
|
12
|
+
}
|
|
13
|
+
catch (cause) {
|
|
14
|
+
report(contextualError(label, cause));
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function deferSafe(label, callback, report = defaultReporter) {
|
|
19
|
+
setTimeout(() => safeInvoke(label, callback, report), 0);
|
|
20
|
+
}
|
package/lib/utils.d.ts
ADDED
package/lib/utils.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ConstantConfig } from "./constant-config.js";
|
|
2
|
+
export function windowToLogical(wx, wy, w, h) {
|
|
3
|
+
return {
|
|
4
|
+
x: Math.round(wx + (ConstantConfig.screenWidth - w) / 2),
|
|
5
|
+
y: Math.round(wy + (ConstantConfig.screenHeight - h) / 2),
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function logicalToWindow(lx, ly, w, h) {
|
|
9
|
+
return {
|
|
10
|
+
x: Math.round(lx - (ConstantConfig.screenWidth - w) / 2),
|
|
11
|
+
y: Math.round(ly - (ConstantConfig.screenHeight - h) / 2),
|
|
12
|
+
};
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frida-ui-runtime",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Lifecycle-safe Android UI runtime for Frida scripts",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "lib/index.js",
|
|
9
|
+
"types": "lib/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"lib",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "frida-compile example.ts -o example.js -w",
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "npm run build",
|
|
18
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
19
|
+
"check": "tsc --noEmit && node --test tests/*.test.mjs"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/Matchman33/frida-ui-runtime"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"frida",
|
|
27
|
+
"android",
|
|
28
|
+
"ui",
|
|
29
|
+
"floating-window",
|
|
30
|
+
"overlay",
|
|
31
|
+
"hooking"
|
|
32
|
+
],
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"frida-java-bridge": "*"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/frida-gum": "^19.0.1",
|
|
38
|
+
"@types/node": "^24.0.10",
|
|
39
|
+
"frida-compile": "^19.0.4",
|
|
40
|
+
"frida-java-bridge": "7.0.4",
|
|
41
|
+
"typescript": "^5.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|