vimp-engine 0.37.1 → 0.38.1

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 (49) hide show
  1. package/package.json +1 -1
  2. package/src/client/boot.js +1 -1
  3. package/src/client/components/controller/ClientReports.js +2 -1
  4. package/src/client/components/controller/Games.js +1 -1
  5. package/src/client/components/controller/LobbyAuth.js +101 -15
  6. package/src/client/components/controller/Profile.js +95 -0
  7. package/src/client/components/model/Games.js +1 -1
  8. package/src/client/components/model/LobbyAuth.js +382 -115
  9. package/src/client/components/model/Profile.js +144 -0
  10. package/src/client/components/view/LobbyAuth.js +150 -62
  11. package/src/client/components/view/Profile.js +372 -0
  12. package/src/client/lib/authApi.js +106 -0
  13. package/src/client/lib/hostCaps.js +3 -2
  14. package/src/client/lib/pkce.js +23 -0
  15. package/src/client/lib/roomLink.js +1 -1
  16. package/src/client/lib/sessionSync.js +45 -0
  17. package/src/client/lib/signalingErrors.js +3 -3
  18. package/src/client/lib/turnstile.js +130 -0
  19. package/src/client/main.js +132 -26
  20. package/src/client/network/TokenRenewer.js +99 -0
  21. package/src/client/session/GuestSession.js +63 -17
  22. package/src/client/session/Membership.js +2 -1
  23. package/src/client/style.css +128 -16
  24. package/src/client/views/includes/lobby.pug +11 -6
  25. package/src/client/views/includes/lobbyAuth.pug +34 -16
  26. package/src/client/views/includes/profile.pug +73 -0
  27. package/src/client/views/index.pug +1 -0
  28. package/src/config/authClient.js +55 -30
  29. package/src/config/chatMessages.js +1 -2
  30. package/src/config/env.js +2 -2
  31. package/src/config/lobby.js +45 -1
  32. package/src/config/master.js +8 -5
  33. package/src/config/wsports.js +4 -1
  34. package/src/devtools/builtinScenario.js +1 -1
  35. package/src/devtools/contract/rules/a2-package-scripts.js +1 -1
  36. package/src/devtools/contract/rules/a4-vite-config.js +1 -1
  37. package/src/devtools/contract/rules/b8-system-messages.js +1 -1
  38. package/src/devtools/invariants.js +1 -1
  39. package/src/devtools/pluginLoader.js +1 -1
  40. package/src/host/GameCoreAdapter.js +0 -3
  41. package/src/host/HostGame.js +28 -0
  42. package/src/host/PortMachine.js +50 -1
  43. package/src/host/meta/SocketManager.js +0 -13
  44. package/src/host/meta/core/RoundManager.js +0 -19
  45. package/src/host/meta/modules/PlayerDataSync.js +29 -3
  46. package/src/host/meta/modules/chat/systemMessages.js +1 -4
  47. package/src/lib/loadGamePackage.js +1 -1
  48. package/src/lib/validators.js +8 -0
  49. package/src/standalone/index.js +1 -1
@@ -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;
@@ -590,6 +589,7 @@ body.hide-cursor {
590
589
  держится в одном месте (#lobby-footer лежит в секции lobby ниже только
591
590
  разметкой) */
592
591
  #auth-link,
592
+ #lobby-auth-footer,
593
593
  #lobby-footer {
594
594
  height: 15px;
595
595
  text-shadow: none;
@@ -602,15 +602,18 @@ body.hide-cursor {
602
602
  padding: 0 5px;
603
603
  }
604
604
 
605
+ #lobby-auth-footer,
605
606
  #lobby-footer {
606
607
  font-size: 10px;
607
608
  padding: 0;
608
609
  }
609
610
 
611
+ #lobby-auth-footer p,
610
612
  #lobby-footer p {
611
613
  margin: 0 5px;
612
614
  }
613
615
 
616
+ #lobby-auth-footer p:not(:last-child)::after,
614
617
  #lobby-footer p:not(:last-child)::after {
