@xivdyetools/core 1.3.7
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/LICENSE +37 -0
- package/README.md +400 -0
- package/dist/constants/index.d.ts +56 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +103 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/data/colors_xiv.json +3130 -0
- package/dist/data/locales/de.json +231 -0
- package/dist/data/locales/en.json +231 -0
- package/dist/data/locales/fr.json +231 -0
- package/dist/data/locales/ja.json +231 -0
- package/dist/data/locales/ko.json +233 -0
- package/dist/data/locales/zh.json +233 -0
- package/dist/data/presets.json +390 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/services/APIService.d.ts +246 -0
- package/dist/services/APIService.d.ts.map +1 -0
- package/dist/services/APIService.js +499 -0
- package/dist/services/APIService.js.map +1 -0
- package/dist/services/ColorService.d.ts +146 -0
- package/dist/services/ColorService.d.ts.map +1 -0
- package/dist/services/ColorService.js +209 -0
- package/dist/services/ColorService.js.map +1 -0
- package/dist/services/DyeService.d.ts +230 -0
- package/dist/services/DyeService.d.ts.map +1 -0
- package/dist/services/DyeService.js +326 -0
- package/dist/services/DyeService.js.map +1 -0
- package/dist/services/LocalizationService.d.ts +338 -0
- package/dist/services/LocalizationService.d.ts.map +1 -0
- package/dist/services/LocalizationService.js +449 -0
- package/dist/services/LocalizationService.js.map +1 -0
- package/dist/services/PaletteService.d.ts +137 -0
- package/dist/services/PaletteService.d.ts.map +1 -0
- package/dist/services/PaletteService.js +349 -0
- package/dist/services/PaletteService.js.map +1 -0
- package/dist/services/PresetService.d.ts +196 -0
- package/dist/services/PresetService.d.ts.map +1 -0
- package/dist/services/PresetService.js +261 -0
- package/dist/services/PresetService.js.map +1 -0
- package/dist/services/color/ColorAccessibility.d.ts +39 -0
- package/dist/services/color/ColorAccessibility.d.ts.map +1 -0
- package/dist/services/color/ColorAccessibility.js +71 -0
- package/dist/services/color/ColorAccessibility.js.map +1 -0
- package/dist/services/color/ColorConverter.d.ts +146 -0
- package/dist/services/color/ColorConverter.d.ts.map +1 -0
- package/dist/services/color/ColorConverter.js +393 -0
- package/dist/services/color/ColorConverter.js.map +1 -0
- package/dist/services/color/ColorManipulator.d.ts +36 -0
- package/dist/services/color/ColorManipulator.d.ts.map +1 -0
- package/dist/services/color/ColorManipulator.js +56 -0
- package/dist/services/color/ColorManipulator.js.map +1 -0
- package/dist/services/color/ColorblindnessSimulator.d.ts +35 -0
- package/dist/services/color/ColorblindnessSimulator.d.ts.map +1 -0
- package/dist/services/color/ColorblindnessSimulator.js +110 -0
- package/dist/services/color/ColorblindnessSimulator.js.map +1 -0
- package/dist/services/dye/DyeDatabase.d.ts +131 -0
- package/dist/services/dye/DyeDatabase.d.ts.map +1 -0
- package/dist/services/dye/DyeDatabase.js +367 -0
- package/dist/services/dye/DyeDatabase.js.map +1 -0
- package/dist/services/dye/DyeSearch.d.ts +55 -0
- package/dist/services/dye/DyeSearch.d.ts.map +1 -0
- package/dist/services/dye/DyeSearch.js +196 -0
- package/dist/services/dye/DyeSearch.js.map +1 -0
- package/dist/services/dye/HarmonyGenerator.d.ts +110 -0
- package/dist/services/dye/HarmonyGenerator.d.ts.map +1 -0
- package/dist/services/dye/HarmonyGenerator.js +221 -0
- package/dist/services/dye/HarmonyGenerator.js.map +1 -0
- package/dist/services/localization/LocaleLoader.d.ts +38 -0
- package/dist/services/localization/LocaleLoader.d.ts.map +1 -0
- package/dist/services/localization/LocaleLoader.js +83 -0
- package/dist/services/localization/LocaleLoader.js.map +1 -0
- package/dist/services/localization/LocaleRegistry.d.ts +73 -0
- package/dist/services/localization/LocaleRegistry.d.ts.map +1 -0
- package/dist/services/localization/LocaleRegistry.js +84 -0
- package/dist/services/localization/LocaleRegistry.js.map +1 -0
- package/dist/services/localization/TranslationProvider.d.ts +157 -0
- package/dist/services/localization/TranslationProvider.d.ts.map +1 -0
- package/dist/services/localization/TranslationProvider.js +289 -0
- package/dist/services/localization/TranslationProvider.js.map +1 -0
- package/dist/types/index.d.ts +409 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +87 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/logger.d.ts +84 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/logger.js +54 -0
- package/dist/types/logger.js.map +1 -0
- package/dist/utils/index.d.ts +441 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +577 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/kd-tree.d.ts +76 -0
- package/dist/utils/kd-tree.d.ts.map +1 -0
- package/dist/utils/kd-tree.js +195 -0
- package/dist/utils/kd-tree.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xivdyetools/core - API Service
|
|
3
|
+
*
|
|
4
|
+
* Universalis API integration with caching and debouncing
|
|
5
|
+
* Environment-agnostic with pluggable cache backends
|
|
6
|
+
*
|
|
7
|
+
* @module services/APIService
|
|
8
|
+
*/
|
|
9
|
+
import { ErrorCode, AppError, NoOpLogger } from '../types/index.js';
|
|
10
|
+
import { UNIVERSALIS_API_BASE, UNIVERSALIS_API_TIMEOUT, UNIVERSALIS_API_RETRY_COUNT, UNIVERSALIS_API_RETRY_DELAY, API_CACHE_TTL, API_RATE_LIMIT_DELAY, API_CACHE_VERSION, API_MAX_RESPONSE_SIZE, } from '../constants/index.js';
|
|
11
|
+
import { retry, sleep, generateChecksum } from '../utils/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Default fetch client using global fetch API
|
|
14
|
+
*/
|
|
15
|
+
export class DefaultFetchClient {
|
|
16
|
+
async fetch(url, options) {
|
|
17
|
+
return fetch(url, options);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Default rate limiter with configurable minimum delay between requests
|
|
22
|
+
*/
|
|
23
|
+
export class DefaultRateLimiter {
|
|
24
|
+
/**
|
|
25
|
+
* Constructor with optional minimum delay
|
|
26
|
+
* @param minDelay Minimum milliseconds between requests (default: API_RATE_LIMIT_DELAY)
|
|
27
|
+
*/
|
|
28
|
+
constructor(minDelay = API_RATE_LIMIT_DELAY) {
|
|
29
|
+
this.minDelay = minDelay;
|
|
30
|
+
this.lastRequestTime = 0;
|
|
31
|
+
}
|
|
32
|
+
async waitIfNeeded() {
|
|
33
|
+
const timeSinceLastRequest = Date.now() - this.lastRequestTime;
|
|
34
|
+
if (timeSinceLastRequest < this.minDelay) {
|
|
35
|
+
await sleep(this.minDelay - timeSinceLastRequest);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
recordRequest() {
|
|
39
|
+
this.lastRequestTime = Date.now();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* In-memory cache backend (default, no persistence)
|
|
44
|
+
*/
|
|
45
|
+
export class MemoryCacheBackend {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.cache = new Map();
|
|
48
|
+
}
|
|
49
|
+
get(key) {
|
|
50
|
+
return this.cache.get(key) || null;
|
|
51
|
+
}
|
|
52
|
+
set(key, value) {
|
|
53
|
+
this.cache.set(key, value);
|
|
54
|
+
}
|
|
55
|
+
delete(key) {
|
|
56
|
+
this.cache.delete(key);
|
|
57
|
+
}
|
|
58
|
+
clear() {
|
|
59
|
+
this.cache.clear();
|
|
60
|
+
}
|
|
61
|
+
keys() {
|
|
62
|
+
return Array.from(this.cache.keys());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Service for Universalis API integration
|
|
67
|
+
* Handles price data fetching with caching and debouncing
|
|
68
|
+
*
|
|
69
|
+
* Refactored for testability: Supports dependency injection of cache, fetch client, and rate limiter
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // With default implementations (memory cache, global fetch, standard rate limiting)
|
|
73
|
+
* const apiService = new APIService();
|
|
74
|
+
*
|
|
75
|
+
* // With custom cache backend (e.g., Redis)
|
|
76
|
+
* const redisCache = new RedisCacheBackend(redisClient);
|
|
77
|
+
* const apiService = new APIService(redisCache);
|
|
78
|
+
*
|
|
79
|
+
* // With custom fetch client for testing
|
|
80
|
+
* const mockFetch = new MockFetchClient();
|
|
81
|
+
* const apiService = new APIService(undefined, mockFetch);
|
|
82
|
+
*
|
|
83
|
+
* // With custom rate limiter (e.g., no rate limiting for tests)
|
|
84
|
+
* const noRateLimit = new NoOpRateLimiter();
|
|
85
|
+
* const apiService = new APIService(undefined, undefined, noRateLimit);
|
|
86
|
+
*
|
|
87
|
+
* // Fetch price data
|
|
88
|
+
* const priceData = await apiService.getPriceData(itemID, worldID, dataCenterID);
|
|
89
|
+
*
|
|
90
|
+
* // With custom logger for debugging
|
|
91
|
+
* import { ConsoleLogger } from 'xivdyetools-core';
|
|
92
|
+
* const apiService = new APIService({ logger: ConsoleLogger });
|
|
93
|
+
*/
|
|
94
|
+
export class APIService {
|
|
95
|
+
/**
|
|
96
|
+
* Constructor with optional dependency injection
|
|
97
|
+
* @param options Configuration options or legacy cache backend
|
|
98
|
+
*/
|
|
99
|
+
constructor(options, fetchClient, rateLimiter) {
|
|
100
|
+
this.pendingRequests = new Map();
|
|
101
|
+
// Support both legacy positional arguments and new options object
|
|
102
|
+
if (options && 'logger' in options) {
|
|
103
|
+
// New options-based API
|
|
104
|
+
this.cache = options.cacheBackend ?? new MemoryCacheBackend();
|
|
105
|
+
this.fetchClient = options.fetchClient ?? new DefaultFetchClient();
|
|
106
|
+
this.rateLimiter = options.rateLimiter ?? new DefaultRateLimiter();
|
|
107
|
+
this.logger = options.logger ?? NoOpLogger;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Legacy positional arguments API
|
|
111
|
+
this.cache = options ?? new MemoryCacheBackend();
|
|
112
|
+
this.fetchClient = fetchClient ?? new DefaultFetchClient();
|
|
113
|
+
this.rateLimiter = rateLimiter ?? new DefaultRateLimiter();
|
|
114
|
+
this.logger = NoOpLogger;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Cache Management
|
|
119
|
+
// ============================================================================
|
|
120
|
+
/**
|
|
121
|
+
* Get price from cache if available and not expired
|
|
122
|
+
* Validates cache version and checksum
|
|
123
|
+
*/
|
|
124
|
+
async getCachedPrice(cacheKey) {
|
|
125
|
+
const cached = await this.cache.get(cacheKey);
|
|
126
|
+
if (!cached) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
// Check cache version
|
|
130
|
+
if (cached.version && cached.version !== API_CACHE_VERSION) {
|
|
131
|
+
await this.cache.delete(cacheKey);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
// Check if cache has expired
|
|
135
|
+
if (Date.now() - cached.timestamp > cached.ttl) {
|
|
136
|
+
await this.cache.delete(cacheKey);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
// Validate checksum if present
|
|
140
|
+
if (cached.checksum) {
|
|
141
|
+
const computedChecksum = generateChecksum(cached.data);
|
|
142
|
+
if (computedChecksum !== cached.checksum) {
|
|
143
|
+
this.logger.warn(`Cache corruption detected for key: ${cacheKey}`);
|
|
144
|
+
await this.cache.delete(cacheKey);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return cached.data;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Set price in cache with version and checksum
|
|
152
|
+
*/
|
|
153
|
+
async setCachedPrice(cacheKey, data) {
|
|
154
|
+
const checksum = generateChecksum(data);
|
|
155
|
+
await this.cache.set(cacheKey, {
|
|
156
|
+
data,
|
|
157
|
+
timestamp: Date.now(),
|
|
158
|
+
ttl: API_CACHE_TTL,
|
|
159
|
+
version: API_CACHE_VERSION,
|
|
160
|
+
checksum,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Clear cache
|
|
165
|
+
*/
|
|
166
|
+
async clearCache() {
|
|
167
|
+
await this.cache.clear();
|
|
168
|
+
this.logger.info('Price cache cleared');
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get cache stats
|
|
172
|
+
*/
|
|
173
|
+
async getCacheStats() {
|
|
174
|
+
const keys = await this.cache.keys();
|
|
175
|
+
return { size: keys.length, keys };
|
|
176
|
+
}
|
|
177
|
+
// ============================================================================
|
|
178
|
+
// API Calls
|
|
179
|
+
// ============================================================================
|
|
180
|
+
/**
|
|
181
|
+
* Fetch price data for a dye from Universalis API
|
|
182
|
+
* Implements caching, debouncing, and retry logic
|
|
183
|
+
*/
|
|
184
|
+
async getPriceData(itemID, worldID, dataCenterID) {
|
|
185
|
+
try {
|
|
186
|
+
// Build cache key
|
|
187
|
+
const cacheKey = this.buildCacheKey(itemID, worldID, dataCenterID);
|
|
188
|
+
// Check cache first
|
|
189
|
+
const cached = await this.getCachedPrice(cacheKey);
|
|
190
|
+
if (cached) {
|
|
191
|
+
this.logger.info(`Price cache hit for item ${itemID}`);
|
|
192
|
+
return cached;
|
|
193
|
+
}
|
|
194
|
+
// Check if request is already pending (deduplication)
|
|
195
|
+
if (this.pendingRequests.has(cacheKey)) {
|
|
196
|
+
this.logger.info(`Using pending request for item ${itemID}`);
|
|
197
|
+
return await this.pendingRequests.get(cacheKey);
|
|
198
|
+
}
|
|
199
|
+
// Create pending request
|
|
200
|
+
const promise = this.fetchPriceData(itemID, worldID, dataCenterID).then(async (data) => {
|
|
201
|
+
this.pendingRequests.delete(cacheKey);
|
|
202
|
+
if (data) {
|
|
203
|
+
await this.setCachedPrice(cacheKey, data);
|
|
204
|
+
}
|
|
205
|
+
return data;
|
|
206
|
+
}, (error) => {
|
|
207
|
+
this.pendingRequests.delete(cacheKey);
|
|
208
|
+
throw error;
|
|
209
|
+
});
|
|
210
|
+
this.pendingRequests.set(cacheKey, promise);
|
|
211
|
+
return await promise;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
this.logger.error('Failed to fetch price data', error);
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Internal method to fetch price data from API
|
|
220
|
+
*/
|
|
221
|
+
async fetchPriceData(itemID, worldID, dataCenterID) {
|
|
222
|
+
// Rate limiting: use injected rate limiter
|
|
223
|
+
await this.rateLimiter.waitIfNeeded();
|
|
224
|
+
this.rateLimiter.recordRequest();
|
|
225
|
+
// Build API URL
|
|
226
|
+
const url = this.buildApiUrl(itemID, worldID, dataCenterID);
|
|
227
|
+
try {
|
|
228
|
+
const data = await retry(() => this.fetchWithTimeout(url, UNIVERSALIS_API_TIMEOUT), UNIVERSALIS_API_RETRY_COUNT, UNIVERSALIS_API_RETRY_DELAY);
|
|
229
|
+
if (!data) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
// Parse and validate response
|
|
233
|
+
return this.parseApiResponse(data, itemID);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
throw new AppError(ErrorCode.API_CALL_FAILED, `Failed to fetch price data for item ${itemID}: ${error instanceof Error ? error.message : 'Unknown error'}`, 'warning');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Fetch with timeout and size limits
|
|
241
|
+
* Uses injected fetch client for better testability
|
|
242
|
+
*/
|
|
243
|
+
async fetchWithTimeout(url, timeoutMs) {
|
|
244
|
+
const controller = new AbortController();
|
|
245
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
246
|
+
try {
|
|
247
|
+
const response = await this.fetchClient.fetch(url, { signal: controller.signal });
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
250
|
+
}
|
|
251
|
+
const contentType = response.headers.get('content-type');
|
|
252
|
+
if (!contentType?.includes('application/json')) {
|
|
253
|
+
throw new Error('Invalid content type: expected application/json');
|
|
254
|
+
}
|
|
255
|
+
// Check content-length header if available
|
|
256
|
+
const contentLength = response.headers.get('content-length');
|
|
257
|
+
if (contentLength) {
|
|
258
|
+
const size = parseInt(contentLength, 10);
|
|
259
|
+
if (!isNaN(size) && size > API_MAX_RESPONSE_SIZE) {
|
|
260
|
+
throw new Error(`Response too large: ${size} bytes (max: ${API_MAX_RESPONSE_SIZE} bytes)`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Read response text and validate size
|
|
264
|
+
const text = await response.text();
|
|
265
|
+
if (text.length > API_MAX_RESPONSE_SIZE) {
|
|
266
|
+
throw new Error(`Response too large: ${text.length} bytes (max: ${API_MAX_RESPONSE_SIZE} bytes)`);
|
|
267
|
+
}
|
|
268
|
+
// Parse JSON with error handling
|
|
269
|
+
try {
|
|
270
|
+
return JSON.parse(text);
|
|
271
|
+
}
|
|
272
|
+
catch (parseError) {
|
|
273
|
+
throw new Error(`Invalid JSON response: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
clearTimeout(timeoutId);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Parse and validate API response
|
|
282
|
+
*/
|
|
283
|
+
parseApiResponse(data, itemID) {
|
|
284
|
+
try {
|
|
285
|
+
// Validate response structure
|
|
286
|
+
if (!data || typeof data !== 'object') {
|
|
287
|
+
this.logger.warn(`Invalid API response structure for item ${itemID}`);
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
// Parse aggregated endpoint response format
|
|
291
|
+
if (!data.results || !Array.isArray(data.results) || data.results.length === 0) {
|
|
292
|
+
this.logger.warn(`No price data available for item ${itemID}`);
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
const result = data.results[0];
|
|
296
|
+
if (!result || typeof result !== 'object') {
|
|
297
|
+
this.logger.warn(`Invalid result structure for item ${itemID}`);
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
// Validate itemId type and match
|
|
301
|
+
if (typeof result.itemId !== 'number' || result.itemId !== itemID) {
|
|
302
|
+
this.logger.warn(`Item ID mismatch: expected ${itemID}, got ${result.itemId}`);
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
// Try to get price from nq.minListing (prefer DC, then world, then region)
|
|
306
|
+
let price = null;
|
|
307
|
+
if (result.nq?.minListing) {
|
|
308
|
+
// Prefer data center price
|
|
309
|
+
if (result.nq.minListing.dc?.price) {
|
|
310
|
+
price = result.nq.minListing.dc.price;
|
|
311
|
+
}
|
|
312
|
+
// Fall back to world price
|
|
313
|
+
else if (result.nq.minListing.world?.price) {
|
|
314
|
+
price = result.nq.minListing.world.price;
|
|
315
|
+
}
|
|
316
|
+
// Fall back to region price
|
|
317
|
+
else if (result.nq.minListing.region?.price) {
|
|
318
|
+
price = result.nq.minListing.region.price;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// If no NQ price, try HQ
|
|
322
|
+
if (!price && result.hq?.minListing) {
|
|
323
|
+
if (result.hq.minListing.dc?.price) {
|
|
324
|
+
price = result.hq.minListing.dc.price;
|
|
325
|
+
}
|
|
326
|
+
else if (result.hq.minListing.world?.price) {
|
|
327
|
+
price = result.hq.minListing.world.price;
|
|
328
|
+
}
|
|
329
|
+
else if (result.hq.minListing.region?.price) {
|
|
330
|
+
price = result.hq.minListing.region.price;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (!price) {
|
|
334
|
+
this.logger.warn(`No price data available for item ${itemID}`);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
itemID,
|
|
339
|
+
currentAverage: Math.round(price),
|
|
340
|
+
currentMinPrice: Math.round(price), // Using same price for all fields
|
|
341
|
+
currentMaxPrice: Math.round(price),
|
|
342
|
+
lastUpdate: Date.now(),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
this.logger.error(`Failed to parse price data for item ${itemID}`, error);
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Build API URL for item price query
|
|
352
|
+
*/
|
|
353
|
+
buildApiUrl(itemID, _worldID, dataCenterID) {
|
|
354
|
+
// Universalis API endpoint: /api/v2/aggregated/{dataCenter or worldName}/{itemIDs}
|
|
355
|
+
// Note: worldID parameter reserved for future use (world-specific queries)
|
|
356
|
+
// Sanitize dataCenterID to prevent URL path injection
|
|
357
|
+
const pathSegment = dataCenterID ? this.sanitizeDataCenterId(dataCenterID) : 'universal';
|
|
358
|
+
return `${UNIVERSALIS_API_BASE}/aggregated/${pathSegment}/${itemID}`;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Sanitize datacenter ID to prevent cache key injection.
|
|
362
|
+
* Only allows alphanumeric characters (a-z, A-Z, 0-9).
|
|
363
|
+
*/
|
|
364
|
+
sanitizeDataCenterId(dataCenterID) {
|
|
365
|
+
return dataCenterID.replace(/[^a-zA-Z0-9]/g, '');
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Build cache key from parameters
|
|
369
|
+
* SECURITY: Uses type prefixes to prevent cache key collisions
|
|
370
|
+
* Format: itemID:type:value where type is 'dc', 'world', or 'global'
|
|
371
|
+
* This prevents crafted dataCenterID values from colliding with other key patterns
|
|
372
|
+
*/
|
|
373
|
+
buildCacheKey(itemID, worldID, dataCenterID) {
|
|
374
|
+
const parts = [itemID];
|
|
375
|
+
if (dataCenterID) {
|
|
376
|
+
// Type prefix 'dc' ensures datacenter keys don't collide with world/global keys
|
|
377
|
+
const sanitized = this.sanitizeDataCenterId(dataCenterID);
|
|
378
|
+
parts.push('dc', sanitized);
|
|
379
|
+
}
|
|
380
|
+
else if (worldID) {
|
|
381
|
+
// Type prefix 'world' ensures world keys don't collide with datacenter/global keys
|
|
382
|
+
parts.push('world', worldID);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
parts.push('global');
|
|
386
|
+
}
|
|
387
|
+
// Use colon delimiter (not in sanitized values) for unambiguous parsing
|
|
388
|
+
return parts.join(':');
|
|
389
|
+
}
|
|
390
|
+
// ============================================================================
|
|
391
|
+
// Batch Operations
|
|
392
|
+
// ============================================================================
|
|
393
|
+
/**
|
|
394
|
+
* Fetch prices for multiple items
|
|
395
|
+
* PERFORMANCE: Uses Promise.allSettled for parallel requests
|
|
396
|
+
* Failed requests are logged but don't block other results
|
|
397
|
+
*/
|
|
398
|
+
async getPricesForItems(itemIDs) {
|
|
399
|
+
const results = new Map();
|
|
400
|
+
// Fire all requests in parallel
|
|
401
|
+
const promises = itemIDs.map(async (itemID) => {
|
|
402
|
+
const price = await this.getPriceData(itemID);
|
|
403
|
+
return { itemID, price };
|
|
404
|
+
});
|
|
405
|
+
// Wait for all to settle (don't fail on individual errors)
|
|
406
|
+
const settled = await Promise.allSettled(promises);
|
|
407
|
+
// Collect successful results
|
|
408
|
+
for (const result of settled) {
|
|
409
|
+
if (result.status === 'fulfilled' && result.value.price) {
|
|
410
|
+
results.set(result.value.itemID, result.value.price);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return results;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Fetch prices for dyes in a specific data center
|
|
417
|
+
* PERFORMANCE: Uses Promise.allSettled for parallel requests
|
|
418
|
+
*/
|
|
419
|
+
async getPricesForDataCenter(itemIDs, dataCenterID) {
|
|
420
|
+
const results = new Map();
|
|
421
|
+
// Fire all requests in parallel
|
|
422
|
+
const promises = itemIDs.map(async (itemID) => {
|
|
423
|
+
const price = await this.getPriceData(itemID, undefined, dataCenterID);
|
|
424
|
+
return { itemID, price };
|
|
425
|
+
});
|
|
426
|
+
// Wait for all to settle (don't fail on individual errors)
|
|
427
|
+
const settled = await Promise.allSettled(promises);
|
|
428
|
+
// Collect successful results
|
|
429
|
+
for (const result of settled) {
|
|
430
|
+
if (result.status === 'fulfilled' && result.value.price) {
|
|
431
|
+
results.set(result.value.itemID, result.value.price);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return results;
|
|
435
|
+
}
|
|
436
|
+
// ============================================================================
|
|
437
|
+
// Health Checks
|
|
438
|
+
// ============================================================================
|
|
439
|
+
/**
|
|
440
|
+
* Check if Universalis API is available
|
|
441
|
+
* Uses injected fetch client
|
|
442
|
+
*/
|
|
443
|
+
async isAPIAvailable() {
|
|
444
|
+
try {
|
|
445
|
+
const response = await this.fetchClient.fetch(`${UNIVERSALIS_API_BASE}/data-centers`);
|
|
446
|
+
return response.ok;
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Get API status information
|
|
454
|
+
*/
|
|
455
|
+
async getAPIStatus() {
|
|
456
|
+
const start = Date.now();
|
|
457
|
+
try {
|
|
458
|
+
const available = await this.isAPIAvailable();
|
|
459
|
+
const latency = Date.now() - start;
|
|
460
|
+
return { available, latency };
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
return { available: false, latency: -1 };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// ============================================================================
|
|
467
|
+
// Utility Methods
|
|
468
|
+
// ============================================================================
|
|
469
|
+
/**
|
|
470
|
+
* Format price for display (FFXIV Gil format: 69,420G)
|
|
471
|
+
*/
|
|
472
|
+
static formatPrice(price) {
|
|
473
|
+
const formattedNumber = new Intl.NumberFormat('en-US', {
|
|
474
|
+
minimumFractionDigits: 0,
|
|
475
|
+
maximumFractionDigits: 0,
|
|
476
|
+
}).format(price);
|
|
477
|
+
// Return with small "G" suffix
|
|
478
|
+
return `${formattedNumber}G`;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Calculate price trend (simplified)
|
|
482
|
+
*/
|
|
483
|
+
static getPriceTrend(currentPrice, previousPrice) {
|
|
484
|
+
const change = currentPrice - previousPrice;
|
|
485
|
+
const changePercent = previousPrice === 0 ? 0 : (change / previousPrice) * 100;
|
|
486
|
+
let trend;
|
|
487
|
+
if (change > previousPrice * 0.05) {
|
|
488
|
+
trend = 'up';
|
|
489
|
+
}
|
|
490
|
+
else if (change < -previousPrice * 0.05) {
|
|
491
|
+
trend = 'down';
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
trend = 'stable';
|
|
495
|
+
}
|
|
496
|
+
return { trend, change, changePercent: Math.round(changePercent * 100) / 100 };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
//# sourceMappingURL=APIService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"APIService.js","sourceRoot":"","sources":["../../src/services/APIService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @xivdyetools/core - Color Service
|
|
3
|
+
*
|
|
4
|
+
* Color conversion algorithms and colorblindness simulation.
|
|
5
|
+
* Provides utilities for converting between RGB, HSV, and hex color formats,
|
|
6
|
+
* calculating color distances, and simulating color vision deficiencies.
|
|
7
|
+
*
|
|
8
|
+
* Per R-4: Facade class that delegates to focused service classes
|
|
9
|
+
* Maintains backward compatibility while using split services internally
|
|
10
|
+
*
|
|
11
|
+
* @module services/ColorService
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { ColorService } from '@xivdyetools/core';
|
|
15
|
+
*
|
|
16
|
+
* // Convert hex to RGB
|
|
17
|
+
* const rgb = ColorService.hexToRgb('#FF0000');
|
|
18
|
+
* // { r: 255, g: 0, b: 0 }
|
|
19
|
+
*
|
|
20
|
+
* // Convert RGB to HSV
|
|
21
|
+
* const hsv = ColorService.rgbToHsv(rgb);
|
|
22
|
+
* // { h: 0, s: 100, v: 100 }
|
|
23
|
+
*
|
|
24
|
+
* // Calculate color distance
|
|
25
|
+
* const distance = ColorService.getColorDistance('#FF0000', '#00FF00');
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import type { RGB, HSV, HexColor, VisionType } from '../types/index.js';
|
|
29
|
+
/**
|
|
30
|
+
* Color conversion and manipulation service (Facade)
|
|
31
|
+
* Per R-4: Delegates to focused service classes for better separation of concerns
|
|
32
|
+
* Maintains backward compatibility with existing API
|
|
33
|
+
*/
|
|
34
|
+
export declare class ColorService {
|
|
35
|
+
/**
|
|
36
|
+
* Clear all caches (useful for testing or memory management)
|
|
37
|
+
*/
|
|
38
|
+
static clearCaches(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Get cache statistics (for monitoring)
|
|
41
|
+
*/
|
|
42
|
+
static getCacheStats(): {
|
|
43
|
+
hexToRgb: number;
|
|
44
|
+
rgbToHex: number;
|
|
45
|
+
rgbToHsv: number;
|
|
46
|
+
hsvToRgb: number;
|
|
47
|
+
hexToHsv: number;
|
|
48
|
+
colorblind: number;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Convert hexadecimal color to RGB
|
|
52
|
+
* @example hexToRgb("#FF0000") -> { r: 255, g: 0, b: 0 }
|
|
53
|
+
*/
|
|
54
|
+
static hexToRgb(hex: string): RGB;
|
|
55
|
+
/**
|
|
56
|
+
* Convert RGB to hexadecimal color
|
|
57
|
+
* @example rgbToHex(255, 0, 0) -> "#FF0000"
|
|
58
|
+
*/
|
|
59
|
+
static rgbToHex(r: number, g: number, b: number): HexColor;
|
|
60
|
+
/**
|
|
61
|
+
* Convert RGB to HSV
|
|
62
|
+
* @example rgbToHsv(255, 0, 0) -> { h: 0, s: 100, v: 100 }
|
|
63
|
+
*/
|
|
64
|
+
static rgbToHsv(r: number, g: number, b: number): HSV;
|
|
65
|
+
/**
|
|
66
|
+
* Convert HSV to RGB
|
|
67
|
+
* @example hsvToRgb(0, 100, 100) -> { r: 255, g: 0, b: 0 }
|
|
68
|
+
*/
|
|
69
|
+
static hsvToRgb(h: number, s: number, v: number): RGB;
|
|
70
|
+
/**
|
|
71
|
+
* Convert hex to HSV
|
|
72
|
+
*/
|
|
73
|
+
static hexToHsv(hex: string): HSV;
|
|
74
|
+
/**
|
|
75
|
+
* Convert HSV to hex
|
|
76
|
+
*/
|
|
77
|
+
static hsvToHex(h: number, s: number, v: number): HexColor;
|
|
78
|
+
/**
|
|
79
|
+
* Normalize a hex color to #RRGGBB format
|
|
80
|
+
*/
|
|
81
|
+
static normalizeHex(hex: string): HexColor;
|
|
82
|
+
/**
|
|
83
|
+
* Calculate Euclidean distance between two RGB colors
|
|
84
|
+
* Returns 0 for identical colors, ~441.67 for white vs black
|
|
85
|
+
*/
|
|
86
|
+
static getColorDistance(hex1: string, hex2: string): number;
|
|
87
|
+
/**
|
|
88
|
+
* Simulate colorblindness on an RGB color
|
|
89
|
+
* @example simulateColorblindness({ r: 255, g: 0, b: 0 }, 'deuteranopia')
|
|
90
|
+
*/
|
|
91
|
+
static simulateColorblindness(rgb: RGB, visionType: VisionType): RGB;
|
|
92
|
+
/**
|
|
93
|
+
* Simulate colorblindness on a hex color
|
|
94
|
+
*/
|
|
95
|
+
static simulateColorblindnessHex(hex: string, visionType: VisionType): HexColor;
|
|
96
|
+
/**
|
|
97
|
+
* Calculate perceived luminance of a color (0-1)
|
|
98
|
+
* Uses relative luminance formula from WCAG
|
|
99
|
+
*/
|
|
100
|
+
static getPerceivedLuminance(hex: string): number;
|
|
101
|
+
/**
|
|
102
|
+
* Calculate contrast ratio between two colors
|
|
103
|
+
* Returns 1 (no contrast) to 21 (maximum contrast)
|
|
104
|
+
*/
|
|
105
|
+
static getContrastRatio(hex1: string, hex2: string): number;
|
|
106
|
+
/**
|
|
107
|
+
* Check if two colors meet WCAG AA contrast ratio
|
|
108
|
+
*/
|
|
109
|
+
static meetsWCAGAA(hex1: string, hex2: string, largeText?: boolean): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Check if two colors meet WCAG AAA contrast ratio
|
|
112
|
+
*/
|
|
113
|
+
static meetsWCAGAAA(hex1: string, hex2: string, largeText?: boolean): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Check if a color is light (for determining text color on background)
|
|
116
|
+
*/
|
|
117
|
+
static isLightColor(hex: string): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Get optimal text color for a background color
|
|
120
|
+
*/
|
|
121
|
+
static getOptimalTextColor(backgroundColor: string): HexColor;
|
|
122
|
+
/**
|
|
123
|
+
* Adjust brightness of a color
|
|
124
|
+
* @param amount -100 to 100 (negative = darker, positive = lighter)
|
|
125
|
+
*/
|
|
126
|
+
static adjustBrightness(hex: string, amount: number): HexColor;
|
|
127
|
+
/**
|
|
128
|
+
* Adjust saturation of a color
|
|
129
|
+
* @param amount -100 to 100 (negative = less saturated, positive = more saturated)
|
|
130
|
+
*/
|
|
131
|
+
static adjustSaturation(hex: string, amount: number): HexColor;
|
|
132
|
+
/**
|
|
133
|
+
* Rotate hue of a color
|
|
134
|
+
* @param degrees 0-360 (amount to rotate hue)
|
|
135
|
+
*/
|
|
136
|
+
static rotateHue(hex: string, degrees: number): HexColor;
|
|
137
|
+
/**
|
|
138
|
+
* Invert a color (create complementary color)
|
|
139
|
+
*/
|
|
140
|
+
static invert(hex: string): HexColor;
|
|
141
|
+
/**
|
|
142
|
+
* Desaturate a color (convert to grayscale)
|
|
143
|
+
*/
|
|
144
|
+
static desaturate(hex: string): HexColor;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=ColorService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColorService.d.ts","sourceRoot":"","sources":["../../src/services/ColorService.ts"],"names":[],"mappings":"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"}
|