@yusufffararatt/dombridge-mcp 2.7.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/README.md +559 -0
- package/bin/cli.js +88 -0
- package/package.json +54 -0
- package/src/bridge/http-server.js +290 -0
- package/src/bridge/middleware.js +56 -0
- package/src/bridge/routes.js +1003 -0
- package/src/bridge-daemon.js +172 -0
- package/src/cli/auto-config.js +120 -0
- package/src/constants.js +13 -0
- package/src/index.js +279 -0
- package/src/mcp-bridge.js +136 -0
- package/src/metrics/error-codes.js +44 -0
- package/src/metrics/index.js +3 -0
- package/src/metrics/metrics-db.js +269 -0
- package/src/metrics/metrics-recorder.js +240 -0
- package/src/metrics/metrics-report.js +146 -0
- package/src/profiles/profile-db.js +159 -0
- package/src/profiles/profile-enricher.js +333 -0
- package/src/profiles/profile-manager.js +563 -0
- package/src/profiles/profile-repo.js +183 -0
- package/src/state/bridge-client.js +272 -0
- package/src/state/bridge-persistence.js +205 -0
- package/src/state/cache.js +38 -0
- package/src/state/extension-state.js +321 -0
- package/src/tools/action_tools.js +218 -0
- package/src/tools/analyze-page.js +247 -0
- package/src/tools/debug-mcp-state.js +172 -0
- package/src/tools/discover-apis.js +186 -0
- package/src/tools/execute-js.js +284 -0
- package/src/tools/export-session.js +171 -0
- package/src/tools/extract-data.js +395 -0
- package/src/tools/get-element.js +281 -0
- package/src/tools/get-network-trace.js +471 -0
- package/src/tools/index.js +110 -0
- package/src/tools/manage-site-profile.js +153 -0
- package/src/tools/paginate.js +444 -0
- package/src/tools/quick-scan.js +418 -0
- package/src/tools/screenshot_tools.js +117 -0
- package/src/utils/circuit-breaker.js +112 -0
- package/src/utils/extract-density.js +21 -0
- package/src/utils/logger.js +31 -0
- package/src/utils/paginate-detector.js +24 -0
- package/src/utils/rate-limiter.js +244 -0
- package/src/utils/run-script.js +37 -0
- package/src/utils/selector-validator.js +95 -0
- package/src/utils/state-validator.js +354 -0
- package/src/utils/tab-resolver.js +70 -0
- package/src/utils/workflow-helper.js +292 -0
- package/src/utils/workflow-state.js +177 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: execute_js
|
|
3
|
+
* Execute custom JavaScript code in the active Chrome tab's page context
|
|
4
|
+
*
|
|
5
|
+
* Phase 2.4: Refactored from (args, extensionData, httpPort) to (args, bridgeClient).
|
|
6
|
+
* All state access goes through BridgeClient HTTP calls.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { StateValidator } from '../utils/state-validator.js';
|
|
10
|
+
import { RateLimiter } from '../utils/rate-limiter.js';
|
|
11
|
+
import { validatePrerequisites, formatHardGuard } from '../utils/workflow-helper.js';
|
|
12
|
+
|
|
13
|
+
function analyzeCodeForInsights(code, domain) {
|
|
14
|
+
if (!domain || domain === 'localhost' || domain === '127.0.0.1') return null;
|
|
15
|
+
const selectorRegex = /querySelector(?:All)?\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
16
|
+
const selectors = [];
|
|
17
|
+
let m;
|
|
18
|
+
while ((m = selectorRegex.exec(code)) !== null) {
|
|
19
|
+
if (!selectors.includes(m[1])) selectors.push(m[1]);
|
|
20
|
+
}
|
|
21
|
+
if (selectors.length === 0) return null;
|
|
22
|
+
const hasDisambiguation = /\.filter\s*\(|\.closest\s*\(|\.find\s*\(/.test(code);
|
|
23
|
+
return { selectors, hasDisambiguation, domain };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const executeJsTool = {
|
|
27
|
+
name: 'execute_js',
|
|
28
|
+
description: `Execute custom JavaScript code in a Chrome tab's page context.
|
|
29
|
+
|
|
30
|
+
WORKFLOW POSITION: π΄ Advanced - Use with caution
|
|
31
|
+
|
|
32
|
+
PREREQUISITES:
|
|
33
|
+
- β
Extension must be connected
|
|
34
|
+
- Chrome tab must be open (active tab used by default)
|
|
35
|
+
|
|
36
|
+
MULTI-TAB: Call debug_mcp_state() first to get tab IDs, then pass tabId to target a specific tab.
|
|
37
|
+
Example: execute_js({ code: "document.title", tabId: 142 })
|
|
38
|
+
|
|
39
|
+
SECURITY & LIMITS:
|
|
40
|
+
- β οΈ Code runs in page context (full page access)
|
|
41
|
+
- β οΈ Max code size: 10KB
|
|
42
|
+
- β οΈ Timeout: 5-30 seconds (configurable)
|
|
43
|
+
- β 'ev' + 'al()' and 'Function()' blocked
|
|
44
|
+
- β No setInterval (memory leak risk)
|
|
45
|
+
- β οΈ cspBypass: true reloads the tab β all SPA state is LOST (open modals, form inputs, navigation history, React/Vue component state). Only use after a confirmed CSP error, never as default.
|
|
46
|
+
|
|
47
|
+
PARAMETERS:
|
|
48
|
+
- code: JavaScript code to execute (required)
|
|
49
|
+
- timeout: Execution timeout in ms (default: 5000, max: 30000)
|
|
50
|
+
- tabId: Target a specific tab by ID (optional β omit to use active tab)
|
|
51
|
+
- context: 'page' (default) or 'background'. Use 'background' to run in the extension service worker β bypasses CORS, no DOM access, can use fetch with credentials.
|
|
52
|
+
|
|
53
|
+
WORKFLOW:
|
|
54
|
+
1. execute_js (Automatically waits for result)
|
|
55
|
+
|
|
56
|
+
EXAMPLE:
|
|
57
|
+
execute_js({ code: "document.querySelectorAll('a').length" })
|
|
58
|
+
|
|
59
|
+
Use this for advanced DOM queries, data extraction, or page manipulation.
|
|
60
|
+
Note: This tool is BLOCKING. It will wait up to 10s for the result from Chrome.`,
|
|
61
|
+
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
code: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'JavaScript code to execute'
|
|
68
|
+
},
|
|
69
|
+
timeout: {
|
|
70
|
+
type: 'number',
|
|
71
|
+
description: 'Execution timeout in ms (default: 5000, max: 30000)',
|
|
72
|
+
default: 5000
|
|
73
|
+
},
|
|
74
|
+
waitResult: {
|
|
75
|
+
type: 'boolean',
|
|
76
|
+
description: 'Whether to wait for results (default: true)',
|
|
77
|
+
default: true
|
|
78
|
+
},
|
|
79
|
+
tabId: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Target tab ID (optional). Omit to use active tab. Get IDs from debug_mcp_state().'
|
|
82
|
+
},
|
|
83
|
+
context: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
enum: ['page', 'background'],
|
|
86
|
+
description: "Execution context. 'page' (default): runs in page via content script, has DOM access, subject to CORS. 'background': runs in extension service worker, no DOM access, no CORS restrictions, has access to fetch/chrome.cookies/chrome.tabs."
|
|
87
|
+
},
|
|
88
|
+
cspBypass: {
|
|
89
|
+
type: 'boolean',
|
|
90
|
+
description: "Enable domain-specific CSP bypass when execute_js fails with CSP/unsafe-eval errors. Reloads the tab ONCE and strips CSP headers for that domain only. WARNING: tab reload resets SPA state. Only use after a CSP error β do not set by default.",
|
|
91
|
+
default: false
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
required: ['code']
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
handler: async (args, bridgeClient) => {
|
|
98
|
+
// CRITICAL: Check for pending execution FIRST
|
|
99
|
+
const noPendingValidation = StateValidator.validateNoPendingExecution(bridgeClient);
|
|
100
|
+
if (!noPendingValidation.valid) {
|
|
101
|
+
return StateValidator.formatValidationError(noPendingValidation);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Hard guard: block if dependency check fails
|
|
105
|
+
const prereqValidation = validatePrerequisites('execute_js', bridgeClient);
|
|
106
|
+
if (!prereqValidation.valid) {
|
|
107
|
+
return formatHardGuard('execute_js', prereqValidation);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return await RateLimiter.executeWithRetry(
|
|
111
|
+
'execute_js',
|
|
112
|
+
async () => {
|
|
113
|
+
const { code, timeout = 5000, waitResult = true, tabId, context = 'page', cspBypass = false } = args || {};
|
|
114
|
+
|
|
115
|
+
// Validate input
|
|
116
|
+
if (!code || typeof code !== 'string') {
|
|
117
|
+
return {
|
|
118
|
+
isError: true,
|
|
119
|
+
content: [{
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: 'Error: Code parameter required (must be a string)\n\nExample: execute_js({ code: "document.title" })'
|
|
122
|
+
}]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Security: Code size limit
|
|
127
|
+
if (code.length > 10000) {
|
|
128
|
+
return {
|
|
129
|
+
isError: true,
|
|
130
|
+
content: [{
|
|
131
|
+
type: 'text',
|
|
132
|
+
text: `Error: Code too large (${code.length} bytes, max 10KB)\n\nTIP: Break complex operations into smaller chunks`
|
|
133
|
+
}]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Security: Block eval and Function constructor
|
|
138
|
+
if (code.includes('ev' + 'al(') || code.includes('Function(')) {
|
|
139
|
+
return {
|
|
140
|
+
isError: true,
|
|
141
|
+
content: [{
|
|
142
|
+
type: 'text',
|
|
143
|
+
text: 'Security Error: ev' + 'al() and Function() constructor are not allowed\n\nUse direct JavaScript instead'
|
|
144
|
+
}]
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Security: Block infinite loops
|
|
149
|
+
if (/while\s*\(\s*true\s*\)/i.test(code) || /for\s*\(\s*;\s*;\s*\)/.test(code)) {
|
|
150
|
+
return {
|
|
151
|
+
isError: true,
|
|
152
|
+
content: [{
|
|
153
|
+
type: 'text',
|
|
154
|
+
text: 'Security Error: Infinite loop detected (while(true) / for(;;)) β not allowed\n\nUse a bounded loop with a counter or break condition instead'
|
|
155
|
+
}]
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Security: Warn about setInterval
|
|
160
|
+
if (code.includes('setInterval')) {
|
|
161
|
+
return {
|
|
162
|
+
content: [{
|
|
163
|
+
type: 'text',
|
|
164
|
+
text: 'Warning: setInterval detected (memory leak risk)\n\nUse setTimeout for one-time delayed execution instead'
|
|
165
|
+
}]
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Validate timeout
|
|
170
|
+
const validTimeout = Math.min(Math.max(timeout, 1000), 30000); // 1s-30s
|
|
171
|
+
|
|
172
|
+
// Validate connection
|
|
173
|
+
const connValidation = StateValidator.validateConnection(bridgeClient);
|
|
174
|
+
if (!connValidation.valid) {
|
|
175
|
+
return StateValidator.formatValidationError(connValidation);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
// Queue execution request via bridge daemon
|
|
180
|
+
const requestId = `js-${Date.now()}`;
|
|
181
|
+
await bridgeClient.queueRequest('execute-js', {
|
|
182
|
+
code,
|
|
183
|
+
timeout: validTimeout,
|
|
184
|
+
id: requestId,
|
|
185
|
+
timestamp: new Date().toISOString(),
|
|
186
|
+
context,
|
|
187
|
+
cspBypass,
|
|
188
|
+
...(tabId ? { tabId } : {})
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// If not waiting, just return the queue message
|
|
192
|
+
if (!waitResult) {
|
|
193
|
+
return {
|
|
194
|
+
content: [{
|
|
195
|
+
type: 'text',
|
|
196
|
+
text: `## JS Execution Sent\n\nRequest ID: ${requestId}\nCommand sent to browser (Fire-and-forget). No result will be returned.`
|
|
197
|
+
}]
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// BLOCKING WAIT: Poll bridge daemon for result
|
|
202
|
+
// Wait longer than code timeout to allow for overhead (max 15s or code timeout + 2s)
|
|
203
|
+
const waitTimeout = Math.min(validTimeout + 2000, 15000);
|
|
204
|
+
const record = await bridgeClient.waitForResult('js-execution', requestId, waitTimeout);
|
|
205
|
+
|
|
206
|
+
if (record) {
|
|
207
|
+
// CSP bypass banner: when cspBypass:true is used, the page is reloaded
|
|
208
|
+
// to strip CSP headers. SPA state (modals, form inputs, navigation
|
|
209
|
+
// history, React/Vue component state) is LOST. Make this explicit so
|
|
210
|
+
// users don't wonder why the DOM looks "different" after execution.
|
|
211
|
+
let cspBanner = '';
|
|
212
|
+
if (record._cspBypassed || cspBypass) {
|
|
213
|
+
cspBanner = `β οΈ **CSP bypass active for \`${record._cspBypassed || domain || 'this domain'}\`** β the tab was reloaded to strip CSP headers. SPA state (modals, form inputs, navigation history, component state) was lost.\n\n`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let output = cspBanner + '## JS Execution Success\n\n';
|
|
217
|
+
if (record.result?.error) {
|
|
218
|
+
output = cspBanner + '## JS Execution Error\n\n';
|
|
219
|
+
output += `\`\`\`\n${record.result.error}\n\`\`\`\n`;
|
|
220
|
+
} else {
|
|
221
|
+
const resultStr = JSON.stringify(record.result, null, 2);
|
|
222
|
+
if (record.result === null && resultStr === 'null') {
|
|
223
|
+
const typeHint = record.__typeHint;
|
|
224
|
+
output += `Result:\n\`\`\`json\nnull\n\`\`\`\n`;
|
|
225
|
+
if (typeHint) {
|
|
226
|
+
output += `\nπ **Type diagnostic:** The code returned a \`${typeHint}\` value. `;
|
|
227
|
+
if (typeHint === 'undefined') {
|
|
228
|
+
output += `Add an explicit \`return\` statement to return a value.\n`;
|
|
229
|
+
} else if (typeHint === 'function') {
|
|
230
|
+
output += `Functions cannot be serialized. Call the function or return its result instead.\n`;
|
|
231
|
+
} else if (typeHint === 'Promise (await or return the resolved value)') {
|
|
232
|
+
output += `Use \`await\` or \`.then()\` to resolve the Promise, or mark the code as async.\n`;
|
|
233
|
+
} else {
|
|
234
|
+
output += `This type cannot be serialized to JSON. Use \`JSON.stringify()\` or extract primitive values.\n`;
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
output += `\nπ‘ Tip: \`null\` means the code returned \`undefined\` or a non-serializable value. `;
|
|
238
|
+
output += `Add an explicit \`return\` or wrap in \`JSON.stringify()\` to inspect complex objects.\n`;
|
|
239
|
+
}
|
|
240
|
+
} else if (resultStr.length > 3000) {
|
|
241
|
+
output += `Result (truncated):\n\`\`\`json\n${resultStr.substring(0, 3000)}...\n\`\`\`\n`;
|
|
242
|
+
} else {
|
|
243
|
+
output += `Result:\n\`\`\`json\n${resultStr}\n\`\`\`\n`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Insight prompt: disambiguation pattern detected β save_site_profile reminder
|
|
247
|
+
const domain = bridgeClient.activeTabUrl
|
|
248
|
+
? (() => { try { return new URL(bridgeClient.activeTabUrl).hostname; } catch { return null; } })()
|
|
249
|
+
: null;
|
|
250
|
+
const insight = analyzeCodeForInsights(code, domain);
|
|
251
|
+
if (insight && insight.hasDisambiguation) {
|
|
252
|
+
await bridgeClient.incrementInsight(insight.domain);
|
|
253
|
+
output += `\n\nπ‘ **Insight fΔ±rsatΔ±** β \`${insight.domain}\` ΓΌzerinde Γ§alΔ±ΕtΔ±n.\n`;
|
|
254
|
+
output += `KeΕfedilen selector'lar: ${insight.selectors.map(s => `\`${s}\``).join(', ')}\n`;
|
|
255
|
+
output += `Disambiguation pattern tespit edildi (.filter / .closest / .find kullanΔ±ldΔ±)\n`;
|
|
256
|
+
output += `β ΓΔrendiΔin varsa kaydet: \`manage_site_profile({ action: 'save', domain: "${insight.domain}", notes: "...", stableSelectors: [...] })\``;
|
|
257
|
+
}
|
|
258
|
+
return { content: [{ type: 'text', text: output }] };
|
|
259
|
+
} else {
|
|
260
|
+
// FALLBACK: Timeout β the request has been consumed by waitForResult
|
|
261
|
+
return {
|
|
262
|
+
isError: true,
|
|
263
|
+
content: [{
|
|
264
|
+
type: 'text',
|
|
265
|
+
text: `## JS Execution Timeout\n\nNo response from Chrome within ${waitTimeout}ms. The request has been cleared.\nRequest ID: ${requestId}\n\nπ‘ Your code may be slow on this heavy DOM page. Alternatives:\n1. Lower timeout: execute_js({ code: "...", timeout: 3000 })\n2. Use background context: execute_js({ code: "...", context: "background" })\n3. Simplify code: break complex queries into steps`
|
|
266
|
+
}]
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return {
|
|
271
|
+
isError: true,
|
|
272
|
+
content: [{
|
|
273
|
+
type: 'text',
|
|
274
|
+
text: `Error during execution: ${error.message}\n\nREQUIRED STEPS:\n1. Verify browser connection in debug_mcp_state()\n2. Check code syntax and context compatibility`
|
|
275
|
+
}]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
maxRetries: 2
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export Session Tool
|
|
3
|
+
* Aktif browser'daki cookie'leri, localStorage/sessionStorage token'larΔ±nΔ± export eder.
|
|
4
|
+
* Playwright instance'Δ±na aktarmak iΓ§in kullanΔ±lΔ±r.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const exportSessionTool = {
|
|
8
|
+
name: 'export_session',
|
|
9
|
+
description: `This is a tool from the dombridge MCP server.
|
|
10
|
+
Exports the active browser session: cookies, auth tokens, and storage data from the current page's domain.
|
|
11
|
+
|
|
12
|
+
WORKFLOW POSITION: π Auth Export β call after login, before Playwright handoff. Requires active browser session.
|
|
13
|
+
|
|
14
|
+
USE CASE: Export session once β hand it to a Playwright instance β headless scraping without re-login.
|
|
15
|
+
|
|
16
|
+
RETURNS:
|
|
17
|
+
- cookies: All cookies for the domain (name, value, domain, path, secure, httpOnly, expiry)
|
|
18
|
+
- localStorage: Auth/token related keys (filtered for security)
|
|
19
|
+
- sessionStorage: Auth/token related keys (filtered for security)
|
|
20
|
+
- domain: The domain the session was exported from
|
|
21
|
+
- exportedAt: ISO timestamp
|
|
22
|
+
|
|
23
|
+
SECURITY NOTE:
|
|
24
|
+
- Only reads the current active tab's domain
|
|
25
|
+
- localStorage/sessionStorage filtered to auth-related keys only (token, auth, session, jwt, etc.)
|
|
26
|
+
- Data is returned directly to you β never stored on disk by this tool
|
|
27
|
+
|
|
28
|
+
MULTI-TAB: Call debug_mcp_state() first to get tab IDs, then pass tabId to export a specific tab's session.
|
|
29
|
+
Example: export_session({ tabId: 142 })
|
|
30
|
+
|
|
31
|
+
WORKFLOW:
|
|
32
|
+
1. Log in to target site in browser (Trendyol, etc.)
|
|
33
|
+
2. Navigate to any page on that site
|
|
34
|
+
3. Call export_session() β get cookies + tokens
|
|
35
|
+
4. Pass the exported data to Playwright to continue headless
|
|
36
|
+
`,
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
properties: {
|
|
40
|
+
tabId: {
|
|
41
|
+
type: 'number',
|
|
42
|
+
description: 'Target tab ID (optional). Omit to use active tab. Get IDs from debug_mcp_state().'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
handler: async (args, bridgeClient) => {
|
|
47
|
+
if (!bridgeClient.isConnected) {
|
|
48
|
+
return {
|
|
49
|
+
content: [{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: `β Error: Extension not connected.\nREQUIRED STEPS:\n1. Reload webpage\n2. Ensure the Chrome extension is active`
|
|
52
|
+
}],
|
|
53
|
+
isError: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { tabId } = args || {};
|
|
58
|
+
const requestId = `export-session-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await bridgeClient.queueRequest('export-session', { id: requestId, ...(tabId ? { tabId } : {}) });
|
|
62
|
+
|
|
63
|
+
const resultItem = await bridgeClient.waitForResult('export-session', requestId, 15000);
|
|
64
|
+
|
|
65
|
+
if (!resultItem) {
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: 'text', text: `β Timeout: export_session did not complete within 15000ms.` }],
|
|
68
|
+
isError: true
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (resultItem.error) {
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: 'text', text: `β export_session failed: ${resultItem.error}` }],
|
|
75
|
+
isError: true
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const r = resultItem.result;
|
|
80
|
+
if (!r) {
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: 'text', text: `β export_session returned no data.` }],
|
|
83
|
+
isError: true
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const lines = [
|
|
88
|
+
`π **Session Export: ${r.domain}**`,
|
|
89
|
+
`Exported at: ${r.exportedAt}`,
|
|
90
|
+
`URL: ${r.url}`,
|
|
91
|
+
''
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// Cookies
|
|
95
|
+
lines.push(`**πͺ Cookies: ${r.cookieCount} found**`);
|
|
96
|
+
if (r.cookies.length > 0) {
|
|
97
|
+
// Auth cookie'leri vurgula (analytics cookie'leri hariΓ§)
|
|
98
|
+
const ANALYTICS_COOKIE_PREFIXES = ['_ym_', '_ga', '_gid', '_fbp', '_fbc', '_clck', '_clsk', '_hjid'];
|
|
99
|
+
const isAnalyticsCookie = (name) => ANALYTICS_COOKIE_PREFIXES.some(p => name.startsWith(p));
|
|
100
|
+
const authCookies = r.cookies.filter(c =>
|
|
101
|
+
!isAnalyticsCookie(c.name) &&
|
|
102
|
+
/token|auth|session|jwt|bearer|access|refresh|login|sid|uid/i.test(c.name)
|
|
103
|
+
);
|
|
104
|
+
const otherCookies = r.cookies.filter(c =>
|
|
105
|
+
isAnalyticsCookie(c.name) ||
|
|
106
|
+
!/token|auth|session|jwt|bearer|access|refresh|login|sid|uid/i.test(c.name)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (authCookies.length > 0) {
|
|
110
|
+
lines.push(`\n_Auth cookies (${authCookies.length}):_`);
|
|
111
|
+
authCookies.forEach(c => {
|
|
112
|
+
const expiry = c.expirationDate
|
|
113
|
+
? ` | expires: ${new Date(c.expirationDate * 1000).toISOString().split('T')[0]}`
|
|
114
|
+
: ' | session';
|
|
115
|
+
lines.push(` - \`${c.name}\` = \`${c.value.substring(0, 40)}${c.value.length > 40 ? 'β¦' : ''}\`${expiry}`);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (otherCookies.length > 0) {
|
|
120
|
+
lines.push(`\n_Other cookies (${otherCookies.length}):_`);
|
|
121
|
+
otherCookies.slice(0, 5).forEach(c => {
|
|
122
|
+
lines.push(` - \`${c.name}\``);
|
|
123
|
+
});
|
|
124
|
+
if (otherCookies.length > 5) {
|
|
125
|
+
lines.push(` - _... ${otherCookies.length - 5} more_`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
lines.push(' _(no cookies found β are you logged in?)_');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// localStorage
|
|
133
|
+
const lsKeys = Object.keys(r.localStorage);
|
|
134
|
+
if (lsKeys.length > 0) {
|
|
135
|
+
lines.push(`\n**ποΈ localStorage (${lsKeys.length} auth keys):**`);
|
|
136
|
+
lsKeys.forEach(k => {
|
|
137
|
+
const val = r.localStorage[k];
|
|
138
|
+
lines.push(` - \`${k}\` = \`${val.substring(0, 60)}${val.length > 60 ? 'β¦' : ''}\``);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// sessionStorage
|
|
143
|
+
const ssKeys = Object.keys(r.sessionStorage);
|
|
144
|
+
if (ssKeys.length > 0) {
|
|
145
|
+
lines.push(`\n**ποΈ sessionStorage (${ssKeys.length} auth keys):**`);
|
|
146
|
+
ssKeys.forEach(k => {
|
|
147
|
+
const val = r.sessionStorage[k];
|
|
148
|
+
lines.push(` - \`${k}\` = \`${val.substring(0, 60)}${val.length > 60 ? 'β¦' : ''}\``);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
lines.push('');
|
|
153
|
+
lines.push('---');
|
|
154
|
+
lines.push('π‘ **Playwright usage:**');
|
|
155
|
+
lines.push('```js');
|
|
156
|
+
lines.push('await context.addCookies(session.cookies);');
|
|
157
|
+
lines.push('await page.evaluate(ls => Object.entries(ls).forEach(([k,v]) => localStorage.setItem(k,v)), session.localStorage);');
|
|
158
|
+
lines.push('```');
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: 'text', text: lines.join('\n') }]
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
} catch (e) {
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: 'text', text: `β Error: ${e.message}` }],
|
|
167
|
+
isError: true
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|