Project Architecture
July 21, 2026 · View on GitHub
High-Level Overview
The high-level architecture is composed of four logical layers and the connections between them:
-
Web UI (React frontend)
- React
- TanStack Query
- Socket.IO client
- WebSocket to maintain frida session state in realtime
- shadcn UI
- tailwindcss
- Base UI
-
Backend Server
- Hono for REST API
- Socket.IO for real-time API
- Drizzle ORM with SQLite for data persistence
- Acts as the Frida session manager and the bridge between the frontend and agent
-
Frida Session / Backend Process
- A Frida session is created per client connection and the platform-specific agent script is loaded into the target process
- RPC surface exposed by the agent follows the pattern
script.exports.invoke(namespace, method, args) - The web server proxies RPC calls, streams logs, and persists hook/trace data into stores
-
Target Device (Frida Agent injected into app)
- Agent runs inside the target app (iOS / Android) and provides:
- Registry for module routing
- Modules for inspection and control
- Hooks for runtime interception
- Bridges for language/runtime interop
- Communicates with the backend over the Frida transport channel
- Agent runs inside the target app (iOS / Android) and provides:
Directory Structure
- agent/ — Frida agent in TypeScript, compiled per platform. iOS (fruity) and Android (droid) agents, compiled outputs (dist/) and generated types/ used by the frontend.
- gui/ — React web frontend (Vite, Tailwind, shadcn/ui). Main code under gui/src with components, hooks, RPC client.
- src/ — Backend server (Hono + Socket.IO)
- app.ts: REST API
- ws.ts: Socket.IO server and session management
- drizzle/ — Database migration files (auto-generated by drizzle-kit)
- scripts/ — Build and development helper scripts
Socket.IO
Two namespaces handle real-time communication:
/devices— Broadcasts device list changes to all connected clients/session— Manages individual Frida sessions (one per connected client)
Session lifecycle:
- Client connects with query params (
device,platform,mode,bundle/pid) - Server attaches to the Frida device and resolves the target PID
- A Frida session is created and the platform-specific agent script is loaded
- Data stores (HookStore, CryptoStore, etc.) are initialized per session
- The
readyevent is emitted with the PID - RPC calls, logs, and hook events are proxied between client and agent
- Pin snapshots are persisted after changes
Data Stores
Each store class follows a consistent pattern: append(), query(), count(), rm(). All stores use a composite key of (deviceId, identifier) for session isolation.
| Store | Backing | Purpose |
|---|---|---|
HookStore | SQLite | ObjC/native method hook logs |
CryptoStore | SQLite | Cryptographic API interception logs |
NSURLStore | SQLite + files | HTTP/HTTPS request logs with body attachments |
HttpStore | SQLite + files | Android HTTP/WebSocket request logs with body attachments |
FlutterStore | SQLite | Flutter method channel events |
JNIStore | SQLite | Android JNI call traces |
XPCStore | SQLite | iOS XPC/NSXPC message traces |
PrivacyStore | SQLite | Sensitive API access logs |
HermesStore | SQLite | Captured Hermes bytecode blobs |
PinStore | File system | Hook rule snapshots for persistence |
LogWriter | File system | Syslog and agent log streaming |
Due to the limitation of web UI, there is no project or workspace concept, all data is stored in a global directory and disambiguated by device ID and target identifier (bundle or PID).
Database Schema (Drizzle + SQLite)
Uses Drizzle ORM with better-sqlite3 for SQLite persistence.
Migrations live in drizzle/ and are auto-generated by drizzle-kit.
Session Context
The SessionContext manages the active instrumentation session:
- Current platform (
fruity/droid) - Device ID, bundle/PID, mode
- Socket.IO connection lifecycle
- Typed RPC proxies that are platform-aware (accessing the wrong platform's API throws)
RPC Client
The frontend creates a proxy-based RPC client (gui/src/lib/rpc.ts) that:
- Queues calls until the
readyevent fires - Provides namespace.method(...args) syntax via
Proxy - Returns typed promises matching the agent's exported interfaces
- Rejects pending calls on disconnect
TanStack Query hooks (useFruityQuery, useDroidQuery, and usePlatformQuery) wrap RPC calls with caching, refetching, and error handling.
Agent
Platform-Specific Agents
Two independent agents compiled by frida-compile:
fruity(iOS) — Usesfrida-objc-bridgeandfrida-swift-bridgedroid(Android) — Usesfrida-java-bridge
Each agent exports four RPC methods:
rpc.exports = {
invoke(namespace, method, args) // Dynamic method dispatch
interfaces() // List all available namespace.method pairs
restore(rules) // Restore previously saved hook rules
snapshot() // Capture current hook state
}
Module Registry Pattern
Each platform defines a static router (router.ts) mapping namespace names to module objects:
// fruity/router.ts
export default { checksec, classdump, cookies, crypto, fs, keychain, ... }
The createRegistry(route) function (in common/registry.ts) wraps the router to produce invoke() and interfaces() functions. When invoke("fs", "ls", ["/"]) is called, it looks up route.fs.ls and calls it with the arguments.
Build & Asset Pipeline
Agent Build
frida-compile compiles TypeScript to Frida-compatible JavaScript:
dist/fruity.js— iOS agentdist/droid.js— Android agentdist/transport.js— File transfer helper
Type definitions are generated via tsgo and output to agent/types/ for use by the frontend (aliased as @agent in Vite config).
Bridge scripts (Java, ObjC, Swift) are downloaded from PyPI packages and stored in dist/bridges/.
Node.js SEA Build
The scripts/build-sea.ts script implements the Node.js single executable application flow:
- Fetches the radare2 WASM asset
- Bundles the server and its JavaScript dependencies into one CommonJS entry
- Embeds the GUI, agent, Drizzle migrations, skills, radare2 WASM, and native addons as SEA assets
- Generates a Node.js SEA preparation blob and injects it into a copy of the current Node.js executable
At runtime, the SEA entry extracts embedded files and native addons to a content-addressed temporary directory before starting the server. Release CI builds each target on its native runner.
npm Package Build
tsdown bundles the backend into ESM output (dist/index.mjs). The package includes pre-built agent and GUI assets.