@opengeni/react 1.4.0 → 1.4.4
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 +8 -0
- package/dist/accounts.js +2 -0
- package/dist/accounts.js.map +1 -1
- package/dist/artifacts-document.js +5 -4
- package/dist/artifacts-presentation.js +5 -4
- package/dist/artifacts-spreadsheet.js +4 -3
- package/dist/artifacts.js +14 -13
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-5EEAS3IU.js +17 -0
- package/dist/chat-interactive-block-ZFHOKTMS.js +12 -0
- package/dist/{chunk-WEHMPOXK.js → chunk-2DDJ6YH6.js} +3 -1
- package/dist/{chunk-WEHMPOXK.js.map → chunk-2DDJ6YH6.js.map} +1 -1
- package/dist/{chunk-C5X5AJXK.js → chunk-2HVLQ26H.js} +5 -4
- package/dist/{chunk-C5X5AJXK.js.map → chunk-2HVLQ26H.js.map} +1 -1
- package/dist/{chunk-HDJWDK7W.js → chunk-2OQA3CPB.js} +300 -166
- package/dist/{chunk-HDJWDK7W.js.map → chunk-2OQA3CPB.js.map} +1 -1
- package/dist/{chunk-A4UJB2MM.js → chunk-32CGLXW2.js} +4 -3
- package/dist/{chunk-A4UJB2MM.js.map → chunk-32CGLXW2.js.map} +1 -1
- package/dist/{chunk-5CVXZA3Y.js → chunk-45NUOSXQ.js} +3 -2
- package/dist/{chunk-5CVXZA3Y.js.map → chunk-45NUOSXQ.js.map} +1 -1
- package/dist/{chunk-WUISZ3WU.js → chunk-53KJBGPZ.js} +5 -4
- package/dist/{chunk-WUISZ3WU.js.map → chunk-53KJBGPZ.js.map} +1 -1
- package/dist/{chunk-MBMPHOMA.js → chunk-55DXD22O.js} +3 -2
- package/dist/{chunk-MBMPHOMA.js.map → chunk-55DXD22O.js.map} +1 -1
- package/dist/{chunk-JT4JW7BC.js → chunk-5I4VRWBB.js} +3 -1
- package/dist/{chunk-JT4JW7BC.js.map → chunk-5I4VRWBB.js.map} +1 -1
- package/dist/{chunk-UK4TINKT.js → chunk-B27E3V37.js} +3 -1
- package/dist/{chunk-UK4TINKT.js.map → chunk-B27E3V37.js.map} +1 -1
- package/dist/{chunk-J6DBS4T3.js → chunk-BNKV5ZLF.js} +5 -4
- package/dist/{chunk-J6DBS4T3.js.map → chunk-BNKV5ZLF.js.map} +1 -1
- package/dist/{chunk-YM2DE7BQ.js → chunk-C5BF4L26.js} +13 -12
- package/dist/{chunk-YM2DE7BQ.js.map → chunk-C5BF4L26.js.map} +1 -1
- package/dist/{chunk-YBXQDQFX.js → chunk-C7CMD56T.js} +6 -5
- package/dist/{chunk-YBXQDQFX.js.map → chunk-C7CMD56T.js.map} +1 -1
- package/dist/{chunk-UGLEYEMP.js → chunk-CM7LVUKC.js} +3 -1
- package/dist/{chunk-UGLEYEMP.js.map → chunk-CM7LVUKC.js.map} +1 -1
- package/dist/{chunk-JVYQRRC4.js → chunk-CVYCLFMH.js} +3 -1
- package/dist/{chunk-JVYQRRC4.js.map → chunk-CVYCLFMH.js.map} +1 -1
- package/dist/{chunk-LJ2UTJJE.js → chunk-EAOSEIXB.js} +182 -97
- package/dist/chunk-EAOSEIXB.js.map +1 -0
- package/dist/{chunk-Y2PRMYLC.js → chunk-G3U2A6GS.js} +3 -2
- package/dist/{chunk-Y2PRMYLC.js.map → chunk-G3U2A6GS.js.map} +1 -1
- package/dist/{chunk-WZMYFJD3.js → chunk-G6YRCIYJ.js} +16 -7
- package/dist/{chunk-WZMYFJD3.js.map → chunk-G6YRCIYJ.js.map} +1 -1
- package/dist/{chunk-B3V6TG77.js → chunk-GC3PH6N7.js} +3 -1
- package/dist/{chunk-B3V6TG77.js.map → chunk-GC3PH6N7.js.map} +1 -1
- package/dist/{chunk-CJUO7J5Z.js → chunk-HXYOSSQO.js} +3 -2
- package/dist/{chunk-CJUO7J5Z.js.map → chunk-HXYOSSQO.js.map} +1 -1
- package/dist/{chunk-GYVRFZDY.js → chunk-I6OY3MWC.js} +310 -115
- package/dist/chunk-I6OY3MWC.js.map +1 -0
- package/dist/{chunk-BRM6FVZA.js → chunk-IEE7WCLN.js} +4 -3
- package/dist/{chunk-BRM6FVZA.js.map → chunk-IEE7WCLN.js.map} +1 -1
- package/dist/{chunk-DYWBUX5Q.js → chunk-IMSUVYQB.js} +6 -5
- package/dist/{chunk-DYWBUX5Q.js.map → chunk-IMSUVYQB.js.map} +1 -1
- package/dist/{chunk-LN6TCXD3.js → chunk-IUHHPMAX.js} +184 -48
- package/dist/chunk-IUHHPMAX.js.map +1 -0
- package/dist/{chunk-SGKD2OCN.js → chunk-IYQCWGU5.js} +6 -5
- package/dist/{chunk-SGKD2OCN.js.map → chunk-IYQCWGU5.js.map} +1 -1
- package/dist/{chunk-J2QWVSQY.js → chunk-JH7V627O.js} +8 -7
- package/dist/{chunk-J2QWVSQY.js.map → chunk-JH7V627O.js.map} +1 -1
- package/dist/{chunk-EESD3PY3.js → chunk-JHTIMBOL.js} +6 -5
- package/dist/{chunk-EESD3PY3.js.map → chunk-JHTIMBOL.js.map} +1 -1
- package/dist/{chunk-LNIRAOA3.js → chunk-L2J3PU7A.js} +7 -6
- package/dist/{chunk-LNIRAOA3.js.map → chunk-L2J3PU7A.js.map} +1 -1
- package/dist/{chunk-ZYRGEKWS.js → chunk-LJUMYUBO.js} +3 -1
- package/dist/{chunk-ZYRGEKWS.js.map → chunk-LJUMYUBO.js.map} +1 -1
- package/dist/{chunk-4RVYOY4O.js → chunk-LSW2OJVV.js} +4 -3
- package/dist/{chunk-4RVYOY4O.js.map → chunk-LSW2OJVV.js.map} +1 -1
- package/dist/{chunk-UTRTG6LH.js → chunk-LVS3MH5T.js} +3 -1
- package/dist/{chunk-UTRTG6LH.js.map → chunk-LVS3MH5T.js.map} +1 -1
- package/dist/chunk-M357MHH3.js +2 -0
- package/dist/{chunk-WRHIZYZF.js → chunk-MEQM4LI3.js} +6 -5
- package/dist/{chunk-WRHIZYZF.js.map → chunk-MEQM4LI3.js.map} +1 -1
- package/dist/chunk-NX4ADH73.js +14 -0
- package/dist/chunk-NX4ADH73.js.map +1 -0
- package/dist/{chunk-XCHWDJ22.js → chunk-OGTTUYNT.js} +3 -1
- package/dist/{chunk-XCHWDJ22.js.map → chunk-OGTTUYNT.js.map} +1 -1
- package/dist/{chunk-GIMRELBW.js → chunk-OYQQJL3J.js} +3 -1
- package/dist/{chunk-GIMRELBW.js.map → chunk-OYQQJL3J.js.map} +1 -1
- package/dist/{chunk-RSI7XYXL.js → chunk-PBCZL4JP.js} +6 -5
- package/dist/{chunk-RSI7XYXL.js.map → chunk-PBCZL4JP.js.map} +1 -1
- package/dist/{chunk-I7IWRQLN.js → chunk-PHBFJCEU.js} +17 -11
- package/dist/chunk-PHBFJCEU.js.map +1 -0
- package/dist/{chunk-3CTRFMAK.js → chunk-PKCAYHDC.js} +5 -4
- package/dist/{chunk-3CTRFMAK.js.map → chunk-PKCAYHDC.js.map} +1 -1
- package/dist/{chunk-K3HQAKQX.js → chunk-PMV53IMM.js} +3 -1
- package/dist/{chunk-K3HQAKQX.js.map → chunk-PMV53IMM.js.map} +1 -1
- package/dist/{chunk-YQEAA6LT.js → chunk-QHUE7E4N.js} +3 -2
- package/dist/{chunk-YQEAA6LT.js.map → chunk-QHUE7E4N.js.map} +1 -1
- package/dist/{chunk-OA6HFRQP.js → chunk-QMMCJGFI.js} +4 -3
- package/dist/{chunk-OA6HFRQP.js.map → chunk-QMMCJGFI.js.map} +1 -1
- package/dist/{chunk-YV4L3ILF.js → chunk-QV3DJVRZ.js} +3 -1
- package/dist/{chunk-YV4L3ILF.js.map → chunk-QV3DJVRZ.js.map} +1 -1
- package/dist/{chunk-447JLCBU.js → chunk-S2QH6EZ5.js} +3 -1
- package/dist/{chunk-447JLCBU.js.map → chunk-S2QH6EZ5.js.map} +1 -1
- package/dist/{chunk-PNY3JKBJ.js → chunk-SFH43JT2.js} +5 -4
- package/dist/{chunk-PNY3JKBJ.js.map → chunk-SFH43JT2.js.map} +1 -1
- package/dist/{chunk-AWZLIMKX.js → chunk-T46QZH3E.js} +3 -1
- package/dist/{chunk-AWZLIMKX.js.map → chunk-T46QZH3E.js.map} +1 -1
- package/dist/{chunk-5J4FOW6Q.js → chunk-TNEZNMSN.js} +3 -1
- package/dist/{chunk-5J4FOW6Q.js.map → chunk-TNEZNMSN.js.map} +1 -1
- package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
- package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
- package/dist/{chunk-H5X55XH7.js → chunk-UDPH5SLV.js} +4 -3
- package/dist/{chunk-H5X55XH7.js.map → chunk-UDPH5SLV.js.map} +1 -1
- package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
- package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
- package/dist/{chunk-5F4WR7X7.js → chunk-XS67YG5R.js} +3 -1
- package/dist/{chunk-5F4WR7X7.js.map → chunk-XS67YG5R.js.map} +1 -1
- package/dist/{chunk-TKOTKRWY.js → chunk-XTD7TZHW.js} +3 -1
- package/dist/{chunk-TKOTKRWY.js.map → chunk-XTD7TZHW.js.map} +1 -1
- package/dist/{chunk-A2PI6UMY.js → chunk-YSRBFJMQ.js} +3 -1
- package/dist/{chunk-A2PI6UMY.js.map → chunk-YSRBFJMQ.js.map} +1 -1
- package/dist/chunk-YVFRBXST.js +1420 -0
- package/dist/chunk-YVFRBXST.js.map +1 -0
- package/dist/{chunk-SCOTPE2I.js → chunk-YYLBXXTE.js} +3 -1
- package/dist/{chunk-SCOTPE2I.js.map → chunk-YYLBXXTE.js.map} +1 -1
- package/dist/{chunk-RHREZ7BO.js → chunk-ZUWF4NF7.js} +3 -1
- package/dist/{chunk-RHREZ7BO.js.map → chunk-ZUWF4NF7.js.map} +1 -1
- package/dist/clipboard.js +3 -2
- package/dist/components/composer-transcription-control.d.ts +4 -0
- package/dist/components/session-conversation.d.ts +6 -0
- package/dist/composer-voice-events.d.ts +8 -0
- package/dist/composer.js +15 -13
- package/dist/computer-viewer-ELNLZGHI.js +20 -0
- package/dist/connect.js +15 -12
- package/dist/connect.js.map +1 -1
- package/dist/desktop.js +6 -5
- package/dist/desktop.js.map +1 -1
- package/dist/diffs.js +2 -1
- package/dist/diffs.js.map +1 -1
- package/dist/editor.js +4 -3
- package/dist/editor.js.map +1 -1
- package/dist/embedded-voice-NIL4RDX3.js +69 -0
- package/dist/embedded-voice-NIL4RDX3.js.map +1 -0
- package/dist/{fleet-decision-row-YZ3ZKWMM.js → fleet-decision-row-SI63O5V4.js} +3 -1
- package/dist/{fleet-decision-row-YZ3ZKWMM.js.map → fleet-decision-row-SI63O5V4.js.map} +1 -1
- package/dist/hooks/use-client-config-flags.d.ts +6 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-realtime-voice-models.d.ts +9 -0
- package/dist/hooks/use-voice-input.d.ts +8 -1
- package/dist/index.js +47 -45
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +14 -13
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-GPYM2JNU.js → latest-question-2POFPYNB.js} +5 -4
- package/dist/{latest-question-GPYM2JNU.js.map → latest-question-2POFPYNB.js.map} +1 -1
- package/dist/machines.js +8 -7
- package/dist/{markdown-table-layout-QXSLK4BY.js → markdown-table-layout-DQ4DRNVT.js} +3 -1
- package/dist/{markdown-table-layout-QXSLK4BY.js.map → markdown-table-layout-DQ4DRNVT.js.map} +1 -1
- package/dist/model-policy-picker-menu-WRNSRFQC.js +15 -0
- package/dist/model-policy.js +2 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
- package/dist/{platform-activity-row-627BFZ46.js → platform-activity-row-PJJVATTZ.js} +3 -1
- package/dist/{platform-activity-row-627BFZ46.js.map → platform-activity-row-PJJVATTZ.js.map} +1 -1
- package/dist/{queue-surface-implementation-ZHFI5JBP.js → queue-surface-implementation-67FS7FUI.js} +10 -9
- package/dist/{queue-surface-implementation-ZHFI5JBP.js.map → queue-surface-implementation-67FS7FUI.js.map} +1 -1
- package/dist/realtime/embedded-voice.d.ts +20 -0
- package/dist/realtime.js +22 -1353
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-34CJJNOY.js +21 -0
- package/dist/session-ui.js +32 -30
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +10 -9
- package/dist/sites.js +2 -1
- package/dist/terminal.js +6 -5
- package/dist/terminal.js.map +1 -1
- package/dist/{timeline-annotation-markers-GNDHTGBD.js → timeline-annotation-markers-U3G66KUD.js} +5 -4
- package/dist/{timeline-annotation-markers-GNDHTGBD.js.map → timeline-annotation-markers-U3G66KUD.js.map} +1 -1
- package/dist/{timeline-annotation-selection-DMLMM2TX.js → timeline-annotation-selection-BI7QTPTZ.js} +4 -3
- package/dist/{timeline-annotation-selection-DMLMM2TX.js.map → timeline-annotation-selection-BI7QTPTZ.js.map} +1 -1
- package/dist/{timeline-annotations-dialog-ELRDBHTS.js → timeline-annotations-dialog-FOMHSDTE.js} +7 -6
- package/dist/{timeline-annotations-dialog-ELRDBHTS.js.map → timeline-annotations-dialog-FOMHSDTE.js.map} +1 -1
- package/dist/usage.js +6 -5
- package/dist/usage.js.map +1 -1
- package/dist/voice-recording-owner.d.ts +7 -0
- package/package.json +3 -3
- package/src/components/composer-transcription-control.tsx +109 -27
- package/src/components/computer-viewer.tsx +139 -30
- package/src/components/markdown.tsx +7 -2
- package/src/components/message-timeline.tsx +10 -1
- package/src/components/session-conversation.tsx +44 -0
- package/src/components/session-list.tsx +1 -1
- package/src/components/session-proxy-scope.tsx +26 -5
- package/src/composer-voice-events.ts +12 -0
- package/src/connection-discovery.tsx +4 -1
- package/src/hooks/use-browser-session.ts +445 -168
- package/src/hooks/use-client-config-flags.ts +8 -0
- package/src/hooks/use-computer-session.ts +334 -98
- package/src/hooks/use-goal.ts +12 -1
- package/src/hooks/use-realtime-voice-models.ts +67 -0
- package/src/hooks/use-voice-input.ts +109 -18
- package/src/realtime/embedded-voice.tsx +81 -0
- package/src/realtime/realtime-control.tsx +82 -17
- package/src/voice-recording-owner.ts +22 -0
- package/styles/compiled.css +77 -18
- package/styles/index.css +49 -0
- package/styles/responsive.css +61 -4
- package/styles/session-capability.css +1 -1
- package/dist/browser-viewer-SHYKQRNX.js +0 -16
- package/dist/chat-interactive-block-UGBTNB3L.js +0 -11
- package/dist/chunk-GYVRFZDY.js.map +0 -1
- package/dist/chunk-I7IWRQLN.js.map +0 -1
- package/dist/chunk-IWIDV7SB.js +0 -1
- package/dist/chunk-LJ2UTJJE.js.map +0 -1
- package/dist/chunk-LN6TCXD3.js.map +0 -1
- package/dist/computer-viewer-6SRGWRUX.js +0 -19
- package/dist/model-policy-picker-menu-DXHUL73H.js +0 -14
- package/dist/sandbox-files-GORZOUUJ.js +0 -20
- /package/dist/{browser-viewer-SHYKQRNX.js.map → browser-viewer-5EEAS3IU.js.map} +0 -0
- /package/dist/{chat-interactive-block-UGBTNB3L.js.map → chat-interactive-block-ZFHOKTMS.js.map} +0 -0
- /package/dist/{chunk-IWIDV7SB.js.map → chunk-M357MHH3.js.map} +0 -0
- /package/dist/{computer-viewer-6SRGWRUX.js.map → computer-viewer-ELNLZGHI.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-DXHUL73H.js.map → model-policy-picker-menu-WRNSRFQC.js.map} +0 -0
- /package/dist/{sandbox-files-GORZOUUJ.js.map → sandbox-files-34CJJNOY.js.map} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import {
|
|
2
3
|
InteractionInterventionBanner,
|
|
3
4
|
isInteractionControlUnavailable,
|
|
@@ -6,13 +7,13 @@ import {
|
|
|
6
7
|
useInteractionInterventions,
|
|
7
8
|
useInteractionInvalidation,
|
|
8
9
|
useViewerMenuDismiss
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BNKV5ZLF.js";
|
|
10
11
|
import {
|
|
11
12
|
copyTextToClipboard
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CVYCLFMH.js";
|
|
13
14
|
import {
|
|
14
15
|
DesktopViewer
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-IMSUVYQB.js";
|
|
16
17
|
import {
|
|
17
18
|
STREAM_CLOSE_REASON_NORMAL,
|
|
18
19
|
STREAM_KIND_COMPUTER,
|
|
@@ -21,16 +22,16 @@ import {
|
|
|
21
22
|
decodeStreamOpenAck,
|
|
22
23
|
encodeStreamClose,
|
|
23
24
|
encodeStreamOpen
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-TNEZNMSN.js";
|
|
25
26
|
import {
|
|
26
27
|
usePageLiveActivity
|
|
27
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-QHUE7E4N.js";
|
|
28
29
|
import {
|
|
29
30
|
useEmbeddedComputerInteraction
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-XTD7TZHW.js";
|
|
31
32
|
import {
|
|
32
33
|
cn
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-TXS65BB2.js";
|
|
34
35
|
|
|
35
36
|
// src/components/computer-viewer.tsx
|
|
36
37
|
import { interactionControlFailureFromError } from "@opengeni/sdk/interaction";
|
|
@@ -407,27 +408,49 @@ function isPlacementGenerationLossError(cause) {
|
|
|
407
408
|
|
|
408
409
|
// src/hooks/use-computer-session.ts
|
|
409
410
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
411
|
+
var actionResultFences = /* @__PURE__ */ new WeakMap();
|
|
412
|
+
function isStaleComputerActionResult(value) {
|
|
413
|
+
return typeof value === "object" && value !== null && actionResultFences.get(value)?.() === false;
|
|
414
|
+
}
|
|
415
|
+
function computerActionReceiptError(receipt) {
|
|
416
|
+
const error = new Error(receipt.error?.message ?? "Desktop input did not complete.");
|
|
417
|
+
const fence = actionResultFences.get(receipt);
|
|
418
|
+
if (fence) actionResultFences.set(error, fence);
|
|
419
|
+
return error;
|
|
420
|
+
}
|
|
410
421
|
function useComputerSession(options) {
|
|
411
422
|
const { client, workspaceId } = useEmbeddedComputerInteraction(options);
|
|
412
423
|
const computerSessionId = options.computerSessionId;
|
|
413
424
|
const enabled = (options.enabled ?? true) && computerSessionId !== null;
|
|
425
|
+
const sourceRef = useRef2({
|
|
426
|
+
client,
|
|
427
|
+
workspaceId,
|
|
428
|
+
computerSessionId,
|
|
429
|
+
enabled
|
|
430
|
+
});
|
|
431
|
+
if (sourceRef.current.client !== client || sourceRef.current.workspaceId !== workspaceId || sourceRef.current.computerSessionId !== computerSessionId || sourceRef.current.enabled !== enabled) {
|
|
432
|
+
sourceRef.current = { client, workspaceId, computerSessionId, enabled };
|
|
433
|
+
}
|
|
434
|
+
const source = sourceRef.current;
|
|
414
435
|
const pageLive = usePageLiveActivity();
|
|
415
436
|
const pollIntervalMs = Math.max(750, options.pollIntervalMs ?? 2e3);
|
|
416
|
-
const [state, setState] = useState2(
|
|
417
|
-
|
|
418
|
-
);
|
|
419
|
-
const visible = state.computerSessionId === computerSessionId ? state : emptyState(computerSessionId, enabled);
|
|
437
|
+
const [state, setState] = useState2(() => emptyState(source, enabled));
|
|
438
|
+
const visible = state.source === source ? state : emptyState(source, enabled);
|
|
420
439
|
const refreshBlocked = isNonRetryableInteractionError(visible.error);
|
|
421
440
|
const selectedTargetIdRef = useRef2(visible.selectedTargetId);
|
|
422
441
|
selectedTargetIdRef.current = visible.selectedTargetId;
|
|
442
|
+
const selectionRevisionRef = useRef2(0);
|
|
443
|
+
const actionResultOrderRef = useRef2({ next: 0, settled: 0 });
|
|
423
444
|
const targetsRef = useRef2({
|
|
445
|
+
source,
|
|
424
446
|
computerSessionId,
|
|
425
447
|
targets: visible.targets
|
|
426
448
|
});
|
|
427
|
-
const observationRef = useRef2({ computerSessionId, observation: visible.observation });
|
|
428
|
-
if (targetsRef.current.
|
|
429
|
-
targetsRef.current = { computerSessionId, targets: visible.targets };
|
|
449
|
+
const observationRef = useRef2({ source, computerSessionId, observation: visible.observation });
|
|
450
|
+
if (targetsRef.current.source !== source) {
|
|
451
|
+
targetsRef.current = { source, computerSessionId, targets: visible.targets };
|
|
430
452
|
observationRef.current = {
|
|
453
|
+
source,
|
|
431
454
|
computerSessionId,
|
|
432
455
|
observation: visible.observation
|
|
433
456
|
};
|
|
@@ -441,16 +464,21 @@ function useComputerSession(options) {
|
|
|
441
464
|
});
|
|
442
465
|
const mutationRef = useRef2({
|
|
443
466
|
computerSessionId,
|
|
467
|
+
source,
|
|
444
468
|
count: 0
|
|
445
469
|
});
|
|
446
470
|
const mountedRef = useRef2(true);
|
|
471
|
+
const isCurrentSource = useCallback2(
|
|
472
|
+
() => mountedRef.current && sourceRef.current === source,
|
|
473
|
+
[source]
|
|
474
|
+
);
|
|
447
475
|
const invalidateRefresh = useCallback2(() => {
|
|
448
476
|
const id = requestRef.current.id + 1;
|
|
449
477
|
requestRef.current.controller?.abort();
|
|
450
478
|
requestRef.current = { id, controller: null };
|
|
451
479
|
}, []);
|
|
452
480
|
const refresh = useCallback2(async () => {
|
|
453
|
-
if (!enabled || !computerSessionId) return;
|
|
481
|
+
if (!enabled || !computerSessionId || !isCurrentSource()) return;
|
|
454
482
|
const id = requestRef.current.id + 1;
|
|
455
483
|
requestRef.current.controller?.abort();
|
|
456
484
|
const controller = new AbortController();
|
|
@@ -466,16 +494,16 @@ function useComputerSession(options) {
|
|
|
466
494
|
})
|
|
467
495
|
]);
|
|
468
496
|
discoveryCompleted = true;
|
|
469
|
-
if (!
|
|
497
|
+
if (!isCurrentSource() || requestRef.current.id !== id) return;
|
|
470
498
|
const targets = sortComputerTargets(targetResponse.targets);
|
|
471
499
|
const selected = chooseTarget(targets, selectedTargetIdRef.current);
|
|
472
|
-
const previous = observationRef.current.observation;
|
|
500
|
+
const previous = observationRef.current.source === source ? observationRef.current.observation : null;
|
|
473
501
|
const retainedObservation = selected && previous?.target.id === selected.id && previous.target.controllerGeneration === selected.controllerGeneration && previous.target.targetGeneration === selected.targetGeneration ? previous : null;
|
|
474
502
|
selectedTargetIdRef.current = selected?.id ?? null;
|
|
475
|
-
targetsRef.current = { computerSessionId, targets };
|
|
476
|
-
observationRef.current = { computerSessionId, observation: retainedObservation };
|
|
503
|
+
targetsRef.current = { source, computerSessionId, targets };
|
|
504
|
+
observationRef.current = { source, computerSessionId, observation: retainedObservation };
|
|
477
505
|
setState(
|
|
478
|
-
(current) => current.computerSessionId === computerSessionId ? {
|
|
506
|
+
(current) => isCurrentSource() && current.source === source && current.computerSessionId === computerSessionId ? {
|
|
479
507
|
...current,
|
|
480
508
|
session,
|
|
481
509
|
targets,
|
|
@@ -489,12 +517,12 @@ function useComputerSession(options) {
|
|
|
489
517
|
const observation = selected ? await client.observeComputerTarget(workspaceId, computerSessionId, selected.id, {
|
|
490
518
|
signal: controller.signal
|
|
491
519
|
}) : null;
|
|
492
|
-
if (!
|
|
520
|
+
if (!isCurrentSource() || requestRef.current.id !== id) return;
|
|
493
521
|
selectedTargetIdRef.current = selected?.id ?? null;
|
|
494
|
-
targetsRef.current = { computerSessionId, targets };
|
|
495
|
-
observationRef.current = { computerSessionId, observation };
|
|
522
|
+
targetsRef.current = { source, computerSessionId, targets };
|
|
523
|
+
observationRef.current = { source, computerSessionId, observation };
|
|
496
524
|
setState(
|
|
497
|
-
(current) => current.computerSessionId === computerSessionId ? {
|
|
525
|
+
(current) => isCurrentSource() && current.source === source && current.computerSessionId === computerSessionId ? {
|
|
498
526
|
...current,
|
|
499
527
|
session,
|
|
500
528
|
targets,
|
|
@@ -505,10 +533,10 @@ function useComputerSession(options) {
|
|
|
505
533
|
} : current
|
|
506
534
|
);
|
|
507
535
|
} catch (cause) {
|
|
508
|
-
if (controller.signal.aborted || !
|
|
536
|
+
if (controller.signal.aborted || !isCurrentSource() || requestRef.current.id !== id) return;
|
|
509
537
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
510
538
|
setState(
|
|
511
|
-
(current) => current.computerSessionId === computerSessionId ? {
|
|
539
|
+
(current) => isCurrentSource() && current.source === source && current.computerSessionId === computerSessionId ? {
|
|
512
540
|
...current,
|
|
513
541
|
loading: false,
|
|
514
542
|
error,
|
|
@@ -521,23 +549,27 @@ function useComputerSession(options) {
|
|
|
521
549
|
} finally {
|
|
522
550
|
if (requestRef.current.id === id) requestRef.current = { id, controller: null };
|
|
523
551
|
}
|
|
524
|
-
}, [client, computerSessionId, enabled, workspaceId]);
|
|
552
|
+
}, [client, computerSessionId, enabled, isCurrentSource, source, workspaceId]);
|
|
525
553
|
useEffect2(() => {
|
|
526
554
|
mountedRef.current = true;
|
|
527
555
|
if (!enabled) {
|
|
528
556
|
invalidateRefresh();
|
|
529
|
-
setState(emptyState(
|
|
557
|
+
setState(emptyState(source, false));
|
|
530
558
|
return;
|
|
531
559
|
}
|
|
532
560
|
setState(
|
|
533
|
-
(current) => current.
|
|
561
|
+
(current) => current.source === source ? {
|
|
562
|
+
...current,
|
|
563
|
+
loading: true,
|
|
564
|
+
mutating: mutationRef.current.source === source && mutationRef.current.count > 0
|
|
565
|
+
} : emptyState(source, true)
|
|
534
566
|
);
|
|
535
567
|
void refresh();
|
|
536
568
|
return () => {
|
|
537
569
|
mountedRef.current = false;
|
|
538
570
|
invalidateRefresh();
|
|
539
571
|
};
|
|
540
|
-
}, [computerSessionId, enabled, invalidateRefresh, refresh]);
|
|
572
|
+
}, [computerSessionId, enabled, invalidateRefresh, refresh, source]);
|
|
541
573
|
useEffect2(() => {
|
|
542
574
|
if (!enabled || !pageLive || visible.mutating || refreshBlocked) return;
|
|
543
575
|
const timer = setInterval(() => {
|
|
@@ -560,55 +592,48 @@ function useComputerSession(options) {
|
|
|
560
592
|
}, [client, computerSessionId, enabled, pageLive, refresh, refreshBlocked, workspaceId]);
|
|
561
593
|
const runMutation = useCallback2(
|
|
562
594
|
async (scopeComputerSessionId, operation) => {
|
|
595
|
+
if (!isCurrentSource()) throw new Error("The desktop source is no longer selected.");
|
|
563
596
|
invalidateRefresh();
|
|
564
|
-
if (mutationRef.current.computerSessionId !== scopeComputerSessionId) {
|
|
597
|
+
if (mutationRef.current.computerSessionId !== scopeComputerSessionId || mutationRef.current.source !== source) {
|
|
565
598
|
mutationRef.current = {
|
|
566
599
|
computerSessionId: scopeComputerSessionId,
|
|
600
|
+
source,
|
|
567
601
|
count: 0
|
|
568
602
|
};
|
|
569
603
|
}
|
|
570
604
|
mutationRef.current.count += 1;
|
|
571
605
|
setState(
|
|
572
|
-
(current) => current.computerSessionId === scopeComputerSessionId ? { ...current, mutating: true, error: null } : current
|
|
606
|
+
(current) => isCurrentSource() && current.source === source && current.computerSessionId === scopeComputerSessionId ? { ...current, mutating: true, error: null } : current
|
|
573
607
|
);
|
|
574
608
|
try {
|
|
575
|
-
|
|
576
|
-
setState(
|
|
577
|
-
(current) => current.computerSessionId === scopeComputerSessionId ? { ...current, controlError: null } : current
|
|
578
|
-
);
|
|
579
|
-
return result;
|
|
580
|
-
} catch (cause) {
|
|
581
|
-
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
582
|
-
setState(
|
|
583
|
-
(current) => current.computerSessionId === scopeComputerSessionId ? {
|
|
584
|
-
...current,
|
|
585
|
-
error,
|
|
586
|
-
controlError: isInteractionControlUnavailable(error) ? error : current.controlError
|
|
587
|
-
} : current
|
|
588
|
-
);
|
|
589
|
-
throw error;
|
|
609
|
+
return await operation();
|
|
590
610
|
} finally {
|
|
591
|
-
if (mutationRef.current.computerSessionId === scopeComputerSessionId) {
|
|
611
|
+
if (mutationRef.current.computerSessionId === scopeComputerSessionId && mutationRef.current.source === source) {
|
|
592
612
|
mutationRef.current.count = Math.max(0, mutationRef.current.count - 1);
|
|
593
613
|
const mutating = mutationRef.current.count > 0;
|
|
594
614
|
setState(
|
|
595
|
-
(current) => current.computerSessionId === scopeComputerSessionId ? { ...current, mutating } : current
|
|
615
|
+
(current) => isCurrentSource() && current.source === source && current.computerSessionId === scopeComputerSessionId ? { ...current, mutating } : current
|
|
596
616
|
);
|
|
597
617
|
}
|
|
598
618
|
}
|
|
599
619
|
},
|
|
600
|
-
[invalidateRefresh]
|
|
620
|
+
[invalidateRefresh, isCurrentSource, source]
|
|
601
621
|
);
|
|
602
622
|
const selectTarget = useCallback2(
|
|
603
623
|
async (targetId) => {
|
|
604
624
|
if (!computerSessionId) throw new Error("No desktop is selected.");
|
|
605
|
-
|
|
625
|
+
if (!isCurrentSource()) throw new Error("The desktop source is no longer selected.");
|
|
626
|
+
const target = targetsRef.current.source === source ? targetsRef.current.targets.find((candidate) => candidate.id === targetId) : null;
|
|
606
627
|
if (!target) throw new Error("The selected desktop target is no longer available.");
|
|
607
628
|
invalidateRefresh();
|
|
629
|
+
const readRequestId = requestRef.current.id;
|
|
630
|
+
const selectionRevision = ++selectionRevisionRef.current;
|
|
631
|
+
const isCurrentSelection = () => isCurrentSource() && selectionRevisionRef.current === selectionRevision;
|
|
632
|
+
const isCurrentSelectionRead = () => isCurrentSelection() && requestRef.current.id === readRequestId;
|
|
608
633
|
selectedTargetIdRef.current = target.id;
|
|
609
|
-
observationRef.current = { computerSessionId, observation: null };
|
|
634
|
+
observationRef.current = { source, computerSessionId, observation: null };
|
|
610
635
|
setState(
|
|
611
|
-
(current) => current.computerSessionId === computerSessionId ? {
|
|
636
|
+
(current) => isCurrentSelection() && current.source === source && current.computerSessionId === computerSessionId ? {
|
|
612
637
|
...current,
|
|
613
638
|
selectedTargetId: target.id,
|
|
614
639
|
observation: null,
|
|
@@ -622,37 +647,40 @@ function useComputerSession(options) {
|
|
|
622
647
|
computerSessionId,
|
|
623
648
|
target.id
|
|
624
649
|
);
|
|
625
|
-
if (selectedTargetIdRef.current !== target.id) return target;
|
|
626
|
-
observationRef.current = { computerSessionId, observation };
|
|
650
|
+
if (!isCurrentSelectionRead() || selectedTargetIdRef.current !== target.id) return target;
|
|
651
|
+
observationRef.current = { source, computerSessionId, observation };
|
|
627
652
|
setState(
|
|
628
|
-
(current) => current.computerSessionId === computerSessionId && current.selectedTargetId === target.id ? { ...current, observation, loading: false, error: null } : current
|
|
653
|
+
(current) => isCurrentSelection() && current.source === source && current.computerSessionId === computerSessionId && current.selectedTargetId === target.id ? { ...current, observation, loading: false, error: null } : current
|
|
629
654
|
);
|
|
630
655
|
return observation.target;
|
|
631
656
|
} catch (cause) {
|
|
632
657
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
633
|
-
|
|
634
|
-
(
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
658
|
+
if (isCurrentSelectionRead()) {
|
|
659
|
+
setState(
|
|
660
|
+
(current) => isCurrentSelection() && current.source === source && current.computerSessionId === computerSessionId && current.selectedTargetId === target.id ? {
|
|
661
|
+
...current,
|
|
662
|
+
loading: false,
|
|
663
|
+
error,
|
|
664
|
+
controlError: isInteractionControlUnavailable(error, "observation") ? error : current.controlError
|
|
665
|
+
} : current
|
|
666
|
+
);
|
|
667
|
+
}
|
|
641
668
|
throw error;
|
|
642
669
|
}
|
|
643
670
|
},
|
|
644
|
-
[client, computerSessionId, invalidateRefresh, workspaceId]
|
|
671
|
+
[client, computerSessionId, invalidateRefresh, isCurrentSource, source, workspaceId]
|
|
645
672
|
);
|
|
646
673
|
const dispatchAction = useCallback2(
|
|
647
674
|
async (action, operationId, frame) => {
|
|
648
675
|
if (!computerSessionId) throw new Error("No desktop is selected.");
|
|
676
|
+
if (!isCurrentSource()) throw new Error("The desktop source is no longer selected.");
|
|
649
677
|
if (frame && frame.computerSessionId !== computerSessionId) {
|
|
650
678
|
throw new Error("The displayed desktop frame belongs to another desktop session.");
|
|
651
679
|
}
|
|
652
|
-
const currentObservation = observationRef.current.computerSessionId === computerSessionId ? observationRef.current.observation : null;
|
|
653
|
-
const focusTarget = action.type === "focus" ? targetsRef.current.targets.find((candidate) => candidate.id === action.targetId) ?? null : null;
|
|
654
|
-
const frameTarget = frame
|
|
655
|
-
const inputTarget = (action.type === "keyboard" || action.type === "clipboard") && targetsRef.current.computerSessionId === computerSessionId ? targetsRef.current.targets.find(
|
|
680
|
+
const currentObservation = observationRef.current.source === source && observationRef.current.computerSessionId === computerSessionId ? observationRef.current.observation : null;
|
|
681
|
+
const focusTarget = action.type === "focus" && targetsRef.current.source === source ? targetsRef.current.targets.find((candidate) => candidate.id === action.targetId) ?? null : null;
|
|
682
|
+
const frameTarget = frame && targetsRef.current.source === source ? currentObservation?.target.id === frame.targetId ? currentObservation.target : targetsRef.current.targets.find((candidate) => candidate.id === frame.targetId) ?? null : null;
|
|
683
|
+
const inputTarget = (action.type === "keyboard" || action.type === "clipboard") && targetsRef.current.source === source && targetsRef.current.computerSessionId === computerSessionId ? targetsRef.current.targets.find(
|
|
656
684
|
(candidate) => candidate.id === selectedTargetIdRef.current
|
|
657
685
|
) : null;
|
|
658
686
|
const target = frameTarget ?? focusTarget ?? currentObservation?.target ?? inputTarget ?? null;
|
|
@@ -660,6 +688,9 @@ function useComputerSession(options) {
|
|
|
660
688
|
if (frame && frame.targetId !== selectedTargetIdRef.current) {
|
|
661
689
|
throw new Error("The displayed desktop frame is no longer selected.");
|
|
662
690
|
}
|
|
691
|
+
if (frame && (frame.controllerGeneration !== target.controllerGeneration || frame.targetGeneration !== target.targetGeneration)) {
|
|
692
|
+
throw new Error("The displayed desktop frame belongs to an earlier target generation.");
|
|
693
|
+
}
|
|
663
694
|
if (action.type === "pointer") {
|
|
664
695
|
const expectedFrameId = frame?.frameId ?? currentObservation?.frameId ?? null;
|
|
665
696
|
if (!expectedFrameId || action.frameId !== expectedFrameId) {
|
|
@@ -669,34 +700,114 @@ function useComputerSession(options) {
|
|
|
669
700
|
if (action.type === "semantic" && !currentObservation) {
|
|
670
701
|
throw new Error("The desktop accessibility tree is not ready for input.");
|
|
671
702
|
}
|
|
703
|
+
const selectedTargetId = selectedTargetIdRef.current;
|
|
704
|
+
const selectedTarget = targetsRef.current.targets.find(
|
|
705
|
+
(candidate) => candidate.id === selectedTargetId
|
|
706
|
+
);
|
|
707
|
+
const selectedFence = selectedTarget && {
|
|
708
|
+
id: selectedTarget.id,
|
|
709
|
+
computerSessionId: selectedTarget.computerSessionId,
|
|
710
|
+
controllerGeneration: selectedTarget.controllerGeneration,
|
|
711
|
+
targetGeneration: selectedTarget.targetGeneration
|
|
712
|
+
};
|
|
713
|
+
const selectionRevision = selectionRevisionRef.current;
|
|
714
|
+
const isCurrentView = () => isCurrentSource() && selectionRevisionRef.current === selectionRevision && selectedTargetIdRef.current === selectedTargetId && targetsRef.current.source === source && targetsRef.current.computerSessionId === computerSessionId && sameTargetFence(
|
|
715
|
+
selectedFence,
|
|
716
|
+
targetsRef.current.targets.find((candidate) => candidate.id === selectedTargetId)
|
|
717
|
+
);
|
|
718
|
+
const resultOrder = ++actionResultOrderRef.current.next;
|
|
719
|
+
let admitted = false;
|
|
720
|
+
let resultTargetId = selectedTargetId;
|
|
721
|
+
let resultFence = selectedFence;
|
|
722
|
+
const isCurrentResultView = () => isCurrentSource() && selectionRevisionRef.current === selectionRevision && selectedTargetIdRef.current === resultTargetId && targetsRef.current.source === source && sameTargetFence(
|
|
723
|
+
resultFence,
|
|
724
|
+
targetsRef.current.targets.find((candidate) => candidate.id === resultTargetId)
|
|
725
|
+
);
|
|
726
|
+
const canProjectResult = () => {
|
|
727
|
+
if (!admitted) {
|
|
728
|
+
if (!isCurrentView() || resultOrder < actionResultOrderRef.current.settled) return false;
|
|
729
|
+
actionResultOrderRef.current.settled = resultOrder;
|
|
730
|
+
admitted = true;
|
|
731
|
+
invalidateRefresh();
|
|
732
|
+
}
|
|
733
|
+
return isCurrentResultView() && resultOrder === actionResultOrderRef.current.settled;
|
|
734
|
+
};
|
|
735
|
+
const projectState = (update) => {
|
|
736
|
+
setState(
|
|
737
|
+
(current) => admitted && isCurrentResultView() && current.source === source && current.computerSessionId === computerSessionId && current.actionResultOrder <= resultOrder ? { ...update(current), actionResultOrder: resultOrder } : current
|
|
738
|
+
);
|
|
739
|
+
};
|
|
672
740
|
return await runMutation(computerSessionId, async () => {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
741
|
+
try {
|
|
742
|
+
const receipt = await client.actInComputer(workspaceId, computerSessionId, {
|
|
743
|
+
operationId,
|
|
744
|
+
targetId: target.id,
|
|
745
|
+
expectedTargetGeneration: frame?.targetGeneration ?? target.targetGeneration,
|
|
746
|
+
expectedObservationId: action.type === "pointer" ? null : currentObservation?.observationId ?? null,
|
|
747
|
+
expectedFrameId: action.type === "pointer" ? action.frameId : null,
|
|
748
|
+
action
|
|
749
|
+
});
|
|
750
|
+
const receiptSettled = receipt.state === "completed" || receipt.state === "failed" || receipt.state === "outcome_unknown";
|
|
751
|
+
actionResultFences.set(
|
|
752
|
+
receipt,
|
|
753
|
+
() => admitted ? isCurrentResultView() && resultOrder === actionResultOrderRef.current.settled : isCurrentView() && resultOrder >= actionResultOrderRef.current.settled
|
|
754
|
+
);
|
|
755
|
+
if (!receiptSettled || !canProjectResult()) return receipt;
|
|
756
|
+
if (receipt.observation) {
|
|
757
|
+
const observation = receipt.observation;
|
|
758
|
+
observationRef.current = { source, computerSessionId, observation };
|
|
759
|
+
selectedTargetIdRef.current = observation.target.id;
|
|
760
|
+
targetsRef.current.targets = replaceTarget(
|
|
761
|
+
targetsRef.current.targets,
|
|
762
|
+
observation.target
|
|
763
|
+
);
|
|
764
|
+
resultTargetId = observation.target.id;
|
|
765
|
+
resultFence = {
|
|
766
|
+
id: observation.target.id,
|
|
767
|
+
computerSessionId: observation.target.computerSessionId,
|
|
768
|
+
controllerGeneration: observation.target.controllerGeneration,
|
|
769
|
+
targetGeneration: observation.target.targetGeneration
|
|
770
|
+
};
|
|
771
|
+
projectState((current) => ({
|
|
687
772
|
...current,
|
|
773
|
+
error: null,
|
|
774
|
+
controlError: null,
|
|
688
775
|
observation,
|
|
689
776
|
selectedTargetId: observation.target.id,
|
|
690
777
|
targets: replaceTarget(current.targets, observation.target)
|
|
691
|
-
}
|
|
778
|
+
}));
|
|
779
|
+
} else {
|
|
780
|
+
projectState((current) => ({ ...current, error: null, controlError: null }));
|
|
781
|
+
void refresh();
|
|
782
|
+
}
|
|
783
|
+
return receipt;
|
|
784
|
+
} catch (cause) {
|
|
785
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
786
|
+
actionResultFences.set(
|
|
787
|
+
error,
|
|
788
|
+
() => admitted && isCurrentResultView() && resultOrder === actionResultOrderRef.current.settled
|
|
692
789
|
);
|
|
693
|
-
|
|
694
|
-
|
|
790
|
+
if (canProjectResult()) {
|
|
791
|
+
projectState((current) => ({
|
|
792
|
+
...current,
|
|
793
|
+
error,
|
|
794
|
+
controlError: isInteractionControlUnavailable(error) ? error : current.controlError
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
throw error;
|
|
695
798
|
}
|
|
696
|
-
return receipt;
|
|
697
799
|
});
|
|
698
800
|
},
|
|
699
|
-
[
|
|
801
|
+
[
|
|
802
|
+
client,
|
|
803
|
+
computerSessionId,
|
|
804
|
+
invalidateRefresh,
|
|
805
|
+
isCurrentSource,
|
|
806
|
+
refresh,
|
|
807
|
+
runMutation,
|
|
808
|
+
source,
|
|
809
|
+
workspaceId
|
|
810
|
+
]
|
|
700
811
|
);
|
|
701
812
|
const act = useCallback2(
|
|
702
813
|
async (action, operationId = crypto.randomUUID()) => await dispatchAction(action, operationId, null),
|
|
@@ -708,12 +819,31 @@ function useComputerSession(options) {
|
|
|
708
819
|
);
|
|
709
820
|
const readClipboard = useCallback2(async () => {
|
|
710
821
|
if (!computerSessionId) throw new Error("No desktop is selected.");
|
|
822
|
+
const selectionRevision = selectionRevisionRef.current;
|
|
823
|
+
const settledOrder = actionResultOrderRef.current.settled;
|
|
824
|
+
const selectedTargetId = selectedTargetIdRef.current;
|
|
825
|
+
const selectedTarget = targetsRef.current.targets.find(
|
|
826
|
+
(candidate) => candidate.id === selectedTargetId
|
|
827
|
+
);
|
|
828
|
+
const selectedFence = selectedTarget && {
|
|
829
|
+
id: selectedTarget.id,
|
|
830
|
+
computerSessionId: selectedTarget.computerSessionId,
|
|
831
|
+
controllerGeneration: selectedTarget.controllerGeneration,
|
|
832
|
+
targetGeneration: selectedTarget.targetGeneration
|
|
833
|
+
};
|
|
834
|
+
const isCurrentRead = () => isCurrentSource() && selectionRevisionRef.current === selectionRevision && actionResultOrderRef.current.settled === settledOrder && selectedTargetIdRef.current === selectedTargetId && targetsRef.current.source === source && sameTargetFence(
|
|
835
|
+
selectedFence,
|
|
836
|
+
targetsRef.current.targets.find((candidate) => candidate.id === selectedTargetId)
|
|
837
|
+
);
|
|
711
838
|
try {
|
|
712
|
-
|
|
839
|
+
const clipboard = await client.readComputerClipboard(workspaceId, computerSessionId);
|
|
840
|
+
actionResultFences.set(clipboard, isCurrentRead);
|
|
841
|
+
return clipboard;
|
|
713
842
|
} catch (cause) {
|
|
714
843
|
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
844
|
+
actionResultFences.set(error, isCurrentRead);
|
|
715
845
|
setState(
|
|
716
|
-
(current) => current.computerSessionId === computerSessionId ? {
|
|
846
|
+
(current) => isCurrentRead() && current.source === source && current.computerSessionId === computerSessionId ? {
|
|
717
847
|
...current,
|
|
718
848
|
error,
|
|
719
849
|
controlError: isInteractionControlUnavailable(error) ? error : current.controlError
|
|
@@ -721,7 +851,7 @@ function useComputerSession(options) {
|
|
|
721
851
|
);
|
|
722
852
|
throw error;
|
|
723
853
|
}
|
|
724
|
-
}, [client, computerSessionId, workspaceId]);
|
|
854
|
+
}, [client, computerSessionId, isCurrentSource, source, workspaceId]);
|
|
725
855
|
return {
|
|
726
856
|
session: visible.session,
|
|
727
857
|
targets: visible.targets,
|
|
@@ -738,9 +868,10 @@ function useComputerSession(options) {
|
|
|
738
868
|
actFromFrame
|
|
739
869
|
};
|
|
740
870
|
}
|
|
741
|
-
function emptyState(
|
|
871
|
+
function emptyState(source, loading) {
|
|
742
872
|
return {
|
|
743
|
-
|
|
873
|
+
source,
|
|
874
|
+
computerSessionId: source.computerSessionId,
|
|
744
875
|
session: null,
|
|
745
876
|
targets: [],
|
|
746
877
|
selectedTargetId: null,
|
|
@@ -748,7 +879,8 @@ function emptyState(computerSessionId, loading) {
|
|
|
748
879
|
loading,
|
|
749
880
|
mutating: false,
|
|
750
881
|
error: null,
|
|
751
|
-
controlError: null
|
|
882
|
+
controlError: null,
|
|
883
|
+
actionResultOrder: 0
|
|
752
884
|
};
|
|
753
885
|
}
|
|
754
886
|
function chooseTarget(targets, preferredId) {
|
|
@@ -768,6 +900,9 @@ function replaceTarget(targets, target) {
|
|
|
768
900
|
target
|
|
769
901
|
]);
|
|
770
902
|
}
|
|
903
|
+
function sameTargetFence(left, right) {
|
|
904
|
+
return left?.id === right?.id && left?.computerSessionId === right?.computerSessionId && left?.controllerGeneration === right?.controllerGeneration && left?.targetGeneration === right?.targetGeneration;
|
|
905
|
+
}
|
|
771
906
|
|
|
772
907
|
// src/hooks/use-computer-input-posture.ts
|
|
773
908
|
import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
@@ -1088,6 +1223,7 @@ function ComputerViewer({
|
|
|
1088
1223
|
}, [enabled, endStaleComputer, registry.sessions]);
|
|
1089
1224
|
const notifyError = useCallback5(
|
|
1090
1225
|
(cause, fallback) => {
|
|
1226
|
+
if (isStaleComputerActionResult(cause)) return;
|
|
1091
1227
|
onNotify?.({
|
|
1092
1228
|
kind: "error",
|
|
1093
1229
|
message: cause instanceof Error ? cause.message : fallback
|
|
@@ -1322,19 +1458,20 @@ function ComputerViewer({
|
|
|
1322
1458
|
suppressGenericCreate
|
|
1323
1459
|
]);
|
|
1324
1460
|
const perform = useCallback5(
|
|
1325
|
-
async (action, frame) => {
|
|
1461
|
+
async (action, frame, operationId) => {
|
|
1326
1462
|
if (computer.controlError) throw computer.controlError;
|
|
1327
1463
|
if (inputDenied) throw new Error("Desktop is view only. Refresh to check desktop controls.");
|
|
1328
1464
|
let receipt;
|
|
1329
1465
|
if (action.type === "pointer") {
|
|
1330
1466
|
if (!frame) throw new Error("Desktop view is not ready for pointer input.");
|
|
1331
|
-
receipt = await actFromFrame(action, frame);
|
|
1467
|
+
receipt = await actFromFrame(action, frame, operationId);
|
|
1332
1468
|
} else {
|
|
1333
|
-
receipt = await act(action);
|
|
1469
|
+
receipt = await act(action, operationId);
|
|
1334
1470
|
}
|
|
1335
|
-
if (receipt.state !== "completed") {
|
|
1336
|
-
throw
|
|
1471
|
+
if (!isStaleComputerActionResult(receipt) && receipt.state !== "completed") {
|
|
1472
|
+
throw computerActionReceiptError(receipt);
|
|
1337
1473
|
}
|
|
1474
|
+
return receipt;
|
|
1338
1475
|
},
|
|
1339
1476
|
[act, actFromFrame, computer.controlError, inputDenied]
|
|
1340
1477
|
);
|
|
@@ -1349,11 +1486,15 @@ function ComputerViewer({
|
|
|
1349
1486
|
return;
|
|
1350
1487
|
event.preventDefault();
|
|
1351
1488
|
event.stopPropagation();
|
|
1352
|
-
void
|
|
1489
|
+
void (async () => {
|
|
1490
|
+
const receipt = await perform({ type: "clipboard", operation: "copy" }, null);
|
|
1491
|
+
if (isStaleComputerActionResult(receipt)) return;
|
|
1492
|
+
const clipboard = await computer.readClipboard();
|
|
1493
|
+
if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return;
|
|
1353
1494
|
if (clipboard.text && !await copyTextToClipboard(clipboard.text)) {
|
|
1354
1495
|
throw new Error("Desktop text could not be copied to the local clipboard");
|
|
1355
1496
|
}
|
|
1356
|
-
}).catch((cause) => notifyError(cause, "Could not copy from the desktop."));
|
|
1497
|
+
})().catch((cause) => notifyError(cause, "Could not copy from the desktop."));
|
|
1357
1498
|
},
|
|
1358
1499
|
[computer, notifyError, perform, rfbInputEnabled, rfbStream]
|
|
1359
1500
|
);
|
|
@@ -1362,8 +1503,11 @@ function ComputerViewer({
|
|
|
1362
1503
|
if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
1363
1504
|
return false;
|
|
1364
1505
|
void (async () => {
|
|
1365
|
-
await perform({ type: "clipboard", operation: "write", text }, null);
|
|
1366
|
-
|
|
1506
|
+
const receipt = await perform({ type: "clipboard", operation: "write", text }, null);
|
|
1507
|
+
if (isStaleComputerActionResult(receipt)) return;
|
|
1508
|
+
const clipboard = await computer.readClipboard();
|
|
1509
|
+
if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return;
|
|
1510
|
+
assertExactComputerClipboard(clipboard, text);
|
|
1367
1511
|
await perform({ type: "clipboard", operation: "paste" }, null);
|
|
1368
1512
|
})().catch((cause) => notifyError(cause, "Could not paste into the desktop."));
|
|
1369
1513
|
return true;
|
|
@@ -1522,6 +1666,7 @@ function ComputerViewer({
|
|
|
1522
1666
|
backgroundInput: computer.session?.capabilities?.backgroundInput === true,
|
|
1523
1667
|
clipboardEnabled: attachmentInputAllowed && computer.session?.capabilities?.clipboard === true,
|
|
1524
1668
|
pointerInput: attachmentInputAllowed && computer.session?.capabilities?.pointerInput === true,
|
|
1669
|
+
pointerClickContinuation: attachmentInputAllowed && computer.session?.capabilities?.pointerClickContinuation === true,
|
|
1525
1670
|
keyboardInput: attachmentInputAllowed && computer.session?.capabilities?.keyboardInput === true,
|
|
1526
1671
|
onAction: perform,
|
|
1527
1672
|
onReadClipboard: computer.readClipboard,
|
|
@@ -1996,21 +2141,38 @@ function ComputerViewport(props) {
|
|
|
1996
2141
|
}
|
|
1997
2142
|
}, [clearBufferedInput, props.keyboardInput, props.pointerInput, rawInputEnabled]);
|
|
1998
2143
|
const enqueue = useCallback5(
|
|
1999
|
-
(action, frame, after) => {
|
|
2144
|
+
(action, frame, after, continuation) => {
|
|
2000
2145
|
const epoch = actionQueueEpochRef.current;
|
|
2001
2146
|
const dispatch = actionRef.current;
|
|
2002
2147
|
const isCurrent = () => mountedRef.current && epoch === actionQueueEpochRef.current;
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2148
|
+
const operationId = crypto.randomUUID();
|
|
2149
|
+
let markStarted;
|
|
2150
|
+
const started = new Promise((resolve) => {
|
|
2151
|
+
markStarted = resolve;
|
|
2152
|
+
});
|
|
2153
|
+
const previous = actionTailRef.current;
|
|
2154
|
+
const ready = continuation && previous === continuation.completion ? continuation.started : previous.then(() => true);
|
|
2155
|
+
const delivery = ready.then(async (firstStarted) => {
|
|
2156
|
+
if (!firstStarted || !isCurrent()) {
|
|
2157
|
+
markStarted(false);
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
const pending = dispatch(action, frame, operationId);
|
|
2161
|
+
markStarted(true);
|
|
2162
|
+
const receipt = await pending;
|
|
2163
|
+
const isCurrentResult = () => isCurrent() && !isStaleComputerActionResult(receipt);
|
|
2164
|
+
if (!isCurrentResult()) return;
|
|
2165
|
+
await after?.(isCurrentResult);
|
|
2008
2166
|
}).catch((cause) => {
|
|
2009
|
-
|
|
2167
|
+
markStarted(false);
|
|
2168
|
+
if (!isCurrent() || isStaleComputerActionResult(cause)) return;
|
|
2010
2169
|
actionQueueEpochRef.current += 1;
|
|
2011
2170
|
clearBufferedInput();
|
|
2012
2171
|
errorRef.current(cause);
|
|
2013
2172
|
});
|
|
2173
|
+
const completion = continuation ? Promise.all([previous, delivery]).then(() => void 0) : delivery;
|
|
2174
|
+
actionTailRef.current = completion;
|
|
2175
|
+
return { operationId, started, completion };
|
|
2014
2176
|
},
|
|
2015
2177
|
[clearBufferedInput]
|
|
2016
2178
|
);
|
|
@@ -2031,6 +2193,7 @@ function ComputerViewport(props) {
|
|
|
2031
2193
|
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
2032
2194
|
clickTimerRef.current = null;
|
|
2033
2195
|
lastClickRef.current = null;
|
|
2196
|
+
if (!pending.pending) return;
|
|
2034
2197
|
enqueue(
|
|
2035
2198
|
{
|
|
2036
2199
|
type: "pointer",
|
|
@@ -2062,7 +2225,8 @@ function ComputerViewport(props) {
|
|
|
2062
2225
|
}, [enqueue]);
|
|
2063
2226
|
const pointerDown = (event) => {
|
|
2064
2227
|
const frame = paintedFrameRef.current;
|
|
2065
|
-
if (!frame || props.mutating || !rawInputEnabled || event.button !== 0)
|
|
2228
|
+
if (!frame || !props.pointerClickContinuation && props.mutating || !rawInputEnabled || event.button !== 0)
|
|
2229
|
+
return;
|
|
2066
2230
|
event.preventDefault();
|
|
2067
2231
|
if (keyboardInputEnabled) inputRef.current?.focus({ preventScroll: true });
|
|
2068
2232
|
if (!pointerInputEnabled) return;
|
|
@@ -2081,6 +2245,7 @@ function ComputerViewport(props) {
|
|
|
2081
2245
|
const start = pointerStartRef.current;
|
|
2082
2246
|
pointerStartRef.current = null;
|
|
2083
2247
|
if (!start || start.pointerId !== event.pointerId) return;
|
|
2248
|
+
const canvasRect = event.currentTarget.getBoundingClientRect();
|
|
2084
2249
|
const from = point(start.frame, start.x, start.y);
|
|
2085
2250
|
const to = point(start.frame, event.clientX, event.clientY);
|
|
2086
2251
|
if (!from || !to) return;
|
|
@@ -2102,7 +2267,7 @@ function ComputerViewport(props) {
|
|
|
2102
2267
|
}
|
|
2103
2268
|
const now = Date.now();
|
|
2104
2269
|
const previous = lastClickRef.current;
|
|
2105
|
-
if (previous && now - previous.at < 280 && Math.hypot(previous.x - to.x, previous.y - to.y) < 6 && sameFrameFence(previous.frame, start.frame)) {
|
|
2270
|
+
if (previous && now - previous.at < 280 && previous.pending === !props.pointerClickContinuation && (props.pointerClickContinuation ? Math.hypot(previous.clientX - event.clientX, previous.clientY - event.clientY) < 6 && previous.canvasRect.left === canvasRect.left && previous.canvasRect.top === canvasRect.top && previous.canvasRect.width === canvasRect.width && previous.canvasRect.height === canvasRect.height && sameComputerTarget(previous.frame, start.frame) && previous.frame.width === start.frame.width && previous.frame.height === start.frame.height : Math.hypot(previous.x - to.x, previous.y - to.y) < 6 && sameFrameFence(previous.frame, start.frame))) {
|
|
2106
2271
|
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
2107
2272
|
clickTimerRef.current = null;
|
|
2108
2273
|
lastClickRef.current = null;
|
|
@@ -2110,15 +2275,45 @@ function ComputerViewport(props) {
|
|
|
2110
2275
|
{
|
|
2111
2276
|
type: "pointer",
|
|
2112
2277
|
frameId: start.frame.frameId,
|
|
2113
|
-
action: "double_click",
|
|
2278
|
+
action: props.pointerClickContinuation ? "click" : "double_click",
|
|
2279
|
+
...props.pointerClickContinuation ? { clickCount: 2 } : {},
|
|
2280
|
+
...previous.delivery ? { continuationOfOperationId: previous.delivery.operationId } : {},
|
|
2281
|
+
x: to.x,
|
|
2282
|
+
y: to.y
|
|
2283
|
+
},
|
|
2284
|
+
start.frame,
|
|
2285
|
+
void 0,
|
|
2286
|
+
previous.delivery ?? void 0
|
|
2287
|
+
);
|
|
2288
|
+
return;
|
|
2289
|
+
}
|
|
2290
|
+
flushPendingClick();
|
|
2291
|
+
lastClickRef.current = {
|
|
2292
|
+
at: now,
|
|
2293
|
+
x: to.x,
|
|
2294
|
+
y: to.y,
|
|
2295
|
+
frame: start.frame,
|
|
2296
|
+
clientX: event.clientX,
|
|
2297
|
+
clientY: event.clientY,
|
|
2298
|
+
canvasRect,
|
|
2299
|
+
pending: !props.pointerClickContinuation,
|
|
2300
|
+
delivery: null
|
|
2301
|
+
};
|
|
2302
|
+
if (props.pointerClickContinuation) {
|
|
2303
|
+
const delivery = enqueue(
|
|
2304
|
+
{
|
|
2305
|
+
type: "pointer",
|
|
2306
|
+
frameId: start.frame.frameId,
|
|
2307
|
+
action: "click",
|
|
2308
|
+
clickCount: 1,
|
|
2114
2309
|
x: to.x,
|
|
2115
2310
|
y: to.y
|
|
2116
2311
|
},
|
|
2117
2312
|
start.frame
|
|
2118
2313
|
);
|
|
2314
|
+
if (lastClickRef.current) lastClickRef.current.delivery = delivery;
|
|
2119
2315
|
return;
|
|
2120
2316
|
}
|
|
2121
|
-
lastClickRef.current = { at: now, x: to.x, y: to.y, frame: start.frame };
|
|
2122
2317
|
clickTimerRef.current = setTimeout(() => {
|
|
2123
2318
|
clickTimerRef.current = null;
|
|
2124
2319
|
lastClickRef.current = null;
|
|
@@ -2730,4 +2925,4 @@ export {
|
|
|
2730
2925
|
ComputerViewer,
|
|
2731
2926
|
computerKey
|
|
2732
2927
|
};
|
|
2733
|
-
//# sourceMappingURL=chunk-
|
|
2928
|
+
//# sourceMappingURL=chunk-I6OY3MWC.js.map
|