fix(k1): harden BLE discovery and bridge recovery

This commit is contained in:
DCCONSTRUCTIONS
2026-08-06 12:43:00 +03:00
parent be50144ca8
commit 52da9b75b7
14 changed files with 1010 additions and 48 deletions
+9
View File
@@ -386,6 +386,11 @@ export interface ConnectRequest {
idempotency_key?: string;
}
export interface ConnectionVerifyRequest {
device_id: string;
compatibility_attestation: CompatibilityAttestation;
}
export interface PrepareAcquisitionRequest {
project_name: string;
mount_type: "handheld";
@@ -593,6 +598,10 @@ export const xgridsK1Api = {
return invokeState(xgridsK1Actions.networkProvision, body);
},
verifyConnection(body?: ConnectionVerifyRequest): Promise<XgridsK1State> {
return invokeState(xgridsK1Actions.connectionVerify, body);
},
prepareAcquisition(body: PrepareAcquisitionRequest): Promise<XgridsK1State> {
return invokeState(xgridsK1Actions.acquisitionPrepare, body);
},
@@ -118,7 +118,7 @@ export function K1ProvisioningPipeline({
phaseLabel: string;
phaseTone: StatusTone;
}) {
const { state, pendingAction, scan, connect } = controller;
const { state, pendingAction, scan, connect, verifyConnection } = controller;
const [powerConfirmed, setPowerConfirmed] = useState(false);
const [selectedDeviceId, setSelectedDeviceId] = useState("");
const [ssid, setSsid] = useState("");
@@ -160,6 +160,11 @@ export function K1ProvisioningPipeline({
() => devices.find((device) => device.device_id === selectedDeviceId),
[devices, selectedDeviceId],
);
const canAdoptExistingBridge = connectionMode === "bridge"
&& powerConfirmed
&& selectedDeviceId.length > 0
&& deviceSummary !== undefined
&& !isBusy;
const resetProvisioningIntent = () => {
provisioningIntentRef.current = null;
@@ -190,6 +195,14 @@ export function K1ProvisioningPipeline({
}
};
const submitExistingBridgeAdoption = async () => {
if (!canAdoptExistingBridge) return;
await verifyConnection({
device_id: selectedDeviceId,
compatibility_attestation: profileSelectionForConnectionMode("bridge"),
});
};
return (
<GlassSurface className="connection-panel" padding="lg">
<header className="panel-heading">
@@ -275,6 +288,24 @@ export function K1ProvisioningPipeline({
? connectionMode === "quick-connect" ? "Включаем точку и подключаем…" : "Подключаем…"
: modeCopy.buttonLabel}
</Button>
{connectionMode === "bridge" ? (
<>
<Button
width="full"
variant="secondary"
icon={<Icon name="refresh" />}
disabled={!canAdoptExistingBridge}
onClick={() => void submitExistingBridgeAdoption()}
>
{pendingAction === "verify"
? "Проверяем существующее подключение…"
: "Подхватить существующее подключение"}
</Button>
<p className="safety-note">
Если K1 уже подключён к этой сети другим способом, Mission Core проверит и примет существующее подключение без изменения настроек WiFi.
</p>
</>
) : null}
<p className="safety-note">
{modeCopy.safetyNote}
{connectionMode === "quick-connect" ? " Это лабораторный путь для уже подготовленного хоста: credential provider должен существовать в системном хранилище заранее. На чистом Mac операция завершится до BLE-записи; браузер, API, журналы и evidence секрета не получают." : " Пароль передаётся только локальному сервису, не сохраняется в браузере и удаляется из формы после успеха."}
@@ -6,6 +6,7 @@ import {
ApiError,
xgridsK1Api,
openEventSocket,
type ConnectionVerifyRequest,
type ConnectRequest,
type EventSocketStatus,
type OpenApplicationControlSessionRequest,
@@ -34,6 +35,7 @@ import { selectMonotonicXgridsState } from "./stateOrdering";
export type PendingAction =
| "scan"
| "connect"
| "verify"
| "control"
| "live"
| "replay"
@@ -191,7 +193,7 @@ function messageFor(error: unknown): string {
return "Запрос к локальному сервису устройства завершился ошибкой.";
}
function networkProvisionFailureMessage(
export function networkProvisionFailureMessage(
operation: XgridsOperation | null | undefined,
): string | null {
if (!operation || operation.status !== "failed") return null;
@@ -199,6 +201,8 @@ function networkProvisionFailureMessage(
if (typeof code !== "string") return null;
const messages: Record<string, string> = {
BleakGATTProtocolError:
"Сканер отклонил запись сетевого профиля. Результат изменения сети неизвестен; автоматический повтор запрещён. Проверьте текущее состояние K1 или подхватите существующее подключение без изменения настроек Wi‑Fi.",
"network-not-found":
"Точка доступа выбранного K1 не найдена. Команда включения точки не повторялась; проверьте питание и состояние K1.",
"credential-entry-cancelled":
@@ -392,6 +396,12 @@ export function useXgridsK1Runtime(enabled: boolean) {
[acceptState, run, state],
);
const verifyConnection = useCallback(
(request?: ConnectionVerifyRequest) =>
run("verify", () => xgridsK1Api.verifyConnection(request)),
[run],
);
const openApplicationControlSession = useCallback(
(request: OpenApplicationControlSessionRequest) =>
run("control", () => xgridsK1Api.openApplicationControlSession(request)),
@@ -773,6 +783,7 @@ export function useXgridsK1Runtime(enabled: boolean) {
clearError: () => setError(null),
scan,
connect,
verifyConnection,
openApplicationControlSession,
enterApplicationWorkspace,
closeApplicationControlSession,