@telosmaylx/dsh-session-notify 0.1.2 → 0.1.3
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 +21 -21
- package/README.md +202 -196
- package/cordis.patch.yml +10 -0
- package/lib/client.js +1180 -1180
- package/lib/core.js +303 -303
- package/lib/index.js +312 -291
- package/package.json +70 -66
- package/scripts/build.sh +16 -16
- package/scripts/probe-card-render.mjs +100 -100
- package/scripts/probe-client-e2e.mjs +134 -134
- package/scripts/probe-client.mjs +145 -145
- package/scripts/probe-diag-settings.mjs +79 -79
- package/scripts/probe-settings-card.mjs +137 -137
- package/scripts/probe-settings-check.mjs +87 -87
- package/scripts/verify-notice.mjs +110 -110
package/scripts/probe-client.mjs
CHANGED
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
// probe-client.mjs — headless Chrome 验证 dsh-session-complete-notify 客户端插件:
|
|
2
|
-
// 1) 打开 http://127.0.0.1:3080
|
|
3
|
-
// 2) 采集 console / pageerror
|
|
4
|
-
// 3) 检查 boot graph 与模块装载
|
|
5
|
-
// 用法: node scripts/probe-client.mjs
|
|
6
|
-
import { spawn } from 'node:child_process'
|
|
7
|
-
import { mkdtempSync, rmSync } from 'node:fs'
|
|
8
|
-
import { tmpdir } from 'node:os'
|
|
9
|
-
import { join } from 'node:path'
|
|
10
|
-
|
|
11
|
-
const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe'
|
|
12
|
-
const PORT = 9333
|
|
13
|
-
const APP_URL = 'http://127.0.0.1:3080'
|
|
14
|
-
const profile = mkdtempSync(join(tmpdir(), 'dsh-probe-'))
|
|
15
|
-
|
|
16
|
-
const chrome = spawn(CHROME, [
|
|
17
|
-
'--headless=new',
|
|
18
|
-
`--remote-debugging-port=${PORT}`,
|
|
19
|
-
`--user-data-dir=${profile}`,
|
|
20
|
-
'--no-first-run',
|
|
21
|
-
'--disable-gpu',
|
|
22
|
-
'--window-size=1400,900',
|
|
23
|
-
'about:blank',
|
|
24
|
-
], { stdio: 'ignore' })
|
|
25
|
-
|
|
26
|
-
async function waitFor(fn, timeoutMs, label) {
|
|
27
|
-
const t0 = Date.now()
|
|
28
|
-
while (Date.now() - t0 < timeoutMs) {
|
|
29
|
-
try { const v = await fn(); if (v) return v } catch { /* retry */ }
|
|
30
|
-
await new Promise((r) => setTimeout(r, 300))
|
|
31
|
-
}
|
|
32
|
-
throw new Error('timeout waiting for ' + label)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const wsUrl = await waitFor(async () => {
|
|
36
|
-
const list = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json()
|
|
37
|
-
const page = list.find((t) => t.type === 'page')
|
|
38
|
-
return page?.webSocketDebuggerUrl
|
|
39
|
-
}, 20000, 'cdp endpoint')
|
|
40
|
-
|
|
41
|
-
const ws = new WebSocket(wsUrl)
|
|
42
|
-
let seq = 0
|
|
43
|
-
const pending = new Map()
|
|
44
|
-
const events = []
|
|
45
|
-
ws.onmessage = (m) => {
|
|
46
|
-
const msg = JSON.parse(m.data)
|
|
47
|
-
if (msg.id !== undefined && pending.has(msg.id)) {
|
|
48
|
-
pending.get(msg.id)(msg)
|
|
49
|
-
pending.delete(msg.id)
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
if (msg.method === 'Runtime.consoleAPICalled') {
|
|
53
|
-
const args = (msg.params.args || []).map((a) => a.value ?? a.description ?? '').join(' ')
|
|
54
|
-
events.push(`[console.${msg.params.type}] ${args}`)
|
|
55
|
-
} else if (msg.method === 'Runtime.exceptionThrown') {
|
|
56
|
-
events.push(`[pageerror] ${msg.params.exceptionDetails?.text ?? ''} ${msg.params.exceptionDetails?.exception?.description ?? ''}`)
|
|
57
|
-
} else if (msg.method === 'Network.loadingFailed') {
|
|
58
|
-
events.push(`[net-fail] ${msg.params.errorText} ${msg.params.requestId}`)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function rpc(method, params = {}) {
|
|
62
|
-
return new Promise((resolve) => {
|
|
63
|
-
const id = ++seq
|
|
64
|
-
pending.set(id, resolve)
|
|
65
|
-
ws.send(JSON.stringify({ id, method, params }))
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
await new Promise((r) => { ws.onopen = r })
|
|
69
|
-
await rpc('Runtime.enable')
|
|
70
|
-
await rpc('Network.enable')
|
|
71
|
-
await rpc('Page.enable')
|
|
72
|
-
|
|
73
|
-
const target = await rpc('Target.createTarget', { url: 'about:blank' })
|
|
74
|
-
const targetId = target.result?.targetId
|
|
75
|
-
// 直接在当前页面导航更简单:拿新的 page target
|
|
76
|
-
const list2 = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json()
|
|
77
|
-
const pageTarget = list2.find((t) => t.id === targetId) ?? list2.find((t) => t.type === 'page')
|
|
78
|
-
const ws2 = new WebSocket(pageTarget.webSocketDebuggerUrl)
|
|
79
|
-
let seq2 = 0
|
|
80
|
-
const pending2 = new Map()
|
|
81
|
-
const pageEvents = []
|
|
82
|
-
ws2.onmessage = (m) => {
|
|
83
|
-
const msg = JSON.parse(m.data)
|
|
84
|
-
if (msg.id !== undefined && pending2.has(msg.id)) {
|
|
85
|
-
pending2.get(msg.id)(msg)
|
|
86
|
-
pending2.delete(msg.id)
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
if (msg.method === 'Runtime.consoleAPICalled') {
|
|
90
|
-
const args = (msg.params.args || []).map((a) => a.value ?? a.description ?? '').join(' ')
|
|
91
|
-
pageEvents.push(`[console.${msg.params.type}] ${args.slice(0, 300)}`)
|
|
92
|
-
} else if (msg.method === 'Runtime.exceptionThrown') {
|
|
93
|
-
const d = msg.params.exceptionDetails
|
|
94
|
-
pageEvents.push(`[pageerror] ${d?.exception?.description ?? d?.text ?? ''}`.slice(0, 500))
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
function rpc2(method, params = {}) {
|
|
98
|
-
return new Promise((resolve) => {
|
|
99
|
-
const id = ++seq2
|
|
100
|
-
pending2.set(id, resolve)
|
|
101
|
-
ws2.send(JSON.stringify({ id, method, params }))
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
await new Promise((r) => { ws2.onopen = r })
|
|
105
|
-
await rpc2('Runtime.enable')
|
|
106
|
-
await rpc2('Page.enable')
|
|
107
|
-
await rpc2('Page.navigate', { url: APP_URL })
|
|
108
|
-
|
|
109
|
-
await waitFor(async () => {
|
|
110
|
-
const r = await rpc2('Runtime.evaluate', { expression: '!!window.__ModuleLoader__ && !!window.__DSH_BOOT__', returnByValue: true })
|
|
111
|
-
return r.result?.result?.value
|
|
112
|
-
}, 30000, 'boot')
|
|
113
|
-
|
|
114
|
-
// 等待客户端插件激活(console 日志由 apply 打印)
|
|
115
|
-
await new Promise((r) => setTimeout(r, 6000))
|
|
116
|
-
|
|
117
|
-
const checks = {}
|
|
118
|
-
{
|
|
119
|
-
const r = await rpc2('Runtime.evaluate', {
|
|
120
|
-
expression: `(() => {
|
|
121
|
-
const boot = window.__DSH_BOOT__ || {}
|
|
122
|
-
const entries = (boot.entries || []).map(e => e.id)
|
|
123
|
-
return JSON.stringify({
|
|
124
|
-
graphLength: entries.length,
|
|
125
|
-
hasEntry: entries.includes('@telosmaylx/dsh-session-notify'),
|
|
126
|
-
entry: entries.find(e => e.includes('session-notify')),
|
|
127
|
-
notificationSupported: typeof window.Notification !== 'undefined',
|
|
128
|
-
notificationPermission: typeof window.Notification !== 'undefined' ? window.Notification.permission : 'n/a',
|
|
129
|
-
})
|
|
130
|
-
})()`,
|
|
131
|
-
returnByValue: true,
|
|
132
|
-
})
|
|
133
|
-
checks.graph = JSON.parse(r.result?.result?.value ?? '{}')
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
console.log('=== console/pageerror events ===')
|
|
137
|
-
for (const e of pageEvents) console.log(e)
|
|
138
|
-
console.log('=== checks ===')
|
|
139
|
-
console.log(JSON.stringify(checks, null, 2))
|
|
140
|
-
|
|
141
|
-
try { ws.close() } catch {}
|
|
142
|
-
try { ws2.close() } catch {}
|
|
143
|
-
chrome.kill()
|
|
144
|
-
await new Promise((r) => setTimeout(r, 300))
|
|
145
|
-
try { rmSync(profile, { recursive: true, force: true }) } catch {}
|
|
1
|
+
// probe-client.mjs — headless Chrome 验证 dsh-session-complete-notify 客户端插件:
|
|
2
|
+
// 1) 打开 http://127.0.0.1:3080
|
|
3
|
+
// 2) 采集 console / pageerror
|
|
4
|
+
// 3) 检查 boot graph 与模块装载
|
|
5
|
+
// 用法: node scripts/probe-client.mjs
|
|
6
|
+
import { spawn } from 'node:child_process'
|
|
7
|
+
import { mkdtempSync, rmSync } from 'node:fs'
|
|
8
|
+
import { tmpdir } from 'node:os'
|
|
9
|
+
import { join } from 'node:path'
|
|
10
|
+
|
|
11
|
+
const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe'
|
|
12
|
+
const PORT = 9333
|
|
13
|
+
const APP_URL = 'http://127.0.0.1:3080'
|
|
14
|
+
const profile = mkdtempSync(join(tmpdir(), 'dsh-probe-'))
|
|
15
|
+
|
|
16
|
+
const chrome = spawn(CHROME, [
|
|
17
|
+
'--headless=new',
|
|
18
|
+
`--remote-debugging-port=${PORT}`,
|
|
19
|
+
`--user-data-dir=${profile}`,
|
|
20
|
+
'--no-first-run',
|
|
21
|
+
'--disable-gpu',
|
|
22
|
+
'--window-size=1400,900',
|
|
23
|
+
'about:blank',
|
|
24
|
+
], { stdio: 'ignore' })
|
|
25
|
+
|
|
26
|
+
async function waitFor(fn, timeoutMs, label) {
|
|
27
|
+
const t0 = Date.now()
|
|
28
|
+
while (Date.now() - t0 < timeoutMs) {
|
|
29
|
+
try { const v = await fn(); if (v) return v } catch { /* retry */ }
|
|
30
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
31
|
+
}
|
|
32
|
+
throw new Error('timeout waiting for ' + label)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const wsUrl = await waitFor(async () => {
|
|
36
|
+
const list = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json()
|
|
37
|
+
const page = list.find((t) => t.type === 'page')
|
|
38
|
+
return page?.webSocketDebuggerUrl
|
|
39
|
+
}, 20000, 'cdp endpoint')
|
|
40
|
+
|
|
41
|
+
const ws = new WebSocket(wsUrl)
|
|
42
|
+
let seq = 0
|
|
43
|
+
const pending = new Map()
|
|
44
|
+
const events = []
|
|
45
|
+
ws.onmessage = (m) => {
|
|
46
|
+
const msg = JSON.parse(m.data)
|
|
47
|
+
if (msg.id !== undefined && pending.has(msg.id)) {
|
|
48
|
+
pending.get(msg.id)(msg)
|
|
49
|
+
pending.delete(msg.id)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
if (msg.method === 'Runtime.consoleAPICalled') {
|
|
53
|
+
const args = (msg.params.args || []).map((a) => a.value ?? a.description ?? '').join(' ')
|
|
54
|
+
events.push(`[console.${msg.params.type}] ${args}`)
|
|
55
|
+
} else if (msg.method === 'Runtime.exceptionThrown') {
|
|
56
|
+
events.push(`[pageerror] ${msg.params.exceptionDetails?.text ?? ''} ${msg.params.exceptionDetails?.exception?.description ?? ''}`)
|
|
57
|
+
} else if (msg.method === 'Network.loadingFailed') {
|
|
58
|
+
events.push(`[net-fail] ${msg.params.errorText} ${msg.params.requestId}`)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function rpc(method, params = {}) {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const id = ++seq
|
|
64
|
+
pending.set(id, resolve)
|
|
65
|
+
ws.send(JSON.stringify({ id, method, params }))
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
await new Promise((r) => { ws.onopen = r })
|
|
69
|
+
await rpc('Runtime.enable')
|
|
70
|
+
await rpc('Network.enable')
|
|
71
|
+
await rpc('Page.enable')
|
|
72
|
+
|
|
73
|
+
const target = await rpc('Target.createTarget', { url: 'about:blank' })
|
|
74
|
+
const targetId = target.result?.targetId
|
|
75
|
+
// 直接在当前页面导航更简单:拿新的 page target
|
|
76
|
+
const list2 = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json()
|
|
77
|
+
const pageTarget = list2.find((t) => t.id === targetId) ?? list2.find((t) => t.type === 'page')
|
|
78
|
+
const ws2 = new WebSocket(pageTarget.webSocketDebuggerUrl)
|
|
79
|
+
let seq2 = 0
|
|
80
|
+
const pending2 = new Map()
|
|
81
|
+
const pageEvents = []
|
|
82
|
+
ws2.onmessage = (m) => {
|
|
83
|
+
const msg = JSON.parse(m.data)
|
|
84
|
+
if (msg.id !== undefined && pending2.has(msg.id)) {
|
|
85
|
+
pending2.get(msg.id)(msg)
|
|
86
|
+
pending2.delete(msg.id)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
if (msg.method === 'Runtime.consoleAPICalled') {
|
|
90
|
+
const args = (msg.params.args || []).map((a) => a.value ?? a.description ?? '').join(' ')
|
|
91
|
+
pageEvents.push(`[console.${msg.params.type}] ${args.slice(0, 300)}`)
|
|
92
|
+
} else if (msg.method === 'Runtime.exceptionThrown') {
|
|
93
|
+
const d = msg.params.exceptionDetails
|
|
94
|
+
pageEvents.push(`[pageerror] ${d?.exception?.description ?? d?.text ?? ''}`.slice(0, 500))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function rpc2(method, params = {}) {
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
const id = ++seq2
|
|
100
|
+
pending2.set(id, resolve)
|
|
101
|
+
ws2.send(JSON.stringify({ id, method, params }))
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
await new Promise((r) => { ws2.onopen = r })
|
|
105
|
+
await rpc2('Runtime.enable')
|
|
106
|
+
await rpc2('Page.enable')
|
|
107
|
+
await rpc2('Page.navigate', { url: APP_URL })
|
|
108
|
+
|
|
109
|
+
await waitFor(async () => {
|
|
110
|
+
const r = await rpc2('Runtime.evaluate', { expression: '!!window.__ModuleLoader__ && !!window.__DSH_BOOT__', returnByValue: true })
|
|
111
|
+
return r.result?.result?.value
|
|
112
|
+
}, 30000, 'boot')
|
|
113
|
+
|
|
114
|
+
// 等待客户端插件激活(console 日志由 apply 打印)
|
|
115
|
+
await new Promise((r) => setTimeout(r, 6000))
|
|
116
|
+
|
|
117
|
+
const checks = {}
|
|
118
|
+
{
|
|
119
|
+
const r = await rpc2('Runtime.evaluate', {
|
|
120
|
+
expression: `(() => {
|
|
121
|
+
const boot = window.__DSH_BOOT__ || {}
|
|
122
|
+
const entries = (boot.entries || []).map(e => e.id)
|
|
123
|
+
return JSON.stringify({
|
|
124
|
+
graphLength: entries.length,
|
|
125
|
+
hasEntry: entries.includes('@telosmaylx/dsh-session-notify'),
|
|
126
|
+
entry: entries.find(e => e.includes('session-notify')),
|
|
127
|
+
notificationSupported: typeof window.Notification !== 'undefined',
|
|
128
|
+
notificationPermission: typeof window.Notification !== 'undefined' ? window.Notification.permission : 'n/a',
|
|
129
|
+
})
|
|
130
|
+
})()`,
|
|
131
|
+
returnByValue: true,
|
|
132
|
+
})
|
|
133
|
+
checks.graph = JSON.parse(r.result?.result?.value ?? '{}')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log('=== console/pageerror events ===')
|
|
137
|
+
for (const e of pageEvents) console.log(e)
|
|
138
|
+
console.log('=== checks ===')
|
|
139
|
+
console.log(JSON.stringify(checks, null, 2))
|
|
140
|
+
|
|
141
|
+
try { ws.close() } catch {}
|
|
142
|
+
try { ws2.close() } catch {}
|
|
143
|
+
chrome.kill()
|
|
144
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
145
|
+
try { rmSync(profile, { recursive: true, force: true }) } catch {}
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
// probe-diag-settings.mjs — 诊断设置面板入口:列出含 data-* 属性的元素与设置相关文本
|
|
2
|
-
import { spawn } from 'node:child_process'
|
|
3
|
-
import { mkdtempSync, rmSync } from 'node:fs'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
|
|
7
|
-
const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe'
|
|
8
|
-
const PORT = 9377
|
|
9
|
-
const profile = mkdtempSync(join(tmpdir(), 'dsh-diag2-'))
|
|
10
|
-
const chrome = spawn(CHROME, ['--headless=new', `--remote-debugging-port=${PORT}`, `--user-data-dir=${profile}`, '--no-first-run', '--disable-gpu', '--window-size=1500,950', 'about:blank'], { stdio: 'ignore' })
|
|
11
|
-
|
|
12
|
-
async function waitFor(fn, t, l) {
|
|
13
|
-
const t0 = Date.now()
|
|
14
|
-
while (Date.now() - t0 < t) { try { const v = await fn(); if (v) return v } catch {} await new Promise((r) => setTimeout(r, 400)) }
|
|
15
|
-
throw new Error('timeout ' + l)
|
|
16
|
-
}
|
|
17
|
-
const page = await waitFor(async () => {
|
|
18
|
-
const l = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json()
|
|
19
|
-
return l.find((x) => x.type === 'page')
|
|
20
|
-
}, 20000, 'page')
|
|
21
|
-
const ws = new WebSocket(page.webSocketDebuggerUrl)
|
|
22
|
-
let seq = 0
|
|
23
|
-
const pending = new Map()
|
|
24
|
-
ws.onmessage = (m) => { const msg = JSON.parse(m.data); if (msg.id !== undefined && pending.has(msg.id)) { pending.get(msg.id)(msg); pending.delete(msg.id) } }
|
|
25
|
-
function rpc(method, params = {}) { return new Promise((res) => { const id = ++seq; pending.set(id, res); ws.send(JSON.stringify({ id, method, params })) }) }
|
|
26
|
-
await new Promise((r) => { ws.onopen = r })
|
|
27
|
-
await rpc('Runtime.enable')
|
|
28
|
-
await rpc('Page.enable')
|
|
29
|
-
await rpc('Page.navigate', { url: 'http://127.0.0.1:3080' })
|
|
30
|
-
await waitFor(async () => { const r = await rpc('Runtime.evaluate', { expression: '!!window.__DSH_BOOT__', returnByValue: true }); return r.result?.result?.value }, 30000, 'boot')
|
|
31
|
-
await new Promise((r) => setTimeout(r, 5000))
|
|
32
|
-
|
|
33
|
-
const diag = await rpc('Runtime.evaluate', {
|
|
34
|
-
expression: `(() => {
|
|
35
|
-
const out = { dataAttrs: [], settingTexts: [] }
|
|
36
|
-
const all = document.querySelectorAll('[data-*]')
|
|
37
|
-
const seen = new Set()
|
|
38
|
-
document.querySelectorAll('[data-dsh-settings-root], [data-settings], [data-settings-page], [data-view]').forEach((e) => out.dataAttrs.push((e.getAttribute('data-dsh-settings-root') || e.getAttribute('data-settings') || e.getAttribute('data-settings-page') || e.getAttribute('data-view') || '') + ' :: ' + (e.textContent || '').slice(0, 40)))
|
|
39
|
-
// 找文本节点包含 设置 的元素
|
|
40
|
-
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)
|
|
41
|
-
let node
|
|
42
|
-
while ((node = walker.nextNode())) {
|
|
43
|
-
const t = (node.textContent || '').trim()
|
|
44
|
-
if (t === '设置') { const p = node.parentElement; const tag = p?.tagName; const attr = p?.outerHTML?.slice(0, 160); out.settingTexts.push(tag + ' :: ' + attr) }
|
|
45
|
-
}
|
|
46
|
-
return JSON.stringify(out)
|
|
47
|
-
})()`,
|
|
48
|
-
returnByValue: true,
|
|
49
|
-
})
|
|
50
|
-
console.log('DIAG:', diag.result?.result?.value)
|
|
51
|
-
|
|
52
|
-
// 点击 settings 入口后再 dump body 结构
|
|
53
|
-
await rpc('Runtime.evaluate', {
|
|
54
|
-
expression: `(() => {
|
|
55
|
-
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)
|
|
56
|
-
let node
|
|
57
|
-
while ((node = walker.nextNode())) {
|
|
58
|
-
if ((node.textContent || '').trim() === '设置') { node.parentElement?.click(); return 'clicked' }
|
|
59
|
-
}
|
|
60
|
-
return 'none'
|
|
61
|
-
})()`,
|
|
62
|
-
returnByValue: true,
|
|
63
|
-
})
|
|
64
|
-
await new Promise((r) => setTimeout(r, 3000))
|
|
65
|
-
const post = await rpc('Runtime.evaluate', {
|
|
66
|
-
expression: `(() => {
|
|
67
|
-
const body = document.body.innerText
|
|
68
|
-
const idx = body.indexOf('设置')
|
|
69
|
-
return JSON.stringify({ len: body.length, ctx: body.slice(idx, idx + 500) })
|
|
70
|
-
})()`,
|
|
71
|
-
returnByValue: true,
|
|
72
|
-
})
|
|
73
|
-
console.log('POST:', post.result?.result?.value)
|
|
74
|
-
|
|
75
|
-
try { ws.close() } catch {}
|
|
76
|
-
chrome.kill()
|
|
77
|
-
await new Promise((r) => setTimeout(r, 300))
|
|
78
|
-
try { rmSync(profile, { recursive: true, force: true }) } catch {}
|
|
79
|
-
process.exit(0)
|
|
1
|
+
// probe-diag-settings.mjs — 诊断设置面板入口:列出含 data-* 属性的元素与设置相关文本
|
|
2
|
+
import { spawn } from 'node:child_process'
|
|
3
|
+
import { mkdtempSync, rmSync } from 'node:fs'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe'
|
|
8
|
+
const PORT = 9377
|
|
9
|
+
const profile = mkdtempSync(join(tmpdir(), 'dsh-diag2-'))
|
|
10
|
+
const chrome = spawn(CHROME, ['--headless=new', `--remote-debugging-port=${PORT}`, `--user-data-dir=${profile}`, '--no-first-run', '--disable-gpu', '--window-size=1500,950', 'about:blank'], { stdio: 'ignore' })
|
|
11
|
+
|
|
12
|
+
async function waitFor(fn, t, l) {
|
|
13
|
+
const t0 = Date.now()
|
|
14
|
+
while (Date.now() - t0 < t) { try { const v = await fn(); if (v) return v } catch {} await new Promise((r) => setTimeout(r, 400)) }
|
|
15
|
+
throw new Error('timeout ' + l)
|
|
16
|
+
}
|
|
17
|
+
const page = await waitFor(async () => {
|
|
18
|
+
const l = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json()
|
|
19
|
+
return l.find((x) => x.type === 'page')
|
|
20
|
+
}, 20000, 'page')
|
|
21
|
+
const ws = new WebSocket(page.webSocketDebuggerUrl)
|
|
22
|
+
let seq = 0
|
|
23
|
+
const pending = new Map()
|
|
24
|
+
ws.onmessage = (m) => { const msg = JSON.parse(m.data); if (msg.id !== undefined && pending.has(msg.id)) { pending.get(msg.id)(msg); pending.delete(msg.id) } }
|
|
25
|
+
function rpc(method, params = {}) { return new Promise((res) => { const id = ++seq; pending.set(id, res); ws.send(JSON.stringify({ id, method, params })) }) }
|
|
26
|
+
await new Promise((r) => { ws.onopen = r })
|
|
27
|
+
await rpc('Runtime.enable')
|
|
28
|
+
await rpc('Page.enable')
|
|
29
|
+
await rpc('Page.navigate', { url: 'http://127.0.0.1:3080' })
|
|
30
|
+
await waitFor(async () => { const r = await rpc('Runtime.evaluate', { expression: '!!window.__DSH_BOOT__', returnByValue: true }); return r.result?.result?.value }, 30000, 'boot')
|
|
31
|
+
await new Promise((r) => setTimeout(r, 5000))
|
|
32
|
+
|
|
33
|
+
const diag = await rpc('Runtime.evaluate', {
|
|
34
|
+
expression: `(() => {
|
|
35
|
+
const out = { dataAttrs: [], settingTexts: [] }
|
|
36
|
+
const all = document.querySelectorAll('[data-*]')
|
|
37
|
+
const seen = new Set()
|
|
38
|
+
document.querySelectorAll('[data-dsh-settings-root], [data-settings], [data-settings-page], [data-view]').forEach((e) => out.dataAttrs.push((e.getAttribute('data-dsh-settings-root') || e.getAttribute('data-settings') || e.getAttribute('data-settings-page') || e.getAttribute('data-view') || '') + ' :: ' + (e.textContent || '').slice(0, 40)))
|
|
39
|
+
// 找文本节点包含 设置 的元素
|
|
40
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)
|
|
41
|
+
let node
|
|
42
|
+
while ((node = walker.nextNode())) {
|
|
43
|
+
const t = (node.textContent || '').trim()
|
|
44
|
+
if (t === '设置') { const p = node.parentElement; const tag = p?.tagName; const attr = p?.outerHTML?.slice(0, 160); out.settingTexts.push(tag + ' :: ' + attr) }
|
|
45
|
+
}
|
|
46
|
+
return JSON.stringify(out)
|
|
47
|
+
})()`,
|
|
48
|
+
returnByValue: true,
|
|
49
|
+
})
|
|
50
|
+
console.log('DIAG:', diag.result?.result?.value)
|
|
51
|
+
|
|
52
|
+
// 点击 settings 入口后再 dump body 结构
|
|
53
|
+
await rpc('Runtime.evaluate', {
|
|
54
|
+
expression: `(() => {
|
|
55
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)
|
|
56
|
+
let node
|
|
57
|
+
while ((node = walker.nextNode())) {
|
|
58
|
+
if ((node.textContent || '').trim() === '设置') { node.parentElement?.click(); return 'clicked' }
|
|
59
|
+
}
|
|
60
|
+
return 'none'
|
|
61
|
+
})()`,
|
|
62
|
+
returnByValue: true,
|
|
63
|
+
})
|
|
64
|
+
await new Promise((r) => setTimeout(r, 3000))
|
|
65
|
+
const post = await rpc('Runtime.evaluate', {
|
|
66
|
+
expression: `(() => {
|
|
67
|
+
const body = document.body.innerText
|
|
68
|
+
const idx = body.indexOf('设置')
|
|
69
|
+
return JSON.stringify({ len: body.length, ctx: body.slice(idx, idx + 500) })
|
|
70
|
+
})()`,
|
|
71
|
+
returnByValue: true,
|
|
72
|
+
})
|
|
73
|
+
console.log('POST:', post.result?.result?.value)
|
|
74
|
+
|
|
75
|
+
try { ws.close() } catch {}
|
|
76
|
+
chrome.kill()
|
|
77
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
78
|
+
try { rmSync(profile, { recursive: true, force: true }) } catch {}
|
|
79
|
+
process.exit(0)
|