vimp-engine 0.37.0 → 0.38.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.
Files changed (44) hide show
  1. package/package.json +1 -1
  2. package/src/client/boot.js +1 -1
  3. package/src/client/components/controller/LobbyAuth.js +97 -15
  4. package/src/client/components/controller/Profile.js +85 -0
  5. package/src/client/components/model/Games.js +1 -1
  6. package/src/client/components/model/LobbyAuth.js +344 -115
  7. package/src/client/components/model/Profile.js +142 -0
  8. package/src/client/components/view/LobbyAuth.js +150 -62
  9. package/src/client/components/view/Profile.js +373 -0
  10. package/src/client/lib/authApi.js +97 -0
  11. package/src/client/lib/pkce.js +23 -0
  12. package/src/client/lib/roomLink.js +1 -1
  13. package/src/client/lib/signalingErrors.js +3 -3
  14. package/src/client/lib/turnstile.js +130 -0
  15. package/src/client/main.js +89 -24
  16. package/src/client/network/TokenRenewer.js +90 -0
  17. package/src/client/session/GuestSession.js +79 -19
  18. package/src/client/style.css +119 -16
  19. package/src/client/views/includes/lobby.pug +11 -6
  20. package/src/client/views/includes/lobbyAuth.pug +34 -16
  21. package/src/client/views/includes/profile.pug +73 -0
  22. package/src/client/views/index.pug +1 -0
  23. package/src/config/authClient.js +54 -30
  24. package/src/config/chatMessages.js +1 -2
  25. package/src/config/env.js +2 -2
  26. package/src/config/lobby.js +50 -1
  27. package/src/config/master.js +15 -8
  28. package/src/config/wsports.js +4 -1
  29. package/src/devtools/builtinScenario.js +1 -1
  30. package/src/devtools/contract/rules/a2-package-scripts.js +1 -1
  31. package/src/devtools/contract/rules/a4-vite-config.js +1 -1
  32. package/src/devtools/contract/rules/b8-system-messages.js +1 -1
  33. package/src/devtools/invariants.js +1 -1
  34. package/src/devtools/pluginLoader.js +1 -1
  35. package/src/host/GameCoreAdapter.js +0 -3
  36. package/src/host/HostGame.js +28 -0
  37. package/src/host/PortMachine.js +50 -1
  38. package/src/host/meta/SocketManager.js +0 -13
  39. package/src/host/meta/core/RoundManager.js +0 -19
  40. package/src/host/meta/modules/PlayerDataSync.js +29 -3
  41. package/src/host/meta/modules/chat/systemMessages.js +1 -4
  42. package/src/lib/loadGamePackage.js +1 -1
  43. package/src/lib/validators.js +8 -0
  44. package/src/standalone/index.js +1 -1
@@ -21,6 +21,9 @@ import WebRtcManager from '../network/WebRtcManager.js';
21
21
  // Без DOM: сигналинг, роли, супервизор, UI-действия и фабрики инъектирует
22
22
  // сборщик (client/main.js).
23
23
 
