@opengeni/react 0.48.0 → 0.57.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/README.md +138 -2
- package/dist/artifacts-document.d.ts +3 -0
- package/dist/artifacts-document.js +17 -0
- package/dist/artifacts-document.js.map +1 -0
- package/dist/artifacts-presentation.d.ts +3 -0
- package/dist/artifacts-presentation.js +17 -0
- package/dist/artifacts-presentation.js.map +1 -0
- package/dist/artifacts-spreadsheet.d.ts +3 -0
- package/dist/artifacts-spreadsheet.js +17 -0
- package/dist/artifacts-spreadsheet.js.map +1 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +199 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/{chunk-NJ7M2B6Z.js → chunk-5UQPNLIO.js} +16 -8
- package/dist/{chunk-NJ7M2B6Z.js.map → chunk-5UQPNLIO.js.map} +1 -1
- package/dist/{chunk-LHLYWG5R.js → chunk-6KLX2RH5.js} +461 -38
- package/dist/chunk-6KLX2RH5.js.map +1 -0
- package/dist/chunk-7WCLAEFV.js +2828 -0
- package/dist/chunk-7WCLAEFV.js.map +1 -0
- package/dist/{chunk-I4MPFDRP.js → chunk-B6WPZSTT.js} +104 -104
- package/dist/{chunk-I4MPFDRP.js.map → chunk-B6WPZSTT.js.map} +1 -1
- package/dist/{chunk-EKZH6IDG.js → chunk-C5ZPSSS5.js} +111 -1
- package/dist/chunk-C5ZPSSS5.js.map +1 -0
- package/dist/{chunk-OL6QVXGF.js → chunk-HA46CYQK.js} +152 -8
- package/dist/chunk-HA46CYQK.js.map +1 -0
- package/dist/chunk-HUIKIDM5.js +2148 -0
- package/dist/chunk-HUIKIDM5.js.map +1 -0
- package/dist/{chunk-FWXGSNXT.js → chunk-IGC6A2ZI.js} +585 -34
- package/dist/chunk-IGC6A2ZI.js.map +1 -0
- package/dist/chunk-JVYQRRC4.js +53 -0
- package/dist/chunk-JVYQRRC4.js.map +1 -0
- package/dist/chunk-KCGXPP5V.js +157 -0
- package/dist/chunk-KCGXPP5V.js.map +1 -0
- package/dist/chunk-LLOLUHVG.js +5937 -0
- package/dist/chunk-LLOLUHVG.js.map +1 -0
- package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
- package/dist/chunk-MKPIAGVL.js.map +1 -0
- package/dist/chunk-PEI6RANI.js +3234 -0
- package/dist/chunk-PEI6RANI.js.map +1 -0
- package/dist/chunk-R3AZOAB5.js +933 -0
- package/dist/chunk-R3AZOAB5.js.map +1 -0
- package/dist/{chunk-O4VXOBGF.js → chunk-WSK7G5LE.js} +70 -2
- package/dist/chunk-WSK7G5LE.js.map +1 -0
- package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
- package/dist/chunk-XBZWOM2K.js.map +1 -0
- package/dist/{chunk-XCNTXWBB.js → chunk-Y7OEDUJ5.js} +856 -338
- package/dist/chunk-Y7OEDUJ5.js.map +1 -0
- package/dist/client.d.ts +10 -2
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +7 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/types.d.ts +1 -1
- package/dist/components/approval-surface.d.ts +2 -2
- package/dist/components/artifacts/artifact-surface.d.ts +18 -0
- package/dist/components/artifacts/document-editor.d.ts +172 -0
- package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
- package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
- package/dist/components/artifacts/editable-document.d.ts +24 -0
- package/dist/components/artifacts/editable-presentation.d.ts +24 -0
- package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
- package/dist/components/artifacts/index.d.ts +9 -0
- package/dist/components/artifacts/presentation-editor.d.ts +297 -0
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
- package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
- package/dist/components/browser-input.d.ts +7 -0
- package/dist/components/browser-viewer.d.ts +30 -0
- package/dist/components/chat-composer.d.ts +8 -8
- package/dist/components/code-editor.d.ts +25 -3
- package/dist/components/command-palette.d.ts +2 -2
- package/dist/components/composer-transcription-control.d.ts +2 -2
- package/dist/components/composer.d.ts +17 -17
- package/dist/components/computer-viewer.d.ts +24 -0
- package/dist/components/copy-button.d.ts +2 -2
- package/dist/components/desktop-viewer.d.ts +50 -22
- package/dist/components/diff-view.d.ts +1 -1
- package/dist/components/enrollment-consent.d.ts +1 -1
- package/dist/components/enrollment-device-flow.d.ts +1 -1
- package/dist/components/file-browser.d.ts +2 -2
- package/dist/components/fleet-tile.d.ts +1 -1
- package/dist/components/generated-video-player.d.ts +10 -0
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +2 -2
- package/dist/components/interaction-intervention-banner.d.ts +11 -0
- package/dist/components/interactive-workbench-viewers.d.ts +2 -0
- package/dist/components/machine-card.d.ts +2 -2
- package/dist/components/machine-dock-bar.d.ts +3 -3
- package/dist/components/machine-health-pill.d.ts +2 -2
- package/dist/components/machine-input-display.d.ts +1 -1
- package/dist/components/machine-metrics.d.ts +2 -2
- package/dist/components/machine-status-pill.d.ts +4 -4
- package/dist/components/machines/health.d.ts +1 -1
- package/dist/components/machines/machine-detail.d.ts +3 -3
- package/dist/components/machines/metric-history-chart.d.ts +1 -1
- package/dist/components/machines/metric-sparkline.d.ts +2 -2
- package/dist/components/machines/series.d.ts +2 -2
- package/dist/components/machines-dashboard.d.ts +2 -2
- package/dist/components/markdown.d.ts +1 -1
- package/dist/components/message-timeline.d.ts +12 -5
- package/dist/components/model-picker.d.ts +2 -2
- package/dist/components/model-policy-picker.d.ts +7 -7
- package/dist/components/pierre-diff.d.ts +1 -1
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/queue-draft-policy.d.ts +1 -1
- package/dist/components/queue-surface-implementation.d.ts +3 -3
- package/dist/components/queue-surface-state.d.ts +4 -4
- package/dist/components/queue-surface.d.ts +5 -5
- package/dist/components/sandbox-files.d.ts +3 -3
- package/dist/components/sandbox-terminal.d.ts +24 -4
- package/dist/components/sandbox-workspace.d.ts +28 -9
- package/dist/components/session-chrome.d.ts +4 -4
- package/dist/components/session-status.d.ts +2 -2
- package/dist/components/timeline-annotation-selection.d.ts +9 -0
- package/dist/components/timeline-annotations-dialog.d.ts +16 -0
- package/dist/components/timeline-annotations.d.ts +13 -0
- package/dist/components/tooltip.d.ts +4 -4
- package/dist/components/user-message-body.d.ts +2 -2
- package/dist/components/workbench-changes.d.ts +2 -2
- package/dist/components/workspace-dock.d.ts +1 -1
- package/dist/composer.d.ts +11 -11
- package/dist/composer.js +7 -7
- package/dist/hooks/internal.d.ts +1 -1
- package/dist/hooks/use-attached-browsers.d.ts +21 -0
- package/dist/hooks/use-auth-runs.d.ts +32 -0
- package/dist/hooks/use-available-models.d.ts +2 -2
- package/dist/hooks/use-billing-usage.d.ts +1 -1
- package/dist/hooks/use-browser-downloads.d.ts +25 -0
- package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
- package/dist/hooks/use-browser-identities.d.ts +30 -0
- package/dist/hooks/use-browser-session.d.ts +29 -0
- package/dist/hooks/use-browser-sessions.d.ts +32 -0
- package/dist/hooks/use-channels.d.ts +26 -0
- package/dist/hooks/use-codex-accounts.d.ts +2 -2
- package/dist/hooks/use-composer.d.ts +36 -9
- package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
- package/dist/hooks/use-computer-session.d.ts +29 -0
- package/dist/hooks/use-computer-sessions.d.ts +28 -0
- package/dist/hooks/use-desktop-stream.d.ts +5 -1
- package/dist/hooks/use-environments.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +1 -1
- package/dist/hooks/use-goal.d.ts +2 -2
- package/dist/hooks/use-human-input.d.ts +2 -2
- package/dist/hooks/use-interaction-interventions.d.ts +27 -0
- package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
- package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
- package/dist/hooks/use-machine-chip.d.ts +2 -2
- package/dist/hooks/use-machines.d.ts +4 -4
- package/dist/hooks/use-network-routes.d.ts +25 -0
- package/dist/hooks/use-packs.d.ts +7 -2
- package/dist/hooks/use-rigs.d.ts +1 -1
- package/dist/hooks/use-sandbox-files.d.ts +6 -4
- package/dist/hooks/use-sandbox-git.d.ts +6 -5
- package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
- package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
- package/dist/hooks/use-session-capabilities.d.ts +5 -4
- package/dist/hooks/use-session-control.d.ts +1 -1
- package/dist/hooks/use-session-events.d.ts +2 -2
- package/dist/hooks/use-session-lineage.d.ts +1 -1
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session.d.ts +2 -2
- package/dist/hooks/use-site-auth-connections.d.ts +25 -0
- package/dist/hooks/use-slash-commands.d.ts +1 -1
- package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
- package/dist/hooks/use-terminal-stream.d.ts +11 -2
- package/dist/hooks/use-turn-queue.d.ts +2 -2
- package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
- package/dist/hooks/use-voice-input.d.ts +1 -1
- package/dist/hooks/use-workspace-capture.d.ts +1 -1
- package/dist/hooks/use-workspace-edit.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +1 -1
- package/dist/hooks/use-workspaces.d.ts +1 -1
- package/dist/index.d.ts +162 -155
- package/dist/index.js +1822 -1703
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +33 -0
- package/dist/interaction.js +550 -0
- package/dist/interaction.js.map +1 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
- package/dist/interactive-workbench-viewers-TOHBXJEE.js.map +1 -0
- package/dist/lib/host-platform.d.ts +5 -0
- package/dist/lib/relay-wire.d.ts +24 -1
- package/dist/lib/sandbox-liveness.d.ts +9 -0
- package/dist/lib/terminal-capability.d.ts +5 -0
- package/dist/lib/xterm-theme.d.ts +1 -1
- package/dist/machines.d.ts +30 -30
- package/dist/machines.js +2 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +6 -5
- package/dist/{queue-surface-implementation-U5JFG7HT.js → queue-surface-implementation-2KVJTYXH.js} +100 -48
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
- package/dist/realtime/dropdown-menu.d.ts +8 -8
- package/dist/realtime/realtime-control.d.ts +8 -8
- package/dist/realtime.d.ts +4 -4
- package/dist/realtime.js +38 -11
- package/dist/realtime.js.map +1 -1
- package/dist/session-context.d.ts +28 -2
- package/dist/session-ui.d.ts +14 -14
- package/dist/session-ui.js +5 -4
- package/dist/session.d.ts +28 -28
- package/dist/session.js +10 -10
- package/dist/timeline/activity-rail.d.ts +5 -3
- package/dist/timeline/disclosure-context.d.ts +1 -1
- package/dist/timeline/fleet-decision-projection.d.ts +1 -1
- package/dist/timeline/fleet-decision-row.d.ts +3 -3
- package/dist/timeline/index.d.ts +19 -19
- package/dist/timeline/parsers.d.ts +4 -1
- package/dist/timeline/projection.d.ts +3 -3
- package/dist/timeline/registry.d.ts +8 -2
- package/dist/timeline/screenshot-lightbox.d.ts +2 -2
- package/dist/timeline/shared.d.ts +21 -10
- package/dist/timeline/tool-diff.d.ts +2 -2
- package/dist/timeline/tool-renderers.d.ts +1 -1
- package/dist/timeline/turn-summary.d.ts +3 -3
- package/dist/timeline/types.d.ts +44 -3
- package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
- package/package.json +36 -7
- package/src/artifacts-document.ts +31 -0
- package/src/artifacts-presentation.ts +36 -0
- package/src/artifacts-spreadsheet.ts +21 -0
- package/src/artifacts.ts +89 -0
- package/src/client.ts +157 -1
- package/src/clipboard.ts +2 -0
- package/src/components/artifacts/artifact-surface.tsx +93 -0
- package/src/components/artifacts/document-editor.tsx +2255 -0
- package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
- package/src/components/artifacts/editable-document.tsx +657 -0
- package/src/components/artifacts/editable-presentation.tsx +1020 -0
- package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
- package/src/components/artifacts/index.ts +104 -0
- package/src/components/artifacts/presentation-editor.tsx +3148 -0
- package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
- package/src/components/browser-input.ts +65 -0
- package/src/components/browser-viewer.tsx +2702 -0
- package/src/components/chat-composer.tsx +13 -0
- package/src/components/code-editor.tsx +89 -30
- package/src/components/composer-transcription-control.tsx +10 -6
- package/src/components/computer-viewer.tsx +1636 -0
- package/src/components/desktop-viewer.tsx +215 -74
- package/src/components/generated-video-player.tsx +89 -0
- package/src/components/interaction-intervention-banner.tsx +99 -0
- package/src/components/interactive-workbench-viewers.ts +2 -0
- package/src/components/machine-input-display.ts +3 -0
- package/src/components/machines/machine-detail.tsx +1 -1
- package/src/components/message-timeline.tsx +289 -32
- package/src/components/model-policy-picker.tsx +15 -0
- package/src/components/queue-surface-implementation.tsx +116 -60
- package/src/components/sandbox-files.tsx +2 -2
- package/src/components/sandbox-terminal.tsx +128 -46
- package/src/components/sandbox-workspace.tsx +256 -90
- package/src/components/session-chrome.tsx +53 -13
- package/src/components/timeline-annotation-selection.tsx +221 -0
- package/src/components/timeline-annotations-dialog.tsx +213 -0
- package/src/components/timeline-annotations.tsx +84 -0
- package/src/components/workbench-changes.tsx +188 -31
- package/src/components/workspace-dock.tsx +59 -5
- package/src/hooks/use-attached-browsers.ts +154 -0
- package/src/hooks/use-auth-runs.ts +278 -0
- package/src/hooks/use-browser-downloads.ts +218 -0
- package/src/hooks/use-browser-frame-stream.ts +425 -0
- package/src/hooks/use-browser-identities.ts +266 -0
- package/src/hooks/use-browser-session.ts +543 -0
- package/src/hooks/use-browser-sessions.ts +286 -0
- package/src/hooks/use-channels.ts +99 -0
- package/src/hooks/use-composer.ts +769 -24
- package/src/hooks/use-computer-frame-stream.ts +434 -0
- package/src/hooks/use-computer-session.ts +462 -0
- package/src/hooks/use-computer-sessions.ts +251 -0
- package/src/hooks/use-desktop-stream.ts +39 -7
- package/src/hooks/use-interaction-interventions.ts +242 -0
- package/src/hooks/use-interaction-invalidation.ts +66 -0
- package/src/hooks/use-machine-chip.ts +15 -3
- package/src/hooks/use-machines.ts +33 -3
- package/src/hooks/use-network-routes.ts +214 -0
- package/src/hooks/use-packs.ts +51 -0
- package/src/hooks/use-sandbox-files.ts +98 -55
- package/src/hooks/use-sandbox-git.ts +93 -38
- package/src/hooks/use-sandbox-terminal.ts +3 -2
- package/src/hooks/use-session-capabilities.ts +59 -46
- package/src/hooks/use-site-auth-connections.ts +226 -0
- package/src/hooks/use-supergrok-accounts.ts +81 -0
- package/src/hooks/use-terminal-stream.ts +359 -47
- package/src/hooks/use-video-artifact-playback.ts +28 -0
- package/src/hooks/use-workspace-edit.ts +2 -1
- package/src/index.ts +18 -0
- package/src/interaction.ts +101 -0
- package/src/lib/host-platform.ts +10 -0
- package/src/lib/relay-wire.ts +47 -4
- package/src/lib/sandbox-liveness.ts +11 -0
- package/src/lib/terminal-capability.ts +10 -0
- package/src/model-policy.ts +19 -4
- package/src/provider.tsx +133 -1
- package/src/realtime/realtime-control.tsx +34 -6
- package/src/session-context.ts +156 -1
- package/src/timeline/activity-rail.tsx +25 -3
- package/src/timeline/index.ts +3 -0
- package/src/timeline/parsers.ts +14 -1
- package/src/timeline/projection.ts +269 -14
- package/src/timeline/registry.ts +13 -1
- package/src/timeline/screenshot-lightbox.tsx +19 -9
- package/src/timeline/shared.tsx +56 -8
- package/src/timeline/tool-renderers.tsx +303 -50
- package/src/timeline/types.ts +57 -3
- package/styles/compiled.css +312 -21
- package/styles/index.css +15 -0
- package/styles/responsive.css +13 -1
- package/dist/chunk-23EJ676W.js.map +0 -1
- package/dist/chunk-EKZH6IDG.js.map +0 -1
- package/dist/chunk-FWXGSNXT.js.map +0 -1
- package/dist/chunk-LHLYWG5R.js.map +0 -1
- package/dist/chunk-O4VXOBGF.js.map +0 -1
- package/dist/chunk-OL6QVXGF.js.map +0 -1
- package/dist/chunk-WYA65Y3F.js.map +0 -1
- package/dist/chunk-XCNTXWBB.js.map +0 -1
- package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
- package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
- package/dist/queue-surface-implementation-U5JFG7HT.js.map +0 -1
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type DesktopRfbLike,
|
|
7
7
|
type DesktopStreamCapability,
|
|
8
8
|
} from "@opengeni/sdk";
|
|
9
|
-
import { type RefObject, useEffect, useRef, useState } from "react";
|
|
9
|
+
import { type RefObject, useCallback, useEffect, useRef, useState } from "react";
|
|
10
10
|
import { type DesktopWebSocketFactory, useRelayFrameStream } from "./use-relay-frame-stream";
|
|
11
11
|
|
|
12
12
|
export type UseDesktopStreamOptions = {
|
|
@@ -19,6 +19,8 @@ export type UseDesktopStreamOptions = {
|
|
|
19
19
|
scaleViewport?: boolean | undefined;
|
|
20
20
|
/** Custom RFB factory (tests / a WebRTC swap). Defaults to a lazy @novnc/novnc. */
|
|
21
21
|
rfbFactory?: DesktopRfbFactory | undefined;
|
|
22
|
+
/** Additional WebSocket protocols for an authenticated placement proxy. */
|
|
23
|
+
webSocketProtocols?: string[] | undefined;
|
|
22
24
|
/** Custom socket factory for the `relay-frames` transport (tests). Defaults to
|
|
23
25
|
* `new WebSocket(url)`. Mirrors `rfbFactory` for the frame renderer. */
|
|
24
26
|
webSocketFactory?: DesktopWebSocketFactory | undefined;
|
|
@@ -29,6 +31,8 @@ export type UseDesktopStreamResult = {
|
|
|
29
31
|
error: Error | null;
|
|
30
32
|
/** Manual reconnect (e.g. after a securityfailure once a fresh URL arrives). */
|
|
31
33
|
reconnect: () => void;
|
|
34
|
+
/** Synchronous input on the already-open RFB authority. */
|
|
35
|
+
sendKey?: ((keysym: number, code: string, down?: boolean) => boolean) | undefined;
|
|
32
36
|
};
|
|
33
37
|
|
|
34
38
|
/** Lazy-load @novnc/novnc's RFB as the default factory. Imported inside the
|
|
@@ -44,7 +48,10 @@ async function defaultRfbFactory(): Promise<DesktopRfbFactory> {
|
|
|
44
48
|
default: new (
|
|
45
49
|
t: HTMLElement,
|
|
46
50
|
u: string,
|
|
47
|
-
o: {
|
|
51
|
+
o: {
|
|
52
|
+
credentials?: { password?: string | undefined } | undefined;
|
|
53
|
+
wsProtocols?: string[] | undefined;
|
|
54
|
+
},
|
|
48
55
|
) => DesktopRfbLike;
|
|
49
56
|
};
|
|
50
57
|
const RFB = mod.default;
|
|
@@ -71,8 +78,15 @@ async function defaultRfbFactory(): Promise<DesktopRfbFactory> {
|
|
|
71
78
|
* way, so `DesktopViewer` is transport-agnostic.
|
|
72
79
|
*/
|
|
73
80
|
export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopStreamResult {
|
|
74
|
-
const {
|
|
75
|
-
|
|
81
|
+
const {
|
|
82
|
+
capability,
|
|
83
|
+
containerRef,
|
|
84
|
+
interactive,
|
|
85
|
+
scaleViewport,
|
|
86
|
+
rfbFactory,
|
|
87
|
+
webSocketProtocols,
|
|
88
|
+
webSocketFactory,
|
|
89
|
+
} = options;
|
|
76
90
|
|
|
77
91
|
// The self-hosted PNG-frame path. Always invoked (rules of hooks); dormant
|
|
78
92
|
// unless `transport === "relay-frames"`, in which case it owns the surface.
|
|
@@ -91,6 +105,12 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
|
|
|
91
105
|
};
|
|
92
106
|
|
|
93
107
|
const reconnect = () => setNonce((n) => n + 1);
|
|
108
|
+
const sendKey = useCallback((keysym: number, code: string, down?: boolean): boolean => {
|
|
109
|
+
const rfb = rfbRef.current;
|
|
110
|
+
if (!rfb?.sendKey || rfb.viewOnly || stateRef.current !== "connected") return false;
|
|
111
|
+
rfb.sendKey(keysym, code, down);
|
|
112
|
+
return true;
|
|
113
|
+
}, []);
|
|
94
114
|
|
|
95
115
|
const url = capability?.url ?? null;
|
|
96
116
|
// The credential is keyed into the connect effect so a token rotation (new
|
|
@@ -99,6 +119,14 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
|
|
|
99
119
|
const token = capability?.token ?? null;
|
|
100
120
|
const transport = capability?.transport ?? null;
|
|
101
121
|
const mode = capability?.mode ?? "read-only";
|
|
122
|
+
// For direct sandbox RFB, authorization is carried in WebSocket
|
|
123
|
+
// subprotocols rather than `capability.token`. The view grant rotates before
|
|
124
|
+
// expiry while the URL stays stable, so the protocol credential is a real
|
|
125
|
+
// connection boundary: keeping the old socket leaves the viewer black once
|
|
126
|
+
// browserd expires it. Key the effect on the primitive value (not the caller's
|
|
127
|
+
// array identity) so credential rotation reconnects exactly once without
|
|
128
|
+
// churning on ordinary renders.
|
|
129
|
+
const webSocketProtocolsKey = webSocketProtocols?.join("\u0000") ?? "";
|
|
102
130
|
|
|
103
131
|
// The live RFB handle. Holding it in a ref lets us flip view-only (take
|
|
104
132
|
// control / return control) and re-scale on the OPEN connection instead of
|
|
@@ -113,10 +141,12 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
|
|
|
113
141
|
const scaleViewportRef = useRef(scaleViewport);
|
|
114
142
|
const modeRef = useRef(mode);
|
|
115
143
|
const rfbFactoryRef = useRef(rfbFactory);
|
|
144
|
+
const webSocketProtocolsRef = useRef(webSocketProtocols);
|
|
116
145
|
interactiveRef.current = interactive;
|
|
117
146
|
scaleViewportRef.current = scaleViewport;
|
|
118
147
|
modeRef.current = mode;
|
|
119
148
|
rfbFactoryRef.current = rfbFactory;
|
|
149
|
+
webSocketProtocolsRef.current = webSocketProtocols;
|
|
120
150
|
|
|
121
151
|
// read-only is forced when the server says so OR the caller didn't opt in.
|
|
122
152
|
const viewOnlyFor = (m: string, want: boolean | undefined) => m === "read-only" || !want;
|
|
@@ -164,6 +194,7 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
|
|
|
164
194
|
setBoth(nextDesktopState(stateRef.current, { type: "negotiated" }));
|
|
165
195
|
rfb = factory(container, socketUrl, {
|
|
166
196
|
credentials: token ? { password: token } : undefined,
|
|
197
|
+
...(webSocketProtocolsRef.current ? { wsProtocols: webSocketProtocolsRef.current } : {}),
|
|
167
198
|
});
|
|
168
199
|
rfb.viewOnly = viewOnlyFor(modeRef.current, interactiveRef.current);
|
|
169
200
|
// Fit-to-panel: SCALE the 1280x800 framebuffer down to the container
|
|
@@ -200,11 +231,12 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
|
|
|
200
231
|
}
|
|
201
232
|
};
|
|
202
233
|
// ONLY a real transport change reconnects: a fresh url (rotation), a new
|
|
203
|
-
// credential, the transport flipping,
|
|
234
|
+
// credential (RFB password OR bearer subprotocol), the transport flipping,
|
|
235
|
+
// or an explicit manual reconnect
|
|
204
236
|
// (`nonce`). `interactive`/`scaleViewport`/`mode`/`rfbFactory` are read via
|
|
205
237
|
// refs and applied live below — they must never re-open the socket.
|
|
206
238
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
207
|
-
}, [url, token, transport, nonce]);
|
|
239
|
+
}, [url, token, transport, webSocketProtocolsKey, nonce]);
|
|
208
240
|
|
|
209
241
|
// Apply take-control / return-control to the OPEN connection in place. noVNC
|
|
210
242
|
// honours `viewOnly` live, so flipping it neither blinks the surface nor drops
|
|
@@ -236,5 +268,5 @@ export function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopSt
|
|
|
236
268
|
// drives; for `vnc-ws` (and everything else) the noVNC path drives while the
|
|
237
269
|
// frame renderer stays idle. Same public shape either way.
|
|
238
270
|
if (transport === "relay-frames") return frameStream;
|
|
239
|
-
return { state, error, reconnect };
|
|
271
|
+
return { state, error, reconnect, sendKey };
|
|
240
272
|
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CreateInteractionInterventionRequest,
|
|
3
|
+
InteractionIntervention,
|
|
4
|
+
InteractionInterventionMutationResponse,
|
|
5
|
+
ResolveInteractionInterventionRequest,
|
|
6
|
+
} from "@opengeni/sdk/interaction";
|
|
7
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
8
|
+
import type { EmbeddedInterventionClientLike } from "../client";
|
|
9
|
+
import {
|
|
10
|
+
type EmbeddedInterventionClientOverride,
|
|
11
|
+
useEmbeddedInterventions,
|
|
12
|
+
} from "../session-context";
|
|
13
|
+
import { useInteractionInvalidation } from "./use-interaction-invalidation";
|
|
14
|
+
|
|
15
|
+
export type UseInteractionInterventionsOptions = EmbeddedInterventionClientOverride & {
|
|
16
|
+
enabled?: boolean | undefined;
|
|
17
|
+
resourceKind?: "browser_session" | "computer_session" | undefined;
|
|
18
|
+
resourceId?: string | null | undefined;
|
|
19
|
+
includeSettled?: boolean | undefined;
|
|
20
|
+
pollIntervalMs?: number | undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type UseInteractionInterventionsResult = {
|
|
24
|
+
interventions: InteractionIntervention[];
|
|
25
|
+
loading: boolean;
|
|
26
|
+
refreshing: boolean;
|
|
27
|
+
mutating: boolean;
|
|
28
|
+
error: Error | null;
|
|
29
|
+
refresh: () => Promise<void>;
|
|
30
|
+
create: (
|
|
31
|
+
request: Omit<CreateInteractionInterventionRequest, "operationId"> & {
|
|
32
|
+
operationId?: string;
|
|
33
|
+
},
|
|
34
|
+
) => Promise<InteractionInterventionMutationResponse>;
|
|
35
|
+
resolve: (
|
|
36
|
+
interventionId: string,
|
|
37
|
+
request: Omit<ResolveInteractionInterventionRequest, "operationId"> & {
|
|
38
|
+
operationId?: string;
|
|
39
|
+
},
|
|
40
|
+
) => Promise<InteractionInterventionMutationResponse>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Human-action bridge shared by Browser and Computer surfaces. */
|
|
44
|
+
export function useInteractionInterventions(
|
|
45
|
+
options: UseInteractionInterventionsOptions = {},
|
|
46
|
+
): UseInteractionInterventionsResult {
|
|
47
|
+
const { client, workspaceId, workspaceInteractionEvent, workspaceInteractionConnectionState } =
|
|
48
|
+
useEmbeddedInterventions(options);
|
|
49
|
+
const enabled = (options.enabled ?? true) && options.resourceId !== null;
|
|
50
|
+
const resourceKind = options.resourceKind;
|
|
51
|
+
const resourceId = options.resourceId ?? undefined;
|
|
52
|
+
const includeSettled = options.includeSettled ?? false;
|
|
53
|
+
const key = `${workspaceId}:${resourceKind ?? "*"}:${resourceId ?? "*"}:${includeSettled}`;
|
|
54
|
+
const [state, setState] = useState(() => emptyState(key, enabled));
|
|
55
|
+
const [mutationCount, setMutationCount] = useState(0);
|
|
56
|
+
const requestRef = useRef<{ id: number; controller: AbortController | null }>({
|
|
57
|
+
id: 0,
|
|
58
|
+
controller: null,
|
|
59
|
+
});
|
|
60
|
+
const mountedRef = useRef(true);
|
|
61
|
+
const visible = state.key === key ? state : emptyState(key, enabled);
|
|
62
|
+
|
|
63
|
+
const cancelLoad = useCallback(() => {
|
|
64
|
+
requestRef.current.controller?.abort();
|
|
65
|
+
requestRef.current = { id: requestRef.current.id + 1, controller: null };
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
const load = useCallback(
|
|
69
|
+
async (foreground: boolean, replace = true): Promise<void> => {
|
|
70
|
+
if (!enabled) return;
|
|
71
|
+
if (requestRef.current.controller && !replace) return;
|
|
72
|
+
const id = requestRef.current.id + 1;
|
|
73
|
+
requestRef.current.controller?.abort();
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
requestRef.current = { id, controller };
|
|
76
|
+
if (foreground || replace) {
|
|
77
|
+
setState((current) => ({
|
|
78
|
+
...(current.key === key ? current : emptyState(key, true)),
|
|
79
|
+
loading: foreground,
|
|
80
|
+
refreshing: !foreground,
|
|
81
|
+
error: foreground ? null : current.error,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const response = await client.listInteractionInterventions(workspaceId, {
|
|
86
|
+
...(resourceKind ? { resourceKind } : {}),
|
|
87
|
+
...(resourceId ? { resourceId } : {}),
|
|
88
|
+
includeSettled,
|
|
89
|
+
signal: controller.signal,
|
|
90
|
+
});
|
|
91
|
+
if (!mountedRef.current || requestRef.current.id !== id) return;
|
|
92
|
+
setState({
|
|
93
|
+
key,
|
|
94
|
+
interventions: sortInterventions(response.interventions),
|
|
95
|
+
loading: false,
|
|
96
|
+
refreshing: false,
|
|
97
|
+
error: null,
|
|
98
|
+
});
|
|
99
|
+
} catch (cause) {
|
|
100
|
+
if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
|
|
101
|
+
return;
|
|
102
|
+
setState((current) => ({
|
|
103
|
+
...(current.key === key ? current : emptyState(key, true)),
|
|
104
|
+
loading: false,
|
|
105
|
+
refreshing: false,
|
|
106
|
+
error: asError(cause),
|
|
107
|
+
}));
|
|
108
|
+
} finally {
|
|
109
|
+
if (requestRef.current.id === id) requestRef.current = { id, controller: null };
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[client, enabled, includeSettled, key, resourceId, resourceKind, workspaceId],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const refresh = useCallback(async () => await load(false, true), [load]);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
mountedRef.current = true;
|
|
119
|
+
if (!enabled) {
|
|
120
|
+
cancelLoad();
|
|
121
|
+
setState(emptyState(key, false));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
void load(true);
|
|
125
|
+
return () => {
|
|
126
|
+
mountedRef.current = false;
|
|
127
|
+
cancelLoad();
|
|
128
|
+
};
|
|
129
|
+
}, [cancelLoad, enabled, key, load]);
|
|
130
|
+
|
|
131
|
+
useInteractionInvalidation({
|
|
132
|
+
workspaceId,
|
|
133
|
+
key,
|
|
134
|
+
enabled,
|
|
135
|
+
event: workspaceInteractionEvent,
|
|
136
|
+
connectionState: workspaceInteractionConnectionState,
|
|
137
|
+
refresh,
|
|
138
|
+
fallbackPollIntervalMs: options.pollIntervalMs ?? 2_000,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const mutate = useCallback(async <T>(operation: () => Promise<T>): Promise<T> => {
|
|
142
|
+
setMutationCount((count) => count + 1);
|
|
143
|
+
try {
|
|
144
|
+
return await operation();
|
|
145
|
+
} finally {
|
|
146
|
+
if (mountedRef.current) setMutationCount((count) => Math.max(0, count - 1));
|
|
147
|
+
}
|
|
148
|
+
}, []);
|
|
149
|
+
|
|
150
|
+
const merge = useCallback(
|
|
151
|
+
(intervention: InteractionIntervention) => {
|
|
152
|
+
setState((current) => {
|
|
153
|
+
if (current.key !== key) return current;
|
|
154
|
+
const interventions = current.interventions.filter(
|
|
155
|
+
(candidate) => candidate.id !== intervention.id,
|
|
156
|
+
);
|
|
157
|
+
const matches =
|
|
158
|
+
(!resourceKind || intervention.resourceKind === resourceKind) &&
|
|
159
|
+
(!resourceId || intervention.resourceId === resourceId);
|
|
160
|
+
if (matches && (includeSettled || intervention.status === "open")) {
|
|
161
|
+
interventions.push(intervention);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
...current,
|
|
165
|
+
interventions: sortInterventions(interventions),
|
|
166
|
+
error: null,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
[includeSettled, key, resourceId, resourceKind],
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const create = useCallback(
|
|
174
|
+
async (
|
|
175
|
+
request: Omit<CreateInteractionInterventionRequest, "operationId"> & {
|
|
176
|
+
operationId?: string;
|
|
177
|
+
},
|
|
178
|
+
): Promise<InteractionInterventionMutationResponse> =>
|
|
179
|
+
await mutate(async () => {
|
|
180
|
+
const response = await client.createInteractionIntervention(workspaceId, {
|
|
181
|
+
...request,
|
|
182
|
+
operationId: request.operationId ?? crypto.randomUUID(),
|
|
183
|
+
});
|
|
184
|
+
merge(response.intervention);
|
|
185
|
+
return response;
|
|
186
|
+
}),
|
|
187
|
+
[client, merge, mutate, workspaceId],
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const resolve = useCallback(
|
|
191
|
+
async (
|
|
192
|
+
interventionId: string,
|
|
193
|
+
request: Omit<ResolveInteractionInterventionRequest, "operationId"> & {
|
|
194
|
+
operationId?: string;
|
|
195
|
+
},
|
|
196
|
+
): Promise<InteractionInterventionMutationResponse> =>
|
|
197
|
+
await mutate(async () => {
|
|
198
|
+
const response = await client.resolveInteractionIntervention(workspaceId, interventionId, {
|
|
199
|
+
...request,
|
|
200
|
+
operationId: request.operationId ?? crypto.randomUUID(),
|
|
201
|
+
});
|
|
202
|
+
merge(response.intervention);
|
|
203
|
+
return response;
|
|
204
|
+
}),
|
|
205
|
+
[client, merge, mutate, workspaceId],
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
interventions: visible.interventions,
|
|
210
|
+
loading: visible.loading,
|
|
211
|
+
refreshing: visible.refreshing,
|
|
212
|
+
mutating: mutationCount > 0,
|
|
213
|
+
error: visible.error,
|
|
214
|
+
refresh,
|
|
215
|
+
create,
|
|
216
|
+
resolve,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function emptyState(key: string, loading: boolean) {
|
|
221
|
+
return {
|
|
222
|
+
key,
|
|
223
|
+
interventions: [] as InteractionIntervention[],
|
|
224
|
+
loading,
|
|
225
|
+
refreshing: false,
|
|
226
|
+
error: null as Error | null,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function sortInterventions(
|
|
231
|
+
interventions: readonly InteractionIntervention[],
|
|
232
|
+
): InteractionIntervention[] {
|
|
233
|
+
return [...interventions].sort(
|
|
234
|
+
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function asError(cause: unknown): Error {
|
|
239
|
+
return cause instanceof Error ? cause : new Error(String(cause));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type InteractionInterventionsClient = EmbeddedInterventionClientLike;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { StreamConnectionState, WorkspaceInteractionRevisionEvent } from "@opengeni/sdk";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import { usePageLiveActivity } from "./internal";
|
|
4
|
+
|
|
5
|
+
export type InteractionInvalidationOptions = {
|
|
6
|
+
workspaceId: string;
|
|
7
|
+
/** Distinguishes filtered projections of the same workspace catalog. */
|
|
8
|
+
key: string;
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
event: WorkspaceInteractionRevisionEvent | null;
|
|
11
|
+
connectionState: StreamConnectionState | "idle" | "error";
|
|
12
|
+
/** Catalog responses carry the workspace revision; live-state lists do not. */
|
|
13
|
+
knownRevision?: number | undefined;
|
|
14
|
+
refresh: () => Promise<void>;
|
|
15
|
+
fallbackPollIntervalMs?: number | undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Refetch authoritative lists on the shared latest-revision stream. Poll only
|
|
20
|
+
* while that stream is unavailable, preserving standalone-hook degradation
|
|
21
|
+
* without multiplying healthy-provider traffic.
|
|
22
|
+
*/
|
|
23
|
+
export function useInteractionInvalidation(options: InteractionInvalidationOptions): void {
|
|
24
|
+
const {
|
|
25
|
+
workspaceId,
|
|
26
|
+
key,
|
|
27
|
+
enabled,
|
|
28
|
+
event,
|
|
29
|
+
connectionState,
|
|
30
|
+
knownRevision,
|
|
31
|
+
refresh,
|
|
32
|
+
fallbackPollIntervalMs,
|
|
33
|
+
} = options;
|
|
34
|
+
const pageLive = usePageLiveActivity();
|
|
35
|
+
const lastUnversionedEvent = useRef<{ key: string; sequence: number }>({
|
|
36
|
+
key,
|
|
37
|
+
sequence: 0,
|
|
38
|
+
});
|
|
39
|
+
const eventSequence = event?.workspaceId === workspaceId ? event.sequence : 0;
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!enabled || eventSequence <= 0) return;
|
|
43
|
+
if (knownRevision !== undefined) {
|
|
44
|
+
if (eventSequence > knownRevision) void refresh();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (lastUnversionedEvent.current.key !== key) {
|
|
48
|
+
lastUnversionedEvent.current = { key, sequence: 0 };
|
|
49
|
+
}
|
|
50
|
+
if (eventSequence <= lastUnversionedEvent.current.sequence) return;
|
|
51
|
+
lastUnversionedEvent.current = { key, sequence: eventSequence };
|
|
52
|
+
void refresh();
|
|
53
|
+
}, [enabled, eventSequence, key, knownRevision, refresh]);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!enabled || !pageLive || connectionState === "live") return;
|
|
57
|
+
const interval = boundedPollInterval(fallbackPollIntervalMs ?? 3_000);
|
|
58
|
+
const timer = setInterval(() => void refresh(), interval);
|
|
59
|
+
return () => clearInterval(timer);
|
|
60
|
+
}, [connectionState, enabled, fallbackPollIntervalMs, pageLive, refresh]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function boundedPollInterval(value: number): number {
|
|
64
|
+
if (!Number.isFinite(value)) return 3_000;
|
|
65
|
+
return Math.min(60_000, Math.max(1_000, Math.floor(value)));
|
|
66
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import type { MachineState } from "@opengeni/sdk";
|
|
3
3
|
import type { SessionCapabilitiesState } from "./use-session-capabilities";
|
|
4
|
+
import { sandboxAcceptsLiveIo } from "../lib/sandbox-liveness";
|
|
5
|
+
import { connectionStatusForState } from "../types/machines";
|
|
4
6
|
|
|
5
7
|
/** The one truthful machine indicator. Honest staleness beats
|
|
6
8
|
* fake liveness: a cold/asleep box reads "offline — as of <time>", never "live". */
|
|
@@ -53,7 +55,7 @@ export function formatAsOf(capturedAt: string, now: number): string {
|
|
|
53
55
|
* it; the whole point is a deterministic, testable projection).
|
|
54
56
|
*
|
|
55
57
|
* Precedence:
|
|
56
|
-
* 1. warm
|
|
58
|
+
* 1. warm lease → **live** (a holder-confirmed box always wins).
|
|
57
59
|
* 2. actively warming (negotiating, wake-on-edit, or a reconnecting/enrolling
|
|
58
60
|
* machine) → **waking**.
|
|
59
61
|
* 3. everything else (a cold/asleep box, a self-hosted machine that's offline,
|
|
@@ -64,8 +66,18 @@ export function deriveMachineChip(input: DeriveMachineChipInput): MachineChip {
|
|
|
64
66
|
const asOf = input.capturedAt ?? null;
|
|
65
67
|
const offlineLabel = asOf ? `Offline — as of ${formatAsOf(asOf, now)}` : "Offline";
|
|
66
68
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
+
// A connected machine has no Modal group lease, so capability negotiation
|
|
70
|
+
// legitimately reports `cold` until a stream holder is minted. Its fleet
|
|
71
|
+
// probe is the authoritative reachability signal; do not label an online Mac
|
|
72
|
+
// "Offline" merely because there is no unrelated Modal lease.
|
|
73
|
+
const selfhostedOnline =
|
|
74
|
+
input.activeIsSelfhosted === true &&
|
|
75
|
+
input.activeMachineState != null &&
|
|
76
|
+
connectionStatusForState(input.activeMachineState) === "online";
|
|
77
|
+
|
|
78
|
+
// 1. A holder-confirmed warm provider or online connected machine is live.
|
|
79
|
+
// Draining remains teardown-owned and may already have lost its instance.
|
|
80
|
+
if (sandboxAcceptsLiveIo(input.liveness) || selfhostedOnline) {
|
|
69
81
|
return { state: "live", label: "Live", asOf: null };
|
|
70
82
|
}
|
|
71
83
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
RemoveEnrollmentRequest,
|
|
4
|
+
RemoveEnrollmentResponse,
|
|
5
|
+
SwapActiveSandboxResponse,
|
|
6
|
+
} from "@opengeni/sdk";
|
|
3
7
|
import { useOpenGeni, type ClientOverride } from "../provider";
|
|
4
8
|
import { useMutationRunner, usePolledValue } from "./internal";
|
|
5
9
|
import type { MachinesResponse, MachineView, MetricSample } from "../types/machines";
|
|
@@ -46,7 +50,7 @@ export type MachinesClientLike = {
|
|
|
46
50
|
workspaceId: string,
|
|
47
51
|
sessionId: string,
|
|
48
52
|
request: { target: string },
|
|
49
|
-
) => Promise<
|
|
53
|
+
) => Promise<SwapActiveSandboxResponse>;
|
|
50
54
|
/**
|
|
51
55
|
* Host-supplied swap adapter (an escape hatch). When present it wins over the
|
|
52
56
|
* default `swapActiveSandbox` path. Session-scoped, like the swap it backs.
|
|
@@ -99,6 +103,24 @@ export type UseMachinesResult = {
|
|
|
99
103
|
|
|
100
104
|
const EMPTY: MachinesResponse = { activeSandboxId: null, activeEpoch: 0, machines: [] };
|
|
101
105
|
|
|
106
|
+
function swapFailureMessage(result: SwapActiveSandboxResponse): string {
|
|
107
|
+
if (result.reason?.trim()) return result.reason;
|
|
108
|
+
switch (result.code) {
|
|
109
|
+
case "recovery_in_progress":
|
|
110
|
+
return "The managed sandbox is still recovering. Try again shortly.";
|
|
111
|
+
case "recovery_degraded":
|
|
112
|
+
return "The managed sandbox recovery is degraded and needs attention.";
|
|
113
|
+
case "recovery_unrecoverable":
|
|
114
|
+
return "The managed sandbox could not be recovered.";
|
|
115
|
+
case "offline_enrollment":
|
|
116
|
+
return "The selected machine is offline.";
|
|
117
|
+
case "concurrent_swap":
|
|
118
|
+
return "The session route changed concurrently. Refresh and try again.";
|
|
119
|
+
default:
|
|
120
|
+
return "The session stayed on its current sandbox.";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
102
124
|
/**
|
|
103
125
|
* The workspace Machines fleet: the selfhosted enrollments + the session's Modal
|
|
104
126
|
* box, each with latest metrics + state, plus the active-sandbox pointer. Polls
|
|
@@ -171,7 +193,15 @@ export function useMachines(options: UseMachinesOptions = {}): UseMachinesResult
|
|
|
171
193
|
const ownedIdentity = identityKey;
|
|
172
194
|
setAttachState({ identity: ownedIdentity, sandboxId });
|
|
173
195
|
const result = await run(async () => {
|
|
174
|
-
await runSwap();
|
|
196
|
+
const response = await runSwap();
|
|
197
|
+
if (
|
|
198
|
+
response &&
|
|
199
|
+
typeof response === "object" &&
|
|
200
|
+
"swapped" in response &&
|
|
201
|
+
response.swapped === false
|
|
202
|
+
) {
|
|
203
|
+
throw new Error(swapFailureMessage(response as SwapActiveSandboxResponse));
|
|
204
|
+
}
|
|
175
205
|
return true;
|
|
176
206
|
});
|
|
177
207
|
if (identityRef.current === ownedIdentity) {
|