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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vimp-engine",
3
- "version": "0.37.1",
3
+ "version": "0.38.1",
4
4
  "description": "VIMP — движок-приложение (мастер, P2P-транспорт, Worker-хост, мета, MVC-каркас клиента)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,7 +1,7 @@
1
1
  // Режим загрузки клиента движка (Этап 2 плана standalone-sdk). Клиент один
2
2
  // на все контуры, а различает их этот модуль:
3
3
  //
4
- // lobby — прод: каталог игр и сигналинг мастера, лобби за OAuth-гейтом;
4
+ // lobby — прод: каталог игр и сигналинг мастера, лобби за экраном профиля;
5
5
  // solo — standalone SDK: хост в этой же вкладке (inline, без Worker'а),
6
6
  // мастера нет вовсе;
7
7
  // dedicated — Node-сервер: прямой WebSocket к игровому процессу.
@@ -25,7 +25,8 @@ export default class ClientReportsCtrl {
25
25
  }
26
26
 
27
27
  // роль решает только видимость кнопки «Errors»: доступ к данным проверяет
28
- // мастер, а auth перечитывает роль из БД
28
+ // мастер, а права админа auth-сервис проверяет сам, по нику из
29
+ // VIMP_ADMIN_NICKS
29
30
  setAdmin(isAdmin) {
30
31
  this._view.setAdmin(isAdmin);
31
32
  }
@@ -42,7 +42,7 @@ export default class GamesCtrl {
42
42
  }
43
43
 
44
44
  // роль решает только видимость кнопки модерации: доступ к данным
