pinokiod 7.2.7 → 7.2.9
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/kernel/api/index.js +28 -0
- package/kernel/api/shell_run_template.js +273 -0
- package/kernel/index.js +2 -0
- package/kernel/shell.js +21 -2
- package/kernel/watch/context.js +42 -0
- package/kernel/watch/drivers/fs.js +71 -0
- package/kernel/watch/drivers/poll.js +33 -0
- package/kernel/watch/index.js +158 -0
- package/package.json +1 -1
- package/server/features/drafts/index.js +41 -0
- package/server/features/drafts/parser.js +169 -0
- package/server/features/drafts/public/drafts.js +1546 -0
- package/server/features/drafts/registry_import.js +427 -0
- package/server/features/drafts/routes.js +68 -0
- package/server/features/drafts/service.js +261 -0
- package/server/features/drafts/watcher.js +76 -0
- package/server/features/index.js +13 -0
- package/server/index.js +56 -7
- package/server/lib/workspace_catalog.js +151 -0
- package/server/lib/workspace_runtime.js +390 -0
- package/server/public/common.js +8 -0
- package/server/routes/workspaces.js +44 -0
- package/server/socket.js +22 -11
- package/server/views/app.ejs +159 -1
- package/server/views/partials/main_sidebar.ejs +1 -0
- package/server/views/partials/workspace_row.ejs +61 -0
- package/server/views/terminal.ejs +8 -0
- package/server/views/terminals.ejs +1 -0
- package/server/views/workspaces.ejs +812 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
function createWorkspaceRuntimeService({ kernel }) {
|
|
4
|
+
if (!kernel) {
|
|
5
|
+
throw new Error("kernel is required");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const normalizePathKey = (value) => {
|
|
9
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
10
|
+
return "";
|
|
11
|
+
}
|
|
12
|
+
const resolved = path.resolve(value.trim());
|
|
13
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const decodeMaybe = (value) => {
|
|
17
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
18
|
+
if (!raw) {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return decodeURIComponent(raw).trim();
|
|
23
|
+
} catch (_) {
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const resolveCandidatePath = (value) => {
|
|
29
|
+
const decoded = decodeMaybe(value);
|
|
30
|
+
if (!decoded || decoded.includes("\0")) {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
if (decoded.startsWith("~/")) {
|
|
34
|
+
return path.resolve(kernel.homedir, decoded.slice(2));
|
|
35
|
+
}
|
|
36
|
+
if (path.isAbsolute(decoded)) {
|
|
37
|
+
return path.resolve(decoded);
|
|
38
|
+
}
|
|
39
|
+
return "";
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const knownRoots = () => {
|
|
43
|
+
const roots = [];
|
|
44
|
+
const addRoot = (type, label) => {
|
|
45
|
+
if (!kernel || typeof kernel.path !== "function") {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const rootPath = kernel.path(type);
|
|
49
|
+
if (typeof rootPath === "string" && rootPath.trim()) {
|
|
50
|
+
roots.push({
|
|
51
|
+
type,
|
|
52
|
+
label,
|
|
53
|
+
path: path.resolve(rootPath)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
addRoot("workspaces", "Workspace");
|
|
58
|
+
addRoot("api", "App");
|
|
59
|
+
addRoot("plugin", "Plugin");
|
|
60
|
+
return roots;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const isPathWithin = (candidate, root) => {
|
|
64
|
+
if (!candidate || !root) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const relative = path.relative(root, candidate);
|
|
68
|
+
return Boolean(relative && !relative.startsWith("..") && !path.isAbsolute(relative));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const resolveWorkspaceForPath = (candidatePath) => {
|
|
72
|
+
const candidate = resolveCandidatePath(candidatePath);
|
|
73
|
+
if (!candidate) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
for (const root of knownRoots()) {
|
|
77
|
+
if (!isPathWithin(candidate, root.path)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const relative = path.relative(root.path, candidate);
|
|
81
|
+
const segments = relative.split(path.sep).filter(Boolean);
|
|
82
|
+
const name = segments[0] || "";
|
|
83
|
+
if (!name) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const cwd = path.resolve(root.path, name);
|
|
87
|
+
return {
|
|
88
|
+
key: normalizePathKey(cwd),
|
|
89
|
+
cwd,
|
|
90
|
+
name,
|
|
91
|
+
root: root.type,
|
|
92
|
+
rootLabel: root.label
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const parseParamsFromText = (value) => {
|
|
99
|
+
const raw = typeof value === "string" ? value : "";
|
|
100
|
+
const index = raw.indexOf("?");
|
|
101
|
+
if (index < 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return new URLSearchParams(raw.slice(index + 1).replace(/&/g, "&"));
|
|
106
|
+
} catch (_) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const firstWorkspaceFromCandidates = (candidates) => {
|
|
112
|
+
for (const candidate of candidates) {
|
|
113
|
+
const workspace = resolveWorkspaceForPath(candidate);
|
|
114
|
+
if (workspace) {
|
|
115
|
+
return workspace;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const getShellCandidates = (shell) => {
|
|
122
|
+
const candidates = [];
|
|
123
|
+
if (!shell || typeof shell !== "object") {
|
|
124
|
+
return candidates;
|
|
125
|
+
}
|
|
126
|
+
const push = (value) => {
|
|
127
|
+
if (typeof value === "string" && value.trim()) {
|
|
128
|
+
candidates.push(value);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
push(shell.path);
|
|
132
|
+
push(shell.group);
|
|
133
|
+
if (shell.params && typeof shell.params === "object") {
|
|
134
|
+
push(shell.params.cwd);
|
|
135
|
+
push(shell.params.path);
|
|
136
|
+
if (shell.params.$parent && typeof shell.params.$parent === "object") {
|
|
137
|
+
push(shell.params.$parent.cwd);
|
|
138
|
+
push(shell.params.$parent.path);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const text of [shell.id, shell.group]) {
|
|
142
|
+
const params = parseParamsFromText(text);
|
|
143
|
+
if (!params) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
push(params.get("cwd"));
|
|
147
|
+
push(params.get("path"));
|
|
148
|
+
}
|
|
149
|
+
return candidates;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const getScriptCandidates = (id) => {
|
|
153
|
+
const candidates = [];
|
|
154
|
+
const raw = typeof id === "string" ? id : "";
|
|
155
|
+
if (!raw) {
|
|
156
|
+
return candidates;
|
|
157
|
+
}
|
|
158
|
+
const params = parseParamsFromText(raw);
|
|
159
|
+
if (params) {
|
|
160
|
+
candidates.push(params.get("cwd"));
|
|
161
|
+
candidates.push(params.get("path"));
|
|
162
|
+
}
|
|
163
|
+
const pathPart = raw.split("?")[0];
|
|
164
|
+
candidates.push(pathPart);
|
|
165
|
+
return candidates;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const parseTerminalIdFromText = (value) => {
|
|
169
|
+
const params = parseParamsFromText(value);
|
|
170
|
+
if (!params) {
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
const terminalId = params.get("terminal_id");
|
|
174
|
+
return typeof terminalId === "string" ? terminalId.trim() : "";
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const buildShellUrl = (shell) => {
|
|
178
|
+
const raw = shell && typeof shell.id === "string" ? shell.id.trim() : "";
|
|
179
|
+
if (!raw) {
|
|
180
|
+
return "";
|
|
181
|
+
}
|
|
182
|
+
const index = raw.indexOf("?");
|
|
183
|
+
const base = index >= 0 ? raw.slice(0, index) : raw;
|
|
184
|
+
const query = index >= 0 ? raw.slice(index + 1) : "";
|
|
185
|
+
const params = new URLSearchParams(query);
|
|
186
|
+
if (!params.has("path") && shell && typeof shell.path === "string" && shell.path.trim()) {
|
|
187
|
+
params.set("path", shell.path.trim());
|
|
188
|
+
}
|
|
189
|
+
if (!params.has("terminal_id") && shell && typeof shell.terminal_id === "string" && shell.terminal_id.trim()) {
|
|
190
|
+
params.set("terminal_id", shell.terminal_id.trim());
|
|
191
|
+
}
|
|
192
|
+
if (!params.has("input")) {
|
|
193
|
+
params.set("input", "1");
|
|
194
|
+
}
|
|
195
|
+
const queryString = params.toString();
|
|
196
|
+
let route = "";
|
|
197
|
+
if (base.startsWith("/shell/")) {
|
|
198
|
+
const shellId = base.slice("/shell/".length);
|
|
199
|
+
route = shellId.includes("/")
|
|
200
|
+
? `/shell/${encodeURIComponent(shellId)}`
|
|
201
|
+
: base;
|
|
202
|
+
} else if (base.startsWith("shell/")) {
|
|
203
|
+
route = `/shell/${encodeURIComponent(base.slice("shell/".length))}`;
|
|
204
|
+
} else {
|
|
205
|
+
route = `/shell/${encodeURIComponent(base)}`;
|
|
206
|
+
}
|
|
207
|
+
return queryString ? `${route}?${queryString}` : route;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const buildRunUrl = (id) => {
|
|
211
|
+
const raw = typeof id === "string" ? id.trim() : "";
|
|
212
|
+
if (!raw) {
|
|
213
|
+
return "";
|
|
214
|
+
}
|
|
215
|
+
const index = raw.indexOf("?");
|
|
216
|
+
const scriptPath = resolveCandidatePath(index >= 0 ? raw.slice(0, index) : raw);
|
|
217
|
+
const query = index >= 0 ? raw.slice(index + 1) : "";
|
|
218
|
+
if (!scriptPath) {
|
|
219
|
+
return "";
|
|
220
|
+
}
|
|
221
|
+
const roots = [
|
|
222
|
+
{ name: "api", path: kernel.path("api") },
|
|
223
|
+
{ name: "plugin", path: kernel.path("plugin") },
|
|
224
|
+
{ name: "scripts", path: kernel.path("scripts") }
|
|
225
|
+
].filter((root) => typeof root.path === "string" && root.path.trim());
|
|
226
|
+
for (const root of roots) {
|
|
227
|
+
const rootPath = path.resolve(root.path);
|
|
228
|
+
const relative = path.relative(rootPath, scriptPath);
|
|
229
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const params = new URLSearchParams(query);
|
|
233
|
+
if (!params.has("chrome")) {
|
|
234
|
+
params.set("chrome", "full");
|
|
235
|
+
}
|
|
236
|
+
const route = relative.split(path.sep).map(encodeURIComponent).join("/");
|
|
237
|
+
const queryString = params.toString();
|
|
238
|
+
return `/run/${root.name}/${route}${queryString ? `?${queryString}` : ""}`;
|
|
239
|
+
}
|
|
240
|
+
return "";
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const shellTitle = (shell) => {
|
|
244
|
+
if (shell && shell.params && typeof shell.params.$title === "string" && shell.params.$title.trim()) {
|
|
245
|
+
return shell.params.$title.trim();
|
|
246
|
+
}
|
|
247
|
+
if (shell && typeof shell.cmd === "string" && shell.cmd.trim()) {
|
|
248
|
+
return shell.cmd.trim().slice(0, 120);
|
|
249
|
+
}
|
|
250
|
+
return "Terminal";
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const scriptTitle = (id) => {
|
|
254
|
+
const raw = typeof id === "string" ? id.trim() : "";
|
|
255
|
+
if (!raw) {
|
|
256
|
+
return "Script";
|
|
257
|
+
}
|
|
258
|
+
const pathPart = raw.split("?")[0];
|
|
259
|
+
if (pathPart && path.isAbsolute(pathPart)) {
|
|
260
|
+
return path.basename(pathPart) || "Script";
|
|
261
|
+
}
|
|
262
|
+
return raw.slice(0, 120);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const createGroup = (workspace) => ({
|
|
266
|
+
cwd: workspace.cwd,
|
|
267
|
+
name: workspace.name,
|
|
268
|
+
root: workspace.root,
|
|
269
|
+
rootLabel: workspace.rootLabel,
|
|
270
|
+
running: true,
|
|
271
|
+
shells: [],
|
|
272
|
+
scripts: []
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const list = () => {
|
|
276
|
+
const groups = new Map();
|
|
277
|
+
const unscoped = {
|
|
278
|
+
shells: [],
|
|
279
|
+
scripts: []
|
|
280
|
+
};
|
|
281
|
+
const getGroup = (workspace) => {
|
|
282
|
+
if (!workspace || !workspace.key) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
if (!groups.has(workspace.key)) {
|
|
286
|
+
groups.set(workspace.key, createGroup(workspace));
|
|
287
|
+
}
|
|
288
|
+
return groups.get(workspace.key);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const shells = kernel.shell && Array.isArray(kernel.shell.shells)
|
|
292
|
+
? kernel.shell.shells
|
|
293
|
+
: [];
|
|
294
|
+
for (const shell of shells) {
|
|
295
|
+
if (!shell || shell.done === true || !shell.ptyProcess) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const item = {
|
|
299
|
+
id: typeof shell.id === "string" ? shell.id : "",
|
|
300
|
+
group: typeof shell.group === "string" ? shell.group : "",
|
|
301
|
+
title: shellTitle(shell),
|
|
302
|
+
cwd: typeof shell.path === "string" ? shell.path : "",
|
|
303
|
+
state: shell.state || null,
|
|
304
|
+
start_time: Number.isFinite(shell.start_time) ? shell.start_time : null,
|
|
305
|
+
terminal_id: shell.terminal_id || parseTerminalIdFromText(shell.id) || parseTerminalIdFromText(shell.group) || null,
|
|
306
|
+
url: buildRunUrl(shell.group) || buildShellUrl(shell)
|
|
307
|
+
};
|
|
308
|
+
const workspace = firstWorkspaceFromCandidates(getShellCandidates(shell));
|
|
309
|
+
const group = getGroup(workspace);
|
|
310
|
+
if (group) {
|
|
311
|
+
group.shells.push(item);
|
|
312
|
+
} else {
|
|
313
|
+
unscoped.shells.push(item);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const running = kernel.api && kernel.api.running && typeof kernel.api.running === "object"
|
|
318
|
+
? kernel.api.running
|
|
319
|
+
: {};
|
|
320
|
+
for (const id of Object.keys(running)) {
|
|
321
|
+
if (typeof id !== "string" || !id || id.startsWith("shell/")) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const item = {
|
|
325
|
+
id,
|
|
326
|
+
title: scriptTitle(id),
|
|
327
|
+
path: id.split("?")[0],
|
|
328
|
+
cwd: "",
|
|
329
|
+
url: buildRunUrl(id)
|
|
330
|
+
};
|
|
331
|
+
const workspace = firstWorkspaceFromCandidates(getScriptCandidates(id));
|
|
332
|
+
if (workspace) {
|
|
333
|
+
item.cwd = workspace.cwd;
|
|
334
|
+
}
|
|
335
|
+
const group = getGroup(workspace);
|
|
336
|
+
if (group) {
|
|
337
|
+
group.scripts.push(item);
|
|
338
|
+
} else {
|
|
339
|
+
unscoped.scripts.push(item);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const workspaces = Array.from(groups.values())
|
|
344
|
+
.map((workspace) => ({
|
|
345
|
+
...workspace,
|
|
346
|
+
counts: {
|
|
347
|
+
shells: workspace.shells.length,
|
|
348
|
+
scripts: workspace.scripts.length
|
|
349
|
+
}
|
|
350
|
+
}))
|
|
351
|
+
.sort((a, b) => {
|
|
352
|
+
const totalA = a.counts.shells + a.counts.scripts;
|
|
353
|
+
const totalB = b.counts.shells + b.counts.scripts;
|
|
354
|
+
if (totalA !== totalB) {
|
|
355
|
+
return totalB - totalA;
|
|
356
|
+
}
|
|
357
|
+
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
workspaces,
|
|
362
|
+
unscoped,
|
|
363
|
+
counts: {
|
|
364
|
+
workspaces: workspaces.length,
|
|
365
|
+
shells: workspaces.reduce((total, workspace) => total + workspace.counts.shells, 0) + unscoped.shells.length,
|
|
366
|
+
scripts: workspaces.reduce((total, workspace) => total + workspace.counts.scripts, 0) + unscoped.scripts.length
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const summary = () => {
|
|
372
|
+
const runtime = list();
|
|
373
|
+
return {
|
|
374
|
+
runningWorkspaces: runtime.counts.workspaces,
|
|
375
|
+
runningShells: runtime.counts.shells,
|
|
376
|
+
runningScripts: runtime.counts.scripts,
|
|
377
|
+
unscopedShells: runtime.unscoped.shells.length,
|
|
378
|
+
unscopedScripts: runtime.unscoped.scripts.length
|
|
379
|
+
};
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
list,
|
|
384
|
+
summary
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
module.exports = {
|
|
389
|
+
createWorkspaceRuntimeService
|
|
390
|
+
};
|
package/server/public/common.js
CHANGED
|
@@ -2328,6 +2328,14 @@ if (typeof hotkeys === 'function') {
|
|
|
2328
2328
|
playNextSound();
|
|
2329
2329
|
};
|
|
2330
2330
|
|
|
2331
|
+
window.PinokioPlayNotificationSound = (sound) => {
|
|
2332
|
+
if (sound === false || isFalseyString(sound)) {
|
|
2333
|
+
return false;
|
|
2334
|
+
}
|
|
2335
|
+
enqueueSound(typeof sound === 'string' && sound ? sound : '/chime.mp3');
|
|
2336
|
+
return true;
|
|
2337
|
+
};
|
|
2338
|
+
|
|
2331
2339
|
const handlePacket = (packet) => {
|
|
2332
2340
|
if (!packet || packet.id !== CHANNEL_ID || packet.type !== 'notification') {
|
|
2333
2341
|
return;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const express = require("express")
|
|
2
|
+
|
|
3
|
+
function registerWorkspacesRoutes(app, options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
workspaceCatalog,
|
|
6
|
+
composePeerAccessPayload,
|
|
7
|
+
getTheme,
|
|
8
|
+
getPeers,
|
|
9
|
+
getCurrentHost,
|
|
10
|
+
getPortal,
|
|
11
|
+
} = options
|
|
12
|
+
|
|
13
|
+
if (!workspaceCatalog) {
|
|
14
|
+
throw new Error("workspaceCatalog is required")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const router = express.Router()
|
|
18
|
+
|
|
19
|
+
router.get("/workspaces", async (req, res, next) => {
|
|
20
|
+
try {
|
|
21
|
+
const catalog = await workspaceCatalog.list({ sort: req.query.sort })
|
|
22
|
+
res.render("workspaces", {
|
|
23
|
+
title: "Workspaces",
|
|
24
|
+
sidebarSelected: "workspaces",
|
|
25
|
+
workspaceCatalog: catalog,
|
|
26
|
+
theme: getTheme ? getTheme(req) : null,
|
|
27
|
+
peers: getPeers ? getPeers() : [],
|
|
28
|
+
currentHost: getCurrentHost ? getCurrentHost(req) : null,
|
|
29
|
+
portal: getPortal ? getPortal(req) : null,
|
|
30
|
+
peerAccess: composePeerAccessPayload ? composePeerAccessPayload(req) : null,
|
|
31
|
+
})
|
|
32
|
+
} catch (err) {
|
|
33
|
+
next(err)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
router.get("/activity", (req, res) => {
|
|
38
|
+
res.redirect("/workspaces")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
app.use(router)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = registerWorkspacesRoutes
|
package/server/socket.js
CHANGED
|
@@ -284,7 +284,7 @@ class Socket {
|
|
|
284
284
|
} else {
|
|
285
285
|
let buf = this.buffer[id]
|
|
286
286
|
let sh = this.active_shell[id]
|
|
287
|
-
this.subscribe(ws, id, buf, sh)
|
|
287
|
+
this.subscribe(ws, id, buf, sh, req)
|
|
288
288
|
if (req.mode !== "listen") {
|
|
289
289
|
// Run only if currently not running
|
|
290
290
|
if (!this.parent.kernel.api.running[id]) {
|
|
@@ -308,7 +308,7 @@ class Socket {
|
|
|
308
308
|
if (req.id) {
|
|
309
309
|
let buf = this.buffer[req.id]
|
|
310
310
|
let sh = this.active_shell[req.id]
|
|
311
|
-
this.subscribe(ws, req.id, buf, sh)
|
|
311
|
+
this.subscribe(ws, req.id, buf, sh, req)
|
|
312
312
|
if (req.mode === "listen") {
|
|
313
313
|
return
|
|
314
314
|
}
|
|
@@ -345,13 +345,7 @@ class Socket {
|
|
|
345
345
|
// Mark local client sockets by IP matching any local address
|
|
346
346
|
try {
|
|
347
347
|
const ip = ws._ip || ''
|
|
348
|
-
|
|
349
|
-
if (!addr || typeof addr !== 'string') return false
|
|
350
|
-
if (this.localAddresses.has(addr)) return true
|
|
351
|
-
const v = addr.trim().toLowerCase()
|
|
352
|
-
return v.startsWith('::ffff:127.') || v.startsWith('127.')
|
|
353
|
-
}
|
|
354
|
-
ws._isLocalClient = isLocal(ip)
|
|
348
|
+
ws._isLocalClient = this.isLocalAddress(ip)
|
|
355
349
|
if (ws._isLocalClient && ws._deviceId) {
|
|
356
350
|
this.localDeviceIds.add(ws._deviceId)
|
|
357
351
|
}
|
|
@@ -431,18 +425,22 @@ class Socket {
|
|
|
431
425
|
this.old_buffer = structuredClone(this.buffer)
|
|
432
426
|
}, 5000)
|
|
433
427
|
}
|
|
434
|
-
subscribe(ws, id, buf, sh) {
|
|
428
|
+
subscribe(ws, id, buf, sh, req = {}) {
|
|
435
429
|
let resolvedShellId = sh || null
|
|
436
430
|
let resolvedState = buf
|
|
437
431
|
let hasState = typeof resolvedState === "string" ? resolvedState.length > 0 : Boolean(resolvedState)
|
|
432
|
+
let resolvedShell = null
|
|
438
433
|
if ((!resolvedShellId || !hasState) && this.parent && this.parent.kernel && this.parent.kernel.shell && Array.isArray(this.parent.kernel.shell.shells)) {
|
|
439
|
-
const
|
|
434
|
+
const directShell = this.parent.kernel.shell.get(id)
|
|
435
|
+
const liveDirectShell = directShell && directShell.done !== true ? directShell : null
|
|
436
|
+
const groupedShell = liveDirectShell || this.parent.kernel.shell.shells.find((candidate) => {
|
|
440
437
|
return candidate
|
|
441
438
|
&& candidate.done !== true
|
|
442
439
|
&& typeof candidate.group === "string"
|
|
443
440
|
&& candidate.group === id
|
|
444
441
|
})
|
|
445
442
|
if (groupedShell) {
|
|
443
|
+
resolvedShell = groupedShell
|
|
446
444
|
if (!resolvedShellId) {
|
|
447
445
|
resolvedShellId = groupedShell.id
|
|
448
446
|
}
|
|
@@ -455,6 +453,12 @@ class Socket {
|
|
|
455
453
|
}
|
|
456
454
|
}
|
|
457
455
|
}
|
|
456
|
+
if (!resolvedShell && resolvedShellId && this.parent && this.parent.kernel && this.parent.kernel.shell) {
|
|
457
|
+
resolvedShell = this.parent.kernel.shell.get(resolvedShellId)
|
|
458
|
+
}
|
|
459
|
+
if (resolvedShell && req && req.input) {
|
|
460
|
+
resolvedShell.input = true
|
|
461
|
+
}
|
|
458
462
|
if (this.parent.kernel.api.running[id] || resolvedShellId || hasState) {
|
|
459
463
|
ws.send(JSON.stringify({
|
|
460
464
|
type: "connect",
|
|
@@ -652,6 +656,13 @@ class Socket {
|
|
|
652
656
|
return this.localDeviceIds.has(deviceId)
|
|
653
657
|
}
|
|
654
658
|
|
|
659
|
+
isLocalAddress(addr) {
|
|
660
|
+
if (!addr || typeof addr !== 'string') return false
|
|
661
|
+
if (this.localAddresses.has(addr)) return true
|
|
662
|
+
const v = addr.trim().toLowerCase()
|
|
663
|
+
return v === 'localhost' || v === '::1' || v.startsWith('::ffff:127.') || v.startsWith('127.')
|
|
664
|
+
}
|
|
665
|
+
|
|
655
666
|
ensureNotificationBridge() {
|
|
656
667
|
if (this.notificationBridgeDispose) {
|
|
657
668
|
return
|