@tangle-network/sandbox-ui 0.105.0 → 0.105.2

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.
@@ -3,7 +3,7 @@ import { useState, useEffect, useRef, useCallback } from "react";
3
3
  function createEmptyBatch() {
4
4
  return { data: "", waiters: [] };
5
5
  }
6
- var WS_OPEN_TIMEOUT_MS = 1e4;
6
+ var WS_READY_TIMEOUT_MS = 1e4;
7
7
  function toWsUrl(apiUrl, sessionId) {
8
8
  try {
9
9
  const base = typeof window !== "undefined" ? window.location.href : void 0;
@@ -37,6 +37,20 @@ function createTerminalConnectionId() {
37
37
  }
38
38
  return `terminal-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
39
39
  }
40
+ function interactiveIdentityKey(incarnationId, control) {
41
+ if (incarnationId === void 0 && control === void 0) return "plain";
42
+ if (incarnationId === void 0 || control === void 0) {
43
+ return `incomplete:${incarnationId ?? ""}`;
44
+ }
45
+ return [
46
+ incarnationId,
47
+ control.refDigest,
48
+ String(control.generation),
49
+ control.leaseId,
50
+ control.holderId,
51
+ control.expiresAt
52
+ ].join("\0");
53
+ }
40
54
  function usePtySession({
41
55
  apiUrl,
42
56
  token,
@@ -64,6 +78,7 @@ function usePtySession({
64
78
  const reconnectRef = useRef(null);
65
79
  const shouldDeleteSessionRef = useRef(false);
66
80
  const transportReadyRef = useRef(false);
81
+ const protocolFailureRef = useRef(false);
67
82
  const colsRef = useRef(DEFAULT_TERMINAL_COLS);
68
83
  const rowsRef = useRef(DEFAULT_TERMINAL_ROWS);
69
84
  const wsRef = useRef(null);
@@ -160,6 +175,7 @@ function usePtySession({
160
175
  }
161
176
  ws.binaryType = "arraybuffer";
162
177
  pendingWsRef.current = ws;
178
+ const isCurrentSocket = () => wsRef.current === ws || pendingWsRef.current === ws;
163
179
  let opened = false;
164
180
  let settled = false;
165
181
  const settle = (ok) => {
@@ -169,13 +185,25 @@ function usePtySession({
169
185
  resolve(ok);
170
186
  };
171
187
  const handshakeTimer = setTimeout(() => {
172
- if (opened) return;
173
188
  try {
174
189
  ws.close();
175
190
  } catch {
176
191
  }
177
192
  settle(false);
178
- }, WS_OPEN_TIMEOUT_MS);
193
+ }, WS_READY_TIMEOUT_MS);
194
+ const failProtocol = (message) => {
195
+ protocolFailureRef.current = true;
196
+ transportReadyRef.current = false;
197
+ if (wsRef.current === ws) wsRef.current = null;
198
+ setIsConnected(false);
199
+ setError(message);
200
+ rejectPendingInput(message);
201
+ try {
202
+ ws.close();
203
+ } catch {
204
+ }
205
+ settle(false);
206
+ };
179
207
  ws.onopen = () => {
180
208
  opened = true;
181
209
  if (pendingWsRef.current !== ws || !mountedRef.current) {
@@ -212,15 +240,11 @@ function usePtySession({
212
240
  return;
213
241
  }
214
242
  }
243
+ if (protocolFailureRef.current) return;
215
244
  wsRef.current = ws;
216
- transportReadyRef.current = true;
217
- setIsConnected(true);
218
- setError(null);
219
- retryCountRef.current = 0;
220
- settle(true);
221
245
  };
222
246
  ws.onmessage = (ev) => {
223
- if (!mountedRef.current) return;
247
+ if (!mountedRef.current || !isCurrentSocket()) return;
224
248
  const data = ev.data;
225
249
  let text;
226
250
  if (typeof data === "string") {
@@ -231,7 +255,7 @@ function usePtySession({
231
255
  text = new TextDecoder().decode(data);
232
256
  } else {
233
257
  data.text().then((t) => {
234
- if (mountedRef.current) onDataRef.current(t);
258
+ if (mountedRef.current && isCurrentSocket()) onDataRef.current(t);
235
259
  }).catch(() => {
236
260
  });
237
261
  return;
@@ -239,17 +263,38 @@ function usePtySession({
239
263
  if (typeof data === "string") {
240
264
  try {
241
265
  const event = JSON.parse(text);
242
- if (event?.type === "ready") {
243
- return;
244
- }
245
- if (event?.type === "error") {
246
- const message = typeof event.message === "string" ? event.message : "Terminal WebSocket error";
247
- setError(message);
248
- return;
249
- }
250
- if (event?.type === "exit") {
251
- setIsConnected(false);
252
- return;
266
+ if (event !== null && typeof event === "object" && !Array.isArray(event)) {
267
+ const controlFrame = event;
268
+ switch (controlFrame.type) {
269
+ case "ready":
270
+ transportReadyRef.current = true;
271
+ setIsConnected(true);
272
+ setError(null);
273
+ retryCountRef.current = 0;
274
+ settle(true);
275
+ return;
276
+ case "error": {
277
+ const message = typeof controlFrame.message === "string" ? controlFrame.message : "Terminal WebSocket error";
278
+ if (!transportReadyRef.current) {
279
+ failProtocol(message);
280
+ } else {
281
+ setError(message);
282
+ }
283
+ return;
284
+ }
285
+ case "exit":
286
+ transportReadyRef.current = false;
287
+ setIsConnected(false);
288
+ return;
289
+ case "rec":
290
+ case "rec.done":
291
+ return;
292
+ default: {
293
+ const frameType = typeof controlFrame.type === "string" ? controlFrame.type : "<missing type>";
294
+ failProtocol(`Unknown terminal control frame: ${frameType}`);
295
+ return;
296
+ }
297
+ }
253
298
  }
254
299
  } catch {
255
300
  }
@@ -263,6 +308,7 @@ function usePtySession({
263
308
  pendingWsRef.current = null;
264
309
  }
265
310
  const wasActive = wsRef.current === ws;
311
+ const wasReady = wasActive && transportReadyRef.current;
266
312
  if (wasActive) {
267
313
  wsRef.current = null;
268
314
  transportReadyRef.current = false;
@@ -271,7 +317,11 @@ function usePtySession({
271
317
  settle(false);
272
318
  return;
273
319
  }
274
- if (!wasActive || !mountedRef.current) return;
320
+ if (!wasReady) {
321
+ settle(false);
322
+ return;
323
+ }
324
+ if (protocolFailureRef.current || !wasActive || !mountedRef.current) return;
275
325
  setIsConnected(false);
276
326
  if (sessionIdRef.current) {
277
327
  retryTimerRef.current = setTimeout(() => {
@@ -286,7 +336,7 @@ function usePtySession({
286
336
  }
287
337
  };
288
338
  });
289
- }, [apiUrl]);
339
+ }, [apiUrl, rejectPendingInput]);
290
340
  const connectStream = useCallback(async (sessionId) => {
291
341
  if (exactAttachRequiredRef.current) {
292
342
  throw new Error(
@@ -384,9 +434,11 @@ function usePtySession({
384
434
  controlRef.current = control;
385
435
  exactAttachRequiredRef.current = incarnationId !== void 0 || control !== void 0;
386
436
  connectStreamRef.current = connectStream;
437
+ const exactIdentityKey = interactiveIdentityKey(incarnationId, control);
387
438
  const connect = useCallback(async () => {
388
439
  cleanup();
389
440
  const myGen = ++connectGenRef.current;
441
+ protocolFailureRef.current = false;
390
442
  retryCountRef.current = 0;
391
443
  setError(null);
392
444
  if (exactAttachRequiredRef.current && (!incarnationIdRef.current || !controlRef.current)) {
@@ -402,6 +454,7 @@ function usePtySession({
402
454
  inputAbortRef.current = new AbortController();
403
455
  const directWsOk = await connectWs(connectionId, { initOnOpen: true });
404
456
  if (!mountedRef.current || connectGenRef.current !== myGen) return;
457
+ if (protocolFailureRef.current) return;
405
458
  ensureDrainRunningRef.current?.();
406
459
  if (directWsOk) {
407
460
  return;
@@ -442,6 +495,7 @@ function usePtySession({
442
495
  inputAbortRef.current = new AbortController();
443
496
  const wsOk = await connectWs(sessionId, { initOnOpen: true });
444
497
  if (!mountedRef.current || connectGenRef.current !== myGen) return;
498
+ if (protocolFailureRef.current) return;
445
499
  ensureDrainRunningRef.current?.();
446
500
  if (wsOk) {
447
501
  return;
@@ -456,7 +510,14 @@ function usePtySession({
456
510
  setIsConnected(false);
457
511
  }
458
512
  }
459
- }, [apiUrl, providedConnectionId, cleanup, connectWs, connectStream]);
513
+ }, [
514
+ apiUrl,
515
+ providedConnectionId,
516
+ cleanup,
517
+ connectWs,
518
+ connectStream,
519
+ exactIdentityKey
520
+ ]);
460
521
  reconnectRef.current = connect;
461
522
  const resizeTerminal = useCallback(async (cols, rows) => {
462
523
  if (cols > 0) colsRef.current = cols;
@@ -564,6 +625,10 @@ function usePtySession({
564
625
  resolve();
565
626
  return;
566
627
  }
628
+ if (protocolFailureRef.current) {
629
+ reject(new Error("Terminal protocol failure"));
630
+ return;
631
+ }
567
632
  pendingBatchRef.current.data += command;
568
633
  pendingBatchRef.current.waiters.push({ resolve, reject });
569
634
  ensureDrainRunning();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  usePtySession
3
- } from "./chunk-7KB22BLB.js";
3
+ } from "./chunk-F7LNWZQR.js";
4
4
 
5
5
  // src/terminal/terminal-view.tsx
6
6
  import "@xterm/xterm/css/xterm.css";
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-K4Q4E7D7.js";
7
7
  import {
8
8
  TerminalView
9
- } from "./chunk-4VLVVKQZ.js";
9
+ } from "./chunk-LWSWKLI5.js";
10
10
  import {
11
11
  cn
12
12
  } from "./chunk-EI44GEQ5.js";
package/dist/hooks.d.ts CHANGED
@@ -51,8 +51,9 @@ interface UsePtySessionReturn {
51
51
  * - Server → client: BINARY frames carrying PTY output; TEXT frames
52
52
  * carrying lifecycle/control messages.
53
53
  * - Client → server: TEXT frames carrying JSON input/resize messages.
54
- * 2. If direct WS does not reach OPEN within WS_OPEN_TIMEOUT_MS, or it
55
- * errors before opening, fall back to the older terminal contract:
54
+ * 2. If direct WS does not reach server `ready` within
55
+ * WS_READY_TIMEOUT_MS, or it errors before ready, fall back to the older
56
+ * terminal contract:
56
57
  * - POST /terminals creates the session.
57
58
  * - GET /terminals/:id/ws tries the older WS transport.
58
59
  * - GET /terminals/:id/stream (SSE for output)
package/dist/hooks.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  } from "./chunk-3GX5BSD4.js";
27
27
  import {
28
28
  usePtySession
29
- } from "./chunk-7KB22BLB.js";
29
+ } from "./chunk-F7LNWZQR.js";
30
30
  import "./chunk-CZ7CSFO4.js";
31
31
  export {
32
32
  RealtimeSessionRegistry,
package/dist/index.js CHANGED
@@ -153,7 +153,7 @@ import {
153
153
  buildUnifiedPatch,
154
154
  computeDiffStats,
155
155
  resolveLanguage
156
- } from "./chunk-NAWEV5UJ.js";
156
+ } from "./chunk-X24MVW2R.js";
157
157
  import {
158
158
  FileArtifactPane,
159
159
  FilePreview,
@@ -238,10 +238,10 @@ import {
238
238
  UsageChart,
239
239
  formatPrice
240
240
  } from "./chunk-OMUIPJQZ.js";
241
- import "./chunk-4VLVVKQZ.js";
241
+ import "./chunk-LWSWKLI5.js";
242
242
  import {
243
243
  usePtySession
244
- } from "./chunk-7KB22BLB.js";
244
+ } from "./chunk-F7LNWZQR.js";
245
245
  import {
246
246
  Avatar,
247
247
  AvatarFallback,
package/dist/terminal.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  DEFAULT_TERMINAL_THEME,
3
3
  TerminalView
4
- } from "./chunk-4VLVVKQZ.js";
5
- import "./chunk-7KB22BLB.js";
4
+ } from "./chunk-LWSWKLI5.js";
5
+ import "./chunk-F7LNWZQR.js";
6
6
  import "./chunk-CZ7CSFO4.js";
7
7
  export {
8
8
  DEFAULT_TERMINAL_THEME,
package/dist/workbench.js CHANGED
@@ -12,12 +12,12 @@ import {
12
12
  buildUnifiedPatch,
13
13
  computeDiffStats,
14
14
  resolveLanguage
15
- } from "./chunk-NAWEV5UJ.js";
15
+ } from "./chunk-X24MVW2R.js";
16
16
  import "./chunk-PZVUIUUW.js";
17
17
  import "./chunk-K4Q4E7D7.js";
18
18
  import "./chunk-OMUIPJQZ.js";
19
- import "./chunk-4VLVVKQZ.js";
20
- import "./chunk-7KB22BLB.js";
19
+ import "./chunk-LWSWKLI5.js";
20
+ import "./chunk-F7LNWZQR.js";
21
21
  import "./chunk-2XPJ2XF7.js";
22
22
  import "./chunk-RQZRXKMR.js";
23
23
  import "./chunk-BGVEHENC.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.105.0",
3
+ "version": "0.105.2",
4
4
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
5
5
  "repository": {
6
6
  "type": "git",