615
618
  content: '|';
616
619
  padding-left: 10px;
@@ -625,6 +628,7 @@ body.hide-cursor {
625
628
  }
626
629
 
627
630
  #auth-link p a,
631
+ #lobby-auth-footer p a,
628
632
  #lobby-footer p a {
629
633
  color: #ff0;
630
634
  }
@@ -1071,7 +1075,7 @@ body.hide-cursor {
1071
1075
  display: none;
1072
1076
  }
1073
1077
 
1074
- /*lobby-auth (Этап B2: логин перед лобби)*/
1078
+ /*lobby-auth (экран профиля перед лобби)*/
1075
1079
 
1076
1080
  #lobby-auth {
1077
1081
  background-color: #222;
@@ -1083,33 +1087,141 @@ body.hide-cursor {
1083
1087
  z-index: 8;
1084
1088
  }
1085
1089
 
1086
- #lobby-auth-login,
1087
- #lobby-auth-nick {
1090
+ #lobby-auth-status,
1091
+ #lobby-auth-welcome {
1088
1092
  text-align: center;
1089
1093
  }
1090
1094
 
1091
- #lobby-auth-login-buttons {
1095
+ #lobby-auth-providers {
1092
1096
  display: flex;
1093
1097
  flex-direction: column;
1094
1098
  gap: 8px;
1095
1099
  }
1096
1100
 
1097
1101
  .lobby-auth-provider,
1098
- #lobby-host,
1099
- #lobby-user-logout {
1102
+ #lobby-host {
1100
1103
  align-self: center;
1101
1104
  }
1102
1105
 
1103
1106
  #lobby-auth-nick-input {
1104
1107
  display: block;
1105
1108
  width: 100%;
1106
- margin-bottom: 10px;
1109
+ margin: 10px 0;
1110
+ box-sizing: border-box;
1111
+ }
1112
+
1113
+ #lobby-auth-nick-error,
1114
+ #lobby-auth-restore-error {
1115
+ text-align: left;
1116
+ }
1117
+
1118
+ #lobby-auth-register,
1119
+ #lobby-auth-restore,
1120
+ #lobby-auth-status-retry,
1121
+ #lobby-auth-turnstile-retry {
1122
+ margin: 8px auto;
1123
+ }
1124
+
1125
+ /* виджет Turnstile шириной 300 px не влезает в карточку 256 px */
1126
+ #lobby-auth-welcome {
1127
+ width: 300px;
1128
+ }
1129
+
1130
+ #lobby-auth-turnstile {
1131
+ display: flex;
1132
+ justify-content: center;
1133
+ }
1134
+
1135
+ /* футер вне рамки: по центру под карточкой */
1136
+ #lobby-auth-footer {
1137
+ justify-content: center;
1138
+ margin: 10px auto 0;
1139
+ }
1140
+
1141
+ /* нейтральная плашка уведомления (профиль не найден, выход, удаление) */
1142
+ .form-notice {
1143
+ background-color: #3d3d3d;
1144
+ border: 1px solid #555;
1145
+ padding: 6px 8px;
1146
+ margin: 8px 0;
1147
+ color: #ddd;
1148
+ }
1149
+
1150
+ .form-notice:empty {
1151
+ display: none;
1152
+ }
1153
+
1154
+ .form-hint {
1155
+ color: #aaa;
1156
+ font-size: 12px;
1157
+ line-height: 16px;
1158
+ margin: 6px 0;
1159
+ }
1160
+
1161
+ /*profile*/
1162
+
1163
+ /* панель профиля (profile.pug): открывается вместо #lobby кнопкой бейджа,
1164
+ каркас — как у панели реестра игр (.card, .lobby-column, .games-head) */
1165
+ #profile-panel {
1166
+ background-color: #222;
1167
+ position: absolute;
1168
+ width: 100%;
1169
+ min-height: 100%;
1170
+ top: 0;
1171
+ left: 0;
1172
+ z-index: 8;
1173
+ display: flex;
1174
+ align-items: flex-start;
1175
+ justify-content: center;
1176
+ box-sizing: border-box;
1107
1177
  }
