2099 lines
66 KiB
TypeScript
2099 lines
66 KiB
TypeScript
import type { ViewerSettings } from "@mission-core/plugin-sdk";
|
|
import { xgridsK1Actions, xgridsK1Manifest } from "./manifest";
|
|
|
|
const PLUGIN_ID = xgridsK1Manifest.metadata.id;
|
|
|
|
export interface BleDevice {
|
|
device_id: string;
|
|
name?: string | null;
|
|
rssi?: number | null;
|
|
address?: string | null;
|
|
connectable?: boolean | null;
|
|
likely_k1?: boolean | null;
|
|
}
|
|
|
|
export type SourceMode = "idle" | "live" | "replay";
|
|
export const XGRIDS_ACTIVE_STREAM_RECOVERY_STATES = [
|
|
"inactive",
|
|
"reconnecting",
|
|
"blocked",
|
|
"recovered",
|
|
"standby",
|
|
"fault",
|
|
"force-finishing",
|
|
"force-finished",
|
|
] as const;
|
|
export type XgridsActiveStreamRecoveryState =
|
|
typeof XGRIDS_ACTIVE_STREAM_RECOVERY_STATES[number];
|
|
export const XGRIDS_CAMERA_RECOVERY_STATES = [
|
|
"inactive",
|
|
"owned",
|
|
"blocked",
|
|
] as const;
|
|
export type XgridsCameraRecoveryState =
|
|
typeof XGRIDS_CAMERA_RECOVERY_STATES[number];
|
|
export const XGRIDS_CAMERA_MEDIA_STATES = [
|
|
"inactive",
|
|
"pending-epoch",
|
|
"pending-init",
|
|
"pending-first-media",
|
|
"ready",
|
|
] as const;
|
|
export type XgridsCameraMediaState =
|
|
typeof XGRIDS_CAMERA_MEDIA_STATES[number];
|
|
export const XGRIDS_CONNECTION_MODES = [
|
|
"bridge",
|
|
"quick-connect",
|
|
"direct-connect",
|
|
] as const;
|
|
export type XgridsConnectionMode = typeof XGRIDS_CONNECTION_MODES[number];
|
|
|
|
export const XGRIDS_HOST_DIAGNOSTIC_CODES = [
|
|
"host.bluetooth.permission-denied",
|
|
"host.bluetooth.adapter-powered-off",
|
|
"host.bluetooth.adapter-unavailable",
|
|
"host.bluetooth.runtime-unavailable",
|
|
"host.bluetooth.operation-timeout",
|
|
"host.wifi.permission-denied",
|
|
"host.wifi.adapter-powered-off",
|
|
"host.wifi.interface-unavailable",
|
|
"host.wifi.ssid-unavailable",
|
|
"host.wifi.operation-timeout",
|
|
"host.wifi.association-failed",
|
|
"host.keychain.interaction-required",
|
|
"host.keychain.permission-denied",
|
|
"host.keychain.unavailable",
|
|
"host.route.unavailable",
|
|
"host.tcp.connection-refused",
|
|
"host.tcp.connection-timeout",
|
|
"host.tcp.endpoint-unavailable",
|
|
"host.mqtt.connection-timeout",
|
|
"host.mqtt.connection-refused",
|
|
"host.mqtt.transport-unavailable",
|
|
"host.filesystem.permission-denied",
|
|
"host.filesystem.ledger-unavailable",
|
|
] as const;
|
|
|
|
export const XGRIDS_HOST_DIAGNOSTIC_DOMAINS = [
|
|
"corebluetooth",
|
|
"corewlan",
|
|
"keychain",
|
|
"route",
|
|
"tcp",
|
|
"mqtt",
|
|
"filesystem",
|
|
] as const;
|
|
|
|
export const XGRIDS_HOST_DIAGNOSTIC_IMPACTS = [
|
|
"discovery",
|
|
"host-network",
|
|
"control",
|
|
"durable-safety",
|
|
] as const;
|
|
|
|
export const XGRIDS_HOST_DIAGNOSTIC_ACTIONS = [
|
|
"grant-bluetooth-permission",
|
|
"power-on-bluetooth",
|
|
"restore-bluetooth-adapter",
|
|
"grant-wifi-permission",
|
|
"power-on-wifi",
|
|
"restore-wifi-interface",
|
|
"unlock-or-authorize-keychain",
|
|
"review-keychain-access",
|
|
"join-expected-network",
|
|
"inspect-host-route",
|
|
"verify-broker-endpoint",
|
|
"inspect-local-storage",
|
|
"restart-local-service",
|
|
"explicit-retry",
|
|
] as const;
|
|
|
|
export type XgridsHostDiagnosticCode = typeof XGRIDS_HOST_DIAGNOSTIC_CODES[number];
|
|
export type XgridsHostDiagnosticDomain = typeof XGRIDS_HOST_DIAGNOSTIC_DOMAINS[number];
|
|
export type XgridsHostDiagnosticImpact = typeof XGRIDS_HOST_DIAGNOSTIC_IMPACTS[number];
|
|
export type XgridsHostDiagnosticAction = typeof XGRIDS_HOST_DIAGNOSTIC_ACTIONS[number];
|
|
|
|
export interface XgridsHostFailureDiagnostic {
|
|
schema_version: "missioncore.host-failure-diagnostic/v1";
|
|
code: XgridsHostDiagnosticCode;
|
|
domain: XgridsHostDiagnosticDomain;
|
|
impact: XgridsHostDiagnosticImpact;
|
|
operator_action: XgridsHostDiagnosticAction;
|
|
automatic_retry: false;
|
|
redacted: true;
|
|
}
|
|
|
|
export type AcquisitionState =
|
|
| "preparing"
|
|
| "prepared"
|
|
| "awaiting_external_start"
|
|
| "starting"
|
|
| "acquiring"
|
|
| "awaiting_external_stop"
|
|
| "stopping"
|
|
| "finalizing"
|
|
| "completed"
|
|
| "failed"
|
|
| "aborted"
|
|
| "interrupted";
|
|
|
|
export type OperationStatus =
|
|
| "accepted"
|
|
| "running"
|
|
| "operator_action_required"
|
|
| "succeeded"
|
|
| "failed"
|
|
| "cancelled"
|
|
| "timed_out"
|
|
| "interrupted";
|
|
|
|
export interface XgridsDeviceRef {
|
|
device_id: string;
|
|
model_id: string;
|
|
identity_stability: "stable" | "provisional";
|
|
identity_basis:
|
|
| "hardware-identifier"
|
|
| "plugin-derived"
|
|
| "operator-assigned"
|
|
| "transport-local";
|
|
transport_alias?: string | null;
|
|
}
|
|
|
|
export interface XgridsDeviceSession {
|
|
device_session_id: string;
|
|
device_id: string;
|
|
opened_at?: string | null;
|
|
compatibility_profile_id?: string | null;
|
|
connectivity?: "unknown" | "offline" | "connecting" | "connected" | "degraded";
|
|
}
|
|
|
|
export const XGRIDS_CONNECTION_VERIFICATION_STATUSES = [
|
|
"not-probed",
|
|
"device-network-applied",
|
|
"device-network-applied-host-failed",
|
|
"adopted",
|
|
"host-route-mismatch",
|
|
"endpoint-unreachable",
|
|
"tcp-reachable-device-info-unverified",
|
|
"reachable",
|
|
"recovered",
|
|
"control-transport-lost",
|
|
"unreachable",
|
|
] as const;
|
|
|
|
export const XGRIDS_CONNECTION_VERIFICATION_LEASE_STATES = [
|
|
"disconnected",
|
|
"configured-unverified",
|
|
"reachable",
|
|
] as const;
|
|
|
|
export const XGRIDS_CONNECTION_NETWORK_REACHABILITY = [
|
|
"unknown",
|
|
"reachable",
|
|
"unreachable",
|
|
] as const;
|
|
|
|
export type XgridsConnectionVerificationStatus =
|
|
typeof XGRIDS_CONNECTION_VERIFICATION_STATUSES[number];
|
|
export type XgridsConnectionVerificationLeaseState =
|
|
typeof XGRIDS_CONNECTION_VERIFICATION_LEASE_STATES[number];
|
|
export type XgridsConnectionNetworkReachability =
|
|
typeof XGRIDS_CONNECTION_NETWORK_REACHABILITY[number];
|
|
|
|
export interface XgridsConnectionVerification {
|
|
status: XgridsConnectionVerificationStatus;
|
|
lease_state: XgridsConnectionVerificationLeaseState;
|
|
lease_generation: number;
|
|
supervisor_revision: number;
|
|
endpoint_validation: string | null;
|
|
network_reachability: XgridsConnectionNetworkReachability;
|
|
host_route_class?: string | null;
|
|
address_source?: string | null;
|
|
connection_origin?: string | null;
|
|
admission_source?: string | null;
|
|
address_changed?: boolean;
|
|
previous_address_present?: boolean;
|
|
write_performed?: boolean;
|
|
observed_at: string | null;
|
|
last_known_reachable_at?: string | null;
|
|
reason_code?: string | null;
|
|
}
|
|
|
|
export interface XgridsConfiguredEndpointProbe {
|
|
schema_version: "missioncore.xgrids-k1-configured-endpoint-probe/v1";
|
|
status: "not-probed" | "reachable" | "endpoint-unreachable" | "host-route-unavailable";
|
|
target_source: "current-supervisor" | "durable-semantic-topology" | null;
|
|
connection_mode: XgridsConnectionMode | null;
|
|
endpoint: string | null;
|
|
transport_ref: string | null;
|
|
intent_id: string | null;
|
|
semantic_revision: number | null;
|
|
host_route_available: boolean | null;
|
|
host_route_class: string | null;
|
|
tcp_reachable: boolean | null;
|
|
identity_validation: "not-performed";
|
|
control_authority_granted: false;
|
|
ble_operation_performed: false;
|
|
network_mutation_performed: false;
|
|
automatic_retry: false;
|
|
observed_at: string | null;
|
|
reason_code: string | null;
|
|
}
|
|
|
|
export interface XgridsNetworkWriteReconciliation {
|
|
status:
|
|
| "prepared-before-dispatch"
|
|
| "device-state-unknown-after-write"
|
|
| "durable-ledger-corrupt";
|
|
operation_id: string;
|
|
transport_ref: string;
|
|
connection_mode: XgridsConnectionMode;
|
|
operation_stage: "prepared" | "dispatching" | "observing" | "ledger-corrupt";
|
|
reason_code: string;
|
|
device_write_confirmed: boolean;
|
|
required_action:
|
|
| "restart-service-to-resolve-prepared"
|
|
| "explicit-read-only-ble-status-observation"
|
|
| "operator-ledger-diagnosis";
|
|
scope: "durable-ledger";
|
|
ledger_revision: number | null;
|
|
observed_at: string | null;
|
|
}
|
|
|
|
export interface XgridsNetworkMutationLedger {
|
|
status: "empty" | "unresolved" | "resolved" | "corrupt";
|
|
mutation_allowed: boolean;
|
|
reason_code: string | null;
|
|
operation_id: string | null;
|
|
transport_ref: string | null;
|
|
intended_mode: XgridsConnectionMode | null;
|
|
stage: "prepared" | "dispatching" | "observing" | "resolved" | null;
|
|
revision: number | null;
|
|
resolution:
|
|
| "not-dispatched"
|
|
| "target-observed"
|
|
| "interrupted"
|
|
| "superseded"
|
|
| null;
|
|
updated_at_utc: string | null;
|
|
diagnostic?: XgridsHostFailureDiagnostic | null;
|
|
}
|
|
|
|
export interface XgridsCurrentDeviceRecovery {
|
|
transport_ref?: string | null;
|
|
connection_mode?: XgridsConnectionMode | null;
|
|
handle_available?: boolean;
|
|
handle_retained?: boolean;
|
|
advertised_now?: boolean;
|
|
gatt_validated_recently?: boolean;
|
|
observed_at?: string | null;
|
|
}
|
|
|
|
export const XGRIDS_CONNECTION_POLICY_ACTIONS = [
|
|
"scan-ble",
|
|
"provision-fresh-device",
|
|
"prepare-select-device",
|
|
"prepare-change-network",
|
|
"cancel-reconfiguration",
|
|
"recover-current-device-network",
|
|
"observe-fresh-device-network",
|
|
"observe-current-device-network",
|
|
"observe-configured-device-network",
|
|
"inspect-configured-endpoint",
|
|
"inspect-host-network",
|
|
"probe-endpoint",
|
|
"verify-control-device-info",
|
|
"start-acquisition",
|
|
"stop-acquisition",
|
|
"stop-local-receiver",
|
|
"retire-unavailable-physical-target",
|
|
"acknowledge-data-loss",
|
|
] as const;
|
|
export type XgridsConnectionPolicyAction =
|
|
typeof XGRIDS_CONNECTION_POLICY_ACTIONS[number];
|
|
|
|
export const XGRIDS_CONNECTION_POLICY_TARGET_SOURCES = [
|
|
"none",
|
|
"fresh-scan",
|
|
"retained-current-process",
|
|
"durable-configured-state",
|
|
"configured-topology",
|
|
"connection-supervisor",
|
|
"local-runtime",
|
|
"local-prestart-handoff",
|
|
"local-reconfiguration-intent",
|
|
"durable-physical-command",
|
|
] as const;
|
|
export type XgridsConnectionPolicyTargetSource =
|
|
typeof XGRIDS_CONNECTION_POLICY_TARGET_SOURCES[number];
|
|
|
|
export interface XgridsConnectionPolicyDecision {
|
|
allowed: boolean;
|
|
reason_codes: string[];
|
|
target_source: XgridsConnectionPolicyTargetSource;
|
|
required_transport_ref: string | null;
|
|
required_connection_mode?: XgridsConnectionMode | null;
|
|
requires_live_gatt_validation: boolean;
|
|
automatic_retry: false;
|
|
execution_mode?: "capture-only";
|
|
physical_command_allowed?: false;
|
|
physical_outcome?: "unknown";
|
|
operator_follow_up?: "manual-device-stop-required";
|
|
device_write_performed?: false;
|
|
}
|
|
|
|
export interface XgridsConnectionPolicy {
|
|
schema_version: "missioncore.xgrids-k1-connection-policy/v1";
|
|
supervisor_revision: number;
|
|
network_ledger_revision: number | null;
|
|
recommended_action: string;
|
|
allowed_actions: XgridsConnectionPolicyAction[];
|
|
actions: Partial<Record<XgridsConnectionPolicyAction, XgridsConnectionPolicyDecision>>;
|
|
facts: {
|
|
fresh_transport_refs: string[];
|
|
retained_transport_ref: string | null;
|
|
retained_context_is_presence: false;
|
|
network_mutation_status: "empty" | "unresolved" | "resolved" | "corrupt";
|
|
network_provisioning_idempotency_status: "empty" | "ready" | "blocked" | "corrupt";
|
|
network_provisioning_idempotency_available: boolean;
|
|
network_provisioning_active_operation_id: string | null;
|
|
network_provisioning_active_operation_matches_ledger: boolean;
|
|
semantic_topology_store_status: "empty" | "available" | "corrupt";
|
|
device_identity_pin_store_status: "empty" | "available" | "corrupt";
|
|
physical_command_status: "empty" | "unresolved" | "resolved" | "corrupt";
|
|
physical_command_requires_reconciliation: boolean;
|
|
retired_transport_refs?: string[];
|
|
eligible_fresh_transport_refs?: string[];
|
|
ble_runtime: {
|
|
active_operation_kind: string | null;
|
|
cleanup_pending: boolean;
|
|
poisoned: boolean;
|
|
};
|
|
lifecycle_process_lease_holders: Array<"control" | "network">;
|
|
control_plane_state: "idle" | "healthy" | "stalled" | "lost";
|
|
data_plane_state: "idle" | "healthy" | "stalled" | "lost";
|
|
physical_network_state: "unknown" | "not-disputed";
|
|
};
|
|
}
|
|
|
|
export const XGRIDS_CONNECTION_RECONFIGURATION_INTENTS = [
|
|
"select-device",
|
|
"change-network",
|
|
] as const;
|
|
export type XgridsConnectionReconfigurationIntent =
|
|
typeof XGRIDS_CONNECTION_RECONFIGURATION_INTENTS[number];
|
|
|
|
export const XGRIDS_CONNECTION_RECONFIGURATION_STATUSES = [
|
|
"idle",
|
|
"awaiting-fresh-scan",
|
|
"fresh-scan-completed",
|
|
] as const;
|
|
export type XgridsConnectionReconfigurationStatus =
|
|
typeof XGRIDS_CONNECTION_RECONFIGURATION_STATUSES[number];
|
|
|
|
export interface XgridsConnectionReconfiguration {
|
|
schema_version: "missioncore.xgrids-k1-connection-reconfiguration/v1";
|
|
revision: number;
|
|
intent_id: string | null;
|
|
intent: XgridsConnectionReconfigurationIntent | null;
|
|
status: XgridsConnectionReconfigurationStatus;
|
|
required_transport_ref: string | null;
|
|
required_connection_mode: XgridsConnectionMode | null;
|
|
minimum_discovery_generation: number | null;
|
|
fresh_discovery_generation: number | null;
|
|
required_transport_observed: boolean | null;
|
|
prepared_at: string | null;
|
|
automatic_retry: false;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorTarget {
|
|
ipv4: string;
|
|
port: number;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorIntent {
|
|
intent_id: string;
|
|
requested_mode: XgridsConnectionMode;
|
|
expected_device_id: string | null;
|
|
requested_at: string;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorHostPath {
|
|
epoch: number;
|
|
available: boolean;
|
|
fingerprint: string | null;
|
|
interface: string | null;
|
|
source_ipv4: string | null;
|
|
route_class: "direct" | "default" | "tunnel" | "unavailable" | "unknown";
|
|
reason_code: string | null;
|
|
observed_at: string;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorEndpoint {
|
|
target: XgridsConnectionSupervisorTarget | null;
|
|
tcp_state: "unknown" | "reachable" | "unreachable";
|
|
intent_id: string | null;
|
|
host_path_epoch: number | null;
|
|
reason_code: string | null;
|
|
observed_at: string | null;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorDeviceNetwork {
|
|
state: "unconfigured" | "applied";
|
|
intent_id: string | null;
|
|
transport_ref: string | null;
|
|
connection_mode: XgridsConnectionMode | null;
|
|
target: XgridsConnectionSupervisorTarget | null;
|
|
source: "ble-post-write-status" | "ble-read-only-status" | null;
|
|
observed_at: string | null;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorDeviceIdentity {
|
|
state: "unverified" | "verified" | "stale" | "mismatch";
|
|
intent_id: string | null;
|
|
logical_device_id: string | null;
|
|
compatibility_profile_id: string | null;
|
|
connection_mode: XgridsConnectionMode | null;
|
|
source: "mqtt-device-info" | null;
|
|
host_path_epoch: number | null;
|
|
observed_at: string | null;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorPlane {
|
|
state: "idle" | "healthy" | "stalled" | "lost";
|
|
session_id: string | null;
|
|
host_path_epoch: number | null;
|
|
reason_code: string | null;
|
|
observed_at: string | null;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorLease {
|
|
state: "absent" | "configured-unverified" | "reachable" | "lost";
|
|
generation: number;
|
|
intent_id: string | null;
|
|
host_path_epoch: number | null;
|
|
connection_mode: XgridsConnectionMode | null;
|
|
target: XgridsConnectionSupervisorTarget | null;
|
|
logical_device_id: string | null;
|
|
reason_code: string | null;
|
|
observed_at: string | null;
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorAuthority {
|
|
network_mutation_allowed: boolean;
|
|
control_allowed: boolean;
|
|
acquisition_start_allowed: boolean;
|
|
data_ingest_authoritative: boolean;
|
|
physical_motion_allowed: false;
|
|
reason_codes: string[];
|
|
}
|
|
|
|
export interface XgridsConnectionSupervisorLastKnown {
|
|
connection_mode: XgridsConnectionMode;
|
|
target: XgridsConnectionSupervisorTarget;
|
|
logical_device_id: string;
|
|
compatibility_profile_id: string;
|
|
verified_at: string;
|
|
}
|
|
|
|
export type XgridsConnectionSupervisorAllowedAction =
|
|
| "select-connection-intent"
|
|
| "inspect-host-network"
|
|
| "probe-endpoint"
|
|
| "verify-control-device-info"
|
|
| "start-acquisition"
|
|
| "stop-acquisition"
|
|
| "stop-local-receiver"
|
|
| "acknowledge-data-loss";
|
|
|
|
export interface XgridsConnectionSupervisor {
|
|
schema_version: "missioncore.k1-connection-supervisor/v1";
|
|
revision: number;
|
|
closed: boolean;
|
|
intent: XgridsConnectionSupervisorIntent | null;
|
|
observed: {
|
|
device_network: XgridsConnectionSupervisorDeviceNetwork;
|
|
host_path: XgridsConnectionSupervisorHostPath;
|
|
endpoint: XgridsConnectionSupervisorEndpoint;
|
|
device_identity: XgridsConnectionSupervisorDeviceIdentity;
|
|
control_plane: XgridsConnectionSupervisorPlane;
|
|
data_plane: XgridsConnectionSupervisorPlane;
|
|
};
|
|
lease: XgridsConnectionSupervisorLease;
|
|
authority: XgridsConnectionSupervisorAuthority;
|
|
last_known: XgridsConnectionSupervisorLastKnown | null;
|
|
diagnostics?: XgridsHostFailureDiagnostic[];
|
|
allowed_actions: XgridsConnectionSupervisorAllowedAction[];
|
|
}
|
|
|
|
export interface XgridsSemanticTopologyRecord {
|
|
schema_version: "missioncore.xgrids-k1-semantic-topology/v1";
|
|
revision: number;
|
|
transport_ref: string;
|
|
connection_mode: XgridsConnectionMode;
|
|
ipv4: string;
|
|
compatibility_profile_id: string;
|
|
firmware_version: string;
|
|
source: "ble-post-write-status" | "ble-read-only-status";
|
|
observed_at_utc: string;
|
|
}
|
|
|
|
export interface XgridsSemanticTopologyStore {
|
|
status: "empty" | "available" | "corrupt";
|
|
configured_offline_evidence: boolean;
|
|
live_connection_authority: false;
|
|
reason_code: string | null;
|
|
record: XgridsSemanticTopologyRecord | null;
|
|
}
|
|
|
|
export interface XgridsCompatibilityState {
|
|
profile_id?: string | null;
|
|
decision?: "compatible" | "limited" | "unknown" | "incompatible";
|
|
permitted_mode?: "blocked" | "evidence-only" | "read-only" | "active-control";
|
|
firmware_claim?: string | null;
|
|
vendor_writes_enabled?: boolean;
|
|
camera_preview?: string | null;
|
|
attestation?: CompatibilityAttestation | null;
|
|
}
|
|
|
|
export interface XgridsModelingControlSafety {
|
|
mode: "shadow-only";
|
|
status_reports_observed: number;
|
|
decode_errors: number;
|
|
vendor_identity_observed: boolean;
|
|
device_serial_observed: boolean;
|
|
identity_conflict: boolean;
|
|
session_state?: string | null;
|
|
project_bound: boolean;
|
|
ready_for_start_shadow: boolean;
|
|
ready_for_stop_shadow: boolean;
|
|
vendor_writes_enabled: false;
|
|
publisher_installed: false;
|
|
}
|
|
|
|
export interface XgridsApplicationControlExecution {
|
|
mode: "dormant-write-disabled";
|
|
state: "disarmed" | "armed-shadow-only" | "expired" | "closed";
|
|
lease?: {
|
|
state: "armed" | "expired" | "closed";
|
|
remaining_seconds: number;
|
|
authority_cached: boolean;
|
|
exportable: false;
|
|
} | null;
|
|
orchestrator?: Record<string, unknown> | null;
|
|
publisher: {
|
|
mode: "write-disabled";
|
|
denied_attempts: number;
|
|
transport_calls: 0;
|
|
vendor_writes_enabled: false;
|
|
publisher_armed: false;
|
|
automatic_retry: false;
|
|
};
|
|
live_transport_installed: false;
|
|
can_emit_requests: false;
|
|
}
|
|
|
|
export interface XgridsPhysicalCommandState {
|
|
status: "empty" | "unresolved" | "resolved" | "corrupt";
|
|
reason_code: string | null;
|
|
requires_reconciliation: boolean;
|
|
resolved_active_recovery_required?: boolean;
|
|
automatic_replay_allowed: false;
|
|
normal_session_recovery_supported: false;
|
|
recovery_requirement?: string | null;
|
|
runtime_bound: boolean;
|
|
reconciliation_ready: boolean;
|
|
observed_session_state?: "ready" | "scanning" | string | null;
|
|
active_operation_id?: string | null;
|
|
operator_retirement?: {
|
|
allowed: boolean;
|
|
reason_codes: string[];
|
|
expected_operation_id: string | null;
|
|
expected_revision: number | null;
|
|
expected_transport_ref: string | null;
|
|
physical_outcome: "unknown";
|
|
device_io_performed: false;
|
|
automatic_retry: false;
|
|
} | null;
|
|
operator_retirement_allowed?: boolean;
|
|
operator_retirement_reason_codes?: string[];
|
|
operator_reconciliation_reopen?: {
|
|
allowed: boolean;
|
|
reason_codes: string[];
|
|
expected_revision: number | null;
|
|
expected_retirement_id: string | null;
|
|
expected_transport_ref: string | null;
|
|
expected_discovery_generation: number | null;
|
|
expected_desired_mode: XgridsConnectionMode;
|
|
expected_desired_mode_revision: number;
|
|
device_io_performed: false;
|
|
automatic_retry: false;
|
|
} | null;
|
|
record?: (Record<string, unknown> & {
|
|
action?: "start" | "stop";
|
|
stage?: string;
|
|
resolution?: string | null;
|
|
}) | null;
|
|
}
|
|
|
|
export type XgridsApplicationControlPhase =
|
|
| "idle"
|
|
| "connecting"
|
|
| "connection-ready"
|
|
| "active-recovery-requested"
|
|
| "workspace-requested"
|
|
| "workspace-ready"
|
|
| "project-requested"
|
|
| "project-ready"
|
|
| "start-requested"
|
|
| "initializing"
|
|
| "scanning"
|
|
| "stop-requested"
|
|
| "stopping"
|
|
| "awaiting-standby-confirmation"
|
|
| "completed"
|
|
| "closed"
|
|
| "failed";
|
|
|
|
export interface XgridsApplicationControlSession {
|
|
mode: "interactive-canonical";
|
|
inspection_only?: boolean;
|
|
inspection_promotion_allowed?: boolean;
|
|
session_generation: number;
|
|
state_revision: number;
|
|
state: XgridsApplicationControlPhase;
|
|
control_socket_open: boolean;
|
|
can_open: boolean;
|
|
can_enter_workspace: boolean;
|
|
can_prepare_project: boolean;
|
|
can_start: boolean;
|
|
can_stop: boolean;
|
|
can_confirm_standby: boolean;
|
|
pending_operator_action?: string | null;
|
|
scripted_transitions: false;
|
|
automatic_retry: false;
|
|
outcome_unknown: boolean;
|
|
failure?: {
|
|
code?: string;
|
|
reason_code?: string;
|
|
message?: string;
|
|
failed_phase?: string | null;
|
|
dialogue_stage?: string | null;
|
|
transport_state?: string | null;
|
|
publish_attempts?: number | null;
|
|
qos2_completions?: number | null;
|
|
correlated_responses?: number | null;
|
|
ignored_known_responses?: number | null;
|
|
late_known_responses?: number | null;
|
|
modeling_command_attempted?: boolean | null;
|
|
diagnostic_snapshot_unavailable?: string[];
|
|
diagnostic_evidence_unavailable?: string[];
|
|
correlation_failure?: {
|
|
phase?: string;
|
|
operation_key?: string;
|
|
response_topic?: string;
|
|
reason_code?: string;
|
|
reason?: string;
|
|
} | null;
|
|
compatibility_failure?: {
|
|
phase?: string;
|
|
operation_key?: string;
|
|
response_topic?: string;
|
|
reason_code?: string;
|
|
expected?: Record<string, unknown>;
|
|
observed?: Record<string, unknown>;
|
|
} | null;
|
|
status_reconciliation?: {
|
|
device_session_state?: string;
|
|
device_project_bound?: boolean;
|
|
system_error_code?: number | null;
|
|
decision?: "safe-explicit-prestart-retry";
|
|
automatic_retry?: false;
|
|
} | null;
|
|
network_change_admissible?: boolean;
|
|
network_change_reconciliation?: {
|
|
device_session_state?: "scan_stopping";
|
|
device_project_bound?: true;
|
|
system_error_code?: null;
|
|
stop_complete?: true;
|
|
standby_confirmed?: false;
|
|
decision?: "explicit-network-change-only-after-acknowledged-stop";
|
|
automatic_retry?: false;
|
|
} | null;
|
|
safe_to_retry?: boolean;
|
|
host_diagnostic?: XgridsHostFailureDiagnostic;
|
|
} | null;
|
|
dialogue?: Record<string, unknown> | null;
|
|
transport?: Record<string, unknown> | null;
|
|
verified_control?: {
|
|
logical_device_id: string;
|
|
compatibility_profile_id: string;
|
|
control_session_id: string;
|
|
source: "mqtt-device-info";
|
|
intent_id: string;
|
|
transport_ref: string;
|
|
host_path_epoch: number;
|
|
target_ipv4: string;
|
|
target_port: number;
|
|
connection_mode: XgridsConnectionMode;
|
|
control_proof_revision: number;
|
|
control_proof_source: string;
|
|
control_proof_fresh: boolean;
|
|
control_proof_age_seconds?: number | null;
|
|
} | null;
|
|
physical_command?: XgridsPhysicalCommandState | null;
|
|
}
|
|
|
|
export interface XgridsAcquisition {
|
|
schema_version?: string;
|
|
acquisition_id: string;
|
|
device_id: string;
|
|
device_session_id: string;
|
|
compatibility_profile_id: string;
|
|
control_mode: "operator-manual" | "plugin-commanded" | "observe-only";
|
|
project_name?: string | null;
|
|
mount_type?: "handheld" | null;
|
|
gnss_mode?: "none" | null;
|
|
cleanup_pending?: boolean;
|
|
requested_streams: string[];
|
|
target_host: string;
|
|
duration_seconds: number | null;
|
|
evidence_policy: "required" | "best-effort" | "disabled";
|
|
state: AcquisitionState;
|
|
state_revision: number;
|
|
created_at?: string | null;
|
|
updated_at?: string | null;
|
|
message_code?: string | null;
|
|
operator_instructions?: string[];
|
|
result?: Record<string, unknown> | null;
|
|
}
|
|
|
|
export interface XgridsOperation {
|
|
schema_version?: string;
|
|
operation_id: string;
|
|
action: string;
|
|
status: OperationStatus;
|
|
accepted_at?: string | null;
|
|
completed_at?: string | null;
|
|
deadline_at?: string | null;
|
|
device_id?: string | null;
|
|
device_session_id?: string | null;
|
|
idempotency_key?: string | null;
|
|
stage_code?: string | null;
|
|
message_code?: string | null;
|
|
sequence?: number;
|
|
state_revision?: number;
|
|
cancellable?: boolean;
|
|
cancel_requested?: boolean;
|
|
result?: Record<string, unknown> | null;
|
|
error?: (Record<string, unknown> & {
|
|
host_diagnostic?: XgridsHostFailureDiagnostic;
|
|
}) | null;
|
|
evidence_refs?: string[];
|
|
context?: Record<string, unknown>;
|
|
events?: XgridsOperationEvent[];
|
|
}
|
|
|
|
export interface XgridsOperationEvent {
|
|
schema_version: "missioncore.operation-event/v1";
|
|
sequence: number;
|
|
status: OperationStatus;
|
|
stage_code: string;
|
|
message_code: string;
|
|
observed_at: string;
|
|
side_effect_status: string | null;
|
|
error_code: string | null;
|
|
safe_to_retry: boolean | null;
|
|
automatic_retry: false;
|
|
}
|
|
|
|
export interface XgridsConnectionDiagnosticBundle {
|
|
schema_version: "missioncore.xgrids-k1-connection-diagnostic/v1";
|
|
redacted: true;
|
|
generated_at_utc: string;
|
|
snapshot_runtime_id: string;
|
|
attempt: Omit<XgridsConnectionAttempt, "diagnostic_bundle">;
|
|
network_mutation_ledger: XgridsNetworkMutationLedger;
|
|
connection_supervisor: Record<string, unknown>;
|
|
automatic_retry: false;
|
|
}
|
|
|
|
export const XGRIDS_CONNECTION_ATTEMPT_PHASES = [
|
|
"network_applied",
|
|
"network_not_applied",
|
|
"network_outcome_unknown",
|
|
] as const;
|
|
export type XgridsConnectionAttemptPhase =
|
|
typeof XGRIDS_CONNECTION_ATTEMPT_PHASES[number];
|
|
|
|
export interface XgridsConnectionAttempt {
|
|
schema_version: "missioncore.xgrids-k1-connection-attempt/v1";
|
|
attempt_id: string;
|
|
connection_mode: XgridsConnectionMode;
|
|
status: OperationStatus;
|
|
stage: string;
|
|
public_error_code: string | null;
|
|
side_effect_status: string;
|
|
phase: XgridsConnectionAttemptPhase;
|
|
control_state: "ready" | "control_not_ready" | "unknown";
|
|
safe_next_action:
|
|
| "wait-for-current-attempt"
|
|
| "continue-with-control-verification"
|
|
| "verify-control-read-only"
|
|
| "start-acquisition"
|
|
| "stop-local-receiver"
|
|
| "retire-unavailable-physical-target"
|
|
| "scan-select-connect"
|
|
| "manual-recovery-required";
|
|
automatic_retry: false;
|
|
accepted_at: string | null;
|
|
completed_at: string | null;
|
|
timeline: XgridsOperationEvent[];
|
|
diagnostic_bundle?: XgridsConnectionDiagnosticBundle;
|
|
}
|
|
|
|
export interface XgridsK1Metrics {
|
|
pcl_frames?: number | null;
|
|
pose_frames?: number | null;
|
|
mqtt_to_decode_ms?: number | null;
|
|
decode_ms?: number | null;
|
|
publish_ms?: number | null;
|
|
pipeline_ms?: number | null;
|
|
end_to_end_ms?: number | null;
|
|
frame_rate?: number | null;
|
|
frame_rate_hz?: number | null;
|
|
point_count?: number | null;
|
|
dropped_preview_frames?: number | null;
|
|
ai_end_to_end_ms?: number | null;
|
|
ai_end_to_end_p95_ms?: number | null;
|
|
ai_frame_rate_hz?: number | null;
|
|
ai_dropped_frames?: number | null;
|
|
ai_stale_ms?: number | null;
|
|
device_elapsed_seconds?: number | null;
|
|
device_route_distance_meters?: number | null;
|
|
device_speed_meters_per_second?: number | null;
|
|
device_speed_mps?: number | null;
|
|
elapsed_seconds?: number | null;
|
|
route_distance_meters?: number | null;
|
|
speed_meters_per_second?: number | null;
|
|
[key: string]: number | null | undefined;
|
|
}
|
|
|
|
export interface XgridsCameraPreviewDelivery {
|
|
id: string;
|
|
kind: "mse-fmp4-websocket";
|
|
url: string;
|
|
media_type: string;
|
|
}
|
|
|
|
export interface XgridsCameraPreviewActivation {
|
|
group_id: string;
|
|
max_active: number;
|
|
selected: boolean;
|
|
controllable: boolean;
|
|
}
|
|
|
|
export interface XgridsCameraPreviewState {
|
|
phase?: string | null;
|
|
revision?: number | null;
|
|
generation?: number | null;
|
|
active_source_id?: string | null;
|
|
delivery?: XgridsCameraPreviewDelivery | null;
|
|
}
|
|
|
|
export interface XgridsSensorCatalogStream {
|
|
stream_id: string;
|
|
source_id?: string | null;
|
|
semantic_channel_id?: string | null;
|
|
label?: string | null;
|
|
sensor_kind?: string | null;
|
|
modality?: string | null;
|
|
availability?: string | null;
|
|
endpoint_label?: string | null;
|
|
activation?: XgridsCameraPreviewActivation | null;
|
|
delivery?: XgridsCameraPreviewDelivery | null;
|
|
decode_status?: string | null;
|
|
frame_id?: string | null;
|
|
coordinate_convention?: string | null;
|
|
}
|
|
|
|
export interface XgridsSensorCatalog {
|
|
schema_version?: string | null;
|
|
revision?: string | null;
|
|
streams?: XgridsSensorCatalogStream[];
|
|
}
|
|
|
|
export interface XgridsConnectionLifecycleBinding {
|
|
binding_key: string;
|
|
intent_id: string;
|
|
transport_ref: string;
|
|
connection_mode: XgridsConnectionMode;
|
|
target_ipv4: string;
|
|
target_port: number;
|
|
host_path_epoch: number;
|
|
control_session_id: string;
|
|
logical_device_id?: string | null;
|
|
compatibility_profile_id?: string | null;
|
|
control_proof_source?: string | null;
|
|
control_proof_revision?: number | null;
|
|
}
|
|
|
|
export interface XgridsConnectionLifecycle {
|
|
schema_version: "missioncore.xgrids-k1-connection-lifecycle/v1";
|
|
revision: number;
|
|
desired_mode: XgridsConnectionMode;
|
|
configured_mode: XgridsConnectionMode | null;
|
|
active_mode: XgridsConnectionMode | null;
|
|
mode_change: {
|
|
state:
|
|
| "disconnected"
|
|
| "awaiting-control"
|
|
| "ready"
|
|
| "switch-selected"
|
|
| "connecting";
|
|
from: XgridsConnectionMode | null;
|
|
to: XgridsConnectionMode;
|
|
};
|
|
mode_selection: {
|
|
allowed: boolean;
|
|
reason_codes: string[];
|
|
automatic_retry: false;
|
|
};
|
|
active_binding_key: string | null;
|
|
active_binding: XgridsConnectionLifecycleBinding | null;
|
|
connection_ready: boolean;
|
|
ready_to_start: boolean;
|
|
operation: XgridsConnectionAttempt | null;
|
|
allowed_actions: string[];
|
|
automatic_retry: false;
|
|
}
|
|
|
|
export interface XgridsActiveStreamRecovery {
|
|
schema_version: "missioncore.xgrids-k1-active-stream-recovery/v1";
|
|
state: XgridsActiveStreamRecoveryState;
|
|
generation: number;
|
|
acquisition_id: string | null;
|
|
attempt: number;
|
|
started_at_utc: string | null;
|
|
elapsed_ms: number | null;
|
|
reason_code: string | null;
|
|
force_finish_allowed: boolean;
|
|
automatic_read_only_rebind: boolean;
|
|
automatic_command_retry: false;
|
|
start_performed: false;
|
|
stop_performed: false;
|
|
ble_operation_performed: false;
|
|
network_mutation_performed: false;
|
|
runtime_producer_generation: number | null;
|
|
camera_recovery: XgridsCameraRecoveryState;
|
|
camera_media_state: XgridsCameraMediaState;
|
|
camera_media_ready: boolean;
|
|
camera_epoch: XgridsCameraMediaEpoch | null;
|
|
}
|
|
|
|
export interface XgridsCameraMediaEpoch {
|
|
generation: number;
|
|
init_committed: boolean;
|
|
init_committed_age_ms: number | null;
|
|
first_media_committed: boolean;
|
|
first_media_committed_age_ms: number | null;
|
|
committed_media_segment_count: number;
|
|
last_media_segment_age_ms: number | null;
|
|
}
|
|
|
|
export interface XgridsK1State {
|
|
contract_version?: string | null;
|
|
snapshot_runtime_started_at_utc?: string | null;
|
|
snapshot_runtime_started_monotonic_ns?: string | null;
|
|
snapshot_runtime_id?: string | null;
|
|
snapshot_revision?: number | null;
|
|
producer_generation?: number | null;
|
|
phase?: string | null;
|
|
message?: string | null;
|
|
devices?: BleDevice[];
|
|
selected_device_id?: string | null;
|
|
ble_discovery_generation?: number;
|
|
k1_ip?: string | null;
|
|
/** Legacy alias for the last configured mode. */
|
|
connection_mode?: XgridsConnectionMode | null;
|
|
configured_connection_mode?: XgridsConnectionMode | null;
|
|
active_connection_mode?: XgridsConnectionMode | null;
|
|
desired_connection_mode?: XgridsConnectionMode;
|
|
desired_connection_mode_revision?: number;
|
|
connection_scenario_reset?: {
|
|
reset_id: string;
|
|
request_revision: number;
|
|
revision: number;
|
|
desired_mode: XgridsConnectionMode;
|
|
/** Missing is treated as active while rolling out the marker lifecycle. */
|
|
active?: boolean;
|
|
settled_by_discovery_generation?: number | null;
|
|
local_session_closed: true;
|
|
previous_device_may_continue_scanning: boolean;
|
|
physical_disposition: string;
|
|
network_disposition: string | null;
|
|
device_command_performed: false;
|
|
network_write_performed: false;
|
|
automatic_scan: false;
|
|
operation_sequence: number;
|
|
timing?: {
|
|
schema_version: "missioncore.xgrids-k1-connection-scenario-reset-timing/v1";
|
|
started_at_utc: string;
|
|
completed_at_utc: string;
|
|
total_ms: number;
|
|
lifecycle_boundary_wait_ms: number;
|
|
monitor_quiescence_wait_ms: number;
|
|
local_retirement_ms: number;
|
|
intent_commit_ms: number;
|
|
};
|
|
} | null;
|
|
connection_scenario_reset_pending?: {
|
|
reset_id: string;
|
|
desired_mode: XgridsConnectionMode;
|
|
expected_revision: number;
|
|
status: "waiting-for-local-lifecycle" | "retiring-local-session";
|
|
device_command_performed: false;
|
|
network_write_performed: false;
|
|
automatic_scan: false;
|
|
} | null;
|
|
foxglove_ws_url?: string | null;
|
|
foxglove_viewer_url?: string | null;
|
|
rerun_grpc_url?: string | null;
|
|
viewer_settings?: ViewerSettings | null;
|
|
source_mode?: SourceMode | null;
|
|
metrics?: XgridsK1Metrics;
|
|
compatibility?: XgridsCompatibilityState | null;
|
|
modeling_control_safety?: XgridsModelingControlSafety | null;
|
|
application_control_execution?: XgridsApplicationControlExecution | null;
|
|
application_control_session?: XgridsApplicationControlSession | null;
|
|
physical_command?: XgridsPhysicalCommandState | null;
|
|
device_ref?: XgridsDeviceRef | null;
|
|
device_session?: XgridsDeviceSession | null;
|
|
connection_verification?: XgridsConnectionVerification | null;
|
|
configured_endpoint_probe?: XgridsConfiguredEndpointProbe | null;
|
|
network_write_reconciliation?: XgridsNetworkWriteReconciliation | null;
|
|
network_mutation_ledger?: XgridsNetworkMutationLedger | null;
|
|
current_device_recovery?: XgridsCurrentDeviceRecovery | null;
|
|
connection_supervisor?: XgridsConnectionSupervisor | null;
|
|
connection_lifecycle?: XgridsConnectionLifecycle | null;
|
|
connection_recovery?: XgridsActiveStreamRecovery | null;
|
|
connection_reconfiguration?: XgridsConnectionReconfiguration | null;
|
|
connection_policy?: XgridsConnectionPolicy | null;
|
|
semantic_topology_store?: XgridsSemanticTopologyStore | null;
|
|
acquisition?: XgridsAcquisition | null;
|
|
operations?: XgridsOperation[];
|
|
last_operation?: XgridsOperation | null;
|
|
connection_attempt?: XgridsConnectionAttempt | null;
|
|
sensor_catalog?: XgridsSensorCatalog | null;
|
|
camera_preview?: XgridsCameraPreviewState | null;
|
|
}
|
|
|
|
export interface HealthResponse {
|
|
ok?: boolean;
|
|
status?: string;
|
|
service?: string;
|
|
version?: string;
|
|
}
|
|
|
|
export interface ScanRequest {
|
|
duration_seconds?: number;
|
|
operation_id?: string;
|
|
}
|
|
|
|
export interface CompatibilityAttestation {
|
|
firmware_version: "3.0.2";
|
|
topology: "direct-lan" | "device-ap" | "controller-hotspot";
|
|
verification: "live-device-info";
|
|
}
|
|
|
|
export interface ConnectRequest {
|
|
device_id: string;
|
|
ssid?: string;
|
|
password?: string;
|
|
connection_mode: "bridge" | "quick-connect" | "direct-connect";
|
|
compatibility_attestation: CompatibilityAttestation;
|
|
operation_id?: string;
|
|
idempotency_key: string;
|
|
expected_mode_revision: number;
|
|
expected_discovery_generation: number;
|
|
expected_reconfiguration_revision: number;
|
|
expected_reconfiguration_intent_id?: string;
|
|
}
|
|
|
|
export interface SelectConnectionModeRequest {
|
|
connection_mode: XgridsConnectionMode;
|
|
expected_revision: number;
|
|
reset_scenario?: true;
|
|
reset_id?: string;
|
|
}
|
|
|
|
export interface PrepareConnectionReconfigurationRequest {
|
|
intent: XgridsConnectionReconfigurationIntent | "cancel";
|
|
expected_reconfiguration_revision: number;
|
|
expected_reconfiguration_intent_id: string | null;
|
|
expected_desired_mode_revision: number;
|
|
expected_active_binding_key: string | null;
|
|
}
|
|
|
|
interface ConnectionVerifyRequestBase {
|
|
device_id: string;
|
|
compatibility_attestation: CompatibilityAttestation;
|
|
operation_id?: string;
|
|
expected_mode_revision: number;
|
|
}
|
|
|
|
export type ConnectionVerifyRequest =
|
|
| (ConnectionVerifyRequestBase & {
|
|
source: "fresh-scan";
|
|
expected_discovery_generation: number;
|
|
expected_reconfiguration_revision: number;
|
|
expected_reconfiguration_intent_id?: string;
|
|
})
|
|
| (ConnectionVerifyRequestBase & {
|
|
source: Extract<
|
|
XgridsConnectionPolicyTargetSource,
|
|
"retained-current-process" | "durable-configured-state"
|
|
>;
|
|
expected_discovery_generation?: never;
|
|
});
|
|
|
|
export interface ConfiguredEndpointProbeRequest {
|
|
operation_id?: string;
|
|
}
|
|
|
|
export interface RetireUnavailablePhysicalCommandRequest {
|
|
retirement_id: string;
|
|
expected_operation_id: string;
|
|
expected_revision: number;
|
|
expected_transport_ref: string;
|
|
operator_confirmed: true;
|
|
reason: "device-permanently-unavailable-or-replaced";
|
|
}
|
|
|
|
export interface ReopenRetiredPhysicalReconciliationRequest {
|
|
reopening_id: string;
|
|
expected_revision: number;
|
|
expected_retirement_id: string;
|
|
expected_transport_ref: string;
|
|
expected_discovery_generation: number;
|
|
expected_desired_mode: XgridsConnectionMode;
|
|
expected_desired_mode_revision: number;
|
|
operator_confirmed: true;
|
|
reason: "device-returned-for-explicit-reconciliation";
|
|
}
|
|
|
|
export interface SnapshotRuntimeFenceRequest {
|
|
expected_snapshot_runtime_id: string;
|
|
}
|
|
|
|
export type SnapshotRuntimeFencedRequest<TRequest extends object> =
|
|
TRequest & SnapshotRuntimeFenceRequest;
|
|
|
|
export interface PrepareAcquisitionRequest {
|
|
project_name: string;
|
|
mount_type: "handheld";
|
|
gnss_mode: "none";
|
|
host?: string;
|
|
duration_seconds?: number;
|
|
requested_streams?: RequestedStreamId[];
|
|
evidence_policy?: "required" | "best-effort" | "disabled";
|
|
compatibility_attestation: CompatibilityAttestation;
|
|
operation_id?: string;
|
|
idempotency_key: string;
|
|
deadline_seconds?: number;
|
|
expected_control_session_generation?: number;
|
|
expected_control_state_revision?: number;
|
|
}
|
|
|
|
export type RequestedStreamId =
|
|
| "spatial.point-cloud.live"
|
|
| "spatial.pose.live"
|
|
| "device.modeling.live"
|
|
| "device.status.live"
|
|
| "device.heartbeat.live";
|
|
|
|
export interface StartAcquisitionRequest {
|
|
acquisition_id: string;
|
|
expected_state_revision?: number;
|
|
expected_control_session_generation: number;
|
|
expected_control_state_revision: number;
|
|
operation_id?: string;
|
|
idempotency_key: string;
|
|
deadline_seconds?: number;
|
|
physical_acceptance?: OperatorPresenceConfirmation;
|
|
}
|
|
|
|
interface StopAcquisitionRequestBase {
|
|
acquisition_id: string;
|
|
operator_confirmed?: boolean;
|
|
operation_id?: string;
|
|
idempotency_key: string;
|
|
deadline_seconds?: number;
|
|
physical_acceptance?: OperatorPresenceConfirmation;
|
|
}
|
|
|
|
export type StopAcquisitionRequest =
|
|
| (StopAcquisitionRequestBase & {
|
|
mode: "capture-only";
|
|
expected_control_session_generation?: never;
|
|
expected_control_state_revision?: never;
|
|
})
|
|
| (StopAcquisitionRequestBase & {
|
|
mode: "graceful";
|
|
expected_control_session_generation: number;
|
|
expected_control_state_revision: number;
|
|
});
|
|
|
|
export interface AbortAcquisitionRequest {
|
|
acquisition_id: string;
|
|
operation_id?: string;
|
|
idempotency_key: string;
|
|
deadline_seconds?: number;
|
|
expected_control_session_generation?: number;
|
|
expected_control_state_revision?: number;
|
|
}
|
|
|
|
export interface ForceFinishAcquisitionRequest {
|
|
acquisition_id: string;
|
|
expected_state_revision: number;
|
|
expected_recovery_generation: number;
|
|
operator_confirmed: true;
|
|
operation_id?: string;
|
|
idempotency_key: string;
|
|
deadline_seconds?: number;
|
|
}
|
|
|
|
export interface CompatibilityLiveRequest {
|
|
project_name: string;
|
|
host?: string;
|
|
duration_seconds?: number;
|
|
compatibility_attestation: CompatibilityAttestation;
|
|
}
|
|
|
|
export interface ReplayRequest {
|
|
path: string;
|
|
speed?: number;
|
|
loop?: boolean;
|
|
}
|
|
|
|
export interface SelectCameraPreviewRequest {
|
|
source_id: string;
|
|
device_session_id: string;
|
|
}
|
|
|
|
export interface StopCameraPreviewRequest {
|
|
device_session_id: string;
|
|
generation: number;
|
|
}
|
|
|
|
export interface ShadowApplicationControlArmRequest {
|
|
operator_confirmed: true;
|
|
lease_seconds?: number;
|
|
timezone_name: string;
|
|
}
|
|
|
|
export interface OperatorPresenceConfirmation {
|
|
operator_present: true;
|
|
owner_controlled_device: true;
|
|
lixelgo_closed: true;
|
|
battery_storage_confirmed: true;
|
|
expected_physical_state_confirmed: true;
|
|
}
|
|
|
|
export interface OpenApplicationControlSessionRequest
|
|
extends OperatorPresenceConfirmation {
|
|
timezone_name: string;
|
|
}
|
|
|
|
export interface EnterApplicationWorkspaceRequest {
|
|
operator_confirmed: true;
|
|
expected_session_generation: number;
|
|
expected_state_revision: number;
|
|
}
|
|
|
|
export interface CloseApplicationControlSessionRequest {
|
|
expected_session_generation: number;
|
|
expected_state_revision: number;
|
|
}
|
|
|
|
export interface ReconcilePhysicalCommandRequest {
|
|
reconciliation_id: string;
|
|
expected_session_generation: number;
|
|
expected_state_revision: number;
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
readonly status: number;
|
|
readonly transportUnavailable: boolean;
|
|
readonly hostDiagnostic: XgridsHostFailureDiagnostic | null;
|
|
|
|
constructor(
|
|
message: string,
|
|
status = 0,
|
|
transportUnavailable = false,
|
|
hostDiagnostic: unknown = null,
|
|
) {
|
|
super(message);
|
|
this.name = "ApiError";
|
|
this.status = status;
|
|
this.transportUnavailable = transportUnavailable;
|
|
this.hostDiagnostic = isXgridsHostFailureDiagnostic(hostDiagnostic)
|
|
? hostDiagnostic
|
|
: null;
|
|
}
|
|
}
|
|
|
|
export class ApiRequestTimeoutError extends ApiError {
|
|
readonly code = "request_timeout_outcome_unknown";
|
|
readonly outcomeUnknown = true;
|
|
readonly automaticRetry = false;
|
|
readonly timeoutMs: number;
|
|
readonly operationLabel: string;
|
|
|
|
constructor(operationLabel: string, timeoutMs: number) {
|
|
super(
|
|
`Локальный запрос «${operationLabel}» не завершился за ${Math.ceil(timeoutMs / 1000)} с. Результат операции неизвестен; автоматический повтор запрещён. Обновите состояние перед отдельным ручным действием.`,
|
|
);
|
|
this.name = "ApiRequestTimeoutError";
|
|
this.timeoutMs = timeoutMs;
|
|
this.operationLabel = operationLabel;
|
|
}
|
|
}
|
|
|
|
const DEFAULT_REQUEST_TIMEOUT_MS = 8_000;
|
|
const ACTION_REQUEST_TIMEOUT_MS: Readonly<Record<string, number>> = {
|
|
[xgridsK1Actions.stateRead]: 8_000,
|
|
[xgridsK1Actions.discoveryScan]: 70_000,
|
|
// Explicit scenario reset queues behind a bounded in-flight lifecycle and
|
|
// then performs local-only retirement. Keep the browser request alive long
|
|
// enough to observe that single committed intent.
|
|
[xgridsK1Actions.connectionModeSelect]: 360_000,
|
|
[xgridsK1Actions.connectionReconfigurePrepare]: 8_000,
|
|
// Bridge and Quick are composite operations: exact BLE baseline/write,
|
|
// bounded post-write observation and, for Quick, a native CoreWLAN handoff.
|
|
// The backend journals a 240-second operation fence. The browser deadline
|
|
// must outlive it; aborting the HTTP request earlier cannot cancel the
|
|
// native/device work and would present an avoidable ambiguous outcome.
|
|
[xgridsK1Actions.networkProvision]: 300_000,
|
|
// A canonical verification performs a bounded fresh CoreBluetooth connect
|
|
// and 7f02 status read before any route/TCP interpretation. The browser
|
|
// must not abandon that read at the generic eight-second HTTP deadline and
|
|
// accidentally invite a second operator attempt while native cleanup still
|
|
// owns the BLE lifecycle.
|
|
[xgridsK1Actions.connectionVerify]: 150_000,
|
|
// This is the separate host-only route -> TCP -> route diagnostic. It does
|
|
// not enter CoreBluetooth and has a much smaller bounded server deadline.
|
|
[xgridsK1Actions.configuredEndpointProbe]: 15_000,
|
|
[xgridsK1Actions.applicationControlSessionOpen]: 120_000,
|
|
[xgridsK1Actions.applicationControlWorkspaceEnter]: 120_000,
|
|
[xgridsK1Actions.applicationControlSessionClose]: 120_000,
|
|
[xgridsK1Actions.physicalCommandReconcile]: 120_000,
|
|
[xgridsK1Actions.physicalCommandRetireUnavailable]: 8_000,
|
|
[xgridsK1Actions.physicalCommandReopenRetiredReconciliation]: 8_000,
|
|
[xgridsK1Actions.acquisitionPrepare]: 120_000,
|
|
[xgridsK1Actions.acquisitionStart]: 120_000,
|
|
[xgridsK1Actions.acquisitionStop]: 120_000,
|
|
[xgridsK1Actions.acquisitionForceFinishLocal]: 45_000,
|
|
};
|
|
|
|
export function requestTimeoutMsForAction(actionId: string): number {
|
|
return ACTION_REQUEST_TIMEOUT_MS[actionId] ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function isLiteralValue<const Values extends readonly string[]>(
|
|
value: unknown,
|
|
values: Values,
|
|
): value is Values[number] {
|
|
return typeof value === "string" && values.some((candidate) => candidate === value);
|
|
}
|
|
|
|
function isOptionalString(value: unknown): value is string | null | undefined {
|
|
return value === undefined || value === null || typeof value === "string";
|
|
}
|
|
|
|
function isOptionalBoolean(value: unknown): value is boolean | undefined {
|
|
return value === undefined || typeof value === "boolean";
|
|
}
|
|
|
|
function isNonNegativeInteger(value: unknown): value is number {
|
|
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
}
|
|
|
|
function isStringOrNull(value: unknown): value is string | null {
|
|
return value === null || typeof value === "string";
|
|
}
|
|
|
|
function isNonNegativeIntegerOrNull(value: unknown): value is number | null {
|
|
return value === null || isNonNegativeInteger(value);
|
|
}
|
|
|
|
function isStringArray(value: unknown): value is string[] {
|
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
}
|
|
|
|
export function isXgridsConnectionAttemptPhase(
|
|
value: unknown,
|
|
): value is XgridsConnectionAttemptPhase {
|
|
return isLiteralValue(value, XGRIDS_CONNECTION_ATTEMPT_PHASES);
|
|
}
|
|
|
|
export function isXgridsHostFailureDiagnostic(
|
|
value: unknown,
|
|
): value is XgridsHostFailureDiagnostic {
|
|
return isRecord(value)
|
|
&& value.schema_version === "missioncore.host-failure-diagnostic/v1"
|
|
&& isLiteralValue(value.code, XGRIDS_HOST_DIAGNOSTIC_CODES)
|
|
&& isLiteralValue(value.domain, XGRIDS_HOST_DIAGNOSTIC_DOMAINS)
|
|
&& isLiteralValue(value.impact, XGRIDS_HOST_DIAGNOSTIC_IMPACTS)
|
|
&& isLiteralValue(value.operator_action, XGRIDS_HOST_DIAGNOSTIC_ACTIONS)
|
|
&& value.automatic_retry === false
|
|
&& value.redacted === true;
|
|
}
|
|
|
|
export function isXgridsActiveStreamRecovery(
|
|
value: unknown,
|
|
): value is XgridsActiveStreamRecovery {
|
|
if (!isRecord(value)) return false;
|
|
return value.schema_version
|
|
=== "missioncore.xgrids-k1-active-stream-recovery/v1"
|
|
&& isLiteralValue(value.state, XGRIDS_ACTIVE_STREAM_RECOVERY_STATES)
|
|
&& isNonNegativeInteger(value.generation)
|
|
&& isStringOrNull(value.acquisition_id)
|
|
&& isNonNegativeInteger(value.attempt)
|
|
&& isStringOrNull(value.started_at_utc)
|
|
&& isNonNegativeIntegerOrNull(value.elapsed_ms)
|
|
&& isStringOrNull(value.reason_code)
|
|
&& typeof value.force_finish_allowed === "boolean"
|
|
&& typeof value.automatic_read_only_rebind === "boolean"
|
|
&& value.automatic_command_retry === false
|
|
&& value.start_performed === false
|
|
&& value.stop_performed === false
|
|
&& value.ble_operation_performed === false
|
|
&& value.network_mutation_performed === false
|
|
&& isNonNegativeIntegerOrNull(value.runtime_producer_generation)
|
|
&& isLiteralValue(value.camera_recovery, XGRIDS_CAMERA_RECOVERY_STATES)
|
|
&& isXgridsCameraMediaProjection(value);
|
|
}
|
|
|
|
function isXgridsCameraMediaEpoch(value: unknown): value is XgridsCameraMediaEpoch {
|
|
return isRecord(value)
|
|
&& isNonNegativeInteger(value.generation)
|
|
&& value.generation > 0
|
|
&& typeof value.init_committed === "boolean"
|
|
&& isNonNegativeIntegerOrNull(value.init_committed_age_ms)
|
|
&& typeof value.first_media_committed === "boolean"
|
|
&& isNonNegativeIntegerOrNull(value.first_media_committed_age_ms)
|
|
&& isNonNegativeInteger(value.committed_media_segment_count)
|
|
&& isNonNegativeIntegerOrNull(value.last_media_segment_age_ms);
|
|
}
|
|
|
|
function isXgridsCameraMediaProjection(
|
|
value: Record<string, unknown>,
|
|
): boolean {
|
|
if (
|
|
!isLiteralValue(value.camera_media_state, XGRIDS_CAMERA_MEDIA_STATES)
|
|
|| typeof value.camera_media_ready !== "boolean"
|
|
) return false;
|
|
const state = value.camera_media_state;
|
|
const epoch = value.camera_epoch;
|
|
if (state === "inactive" || state === "pending-epoch") {
|
|
return value.camera_media_ready === false && epoch === null;
|
|
}
|
|
if (!isXgridsCameraMediaEpoch(epoch) || value.camera_media_ready !== (state === "ready")) {
|
|
return false;
|
|
}
|
|
if (state === "pending-init") {
|
|
return epoch.init_committed === false
|
|
&& epoch.init_committed_age_ms === null
|
|
&& epoch.first_media_committed === false
|
|
&& epoch.first_media_committed_age_ms === null
|
|
&& epoch.committed_media_segment_count === 0
|
|
&& epoch.last_media_segment_age_ms === null;
|
|
}
|
|
if (state === "pending-first-media") {
|
|
return epoch.init_committed === true
|
|
&& isNonNegativeInteger(epoch.init_committed_age_ms)
|
|
&& epoch.first_media_committed === false
|
|
&& epoch.first_media_committed_age_ms === null
|
|
&& epoch.committed_media_segment_count === 0
|
|
&& epoch.last_media_segment_age_ms === null;
|
|
}
|
|
return epoch.init_committed === true
|
|
&& isNonNegativeInteger(epoch.init_committed_age_ms)
|
|
&& epoch.first_media_committed === true
|
|
&& isNonNegativeInteger(epoch.first_media_committed_age_ms)
|
|
&& epoch.committed_media_segment_count > 0
|
|
&& isNonNegativeInteger(epoch.last_media_segment_age_ms);
|
|
}
|
|
|
|
export function isXgridsConnectionVerification(
|
|
value: unknown,
|
|
): value is XgridsConnectionVerification {
|
|
if (!isRecord(value)) return false;
|
|
return (
|
|
isLiteralValue(value.status, XGRIDS_CONNECTION_VERIFICATION_STATUSES)
|
|
&& isLiteralValue(
|
|
value.lease_state,
|
|
XGRIDS_CONNECTION_VERIFICATION_LEASE_STATES,
|
|
)
|
|
&& isNonNegativeInteger(value.lease_generation)
|
|
&& isNonNegativeInteger(value.supervisor_revision)
|
|
&& isStringOrNull(value.endpoint_validation)
|
|
&& isLiteralValue(
|
|
value.network_reachability,
|
|
XGRIDS_CONNECTION_NETWORK_REACHABILITY,
|
|
)
|
|
&& isOptionalString(value.host_route_class)
|
|
&& isOptionalString(value.address_source)
|
|
&& isOptionalString(value.connection_origin)
|
|
&& isOptionalString(value.admission_source)
|
|
&& isOptionalBoolean(value.address_changed)
|
|
&& isOptionalBoolean(value.previous_address_present)
|
|
&& isOptionalBoolean(value.write_performed)
|
|
&& isStringOrNull(value.observed_at)
|
|
&& isOptionalString(value.last_known_reachable_at)
|
|
&& isOptionalString(value.reason_code)
|
|
);
|
|
}
|
|
|
|
export function isXgridsConnectionReconfiguration(
|
|
value: unknown,
|
|
): value is XgridsConnectionReconfiguration {
|
|
if (!isRecord(value)) return false;
|
|
const intent = value.intent;
|
|
const intentId = value.intent_id;
|
|
const status = value.status;
|
|
const requiredTransportRef = value.required_transport_ref;
|
|
const requiredMode = value.required_connection_mode;
|
|
const observed = value.required_transport_observed;
|
|
return Boolean(
|
|
value.schema_version
|
|
=== "missioncore.xgrids-k1-connection-reconfiguration/v1"
|
|
&& isNonNegativeInteger(value.revision)
|
|
&& isStringOrNull(intentId)
|
|
&& (
|
|
intent === null
|
|
|| isLiteralValue(intent, XGRIDS_CONNECTION_RECONFIGURATION_INTENTS)
|
|
)
|
|
&& isLiteralValue(
|
|
status,
|
|
XGRIDS_CONNECTION_RECONFIGURATION_STATUSES,
|
|
)
|
|
&& isStringOrNull(requiredTransportRef)
|
|
&& (
|
|
requiredMode === null
|
|
|| isLiteralValue(requiredMode, XGRIDS_CONNECTION_MODES)
|
|
)
|
|
&& isNonNegativeIntegerOrNull(value.minimum_discovery_generation)
|
|
&& isNonNegativeIntegerOrNull(value.fresh_discovery_generation)
|
|
&& (observed === null || typeof observed === "boolean")
|
|
&& isStringOrNull(value.prepared_at)
|
|
&& value.automatic_retry === false
|
|
&& (
|
|
status === "idle"
|
|
? intent === null
|
|
&& intentId === null
|
|
&& requiredTransportRef === null
|
|
&& requiredMode === null
|
|
&& value.minimum_discovery_generation === null
|
|
&& value.fresh_discovery_generation === null
|
|
&& observed === null
|
|
&& value.prepared_at === null
|
|
: intent !== null
|
|
&& Boolean(intentId?.trim())
|
|
&& requiredMode === "bridge"
|
|
&& isNonNegativeInteger(value.minimum_discovery_generation)
|
|
&& Boolean(value.prepared_at?.trim())
|
|
&& (
|
|
intent === "select-device"
|
|
? requiredTransportRef === null
|
|
: Boolean(requiredTransportRef?.trim())
|
|
)
|
|
&& (
|
|
status === "awaiting-fresh-scan"
|
|
? value.fresh_discovery_generation === null
|
|
&& observed === null
|
|
: isNonNegativeInteger(value.fresh_discovery_generation)
|
|
&& value.fresh_discovery_generation
|
|
>= value.minimum_discovery_generation
|
|
&& (
|
|
intent === "change-network"
|
|
? typeof observed === "boolean"
|
|
: observed === null
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
export function isXgridsConnectionPolicyDecision(
|
|
value: unknown,
|
|
): value is XgridsConnectionPolicyDecision {
|
|
if (!isRecord(value)) return false;
|
|
const requiredMode = value.required_connection_mode;
|
|
return (
|
|
typeof value.allowed === "boolean"
|
|
&& isStringArray(value.reason_codes)
|
|
&& isLiteralValue(value.target_source, XGRIDS_CONNECTION_POLICY_TARGET_SOURCES)
|
|
&& isStringOrNull(value.required_transport_ref)
|
|
&& (
|
|
requiredMode === undefined
|
|
|| requiredMode === null
|
|
|| isLiteralValue(requiredMode, XGRIDS_CONNECTION_MODES)
|
|
)
|
|
&& typeof value.requires_live_gatt_validation === "boolean"
|
|
&& value.automatic_retry === false
|
|
&& (
|
|
value.execution_mode === undefined
|
|
|| value.execution_mode === "capture-only"
|
|
)
|
|
&& (
|
|
value.physical_command_allowed === undefined
|
|
|| value.physical_command_allowed === false
|
|
)
|
|
&& (
|
|
value.physical_outcome === undefined
|
|
|| value.physical_outcome === "unknown"
|
|
)
|
|
&& (
|
|
value.operator_follow_up === undefined
|
|
|| value.operator_follow_up === "manual-device-stop-required"
|
|
)
|
|
&& (
|
|
value.device_write_performed === undefined
|
|
|| value.device_write_performed === false
|
|
)
|
|
);
|
|
}
|
|
|
|
export function isXgridsConnectionPolicy(
|
|
value: unknown,
|
|
): value is XgridsConnectionPolicy {
|
|
if (
|
|
!isRecord(value)
|
|
|| value.schema_version !== "missioncore.xgrids-k1-connection-policy/v1"
|
|
|| !isNonNegativeInteger(value.supervisor_revision)
|
|
|| !isNonNegativeIntegerOrNull(value.network_ledger_revision)
|
|
|| typeof value.recommended_action !== "string"
|
|
|| !Array.isArray(value.allowed_actions)
|
|
|| !isRecord(value.actions)
|
|
|| !isRecord(value.facts)
|
|
|| value.facts.retained_context_is_presence !== false
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const allowedActions: XgridsConnectionPolicyAction[] = [];
|
|
const policyActions = value.actions;
|
|
for (const action of value.allowed_actions) {
|
|
if (!isLiteralValue(action, XGRIDS_CONNECTION_POLICY_ACTIONS)) return false;
|
|
allowedActions.push(action);
|
|
}
|
|
|
|
for (const [action, decision] of Object.entries(policyActions)) {
|
|
if (
|
|
!isLiteralValue(action, XGRIDS_CONNECTION_POLICY_ACTIONS)
|
|
|| !isXgridsConnectionPolicyDecision(decision)
|
|
) {
|
|
return false;
|
|
}
|
|
if (decision.allowed !== allowedActions.includes(action)) return false;
|
|
|
|
if (
|
|
action === "observe-current-device-network"
|
|
&& decision.target_source !== "retained-current-process"
|
|
) {
|
|
return false;
|
|
}
|
|
if (
|
|
action === "observe-configured-device-network"
|
|
&& decision.target_source !== "durable-configured-state"
|
|
) {
|
|
return false;
|
|
}
|
|
if (
|
|
action === "retire-unavailable-physical-target"
|
|
&& (
|
|
decision.target_source !== "durable-physical-command"
|
|
|| decision.physical_command_allowed !== false
|
|
|| decision.physical_outcome !== "unknown"
|
|
|| decision.device_write_performed !== false
|
|
|| decision.requires_live_gatt_validation !== false
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
if (
|
|
decision.allowed
|
|
&& (action === "observe-current-device-network"
|
|
|| action === "observe-configured-device-network")
|
|
&& (
|
|
!decision.required_transport_ref?.trim()
|
|
|| decision.required_connection_mode == null
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return allowedActions.every((action) => {
|
|
const decision = policyActions[action];
|
|
return isRecord(decision) && decision.allowed === true;
|
|
});
|
|
}
|
|
|
|
function hasValidRuntimeContracts(
|
|
value: unknown,
|
|
): value is XgridsK1State {
|
|
if (!isRecord(value)) return false;
|
|
const verification = value.connection_verification;
|
|
const reconfiguration = value.connection_reconfiguration;
|
|
const policy = value.connection_policy;
|
|
const attempt = value.connection_attempt;
|
|
const activeStreamRecovery = value.connection_recovery;
|
|
const verificationValid = verification === undefined
|
|
|| verification === null
|
|
|| isXgridsConnectionVerification(verification);
|
|
const policyValid = policy === undefined
|
|
|| policy === null
|
|
|| isXgridsConnectionPolicy(policy);
|
|
const reconfigurationValid = reconfiguration === undefined
|
|
|| reconfiguration === null
|
|
|| isXgridsConnectionReconfiguration(reconfiguration);
|
|
const attemptValid = attempt === undefined
|
|
|| attempt === null
|
|
|| (
|
|
isRecord(attempt)
|
|
&& attempt.schema_version === "missioncore.xgrids-k1-connection-attempt/v1"
|
|
&& isXgridsConnectionAttemptPhase(attempt.phase)
|
|
&& attempt.automatic_retry === false
|
|
);
|
|
const activeStreamRecoveryValid = activeStreamRecovery === undefined
|
|
|| activeStreamRecovery === null
|
|
|| isXgridsActiveStreamRecovery(activeStreamRecovery);
|
|
const producerGenerationValid = value.producer_generation === undefined
|
|
|| value.producer_generation === null
|
|
|| isNonNegativeInteger(value.producer_generation);
|
|
return verificationValid
|
|
&& reconfigurationValid
|
|
&& policyValid
|
|
&& attemptValid
|
|
&& activeStreamRecoveryValid
|
|
&& producerGenerationValid;
|
|
}
|
|
|
|
function unwrapState(payload: unknown): XgridsK1State {
|
|
const value = isRecord(payload) && isRecord(payload.state) ? payload.state : payload;
|
|
|
|
if (!hasValidRuntimeContracts(value)) {
|
|
throw new ApiError("Локальный сервер вернул некорректное состояние.");
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
async function requestJson(
|
|
path: string,
|
|
init: RequestInit | undefined,
|
|
operationLabel: string,
|
|
timeoutMs: number,
|
|
): Promise<unknown> {
|
|
const controller = new AbortController();
|
|
let timedOut = false;
|
|
const timeout = setTimeout(() => {
|
|
timedOut = true;
|
|
controller.abort();
|
|
}, timeoutMs);
|
|
|
|
try {
|
|
const response = await fetch(path, {
|
|
...init,
|
|
signal: controller.signal,
|
|
headers: {
|
|
Accept: "application/json",
|
|
...(init?.body ? { "Content-Type": "application/json" } : {}),
|
|
...init?.headers,
|
|
},
|
|
});
|
|
const bodyText = await response.text();
|
|
let body: unknown;
|
|
|
|
if (bodyText) {
|
|
try {
|
|
body = JSON.parse(bodyText) as unknown;
|
|
} catch {
|
|
body = bodyText;
|
|
}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const detail =
|
|
isRecord(body) && typeof body.detail === "string"
|
|
? body.detail
|
|
: typeof body === "string" && body.trim()
|
|
? body.trim()
|
|
: `Запрос к API устройства завершился ошибкой HTTP ${response.status}.`;
|
|
throw new ApiError(
|
|
detail || `Запрос к API устройства завершился ошибкой HTTP ${response.status}.`,
|
|
response.status,
|
|
);
|
|
}
|
|
|
|
return body;
|
|
} catch (error) {
|
|
if (error instanceof ApiError) throw error;
|
|
if (timedOut) {
|
|
throw new ApiRequestTimeoutError(operationLabel, timeoutMs);
|
|
}
|
|
throw new ApiError(
|
|
"Не удалось подключиться к локальному сервису устройства.",
|
|
0,
|
|
true,
|
|
);
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
async function postState(
|
|
path: string,
|
|
body: object | undefined,
|
|
operationLabel: string,
|
|
timeoutMs: number,
|
|
): Promise<XgridsK1State> {
|
|
const payload = await requestJson(path, {
|
|
method: "POST",
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
}, operationLabel, timeoutMs);
|
|
|
|
if (payload === undefined) {
|
|
return xgridsK1Api.getState();
|
|
}
|
|
|
|
return unwrapState(payload);
|
|
}
|
|
|
|
function invokeState(actionId: string, input: object = {}): Promise<XgridsK1State> {
|
|
return postState(
|
|
`/api/v1/device-plugins/${encodeURIComponent(PLUGIN_ID)}/actions/${encodeURIComponent(actionId)}`,
|
|
{ input },
|
|
actionId,
|
|
requestTimeoutMsForAction(actionId),
|
|
);
|
|
}
|
|
|
|
let stateReadInFlight: Promise<XgridsK1State> | null = null;
|
|
|
|
function readStateSingleFlight(): Promise<XgridsK1State> {
|
|
if (stateReadInFlight) return stateReadInFlight;
|
|
|
|
const request = invokeState(xgridsK1Actions.stateRead);
|
|
stateReadInFlight = request;
|
|
const release = () => {
|
|
if (stateReadInFlight === request) stateReadInFlight = null;
|
|
};
|
|
void request.then(release, release);
|
|
return request;
|
|
}
|
|
|
|
export const xgridsK1Api = {
|
|
async getHealth(): Promise<HealthResponse> {
|
|
const payload = await requestJson(
|
|
"/api/health",
|
|
undefined,
|
|
"health.read",
|
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
);
|
|
if (!isRecord(payload)) {
|
|
throw new ApiError("Локальный сервер вернул некорректный ответ проверки.");
|
|
}
|
|
return payload as HealthResponse;
|
|
},
|
|
|
|
async getState(): Promise<XgridsK1State> {
|
|
return readStateSingleFlight();
|
|
},
|
|
|
|
scanBle(
|
|
body: SnapshotRuntimeFencedRequest<ScanRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.discoveryScan, body);
|
|
},
|
|
|
|
selectConnectionMode(
|
|
body: SnapshotRuntimeFencedRequest<SelectConnectionModeRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.connectionModeSelect, body);
|
|
},
|
|
|
|
prepareConnectionReconfiguration(
|
|
body: SnapshotRuntimeFencedRequest<PrepareConnectionReconfigurationRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.connectionReconfigurePrepare, body);
|
|
},
|
|
|
|
connect(
|
|
body: SnapshotRuntimeFencedRequest<ConnectRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.networkProvision, body);
|
|
},
|
|
|
|
verifyConnection(
|
|
body: SnapshotRuntimeFencedRequest<ConnectionVerifyRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.connectionVerify, body);
|
|
},
|
|
|
|
probeConfiguredEndpoint(
|
|
body: SnapshotRuntimeFencedRequest<ConfiguredEndpointProbeRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.configuredEndpointProbe, body);
|
|
},
|
|
|
|
retireUnavailablePhysicalCommand(
|
|
body: SnapshotRuntimeFencedRequest<RetireUnavailablePhysicalCommandRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.physicalCommandRetireUnavailable, body);
|
|
},
|
|
|
|
reopenRetiredPhysicalReconciliation(
|
|
body: SnapshotRuntimeFencedRequest<ReopenRetiredPhysicalReconciliationRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(
|
|
xgridsK1Actions.physicalCommandReopenRetiredReconciliation,
|
|
body,
|
|
);
|
|
},
|
|
|
|
prepareAcquisition(
|
|
body: SnapshotRuntimeFencedRequest<PrepareAcquisitionRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.acquisitionPrepare, body);
|
|
},
|
|
|
|
startAcquisition(
|
|
body: SnapshotRuntimeFencedRequest<StartAcquisitionRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.acquisitionStart, body);
|
|
},
|
|
|
|
stopAcquisition(
|
|
body: SnapshotRuntimeFencedRequest<StopAcquisitionRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.acquisitionStop, body);
|
|
},
|
|
|
|
abortAcquisition(
|
|
body: SnapshotRuntimeFencedRequest<AbortAcquisitionRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.acquisitionAbort, body);
|
|
},
|
|
|
|
forceFinishAcquisitionLocally(
|
|
body: SnapshotRuntimeFencedRequest<ForceFinishAcquisitionRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.acquisitionForceFinishLocal, body);
|
|
},
|
|
|
|
startReplay(body: ReplayRequest): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.streamStartReplay, body);
|
|
},
|
|
|
|
startLiveCompatibility(body: CompatibilityLiveRequest): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.compatibilityStreamStartLive, body);
|
|
},
|
|
|
|
stopSessionCompatibility(
|
|
body: SnapshotRuntimeFenceRequest,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.compatibilityStreamStop, body);
|
|
},
|
|
|
|
selectCameraPreview(body: SelectCameraPreviewRequest): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.cameraPreviewSelect, body);
|
|
},
|
|
|
|
stopCameraPreview(body: StopCameraPreviewRequest): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.cameraPreviewStop, body);
|
|
},
|
|
|
|
updateViewerSettings(body: ViewerSettings): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.viewerSettingsUpdate, body);
|
|
},
|
|
|
|
getShadowApplicationControlState(): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.applicationControlShadowState);
|
|
},
|
|
|
|
armShadowApplicationControl(
|
|
body: ShadowApplicationControlArmRequest,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.applicationControlShadowArm, body);
|
|
},
|
|
|
|
disarmShadowApplicationControl(): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.applicationControlShadowDisarm);
|
|
},
|
|
|
|
openApplicationControlSession(
|
|
body: SnapshotRuntimeFencedRequest<OpenApplicationControlSessionRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.applicationControlSessionOpen, body);
|
|
},
|
|
|
|
enterApplicationWorkspace(
|
|
body: SnapshotRuntimeFencedRequest<EnterApplicationWorkspaceRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.applicationControlWorkspaceEnter, body);
|
|
},
|
|
|
|
closeApplicationControlSession(
|
|
body: SnapshotRuntimeFencedRequest<CloseApplicationControlSessionRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.applicationControlSessionClose, body);
|
|
},
|
|
|
|
reconcilePhysicalCommand(
|
|
body: SnapshotRuntimeFencedRequest<ReconcilePhysicalCommandRequest>,
|
|
): Promise<XgridsK1State> {
|
|
return invokeState(xgridsK1Actions.physicalCommandReconcile, body);
|
|
},
|
|
};
|
|
|
|
export type EventSocketStatus = "connecting" | "open" | "closed" | "error";
|
|
|
|
function eventSocketUrl(): string {
|
|
const url = new URL(
|
|
`/api/v1/device-plugins/${encodeURIComponent(PLUGIN_ID)}/events`,
|
|
window.location.href,
|
|
);
|
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
return url.toString();
|
|
}
|
|
|
|
export function openEventSocket(
|
|
onState: (state: XgridsK1State) => void,
|
|
onStatus: (status: EventSocketStatus) => void,
|
|
): () => void {
|
|
onStatus("connecting");
|
|
const socket = new WebSocket(eventSocketUrl());
|
|
|
|
socket.addEventListener("open", () => onStatus("open"));
|
|
socket.addEventListener("message", (event) => {
|
|
try {
|
|
const payload = JSON.parse(String(event.data)) as unknown;
|
|
onState(unwrapState(payload));
|
|
} catch {
|
|
// The REST poll remains authoritative if an unrelated event is received.
|
|
}
|
|
});
|
|
socket.addEventListener("error", () => onStatus("error"));
|
|
socket.addEventListener("close", () => onStatus("closed"));
|
|
|
|
return () => socket.close(1000, "Пункт управления закрыт");
|
|
}
|