browser-cdp 0.6.1 → 0.6.4
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 +158 -0
- package/cli.js +8 -0
- package/package.json +1 -1
- package/src/cookies.js +88 -0
- package/src/network.js +171 -0
- package/src/storage.js +170 -0
package/README.md
CHANGED
|
@@ -37,6 +37,23 @@ browser-cdp pick '<message>'
|
|
|
37
37
|
# Stream browser console output (network errors, exceptions, logs)
|
|
38
38
|
browser-cdp console [--duration=SECONDS]
|
|
39
39
|
|
|
40
|
+
# Stream network requests/responses
|
|
41
|
+
browser-cdp network [--filter=PATTERN] [--json] [--errors] [--duration=SECONDS]
|
|
42
|
+
|
|
43
|
+
# Manage cookies (export/import/clear)
|
|
44
|
+
browser-cdp cookies export [--path=FILE]
|
|
45
|
+
browser-cdp cookies import <file>
|
|
46
|
+
browser-cdp cookies clear
|
|
47
|
+
|
|
48
|
+
# Manage localStorage/sessionStorage
|
|
49
|
+
browser-cdp storage get <key>
|
|
50
|
+
browser-cdp storage set <key> <value>
|
|
51
|
+
browser-cdp storage list
|
|
52
|
+
browser-cdp storage clear
|
|
53
|
+
browser-cdp storage export [--path=FILE]
|
|
54
|
+
browser-cdp storage import <file>
|
|
55
|
+
# Add --session for sessionStorage instead of localStorage
|
|
56
|
+
|
|
40
57
|
# Show page performance metrics
|
|
41
58
|
browser-cdp insights [--json]
|
|
42
59
|
|
|
@@ -89,6 +106,46 @@ browser-cdp console
|
|
|
89
106
|
# Stream console for 10 seconds
|
|
90
107
|
browser-cdp console --duration=10
|
|
91
108
|
|
|
109
|
+
# Stream network traffic
|
|
110
|
+
browser-cdp network
|
|
111
|
+
# Then navigate to see requests
|
|
112
|
+
|
|
113
|
+
# Filter to API calls only
|
|
114
|
+
browser-cdp network --filter=api
|
|
115
|
+
|
|
116
|
+
# Only show failed requests (4xx/5xx)
|
|
117
|
+
browser-cdp network --errors
|
|
118
|
+
|
|
119
|
+
# JSON output for parsing
|
|
120
|
+
browser-cdp network --json --duration=5 | jq '.url'
|
|
121
|
+
|
|
122
|
+
# Export cookies to JSON file
|
|
123
|
+
browser-cdp cookies export
|
|
124
|
+
# Returns: cookies.json with all cookies
|
|
125
|
+
|
|
126
|
+
# Export to custom file
|
|
127
|
+
browser-cdp cookies export --path session.json
|
|
128
|
+
|
|
129
|
+
# Import cookies from file
|
|
130
|
+
browser-cdp cookies import session.json
|
|
131
|
+
|
|
132
|
+
# Clear all cookies
|
|
133
|
+
browser-cdp cookies clear
|
|
134
|
+
|
|
135
|
+
# Get/set localStorage values
|
|
136
|
+
browser-cdp storage get authToken
|
|
137
|
+
browser-cdp storage set theme dark
|
|
138
|
+
|
|
139
|
+
# List all storage keys
|
|
140
|
+
browser-cdp storage list
|
|
141
|
+
|
|
142
|
+
# Export/import storage
|
|
143
|
+
browser-cdp storage export
|
|
144
|
+
browser-cdp storage import storage.json
|
|
145
|
+
|
|
146
|
+
# Work with sessionStorage
|
|
147
|
+
browser-cdp storage get tempData --session
|
|
148
|
+
|
|
92
149
|
# Get page performance insights
|
|
93
150
|
browser-cdp insights
|
|
94
151
|
# Returns: TTFB, First Paint, FCP, DOM loaded, resources, memory
|
|
@@ -103,6 +160,107 @@ browser-cdp lighthouse
|
|
|
103
160
|
browser-cdp close
|
|
104
161
|
```
|
|
105
162
|
|
|
163
|
+
## Cookies Command
|
|
164
|
+
|
|
165
|
+
The `cookies` command provides session persistence for authenticated workflows:
|
|
166
|
+
|
|
167
|
+
### Export Cookies
|
|
168
|
+
|
|
169
|
+
Save your browser cookies to a JSON file for later use:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
browser-cdp cookies export # Saves to cookies.json
|
|
173
|
+
browser-cdp cookies export --path auth.json # Save to specific file
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Output format:
|
|
177
|
+
```json
|
|
178
|
+
[
|
|
179
|
+
{
|
|
180
|
+
"name": "session_id",
|
|
181
|
+
"value": "abc123...",
|
|
182
|
+
"domain": "example.com",
|
|
183
|
+
"path": "/",
|
|
184
|
+
"httpOnly": true,
|
|
185
|
+
"secure": true,
|
|
186
|
+
"sameSite": "Strict",
|
|
187
|
+
"expires": 1735689600
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Import Cookies
|
|
193
|
+
|
|
194
|
+
Load previously exported cookies into the browser:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
browser-cdp cookies import auth.json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Useful for:
|
|
201
|
+
- Resuming authenticated sessions across browser restarts
|
|
202
|
+
- Sharing sessions across team members
|
|
203
|
+
- Preserving login state for automation workflows
|
|
204
|
+
|
|
205
|
+
### Clear Cookies
|
|
206
|
+
|
|
207
|
+
Delete all cookies from the browser:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
browser-cdp cookies clear
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Storage Command
|
|
214
|
+
|
|
215
|
+
The `storage` command provides localStorage and sessionStorage management:
|
|
216
|
+
|
|
217
|
+
### Get/Set Values
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
browser-cdp storage get authToken # Get from localStorage
|
|
221
|
+
browser-cdp storage set theme dark # Set in localStorage
|
|
222
|
+
browser-cdp storage set tempData "session" --session # Set in sessionStorage
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### List Keys
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
browser-cdp storage list # List localStorage keys
|
|
229
|
+
browser-cdp storage list --session # List sessionStorage keys
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Export/Import
|
|
233
|
+
|
|
234
|
+
Save storage to a JSON file for backup or restore:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
browser-cdp storage export # Saves to storage.json
|
|
238
|
+
browser-cdp storage export --path app-state.json # Save to specific file
|
|
239
|
+
browser-cdp storage import app-state.json # Restore from file
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Output format:
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"authToken": "eyJhbGciOiJIUzI1NiIs...",
|
|
246
|
+
"theme": "dark"
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Clear Storage
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
browser-cdp storage clear # Clear localStorage
|
|
254
|
+
browser-cdp storage clear --session # Clear sessionStorage
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### localStorage vs sessionStorage
|
|
258
|
+
|
|
259
|
+
| Storage Type | Lifetime | Scope | Flag |
|
|
260
|
+
|--------------|----------|-------|------|
|
|
261
|
+
| localStorage | Permanent | Per origin | (default) |
|
|
262
|
+
| sessionStorage | Tab session | Per tab | `--session` |
|
|
263
|
+
|
|
106
264
|
## Pre-started Browser
|
|
107
265
|
|
|
108
266
|
If you already have a browser running with CDP enabled, the CLI will connect to it:
|
package/cli.js
CHANGED
|
@@ -20,6 +20,9 @@ const commands = {
|
|
|
20
20
|
pdf: "./src/pdf.js",
|
|
21
21
|
pick: "./src/pick.js",
|
|
22
22
|
console: "./src/console.js",
|
|
23
|
+
network: "./src/network.js",
|
|
24
|
+
cookies: "./src/cookies.js",
|
|
25
|
+
storage: "./src/storage.js",
|
|
23
26
|
insights: "./src/insights.js",
|
|
24
27
|
lighthouse: "./src/lighthouse.js",
|
|
25
28
|
};
|
|
@@ -39,6 +42,9 @@ function printUsage() {
|
|
|
39
42
|
console.log(" pdf Export current page as PDF");
|
|
40
43
|
console.log(" pick '<message>' Interactive element picker");
|
|
41
44
|
console.log(" console Stream browser console output");
|
|
45
|
+
console.log(" network Stream network requests/responses");
|
|
46
|
+
console.log(" cookies Export/import/clear browser cookies");
|
|
47
|
+
console.log(" storage Manage localStorage/sessionStorage");
|
|
42
48
|
console.log(" insights Show page performance metrics");
|
|
43
49
|
console.log(" lighthouse Run Lighthouse audit");
|
|
44
50
|
console.log("");
|
|
@@ -53,6 +59,8 @@ function printUsage() {
|
|
|
53
59
|
console.log(" browser-cdp eval 'document.title'");
|
|
54
60
|
console.log(" browser-cdp dom > page.html");
|
|
55
61
|
console.log(" browser-cdp console --duration=10");
|
|
62
|
+
console.log(" browser-cdp network --filter=api");
|
|
63
|
+
console.log(" browser-cdp cookies export --path session.json");
|
|
56
64
|
console.log(" browser-cdp insights --json");
|
|
57
65
|
process.exit(0);
|
|
58
66
|
}
|
package/package.json
CHANGED
package/src/cookies.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { chromium } from "playwright";
|
|
5
|
+
import { DEFAULT_PORT, getActivePage } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const subcommand = args[0];
|
|
9
|
+
const showHelp = args.includes("--help") || args.includes("-h");
|
|
10
|
+
|
|
11
|
+
function printUsage() {
|
|
12
|
+
console.log("Usage: cookies <subcommand> [options]");
|
|
13
|
+
console.log("");
|
|
14
|
+
console.log("Subcommands:");
|
|
15
|
+
console.log(" export Export cookies to JSON file");
|
|
16
|
+
console.log(" import <file> Import cookies from JSON file");
|
|
17
|
+
console.log(" clear Clear all cookies from browser");
|
|
18
|
+
console.log("");
|
|
19
|
+
console.log("Options:");
|
|
20
|
+
console.log(" --path <file> Output file path for export (default: cookies.json)");
|
|
21
|
+
console.log("");
|
|
22
|
+
console.log("Examples:");
|
|
23
|
+
console.log(" cookies export");
|
|
24
|
+
console.log(" cookies export --path session.json");
|
|
25
|
+
console.log(" cookies import session.json");
|
|
26
|
+
console.log(" cookies clear");
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!subcommand || showHelp) {
|
|
31
|
+
printUsage();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!["export", "import", "clear"].includes(subcommand)) {
|
|
35
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
36
|
+
console.log("Available: export, import, clear");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
|
|
41
|
+
const contexts = browser.contexts();
|
|
42
|
+
const context = contexts[0];
|
|
43
|
+
|
|
44
|
+
if (!context) {
|
|
45
|
+
console.error("No browser context found");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const pages = context.pages();
|
|
50
|
+
const page = getActivePage(pages);
|
|
51
|
+
|
|
52
|
+
if (!page) {
|
|
53
|
+
console.error("No active tab found");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const cdp = await page.context().newCDPSession(page);
|
|
58
|
+
|
|
59
|
+
if (subcommand === "export") {
|
|
60
|
+
const pathIdx = args.findIndex((a) => a === "--path");
|
|
61
|
+
const outputFile = pathIdx !== -1 ? args[pathIdx + 1] : "cookies.json";
|
|
62
|
+
|
|
63
|
+
const { cookies } = await cdp.send("Network.getCookies");
|
|
64
|
+
writeFileSync(outputFile, JSON.stringify(cookies, null, 2));
|
|
65
|
+
console.log(`Exported ${cookies.length} cookie(s) to ${outputFile}`);
|
|
66
|
+
} else if (subcommand === "import") {
|
|
67
|
+
const importFile = args[1];
|
|
68
|
+
|
|
69
|
+
if (!importFile) {
|
|
70
|
+
console.error("Error: import requires a file path");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const cookies = JSON.parse(readFileSync(importFile, "utf8"));
|
|
75
|
+
|
|
76
|
+
if (!Array.isArray(cookies)) {
|
|
77
|
+
console.error("Error: Cookie file must contain a JSON array");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await cdp.send("Network.setCookies", { cookies });
|
|
82
|
+
console.log(`Imported ${cookies.length} cookie(s) from ${importFile}`);
|
|
83
|
+
} else if (subcommand === "clear") {
|
|
84
|
+
await cdp.send("Network.clearBrowserCookies");
|
|
85
|
+
console.log("Cleared all cookies");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await browser.close();
|
package/src/network.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { chromium } from "playwright";
|
|
4
|
+
import { DEFAULT_PORT, getActivePage, formatTime, resetColor } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const filterPattern = args.find((a) => a.startsWith("--filter="))?.split("=")[1];
|
|
8
|
+
const jsonOutput = args.includes("--json");
|
|
9
|
+
const errorsOnly = args.includes("--errors");
|
|
10
|
+
const duration = args.find((a) => a.startsWith("--duration="));
|
|
11
|
+
const durationMs = duration ? parseInt(duration.split("=")[1]) * 1000 : null;
|
|
12
|
+
const showHelp = args.includes("--help") || args.includes("-h");
|
|
13
|
+
|
|
14
|
+
if (showHelp) {
|
|
15
|
+
console.log("Usage: network.js [options]");
|
|
16
|
+
console.log("\nStream network requests and responses in real-time.");
|
|
17
|
+
console.log("\nOptions:");
|
|
18
|
+
console.log(" --filter=PATTERN Filter URLs by regex pattern");
|
|
19
|
+
console.log(" --json Output as JSON for piping");
|
|
20
|
+
console.log(" --errors Only show failed requests (4xx/5xx)");
|
|
21
|
+
console.log(" --duration=N Stop after N seconds (default: run until Ctrl+C)");
|
|
22
|
+
console.log("\nExamples:");
|
|
23
|
+
console.log(" network.js # Stream all network traffic");
|
|
24
|
+
console.log(" network.js --filter=api # Only show URLs containing 'api'");
|
|
25
|
+
console.log(" network.js --errors # Only show failed requests");
|
|
26
|
+
console.log(" network.js --json # JSON output for jq/parsing");
|
|
27
|
+
console.log(" network.js --duration=10 # Capture for 10 seconds");
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
|
|
32
|
+
const contexts = browser.contexts();
|
|
33
|
+
const context = contexts[0];
|
|
34
|
+
|
|
35
|
+
if (!context) {
|
|
36
|
+
console.error("No browser context found");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const pages = context.pages();
|
|
41
|
+
const page = getActivePage(pages);
|
|
42
|
+
|
|
43
|
+
if (!page) {
|
|
44
|
+
console.error("No active tab found");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.error(`Connected to: ${page.url()}`);
|
|
49
|
+
|
|
50
|
+
const cdp = await page.context().newCDPSession(page);
|
|
51
|
+
await cdp.send("Network.enable");
|
|
52
|
+
|
|
53
|
+
const requests = new Map();
|
|
54
|
+
|
|
55
|
+
const statusColors = {
|
|
56
|
+
success: "\x1b[32m", // green for 2xx
|
|
57
|
+
redirect: "\x1b[33m", // yellow for 3xx
|
|
58
|
+
error: "\x1b[31m", // red for 4xx/5xx
|
|
59
|
+
request: "\x1b[36m", // cyan for requests
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function matchesFilter(url) {
|
|
63
|
+
if (!filterPattern) return true;
|
|
64
|
+
try {
|
|
65
|
+
return new RegExp(filterPattern).test(url);
|
|
66
|
+
} catch {
|
|
67
|
+
return url.includes(filterPattern);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
cdp.on("Network.requestWillBeSent", ({ requestId, request, timestamp }) => {
|
|
72
|
+
requests.set(requestId, { method: request.method, url: request.url, timestamp });
|
|
73
|
+
|
|
74
|
+
if (!matchesFilter(request.url)) return;
|
|
75
|
+
if (errorsOnly) return;
|
|
76
|
+
|
|
77
|
+
if (jsonOutput) {
|
|
78
|
+
console.log(
|
|
79
|
+
JSON.stringify({
|
|
80
|
+
type: "request",
|
|
81
|
+
timestamp: new Date(timestamp * 1000).toISOString(),
|
|
82
|
+
method: request.method,
|
|
83
|
+
url: request.url,
|
|
84
|
+
requestId,
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
console.log(`${statusColors.request}[${formatTime()}] → ${request.method} ${request.url}${resetColor}`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
cdp.on("Network.responseReceived", ({ requestId, response, timestamp, type }) => {
|
|
93
|
+
const req = requests.get(requestId);
|
|
94
|
+
|
|
95
|
+
if (!matchesFilter(response.url)) return;
|
|
96
|
+
if (errorsOnly && response.status < 400) return;
|
|
97
|
+
|
|
98
|
+
if (jsonOutput) {
|
|
99
|
+
console.log(
|
|
100
|
+
JSON.stringify({
|
|
101
|
+
type: "response",
|
|
102
|
+
timestamp: new Date(timestamp * 1000).toISOString(),
|
|
103
|
+
status: response.status,
|
|
104
|
+
statusText: response.statusText,
|
|
105
|
+
url: response.url,
|
|
106
|
+
mimeType: response.mimeType,
|
|
107
|
+
resourceType: type,
|
|
108
|
+
requestId,
|
|
109
|
+
method: req?.method,
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
} else {
|
|
113
|
+
let color = statusColors.success;
|
|
114
|
+
if (response.status >= 400) {
|
|
115
|
+
color = statusColors.error;
|
|
116
|
+
} else if (response.status >= 300) {
|
|
117
|
+
color = statusColors.redirect;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const method = req ? `${req.method} ` : "";
|
|
121
|
+
console.log(`${color}[${formatTime()}] ← ${response.status} ${method}${response.url}${resetColor}`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
cdp.on("Network.loadingFailed", ({ requestId, errorText, blockedReason, canceled }) => {
|
|
126
|
+
const req = requests.get(requestId);
|
|
127
|
+
|
|
128
|
+
if (req && !matchesFilter(req.url)) return;
|
|
129
|
+
|
|
130
|
+
if (jsonOutput) {
|
|
131
|
+
console.log(
|
|
132
|
+
JSON.stringify({
|
|
133
|
+
type: "failed",
|
|
134
|
+
timestamp: new Date().toISOString(),
|
|
135
|
+
url: req?.url,
|
|
136
|
+
method: req?.method,
|
|
137
|
+
error: errorText,
|
|
138
|
+
blockedReason,
|
|
139
|
+
canceled,
|
|
140
|
+
requestId,
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
const reason = blockedReason ? ` (${blockedReason})` : "";
|
|
145
|
+
const canceledStr = canceled ? " [CANCELED]" : "";
|
|
146
|
+
console.log(
|
|
147
|
+
`${statusColors.error}[${formatTime()}] ✗ ${errorText}${reason}${canceledStr} - ${req?.url || "unknown"}${resetColor}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (requestId) {
|
|
152
|
+
requests.delete(requestId);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
console.error(`Listening for network activity... (Ctrl+C to stop)`);
|
|
157
|
+
|
|
158
|
+
if (durationMs) {
|
|
159
|
+
await new Promise((r) => setTimeout(r, durationMs));
|
|
160
|
+
await browser.close();
|
|
161
|
+
} else {
|
|
162
|
+
// Keep running until interrupted
|
|
163
|
+
process.on("SIGINT", async () => {
|
|
164
|
+
console.error("\nStopping...");
|
|
165
|
+
await browser.close();
|
|
166
|
+
process.exit(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Keep the process alive
|
|
170
|
+
await new Promise(() => {});
|
|
171
|
+
}
|
package/src/storage.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { chromium } from "playwright";
|
|
5
|
+
import { DEFAULT_PORT, getActivePage } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const subcommand = args[0];
|
|
9
|
+
const showHelp = args.includes("--help") || args.includes("-h");
|
|
10
|
+
const useSessionStorage = args.includes("--session");
|
|
11
|
+
const storageType = useSessionStorage ? "sessionStorage" : "localStorage";
|
|
12
|
+
|
|
13
|
+
function printUsage() {
|
|
14
|
+
console.log("Usage: storage <subcommand> [options]");
|
|
15
|
+
console.log("");
|
|
16
|
+
console.log("Subcommands:");
|
|
17
|
+
console.log(" get <key> Get value for key");
|
|
18
|
+
console.log(" set <key> <value> Set key to value");
|
|
19
|
+
console.log(" list List all keys");
|
|
20
|
+
console.log(" clear Clear all storage");
|
|
21
|
+
console.log(" export Export storage to JSON file");
|
|
22
|
+
console.log(" import <file> Import storage from JSON file");
|
|
23
|
+
console.log("");
|
|
24
|
+
console.log("Options:");
|
|
25
|
+
console.log(" --session Use sessionStorage instead of localStorage");
|
|
26
|
+
console.log(" --path <file> Output file path for export (default: storage.json)");
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log("Examples:");
|
|
29
|
+
console.log(" storage get token");
|
|
30
|
+
console.log(" storage set theme dark");
|
|
31
|
+
console.log(" storage list");
|
|
32
|
+
console.log(" storage export");
|
|
33
|
+
console.log(" storage export --path session.json --session");
|
|
34
|
+
console.log(" storage import session.json");
|
|
35
|
+
console.log(" storage clear");
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!subcommand || showHelp) {
|
|
40
|
+
printUsage();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!["get", "set", "list", "clear", "export", "import"].includes(subcommand)) {
|
|
44
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
45
|
+
console.log("Available: get, set, list, clear, export, import");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const browser = await chromium.connectOverCDP(`http://localhost:${DEFAULT_PORT}`);
|
|
50
|
+
const contexts = browser.contexts();
|
|
51
|
+
const context = contexts[0];
|
|
52
|
+
|
|
53
|
+
if (!context) {
|
|
54
|
+
console.error("No browser context found");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const pages = context.pages();
|
|
59
|
+
const page = getActivePage(pages);
|
|
60
|
+
|
|
61
|
+
if (!page) {
|
|
62
|
+
console.error("No active tab found");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const positionalArgs = args.filter((a) => !a.startsWith("--"));
|
|
67
|
+
|
|
68
|
+
if (subcommand === "get") {
|
|
69
|
+
const key = positionalArgs[1];
|
|
70
|
+
|
|
71
|
+
if (!key) {
|
|
72
|
+
console.error("Error: get requires a key");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = await page.evaluate(
|
|
77
|
+
([storageType, key]) => window[storageType].getItem(key),
|
|
78
|
+
[storageType, key]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (result === null) {
|
|
82
|
+
console.error(`Key "${key}" not found in ${storageType}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(result);
|
|
87
|
+
} else if (subcommand === "set") {
|
|
88
|
+
const key = positionalArgs[1];
|
|
89
|
+
const value = positionalArgs.slice(2).join(" ");
|
|
90
|
+
|
|
91
|
+
if (!key || value === "") {
|
|
92
|
+
console.error("Error: set requires <key> <value>");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await page.evaluate(
|
|
97
|
+
([storageType, key, value]) => window[storageType].setItem(key, value),
|
|
98
|
+
[storageType, key, value]
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
console.log(`Set ${key} in ${storageType}`);
|
|
102
|
+
} else if (subcommand === "list") {
|
|
103
|
+
const keys = await page.evaluate(
|
|
104
|
+
(storageType) => Object.keys(window[storageType]),
|
|
105
|
+
storageType
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (keys.length === 0) {
|
|
109
|
+
console.log(`No keys found in ${storageType}`);
|
|
110
|
+
} else {
|
|
111
|
+
console.log(`Keys in ${storageType}:`);
|
|
112
|
+
keys.forEach((key) => console.log(` ${key}`));
|
|
113
|
+
}
|
|
114
|
+
} else if (subcommand === "clear") {
|
|
115
|
+
await page.evaluate((storageType) => window[storageType].clear(), storageType);
|
|
116
|
+
|
|
117
|
+
console.log(`Cleared all ${storageType}`);
|
|
118
|
+
} else if (subcommand === "export") {
|
|
119
|
+
const pathIdx = args.findIndex((a) => a === "--path");
|
|
120
|
+
const outputFile = pathIdx !== -1 ? args[pathIdx + 1] : "storage.json";
|
|
121
|
+
|
|
122
|
+
const data = await page.evaluate((storageType) => {
|
|
123
|
+
const storage = window[storageType];
|
|
124
|
+
const result = {};
|
|
125
|
+
for (let i = 0; i < storage.length; i++) {
|
|
126
|
+
const key = storage.key(i);
|
|
127
|
+
result[key] = storage.getItem(key);
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}, storageType);
|
|
131
|
+
|
|
132
|
+
writeFileSync(outputFile, JSON.stringify(data, null, 2));
|
|
133
|
+
const count = Object.keys(data).length;
|
|
134
|
+
console.log(`Exported ${count} item(s) from ${storageType} to ${outputFile}`);
|
|
135
|
+
} else if (subcommand === "import") {
|
|
136
|
+
const importFile = positionalArgs[1];
|
|
137
|
+
|
|
138
|
+
if (!importFile) {
|
|
139
|
+
console.error("Error: import requires a file path");
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let data;
|
|
144
|
+
try {
|
|
145
|
+
data = JSON.parse(readFileSync(importFile, "utf8"));
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(`Error reading file: ${error.message}`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (typeof data !== "object" || Array.isArray(data)) {
|
|
152
|
+
console.error("Error: Storage file must contain a JSON object");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await page.evaluate(
|
|
157
|
+
([storageType, data]) => {
|
|
158
|
+
const storage = window[storageType];
|
|
159
|
+
for (const [key, value] of Object.entries(data)) {
|
|
160
|
+
storage.setItem(key, value);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
[storageType, data]
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const count = Object.keys(data).length;
|
|
167
|
+
console.log(`Imported ${count} item(s) to ${storageType} from ${importFile}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await browser.close();
|