@zhanguym/chatroom 1.1.0 → 1.2.0-beta.0
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/dist/core/runtime/child-environment.js +3 -2
- package/dist/infrastructure/http/http-server.js +7 -1
- package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
- package/dist/native/windows/chatroom-computer-helper.exe +0 -0
- package/dist/plugins/cloud/tunnel-client.d.ts +2 -84
- package/dist/plugins/cloud/tunnel-client.js +2 -426
- package/dist/plugins/cloud/v2/stream.d.ts +48 -0
- package/dist/plugins/cloud/v2/stream.js +156 -0
- package/dist/plugins/cloud/v2/tunnel-client.d.ts +45 -0
- package/dist/plugins/cloud/v2/tunnel-client.js +426 -0
- package/dist/plugins/process/backend.d.ts +3 -0
- package/dist/plugins/process/infrastructure/pty-process-backend.d.ts +2 -0
- package/dist/plugins/process/infrastructure/pty-process-backend.js +5 -2
- package/dist/plugins/process/infrastructure/terminal-shell.d.ts +4 -0
- package/dist/plugins/process/infrastructure/terminal-shell.js +10 -0
- package/dist/plugins/process/process-supervisor.d.ts +6 -0
- package/dist/plugins/process/process-supervisor.js +26 -0
- package/dist/plugins/process/types.d.ts +2 -0
- package/dist/plugins/web/http/api-router.d.ts +2 -1
- package/dist/plugins/web/http/api-router.js +5 -1
- package/dist/plugins/web/http/logs-api-router.js +2 -2
- package/dist/plugins/web/http/process-api-router.js +48 -0
- package/dist/plugins/web/http/rtc-api-router.d.ts +4 -0
- package/dist/plugins/web/http/rtc-api-router.js +316 -0
- package/dist/plugins/web/http/runtime-api-router.js +2 -2
- package/dist/plugins/web/http/terminal-api-router.d.ts +4 -0
- package/dist/plugins/web/http/terminal-api-router.js +132 -0
- package/dist/web/assets/{AgentView-BGLIBoxF.css → AgentView-CMxIn3KB.css} +1 -1
- package/dist/web/assets/AgentView-DQr-5aLK.js +4 -0
- package/dist/web/assets/CloudView-B2VBut3O.js +1 -0
- package/dist/web/assets/CloudView-Bgp5G965.css +1 -0
- package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-DkN8x_6L.js +3 -0
- package/dist/web/assets/ComputerView-CIFF1zUb.js +1 -0
- package/dist/web/assets/{MarkdownContent-CkjR5fs7.js → MarkdownContent-DRZp88_G.js} +2 -2
- package/dist/web/assets/McpToolsView-CSEru3ck.js +1 -0
- package/dist/web/assets/OperationsView-B_Nv1Mgi.js +4 -0
- package/dist/web/assets/ProcessesView-5K8wNiPC.js +5 -0
- package/dist/web/assets/ProcessesView-BmNdR3C0.css +1 -0
- package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-CQEj-YRM.js +1 -0
- package/dist/web/assets/SystemLogsView-CBINILPo.js +1 -0
- package/dist/web/assets/SystemLogsView-COvwamo5.css +1 -0
- package/dist/web/assets/TerminalView-DNaKG3II.css +1 -0
- package/dist/web/assets/TerminalView-Dvyy2GKF.js +16 -0
- package/dist/web/assets/VSelect-CBX17QQj.js +1 -0
- package/dist/web/assets/VSpacer-9N_Ffoce.js +1 -0
- package/dist/web/assets/VSwitch-CvQYU48e.js +1 -0
- package/dist/web/assets/VTabs-CftrBFlF.js +1 -0
- package/dist/web/assets/VTextarea-Dcui00tn.js +1 -0
- package/dist/web/assets/WorkspaceDownloadPreview-DJKSNaZ_.css +1 -0
- package/dist/web/assets/WorkspaceDownloadPreview-DzM2p_xl.js +1 -0
- package/dist/web/assets/WorkspaceMediaPreview-ctIyG3Ru.js +4 -0
- package/dist/web/assets/{WorkspacePdfPreview-CO-ZtDHG.js → WorkspacePdfPreview-CeizxEAH.js} +2 -2
- package/dist/web/assets/WorkspacesView-DcmqHGvr.js +2 -0
- package/dist/web/assets/index-BRI_re-n.css +1 -0
- package/dist/web/assets/index-DTyxBHlJ.js +19 -0
- package/dist/web/assets/{useMasterDetailFocus-D1mQ8Qjk.js → useMasterDetailFocus-DR5N95_A.js} +1 -1
- package/dist/web/assets/useOperationLabels-Dou4m0ac.js +1 -0
- package/dist/web/index.html +2 -2
- package/dist/web/transport-sw.js +411 -0
- package/package.json +9 -4
- package/scripts/ensure-pty-helper.mjs +28 -0
- package/dist/web/assets/AgentView-Dd6zCbr-.js +0 -4
- package/dist/web/assets/CloudView-DVVvwuWW.css +0 -1
- package/dist/web/assets/CloudView-DquY1HDZ.js +0 -1
- package/dist/web/assets/CodeViewer.vue_vue_type_script_setup_true_lang-ekOImmBP.js +0 -3
- package/dist/web/assets/ComputerView-RD2gMxJZ.js +0 -1
- package/dist/web/assets/McpToolsView-Dg2-iaDP.js +0 -1
- package/dist/web/assets/OperationsView-C8yCX7WZ.js +0 -4
- package/dist/web/assets/ProcessesView-C834LC-f.js +0 -4
- package/dist/web/assets/ProcessesView-Ce6iVwjA.css +0 -1
- package/dist/web/assets/StateChip.vue_vue_type_script_setup_true_lang-By2gilBw.js +0 -1
- package/dist/web/assets/SystemLogsView-B6KcZv1J.js +0 -1
- package/dist/web/assets/SystemLogsView-CofP4Jvi.css +0 -1
- package/dist/web/assets/VSelect-BtAJX-XJ.js +0 -1
- package/dist/web/assets/VSpacer-M8bpkhTH.js +0 -1
- package/dist/web/assets/VSwitch-C0VTnwLJ.js +0 -1
- package/dist/web/assets/VTabs-g61mykvY.js +0 -1
- package/dist/web/assets/VTextarea-CRYQYRqg.js +0 -1
- package/dist/web/assets/WorkspaceDownloadPreview-BrESF-82.js +0 -1
- package/dist/web/assets/WorkspaceDownloadPreview-DnxhH5Q7.css +0 -1
- package/dist/web/assets/WorkspaceMediaPreview-HYajqgZ3.js +0 -4
- package/dist/web/assets/WorkspacesView-BJCnzBLj.js +0 -2
- package/dist/web/assets/_plugin-vue_export-helper-DlAUqK2U.js +0 -1
- package/dist/web/assets/index-CZ-4tJlF.css +0 -1
- package/dist/web/assets/index-u-aADQP-.js +0 -19
- package/dist/web/assets/useOperationLabels-BjPIVnJW.js +0 -1
- package/dist/web/assets/utils-rtPwWNel.js +0 -1
package/dist/web/assets/{useMasterDetailFocus-D1mQ8Qjk.js → useMasterDetailFocus-DR5N95_A.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{ab as o,ae as u}from"./index-DTyxBHlJ.js";function i(e){const t=o("layout"),a=o("detailPane");function r(){e.value&&u(()=>{t.value?.scrollIntoView({behavior:"smooth",block:"start"}),a.value?.focus({preventScroll:!0})})}function l(n){e.value&&u(()=>{t.value?.querySelector(n)?.focus({preventScroll:!0})})}return{focusDetail:r,focusMaster:l}}export{i as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{u,bR as c,bS as r,bT as i}from"./index-DTyxBHlJ.js";function l(){const s=u();function a(n,e){const t=c(n,e);return t?s.t(t):r(e)}function o(n){const e=i(n);return e?s.t(e):n}return{actionLabel:a,sourceLabel:o}}export{l as u};
|
package/dist/web/index.html
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
href="/assets/chatroom-B5p-5n3y.svg"
|
|
11
11
|
/>
|
|
12
12
|
<title>ChatRoom Runtime</title>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-DTyxBHlJ.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BRI_re-n.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
17
17
|
<div id="app"></div>
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One origin-wide request path. The controlling page owns the authenticated
|
|
3
|
+
* WebRTC transport; this service worker only bridges native browser fetches.
|
|
4
|
+
* No data is cached, persisted, or sent to any third-party origin.
|
|
5
|
+
*/
|
|
6
|
+
self.addEventListener("install", () => self.skipWaiting());
|
|
7
|
+
self.addEventListener("activate", (event) =>
|
|
8
|
+
event.waitUntil(self.clients.claim()),
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
const MAX_INLINE_UPLOAD = 8 * 1024 * 1024;
|
|
12
|
+
const ACCEPT_TIMEOUT = 1200;
|
|
13
|
+
const activeClients = new Set();
|
|
14
|
+
const downloads = new Map();
|
|
15
|
+
const DOWNLOAD_ROUTE = "/__chatroom_download/";
|
|
16
|
+
const DOWNLOAD_TIMEOUT = 15000;
|
|
17
|
+
|
|
18
|
+
function downloadHeader(name) {
|
|
19
|
+
const ascii = name.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
|
|
20
|
+
return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(name)}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function registerDownload(event) {
|
|
24
|
+
const port = event.ports[0];
|
|
25
|
+
const source = event.source;
|
|
26
|
+
const data = event.data;
|
|
27
|
+
if (!port || !source?.id) return;
|
|
28
|
+
const url =
|
|
29
|
+
typeof data.url === "string"
|
|
30
|
+
? new URL(data.url, self.location.origin)
|
|
31
|
+
: null;
|
|
32
|
+
const name = data.name;
|
|
33
|
+
if (
|
|
34
|
+
!activeClients.has(source.id) ||
|
|
35
|
+
typeof data.id !== "string" ||
|
|
36
|
+
!/^[0-9a-f-]{36}$/i.test(data.id) ||
|
|
37
|
+
!url ||
|
|
38
|
+
url.origin !== self.location.origin ||
|
|
39
|
+
!url.pathname.startsWith("/api/") ||
|
|
40
|
+
url.pathname.startsWith("/api/auth/") ||
|
|
41
|
+
url.pathname.startsWith("/api/rtc/") ||
|
|
42
|
+
typeof name !== "string" ||
|
|
43
|
+
!name ||
|
|
44
|
+
name.length > 255 ||
|
|
45
|
+
downloads.size >= 8
|
|
46
|
+
) {
|
|
47
|
+
port.postMessage({ type: "unavailable" });
|
|
48
|
+
port.close();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
downloads.delete(data.id);
|
|
53
|
+
port.postMessage({ type: "unavailable" });
|
|
54
|
+
port.close();
|
|
55
|
+
}, DOWNLOAD_TIMEOUT);
|
|
56
|
+
downloads.set(data.id, {
|
|
57
|
+
url: url.href,
|
|
58
|
+
name,
|
|
59
|
+
clientId: source.id,
|
|
60
|
+
port,
|
|
61
|
+
timer,
|
|
62
|
+
});
|
|
63
|
+
port.postMessage({ type: "ready" });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function streamDownload(id) {
|
|
67
|
+
const entry = downloads.get(id);
|
|
68
|
+
if (!entry) return new Response("Download session expired", { status: 410 });
|
|
69
|
+
downloads.delete(id);
|
|
70
|
+
clearTimeout(entry.timer);
|
|
71
|
+
const { port } = entry;
|
|
72
|
+
const client = await self.clients.get(entry.clientId);
|
|
73
|
+
if (!client) {
|
|
74
|
+
port.postMessage({ type: "unavailable" });
|
|
75
|
+
port.close();
|
|
76
|
+
return new Response(null, { status: 204 });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const request = new Request(entry.url, {
|
|
80
|
+
credentials: "same-origin",
|
|
81
|
+
cache: "no-store",
|
|
82
|
+
});
|
|
83
|
+
let response;
|
|
84
|
+
try {
|
|
85
|
+
response = await route({ request, clientId: client.id });
|
|
86
|
+
} catch {
|
|
87
|
+
port.postMessage({ type: "unavailable" });
|
|
88
|
+
port.close();
|
|
89
|
+
return new Response(null, { status: 204 });
|
|
90
|
+
}
|
|
91
|
+
if (!response.ok || !response.body) {
|
|
92
|
+
port.postMessage({ type: "failed", status: response.status });
|
|
93
|
+
port.close();
|
|
94
|
+
return new Response(null, { status: 204 });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const headers = new Headers(response.headers);
|
|
98
|
+
headers.set("Content-Disposition", downloadHeader(entry.name));
|
|
99
|
+
headers.set(
|
|
100
|
+
"Content-Type",
|
|
101
|
+
headers.get("content-type") || "application/octet-stream",
|
|
102
|
+
);
|
|
103
|
+
headers.set("Cache-Control", "private, no-store, no-transform");
|
|
104
|
+
// Let the browser's native download manager consume each chunk. No whole-file Blob.
|
|
105
|
+
const reader = response.body.getReader();
|
|
106
|
+
let received = 0;
|
|
107
|
+
let downstream = null;
|
|
108
|
+
let complete = false;
|
|
109
|
+
let stopped = false;
|
|
110
|
+
let lastReport = 0;
|
|
111
|
+
port.onmessage = (event) => {
|
|
112
|
+
if (event.data?.type === "cancel" && !stopped) {
|
|
113
|
+
stopped = true;
|
|
114
|
+
try {
|
|
115
|
+
downstream?.error(new Error("Download canceled"));
|
|
116
|
+
} catch {
|
|
117
|
+
// The browser may already have stopped reading the stream.
|
|
118
|
+
}
|
|
119
|
+
void reader.cancel("Download canceled");
|
|
120
|
+
port.postMessage({ type: "canceled" });
|
|
121
|
+
port.close();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
port.start();
|
|
125
|
+
const stream = new ReadableStream(
|
|
126
|
+
{
|
|
127
|
+
start(controller) {
|
|
128
|
+
downstream = controller;
|
|
129
|
+
},
|
|
130
|
+
async pull(controller) {
|
|
131
|
+
try {
|
|
132
|
+
const { done, value } = await reader.read();
|
|
133
|
+
if (done) {
|
|
134
|
+
complete = true;
|
|
135
|
+
const declared = headers.get("content-length");
|
|
136
|
+
if (
|
|
137
|
+
declared !== null &&
|
|
138
|
+
/^\d+$/.test(declared) &&
|
|
139
|
+
Number(declared) !== received
|
|
140
|
+
)
|
|
141
|
+
throw Error("Incomplete download");
|
|
142
|
+
controller.close();
|
|
143
|
+
if (!stopped) {
|
|
144
|
+
port.postMessage({ type: "finished", bytes: received });
|
|
145
|
+
port.close();
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (stopped) throw Error("Download canceled");
|
|
150
|
+
received += value.byteLength;
|
|
151
|
+
controller.enqueue(value);
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
if (now - lastReport >= 150) {
|
|
154
|
+
lastReport = now;
|
|
155
|
+
port.postMessage({ type: "progress", bytes: received });
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
if (!stopped) controller.error(new Error("Download interrupted"));
|
|
159
|
+
if (!stopped) {
|
|
160
|
+
port.postMessage({ type: "failed" });
|
|
161
|
+
port.close();
|
|
162
|
+
}
|
|
163
|
+
stopped = true;
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
cancel() {
|
|
167
|
+
if (!complete && !stopped) {
|
|
168
|
+
stopped = true;
|
|
169
|
+
void reader.cancel();
|
|
170
|
+
port.postMessage({ type: "canceled" });
|
|
171
|
+
port.close();
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{ highWaterMark: 0 },
|
|
176
|
+
);
|
|
177
|
+
port.postMessage({
|
|
178
|
+
type: "started",
|
|
179
|
+
mode: headers.get("x-chatroom-transport") === "p2p" ? "p2p" : "http",
|
|
180
|
+
total: headers.get("content-length"),
|
|
181
|
+
});
|
|
182
|
+
headers.delete("x-chatroom-transport");
|
|
183
|
+
return new Response(stream, { status: 200, headers });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
self.addEventListener("message", (event) => {
|
|
187
|
+
if (event.data?.type === "chatroom-download-register") {
|
|
188
|
+
registerDownload(event);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (event.data?.type !== "chatroom-v2-state" || !event.source?.id) return;
|
|
192
|
+
if (event.data.active) activeClients.add(event.source.id);
|
|
193
|
+
else activeClients.delete(event.source.id);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
self.addEventListener("fetch", (event) => {
|
|
197
|
+
const request = event.request;
|
|
198
|
+
const url = new URL(request.url);
|
|
199
|
+
if (
|
|
200
|
+
url.origin === self.location.origin &&
|
|
201
|
+
request.method === "GET" &&
|
|
202
|
+
url.pathname.startsWith(DOWNLOAD_ROUTE)
|
|
203
|
+
) {
|
|
204
|
+
const id = url.pathname.slice(DOWNLOAD_ROUTE.length);
|
|
205
|
+
event.respondWith(streamDownload(id));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (request.mode === "navigate" && !url.pathname.startsWith("/api/")) {
|
|
209
|
+
// A page navigation must not inherit the previous document's P2P session.
|
|
210
|
+
if (event.clientId) activeClients.delete(event.clientId);
|
|
211
|
+
if (event.resultingClientId) activeClients.delete(event.resultingClientId);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (
|
|
215
|
+
url.origin !== self.location.origin ||
|
|
216
|
+
!(
|
|
217
|
+
url.pathname.startsWith("/api/") || url.pathname.startsWith("/assets/")
|
|
218
|
+
) ||
|
|
219
|
+
url.pathname.startsWith("/api/auth/") ||
|
|
220
|
+
url.pathname === "/api/rtc/offer" ||
|
|
221
|
+
request.method === "HEAD" ||
|
|
222
|
+
!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(request.method)
|
|
223
|
+
)
|
|
224
|
+
return;
|
|
225
|
+
|
|
226
|
+
event.respondWith(route(event));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
async function route(event) {
|
|
230
|
+
const request = event.request;
|
|
231
|
+
const url = new URL(request.url);
|
|
232
|
+
if (!activeClients.has(event.clientId)) return fetch(request);
|
|
233
|
+
const client = event.clientId && (await self.clients.get(event.clientId));
|
|
234
|
+
if (!client) {
|
|
235
|
+
activeClients.delete(event.clientId);
|
|
236
|
+
return fetch(request);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const length = Number(request.headers.get("content-length"));
|
|
240
|
+
if (Number.isFinite(length) && length > MAX_INLINE_UPLOAD)
|
|
241
|
+
return fetch(request);
|
|
242
|
+
let body = new ArrayBuffer(0);
|
|
243
|
+
if (request.method !== "GET") {
|
|
244
|
+
try {
|
|
245
|
+
const clone = request.clone();
|
|
246
|
+
if (request.body && clone.body) {
|
|
247
|
+
// Preserve bounded streaming for upload requests that expose a body.
|
|
248
|
+
const reader = clone.body.getReader();
|
|
249
|
+
const chunks = [];
|
|
250
|
+
let size = 0;
|
|
251
|
+
while (true) {
|
|
252
|
+
const { done, value } = await reader.read();
|
|
253
|
+
if (done) break;
|
|
254
|
+
size += value.byteLength;
|
|
255
|
+
if (size > MAX_INLINE_UPLOAD) {
|
|
256
|
+
await reader.cancel();
|
|
257
|
+
return fetch(request);
|
|
258
|
+
}
|
|
259
|
+
chunks.push(value);
|
|
260
|
+
}
|
|
261
|
+
const buffer = new Uint8Array(size);
|
|
262
|
+
let offset = 0;
|
|
263
|
+
for (const chunk of chunks) {
|
|
264
|
+
buffer.set(chunk, offset);
|
|
265
|
+
offset += chunk.byteLength;
|
|
266
|
+
}
|
|
267
|
+
body = buffer.buffer;
|
|
268
|
+
} else {
|
|
269
|
+
// Firefox can expose a null FetchEvent.request.body even when POST
|
|
270
|
+
// data is present. Request.arrayBuffer() still returns that payload.
|
|
271
|
+
body = await clone.arrayBuffer();
|
|
272
|
+
if (body.byteLength > MAX_INLINE_UPLOAD) return fetch(request);
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
return fetch(request);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const bridge = new MessageChannel();
|
|
280
|
+
const reply = bridge.port1;
|
|
281
|
+
const init = new Promise((resolve) => {
|
|
282
|
+
const timer = setTimeout(() => resolve("timed-out"), ACCEPT_TIMEOUT);
|
|
283
|
+
reply.onmessage = (event) => {
|
|
284
|
+
if (event.data?.type === "accepted") {
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
resolve("accepted");
|
|
287
|
+
}
|
|
288
|
+
if (event.data?.type === "declined") {
|
|
289
|
+
clearTimeout(timer);
|
|
290
|
+
resolve("declined");
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
reply.start();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const headers = {};
|
|
297
|
+
for (const name of [
|
|
298
|
+
"accept",
|
|
299
|
+
"content-type",
|
|
300
|
+
"range",
|
|
301
|
+
"if-none-match",
|
|
302
|
+
"if-modified-since",
|
|
303
|
+
"x-chatroom-mime-type",
|
|
304
|
+
]) {
|
|
305
|
+
const value = request.headers.get(name);
|
|
306
|
+
if (value) headers[name] = value;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
client.postMessage(
|
|
310
|
+
{
|
|
311
|
+
type: "chatroom-v2-request",
|
|
312
|
+
url: url.pathname + url.search,
|
|
313
|
+
method: request.method,
|
|
314
|
+
headers,
|
|
315
|
+
body,
|
|
316
|
+
},
|
|
317
|
+
[bridge.port2, body],
|
|
318
|
+
);
|
|
319
|
+
} catch {
|
|
320
|
+
reply.close();
|
|
321
|
+
return fetch(request);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const decision = await init;
|
|
325
|
+
if (decision !== "accepted") {
|
|
326
|
+
reply.close();
|
|
327
|
+
// An unacknowledged write might already have executed. Never replay it.
|
|
328
|
+
if (decision === "declined" || request.method === "GET")
|
|
329
|
+
return fetch(request);
|
|
330
|
+
return new Response("P2P request status unknown", { status: 504 });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Once accepted, a mutation may already have executed. No HTTP retry.
|
|
334
|
+
const response = await new Promise((resolve) => {
|
|
335
|
+
let controller;
|
|
336
|
+
let finished = false;
|
|
337
|
+
let opened = false;
|
|
338
|
+
let lastChunkCredit = 0;
|
|
339
|
+
const timer = setTimeout(() => {
|
|
340
|
+
if (!opened) {
|
|
341
|
+
resolve(new Response("P2P response timed out", { status: 504 }));
|
|
342
|
+
reply.postMessage({ type: "cancel" });
|
|
343
|
+
close();
|
|
344
|
+
}
|
|
345
|
+
}, 120_000);
|
|
346
|
+
const close = () => {
|
|
347
|
+
finished = true;
|
|
348
|
+
clearTimeout(timer);
|
|
349
|
+
reply.close();
|
|
350
|
+
};
|
|
351
|
+
const stream = new ReadableStream(
|
|
352
|
+
{
|
|
353
|
+
start(value) {
|
|
354
|
+
controller = value;
|
|
355
|
+
},
|
|
356
|
+
pull() {
|
|
357
|
+
if (lastChunkCredit) {
|
|
358
|
+
reply.postMessage({ type: "credit", bytes: lastChunkCredit });
|
|
359
|
+
lastChunkCredit = 0;
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
cancel() {
|
|
363
|
+
if (!finished) reply.postMessage({ type: "cancel" });
|
|
364
|
+
close();
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
{ highWaterMark: 0 },
|
|
368
|
+
);
|
|
369
|
+
reply.onmessage = (message) => {
|
|
370
|
+
const data = message.data;
|
|
371
|
+
if (finished) return;
|
|
372
|
+
if (data?.type === "meta") {
|
|
373
|
+
opened = true;
|
|
374
|
+
const status = data.status;
|
|
375
|
+
if (!Number.isInteger(status) || status < 200 || status > 599) {
|
|
376
|
+
close();
|
|
377
|
+
resolve(Response.error());
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const headers = new Headers(data.headers);
|
|
381
|
+
headers.set("X-ChatRoom-Transport", "p2p");
|
|
382
|
+
resolve(
|
|
383
|
+
new Response([204, 205, 304].includes(status) ? null : stream, {
|
|
384
|
+
status,
|
|
385
|
+
headers,
|
|
386
|
+
}),
|
|
387
|
+
);
|
|
388
|
+
} else if (data?.type === "chunk") {
|
|
389
|
+
if (!opened) return;
|
|
390
|
+
const chunk = new Uint8Array(data.bytes);
|
|
391
|
+
try {
|
|
392
|
+
controller.enqueue(chunk);
|
|
393
|
+
lastChunkCredit += chunk.byteLength;
|
|
394
|
+
} catch {
|
|
395
|
+
reply.postMessage({ type: "cancel" });
|
|
396
|
+
close();
|
|
397
|
+
}
|
|
398
|
+
} else if (data?.type === "end") {
|
|
399
|
+
if (!opened) resolve(Response.error());
|
|
400
|
+
else controller.close();
|
|
401
|
+
close();
|
|
402
|
+
} else if (data?.type === "error") {
|
|
403
|
+
if (!opened)
|
|
404
|
+
resolve(new Response("P2P transport interrupted", { status: 502 }));
|
|
405
|
+
else controller.error(new Error("P2P transport interrupted"));
|
|
406
|
+
close();
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
});
|
|
410
|
+
return response;
|
|
411
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhanguym/chatroom",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-beta.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local MCP runtime for ChatGPT with workspace, process, Cloud, and WebUI controls.",
|
|
6
6
|
"bin": {
|
|
@@ -26,12 +26,15 @@
|
|
|
26
26
|
"build:native:macos": "node scripts/build-native-macos.mjs",
|
|
27
27
|
"build:native:windows": "node scripts/build-native-windows.mjs",
|
|
28
28
|
"build:native:linux": "node scripts/build-native-linux.mjs",
|
|
29
|
-
"clean:all": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
29
|
+
"clean:all": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
30
|
+
"postinstall": "node scripts/ensure-pty-helper.mjs"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@modelcontextprotocol/node": "^2.0.0",
|
|
33
34
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
34
35
|
"@simplewebauthn/server": "13.3.2",
|
|
36
|
+
"@xterm/addon-fit": "0.11.0",
|
|
37
|
+
"@xterm/xterm": "6.0.0",
|
|
35
38
|
"compression": "^1.8.2",
|
|
36
39
|
"express": "^5.1.0",
|
|
37
40
|
"pdfjs-dist": "6.4.299",
|
|
@@ -39,6 +42,7 @@
|
|
|
39
42
|
"zod": "^4.0.17"
|
|
40
43
|
},
|
|
41
44
|
"optionalDependencies": {
|
|
45
|
+
"node-datachannel": "^0.33.4",
|
|
42
46
|
"node-pty": "^1.1.0"
|
|
43
47
|
},
|
|
44
48
|
"devDependencies": {
|
|
@@ -76,12 +80,13 @@
|
|
|
76
80
|
},
|
|
77
81
|
"publishConfig": {
|
|
78
82
|
"access": "public",
|
|
79
|
-
"tag": "
|
|
83
|
+
"tag": "beta"
|
|
80
84
|
},
|
|
81
85
|
"files": [
|
|
82
86
|
"dist",
|
|
83
87
|
"assets",
|
|
84
|
-
"chatroom.config.example.json"
|
|
88
|
+
"chatroom.config.example.json",
|
|
89
|
+
"scripts/ensure-pty-helper.mjs"
|
|
85
90
|
],
|
|
86
91
|
"imports": {
|
|
87
92
|
"#app/*": {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { chmodSync, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
// node-pty 1.1.0 ships macOS prebuilt spawn-helper files without executable bits.
|
|
6
|
+
// Restore them at install time, while the package directory is writable.
|
|
7
|
+
if (process.platform === "darwin") {
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
let root;
|
|
10
|
+
try {
|
|
11
|
+
root = path.dirname(require.resolve("node-pty/package.json"));
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (error?.code !== "MODULE_NOT_FOUND") throw error;
|
|
14
|
+
}
|
|
15
|
+
if (root) {
|
|
16
|
+
for (const arch of ["x64", "arm64"]) {
|
|
17
|
+
const helper = path.join(
|
|
18
|
+
root,
|
|
19
|
+
"prebuilds",
|
|
20
|
+
`darwin-${arch}`,
|
|
21
|
+
"spawn-helper",
|
|
22
|
+
);
|
|
23
|
+
if (!existsSync(helper)) continue;
|
|
24
|
+
const mode = statSync(helper).mode;
|
|
25
|
+
if (!(mode & 0o111)) chmodSync(helper, mode | 0o111);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|