45
- // проверяет мастер, а запись — auth-сервис по роли из БД
45
+ // проверяет мастер, а запись — auth-сервис, по нику из VIMP_ADMIN_NICKS
46
46
  setAdmin(isAdmin) {
47
47
  this._view.setAdmin(isAdmin);
48
48
 
@@ -3,7 +3,23 @@
3
3
  let lobbyAuthCtrl;
4
4
 
5
5
  export default class LobbyAuthCtrl {
6
- constructor(model, view) {
6
+ /**
7
+ * @param {Object} model - LobbyAuthModel.
8
+ * @param {Object} view - LobbyAuthView.
9
+ * @param {Object} deps
10
+ * @param {Object} deps.turnstile - client/lib/turnstile.js.
11
+ * @param {Function} [deps.navigate] - переход вкладки на URL.
12
+ */
13
+ constructor(
14
+ model,
15
+ view,
16
+ {
17
+ turnstile,
18
+ navigate = url => {
19
+ window.location.href = url;
20
+ },
21
+ },
22
+ ) {
7
23
  if (lobbyAuthCtrl) {
8
24
  return lobbyAuthCtrl;
9
25
  }
@@ -12,30 +28,100 @@ export default class LobbyAuthCtrl {
12
28
 
13
29
  this._model = model;
14
30
  this._view = view;
31
+ this._turnstile = turnstile;
32
+ this._navigate = navigate;
33
+ this._busy = false;
15
34
 
16
35
  const vp = view.publisher;
17
36
 
18
- vp.on('login', 'login', this);
19
- vp.on('nick', 'submitNick', this);
20
- vp.on('logout', 'logout', this);
37
+ vp.on('register', 'register', this);
38
+ vp.on('turnstile-retry', 'retryRegister', this);
39
+ vp.on('restore', 'restore', this);
40
+ vp.on('retry', 'retry', this);
21
41
  }
22
42
 
23
- // разбирает query string текущего location (OAuth-редиректы) либо
24
- // восстанавливает сессию из localStorage; возвращает true, если query
25
- // содержала auth-параметры (вызывающий должен подчистить адресную строку)
26
- init(search) {
27
- return this._model.boot(search);
43
+ // ник проверяется на клиенте до Turnstile и сервера; Turnstile — до
44
+ // запроса регистрации
45
+ async register(rawNick) {
46
+ if (this._busy) {
47
+ return;
48
+ }
49
+
50
+ const view = this._view;
51
+ const nick = rawNick.trim();
52
+
53
+ view.renderRegisterError(null);
54
+ view.showTurnstileRetry(false);
55
+
56
+ if (!this._model.validateNick(nick)) {
57
+ view.renderRegisterError('invalidNick');
58
+ return;
59
+ }
60
+
61
+ this._busy = true;
62
+ view.setBusy(true);
63
+
64
+ try {
65
+ const { turnstile } = this._model.getClientConfig();
66
+ let token = null;
67
+
68
+ if (turnstile.enabled) {
69
+ try {
70
+ token = await this._turnstile.getToken(
71
+ view.getTurnstileContainer(),
72
+ turnstile.siteKey,
73
+ );
74
+ } catch (error) {
75
+ view.renderRegisterError(
76
+ error?.code === 'expired' ? 'turnstileFailed' : 'turnstileBlocked',
77
+ );
78
+ view.showTurnstileRetry(true);
79
+ return;
80
+ }
81
+ }
82
+
83
+ const ok = await this._model.register(nick, token);
84
+
85
+ // токен Turnstile одноразовый и уже израсходован проверкой на сервере;
86
+ // новый берётся следующей отправкой формы, а не впрок: за 300 с жизни
87
+ // он истёк бы, пока игрок подбирает ник
88
+ if (!ok) {
89
+ this._turnstile.reset();
90
+ }
91
+ } finally {
92
+ this._busy = false;
93
+ view.setBusy(false);
94
+
95
+ // отказ возвращает игрока к полю с его ником
96
+ view.focusNick();
97
+ }
28
98
  }
29
99
 
30
- login(provider) {
31
- window.location.href = this._model.loginUrl(provider);
100
+ retryRegister() {
101
+ return this.register(this._view.getNickValue());
32
102
  }
33
103
 
34
- submitNick(nick) {
35
- this._model.submitNick(nick);
104
+ // флаг закрывает только построение URL (хэш verifier считается
105
+ // асинхронно): два построения подряд — лишняя работа. После navigate() он
106
+ // снят — отменённый переход или возврат кнопкой «Назад» не должен
107
+ // оставить экран без регистрации и входа
108
+ async restore(provider) {
109
+ if (this._busy) {
110
+ return;
111
+ }
112
+
113
+ this._busy = true;
114
+
115
+ try {
116
+ this._navigate(await this._model.restoreUrl(provider));
117
+ } catch {
118
+ this._view.renderRestoreError('failed');
119
+ } finally {
120
+ this._busy = false;
121
+ }
36
122
  }
37
123
 
38
- logout() {
39
- this._model.logout();
124
+ retry() {
125
+ return this._model.boot();
40
126
  }
41
127
  }
@@ -0,0 +1,95 @@
1
+ import Publisher from '../../../lib/Publisher.js';
2
+
3
+ // Singleton ProfileCtrl
4
+
5
+ let profileCtrl;
6
+
7
+ // Контроллер панели профиля: связывает события вида с моделью, как GamesCtrl.
8
+ // Сетевого I/O не делает. Наружу (main.js) пробрасывает 'left' — выход или
9
+ // удаление профиля, после которых вкладка перезагружается
10
+ export default class ProfileCtrl {
11
+ /**
12
+ * @param {Object} model - ProfileModel.
13
+ * @param {Object} view - ProfileView.
14
+ * @param {Object} [deps]
15
+ * @param {Function} [deps.navigate] - уход вкладки на URL провайдера.
16
+ */
17
+ constructor(
18
+ model,
19
+ view,
20
+ {
21
+ navigate = url => {
22
+ window.location.href = url;
23
+ },
24
+ } = {},
25
+ ) {
26
+ if (profileCtrl) {
27
+ return profileCtrl;
28
+ }
29
+
30
+ profileCtrl = this;
31
+
32
+ this._model = model;
33
+ this._view = view;
34
+ this._navigate = navigate;
35
+ this._linking = false;
36
+
37
+ this.publisher = new Publisher();
38
+
39
+ const vp = view.publisher;
40
+ const mp = model.publisher;
41
+
42
+ vp.on('open', 'open', this);
43
+ vp.on('link', 'link', this);
44
+ vp.on('unlink', 'unlink', this);
45
+ vp.on('sign-out', 'signOut', this);
46
+ vp.on('export', 'exportData', this);
47
+ vp.on('delete', 'deleteProfile', this);
48
+
49
+ mp.on('left', () => this.publisher.emit('left'));
50
+ }
51
+
52
+ open() {
53
+ this._view.show();
54
+ }
55
+
56
+ // кнопки вида неактивны без согласий; проверка здесь — второй рубеж.
57
+ // Флаг закрывает только построение URL — как у LobbyAuthCtrl.restore()
58
+ async link(provider) {
59
+ if (this._linking || !this._view.consentGiven()) {
60
+ return;
61
+ }
62
+
63
+ this._linking = true;
64
+
65
+ try {
66
+ this._navigate(await this._model.linkUrl(provider));
67
+ } catch {
68
+ this._view.renderError({ area: 'link', code: 'failed' });
69
+ } finally {
70
+ this._linking = false;
71
+ }
72
+ }
73
+
74
+ unlink(provider) {
75
+ return this._model.unlink(provider);
76
+ }
77
+
78
+ signOut() {
79
+ return this._model.signOut();
80
+ }
81
+
82
+ exportData() {
83
+ return this._model.exportData();
84
+ }
85
+
86
+ deleteProfile(confirmNick) {
87
+ return this._model.deleteProfile(confirmNick);
88
+ }
89
+
90
+ // вкладка вернулась от провайдера: итог привязки показывает сама панель
91
+ openWithLinkResult(result) {
92
+ this._view.show();
93
+ this._view.renderLinkResult(result);
94
+ }
95
+ }
@@ -12,7 +12,7 @@ const REQUEST_FAILED = 'requestFailed';
12
12
  // вызывающего, очередь модерации и «Test» новой версии. В отличие от
13
13
  // LobbyModel сетевые запросы делает сама — это простой REST к мастеру,
14
14
  // сигнальный сокет здесь ни при чём (тот же приём, что у LobbyAuthModel с
15
- // POST /nick). DOM не трогает.
15
+ // REST к auth-сервису). DOM не трогает.
16
16
  export default class GamesModel {
17
17
  /**
18
18
  * @param {Object} config - Блок `games` конфига лобби (urls, statuses).