1108
1178
 
1109
- #lobby-auth-login-error,
1110
- #lobby-auth-nick-error {
1111
- color: #f56;
1179
+ #profile-panel .card {
1180
+ width: 700px;
1181
+ max-width: 100%;
1182
+ padding: 10px;
1183
+ text-align: left;
1184
+ box-sizing: border-box;
1185
+ }
1186
+
1187
+ #profile-link-benefits {
1188
+ margin: 6px 0;
1189
+ padding-left: 20px;
1190
+ }
1191
+
1192
+ .profile-provider {
1193
+ display: flex;
1194
+ align-items: center;
1195
+ gap: 8px;
1196
+ margin: 6px 0;
1197
+ }
1198
+
1199
+ /* чекбокс и текст в одну строку, текст переносится под себя */
1200
+ #profile-consent label {
1201
+ display: flex;
1202
+ align-items: flex-start;
1203
+ gap: 8px;
1112
1204
  margin: 8px 0;
1205
+ font-size: 12px;
1206
+ line-height: 16px;
1207
+ }
1208
+
1209
+ #profile-consent input[type='checkbox'] {
1210
+ flex: none;
1211
+ margin-top: 2px;
1212
+ }
1213
+
1214
+ #profile-consent a {
1215
+ color: #ccc;
1216
+ }
1217
+
1218
+ /* удаление необратимо — блок выделен цветом ошибок */
1219
+ #profile-delete {
1220
+ border: 1px solid #b33;
1221
+ }
1222
+
1223
+ #profile-delete .lobby-section-title {
1224
+ color: #e66;
1113
1225
  }
1114
1226
 
1115
1227
  /*games*/
@@ -1320,7 +1432,7 @@ body.hide-cursor {
1320
1432
  margin-right: 8px;
1321
1433
  }
1322
1434
 
1323
- /* кнопки входа в панель стоят рядом с Sign out в бейдже пользователя */
1435
+ /* кнопки входа в панели стоят в бейдже игрока */
1324
1436
  .lobby-user-actions {
1325
1437
  display: flex;
1326
1438
  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#lobby-auth-notice.form-notice
13
+ input#lobby-auth-nick-input.field-text(type='text', maxlength='15', placeholder='Your nick', autocomplete='off')
14
+ div#lobby-auth-nick-error.form-error
15
+ input#lobby-auth-turnstile-retry(type='button', value='Retry', style='display:none')
16
+ //- контейнер виджета Turnstile: виден, только когда проверке нужен клик
17
+ div#lobby-auth-turnstile
18
+ input#lobby-auth-register(type='button', value='Create profile')
19
+ hr.lobby-divider
20
+ input#lobby-auth-restore(type='button', value='Restore profile')
21
+ div#lobby-auth-providers(style='display:none')
22
+ p= 'Restore a profile linked to:'
23
+ input.lobby-auth-provider(type='button', data-provider='google', value='Continue with Google')
24
+ input.lobby-auth-provider(type='button', data-provider='discord', value='Continue with Discord')
25
+ input.lobby-auth-provider(type='button', data-provider='github', value='Continue with GitHub')
26
+ div#lobby-auth-restore-error.form-error
27
+
28
+ //- футер под карточкой Welcome: ссылки заполняет LobbyAuthView,
29
+ //- версию движка — он же (engineVersion.js)
30
+ div#lobby-auth-footer(style='display:none')
31
+ p
32
+ a#lobby-auth-terms(target='_blank', rel='noopener') Terms of Service
33
+ | ·
34
+ |
35
+ a#lobby-auth-privacy(target='_blank', rel='noopener') Privacy Policy
36
+ p#lobby-auth-version
37
+ p © #{new Date().getFullYear()} VIMP
@@ -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,67 @@ 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
+ footerId: 'lobby-auth-footer',
63
+ noticeId: 'lobby-auth-notice',
44
64
  nickInputId: 'lobby-auth-nick-input',
45
65
  nickErrorId: 'lobby-auth-nick-error',
46
- nickSubmitId: 'lobby-auth-nick-submit',
66
+ turnstileRetryId: 'lobby-auth-turnstile-retry',
67
+ turnstileId: 'lobby-auth-turnstile',
68
+ registerId: 'lobby-auth-register',
69
+ termsId: 'lobby-auth-terms',
70
+ privacyId: 'lobby-auth-privacy',
71
+ versionId: 'lobby-auth-version',
72
+ restoreId: 'lobby-auth-restore',
73
+ providersId: 'lobby-auth-providers',
74
+ restoreErrorId: 'lobby-auth-restore-error',
75
+ providerClass: 'lobby-auth-provider',
47
76
  lobbyId: 'lobby',
48
77
  userId: 'lobby-user',
49
78
  userNickId: 'lobby-user-nick',
50
- userLogoutId: 'lobby-user-logout',
51
79
  },
