@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.
@@ -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)