vimp-engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/package.json +34 -0
- package/src/config/authClient.js +47 -0
- package/src/config/clientDefaults.js +113 -0
- package/src/config/hostDefaults.js +79 -0
- package/src/config/lobby.js +104 -0
- package/src/config/master.js +103 -0
- package/src/config/opcodes.js +29 -0
- package/src/config/wsports.js +35 -0
- package/src/host/GameCoreAdapter.js +206 -0
- package/src/host/HostGame.js +857 -0
- package/src/host/host.worker.js +438 -0
- package/src/host/meta/SocketManager.js +431 -0
- package/src/host/meta/core/CommandProcessor.js +99 -0
- package/src/host/meta/core/RoundManager.js +646 -0
- package/src/host/meta/core/VoteCoordinator.js +71 -0
- package/src/host/meta/modules/Panel.js +181 -0
- package/src/host/meta/modules/PlayerDataSync.js +176 -0
- package/src/host/meta/modules/RTTManager.js +168 -0
- package/src/host/meta/modules/Stat.js +294 -0
- package/src/host/meta/modules/TimerManager.js +277 -0
- package/src/host/meta/modules/Vote.js +179 -0
- package/src/host/meta/modules/chat/Chat.js +66 -0
- package/src/host/meta/modules/chat/index.js +1 -0
- package/src/host/meta/modules/chat/systemMessages.js +51 -0
- package/src/host/meta/player/HumanParticipant.js +36 -0
- package/src/host/meta/player/Participant.js +22 -0
- package/src/host/meta/player/ParticipantManager.js +247 -0
- package/src/host/meta/player/ScriptedParticipant.js +19 -0
- package/src/lib/AbstractTimer.js +84 -0
- package/src/lib/Publisher.js +43 -0
- package/src/lib/applyRoomOverrides.js +48 -0
- package/src/lib/buildClientConfig.js +48 -0
- package/src/lib/clientCoreConfig.js +59 -0
- package/src/lib/config.js +83 -0
- package/src/lib/coreConfig.js +60 -0
- package/src/lib/factory.js +21 -0
- package/src/lib/formatters.js +33 -0
- package/src/lib/gamePlugin.js +95 -0
- package/src/lib/jwt.js +103 -0
- package/src/lib/math.js +75 -0
- package/src/lib/rateLimiter.js +40 -0
- package/src/lib/sanitizers.js +17 -0
- package/src/lib/security.js +45 -0
- package/src/lib/validators.js +52 -0
package/src/lib/math.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Линейная интерполяция
|
|
3
|
+
* @param {number} a
|
|
4
|
+
* @param {number} b
|
|
5
|
+
* @param {number} t - значение от 0 до 1
|
|
6
|
+
*/
|
|
7
|
+
export const lerp = (a, b, t) => a + (b - a) * t;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Интерполяция угла в радианах по кратчайшему пути
|
|
11
|
+
* @param {number} a
|
|
12
|
+
* @param {number} b
|
|
13
|
+
* @param {number} t - значение от 0 до 1
|
|
14
|
+
*/
|
|
15
|
+
export const lerpAngle = (a, b, t) => {
|
|
16
|
+
let diff = b - a;
|
|
17
|
+
|
|
18
|
+
while (diff > Math.PI) {
|
|
19
|
+
diff -= Math.PI * 2;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
while (diff < -Math.PI) {
|
|
23
|
+
diff += Math.PI * 2;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return a + diff * t;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Ограничивает число в диапазоне [min, max]
|
|
31
|
+
* @param {number} value
|
|
32
|
+
* @param {number} min
|
|
33
|
+
* @param {number} max
|
|
34
|
+
* @returns {number}
|
|
35
|
+
*/
|
|
36
|
+
export const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Перевод градусов в радианы
|
|
40
|
+
* @param {number} degrees
|
|
41
|
+
* @returns {number}
|
|
42
|
+
*/
|
|
43
|
+
export const degToRad = degrees => degrees * (Math.PI / 180);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Перевод радиан в градусы
|
|
47
|
+
* @param {number} radians
|
|
48
|
+
* @returns {number}
|
|
49
|
+
*/
|
|
50
|
+
export const radToDeg = radians => radians * (180 / Math.PI);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Нормализует угол в радианах к диапазону [-PI, PI]
|
|
54
|
+
* @param {number} angle
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
57
|
+
export const normalizeAngle = angle => {
|
|
58
|
+
while (angle > Math.PI) {
|
|
59
|
+
angle -= Math.PI * 2;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
while (angle < -Math.PI) {
|
|
63
|
+
angle += Math.PI * 2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return angle;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Случайное число в диапазоне [min, max)
|
|
71
|
+
* @param {number} min
|
|
72
|
+
* @param {number} max
|
|
73
|
+
* @returns {number}
|
|
74
|
+
*/
|
|
75
|
+
export const randomRange = (min, max) => Math.random() * (max - min) + min;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Rate limiter с фиксированным окном: не более `limit` событий
|
|
2
|
+
// на ключ (например, IP) за `windowMs` миллисекунд.
|
|
3
|
+
export default class RateLimiter {
|
|
4
|
+
constructor({ limit, windowMs }) {
|
|
5
|
+
this._limit = limit;
|
|
6
|
+
this._windowMs = windowMs;
|
|
7
|
+
this._buckets = new Map(); // key -> { count, windowStart }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// регистрирует событие; false — лимит для ключа исчерпан
|
|
11
|
+
consume(key, now = Date.now()) {
|
|
12
|
+
const bucket = this._buckets.get(key);
|
|
13
|
+
|
|
14
|
+
if (!bucket || now - bucket.windowStart >= this._windowMs) {
|
|
15
|
+
this._buckets.set(key, { count: 1, windowStart: now });
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (bucket.count >= this._limit) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
bucket.count += 1;
|
|
24
|
+
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// удаляет истёкшие окна (периодическая уборка памяти)
|
|
29
|
+
sweep(now = Date.now()) {
|
|
30
|
+
for (const [key, bucket] of this._buckets) {
|
|
31
|
+
if (now - bucket.windowStart >= this._windowMs) {
|
|
32
|
+
this._buckets.delete(key);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
clear() {
|
|
38
|
+
this._buckets.clear();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Управляющие символы (переводы строк, табы и т.п.) ломают однострочный чат.
|
|
2
|
+
// eslint-disable-next-line no-control-regex
|
|
3
|
+
const controlCharsRegExp = /[\x00-\x1f\x7f]/g;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Убирает из сообщения управляющие символы. Не XSS-защита — экранирование
|
|
7
|
+
* на выводе (`textContent`/`setAttribute`).
|
|
8
|
+
* @param {string} message
|
|
9
|
+
* @returns {string} - Очищенная строка (пустая, если на входе не строка).
|
|
10
|
+
*/
|
|
11
|
+
export const sanitizeMessage = message => {
|
|
12
|
+
if (typeof message !== 'string') {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return message.replace(controlCharsRegExp, '');
|
|
17
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import config from './config.js';
|
|
2
|
+
|
|
3
|
+
// создаёт проверку origin для заданных protocol/domain/port
|
|
4
|
+
// (используется и игровым сервером, и мастер-сервером)
|
|
5
|
+
const createOriginValidator = function ({ protocol, domain, port }) {
|
|
6
|
+
return function (requestOrigin, cb) {
|
|
7
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
8
|
+
|
|
9
|
+
const allowedOrigins = [
|
|
10
|
+
// origin'ы для локальной разработки
|
|
11
|
+
`${protocol}//localhost:${port}`,
|
|
12
|
+
`${protocol}//127.0.0.1:${port}`,
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
if (isProduction) {
|
|
16
|
+
// в продакшене разрешен только настоящий домен по стандартному HTTPS
|
|
17
|
+
// порт не требуется, так как браузер не добавляет его
|
|
18
|
+
// для стандартного порта 443
|
|
19
|
+
allowedOrigins.push(`https://${domain}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let err = null;
|
|
23
|
+
|
|
24
|
+
if (!allowedOrigins.includes(requestOrigin)) {
|
|
25
|
+
err = `Blocked connection from invalid origin: ${requestOrigin}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
process.nextTick(() => cb(err));
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const origin = function (requestOrigin, cb) {
|
|
33
|
+
const validator = createOriginValidator({
|
|
34
|
+
protocol: config.get('server:protocol'),
|
|
35
|
+
domain: config.get('server:domain'),
|
|
36
|
+
port: config.get('server:port'),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
validator(requestOrigin, cb);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default {
|
|
43
|
+
origin,
|
|
44
|
+
createOriginValidator,
|
|
45
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const NAME_REGEXP = new RegExp('^[a-zA-Z]([\\w\\s#]{0,13})[\\w]{1}$');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Проверяет, является ли строка валидным именем пользователя.
|
|
5
|
+
* @param {string} name - Имя для проверки.
|
|
6
|
+
* @returns {boolean} - true, если имя валидно, иначе false.
|
|
7
|
+
*/
|
|
8
|
+
export const isValidName = name =>
|
|
9
|
+
typeof name === 'string' && NAME_REGEXP.test(name);
|
|
10
|
+
|
|
11
|
+
// движковые правила валидации; игровые (например isValidModel)
|
|
12
|
+
// инжектируются третьим аргументом validateAuth (authSchema игры)
|
|
13
|
+
const validationRules = {
|
|
14
|
+
isValidName,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Валидирует объект с данными для авторизации.
|
|
19
|
+
* @param {object} data - Объект с данными для проверки.
|
|
20
|
+
* @param {Array} authParams - Массив правил для валидации
|
|
21
|
+
* @param {Object} [validators] - Игровые валидаторы (имя → функция),
|
|
22
|
+
* дополняют движковые validationRules.
|
|
23
|
+
* @returns {Array|undefined} - Массив ошибок или undefined.
|
|
24
|
+
*/
|
|
25
|
+
export const validateAuth = (data, authParams, validators = {}) => {
|
|
26
|
+
const rules = { ...validationRules, ...validators };
|
|
27
|
+
const errors = [];
|
|
28
|
+
|
|
29
|
+
for (const { name, options } of authParams) {
|
|
30
|
+
if (!(name in data)) {
|
|
31
|
+
return [{ name, error: `Property is missing` }];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const value = data[name];
|
|
35
|
+
|
|
36
|
+
if (typeof value !== 'string') {
|
|
37
|
+
return [{ name, error: `Property must be a string` }];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (options?.validator) {
|
|
41
|
+
const validatorFn = rules[options.validator];
|
|
42
|
+
|
|
43
|
+
if (validatorFn && !validatorFn(value)) {
|
|
44
|
+
errors.push({ name, error: 'not valid' });
|
|
45
|
+
} else if (!validatorFn) {
|
|
46
|
+
console.warn(`Validator function '${options.validator}' not found.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return errors.length ? errors : undefined;
|
|
52
|
+
};
|