тест 9

This commit is contained in:
2026-08-24 18:38:12 +03:00
parent aac9e5b8bc
commit 10a65e4d47
6 changed files with 233 additions and 22 deletions

View File

@@ -308,6 +308,82 @@
return String(window.HockeyAgentRuntime?.currentDeviceId?.() || localStorage.getItem(storage.vmixDevice) || "").trim();
}
function rememberAssignedAgent(deviceId) {
const clean = String(deviceId || "").trim();
if (!clean) return;
localStorage.setItem(storage.vmixDevice, clean);
try { window.HockeyAgentRuntime?.selectDevice?.(clean); } catch (_) {}
}
async function ensureSessionAgentAssignment(session) {
const token = String(session?.token || localStorage.getItem(storage.session) || "").trim();
let assignment = session?.agent_assignment && typeof session.agent_assignment === "object"
? session.agent_assignment
: null;
if (assignment?.device_id) rememberAssignedAgent(assignment.device_id);
if (assignment?.delivered) return assignment;
if (!token) return assignment;
// BUILD111: opening the web match is not proof that Agent received
// match.assign. Recover from stale/empty localStorage only when routing is
// unambiguous: the selected live Agent or exactly one live active Agent.
let devicesPayload = null;
try {
devicesPayload = await request("/api/hockey/agents/devices", { timeoutMs: 3500 });
} catch (_) {
return assignment;
}
const devices = Array.isArray(devicesPayload?.devices) ? devicesPayload.devices : [];
const live = devices.filter((item) => (
item?.paired_to_me
&& item?.active_for_account
&& item?.online
));
const preferredIds = [
String(assignment?.device_id || "").trim(),
currentAgentDeviceId(),
].filter(Boolean);
let target = null;
for (const preferred of preferredIds) {
target = live.find((item) => String(item.device_id || "") === preferred) || null;
if (target) break;
}
if (!target && live.length === 1) target = live[0];
if (!target?.device_id) return assignment;
try {
const rebound = await request(
`/api/hockey/agents/devices/${encodeURIComponent(target.device_id)}/select-session`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_token: token, apply_mapping: false }),
timeoutMs: 5000,
}
);
assignment = rebound?.assignment || assignment;
if (assignment?.device_id) rememberAssignedAgent(assignment.device_id);
if (session && assignment) session.agent_assignment = assignment;
return assignment;
} catch (error) {
console.warn("[Hockey] Agent match.assign retry failed:", error);
return assignment;
}
}
async function refreshSessionAgentMapping(session) {
const assignment = await ensureSessionAgentAssignment(session);
const deviceId = String(assignment?.device_id || "").trim();
if (!deviceId || !assignment?.delivered) {
return { ok: false, reason: "agent_match_not_delivered" };
}
return request(
`/api/hockey/agents/devices/${encodeURIComponent(deviceId)}/apply-mapping`,
{ method: "POST", timeoutMs: 60000 }
);
}
async function ensureAccountScopedRuntimeState() {
let user = null;
try {
@@ -1660,6 +1736,12 @@ function gameCard(game) {
}
}
// Confirm that the concrete Agent actually received this new match.
// A successful /sessions response alone is not enough: assignment may be
// null/offline when the browser stored a stale device id.
const initialAgentAssignment = await ensureSessionAgentAssignment(session);
if (initialAgentAssignment && session) session.agent_assignment = initialAgentAssignment;
state.lastGameOpenError = "";
state.selectedGameId = String(id);
localStorage.setItem(storage.game, state.selectedGameId);
@@ -1700,17 +1782,34 @@ function gameCard(game) {
if (
generation === state.gameOpenGeneration
&& String(state.selectedGameId || "") === String(id)
) startSelectedGamePolling();
) {
startSelectedGamePolling();
void refreshSessionAgentMapping(session).then((mapping) => {
if (mapping?.ok === false && mapping?.reason === "agent_match_not_delivered") {
notify("Матч открыт, но Agent не получил новый матч", true);
}
}).catch((error) => console.warn("[Hockey] Mapping refresh after roster failed:", error));
}
} else if (!manual) {
// This exact match already has its own cached roster, so vMix can be
// refreshed immediately without waiting for the optional network sync.
void refreshSessionAgentMapping(session).then((mapping) => {
if (mapping?.ok === false && mapping?.reason === "agent_match_not_delivered") {
notify("Матч открыт, но Agent не получил новый матч", true);
}
}).catch((error) => console.warn("[Hockey] Mapping refresh from cached roster failed:", error));
void enrichOpenedGame(String(id), state.selectedId, generation, { opening: true })
.finally(() => {
if (
generation === state.gameOpenGeneration
&& String(state.selectedGameId || "") === String(id)
) startSelectedGamePolling();
) {
startSelectedGamePolling();
}
});
} else {
startSelectedGamePolling();
void refreshSessionAgentMapping(session).catch((error) => console.warn("[Hockey] Mapping refresh for manual match failed:", error));
}
updateMatchLoading(t("loadingMatchFinishing"));