adb-sqlite-viewer 1.0.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Siddharth Bisht
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # SQLite DevTools for Mobile (React Native)
2
+
3
+ A browser-based tool for inspecting SQLite databases on Android devices. Browse tables, view schemas, and execute SQL queries directly on your device.
4
+
5
+ ## Four Ways to Use
6
+
7
+ ### Option 1: Desktop App (Easiest)
8
+
9
+ Download the installer from [Releases](https://github.com/amitwinit/SQLite-DevTools-Mobile-ReactNative/releases) and run it. The app bundles the ADB bridge server — it starts automatically when you launch the app. No separate downloads, no terminal commands.
10
+
11
+ **Requirements:**
12
+ - Windows (NSIS installer)
13
+ - `adb` on your PATH (Android SDK Platform-Tools)
14
+ - Android device with USB debugging enabled
15
+
16
+ ### Option 2: Hosted Version + ADB Bridge (Best for React Native Developers)
17
+
18
+ Use the deployed version at **[amitwinit.github.io/SQLite-DevTools-Mobile-ReactNative](https://amitwinit.github.io/SQLite-DevTools-Mobile-ReactNative/)** together with the **ADB Bridge** — a small localhost server that wraps `adb shell` commands. This lets you inspect databases while ADB stays running for React Native development.
19
+
20
+ **Setup:**
21
+
22
+ 1. Download `adb-bridge.exe` from [Releases](https://github.com/amitwinit/SQLite-DevTools-Mobile-ReactNative/releases), or build it yourself:
23
+ ```bash
24
+ cd bridge
25
+ npm install
26
+ npm run build # produces adb-bridge.exe
27
+ ```
28
+
29
+ 2. Run the bridge:
30
+ ```bash
31
+ # Either run the exe directly:
32
+ adb-bridge.exe
33
+
34
+ # Or with Node.js:
35
+ cd bridge && node server.js
36
+ ```
37
+
38
+ 3. Open the hosted website — it auto-detects the bridge and connects through it.
39
+
40
+ **How it works:**
41
+ ```
42
+ Hosted website (HTTPS) ──HTTP──> localhost:15555 (bridge) ──> adb shell ──> Device
43
+ ```
44
+ The website detects the bridge on startup and routes all commands through HTTP instead of WebUSB. No need to kill ADB.
45
+
46
+ ### Option 3: Hosted Version with WebUSB (No Setup Required)
47
+
48
+ Use the deployed version at **[amitwinit.github.io/SQLite-DevTools-Mobile-ReactNative](https://amitwinit.github.io/SQLite-DevTools-Mobile-ReactNative/)**
49
+
50
+ This version uses **WebUSB** to communicate with your Android device directly from the browser. No backend server needed.
51
+
52
+ **Requirements:**
53
+ - Chrome or Edge (WebUSB is not supported in Firefox/Safari)
54
+ - Android device with USB debugging enabled
55
+ - You must **stop the local ADB server** first: `adb kill-server`
56
+
57
+ **Important:** WebUSB and the local ADB server cannot use the USB interface at the same time. If you are actively developing a React Native app and need ADB running, use **Option 1** or **Option 2** instead.
58
+
59
+ **Steps:**
60
+ 1. Run `adb kill-server` in your terminal
61
+ 2. Open the hosted URL in Chrome/Edge
62
+ 3. Click **Connect Device** and select your phone from the USB picker
63
+ 4. Approve the USB debugging prompt on your phone (first time only)
64
+ 5. Select a package and database, then start querying
65
+
66
+ ### Option 4: Local Flask Server (Legacy)
67
+
68
+ If you are developing a React Native app and need ADB running alongside, use the local Flask backend. Both tools share the same ADB server so there is no conflict.
69
+
70
+ **Setup:**
71
+
72
+ 1. Install Python dependencies:
73
+ ```bash
74
+ pip install -r requirements.txt
75
+ ```
76
+
77
+ 2. Copy and configure environment:
78
+ ```bash
79
+ cp .env.example .env
80
+ ```
81
+
82
+ Update `.env` with your configuration:
83
+ - `DEVICE_SERIAL` — run `adb devices` to find it
84
+ - `PACKAGE_NAME` — your app's package name
85
+ - `DB_NAME` — the SQLite database filename
86
+ - `PYTHON_TOOLS_PATH` — path to the python_tools directory
87
+
88
+ 3. Run the server:
89
+ ```bash
90
+ python app.py
91
+ ```
92
+
93
+ 4. Open http://localhost:5001 in any browser
94
+
95
+ ## When to Use Which
96
+
97
+ | Scenario | Use |
98
+ |----------|-----|
99
+ | Just want it to work, one click | Desktop App (Option 1) |
100
+ | Active React Native development | Desktop App (Option 1) or ADB Bridge (Option 2) |
101
+ | Quick DB inspection, no local setup | WebUSB (Option 3) |
102
+ | Sharing with teammates who don't have Python | WebUSB (Option 3) |
103
+ | Need ADB for other tools simultaneously | Desktop App (Option 1) or ADB Bridge (Option 2) |
104
+
105
+ ## Environment Variables (Option 4)
106
+
107
+ ### Application Configuration
108
+ - `PACKAGE_NAME`: Android app package name
109
+ - `DB_NAME`: Database name on the device
110
+ - `DEVICE_SERIAL`: ADB device serial number
111
+ - `PYTHON_TOOLS_PATH`: Path to python_tools directory
112
+
113
+ ### Flask Server Configuration
114
+ - `FLASK_HOST`: Flask server host (default: 0.0.0.0)
115
+ - `FLASK_PORT`: Flask server port (default: 5001)
116
+ - `FLASK_DEBUG`: Enable debug mode (default: True)
117
+
118
+ ### Cache Configuration
119
+ - `USE_CACHE`: Enable database caching (default: True)
120
+ - `FORCE_LOCAL`: Force local database operations (default: False)
121
+
122
+ ## Development
123
+
124
+ To work on the WebUSB frontend:
125
+
126
+ ```bash
127
+ npm install
128
+ npm run dev
129
+ ```
130
+
131
+ To build for production (GitHub Pages):
132
+
133
+ ```bash
134
+ npm run build
135
+ ```
136
+
137
+ The built files go to `dist/` and are deployed to GitHub Pages automatically on push to `main`.
138
+
139
+ To run the Electron desktop app in development:
140
+
141
+ ```bash
142
+ npm run electron:dev
143
+ ```
144
+
145
+ To build the Electron installer:
146
+
147
+ ```bash
148
+ npm run electron:build
149
+ ```
150
+
151
+ The installer is output to `electron-dist/`.
@@ -0,0 +1,251 @@
1
+ const http = require("http");
2
+ const { execFile, spawn } = require("child_process");
3
+
4
+ const TIMEOUT = 30_000;
5
+ const MAX_BUFFER = 10 * 1024 * 1024;
6
+
7
+ // ── Helpers ────────────────────────────────────────────
8
+
9
+ function cors(res) {
10
+ res.setHeader("Access-Control-Allow-Origin", "*");
11
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
12
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
13
+ }
14
+
15
+ function json(res, status, data) {
16
+ cors(res);
17
+ res.writeHead(status, { "Content-Type": "application/json" });
18
+ res.end(JSON.stringify(data));
19
+ }
20
+
21
+ function readBody(req) {
22
+ return new Promise((resolve, reject) => {
23
+ const chunks = [];
24
+ let size = 0;
25
+ req.on("data", (chunk) => {
26
+ size += chunk.length;
27
+ if (size > 1_000_000) {
28
+ reject(new Error("Request body too large"));
29
+ req.destroy();
30
+ return;
31
+ }
32
+ chunks.push(chunk);
33
+ });
34
+ req.on("end", () => resolve(Buffer.concat(chunks).toString()));
35
+ req.on("error", reject);
36
+ });
37
+ }
38
+
39
+ /** Run adb with simple args (devices, version). Rejects on non-zero exit. */
40
+ function adb(args) {
41
+ return new Promise((resolve, reject) => {
42
+ execFile("adb", args, { timeout: TIMEOUT, maxBuffer: MAX_BUFFER }, (err, stdout, stderr) => {
43
+ if (err) {
44
+ reject(new Error(stderr?.trim() || err.message));
45
+ } else {
46
+ resolve(stdout);
47
+ }
48
+ });
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Run a shell command on device via stdin piping.
54
+ * Avoids Windows argument quoting issues with complex shell commands.
55
+ * Returns stdout even on non-zero exit codes (common for probe scripts).
56
+ */
57
+ function adbShell(command, serial) {
58
+ return new Promise((resolve, reject) => {
59
+ const args = [];
60
+ if (serial) args.push("-s", serial);
61
+ args.push("shell");
62
+
63
+ const proc = spawn("adb", args, { windowsHide: true });
64
+ let stdout = "";
65
+ let stderr = "";
66
+ let settled = false;
67
+
68
+ const timer = setTimeout(() => {
69
+ if (!settled) {
70
+ settled = true;
71
+ proc.kill();
72
+ reject(new Error("Command timed out"));
73
+ }
74
+ }, TIMEOUT);
75
+
76
+ proc.stdout.on("data", (data) => { stdout += data; });
77
+ proc.stderr.on("data", (data) => { stderr += data; });
78
+
79
+ proc.on("close", () => {
80
+ clearTimeout(timer);
81
+ if (settled) return;
82
+ settled = true;
83
+ // Always resolve with stdout — device commands often exit non-zero
84
+ // (e.g. probe scripts where some iterations fail) but still produce
85
+ // valid output. Only reject if we got nothing and stderr has content.
86
+ if (!stdout && stderr.trim()) {
87
+ reject(new Error(stderr.trim()));
88
+ } else {
89
+ resolve(stdout);
90
+ }
91
+ });
92
+
93
+ proc.on("error", (err) => {
94
+ clearTimeout(timer);
95
+ if (settled) return;
96
+ settled = true;
97
+ reject(err);
98
+ });
99
+
100
+ // Send command through stdin — bypasses Windows command-line quoting entirely
101
+ proc.stdin.write(command + "\n");
102
+ proc.stdin.end();
103
+ });
104
+ }
105
+
106
+ // ── Routes ─────────────────────────────────────────────
107
+
108
+ async function handlePing(_req, res) {
109
+ json(res, 200, { ok: true });
110
+ }
111
+
112
+ async function handleDevices(_req, res) {
113
+ try {
114
+ const raw = await adb(["devices", "-l"]);
115
+ const lines = raw.split("\n").slice(1); // skip header
116
+ const devices = [];
117
+ for (const line of lines) {
118
+ const trimmed = line.trim();
119
+ if (!trimmed || trimmed.startsWith("*")) continue;
120
+ const parts = trimmed.split(/\s+/);
121
+ const serial = parts[0];
122
+ const state = parts[1];
123
+ if (state !== "device") continue;
124
+
125
+ // Extract model from "model:<value>" token
126
+ let model = "";
127
+ for (const p of parts.slice(2)) {
128
+ if (p.startsWith("model:")) {
129
+ model = p.slice(6);
130
+ break;
131
+ }
132
+ }
133
+ devices.push({
134
+ serial,
135
+ display_name: model ? `${model} (${serial})` : serial,
136
+ });
137
+ }
138
+ json(res, 200, { devices });
139
+ } catch (err) {
140
+ json(res, 500, { error: err.message });
141
+ }
142
+ }
143
+
144
+ async function handleShell(req, res) {
145
+ let body;
146
+ try {
147
+ body = JSON.parse(await readBody(req));
148
+ } catch {
149
+ json(res, 400, { error: "Invalid JSON body" });
150
+ return;
151
+ }
152
+
153
+ const { command, serial } = body;
154
+ if (!command || typeof command !== "string") {
155
+ json(res, 400, { error: "Missing 'command' string in body" });
156
+ return;
157
+ }
158
+
159
+ try {
160
+ const output = await adbShell(command, serial);
161
+ json(res, 200, { output });
162
+ } catch (err) {
163
+ json(res, 500, { error: err.message });
164
+ }
165
+ }
166
+
167
+ // ── Exports (for use by cli/server.cjs and electron) ───
168
+
169
+ /**
170
+ * Dispatch an incoming request to the appropriate API handler.
171
+ * Returns true if a route was matched, false otherwise.
172
+ */
173
+ async function handleRequest(req, res) {
174
+ const url = new URL(req.url, "http://localhost");
175
+ const p = url.pathname;
176
+
177
+ if (req.method === "OPTIONS") {
178
+ cors(res);
179
+ res.writeHead(204);
180
+ res.end();
181
+ return true;
182
+ }
183
+
184
+ try {
185
+ if (p === "/api/ping" && req.method === "GET") {
186
+ await handlePing(req, res);
187
+ return true;
188
+ } else if (p === "/api/devices" && req.method === "GET") {
189
+ await handleDevices(req, res);
190
+ return true;
191
+ } else if (p === "/api/shell" && req.method === "POST") {
192
+ await handleShell(req, res);
193
+ return true;
194
+ }
195
+ } catch (err) {
196
+ json(res, 500, { error: err.message });
197
+ return true;
198
+ }
199
+
200
+ return false;
201
+ }
202
+
203
+ /**
204
+ * Verify adb is in PATH. Returns the version string, or rejects.
205
+ */
206
+ function checkAdb() {
207
+ return new Promise((resolve, reject) => {
208
+ execFile("adb", ["version"], { timeout: 5000 }, (err, stdout) => {
209
+ if (err) {
210
+ reject(new Error("'adb' not found in PATH. Install Android SDK Platform-Tools."));
211
+ return;
212
+ }
213
+ resolve(stdout.split("\n")[0].trim());
214
+ });
215
+ });
216
+ }
217
+
218
+ module.exports = { handleRequest, checkAdb };
219
+
220
+ // ── Standalone startup (node bridge/server.js) ─────────
221
+
222
+ if (require.main === module) {
223
+ const PORT = 15555;
224
+ const HOST = "127.0.0.1";
225
+
226
+ const server = http.createServer(async (req, res) => {
227
+ const handled = await handleRequest(req, res);
228
+ if (!handled) {
229
+ json(res, 404, { error: "Not found" });
230
+ }
231
+ });
232
+
233
+ checkAdb()
234
+ .then((versionLine) => {
235
+ console.log(`Found: ${versionLine}`);
236
+ server.listen(PORT, HOST, () => {
237
+ console.log(`ADB Bridge listening on http://${HOST}:${PORT}`);
238
+ console.log("Endpoints:");
239
+ console.log(" GET /api/ping — health check");
240
+ console.log(" GET /api/devices — list connected devices");
241
+ console.log(" POST /api/shell — run adb shell command");
242
+ console.log("\nPress Ctrl+C to stop.");
243
+ if (process.send) process.send({ type: "ready" });
244
+ });
245
+ })
246
+ .catch((err) => {
247
+ console.error("ERROR:", err.message);
248
+ if (process.send) process.send({ type: "error", message: err.message });
249
+ process.exit(1);
250
+ });
251
+ }
package/cli/bin.cjs ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ let port = 8085;
5
+ const args = process.argv.slice(2);
6
+
7
+ for (let i = 0; i < args.length; i++) {
8
+ if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) {
9
+ port = parseInt(args[i + 1], 10);
10
+ i++;
11
+ } else if (args[i].startsWith("--port=")) {
12
+ port = parseInt(args[i].split("=")[1], 10);
13
+ } else if (args[i] === "--help" || args[i] === "-h") {
14
+ console.log("Usage: sqlite-viewer [--port <number>]");
15
+ console.log(" --port, -p Port to listen on (default: 8085)");
16
+ process.exit(0);
17
+ }
18
+ }
19
+
20
+ if (isNaN(port) || port < 1 || port > 65535) {
21
+ console.error("Invalid port number.");
22
+ process.exit(1);
23
+ }
24
+
25
+ require("./server.cjs").start(port);
package/cli/server.cjs ADDED
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+
3
+ const http = require("http");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const { exec } = require("child_process");
7
+ const { handleRequest: bridgeHandler, checkAdb } = require("../bridge/server.js");
8
+
9
+ const DIST_DIR = path.join(__dirname, "..", "dist");
10
+
11
+ const MIME = {
12
+ ".html": "text/html; charset=utf-8",
13
+ ".js": "application/javascript; charset=utf-8",
14
+ ".css": "text/css; charset=utf-8",
15
+ ".json": "application/json",
16
+ ".svg": "image/svg+xml",
17
+ ".png": "image/png",
18
+ ".ico": "image/x-icon",
19
+ ".woff": "font/woff",
20
+ ".woff2": "font/woff2",
21
+ };
22
+
23
+ function serveStatic(req, res) {
24
+ let urlPath = new URL(req.url, "http://localhost").pathname;
25
+ if (urlPath === "/") urlPath = "/index.html";
26
+
27
+ const filePath = path.join(DIST_DIR, urlPath);
28
+ const resolved = path.resolve(filePath);
29
+
30
+ // Prevent path traversal
31
+ if (!resolved.startsWith(path.resolve(DIST_DIR))) {
32
+ res.writeHead(403);
33
+ res.end("Forbidden");
34
+ return;
35
+ }
36
+
37
+ fs.readFile(resolved, (err, data) => {
38
+ if (err) {
39
+ // SPA fallback — serve index.html for unknown paths
40
+ fs.readFile(path.join(DIST_DIR, "index.html"), (err2, html) => {
41
+ if (err2) {
42
+ res.writeHead(500);
43
+ res.end("dist/index.html not found. Was the package built?");
44
+ return;
45
+ }
46
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
47
+ res.end(html);
48
+ });
49
+ return;
50
+ }
51
+ const ext = path.extname(resolved);
52
+ const mime = MIME[ext] || "application/octet-stream";
53
+ const isHashed = /assets[\\/].*\.[a-f0-9A-Z_-]{5,}\.(js|css)$/.test(resolved);
54
+ res.writeHead(200, {
55
+ "Content-Type": mime,
56
+ "Cache-Control": isHashed ? "public, max-age=31536000, immutable" : "no-cache",
57
+ });
58
+ res.end(data);
59
+ });
60
+ }
61
+
62
+ async function requestHandler(req, res) {
63
+ const urlPath = new URL(req.url, "http://localhost").pathname;
64
+
65
+ if (urlPath.startsWith("/api/") || req.method === "OPTIONS") {
66
+ const handled = await bridgeHandler(req, res);
67
+ if (handled) return;
68
+ }
69
+
70
+ serveStatic(req, res);
71
+ }
72
+
73
+ function openBrowser(url) {
74
+ const cmd =
75
+ process.platform === "win32" ? `start "" "${url}"`
76
+ : process.platform === "darwin" ? `open "${url}"`
77
+ : `xdg-open "${url}"`;
78
+ exec(cmd, () => {});
79
+ }
80
+
81
+ async function start(port) {
82
+ // Verify dist/ exists
83
+ if (!fs.existsSync(path.join(DIST_DIR, "index.html"))) {
84
+ console.error("ERROR: dist/index.html not found.");
85
+ console.error("Run: npm run build:npm");
86
+ process.exit(1);
87
+ }
88
+
89
+ // Check adb (warn only — WebUSB still works without it)
90
+ try {
91
+ const version = await checkAdb();
92
+ console.log(`Found: ${version}`);
93
+ } catch (err) {
94
+ console.warn(`WARNING: ${err.message}`);
95
+ console.warn("ADB bridge features will not work.");
96
+ }
97
+
98
+ const server = http.createServer(requestHandler);
99
+
100
+ server.listen(port, "127.0.0.1", () => {
101
+ const url = `http://127.0.0.1:${port}`;
102
+ console.log("");
103
+ console.log(" ADB SQLite DevTools");
104
+ console.log(` Running at ${url}`);
105
+ console.log("");
106
+ console.log(" Press Ctrl+C to stop.");
107
+ console.log("");
108
+ setTimeout(() => openBrowser(url), 300);
109
+ });
110
+
111
+ server.on("error", (err) => {
112
+ if (err.code === "EADDRINUSE") {
113
+ console.error(`Port ${port} is already in use. Try: sqlite-viewer --port ${port + 1}`);
114
+ } else {
115
+ console.error("Server error:", err.message);
116
+ }
117
+ process.exit(1);
118
+ });
119
+ }
120
+
121
+ module.exports = { start };