Защита Bridge и камеры K1 при переподключении
This commit is contained in:
@@ -96,6 +96,23 @@ export function livePresentationCloseFence(
|
||||
return acquisitionId ? JSON.stringify([source.id, acquisitionId]) : null;
|
||||
}
|
||||
|
||||
function acquisitionIdFromLivePresentationCloseFence(fence: string): string | null {
|
||||
try {
|
||||
const decoded: unknown = JSON.parse(fence);
|
||||
if (
|
||||
!Array.isArray(decoded)
|
||||
|| decoded.length !== 2
|
||||
|| typeof decoded[0] !== "string"
|
||||
|| typeof decoded[1] !== "string"
|
||||
|| !decoded[0].trim()
|
||||
|| !decoded[1].trim()
|
||||
) return null;
|
||||
return decoded[1].trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface LiveDefaultPresentationAdmission {
|
||||
visibleIds: string[];
|
||||
removedIds: string[];
|
||||
@@ -132,9 +149,11 @@ export function admitLiveDefaultPresentations(
|
||||
/**
|
||||
* A restored workspace may hide cameras that the device plugin has not selected.
|
||||
* It must still admit the exact selected delivery in a fresh browser document,
|
||||
* or a replacement delivery for a camera already presented in this acquisition.
|
||||
* a replacement delivery for a camera already presented in this acquisition,
|
||||
* or the first delivery of a later acquisition in the same browser document.
|
||||
* An explicit close remains a separate acquisition-scoped fence in
|
||||
* `admitLiveDefaultPresentations` and de-selects the source in the plugin first.
|
||||
* `admitLiveDefaultPresentations`; whether it may de-select the source remains
|
||||
* the plugin's current activation-authority decision.
|
||||
*/
|
||||
export function restoredLayoutMayAdmitLiveDefault(
|
||||
sources: readonly ObservationSourceDescriptor[],
|
||||
@@ -145,7 +164,13 @@ export function restoredLayoutMayAdmitLiveDefault(
|
||||
if (presentedAcquisitionSources.size === 0) return true;
|
||||
return selectedSources.some((source) => {
|
||||
const lineage = livePresentationCloseFence(source);
|
||||
return Boolean(lineage && presentedAcquisitionSources.has(lineage));
|
||||
if (!lineage) return false;
|
||||
if (presentedAcquisitionSources.has(lineage)) return true;
|
||||
const acquisitionId = source.binding.acquisitionId?.trim();
|
||||
if (!acquisitionId) return false;
|
||||
return ![...presentedAcquisitionSources].some(
|
||||
(presented) => acquisitionIdFromLivePresentationCloseFence(presented) === acquisitionId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5355,6 +5355,55 @@ test("an unresolved physical command has one explicit server-bound read-only rec
|
||||
/Результаты последнего Bluetooth-поиска|nearby-unrelated-device|>Выбрать<|>Применить</,
|
||||
);
|
||||
|
||||
const quickRecoveryInsideBridgeState = structuredClone(state);
|
||||
quickRecoveryInsideBridgeState.physical_command.record.connection.connection_mode =
|
||||
"quick-connect";
|
||||
quickRecoveryInsideBridgeState.physical_command.record.connection.target_ipv4 =
|
||||
"192.168.56.1";
|
||||
quickRecoveryInsideBridgeState.semantic_topology_store.record.connection_mode =
|
||||
"quick-connect";
|
||||
quickRecoveryInsideBridgeState.semantic_topology_store.record.ipv4 = "192.168.56.1";
|
||||
quickRecoveryInsideBridgeState.connection_policy.actions[
|
||||
"observe-configured-device-network"
|
||||
].required_connection_mode = "quick-connect";
|
||||
const quickRecoveryInsideBridgeMarkup = renderToStaticMarkup(createElement(
|
||||
K1ProvisioningPipeline,
|
||||
{
|
||||
controller: provisioningController(quickRecoveryInsideBridgeState),
|
||||
desiredMode: "bridge",
|
||||
},
|
||||
));
|
||||
assert.equal(
|
||||
buttonMarkupWithText(
|
||||
quickRecoveryInsideBridgeMarkup,
|
||||
"Переподключиться",
|
||||
).length,
|
||||
0,
|
||||
);
|
||||
assert.equal(
|
||||
buttonMarkupWithText(
|
||||
quickRecoveryInsideBridgeMarkup,
|
||||
"Подключить новый K1",
|
||||
).length,
|
||||
1,
|
||||
);
|
||||
|
||||
const selectedQuickRecoveryState = structuredClone(
|
||||
quickRecoveryInsideBridgeState,
|
||||
);
|
||||
selectedQuickRecoveryState.desired_connection_mode = "quick-connect";
|
||||
const selectedQuickRecoveryMarkup = renderToStaticMarkup(createElement(
|
||||
K1ProvisioningPipeline,
|
||||
{
|
||||
controller: provisioningController(selectedQuickRecoveryState),
|
||||
desiredMode: "quick-connect",
|
||||
},
|
||||
));
|
||||
assert.equal(
|
||||
buttonMarkupWithText(selectedQuickRecoveryMarkup, "Переподключиться").length,
|
||||
1,
|
||||
);
|
||||
|
||||
const networkSetupState = structuredClone(state);
|
||||
networkSetupState.connection_verification = {
|
||||
status: "unreachable",
|
||||
@@ -5454,6 +5503,14 @@ test("an unresolved physical command has one explicit server-bound read-only rec
|
||||
);
|
||||
assert.match(physicalRecovery, /physicalRecoveryTarget\?\.serverBound/);
|
||||
assert.match(physicalRecovery, /surfaceErrors: false/);
|
||||
assert.match(
|
||||
physicalRecovery,
|
||||
/physicalRecoveryTarget\.connectionMode !== connectionMode/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
physicalRecovery,
|
||||
/commitDesiredModeForExplicitAction|selectConnectionMode\(/,
|
||||
);
|
||||
});
|
||||
|
||||
test("supervisor DeviceInfo Verify outranks a stale exact fresh row for physical recovery", () => {
|
||||
|
||||
@@ -310,7 +310,7 @@ test("a sequential live acquisition re-arms the same camera without reopening a
|
||||
]);
|
||||
});
|
||||
|
||||
test("a restored layout admits a selected delivery after reload and only same-acquisition successors", () => {
|
||||
test("a restored layout admits same-lineage replacements and the next acquisition", () => {
|
||||
const camera = (acquisitionId, deliveryId) => ({
|
||||
id: "k1:sensor.camera.right",
|
||||
sourceId: "sensor.camera.right",
|
||||
@@ -344,7 +344,11 @@ test("a restored layout admits a selected delivery after reload and only same-ac
|
||||
|
||||
assert.equal(restoredLayoutMayAdmitLiveDefault([original], new Set()), true);
|
||||
assert.equal(restoredLayoutMayAdmitLiveDefault([successor], presented), true);
|
||||
assert.equal(restoredLayoutMayAdmitLiveDefault([unrelated], presented), false);
|
||||
assert.equal(
|
||||
restoredLayoutMayAdmitLiveDefault([unrelated], presented),
|
||||
true,
|
||||
"Bridge acquisition A must not suppress the first Quick Connect camera in acquisition B",
|
||||
);
|
||||
assert.equal(restoredLayoutMayAdmitLiveDefault([{
|
||||
...original,
|
||||
activation: { ...original.activation, selected: false },
|
||||
|
||||
Reference in New Issue
Block a user