vimp-engine 0.35.6 → 0.37.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/bin/vimp-sim.js +19 -1
- package/core/Cargo.toml +1 -1
- package/package.json +1 -1
- package/src/client/components/controller/Controls.js +5 -0
- package/src/client/components/controller/Lobby.js +4 -4
- package/src/client/components/controller/RoomMenu.js +43 -0
- package/src/client/components/controller/Vote.js +18 -2
- package/src/client/components/model/Chat.js +19 -2
- package/src/client/components/model/ClientReports.js +6 -2
- package/src/client/components/model/Controls.js +27 -0
- package/src/client/components/model/Games.js +5 -1
- package/src/client/components/model/Lobby.js +32 -24
- package/src/client/components/model/LobbyAuth.js +20 -4
- package/src/client/components/model/RoomMenu.js +121 -0
- package/src/client/components/model/Stat.js +2 -1
- package/src/client/components/model/Vote.js +122 -4
- package/src/client/components/view/Auth.js +20 -10
- package/src/client/components/view/ClientReports.js +16 -5
- package/src/client/components/view/Games.js +39 -14
- package/src/client/components/view/Lobby.js +90 -43
- package/src/client/components/view/LobbyAuth.js +13 -7
- package/src/client/components/view/Panel.js +1 -2
- package/src/client/components/view/RoomMenu.js +69 -0
- package/src/client/debug.js +24 -1
- package/src/client/lib/FpsMeter.js +46 -0
- package/src/client/lib/JoinRetry.js +73 -0
- package/src/client/lib/diagnostics.js +28 -5
- package/src/client/lib/hostCaps.js +82 -0
- package/src/client/lib/hostUnloadGuard.js +108 -0
- package/src/client/lib/hostVoteCommand.js +92 -0
- package/src/client/lib/pageReload.js +29 -0
- package/src/client/lib/roomLink.js +257 -0
- package/src/client/lib/signalingErrors.js +74 -0
- package/src/client/main.js +676 -451
- package/src/client/network/HiddenHostHealthLog.js +76 -0
- package/src/client/network/HostConnectionManager.js +62 -8
- package/src/client/network/HostController.js +376 -23
- package/src/client/network/HostHealthPolicy.js +307 -0
- package/src/client/network/HostHealthReporter.js +54 -0
- package/src/client/network/HostPrewarm.js +235 -0
- package/src/client/network/LoopbackTransport.js +16 -1
- package/src/client/network/PlannedHandoff.js +366 -0
- package/src/client/network/Promotion.js +420 -0
- package/src/client/network/RoomPeersReporter.js +81 -0
- package/src/client/network/SessionSupervisor.js +730 -0
- package/src/client/network/SignalingClient.js +183 -30
- package/src/client/network/StandbyReceiver.js +287 -0
- package/src/client/network/StandbySender.js +319 -0
- package/src/client/network/TokenHandoffTimer.js +81 -0
- package/src/client/network/WebRtcManager.js +338 -11
- package/src/client/network/policyClose.js +32 -2
- package/src/client/network/standbyChunks.js +185 -0
- package/src/client/session/GuestSession.js +428 -0
- package/src/client/session/HandoffFlow.js +399 -0
- package/src/client/session/HostRole.js +708 -0
- package/src/client/session/Membership.js +179 -0
- package/src/client/session/PromotionFlow.js +325 -0
- package/src/client/session/RouteBoot.js +432 -0
- package/src/client/session/StandbyRole.js +195 -0
- package/src/client/session/hostRoomPrep.js +152 -0
- package/src/client/session/roomContext.js +32 -0
- package/src/client/style.css +83 -22
- package/src/client/views/gameShell.js +7 -0
- package/src/client/views/includes/informer.pug +4 -0
- package/src/client/views/includes/lobby.pug +0 -3
- package/src/client/views/includes/roomMenu.pug +9 -0
- package/src/client/views/index.pug +1 -0
- package/src/config/chatMessages.js +45 -0
- package/src/config/closeCodes.js +1 -1
- package/src/config/env.js +37 -0
- package/src/config/hostDefaults.js +24 -0
- package/src/config/lobby.js +154 -3
- package/src/config/master.js +104 -21
- package/src/config/wsports.js +8 -0
- package/src/devtools/RecordingSocketManager.js +3 -2
- package/src/devtools/ScenarioRunner.js +123 -8
- package/src/devtools/VirtualClient.js +20 -0
- package/src/devtools/contract/format.js +4 -1
- package/src/devtools/contract/index.js +1 -2
- package/src/devtools/contract/rules/a2-package-scripts.js +1 -3
- package/src/devtools/contract/rules/b8-system-messages.js +2 -2
- package/src/devtools/contract/rules/b9-vote-names.js +25 -1
- package/src/devtools/contract/rules/c11-stat-mode.js +4 -1
- package/src/devtools/contract/rules/c6-stat-columns.js +1 -1
- package/src/devtools/contract/rules/c7-key-sets.js +8 -4
- package/src/devtools/contract/rules/d3-snapshot-interp.js +2 -1
- package/src/devtools/contract/rules/e1-sound-pairs.js +3 -1
- package/src/devtools/contract/rules/e4-map-layers.js +13 -2
- package/src/devtools/contract/rules/e5-map-radar-walls.js +7 -1
- package/src/devtools/contract/rules/index.js +38 -5
- package/src/devtools/invariants.js +10 -4
- package/src/devtools/pluginLoader.js +1 -4
- package/src/devtools/report.js +17 -1
- package/src/host/DebugRecorder.js +6 -1
- package/src/host/GameCoreAdapter.js +45 -0
- package/src/host/HostGame.js +928 -59
- package/src/host/PortMachine.js +268 -20
- package/src/host/host.worker.js +222 -16
- package/src/host/meta/SocketManager.js +20 -0
- package/src/host/meta/core/RoundManager.js +89 -14
- package/src/host/meta/modules/Accolades.js +4 -1
- package/src/host/meta/modules/Panel.js +30 -0
- package/src/host/meta/modules/PlayerDataSync.js +255 -46
- package/src/host/meta/modules/RTTManager.js +34 -0
- package/src/host/meta/modules/TimerManager.js +215 -18
- package/src/host/meta/modules/chat/systemMessages.js +29 -3
- package/src/host/meta/player/HumanParticipant.js +10 -0
- package/src/host/meta/player/ParticipantManager.js +98 -5
- package/src/lib/AbstractTimer.js +63 -1
- package/src/lib/Publisher.js +26 -0
- package/src/lib/capabilities.js +5 -0
- package/src/lib/checkpointCodec.js +125 -0
- package/src/lib/clientIp.js +9 -2
- package/src/lib/createHostRuntime.js +95 -6
- package/src/lib/formOptions.js +3 -1
- package/src/lib/gameConfigView.js +10 -0
- package/src/lib/gameId.js +13 -0
- package/src/lib/jwt.js +3 -1
- package/src/lib/loadGamePackage.js +2 -2
- package/src/lib/resumeKey.js +38 -0
- package/src/lib/roomId.js +39 -0
- package/src/lib/roomSettings.js +59 -0
- package/src/lib/validators.js +7 -0
- package/tests/fixtures/generations/gen-api3/host/ScriptedManager.js +2 -3
- package/tests/fixtures/generations/gen-api3/host/fakeCore.js +31 -5
- package/tests/fixtures/generations/gen-api3/manifest.json +1 -3
- package/tests/fixtures/generations/gen-api4/host/ScriptedManager.js +2 -3
- package/tests/fixtures/generations/gen-api4/host/fakeCore.js +31 -5
- package/tests/fixtures/generations/gen-api4/manifest.json +1 -3
- package/tests/fixtures/miniGame/config/client.js +0 -20
- package/tests/fixtures/miniGame/host/ScriptedManager.js +2 -3
- package/tests/fixtures/miniGame/host/fakeCore.js +31 -5
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { buildSystemMessage } from '../../host/meta/modules/chat/systemMessages.js';
|
|
2
|
+
import {
|
|
3
|
+
CHANGE_HOST_VALUES,
|
|
4
|
+
CHANGE_HOST_VOTE,
|
|
5
|
+
changeHostTitle,
|
|
6
|
+
rejectionMessageKey,
|
|
7
|
+
resultMessage,
|
|
8
|
+
} from '../lib/hostVoteCommand.js';
|
|
9
|
+
import {
|
|
10
|
+
decideInvalidToken,
|
|
11
|
+
decideUnknownRoom,
|
|
12
|
+
} from '../lib/signalingErrors.js';
|
|
13
|
+
import { SESSION_STATES } from '../network/SessionSupervisor.js';
|
|
14
|
+
import WebRtcManager from '../network/WebRtcManager.js';
|
|
15
|
+
|
|
16
|
+
// Сессия гостя комнаты (лобби-режим): вход по WebRTC и переподключение к
|
|
17
|
+
// текущему хосту, смена хоста (host_migrating / host_changed), закрытие
|
|
18
|
+
// комнаты, реконнект сигналинга с возвратом членства, голосование
|
|
19
|
+
// «Change host» (этап 10) и отказы мастера на сигнальные сообщения.
|
|
20
|
+
//
|
|
21
|
+
// Без DOM: сигналинг, роли, супервизор, UI-действия и фабрики инъектирует
|
|
22
|
+
// сборщик (client/main.js).
|
|
23
|
+
|
|
24
|
+
export default class GuestSession {
|
|
25
|
+
/**
|
|
26
|
+
* @param {Object} deps
|
|
27
|
+
* @param {Object} deps.signaling - SignalingClient.
|
|
28
|
+
* @param {Object} deps.ctx - client/session/roomContext.js.
|
|
29
|
+
* @param {Object} deps.membership - client/session/Membership.js.
|
|
30
|
+
* @param {Object} deps.hostRole - client/session/HostRole.js.
|
|
31
|
+
* @param {Object} deps.standby - client/session/StandbyRole.js.
|
|
32
|
+
* @param {Function} deps.getPromotion - () → PromotionFlow.
|
|
33
|
+
* @param {Function} deps.getSupervisor - () → SessionSupervisor | null.
|
|
34
|
+
* @param {Function} deps.getToken - () → identity-токен.
|
|
35
|
+
* @param {Function} deps.logout - выход из лобби-авторизации.
|
|
36
|
+
* @param {Object} deps.config - lobby-конфиг (webrtc, reconnect).
|
|
37
|
+
* @param {Object} deps.diagnostics
|
|
38
|
+
* @param {Object} deps.ui - { ensureWebRtc() → bool, showRoomLink(roomId),
|
|
39
|
+
* startSession(transport, reconnect), closeLobby(),
|
|
40
|
+
* leaveRoomWith(text), chat(message), getVote() → модуль голосования }.
|
|
41
|
+
* @param {Object} [deps.factories] - конструкторы (тесты).
|
|
42
|
+
* @param {Object} [deps.timers] - { setTimeout }.
|
|
43
|
+
* @param {Function} [deps.now]
|
|
44
|
+
*/
|
|
45
|
+
constructor({
|
|
46
|
+
signaling,
|
|
47
|
+
ctx,
|
|
48
|
+
membership,
|
|
49
|
+
hostRole,
|
|
50
|
+
standby,
|
|
51
|
+
getPromotion,
|
|
52
|
+
getSupervisor,
|
|
53
|
+
getToken,
|
|
54
|
+
logout,
|
|
55
|
+
config,
|
|
56
|
+
diagnostics,
|
|
57
|
+
ui,
|
|
58
|
+
factories = {},
|
|
59
|
+
timers = globalThis,
|
|
60
|
+
now = () => Date.now(),
|
|
61
|
+
}) {
|
|
62
|
+
this._signaling = signaling;
|
|
63
|
+
this._ctx = ctx;
|
|
64
|
+
this._membership = membership;
|
|
65
|
+
this._hostRole = hostRole;
|
|
66
|
+
this._standby = standby;
|
|
67
|
+
this._getPromotion = getPromotion;
|
|
68
|
+
this._getSupervisor = getSupervisor;
|
|
69
|
+
this._getToken = getToken;
|
|
70
|
+
this._logout = logout;
|
|
71
|
+
this._config = config;
|
|
72
|
+
this._diagnostics = diagnostics;
|
|
73
|
+
this._ui = ui;
|
|
74
|
+
this._factories = {
|
|
75
|
+
transport: (sig, options) => new WebRtcManager(sig, options),
|
|
76
|
+
...factories,
|
|
77
|
+
};
|
|
78
|
+
this._timers = timers;
|
|
79
|
+
this._now = now;
|
|
80
|
+
|
|
81
|
+
// WebRTC-транспорты к хосту комнаты, созданные этой сессией
|
|
82
|
+
this._roomTransports = new WeakSet();
|
|
83
|
+
// открытое у гостя голосование «Change host» ({ roomId, voteId }) —
|
|
84
|
+
// ответ окна уходит мастеру с этим voteId
|
|
85
|
+
this._hostVote = null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get hostVote() {
|
|
89
|
+
return this._hostVote;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// устанавливает P2P-соединение с текущим хостом комнаты и уходит из лобби
|
|
93
|
+
connectToRoom(roomId) {
|
|
94
|
+
if (!this._ui.ensureWebRtc()) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this._ctx.roomId = roomId;
|
|
99
|
+
this._ctx.entered = true;
|
|
100
|
+
this._ui.showRoomLink(roomId);
|
|
101
|
+
|
|
102
|
+
// первая попытка — обычный вход с рукопожатием, следующие (после обрыва
|
|
103
|
+
// в матче) — переподключение с resume к текущему хосту той же комнаты
|
|
104
|
+
this._ui.startSession(this.openTransport(roomId), this.reconnect());
|
|
105
|
+
|
|
106
|
+
this._ui.closeLobby();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// попытки переподключения гостя: resume к текущему хосту комнаты
|
|
110
|
+
reconnect() {
|
|
111
|
+
return {
|
|
112
|
+
createTransport: () =>
|
|
113
|
+
this.openTransport(this._ctx.roomId, { resume: true }),
|
|
114
|
+
getToken: () => this._getToken(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// WebRTC-попытка к текущему хосту комнаты (мастер резолвит roomId)
|
|
119
|
+
openTransport(roomId, { resume = false } = {}) {
|
|
120
|
+
const transport = this._factories.transport(this._signaling, {
|
|
121
|
+
iceServers: this._signaling.iceServers,
|
|
122
|
+
memberId: this._membership.memberId,
|
|
123
|
+
resume,
|
|
124
|
+
minEpoch: this._ctx.epoch,
|
|
125
|
+
connectTimeoutMs: this._config.webrtc.connectTimeoutMs,
|
|
126
|
+
offerRetryMs: this._config.webrtc.offerRetryMs,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this._roomTransports.add(transport);
|
|
130
|
+
|
|
131
|
+
transport.publisher.on('open', () => {
|
|
132
|
+
this._ctx.epoch = transport.epoch ?? this._ctx.epoch;
|
|
133
|
+
});
|
|
134
|
+
// тип ICE-кандидата — мастеру: по нему он выбирает бету (этап 6)
|
|
135
|
+
transport.publisher.on('iceType', type =>
|
|
136
|
+
this._membership.setIceType(type),
|
|
137
|
+
);
|
|
138
|
+
// канал standby открывает хост только назначенной бете
|
|
139
|
+
transport.publisher.on('standby', channel => this._standby.attach(channel));
|
|
140
|
+
transport.connect(roomId).catch(() => transport.close());
|
|
141
|
+
|
|
142
|
+
return transport;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// оффер ушёл, каналы ещё не открыты: unknownRoom в этот момент —
|
|
146
|
+
// комнаты нет
|
|
147
|
+
offerPending() {
|
|
148
|
+
const transport = this._getSupervisor()?.transport;
|
|
149
|
+
|
|
150
|
+
return this._roomTransports.has(transport) && !transport.isOpen;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// транспорт к хосту оборвался в матче: мастер узнаёт об этом от гостя
|
|
154
|
+
// раньше, чем сам (host-migration этап 7.2) — проба хоста или его смена.
|
|
155
|
+
// Эпоха — хоста, к которому был транспорт: чужую мастер отбросит
|
|
156
|
+
reportHostUnreachable() {
|
|
157
|
+
if (
|
|
158
|
+
this._ctx.roomId &&
|
|
159
|
+
this._ctx.epoch !== null &&
|
|
160
|
+
!this._hostRole.controller
|
|
161
|
+
) {
|
|
162
|
+
this._signaling.hostUnreachable(this._ctx.roomId, this._ctx.epoch);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// сигнальный WS живёт постоянно и у хоста (офферы, heartbeat), и у гостя
|
|
167
|
+
// (членство в комнате): при разрыве переподключаемся с бэкоффом, а welcome
|
|
168
|
+
// возвращает комнату — хост reclaim_host'ом, гость повторным join_room с
|
|
169
|
+
// тем же memberId
|
|
170
|
+
bind() {
|
|
171
|
+
const publisher = this._signaling.publisher;
|
|
172
|
+
let reconnectAttempt = 0;
|
|
173
|
+
|
|
174
|
+
publisher.on('close', () => {
|
|
175
|
+
const { baseDelay, maxDelay } = this._config.reconnect;
|
|
176
|
+
const delay = Math.min(maxDelay, baseDelay * 2 ** reconnectAttempt);
|
|
177
|
+
|
|
178
|
+
reconnectAttempt += 1;
|
|
179
|
+
this._timers.setTimeout(() => this._signaling.connect(), delay);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
publisher.on('welcome', () => {
|
|
183
|
+
reconnectAttempt = 0;
|
|
184
|
+
|
|
185
|
+
if (this._hostRole.controller) {
|
|
186
|
+
this._hostRole.reRegister();
|
|
187
|
+
} else if (this._ctx.roomId && this._ctx.memberJoined) {
|
|
188
|
+
this._membership.sendJoinRoom();
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// смена хоста комнаты (host-migration этап 7.2): старый транспорт
|
|
193
|
+
// закрывается сразу, ждём host_changed. Хост этой вкладки сообщения не
|
|
194
|
+
// получает (его ждёт host_revoked)
|
|
195
|
+
publisher.on('host_migrating', msg => {
|
|
196
|
+
if (
|
|
197
|
+
!this._ownRoom(msg) ||
|
|
198
|
+
this._hostRole.controller ||
|
|
199
|
+
(this._ctx.epoch !== null && msg.epoch <= this._ctx.epoch)
|
|
200
|
+
) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const supervisor = this._getSupervisor();
|
|
205
|
+
|
|
206
|
+
// повтор той же эпохи — следующий кандидат: ждать дольше (waitMs)
|
|
207
|
+
if (supervisor?.state === SESSION_STATES.migrating) {
|
|
208
|
+
supervisor.extendMigration(msg.waitMs);
|
|
209
|
+
} else {
|
|
210
|
+
supervisor?.migrate({ waitMs: msg.waitMs });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// у комнаты новый хост (или прежний вернулся — reclaimed, та же эпоха):
|
|
215
|
+
// возобновление у него, cold — перезагрузка в комнату
|
|
216
|
+
publisher.on('host_changed', msg => {
|
|
217
|
+
if (
|
|
218
|
+
!this._ownRoom(msg) ||
|
|
219
|
+
this._hostRole.controller ||
|
|
220
|
+
(this._ctx.epoch !== null && msg.epoch < this._ctx.epoch)
|
|
221
|
+
) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ответы прежних эпох попыткам возобновления чужие
|
|
226
|
+
this._ctx.epoch = msg.epoch;
|
|
227
|
+
this._getSupervisor()?.hostChanged({ mode: msg.mode });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
publisher.on('room_closed', msg => {
|
|
231
|
+
if (!this._ownRoom(msg)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this._membership.stopJoinRetry();
|
|
236
|
+
|
|
237
|
+
// быстрее, чем ждать падения WebRTC; причина важнее общего текста
|
|
238
|
+
// handleDisconnect
|
|
239
|
+
this._ui.leaveRoomWith(
|
|
240
|
+
'The host left — the room is closed. Finding another room…',
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// мастер принял членство — повторы join_room больше не нужны
|
|
245
|
+
publisher.on('room_joined', msg => {
|
|
246
|
+
if (msg.roomId === this._ctx.roomId) {
|
|
247
|
+
this._membership.stopJoinRetry();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
publisher.on('error', msg => this.handleSignalingError(msg));
|
|
252
|
+
|
|
253
|
+
this._bindHostVote();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// отказы мастера на сигнальные сообщения
|
|
257
|
+
handleSignalingError(msg = {}) {
|
|
258
|
+
const { code, reason } = msg;
|
|
259
|
+
const hostRole = this._hostRole;
|
|
260
|
+
|
|
261
|
+
switch (code) {
|
|
262
|
+
// мастер не начал голосование «Change host» (этап 10)
|
|
263
|
+
case 'voteRejected':
|
|
264
|
+
case 'noSuccessor':
|
|
265
|
+
this._ui.chat(
|
|
266
|
+
buildSystemMessage(rejectionMessageKey({ code, reason })),
|
|
267
|
+
);
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
// запрос ушёл в комнату, которой уже нет
|
|
271
|
+
case 'unknownRoom': {
|
|
272
|
+
const action = decideUnknownRoom({
|
|
273
|
+
msg,
|
|
274
|
+
currentRoomId: this._ctx.roomId,
|
|
275
|
+
promoting: Boolean(hostRole.promotion),
|
|
276
|
+
sessionState: this._getSupervisor()?.state ?? null,
|
|
277
|
+
offerPending: this.offerPending(),
|
|
278
|
+
memberJoined: this._ctx.memberJoined,
|
|
279
|
+
isHost: Boolean(hostRole.controller),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (action === 'abandonPromotion') {
|
|
283
|
+
this._getPromotion().abandon({ code, report: true });
|
|
284
|
+
} else if (action === 'retryJoin') {
|
|
285
|
+
this._membership.scheduleJoinRetry();
|
|
286
|
+
} else if (action === 'leave') {
|
|
287
|
+
this._ui.leaveRoomWith(
|
|
288
|
+
'Room no longer exists. Finding another room…',
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// memberId вкладки (randomUUID) занят другим пользователем — честная
|
|
295
|
+
// вкладка сюда не попадает; только в журнал
|
|
296
|
+
case 'memberTaken':
|
|
297
|
+
console.warn('join_room rejected: memberTaken', msg.roomId);
|
|
298
|
+
break;
|
|
299
|
+
|
|
300
|
+
// токен истёк или отозван: решение — по запросу, на который отказ
|
|
301
|
+
case 'invalidToken': {
|
|
302
|
+
const action = decideInvalidToken({
|
|
303
|
+
msg,
|
|
304
|
+
promoting: Boolean(hostRole.promotion),
|
|
305
|
+
inRoom: Boolean(this._ctx.roomId),
|
|
306
|
+
sessionState: this._getSupervisor()?.state ?? null,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
if (action === 'abandonPromotion') {
|
|
310
|
+
this._getPromotion().abandon({ code, report: true });
|
|
311
|
+
} else if (action === 'keepPlaying') {
|
|
312
|
+
// P2P-матчу токен не нужен; без нового текста в UI (системные
|
|
313
|
+
// сообщения — только кодами, тексты в играх)
|
|
314
|
+
this._diagnostics.warn(
|
|
315
|
+
'engine.session.tokenExpired',
|
|
316
|
+
{ re: msg.re },
|
|
317
|
+
{ source: 'client' },
|
|
318
|
+
);
|
|
319
|
+
this._membership.stopJoinRetry();
|
|
320
|
+
} else {
|
|
321
|
+
this._logout();
|
|
322
|
+
|
|
323
|
+
if (action === 'logoutAndLeave') {
|
|
324
|
+
this._ui.leaveRoomWith(
|
|
325
|
+
'Your session has expired — please sign in again.',
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// с этого адреса уже хостится другая комната
|
|
333
|
+
case 'hostLimit':
|
|
334
|
+
if (hostRole.controller) {
|
|
335
|
+
// быстрая игра подняла бы комнату снова и упёрлась бы в тот же
|
|
336
|
+
// лимит — уход на главную, как при несостоявшемся старте
|
|
337
|
+
this._ctx.startFailed = true;
|
|
338
|
+
this._ui.leaveRoomWith(
|
|
339
|
+
'Another room is already hosted from your network — this room is ' +
|
|
340
|
+
'closed. Click to return to the lobby.',
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
|
|
345
|
+
// прежнюю комнату вернуть нельзя (id занят или секрет не принят —
|
|
346
|
+
// dev-мастер без VIMP_ROOM_SECRET_KEY после рестарта): новая
|
|
347
|
+
// регистрация
|
|
348
|
+
case 'roomTaken':
|
|
349
|
+
case 'invalidRoomSecret':
|
|
350
|
+
if (hostRole.controller) {
|
|
351
|
+
hostRole.reRegister({ fresh: true });
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
|
|
355
|
+
// хоста комнаты уже сменили (host-migration этап 7.5): преемник
|
|
356
|
+
// опоздал — роль снимается; бывший хост вернулся после смены — он
|
|
357
|
+
// гость новой эпохи
|
|
358
|
+
case 'staleEpoch':
|
|
359
|
+
if (hostRole.promotion) {
|
|
360
|
+
this._getPromotion().abandon({ code, report: true });
|
|
361
|
+
} else if (hostRole.controller && hostRole.room) {
|
|
362
|
+
this._getPromotion().demote(hostRole.room.epoch + 1);
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
|
|
366
|
+
// promotionToken не принят (промоушен отменён, кандидат сменился)
|
|
367
|
+
case 'invalidPromotion':
|
|
368
|
+
if (hostRole.promotion) {
|
|
369
|
+
this._getPromotion().abandon({ code, report: true });
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
_ownRoom(msg) {
|
|
376
|
+
return Boolean(this._ctx.roomId) && msg.roomId === this._ctx.roomId;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// голосование «Change host» (этап 10): окно у гостей, кроме инициатора;
|
|
380
|
+
// хосту мастер его не шлёт
|
|
381
|
+
_bindHostVote() {
|
|
382
|
+
const publisher = this._signaling.publisher;
|
|
383
|
+
|
|
384
|
+
publisher.on('host_vote', msg => {
|
|
385
|
+
if (!this._ownRoom(msg) || this._hostRole.controller) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
this._hostVote = { roomId: msg.roomId, voteId: msg.voteId };
|
|
390
|
+
this._ui.getVote()?.openEngineVote({
|
|
391
|
+
name: CHANGE_HOST_VOTE,
|
|
392
|
+
title: changeHostTitle(msg.initiatorNick),
|
|
393
|
+
values: CHANGE_HOST_VALUES,
|
|
394
|
+
// часы мастера и вкладки расходятся — окно считается от прихода
|
|
395
|
+
deadline: this._now() + msg.durationMs,
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// своё голосование началось — как у голосований хоста
|
|
400
|
+
publisher.on('host_vote_started', msg => {
|
|
401
|
+
if (this._ownRoom(msg)) {
|
|
402
|
+
this._ui.chat(buildSystemMessage('VOTE_STARTED'));
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// свой ответ в окне «Change host?» засчитан — как у голосований хоста
|
|
407
|
+
publisher.on('host_vote_accepted', msg => {
|
|
408
|
+
if (this._ownRoom(msg)) {
|
|
409
|
+
this._ui.chat(buildSystemMessage('VOTE_ACCEPTED'));
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
publisher.on('host_vote_result', msg => {
|
|
414
|
+
if (!this._ownRoom(msg)) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (this._hostVote?.voteId === msg.voteId) {
|
|
419
|
+
this._hostVote = null;
|
|
420
|
+
this._ui.getVote()?.closeEngineVote(CHANGE_HOST_VOTE);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const { key, params } = resultMessage(msg);
|
|
424
|
+
|
|
425
|
+
this._ui.chat(buildSystemMessage(key, params));
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|