@platless/axom-cli 0.5.9 → 0.6.52
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 +25 -6
- package/dist/axom-patch/cli/README.md +129 -12
- package/dist/axom-patch/cli/account-limits/generate.py +15 -12
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
- package/dist/axom-patch/cli/claude-account-limits.json +9 -9
- package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
- package/dist/axom-patch/cli/codex-account-limits.json +11 -11
- package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
- package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
- package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
- package/dist/axom-patch/cli/nightly-task.md +55 -13
- package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
- package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
- package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
- package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
- package/dist/axom-patch/cli/qoder-steering.md +20 -25
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
- package/dist/axom-patch/cli/runtime.json +19 -19
- package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
- package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
- package/dist/axom-patch/cli/zcode-env-account.json +58 -0
- package/dist/axom-patch/cli/zcode-models.json +9 -10
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
- package/dist/cli.js +11 -45
- package/dist/commands/auth.js +4 -1
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +126 -3
- package/dist/commands/workstation.js +101 -164
- package/dist/commands/workstationRegistrations.js +116 -0
- package/dist/credentialBroker.js +13 -2
- package/dist/credentials.js +16 -0
- package/dist/install/claude/commands.js +15 -15
- package/dist/keepers/terminal/keeper.mjs +1089 -673
- package/dist/keepers/terminal/manifest.json +4 -4
- package/dist/mcp.js +1 -1
- package/dist/native/darwin-x64/axom-quic +0 -0
- package/dist/workstation/acp/acpAgents.js +113 -102
- package/dist/workstation/acp/acpClient.js +26 -0
- package/dist/workstation/acp/builds/install/npm.js +99 -10
- package/dist/workstation/acp/initializeResponse.js +6 -1
- package/dist/workstation/acp/managedPatches.js +189 -25
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +12 -5
- package/dist/workstation/acp/patchAssets.js +40 -0
- package/dist/workstation/acp/permission.js +2 -2
- package/dist/workstation/acp/processor/checkpoint.js +26 -34
- package/dist/workstation/acp/processor/control.js +7 -80
- package/dist/workstation/acp/processor/jsonrpc.js +8 -0
- package/dist/workstation/acp/processor/runtime.js +5 -81
- package/dist/workstation/acp/processor/session.js +54 -82
- package/dist/workstation/acp/processor/state.js +7 -13
- package/dist/workstation/acp/processor/step.js +3 -20
- package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
- package/dist/workstation/acp/processor/transport/local.js +19 -3
- package/dist/workstation/acp/processor/updates.js +156 -105
- package/dist/workstation/acp/processorSession.js +20 -173
- package/dist/workstation/acp/registry/catalog.js +65 -39
- package/dist/workstation/acp/registry/launch.js +16 -1
- package/dist/workstation/acp/switching.js +0 -3
- package/dist/workstation/acp/test/fakeAgent.js +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +32 -3
- package/dist/workstation/acp/test/raw/sources.js +16 -130
- package/dist/workstation/acp/test/realAdapter.js +1 -47
- package/dist/workstation/acp/test/scriptAgent.js +11 -3
- package/dist/workstation/agentFlow.js +1 -1
- package/dist/workstation/agentSessions.js +114 -166
- package/dist/workstation/channels/agent.js +9 -1
- package/dist/workstation/channels/computer.js +27 -4
- package/dist/workstation/channels/management.js +130 -15
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +33 -9
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +204 -0
- package/dist/workstation/control/commands.js +125 -0
- package/dist/workstation/control/daemonIdentity.js +143 -0
- package/dist/workstation/control/deviceCommands.js +191 -0
- package/dist/workstation/control/deviceProtocol.js +68 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +110 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/rootFormat.js +16 -0
- package/dist/workstation/control/runtimeClient.js +152 -0
- package/dist/workstation/control/runtimeHost.js +235 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/control/updateTransition.js +91 -0
- package/dist/workstation/daemon.js +266 -264
- package/dist/workstation/daemonctl.js +226 -25
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/automationDevice.js +219 -0
- package/dist/workstation/desktopControl/automationJson.js +74 -0
- package/dist/workstation/desktopControl/automationLocalState.js +23 -0
- package/dist/workstation/desktopControl/automationProtocol.js +253 -0
- package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
- package/dist/workstation/desktopControl/automationSession.js +81 -0
- package/dist/workstation/desktopControl/automationTransport.js +138 -0
- package/dist/workstation/desktopControl/capabilities.js +93 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
- package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +602 -0
- package/dist/workstation/desktopControl/hostGrant.js +65 -0
- package/dist/workstation/desktopControl/lobby.js +131 -0
- package/dist/workstation/desktopControl/macProvider.js +156 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
- package/dist/workstation/desktopControl/protocol.js +311 -0
- package/dist/workstation/desktopControl/provider.js +1 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1774 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +98 -0
- package/dist/workstation/exec/policy.js +5 -0
- package/dist/workstation/files/client.js +10 -1
- package/dist/workstation/files/credentials.js +142 -0
- package/dist/workstation/files/host.js +9 -2
- package/dist/workstation/files/text.js +42 -8
- package/dist/workstation/journal.js +5 -2
- package/dist/workstation/keepers/acp/client.js +38 -2
- package/dist/workstation/keepers/kit/buildVersion.js +35 -11
- package/dist/workstation/keepers/kit/bundle.js +21 -17
- package/dist/workstation/keepers/kit/client.js +2 -2
- package/dist/workstation/keepers/kit/launcher.js +123 -7
- package/dist/workstation/keepers/kit/paths.js +2 -1
- package/dist/workstation/keepers/legacy.js +16 -2
- package/dist/workstation/keepers/policy.js +5 -34
- package/dist/workstation/keepers/terminal/client.js +115 -23
- package/dist/workstation/keepers/terminal/exec.js +6 -3
- package/dist/workstation/keepers/terminal/frames.js +73 -0
- package/dist/workstation/keepers/terminal/generations.js +5 -8
- package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
- package/dist/workstation/keepers/terminal/input.js +146 -0
- package/dist/workstation/keepers/terminal/keeper.js +22 -8
- package/dist/workstation/keepers/terminal/main.js +1 -1
- package/dist/workstation/keepers/terminal/metadata.js +138 -0
- package/dist/workstation/keepers/terminal/protocol.js +4 -28
- package/dist/workstation/keepers/terminal/pty.js +1 -330
- package/dist/workstation/keepers/terminal/screen.js +246 -0
- package/dist/workstation/keepers/terminal/sessions.js +526 -0
- package/dist/workstation/managedInstall.js +57 -0
- package/dist/workstation/oauth/deviceAuthorization.js +31 -17
- package/dist/workstation/protocol/agent.js +2 -15
- package/dist/workstation/protocol/management.js +113 -5
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +120 -0
- package/dist/workstation/rd/control.js +117 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/host.js +213 -0
- package/dist/workstation/rd/migrationReadiness.js +46 -0
- package/dist/workstation/rd/provider.js +256 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +57 -0
- package/dist/workstation/rd/update.js +168 -0
- package/dist/workstation/rd/upkeep.js +80 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/registrations/enrollment.js +250 -0
- package/dist/workstation/registrations/enrollmentPending.js +361 -0
- package/dist/workstation/registrations/migration.js +356 -0
- package/dist/workstation/registrations/model.js +34 -0
- package/dist/workstation/registrations/removal.js +222 -0
- package/dist/workstation/registrations/store.js +737 -0
- package/dist/workstation/runtime/desktopBinding.js +20 -0
- package/dist/workstation/runtime/deviceDesktop.js +193 -0
- package/dist/workstation/runtime/deviceRuntime.js +568 -0
- package/dist/workstation/runtime/deviceServices.js +117 -0
- package/dist/workstation/runtime/events.js +20 -0
- package/dist/workstation/runtime/ownership.js +100 -0
- package/dist/workstation/runtime/registrationRuntime.js +575 -0
- package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
- package/dist/workstation/runtime/removalActivity.js +184 -0
- package/dist/workstation/service/installer.js +197 -14
- package/dist/workstation/service/launchd.js +2 -0
- package/dist/workstation/service/systemd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +47 -92
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- package/dist/workstation/tools/client.js +8 -27
- package/dist/workstation/tools/info.js +6 -13
- package/dist/workstation/tools/terminalRouter.js +17 -172
- package/dist/workstation/tools/validation.js +55 -1
- package/dist/workstation/update/archiveEvidence.js +206 -0
- package/dist/workstation/update/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +34 -13
- package/dist/workstation/update/deviceCompatibility.js +119 -0
- package/dist/workstation/update/deviceCoordinator.js +369 -0
- package/dist/workstation/update/deviceHealth.js +104 -0
- package/dist/workstation/update/deviceHealthProtocol.js +26 -0
- package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
- package/dist/workstation/update/deviceRollback.js +78 -0
- package/dist/workstation/update/globalInstall.js +186 -0
- package/dist/workstation/update/legacyBridge.js +414 -0
- package/dist/workstation/update/legacyOwner.js +39 -0
- package/dist/workstation/update/npmInstaller.js +51 -25
- package/dist/workstation/update/privateDurability.js +61 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/recoveryPlan.js +124 -0
- package/dist/workstation/update/runUpdate.js +10 -3
- package/dist/workstation/update/taskRegistry.js +161 -3
- package/dist/workstation/update/watchdog.js +29806 -333
- package/dist/workstation/update/watchdogProcess.js +72 -0
- package/package.json +11 -7
- package/skills/axom/SKILL.md +11 -11
- package/skills/axom-definition/SKILL.md +3 -3
- package/skills/axom-platform/SKILL.md +3 -3
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
- package/dist/axom-patch/cli/qoder-steering.json +0 -52
- package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
- package/dist/commands/guide.js +0 -38
- package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
- package/dist/runtime-deps/qoder-sdk/package.json +0 -8
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/processor/sideChannel.js +0 -80
- package/dist/workstation/acp/qoderStartup.js +0 -45
- package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
- package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
- package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
- package/dist/workstation/qoderAdapter/history.js +0 -85
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -95
- package/dist/workstation/qoderAdapter/mapper.js +0 -117
- package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
- package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
- package/dist/workstation/qoderAdapter/permissions.js +0 -61
- package/dist/workstation/qoderAdapter/profile.js +0 -93
- package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
- package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -85
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { parseDesktopJson } from './protocol.js';
|
|
2
|
+
export function parseAutomationJson(source, limit) {
|
|
3
|
+
if (typeof source !== 'string' && source.byteLength > limit)
|
|
4
|
+
throw new Error('invalid_automation_protocol');
|
|
5
|
+
const text = typeof source === 'string' ? source : new TextDecoder('utf-8', { fatal: true }).decode(source);
|
|
6
|
+
const value = parseDesktopJson(text, limit, new Set());
|
|
7
|
+
const numbers = new Map();
|
|
8
|
+
let offset = 0;
|
|
9
|
+
const space = () => { while (/[ \t\r\n]/.test(text[offset] ?? '\0'))
|
|
10
|
+
offset++; };
|
|
11
|
+
const string = () => {
|
|
12
|
+
const start = offset++;
|
|
13
|
+
while (offset < text.length) {
|
|
14
|
+
const character = text[offset++];
|
|
15
|
+
if (character === '\\')
|
|
16
|
+
offset++;
|
|
17
|
+
else if (character === '"')
|
|
18
|
+
return JSON.parse(text.slice(start, offset));
|
|
19
|
+
}
|
|
20
|
+
throw new Error('invalid_automation_protocol');
|
|
21
|
+
};
|
|
22
|
+
const visit = (path) => {
|
|
23
|
+
space();
|
|
24
|
+
if (text[offset] === '{') {
|
|
25
|
+
offset++;
|
|
26
|
+
space();
|
|
27
|
+
if (text[offset] === '}') {
|
|
28
|
+
offset++;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
do {
|
|
32
|
+
space();
|
|
33
|
+
const key = string();
|
|
34
|
+
space();
|
|
35
|
+
offset++;
|
|
36
|
+
visit([...path, key]);
|
|
37
|
+
space();
|
|
38
|
+
if (text[offset] !== ',')
|
|
39
|
+
break;
|
|
40
|
+
offset++;
|
|
41
|
+
} while (true);
|
|
42
|
+
offset++;
|
|
43
|
+
}
|
|
44
|
+
else if (text[offset] === '[') {
|
|
45
|
+
offset++;
|
|
46
|
+
space();
|
|
47
|
+
if (text[offset] === ']') {
|
|
48
|
+
offset++;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let index = 0;
|
|
52
|
+
do {
|
|
53
|
+
visit([...path, index++]);
|
|
54
|
+
space();
|
|
55
|
+
if (text[offset] !== ',')
|
|
56
|
+
break;
|
|
57
|
+
offset++;
|
|
58
|
+
} while (true);
|
|
59
|
+
offset++;
|
|
60
|
+
}
|
|
61
|
+
else if (text[offset] === '"')
|
|
62
|
+
string();
|
|
63
|
+
else {
|
|
64
|
+
const start = offset;
|
|
65
|
+
while (offset < text.length && !/[,\]} \t\r\n]/.test(text[offset]))
|
|
66
|
+
offset++;
|
|
67
|
+
const token = text.slice(start, offset);
|
|
68
|
+
if (/^-?[0-9]/.test(token))
|
|
69
|
+
numbers.set(JSON.stringify(path), token);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
visit([]);
|
|
73
|
+
return { value, numbers };
|
|
74
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { isAbsolute, join } from 'node:path';
|
|
4
|
+
export async function managedAutomationBinding(configDirectory, binding) {
|
|
5
|
+
const leaseMs = binding.native?.leaseMs;
|
|
6
|
+
if (!Number.isInteger(leaseMs) || leaseMs < 1 || leaseMs > 30000 || !binding.executionId)
|
|
7
|
+
throw new Error('trusted_preparation_unavailable');
|
|
8
|
+
if (!isAbsolute(configDirectory))
|
|
9
|
+
throw new Error('trusted_storage_unavailable');
|
|
10
|
+
const directory = join(configDirectory, 'desktop-automation');
|
|
11
|
+
await mkdir(directory, { mode: 0o700 }).catch(error => { if (error?.code !== 'EEXIST')
|
|
12
|
+
throw error; });
|
|
13
|
+
const status = await lstat(directory);
|
|
14
|
+
if (!status.isDirectory() || status.isSymbolicLink() || status.uid !== process.getuid?.() || (status.mode & 0o077) !== 0)
|
|
15
|
+
throw new Error('trusted_storage_unavailable');
|
|
16
|
+
const route = binding.route;
|
|
17
|
+
const identity = [binding.registrationId ?? 'legacy-single-registration', route.workstation, route.desktopId, route.inputDomainId, route.consumerId, route.deviceGeneration, route.controlGeneration, binding.executionId];
|
|
18
|
+
if (identity.some(value => value === undefined || value === null))
|
|
19
|
+
throw new Error('trusted_preparation_unavailable');
|
|
20
|
+
const key = createHash('sha256').update(JSON.stringify(identity)).digest('hex');
|
|
21
|
+
const domainKey = createHash('sha256').update(JSON.stringify([route.workstation, route.desktopId, route.inputDomainId])).digest('hex');
|
|
22
|
+
return { ...binding, native: { waitLockPath: join(directory, `${domainKey}.wait`), ledgerPath: join(directory, `${key}.jsonl`), leaseMs } };
|
|
23
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { parseAutomationJson } from './automationJson.js';
|
|
2
|
+
import { automationSchema } from './automationSchema.generated.js';
|
|
3
|
+
export { automationSchema };
|
|
4
|
+
export function decodeAutomationFailureDetail(value) {
|
|
5
|
+
if (value == null)
|
|
6
|
+
return null;
|
|
7
|
+
shape(value, automationSchema.types.FailureDetail);
|
|
8
|
+
return structuredClone(value);
|
|
9
|
+
}
|
|
10
|
+
const maxJson = 48 * 1024;
|
|
11
|
+
const routeKeys = ['requestId', 'consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'];
|
|
12
|
+
const envelopeKeys = ['automationProtocol', ...routeKeys, 'method', 'payload'];
|
|
13
|
+
function requireValue(value) { if (!value)
|
|
14
|
+
throw new Error('invalid_automation_protocol'); }
|
|
15
|
+
function object(value) {
|
|
16
|
+
requireValue(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function equal(left, right) {
|
|
20
|
+
if (left === right)
|
|
21
|
+
return true;
|
|
22
|
+
if (left === null || right === null || typeof left !== 'object' || typeof right !== 'object')
|
|
23
|
+
return false;
|
|
24
|
+
const keys = Object.keys(left);
|
|
25
|
+
return keys.length === Object.keys(right).length && keys.every(key => Object.hasOwn(right, key) && equal(left[key], right[key]));
|
|
26
|
+
}
|
|
27
|
+
function integerToken(numbers, path) {
|
|
28
|
+
if (numbers)
|
|
29
|
+
requireValue(/^-?(0|[1-9][0-9]*)$/.test(numbers.get(JSON.stringify(path)) ?? ''));
|
|
30
|
+
}
|
|
31
|
+
function shape(value, rule, numbers, path = []) {
|
|
32
|
+
if (rule.ref) {
|
|
33
|
+
shape(value, automationSchema.types[rule.ref], numbers, path);
|
|
34
|
+
semantics(rule.ref, value);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (rule.nullable) {
|
|
38
|
+
if (value !== null)
|
|
39
|
+
shape(value, rule.nullable, numbers, path);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (rule.enum) {
|
|
43
|
+
requireValue(rule.enum.includes(value));
|
|
44
|
+
if (typeof rule.enum[0] === 'number')
|
|
45
|
+
integerToken(numbers, path);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (rule.tag) {
|
|
49
|
+
const data = object(value), variant = rule.variants[data[rule.tag]];
|
|
50
|
+
requireValue(variant);
|
|
51
|
+
const fields = { ...data };
|
|
52
|
+
delete fields[rule.tag];
|
|
53
|
+
shape(fields, variant, numbers, path);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (rule.fields) {
|
|
57
|
+
const data = object(value), optional = rule.optional;
|
|
58
|
+
requireValue(Object.keys(data).every(key => Object.hasOwn(rule.fields, key)));
|
|
59
|
+
for (const [key, field] of Object.entries(rule.fields)) {
|
|
60
|
+
if (Object.hasOwn(data, key))
|
|
61
|
+
shape(data[key], field, numbers, [...path, key]);
|
|
62
|
+
else
|
|
63
|
+
requireValue(optional.includes(key));
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (rule.items) {
|
|
68
|
+
requireValue(Array.isArray(value) && value.length >= rule.min && value.length <= rule.max);
|
|
69
|
+
if (rule.unique)
|
|
70
|
+
requireValue(new Set(value.map(item => JSON.stringify(item))).size === value.length);
|
|
71
|
+
value.forEach((item, index) => shape(item, rule.items, numbers, [...path, index]));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (rule.string) {
|
|
75
|
+
requireValue(typeof value === 'string' && value.length >= (rule.min ?? 0) && value.length <= rule.max && !value.includes('\u0000'));
|
|
76
|
+
requireValue(rule.blank || [...value].some(character => !automationSchema.whitespaceRanges.some((range) => character.codePointAt(0) >= range[0] && character.codePointAt(0) <= range[1])));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (rule.boolean) {
|
|
80
|
+
requireValue(typeof value === 'boolean');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
requireValue(typeof value === 'number' && Number.isFinite(value));
|
|
84
|
+
if (rule.integer) {
|
|
85
|
+
requireValue(Number.isSafeInteger(value));
|
|
86
|
+
integerToken(numbers, path);
|
|
87
|
+
}
|
|
88
|
+
if (rule.min !== undefined)
|
|
89
|
+
requireValue(value >= rule.min);
|
|
90
|
+
if (rule.max !== undefined)
|
|
91
|
+
requireValue(value <= rule.max);
|
|
92
|
+
if (rule.positive)
|
|
93
|
+
requireValue(value > 0);
|
|
94
|
+
}
|
|
95
|
+
function semantics(name, value) {
|
|
96
|
+
if (name === 'Acquisition' && value.type === 'takeover')
|
|
97
|
+
requireValue((value.expectedDeviceEpoch == null) === (value.expectedDeviceRevision == null));
|
|
98
|
+
if (name === 'WaitResult') {
|
|
99
|
+
requireValue((value.status === 'met') === (value.conditionState === 'met'));
|
|
100
|
+
requireValue(value.conditionState !== 'unavailable' || value.image === null);
|
|
101
|
+
}
|
|
102
|
+
if (name === 'Rect')
|
|
103
|
+
requireValue(Number.isFinite(value.x + value.width) && Number.isFinite(value.y + value.height));
|
|
104
|
+
if (name === 'Target') {
|
|
105
|
+
requireValue(Boolean(value.display) !== Boolean(value.window));
|
|
106
|
+
requireValue((value.selectionReason === 'EXPLICIT_DISPLAY') === Boolean(value.display));
|
|
107
|
+
}
|
|
108
|
+
if (name === 'Metadata') {
|
|
109
|
+
const binding = value.binding, target = value.target, transform = value.coordinates.imageToSourceTransform, rect = value.sourceRect;
|
|
110
|
+
requireValue(target.id === binding.targetId && target.workstation === binding.workstation && target.desktopId === binding.desktopId && target.inputDomain === binding.inputDomainId);
|
|
111
|
+
requireValue(Math.abs(transform.originX - rect.x) <= 0.001 && Math.abs(transform.originY - rect.y) <= 0.001 &&
|
|
112
|
+
Math.abs(value.width * transform.scaleX - rect.width) <= 0.001 && Math.abs(value.height * transform.scaleY - rect.height) <= 0.001);
|
|
113
|
+
}
|
|
114
|
+
if (name === 'Transfer') {
|
|
115
|
+
const header = value.header, metadata = value.metadata, binding = metadata.binding;
|
|
116
|
+
requireValue(header.consumerId === binding.consumerId && header.deviceGeneration === binding.revisions.deviceGeneration &&
|
|
117
|
+
header.controlGeneration === binding.revisions.controlGeneration && header.inputDomainId === binding.inputDomainId &&
|
|
118
|
+
header.captureSerial === binding.captureSerial && header.mimeType === metadata.mimeType);
|
|
119
|
+
}
|
|
120
|
+
if (name === 'ActRequest')
|
|
121
|
+
requireValue(value.actions.reduce((sum, action) => sum + (action.type === 'wait' ? action.durationMs : action.type === 'drag' ? (action.durationMs ?? 500) : 0), 0) <= 10000);
|
|
122
|
+
if (name === 'ActionResult') {
|
|
123
|
+
requireValue(value.postImage == null || value.captureError == null);
|
|
124
|
+
requireValue(value.receipts.length <= (value.actionCount ?? value.receipts.length));
|
|
125
|
+
value.receipts.forEach((receipt, index) => {
|
|
126
|
+
requireValue(receipt.index === index && (index === value.receipts.length - 1 || receipt.outcome === 'injected'));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function envelope(value, numbers) {
|
|
131
|
+
requireValue(equal(Object.keys(value).sort(), [...envelopeKeys].sort()) && value.automationProtocol === 1);
|
|
132
|
+
shape(value.automationProtocol, { enum: [1] }, numbers, ['automationProtocol']);
|
|
133
|
+
for (const key of routeKeys)
|
|
134
|
+
shape(value[key], key === 'controlGeneration' ? { integer: true, min: 0, max: Number.MAX_SAFE_INTEGER } : automationSchema.types.Identity, numbers, [key]);
|
|
135
|
+
requireValue(typeof value.method === 'string' && Object.hasOwn(automationSchema.requests, value.method));
|
|
136
|
+
}
|
|
137
|
+
export function decodeAutomationRequest(source) {
|
|
138
|
+
const parsed = parseAutomationJson(source, maxJson), value = object(parsed.value);
|
|
139
|
+
envelope(value, parsed.numbers);
|
|
140
|
+
shape(value.payload, automationSchema.requests[value.method], parsed.numbers, ['payload']);
|
|
141
|
+
if (value.method === 'open')
|
|
142
|
+
requireValue((value.payload.mode ?? 'observer') === 'controller' || (value.payload.acquisition?.type ?? 'if_free') === 'if_free');
|
|
143
|
+
if (value.method === 'open' && value.payload.acquisition?.type === 'takeover')
|
|
144
|
+
requireValue(value.payload.acquisition.expectedDeviceGeneration === value.deviceGeneration);
|
|
145
|
+
if (value.method === 'act')
|
|
146
|
+
requireValue(value.payload.request.observationId === value.payload.admission.delivery.binding.observationId);
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
export function encodeAutomationRequest(value) {
|
|
150
|
+
const text = JSON.stringify(value);
|
|
151
|
+
decodeAutomationRequest(text);
|
|
152
|
+
return text;
|
|
153
|
+
}
|
|
154
|
+
export function validateAutomationRequestFor(value, route, owner, observation, hostDelivery = false) {
|
|
155
|
+
decodeAutomationRequest(JSON.stringify(value));
|
|
156
|
+
requireValue(route && ['consumerId', 'workstation', 'desktopId', 'inputDomainId', 'deviceGeneration', 'controlGeneration'].every(key => route[key] !== undefined));
|
|
157
|
+
for (const key of ['consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'])
|
|
158
|
+
requireValue(value[key] === route[key]);
|
|
159
|
+
const payload = value.payload;
|
|
160
|
+
if (payload.workstation !== undefined)
|
|
161
|
+
requireValue(payload.workstation === route.workstation);
|
|
162
|
+
const handle = payload.handle ?? payload.capture?.handle ?? payload.request?.handle;
|
|
163
|
+
if (handle)
|
|
164
|
+
requireValue(owner && equal(owner.handle, handle) && equal(owner.route, route));
|
|
165
|
+
const binding = value.method === 'act' ? payload.admission.delivery.binding : value.method === 'delivery_ack' ? payload.binding : undefined;
|
|
166
|
+
if (binding) {
|
|
167
|
+
requireValue(equal(binding, observation));
|
|
168
|
+
validateObservationRoute(binding, route);
|
|
169
|
+
if (value.method === 'act')
|
|
170
|
+
requireValue(owner?.targetId === binding.targetId && payload.request.observationId === binding.observationId);
|
|
171
|
+
else
|
|
172
|
+
requireValue(hostDelivery);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function validateObservationRoute(binding, route) {
|
|
176
|
+
for (const key of ['consumerId', 'workstation', 'desktopId', 'inputDomainId'])
|
|
177
|
+
requireValue(binding[key] === route[key]);
|
|
178
|
+
requireValue(binding.revisions.deviceGeneration === route.deviceGeneration && binding.revisions.controlGeneration === route.controlGeneration);
|
|
179
|
+
}
|
|
180
|
+
function transfers(value) {
|
|
181
|
+
if (value.payload.type !== 'success')
|
|
182
|
+
return [];
|
|
183
|
+
if (value.method === 'capture')
|
|
184
|
+
return [value.payload.data];
|
|
185
|
+
if (value.method === 'capture_wait_v1' && value.payload.data.image)
|
|
186
|
+
return [value.payload.data.image];
|
|
187
|
+
if ((value.method === 'act' || value.method === 'status') && value.payload.data.postImage)
|
|
188
|
+
return [value.payload.data.postImage];
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
export function decodeAutomationResponse(source, request) {
|
|
192
|
+
decodeAutomationRequest(JSON.stringify(request));
|
|
193
|
+
const parsed = parseAutomationJson(source, maxJson), value = object(parsed.value);
|
|
194
|
+
envelope(value, parsed.numbers);
|
|
195
|
+
for (const key of ['automationProtocol', ...routeKeys, 'method'])
|
|
196
|
+
requireValue(value[key] === request[key]);
|
|
197
|
+
if (value.payload?.type === 'failure')
|
|
198
|
+
shape(value.payload, automationSchema.types.Failure, parsed.numbers, ['payload']);
|
|
199
|
+
else {
|
|
200
|
+
requireValue(equal(Object.keys(object(value.payload)).sort(), ['data', 'type']) && value.payload.type === 'success');
|
|
201
|
+
shape(value.payload.data, automationSchema.responses[value.method], parsed.numbers, ['payload', 'data']);
|
|
202
|
+
if (value.method === 'open')
|
|
203
|
+
requireValue(value.payload.data.mode !== 'controller' || value.payload.data.lease !== null);
|
|
204
|
+
}
|
|
205
|
+
for (const transfer of transfers(value))
|
|
206
|
+
for (const key of ['automationProtocol', ...routeKeys])
|
|
207
|
+
requireValue(transfer.header[key] === request[key]);
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
export function acceptsSequence(previous, incoming) {
|
|
211
|
+
return Number.isSafeInteger(previous) && previous >= 0 && Number.isSafeInteger(incoming) && incoming > previous;
|
|
212
|
+
}
|
|
213
|
+
export function encodeAutomationBinary(header, body) {
|
|
214
|
+
shape(header, automationSchema.types.Header);
|
|
215
|
+
requireValue(body.byteLength === header.bytesLength);
|
|
216
|
+
const text = new TextEncoder().encode(JSON.stringify(header));
|
|
217
|
+
requireValue(text.length <= maxJson);
|
|
218
|
+
const frame = new Uint8Array(4 + text.length + body.length);
|
|
219
|
+
new DataView(frame.buffer).setUint32(0, text.length, false);
|
|
220
|
+
frame.set(text, 4);
|
|
221
|
+
frame.set(body, 4 + text.length);
|
|
222
|
+
return frame;
|
|
223
|
+
}
|
|
224
|
+
export function decodeAutomationBinaryHeader(source) {
|
|
225
|
+
const parsed = parseAutomationJson(source, maxJson), header = object(parsed.value);
|
|
226
|
+
shape(header, automationSchema.types.Header, parsed.numbers);
|
|
227
|
+
return header;
|
|
228
|
+
}
|
|
229
|
+
export class AutomationBinaryReceiver {
|
|
230
|
+
consumed = false;
|
|
231
|
+
header;
|
|
232
|
+
constructor(request, transfer, route) {
|
|
233
|
+
decodeAutomationRequest(JSON.stringify(request));
|
|
234
|
+
requireValue(['capture', 'capture_wait_v1', 'act', 'status'].includes(request.method));
|
|
235
|
+
shape(transfer, automationSchema.types.Transfer);
|
|
236
|
+
this.header = JSON.parse(JSON.stringify(transfer.header));
|
|
237
|
+
for (const key of ['automationProtocol', ...routeKeys])
|
|
238
|
+
requireValue(this.header[key] === request[key]);
|
|
239
|
+
validateObservationRoute(transfer.metadata.binding, route);
|
|
240
|
+
for (const key of ['consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'])
|
|
241
|
+
requireValue(request[key] === route[key]);
|
|
242
|
+
}
|
|
243
|
+
accept(frame) {
|
|
244
|
+
requireValue(!this.consumed && frame.length >= 4 && frame.length <= 4 + maxJson + 8388608);
|
|
245
|
+
const length = new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(0, false);
|
|
246
|
+
requireValue(length > 0 && length <= maxJson && frame.length === 4 + length + this.header.bytesLength);
|
|
247
|
+
const parsed = parseAutomationJson(frame.subarray(4, 4 + length), maxJson), header = object(parsed.value);
|
|
248
|
+
shape(header, automationSchema.types.Header, parsed.numbers);
|
|
249
|
+
requireValue(equal(header, this.header));
|
|
250
|
+
this.consumed = true;
|
|
251
|
+
return frame.slice(4 + length);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const automationSchema = { "whitespaceRanges": [[9, 13], [28, 32], [160, 160], [5760, 5760], [8192, 8202], [8232, 8233], [8239, 8239], [8287, 8287], [12288, 12288]], "types": { "Identity": { "string": true, "max": 256, "blank": false }, "SelectorText": { "string": true, "max": 4096, "blank": false }, "Handle": { "fields": { "id": { "ref": "Identity" } }, "optional": [] }, "Point": { "fields": { "x": { "number": true }, "y": { "number": true } }, "optional": [] }, "Rect": { "fields": { "x": { "number": true }, "y": { "number": true }, "width": { "number": true, "positive": true }, "height": { "number": true, "positive": true } }, "optional": [] }, "Transform": { "fields": { "originX": { "number": true }, "originY": { "number": true }, "scaleX": { "number": true, "positive": true }, "scaleY": { "number": true, "positive": true } }, "optional": [] }, "Revisions": { "fields": { "deviceGeneration": { "ref": "Identity" }, "controlGeneration": { "integer": true, "min": 0, "max": 9007199254740991 }, "targetRevision": { "integer": true, "min": 0, "max": 9007199254740991 }, "geometryRevision": { "integer": true, "min": 0, "max": 9007199254740991 } }, "optional": [] }, "Binding": { "fields": { "consumerId": { "ref": "Identity" }, "workstation": { "ref": "Identity" }, "desktopId": { "ref": "Identity" }, "targetId": { "ref": "Identity" }, "revisions": { "ref": "Revisions" }, "observationId": { "ref": "Identity" }, "captureSerial": { "integer": true, "min": 0, "max": 9007199254740991 }, "captureToken": { "ref": "Identity" }, "inputDomainId": { "ref": "Identity" } }, "optional": [] }, "Delivery": { "fields": { "deliveryId": { "ref": "Identity" }, "binding": { "ref": "Binding" }, "surface": { "enum": ["PROVIDER_REQUEST", "SDK_RETURN"] }, "surfaceRequestId": { "ref": "Identity" } }, "optional": [] }, "Admission": { "fields": { "token": { "ref": "Identity" }, "delivery": { "ref": "Delivery" } }, "optional": [] }, "ApplicationSelector": { "tag": "type", "variants": { "name": { "fields": { "value": { "ref": "SelectorText" } }, "optional": [] }, "bundle_id": { "fields": { "value": { "ref": "SelectorText" } }, "optional": [] }, "path": { "fields": { "value": { "ref": "SelectorText" } }, "optional": [] } } }, "TargetSelector": { "tag": "type", "variants": { "display": { "fields": { "displayId": { "ref": "Identity" } }, "optional": [] }, "window": { "fields": { "windowId": { "ref": "Identity" } }, "optional": [] }, "application": { "fields": { "selector": { "ref": "ApplicationSelector" } }, "optional": [] } } }, "Application": { "fields": { "id": { "ref": "Identity" }, "name": { "ref": "SelectorText" }, "installationPath": { "ref": "SelectorText" }, "bundleId": { "nullable": { "ref": "SelectorText" } }, "desktopEntryId": { "nullable": { "ref": "SelectorText" } } }, "optional": ["bundleId", "desktopEntryId"] }, "Process": { "fields": { "pid": { "integer": true, "min": 1, "max": 9007199254740991 }, "instanceId": { "ref": "Identity" } }, "optional": [] }, "Display": { "fields": { "id": { "ref": "Identity" }, "sourceRect": { "ref": "Rect" } }, "optional": [] }, "Window": { "fields": { "id": { "ref": "Identity" }, "title": { "string": true, "max": 16384, "blank": true }, "applicationId": { "nullable": { "ref": "Identity" } }, "process": { "nullable": { "ref": "Process" } }, "capturable": { "boolean": true }, "focused": { "boolean": true }, "main": { "boolean": true }, "sourceRect": { "nullable": { "ref": "Rect" } } }, "optional": ["sourceRect"] }, "Resolution": { "fields": { "application": { "ref": "Application" }, "state": { "enum": ["INSTALLED", "LAUNCH_PENDING", "RUNNING_WITHOUT_WINDOWS", "RUNNING_WITH_WINDOWS"] }, "processes": { "items": { "ref": "Process" }, "min": 0, "max": 64, "unique": false }, "windows": { "items": { "ref": "Window" }, "min": 0, "max": 64, "unique": false } }, "optional": [] }, "Target": { "fields": { "id": { "ref": "Identity" }, "workstation": { "ref": "Identity" }, "desktopId": { "ref": "Identity" }, "inputDomain": { "ref": "Identity" }, "selectionReason": { "enum": ["EXPLICIT_DISPLAY", "EXPLICIT_WINDOW", "FOCUSED_APPLICATION_WINDOW", "MAIN_APPLICATION_WINDOW", "UNIQUE_CAPTURABLE_WINDOW"] }, "display": { "nullable": { "ref": "Display" } }, "window": { "nullable": { "ref": "Window" } }, "application": { "nullable": { "ref": "Application" } }, "process": { "nullable": { "ref": "Process" } } }, "optional": ["display", "window", "application", "process"] }, "Coordinates": { "fields": { "sourceSpace": { "enum": ["DESKTOP_GLOBAL", "WINDOW_LOCAL"] }, "inputUnit": { "enum": ["NATIVE_PIXEL", "LOGICAL_POINT"] }, "imageToSourceTransform": { "ref": "Transform" }, "sourceToInputTransform": { "ref": "Transform" }, "inputSpace": { "enum": ["DESKTOP_GLOBAL", "WINDOW_LOCAL"] }, "sourceUnit": { "enum": ["NATIVE_PIXEL"] } }, "optional": ["inputSpace", "sourceUnit"] }, "Fidelity": { "fields": { "captureMode": { "enum": ["NATIVE_SURFACE", "VISIBLE_CROP"] }, "stable": { "nullable": { "boolean": true } }, "limitations": { "items": { "enum": ["OCCLUDED", "MINIMIZED", "OTHER_SPACE", "CURSOR_EXCLUDED", "STABILITY_UNKNOWN"] }, "min": 0, "max": 64, "unique": false } }, "optional": [] }, "Metadata": { "fields": { "binding": { "ref": "Binding" }, "mimeType": { "enum": ["image/png", "image/jpeg"] }, "width": { "integer": true, "min": 1, "max": 4096 }, "height": { "integer": true, "min": 1, "max": 4096 }, "sourceRect": { "ref": "Rect" }, "coordinates": { "ref": "Coordinates" }, "capturedAtEpochMs": { "integer": true, "min": 0, "max": 9007199254740991 }, "afterActionId": { "nullable": { "ref": "Identity" } }, "appliedInputSequence": { "nullable": { "integer": true, "min": 0, "max": 9007199254740991 } }, "fidelity": { "ref": "Fidelity" }, "target": { "ref": "Target" }, "parentObservationId": { "nullable": { "ref": "Identity" } }, "cursor": { "nullable": { "fields": { "sourcePosition": { "ref": "Point" }, "includedInImage": { "boolean": true } }, "optional": [] } }, "focus": { "nullable": { "fields": { "windowId": { "nullable": { "ref": "Identity" } }, "applicationId": { "nullable": { "ref": "Identity" } }, "process": { "nullable": { "ref": "Process" } } }, "optional": [] } }, "inputEligible": { "boolean": true } }, "optional": ["parentObservationId", "cursor", "focus", "inputEligible"] }, "Header": { "fields": { "automationProtocol": { "enum": [1] }, "requestId": { "ref": "Identity" }, "consumerId": { "ref": "Identity" }, "deviceGeneration": { "ref": "Identity" }, "controlGeneration": { "integer": true, "min": 0, "max": 9007199254740991 }, "inputDomainId": { "ref": "Identity" }, "transferId": { "ref": "Identity" }, "captureSerial": { "integer": true, "min": 0, "max": 9007199254740991 }, "bytesLength": { "integer": true, "min": 1, "max": 8388608 }, "mimeType": { "enum": ["image/png", "image/jpeg"] } }, "optional": [] }, "Transfer": { "fields": { "header": { "ref": "Header" }, "metadata": { "ref": "Metadata" } }, "optional": [] }, "Acquisition": { "tag": "type", "variants": { "if_free": { "fields": {}, "optional": [] }, "takeover": { "fields": { "expectedOwnershipRevision": { "integer": true, "min": 0, "max": 9007199254740991 }, "expectedDeviceGeneration": { "ref": "Identity" }, "expectedDeviceEpoch": { "ref": "Identity" }, "expectedDeviceRevision": { "integer": true, "min": 0, "max": 9007199254740991 } }, "optional": ["expectedDeviceEpoch", "expectedDeviceRevision"] } } }, "Action": { "tag": "type", "variants": { "move": { "fields": { "point": { "ref": "Point" } }, "optional": [] }, "click": { "fields": { "point": { "ref": "Point" }, "button": { "enum": ["LEFT", "MIDDLE", "RIGHT"] }, "count": { "integer": true, "min": 1, "max": 2 } }, "optional": ["button", "count"] }, "drag": { "fields": { "path": { "items": { "ref": "Point" }, "min": 2, "max": 256, "unique": false }, "button": { "enum": ["LEFT", "MIDDLE", "RIGHT"] }, "durationMs": { "integer": true, "min": 1, "max": 10000 } }, "optional": ["button", "durationMs"] }, "scroll": { "fields": { "deltaX": { "number": true, "min": -10000, "max": 10000 }, "deltaY": { "number": true, "min": -10000, "max": 10000 }, "unit": { "enum": ["PIXEL", "LINE", "PAGE"] }, "point": { "nullable": { "ref": "Point" } } }, "optional": ["point"] }, "key_chord": { "fields": { "keys": { "items": { "string": true, "max": 64, "blank": false }, "min": 1, "max": 8, "unique": true } }, "optional": [] }, "type_text": { "fields": { "text": { "string": true, "max": 16384, "blank": true, "min": 1 }, "mode": { "enum": ["UNICODE", "PASTE"] } }, "optional": ["mode"] }, "focus_window": { "fields": { "windowId": { "ref": "Identity" } }, "optional": [] }, "wait": { "fields": { "durationMs": { "integer": true, "min": 0, "max": 2000 } }, "optional": [] }, "release_all": { "fields": {}, "optional": [] } } }, "ActRequest": { "fields": { "handle": { "ref": "Handle" }, "observationId": { "ref": "Identity" }, "requestId": { "ref": "Identity" }, "actions": { "items": { "ref": "Action" }, "min": 1, "max": 16, "unique": false }, "postObservation": { "boolean": true } }, "optional": ["postObservation"] }, "Controller": { "fields": { "consumerId": { "ref": "Identity" }, "userId": { "nullable": { "ref": "Identity" } }, "ownershipRevision": { "integer": true, "min": 0, "max": 9007199254740991 }, "controlGeneration": { "integer": true, "min": 0, "max": 9007199254740991 } }, "optional": [] }, "Diagnostic": { "fields": { "reason": { "enum": ["capture_timeout", "capture_not_after_action", "too_large", "target_changed", "unsupported_automation_protocol", "delivery_not_acknowledged", "observation_expired", "foreign_handle", "launch_pending", "cancelled", "request_digest_mismatch", "budget_exhausted"] }, "description": { "nullable": { "string": true, "max": 16384, "blank": true } } }, "optional": ["description"] }, "Receipt": { "fields": { "index": { "integer": true, "min": 0, "max": 15 }, "outcome": { "enum": ["accepted", "executing", "injected", "rejected", "unknown"] }, "sequence": { "nullable": { "integer": true, "min": 0, "max": 9007199254740991 } }, "code": { "nullable": { "string": true, "max": 16384, "blank": true } }, "diagnostic": { "nullable": { "ref": "Diagnostic" } } }, "optional": ["diagnostic"] }, "ActionResult": { "fields": { "requestId": { "ref": "Identity" }, "receipts": { "items": { "ref": "Receipt" }, "min": 0, "max": 16, "unique": false }, "postImage": { "nullable": { "ref": "Transfer" } }, "captureError": { "nullable": { "string": true, "max": 16384, "blank": true } }, "captureDiagnostic": { "nullable": { "ref": "Diagnostic" } }, "actionCount": { "integer": true, "min": 0, "max": 16 } }, "optional": ["captureDiagnostic", "actionCount"] }, "LaunchQuery": { "fields": { "workstation": { "ref": "Identity" }, "requestId": { "ref": "Identity" } }, "optional": [] }, "Launch": { "fields": { "identity": { "ref": "LaunchQuery" }, "state": { "enum": ["ACCEPTED", "LAUNCHING", "WINDOW_NOT_READY", "READY", "FAILED", "UNKNOWN", "CANCELLED", "TIMED_OUT"] }, "application": { "nullable": { "ref": "Application" } }, "process": { "nullable": { "ref": "Process" } }, "windows": { "items": { "ref": "Window" }, "min": 0, "max": 64, "unique": false }, "canonicalArgsDigest": { "nullable": { "ref": "Identity" } }, "diagnostic": { "nullable": { "ref": "Diagnostic" } } }, "optional": ["canonicalArgsDigest", "diagnostic"] }, "Candidate": { "tag": "type", "variants": { "display": { "fields": { "display": { "ref": "Display" } }, "optional": [] }, "window": { "fields": { "window": { "ref": "Window" } }, "optional": [] }, "application": { "fields": { "application": { "ref": "Resolution" } }, "optional": [] } } }, "FailureDetail": { "fields": { "diagnostic": { "nullable": { "ref": "Diagnostic" } }, "candidates": { "items": { "ref": "Candidate" }, "min": 0, "max": 64, "unique": false }, "launch": { "nullable": { "ref": "Launch" } }, "controller": { "nullable": { "ref": "Controller" } } }, "optional": ["diagnostic", "candidates", "launch", "controller"] }, "Failure": { "fields": { "type": { "enum": ["failure"] }, "code": { "enum": ["invalid_argument", "forbidden", "unsupported", "not_found", "ambiguous_target", "window_not_ready", "launch_failed", "control_lost", "stale_observation", "outcome_unknown", "resource_limit", "approval_required", "service_unavailable", "conflict", "capture_failed"] }, "message": { "string": true, "max": 16384, "blank": true }, "detail": { "nullable": { "ref": "FailureDetail" } } }, "optional": ["detail"] }, "WaitCondition": { "tag": "type", "variants": { "target_capturable": { "fields": {}, "optional": [] }, "input_applied": { "fields": { "actionId": { "ref": "Identity" } }, "optional": [] }, "changed_from": { "fields": { "observationId": { "ref": "Identity" }, "roi": { "nullable": { "ref": "Rect" } } }, "optional": [] }, "stable": { "fields": { "roi": { "nullable": { "ref": "Rect" } }, "stableMs": { "integer": true, "min": 100, "max": 10000 } }, "optional": [] } } }, "WaitResult": { "fields": { "status": { "enum": ["met", "timeout"] }, "conditionState": { "enum": ["met", "not_met", "unavailable"] }, "elapsedMs": { "integer": true, "min": 0, "max": 60000 }, "image": { "nullable": { "ref": "Transfer" } } }, "optional": [] } }, "requests": { "discover": { "fields": { "workstation": { "ref": "Identity" }, "kinds": { "items": { "enum": ["DISPLAY", "WINDOW", "APPLICATION"] }, "min": 1, "max": 3, "unique": true }, "application": { "nullable": { "ref": "ApplicationSelector" } }, "limit": { "integer": true, "min": 1, "max": 64 }, "cursor": { "nullable": { "string": true, "max": 16384, "blank": true } } }, "optional": ["kinds", "application", "limit", "cursor"] }, "application_resolve": { "fields": { "workstation": { "ref": "Identity" }, "selector": { "ref": "ApplicationSelector" } }, "optional": [] }, "application_ensure": { "fields": { "workstation": { "ref": "Identity" }, "application": { "ref": "Application" }, "requestId": { "ref": "Identity" }, "timeoutMs": { "integer": true, "min": 1, "max": 60000 } }, "optional": ["timeoutMs"] }, "application_status": { "ref": "LaunchQuery" }, "open": { "fields": { "workstation": { "ref": "Identity" }, "target": { "ref": "TargetSelector" }, "mode": { "enum": ["observer", "controller"] }, "acquisition": { "ref": "Acquisition" }, "launchIfNeeded": { "boolean": true }, "requestId": { "ref": "Identity" }, "timeoutMs": { "integer": true, "min": 1, "max": 60000 } }, "optional": ["mode", "acquisition", "launchIfNeeded", "timeoutMs"] }, "capture": { "fields": { "handle": { "ref": "Handle" }, "crop": { "nullable": { "ref": "Rect" } }, "afterActionId": { "nullable": { "ref": "Identity" } }, "timeoutMs": { "integer": true, "min": 1, "max": 60000 }, "maxImageSide": { "integer": true, "min": 1, "max": 4096 }, "maxImageBytes": { "integer": true, "min": 1, "max": 8388608 } }, "optional": ["crop", "afterActionId", "timeoutMs", "maxImageSide", "maxImageBytes"] }, "act": { "fields": { "request": { "ref": "ActRequest" }, "admission": { "ref": "Admission" } }, "optional": [] }, "status": { "fields": { "handle": { "ref": "Handle" }, "requestId": { "ref": "Identity" } }, "optional": [] }, "close": { "fields": { "handle": { "ref": "Handle" } }, "optional": [] }, "delivery_ack": { "ref": "Delivery" }, "capture_wait_v1": { "fields": { "capture": { "fields": { "handle": { "ref": "Handle" }, "crop": { "nullable": { "ref": "Rect" } }, "afterActionId": { "nullable": { "ref": "Identity" } }, "timeoutMs": { "integer": true, "min": 1, "max": 60000 }, "maxImageSide": { "integer": true, "min": 1, "max": 4096 }, "maxImageBytes": { "integer": true, "min": 1, "max": 8388608 } }, "optional": ["crop", "afterActionId", "timeoutMs", "maxImageSide", "maxImageBytes"] }, "condition": { "ref": "WaitCondition" } }, "optional": [] } }, "responses": { "discover": { "fields": { "workstation": { "ref": "Identity" }, "desktopId": { "ref": "Identity" }, "inputDomain": { "ref": "Identity" }, "candidates": { "items": { "ref": "Candidate" }, "min": 0, "max": 64, "unique": false }, "capabilities": { "fields": { "capture": { "boolean": true }, "input": { "boolean": true }, "applicationLaunch": { "boolean": true }, "clipboard": { "boolean": true }, "operations": { "items": { "string": true, "max": 256, "blank": false }, "min": 0, "max": 64, "unique": true } }, "optional": [] }, "controller": { "nullable": { "ref": "Controller" } }, "nextCursor": { "nullable": { "string": true, "max": 16384, "blank": true } }, "deviceOwnership": { "nullable": { "fields": { "deviceEpoch": { "ref": "Identity" }, "revision": { "integer": true, "min": 0, "max": 9007199254740991 }, "state": { "enum": ["free", "draining", "active"] }, "controller": { "nullable": { "fields": { "registrationId": { "ref": "Identity" }, "controllerId": { "ref": "Identity" }, "kind": { "ref": "Identity" } }, "optional": [] } } }, "optional": [] } } }, "optional": ["nextCursor", "deviceOwnership"] }, "application_resolve": { "ref": "Resolution" }, "application_ensure": { "ref": "Launch" }, "application_status": { "ref": "Launch" }, "open": { "fields": { "handle": { "ref": "Handle" }, "target": { "ref": "Target" }, "mode": { "enum": ["observer", "controller"] }, "revisions": { "ref": "Revisions" }, "lease": { "nullable": { "fields": { "id": { "ref": "Identity" }, "expiresAtEpochMs": { "integer": true, "min": 0, "max": 9007199254740991 } }, "optional": [] } } }, "optional": [] }, "capture": { "ref": "Transfer" }, "act": { "ref": "ActionResult" }, "status": { "ref": "ActionResult" }, "close": { "fields": { "handle": { "ref": "Handle" }, "alreadyClosed": { "boolean": true } }, "optional": [] }, "delivery_ack": { "ref": "Admission" }, "capture_wait_v1": { "ref": "WaitResult" } } };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { automationSchema } from './automationProtocol.js';
|
|
3
|
+
export class AutomationSession {
|
|
4
|
+
transport;
|
|
5
|
+
control;
|
|
6
|
+
operations;
|
|
7
|
+
binding;
|
|
8
|
+
statusRequest;
|
|
9
|
+
constructor(transport, binding, control) {
|
|
10
|
+
this.transport = transport;
|
|
11
|
+
this.control = control;
|
|
12
|
+
const route = binding.route;
|
|
13
|
+
if (!['consumerId', 'workstation', 'desktopId', 'inputDomainId', 'deviceGeneration'].every(key => typeof route[key] === 'string' && route[key].length > 0) ||
|
|
14
|
+
!Number.isSafeInteger(route.controlGeneration) || route.controlGeneration < 0 || !binding.executionId)
|
|
15
|
+
throw new Error('live_route_unavailable');
|
|
16
|
+
this.binding = structuredClone(binding);
|
|
17
|
+
this.operations = [...control.operations];
|
|
18
|
+
}
|
|
19
|
+
prepareAttachment(preparation, options) {
|
|
20
|
+
if (this.statusRequest !== undefined)
|
|
21
|
+
return Promise.reject(new Error('control_lost'));
|
|
22
|
+
if (!this.control.prepare)
|
|
23
|
+
return Promise.reject(new Error('unsupported_automation_attachment'));
|
|
24
|
+
return this.control.prepare(structuredClone(preparation), options);
|
|
25
|
+
}
|
|
26
|
+
controlStatus() {
|
|
27
|
+
return this.control.status ? this.control.status() : Promise.reject(new Error('unsupported_automation_control_status'));
|
|
28
|
+
}
|
|
29
|
+
call(method, payload, options) {
|
|
30
|
+
if (this.statusRequest !== undefined && (method !== 'application_status' || payload.requestId !== this.statusRequest))
|
|
31
|
+
return Promise.reject(new Error('control_lost'));
|
|
32
|
+
if (!Object.hasOwn(automationSchema.requests, method) || !this.operations.includes(method))
|
|
33
|
+
return Promise.reject(new Error('unsupported'));
|
|
34
|
+
const route = this.binding.route;
|
|
35
|
+
return this.transport.request({ automationProtocol: 1, requestId: randomBytes(16).toString('hex'), consumerId: route.consumerId,
|
|
36
|
+
deviceGeneration: route.deviceGeneration, controlGeneration: route.controlGeneration, inputDomainId: route.inputDomainId, method, payload }, options);
|
|
37
|
+
}
|
|
38
|
+
execute(request, options) {
|
|
39
|
+
if (this.statusRequest !== undefined && (request.method !== 'application_status' || request.payload.requestId !== this.statusRequest))
|
|
40
|
+
return Promise.reject(new Error('control_lost'));
|
|
41
|
+
if (!this.operations.includes(request.method))
|
|
42
|
+
return Promise.reject(new Error('unsupported'));
|
|
43
|
+
if (['consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'].some(key => request[key] !== this.binding.route[key]))
|
|
44
|
+
return Promise.reject(new Error('stale_generation'));
|
|
45
|
+
if (request.method === 'close')
|
|
46
|
+
this.transport.cancelPending();
|
|
47
|
+
return this.transport.request(request, options);
|
|
48
|
+
}
|
|
49
|
+
discover(payload, options) { return this.call('discover', payload, options); }
|
|
50
|
+
applicationResolve(payload, options) { return this.call('application_resolve', payload, options); }
|
|
51
|
+
applicationEnsure(payload, options) { return this.call('application_ensure', payload, options); }
|
|
52
|
+
applicationStatus(payload, options) { return this.call('application_status', payload, options); }
|
|
53
|
+
open(payload, options) { return this.call('open', payload, options); }
|
|
54
|
+
capture(payload, options) { return this.call('capture', payload, options); }
|
|
55
|
+
act(payload, options) { return this.call('act', payload, options); }
|
|
56
|
+
status(payload, options) { return this.call('status', payload, options); }
|
|
57
|
+
close(payload, options) { this.transport.cancelPending(); return this.call('close', payload, options); }
|
|
58
|
+
deliveryAck(payload, options) { return this.call('delivery_ack', payload, options); }
|
|
59
|
+
async renewAuthenticatedLease(leaseMs) {
|
|
60
|
+
if (this.statusRequest !== undefined)
|
|
61
|
+
return { admitted: true, physical: 'failed' };
|
|
62
|
+
if (!Number.isInteger(leaseMs) || leaseMs < 1 || leaseMs > 30000)
|
|
63
|
+
return Promise.reject(new Error('invalid_argument'));
|
|
64
|
+
return await this.control.renew(leaseMs) ?? { admitted: true, physical: 'pending' };
|
|
65
|
+
}
|
|
66
|
+
async dispose() { this.transport.close(); await this.control.close(); }
|
|
67
|
+
async detachApplicationStatus(requestId, digest) {
|
|
68
|
+
if (this.statusRequest !== undefined && this.statusRequest !== requestId)
|
|
69
|
+
throw new Error('conflict');
|
|
70
|
+
this.statusRequest = requestId;
|
|
71
|
+
if (!this.control.readControl)
|
|
72
|
+
throw new Error('unsupported');
|
|
73
|
+
return this.control.readControl('status_only', { launchRequestId: requestId, canonicalArgsDigest: digest });
|
|
74
|
+
}
|
|
75
|
+
async isolate() {
|
|
76
|
+
this.statusRequest = this.statusRequest ?? '';
|
|
77
|
+
if (!this.control.readControl)
|
|
78
|
+
throw new Error('unsupported');
|
|
79
|
+
return this.control.readControl('isolate', {});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { parseAutomationJson } from './automationJson.js';
|
|
2
|
+
import { AutomationBinaryReceiver, decodeAutomationResponse, decodeAutomationBinaryHeader, encodeAutomationBinary, encodeAutomationRequest } from './automationProtocol.js';
|
|
3
|
+
export class AutomationTransport {
|
|
4
|
+
send;
|
|
5
|
+
cancel;
|
|
6
|
+
pending = new Map();
|
|
7
|
+
ended = false;
|
|
8
|
+
imageBytes = 0;
|
|
9
|
+
constructor(send, cancel = () => { }) {
|
|
10
|
+
this.send = send;
|
|
11
|
+
this.cancel = cancel;
|
|
12
|
+
}
|
|
13
|
+
pendingCount() { return this.pending.size; }
|
|
14
|
+
request(value, options = {}) {
|
|
15
|
+
if (this.ended)
|
|
16
|
+
return Promise.reject(new Error('closed'));
|
|
17
|
+
if (this.pending.size >= 64)
|
|
18
|
+
return Promise.reject(new Error('resource_limit'));
|
|
19
|
+
if (options.signal?.aborted)
|
|
20
|
+
return Promise.reject(new Error('cancelled'));
|
|
21
|
+
const request = structuredClone(value), text = encodeAutomationRequest(request);
|
|
22
|
+
if (this.pending.has(request.requestId))
|
|
23
|
+
return Promise.reject(new Error('conflict'));
|
|
24
|
+
const timeout = options.timeoutMs ?? 65000;
|
|
25
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 65000)
|
|
26
|
+
return Promise.reject(new Error('invalid_argument'));
|
|
27
|
+
const promise = new Promise((resolve, reject) => {
|
|
28
|
+
const abort = () => this.retire(request.requestId, 'cancelled');
|
|
29
|
+
const timer = setTimeout(() => this.retire(request.requestId, 'timed_out'), timeout);
|
|
30
|
+
this.pending.set(request.requestId, { request, resolve, reject, timer, bytes: 0,
|
|
31
|
+
detach: () => options.signal?.removeEventListener('abort', abort) });
|
|
32
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
33
|
+
});
|
|
34
|
+
void this.send(text).catch(() => this.retire(request.requestId, 'transport_failed'));
|
|
35
|
+
return promise;
|
|
36
|
+
}
|
|
37
|
+
acceptResponse(text) {
|
|
38
|
+
try {
|
|
39
|
+
const raw = parseAutomationJson(text, 49152).value;
|
|
40
|
+
const pending = this.pending.get(raw.requestId);
|
|
41
|
+
if (!pending || pending.response)
|
|
42
|
+
return { ok: false, code: 'unknown_request' };
|
|
43
|
+
const response = decodeAutomationResponse(text, pending.request);
|
|
44
|
+
const transfer = response.payload.type === 'success' ? response.method === 'capture' ? response.payload.data :
|
|
45
|
+
response.method === 'capture_wait_v1' ? response.payload.data.image : ['act', 'status'].includes(response.method) ? response.payload.data.postImage : undefined : undefined;
|
|
46
|
+
if (transfer) {
|
|
47
|
+
const size = transfer.header.bytesLength;
|
|
48
|
+
if (this.imageBytes - pending.bytes + size > 8388608) {
|
|
49
|
+
this.retire(raw.requestId, 'resource_limit');
|
|
50
|
+
return { ok: false, code: 'resource_limit' };
|
|
51
|
+
}
|
|
52
|
+
pending.receiver = new AutomationBinaryReceiver(pending.request, transfer, {
|
|
53
|
+
...pending.request, workstation: transfer.metadata.binding.workstation, desktopId: transfer.metadata.binding.desktopId,
|
|
54
|
+
});
|
|
55
|
+
this.imageBytes += size - pending.bytes;
|
|
56
|
+
pending.bytes = size;
|
|
57
|
+
pending.response = response;
|
|
58
|
+
if (pending.early) {
|
|
59
|
+
const early = pending.early;
|
|
60
|
+
pending.early = undefined;
|
|
61
|
+
this.acceptBinary(early);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
this.remove(raw.requestId)?.resolve({ response });
|
|
66
|
+
}
|
|
67
|
+
return { ok: true };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { ok: false, code: 'invalid_response' };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
acceptImage(header, bytes) {
|
|
74
|
+
if (bytes.byteLength !== (header.bytesLength ?? header.byteLength))
|
|
75
|
+
return { ok: false, code: 'invalid_length' };
|
|
76
|
+
try {
|
|
77
|
+
return this.acceptBinary(encodeAutomationBinary(header, bytes));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { ok: false, code: 'invalid_binary' };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
acceptBinary(frame) {
|
|
84
|
+
try {
|
|
85
|
+
if (frame.length < 4 || frame.length > 8388608 + 49156)
|
|
86
|
+
throw new Error();
|
|
87
|
+
const length = new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(0, false);
|
|
88
|
+
if (length < 1 || length > 49152 || frame.length < 4 + length)
|
|
89
|
+
throw new Error();
|
|
90
|
+
const header = decodeAutomationBinaryHeader(frame.subarray(4, 4 + length));
|
|
91
|
+
const pending = this.pending.get(header.requestId);
|
|
92
|
+
if (!pending)
|
|
93
|
+
return { ok: false, code: 'unknown_request' };
|
|
94
|
+
if (!pending.receiver || !pending.response) {
|
|
95
|
+
if (!['capture', 'capture_wait_v1', 'act', 'status'].includes(pending.request.method) || frame.length !== 4 + length + header.bytesLength || pending.early ||
|
|
96
|
+
['consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'].some(key => header[key] !== pending.request[key]))
|
|
97
|
+
return { ok: false, code: 'invalid_binary' };
|
|
98
|
+
if (this.imageBytes + header.bytesLength > 8388608)
|
|
99
|
+
return { ok: false, code: 'resource_limit' };
|
|
100
|
+
pending.early = Buffer.from(frame);
|
|
101
|
+
pending.bytes = header.bytesLength;
|
|
102
|
+
this.imageBytes += pending.bytes;
|
|
103
|
+
return { ok: true };
|
|
104
|
+
}
|
|
105
|
+
const image = Buffer.from(pending.receiver.accept(frame));
|
|
106
|
+
this.remove(header.requestId)?.resolve({ response: pending.response, image });
|
|
107
|
+
return { ok: true };
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return { ok: false, code: 'invalid_binary' };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
remove(requestId) {
|
|
114
|
+
const pending = this.pending.get(requestId);
|
|
115
|
+
if (pending) {
|
|
116
|
+
this.pending.delete(requestId);
|
|
117
|
+
clearTimeout(pending.timer);
|
|
118
|
+
pending.detach();
|
|
119
|
+
this.imageBytes -= pending.bytes;
|
|
120
|
+
}
|
|
121
|
+
return pending;
|
|
122
|
+
}
|
|
123
|
+
retire(requestId, code) {
|
|
124
|
+
const pending = this.remove(requestId);
|
|
125
|
+
if (pending) {
|
|
126
|
+
pending.reject(new Error(code));
|
|
127
|
+
try {
|
|
128
|
+
this.cancel(requestId);
|
|
129
|
+
}
|
|
130
|
+
catch { }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
invalidateGeneration(_generation) { this.close('stale_generation'); }
|
|
134
|
+
cancelPending() { for (const requestId of this.pending.keys())
|
|
135
|
+
this.retire(requestId, 'closed'); }
|
|
136
|
+
close(code = 'closed') { this.ended = true; for (const requestId of this.pending.keys())
|
|
137
|
+
this.retire(requestId, code); }
|
|
138
|
+
}
|