vimp-engine 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/vimp-sim.js +2 -2
- package/core/Cargo.toml +2 -2
- package/package.json +3 -4
- package/src/client/boot.js +11 -87
- package/src/client/components/controller/Vote.js +26 -0
- package/src/client/components/model/Controls.js +9 -1
- package/src/client/components/model/RoomMenu.js +1 -1
- package/src/client/components/model/Vote.js +202 -0
- package/src/client/components/view/Auth.js +3 -3
- package/src/client/components/view/Panel.js +176 -6
- package/src/client/components/view/Voice.js +6 -1
- package/src/client/components/view/Vote.js +71 -2
- package/src/client/lib/formBuilder.js +7 -6
- package/src/client/lib/hostVoteCommand.js +2 -2
- package/src/client/lib/pickActiveGame.js +3 -12
- package/src/client/lib/retryOnNetworkError.js +0 -9
- package/src/client/main.js +47 -90
- package/src/client/network/SessionSupervisor.js +14 -14
- package/src/client/network/WebRtcManager.js +1 -2
- package/src/client/network/policyClose.js +10 -77
- package/src/client/style.css +85 -10
- package/src/client/views/gameShell.js +2 -4
- package/src/client/views/includes/auth.pug +1 -4
- package/src/client/views/includes/lobby.pug +3 -6
- package/src/client/voice/MicCapture.js +3 -1
- package/src/config/abiOps.js +42 -0
- package/src/config/closeCodes.js +7 -9
- package/src/config/gameCodes.js +1 -1
- package/src/config/hostDefaults.js +2 -2
- package/src/config/lobby.js +3 -4
- package/src/config/menuName.js +3 -0
- package/src/config/wsports.js +1 -1
- package/src/devtools/ScenarioRunner.js +1 -1
- package/src/devtools/builtinScenario.js +2 -2
- package/src/devtools/contract/rules/a2-package-scripts.js +1 -1
- package/src/devtools/contract/rules/a4-vite-config.js +1 -1
- package/src/devtools/contract/rules/c4-component-dependencies.js +1 -1
- package/src/devtools/invariants.js +1 -1
- package/src/devtools/pluginLoader.js +2 -2
- package/src/host/GameCoreAdapter.js +83 -4
- package/src/host/HostGame.js +93 -17
- package/src/host/PortMachine.js +4 -21
- package/src/host/commandFailure.js +2 -2
- package/src/host/identity.js +3 -3
- package/src/host/meta/core/CommandProcessor.js +2 -2
- package/src/host/meta/core/RoundManager.js +8 -1
- package/src/host/meta/core/VoteCoordinator.js +56 -1
- package/src/host/meta/modules/Accolades.js +1 -1
- package/src/host/meta/modules/TimerManager.js +16 -2
- package/src/host/meta/modules/Vote.js +65 -0
- package/src/host/meta/player/ParticipantManager.js +19 -3
- package/src/lib/capabilities.js +17 -0
- package/src/lib/createHostRuntime.js +1 -1
- package/src/lib/gameConfigView.js +7 -0
- package/src/lib/loadGamePackage.js +7 -9
- package/src/lib/offlinePlayerData.js +1 -1
- package/src/lib/validators.js +2 -2
- package/src/lib/voicePacket.js +1 -1
- package/src/standalone/index.js +3 -2
- package/src/client/lib/footerLink.js +0 -30
- package/src/client/network/WebSocketTransport.js +0 -78
package/src/client/main.js
CHANGED
|
@@ -35,11 +35,10 @@ import {
|
|
|
35
35
|
bindLiveErrors,
|
|
36
36
|
} from './lib/formBuilder.js';
|
|
37
37
|
import { normalizeAuthParams } from './lib/authParams.js';
|
|
38
|
-
import { renderDocsLink } from './lib/footerLink.js';
|
|
39
38
|
import { createGameActivator } from './lib/gameActivator.js';
|
|
40
39
|
import createAutostart from './lib/autostart.js';
|
|
41
40
|
import { applyCatalogState } from './lib/catalogState.js';
|
|
42
|
-
import {
|
|
41
|
+
import { resolveBootConfig } from './boot.js';
|
|
43
42
|
import {
|
|
44
43
|
ensureGameShell,
|
|
45
44
|
ensureCanvas,
|
|
@@ -67,7 +66,6 @@ import { validateAuth } from '../lib/validators.js';
|
|
|
67
66
|
import SoundManager from './SoundManager.js';
|
|
68
67
|
import SignalingClient from './network/SignalingClient.js';
|
|
69
68
|
import LoopbackTransport from './network/LoopbackTransport.js';
|
|
70
|
-
import WebSocketTransport from './network/WebSocketTransport.js';
|
|
71
69
|
import InlineHostBridge from './network/InlineHostBridge.js';
|
|
72
70
|
import SessionSupervisor, {
|
|
73
71
|
SESSION_STATES,
|
|
@@ -83,11 +81,7 @@ import {
|
|
|
83
81
|
import { createVerifier, challengeOf } from './lib/pkce.js';
|
|
84
82
|
import { createTurnstile } from './lib/turnstile.js';
|
|
85
83
|
import TokenRenewer from './network/TokenRenewer.js';
|
|
86
|
-
import {
|
|
87
|
-
isKickClose,
|
|
88
|
-
POLICY_CLOSE_INFORMS,
|
|
89
|
-
shouldReloadAfterClose,
|
|
90
|
-
} from './network/policyClose.js';
|
|
84
|
+
import { isKickClose } from './network/policyClose.js';
|
|
91
85
|
import LobbyModel from './components/model/Lobby.js';
|
|
92
86
|
import LobbyView from './components/view/Lobby.js';
|
|
93
87
|
import LobbyCtrl from './components/controller/Lobby.js';
|
|
@@ -145,8 +139,8 @@ import runHotTick from './lib/hotTick.js';
|
|
|
145
139
|
|
|
146
140
|
// Динамическая загрузка игры по каталогу мастера (Этап 6.3): ClientPlugin
|
|
147
141
|
// (parts, bakers, игровой CSS, хуки ядра) грузится по entries.client манифеста
|
|
148
|
-
// — движок не импортирует игру статически. Первой активируется
|
|
149
|
-
//
|
|
142
|
+
// — движок не импортирует игру статически. Первой активируется первая
|
|
143
|
+
// доступная игра каталога, но активная игра не заморожена: выбор в #lobby-game и
|
|
150
144
|
// вход в чужую комнату переключают её через gameActivator (см. bindActiveGame).
|
|
151
145
|
// Переключение безопасно ровно до старта матча: всё пер-игровое состояние
|
|
152
146
|
// (Factory, Pixi-приложения, clientCore, звук) появляется только в CONFIG_DATA,
|
|
@@ -179,37 +173,24 @@ function bindActiveGame(manifest, plugin) {
|
|
|
179
173
|
}
|
|
180
174
|
|
|
181
175
|
// режим загрузки (Этап 2 плана standalone-sdk): lobby — прод с мастером,
|
|
182
|
-
// solo — хост в этой же вкладке (standalone SDK)
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
// каталог манифестов нужен только лобби-контуру, а его запрос не зависит от
|
|
187
|
-
// ответа /config — пускаем оба в полёт разом, иначе старт лобби платит лишний
|
|
188
|
-
// последовательный round-trip
|
|
189
|
-
const injectedBoot = getBootConfig();
|
|
190
|
-
const manifestPromise = injectedBoot
|
|
191
|
-
? null
|
|
192
|
-
: fetchGamesManifest(lobbyConfig.gamesManifestUrl, (...args) =>
|
|
193
|
-
fetchRetrying(fetch, ...args),
|
|
194
|
-
).catch(err => err);
|
|
195
|
-
|
|
196
|
-
let boot;
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
boot = injectedBoot ?? (await resolveBootConfig());
|
|
200
|
-
} catch (e) {
|
|
201
|
-
// сети нет и режим неизвестен: репортёр ещё не создан (ему нужен режим),
|
|
202
|
-
// игроку остаётся надпись отказа
|
|
203
|
-
showBootFailure(`Failed to load the game: ${e.message}`);
|
|
204
|
-
throw e;
|
|
205
|
-
}
|
|
206
|
-
|
|
176
|
+
// solo — хост в этой же вкладке (standalone SDK). Ветвлений ровно пять:
|
|
177
|
+
// манифест, сигналинг/лобби, транспорт, авто-аутентификация и точка
|
|
178
|
+
// монтирования канвасов.
|
|
179
|
+
const boot = resolveBootConfig();
|
|
207
180
|
const bootMode = boot.mode;
|
|
208
181
|
const isLobbyMode = bootMode === 'lobby';
|
|
209
182
|
|
|
183
|
+
// каталог манифестов нужен только лобби-контуру: запрос уходит до создания
|
|
184
|
+
// журнала и каркаса, а ждут его только в загрузке игры ниже
|
|
185
|
+
const manifestPromise = isLobbyMode
|
|
186
|
+
? fetchGamesManifest(lobbyConfig.gamesManifestUrl, (...args) =>
|
|
187
|
+
fetchRetrying(fetch, ...args),
|
|
188
|
+
).catch(err => err)
|
|
189
|
+
: null;
|
|
190
|
+
|
|
210
191
|
// журнал клиентских ошибок: ставится как можно
|
|
211
|
-
// раньше, чтобы ловить и сбои самого старта. Лобби
|
|
212
|
-
//
|
|
192
|
+
// раньше, чтобы ловить и сбои самого старта. Лобби шлёт на свой бокс;
|
|
193
|
+
// SDK (solo) — только если встраивающий передал reportUrl
|
|
213
194
|
const diagnostics = createDiagnostics({
|
|
214
195
|
url:
|
|
215
196
|
bootMode === 'solo'
|
|
@@ -258,7 +239,7 @@ try {
|
|
|
258
239
|
|
|
259
240
|
try {
|
|
260
241
|
// недоступная игра активной быть не может (lib/pickActiveGame.js)
|
|
261
|
-
activeGameManifest = pickActiveGame(gamesManifest
|
|
242
|
+
activeGameManifest = pickActiveGame(gamesManifest);
|
|
262
243
|
syncDiagnosticsGame();
|
|
263
244
|
} catch (e) {
|
|
264
245
|
// «каталог непустой, но играбельного в нём нет» (движок обновили, все
|
|
@@ -266,7 +247,7 @@ try {
|
|
|
266
247
|
// лобби это то же самое, что пустой каталог: играть не во что, но
|
|
267
248
|
// модерация и заявка на месте, и только они это чинят. Терять их из-за
|
|
268
249
|
// такого нельзя, поэтому причина едет строкой отказа лобби, а не
|
|
269
|
-
// терминальным оверлеем. В solo
|
|
250
|
+
// терминальным оверлеем. В solo отказ остаётся отказом
|
|
270
251
|
if (!isLobbyMode) {
|
|
271
252
|
throw e;
|
|
272
253
|
}
|
|
@@ -278,7 +259,7 @@ try {
|
|
|
278
259
|
// пустой каталог — состояние лобби, а не отказ загрузки: модератор вправе
|
|
279
260
|
// отключить последнюю игру, реестр вправе ещё ничего не одобрить, и вернуть
|
|
280
261
|
// каталог к жизни можно только из панели, которая живёт в этой же вкладке.
|
|
281
|
-
// В solo
|
|
262
|
+
// В solo игры нет — это отказ: там весь смысл вкладки в матче
|
|
282
263
|
if (!activeGameManifest && !isLobbyMode) {
|
|
283
264
|
throw new Error('master has no games in its catalog');
|
|
284
265
|
}
|
|
@@ -338,7 +319,7 @@ const PC_LEAVE = wsports.client.LEAVE;
|
|
|
338
319
|
|
|
339
320
|
// сигнальный WebSocket мастера (лобби + установка P2P); игровой трафик идёт
|
|
340
321
|
// по WebRTC (transport), не через мастер
|
|
341
|
-
// (только lobby: solo
|
|
322
|
+
// (только lobby: у solo мастера нет вовсе)
|
|
342
323
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
343
324
|
const signaling = isLobbyMode
|
|
344
325
|
? new SignalingClient(`${wsProtocol}//${location.host}/`)
|
|
@@ -346,7 +327,7 @@ const signaling = isLobbyMode
|
|
|
346
327
|
|
|
347
328
|
// супервизор сессии (host-migration этап 4): держит текущий транспорт к
|
|
348
329
|
// хосту и переживает его обрыв — переподключается и возвращает место в
|
|
349
|
-
// матче. Создаётся при входе в комнату (или в solo
|
|
330
|
+
// матче. Создаётся при входе в комнату (или в solo-матч)
|
|
350
331
|
let supervisor = null;
|
|
351
332
|
|
|
352
333
|
// оверлей «Reconnecting…» на время переподключения (только лобби-режим)
|
|
@@ -406,7 +387,7 @@ const GAME_ROUND_START_CODE = GAME_CODES.roundStart[0];
|
|
|
406
387
|
// соединения важнее общего сообщения handleDisconnect
|
|
407
388
|
let terminalInformShown = false;
|
|
408
389
|
// ключ последнего терминального tech-кода: по нему лобби-режим отличает кик
|
|
409
|
-
// (policyClose.isKickClose) от закрытия комнаты —
|
|
390
|
+
// (policyClose.isKickClose) от закрытия комнаты — у транспортов кода закрытия нет
|
|
410
391
|
let terminalTechKey = null;
|
|
411
392
|
// снятие предыдущего animationend-листенера логотипа при повторном
|
|
412
393
|
// playLogoRoundStart — иначе листенеры копятся при частых стартах раунда
|
|
@@ -618,9 +599,9 @@ socketMethods[PS_AUTH_DATA] = data => {
|
|
|
618
599
|
|
|
619
600
|
document.getElementById('logo').textContent = texts?.title || 'VIMP';
|
|
620
601
|
|
|
621
|
-
// футер формы входа: версия пакета
|
|
622
|
-
//
|
|
623
|
-
//
|
|
602
|
+
// футер формы входа: версия пакета ИГРЫ. Здесь, а не в AuthView: его
|
|
603
|
+
// elems приходят из authSchema.elems игрового плагина, и футер каркаса —
|
|
604
|
+
// не его забота.
|
|
624
605
|
// Версию кладёт в манифест мастер (GameCatalog читает package.json
|
|
625
606
|
// пакета); в standalone-манифесте её может не быть — тогда ячейка пустая,
|
|
626
607
|
// раскладку space-between это не ломает
|
|
@@ -630,8 +611,6 @@ socketMethods[PS_AUTH_DATA] = data => {
|
|
|
630
611
|
authVersion.textContent = activeGameManifest?.packageVersion ?? '';
|
|
631
612
|
}
|
|
632
613
|
|
|
633
|
-
renderDocsLink(document.getElementById('auth-docs-link'), isDevBuild);
|
|
634
|
-
|
|
635
614
|
// память клиента (localStorage) + принудительное значение поля с
|
|
636
615
|
// единственным вариантом. Здесь, а не в AuthView: solo-путь ниже отвечает
|
|
637
616
|
// хосту вообще без формы и обязан прийти к тем же значениям
|
|
@@ -987,7 +966,7 @@ socketMethods[PS_GAME_INFORM_DATA] = data => {
|
|
|
987
966
|
|
|
988
967
|
// проигрывает shimmer-волну по логотипу в начале раунда; логотип живёт вне
|
|
989
968
|
// PanelView.containerId, поэтому панель им не управляет
|
|
990
|
-
// (см. docs
|
|
969
|
+
// (см. VIMP docs: client-api.md)
|
|
991
970
|
const playLogoRoundStart = () => {
|
|
992
971
|
const logo = document.getElementById('logo');
|
|
993
972
|
|
|
@@ -1066,6 +1045,10 @@ socketMethods[PS_CLEAR] = function (setIdList) {
|
|
|
1066
1045
|
}
|
|
1067
1046
|
}
|
|
1068
1047
|
|
|
1048
|
+
// личное меню и подсказки прошлого раунда гаснут; ядро игры зажжёт
|
|
1049
|
+
// подсказку заново при спавне
|
|
1050
|
+
modules.vote?.clearPersonal();
|
|
1051
|
+
|
|
1069
1052
|
clientCore?.reset();
|
|
1070
1053
|
soundManager.reset();
|
|
1071
1054
|
};
|
|
@@ -1672,10 +1655,8 @@ function handleMessage(data) {
|
|
|
1672
1655
|
|
|
1673
1656
|
// терминальное закрытие сессии (супервизор: возвращаться некуда — кик,
|
|
1674
1657
|
// политика, уход хоста, окно переподключения истекло). Останавливаем
|
|
1675
|
-
// рендер, показываем заглушку и возвращаемся в лобби
|
|
1676
|
-
|
|
1677
|
-
// транспорты эмитят close без него
|
|
1678
|
-
function handleDisconnect(closeCode) {
|
|
1658
|
+
// рендер, показываем заглушку и возвращаемся в лобби перезагрузкой
|
|
1659
|
+
function handleDisconnect() {
|
|
1679
1660
|
showSessionOverlay(null);
|
|
1680
1661
|
|
|
1681
1662
|
// app.stop() здесь не зовём: при sharedTicker это Ticker.shared.stop()
|
|
@@ -1729,31 +1710,18 @@ function handleDisconnect(closeCode) {
|
|
|
1729
1710
|
inlineHost?.destroy();
|
|
1730
1711
|
inlineHost = null;
|
|
1731
1712
|
|
|
1732
|
-
//
|
|
1733
|
-
//
|
|
1734
|
-
// не пишется вообще ничего, и побеждает серверный текст
|
|
1735
|
-
const policyInform = POLICY_CLOSE_INFORMS[closeCode];
|
|
1736
|
-
|
|
1737
|
-
// терминальную причину закрытия (кик, полная комната) не затираем
|
|
1713
|
+
// терминальную причину закрытия (кик, полная комната — TECH_INFORM перед
|
|
1714
|
+
// закрытием) не затираем
|
|
1738
1715
|
if (!terminalInformShown) {
|
|
1739
1716
|
socketMethods[PS_TECH_INFORM_DATA](
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
: 'The match is over — connection to the host is closed.'),
|
|
1717
|
+
isLobbyMode
|
|
1718
|
+
? 'Host left — the room is closed. Finding another room…'
|
|
1719
|
+
: 'The match is over — connection to the host is closed.',
|
|
1744
1720
|
);
|
|
1745
1721
|
}
|
|
1746
1722
|
|
|
1747
|
-
// в solo перезагружаться некуда: лобби нет, а матч поднимается с
|
|
1748
|
-
// В dedicated сервер жив — переподключение перезагрузкой уместно, но не
|
|
1749
|
-
// тогда, когда он сам нас и отбил по политике (network/policyClose.js)
|
|
1750
|
-
if (bootMode === 'solo' || !shouldReloadAfterClose(closeCode)) {
|
|
1751
|
-
return;
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1723
|
+
// в solo перезагружаться некуда: лобби нет, а матч поднимается с нуля
|
|
1754
1724
|
if (!isLobbyMode) {
|
|
1755
|
-
setTimeout(() => reloadPage(), 3000);
|
|
1756
|
-
|
|
1757
1725
|
return;
|
|
1758
1726
|
}
|
|
1759
1727
|
|
|
@@ -1763,7 +1731,7 @@ function handleDisconnect(closeCode) {
|
|
|
1763
1731
|
// — на главную без перезагрузки (причина остаётся в #tech-informer, клик
|
|
1764
1732
|
// по нему открывает лобби); закрытие комнаты — в быструю игру той же игры
|
|
1765
1733
|
const exit = decideExitRoute({
|
|
1766
|
-
kicked: isKickClose(
|
|
1734
|
+
kicked: isKickClose(terminalTechKey) || roomCtx.startFailed,
|
|
1767
1735
|
gameId: activeGameManifest?.id,
|
|
1768
1736
|
});
|
|
1769
1737
|
|
|
@@ -1803,10 +1771,9 @@ function startSession(transport, reconnect = null) {
|
|
|
1803
1771
|
onResumeRejected: handleResumeRejected,
|
|
1804
1772
|
onHostLost: reportHostUnreachable,
|
|
1805
1773
|
onColdRestart: handleColdRestart,
|
|
1806
|
-
// кик (TECH_INFORM перед закрытием)
|
|
1774
|
+
// кик и полная комната (TECH_INFORM перед закрытием) не лечатся
|
|
1807
1775
|
// переподключением
|
|
1808
|
-
isTerminalClose:
|
|
1809
|
-
terminalInformShown || !shouldReloadAfterClose(closeCode),
|
|
1776
|
+
isTerminalClose: () => terminalInformShown,
|
|
1810
1777
|
reconnect,
|
|
1811
1778
|
timing: lobbyConfig.session,
|
|
1812
1779
|
});
|
|
@@ -2221,14 +2188,6 @@ async function connectSolo() {
|
|
|
2221
2188
|
transport.connect();
|
|
2222
2189
|
}
|
|
2223
2190
|
|
|
2224
|
-
// dedicated-режим: прямой WebSocket к Node-серверу игры
|
|
2225
|
-
function connectDedicated() {
|
|
2226
|
-
const transport = new WebSocketTransport(boot.wsUrl);
|
|
2227
|
-
|
|
2228
|
-
startSession(transport);
|
|
2229
|
-
transport.connect();
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
2191
|
// REST-запрос списка серверов у мастера (поиск игнорирует пагинацию)
|
|
2233
2192
|
async function fetchServers({ offset, limit, search }) {
|
|
2234
2193
|
const params = new URLSearchParams();
|
|
@@ -2317,7 +2276,7 @@ let roomFormFields = new Map();
|
|
|
2317
2276
|
let roomFormDescriptors = [];
|
|
2318
2277
|
|
|
2319
2278
|
// генерирует форму создания комнаты по явной схеме манифеста (roomForm,
|
|
2320
|
-
// docs
|
|
2279
|
+
// VIMP docs: plugin-api.md § "Form schema") — движок не выводит контролы
|
|
2321
2280
|
// из типа значения, схема плагина полностью описывает форму
|
|
2322
2281
|
function populateRoomForm(manifest) {
|
|
2323
2282
|
const container = document.getElementById(lobbyConfig.elems.fieldsId);
|
|
@@ -2705,7 +2664,7 @@ function initLobby() {
|
|
|
2705
2664
|
// валидация (pattern/required/min/max) — единственная граница
|
|
2706
2665
|
// room-формы: она едет клиенту как JSON манифеста, JS-валидаторы
|
|
2707
2666
|
// (как в auth-форме) туда не сериализуются
|
|
2708
|
-
// (docs
|
|
2667
|
+
// (VIMP docs: plugin-api.md § "Form schema"); авторитетный клампинг
|
|
2709
2668
|
// всё равно в applyRoomOverrides.js (вызывается из host.worker.js при
|
|
2710
2669
|
// создании комнаты). Проверяем до активации игры: неверная форма не должна
|
|
2711
2670
|
// стоить загрузки плагина. arm() и рисует ошибки, и снимает фильтр «только
|
|
@@ -2813,7 +2772,7 @@ function leaveRoomWith(message) {
|
|
|
2813
2772
|
}
|
|
2814
2773
|
|
|
2815
2774
|
// точка 2 ветвления: лобби, экран профиля и сигналинг живут только в lobby;
|
|
2816
|
-
// solo
|
|
2775
|
+
// solo идёт сразу к транспорту
|
|
2817
2776
|
if (isLobbyMode) {
|
|
2818
2777
|
authApi = createAuthApi({ serviceUrl: authClientConfig.serviceUrl });
|
|
2819
2778
|
|
|
@@ -2956,7 +2915,7 @@ if (isLobbyMode) {
|
|
|
2956
2915
|
window.addEventListener('hashchange', () => route.handleHashChange());
|
|
2957
2916
|
|
|
2958
2917
|
signaling.connect();
|
|
2959
|
-
} else
|
|
2918
|
+
} else {
|
|
2960
2919
|
try {
|
|
2961
2920
|
await connectSolo();
|
|
2962
2921
|
} catch (e) {
|
|
@@ -2965,6 +2924,4 @@ if (isLobbyMode) {
|
|
|
2965
2924
|
);
|
|
2966
2925
|
throw e;
|
|
2967
2926
|
}
|
|
2968
|
-
} else {
|
|
2969
|
-
connectDedicated();
|
|
2970
2927
|
}
|
|
@@ -52,8 +52,8 @@ export default class SessionSupervisor {
|
|
|
52
52
|
* @param {Object} opts
|
|
53
53
|
* @param {Function} opts.onMessage - входящее сообщение текущего
|
|
54
54
|
* транспорта (строка или ArrayBuffer).
|
|
55
|
-
* @param {Function} opts.onTerminal -
|
|
56
|
-
*
|
|
55
|
+
* @param {Function} opts.onTerminal - сессия закрыта окончательно
|
|
56
|
+
* (handleDisconnect).
|
|
57
57
|
* @param {Function} [opts.onVoice] - (data) голосовой пакет текущего
|
|
58
58
|
* транспорта в состоянии inGame (бинарный, без разбора).
|
|
59
59
|
* @param {Function} [opts.onStateChange] - (state, prev).
|
|
@@ -63,10 +63,10 @@ export default class SessionSupervisor {
|
|
|
63
63
|
* матче (до переподключения): свидетельство мастеру (host_unreachable).
|
|
64
64
|
* @param {Function} [opts.onColdRestart] - новый хост поднял матч заново
|
|
65
65
|
* (host_changed cold): возобновлять нечего, только чистый вход.
|
|
66
|
-
* @param {Function} [opts.isTerminalClose] - (
|
|
67
|
-
*
|
|
66
|
+
* @param {Function} [opts.isTerminalClose] - () закрытие после
|
|
67
|
+
* терминальной причины (кик, полная комната): возвращаться некуда.
|
|
68
68
|
* @param {Object|null} [opts.reconnect] - null — переподключения нет
|
|
69
|
-
* (solo,
|
|
69
|
+
* (solo, вкладка-хост на loopback). Иначе:
|
|
70
70
|
* { createTransport() → новый транспорт с resume (уже connect'нутый),
|
|
71
71
|
* getToken() → identity-токен для RESUME_REQUEST }.
|
|
72
72
|
* @param {Object} [opts.timing] - { reconnectWindowMs, reconnectBaseDelayMs,
|
|
@@ -493,7 +493,7 @@ export default class SessionSupervisor {
|
|
|
493
493
|
const handlers = {
|
|
494
494
|
message: data => this._handleMessage(transport, data),
|
|
495
495
|
voice: data => this._handleVoice(transport, data),
|
|
496
|
-
close:
|
|
496
|
+
close: () => this._handleClose(transport),
|
|
497
497
|
open: () => this._handleOpen(transport),
|
|
498
498
|
};
|
|
499
499
|
|
|
@@ -573,7 +573,7 @@ export default class SessionSupervisor {
|
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
575
|
|
|
576
|
-
_handleClose(transport
|
|
576
|
+
_handleClose(transport) {
|
|
577
577
|
if (transport !== this._transport) {
|
|
578
578
|
return;
|
|
579
579
|
}
|
|
@@ -590,7 +590,7 @@ export default class SessionSupervisor {
|
|
|
590
590
|
// транспорт, выданный resumeWith без фабрики попыток: повторять нечем
|
|
591
591
|
if (this._reconnect === null) {
|
|
592
592
|
if (!this._failRoleResume('closed')) {
|
|
593
|
-
this._terminate(
|
|
593
|
+
this._terminate();
|
|
594
594
|
}
|
|
595
595
|
|
|
596
596
|
return;
|
|
@@ -600,23 +600,23 @@ export default class SessionSupervisor {
|
|
|
600
600
|
return;
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
-
if (this._canResume(
|
|
603
|
+
if (this._canResume()) {
|
|
604
604
|
this._startReconnect();
|
|
605
605
|
return;
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
-
this._terminate(
|
|
608
|
+
this._terminate();
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
// нетерминальным бывает только обрыв посреди матча, у которого есть место
|
|
612
612
|
// для возврата и кому возвращаться
|
|
613
|
-
_canResume(
|
|
613
|
+
_canResume() {
|
|
614
614
|
return (
|
|
615
615
|
this._reconnect !== null &&
|
|
616
616
|
!this._userLeft &&
|
|
617
617
|
this._state === S.inGame &&
|
|
618
618
|
this._session !== null &&
|
|
619
|
-
!this._isTerminalClose(
|
|
619
|
+
!this._isTerminalClose()
|
|
620
620
|
);
|
|
621
621
|
}
|
|
622
622
|
|
|
@@ -728,7 +728,7 @@ export default class SessionSupervisor {
|
|
|
728
728
|
|
|
729
729
|
// ***** завершение ***** //
|
|
730
730
|
|
|
731
|
-
_terminate(
|
|
731
|
+
_terminate() {
|
|
732
732
|
if (this._state === S.closed) {
|
|
733
733
|
return;
|
|
734
734
|
}
|
|
@@ -738,7 +738,7 @@ export default class SessionSupervisor {
|
|
|
738
738
|
this._clearMigrationTimer();
|
|
739
739
|
this._onRoleResumeFailed = null;
|
|
740
740
|
this._setState(S.closed);
|
|
741
|
-
this._onTerminal(
|
|
741
|
+
this._onTerminal();
|
|
742
742
|
}
|
|
743
743
|
|
|
744
744
|
_setState(state) {
|
|
@@ -493,8 +493,7 @@ export default class WebRtcManager {
|
|
|
493
493
|
channel.onmessage = event => this.publisher.emit('voice', event.data);
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
-
// кода закрытия у data channel нет: 'close' эмитится без
|
|
497
|
-
// и раньше (код приходит только от WebSocket-транспорта dedicated)
|
|
496
|
+
// кода закрытия у data channel нет: 'close' эмитится без аргумента
|
|
498
497
|
_emitClose() {
|
|
499
498
|
if (this._closed) {
|
|
500
499
|
return;
|
|
@@ -1,84 +1,17 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
// знание разведено на два экспорта (review-4.md, R4-2):
|
|
10
|
-
//
|
|
11
|
-
// - перезагружаться ли. Ни на одном из этих кодов перезагрузка не помогает:
|
|
12
|
-
// лимит частоты подключений съел бы ей же очередное соединение, таймаут
|
|
13
|
-
// хендшейка запустился бы заново, origin страницы не меняется вовсе, а
|
|
14
|
-
// слот в полной комнате перезагрузка не освобождает. Брошенная вкладка
|
|
15
|
-
// перезагружалась бы вечно, а ждущий слот игрок за 30 витков сам упёрся бы
|
|
16
|
-
// в лимит подключений и получил бы чужую причину отказа;
|
|
17
|
-
// - какой текст показать. Тексты запасные: `handleDisconnect` берёт их
|
|
18
|
-
// только тогда, когда сервер причину не прислал сам (`terminalInformShown`
|
|
19
|
-
// — `client/main.js`). Полная комната, например, приезжает с причиной от
|
|
20
|
-
// порт-машины (TECH_INFORM перед `close`), и она всегда побеждает —
|
|
21
|
-
// запись 4006 ниже отработает, только если тот кадр не доехал. Тексты
|
|
22
|
-
// здесь, а не в `techInformList`: список переопределяет игра, и движковый
|
|
23
|
-
// индекс дал бы у неё «Unknown error».
|
|
24
|
-
|
|
25
|
-
import closeCodes from '../../config/closeCodes.js';
|
|
26
|
-
|
|
27
|
-
const {
|
|
28
|
-
invalidOrigin,
|
|
29
|
-
roomFull,
|
|
30
|
-
handshakeTimeout,
|
|
31
|
-
tooManyConnections,
|
|
32
|
-
kickIdle,
|
|
33
|
-
kickForMaxLatency,
|
|
34
|
-
kickForMissedPings,
|
|
35
|
-
} = closeCodes;
|
|
36
|
-
|
|
37
|
-
const NO_RELOAD_CLOSE_CODES = new Set([
|
|
38
|
-
invalidOrigin,
|
|
39
|
-
roomFull,
|
|
40
|
-
handshakeTimeout,
|
|
41
|
-
tooManyConnections,
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
export const POLICY_CLOSE_INFORMS = {
|
|
45
|
-
[invalidOrigin]:
|
|
46
|
-
'This page is not allowed by the game server. Check the address you opened.',
|
|
47
|
-
[roomFull]: 'The room is full. Try again later.',
|
|
48
|
-
[handshakeTimeout]:
|
|
49
|
-
'Idle connection closed before the match started. Reload the page when you are ready to play.',
|
|
50
|
-
[tooManyConnections]:
|
|
51
|
-
'Too many connections from your address. Wait a minute and reload the page.',
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @param {number} [closeCode] - Код закрытия транспорта. Его отдаёт только
|
|
56
|
-
* WebSocket-транспорт (dedicated); в P2P и solo кода нет вовсе.
|
|
57
|
-
* @returns {boolean} Уместна ли перезагрузка страницы после разрыва.
|
|
58
|
-
*/
|
|
59
|
-
export function shouldReloadAfterClose(closeCode) {
|
|
60
|
-
return !NO_RELOAD_CLOSE_CODES.has(closeCode);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Кики хоста (host/HostGame.js). В P2P кода закрытия нет вовсе — причина
|
|
64
|
-
// приезжает только TECH_INFORM-ключом перед закрытием (индексы
|
|
65
|
-
// TECH_CODES из host/meta/SocketManager.js = индексы techInformList);
|
|
66
|
-
// в dedicated есть и код. Лобби-режим уводит кикнутого на главную без
|
|
67
|
-
// перезагрузки (client/lib/roomLink.js decideExitRoute): быстрая игра по
|
|
68
|
-
// hash вернула бы его в ту же комнату
|
|
69
|
-
const KICK_CLOSE_CODES = new Set([
|
|
70
|
-
kickIdle,
|
|
71
|
-
kickForMaxLatency,
|
|
72
|
-
kickForMissedPings,
|
|
73
|
-
]);
|
|
74
|
-
|
|
1
|
+
// Кики хоста (host/HostGame.js). Кода закрытия у транспортов клиента нет —
|
|
2
|
+
// причина приезжает только TECH_INFORM-ключом перед закрытием (индексы
|
|
3
|
+
// TECH_CODES из host/meta/SocketManager.js = индексы techInformList).
|
|
4
|
+
// Лобби-режим уводит кикнутого на главную без перезагрузки
|
|
5
|
+
// (client/lib/roomLink.js decideExitRoute): быстрая игра по hash вернула бы
|
|
6
|
+
// его в ту же комнату.
|
|
7
|
+
|
|
8
|
+
// kickIdle, kickForMaxLatency, kickForMissedPings
|
|
75
9
|
export const KICK_TECH_KEYS = new Set([3, 4, 5]);
|
|
76
10
|
|
|
77
11
|
/**
|
|
78
|
-
* @param {number} [closeCode] - Код закрытия транспорта (только WebSocket).
|
|
79
12
|
* @param {number|null} [techKey] - Последний терминальный TECH_INFORM-ключ.
|
|
80
13
|
* @returns {boolean} Закрыто ли соединение киком хоста.
|
|
81
14
|
*/
|
|
82
|
-
export function isKickClose(
|
|
83
|
-
return
|
|
15
|
+
export function isKickClose(techKey) {
|
|
16
|
+
return KICK_TECH_KEYS.has(techKey);
|
|
84
17
|
}
|