52
-
53
- // CSS-класс кнопок провайдеров в lobbyAuth.pug (data-provider на каждой)
54
- providerButtonClass: 'lobby-auth-provider',
55
80
  };
@@ -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
  }
@@ -213,7 +213,8 @@ export default {
213
213
  // подключён по WebRTC; при смене состава отчёт уходит сразу, с дебаунсом);
214
214
  // minTokenLifetimeMs — вкладка, чей вход истечёт раньше, не объявляет
215
215
  // canHost и отказывается от промоушена (токен предъявляется мастеру
216
- // посреди матча, продления нет); tokenHandoffLeadMs — за столько до
216
+ // посреди матча; обычно его заранее продлевает TokenRenewer, порог работает,
217
+ // пока продление не удаётся); tokenHandoffLeadMs — за столько до
217
218
  // истечения входа хост с бетой сам передаёт роль (плановая передача на
218
219
  // границе раунда); tokenHandoffRetryMs — через сколько повторить её, если
219
220
  // беты ещё нет, идёт эстафета Worker'ов или передача сорвалась (повторы —
@@ -445,6 +446,49 @@ export default {
445
446
  },
446
447
  },
447
448
 
449
+ // панель профиля (profile.pug): привязка OAuth, выход, выгрузка данных,
450
+ // удаление. Открывается кнопкой бейджа и прячет #lobby, как панель игр
451
+ profile: {
452
+ // подписи провайдеров в интерфейсе
453
+ providerTitles: { google: 'Google', discord: 'Discord', github: 'GitHub' },
454
+ // имя файла выгрузки данных
455
+ exportFileName: nick => `vimp-profile-${nick}.json`,
456
+ elems: {
457
+ panelId: 'profile-panel',
458
+ lobbyId: 'lobby',
459
+ openBtnId: 'profile-open',
460
+ closeBtnId: 'profile-close',
461
+ nickId: 'profile-nick',
462
+ statusId: 'profile-status',
463
+ benefitsId: 'profile-link-benefits',
464
+ providersId: 'profile-providers',
465
+ consentId: 'profile-consent',
466
+ consentTermsId: 'profile-consent-terms',
467
+ consentDataId: 'profile-consent-data',
468
+ consentAgeId: 'profile-consent-age',
469
+ termsLinkClass: 'profile-terms-link',
470
+ privacyLinkClass: 'profile-privacy-link',
471
+ linkNoticeId: 'profile-link-notice',
472
+ linkErrorId: 'profile-link-error',
473
+ sessionCardId: 'profile-session',
474
+ signOutBtnId: 'profile-sign-out',
475
+ sessionErrorId: 'profile-session-error',
476
+ exportBtnId: 'profile-export',
477
+ exportErrorId: 'profile-export-error',
478
+ deleteAdminNoteId: 'profile-delete-admin',
479
+ deleteStartBtnId: 'profile-delete-start',
480
+ deleteConfirmId: 'profile-delete-confirm',
481
+ deleteNickId: 'profile-delete-nick',
482
+ deleteInputId: 'profile-delete-input',
483
+ deleteSubmitBtnId: 'profile-delete-submit',
484
+ deleteCancelBtnId: 'profile-delete-cancel',
485
+ deleteErrorId: 'profile-delete-error',
486
+ // ссылки на юридические страницы в футере лобби
487
+ footerTermsId: 'lobby-terms-link',
488
+ footerPrivacyId: 'lobby-privacy-link',
489
+ },
490
+ },
491
+
448
492
  // журнал клиентских ошибок (plan/client-reports, этап 5): только админ.
