fix(k1): harden BLE discovery and bridge recovery
This commit is contained in:
@@ -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 проверит и примет существующее подключение без изменения настроек Wi‑Fi.
|
||||
</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,
|
||||
|
||||
Reference in New Issue
Block a user