NathMen12 commited on
Commit
b14a10f
·
verified ·
1 Parent(s): e262f60

Upload 18 files

Browse files
Dockerfile.txt ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multi-stage build for Translator API
2
+ FROM node:20-alpine AS builder
3
+
4
+ WORKDIR /app
5
+
6
+ # Copy package files
7
+ COPY package*.json ./
8
+ COPY central/package*.json ./central/
9
+ COPY worker/package*.json ./worker/
10
+
11
+ # Install all dependencies
12
+ RUN npm ci && \
13
+ cd central && npm ci && \
14
+ cd ../worker && npm ci
15
+
16
+ # Production stage
17
+ FROM node:20-alpine AS production
18
+
19
+ WORKDIR /app
20
+
21
+ # Install dumb-init for proper signal handling
22
+ RUN apk add --no-cache dumb-init
23
+
24
+ # Create non-root user
25
+ RUN addgroup -g 1001 -S nodejs && \
26
+ adduser -S nodejs -u 1001
27
+
28
+ # Copy built dependencies
29
+ COPY --from=builder /app/node_modules ./node_modules
30
+ COPY --from=builder /app/central/node_modules ./central/node_modules
31
+ COPY --from=builder /app/worker/node_modules ./worker/node_modules
32
+
33
+ # Copy source code
34
+ COPY --chown=nodejs:nodejs package.json ./
35
+ COPY --chown=nodejs:nodejs central ./central
36
+ COPY --chown=nodejs:nodejs worker ./worker
37
+ COPY --chown=nodejs:nodejs shared ./shared
38
+
39
+ # Create logs directory
40
+ RUN mkdir -p central/logs worker/logs && chown -R nodejs:nodejs central/logs worker/logs
41
+
42
+ # Switch to non-root user
43
+ USER nodejs
44
+
45
+ # Expose port
46
+ EXPOSE 7860
47
+
48
+ # Health check
49
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
50
+ CMD wget --no-verbose --tries=1 --spider http://localhost:7820/api/health || exit 1
51
+
52
+ # Start central server (worker will be started separately in docker-compose)
53
+ ENTRYPOINT ["dumb-init", "--"]
54
+ CMD ["node", "central/src/index.js"]
central/secrets/.env.example ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Environment Variables
2
+ # Copy this file to .env and fill in your values
3
+ # NEVER commit the actual .env file!
4
+
5
+ # Admin panel access code (required for /admin access)
6
+ ADMIN_ACCESS_CODE=your-secure-admin-code-here
7
+
8
+ # Tailscale API key for worker discovery (optional)
9
+ TAILSCALE_API_KEY=tskey-xxxxxxxxx
10
+
11
+ # Optional: Custom port (default 7820)
12
+ # PORT=7820
13
+
14
+ # Optional: Node environment
15
+ # NODE_ENV=production
central/settings.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "port": 7820,
3
+ "rateLimit": {
4
+ "maxRequestsPerMinutePerIP": 30,
5
+ "windowMs": 60000
6
+ },
7
+ "scanLocalWorkers": true,
8
+ "metricsWindowSeconds": 120,
9
+ "maxLocalJobs": 4
10
+ }
central/src/index.js ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('dotenv').config({ path: require('path').join(__dirname, '../../secrets/.env') });
2
+ const express = require('express');
3
+ const cors = require('cors');
4
+ const rateLimit = require('express-rate-limit');
5
+ const { WebSocketServer } = require('ws');
6
+ const http = require('http');
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const { v4: uuidv4 } = require('uuid');
10
+ const PQueue = require('p-queue').default;
11
+ const os = require('os');
12
+ const winston = require('winston');
13
+
14
+ const settings = JSON.parse(fs.readFileSync(path.join(__dirname, '../settings.json'), 'utf8'));
15
+
16
+ // Logger configuration
17
+ const logger = winston.createLogger({
18
+ level: 'info',
19
+ format: winston.format.combine(
20
+ winston.format.timestamp(),
21
+ winston.format.json()
22
+ ),
23
+ transports: [
24
+ new winston.transports.Console(),
25
+ new winston.transports.File({ filename: path.join(__dirname, '../../logs/central.log') })
26
+ ]
27
+ });
28
+
29
+ // Metrics storage
30
+ const metrics = {
31
+ requests: [], // { timestamp, ip, duration, sourceLang, targetLang, inputText, outputText }
32
+ cpuHistory: [],
33
+ ramHistory: [],
34
+ workers: new Map() // workerId -> { id, ws, cpu, ram, lastHeartbeat, jobsActive }
35
+ };
36
+
37
+ // Job queue (local processing + worker dispatch)
38
+ const localQueue = new PQueue({ concurrency: settings.maxLocalJobs });
39
+ const workerQueue = new PQueue({ concurrency: 10 }); // dispatch to workers
40
+
41
+ // Express app
42
+ const app = express();
43
+ const server = http.createServer(app);
44
+
45
+ // WebSocket server for workers
46
+ const wss = new WebSocketServer({ server });
47
+
48
+ // Middleware
49
+ app.use(cors());
50
+ app.use(express.json({ limit: '10mb' }));
51
+ app.use(express.static(path.join(__dirname, '../../public')));
52
+
53
+ // Rate limiting
54
+ const limiter = rateLimit({
55
+ windowMs: settings.rateLimit.windowMs,
56
+ max: settings.rateLimit.maxRequestsPerMinutePerIP,
57
+ message: { error: 'Trop de requêtes, veuillez réessayer plus tard.' },
58
+ standardHeaders: true,
59
+ legacyHeaders: false,
60
+ keyGenerator: (req) => req.ip
61
+ });
62
+ app.use('/translate', limiter);
63
+
64
+ // Admin authentication middleware
65
+ const adminAuth = (req, res, next) => {
66
+ const adminCode = process.env.ADMIN_ACCESS_CODE;
67
+ const providedCode = req.headers['x-admin-code'] || req.query.admin_code;
68
+ if (!adminCode || providedCode !== adminCode) {
69
+ return res.status(401).json({ error: 'Code admin invalide' });
70
+ }
71
+ next();
72
+ };
73
+
74
+ // Load translation function
75
+ const translate = require('@vitalets/google-translate-api');
76
+
77
+ // Metrics collection
78
+ setInterval(() => {
79
+ const cpuUsage = process.cpuUsage();
80
+ const memUsage = process.memoryUsage();
81
+ const totalMem = os.totalmem();
82
+ const freeMem = os.freemem();
83
+
84
+ metrics.cpuHistory.push({ timestamp: Date.now(), cpu: cpuUsage.user + cpuUsage.system });
85
+ metrics.ramHistory.push({ timestamp: Date.now(), used: totalMem - freeMem, total: totalMem });
86
+
87
+ // Keep only last metricsWindowSeconds
88
+ const cutoff = Date.now() - settings.metricsWindowSeconds * 1000;
89
+ metrics.cpuHistory = metrics.cpuHistory.filter(m => m.timestamp > cutoff);
90
+ metrics.ramHistory = metrics.ramHistory.filter(m => m.timestamp > cutoff);
91
+ metrics.requests = metrics.requests.filter(m => m.timestamp > cutoff);
92
+ }, 1000);
93
+
94
+ // Clean up inactive workers
95
+ setInterval(() => {
96
+ const now = Date.now();
97
+ for (const [id, worker] of metrics.workers) {
98
+ if (now - worker.lastHeartbeat > 15000) {
99
+ logger.info(`Worker ${id} disconnected (timeout)`);
100
+ metrics.workers.delete(id);
101
+ }
102
+ }
103
+ }, 5000);
104
+
105
+ // WebSocket handling for workers
106
+ wss.on('connection', (ws, req) => {
107
+ const workerId = uuidv4();
108
+ logger.info(`Worker connected: ${workerId}`);
109
+
110
+ const worker = {
111
+ id: workerId,
112
+ ws,
113
+ cpu: 0,
114
+ ram: 0,
115
+ lastHeartbeat: Date.now(),
116
+ jobsActive: 0,
117
+ maxJobs: 2
118
+ };
119
+ metrics.workers.set(workerId, worker);
120
+
121
+ ws.on('message', (data) => {
122
+ try {
123
+ const msg = JSON.parse(data);
124
+ handleWorkerMessage(workerId, msg);
125
+ } catch (e) {
126
+ logger.error('Invalid worker message', e);
127
+ }
128
+ });
129
+
130
+ ws.on('close', () => {
131
+ logger.info(`Worker disconnected: ${workerId}`);
132
+ metrics.workers.delete(workerId);
133
+ });
134
+
135
+ ws.on('error', (err) => {
136
+ logger.error(`Worker ${workerId} error`, err);
137
+ });
138
+
139
+ // Send welcome message with config
140
+ ws.send(JSON.stringify({
141
+ type: 'welcome',
142
+ workerId,
143
+ maxConcurrentJobs: 2
144
+ }));
145
+ });
146
+
147
+ function handleWorkerMessage(workerId, msg) {
148
+ const worker = metrics.workers.get(workerId);
149
+ if (!worker) return;
150
+
151
+ switch (msg.type) {
152
+ case 'heartbeat':
153
+ worker.lastHeartbeat = Date.now();
154
+ worker.cpu = msg.cpu || 0;
155
+ worker.ram = msg.ram || 0;
156
+ worker.jobsActive = msg.jobsActive || 0;
157
+ break;
158
+ case 'result':
159
+ // Job completed by worker
160
+ if (msg.jobId) {
161
+ // Find and resolve the waiting promise
162
+ const pending = pendingJobs.get(msg.jobId);
163
+ if (pending) {
164
+ pending.resolve(msg.result);
165
+ pendingJobs.delete(msg.jobId);
166
+ }
167
+ }
168
+ worker.jobsActive = Math.max(0, worker.jobsActive - 1);
169
+ break;
170
+ case 'log':
171
+ logger.info(`Worker ${workerId} log: ${msg.message}`);
172
+ break;
173
+ }
174
+ }
175
+
176
+ // Pending jobs waiting for worker results
177
+ const pendingJobs = new Map();
178
+
179
+ // API Routes
180
+
181
+ // Health check
182
+ app.get('/api/health', (req, res) => {
183
+ res.json({ status: 'ok', uptime: process.uptime() });
184
+ });
185
+
186
+ // Translate endpoint
187
+ app.post('/translate', async (req, res) => {
188
+ const startTime = Date.now();
189
+ const { text, source = 'auto', target = 'fr' } = req.body;
190
+ const ip = req.ip;
191
+
192
+ if (!text || typeof text !== 'string') {
193
+ return res.status(400).json({ error: 'Texte requis' });
194
+ }
195
+
196
+ if (text.length > 5000) {
197
+ return res.status(400).json({ error: 'Texte trop long (max 5000 caractères)' });
198
+ }
199
+
200
+ try {
201
+ // Try to dispatch to available worker first
202
+ let result;
203
+ const availableWorker = findAvailableWorker();
204
+
205
+ if (availableWorker) {
206
+ result = await dispatchToWorker(availableWorker, { text, source, target });
207
+ } else {
208
+ // Process locally
209
+ result = await localQueue.add(() => translateText(text, source, target));
210
+ }
211
+
212
+ const duration = Date.now() - startTime;
213
+
214
+ // Extract translated text from worker result object if needed
215
+ const translatedText = result.translatedText || result;
216
+
217
+ // Log request
218
+ metrics.requests.push({
219
+ timestamp: Date.now(),
220
+ ip,
221
+ duration,
222
+ sourceLang: source,
223
+ targetLang: target,
224
+ inputText: text.substring(0, 100),
225
+ outputText: translatedText.substring(0, 100)
226
+ });
227
+
228
+ res.json({ translatedText, source, target, duration });
229
+ } catch (error) {
230
+ logger.error('Translation error', error);
231
+ res.status(500).json({ error: 'Erreur de traduction' });
232
+ }
233
+ });
234
+
235
+ function translateText(text, source, target) {
236
+ return translate(text, { from: source, to: target }).then(res => res.text);
237
+ }
238
+
239
+ function findAvailableWorker() {
240
+ for (const [id, worker] of metrics.workers) {
241
+ if (worker.jobsActive < worker.maxJobs && worker.ws.readyState === 1) {
242
+ return worker;
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+
248
+ function dispatchToWorker(worker, job) {
249
+ return new Promise((resolve, reject) => {
250
+ const jobId = uuidv4();
251
+ pendingJobs.set(jobId, { resolve, reject, timeout: setTimeout(() => {
252
+ pendingJobs.delete(jobId);
253
+ reject(new Error('Worker timeout'));
254
+ }, 30000) });
255
+
256
+ worker.ws.send(JSON.stringify({
257
+ type: 'translate',
258
+ jobId,
259
+ ...job
260
+ }));
261
+ worker.jobsActive++;
262
+ });
263
+ }
264
+
265
+ // Admin dashboard routes
266
+ app.get('/admin', adminAuth, (req, res) => {
267
+ res.sendFile(path.join(__dirname, '../../public/admin.html'));
268
+ });
269
+
270
+ app.get('/api/admin/metrics', adminAuth, (req, res) => {
271
+ const workers = [];
272
+ for (const [id, w] of metrics.workers) {
273
+ workers.push({
274
+ id,
275
+ cpu: w.cpu,
276
+ ram: w.ram,
277
+ jobsActive: w.jobsActive,
278
+ maxJobs: w.maxJobs,
279
+ lastHeartbeat: w.lastHeartbeat
280
+ });
281
+ }
282
+
283
+ res.json({
284
+ workers,
285
+ cpuHistory: metrics.cpuHistory,
286
+ ramHistory: metrics.ramHistory,
287
+ requests: metrics.requests.slice(-200), // Last 200 requests
288
+ requestRate: calculateRequestRate()
289
+ });
290
+ });
291
+
292
+ app.get('/api/admin/logs', adminAuth, (req, res) => {
293
+ // Return recent logs from metrics.requests
294
+ const logs = metrics.requests.slice(-100).map(r => ({
295
+ timestamp: r.timestamp,
296
+ ip: r.ip,
297
+ duration: r.duration,
298
+ sourceLang: r.sourceLang,
299
+ targetLang: r.targetLang,
300
+ inputText: r.inputText,
301
+ outputText: r.outputText
302
+ }));
303
+ res.json({ logs });
304
+ });
305
+
306
+ function calculateRequestRate() {
307
+ const now = Date.now();
308
+ const windowMs = 120000; // 2 minutes
309
+ const recent = metrics.requests.filter(r => r.timestamp > now - windowMs);
310
+ return recent.length;
311
+ }
312
+
313
+ // Main page
314
+ app.get('/', (req, res) => {
315
+ res.sendFile(path.join(__dirname, '../../public/index.html'));
316
+ });
317
+
318
+ // Wiki page
319
+ app.get('/wiki', (req, res) => {
320
+ res.sendFile(path.join(__dirname, '../../public/wiki.html'));
321
+ });
322
+
323
+ // Start server
324
+ const PORT = process.env.PORT || settings.port;
325
+ server.listen(PORT, '0.0.0.0', () => {
326
+ logger.info(`Central server running on port ${PORT}`);
327
+ logger.info(`Admin panel: http://localhost:${PORT}/admin`);
328
+ logger.info(`API: http://localhost:${PORT}/translate`);
329
+ });
docker-compose.yml ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ central:
5
+ build: .
6
+ container_name: translator-central
7
+ ports:
8
+ - "7820:7820"
9
+ environment:
10
+ - NODE_ENV=production
11
+ - PORT=7820
12
+ - ADMIN_ACCESS_CODE=${ADMIN_ACCESS_CODE}
13
+ - TAILSCALE_API_KEY=${TAILSCALE_API_KEY}
14
+ volumes:
15
+ - ./central/logs:/app/central/logs
16
+ restart: unless-stopped
17
+ deploy:
18
+ resources:
19
+ limits:
20
+ cpus: '1.5'
21
+ memory: 12G
22
+ reservations:
23
+ cpus: '1'
24
+ memory: 8G
25
+ healthcheck:
26
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7820/api/health"]
27
+ interval: 30s
28
+ timeout: 10s
29
+ retries: 3
30
+ start_period: 10s
31
+
32
+ worker:
33
+ build: .
34
+ container_name: translator-worker
35
+ command: node worker/src/index.js
36
+ environment:
37
+ - NODE_ENV=production
38
+ - CENTRAL_HOST=central
39
+ - CENTRAL_PORT=7820
40
+ depends_on:
41
+ central:
42
+ condition: service_healthy
43
+ volumes:
44
+ - ./worker/logs:/app/worker/logs
45
+ restart: unless-stopped
46
+ deploy:
47
+ resources:
48
+ limits:
49
+ cpus: '0.5'
50
+ memory: 4G
51
+ reservations:
52
+ cpus: '0.25'
53
+ memory: 2G
54
+
55
+ networks:
56
+ default:
57
+ name: translator-network
env.example ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Translator API - Environment Variables
2
+ # Copy this file to .env and fill in your values
3
+ # For HuggingFace Spaces, add these as Repository Secrets instead
4
+
5
+ # Admin panel access code (REQUIRED - choose a strong password)
6
+ ADMIN_ACCESS_CODE=your_strong_admin_code_here
7
+
8
+ # Tailscale API key for worker discovery (optional)
9
+ # Get from: https://login.tailscale.com/admin/settings/keys
10
+ TAILSCALE_API_KEY=tskey-xxxxxxxxxxxxxxxxx
11
+
12
+ # Port (default: 7820)
13
+ PORT=7860
14
+
15
+ # Node environment
16
+ NODE_ENV=production
gitignore.txt ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ lerna-debug.log*
8
+
9
+ # Diagnostic reports (https://nodejs.org/api/report.html)
10
+ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11
+
12
+ # Runtime data
13
+ pids
14
+ *.pid
15
+ *.seed
16
+ *.pid.lock
17
+
18
+ # Directory for instrumented libs generated by jscoverage/JSCover
19
+ lib-cov
20
+
21
+ # Coverage directory used by tools like istanbul
22
+ coverage
23
+ *.lcov
24
+
25
+ # nyc test coverage
26
+ .nyc_output
27
+
28
+ # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29
+ .grunt
30
+
31
+ # Bower dependency directory (https://bower.io/)
32
+ bower_components
33
+
34
+ # node-waf configuration
35
+ .lock-wscript
36
+
37
+ # Compiled binary addons (https://nodejs.org/api/addons.html)
38
+ build/Release
39
+
40
+ # Dependency directories
41
+ node_modules/
42
+ jspm_packages/
43
+
44
+ # Snowpack dependency directory (https://snowpack.dev/)
45
+ web_modules/
46
+
47
+ # TypeScript cache
48
+ *.tsbuildinfo
49
+
50
+ # Optional npm cache directory
51
+ .npm
52
+
53
+ # Optional eslint cache
54
+ .eslintcache
55
+
56
+ # Optional stylelint cache
57
+ .stylelintcache
58
+
59
+ # Optional REPL history
60
+ .node_repl_history
61
+
62
+ # Output of 'npm pack'
63
+ *.tgz
64
+
65
+ # Yarn Integrity file
66
+ .yarn-integrity
67
+
68
+ # dotenv environment variable files
69
+ .env
70
+ .env.*
71
+ !.env.example
72
+
73
+ # parcel-bundler cache (https://parceljs.org/)
74
+ .cache
75
+ .parcel-cache
76
+
77
+ # Next.js build output
78
+ .next
79
+ out
80
+
81
+ # Nuxt.js build / generate output
82
+ .nuxt
83
+ dist
84
+ .output
85
+
86
+ # Gatsby files
87
+ .cache/
88
+ # Comment in the public line in if your project uses Gatsby and not Next.js
89
+ # https://nextjs.org/blog/next-9-1#public-directory-support
90
+ # public
91
+
92
+ # vuepress build output
93
+ .vuepress/dist
94
+
95
+ # vuepress v2.x temp directory
96
+ .temp
97
+
98
+ # Sveltekit cache directory
99
+ .svelte-kit/
100
+
101
+ # vitepress build output
102
+ **/.vitepress/dist
103
+
104
+ # vitepress cache directory
105
+ **/.vitepress/cache
106
+
107
+ # Docusaurus cache and generated files
108
+ .docusaurus
109
+
110
+ # Serverless directories
111
+ .serverless/
112
+
113
+ # FuseBox cache
114
+ .fusebox/
115
+
116
+ # DynamoDB Local files
117
+ .dynamodb/
118
+
119
+ # Firebase cache directory
120
+ .firebase/
121
+
122
+ # TernJS port file
123
+ .tern-port
124
+
125
+ # Stores VSCode versions used for testing VSCode extensions
126
+ .vscode-test
127
+
128
+ # pnpm
129
+ .pnpm-store
130
+
131
+ # yarn v3
132
+ .pnp.*
133
+ .yarn/*
134
+ !.yarn/patches
135
+ !.yarn/plugins
136
+ !.yarn/releases
137
+ !.yarn/sdks
138
+ !.yarn/versions
139
+
140
+ # Vite files
141
+ vite.config.js.timestamp-*
142
+ vite.config.ts.timestamp-*
143
+ .vite/
logs/central.log ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-09T19:18:11.011Z"}
2
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-09T19:18:11.024Z"}
3
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-09T19:18:11.024Z"}
4
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-09T19:47:53.091Z"}
5
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-09T19:47:53.124Z"}
6
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-09T19:47:53.124Z"}
7
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-09T20:44:46.418Z"}
8
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-09T20:44:46.420Z"}
9
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-09T20:44:46.420Z"}
10
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-09T20:44:55.903Z"}
11
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-09T20:44:55.905Z"}
12
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-09T20:44:55.906Z"}
13
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-09T20:45:28.935Z"}
14
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-09T20:45:28.937Z"}
15
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-09T20:45:28.937Z"}
16
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T09:51:43.935Z"}
17
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T09:51:43.938Z"}
18
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T09:51:43.938Z"}
19
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T09:55:26.827Z"}
20
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T09:55:26.829Z"}
21
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T09:55:26.830Z"}
22
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T09:56:33.721Z"}
23
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T09:56:33.725Z"}
24
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T09:56:33.726Z"}
25
+ {"level":"error","message":"Translation error translate is not a function","stack":"TypeError: translate is not a function\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)\n at /workspaces/Translator-API/central/src/index.js:209:33\n at Layer.handle [as handle_request] (/workspaces/Translator-API/node_modules/express/lib/router/layer.js:95:5)\n at next (/workspaces/Translator-API/node_modules/express/lib/router/route.js:149:13)","timestamp":"2026-07-10T09:56:35.501Z"}
26
+ {"level":"error","message":"Translation error translate is not a function","stack":"TypeError: translate is not a function\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)\n at /workspaces/Translator-API/central/src/index.js:209:33\n at Layer.handle [as handle_request] (/workspaces/Translator-API/node_modules/express/lib/router/layer.js:95:5)\n at next (/workspaces/Translator-API/node_modules/express/lib/router/route.js:149:13)","timestamp":"2026-07-10T09:57:14.622Z"}
27
+ {"level":"error","message":"Translation error translate is not a function","stack":"TypeError: translate is not a function\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)\n at /workspaces/Translator-API/central/src/index.js:209:33\n at Layer.handle [as handle_request] (/workspaces/Translator-API/node_modules/express/lib/router/layer.js:95:5)\n at next (/workspaces/Translator-API/node_modules/express/lib/router/route.js:149:13)","timestamp":"2026-07-10T09:57:45.359Z"}
28
+ {"level":"error","message":"Translation error translate is not a function","stack":"TypeError: translate is not a function\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)\n at /workspaces/Translator-API/central/src/index.js:209:33\n at Layer.handle [as handle_request] (/workspaces/Translator-API/node_modules/express/lib/router/layer.js:95:5)\n at next (/workspaces/Translator-API/node_modules/express/lib/router/route.js:149:13)","timestamp":"2026-07-10T09:57:46.173Z"}
29
+ {"level":"error","message":"Translation error translate is not a function","stack":"TypeError: translate is not a function\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)\n at /workspaces/Translator-API/central/src/index.js:209:33\n at Layer.handle [as handle_request] (/workspaces/Translator-API/node_modules/express/lib/router/layer.js:95:5)\n at next (/workspaces/Translator-API/node_modules/express/lib/router/route.js:149:13)","timestamp":"2026-07-10T09:57:46.260Z"}
30
+ {"level":"error","message":"Translation error translate is not a function","stack":"TypeError: translate is not a function\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)\n at /workspaces/Translator-API/central/src/index.js:209:33\n at Layer.handle [as handle_request] (/workspaces/Translator-API/node_modules/express/lib/router/layer.js:95:5)\n at next (/workspaces/Translator-API/node_modules/express/lib/router/route.js:149:13)","timestamp":"2026-07-10T09:57:46.547Z"}
31
+ {"level":"error","message":"Translation error translate is not a function","stack":"TypeError: translate is not a function\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)\n at /workspaces/Translator-API/central/src/index.js:209:33\n at Layer.handle [as handle_request] (/workspaces/Translator-API/node_modules/express/lib/router/layer.js:95:5)\n at next (/workspaces/Translator-API/node_modules/express/lib/router/route.js:149:13)","timestamp":"2026-07-10T09:57:46.842Z"}
32
+ {"level":"error","message":"Translation error translate is not a function","stack":"TypeError: translate is not a function\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)\n at /workspaces/Translator-API/central/src/index.js:209:33\n at Layer.handle [as handle_request] (/workspaces/Translator-API/node_modules/express/lib/router/layer.js:95:5)\n at next (/workspaces/Translator-API/node_modules/express/lib/router/route.js:149:13)","timestamp":"2026-07-10T09:59:04.402Z"}
33
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:01:56.364Z"}
34
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:01:56.367Z"}
35
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:01:56.368Z"}
36
+ {"level":"info","message":"Worker connected: 3af1c5f8-4c45-4d07-bb4c-c04bcbc5d78d","timestamp":"2026-07-10T10:03:27.488Z"}
37
+ {"level":"info","message":"Worker disconnected: 3af1c5f8-4c45-4d07-bb4c-c04bcbc5d78d","timestamp":"2026-07-10T10:03:40.544Z"}
38
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:03:40.916Z"}
39
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:03:40.919Z"}
40
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:03:40.920Z"}
41
+ {"level":"info","message":"Worker connected: f2ce5edb-2c63-437b-a60a-93e354750b36","timestamp":"2026-07-10T10:03:42.792Z"}
42
+ {"level":"info","message":"Worker disconnected: f2ce5edb-2c63-437b-a60a-93e354750b36","timestamp":"2026-07-10T10:03:57.661Z"}
43
+ {"level":"error","message":"Translation error Worker timeout","stack":"Error: Worker timeout\n at Timeout._onTimeout (/workspaces/Translator-API/central/src/index.js:250:14)\n at listOnTimeout (node:internal/timers:605:17)\n at process.processTimers (node:internal/timers:541:7)","timestamp":"2026-07-10T10:04:14.525Z"}
44
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:05:30.334Z"}
45
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:05:30.336Z"}
46
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:05:30.336Z"}
47
+ {"level":"info","message":"Worker connected: c9886383-d891-4972-811a-61f85c9e542b","timestamp":"2026-07-10T10:05:32.293Z"}
48
+ {"level":"error","message":"Translation error Worker timeout","stack":"Error: Worker timeout\n at Timeout._onTimeout (/workspaces/Translator-API/central/src/index.js:250:14)\n at listOnTimeout (node:internal/timers:605:17)\n at process.processTimers (node:internal/timers:541:7)","timestamp":"2026-07-10T10:06:02.809Z"}
49
+ {"level":"error","message":"Translation error Worker timeout","stack":"Error: Worker timeout\n at Timeout._onTimeout (/workspaces/Translator-API/central/src/index.js:250:14)\n at listOnTimeout (node:internal/timers:605:17)\n at process.processTimers (node:internal/timers:541:7)","timestamp":"2026-07-10T10:06:05.130Z"}
50
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:07:21.394Z"}
51
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:07:21.396Z"}
52
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:07:21.396Z"}
53
+ {"level":"info","message":"Worker connected: 4d87843e-da23-40dc-8351-f8146062418a","timestamp":"2026-07-10T10:07:23.278Z"}
54
+ {"level":"info","message":"Worker disconnected: 4d87843e-da23-40dc-8351-f8146062418a","timestamp":"2026-07-10T10:07:38.150Z"}
55
+ {"code":400,"level":"error","message":"Translation error The language 'zh' is not supported","stack":"Error: The language 'zh' is not supported\n at /workspaces/Translator-API/node_modules/@vitalets/google-translate-api/index.js:22:17\n at Array.forEach (<anonymous>)\n at translate (/workspaces/Translator-API/node_modules/@vitalets/google-translate-api/index.js:20:26)\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)","timestamp":"2026-07-10T10:07:59.933Z"}
56
+ {"code":400,"level":"error","message":"Translation error The language 'zh' is not supported","stack":"Error: The language 'zh' is not supported\n at /workspaces/Translator-API/node_modules/@vitalets/google-translate-api/index.js:22:17\n at Array.forEach (<anonymous>)\n at translate (/workspaces/Translator-API/node_modules/@vitalets/google-translate-api/index.js:20:26)\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)","timestamp":"2026-07-10T10:08:01.171Z"}
57
+ {"code":400,"level":"error","message":"Translation error The language 'zh' is not supported","stack":"Error: The language 'zh' is not supported\n at /workspaces/Translator-API/node_modules/@vitalets/google-translate-api/index.js:22:17\n at Array.forEach (<anonymous>)\n at translate (/workspaces/Translator-API/node_modules/@vitalets/google-translate-api/index.js:20:26)\n at translateText (/workspaces/Translator-API/central/src/index.js:233:10)\n at /workspaces/Translator-API/central/src/index.js:209:43\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:111:37\n at PQueue._PQueue_tryToStartAnother (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:285:13)\n at file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:135:93\n at new Promise (<anonymous>)\n at PQueue.add (file:///workspaces/Translator-API/node_modules/p-queue/dist/index.js:99:16)","timestamp":"2026-07-10T10:08:06.009Z"}
58
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:08:20.354Z"}
59
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:08:20.356Z"}
60
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:08:20.356Z"}
61
+ {"level":"info","message":"Worker connected: 2e9f4bc6-cb3d-44b4-8f7f-f4f7437c3c5c","timestamp":"2026-07-10T10:08:22.266Z"}
62
+ {"level":"error","message":"Translation error result.substring is not a function","stack":"TypeError: result.substring is not a function\n at /workspaces/Translator-API/central/src/index.js:222:26\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)","timestamp":"2026-07-10T10:08:23.954Z"}
63
+ {"level":"error","message":"Translation error result.substring is not a function","stack":"TypeError: result.substring is not a function\n at /workspaces/Translator-API/central/src/index.js:222:26\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)","timestamp":"2026-07-10T10:08:32.356Z"}
64
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:09:13.314Z"}
65
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:09:13.316Z"}
66
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:09:13.316Z"}
67
+ {"level":"info","message":"Worker connected: c717ef9d-b2b2-4e4a-ad91-e035714d3d27","timestamp":"2026-07-10T10:09:15.265Z"}
68
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:10:02.173Z"}
69
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:10:02.175Z"}
70
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:10:02.175Z"}
71
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:10:47.151Z"}
72
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:10:47.154Z"}
73
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:10:47.154Z"}
74
+ {"level":"info","message":"Central server running on port 7820","timestamp":"2026-07-10T10:14:18.288Z"}
75
+ {"level":"info","message":"Admin panel: http://localhost:7820/admin","timestamp":"2026-07-10T10:14:18.291Z"}
76
+ {"level":"info","message":"API: http://localhost:7820/translate","timestamp":"2026-07-10T10:14:18.293Z"}
logs/worker.log ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-09T20:46:32.439Z"}
2
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-09T20:46:32.441Z"}
3
+ {"code":"ECONNREFUSED","level":"error","message":"WebSocket error","stack":"AggregateError [ECONNREFUSED]: \n at internalConnectMultiple (node:net:1142:49)\n at afterConnectMultiple (node:net:1723:7)","timestamp":"2026-07-09T20:46:32.458Z"}
4
+ {"level":"warn","message":"Disconnected from central server","timestamp":"2026-07-09T20:46:32.458Z"}
5
+ {"level":"info","message":"Reconnecting in 5000ms (attempt 1/10)","timestamp":"2026-07-09T20:46:32.458Z"}
6
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-09T20:46:37.462Z"}
7
+ {"code":"ECONNREFUSED","level":"error","message":"WebSocket error","stack":"AggregateError [ECONNREFUSED]: \n at internalConnectMultiple (node:net:1142:49)\n at afterConnectMultiple (node:net:1723:7)","timestamp":"2026-07-09T20:46:37.464Z"}
8
+ {"level":"warn","message":"Disconnected from central server","timestamp":"2026-07-09T20:46:37.464Z"}
9
+ {"level":"info","message":"Reconnecting in 10000ms (attempt 2/10)","timestamp":"2026-07-09T20:46:37.465Z"}
10
+ {"level":"info","message":"SIGTERM received, shutting down gracefully","timestamp":"2026-07-09T20:46:42.324Z"}
11
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-09T20:47:58.963Z"}
12
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-09T20:47:58.967Z"}
13
+ {"code":"ECONNREFUSED","level":"error","message":"WebSocket error","stack":"AggregateError [ECONNREFUSED]: \n at internalConnectMultiple (node:net:1142:49)\n at afterConnectMultiple (node:net:1723:7)","timestamp":"2026-07-09T20:47:58.993Z"}
14
+ {"level":"warn","message":"Disconnected from central server","timestamp":"2026-07-09T20:47:58.993Z"}
15
+ {"level":"info","message":"Reconnecting in 5000ms (attempt 1/10)","timestamp":"2026-07-09T20:47:58.994Z"}
16
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-09T20:48:03.999Z"}
17
+ {"code":"ECONNREFUSED","level":"error","message":"WebSocket error","stack":"AggregateError [ECONNREFUSED]: \n at internalConnectMultiple (node:net:1142:49)\n at afterConnectMultiple (node:net:1723:7)","timestamp":"2026-07-09T20:48:04.001Z"}
18
+ {"level":"warn","message":"Disconnected from central server","timestamp":"2026-07-09T20:48:04.001Z"}
19
+ {"level":"info","message":"Reconnecting in 10000ms (attempt 2/10)","timestamp":"2026-07-09T20:48:04.001Z"}
20
+ {"level":"info","message":"SIGTERM received, shutting down gracefully","timestamp":"2026-07-09T20:48:08.827Z"}
21
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-10T09:49:34.496Z"}
22
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T09:49:34.497Z"}
23
+ {"code":"ECONNREFUSED","level":"error","message":"WebSocket error","stack":"AggregateError [ECONNREFUSED]: \n at internalConnectMultiple (node:net:1142:49)\n at afterConnectMultiple (node:net:1723:7)","timestamp":"2026-07-10T09:49:34.513Z"}
24
+ {"level":"warn","message":"Disconnected from central server","timestamp":"2026-07-10T09:49:34.513Z"}
25
+ {"level":"info","message":"Reconnecting in 5000ms (attempt 1/10)","timestamp":"2026-07-10T09:49:34.513Z"}
26
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T09:49:39.518Z"}
27
+ {"code":"ECONNREFUSED","level":"error","message":"WebSocket error","stack":"AggregateError [ECONNREFUSED]: \n at internalConnectMultiple (node:net:1142:49)\n at afterConnectMultiple (node:net:1723:7)","timestamp":"2026-07-10T09:49:39.520Z"}
28
+ {"level":"warn","message":"Disconnected from central server","timestamp":"2026-07-10T09:49:39.520Z"}
29
+ {"level":"info","message":"Reconnecting in 10000ms (attempt 2/10)","timestamp":"2026-07-10T09:49:39.520Z"}
30
+ {"level":"info","message":"SIGTERM received, shutting down gracefully","timestamp":"2026-07-10T09:49:44.267Z"}
31
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-10T09:55:44.153Z"}
32
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T09:55:44.155Z"}
33
+ {"code":"ECONNREFUSED","level":"error","message":"WebSocket error","stack":"AggregateError [ECONNREFUSED]: \n at internalConnectMultiple (node:net:1142:49)\n at afterConnectMultiple (node:net:1723:7)","timestamp":"2026-07-10T09:55:44.175Z"}
34
+ {"level":"warn","message":"Disconnected from central server","timestamp":"2026-07-10T09:55:44.176Z"}
35
+ {"level":"info","message":"Reconnecting in 5000ms (attempt 1/10)","timestamp":"2026-07-10T09:55:44.176Z"}
36
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T09:55:49.182Z"}
37
+ {"code":"ECONNREFUSED","level":"error","message":"WebSocket error","stack":"AggregateError [ECONNREFUSED]: \n at internalConnectMultiple (node:net:1142:49)\n at afterConnectMultiple (node:net:1723:7)","timestamp":"2026-07-10T09:55:49.184Z"}
38
+ {"level":"warn","message":"Disconnected from central server","timestamp":"2026-07-10T09:55:49.184Z"}
39
+ {"level":"info","message":"Reconnecting in 10000ms (attempt 2/10)","timestamp":"2026-07-10T09:55:49.184Z"}
40
+ {"level":"info","message":"SIGTERM received, shutting down gracefully","timestamp":"2026-07-10T09:55:53.963Z"}
41
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-10T10:03:27.455Z"}
42
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T10:03:27.458Z"}
43
+ {"level":"info","message":"Connected to central server","timestamp":"2026-07-10T10:03:27.490Z"}
44
+ {"level":"info","message":"Worker registered with ID: 3af1c5f8-4c45-4d07-bb4c-c04bcbc5d78d, max jobs: 2","timestamp":"2026-07-10T10:03:27.491Z"}
45
+ {"level":"info","message":"SIGINT received, shutting down gracefully","timestamp":"2026-07-10T10:03:40.535Z"}
46
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-10T10:03:42.771Z"}
47
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T10:03:42.773Z"}
48
+ {"level":"info","message":"Connected to central server","timestamp":"2026-07-10T10:03:42.794Z"}
49
+ {"level":"info","message":"Worker registered with ID: f2ce5edb-2c63-437b-a60a-93e354750b36, max jobs: 2","timestamp":"2026-07-10T10:03:42.795Z"}
50
+ {"level":"info","message":"Starting job b1f7ec22-8352-4f86-9755-78c54ff47e40 (1/2)","timestamp":"2026-07-10T10:03:44.523Z"}
51
+ {"level":"error","message":"Job b1f7ec22-8352-4f86-9755-78c54ff47e40 failed translate is not a function","stack":"TypeError: translate is not a function\n at handleTranslateJob (/workspaces/Translator-API/worker/src/index.js:110:26)\n at handleMessage (/workspaces/Translator-API/worker/src/index.js:87:7)\n at WebSocket.<anonymous> (/workspaces/Translator-API/worker/src/index.js:48:7)\n at WebSocket.emit (node:events:508:28)\n at Receiver.receiverOnMessage (/workspaces/Translator-API/node_modules/ws/lib/websocket.js:1239:20)\n at Receiver.emit (node:events:508:28)\n at Receiver.dataMessage (/workspaces/Translator-API/node_modules/ws/lib/receiver.js:650:14)\n at Receiver.getData (/workspaces/Translator-API/node_modules/ws/lib/receiver.js:534:10)\n at Receiver.startLoop (/workspaces/Translator-API/node_modules/ws/lib/receiver.js:189:16)\n at Receiver._write (/workspaces/Translator-API/node_modules/ws/lib/receiver.js:116:10)","timestamp":"2026-07-10T10:03:44.524Z"}
52
+ {"level":"info","message":"SIGTERM received, shutting down gracefully","timestamp":"2026-07-10T10:03:57.655Z"}
53
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-10T10:05:32.261Z"}
54
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T10:05:32.264Z"}
55
+ {"level":"info","message":"Connected to central server","timestamp":"2026-07-10T10:05:32.295Z"}
56
+ {"level":"info","message":"Worker registered with ID: c9886383-d891-4972-811a-61f85c9e542b, max jobs: 2","timestamp":"2026-07-10T10:05:32.298Z"}
57
+ {"level":"info","message":"Starting job 2f72521b-35b3-4602-808e-768f42073858 (1/2)","timestamp":"2026-07-10T10:05:32.804Z"}
58
+ {"level":"info","message":"Starting job 3dcdee0d-2e0f-4b34-92af-aca60537ec7d (1/2)","timestamp":"2026-07-10T10:05:35.130Z"}
59
+ {"level":"info","message":"Starting job 8f8df025-83be-4a7e-9696-bc7aa64e5bb7 (1/2)","timestamp":"2026-07-10T10:06:54.038Z"}
60
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-10T10:07:23.257Z"}
61
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T10:07:23.258Z"}
62
+ {"level":"info","message":"Connected to central server","timestamp":"2026-07-10T10:07:23.282Z"}
63
+ {"level":"info","message":"Worker registered with ID: 4d87843e-da23-40dc-8351-f8146062418a, max jobs: 2","timestamp":"2026-07-10T10:07:23.283Z"}
64
+ {"level":"info","message":"SIGTERM received, shutting down gracefully","timestamp":"2026-07-10T10:07:38.138Z"}
65
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-10T10:08:22.230Z"}
66
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T10:08:22.234Z"}
67
+ {"level":"info","message":"Connected to central server","timestamp":"2026-07-10T10:08:22.265Z"}
68
+ {"level":"info","message":"Worker registered with ID: 2e9f4bc6-cb3d-44b4-8f7f-f4f7437c3c5c, max jobs: 2","timestamp":"2026-07-10T10:08:22.269Z"}
69
+ {"level":"info","message":"Starting job ed80e47a-e270-4fe3-931e-7eecff16af06 (1/2)","timestamp":"2026-07-10T10:08:23.192Z"}
70
+ {"level":"info","message":"Starting job 0d9b810b-80de-410a-a72e-ba7d2d551b37 (1/2)","timestamp":"2026-07-10T10:08:31.873Z"}
71
+ {"level":"info","message":"Starting Translator Worker","timestamp":"2026-07-10T10:09:15.237Z"}
72
+ {"level":"info","message":"Connecting to central server at ws://localhost:7820","timestamp":"2026-07-10T10:09:15.240Z"}
73
+ {"level":"info","message":"Connected to central server","timestamp":"2026-07-10T10:09:15.268Z"}
74
+ {"level":"info","message":"Worker registered with ID: c717ef9d-b2b2-4e4a-ad91-e035714d3d27, max jobs: 2","timestamp":"2026-07-10T10:09:15.271Z"}
75
+ {"level":"info","message":"Starting job 4194b556-1349-40ee-9238-270008807da0 (1/2)","timestamp":"2026-07-10T10:09:16.125Z"}
package-lock.json ADDED
@@ -0,0 +1,2019 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "translator-api",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "translator-api",
9
+ "version": "1.0.0",
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "@vitalets/google-translate-api": "^8.0.0",
13
+ "cors": "^2.8.5",
14
+ "dotenv": "^16.3.1",
15
+ "express": "^4.18.2",
16
+ "express-rate-limit": "^7.1.5",
17
+ "p-queue": "^7.4.1",
18
+ "uuid": "^9.0.1",
19
+ "winston": "^3.11.0",
20
+ "ws": "^8.14.2"
21
+ },
22
+ "devDependencies": {
23
+ "nodemon": "^3.0.2"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ }
28
+ },
29
+ "node_modules/@colors/colors": {
30
+ "version": "1.6.0",
31
+ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
32
+ "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
33
+ "license": "MIT",
34
+ "engines": {
35
+ "node": ">=0.1.90"
36
+ }
37
+ },
38
+ "node_modules/@dabh/diagnostics": {
39
+ "version": "2.0.8",
40
+ "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
41
+ "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "@so-ric/colorspace": "^1.1.6",
45
+ "enabled": "2.0.x",
46
+ "kuler": "^2.0.0"
47
+ }
48
+ },
49
+ "node_modules/@sindresorhus/is": {
50
+ "version": "0.14.0",
51
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
52
+ "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
53
+ "license": "MIT",
54
+ "engines": {
55
+ "node": ">=6"
56
+ }
57
+ },
58
+ "node_modules/@so-ric/colorspace": {
59
+ "version": "1.1.6",
60
+ "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
61
+ "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
62
+ "license": "MIT",
63
+ "dependencies": {
64
+ "color": "^5.0.2",
65
+ "text-hex": "1.0.x"
66
+ }
67
+ },
68
+ "node_modules/@szmarczak/http-timer": {
69
+ "version": "1.1.2",
70
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
71
+ "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
72
+ "license": "MIT",
73
+ "dependencies": {
74
+ "defer-to-connect": "^1.0.1"
75
+ },
76
+ "engines": {
77
+ "node": ">=6"
78
+ }
79
+ },
80
+ "node_modules/@types/triple-beam": {
81
+ "version": "1.3.5",
82
+ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
83
+ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
84
+ "license": "MIT"
85
+ },
86
+ "node_modules/@vitalets/google-translate-api": {
87
+ "version": "8.0.0",
88
+ "resolved": "https://registry.npmjs.org/@vitalets/google-translate-api/-/google-translate-api-8.0.0.tgz",
89
+ "integrity": "sha512-CHOi8DRgDAoLlFQy9pm1KHUauIk5ZXys3+Eh0aSYxzJSuxAE7Z2UL8+5MgNdl1oX7t/O7u+SwGhkhu6+U1bNcg==",
90
+ "license": "MIT",
91
+ "dependencies": {
92
+ "configstore": "^5.0.1",
93
+ "got": "^9.6.0"
94
+ }
95
+ },
96
+ "node_modules/accepts": {
97
+ "version": "1.3.8",
98
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
99
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
100
+ "license": "MIT",
101
+ "dependencies": {
102
+ "mime-types": "~2.1.34",
103
+ "negotiator": "0.6.3"
104
+ },
105
+ "engines": {
106
+ "node": ">= 0.6"
107
+ }
108
+ },
109
+ "node_modules/anymatch": {
110
+ "version": "3.1.3",
111
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
112
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
113
+ "dev": true,
114
+ "license": "ISC",
115
+ "dependencies": {
116
+ "normalize-path": "^3.0.0",
117
+ "picomatch": "^2.0.4"
118
+ },
119
+ "engines": {
120
+ "node": ">= 8"
121
+ }
122
+ },
123
+ "node_modules/array-flatten": {
124
+ "version": "1.1.1",
125
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
126
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
127
+ "license": "MIT"
128
+ },
129
+ "node_modules/async": {
130
+ "version": "3.2.6",
131
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
132
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
133
+ "license": "MIT"
134
+ },
135
+ "node_modules/balanced-match": {
136
+ "version": "4.0.4",
137
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
138
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
139
+ "dev": true,
140
+ "license": "MIT",
141
+ "engines": {
142
+ "node": "18 || 20 || >=22"
143
+ }
144
+ },
145
+ "node_modules/binary-extensions": {
146
+ "version": "2.3.0",
147
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
148
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
149
+ "dev": true,
150
+ "license": "MIT",
151
+ "engines": {
152
+ "node": ">=8"
153
+ },
154
+ "funding": {
155
+ "url": "https://github.com/sponsors/sindresorhus"
156
+ }
157
+ },
158
+ "node_modules/body-parser": {
159
+ "version": "1.20.6",
160
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz",
161
+ "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==",
162
+ "license": "MIT",
163
+ "dependencies": {
164
+ "bytes": "~3.1.2",
165
+ "content-type": "~1.0.5",
166
+ "debug": "2.6.9",
167
+ "depd": "2.0.0",
168
+ "destroy": "~1.2.0",
169
+ "http-errors": "~2.0.1",
170
+ "iconv-lite": "~0.4.24",
171
+ "on-finished": "~2.4.1",
172
+ "qs": "~6.15.1",
173
+ "raw-body": "~2.5.3",
174
+ "type-is": "~1.6.18",
175
+ "unpipe": "~1.0.0"
176
+ },
177
+ "engines": {
178
+ "node": ">= 0.8",
179
+ "npm": "1.2.8000 || >= 1.4.16"
180
+ }
181
+ },
182
+ "node_modules/brace-expansion": {
183
+ "version": "5.0.7",
184
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
185
+ "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
186
+ "dev": true,
187
+ "license": "MIT",
188
+ "dependencies": {
189
+ "balanced-match": "^4.0.2"
190
+ },
191
+ "engines": {
192
+ "node": "18 || 20 || >=22"
193
+ }
194
+ },
195
+ "node_modules/braces": {
196
+ "version": "3.0.3",
197
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
198
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
199
+ "dev": true,
200
+ "license": "MIT",
201
+ "dependencies": {
202
+ "fill-range": "^7.1.1"
203
+ },
204
+ "engines": {
205
+ "node": ">=8"
206
+ }
207
+ },
208
+ "node_modules/bytes": {
209
+ "version": "3.1.2",
210
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
211
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
212
+ "license": "MIT",
213
+ "engines": {
214
+ "node": ">= 0.8"
215
+ }
216
+ },
217
+ "node_modules/cacheable-request": {
218
+ "version": "6.1.0",
219
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
220
+ "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
221
+ "license": "MIT",
222
+ "dependencies": {
223
+ "clone-response": "^1.0.2",
224
+ "get-stream": "^5.1.0",
225
+ "http-cache-semantics": "^4.0.0",
226
+ "keyv": "^3.0.0",
227
+ "lowercase-keys": "^2.0.0",
228
+ "normalize-url": "^4.1.0",
229
+ "responselike": "^1.0.2"
230
+ },
231
+ "engines": {
232
+ "node": ">=8"
233
+ }
234
+ },
235
+ "node_modules/cacheable-request/node_modules/get-stream": {
236
+ "version": "5.2.0",
237
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
238
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
239
+ "license": "MIT",
240
+ "dependencies": {
241
+ "pump": "^3.0.0"
242
+ },
243
+ "engines": {
244
+ "node": ">=8"
245
+ },
246
+ "funding": {
247
+ "url": "https://github.com/sponsors/sindresorhus"
248
+ }
249
+ },
250
+ "node_modules/cacheable-request/node_modules/lowercase-keys": {
251
+ "version": "2.0.0",
252
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
253
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
254
+ "license": "MIT",
255
+ "engines": {
256
+ "node": ">=8"
257
+ }
258
+ },
259
+ "node_modules/call-bind-apply-helpers": {
260
+ "version": "1.0.2",
261
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
262
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
263
+ "license": "MIT",
264
+ "dependencies": {
265
+ "es-errors": "^1.3.0",
266
+ "function-bind": "^1.1.2"
267
+ },
268
+ "engines": {
269
+ "node": ">= 0.4"
270
+ }
271
+ },
272
+ "node_modules/call-bound": {
273
+ "version": "1.0.4",
274
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
275
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
276
+ "license": "MIT",
277
+ "dependencies": {
278
+ "call-bind-apply-helpers": "^1.0.2",
279
+ "get-intrinsic": "^1.3.0"
280
+ },
281
+ "engines": {
282
+ "node": ">= 0.4"
283
+ },
284
+ "funding": {
285
+ "url": "https://github.com/sponsors/ljharb"
286
+ }
287
+ },
288
+ "node_modules/chokidar": {
289
+ "version": "3.6.0",
290
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
291
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
292
+ "dev": true,
293
+ "license": "MIT",
294
+ "dependencies": {
295
+ "anymatch": "~3.1.2",
296
+ "braces": "~3.0.2",
297
+ "glob-parent": "~5.1.2",
298
+ "is-binary-path": "~2.1.0",
299
+ "is-glob": "~4.0.1",
300
+ "normalize-path": "~3.0.0",
301
+ "readdirp": "~3.6.0"
302
+ },
303
+ "engines": {
304
+ "node": ">= 8.10.0"
305
+ },
306
+ "funding": {
307
+ "url": "https://paulmillr.com/funding/"
308
+ },
309
+ "optionalDependencies": {
310
+ "fsevents": "~2.3.2"
311
+ }
312
+ },
313
+ "node_modules/clone-response": {
314
+ "version": "1.0.3",
315
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
316
+ "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
317
+ "license": "MIT",
318
+ "dependencies": {
319
+ "mimic-response": "^1.0.0"
320
+ },
321
+ "funding": {
322
+ "url": "https://github.com/sponsors/sindresorhus"
323
+ }
324
+ },
325
+ "node_modules/color": {
326
+ "version": "5.0.3",
327
+ "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
328
+ "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==",
329
+ "license": "MIT",
330
+ "dependencies": {
331
+ "color-convert": "^3.1.3",
332
+ "color-string": "^2.1.3"
333
+ },
334
+ "engines": {
335
+ "node": ">=18"
336
+ }
337
+ },
338
+ "node_modules/color-convert": {
339
+ "version": "3.1.3",
340
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
341
+ "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
342
+ "license": "MIT",
343
+ "dependencies": {
344
+ "color-name": "^2.0.0"
345
+ },
346
+ "engines": {
347
+ "node": ">=14.6"
348
+ }
349
+ },
350
+ "node_modules/color-name": {
351
+ "version": "2.1.0",
352
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
353
+ "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
354
+ "license": "MIT",
355
+ "engines": {
356
+ "node": ">=12.20"
357
+ }
358
+ },
359
+ "node_modules/color-string": {
360
+ "version": "2.1.4",
361
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
362
+ "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
363
+ "license": "MIT",
364
+ "dependencies": {
365
+ "color-name": "^2.0.0"
366
+ },
367
+ "engines": {
368
+ "node": ">=18"
369
+ }
370
+ },
371
+ "node_modules/configstore": {
372
+ "version": "5.0.1",
373
+ "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz",
374
+ "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==",
375
+ "license": "BSD-2-Clause",
376
+ "dependencies": {
377
+ "dot-prop": "^5.2.0",
378
+ "graceful-fs": "^4.1.2",
379
+ "make-dir": "^3.0.0",
380
+ "unique-string": "^2.0.0",
381
+ "write-file-atomic": "^3.0.0",
382
+ "xdg-basedir": "^4.0.0"
383
+ },
384
+ "engines": {
385
+ "node": ">=8"
386
+ }
387
+ },
388
+ "node_modules/content-disposition": {
389
+ "version": "0.5.4",
390
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
391
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
392
+ "license": "MIT",
393
+ "dependencies": {
394
+ "safe-buffer": "5.2.1"
395
+ },
396
+ "engines": {
397
+ "node": ">= 0.6"
398
+ }
399
+ },
400
+ "node_modules/content-type": {
401
+ "version": "1.0.5",
402
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
403
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
404
+ "license": "MIT",
405
+ "engines": {
406
+ "node": ">= 0.6"
407
+ }
408
+ },
409
+ "node_modules/cookie": {
410
+ "version": "0.7.2",
411
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
412
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
413
+ "license": "MIT",
414
+ "engines": {
415
+ "node": ">= 0.6"
416
+ }
417
+ },
418
+ "node_modules/cookie-signature": {
419
+ "version": "1.0.7",
420
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
421
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
422
+ "license": "MIT"
423
+ },
424
+ "node_modules/cors": {
425
+ "version": "2.8.6",
426
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
427
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
428
+ "license": "MIT",
429
+ "dependencies": {
430
+ "object-assign": "^4",
431
+ "vary": "^1"
432
+ },
433
+ "engines": {
434
+ "node": ">= 0.10"
435
+ },
436
+ "funding": {
437
+ "type": "opencollective",
438
+ "url": "https://opencollective.com/express"
439
+ }
440
+ },
441
+ "node_modules/crypto-random-string": {
442
+ "version": "2.0.0",
443
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
444
+ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
445
+ "license": "MIT",
446
+ "engines": {
447
+ "node": ">=8"
448
+ }
449
+ },
450
+ "node_modules/debug": {
451
+ "version": "2.6.9",
452
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
453
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
454
+ "license": "MIT",
455
+ "dependencies": {
456
+ "ms": "2.0.0"
457
+ }
458
+ },
459
+ "node_modules/decompress-response": {
460
+ "version": "3.3.0",
461
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
462
+ "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==",
463
+ "license": "MIT",
464
+ "dependencies": {
465
+ "mimic-response": "^1.0.0"
466
+ },
467
+ "engines": {
468
+ "node": ">=4"
469
+ }
470
+ },
471
+ "node_modules/defer-to-connect": {
472
+ "version": "1.1.3",
473
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
474
+ "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==",
475
+ "license": "MIT"
476
+ },
477
+ "node_modules/depd": {
478
+ "version": "2.0.0",
479
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
480
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
481
+ "license": "MIT",
482
+ "engines": {
483
+ "node": ">= 0.8"
484
+ }
485
+ },
486
+ "node_modules/destroy": {
487
+ "version": "1.2.0",
488
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
489
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
490
+ "license": "MIT",
491
+ "engines": {
492
+ "node": ">= 0.8",
493
+ "npm": "1.2.8000 || >= 1.4.16"
494
+ }
495
+ },
496
+ "node_modules/dot-prop": {
497
+ "version": "5.3.0",
498
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
499
+ "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
500
+ "license": "MIT",
501
+ "dependencies": {
502
+ "is-obj": "^2.0.0"
503
+ },
504
+ "engines": {
505
+ "node": ">=8"
506
+ }
507
+ },
508
+ "node_modules/dotenv": {
509
+ "version": "16.6.1",
510
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
511
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
512
+ "license": "BSD-2-Clause",
513
+ "engines": {
514
+ "node": ">=12"
515
+ },
516
+ "funding": {
517
+ "url": "https://dotenvx.com"
518
+ }
519
+ },
520
+ "node_modules/dunder-proto": {
521
+ "version": "1.0.1",
522
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
523
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
524
+ "license": "MIT",
525
+ "dependencies": {
526
+ "call-bind-apply-helpers": "^1.0.1",
527
+ "es-errors": "^1.3.0",
528
+ "gopd": "^1.2.0"
529
+ },
530
+ "engines": {
531
+ "node": ">= 0.4"
532
+ }
533
+ },
534
+ "node_modules/duplexer3": {
535
+ "version": "0.1.5",
536
+ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz",
537
+ "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==",
538
+ "license": "BSD-3-Clause"
539
+ },
540
+ "node_modules/ee-first": {
541
+ "version": "1.1.1",
542
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
543
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
544
+ "license": "MIT"
545
+ },
546
+ "node_modules/enabled": {
547
+ "version": "2.0.0",
548
+ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
549
+ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
550
+ "license": "MIT"
551
+ },
552
+ "node_modules/encodeurl": {
553
+ "version": "2.0.0",
554
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
555
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
556
+ "license": "MIT",
557
+ "engines": {
558
+ "node": ">= 0.8"
559
+ }
560
+ },
561
+ "node_modules/end-of-stream": {
562
+ "version": "1.4.5",
563
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
564
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
565
+ "license": "MIT",
566
+ "dependencies": {
567
+ "once": "^1.4.0"
568
+ }
569
+ },
570
+ "node_modules/es-define-property": {
571
+ "version": "1.0.1",
572
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
573
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
574
+ "license": "MIT",
575
+ "engines": {
576
+ "node": ">= 0.4"
577
+ }
578
+ },
579
+ "node_modules/es-errors": {
580
+ "version": "1.3.0",
581
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
582
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
583
+ "license": "MIT",
584
+ "engines": {
585
+ "node": ">= 0.4"
586
+ }
587
+ },
588
+ "node_modules/es-object-atoms": {
589
+ "version": "1.1.2",
590
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
591
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
592
+ "license": "MIT",
593
+ "dependencies": {
594
+ "es-errors": "^1.3.0"
595
+ },
596
+ "engines": {
597
+ "node": ">= 0.4"
598
+ }
599
+ },
600
+ "node_modules/escape-html": {
601
+ "version": "1.0.3",
602
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
603
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
604
+ "license": "MIT"
605
+ },
606
+ "node_modules/etag": {
607
+ "version": "1.8.1",
608
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
609
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
610
+ "license": "MIT",
611
+ "engines": {
612
+ "node": ">= 0.6"
613
+ }
614
+ },
615
+ "node_modules/eventemitter3": {
616
+ "version": "5.0.4",
617
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
618
+ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
619
+ "license": "MIT"
620
+ },
621
+ "node_modules/express": {
622
+ "version": "4.22.2",
623
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
624
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
625
+ "license": "MIT",
626
+ "dependencies": {
627
+ "accepts": "~1.3.8",
628
+ "array-flatten": "1.1.1",
629
+ "body-parser": "~1.20.5",
630
+ "content-disposition": "~0.5.4",
631
+ "content-type": "~1.0.4",
632
+ "cookie": "~0.7.1",
633
+ "cookie-signature": "~1.0.6",
634
+ "debug": "2.6.9",
635
+ "depd": "2.0.0",
636
+ "encodeurl": "~2.0.0",
637
+ "escape-html": "~1.0.3",
638
+ "etag": "~1.8.1",
639
+ "finalhandler": "~1.3.1",
640
+ "fresh": "~0.5.2",
641
+ "http-errors": "~2.0.0",
642
+ "merge-descriptors": "1.0.3",
643
+ "methods": "~1.1.2",
644
+ "on-finished": "~2.4.1",
645
+ "parseurl": "~1.3.3",
646
+ "path-to-regexp": "~0.1.12",
647
+ "proxy-addr": "~2.0.7",
648
+ "qs": "~6.15.1",
649
+ "range-parser": "~1.2.1",
650
+ "safe-buffer": "5.2.1",
651
+ "send": "~0.19.0",
652
+ "serve-static": "~1.16.2",
653
+ "setprototypeof": "1.2.0",
654
+ "statuses": "~2.0.1",
655
+ "type-is": "~1.6.18",
656
+ "utils-merge": "1.0.1",
657
+ "vary": "~1.1.2"
658
+ },
659
+ "engines": {
660
+ "node": ">= 0.10.0"
661
+ },
662
+ "funding": {
663
+ "type": "opencollective",
664
+ "url": "https://opencollective.com/express"
665
+ }
666
+ },
667
+ "node_modules/express-rate-limit": {
668
+ "version": "7.5.1",
669
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz",
670
+ "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==",
671
+ "license": "MIT",
672
+ "engines": {
673
+ "node": ">= 16"
674
+ },
675
+ "funding": {
676
+ "url": "https://github.com/sponsors/express-rate-limit"
677
+ },
678
+ "peerDependencies": {
679
+ "express": ">= 4.11"
680
+ }
681
+ },
682
+ "node_modules/fecha": {
683
+ "version": "4.2.3",
684
+ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
685
+ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
686
+ "license": "MIT"
687
+ },
688
+ "node_modules/fill-range": {
689
+ "version": "7.1.1",
690
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
691
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
692
+ "dev": true,
693
+ "license": "MIT",
694
+ "dependencies": {
695
+ "to-regex-range": "^5.0.1"
696
+ },
697
+ "engines": {
698
+ "node": ">=8"
699
+ }
700
+ },
701
+ "node_modules/finalhandler": {
702
+ "version": "1.3.2",
703
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
704
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
705
+ "license": "MIT",
706
+ "dependencies": {
707
+ "debug": "2.6.9",
708
+ "encodeurl": "~2.0.0",
709
+ "escape-html": "~1.0.3",
710
+ "on-finished": "~2.4.1",
711
+ "parseurl": "~1.3.3",
712
+ "statuses": "~2.0.2",
713
+ "unpipe": "~1.0.0"
714
+ },
715
+ "engines": {
716
+ "node": ">= 0.8"
717
+ }
718
+ },
719
+ "node_modules/fn.name": {
720
+ "version": "1.1.0",
721
+ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
722
+ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
723
+ "license": "MIT"
724
+ },
725
+ "node_modules/forwarded": {
726
+ "version": "0.2.0",
727
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
728
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
729
+ "license": "MIT",
730
+ "engines": {
731
+ "node": ">= 0.6"
732
+ }
733
+ },
734
+ "node_modules/fresh": {
735
+ "version": "0.5.2",
736
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
737
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
738
+ "license": "MIT",
739
+ "engines": {
740
+ "node": ">= 0.6"
741
+ }
742
+ },
743
+ "node_modules/fsevents": {
744
+ "version": "2.3.3",
745
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
746
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
747
+ "dev": true,
748
+ "hasInstallScript": true,
749
+ "license": "MIT",
750
+ "optional": true,
751
+ "os": [
752
+ "darwin"
753
+ ],
754
+ "engines": {
755
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
756
+ }
757
+ },
758
+ "node_modules/function-bind": {
759
+ "version": "1.1.2",
760
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
761
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
762
+ "license": "MIT",
763
+ "funding": {
764
+ "url": "https://github.com/sponsors/ljharb"
765
+ }
766
+ },
767
+ "node_modules/get-intrinsic": {
768
+ "version": "1.3.0",
769
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
770
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
771
+ "license": "MIT",
772
+ "dependencies": {
773
+ "call-bind-apply-helpers": "^1.0.2",
774
+ "es-define-property": "^1.0.1",
775
+ "es-errors": "^1.3.0",
776
+ "es-object-atoms": "^1.1.1",
777
+ "function-bind": "^1.1.2",
778
+ "get-proto": "^1.0.1",
779
+ "gopd": "^1.2.0",
780
+ "has-symbols": "^1.1.0",
781
+ "hasown": "^2.0.2",
782
+ "math-intrinsics": "^1.1.0"
783
+ },
784
+ "engines": {
785
+ "node": ">= 0.4"
786
+ },
787
+ "funding": {
788
+ "url": "https://github.com/sponsors/ljharb"
789
+ }
790
+ },
791
+ "node_modules/get-proto": {
792
+ "version": "1.0.1",
793
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
794
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
795
+ "license": "MIT",
796
+ "dependencies": {
797
+ "dunder-proto": "^1.0.1",
798
+ "es-object-atoms": "^1.0.0"
799
+ },
800
+ "engines": {
801
+ "node": ">= 0.4"
802
+ }
803
+ },
804
+ "node_modules/get-stream": {
805
+ "version": "4.1.0",
806
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
807
+ "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
808
+ "license": "MIT",
809
+ "dependencies": {
810
+ "pump": "^3.0.0"
811
+ },
812
+ "engines": {
813
+ "node": ">=6"
814
+ }
815
+ },
816
+ "node_modules/glob-parent": {
817
+ "version": "5.1.2",
818
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
819
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
820
+ "dev": true,
821
+ "license": "ISC",
822
+ "dependencies": {
823
+ "is-glob": "^4.0.1"
824
+ },
825
+ "engines": {
826
+ "node": ">= 6"
827
+ }
828
+ },
829
+ "node_modules/gopd": {
830
+ "version": "1.2.0",
831
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
832
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
833
+ "license": "MIT",
834
+ "engines": {
835
+ "node": ">= 0.4"
836
+ },
837
+ "funding": {
838
+ "url": "https://github.com/sponsors/ljharb"
839
+ }
840
+ },
841
+ "node_modules/got": {
842
+ "version": "9.6.0",
843
+ "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
844
+ "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
845
+ "license": "MIT",
846
+ "dependencies": {
847
+ "@sindresorhus/is": "^0.14.0",
848
+ "@szmarczak/http-timer": "^1.1.2",
849
+ "cacheable-request": "^6.0.0",
850
+ "decompress-response": "^3.3.0",
851
+ "duplexer3": "^0.1.4",
852
+ "get-stream": "^4.1.0",
853
+ "lowercase-keys": "^1.0.1",
854
+ "mimic-response": "^1.0.1",
855
+ "p-cancelable": "^1.0.0",
856
+ "to-readable-stream": "^1.0.0",
857
+ "url-parse-lax": "^3.0.0"
858
+ },
859
+ "engines": {
860
+ "node": ">=8.6"
861
+ }
862
+ },
863
+ "node_modules/graceful-fs": {
864
+ "version": "4.2.11",
865
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
866
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
867
+ "license": "ISC"
868
+ },
869
+ "node_modules/has-flag": {
870
+ "version": "3.0.0",
871
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
872
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
873
+ "dev": true,
874
+ "license": "MIT",
875
+ "engines": {
876
+ "node": ">=4"
877
+ }
878
+ },
879
+ "node_modules/has-symbols": {
880
+ "version": "1.1.0",
881
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
882
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
883
+ "license": "MIT",
884
+ "engines": {
885
+ "node": ">= 0.4"
886
+ },
887
+ "funding": {
888
+ "url": "https://github.com/sponsors/ljharb"
889
+ }
890
+ },
891
+ "node_modules/hasown": {
892
+ "version": "2.0.4",
893
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
894
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
895
+ "license": "MIT",
896
+ "dependencies": {
897
+ "function-bind": "^1.1.2"
898
+ },
899
+ "engines": {
900
+ "node": ">= 0.4"
901
+ }
902
+ },
903
+ "node_modules/http-cache-semantics": {
904
+ "version": "4.2.0",
905
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
906
+ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
907
+ "license": "BSD-2-Clause"
908
+ },
909
+ "node_modules/http-errors": {
910
+ "version": "2.0.1",
911
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
912
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
913
+ "license": "MIT",
914
+ "dependencies": {
915
+ "depd": "~2.0.0",
916
+ "inherits": "~2.0.4",
917
+ "setprototypeof": "~1.2.0",
918
+ "statuses": "~2.0.2",
919
+ "toidentifier": "~1.0.1"
920
+ },
921
+ "engines": {
922
+ "node": ">= 0.8"
923
+ },
924
+ "funding": {
925
+ "type": "opencollective",
926
+ "url": "https://opencollective.com/express"
927
+ }
928
+ },
929
+ "node_modules/iconv-lite": {
930
+ "version": "0.4.24",
931
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
932
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
933
+ "license": "MIT",
934
+ "dependencies": {
935
+ "safer-buffer": ">= 2.1.2 < 3"
936
+ },
937
+ "engines": {
938
+ "node": ">=0.10.0"
939
+ }
940
+ },
941
+ "node_modules/ignore-by-default": {
942
+ "version": "1.0.1",
943
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
944
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
945
+ "dev": true,
946
+ "license": "ISC"
947
+ },
948
+ "node_modules/imurmurhash": {
949
+ "version": "0.1.4",
950
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
951
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
952
+ "license": "MIT",
953
+ "engines": {
954
+ "node": ">=0.8.19"
955
+ }
956
+ },
957
+ "node_modules/inherits": {
958
+ "version": "2.0.4",
959
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
960
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
961
+ "license": "ISC"
962
+ },
963
+ "node_modules/ipaddr.js": {
964
+ "version": "1.9.1",
965
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
966
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
967
+ "license": "MIT",
968
+ "engines": {
969
+ "node": ">= 0.10"
970
+ }
971
+ },
972
+ "node_modules/is-binary-path": {
973
+ "version": "2.1.0",
974
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
975
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
976
+ "dev": true,
977
+ "license": "MIT",
978
+ "dependencies": {
979
+ "binary-extensions": "^2.0.0"
980
+ },
981
+ "engines": {
982
+ "node": ">=8"
983
+ }
984
+ },
985
+ "node_modules/is-extglob": {
986
+ "version": "2.1.1",
987
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
988
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
989
+ "dev": true,
990
+ "license": "MIT",
991
+ "engines": {
992
+ "node": ">=0.10.0"
993
+ }
994
+ },
995
+ "node_modules/is-glob": {
996
+ "version": "4.0.3",
997
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
998
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
999
+ "dev": true,
1000
+ "license": "MIT",
1001
+ "dependencies": {
1002
+ "is-extglob": "^2.1.1"
1003
+ },
1004
+ "engines": {
1005
+ "node": ">=0.10.0"
1006
+ }
1007
+ },
1008
+ "node_modules/is-number": {
1009
+ "version": "7.0.0",
1010
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1011
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1012
+ "dev": true,
1013
+ "license": "MIT",
1014
+ "engines": {
1015
+ "node": ">=0.12.0"
1016
+ }
1017
+ },
1018
+ "node_modules/is-obj": {
1019
+ "version": "2.0.0",
1020
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
1021
+ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
1022
+ "license": "MIT",
1023
+ "engines": {
1024
+ "node": ">=8"
1025
+ }
1026
+ },
1027
+ "node_modules/is-stream": {
1028
+ "version": "2.0.1",
1029
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
1030
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
1031
+ "license": "MIT",
1032
+ "engines": {
1033
+ "node": ">=8"
1034
+ },
1035
+ "funding": {
1036
+ "url": "https://github.com/sponsors/sindresorhus"
1037
+ }
1038
+ },
1039
+ "node_modules/is-typedarray": {
1040
+ "version": "1.0.0",
1041
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
1042
+ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
1043
+ "license": "MIT"
1044
+ },
1045
+ "node_modules/json-buffer": {
1046
+ "version": "3.0.0",
1047
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
1048
+ "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==",
1049
+ "license": "MIT"
1050
+ },
1051
+ "node_modules/keyv": {
1052
+ "version": "3.1.0",
1053
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
1054
+ "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
1055
+ "license": "MIT",
1056
+ "dependencies": {
1057
+ "json-buffer": "3.0.0"
1058
+ }
1059
+ },
1060
+ "node_modules/kuler": {
1061
+ "version": "2.0.0",
1062
+ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
1063
+ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
1064
+ "license": "MIT"
1065
+ },
1066
+ "node_modules/logform": {
1067
+ "version": "2.7.0",
1068
+ "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
1069
+ "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
1070
+ "license": "MIT",
1071
+ "dependencies": {
1072
+ "@colors/colors": "1.6.0",
1073
+ "@types/triple-beam": "^1.3.2",
1074
+ "fecha": "^4.2.0",
1075
+ "ms": "^2.1.1",
1076
+ "safe-stable-stringify": "^2.3.1",
1077
+ "triple-beam": "^1.3.0"
1078
+ },
1079
+ "engines": {
1080
+ "node": ">= 12.0.0"
1081
+ }
1082
+ },
1083
+ "node_modules/logform/node_modules/ms": {
1084
+ "version": "2.1.3",
1085
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1086
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1087
+ "license": "MIT"
1088
+ },
1089
+ "node_modules/lowercase-keys": {
1090
+ "version": "1.0.1",
1091
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
1092
+ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
1093
+ "license": "MIT",
1094
+ "engines": {
1095
+ "node": ">=0.10.0"
1096
+ }
1097
+ },
1098
+ "node_modules/make-dir": {
1099
+ "version": "3.1.0",
1100
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
1101
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
1102
+ "license": "MIT",
1103
+ "dependencies": {
1104
+ "semver": "^6.0.0"
1105
+ },
1106
+ "engines": {
1107
+ "node": ">=8"
1108
+ },
1109
+ "funding": {
1110
+ "url": "https://github.com/sponsors/sindresorhus"
1111
+ }
1112
+ },
1113
+ "node_modules/math-intrinsics": {
1114
+ "version": "1.1.0",
1115
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1116
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1117
+ "license": "MIT",
1118
+ "engines": {
1119
+ "node": ">= 0.4"
1120
+ }
1121
+ },
1122
+ "node_modules/media-typer": {
1123
+ "version": "0.3.0",
1124
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
1125
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
1126
+ "license": "MIT",
1127
+ "engines": {
1128
+ "node": ">= 0.6"
1129
+ }
1130
+ },
1131
+ "node_modules/merge-descriptors": {
1132
+ "version": "1.0.3",
1133
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
1134
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
1135
+ "license": "MIT",
1136
+ "funding": {
1137
+ "url": "https://github.com/sponsors/sindresorhus"
1138
+ }
1139
+ },
1140
+ "node_modules/methods": {
1141
+ "version": "1.1.2",
1142
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
1143
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
1144
+ "license": "MIT",
1145
+ "engines": {
1146
+ "node": ">= 0.6"
1147
+ }
1148
+ },
1149
+ "node_modules/mime": {
1150
+ "version": "1.6.0",
1151
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1152
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
1153
+ "license": "MIT",
1154
+ "bin": {
1155
+ "mime": "cli.js"
1156
+ },
1157
+ "engines": {
1158
+ "node": ">=4"
1159
+ }
1160
+ },
1161
+ "node_modules/mime-db": {
1162
+ "version": "1.52.0",
1163
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1164
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1165
+ "license": "MIT",
1166
+ "engines": {
1167
+ "node": ">= 0.6"
1168
+ }
1169
+ },
1170
+ "node_modules/mime-types": {
1171
+ "version": "2.1.35",
1172
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1173
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1174
+ "license": "MIT",
1175
+ "dependencies": {
1176
+ "mime-db": "1.52.0"
1177
+ },
1178
+ "engines": {
1179
+ "node": ">= 0.6"
1180
+ }
1181
+ },
1182
+ "node_modules/mimic-response": {
1183
+ "version": "1.0.1",
1184
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
1185
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
1186
+ "license": "MIT",
1187
+ "engines": {
1188
+ "node": ">=4"
1189
+ }
1190
+ },
1191
+ "node_modules/minimatch": {
1192
+ "version": "10.2.5",
1193
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
1194
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
1195
+ "dev": true,
1196
+ "license": "BlueOak-1.0.0",
1197
+ "dependencies": {
1198
+ "brace-expansion": "^5.0.5"
1199
+ },
1200
+ "engines": {
1201
+ "node": "18 || 20 || >=22"
1202
+ },
1203
+ "funding": {
1204
+ "url": "https://github.com/sponsors/isaacs"
1205
+ }
1206
+ },
1207
+ "node_modules/ms": {
1208
+ "version": "2.0.0",
1209
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1210
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
1211
+ "license": "MIT"
1212
+ },
1213
+ "node_modules/negotiator": {
1214
+ "version": "0.6.3",
1215
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1216
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
1217
+ "license": "MIT",
1218
+ "engines": {
1219
+ "node": ">= 0.6"
1220
+ }
1221
+ },
1222
+ "node_modules/nodemon": {
1223
+ "version": "3.1.14",
1224
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
1225
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
1226
+ "dev": true,
1227
+ "license": "MIT",
1228
+ "dependencies": {
1229
+ "chokidar": "^3.5.2",
1230
+ "debug": "^4",
1231
+ "ignore-by-default": "^1.0.1",
1232
+ "minimatch": "^10.2.1",
1233
+ "pstree.remy": "^1.1.8",
1234
+ "semver": "^7.5.3",
1235
+ "simple-update-notifier": "^2.0.0",
1236
+ "supports-color": "^5.5.0",
1237
+ "touch": "^3.1.0",
1238
+ "undefsafe": "^2.0.5"
1239
+ },
1240
+ "bin": {
1241
+ "nodemon": "bin/nodemon.js"
1242
+ },
1243
+ "engines": {
1244
+ "node": ">=10"
1245
+ },
1246
+ "funding": {
1247
+ "type": "opencollective",
1248
+ "url": "https://opencollective.com/nodemon"
1249
+ }
1250
+ },
1251
+ "node_modules/nodemon/node_modules/debug": {
1252
+ "version": "4.4.3",
1253
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1254
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1255
+ "dev": true,
1256
+ "license": "MIT",
1257
+ "dependencies": {
1258
+ "ms": "^2.1.3"
1259
+ },
1260
+ "engines": {
1261
+ "node": ">=6.0"
1262
+ },
1263
+ "peerDependenciesMeta": {
1264
+ "supports-color": {
1265
+ "optional": true
1266
+ }
1267
+ }
1268
+ },
1269
+ "node_modules/nodemon/node_modules/ms": {
1270
+ "version": "2.1.3",
1271
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1272
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1273
+ "dev": true,
1274
+ "license": "MIT"
1275
+ },
1276
+ "node_modules/nodemon/node_modules/semver": {
1277
+ "version": "7.8.5",
1278
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
1279
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
1280
+ "dev": true,
1281
+ "license": "ISC",
1282
+ "bin": {
1283
+ "semver": "bin/semver.js"
1284
+ },
1285
+ "engines": {
1286
+ "node": ">=10"
1287
+ }
1288
+ },
1289
+ "node_modules/normalize-path": {
1290
+ "version": "3.0.0",
1291
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1292
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1293
+ "dev": true,
1294
+ "license": "MIT",
1295
+ "engines": {
1296
+ "node": ">=0.10.0"
1297
+ }
1298
+ },
1299
+ "node_modules/normalize-url": {
1300
+ "version": "4.5.1",
1301
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz",
1302
+ "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==",
1303
+ "license": "MIT",
1304
+ "engines": {
1305
+ "node": ">=8"
1306
+ }
1307
+ },
1308
+ "node_modules/object-assign": {
1309
+ "version": "4.1.1",
1310
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1311
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1312
+ "license": "MIT",
1313
+ "engines": {
1314
+ "node": ">=0.10.0"
1315
+ }
1316
+ },
1317
+ "node_modules/object-inspect": {
1318
+ "version": "1.13.4",
1319
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1320
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1321
+ "license": "MIT",
1322
+ "engines": {
1323
+ "node": ">= 0.4"
1324
+ },
1325
+ "funding": {
1326
+ "url": "https://github.com/sponsors/ljharb"
1327
+ }
1328
+ },
1329
+ "node_modules/on-finished": {
1330
+ "version": "2.4.1",
1331
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1332
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1333
+ "license": "MIT",
1334
+ "dependencies": {
1335
+ "ee-first": "1.1.1"
1336
+ },
1337
+ "engines": {
1338
+ "node": ">= 0.8"
1339
+ }
1340
+ },
1341
+ "node_modules/once": {
1342
+ "version": "1.4.0",
1343
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1344
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1345
+ "license": "ISC",
1346
+ "dependencies": {
1347
+ "wrappy": "1"
1348
+ }
1349
+ },
1350
+ "node_modules/one-time": {
1351
+ "version": "1.0.0",
1352
+ "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
1353
+ "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
1354
+ "license": "MIT",
1355
+ "dependencies": {
1356
+ "fn.name": "1.x.x"
1357
+ }
1358
+ },
1359
+ "node_modules/p-cancelable": {
1360
+ "version": "1.1.0",
1361
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
1362
+ "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
1363
+ "license": "MIT",
1364
+ "engines": {
1365
+ "node": ">=6"
1366
+ }
1367
+ },
1368
+ "node_modules/p-queue": {
1369
+ "version": "7.4.1",
1370
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.4.1.tgz",
1371
+ "integrity": "sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA==",
1372
+ "license": "MIT",
1373
+ "dependencies": {
1374
+ "eventemitter3": "^5.0.1",
1375
+ "p-timeout": "^5.0.2"
1376
+ },
1377
+ "engines": {
1378
+ "node": ">=12"
1379
+ },
1380
+ "funding": {
1381
+ "url": "https://github.com/sponsors/sindresorhus"
1382
+ }
1383
+ },
1384
+ "node_modules/p-timeout": {
1385
+ "version": "5.1.0",
1386
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz",
1387
+ "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==",
1388
+ "license": "MIT",
1389
+ "engines": {
1390
+ "node": ">=12"
1391
+ },
1392
+ "funding": {
1393
+ "url": "https://github.com/sponsors/sindresorhus"
1394
+ }
1395
+ },
1396
+ "node_modules/parseurl": {
1397
+ "version": "1.3.3",
1398
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1399
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1400
+ "license": "MIT",
1401
+ "engines": {
1402
+ "node": ">= 0.8"
1403
+ }
1404
+ },
1405
+ "node_modules/path-to-regexp": {
1406
+ "version": "0.1.13",
1407
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
1408
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
1409
+ "license": "MIT"
1410
+ },
1411
+ "node_modules/picomatch": {
1412
+ "version": "2.3.2",
1413
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
1414
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
1415
+ "dev": true,
1416
+ "license": "MIT",
1417
+ "engines": {
1418
+ "node": ">=8.6"
1419
+ },
1420
+ "funding": {
1421
+ "url": "https://github.com/sponsors/jonschlinkert"
1422
+ }
1423
+ },
1424
+ "node_modules/prepend-http": {
1425
+ "version": "2.0.0",
1426
+ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
1427
+ "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==",
1428
+ "license": "MIT",
1429
+ "engines": {
1430
+ "node": ">=4"
1431
+ }
1432
+ },
1433
+ "node_modules/proxy-addr": {
1434
+ "version": "2.0.7",
1435
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1436
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1437
+ "license": "MIT",
1438
+ "dependencies": {
1439
+ "forwarded": "0.2.0",
1440
+ "ipaddr.js": "1.9.1"
1441
+ },
1442
+ "engines": {
1443
+ "node": ">= 0.10"
1444
+ }
1445
+ },
1446
+ "node_modules/pstree.remy": {
1447
+ "version": "1.1.8",
1448
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
1449
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
1450
+ "dev": true,
1451
+ "license": "MIT"
1452
+ },
1453
+ "node_modules/pump": {
1454
+ "version": "3.0.4",
1455
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
1456
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
1457
+ "license": "MIT",
1458
+ "dependencies": {
1459
+ "end-of-stream": "^1.1.0",
1460
+ "once": "^1.3.1"
1461
+ }
1462
+ },
1463
+ "node_modules/qs": {
1464
+ "version": "6.15.3",
1465
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
1466
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
1467
+ "license": "BSD-3-Clause",
1468
+ "dependencies": {
1469
+ "es-define-property": "^1.0.1",
1470
+ "side-channel": "^1.1.1"
1471
+ },
1472
+ "engines": {
1473
+ "node": ">=0.6"
1474
+ },
1475
+ "funding": {
1476
+ "url": "https://github.com/sponsors/ljharb"
1477
+ }
1478
+ },
1479
+ "node_modules/range-parser": {
1480
+ "version": "1.2.1",
1481
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1482
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1483
+ "license": "MIT",
1484
+ "engines": {
1485
+ "node": ">= 0.6"
1486
+ }
1487
+ },
1488
+ "node_modules/raw-body": {
1489
+ "version": "2.5.3",
1490
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
1491
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
1492
+ "license": "MIT",
1493
+ "dependencies": {
1494
+ "bytes": "~3.1.2",
1495
+ "http-errors": "~2.0.1",
1496
+ "iconv-lite": "~0.4.24",
1497
+ "unpipe": "~1.0.0"
1498
+ },
1499
+ "engines": {
1500
+ "node": ">= 0.8"
1501
+ }
1502
+ },
1503
+ "node_modules/readable-stream": {
1504
+ "version": "3.6.2",
1505
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
1506
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
1507
+ "license": "MIT",
1508
+ "dependencies": {
1509
+ "inherits": "^2.0.3",
1510
+ "string_decoder": "^1.1.1",
1511
+ "util-deprecate": "^1.0.1"
1512
+ },
1513
+ "engines": {
1514
+ "node": ">= 6"
1515
+ }
1516
+ },
1517
+ "node_modules/readdirp": {
1518
+ "version": "3.6.0",
1519
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1520
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1521
+ "dev": true,
1522
+ "license": "MIT",
1523
+ "dependencies": {
1524
+ "picomatch": "^2.2.1"
1525
+ },
1526
+ "engines": {
1527
+ "node": ">=8.10.0"
1528
+ }
1529
+ },
1530
+ "node_modules/responselike": {
1531
+ "version": "1.0.2",
1532
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
1533
+ "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==",
1534
+ "license": "MIT",
1535
+ "dependencies": {
1536
+ "lowercase-keys": "^1.0.0"
1537
+ }
1538
+ },
1539
+ "node_modules/safe-buffer": {
1540
+ "version": "5.2.1",
1541
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1542
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1543
+ "funding": [
1544
+ {
1545
+ "type": "github",
1546
+ "url": "https://github.com/sponsors/feross"
1547
+ },
1548
+ {
1549
+ "type": "patreon",
1550
+ "url": "https://www.patreon.com/feross"
1551
+ },
1552
+ {
1553
+ "type": "consulting",
1554
+ "url": "https://feross.org/support"
1555
+ }
1556
+ ],
1557
+ "license": "MIT"
1558
+ },
1559
+ "node_modules/safe-stable-stringify": {
1560
+ "version": "2.5.0",
1561
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
1562
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
1563
+ "license": "MIT",
1564
+ "engines": {
1565
+ "node": ">=10"
1566
+ }
1567
+ },
1568
+ "node_modules/safer-buffer": {
1569
+ "version": "2.1.2",
1570
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1571
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1572
+ "license": "MIT"
1573
+ },
1574
+ "node_modules/semver": {
1575
+ "version": "6.3.1",
1576
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1577
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1578
+ "license": "ISC",
1579
+ "bin": {
1580
+ "semver": "bin/semver.js"
1581
+ }
1582
+ },
1583
+ "node_modules/send": {
1584
+ "version": "0.19.2",
1585
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
1586
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
1587
+ "license": "MIT",
1588
+ "dependencies": {
1589
+ "debug": "2.6.9",
1590
+ "depd": "2.0.0",
1591
+ "destroy": "1.2.0",
1592
+ "encodeurl": "~2.0.0",
1593
+ "escape-html": "~1.0.3",
1594
+ "etag": "~1.8.1",
1595
+ "fresh": "~0.5.2",
1596
+ "http-errors": "~2.0.1",
1597
+ "mime": "1.6.0",
1598
+ "ms": "2.1.3",
1599
+ "on-finished": "~2.4.1",
1600
+ "range-parser": "~1.2.1",
1601
+ "statuses": "~2.0.2"
1602
+ },
1603
+ "engines": {
1604
+ "node": ">= 0.8.0"
1605
+ }
1606
+ },
1607
+ "node_modules/send/node_modules/ms": {
1608
+ "version": "2.1.3",
1609
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1610
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1611
+ "license": "MIT"
1612
+ },
1613
+ "node_modules/serve-static": {
1614
+ "version": "1.16.3",
1615
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
1616
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
1617
+ "license": "MIT",
1618
+ "dependencies": {
1619
+ "encodeurl": "~2.0.0",
1620
+ "escape-html": "~1.0.3",
1621
+ "parseurl": "~1.3.3",
1622
+ "send": "~0.19.1"
1623
+ },
1624
+ "engines": {
1625
+ "node": ">= 0.8.0"
1626
+ }
1627
+ },
1628
+ "node_modules/setprototypeof": {
1629
+ "version": "1.2.0",
1630
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1631
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1632
+ "license": "ISC"
1633
+ },
1634
+ "node_modules/side-channel": {
1635
+ "version": "1.1.1",
1636
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
1637
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
1638
+ "license": "MIT",
1639
+ "dependencies": {
1640
+ "es-errors": "^1.3.0",
1641
+ "object-inspect": "^1.13.4",
1642
+ "side-channel-list": "^1.0.1",
1643
+ "side-channel-map": "^1.0.1",
1644
+ "side-channel-weakmap": "^1.0.2"
1645
+ },
1646
+ "engines": {
1647
+ "node": ">= 0.4"
1648
+ },
1649
+ "funding": {
1650
+ "url": "https://github.com/sponsors/ljharb"
1651
+ }
1652
+ },
1653
+ "node_modules/side-channel-list": {
1654
+ "version": "1.0.1",
1655
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
1656
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
1657
+ "license": "MIT",
1658
+ "dependencies": {
1659
+ "es-errors": "^1.3.0",
1660
+ "object-inspect": "^1.13.4"
1661
+ },
1662
+ "engines": {
1663
+ "node": ">= 0.4"
1664
+ },
1665
+ "funding": {
1666
+ "url": "https://github.com/sponsors/ljharb"
1667
+ }
1668
+ },
1669
+ "node_modules/side-channel-map": {
1670
+ "version": "1.0.1",
1671
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1672
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1673
+ "license": "MIT",
1674
+ "dependencies": {
1675
+ "call-bound": "^1.0.2",
1676
+ "es-errors": "^1.3.0",
1677
+ "get-intrinsic": "^1.2.5",
1678
+ "object-inspect": "^1.13.3"
1679
+ },
1680
+ "engines": {
1681
+ "node": ">= 0.4"
1682
+ },
1683
+ "funding": {
1684
+ "url": "https://github.com/sponsors/ljharb"
1685
+ }
1686
+ },
1687
+ "node_modules/side-channel-weakmap": {
1688
+ "version": "1.0.2",
1689
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1690
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1691
+ "license": "MIT",
1692
+ "dependencies": {
1693
+ "call-bound": "^1.0.2",
1694
+ "es-errors": "^1.3.0",
1695
+ "get-intrinsic": "^1.2.5",
1696
+ "object-inspect": "^1.13.3",
1697
+ "side-channel-map": "^1.0.1"
1698
+ },
1699
+ "engines": {
1700
+ "node": ">= 0.4"
1701
+ },
1702
+ "funding": {
1703
+ "url": "https://github.com/sponsors/ljharb"
1704
+ }
1705
+ },
1706
+ "node_modules/signal-exit": {
1707
+ "version": "3.0.7",
1708
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
1709
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
1710
+ "license": "ISC"
1711
+ },
1712
+ "node_modules/simple-update-notifier": {
1713
+ "version": "2.0.0",
1714
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
1715
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
1716
+ "dev": true,
1717
+ "license": "MIT",
1718
+ "dependencies": {
1719
+ "semver": "^7.5.3"
1720
+ },
1721
+ "engines": {
1722
+ "node": ">=10"
1723
+ }
1724
+ },
1725
+ "node_modules/simple-update-notifier/node_modules/semver": {
1726
+ "version": "7.8.5",
1727
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
1728
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
1729
+ "dev": true,
1730
+ "license": "ISC",
1731
+ "bin": {
1732
+ "semver": "bin/semver.js"
1733
+ },
1734
+ "engines": {
1735
+ "node": ">=10"
1736
+ }
1737
+ },
1738
+ "node_modules/stack-trace": {
1739
+ "version": "0.0.10",
1740
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
1741
+ "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
1742
+ "license": "MIT",
1743
+ "engines": {
1744
+ "node": "*"
1745
+ }
1746
+ },
1747
+ "node_modules/statuses": {
1748
+ "version": "2.0.2",
1749
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
1750
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
1751
+ "license": "MIT",
1752
+ "engines": {
1753
+ "node": ">= 0.8"
1754
+ }
1755
+ },
1756
+ "node_modules/string_decoder": {
1757
+ "version": "1.3.0",
1758
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
1759
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1760
+ "license": "MIT",
1761
+ "dependencies": {
1762
+ "safe-buffer": "~5.2.0"
1763
+ }
1764
+ },
1765
+ "node_modules/supports-color": {
1766
+ "version": "5.5.0",
1767
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1768
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1769
+ "dev": true,
1770
+ "license": "MIT",
1771
+ "dependencies": {
1772
+ "has-flag": "^3.0.0"
1773
+ },
1774
+ "engines": {
1775
+ "node": ">=4"
1776
+ }
1777
+ },
1778
+ "node_modules/text-hex": {
1779
+ "version": "1.0.0",
1780
+ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
1781
+ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
1782
+ "license": "MIT"
1783
+ },
1784
+ "node_modules/to-readable-stream": {
1785
+ "version": "1.0.0",
1786
+ "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
1787
+ "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
1788
+ "license": "MIT",
1789
+ "engines": {
1790
+ "node": ">=6"
1791
+ }
1792
+ },
1793
+ "node_modules/to-regex-range": {
1794
+ "version": "5.0.1",
1795
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1796
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1797
+ "dev": true,
1798
+ "license": "MIT",
1799
+ "dependencies": {
1800
+ "is-number": "^7.0.0"
1801
+ },
1802
+ "engines": {
1803
+ "node": ">=8.0"
1804
+ }
1805
+ },
1806
+ "node_modules/toidentifier": {
1807
+ "version": "1.0.1",
1808
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1809
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1810
+ "license": "MIT",
1811
+ "engines": {
1812
+ "node": ">=0.6"
1813
+ }
1814
+ },
1815
+ "node_modules/touch": {
1816
+ "version": "3.1.1",
1817
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
1818
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
1819
+ "dev": true,
1820
+ "license": "ISC",
1821
+ "bin": {
1822
+ "nodetouch": "bin/nodetouch.js"
1823
+ }
1824
+ },
1825
+ "node_modules/triple-beam": {
1826
+ "version": "1.4.1",
1827
+ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
1828
+ "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
1829
+ "license": "MIT",
1830
+ "engines": {
1831
+ "node": ">= 14.0.0"
1832
+ }
1833
+ },
1834
+ "node_modules/type-is": {
1835
+ "version": "1.6.18",
1836
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1837
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1838
+ "license": "MIT",
1839
+ "dependencies": {
1840
+ "media-typer": "0.3.0",
1841
+ "mime-types": "~2.1.24"
1842
+ },
1843
+ "engines": {
1844
+ "node": ">= 0.6"
1845
+ }
1846
+ },
1847
+ "node_modules/typedarray-to-buffer": {
1848
+ "version": "3.1.5",
1849
+ "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
1850
+ "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
1851
+ "license": "MIT",
1852
+ "dependencies": {
1853
+ "is-typedarray": "^1.0.0"
1854
+ }
1855
+ },
1856
+ "node_modules/undefsafe": {
1857
+ "version": "2.0.5",
1858
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1859
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1860
+ "dev": true,
1861
+ "license": "MIT"
1862
+ },
1863
+ "node_modules/unique-string": {
1864
+ "version": "2.0.0",
1865
+ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
1866
+ "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
1867
+ "license": "MIT",
1868
+ "dependencies": {
1869
+ "crypto-random-string": "^2.0.0"
1870
+ },
1871
+ "engines": {
1872
+ "node": ">=8"
1873
+ }
1874
+ },
1875
+ "node_modules/unpipe": {
1876
+ "version": "1.0.0",
1877
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1878
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1879
+ "license": "MIT",
1880
+ "engines": {
1881
+ "node": ">= 0.8"
1882
+ }
1883
+ },
1884
+ "node_modules/url-parse-lax": {
1885
+ "version": "3.0.0",
1886
+ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
1887
+ "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==",
1888
+ "license": "MIT",
1889
+ "dependencies": {
1890
+ "prepend-http": "^2.0.0"
1891
+ },
1892
+ "engines": {
1893
+ "node": ">=4"
1894
+ }
1895
+ },
1896
+ "node_modules/util-deprecate": {
1897
+ "version": "1.0.2",
1898
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1899
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
1900
+ "license": "MIT"
1901
+ },
1902
+ "node_modules/utils-merge": {
1903
+ "version": "1.0.1",
1904
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1905
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1906
+ "license": "MIT",
1907
+ "engines": {
1908
+ "node": ">= 0.4.0"
1909
+ }
1910
+ },
1911
+ "node_modules/uuid": {
1912
+ "version": "9.0.1",
1913
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
1914
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
1915
+ "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).",
1916
+ "funding": [
1917
+ "https://github.com/sponsors/broofa",
1918
+ "https://github.com/sponsors/ctavan"
1919
+ ],
1920
+ "license": "MIT",
1921
+ "bin": {
1922
+ "uuid": "dist/bin/uuid"
1923
+ }
1924
+ },
1925
+ "node_modules/vary": {
1926
+ "version": "1.1.2",
1927
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1928
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1929
+ "license": "MIT",
1930
+ "engines": {
1931
+ "node": ">= 0.8"
1932
+ }
1933
+ },
1934
+ "node_modules/winston": {
1935
+ "version": "3.19.0",
1936
+ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
1937
+ "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
1938
+ "license": "MIT",
1939
+ "dependencies": {
1940
+ "@colors/colors": "^1.6.0",
1941
+ "@dabh/diagnostics": "^2.0.8",
1942
+ "async": "^3.2.3",
1943
+ "is-stream": "^2.0.0",
1944
+ "logform": "^2.7.0",
1945
+ "one-time": "^1.0.0",
1946
+ "readable-stream": "^3.4.0",
1947
+ "safe-stable-stringify": "^2.3.1",
1948
+ "stack-trace": "0.0.x",
1949
+ "triple-beam": "^1.3.0",
1950
+ "winston-transport": "^4.9.0"
1951
+ },
1952
+ "engines": {
1953
+ "node": ">= 12.0.0"
1954
+ }
1955
+ },
1956
+ "node_modules/winston-transport": {
1957
+ "version": "4.9.0",
1958
+ "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
1959
+ "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
1960
+ "license": "MIT",
1961
+ "dependencies": {
1962
+ "logform": "^2.7.0",
1963
+ "readable-stream": "^3.6.2",
1964
+ "triple-beam": "^1.3.0"
1965
+ },
1966
+ "engines": {
1967
+ "node": ">= 12.0.0"
1968
+ }
1969
+ },
1970
+ "node_modules/wrappy": {
1971
+ "version": "1.0.2",
1972
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1973
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1974
+ "license": "ISC"
1975
+ },
1976
+ "node_modules/write-file-atomic": {
1977
+ "version": "3.0.3",
1978
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
1979
+ "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
1980
+ "license": "ISC",
1981
+ "dependencies": {
1982
+ "imurmurhash": "^0.1.4",
1983
+ "is-typedarray": "^1.0.0",
1984
+ "signal-exit": "^3.0.2",
1985
+ "typedarray-to-buffer": "^3.1.5"
1986
+ }
1987
+ },
1988
+ "node_modules/ws": {
1989
+ "version": "8.21.0",
1990
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
1991
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
1992
+ "license": "MIT",
1993
+ "engines": {
1994
+ "node": ">=10.0.0"
1995
+ },
1996
+ "peerDependencies": {
1997
+ "bufferutil": "^4.0.1",
1998
+ "utf-8-validate": ">=5.0.2"
1999
+ },
2000
+ "peerDependenciesMeta": {
2001
+ "bufferutil": {
2002
+ "optional": true
2003
+ },
2004
+ "utf-8-validate": {
2005
+ "optional": true
2006
+ }
2007
+ }
2008
+ },
2009
+ "node_modules/xdg-basedir": {
2010
+ "version": "4.0.0",
2011
+ "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
2012
+ "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
2013
+ "license": "MIT",
2014
+ "engines": {
2015
+ "node": ">=8"
2016
+ }
2017
+ }
2018
+ }
2019
+ }
package.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "translator-api",
3
+ "version": "1.0.0",
4
+ "description": "Alternative gratuite à Google Translate via API",
5
+ "main": "central/src/index.js",
6
+ "scripts": {
7
+ "start:central": "node central/src/index.js",
8
+ "start:worker": "node worker/src/index.js",
9
+ "dev:central": "nodemon central/src/index.js",
10
+ "dev:worker": "nodemon worker/src/index.js",
11
+ "install:all": "npm install && cd central && npm install && cd ../worker && npm install",
12
+ "build": "echo 'No build step required'"
13
+ },
14
+ "keywords": ["translation", "api", "google-translate", "huggingface"],
15
+ "author": "NathMen12",
16
+ "license": "MIT",
17
+ "dependencies": {
18
+ "express": "^4.18.2",
19
+ "cors": "^2.8.5",
20
+ "express-rate-limit": "^7.1.5",
21
+ "ws": "^8.14.2",
22
+ "uuid": "^9.0.1",
23
+ "p-queue": "^7.4.1",
24
+ "dotenv": "^16.3.1",
25
+ "winston": "^3.11.0",
26
+ "@vitalets/google-translate-api": "^8.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "nodemon": "^3.0.2"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }
public/admin.html ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Admin Panel - Translator API</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
8
+ <style>
9
+ * { box-sizing: border-box; margin: 0; padding: 0; }
10
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f0f2f5; min-height: 100vh; }
11
+ .header { background: #1a1a2e; color: white; padding: 20px 30px; display: flex; justify-content: space-between; align-items: center; }
12
+ .header h1 { font-size: 24px; }
13
+ .logout-btn { background: #e94560; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; }
14
+ .container { max-width: 1400px; margin: 0 auto; padding: 20px; }
15
+ .tabs { display: flex; gap: 10px; margin-bottom: 20px; background: white; border-radius: 8px; padding: 5px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
16
+ .tab { padding: 12px 24px; background: none; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; color: #666; transition: all 0.2s; }
17
+ .tab.active { background: #1a73e8; color: white; }
18
+ .tab:hover:not(.active) { background: #f0f0f0; }
19
+ .tab-content { display: none; }
20
+ .tab-content.active { display: block; }
21
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; margin-bottom: 20px; }
22
+ .card { background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 20px; }
23
+ .card h3 { margin-bottom: 15px; color: #333; font-size: 16px; }
24
+ .chart-container { height: 300px; position: relative; }
25
+ .workers-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 15px; }
26
+ .worker-card { background: #f8f9fa; border-radius: 8px; padding: 15px; border: 1px solid #e9ecef; }
27
+ .worker-card h4 { color: #1a73e8; margin-bottom: 10px; }
28
+ .metric { display: flex; justify-content: space-between; margin: 8px 0; font-size: 14px; }
29
+ .metric-label { color: #666; }
30
+ .metric-value { font-weight: 600; }
31
+ .status { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 8px; }
32
+ .status.online { background: #28a745; }
33
+ .status.offline { background: #dc3545; }
34
+ .logs-table { width: 100%; border-collapse: collapse; font-size: 13px; }
35
+ .logs-table th, .logs-table td { padding: 10px; text-align: left; border-bottom: 1px solid #eee; }
36
+ .logs-table th { background: #f8f9fa; font-weight: 600; color: #333; }
37
+ .logs-table tr:hover { background: #f8f9fa; }
38
+ .logs-table .text-cell { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
39
+ .loading { text-align: center; padding: 40px; color: #666; }
40
+ .auth-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
41
+ .auth-modal.hidden { display: none; }
42
+ .auth-box { background: white; padding: 30px; border-radius: 12px; width: 100%; max-width: 400px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); }
43
+ .auth-box h2 { margin-bottom: 20px; color: #333; }
44
+ .auth-box input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; margin-bottom: 15px; }
45
+ .auth-box button { width: 100%; padding: 12px; background: #1a73e8; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; }
46
+ .auth-box button:hover { background: #1557b0; }
47
+ .error-msg { color: #dc3545; margin-bottom: 15px; display: none; }
48
+ .refresh-info { font-size: 12px; color: #999; margin-left: 10px; }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <div class="auth-modal" id="authModal">
53
+ <div class="auth-box">
54
+ <h2>🔐 Accès Admin</h2>
55
+ <div class="error-msg" id="authError"></div>
56
+ <input type="password" id="adminCode" placeholder="Code d'accès admin" autocomplete="off">
57
+ <button onclick="login()">Accéder</button>
58
+ </div>
59
+ </div>
60
+
61
+ <header class="header">
62
+ <h1>📊 Panel Admin - Translator API</h1>
63
+ <button class="logout-btn" onclick="logout()">Déconnexion</button>
64
+ </header>
65
+
66
+ <div class="container">
67
+ <div class="tabs">
68
+ <button class="tab active" onclick="switchTab('machines')">🖥️ Machines</button>
69
+ <button class="tab" onclick="switchTab('requests')">📈 Requêtes (120s)</button>
70
+ <button class="tab" onclick="switchTab('logs')">📋 Logs</button>
71
+ </div>
72
+
73
+ <div id="machines" class="tab-content active">
74
+ <div class="grid">
75
+ <div class="card">
76
+ <h3>🖥️ Machine Centrale - CPU</h3>
77
+ <div class="chart-container"><canvas id="cpuChart"></canvas></div>
78
+ </div>
79
+ <div class="card">
80
+ <h3>💾 Machine Centrale - RAM</h3>
81
+ <div class="chart-container"><canvas id="ramChart"></canvas></div>
82
+ </div>
83
+ </div>
84
+ <div class="card">
85
+ <h3>👥 Workers Connectés</h3>
86
+ <div class="workers-list" id="workersList">
87
+ <div class="loading">Chargement...</div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+
92
+ <div id="requests" class="tab-content">
93
+ <div class="grid">
94
+ <div class="card">
95
+ <h3>📊 Requêtes par minute (120s)</h3>
96
+ <div class="chart-container"><canvas id="requestsChart"></canvas></div>
97
+ </div>
98
+ <div class="card">
99
+ <h3>📈 Statistiques</h3>
100
+ <div id="requestStats" style="padding: 10px;">Chargement...</div>
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ <div id="logs" class="tab-content">
106
+ <div class="card">
107
+ <h3>📋 Derniers logs de traduction</h3>
108
+ <table class="logs-table">
109
+ <thead>
110
+ <tr>
111
+ <th>Heure</th>
112
+ <th>IP</th>
113
+ <th>Durée (ms)</th>
114
+ <th>Langues</th>
115
+ <th>Entrée</th>
116
+ <th>Sortie</th>
117
+ </tr>
118
+ </thead>
119
+ <tbody id="logsBody">
120
+ <tr><td colspan="6" class="loading">Chargement...</td></tr>
121
+ </tbody>
122
+ </table>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <script>
128
+ let adminCode = '';
129
+ let charts = {};
130
+ let refreshInterval;
131
+
132
+ // Check auth on load
133
+ window.onload = () => {
134
+ const savedCode = sessionStorage.getItem('adminCode');
135
+ if (savedCode) {
136
+ adminCode = savedCode;
137
+ document.getElementById('authModal').classList.add('hidden');
138
+ initDashboard();
139
+ }
140
+ };
141
+
142
+ function login() {
143
+ const code = document.getElementById('adminCode').value;
144
+ if (!code) return;
145
+
146
+ fetch('/api/admin/metrics', {
147
+ headers: { 'x-admin-code': code }
148
+ })
149
+ .then(res => {
150
+ if (res.ok) {
151
+ adminCode = code;
152
+ sessionStorage.setItem('adminCode', code);
153
+ document.getElementById('authModal').classList.add('hidden');
154
+ initDashboard();
155
+ } else {
156
+ document.getElementById('authError').textContent = 'Code invalide';
157
+ document.getElementById('authError').style.display = 'block';
158
+ }
159
+ })
160
+ .catch(() => {
161
+ document.getElementById('authError').textContent = 'Erreur de connexion';
162
+ document.getElementById('authError').style.display = 'block';
163
+ });
164
+ }
165
+
166
+ function logout() {
167
+ adminCode = '';
168
+ sessionStorage.removeItem('adminCode');
169
+ document.getElementById('authModal').classList.remove('hidden');
170
+ if (refreshInterval) clearInterval(refreshInterval);
171
+ }
172
+
173
+ function apiCall(endpoint) {
174
+ return fetch(endpoint, {
175
+ headers: { 'x-admin-code': adminCode }
176
+ }).then(res => res.json());
177
+ }
178
+
179
+ function initDashboard() {
180
+ createCharts();
181
+ loadData();
182
+ refreshInterval = setInterval(loadData, 5000);
183
+ }
184
+
185
+ function createCharts() {
186
+ const commonOptions = {
187
+ responsive: true,
188
+ maintainAspectRatio: false,
189
+ animation: { duration: 300 },
190
+ scales: {
191
+ x: { type: 'time', time: { unit: 'second', displayFormats: { second: 'HH:mm:ss' } }, title: { display: true, text: 'Temps' } },
192
+ y: { beginAtZero: true, title: { display: true } }
193
+ },
194
+ plugins: { legend: { display: false } }
195
+ };
196
+
197
+ charts.cpu = new Chart(document.getElementById('cpuChart'), {
198
+ type: 'line',
199
+ data: { datasets: [{ label: 'CPU %', data: [], borderColor: '#1a73e8', backgroundColor: 'rgba(26,115,232,0.1)', fill: true, tension: 0.3 }] },
200
+ options: { ...commonOptions, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, max: 100, title: { display: true, text: '% CPU' } } } }
201
+ });
202
+
203
+ charts.ram = new Chart(document.getElementById('ramChart'), {
204
+ type: 'line',
205
+ data: { datasets: [{ label: 'RAM Used (GB)', data: [], borderColor: '#34a853', backgroundColor: 'rgba(52,168,83,0.1)', fill: true, tension: 0.3 }] },
206
+ options: { ...commonOptions, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, title: { display: true, text: 'GB' } } } }
207
+ });
208
+
209
+ charts.requests = new Chart(document.getElementById('requestsChart'), {
210
+ type: 'bar',
211
+ data: { labels: [], datasets: [{ label: 'Req/min', data: [], backgroundColor: '#ea4335' }] },
212
+ options: {
213
+ ...commonOptions,
214
+ scales: {
215
+ x: { type: 'time', time: { unit: 'second', displayFormats: { second: 'HH:mm:ss' } }, title: { display: true, text: 'Temps' } },
216
+ y: { beginAtZero: true, title: { display: true, text: 'Requêtes/min' } }
217
+ }
218
+ }
219
+ });
220
+ }
221
+
222
+ async function loadData() {
223
+ try {
224
+ const data = await apiCall('/api/admin/metrics');
225
+ updateMachinesTab(data);
226
+ updateRequestsTab(data);
227
+ updateLogsTab(data);
228
+ } catch (err) {
229
+ console.error('Failed to load metrics', err);
230
+ }
231
+ }
232
+
233
+ function updateMachinesTab(data) {
234
+ // Update CPU chart
235
+ const cpuData = data.cpuHistory.map(m => ({ x: new Date(m.timestamp), y: (m.cpu / 1000000).toFixed(1) })).slice(-60);
236
+ charts.cpu.data.datasets[0].data = cpuData;
237
+ charts.cpu.update('none');
238
+
239
+ // Update RAM chart
240
+ const ramData = data.ramHistory.map(m => ({ x: new Date(m.timestamp), y: (m.used / 1024 / 1024 / 1024).toFixed(2) })).slice(-60);
241
+ charts.ram.data.datasets[0].data = ramData;
242
+ charts.ram.update('none');
243
+
244
+ // Update workers list
245
+ const workersList = document.getElementById('workersList');
246
+ if (data.workers.length === 0) {
247
+ workersList.innerHTML = '<div class="loading">Aucun worker connecté</div>';
248
+ } else {
249
+ workersList.innerHTML = data.workers.map(w => `
250
+ <div class="worker-card">
251
+ <h4><span class="status ${Date.now() - w.lastHeartbeat < 10000 ? 'online' : 'offline'}"></span> Worker ${w.id.substring(0,8)}</h4>
252
+ <div class="metric"><span class="metric-label">CPU</span><span class="metric-value">${w.cpu.toFixed(1)}%</span></div>
253
+ <div class="metric"><span class="metric-label">RAM</span><span class="metric-value">${(w.ram / 1024 / 1024).toFixed(1)} MB</span></div>
254
+ <div class="metric"><span class="metric-label">Jobs actifs</span><span class="metric-value">${w.jobsActive} / ${w.maxJobs}</span></div>
255
+ <div class="metric"><span class="metric-label">Dernier heartbeat</span><span class="metric-value">${new Date(w.lastHeartbeat).toLocaleTimeString()}</span></div>
256
+ </div>
257
+ `).join('');
258
+ }
259
+ }
260
+
261
+ function updateRequestsTab(data) {
262
+ // Group requests by 10-second intervals
263
+ const now = Date.now();
264
+ const intervals = [];
265
+ for (let i = 11; i >= 0; i--) {
266
+ const start = now - (i + 1) * 10000;
267
+ const end = now - i * 10000;
268
+ const count = data.requests.filter(r => r.timestamp >= start && r.timestamp < end).length * 6; // project to per minute
269
+ intervals.push({ time: new Date(start), count });
270
+ }
271
+
272
+ charts.requests.data.labels = intervals.map(i => i.time);
273
+ charts.requests.data.datasets[0].data = intervals.map(i => i.count);
274
+ charts.requests.update('none');
275
+
276
+ // Stats
277
+ const totalLast2min = data.requests.filter(r => r.timestamp > now - 120000).length;
278
+ const avgDuration = data.requests.length ? (data.requests.reduce((a, b) => a + b.duration, 0) / data.requests.length).toFixed(0) : 0;
279
+
280
+ document.getElementById('requestStats').innerHTML = `
281
+ <div class="metric"><span class="metric-label">Total requêtes (2 min)</span><span class="metric-value">${totalLast2min}</span></div>
282
+ <div class="metric"><span class="metric-label">Durée moyenne</span><span class="metric-value">${avgDuration} ms</span></div>
283
+ <div class="metric"><span class="metric-label">Workers actifs</span><span class="metric-value">${data.workers.filter(w => w.jobsActive > 0).length}</span></div>
284
+ <div class="metric"><span class="metric-label">Taux actuel</span><span class="metric-value">${data.requestRate} req/min</span></div>
285
+ `;
286
+ }
287
+
288
+ function updateLogsTab(data) {
289
+ const logs = data.requests.slice(-50).reverse();
290
+ const tbody = document.getElementById('logsBody');
291
+ if (logs.length === 0) {
292
+ tbody.innerHTML = '<tr><td colspan="6" class="loading">Aucun log</td></tr>';
293
+ return;
294
+ }
295
+ tbody.innerHTML = logs.map(log => `
296
+ <tr>
297
+ <td>${new Date(log.timestamp).toLocaleTimeString()}</td>
298
+ <td>${log.ip}</td>
299
+ <td>${log.duration} ms</td>
300
+ <td>${log.sourceLang} → ${log.targetLang}</td>
301
+ <td class="text-cell" title="${log.inputText}">${log.inputText}</td>
302
+ <td class="text-cell" title="${log.outputText}">${log.outputText}</td>
303
+ </tr>
304
+ `).join('');
305
+ }
306
+
307
+ function switchTab(tabId) {
308
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
309
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
310
+ document.querySelector(`[onclick="switchTab('${tabId}')"]`).classList.add('active');
311
+ document.getElementById(tabId).classList.add('active');
312
+ }
313
+
314
+ // Allow Enter key in auth input
315
+ document.getElementById('adminCode')?.addEventListener('keypress', (e) => {
316
+ if (e.key === 'Enter') login();
317
+ });
318
+ </script>
319
+ </body>
320
+ </html>
public/index.html ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Translator API - Alternative gratuite à Google Translate</title>
7
+ <style>
8
+ * { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; min-height: 100vh; padding: 20px; }
10
+ .container { max-width: 800px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); padding: 30px; }
11
+ h1 { color: #1a73e8; margin-bottom: 10px; }
12
+ .subtitle { color: #666; margin-bottom: 30px; }
13
+ .form-group { margin-bottom: 20px; }
14
+ label { display: block; margin-bottom: 8px; font-weight: 500; color: #333; }
15
+ textarea { width: 100%; min-height: 150px; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 16px; resize: vertical; }
16
+ .row { display: flex; gap: 20px; margin-bottom: 20px; }
17
+ .row .form-group { flex: 1; }
18
+ select { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 16px; background: white; }
19
+ button { background: #1a73e8; color: white; border: none; padding: 14px 28px; border-radius: 8px; font-size: 16px; cursor: pointer; transition: background 0.2s; width: 100%; }
20
+ button:hover { background: #1557b0; }
21
+ button:disabled { background: #ccc; cursor: not-allowed; }
22
+ .result { margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px; display: none; }
23
+ .result h3 { margin-bottom: 10px; color: #333; }
24
+ .result pre { white-space: pre-wrap; word-wrap: break-word; font-family: inherit; }
25
+ .error { color: #dc3545; margin-top: 10px; display: none; }
26
+ .links { margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }
27
+ .links a { color: #1a73e8; text-decoration: none; margin-right: 20px; }
28
+ .links a:hover { text-decoration: underline; }
29
+ .stats { display: flex; gap: 20px; margin-bottom: 20px; font-size: 14px; color: #666; }
30
+ .stat { background: #f8f9fa; padding: 8px 16px; border-radius: 20px; }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div class="container">
35
+ <h1>🌐 Translator API</h1>
36
+ <p class="subtitle">Alternative gratuite à Google Translate via API REST</p>
37
+
38
+ <div class="stats">
39
+ <div class="stat">⚡ 30 req/min/IP</div>
40
+ <div class="stat">🔓 Pas de clé API</div>
41
+ <div class="stat">📚 Wiki intégré</div>
42
+ </div>
43
+
44
+ <form id="translateForm">
45
+ <div class="form-group">
46
+ <label for="sourceText">Texte à traduire</label>
47
+ <textarea id="sourceText" placeholder="Entrez votre texte ici..." required></textarea>
48
+ </div>
49
+
50
+ <div class="row">
51
+ <div class="form-group">
52
+ <label for="sourceLang">De</label>
53
+ <select id="sourceLang">
54
+ <option value="auto">🔍 Détection auto</option>
55
+ <option value="fr">🇫🇷 Français</option>
56
+ <option value="en">🇺🇸 Anglais</option>
57
+ <option value="es">🇪🇸 Espagnol</option>
58
+ <option value="de">🇩🇪 Allemand</option>
59
+ <option value="it">🇮🇹 Italien</option>
60
+ <option value="pt">🇵🇹 Portugais</option>
61
+ <option value="ru">🇷🇺 Russe</option>
62
+ <option value="zh">🇨🇳 Chinois</option>
63
+ <option value="ja">🇯🇵 Japonais</option>
64
+ <option value="ko">🇰🇷 Coréen</option>
65
+ <option value="ar">🇸🇦 Arabe</option>
66
+ </select>
67
+ </div>
68
+ <div class="form-group">
69
+ <label for="targetLang">Vers</label>
70
+ <select id="targetLang">
71
+ <option value="fr">🇫🇷 Français</option>
72
+ <option value="en">🇺🇸 Anglais</option>
73
+ <option value="es">🇪🇸 Espagnol</option>
74
+ <option value="de">🇩🇪 Allemand</option>
75
+ <option value="it">🇮🇹 Italien</option>
76
+ <option value="pt">🇵🇹 Portugais</option>
77
+ <option value="ru">🇷🇺 Russe</option>
78
+ <option value="zh">🇨🇳 Chinois</option>
79
+ <option value="ja">🇯🇵 Japonais</option>
80
+ <option value="ko">🇰🇷 Coréen</option>
81
+ <option value="ar">🇸🇦 Arabe</option>
82
+ </select>
83
+ </div>
84
+ </div>
85
+
86
+ <button type="submit" id="translateBtn">Traduire</button>
87
+ <div class="error" id="errorMsg"></div>
88
+ </form>
89
+
90
+ <div class="result" id="result">
91
+ <h3>Résultat</h3>
92
+ <pre id="translatedText"></pre>
93
+ <small id="metaInfo"></small>
94
+ </div>
95
+
96
+ <div class="links">
97
+ <a href="/wiki">📖 Wiki / Documentation</a>
98
+ <a href="/admin">🔐 Panel Admin</a>
99
+ <a href="https://github.com/NathMen12/Translator-API" target="_blank">💻 GitHub</a>
100
+ </div>
101
+ </div>
102
+
103
+ <script>
104
+ document.getElementById('translateForm').addEventListener('submit', async (e) => {
105
+ e.preventDefault();
106
+
107
+ const btn = document.getElementById('translateBtn');
108
+ const errorMsg = document.getElementById('errorMsg');
109
+ const result = document.getElementById('result');
110
+ const translatedText = document.getElementById('translatedText');
111
+ const metaInfo = document.getElementById('metaInfo');
112
+
113
+ btn.disabled = true;
114
+ btn.textContent = 'Traduction en cours...';
115
+ errorMsg.style.display = 'none';
116
+ result.style.display = 'none';
117
+
118
+ try {
119
+ const response = await fetch('/translate', {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify({
123
+ text: document.getElementById('sourceText').value,
124
+ source: document.getElementById('sourceLang').value,
125
+ target: document.getElementById('targetLang').value
126
+ })
127
+ });
128
+
129
+ const data = await response.json();
130
+
131
+ if (!response.ok) {
132
+ throw new Error(data.error || 'Erreur de traduction');
133
+ }
134
+
135
+ translatedText.textContent = data.translatedText;
136
+ metaInfo.textContent = `Temps: ${data.duration}ms • De: ${data.source} → Vers: ${data.target}`;
137
+ result.style.display = 'block';
138
+ } catch (err) {
139
+ errorMsg.textContent = err.message;
140
+ errorMsg.style.display = 'block';
141
+ } finally {
142
+ btn.disabled = false;
143
+ btn.textContent = 'Traduire';
144
+ }
145
+ });
146
+ </script>
147
+ </body>
148
+ </html>
public/wiki.html ADDED
@@ -0,0 +1,732 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Wiki - Translator API</title>
7
+ <style>
8
+ * { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background: #f8f9fa; }
10
+ .container { max-width: 900px; margin: 0 auto; padding: 40px 20px; }
11
+ header { text-align: center; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 1px solid #e9ecef; }
12
+ h1 { font-size: 36px; color: #1a73e8; margin-bottom: 10px; }
13
+ .subtitle { color: #666; font-size: 18px; }
14
+ .nav { display: flex; gap: 10px; justify-content: center; margin-bottom: 30px; flex-wrap: wrap; }
15
+ .nav-btn { padding: 10px 20px; background: white; border: 1px solid #ddd; border-radius: 6px; cursor: pointer; transition: all 0.2s; }
16
+ .nav-btn:hover, .nav-btn.active { background: #1a73e8; color: white; border-color: #1a73e8; }
17
+ .section { background: white; border-radius: 12px; padding: 30px; margin-bottom: 30px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); display: none; }
18
+ .section.active { display: block; animation: fadeIn 0.3s; }
19
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
20
+ h2 { color: #1a1a2e; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid #1a73e8; }
21
+ h3 { color: #333; margin: 25px 0 15px; }
22
+ p { margin-bottom: 15px; }
23
+ code { background: #f1f3f4; padding: 2px 6px; border-radius: 4px; font-family: 'Monaco', 'Menlo', monospace; font-size: 0.9em; }
24
+ pre { background: #1e1e1e; color: #d4d4d4; padding: 20px; border-radius: 8px; overflow-x: auto; margin: 15px 0; }
25
+ pre code { background: none; padding: 0; color: inherit; }
26
+ .token.comment { color: #6a9955; }
27
+ .token.keyword { color: #c586c0; }
28
+ .token.string { color: #ce9178; }
29
+ .token.function { color: #dcdcaa; }
30
+ .token.property { color: #9cdcfe; }
31
+ .endpoint { background: #e8f0fe; border-left: 4px solid #1a73e8; padding: 15px; margin: 15px 0; border-radius: 0 8px 8px 0; }
32
+ .endpoint.method { display: inline-block; background: #1a73e8; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; margin-right: 10px; }
33
+ .endpoint.path { font-family: monospace; font-weight: bold; }
34
+ table { width: 100%; border-collapse: collapse; margin: 15px 0; }
35
+ th, td { padding: 12px; text-align: left; border-bottom: 1px solid #e9ecef; }
36
+ th { background: #f8f9fa; font-weight: 600; }
37
+ .badge { display: inline-block; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: bold; }
38
+ .badge-success { background: #d4edda; color: #155724; }
39
+ .badge-info { background: #d1ecf1; color: #0c5460; }
40
+ .badge-warning { background: #fff3cd; color: #856404; }
41
+ .lang-tabs { display: flex; gap: 8px; margin-bottom: 15px; flex-wrap: wrap; }
42
+ .lang-tab { padding: 8px 16px; background: #f1f3f4; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; }
43
+ .lang-tab.active { background: #1a73e8; color: white; }
44
+ .code-block { display: none; }
45
+ .code-block.active { display: block; }
46
+ .back-link { display: inline-block; margin-bottom: 30px; color: #1a73e8; text-decoration: none; }
47
+ .back-link:hover { text-decoration: underline; }
48
+ .info-box { background: #e8f0fe; border-radius: 8px; padding: 20px; margin: 20px 0; }
49
+ .info-box h4 { color: #1a73e8; margin-bottom: 10px; }
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div class="container">
54
+ <a href="/" class="back-link">← Retour à l'accueil</a>
55
+
56
+ <header>
57
+ <h1>📖 Wiki - Translator API</h1>
58
+ <p class="subtitle">Documentation complète pour intégrer l'API de traduction</p>
59
+ </header>
60
+
61
+ <nav class="nav">
62
+ <button class="nav-btn active" onclick="showSection('intro')">🚀 Démarrage</button>
63
+ <button class="nav-btn" onclick="showSection('endpoints')">🔌 Endpoints</button>
64
+ <button class="nav-btn" onclick="showSection('javascript')">📜 JavaScript</button>
65
+ <button class="nav-btn" onclick="showSection('python')">🐍 Python</button>
66
+ <button class="nav-btn" onclick="showSection('java')">☕ Java</button>
67
+ <button class="nav-btn" onclick="showSection('curl')">🌐 cURL</button>
68
+ <button class="nav-btn" onclick="showSection('limits')">⚙️ Limites</button>
69
+ </nav>
70
+
71
+ <!-- Intro Section -->
72
+ <section id="intro" class="section active">
73
+ <h2>🚀 Démarrage rapide</h2>
74
+ <p>Translator API est une alternative gratuite à Google Translate, accessible via une API REST simple. Pas de clé API requise, pas d'inscription nécessaire.</p>
75
+
76
+ <div class="info-box">
77
+ <h4>✨ Fonctionnalités</h4>
78
+ <ul style="margin-left: 20px;">
79
+ <li>🆓 Gratuit et sans limite de compte</li>
80
+ <li>⚡ 30 requêtes/minute par IP</li>
81
+ <li>🌍 100+ langues supportées</li>
82
+ <li>🔒 Détection automatique de langue</li>
83
+ <li>📊 Dashboard admin avec métriques temps réel</li>
84
+ <li>⚖️ Architecture distribuée (Central + Workers)</li>
85
+ </ul>
86
+ </div>
87
+
88
+ <h3>URL de base</h3>
89
+ <p>Remplacez <code>VOTRE_HOTE</code> par l'adresse de votre instance :</p>
90
+ <pre><code>https://VOTRE_HOTE:7820</code></pre>
91
+
92
+ <h3>Test rapide</h3>
93
+ <pre><code class="language-bash">curl -X POST https://VOTRE_HOTE:7820/translate \
94
+ -H "Content-Type: application/json" \
95
+ -d '{"text": "Bonjour le monde", "source": "fr", "target": "en"}'</code></pre>
96
+
97
+ <p><strong>Réponse :</strong></p>
98
+ <pre><code>{
99
+ "translatedText": "Hello world",
100
+ "source": "fr",
101
+ "target": "en",
102
+ "duration": 245
103
+ }</code></pre>
104
+ </section>
105
+
106
+ <!-- Endpoints Section -->
107
+ <section id="endpoints" class="section">
108
+ <h2>🔌 Endpoints API</h2>
109
+
110
+ <div class="endpoint">
111
+ <span class="endpoint.method">POST</span>
112
+ <span class="endpoint.path">/translate</span>
113
+ <p>Traduit un texte d'une langue vers une autre.</p>
114
+
115
+ <h4>Paramètres (JSON body)</h4>
116
+ <table>
117
+ <thead><tr><th>Paramètre</th><th>Type</th><th>Requis</th><th>Description</th></tr></thead>
118
+ <tbody>
119
+ <tr><td>text</td><td>string</td><td>Oui</td><td>Texte à traduire (max 5000 caractères)</td></tr>
120
+ <tr><td>source</td><td>string</td><td>Non</td><td>Langue source (code ISO 639-1). Défaut: "auto" (détection auto)</td></tr>
121
+ <tr><td>target</td><td>string</td><td>Non</td><td>Langue cible (code ISO 639-1). Défaut: "fr"</td></tr>
122
+ </tbody>
123
+ </table>
124
+
125
+ <h4>Réponse</h4>
126
+ <table>
127
+ <thead><tr><th>Champ</th><th>Type</th><th>Description</th></tr></thead>
128
+ <tbody>
129
+ <tr><td>translatedText</td><td>string</td><td>Texte traduit</td></tr>
130
+ <tr><td>source</td><td>string</td><td>Langue source détectée/utilisée</td></tr>
131
+ <tr><td>target</td><td>string</td><td>Langue cible</td></tr>
132
+ <tr><td>duration</td><td>number</td><td>Temps de traitement en ms</td></tr>
133
+ </tbody>
134
+ </table>
135
+ </div>
136
+
137
+ <div class="endpoint">
138
+ <span class="endpoint.method">GET</span>
139
+ <span class="endpoint.path">/api/health</span>
140
+ <p>Vérifie l'état du service.</p>
141
+ </div>
142
+
143
+ <div class="endpoint">
144
+ <span class="endpoint.method">GET</span>
145
+ <span class="endpoint.path">/admin</span>
146
+ <p>Panel d'administration (nécessite code d'accès via header <code>x-admin-code</code> ou paramètre <code>admin_code</code>).</p>
147
+ </div>
148
+
149
+ <h3>Codes de langue supportés (exemples)</h3>
150
+ <table>
151
+ <thead><tr><th>Code</th><th>Langue</th><th>Code</th><th>Langue</th></tr></thead>
152
+ <tbody>
153
+ <tr><td>fr</td><td>Français</td><td>en</td><td>Anglais</td></tr>
154
+ <tr><td>es</td><td>Espagnol</td><td>de</td><td>Allemand</td></tr>
155
+ <tr><td>it</td><td>Italien</td><td>pt</td><td>Portugais</td></tr>
156
+ <tr><td>ru</td><td>Russe</td><td>zh</td><td>Chinois</td></tr>
157
+ <tr><td>ja</td><td>Japonais</td><td>ko</td><td>Coréen</td></tr>
158
+ <tr><td>ar</td><td>Arabe</td><td>auto</td><td>Détection auto</td></tr>
159
+ </tbody>
160
+ </table>
161
+ </section>
162
+
163
+ <!-- JavaScript Section -->
164
+ <section id="javascript" class="section">
165
+ <h2>📜 JavaScript / TypeScript</h2>
166
+
167
+ <h3>Fetch API (Navigateur / Node 18+)</h3>
168
+ <div class="lang-tabs">
169
+ <button class="lang-tab active" onclick="showCode('js-fetch')">Fetch</button>
170
+ <button class="lang-tab" onclick="showCode('js-axios')">Axios</button>
171
+ <button class="lang-tab" onclick="showCode('js-node')">Node.js (natif)</button>
172
+ </div>
173
+
174
+ <pre class="code-block active" id="js-fetch"><code class="language-javascript">async function translate(text, source = 'auto', target = 'fr') {
175
+ const response = await fetch('https://VOTRE_HOTE:7820/translate', {
176
+ method: 'POST',
177
+ headers: { 'Content-Type': 'application/json' },
178
+ body: JSON.stringify({ text, source, target })
179
+ });
180
+
181
+ if (!response.ok) {
182
+ const error = await response.json();
183
+ throw new Error(error.error || 'Erreur de traduction');
184
+ }
185
+
186
+ return response.json();
187
+ }
188
+
189
+ // Utilisation
190
+ translate('Bonjour le monde', 'fr', 'en')
191
+ .then(result => console.log(result.translatedText)) // "Hello world"
192
+ .catch(err => console.error(err));</code></pre>
193
+
194
+ <pre class="code-block" id="js-axios"><code class="language-javascript">const axios = require('axios');
195
+
196
+ async function translate(text, source = 'auto', target = 'fr') {
197
+ try {
198
+ const { data } = await axios.post('https://VOTRE_HOTE:7820/translate', {
199
+ text, source, target
200
+ });
201
+ return data;
202
+ } catch (error) {
203
+ throw new Error(error.response?.data?.error || error.message);
204
+ }
205
+ }
206
+
207
+ // Utilisation
208
+ translate('Hello world', 'en', 'fr')
209
+ .then(r => console.log(r.translatedText)); // "Bonjour le monde"</code></pre>
210
+
211
+ <pre class="code-block" id="js-node"><code class="language-javascript">const https = require('https');
212
+
213
+ function translate(text, source = 'auto', target = 'fr') {
214
+ return new Promise((resolve, reject) => {
215
+ const postData = JSON.stringify({ text, source, target });
216
+
217
+ const options = {
218
+ hostname: 'VOTRE_HOTE',
219
+ port: 7820,
220
+ path: '/translate',
221
+ method: 'POST',
222
+ headers: {
223
+ 'Content-Type': 'application/json',
224
+ 'Content-Length': Buffer.byteLength(postData)
225
+ }
226
+ };
227
+
228
+ const req = https.request(options, (res) => {
229
+ let data = '';
230
+ res.on('data', chunk => data += chunk);
231
+ res.on('end', () => {
232
+ try {
233
+ const result = JSON.parse(data);
234
+ if (res.statusCode >= 400) reject(new Error(result.error));
235
+ else resolve(result);
236
+ } catch (e) {
237
+ reject(new Error('Réponse invalide'));
238
+ }
239
+ });
240
+ });
241
+
242
+ req.on('error', reject);
243
+ req.write(postData);
244
+ req.end();
245
+ });
246
+ }</code></pre>
247
+
248
+ <h3>Classe réutilisable (ES6)</h3>
249
+ <pre><code class="language-javascript">class TranslatorAPI {
250
+ constructor(baseUrl = 'https://VOTRE_HOTE:7820') {
251
+ this.baseUrl = baseUrl;
252
+ }
253
+
254
+ async translate(text, source = 'auto', target = 'fr') {
255
+ const res = await fetch(`${this.baseUrl}/translate`, {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify({ text, source, target })
259
+ });
260
+
261
+ if (!res.ok) {
262
+ const err = await res.json();
263
+ throw new Error(err.error);
264
+ }
265
+
266
+ return res.json();
267
+ }
268
+
269
+ async detectLanguage(text) {
270
+ const result = await this.translate(text, 'auto', 'en');
271
+ return result.source;
272
+ }
273
+
274
+ async translateBatch(texts, source = 'auto', target = 'fr') {
275
+ // Paralléliser avec Promise.all (respecter rate limit 30/min)
276
+ const results = await Promise.all(
277
+ texts.map(t => this.translate(t, source, target))
278
+ );
279
+ return results.map(r => r.translatedText);
280
+ }
281
+ }
282
+
283
+ // Usage
284
+ const translator = new TranslatorAPI();
285
+ const result = await translator.translate('Bonjour', 'fr', 'es');
286
+ console.log(result.translatedText); // "Hola"</code></pre>
287
+ </section>
288
+
289
+ <!-- Python Section -->
290
+ <section id="python" class="section">
291
+ <h2>🐍 Python</h2>
292
+
293
+ <div class="lang-tabs">
294
+ <button class="lang-tab active" onclick="showCode('py-requests')">Requests</button>
295
+ <button class="lang-tab" onclick="showCode('py-httpx')">HTTPX (async)</button>
296
+ <button class="lang-tab" onclick="showCode('py-stdlib')">stdlib (urllib)</button>
297
+ </div>
298
+
299
+ <pre class="code-block active" id="py-requests"><code class="language-python">import requests
300
+
301
+ def translate(text, source='auto', target='fr', base_url='https://VOTRE_HOTE:7820'):
302
+ \"\"\"Traduit un texte via l'API Translator.\"\"\"
303
+ response = requests.post(
304
+ f'{base_url}/translate',
305
+ json={'text': text, 'source': source, 'target': target},
306
+ timeout=30
307
+ )
308
+ response.raise_for_status()
309
+ return response.json()
310
+
311
+ # Utilisation simple
312
+ result = translate('Bonjour le monde', 'fr', 'en')
313
+ print(result['translatedText']) # "Hello world"
314
+
315
+ # Avec gestion d'erreurs
316
+ try:
317
+ result = translate('Hola', 'es', 'fr')
318
+ print(f"Traduit: {result['translatedText']} (durée: {result['duration']}ms)")
319
+ except requests.HTTPError as e:
320
+ if e.response.status_code == 429:
321
+ print("Rate limit dépassé (30 req/min)")
322
+ else:
323
+ print(f"Erreur: {e.response.json().get('error')}")</code></pre>
324
+
325
+ <pre class="code-block" id="py-httpx"><code class="language-python">import httpx
326
+ import asyncio
327
+
328
+ class TranslatorClient:
329
+ def __init__(self, base_url='https://VOTRE_HOTE:7820'):
330
+ self.base_url = base_url
331
+ self.client = httpx.AsyncClient(timeout=30.0)
332
+
333
+ async def translate(self, text, source='auto', target='fr'):
334
+ response = await self.client.post(
335
+ f'{self.base_url}/translate',
336
+ json={'text': text, 'source': source, 'target': target}
337
+ )
338
+ response.raise_for_status()
339
+ return response.json()
340
+
341
+ async def translate_batch(self, texts, source='auto', target='fr', concurrency=5):
342
+ \"\"\"Traduit plusieurs textes avec limite de concurrence.\"\"\"
343
+ semaphore = asyncio.Semaphore(concurrency)
344
+
345
+ async def limited_translate(text):
346
+ async with semaphore:
347
+ return await self.translate(text, source, target)
348
+
349
+ tasks = [limited_translate(t) for t in texts]
350
+ results = await asyncio.gather(*tasks, return_exceptions=True)
351
+ return [r['translatedText'] if not isinstance(r, Exception) else str(r) for r in results]
352
+
353
+ async def close(self):
354
+ await self.client.aclose()
355
+
356
+ # Usage
357
+ async def main():
358
+ client = TranslatorClient()
359
+ try:
360
+ result = await client.translate('Hello world', 'en', 'fr')
361
+ print(result['translatedText'])
362
+
363
+ # Batch
364
+ texts = ['Hello', 'World', 'Python']
365
+ translations = await client.translate_batch(texts, 'en', 'fr')
366
+ print(translations)
367
+ finally:
368
+ await client.close()
369
+
370
+ asyncio.run(main())</code></pre>
371
+
372
+ <pre class="code-block" id="py-stdlib"><code class="language-python">import urllib.request
373
+ import json
374
+
375
+ def translate(text, source='auto', target='fr', base_url='https://VOTRE_HOTE:7820'):
376
+ data = json.dumps({'text': text, 'source': source, 'target': target}).encode('utf-8')
377
+
378
+ req = urllib.request.Request(
379
+ f'{base_url}/translate',
380
+ data=data,
381
+ headers={'Content-Type': 'application/json'},
382
+ method='POST'
383
+ )
384
+
385
+ with urllib.request.urlopen(req, timeout=30) as response:
386
+ result = json.loads(response.read().decode('utf-8'))
387
+ if response.status >= 400:
388
+ raise Exception(result.get('error', 'Erreur inconnue'))
389
+ return result
390
+
391
+ # Utilisation
392
+ try:
393
+ result = translate('Bonjour', 'fr', 'de')
394
+ print(result['translatedText']) # "Hallo"
395
+ except Exception as e:
396
+ print(f"Erreur: {e}")</code></pre>
397
+ </section>
398
+
399
+ <!-- Java Section -->
400
+ <section id="java" class="section">
401
+ <h2>☕ Java</h2>
402
+
403
+ <div class="lang-tabs">
404
+ <button class="lang-tab active" onclick="showCode('java-httpclient')">HttpClient (Java 11+)</button>
405
+ <button class="lang-tab" onclick="showCode('java-okhttp')">OkHttp</button>
406
+ <button class="lang-tab" onclick="showCode('java-spring')">Spring RestTemplate</button>
407
+ </div>
408
+
409
+ <pre class="code-block active" id="java-httpclient"><code class="language-java">import java.net.URI;
410
+ import java.net.http.HttpClient;
411
+ import java.net.http.HttpRequest;
412
+ import java.net.http.HttpResponse;
413
+ import java.time.Duration;
414
+ import com.google.gson.Gson;
415
+ import com.google.gson.JsonObject;
416
+
417
+ public class TranslatorClient {
418
+ private final HttpClient client;
419
+ private final String baseUrl;
420
+ private final Gson gson = new Gson();
421
+
422
+ public TranslatorClient(String baseUrl) {
423
+ this.baseUrl = baseUrl;
424
+ this.client = HttpClient.newBuilder()
425
+ .connectTimeout(Duration.ofSeconds(30))
426
+ .build();
427
+ }
428
+
429
+ public TranslationResult translate(String text, String source, String target)
430
+ throws Exception {
431
+ JsonObject requestBody = new JsonObject();
432
+ requestBody.addProperty("text", text);
433
+ requestBody.addProperty("source", source);
434
+ requestBody.addProperty("target", target);
435
+
436
+ HttpRequest request = HttpRequest.newBuilder()
437
+ .uri(URI.create(baseUrl + "/translate"))
438
+ .header("Content-Type", "application/json")
439
+ .POST(HttpRequest.BodyPublishers.ofString(gson.toJson(requestBody)))
440
+ .build();
441
+
442
+ HttpResponse<String> response = client.send(request,
443
+ HttpResponse.BodyHandlers.ofString());
444
+
445
+ if (response.statusCode() >= 400) {
446
+ JsonObject error = gson.fromJson(response.body(), JsonObject.class);
447
+ throw new RuntimeException(error.get("error").getAsString());
448
+ }
449
+
450
+ return gson.fromJson(response.body(), TranslationResult.class);
451
+ }
452
+
453
+ public static class TranslationResult {
454
+ public String translatedText;
455
+ public String source;
456
+ public String target;
457
+ public long duration;
458
+ }
459
+
460
+ // Usage
461
+ public static void main(String[] args) throws Exception {
462
+ TranslatorClient translator = new TranslatorClient("https://VOTRE_HOTE:7820");
463
+ TranslationResult result = translator.translate("Bonjour", "fr", "en");
464
+ System.out.println(result.translatedText); // "Hello"
465
+ }
466
+ }</code></pre>
467
+
468
+ <pre class="code-block" id="java-okhttp"><code class="language-java">// Dependencies: OkHttp + Gson
469
+ // implementation "com.squareup.okhttp3:okhttp:4.12.0"
470
+ // implementation "com.google.code.gson:gson:2.10.1"
471
+
472
+ import com.google.gson.Gson;
473
+ import com.google.gson.JsonObject;
474
+ import okhttp3.*;
475
+
476
+ public class TranslatorClient {
477
+ private final OkHttpClient client;
478
+ private final String baseUrl;
479
+ private final Gson gson = new Gson();
480
+
481
+ public TranslatorClient(String baseUrl) {
482
+ this.baseUrl = baseUrl;
483
+ this.client = new OkHttpClient.Builder()
484
+ .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
485
+ .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
486
+ .build();
487
+ }
488
+
489
+ public TranslationResult translate(String text, String source, String target)
490
+ throws Exception {
491
+ JsonObject json = new JsonObject();
492
+ json.addProperty("text", text);
493
+ json.addProperty("source", source);
494
+ json.addProperty("target", target);
495
+
496
+ RequestBody body = RequestBody.create(
497
+ gson.toJson(json),
498
+ MediaType.get("application/json; charset=utf-8")
499
+ );
500
+
501
+ Request request = new Request.Builder()
502
+ .url(baseUrl + "/translate")
503
+ .post(body)
504
+ .build();
505
+
506
+ try (Response response = client.newCall(request).execute()) {
507
+ String responseBody = response.body().string();
508
+
509
+ if (!response.isSuccessful()) {
510
+ JsonObject error = gson.fromJson(responseBody, JsonObject.class);
511
+ throw new RuntimeException(error.get("error").getAsString());
512
+ }
513
+
514
+ return gson.fromJson(responseBody, TranslationResult.class);
515
+ }
516
+ }
517
+
518
+ public static class TranslationResult {
519
+ public String translatedText;
520
+ public String source;
521
+ public String target;
522
+ public long duration;
523
+ }
524
+ }</code></pre>
525
+
526
+ <pre class="code-block" id="java-spring"><code class="language-java">// Spring Boot RestTemplate
527
+ // implementation 'org.springframework.boot:spring-boot-starter-web'
528
+
529
+ import org.springframework.web.client.RestTemplate;
530
+ import org.springframework.http.*;
531
+ import java.util.Map;
532
+
533
+ public class TranslatorClient {
534
+ private final RestTemplate restTemplate = new RestTemplate();
535
+ private final String baseUrl;
536
+
537
+ public TranslatorClient(String baseUrl) {
538
+ this.baseUrl = baseUrl;
539
+ }
540
+
541
+ public TranslationResult translate(String text, String source, String target) {
542
+ HttpHeaders headers = new HttpHeaders();
543
+ headers.setContentType(MediaType.APPLICATION_JSON);
544
+
545
+ Map<String, String> body = Map.of(
546
+ "text", text,
547
+ "source", source,
548
+ "target", target
549
+ );
550
+
551
+ HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers);
552
+
553
+ try {
554
+ ResponseEntity<TranslationResult> response = restTemplate.postForEntity(
555
+ baseUrl + "/translate", request, TranslationResult.class);
556
+
557
+ if (!response.getStatusCode().is2xxSuccessful()) {
558
+ throw new RuntimeException("Erreur HTTP: " + response.getStatusCode());
559
+ }
560
+
561
+ return response.getBody();
562
+ } catch (Exception e) {
563
+ throw new RuntimeException("Erreur traduction: " + e.getMessage());
564
+ }
565
+ }
566
+
567
+ public static class TranslationResult {
568
+ private String translatedText;
569
+ private String source;
570
+ private String target;
571
+ private long duration;
572
+
573
+ // Getters/Setters
574
+ public String getTranslatedText() { return translatedText; }
575
+ public void setTranslatedText(String translatedText) { this.translatedText = translatedText; }
576
+ public String getSource() { return source; }
577
+ public void setSource(String source) { this.source = source; }
578
+ public String getTarget() { return target; }
579
+ public void setTarget(String target) { this.target = target; }
580
+ public long getDuration() { return duration; }
581
+ public void setDuration(long duration) { this.duration = duration; }
582
+ }
583
+ }</code></pre>
584
+ </section>
585
+
586
+ <!-- cURL Section -->
587
+ <section id="curl" class="section">
588
+ <h2>🌐 cURL / Ligne de commande</h2>
589
+
590
+ <h3>Traduction simple</h3>
591
+ <pre><code class="language-bash">curl -X POST https://VOTRE_HOTE:7820/translate \
592
+ -H "Content-Type: application/json" \
593
+ -d '{"text": "Bonjour le monde", "source": "fr", "target": "en"}'</code></pre>
594
+
595
+ <h3>Avec détection automatique</h3>
596
+ <pre><code class="language-bash">curl -X POST https://VOTRE_HOTE:7820/translate \
597
+ -H "Content-Type: application/json" \
598
+ -d '{"text": "Hello world", "target": "fr"}'</code></pre>
599
+
600
+ <h3>Script bash réutilisable</h3>
601
+ <pre><code class="language-bash">#!/bin/bash
602
+ # translate.sh - Script de traduction en ligne de commande
603
+
604
+ BASE_URL="https://VOTRE_HOTE:7820"
605
+ SOURCE="auto"
606
+ TARGET="fr"
607
+
608
+ usage() {
609
+ echo "Usage: $0 [-s source] [-t target] \"texte à traduire\""
610
+ echo " -s Langue source (défaut: auto)"
611
+ echo " -t Langue cible (défaut: fr)"
612
+ exit 1
613
+ }
614
+
615
+ while getopts "s:t:h" opt; do
616
+ case $opt in
617
+ s) SOURCE="$OPTARG" ;;
618
+ t) TARGET="$OPTARG" ;;
619
+ h) usage ;;
620
+ *) usage ;;
621
+ esac
622
+ done
623
+ shift $((OPTIND-1))
624
+
625
+ TEXT="$*"
626
+ if [ -z "$TEXT" ]; then
627
+ usage
628
+ fi
629
+
630
+ response=$(curl -s -X POST "$BASE_URL/translate" \
631
+ -H "Content-Type: application/json" \
632
+ -d "$(jq -n --arg text "$TEXT" --arg src "$SOURCE" --arg tgt "$TARGET" \
633
+ '{text: $text, source: $src, target: $tgt}')")
634
+
635
+ # Extraire le texte traduit (nécessite jq)
636
+ translated=$(echo "$response" | jq -r '.translatedText // .error // "Erreur"')
637
+ echo "$translated"</code></pre>
638
+
639
+ <h3>Test de charge simple</h3>
640
+ <pre><code class="language-bash"># Tester le rate limit (30 req/min)
641
+ for i in {1..35}; do
642
+ curl -s -X POST https://VOTRE_HOTE:7820/translate \
643
+ -H "Content-Type: application/json" \
644
+ -d "{\"text\": \"Test $i\", \"target\": \"en\"}" \
645
+ -w " HTTP %{http_code}\\n" \
646
+ -o /dev/null
647
+ sleep 1
648
+ done</code></pre>
649
+ </section>
650
+
651
+ <!-- Limits Section -->
652
+ <section id="limits" class="section">
653
+ <h2>⚙️ Limites et bonnes pratiques</h2>
654
+
655
+ <div class="info-box">
656
+ <h4>📋 Limites actuelles</h4>
657
+ <table>
658
+ <thead><tr><th>Limite</th><th>Valeur</th><th>Description</th></tr></thead>
659
+ <tbody>
660
+ <tr><td>Rate limit</td><td>30 req/min/IP</td><td>Bloque avec HTTP 429 si dépassé</td></tr>
661
+ <tr><td>Taille max texte</td><td>5000 caractères</td><td>Erreur 400 si dépassé</td></tr>
662
+ <tr><td>Timeout</td><td>30 secondes</td><td>Côté serveur et client recommandé</td></tr>
663
+ <tr><td>Langues</td><td>100+</td><td>Codes ISO 639-1</td></tr>
664
+ </tbody>
665
+ </table>
666
+ </div>
667
+
668
+ <h3>Gestion du rate limit (HTTP 429)</h3>
669
+ <pre><code class="language-javascript">async function translateWithRetry(text, source, target, retries = 3) {
670
+ for (let attempt = 0; attempt <= retries; attempt++) {
671
+ const response = await fetch('/translate', {
672
+ method: 'POST',
673
+ headers: { 'Content-Type': 'application/json' },
674
+ body: JSON.stringify({ text, source, target })
675
+ });
676
+
677
+ if (response.status === 429) {
678
+ const waitTime = Math.min(1000 * Math.pow(2, attempt), 30000);
679
+ console.log(`Rate limit, attente ${waitTime}ms...`);
680
+ await new Promise(r => setTimeout(r, waitTime));
681
+ continue;
682
+ }
683
+
684
+ if (!response.ok) {
685
+ const err = await response.json();
686
+ throw new Error(err.error);
687
+ }
688
+
689
+ return response.json();
690
+ }
691
+ throw new Error('Max retries exceeded');
692
+ }</code></pre>
693
+
694
+ <h3>Bonnes pratiques</h3>
695
+ <ul>
696
+ <li><strong>Mettez en cache</strong> les traductions récurrentes côté client</li>
697
+ <li><strong>Regroupez</strong> les requêtes (batch) quand possible</li>
698
+ <li><strong>Respectez</strong> le rate limit : implémentez un backoff exponentiel</li>
699
+ <li><strong>Surveillez</strong> vos logs via le panel admin <code>/admin</code></li>
700
+ <li><strong>Utilisez</strong> la détection auto (<code>source: "auto"</code>) seulement si nécessaire</li>
701
+ </ul>
702
+
703
+ <h3>Codes d'erreur HTTP</h3>
704
+ <table>
705
+ <thead><tr><th>Code</th><th>Signification</th><th>Action</th></tr></thead>
706
+ <tbody>
707
+ <tr><td>200</td><td>Succès</td><td>-</td></tr>
708
+ <tr><td>400</td><td>Requête invalide</td><td>Vérifiez le JSON et la taille du texte</td></tr>
709
+ <tr><td>429</td><td>Trop de requêtes</td><td>Attendez et réessayez (backoff)</td></tr>
710
+ <tr><td>500</td><td>Erreur serveur</td><td>Réessayez plus tard, contactez admin si persiste</td></tr>
711
+ </tbody>
712
+ </table>
713
+ </section>
714
+ </div>
715
+
716
+ <script>
717
+ function showSection(sectionId) {
718
+ document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
719
+ document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
720
+ document.getElementById(sectionId).classList.add('active');
721
+ document.querySelector(`[onclick="showSection('${sectionId}')"]`).classList.add('active');
722
+ }
723
+
724
+ function showCode(codeId) {
725
+ document.querySelectorAll('.code-block').forEach(c => c.classList.remove('active'));
726
+ document.querySelectorAll('.lang-tab').forEach(t => t.classList.remove('active'));
727
+ document.getElementById(codeId).classList.add('active');
728
+ event.target.classList.add('active');
729
+ }
730
+ </script>
731
+ </body>
732
+ </html>
settings.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "maxConcurrentJobs": 2,
3
+ "centralHost": "localhost",
4
+ "centralPort": 7820
5
+ }
src/index.js ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('dotenv').config();
2
+ const WebSocket = require('ws');
3
+ const translate = require('@vitalets/google-translate-api');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const winston = require('winston');
8
+
9
+ const settings = JSON.parse(fs.readFileSync(path.join(__dirname, '../settings.json'), 'utf8'));
10
+
11
+ // Logger
12
+ const logger = winston.createLogger({
13
+ level: 'info',
14
+ format: winston.format.combine(
15
+ winston.format.timestamp(),
16
+ winston.format.json()
17
+ ),
18
+ transports: [
19
+ new winston.transports.Console(),
20
+ new winston.transports.File({ filename: path.join(__dirname, '../../logs/worker.log') })
21
+ ]
22
+ });
23
+
24
+ // Worker state
25
+ let workerId = null;
26
+ let ws = null;
27
+ let maxConcurrentJobs = settings.maxConcurrentJobs;
28
+ let activeJobs = 0;
29
+ let reconnectAttempts = 0;
30
+ const MAX_RECONNECT_ATTEMPTS = 10;
31
+ const RECONNECT_DELAY = 5000;
32
+
33
+ // Connect to central server
34
+ function connect() {
35
+ const url = `ws://${settings.centralHost}:${settings.centralPort}`;
36
+ logger.info(`Connecting to central server at ${url}`);
37
+
38
+ ws = new WebSocket(url);
39
+
40
+ ws.on('open', () => {
41
+ logger.info('Connected to central server');
42
+ reconnectAttempts = 0;
43
+ });
44
+
45
+ ws.on('message', (data) => {
46
+ try {
47
+ const msg = JSON.parse(data);
48
+ handleMessage(msg);
49
+ } catch (e) {
50
+ logger.error('Invalid message from central', e);
51
+ }
52
+ });
53
+
54
+ ws.on('close', () => {
55
+ logger.warn('Disconnected from central server');
56
+ scheduleReconnect();
57
+ });
58
+
59
+ ws.on('error', (err) => {
60
+ logger.error('WebSocket error', err);
61
+ });
62
+ }
63
+
64
+ function scheduleReconnect() {
65
+ if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
66
+ logger.error('Max reconnect attempts reached, exiting');
67
+ process.exit(1);
68
+ }
69
+
70
+ reconnectAttempts++;
71
+ const delay = RECONNECT_DELAY * reconnectAttempts;
72
+ logger.info(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
73
+
74
+ setTimeout(connect, delay);
75
+ }
76
+
77
+ function handleMessage(msg) {
78
+ switch (msg.type) {
79
+ case 'welcome':
80
+ workerId = msg.workerId;
81
+ maxConcurrentJobs = msg.maxConcurrentJobs || maxConcurrentJobs;
82
+ logger.info(`Worker registered with ID: ${workerId}, max jobs: ${maxConcurrentJobs}`);
83
+ startHeartbeat();
84
+ break;
85
+
86
+ case 'translate':
87
+ handleTranslateJob(msg);
88
+ break;
89
+
90
+ case 'ping':
91
+ // Keep alive
92
+ ws.send(JSON.stringify({ type: 'pong' }));
93
+ break;
94
+ }
95
+ }
96
+
97
+ async function handleTranslateJob(msg) {
98
+ const { jobId, text, source, target } = msg;
99
+
100
+ if (activeJobs >= maxConcurrentJobs) {
101
+ logger.warn(`Job ${jobId} rejected: at capacity (${activeJobs}/${maxConcurrentJobs})`);
102
+ sendResult(jobId, { error: 'Worker at capacity' });
103
+ return;
104
+ }
105
+
106
+ activeJobs++;
107
+ logger.info(`Starting job ${jobId} (${activeJobs}/${maxConcurrentJobs})`);
108
+
109
+ try {
110
+ const result = await translate(text, { from: source, to: target });
111
+ sendResult(jobId, { translatedText: result.text });
112
+ } catch (error) {
113
+ logger.error(`Job ${jobId} failed`, error);
114
+ sendResult(jobId, { error: error.message });
115
+ } finally {
116
+ activeJobs--;
117
+ }
118
+ }
119
+
120
+ function sendResult(jobId, result) {
121
+ if (ws && ws.readyState === WebSocket.OPEN) {
122
+ ws.send(JSON.stringify({
123
+ type: 'result',
124
+ jobId,
125
+ result
126
+ }));
127
+ }
128
+ }
129
+
130
+ // Heartbeat with metrics
131
+ function startHeartbeat() {
132
+ setInterval(() => {
133
+ if (ws && ws.readyState === WebSocket.OPEN) {
134
+ const cpuUsage = process.cpuUsage();
135
+ const memUsage = process.memoryUsage();
136
+
137
+ ws.send(JSON.stringify({
138
+ type: 'heartbeat',
139
+ cpu: (cpuUsage.user + cpuUsage.system) / 10000, // Convert to percentage-ish
140
+ ram: memUsage.heapUsed,
141
+ jobsActive: activeJobs
142
+ }));
143
+ }
144
+ }, 5000);
145
+ }
146
+
147
+ // Handle graceful shutdown
148
+ process.on('SIGTERM', () => {
149
+ logger.info('SIGTERM received, shutting down gracefully');
150
+ if (ws) ws.close();
151
+ process.exit(0);
152
+ });
153
+
154
+ process.on('SIGINT', () => {
155
+ logger.info('SIGINT received, shutting down gracefully');
156
+ if (ws) ws.close();
157
+ process.exit(0);
158
+ });
159
+
160
+ // Start
161
+ logger.info('Starting Translator Worker');
162
+ connect();
worker/settings.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "maxConcurrentJobs": 2,
3
+ "centralHost": "localhost",
4
+ "centralPort": 7820
5
+ }
worker/src/index.js ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('dotenv').config();
2
+ const WebSocket = require('ws');
3
+ const translate = require('@vitalets/google-translate-api');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const winston = require('winston');
8
+
9
+ const settings = JSON.parse(fs.readFileSync(path.join(__dirname, '../settings.json'), 'utf8'));
10
+
11
+ // Logger
12
+ const logger = winston.createLogger({
13
+ level: 'info',
14
+ format: winston.format.combine(
15
+ winston.format.timestamp(),
16
+ winston.format.json()
17
+ ),
18
+ transports: [
19
+ new winston.transports.Console(),
20
+ new winston.transports.File({ filename: path.join(__dirname, '../../logs/worker.log') })
21
+ ]
22
+ });
23
+
24
+ // Worker state
25
+ let workerId = null;
26
+ let ws = null;
27
+ let maxConcurrentJobs = settings.maxConcurrentJobs;
28
+ let activeJobs = 0;
29
+ let reconnectAttempts = 0;
30
+ const MAX_RECONNECT_ATTEMPTS = 10;
31
+ const RECONNECT_DELAY = 5000;
32
+
33
+ // Connect to central server
34
+ function connect() {
35
+ const url = `ws://${settings.centralHost}:${settings.centralPort}`;
36
+ logger.info(`Connecting to central server at ${url}`);
37
+
38
+ ws = new WebSocket(url);
39
+
40
+ ws.on('open', () => {
41
+ logger.info('Connected to central server');
42
+ reconnectAttempts = 0;
43
+ });
44
+
45
+ ws.on('message', (data) => {
46
+ try {
47
+ const msg = JSON.parse(data);
48
+ handleMessage(msg);
49
+ } catch (e) {
50
+ logger.error('Invalid message from central', e);
51
+ }
52
+ });
53
+
54
+ ws.on('close', () => {
55
+ logger.warn('Disconnected from central server');
56
+ scheduleReconnect();
57
+ });
58
+
59
+ ws.on('error', (err) => {
60
+ logger.error('WebSocket error', err);
61
+ });
62
+ }
63
+
64
+ function scheduleReconnect() {
65
+ if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
66
+ logger.error('Max reconnect attempts reached, exiting');
67
+ process.exit(1);
68
+ }
69
+
70
+ reconnectAttempts++;
71
+ const delay = RECONNECT_DELAY * reconnectAttempts;
72
+ logger.info(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
73
+
74
+ setTimeout(connect, delay);
75
+ }
76
+
77
+ function handleMessage(msg) {
78
+ switch (msg.type) {
79
+ case 'welcome':
80
+ workerId = msg.workerId;
81
+ maxConcurrentJobs = msg.maxConcurrentJobs || maxConcurrentJobs;
82
+ logger.info(`Worker registered with ID: ${workerId}, max jobs: ${maxConcurrentJobs}`);
83
+ startHeartbeat();
84
+ break;
85
+
86
+ case 'translate':
87
+ handleTranslateJob(msg);
88
+ break;
89
+
90
+ case 'ping':
91
+ // Keep alive
92
+ ws.send(JSON.stringify({ type: 'pong' }));
93
+ break;
94
+ }
95
+ }
96
+
97
+ async function handleTranslateJob(msg) {
98
+ const { jobId, text, source, target } = msg;
99
+
100
+ if (activeJobs >= maxConcurrentJobs) {
101
+ logger.warn(`Job ${jobId} rejected: at capacity (${activeJobs}/${maxConcurrentJobs})`);
102
+ sendResult(jobId, { error: 'Worker at capacity' });
103
+ return;
104
+ }
105
+
106
+ activeJobs++;
107
+ logger.info(`Starting job ${jobId} (${activeJobs}/${maxConcurrentJobs})`);
108
+
109
+ try {
110
+ const result = await translate(text, { from: source, to: target });
111
+ sendResult(jobId, { translatedText: result.text });
112
+ } catch (error) {
113
+ logger.error(`Job ${jobId} failed`, error);
114
+ sendResult(jobId, { error: error.message });
115
+ } finally {
116
+ activeJobs--;
117
+ }
118
+ }
119
+
120
+ function sendResult(jobId, result) {
121
+ if (ws && ws.readyState === WebSocket.OPEN) {
122
+ ws.send(JSON.stringify({
123
+ type: 'result',
124
+ jobId,
125
+ result
126
+ }));
127
+ }
128
+ }
129
+
130
+ // Heartbeat with metrics
131
+ function startHeartbeat() {
132
+ setInterval(() => {
133
+ if (ws && ws.readyState === WebSocket.OPEN) {
134
+ const cpuUsage = process.cpuUsage();
135
+ const memUsage = process.memoryUsage();
136
+
137
+ ws.send(JSON.stringify({
138
+ type: 'heartbeat',
139
+ cpu: (cpuUsage.user + cpuUsage.system) / 10000, // Convert to percentage-ish
140
+ ram: memUsage.heapUsed,
141
+ jobsActive: activeJobs
142
+ }));
143
+ }
144
+ }, 5000);
145
+ }
146
+
147
+ // Handle graceful shutdown
148
+ process.on('SIGTERM', () => {
149
+ logger.info('SIGTERM received, shutting down gracefully');
150
+ if (ws) ws.close();
151
+ process.exit(0);
152
+ });
153
+
154
+ process.on('SIGINT', () => {
155
+ logger.info('SIGINT received, shutting down gracefully');
156
+ if (ws) ws.close();
157
+ process.exit(0);
158
+ });
159
+
160
+ // Start
161
+ logger.info('Starting Translator Worker');
162
+ connect();