@xcanwin/manyoyo 5.8.9 → 5.8.10
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/bin/manyoyo.js +24 -66
- package/lib/plugin/playwright.js +1049 -169
- package/lib/web/server.js +2173 -209
- package/package.json +1 -1
- package/lib/plugin/playwright-bootstrap.js +0 -116
- package/lib/plugin/playwright-command-output.js +0 -95
- package/lib/plugin/playwright-container-runtime.js +0 -94
- package/lib/plugin/playwright-extension-manager.js +0 -265
- package/lib/plugin/playwright-extension-paths.js +0 -98
- package/lib/plugin/playwright-host-runtime.js +0 -114
- package/lib/plugin/playwright-scene-config.js +0 -137
- package/lib/plugin/playwright-scene-drivers.js +0 -285
- package/lib/plugin/playwright-scene-state.js +0 -80
- package/lib/web/agent-command.js +0 -153
- package/lib/web/api-route-helpers.js +0 -88
- package/lib/web/container-exec.js +0 -215
- package/lib/web/http-handlers.js +0 -163
- package/lib/web/runtime-state.js +0 -50
- package/lib/web/server-context.js +0 -71
- package/lib/web/server-lifecycle.js +0 -129
- package/lib/web/session-api-routes.js +0 -390
- package/lib/web/structured-output.js +0 -149
- package/lib/web/structured-trace.js +0 -603
- package/lib/web/system-api-routes.js +0 -114
- package/lib/web/terminal-session.js +0 -205
- package/lib/web/upgrade-handler.js +0 -94
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function createSystemApiRoutes(deps) {
|
|
4
|
-
const {
|
|
5
|
-
req,
|
|
6
|
-
res,
|
|
7
|
-
ctx,
|
|
8
|
-
state,
|
|
9
|
-
fs,
|
|
10
|
-
os,
|
|
11
|
-
path,
|
|
12
|
-
withJsonBody,
|
|
13
|
-
sendJson,
|
|
14
|
-
expandHomeAliasPath,
|
|
15
|
-
readWebConfigSnapshot,
|
|
16
|
-
buildSafeWebConfigSnapshot,
|
|
17
|
-
restoreWebConfigSecrets,
|
|
18
|
-
parseAndValidateConfigRaw,
|
|
19
|
-
buildConfigDefaults
|
|
20
|
-
} = deps;
|
|
21
|
-
|
|
22
|
-
return [
|
|
23
|
-
{
|
|
24
|
-
method: 'GET',
|
|
25
|
-
match: currentPath => currentPath === '/api/fs/directories' ? [] : null,
|
|
26
|
-
handler: async () => {
|
|
27
|
-
const requestUrl = new URL(req.url || '/api/fs/directories', 'http://localhost');
|
|
28
|
-
const requestedPath = expandHomeAliasPath(String(requestUrl.searchParams.get('path') || '').trim() || os.homedir());
|
|
29
|
-
const requestedBasePath = expandHomeAliasPath(String(requestUrl.searchParams.get('basePath') || '').trim());
|
|
30
|
-
const realPath = fs.realpathSync(requestedPath);
|
|
31
|
-
if (!fs.statSync(realPath).isDirectory()) {
|
|
32
|
-
sendJson(res, 400, { error: `目录不存在: ${realPath}` });
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let realBasePath = '';
|
|
37
|
-
if (requestedBasePath) {
|
|
38
|
-
realBasePath = fs.realpathSync(requestedBasePath);
|
|
39
|
-
if (!fs.statSync(realBasePath).isDirectory()) {
|
|
40
|
-
sendJson(res, 400, { error: `basePath 不是目录: ${realBasePath}` });
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const relativeToBase = path.relative(realBasePath, realPath);
|
|
44
|
-
if (relativeToBase.startsWith('..') || path.isAbsolute(relativeToBase)) {
|
|
45
|
-
sendJson(res, 400, { error: '目录超出 basePath 范围' });
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const parentPath = realBasePath
|
|
51
|
-
? (realPath === realBasePath ? '' : path.dirname(realPath))
|
|
52
|
-
: (realPath === path.parse(realPath).root ? '' : path.dirname(realPath));
|
|
53
|
-
const entries = fs.readdirSync(realPath, { withFileTypes: true })
|
|
54
|
-
.filter(entry => entry && entry.isDirectory())
|
|
55
|
-
.map(entry => ({
|
|
56
|
-
name: entry.name,
|
|
57
|
-
path: path.join(realPath, entry.name)
|
|
58
|
-
}))
|
|
59
|
-
.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));
|
|
60
|
-
|
|
61
|
-
sendJson(res, 200, {
|
|
62
|
-
currentPath: realPath,
|
|
63
|
-
basePath: realBasePath || '',
|
|
64
|
-
parentPath,
|
|
65
|
-
entries
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
method: 'GET',
|
|
71
|
-
match: currentPath => currentPath === '/api/config' ? [] : null,
|
|
72
|
-
handler: async () => {
|
|
73
|
-
const snapshot = readWebConfigSnapshot(state.webConfigPath);
|
|
74
|
-
sendJson(res, 200, buildSafeWebConfigSnapshot(snapshot, ctx));
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
method: 'PUT',
|
|
79
|
-
match: currentPath => currentPath === '/api/config' ? [] : null,
|
|
80
|
-
handler: withJsonBody(async payload => {
|
|
81
|
-
const raw = typeof payload.raw === 'string' ? payload.raw : '';
|
|
82
|
-
if (!raw.trim()) {
|
|
83
|
-
sendJson(res, 400, { error: '配置内容不能为空' });
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const currentSnapshot = readWebConfigSnapshot(state.webConfigPath);
|
|
88
|
-
let finalRaw = raw;
|
|
89
|
-
let parsed = null;
|
|
90
|
-
try {
|
|
91
|
-
finalRaw = restoreWebConfigSecrets(raw, currentSnapshot);
|
|
92
|
-
parsed = parseAndValidateConfigRaw(finalRaw);
|
|
93
|
-
} catch (e) {
|
|
94
|
-
sendJson(res, 400, { error: '配置格式错误', detail: e.message || '解析失败' });
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const savePath = path.resolve(state.webConfigPath);
|
|
99
|
-
fs.mkdirSync(path.dirname(savePath), { recursive: true });
|
|
100
|
-
fs.writeFileSync(savePath, finalRaw, 'utf-8');
|
|
101
|
-
|
|
102
|
-
sendJson(res, 200, {
|
|
103
|
-
saved: true,
|
|
104
|
-
path: savePath,
|
|
105
|
-
defaults: buildConfigDefaults(ctx, parsed)
|
|
106
|
-
});
|
|
107
|
-
})
|
|
108
|
-
}
|
|
109
|
-
];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = {
|
|
113
|
-
createSystemApiRoutes
|
|
114
|
-
};
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function createWebTerminalHelpers(options = {}) {
|
|
4
|
-
const WebSocket = options.WebSocket;
|
|
5
|
-
const spawn = options.spawn;
|
|
6
|
-
const forceKillMs = Number.isInteger(options.forceKillMs) ? options.forceKillMs : 2000;
|
|
7
|
-
const defaultCols = Number.isInteger(options.defaultCols) ? options.defaultCols : 120;
|
|
8
|
-
const defaultRows = Number.isInteger(options.defaultRows) ? options.defaultRows : 36;
|
|
9
|
-
const minCols = Number.isInteger(options.minCols) ? options.minCols : 40;
|
|
10
|
-
const minRows = Number.isInteger(options.minRows) ? options.minRows : 12;
|
|
11
|
-
|
|
12
|
-
function toPositiveInt(value, fallback) {
|
|
13
|
-
const parsed = Number.parseInt(value, 10);
|
|
14
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
15
|
-
return fallback;
|
|
16
|
-
}
|
|
17
|
-
return parsed;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function getUpgradeStatusText(statusCode) {
|
|
21
|
-
if (statusCode === 400) return 'Bad Request';
|
|
22
|
-
if (statusCode === 401) return 'Unauthorized';
|
|
23
|
-
if (statusCode === 404) return 'Not Found';
|
|
24
|
-
if (statusCode === 429) return 'Too Many Requests';
|
|
25
|
-
if (statusCode === 500) return 'Internal Server Error';
|
|
26
|
-
return 'Error';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function sendTerminalEvent(ws, type, payload = {}) {
|
|
30
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
ws.send(JSON.stringify({ type, ...payload }));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function spawnWebTerminalProcess(ctx, containerName, cols, rows) {
|
|
37
|
-
const terminalBootstrap = [
|
|
38
|
-
'MANYOYO_WEB_BASHRC="$(mktemp /tmp/manyoyo-web-bashrc.XXXXXX 2>/dev/null || mktemp)"',
|
|
39
|
-
'cat > "$MANYOYO_WEB_BASHRC" <<\'EOF_MANYOYO_RC\'',
|
|
40
|
-
'if [ -f /etc/bash.bashrc ]; then',
|
|
41
|
-
' . /etc/bash.bashrc',
|
|
42
|
-
'fi',
|
|
43
|
-
'if [ -f ~/.bashrc ]; then',
|
|
44
|
-
' . ~/.bashrc',
|
|
45
|
-
'fi',
|
|
46
|
-
'if [ -n "${MANYOYO_TERM_COLS:-}" ] && [ -n "${MANYOYO_TERM_ROWS:-}" ]; then',
|
|
47
|
-
' COLUMNS="$MANYOYO_TERM_COLS"',
|
|
48
|
-
' LINES="$MANYOYO_TERM_ROWS"',
|
|
49
|
-
' export COLUMNS LINES',
|
|
50
|
-
' stty cols "$MANYOYO_TERM_COLS" rows "$MANYOYO_TERM_ROWS" >/dev/null 2>&1 || true',
|
|
51
|
-
'fi',
|
|
52
|
-
'EOF_MANYOYO_RC',
|
|
53
|
-
'chmod 600 "$MANYOYO_WEB_BASHRC" >/dev/null 2>&1 || true',
|
|
54
|
-
'if command -v script >/dev/null 2>&1; then',
|
|
55
|
-
' exec script -qefc "/bin/bash --rcfile $MANYOYO_WEB_BASHRC -i" /dev/null;',
|
|
56
|
-
'fi;',
|
|
57
|
-
'if command -v python3 >/dev/null 2>&1; then',
|
|
58
|
-
' exec python3 -c \'import os, pty; pty.spawn(["/bin/bash","--rcfile",os.environ.get("MANYOYO_WEB_BASHRC","/dev/null"),"-i"])\';',
|
|
59
|
-
'fi;',
|
|
60
|
-
'if command -v python >/dev/null 2>&1; then',
|
|
61
|
-
' exec python -c \'import os, pty; pty.spawn(["/bin/bash","--rcfile",os.environ.get("MANYOYO_WEB_BASHRC","/dev/null"),"-i"])\';',
|
|
62
|
-
'fi;',
|
|
63
|
-
'echo "[manyoyo] 容器内未找到 script/python,终端将降级为非 TTY 模式" >&2;',
|
|
64
|
-
'exec /bin/bash --rcfile "$MANYOYO_WEB_BASHRC" -i'
|
|
65
|
-
].join('\n');
|
|
66
|
-
|
|
67
|
-
const termValue = process.env.TERM && process.env.TERM !== 'dumb' ? process.env.TERM : 'xterm-256color';
|
|
68
|
-
const colorTermValue = process.env.COLORTERM || 'truecolor';
|
|
69
|
-
const dockerExecArgs = [
|
|
70
|
-
'exec',
|
|
71
|
-
'-i',
|
|
72
|
-
'-e', `TERM=${termValue}`,
|
|
73
|
-
'-e', `COLORTERM=${colorTermValue}`,
|
|
74
|
-
'-e', `MANYOYO_TERM_COLS=${String(cols)}`,
|
|
75
|
-
'-e', `MANYOYO_TERM_ROWS=${String(rows)}`,
|
|
76
|
-
containerName,
|
|
77
|
-
'/bin/bash',
|
|
78
|
-
'-lc',
|
|
79
|
-
terminalBootstrap
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
return spawn(ctx.dockerCmd, dockerExecArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
normalizeTerminalSize(cols, rows) {
|
|
87
|
-
return {
|
|
88
|
-
cols: Math.max(minCols, toPositiveInt(cols, defaultCols)),
|
|
89
|
-
rows: Math.max(minRows, toPositiveInt(rows, defaultRows))
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
sendWebSocketUpgradeError(socket, statusCode, message) {
|
|
93
|
-
const body = String(message || getUpgradeStatusText(statusCode));
|
|
94
|
-
const reason = getUpgradeStatusText(statusCode);
|
|
95
|
-
if (!socket.destroyed) {
|
|
96
|
-
socket.write(
|
|
97
|
-
`HTTP/1.1 ${statusCode} ${reason}\r\n` +
|
|
98
|
-
'Content-Type: text/plain; charset=utf-8\r\n' +
|
|
99
|
-
'Connection: close\r\n' +
|
|
100
|
-
`Content-Length: ${Buffer.byteLength(body, 'utf-8')}\r\n` +
|
|
101
|
-
'\r\n' +
|
|
102
|
-
body
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
socket.destroy();
|
|
106
|
-
},
|
|
107
|
-
bindTerminalWebSocket(ctx, state, ws, containerName, cols, rows) {
|
|
108
|
-
const sessionId = `${containerName}-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
|
|
109
|
-
const ptyProcess = spawnWebTerminalProcess(ctx, containerName, cols, rows);
|
|
110
|
-
const session = {
|
|
111
|
-
id: sessionId,
|
|
112
|
-
containerName,
|
|
113
|
-
ptyProcess,
|
|
114
|
-
closing: false
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
state.terminalSessions.set(sessionId, session);
|
|
118
|
-
sendTerminalEvent(ws, 'status', {
|
|
119
|
-
phase: 'ready',
|
|
120
|
-
sessionId,
|
|
121
|
-
containerName,
|
|
122
|
-
cols,
|
|
123
|
-
rows
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const cleanup = () => {
|
|
127
|
-
if (session.closing) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
session.closing = true;
|
|
131
|
-
state.terminalSessions.delete(sessionId);
|
|
132
|
-
if (ptyProcess && !ptyProcess.killed) {
|
|
133
|
-
ptyProcess.kill('SIGTERM');
|
|
134
|
-
setTimeout(() => {
|
|
135
|
-
if (!ptyProcess.killed) {
|
|
136
|
-
ptyProcess.kill('SIGKILL');
|
|
137
|
-
}
|
|
138
|
-
}, forceKillMs);
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
ptyProcess.stdout.on('data', chunk => {
|
|
143
|
-
sendTerminalEvent(ws, 'output', { data: chunk.toString('utf-8') });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
ptyProcess.stderr.on('data', chunk => {
|
|
147
|
-
sendTerminalEvent(ws, 'output', { data: chunk.toString('utf-8') });
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
ptyProcess.on('error', err => {
|
|
151
|
-
sendTerminalEvent(ws, 'error', {
|
|
152
|
-
error: err && err.message ? err.message : '终端进程启动失败'
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
ptyProcess.on('close', (code, signal) => {
|
|
157
|
-
sendTerminalEvent(ws, 'status', {
|
|
158
|
-
phase: 'closed',
|
|
159
|
-
code: typeof code === 'number' ? code : null,
|
|
160
|
-
signal: signal || null
|
|
161
|
-
});
|
|
162
|
-
cleanup();
|
|
163
|
-
if (ws.readyState === WebSocket.OPEN) {
|
|
164
|
-
ws.close();
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
ws.on('message', raw => {
|
|
169
|
-
let payload = null;
|
|
170
|
-
try {
|
|
171
|
-
payload = JSON.parse(raw.toString('utf-8'));
|
|
172
|
-
} catch {
|
|
173
|
-
payload = {
|
|
174
|
-
type: 'input',
|
|
175
|
-
data: raw.toString('utf-8')
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
if (!payload || typeof payload !== 'object') {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (payload.type === 'input' && typeof payload.data === 'string' && payload.data.length) {
|
|
183
|
-
ptyProcess.stdin.write(payload.data);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (payload.type === 'resize') {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (payload.type === 'close') {
|
|
192
|
-
ws.close();
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
ws.on('close', cleanup);
|
|
197
|
-
ws.on('error', cleanup);
|
|
198
|
-
},
|
|
199
|
-
cleanupWebRuntimeState() {}
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
module.exports = {
|
|
204
|
-
createWebTerminalHelpers
|
|
205
|
-
};
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function createWebUpgradeHandler(options = {}) {
|
|
4
|
-
const formatUrlHost = options.formatUrlHost || (host => host);
|
|
5
|
-
const sendWebSocketUpgradeError = options.sendWebSocketUpgradeError || (() => {});
|
|
6
|
-
const getWebAuthSession = options.getWebAuthSession || (() => null);
|
|
7
|
-
const parseWebSessionKey = options.parseWebSessionKey || (() => ({ containerName: '', agentId: '' }));
|
|
8
|
-
const decodeSessionName = options.decodeSessionName || (value => value);
|
|
9
|
-
const safeContainerNamePattern = options.safeContainerNamePattern || /^[A-Za-z0-9_.-]+$/;
|
|
10
|
-
const normalizeTerminalSize = options.normalizeTerminalSize || ((cols, rows) => ({ cols, rows }));
|
|
11
|
-
const ensureWebContainer = options.ensureWebContainer || (async () => {});
|
|
12
|
-
const maxTerminalSessions = Number.isInteger(options.maxTerminalSessions) ? options.maxTerminalSessions : 20;
|
|
13
|
-
|
|
14
|
-
return function handleWebUpgradeRequest(req, socket, head, wsServer, ctx, state, listenPort) {
|
|
15
|
-
const fallbackHost = `${formatUrlHost(ctx.serverHost)}:${ctx.serverPort}`;
|
|
16
|
-
let url;
|
|
17
|
-
try {
|
|
18
|
-
url = new URL(req.url || '/', `http://${req.headers.host || fallbackHost}`);
|
|
19
|
-
} catch {
|
|
20
|
-
sendWebSocketUpgradeError(socket, 400, 'Invalid URL');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const terminalMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/terminal\/ws$/);
|
|
25
|
-
if (!terminalMatch) {
|
|
26
|
-
socket.destroy();
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const requestOrigin = req.headers.origin;
|
|
31
|
-
if (requestOrigin) {
|
|
32
|
-
const allowedOrigins = new Set();
|
|
33
|
-
const hostHeader = req.headers.host || '';
|
|
34
|
-
if (hostHeader) {
|
|
35
|
-
allowedOrigins.add(`http://${hostHeader}`);
|
|
36
|
-
allowedOrigins.add(`https://${hostHeader}`);
|
|
37
|
-
}
|
|
38
|
-
if (ctx.serverHost !== '0.0.0.0') {
|
|
39
|
-
allowedOrigins.add(`http://${formatUrlHost(ctx.serverHost)}:${listenPort}`);
|
|
40
|
-
if (ctx.serverHost === '127.0.0.1') {
|
|
41
|
-
allowedOrigins.add(`http://localhost:${listenPort}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (allowedOrigins.size > 0 && !allowedOrigins.has(requestOrigin)) {
|
|
45
|
-
sendWebSocketUpgradeError(socket, 403, 'Forbidden');
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const authSession = getWebAuthSession(state, req);
|
|
51
|
-
if (!authSession) {
|
|
52
|
-
sendWebSocketUpgradeError(socket, 401, 'UNAUTHORIZED');
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const sessionRef = parseWebSessionKey(decodeSessionName(terminalMatch[1]));
|
|
57
|
-
if (!ctx.isValidContainerName(sessionRef.containerName)) {
|
|
58
|
-
sendWebSocketUpgradeError(socket, 400, `containerName 非法: ${sessionRef.containerName}`);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (!safeContainerNamePattern.test(sessionRef.agentId)) {
|
|
62
|
-
sendWebSocketUpgradeError(socket, 400, `agentId 非法: ${sessionRef.agentId}`);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (state.terminalSessions.size >= maxTerminalSessions) {
|
|
67
|
-
sendWebSocketUpgradeError(socket, 429, 'TERMINAL_LIMIT_REACHED');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const { cols, rows } = normalizeTerminalSize(
|
|
72
|
-
url.searchParams.get('cols'),
|
|
73
|
-
url.searchParams.get('rows')
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
ensureWebContainer(ctx, state, sessionRef.containerName)
|
|
77
|
-
.then(() => {
|
|
78
|
-
wsServer.handleUpgrade(req, socket, head, ws => {
|
|
79
|
-
wsServer.emit('connection', ws, req, {
|
|
80
|
-
containerName: sessionRef.containerName,
|
|
81
|
-
cols,
|
|
82
|
-
rows
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
})
|
|
86
|
-
.catch(error => {
|
|
87
|
-
sendWebSocketUpgradeError(socket, 500, error && error.message ? error.message : '终端创建失败');
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
module.exports = {
|
|
93
|
-
createWebUpgradeHandler
|
|
94
|
-
};
|