449
493
  // URL и id элементов — здесь, как у панели реестра игр (reports.pug)
450
494
  clientReports: {
@@ -218,10 +218,12 @@ export default {
218
218
  // политики; мастер применяет её к своим ответам только в проде (в dev CSP
219
219
  // сломала бы Vite HMR). WASM требует 'wasm-unsafe-eval', Worker — 'blob:';
220
220
  // connect-src data: — PixiJS фетчит тестовый data:-URL для проверки ImageBitmap.
221
- // authServiceUrl (Этап B2) — домен central auth-сервиса (packages/auth):
222
- // лобби делает туда прямой fetch (POST /nick), поэтому connect-src должен
223
- // его разрешать; сам OAuth-редирект (location.href на auth-сервис/провайдера)
224
- // CSP не ограничивает — это навигация верхнего уровня, не fetch/XHR.
221
+ // authServiceUrl — домен central auth-сервиса (packages/auth): лобби
222
+ // делает туда прямой fetch (браузерные ручки auth-сервиса), поэтому
223
+ // connect-src должен его разрешать; сам OAuth-редирект (location.href на
224
+ // auth-сервис/провайдера) CSP не ограничивает — это навигация верхнего
225
+ // уровня, не fetch/XHR. challenges.cloudflare.com в script-src и frame-src
226
+ // — скрипт и iframe виджета Turnstile на экране регистрации.
225
227
  // script-src несёт sha256-хэш инлайнового importmap из index.html (для
226
228
  // pixi.js — src="..." на <script type="importmap"> браузеры не
227
229
  // поддерживают, инлайн обязателен). Хэш посчитан по факту собранного
@@ -235,8 +237,9 @@ export default {
235
237
  csp: authServiceUrl =>
236
238
  [
237
239
  "default-src 'self'",
238
- "script-src 'self' 'wasm-unsafe-eval' 'sha256-XJmzkFBLHYpcM8KgGRFztTJTwfMb5xIFKAmqlgTpobo='",
240
+ "script-src 'self' 'wasm-unsafe-eval' 'sha256-XJmzkFBLHYpcM8KgGRFztTJTwfMb5xIFKAmqlgTpobo=' https://challenges.cloudflare.com",
239
241
  "worker-src 'self' blob:",
242
+ 'frame-src https://challenges.cloudflare.com',
240
243
  `connect-src 'self' wss: data:${authServiceUrl ? ` ${authServiceUrl}` : ''}`,
241
244
  "img-src 'self' data: blob:",
242
245
  "style-src 'self' 'unsafe-inline'",
@@ -19,7 +19,6 @@ export default {
19
19
  SOUND_DATA: 6,
20
20
  GAME_INFORM_DATA: 7,
21
21
  TECH_INFORM_DATA: 8,
22
- MISC: 9,
23
22
  PING: 10,
24
23
  CLEAR: 11,
25
24
  CONSOLE: 12,
@@ -51,5 +50,9 @@ export default {
51
50
  RESUME_REQUEST: 9,
52
51
  // игрок уходит сам: хост снимает его сразу, без ожидания возврата
53
52
  LEAVE: 10,
53
+ // свежий identity-токен игрока: клиент продлевает JWT молча, а хост пишет
54
+ // rank/state его токеном — без порта долгий матч писал бы истёкшим.
55
+ // Прежний хост порт игнорирует
56
+ TOKEN_REFRESH: 11,
54
57
  },
55
58
  };