24
+ // минимум между продлениями входа по одному виду запроса
25
+ const RENEW_RETRY_MIN_MS = 60000;
26
+
24
27
  export default class GuestSession {
25
28
  /**
26
29
  * @param {Object} deps
@@ -32,7 +35,8 @@ export default class GuestSession {
32
35
  * @param {Function} deps.getPromotion - () → PromotionFlow.
33
36
  * @param {Function} deps.getSupervisor - () → SessionSupervisor | null.
34
37
  * @param {Function} deps.getToken - () → identity-токен.
35
- * @param {Function} deps.logout - выход из лобби-авторизации.
38
+ * @param {Function} deps.refreshIdentity - () → Promise<'ok'|'retry'|'lost'>,
39
+ * продление identity-токена по сессии устройства.
36
40
  * @param {Object} deps.config - lobby-конфиг (webrtc, reconnect).
37
41
  * @param {Object} deps.diagnostics
38
42
  * @param {Object} deps.ui - { ensureWebRtc() → bool, showRoomLink(roomId),
@@ -51,7 +55,7 @@ export default class GuestSession {
51
55
  getPromotion,
52
56
  getSupervisor,
53
57
  getToken,
54
- logout,
58
+ refreshIdentity,
55
59
  config,
56
60
  diagnostics,
57
61
  ui,
@@ -67,7 +71,7 @@ export default class GuestSession {
67
71
  this._getPromotion = getPromotion;
68
72
  this._getSupervisor = getSupervisor;
69
73
  this._getToken = getToken;
70
- this._logout = logout;
74
+ this._refreshIdentity = refreshIdentity;
71
75
  this._config = config;
72
76
  this._diagnostics = diagnostics;
73
77
  this._ui = ui;
@@ -83,6 +87,8 @@ export default class GuestSession {
83
87
  // открытое у гостя голосование «Change host» ({ roomId, voteId }) —
84
88
  // ответ окна уходит мастеру с этим voteId
85
89
  this._hostVote = null;
90
+ // вид запроса (msg.re) → время последнего продления (защита от цикла)
91
+ this._renewAttempts = new Map();
86
92
  }
87
93
 
88
94
  get hostVote() {
@@ -172,8 +178,22 @@ export default class GuestSession {
172
178
  let reconnectAttempt = 0;
173
179
 
174
180
  publisher.on('close', () => {
175
- const { baseDelay, maxDelay } = this._config.reconnect;
176
- const delay = Math.min(maxDelay, baseDelay * 2 ** reconnectAttempt);
181
+ const {
182
+ baseDelay,
183
+ maxDelay,
184
+ hostFirstDelay = 0,
185
+ } = this._config.reconnect;
186
+ // хост: мастер держит комнату hostDisconnectGraceMs — первая попытка
187
+ // сразу, дальше тот же бэкофф, сдвинутый на одну ступень
188
+ const isHost = Boolean(this._hostRole.controller);
189
+ const delay =
190
+ isHost && reconnectAttempt === 0
191
+ ? hostFirstDelay
192
+ : Math.min(
193
+ maxDelay,
194
+ baseDelay *
195
+ 2 ** (isHost ? reconnectAttempt - 1 : reconnectAttempt),
196
+ );
177
197
 
178
198
  reconnectAttempt += 1;
179
199
  this._timers.setTimeout(() => this._signaling.connect(), delay);
@@ -309,22 +329,15 @@ export default class GuestSession {
309
329
  if (action === 'abandonPromotion') {
310
330
  this._getPromotion().abandon({ code, report: true });
311
331
  } else if (action === 'keepPlaying') {
312
- // P2P-матчу токен не нужен; без нового текста в UI (системные
313
- // сообщения — только кодами, тексты в играх)
314
- this._diagnostics.warn(
315
- 'engine.session.tokenExpired',
316
- { re: msg.re },
317
- { source: 'client' },
318
- );
332
+ // P2P-матчу токен не нужен, без нового текста в UI (системные
333
+ // сообщения — только кодами, тексты в играх). Членство на мастере
334
+ // вернёт повторный join_room после продления; о неудаче продления
335
+ // пишет _renewAndRetry
319
336
  this._membership.stopJoinRetry();
320
- } else {
321
- this._logout();
337
+ }
322
338
 
323
- if (action === 'logoutAndLeave') {
324
- this._ui.leaveRoomWith(
325
- 'Your session has expired — please sign in again.',
326
- );
327
- }
339
+ if (action !== 'abandonPromotion') {
340
+ this._renewAndRetry(msg);
328
341
  }
329
342
  break;
330
343
  }
@@ -372,6 +385,53 @@ export default class GuestSession {
372
385
  }
373
386
  }
374
387
 
388
+ // продление входа и повтор отклонённого запроса. Не чаще раза в
389
+ // RENEW_RETRY_MIN_MS на вид запроса: мастер, отвергающий и свежий токен,
390
+ // не должен завести цикл
391
+ async _renewAndRetry(msg) {
392
+ const now = this._now();
393
+ const last = this._renewAttempts.get(msg.re);
394
+
395
+ if (last !== undefined && now - last < RENEW_RETRY_MIN_MS) {
396
+ this._warnTokenExpired(msg);
397
+ return;
398
+ }
399
+
400
+ this._renewAttempts.set(msg.re, now);
401
+
402
+ const result = await this._refreshIdentity();
403
+
404
+ if (result === 'ok') {
405
+ if (msg.re === 'join_room') {
406
+ this._membership.sendJoinRoom();
407
+ } else if (msg.re === 'register_host' || msg.re === 'reclaim_host') {
408
+ this._hostRole.reRegister();
409
+ }
410
+ return;
411
+ }
412
+
413
+ // хост, не продливший вход, не удержит комнату
414
+ if (
415
+ this._ctx.roomId &&
416
+ (msg.re === 'register_host' || msg.re === 'reclaim_host')
417
+ ) {
418
+ this._ui.leaveRoomWith(
419
+ 'Your session could not be renewed — please reload the page.',
420
+ );
421
+ return;
422
+ }
423
+
424
+ this._warnTokenExpired(msg);
425
+ }
426
+
427
+ _warnTokenExpired(msg) {
428
+ this._diagnostics.warn(
429
+ 'engine.session.tokenExpired',
430
+ { re: msg.re },
431
+ { source: 'client' },
432
+ );
433
+ }
434
+
375
435
  _ownRoom(msg) {
376
436
  return Boolean(this._ctx.roomId) && msg.roomId === this._ctx.roomId;
377
437
  }
@@ -507,12 +507,11 @@ body.hide-cursor {
507
507
  padding: 5px 0 10px;
508
508
  }
509
509
 
510
- /* кнопки: провайдеры логина, Choose nick, Start, Load more — нативный вид
511
- input[type=button], без декоративных стилей, только размер. Create server и
512
- Sign out сюда не входят: они лежат во флекс-контейнерах с gap и
513
- центрируются через align-self (секция lobby-auth ниже) */
510
+ /* кнопки: провайдеры Restore, Start, Load more — нативный вид
511
+ input[type=button], без декоративных стилей, только размер. Create server
512
+ сюда не входит: он лежит во флекс-контейнере с gap и центрируется через
513
+ align-self (секция lobby-auth ниже) */
514
514
  .lobby-auth-provider,
515
- #lobby-auth-nick-submit,
516
515
  #lobby-more,
517
516
  #auth-enter {
518
517
  margin: 15px auto;
@@ -1071,7 +1070,7 @@ body.hide-cursor {
1071
1070
  display: none;
1072
1071
  }
1073
1072
 
1074
- /*lobby-auth (Этап B2: логин перед лобби)*/
1073
+ /*lobby-auth (экран профиля перед лобби)*/
1075
1074
 
1076
1075
  #lobby-auth {
1077
1076
  background-color: #222;
@@ -1083,33 +1082,137 @@ body.hide-cursor {
1083
1082
  z-index: 8;
1084
1083
  }
1085
1084
 
1086
- #lobby-auth-login,
1087
- #lobby-auth-nick {
1085
+ #lobby-auth-status,
1086
+ #lobby-auth-welcome {
1088
1087
  text-align: center;
1089
1088
  }
