skykoi 2026.3.85 → 2026.3.87
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/assets/chrome-extension/background.js +317 -206
- package/assets/chrome-extension/manifest.json +4 -4
- package/dist/{acp-cli-Dgvy6bKG.js → acp-cli-Cue9eQD1.js} +3 -3
- package/dist/{agent-B33p8GO3.js → agent-7qp7NaRz.js} +7 -7
- package/dist/{archive-09rsl6Ll.js → archive-C91mkBc-.js} +1 -1
- package/dist/{audit-Bk2eeCaJ.js → audit-2oghn2vt.js} +5 -5
- package/dist/{auth-health-zenw66eK.js → auth-health-utpbB5vt.js} +1 -1
- package/dist/build-info.json +3 -3
- package/dist/{call-CWNr3uUO.js → call-CB8hoic2.js} +3 -8
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{channel-options-CclY3Kao.js → channel-options-wuxVoUIe.js} +2 -2
- package/dist/{channel-summary-Ae-IsE8h.js → channel-summary-CfxC-1nZ.js} +4 -4
- package/dist/{channels-cli-BkHQm13G.js → channels-cli-DAF3ekZo.js} +23 -23
- package/dist/{chrome-BXUOz50D.js → chrome-CKj-JW7a.js} +1 -2
- package/dist/cli/daemon-cli.js +1 -1
- package/dist/{cli-C0FJD3jr.js → cli-DWiC-T7N.js} +17 -17
- package/dist/{completion-cli-CBuTZYSt.js → completion-cli-BXlPFqTC.js} +1 -1
- package/dist/{config-DKY_dinf.js → config-CtAp-ADe.js} +1 -1
- package/dist/{config-guard-DEz1BuaV.js → config-guard-DZZyYL4S.js} +20 -20
- package/dist/{configure-DDNEcXdF.js → configure-BdS5VyTl.js} +7 -7
- package/dist/{control-service-IfyiVJzo.js → control-service-BbXQeQwf.js} +4 -4
- package/dist/{cron-cli--_8V5Gf3.js → cron-cli-B39VyfFH.js} +4 -4
- package/dist/{daemon-cli-B9O89I4b.js → daemon-cli-CpcZdCm0.js} +8 -8
- package/dist/{daemon-runtime-BlJMOmTd.js → daemon-runtime-CUIuNwaa.js} +1 -1
- package/dist/{deliver-MpXBgM3h.js → deliver-CQeQBobe.js} +11 -11
- package/dist/{deps-DBDWSLXZ.js → deps-BjTw2X80.js} +2 -2
- package/dist/{devices-cli-DSK6HYcw.js → devices-cli-AHcYvI4G.js} +3 -3
- package/dist/{directory-cli-uUVaHSGb.js → directory-cli-Ceuljq1g.js} +2 -2
- package/dist/{dispatcher-SOgLWHV0.js → dispatcher-D-yTQPvS.js} +1 -1
- package/dist/{dns-cli-BQYu44-7.js → dns-cli-GL7D9rXf.js} +2 -2
- package/dist/{doctor-2S0oU0gS.js → doctor-DhRjIF9C.js} +13 -13
- package/dist/entry.js +1 -1
- package/dist/{exec-approvals-cli-BjXeuNZX.js → exec-approvals-cli-DAlM2LHw.js} +5 -5
- package/dist/extensionAPI.js +17 -17
- package/dist/{gateway-cli-I9an3dZ_.js → gateway-cli-CWu5Ho2O.js} +146 -78
- package/dist/{gateway-rpc-CLWNaD7x.js → gateway-rpc-B2QEauAF.js} +1 -1
- package/dist/{github-copilot-auth-vZ5-Nn7c.js → github-copilot-auth-vHH_raqP.js} +4 -4
- package/dist/{health-format-CJjcHVlY.js → health-format-CzWNMbcr.js} +6 -6
- package/dist/{hooks-cli-DpD_F4PN.js → hooks-cli-BTNJ3TcY.js} +19 -19
- package/dist/{image-CwUNm3Vl.js → image-C1XBkrja.js} +3 -3
- package/dist/index.js +43 -43
- package/dist/{installs-DzWk8S7n.js → installs-BlumYhIK.js} +1 -1
- package/dist/{login-qr-BL0IdPTR.js → login-qr-C1VpjiOL.js} +1 -1
- package/dist/{logs-cli-DhMg4ByY.js → logs-cli-DzVqMDvp.js} +4 -4
- package/dist/{manager-CSZdstQ8.js → manager-BYL4_VQc.js} +1 -1
- package/dist/{model-selection-CkeQWhia.js → model-selection-BOiaAkb5.js} +117 -2
- package/dist/{models-cli-cQKCcCPL.js → models-cli-DzDVIpeN.js} +19 -19
- package/dist/{node-cli-DnLm5atJ.js → node-cli-DkC6wrL-.js} +9 -9
- package/dist/{nodes-cli-ka_n7rhe.js → nodes-cli-BlOW7-46.js} +4 -4
- package/dist/{onboard-channels-BhNBrV_Q.js → onboard-channels-XZYp7zlA.js} +3 -3
- package/dist/{onboard-skills-BYBSWtSD.js → onboard-skills-C1A_Lqwu.js} +5 -5
- package/dist/{onboarding-CfL5vV28.js → onboarding-DBKDeCdL.js} +23 -25
- package/dist/{pairing-cli-B5vocCzh.js → pairing-cli-DpnNmz7M.js} +2 -2
- package/dist/{pi-embedded-helpers-Cu5s400O.js → pi-embedded-helpers-BPKQ1WbA.js} +2 -2
- package/dist/{pi-tools.policy-BZg7tJcw.js → pi-tools.policy-DRWwTCPx.js} +1 -1
- package/dist/{plugin-auto-enable-DQnhAmyB.js → plugin-auto-enable-CJPNyFbz.js} +1 -1
- package/dist/{plugin-registry-zFUHKwEt.js → plugin-registry-k7Abn6gN.js} +2 -2
- package/dist/plugin-sdk/browser/constants.d.ts +0 -9
- package/dist/plugin-sdk/gateway/call.d.ts +0 -5
- package/dist/plugin-sdk/index.js +127 -15
- package/dist/plugin-sdk/media/input-files.d.ts +1 -1
- package/dist/{plugins-cli-BldSwhzI.js → plugins-cli-OM3taC51.js} +21 -21
- package/dist/{ports-CrDpWbK_.js → ports-_xuvA1tL.js} +1 -1
- package/dist/{program-CBdq2IEp.js → program-BLBYWrqo.js} +6 -6
- package/dist/{pw-ai-D8a23rhS.js → pw-ai-DeetJxIQ.js} +1 -1
- package/dist/{register.subclis-B2TAYlxQ.js → register.subclis-CCxtEfCG.js} +27 -27
- package/dist/{reply-CogA-BUj.js → reply-3b9cJ0zw.js} +25 -25
- package/dist/{routes-DwesuQto.js → routes-C3SV3r0e.js} +6 -6
- package/dist/{rpc-DyvRsbXd.js → rpc-CgIk2J7j.js} +1 -1
- package/dist/{run-main-B7Dw2tsC.js → run-main-B-FVRwsN.js} +44 -44
- package/dist/{runner-DEGFhun2.js → runner-DAvW5RKS.js} +5 -5
- package/dist/{sandbox-hEnK_s1a.js → sandbox-Dwo6SOe8.js} +4 -4
- package/dist/{sandbox-cli-8ZGWxwkF.js → sandbox-cli-CzUmMGCu.js} +6 -6
- package/dist/{security-cli-C_qpjrTc.js → security-cli-Bli2MaXl.js} +9 -9
- package/dist/{server-context-DWchuAqq.js → server-context-Mxz82xGQ.js} +4 -5
- package/dist/{server-node-events-DDSSdjZd.js → server-node-events-BywQiZ0z.js} +19 -19
- package/dist/{service-audit-B-95c3u8.js → service-audit-DqDVkfEJ.js} +1 -1
- package/dist/{skills-cli-SZCYw7cN.js → skills-cli-DnMWBmAF.js} +2 -2
- package/dist/{status-JDhaBIhA.js → status-DuFP6Ftr.js} +2 -2
- package/dist/{system-cli-CEvYC8RJ.js → system-cli-Bu41yZ2t.js} +4 -4
- package/dist/{tui-Bo9ufl9J.js → tui-D-P5u7lH.js} +4 -4
- package/dist/{tui-cli-B2eE3wj1.js → tui-cli-BPdJ1VNP.js} +10 -10
- package/dist/{update-7hnqqWjP.js → update-Cfz1LOGa.js} +1 -1
- package/dist/{update-cli-BIYn-QeV.js → update-cli-DvAQb1FT.js} +31 -31
- package/dist/{update-runner-B2RC_Kcz.js → update-runner-BCnc1i3P.js} +5 -5
- package/dist/{webhooks-cli-D-BRmxqt.js → webhooks-cli-S_jSOfkR.js} +2 -2
- package/package.json +1 -1
- package/assets/chrome-extension/offscreen.html +0 -4
- package/assets/chrome-extension/offscreen.js +0 -107
|
@@ -1,89 +1,178 @@
|
|
|
1
|
-
|
|
2
|
-
* SKYKOI Browser Relay — Service Worker (MV3)
|
|
3
|
-
*
|
|
4
|
-
* Handles chrome.debugger API calls. WebSocket to the relay server is maintained
|
|
5
|
-
* by an offscreen document (offscreen.js) for persistent connectivity.
|
|
6
|
-
*/
|
|
1
|
+
const DEFAULT_PORT = 18792
|
|
7
2
|
|
|
8
3
|
const BADGE = {
|
|
9
4
|
on: { text: 'ON', color: '#FF5A36' },
|
|
10
5
|
off: { text: '', color: '#000000' },
|
|
11
|
-
connecting: { text: '
|
|
6
|
+
connecting: { text: '…', color: '#F59E0B' },
|
|
12
7
|
error: { text: '!', color: '#B91C1C' },
|
|
13
8
|
}
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
let
|
|
10
|
+
/** @type {WebSocket|null} */
|
|
11
|
+
let relayWs = null
|
|
12
|
+
/** @type {Promise<void>|null} */
|
|
13
|
+
let relayConnectPromise = null
|
|
14
|
+
|
|
17
15
|
let debuggerListenersInstalled = false
|
|
16
|
+
|
|
18
17
|
let nextSession = 1
|
|
19
18
|
|
|
20
|
-
/** @type {Map<number, {state:'connecting'|'connected', sessionId?:string, targetId?:string}>} */
|
|
19
|
+
/** @type {Map<number, {state:'connecting'|'connected', sessionId?:string, targetId?:string, attachOrder?:number}>} */
|
|
21
20
|
const tabs = new Map()
|
|
22
21
|
/** @type {Map<string, number>} */
|
|
23
22
|
const tabBySession = new Map()
|
|
24
23
|
/** @type {Map<string, number>} */
|
|
25
24
|
const childSessionToTab = new Map()
|
|
25
|
+
|
|
26
26
|
/** @type {Map<number, {resolve:(v:any)=>void, reject:(e:Error)=>void}>} */
|
|
27
27
|
const pending = new Map()
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
function nowStack() {
|
|
30
|
+
try {
|
|
31
|
+
return new Error().stack || ''
|
|
32
|
+
} catch {
|
|
33
|
+
return ''
|
|
34
|
+
}
|
|
35
|
+
}
|
|
32
36
|
|
|
33
|
-
async function
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
37
|
+
async function getRelayPort() {
|
|
38
|
+
const stored = await chrome.storage.local.get(['relayPort'])
|
|
39
|
+
const raw = stored.relayPort
|
|
40
|
+
const n = Number.parseInt(String(raw || ''), 10)
|
|
41
|
+
if (!Number.isFinite(n) || n <= 0 || n > 65535) return DEFAULT_PORT
|
|
42
|
+
return n
|
|
43
|
+
}
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
})
|
|
45
|
+
function setBadge(tabId, kind) {
|
|
46
|
+
const cfg = BADGE[kind]
|
|
47
|
+
void chrome.action.setBadgeText({ tabId, text: cfg.text })
|
|
48
|
+
void chrome.action.setBadgeBackgroundColor({ tabId, color: cfg.color })
|
|
49
|
+
void chrome.action.setBadgeTextColor({ tabId, color: '#FFFFFF' }).catch(() => {})
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
function
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
async function ensureRelayConnection() {
|
|
53
|
+
if (relayWs && relayWs.readyState === WebSocket.OPEN) return
|
|
54
|
+
if (relayConnectPromise) return await relayConnectPromise
|
|
55
|
+
|
|
56
|
+
relayConnectPromise = (async () => {
|
|
57
|
+
const port = await getRelayPort()
|
|
58
|
+
const httpBase = `http://127.0.0.1:${port}`
|
|
59
|
+
const wsUrl = `ws://127.0.0.1:${port}/extension`
|
|
60
|
+
|
|
61
|
+
// Fast preflight: is the relay server up?
|
|
62
|
+
try {
|
|
63
|
+
await fetch(`${httpBase}/`, { method: 'HEAD', signal: AbortSignal.timeout(2000) })
|
|
64
|
+
} catch (err) {
|
|
65
|
+
throw new Error(`Relay server not reachable at ${httpBase} (${String(err)})`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const ws = new WebSocket(wsUrl)
|
|
69
|
+
relayWs = ws
|
|
70
|
+
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
const t = setTimeout(() => reject(new Error('WebSocket connect timeout')), 5000)
|
|
73
|
+
ws.onopen = () => {
|
|
74
|
+
clearTimeout(t)
|
|
75
|
+
resolve()
|
|
76
|
+
}
|
|
77
|
+
ws.onerror = () => {
|
|
78
|
+
clearTimeout(t)
|
|
79
|
+
reject(new Error('WebSocket connect failed'))
|
|
80
|
+
}
|
|
81
|
+
ws.onclose = (ev) => {
|
|
82
|
+
clearTimeout(t)
|
|
83
|
+
reject(new Error(`WebSocket closed (${ev.code} ${ev.reason || 'no reason'})`))
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
ws.onmessage = (event) => void onRelayMessage(String(event.data || ''))
|
|
88
|
+
ws.onclose = () => onRelayClosed('closed')
|
|
89
|
+
ws.onerror = () => onRelayClosed('error')
|
|
90
|
+
|
|
91
|
+
if (!debuggerListenersInstalled) {
|
|
92
|
+
debuggerListenersInstalled = true
|
|
93
|
+
chrome.debugger.onEvent.addListener(onDebuggerEvent)
|
|
94
|
+
chrome.debugger.onDetach.addListener(onDebuggerDetach)
|
|
95
|
+
}
|
|
96
|
+
})()
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await relayConnectPromise
|
|
100
|
+
} finally {
|
|
101
|
+
relayConnectPromise = null
|
|
102
|
+
}
|
|
57
103
|
}
|
|
58
104
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
105
|
+
function onRelayClosed(reason) {
|
|
106
|
+
relayWs = null
|
|
107
|
+
for (const [id, p] of pending.entries()) {
|
|
108
|
+
pending.delete(id)
|
|
109
|
+
p.reject(new Error(`Relay disconnected (${reason})`))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const tabId of tabs.keys()) {
|
|
113
|
+
void chrome.debugger.detach({ tabId }).catch(() => {})
|
|
114
|
+
setBadge(tabId, 'connecting')
|
|
115
|
+
void chrome.action.setTitle({
|
|
116
|
+
tabId,
|
|
117
|
+
title: 'SKYKOI Browser Relay: disconnected (click to re-attach)',
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
tabs.clear()
|
|
121
|
+
tabBySession.clear()
|
|
122
|
+
childSessionToTab.clear()
|
|
62
123
|
}
|
|
63
124
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
125
|
+
function sendToRelay(payload) {
|
|
126
|
+
const ws = relayWs
|
|
127
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
128
|
+
throw new Error('Relay not connected')
|
|
129
|
+
}
|
|
130
|
+
ws.send(JSON.stringify(payload))
|
|
131
|
+
}
|
|
67
132
|
|
|
68
|
-
function
|
|
69
|
-
|
|
133
|
+
async function maybeOpenHelpOnce() {
|
|
134
|
+
try {
|
|
135
|
+
const stored = await chrome.storage.local.get(['helpOnErrorShown'])
|
|
136
|
+
if (stored.helpOnErrorShown === true) return
|
|
137
|
+
await chrome.storage.local.set({ helpOnErrorShown: true })
|
|
138
|
+
await chrome.runtime.openOptionsPage()
|
|
139
|
+
} catch {
|
|
140
|
+
// ignore
|
|
141
|
+
}
|
|
142
|
+
}
|
|
70
143
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
144
|
+
function requestFromRelay(command) {
|
|
145
|
+
const id = command.id
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
pending.set(id, { resolve, reject })
|
|
148
|
+
try {
|
|
149
|
+
sendToRelay(command)
|
|
150
|
+
} catch (err) {
|
|
151
|
+
pending.delete(id)
|
|
152
|
+
reject(err instanceof Error ? err : new Error(String(err)))
|
|
78
153
|
}
|
|
79
|
-
|
|
80
|
-
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function onRelayMessage(text) {
|
|
158
|
+
/** @type {any} */
|
|
159
|
+
let msg
|
|
160
|
+
try {
|
|
161
|
+
msg = JSON.parse(text)
|
|
162
|
+
} catch {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (msg && msg.method === 'ping') {
|
|
167
|
+
try {
|
|
168
|
+
sendToRelay({ method: 'pong' })
|
|
169
|
+
} catch {
|
|
170
|
+
// ignore
|
|
81
171
|
}
|
|
82
172
|
return
|
|
83
173
|
}
|
|
84
174
|
|
|
85
|
-
|
|
86
|
-
if (typeof msg.id === 'number' && (msg.result !== undefined || msg.error !== undefined)) {
|
|
175
|
+
if (msg && typeof msg.id === 'number' && (msg.result !== undefined || msg.error !== undefined)) {
|
|
87
176
|
const p = pending.get(msg.id)
|
|
88
177
|
if (!p) return
|
|
89
178
|
pending.delete(msg.id)
|
|
@@ -92,50 +181,32 @@ function onOffscreenMessage(msg) {
|
|
|
92
181
|
return
|
|
93
182
|
}
|
|
94
183
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
sendToRelay({ id: msg.id, error: err instanceof Error ? err.message : String(err) })
|
|
103
|
-
}
|
|
104
|
-
})()
|
|
184
|
+
if (msg && typeof msg.id === 'number' && msg.method === 'forwardCDPCommand') {
|
|
185
|
+
try {
|
|
186
|
+
const result = await handleForwardCdpCommand(msg)
|
|
187
|
+
sendToRelay({ id: msg.id, result })
|
|
188
|
+
} catch (err) {
|
|
189
|
+
sendToRelay({ id: msg.id, error: err instanceof Error ? err.message : String(err) })
|
|
190
|
+
}
|
|
105
191
|
}
|
|
106
192
|
}
|
|
107
193
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
void chrome.action.setBadgeText({ tabId, text: cfg.text })
|
|
115
|
-
void chrome.action.setBadgeBackgroundColor({ tabId, color: cfg.color })
|
|
116
|
-
void chrome.action.setBadgeTextColor({ tabId, color: '#FFFFFF' }).catch(() => {})
|
|
194
|
+
function getTabBySessionId(sessionId) {
|
|
195
|
+
const direct = tabBySession.get(sessionId)
|
|
196
|
+
if (direct) return { tabId: direct, kind: 'main' }
|
|
197
|
+
const child = childSessionToTab.get(sessionId)
|
|
198
|
+
if (child) return { tabId: child, kind: 'child' }
|
|
199
|
+
return null
|
|
117
200
|
}
|
|
118
201
|
|
|
119
|
-
function
|
|
120
|
-
for (const [
|
|
121
|
-
|
|
122
|
-
p.reject(new Error(`Relay disconnected (${reason})`))
|
|
202
|
+
function getTabByTargetId(targetId) {
|
|
203
|
+
for (const [tabId, tab] of tabs.entries()) {
|
|
204
|
+
if (tab.targetId === targetId) return tabId
|
|
123
205
|
}
|
|
124
|
-
|
|
125
|
-
void chrome.debugger.detach({ tabId }).catch(() => {})
|
|
126
|
-
setBadge(tabId, 'off')
|
|
127
|
-
}
|
|
128
|
-
tabs.clear()
|
|
129
|
-
tabBySession.clear()
|
|
130
|
-
childSessionToTab.clear()
|
|
206
|
+
return null
|
|
131
207
|
}
|
|
132
208
|
|
|
133
|
-
|
|
134
|
-
// Tab attach / detach
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
|
|
137
|
-
async function attachTab(tabId) {
|
|
138
|
-
if (tabs.has(tabId)) return
|
|
209
|
+
async function attachTab(tabId, opts = {}) {
|
|
139
210
|
const debuggee = { tabId }
|
|
140
211
|
await chrome.debugger.attach(debuggee, '1.3')
|
|
141
212
|
await chrome.debugger.sendCommand(debuggee, 'Page.enable').catch(() => {})
|
|
@@ -143,21 +214,36 @@ async function attachTab(tabId) {
|
|
|
143
214
|
const info = /** @type {any} */ (await chrome.debugger.sendCommand(debuggee, 'Target.getTargetInfo'))
|
|
144
215
|
const targetInfo = info?.targetInfo
|
|
145
216
|
const targetId = String(targetInfo?.targetId || '').trim()
|
|
146
|
-
if (!targetId)
|
|
217
|
+
if (!targetId) {
|
|
218
|
+
throw new Error('Target.getTargetInfo returned no targetId')
|
|
219
|
+
}
|
|
147
220
|
|
|
148
221
|
const sessionId = `cb-tab-${nextSession++}`
|
|
149
|
-
|
|
150
|
-
tabBySession.set(sessionId, tabId)
|
|
222
|
+
const attachOrder = nextSession
|
|
151
223
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
},
|
|
224
|
+
tabs.set(tabId, { state: 'connected', sessionId, targetId, attachOrder })
|
|
225
|
+
tabBySession.set(sessionId, tabId)
|
|
226
|
+
void chrome.action.setTitle({
|
|
227
|
+
tabId,
|
|
228
|
+
title: 'SKYKOI Browser Relay: attached (click to detach)',
|
|
158
229
|
})
|
|
230
|
+
|
|
231
|
+
if (!opts.skipAttachedEvent) {
|
|
232
|
+
sendToRelay({
|
|
233
|
+
method: 'forwardCDPEvent',
|
|
234
|
+
params: {
|
|
235
|
+
method: 'Target.attachedToTarget',
|
|
236
|
+
params: {
|
|
237
|
+
sessionId,
|
|
238
|
+
targetInfo: { ...targetInfo, attached: true },
|
|
239
|
+
waitingForDebugger: false,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
159
245
|
setBadge(tabId, 'on')
|
|
160
|
-
|
|
246
|
+
return { sessionId, targetId }
|
|
161
247
|
}
|
|
162
248
|
|
|
163
249
|
async function detachTab(tabId, reason) {
|
|
@@ -166,30 +252,69 @@ async function detachTab(tabId, reason) {
|
|
|
166
252
|
try {
|
|
167
253
|
sendToRelay({
|
|
168
254
|
method: 'forwardCDPEvent',
|
|
169
|
-
params: {
|
|
255
|
+
params: {
|
|
256
|
+
method: 'Target.detachedFromTarget',
|
|
257
|
+
params: { sessionId: tab.sessionId, targetId: tab.targetId, reason },
|
|
258
|
+
},
|
|
170
259
|
})
|
|
171
|
-
} catch {
|
|
260
|
+
} catch {
|
|
261
|
+
// ignore
|
|
262
|
+
}
|
|
172
263
|
}
|
|
264
|
+
|
|
173
265
|
if (tab?.sessionId) tabBySession.delete(tab.sessionId)
|
|
174
266
|
tabs.delete(tabId)
|
|
175
|
-
|
|
176
|
-
|
|
267
|
+
|
|
268
|
+
for (const [childSessionId, parentTabId] of childSessionToTab.entries()) {
|
|
269
|
+
if (parentTabId === tabId) childSessionToTab.delete(childSessionId)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
await chrome.debugger.detach({ tabId })
|
|
274
|
+
} catch {
|
|
275
|
+
// ignore
|
|
276
|
+
}
|
|
277
|
+
|
|
177
278
|
setBadge(tabId, 'off')
|
|
178
|
-
void chrome.action.setTitle({
|
|
279
|
+
void chrome.action.setTitle({
|
|
280
|
+
tabId,
|
|
281
|
+
title: 'SKYKOI Browser Relay (click to attach/detach)',
|
|
282
|
+
})
|
|
179
283
|
}
|
|
180
284
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
285
|
+
async function connectOrToggleForActiveTab() {
|
|
286
|
+
const [active] = await chrome.tabs.query({ active: true, currentWindow: true })
|
|
287
|
+
const tabId = active?.id
|
|
288
|
+
if (!tabId) return
|
|
184
289
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
290
|
+
const existing = tabs.get(tabId)
|
|
291
|
+
if (existing?.state === 'connected') {
|
|
292
|
+
await detachTab(tabId, 'toggle')
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
tabs.set(tabId, { state: 'connecting' })
|
|
297
|
+
setBadge(tabId, 'connecting')
|
|
298
|
+
void chrome.action.setTitle({
|
|
299
|
+
tabId,
|
|
300
|
+
title: 'SKYKOI Browser Relay: connecting to local relay…',
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
await ensureRelayConnection()
|
|
305
|
+
await attachTab(tabId)
|
|
306
|
+
} catch (err) {
|
|
307
|
+
tabs.delete(tabId)
|
|
308
|
+
setBadge(tabId, 'error')
|
|
309
|
+
void chrome.action.setTitle({
|
|
310
|
+
tabId,
|
|
311
|
+
title: 'SKYKOI Browser Relay: relay not running (open options for setup)',
|
|
312
|
+
})
|
|
313
|
+
void maybeOpenHelpOnce()
|
|
314
|
+
// Extra breadcrumbs in chrome://extensions service worker logs.
|
|
315
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
316
|
+
console.warn('attach failed', message, nowStack())
|
|
317
|
+
}
|
|
193
318
|
}
|
|
194
319
|
|
|
195
320
|
async function handleForwardCdpCommand(msg) {
|
|
@@ -197,131 +322,117 @@ async function handleForwardCdpCommand(msg) {
|
|
|
197
322
|
const params = msg?.params?.params || undefined
|
|
198
323
|
const sessionId = typeof msg?.params?.sessionId === 'string' ? msg.params.sessionId : undefined
|
|
199
324
|
|
|
325
|
+
// Map command to tab
|
|
200
326
|
const bySession = sessionId ? getTabBySessionId(sessionId) : null
|
|
201
327
|
const targetId = typeof params?.targetId === 'string' ? params.targetId : undefined
|
|
202
|
-
const tabId =
|
|
203
|
-
||
|
|
204
|
-
|
|
205
|
-
|
|
328
|
+
const tabId =
|
|
329
|
+
bySession?.tabId ||
|
|
330
|
+
(targetId ? getTabByTargetId(targetId) : null) ||
|
|
331
|
+
(() => {
|
|
332
|
+
// No sessionId: pick the first connected tab (stable-ish).
|
|
333
|
+
for (const [id, tab] of tabs.entries()) {
|
|
334
|
+
if (tab.state === 'connected') return id
|
|
335
|
+
}
|
|
336
|
+
return null
|
|
337
|
+
})()
|
|
206
338
|
|
|
339
|
+
if (!tabId) throw new Error(`No attached tab for method ${method}`)
|
|
340
|
+
|
|
341
|
+
/** @type {chrome.debugger.DebuggerSession} */
|
|
207
342
|
const debuggee = { tabId }
|
|
208
343
|
|
|
209
344
|
if (method === 'Runtime.enable') {
|
|
210
|
-
|
|
211
|
-
|
|
345
|
+
try {
|
|
346
|
+
await chrome.debugger.sendCommand(debuggee, 'Runtime.disable')
|
|
347
|
+
await new Promise((r) => setTimeout(r, 50))
|
|
348
|
+
} catch {
|
|
349
|
+
// ignore
|
|
350
|
+
}
|
|
212
351
|
return await chrome.debugger.sendCommand(debuggee, 'Runtime.enable', params)
|
|
213
352
|
}
|
|
353
|
+
|
|
214
354
|
if (method === 'Target.createTarget') {
|
|
215
355
|
const url = typeof params?.url === 'string' ? params.url : 'about:blank'
|
|
216
356
|
const tab = await chrome.tabs.create({ url, active: false })
|
|
217
357
|
if (!tab.id) throw new Error('Failed to create tab')
|
|
218
|
-
await new Promise(r => setTimeout(r, 100))
|
|
219
|
-
await attachTab(tab.id)
|
|
220
|
-
|
|
221
|
-
return { targetId: t?.targetId }
|
|
358
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
359
|
+
const attached = await attachTab(tab.id)
|
|
360
|
+
return { targetId: attached.targetId }
|
|
222
361
|
}
|
|
362
|
+
|
|
223
363
|
if (method === 'Target.closeTarget') {
|
|
224
|
-
const
|
|
225
|
-
const toClose =
|
|
364
|
+
const target = typeof params?.targetId === 'string' ? params.targetId : ''
|
|
365
|
+
const toClose = target ? getTabByTargetId(target) : tabId
|
|
226
366
|
if (!toClose) return { success: false }
|
|
227
|
-
|
|
367
|
+
try {
|
|
368
|
+
await chrome.tabs.remove(toClose)
|
|
369
|
+
} catch {
|
|
370
|
+
return { success: false }
|
|
371
|
+
}
|
|
228
372
|
return { success: true }
|
|
229
373
|
}
|
|
374
|
+
|
|
230
375
|
if (method === 'Target.activateTarget') {
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
376
|
+
const target = typeof params?.targetId === 'string' ? params.targetId : ''
|
|
377
|
+
const toActivate = target ? getTabByTargetId(target) : tabId
|
|
378
|
+
if (!toActivate) return {}
|
|
379
|
+
const tab = await chrome.tabs.get(toActivate).catch(() => null)
|
|
380
|
+
if (!tab) return {}
|
|
381
|
+
if (tab.windowId) {
|
|
382
|
+
await chrome.windows.update(tab.windowId, { focused: true }).catch(() => {})
|
|
237
383
|
}
|
|
384
|
+
await chrome.tabs.update(toActivate, { active: true }).catch(() => {})
|
|
238
385
|
return {}
|
|
239
386
|
}
|
|
240
387
|
|
|
241
388
|
const tabState = tabs.get(tabId)
|
|
242
|
-
const
|
|
243
|
-
const debuggerSession =
|
|
389
|
+
const mainSessionId = tabState?.sessionId
|
|
390
|
+
const debuggerSession =
|
|
391
|
+
sessionId && mainSessionId && sessionId !== mainSessionId
|
|
392
|
+
? { ...debuggee, sessionId }
|
|
393
|
+
: debuggee
|
|
394
|
+
|
|
244
395
|
return await chrome.debugger.sendCommand(debuggerSession, method, params)
|
|
245
396
|
}
|
|
246
397
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (debuggerListenersInstalled) return
|
|
253
|
-
debuggerListenersInstalled = true
|
|
254
|
-
|
|
255
|
-
chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
256
|
-
const tabId = source.tabId; if (!tabId) return
|
|
257
|
-
const tab = tabs.get(tabId); if (!tab?.sessionId) return
|
|
258
|
-
if (method === 'Target.attachedToTarget' && params?.sessionId) childSessionToTab.set(String(params.sessionId), tabId)
|
|
259
|
-
if (method === 'Target.detachedFromTarget' && params?.sessionId) childSessionToTab.delete(String(params.sessionId))
|
|
260
|
-
try {
|
|
261
|
-
sendToRelay({ method: 'forwardCDPEvent', params: { sessionId: source.sessionId || tab.sessionId, method, params } })
|
|
262
|
-
} catch { /* ignore */ }
|
|
263
|
-
})
|
|
398
|
+
function onDebuggerEvent(source, method, params) {
|
|
399
|
+
const tabId = source.tabId
|
|
400
|
+
if (!tabId) return
|
|
401
|
+
const tab = tabs.get(tabId)
|
|
402
|
+
if (!tab?.sessionId) return
|
|
264
403
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
268
|
-
}
|
|
404
|
+
if (method === 'Target.attachedToTarget' && params?.sessionId) {
|
|
405
|
+
childSessionToTab.set(String(params.sessionId), tabId)
|
|
406
|
+
}
|
|
269
407
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
408
|
+
if (method === 'Target.detachedFromTarget' && params?.sessionId) {
|
|
409
|
+
childSessionToTab.delete(String(params.sessionId))
|
|
410
|
+
}
|
|
273
411
|
|
|
274
|
-
async function autoAttachTab(tabId) {
|
|
275
|
-
if (tabs.has(tabId)) return
|
|
276
|
-
if (!relayConnected) return
|
|
277
412
|
try {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if (tab.id && tab.url && !tab.url.startsWith('chrome://') && !tab.url.startsWith('chrome-extension://') && !tab.url.startsWith('devtools://')) {
|
|
289
|
-
void autoAttachTab(tab.id)
|
|
290
|
-
}
|
|
413
|
+
sendToRelay({
|
|
414
|
+
method: 'forwardCDPEvent',
|
|
415
|
+
params: {
|
|
416
|
+
sessionId: source.sessionId || tab.sessionId,
|
|
417
|
+
method,
|
|
418
|
+
params,
|
|
419
|
+
},
|
|
420
|
+
})
|
|
421
|
+
} catch {
|
|
422
|
+
// ignore
|
|
291
423
|
}
|
|
292
424
|
}
|
|
293
425
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// Manual toggle
|
|
301
|
-
chrome.action.onClicked.addListener(async () => {
|
|
302
|
-
const [active] = await chrome.tabs.query({ active: true, currentWindow: true })
|
|
303
|
-
if (!active?.id) return
|
|
304
|
-
if (tabs.has(active.id)) { await detachTab(active.id, 'toggle') }
|
|
305
|
-
else { void autoAttachTab(active.id) }
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
// ---------------------------------------------------------------------------
|
|
309
|
-
// Startup: create offscreen document and connect
|
|
310
|
-
// ---------------------------------------------------------------------------
|
|
311
|
-
|
|
312
|
-
async function init() {
|
|
313
|
-
await ensureOffscreen()
|
|
314
|
-
ensureOffscreenPort()
|
|
426
|
+
function onDebuggerDetach(source, reason) {
|
|
427
|
+
const tabId = source.tabId
|
|
428
|
+
if (!tabId) return
|
|
429
|
+
if (!tabs.has(tabId)) return
|
|
430
|
+
void detachTab(tabId, reason)
|
|
315
431
|
}
|
|
316
432
|
|
|
317
|
-
chrome.
|
|
318
|
-
chrome.runtime.onInstalled.addListener(() => void init())
|
|
433
|
+
chrome.action.onClicked.addListener(() => void connectOrToggleForActiveTab())
|
|
319
434
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
chrome.
|
|
323
|
-
if (alarm.name === 'skykoi-keepalive') void init()
|
|
435
|
+
chrome.runtime.onInstalled.addListener(() => {
|
|
436
|
+
// Useful: first-time instructions.
|
|
437
|
+
void chrome.runtime.openOptionsPage()
|
|
324
438
|
})
|
|
325
|
-
|
|
326
|
-
// Fire immediately
|
|
327
|
-
void init()
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "SKYKOI Browser Relay",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "Attach SKYKOI to your existing Chrome
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Attach SKYKOI to your existing Chrome tab via a local CDP relay server.",
|
|
6
6
|
"icons": {
|
|
7
7
|
"16": "icons/icon16.png",
|
|
8
8
|
"32": "icons/icon32.png",
|
|
9
9
|
"48": "icons/icon48.png",
|
|
10
10
|
"128": "icons/icon128.png"
|
|
11
11
|
},
|
|
12
|
-
"permissions": ["debugger", "tabs", "activeTab", "storage"
|
|
12
|
+
"permissions": ["debugger", "tabs", "activeTab", "storage"],
|
|
13
13
|
"host_permissions": ["http://127.0.0.1/*", "http://localhost/*"],
|
|
14
|
-
"background": { "service_worker": "background.js" },
|
|
14
|
+
"background": { "service_worker": "background.js", "type": "module" },
|
|
15
15
|
"action": {
|
|
16
16
|
"default_title": "SKYKOI Browser Relay (click to attach/detach)",
|
|
17
17
|
"default_icon": {
|