@alepot55/chessboardjs 2.2.1 → 2.3.2
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/.eslintrc.json +227 -0
- package/.github/instructions/copilot-instuctions.md +1671 -0
- package/README.md +125 -401
- package/assets/themes/alepot/theme.json +42 -0
- package/assets/themes/default/theme.json +42 -0
- package/chessboard.bundle.js +708 -58
- package/config/jest.config.js +15 -0
- package/config/rollup.config.js +35 -0
- package/dist/chessboard.cjs.js +10476 -0
- package/dist/chessboard.css +197 -0
- package/dist/chessboard.esm.js +10407 -0
- package/dist/chessboard.iife.js +10481 -0
- package/dist/chessboard.umd.js +10482 -0
- package/jest.config.js +2 -7
- package/package.json +18 -3
- package/rollup.config.js +2 -11
- package/{chessboard.move.js → src/components/Move.js} +3 -3
- package/src/components/Piece.js +273 -0
- package/{chessboard.square.js → src/components/Square.js} +60 -7
- package/src/constants/index.js +15 -0
- package/src/constants/positions.js +62 -0
- package/src/core/Chessboard.js +1930 -0
- package/src/core/ChessboardConfig.js +458 -0
- package/src/core/ChessboardFactory.js +385 -0
- package/src/core/index.js +141 -0
- package/src/errors/ChessboardError.js +133 -0
- package/src/errors/index.js +15 -0
- package/src/errors/messages.js +189 -0
- package/src/index.js +103 -0
- package/src/services/AnimationService.js +180 -0
- package/src/services/BoardService.js +156 -0
- package/src/services/CoordinateService.js +355 -0
- package/src/services/EventService.js +807 -0
- package/src/services/MoveService.js +594 -0
- package/src/services/PieceService.js +303 -0
- package/src/services/PositionService.js +237 -0
- package/src/services/ValidationService.js +673 -0
- package/src/services/index.js +14 -0
- package/src/styles/animations.css +46 -0
- package/{chessboard.css → src/styles/board.css} +3 -0
- package/src/styles/index.css +4 -0
- package/src/styles/pieces.css +66 -0
- package/src/utils/animations.js +37 -0
- package/{chess.js → src/utils/chess.js} +16 -16
- package/src/utils/coordinates.js +62 -0
- package/src/utils/cross-browser.js +150 -0
- package/src/utils/logger.js +422 -0
- package/src/utils/performance.js +311 -0
- package/src/utils/validation.js +458 -0
- package/tests/unit/chessboard-config-animations.test.js +106 -0
- package/tests/unit/chessboard-robust.test.js +163 -0
- package/tests/unit/chessboard.test.js +183 -0
- package/chessboard.config.js +0 -147
- package/chessboard.js +0 -979
- package/chessboard.piece.js +0 -115
- package/test/chessboard.test.js +0 -128
- /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
- /package/{.babelrc → config/.babelrc} +0 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service for input validation and data sanitization
|
|
3
|
+
* @module services/ValidationService
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PIECE_TYPES, PIECE_COLORS, BOARD_LETTERS } from '../constants/positions.js';
|
|
8
|
+
import { ValidationError } from '../errors/ChessboardError.js';
|
|
9
|
+
import { ERROR_MESSAGES, ERROR_CODES } from '../errors/messages.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validation patterns compiled for performance
|
|
13
|
+
* @constant
|
|
14
|
+
* @type {Object}
|
|
15
|
+
*/
|
|
16
|
+
const VALIDATION_PATTERNS = Object.freeze({
|
|
17
|
+
square: /^[a-h][1-8]$/,
|
|
18
|
+
piece: /^[prnbqkPRNBQK][wb]$/,
|
|
19
|
+
fen: /^([rnbqkpRNBQKP1-8]+\/){7}[rnbqkpRNBQKP1-8]+\s[wb]\s[KQkq-]+\s[a-h1-8-]+\s\d+\s\d+$/,
|
|
20
|
+
move: /^[a-h][1-8][a-h][1-8][qrnb]?$/,
|
|
21
|
+
color: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Valid values for various configuration options
|
|
26
|
+
* @constant
|
|
27
|
+
* @type {Object}
|
|
28
|
+
*/
|
|
29
|
+
const VALID_VALUES = Object.freeze({
|
|
30
|
+
orientations: ['w', 'b', 'white', 'black'],
|
|
31
|
+
colors: ['w', 'b', 'white', 'black'],
|
|
32
|
+
movableColors: ['w', 'b', 'white', 'black', 'both', 'none'],
|
|
33
|
+
dropOffBoard: ['snapback', 'trash'],
|
|
34
|
+
easingTypes: ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'],
|
|
35
|
+
animationStyles: ['sequential', 'simultaneous'],
|
|
36
|
+
modes: ['normal', 'creative', 'analysis'],
|
|
37
|
+
promotionPieces: ['q', 'r', 'b', 'n', 'Q', 'R', 'B', 'N']
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Size constraints for validation
|
|
42
|
+
* @constant
|
|
43
|
+
* @type {Object}
|
|
44
|
+
*/
|
|
45
|
+
const SIZE_CONSTRAINTS = Object.freeze({
|
|
46
|
+
min: 50,
|
|
47
|
+
max: 2000,
|
|
48
|
+
maxTime: 10000,
|
|
49
|
+
maxDelay: 5000
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Service responsible for validating inputs and data
|
|
54
|
+
* Implements caching for performance optimization
|
|
55
|
+
* @class
|
|
56
|
+
*/
|
|
57
|
+
export class ValidationService {
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new ValidationService instance
|
|
60
|
+
*/
|
|
61
|
+
constructor() {
|
|
62
|
+
// Cache for validation results to improve performance
|
|
63
|
+
this._validationCache = new Map();
|
|
64
|
+
this._cacheMaxSize = 1000;
|
|
65
|
+
|
|
66
|
+
// Compile patterns for reuse
|
|
67
|
+
this._patterns = VALIDATION_PATTERNS;
|
|
68
|
+
this._validValues = VALID_VALUES;
|
|
69
|
+
this._constraints = SIZE_CONSTRAINTS;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validates a square identifier with caching
|
|
74
|
+
* @param {string} square - Square to validate (e.g., 'e4')
|
|
75
|
+
* @returns {boolean} True if valid
|
|
76
|
+
*/
|
|
77
|
+
isValidSquare(square) {
|
|
78
|
+
const cacheKey = `square:${square}`;
|
|
79
|
+
|
|
80
|
+
if (this._validationCache.has(cacheKey)) {
|
|
81
|
+
return this._validationCache.get(cacheKey);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const isValid = typeof square === 'string' &&
|
|
85
|
+
square.length === 2 &&
|
|
86
|
+
this._patterns.square.test(square);
|
|
87
|
+
|
|
88
|
+
this._cacheValidationResult(cacheKey, isValid);
|
|
89
|
+
return isValid;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Validates a piece identifier with enhanced format support
|
|
94
|
+
* @param {string} piece - Piece to validate (e.g., 'wK', 'bp')
|
|
95
|
+
* @returns {boolean} True if valid
|
|
96
|
+
*/
|
|
97
|
+
isValidPiece(piece) {
|
|
98
|
+
if (typeof piece !== 'string' || piece.length !== 2) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const cacheKey = `piece:${piece}`;
|
|
103
|
+
|
|
104
|
+
if (this._validationCache.has(cacheKey)) {
|
|
105
|
+
return this._validationCache.get(cacheKey);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const [first, second] = piece.split('');
|
|
109
|
+
|
|
110
|
+
// Check both formats: [type][color] and [color][type]
|
|
111
|
+
const format1 = PIECE_TYPES.includes(first.toLowerCase()) &&
|
|
112
|
+
PIECE_COLORS.includes(second);
|
|
113
|
+
const format2 = PIECE_COLORS.includes(first) &&
|
|
114
|
+
PIECE_TYPES.includes(second.toLowerCase());
|
|
115
|
+
|
|
116
|
+
const isValid = format1 || format2;
|
|
117
|
+
this._cacheValidationResult(cacheKey, isValid);
|
|
118
|
+
return isValid;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validates a FEN string with comprehensive checks
|
|
123
|
+
* @param {string} fen - FEN string to validate
|
|
124
|
+
* @returns {boolean} True if valid
|
|
125
|
+
*/
|
|
126
|
+
isValidFen(fen) {
|
|
127
|
+
if (typeof fen !== 'string') {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const cacheKey = `fen:${fen}`;
|
|
132
|
+
|
|
133
|
+
if (this._validationCache.has(cacheKey)) {
|
|
134
|
+
return this._validationCache.get(cacheKey);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Basic pattern check
|
|
138
|
+
if (!this._patterns.fen.test(fen)) {
|
|
139
|
+
this._cacheValidationResult(cacheKey, false);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Additional FEN validation
|
|
144
|
+
const isValid = this._validateFenStructure(fen);
|
|
145
|
+
this._cacheValidationResult(cacheKey, isValid);
|
|
146
|
+
return isValid;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Validates FEN structure in detail
|
|
151
|
+
* @private
|
|
152
|
+
* @param {string} fen - FEN string to validate
|
|
153
|
+
* @returns {boolean} True if valid
|
|
154
|
+
*/
|
|
155
|
+
_validateFenStructure(fen) {
|
|
156
|
+
const parts = fen.split(' ');
|
|
157
|
+
|
|
158
|
+
if (parts.length !== 6) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Validate piece placement
|
|
163
|
+
const ranks = parts[0].split('/');
|
|
164
|
+
if (ranks.length !== 8) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Validate each rank
|
|
169
|
+
for (const rank of ranks) {
|
|
170
|
+
if (!this._validateRank(rank)) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Validate active color
|
|
176
|
+
if (!['w', 'b'].includes(parts[1])) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Validate castling rights
|
|
181
|
+
if (!/^[KQkq-]*$/.test(parts[2])) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Validate en passant target
|
|
186
|
+
if (parts[3] !== '-' && !this.isValidSquare(parts[3])) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Validate halfmove clock and fullmove number
|
|
191
|
+
const halfmove = parseInt(parts[4], 10);
|
|
192
|
+
const fullmove = parseInt(parts[5], 10);
|
|
193
|
+
|
|
194
|
+
return !isNaN(halfmove) && !isNaN(fullmove) &&
|
|
195
|
+
halfmove >= 0 && fullmove >= 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Validates a single rank in FEN notation
|
|
200
|
+
* @private
|
|
201
|
+
* @param {string} rank - Rank to validate
|
|
202
|
+
* @returns {boolean} True if valid
|
|
203
|
+
*/
|
|
204
|
+
_validateRank(rank) {
|
|
205
|
+
let squares = 0;
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < rank.length; i++) {
|
|
208
|
+
const char = rank[i];
|
|
209
|
+
|
|
210
|
+
if (/[1-8]/.test(char)) {
|
|
211
|
+
squares += parseInt(char, 10);
|
|
212
|
+
} else if (/[prnbqkPRNBQK]/.test(char)) {
|
|
213
|
+
squares += 1;
|
|
214
|
+
} else {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return squares === 8;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Validates a move string with comprehensive format support
|
|
224
|
+
* @param {string} move - Move string to validate (e.g., 'e2e4', 'e7e8q')
|
|
225
|
+
* @returns {boolean} True if valid
|
|
226
|
+
*/
|
|
227
|
+
isValidMove(move) {
|
|
228
|
+
if (typeof move !== 'string') {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const cacheKey = `move:${move}`;
|
|
233
|
+
|
|
234
|
+
if (this._validationCache.has(cacheKey)) {
|
|
235
|
+
return this._validationCache.get(cacheKey);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const isValid = this._validateMoveFormat(move);
|
|
239
|
+
this._cacheValidationResult(cacheKey, isValid);
|
|
240
|
+
return isValid;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Validates move format in detail
|
|
245
|
+
* @private
|
|
246
|
+
* @param {string} move - Move string to validate
|
|
247
|
+
* @returns {boolean} True if valid
|
|
248
|
+
*/
|
|
249
|
+
_validateMoveFormat(move) {
|
|
250
|
+
if (move.length < 4 || move.length > 5) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const from = move.slice(0, 2);
|
|
255
|
+
const to = move.slice(2, 4);
|
|
256
|
+
const promotion = move.slice(4, 5);
|
|
257
|
+
|
|
258
|
+
if (!this.isValidSquare(from) || !this.isValidSquare(to)) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (promotion && !this._validValues.promotionPieces.includes(promotion)) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Additional move validation (e.g., source and target different)
|
|
267
|
+
return from !== to;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Validates board orientation
|
|
272
|
+
* @param {string} orientation - Orientation to validate
|
|
273
|
+
* @returns {boolean} True if valid
|
|
274
|
+
*/
|
|
275
|
+
isValidOrientation(orientation) {
|
|
276
|
+
return this._validValues.orientations.includes(orientation);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Validates a color value
|
|
281
|
+
* @param {string} color - Color to validate
|
|
282
|
+
* @returns {boolean} True if valid
|
|
283
|
+
*/
|
|
284
|
+
isValidColor(color) {
|
|
285
|
+
return this._validValues.colors.includes(color);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Validates a size value with constraints
|
|
290
|
+
* @param {number|string} size - Size to validate
|
|
291
|
+
* @returns {boolean} True if valid
|
|
292
|
+
*/
|
|
293
|
+
isValidSize(size) {
|
|
294
|
+
if (size === 'auto') {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (typeof size === 'number') {
|
|
299
|
+
return size >= this._constraints.min && size <= this._constraints.max;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Validates a time value with constraints
|
|
307
|
+
* @param {number} time - Time value to validate
|
|
308
|
+
* @returns {boolean} True if valid
|
|
309
|
+
*/
|
|
310
|
+
isValidTime(time) {
|
|
311
|
+
return typeof time === 'number' &&
|
|
312
|
+
time >= 0 &&
|
|
313
|
+
time <= this._constraints.maxTime;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Validates a callback function
|
|
318
|
+
* @param {Function} callback - Callback to validate
|
|
319
|
+
* @returns {boolean} True if valid
|
|
320
|
+
*/
|
|
321
|
+
isValidCallback(callback) {
|
|
322
|
+
return typeof callback === 'function';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Validates a DOM element ID
|
|
327
|
+
* @param {string} id - Element ID to validate
|
|
328
|
+
* @returns {boolean} True if valid
|
|
329
|
+
*/
|
|
330
|
+
isValidElementId(id) {
|
|
331
|
+
return typeof id === 'string' &&
|
|
332
|
+
id.length > 0 &&
|
|
333
|
+
id.length <= 100 && // Reasonable length limit
|
|
334
|
+
/^[a-zA-Z][\w:-]*$/.test(id); // Valid HTML ID format
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Validates movable colors configuration
|
|
339
|
+
* @param {string} colors - Colors value to validate
|
|
340
|
+
* @returns {boolean} True if valid
|
|
341
|
+
*/
|
|
342
|
+
isValidMovableColors(colors) {
|
|
343
|
+
return this._validValues.movableColors.includes(colors);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Validates drop off board configuration
|
|
348
|
+
* @param {string} dropOff - Drop off board value to validate
|
|
349
|
+
* @returns {boolean} True if valid
|
|
350
|
+
*/
|
|
351
|
+
isValidDropOffBoard(dropOff) {
|
|
352
|
+
return this._validValues.dropOffBoard.includes(dropOff);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Validates animation easing type
|
|
357
|
+
* @param {string} easing - Easing type to validate
|
|
358
|
+
* @returns {boolean} True if valid
|
|
359
|
+
*/
|
|
360
|
+
isValidEasing(easing) {
|
|
361
|
+
return this._validValues.easingTypes.includes(easing);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Validates animation style
|
|
366
|
+
* @param {string} style - Animation style to validate
|
|
367
|
+
* @returns {boolean} True if valid
|
|
368
|
+
*/
|
|
369
|
+
isValidAnimationStyle(style) {
|
|
370
|
+
return this._validValues.animationStyles.includes(style);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Validates CSS color format
|
|
375
|
+
* @param {string} color - Color string to validate
|
|
376
|
+
* @returns {boolean} True if valid
|
|
377
|
+
*/
|
|
378
|
+
isValidCSSColor(color) {
|
|
379
|
+
if (typeof color !== 'string') {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Check for hex colors
|
|
384
|
+
if (this._patterns.color.test(color)) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Check for named colors (basic set)
|
|
389
|
+
const namedColors = ['white', 'black', 'red', 'green', 'blue', 'yellow', 'cyan', 'magenta'];
|
|
390
|
+
if (namedColors.includes(color.toLowerCase())) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Check for rgb/rgba format
|
|
395
|
+
if (/^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/.test(color) ||
|
|
396
|
+
/^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[0-1](\.\d+)?\s*\)$/.test(color)) {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Validates and sanitizes a square identifier
|
|
405
|
+
* @param {string} square - Square to validate
|
|
406
|
+
* @returns {string} Sanitized square
|
|
407
|
+
* @throws {ValidationError} If square is invalid
|
|
408
|
+
*/
|
|
409
|
+
validateSquare(square) {
|
|
410
|
+
if (!this.isValidSquare(square)) {
|
|
411
|
+
throw new ValidationError(
|
|
412
|
+
ERROR_MESSAGES.invalid_square + square,
|
|
413
|
+
'square',
|
|
414
|
+
square
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return square.toLowerCase();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Validates and sanitizes a piece identifier
|
|
422
|
+
* @param {string} piece - Piece to validate
|
|
423
|
+
* @returns {string} Sanitized piece
|
|
424
|
+
* @throws {ValidationError} If piece is invalid
|
|
425
|
+
*/
|
|
426
|
+
validatePiece(piece) {
|
|
427
|
+
if (!this.isValidPiece(piece)) {
|
|
428
|
+
throw new ValidationError(
|
|
429
|
+
ERROR_MESSAGES.invalid_piece + piece,
|
|
430
|
+
'piece',
|
|
431
|
+
piece
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
return piece.toLowerCase();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Validates and sanitizes a FEN string
|
|
439
|
+
* @param {string} fen - FEN to validate
|
|
440
|
+
* @returns {string} Sanitized FEN
|
|
441
|
+
* @throws {ValidationError} If FEN is invalid
|
|
442
|
+
*/
|
|
443
|
+
validateFen(fen) {
|
|
444
|
+
if (!this.isValidFen(fen)) {
|
|
445
|
+
throw new ValidationError(
|
|
446
|
+
ERROR_MESSAGES.invalid_fen + fen,
|
|
447
|
+
'fen',
|
|
448
|
+
fen
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
return fen.trim();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Validates and sanitizes a move string
|
|
456
|
+
* @param {string} move - Move to validate
|
|
457
|
+
* @returns {string} Sanitized move
|
|
458
|
+
* @throws {ValidationError} If move is invalid
|
|
459
|
+
*/
|
|
460
|
+
validateMove(move) {
|
|
461
|
+
if (!this.isValidMove(move)) {
|
|
462
|
+
throw new ValidationError(
|
|
463
|
+
ERROR_MESSAGES.invalid_move + move,
|
|
464
|
+
'move',
|
|
465
|
+
move
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
return move.toLowerCase();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Validates and sanitizes orientation
|
|
473
|
+
* @param {string} orientation - Orientation to validate
|
|
474
|
+
* @returns {string} Sanitized orientation ('w' or 'b')
|
|
475
|
+
* @throws {ValidationError} If orientation is invalid
|
|
476
|
+
*/
|
|
477
|
+
validateOrientation(orientation) {
|
|
478
|
+
if (!this.isValidOrientation(orientation)) {
|
|
479
|
+
throw new ValidationError(
|
|
480
|
+
ERROR_MESSAGES.invalid_orientation + orientation,
|
|
481
|
+
'orientation',
|
|
482
|
+
orientation
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Normalize to 'w' or 'b'
|
|
487
|
+
return orientation === 'white' ? 'w' :
|
|
488
|
+
orientation === 'black' ? 'b' :
|
|
489
|
+
orientation;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Validates and sanitizes color
|
|
494
|
+
* @param {string} color - Color to validate
|
|
495
|
+
* @returns {string} Sanitized color ('w' or 'b')
|
|
496
|
+
* @throws {ValidationError} If color is invalid
|
|
497
|
+
*/
|
|
498
|
+
validateColor(color) {
|
|
499
|
+
if (!this.isValidColor(color)) {
|
|
500
|
+
throw new ValidationError(
|
|
501
|
+
ERROR_MESSAGES.invalid_color + color,
|
|
502
|
+
'color',
|
|
503
|
+
color
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Normalize to 'w' or 'b'
|
|
508
|
+
return color === 'white' ? 'w' :
|
|
509
|
+
color === 'black' ? 'b' :
|
|
510
|
+
color;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Validates and sanitizes size
|
|
515
|
+
* @param {number|string} size - Size to validate
|
|
516
|
+
* @returns {number|string} Sanitized size
|
|
517
|
+
* @throws {ValidationError} If size is invalid
|
|
518
|
+
*/
|
|
519
|
+
validateSize(size) {
|
|
520
|
+
if (!this.isValidSize(size)) {
|
|
521
|
+
throw new ValidationError(
|
|
522
|
+
ERROR_MESSAGES.invalid_size + size,
|
|
523
|
+
'size',
|
|
524
|
+
size
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
return size;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Validates configuration object with comprehensive checks
|
|
532
|
+
* @param {Object} config - Configuration to validate
|
|
533
|
+
* @returns {Object} Validated configuration
|
|
534
|
+
* @throws {ValidationError} If configuration is invalid
|
|
535
|
+
*/
|
|
536
|
+
validateConfig(config) {
|
|
537
|
+
if (!config || typeof config !== 'object') {
|
|
538
|
+
throw new ValidationError(
|
|
539
|
+
'Configuration must be an object',
|
|
540
|
+
'config',
|
|
541
|
+
config
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const errors = [];
|
|
546
|
+
|
|
547
|
+
// Validate required fields
|
|
548
|
+
if (!config.id && !config.id_div) {
|
|
549
|
+
errors.push('Configuration must include id or id_div');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Validate optional fields
|
|
553
|
+
if (config.orientation && !this.isValidOrientation(config.orientation)) {
|
|
554
|
+
errors.push(ERROR_MESSAGES.invalid_orientation + config.orientation);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (config.size && !this.isValidSize(config.size)) {
|
|
558
|
+
errors.push(ERROR_MESSAGES.invalid_size + config.size);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (config.movableColors && !this.isValidMovableColors(config.movableColors)) {
|
|
562
|
+
errors.push(ERROR_MESSAGES.invalid_color + config.movableColors);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (config.dropOffBoard && !this.isValidDropOffBoard(config.dropOffBoard)) {
|
|
566
|
+
errors.push(ERROR_MESSAGES.invalid_dropOffBoard + config.dropOffBoard);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (config.animationStyle && !this.isValidAnimationStyle(config.animationStyle)) {
|
|
570
|
+
errors.push(ERROR_MESSAGES.invalid_animationStyle + config.animationStyle);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Validate callbacks
|
|
574
|
+
const callbacks = ['onMove', 'onMoveEnd', 'onChange', 'onDragStart', 'onDragMove', 'onDrop', 'onSnapbackEnd'];
|
|
575
|
+
for (const callback of callbacks) {
|
|
576
|
+
if (config[callback] && !this.isValidCallback(config[callback])) {
|
|
577
|
+
errors.push(`Invalid ${callback} callback`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Validate colors
|
|
582
|
+
const colorFields = ['whiteSquare', 'blackSquare', 'highlight', 'hintColor'];
|
|
583
|
+
for (const field of colorFields) {
|
|
584
|
+
if (config[field] && !this.isValidCSSColor(config[field])) {
|
|
585
|
+
errors.push(`Invalid ${field} color: ${config[field]}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (errors.length > 0) {
|
|
590
|
+
throw new ValidationError(
|
|
591
|
+
`Configuration validation failed: ${errors.join(', ')}`,
|
|
592
|
+
'config',
|
|
593
|
+
config
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return config;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Caches validation result for performance
|
|
602
|
+
* @private
|
|
603
|
+
* @param {string} key - Cache key
|
|
604
|
+
* @param {boolean} result - Validation result
|
|
605
|
+
*/
|
|
606
|
+
_cacheValidationResult(key, result) {
|
|
607
|
+
if (this._validationCache.size >= this._cacheMaxSize) {
|
|
608
|
+
// Remove oldest entry
|
|
609
|
+
const firstKey = this._validationCache.keys().next().value;
|
|
610
|
+
this._validationCache.delete(firstKey);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
this._validationCache.set(key, result);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Clears the validation cache
|
|
618
|
+
*/
|
|
619
|
+
clearCache() {
|
|
620
|
+
this._validationCache.clear();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Gets validation cache statistics
|
|
625
|
+
* @returns {Object} Cache statistics
|
|
626
|
+
*/
|
|
627
|
+
getCacheStats() {
|
|
628
|
+
return {
|
|
629
|
+
size: this._validationCache.size,
|
|
630
|
+
maxSize: this._cacheMaxSize,
|
|
631
|
+
hitRate: this._cacheHits / (this._cacheHits + this._cacheMisses) || 0
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Batch validates multiple values
|
|
637
|
+
* @param {Array} validations - Array of validation objects
|
|
638
|
+
* @returns {Array} Array of validation results
|
|
639
|
+
*/
|
|
640
|
+
batchValidate(validations) {
|
|
641
|
+
return validations.map(validation => {
|
|
642
|
+
try {
|
|
643
|
+
const { type, value } = validation;
|
|
644
|
+
|
|
645
|
+
switch (type) {
|
|
646
|
+
case 'square':
|
|
647
|
+
return { valid: this.isValidSquare(value), value };
|
|
648
|
+
case 'piece':
|
|
649
|
+
return { valid: this.isValidPiece(value), value };
|
|
650
|
+
case 'fen':
|
|
651
|
+
return { valid: this.isValidFen(value), value };
|
|
652
|
+
case 'move':
|
|
653
|
+
return { valid: this.isValidMove(value), value };
|
|
654
|
+
default:
|
|
655
|
+
return { valid: false, value, error: 'Unknown validation type' };
|
|
656
|
+
}
|
|
657
|
+
} catch (error) {
|
|
658
|
+
return { valid: false, value: validation.value, error: error.message };
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Cleans up resources
|
|
665
|
+
*/
|
|
666
|
+
destroy() {
|
|
667
|
+
this.clearCache();
|
|
668
|
+
this._validationCache = null;
|
|
669
|
+
this._patterns = null;
|
|
670
|
+
this._validValues = null;
|
|
671
|
+
this._constraints = null;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Services module exports
|
|
3
|
+
* @module services
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { AnimationService } from './AnimationService.js';
|
|
8
|
+
export { BoardService } from './BoardService.js';
|
|
9
|
+
export { CoordinateService } from './CoordinateService.js';
|
|
10
|
+
export { EventService } from './EventService.js';
|
|
11
|
+
export { MoveService } from './MoveService.js';
|
|
12
|
+
export { PieceService } from './PieceService.js';
|
|
13
|
+
export { PositionService } from './PositionService.js';
|
|
14
|
+
export { ValidationService } from './ValidationService.js';
|