1090
1089
 
1091
- #lobby-auth-login-buttons {
1090
+ #lobby-auth-providers {
1092
1091
  display: flex;
1093
1092
  flex-direction: column;
1094
1093
  gap: 8px;
1095
1094
  }
1096
1095
 
1097
1096
  .lobby-auth-provider,
1098
- #lobby-host,
1099
- #lobby-user-logout {
1097
+ #lobby-host {
1100
1098
  align-self: center;
1101
1099
  }
1102
1100
 
1103
1101
  #lobby-auth-nick-input {
1104
1102
  display: block;
1105
1103
  width: 100%;
1106
- margin-bottom: 10px;
1104
+ margin: 10px 0;
1105
+ box-sizing: border-box;
1106
+ }
1107
+
1108
+ #lobby-auth-nick-error,
1109
+ #lobby-auth-restore-error {
1110
+ text-align: left;
1111
+ }
1112
+
1113
+ #lobby-auth-register,
1114
+ #lobby-auth-restore,
1115
+ #lobby-auth-status-retry,
1116
+ #lobby-auth-turnstile-retry {
1117
+ margin: 8px auto;
1118
+ }
1119
+
1120
+ #lobby-auth-legal,
1121
+ #lobby-auth-lifetime {
1122
+ color: #aaa;
1123
+ font-size: 11px;
1124
+ line-height: 14px;
1125
+ margin: 8px 0;
1126
+ }
1127
+
1128
+ #lobby-auth-legal a {
1129
+ color: #ccc;
1130
+ }
1131
+
1132
+ /* нейтральная плашка уведомления (профиль не найден, выход, удаление) */
1133
+ .form-notice {
1134
+ background-color: #3d3d3d;
1135
+ border: 1px solid #555;
1136
+ padding: 6px 8px;
1137
+ margin: 8px 0;
1138
+ color: #ddd;
1139
+ }
1140
+
1141
+ .form-notice:empty {
1142
+ display: none;
1143
+ }
1144
+
1145
+ .form-hint {
1146
+ color: #aaa;
1147
+ font-size: 12px;
1148
+ line-height: 16px;
1149
+ margin: 6px 0;
1150
+ }
1151
+
1152
+ /*profile*/
1153
+
1154
+ /* панель профиля (profile.pug): открывается вместо #lobby кнопкой бейджа,
1155
+ каркас — как у панели реестра игр (.card, .lobby-column, .games-head) */
1156
+ #profile-panel {
1157
+ background-color: #222;
1158
+ position: absolute;
1159
+ width: 100%;
1160
+ min-height: 100%;
1161
+ top: 0;
1162
+ left: 0;
1163
+ z-index: 8;
1164
+ display: flex;
1165
+ align-items: flex-start;
1166
+ justify-content: center;
1167
+ box-sizing: border-box;
1168
+ }
1169
+
1170
+ #profile-panel .card {
1171
+ width: 700px;
1172
+ max-width: 100%;
1173
+ padding: 10px;
1174
+ text-align: left;
1175
+ box-sizing: border-box;
1176
+ }
1177
+
1178
+ #profile-link-benefits {
1179
+ margin: 6px 0;
1180
+ padding-left: 20px;
1181
+ }
1182
+
1183
+ .profile-provider {
1184
+ display: flex;
1185
+ align-items: center;
1186
+ gap: 8px;
1187
+ margin: 6px 0;
1107
1188
  }
1108
1189
 
1109
- #lobby-auth-login-error,
1110
- #lobby-auth-nick-error {
1111
- color: #f56;
1190
+ /* чекбокс и текст в одну строку, текст переносится под себя */
1191
+ #profile-consent label {
1192
+ display: flex;
1193
+ align-items: flex-start;
1194
+ gap: 8px;
1112
1195
  margin: 8px 0;
1196
+ font-size: 12px;
1197
+ line-height: 16px;
1198
+ }
1199
+
1200
+ #profile-consent input[type='checkbox'] {
1201
+ flex: none;
1202
+ margin-top: 2px;
1203
+ }
1204
+
1205
+ #profile-consent a {
1206
+ color: #ccc;
1207
+ }
1208
+
1209
+ /* удаление необратимо — блок выделен цветом ошибок */
1210
+ #profile-delete {
1211
+ border: 1px solid #b33;
1212
+ }
1213
+
1214
+ #profile-delete .lobby-section-title {
1215
+ color: #e66;
1113
1216
  }
1114
1217
 
1115
1218
  /*games*/
@@ -1320,7 +1423,7 @@ body.hide-cursor {
1320
1423
  margin-right: 8px;
1321
1424
  }
1322
1425
 
1323
- /* кнопки входа в панель стоят рядом с Sign out в бейдже пользователя */
1426
+ /* кнопки входа в панели стоят в бейдже игрока */
1324
1427
  .lobby-user-actions {
1325
1428
  display: flex;
1326
1429
  gap: 8px;
@@ -1,5 +1,5 @@
1
- //- скрыт по умолчанию: открывается из main.js только после успешного логина
2
- //- в #lobby-auth (Этап B2, LobbyAuthCtrl 'authenticated')
1
+ //- скрыт по умолчанию: открывается из main.js, когда у вкладки есть личность
2
+ //- (экран профиля #lobby-auth, событие LobbyAuthModel 'authenticated')
3
3
  div#lobby(style='display:none')
4
4
  //- колоночная обёртка: сам #lobby — флекс-строка, центрирующая содержимое,
5
5
  //- и футер под панелями иначе встал бы третьей колонкой рядом с ними
@@ -8,21 +8,21 @@ div#lobby(style='display:none')
8
8
  //- левая панель: профиль + форма создания комнаты (lobby-page-plan)
9
9
  div#lobby-setup-panel.card
10
10
  h2= 'VIMP'
11
- //- бейдж входа (Этап B2): управляется LobbyAuthView, показывается
12
- //- вместе с #lobby после успешной авторизации
11
+ //- бейдж игрока: управляется LobbyAuthView, показывается вместе с
12
+ //- #lobby, когда у вкладки есть личность
13
13
  div#lobby-user
14
14
  div
15
- span= 'Signed in as '
15
+ span= 'Playing as '
16
16
  b#lobby-user-nick
17
17
  //- вход в панель реестра игр (master-game-registry, этап 4):
18
18
  //- «My games» видит любой авторизованный, «Moderation» — только
19
19
  //- админская роль (main.js по LobbyAuthModel.getRole())
20
20
  div.lobby-user-actions
21
+ input#profile-open(type='button', value='Profile')
21
22
  input#games-open-mine(type='button', value='My games')
22
23
  input#games-open-moderation(type='button', value='Moderation', style='display:none')
23
24
  //- журнал клиентских ошибок (plan/client-reports, этап 5): только админ
24
25
  input#reports-open(type='button', value='Errors', style='display:none')
25
- input#lobby-user-logout(type='button', value='Sign out')
26
26
  hr.lobby-divider
27
27
  h3.lobby-section-title= 'HOST A GAME'
28
28
  div#lobby-create
@@ -69,6 +69,11 @@ div#lobby(style='display:none')
69
69
  //- футер: ссылку и версию пакета движка проставляет LobbyView
70
70
  //- (engineVersion.js + lib/packageLink.js)
71
71
  div#lobby-footer
72
+ p
73
+ a#lobby-terms-link(target='_blank', rel='noopener') Terms of Service
74
+ | ·
75
+ |
76
+ a#lobby-privacy-link(target='_blank', rel='noopener') Privacy Policy
72
77
  p
73
78
  a#lobby-link
74
79
  p#lobby-version
@@ -1,19 +1,37 @@
1
- //- логин-гейт лобби (Этап B2): показывается до #lobby, пока клиент не
2
- //- авторизован через central auth-сервис (packages/auth). Кнопки провайдеров
3
- //- фильтруются JS по authClient.providers; неактивные скрыты
1
+ //- экран профиля перед лобби: загрузка, отказ сервиса, Welcome (регистрация
2
+ //- ника и восстановление профиля через OAuth). Кнопки провайдеров
3
+ //- фильтруются JS по списку из /client-config
4
4
  div#lobby-auth
5
5
 
6
- div#lobby-auth-login.card
7
- h2= 'VIMP'
8
- p= 'Sign in to play'
9
- div#lobby-auth-login-buttons
10
- input.lobby-auth-provider#lobby-auth-login-github(type='button', data-provider='github', value='Sign in with GitHub')
11
- input.lobby-auth-provider#lobby-auth-login-google(type='button', data-provider='google', value='Sign in with Google')
12
- input.lobby-auth-provider#lobby-auth-login-apple(type='button', data-provider='apple', value='Sign in with Apple')
13
- div#lobby-auth-login-error
6
+ div#lobby-auth-status.card(style='display:none')
7
+ p#lobby-auth-status-text
8
+ input#lobby-auth-status-retry(type='button', value='Retry', style='display:none')
14
9
 
15
- div#lobby-auth-nick.card(style='display:none')
16
- h2= 'Choose your nick'
17
- input#lobby-auth-nick-input.field-text(type='text', maxlength='14', placeholder='YourNick', autocomplete='off')
18
- div#lobby-auth-nick-error
19
- input#lobby-auth-nick-submit(type='button', value='Continue')
10
+ div#lobby-auth-welcome.card(style='display:none')
11
+ h2= 'Welcome to VIMP!'
12
+ p= 'Pick a nick and play. No e-mail or password needed.'
13
+ p#lobby-auth-notice.form-notice
14
+ input#lobby-auth-nick-input.field-text(type='text', maxlength='14', placeholder='Your nick', autocomplete='off')
15
+ div#lobby-auth-nick-error.form-error
16
+ input#lobby-auth-turnstile-retry(type='button', value='Retry', style='display:none')
17
+ //- контейнер виджета Turnstile: виден, только когда проверке нужен клик
18
+ div#lobby-auth-turnstile
19
+ input#lobby-auth-register(type='button', value='Create profile')
20
+ p#lobby-auth-legal
21
+ | By creating a profile you agree to the
22
+ |
23
+ a#lobby-auth-terms(target='_blank', rel='noopener') Terms of Service
24
+ |
25
+ | and the
26
+ |
27
+ a#lobby-auth-privacy(target='_blank', rel='noopener') Privacy Policy
28
+ | .
29
+ p#lobby-auth-lifetime
30
+ hr.lobby-divider
31
+ input#lobby-auth-restore(type='button', value='Restore profile')
32
+ div#lobby-auth-providers(style='display:none')
33
+ p= 'Restore a profile linked to:'
34
+ input.lobby-auth-provider(type='button', data-provider='google', value='Continue with Google')
35
+ input.lobby-auth-provider(type='button', data-provider='discord', value='Continue with Discord')
36
+ input.lobby-auth-provider(type='button', data-provider='github', value='Continue with GitHub')
37
+ div#lobby-auth-restore-error.form-error
@@ -0,0 +1,73 @@
1
+ //- Панель профиля в лобби: привязка аккаунта (необязательная), выход,
2
+ //- выгрузка данных, удаление. Скрыта по умолчанию — открывается кнопкой
3
+ //- бейджа пользователя (#profile-open в lobby.pug); id элементов
4
+ //- перечислены в config/lobby.js. Подписи — по-английски, как всё лобби
5
+ div#profile-panel(style='display:none')
6
+ div.lobby-column
7
+ div.games-head
8
+ h3.lobby-section-title= 'Profile'
9
+ input#profile-close(type='button', value='Back to lobby')
10
+
11
+ div.card
12
+ p
13
+ span= 'Nick: '
14
+ b#profile-nick
15
+ p.form-hint= 'The nick cannot be changed.'
16
+ p#profile-status
17
+
18
+ div.card#profile-link
19
+ h3.lobby-section-title= 'Link an account (optional)'
20
+ p= 'Linking is not required — you can keep playing without it. It gives you:'
21
+ ul#profile-link-benefits
22
+ p.form-hint= 'We receive and store only the account ID from the provider — no e-mail, name, avatar or contacts.'
23
+ div#profile-providers
24
+ div#profile-consent
25
+ label
26
+ input#profile-consent-terms(type='checkbox')
27
+ |
28
+ | I have read and agree to the
29
+ |
30
+ a.profile-terms-link(target='_blank', rel='noopener') Terms of Service
31
+ |
32
+ | and the
33
+ |
34
+ a.profile-privacy-link(target='_blank', rel='noopener') Privacy Policy
35
+ | .
36
+ label
37
+ input#profile-consent-data(type='checkbox')
38
+ |
39
+ | I consent to the processing of my personal data — the account ID received from the provider I choose — in order to restore access to my profile, as described in the
40
+ |
41
+ a.profile-privacy-link(target='_blank', rel='noopener') Privacy Policy
42
+ | . I can withdraw this consent at any time by unlinking the account or deleting my profile.
43
+ label
44
+ input#profile-consent-age(type='checkbox')
45
+ |
46
+ | I confirm that I am at least 16 years old, or have reached the age of digital consent in my country if that is higher.
47
+ p#profile-link-notice.form-notice
48
+ div#profile-link-error.form-error
49
+
50
+ div.card#profile-session(style='display:none')
51
+ input#profile-sign-out(type='button', value='Sign out')
52
+ p.form-hint= 'You can sign back in with a linked account.'
53
+ div#profile-session-error.form-error
54
+
55
+ div.card
56
+ input#profile-export(type='button', value='Download my data')
57
+ p.form-hint= 'A JSON file with everything stored about this profile.'
58
+ div#profile-export-error.form-error
59
+
60
+ div.card#profile-delete
61
+ h3.lobby-section-title= 'Delete profile'
62
+ p= 'Deleting the profile is permanent. Your nick, ratings and saved progress in every game will be erased and removed from the leaderboards. The nick will become available to other players.'
63
+ p#profile-delete-admin(style='display:none')= 'Admin profiles cannot be deleted. Remove the nick from the admin list first.'
64
+ input#profile-delete-start(type='button', value='Delete this profile')
65
+ div#profile-delete-confirm(style='display:none')
66
+ p
67
+ span= 'To confirm, type your nick '
68
+ b#profile-delete-nick
69
+ span= ' below:'
70
+ input#profile-delete-input.field-text(type='text', maxlength='15', autocomplete='off')
71
+ input#profile-delete-submit(type='button', value='I understand, delete this profile', disabled)
72
+ input#profile-delete-cancel(type='button', value='Cancel')
73
+ div#profile-delete-error.form-error
@@ -6,6 +6,7 @@ include includes/roomMenu
6
6
  include includes/lobbyAuth
7
7
  include includes/lobby
8
8
  include includes/games
9
+ include includes/profile
9
10
  include includes/reports
10
11
  include includes/auth
11
12
  include includes/informer
@@ -1,13 +1,12 @@
1
- // Конфиг клиентского логина лобби (Этап B2). Бандлится в клиентскую сборку,
2
- // как config/lobby.js — auth-сервис живёт на отдельном домене/порте, поэтому
3
- // serviceUrl должен быть подставлен под конкретный деплой перед сборкой прода
4
- // (см. docs/en/auth.md).
1
+ // Конфиг клиента профиля лобби. Бандлится в клиентскую сборку, как
2
+ // config/lobby.js — auth-сервис живёт на отдельном домене/порте, поэтому
3
+ // serviceUrl подставляется под конкретный деплой при сборке (см.
4
+ // docs/en/auth.md).
5
5
  export default {
6
6
  // базовый URL центрального auth-сервиса (packages/auth). Дефолт — dev-порт
7
7
  // из packages/auth/src/config/auth.js; в проде подставляется Vite'ом из
8
8
  // VITE_AUTH_SERVICE_URL (см. Dockerfile — тот же VIMP_AUTH_SERVICE_URL,
9
- // что и серверный master/main.js), никакой ручной правки перед сборкой
10
- // больше не требуется. Этот модуль импортируется и серверным
9
+ // что и серверный master/main.js). Этот модуль импортируется и серверным
11
10
  // master/main.js под чистым Node, где import.meta.env не определён —
12
11
  // typeof-guard нужен, иначе TypeError роняет мастер на старте (502)
13
12
  serviceUrl:
@@ -15,41 +14,66 @@ export default {
15
14
  import.meta.env.VITE_AUTH_SERVICE_URL) ||
16
15
  'http://localhost:3010',
17
16
 
18
- // включённые провайдеры (должны совпадать с packages/auth config.oauth)
19
- providers: ['github'],
17
+ // issuer, которым central auth-сервис подписывает identity-токен; должен
18
+ // совпадать с packages/auth/src/config/auth.js: jwt.issuer — хост сверяет
19
+ // claim iss токена с этим значением при проверке подписи по /jwks
20
+ issuer: 'vimp-auth',
20
21
 
21
- // ключ localStorage для identity JWT
22
- tokenStorageKey: 'vimpAuthToken',
22
+ // секрет сессии устройства ('s_…') — единственный ключ к профилю: им
23
+ // получают identity-токен и управляют профилем
24
+ sessionStorageKey: 'vimpSession',
23
25
 
24
- // issuer, которым central auth-сервис подписывает identity-токен (Этап B3);
25
- // должен совпадать с packages/auth/src/config/auth.js: jwt.issuer — хост
26
- // сверяет claim iss токена с этим значением при проверке подписи по /jwks
27
- issuer: 'vimp-auth',
26
+ // dev-логин auth-сервиса (только dev-сборка): ?devSession=s_…
27
+ devSessionParam: 'devSession',
28
28
 
29
- // имя query-параметров, которыми auth-сервис возвращает управление
30
- // (redirect на returnUrl после /oauth/:provider/callback)
31
- queryParams: {
32
- token: 'token',
33
- pendingToken: 'pendingToken',
34
- error: 'authError',
29
+ // возврат с OAuth-провайдера и незавершённая попытка (sessionStorage)
30
+ oauth: {
31
+ codeParam: 'oauthCode',
32
+ errorParam: 'oauthError',
33
+ pendingKey: 'vimpOauthPending',
35
34
  },
36
35
 
37
- // DOM-элементы экрана логина/ника (views/includes/lobbyAuth.pug) и бейджа
38
- // пользователя в лобби (views/includes/lobby.pug)
36
+ // уведомление, которое должно пережить перезагрузку страницы
37
+ // (sessionStorage)
38
+ noticeKey: 'vimpAuthNotice',
39
+
40
+ // тихое продление identity-токена: за leadMs до exp, повтор при сбое
41
+ // через retryMs
42
+ renew: { leadMs: 3600000, retryMs: 60000 },
43
+
44
+ // Cloudflare Turnstile на регистрации; источник скрипта — тот же, что в
45
+ // CSP мастера (config/master.js security.csp)
46
+ turnstile: {
47
+ scriptUrl:
48
+ 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit',
49
+ // дольше не ждём: скрипт, скорее всего, заблокирован расширением
50
+ loadTimeoutMs: 4000,
51
+ action: 'register',
52
+ },
53
+
54
+ // DOM-элементы экрана профиля (views/includes/lobbyAuth.pug) и бейджа
55
+ // игрока в лобби (views/includes/lobby.pug)
39
56
  elems: {
40
57
  containerId: 'lobby-auth',
41
- loginSectionId: 'lobby-auth-login',
42
- loginErrorId: 'lobby-auth-login-error',
43
- nickSectionId: 'lobby-auth-nick',
58
+ statusId: 'lobby-auth-status',
59
+ statusTextId: 'lobby-auth-status-text',
60
+ statusRetryId: 'lobby-auth-status-retry',
61
+ welcomeId: 'lobby-auth-welcome',
62
+ noticeId: 'lobby-auth-notice',
44
63
  nickInputId: 'lobby-auth-nick-input',
45
64
  nickErrorId: 'lobby-auth-nick-error',
46
- nickSubmitId: 'lobby-auth-nick-submit',
65
+ turnstileRetryId: 'lobby-auth-turnstile-retry',
66
+ turnstileId: 'lobby-auth-turnstile',
67
+ registerId: 'lobby-auth-register',
68
+ termsId: 'lobby-auth-terms',
69
+ privacyId: 'lobby-auth-privacy',
70
+ lifetimeId: 'lobby-auth-lifetime',
71
+ restoreId: 'lobby-auth-restore',
72
+ providersId: 'lobby-auth-providers',
73
+ restoreErrorId: 'lobby-auth-restore-error',
74
+ providerClass: 'lobby-auth-provider',
47
75
  lobbyId: 'lobby',
48
76
  userId: 'lobby-user',
49
77
  userNickId: 'lobby-user-nick',
50
- userLogoutId: 'lobby-user-logout',
51
78
  },
52
-
53
- // CSS-класс кнопок провайдеров в lobbyAuth.pug (data-provider на каждой)
54
- providerButtonClass: 'lobby-auth-provider',
55
79
  };
@@ -1,4 +1,4 @@
1
- // Английские тексты движковых кодов чата по умолчанию (группы s, v, m, c, n);
1
+ // Английские тексты движковых кодов чата по умолчанию (группы s, v, m, c);
2
2
  // индекс = номер кода. Лежат здесь, а не в clientDefaults: при слиянии
3
3
  // конфига массивы заменяются целиком, и массив `s` игры стёр бы умолчания.
4
4
  // Правило перекрытия: строка игры по тому же индексу важнее умолчания;
@@ -41,5 +41,4 @@ export default {
41
41
  ],
42
42
  m: ['Current map: {0}', 'Next map: {0}'],
43
43
  c: ['Command not found', 'Your rank: {0}'],
44
- n: ['Invalid name', '{0} changed name to {1}'],
45
44
  };
package/src/config/env.js CHANGED
@@ -20,8 +20,8 @@ export function applyMasterEnv(config, env = process.env) {
20
20
  config.set('master:port', Number(env.VIMP_MASTER_PORT));
21
21
  }
22
22
 
23
- // домен central auth-сервиса (Этап B2) — попадает в CSP connect-src, т.к.
24
- // лобби делает туда прямой fetch (POST /nick)
23
+ // домен central auth-сервиса — попадает в CSP connect-src, т.к. лобби
24
+ // делает туда прямые fetch (ручки профиля)
25
25
  if (env.VIMP_AUTH_SERVICE_URL) {
26
26
  config.set('master:security:authServiceUrl', env.VIMP_AUTH_SERVICE_URL);
27
27
  }