@dollhousemcp/mcp-server 2.0.0-rc.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -61
- package/README.github.md +2 -2
- package/README.md +2 -2
- package/README.md.backup +284 -224
- package/README.npm.md +2 -2
- package/dist/cache/LRUCache.d.ts +3 -0
- package/dist/cache/LRUCache.d.ts.map +1 -1
- package/dist/cache/LRUCache.js +36 -26
- package/dist/config/env.d.ts +14 -4
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +20 -6
- package/dist/di/Container.d.ts +21 -0
- package/dist/di/Container.d.ts.map +1 -1
- package/dist/di/Container.js +250 -53
- package/dist/elements/BaseElement.d.ts.map +1 -1
- package/dist/elements/BaseElement.js +5 -10
- package/dist/elements/base/BaseElementManager.d.ts +22 -0
- package/dist/elements/base/BaseElementManager.d.ts.map +1 -1
- package/dist/elements/base/BaseElementManager.js +47 -7
- package/dist/elements/memories/Memory.d.ts +1 -0
- package/dist/elements/memories/Memory.d.ts.map +1 -1
- package/dist/elements/memories/Memory.js +12 -8
- package/dist/elements/memories/MemoryManager.d.ts.map +1 -1
- package/dist/elements/memories/MemoryManager.js +23 -42
- package/dist/elements/memories/MemorySearchIndex.js +2 -2
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.d.ts.map +1 -1
- package/dist/generated/version.js +3 -3
- package/dist/handlers/EnhancedIndexHandler.js +6 -6
- package/dist/handlers/element-crud/listElements.d.ts +2 -0
- package/dist/handlers/element-crud/listElements.d.ts.map +1 -1
- package/dist/handlers/element-crud/listElements.js +3 -1
- package/dist/handlers/mcp-aql/Gatekeeper.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/Gatekeeper.js +23 -17
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts +14 -0
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/MCPAQLHandler.js +110 -14
- package/dist/handlers/mcp-aql/OperationRouter.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/OperationRouter.js +13 -1
- package/dist/handlers/mcp-aql/OperationSchema.d.ts +7 -0
- package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/OperationSchema.js +52 -1
- package/dist/handlers/mcp-aql/evaluatePermission.d.ts +53 -0
- package/dist/handlers/mcp-aql/evaluatePermission.d.ts.map +1 -0
- package/dist/handlers/mcp-aql/evaluatePermission.js +132 -0
- package/dist/handlers/mcp-aql/policies/ToolClassification.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/policies/ToolClassification.js +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/logging/LogHooks.js +11 -11
- package/dist/logging/LogManager.d.ts +0 -2
- package/dist/logging/LogManager.d.ts.map +1 -1
- package/dist/logging/LogManager.js +1 -3
- package/dist/logging/sinks/MemoryLogSink.d.ts +2 -0
- package/dist/logging/sinks/MemoryLogSink.d.ts.map +1 -1
- package/dist/logging/sinks/MemoryLogSink.js +12 -3
- package/dist/logging/types.d.ts +0 -2
- package/dist/logging/types.d.ts.map +1 -1
- package/dist/logging/types.js +1 -1
- package/dist/metrics/GatekeeperMetricsTracker.d.ts +32 -0
- package/dist/metrics/GatekeeperMetricsTracker.d.ts.map +1 -0
- package/dist/metrics/GatekeeperMetricsTracker.js +42 -0
- package/dist/metrics/MetricsManager.d.ts +47 -0
- package/dist/metrics/MetricsManager.d.ts.map +1 -0
- package/dist/metrics/MetricsManager.js +232 -0
- package/dist/metrics/OperationMetricsTracker.d.ts +32 -0
- package/dist/metrics/OperationMetricsTracker.d.ts.map +1 -0
- package/dist/metrics/OperationMetricsTracker.js +53 -0
- package/dist/metrics/collectors/DefaultElementProviderCollector.d.ts +27 -0
- package/dist/metrics/collectors/DefaultElementProviderCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/DefaultElementProviderCollector.js +69 -0
- package/dist/metrics/collectors/FileLockManagerCollector.d.ts +16 -0
- package/dist/metrics/collectors/FileLockManagerCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/FileLockManagerCollector.js +51 -0
- package/dist/metrics/collectors/GatekeeperMetricsCollector.d.ts +16 -0
- package/dist/metrics/collectors/GatekeeperMetricsCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/GatekeeperMetricsCollector.js +76 -0
- package/dist/metrics/collectors/LRUCacheCollector.d.ts +18 -0
- package/dist/metrics/collectors/LRUCacheCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/LRUCacheCollector.js +95 -0
- package/dist/metrics/collectors/OperationMetricsCollector.d.ts +16 -0
- package/dist/metrics/collectors/OperationMetricsCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/OperationMetricsCollector.js +80 -0
- package/dist/metrics/collectors/OperationalTelemetryCollector.d.ts +17 -0
- package/dist/metrics/collectors/OperationalTelemetryCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/OperationalTelemetryCollector.js +26 -0
- package/dist/metrics/collectors/PerformanceMonitorCollector.d.ts +14 -0
- package/dist/metrics/collectors/PerformanceMonitorCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/PerformanceMonitorCollector.js +141 -0
- package/dist/metrics/collectors/SecurityMonitorCollector.d.ts +21 -0
- package/dist/metrics/collectors/SecurityMonitorCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/SecurityMonitorCollector.js +56 -0
- package/dist/metrics/collectors/SecurityTelemetryCollector.d.ts +15 -0
- package/dist/metrics/collectors/SecurityTelemetryCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/SecurityTelemetryCollector.js +112 -0
- package/dist/metrics/collectors/TriggerMetricsTrackerCollector.d.ts +16 -0
- package/dist/metrics/collectors/TriggerMetricsTrackerCollector.d.ts.map +1 -0
- package/dist/metrics/collectors/TriggerMetricsTrackerCollector.js +26 -0
- package/dist/metrics/collectors/index.d.ts +11 -0
- package/dist/metrics/collectors/index.d.ts.map +1 -0
- package/dist/metrics/collectors/index.js +11 -0
- package/dist/metrics/sinks/MemoryMetricsSink.d.ts +22 -0
- package/dist/metrics/sinks/MemoryMetricsSink.d.ts.map +1 -0
- package/dist/metrics/sinks/MemoryMetricsSink.js +121 -0
- package/dist/metrics/types.d.ts +98 -0
- package/dist/metrics/types.d.ts.map +1 -0
- package/dist/metrics/types.js +24 -0
- package/dist/portfolio/DefaultElementProvider.d.ts.map +1 -1
- package/dist/portfolio/DefaultElementProvider.js +1 -7
- package/dist/portfolio/EnhancedIndexManager.d.ts.map +1 -1
- package/dist/portfolio/EnhancedIndexManager.js +18 -18
- package/dist/portfolio/NLPScoringManager.d.ts.map +1 -1
- package/dist/portfolio/NLPScoringManager.js +5 -9
- package/dist/portfolio/PortfolioIndexManager.js +2 -2
- package/dist/portfolio/RelationshipManager.js +2 -2
- package/dist/portfolio/VerbTriggerManager.d.ts.map +1 -1
- package/dist/portfolio/VerbTriggerManager.js +5 -19
- package/dist/portfolio/config/IndexConfig.d.ts.map +1 -1
- package/dist/portfolio/config/IndexConfig.js +1 -12
- package/dist/portfolio/enhanced-index/ElementDefinitionBuilder.d.ts.map +1 -1
- package/dist/portfolio/enhanced-index/ElementDefinitionBuilder.js +3 -15
- package/dist/portfolio/enhanced-index/SemanticRelationshipService.d.ts.map +1 -1
- package/dist/portfolio/enhanced-index/SemanticRelationshipService.js +2 -16
- package/dist/portfolio/types/RelationshipTypes.d.ts.map +1 -1
- package/dist/portfolio/types/RelationshipTypes.js +3 -17
- package/dist/security/audit/config/suppressions.d.ts.map +1 -1
- package/dist/security/audit/config/suppressions.js +36 -8
- package/dist/security/constants.d.ts.map +1 -1
- package/dist/security/constants.js +10 -6
- package/dist/security/fileLockManager.d.ts.map +1 -1
- package/dist/security/fileLockManager.js +8 -6
- package/dist/security/secureYamlParser.d.ts.map +1 -1
- package/dist/security/secureYamlParser.js +1 -13
- package/dist/security/securityMonitor.d.ts +2 -1
- package/dist/security/securityMonitor.d.ts.map +1 -1
- package/dist/security/securityMonitor.js +14 -3
- package/dist/security/telemetry/SecurityTelemetry.d.ts +16 -0
- package/dist/security/telemetry/SecurityTelemetry.d.ts.map +1 -1
- package/dist/security/telemetry/SecurityTelemetry.js +30 -2
- package/dist/security/tokenManager.d.ts +3 -0
- package/dist/security/tokenManager.d.ts.map +1 -1
- package/dist/security/tokenManager.js +13 -5
- package/dist/security/validation/BackgroundValidator.d.ts.map +1 -1
- package/dist/security/validation/BackgroundValidator.js +7 -7
- package/dist/server/startup.d.ts.map +1 -1
- package/dist/server/startup.js +8 -24
- package/dist/server/tools/MCPAQLTools.js +4 -1
- package/dist/services/ActivationStore.d.ts.map +1 -1
- package/dist/services/ActivationStore.js +9 -3
- package/dist/services/FileWatchService.d.ts +1 -0
- package/dist/services/FileWatchService.d.ts.map +1 -1
- package/dist/services/FileWatchService.js +83 -48
- package/dist/services/MetadataService.d.ts.map +1 -1
- package/dist/services/MetadataService.js +7 -2
- package/dist/services/query/ElementQueryService.d.ts.map +1 -1
- package/dist/services/query/ElementQueryService.js +1 -41
- package/dist/services/query/PaginationService.d.ts.map +1 -1
- package/dist/services/query/PaginationService.js +1 -14
- package/dist/services/query/SortService.d.ts.map +1 -1
- package/dist/services/query/SortService.js +1 -6
- package/dist/services/validation/ValidationService.d.ts.map +1 -1
- package/dist/services/validation/ValidationService.js +3 -8
- package/dist/storage/ElementStorageLayer.d.ts.map +1 -1
- package/dist/storage/ElementStorageLayer.js +5 -2
- package/dist/storage/MemoryStorageLayer.d.ts.map +1 -1
- package/dist/storage/MemoryStorageLayer.js +5 -2
- package/dist/telemetry/OperationalTelemetry.js +2 -2
- package/dist/utils/EventDeduplicator.d.ts +44 -0
- package/dist/utils/EventDeduplicator.d.ts.map +1 -0
- package/dist/utils/EventDeduplicator.js +93 -0
- package/dist/utils/FileLock.d.ts.map +1 -1
- package/dist/utils/FileLock.js +1 -9
- package/dist/utils/PerformanceMonitor.d.ts.map +1 -1
- package/dist/utils/PerformanceMonitor.js +5 -5
- package/dist/utils/SlidingWindowRateLimiter.d.ts +13 -0
- package/dist/utils/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/utils/SlidingWindowRateLimiter.js +23 -0
- package/dist/web/console/IngestRoutes.d.ts +84 -0
- package/dist/web/console/IngestRoutes.d.ts.map +1 -0
- package/dist/web/console/IngestRoutes.js +252 -0
- package/dist/web/console/LeaderElection.d.ts +89 -0
- package/dist/web/console/LeaderElection.d.ts.map +1 -0
- package/dist/web/console/LeaderElection.js +205 -0
- package/dist/web/console/LeaderForwardingSink.d.ts +61 -0
- package/dist/web/console/LeaderForwardingSink.d.ts.map +1 -0
- package/dist/web/console/LeaderForwardingSink.js +197 -0
- package/dist/web/console/SessionNames.d.ts +46 -0
- package/dist/web/console/SessionNames.d.ts.map +1 -0
- package/dist/web/console/SessionNames.js +257 -0
- package/dist/web/console/UnifiedConsole.d.ts +64 -0
- package/dist/web/console/UnifiedConsole.d.ts.map +1 -0
- package/dist/web/console/UnifiedConsole.js +119 -0
- package/dist/web/contentPipeline.d.ts +58 -0
- package/dist/web/contentPipeline.d.ts.map +1 -0
- package/dist/web/contentPipeline.js +112 -0
- package/dist/web/portDiscovery.d.ts +58 -0
- package/dist/web/portDiscovery.d.ts.map +1 -0
- package/dist/web/portDiscovery.js +143 -0
- package/dist/web/public/app.js +148 -60
- package/dist/web/public/logs.js +638 -0
- package/dist/web/public/metrics.js +682 -0
- package/dist/web/public/permissions.js +394 -0
- package/dist/web/public/sessions.js +369 -0
- package/dist/web/routes/healthRoutes.d.ts +16 -0
- package/dist/web/routes/healthRoutes.d.ts.map +1 -0
- package/dist/web/routes/healthRoutes.js +29 -0
- package/dist/web/routes/logRoutes.d.ts +18 -0
- package/dist/web/routes/logRoutes.d.ts.map +1 -0
- package/dist/web/routes/logRoutes.js +126 -0
- package/dist/web/routes/metricsRoutes.d.ts +17 -0
- package/dist/web/routes/metricsRoutes.d.ts.map +1 -0
- package/dist/web/routes/metricsRoutes.js +90 -0
- package/dist/web/routes/permissionRoutes.d.ts +16 -0
- package/dist/web/routes/permissionRoutes.d.ts.map +1 -0
- package/dist/web/routes/permissionRoutes.js +133 -0
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +309 -339
- package/dist/web/server.d.ts +21 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +42 -4
- package/dist/web/sinks/WebSSELogSink.d.ts +15 -0
- package/dist/web/sinks/WebSSELogSink.d.ts.map +1 -0
- package/dist/web/sinks/WebSSELogSink.js +22 -0
- package/dist/web/sinks/WebSSEMetricsSink.d.ts +16 -0
- package/dist/web/sinks/WebSSEMetricsSink.d.ts.map +1 -0
- package/dist/web/sinks/WebSSEMetricsSink.js +23 -0
- package/package.json +2 -2
- package/server.json +2 -2
- package/dist/constants/version.d.ts +0 -3
- package/dist/constants/version.d.ts.map +0 -1
- package/dist/constants/version.js +0 -4
- package/dist/logging/sinks/SSELogSink.d.ts +0 -35
- package/dist/logging/sinks/SSELogSink.d.ts.map +0 -1
- package/dist/logging/sinks/SSELogSink.js +0 -181
- package/dist/logging/viewer/viewerHtml.d.ts +0 -8
- package/dist/logging/viewer/viewerHtml.d.ts.map +0 -1
- package/dist/logging/viewer/viewerHtml.js +0 -204
- package/dist/web/public/public/app.js +0 -1878
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event ingestion routes for the unified web console.
|
|
3
|
+
*
|
|
4
|
+
* The console leader mounts these routes so follower MCP servers can
|
|
5
|
+
* forward their logs, metrics, and session lifecycle events. All ingested
|
|
6
|
+
* entries are stamped with `_sessionId` in their data field and then
|
|
7
|
+
* broadcast to SSE clients via the existing log/metrics broadcast hooks.
|
|
8
|
+
*
|
|
9
|
+
* Routes:
|
|
10
|
+
* - POST /api/ingest/logs — Batched log entries from a follower
|
|
11
|
+
* - POST /api/ingest/metrics — Metric snapshots from a follower
|
|
12
|
+
* - POST /api/ingest/session — Session lifecycle events (started/stopped/heartbeat)
|
|
13
|
+
* - GET /api/sessions — Active session list for the UI
|
|
14
|
+
*
|
|
15
|
+
* @since v2.1.0 — Issue #1700
|
|
16
|
+
*/
|
|
17
|
+
import express, { Router } from 'express';
|
|
18
|
+
import { SlidingWindowRateLimiter } from '../../utils/SlidingWindowRateLimiter.js';
|
|
19
|
+
import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
|
|
20
|
+
import { SessionNamePool } from './SessionNames.js';
|
|
21
|
+
import { logger } from '../../utils/logger.js';
|
|
22
|
+
/** Maximum payload size for ingestion requests */
|
|
23
|
+
const MAX_PAYLOAD_SIZE = '1mb';
|
|
24
|
+
/** Rate limit: max requests per window per source */
|
|
25
|
+
const RATE_LIMIT_MAX = 1000;
|
|
26
|
+
const RATE_LIMIT_WINDOW_MS = 60_000;
|
|
27
|
+
/** How often to check for stale sessions (ms) */
|
|
28
|
+
const REAPER_INTERVAL_MS = 5_000;
|
|
29
|
+
/** How long since last heartbeat before a session is considered dead (ms) */
|
|
30
|
+
const SESSION_STALE_MS = 15_000;
|
|
31
|
+
/** Normalize a string via UnicodeValidator (DMCP-SEC-004) */
|
|
32
|
+
function normalizeInput(s) {
|
|
33
|
+
return UnicodeValidator.normalize(s).normalizedContent;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create the ingestion routes and session registry.
|
|
37
|
+
*
|
|
38
|
+
* @param broadcasts - Callbacks to forward ingested events to SSE clients
|
|
39
|
+
* @returns Router and session management functions
|
|
40
|
+
*/
|
|
41
|
+
export function createIngestRoutes(broadcasts) {
|
|
42
|
+
const router = Router();
|
|
43
|
+
const sessions = new Map();
|
|
44
|
+
const namePool = new SessionNamePool();
|
|
45
|
+
const rateLimiter = new SlidingWindowRateLimiter(RATE_LIMIT_MAX, RATE_LIMIT_WINDOW_MS);
|
|
46
|
+
// JSON body parsing with size limit
|
|
47
|
+
router.use(express.json({ limit: MAX_PAYLOAD_SIZE }));
|
|
48
|
+
/**
|
|
49
|
+
* POST /api/ingest/logs — Receive batched log entries from a follower.
|
|
50
|
+
*/
|
|
51
|
+
router.post('/api/ingest/logs', (req, res) => {
|
|
52
|
+
if (!rateLimiter.tryAcquire()) {
|
|
53
|
+
res.status(429).json({ error: 'Rate limit exceeded' });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const payload = req.body;
|
|
57
|
+
if (!payload?.sessionId || !Array.isArray(payload.entries)) {
|
|
58
|
+
const received = payload ? Object.keys(payload) : [];
|
|
59
|
+
logger.warn('[IngestRoutes] Invalid log payload', { received, hasSessionId: !!payload?.sessionId, hasEntries: Array.isArray(payload?.entries) });
|
|
60
|
+
res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'entries'], received });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
payload.sessionId = normalizeInput(payload.sessionId);
|
|
64
|
+
let count = 0;
|
|
65
|
+
let skipped = 0;
|
|
66
|
+
for (const entry of payload.entries) {
|
|
67
|
+
if (!entry || typeof entry.message !== 'string') {
|
|
68
|
+
skipped++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const stamped = {
|
|
72
|
+
...entry,
|
|
73
|
+
data: { ...entry.data, _sessionId: payload.sessionId },
|
|
74
|
+
};
|
|
75
|
+
broadcasts.logBroadcast(stamped);
|
|
76
|
+
count++;
|
|
77
|
+
}
|
|
78
|
+
// Update session heartbeat
|
|
79
|
+
const session = sessions.get(payload.sessionId);
|
|
80
|
+
if (session) {
|
|
81
|
+
session.lastHeartbeat = new Date().toISOString();
|
|
82
|
+
}
|
|
83
|
+
if (skipped > 0) {
|
|
84
|
+
logger.debug(`[IngestRoutes] Log ingest from ${session?.displayName ?? payload.sessionId}: accepted=${count}, skipped=${skipped}`);
|
|
85
|
+
}
|
|
86
|
+
res.status(200).json({ accepted: count, skipped });
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* POST /api/ingest/metrics — Receive metric snapshots from a follower.
|
|
90
|
+
*/
|
|
91
|
+
router.post('/api/ingest/metrics', (req, res) => {
|
|
92
|
+
if (!rateLimiter.tryAcquire()) {
|
|
93
|
+
res.status(429).json({ error: 'Rate limit exceeded' });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const payload = req.body;
|
|
97
|
+
if (!payload?.sessionId || !payload.snapshot) {
|
|
98
|
+
const received = payload ? Object.keys(payload) : [];
|
|
99
|
+
logger.warn('[IngestRoutes] Invalid metrics payload', { received });
|
|
100
|
+
res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'snapshot'], received });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
payload.sessionId = normalizeInput(payload.sessionId);
|
|
104
|
+
if (broadcasts.metricsOnSnapshot) {
|
|
105
|
+
broadcasts.metricsOnSnapshot(payload.snapshot);
|
|
106
|
+
}
|
|
107
|
+
const session = sessions.get(payload.sessionId);
|
|
108
|
+
logger.debug(`[IngestRoutes] Metrics ingested from ${session?.displayName ?? payload.sessionId}`);
|
|
109
|
+
res.status(200).json({ accepted: true });
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* POST /api/ingest/session — Session lifecycle events.
|
|
113
|
+
*/
|
|
114
|
+
router.post('/api/ingest/session', (req, res) => {
|
|
115
|
+
const payload = req.body;
|
|
116
|
+
if (!payload?.sessionId || !payload.event) {
|
|
117
|
+
const received = payload ? Object.keys(payload) : [];
|
|
118
|
+
logger.warn('[IngestRoutes] Invalid session event payload', { received });
|
|
119
|
+
res.status(400).json({ error: 'Invalid payload', required: ['sessionId', 'event'], received });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
payload.sessionId = normalizeInput(payload.sessionId);
|
|
123
|
+
const now = new Date().toISOString();
|
|
124
|
+
switch (payload.event) {
|
|
125
|
+
case 'started': {
|
|
126
|
+
const displayName = namePool.assign(payload.sessionId);
|
|
127
|
+
const color = namePool.getColor(payload.sessionId) ?? '#3b82f6';
|
|
128
|
+
const info = {
|
|
129
|
+
sessionId: payload.sessionId,
|
|
130
|
+
displayName,
|
|
131
|
+
color,
|
|
132
|
+
pid: payload.pid,
|
|
133
|
+
startedAt: payload.startedAt || now,
|
|
134
|
+
lastHeartbeat: now,
|
|
135
|
+
status: 'active',
|
|
136
|
+
isLeader: false,
|
|
137
|
+
};
|
|
138
|
+
sessions.set(payload.sessionId, info);
|
|
139
|
+
logger.info('[IngestRoutes] Session registered', {
|
|
140
|
+
displayName, sessionId: payload.sessionId, pid: payload.pid, color,
|
|
141
|
+
activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length,
|
|
142
|
+
});
|
|
143
|
+
broadcasts.sessionBroadcast?.(info);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'stopped': {
|
|
147
|
+
const existing = sessions.get(payload.sessionId);
|
|
148
|
+
if (existing) {
|
|
149
|
+
existing.status = 'ended';
|
|
150
|
+
existing.lastHeartbeat = now;
|
|
151
|
+
namePool.release(payload.sessionId);
|
|
152
|
+
logger.info('[IngestRoutes] Session stopped', {
|
|
153
|
+
displayName: existing.displayName, sessionId: payload.sessionId, pid: existing.pid,
|
|
154
|
+
activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1,
|
|
155
|
+
});
|
|
156
|
+
broadcasts.sessionBroadcast?.(existing);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 'heartbeat': {
|
|
161
|
+
const existing = sessions.get(payload.sessionId);
|
|
162
|
+
if (existing) {
|
|
163
|
+
existing.lastHeartbeat = now;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
res.status(200).json({ ok: true });
|
|
169
|
+
});
|
|
170
|
+
/**
|
|
171
|
+
* GET /api/sessions — List all tracked sessions.
|
|
172
|
+
*/
|
|
173
|
+
router.get('/api/sessions', (_req, res) => {
|
|
174
|
+
const allSessions = Array.from(sessions.values());
|
|
175
|
+
res.json({ sessions: allSessions });
|
|
176
|
+
});
|
|
177
|
+
/**
|
|
178
|
+
* POST /api/sessions/:sessionId/kill — Terminate a session's server process.
|
|
179
|
+
*/
|
|
180
|
+
router.post('/api/sessions/:sessionId/kill', (req, res) => {
|
|
181
|
+
const sessionId = req.params['sessionId'];
|
|
182
|
+
const session = sessions.get(sessionId);
|
|
183
|
+
if (!session) {
|
|
184
|
+
logger.warn('[IngestRoutes] Kill requested for unknown session', { sessionId });
|
|
185
|
+
res.status(404).json({ error: 'Session not found', sessionId });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!session.pid) {
|
|
189
|
+
res.status(400).json({ error: 'No PID for session', sessionId, displayName: session.displayName });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
process.kill(session.pid, 'SIGTERM');
|
|
194
|
+
session.status = 'ended';
|
|
195
|
+
namePool.release(sessionId);
|
|
196
|
+
logger.info('[IngestRoutes] Session killed', {
|
|
197
|
+
displayName: session.displayName, sessionId, pid: session.pid,
|
|
198
|
+
activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1,
|
|
199
|
+
});
|
|
200
|
+
res.json({ ok: true, killed: session.displayName, pid: session.pid });
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
const message = err.message;
|
|
204
|
+
logger.error('[IngestRoutes] Failed to kill session', {
|
|
205
|
+
displayName: session.displayName, sessionId, pid: session.pid, error: message,
|
|
206
|
+
});
|
|
207
|
+
res.status(500).json({ error: 'Failed to kill session', sessionId, displayName: session.displayName, pid: session.pid, detail: message });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
// Reaper: periodically check for stale sessions whose heartbeat has expired
|
|
211
|
+
const reaperInterval = setInterval(() => {
|
|
212
|
+
const now = Date.now();
|
|
213
|
+
for (const [id, session] of sessions) {
|
|
214
|
+
if (session.status !== 'active')
|
|
215
|
+
continue;
|
|
216
|
+
if (session.isLeader)
|
|
217
|
+
continue; // leader manages itself
|
|
218
|
+
const age = now - new Date(session.lastHeartbeat).getTime();
|
|
219
|
+
if (age > SESSION_STALE_MS) {
|
|
220
|
+
session.status = 'ended';
|
|
221
|
+
namePool.release(id);
|
|
222
|
+
logger.info('[IngestRoutes] Reaped stale session', {
|
|
223
|
+
displayName: session.displayName, sessionId: id, pid: session.pid,
|
|
224
|
+
lastHeartbeatAgo: `${Math.round(age / 1000)}s`,
|
|
225
|
+
activeSessions: Array.from(sessions.values()).filter(s => s.status === 'active').length - 1,
|
|
226
|
+
});
|
|
227
|
+
broadcasts.sessionBroadcast?.(session);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}, REAPER_INTERVAL_MS);
|
|
231
|
+
reaperInterval.unref();
|
|
232
|
+
function getSessions() {
|
|
233
|
+
return Array.from(sessions.values()).filter(s => s.status === 'active');
|
|
234
|
+
}
|
|
235
|
+
function registerLeaderSession(sessionId, pid) {
|
|
236
|
+
const displayName = namePool.assign(sessionId, true);
|
|
237
|
+
const color = namePool.getColor(sessionId) ?? '#3b82f6';
|
|
238
|
+
sessions.set(sessionId, {
|
|
239
|
+
sessionId,
|
|
240
|
+
displayName,
|
|
241
|
+
color,
|
|
242
|
+
pid,
|
|
243
|
+
startedAt: new Date().toISOString(),
|
|
244
|
+
lastHeartbeat: new Date().toISOString(),
|
|
245
|
+
status: 'active',
|
|
246
|
+
isLeader: true,
|
|
247
|
+
});
|
|
248
|
+
logger.info('[IngestRoutes] Leader session registered', { displayName, sessionId, pid, color });
|
|
249
|
+
}
|
|
250
|
+
return { router, getSessions, registerLeaderSession };
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiSW5nZXN0Um91dGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3dlYi9jb25zb2xlL0luZ2VzdFJvdXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLE9BQU8sRUFBRSxFQUFFLE1BQU0sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUkxQyxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSx5Q0FBeUMsQ0FBQztBQUNuRixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwrQ0FBK0MsQ0FBQztBQUNqRixPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDcEQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRS9DLGtEQUFrRDtBQUNsRCxNQUFNLGdCQUFnQixHQUFHLEtBQUssQ0FBQztBQUUvQixxREFBcUQ7QUFDckQsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDO0FBQzVCLE1BQU0sb0JBQW9CLEdBQUcsTUFBTSxDQUFDO0FBRXBDLGlEQUFpRDtBQUNqRCxNQUFNLGtCQUFrQixHQUFHLEtBQUssQ0FBQztBQUVqQyw2RUFBNkU7QUFDN0UsTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLENBQUM7QUFpRWhDLDZEQUE2RDtBQUM3RCxTQUFTLGNBQWMsQ0FBQyxDQUFTO0lBQy9CLE9BQU8sZ0JBQWdCLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLGlCQUFpQixDQUFDO0FBQ3pELENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxVQUE0QjtJQUM3RCxNQUFNLE1BQU0sR0FBRyxNQUFNLEVBQUUsQ0FBQztJQUN4QixNQUFNLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBdUIsQ0FBQztJQUNoRCxNQUFNLFFBQVEsR0FBRyxJQUFJLGVBQWUsRUFBRSxDQUFDO0lBQ3ZDLE1BQU0sV0FBVyxHQUFHLElBQUks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LENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxLQUFLLFFBQVEsQ0FBQyxDQUFDO0lBQzFFLENBQUM7SUFFRCxTQUFTLHFCQUFxQixDQUFDLFNBQWlCLEVBQUUsR0FBVztRQUMzRCxNQUFNLFdBQVcsR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNyRCxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxJQUFJLFNBQVMsQ0FBQztRQUN4RCxRQUFRLENBQUMsR0FBRyxDQUFDLFNBQVMsRUFBRTtZQUN0QixTQUFTO1lBQ1QsV0FBVztZQUNYLEtBQUs7WUFDTCxHQUFHO1lBQ0gsU0FBUyxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFO1lBQ25DLGFBQWEsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRTtZQUN2QyxNQUFNLEVBQUUsUUFBUTtZQUNoQixRQUFRLEVBQUUsSUFBSTtTQUNmLENBQUMsQ0FBQztRQUNILE1BQU0sQ0FBQyxJQUFJLENBQUMsMENBQTBDLEVBQUUsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBQ2xHLENBQUM7SUFFRCxPQUFPLEVBQUUsTUFBTSxFQUFFLFdBQVcsRUFBRSxxQkFBcUIsRUFBRSxDQUFDO0FBQ3hELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEV2ZW50IGluZ2VzdGlvbiByb3V0ZXMgZm9yIHRoZSB1bmlmaWVkIHdlYiBjb25zb2xlLlxuICpcbiAqIFRoZSBjb25zb2xlIGxlYWRlciBtb3VudHMgdGhlc2Ugcm91dGVzIHNvIGZvbGxvd2VyIE1DUCBzZXJ2ZXJzIGNhblxuICogZm9yd2FyZCB0aGVpciBsb2dzLCBtZXRyaWNzLCBhbmQgc2Vzc2lvbiBsaWZlY3ljbGUgZXZlbnRzLiBBbGwgaW5nZXN0ZWRcbiAqIGVudHJpZXMgYXJlIHN0YW1wZWQgd2l0aCBgX3Nlc3Npb25JZGAgaW4gdGhlaXIgZGF0YSBmaWVsZCBhbmQgdGhlblxuICogYnJvYWRjYXN0IHRvIFNTRSBjbGllbnRzIHZpYSB0aGUgZXhpc3RpbmcgbG9nL21ldHJpY3MgYnJvYWRjYXN0IGhvb2tzLlxuICpcbiAqIFJvdXRlczpcbiAqIC0gUE9TVCAvYXBpL2luZ2VzdC9sb2dzICAgICDigJQgQmF0Y2hlZCBsb2cgZW50cmllcyBmcm9tIGEgZm9sbG93ZXJcbiAqIC0gUE9TVCAvYXBpL2luZ2VzdC9tZXRyaWNzICDigJQgTWV0cmljIHNuYXBzaG90cyBmcm9tIGEgZm9sbG93ZXJcbiAqIC0gUE9TVCAvYXBpL2luZ2VzdC9zZXNzaW9uICDigJQgU2Vzc2lvbiBsaWZlY3ljbGUgZXZlbnRzIChzdGFydGVkL3N0b3BwZWQvaGVhcnRiZWF0KVxuICogLSBHRVQgIC9hcGkvc2Vzc2lvbnMgICAgICAgIOKAlCBBY3RpdmUgc2Vzc2lvbiBsaXN0IGZvciB0aGUgVUlcbiAqXG4gKiBAc2luY2UgdjIuMS4wIOKAlCBJc3N1ZSAjMTcwMFxuICovXG5cbmltcG9ydCBleHByZXNzLCB7IFJvdXRlciB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0LCBSZXNwb25zZSB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHR5cGUgeyBVbmlmaWVkTG9nRW50cnkgfSBmcm9tICcuLi8uLi9sb2dnaW5nL3R5cGVzLmpzJztcbmltcG9ydCB0eXBlIHsgTWV0cmljU25hcHNob3QgfSBmcm9tICcuLi8uLi9tZXRyaWNzL3R5cGVzLmpzJztcbmltcG9ydCB7IFNsaWRpbmdXaW5kb3dSYXRlTGltaXRlciB9IGZyb20gJy4uLy4uL3V0aWxzL1NsaWRpbmdXaW5kb3dSYXRlTGltaXRlci5qcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IFNlc3Npb25OYW1lUG9vbCB9IGZyb20gJy4vU2Vzc2lvbk5hbWVzLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbi8qKiBNYXhpbXVtIHBheWxvYWQgc2l6ZSBmb3IgaW5nZXN0aW9uIHJlcXVlc3RzICovXG5jb25zdCBNQVhfUEFZTE9BRF9TSVpFID0gJzFtYic7XG5cbi8qKiBSYXRlIGxpbWl0OiBtYXggcmVxdWVzdHMgcGVyIHdpbmRvdyBwZXIgc291cmNlICovXG5jb25zdCBSQVRFX0xJTUlUX01BWCA9IDEwMDA7XG5jb25zdCBSQVRFX0xJTUlUX1dJTkRPV19NUyA9IDYwXzAwMDtcblxuLyoqIEhvdyBvZnRlbiB0byBjaGVjayBmb3Igc3RhbGUgc2Vzc2lvbnMgKG1zKSAqL1xuY29uc3QgUkVBUEVSX0lOVEVSVkFMX01TID0gNV8wMDA7XG5cbi8qKiBIb3cgbG9uZyBzaW5jZSBsYXN0IGhlYXJ0YmVhdCBiZWZvcmUgYSBzZXNzaW9uIGlzIGNvbnNpZGVyZWQgZGVhZCAobXMpICovXG5jb25zdCBTRVNTSU9OX1NUQUxFX01TID0gMTVfMDAwO1xuXG4vKipcbiAqIFRyYWNrZWQgc2Vzc2lvbiBpbmZvcm1hdGlvbi5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZXNzaW9uSW5mbyB7XG4gIHNlc3Npb25JZDogc3RyaW5nO1xuICAvKiogRnJpZW5kbHkgcHVwcGV0IG5hbWUgKGUuZy4sIFwiS2VybWl0XCIsIFwiUHVuY2hcIikgKi9cbiAgZGlzcGxheU5hbWU6IHN0cmluZztcbiAgLyoqIENhbm9uaWNhbCBoZXggY29sb3IgZm9yIHRoaXMgcHVwcGV0IGNoYXJhY3RlciAqL1xuICBjb2xvcjogc3RyaW5nO1xuICBwaWQ6IG51bWJlcjtcbiAgc3RhcnRlZEF0OiBzdHJpbmc7XG4gIGxhc3RIZWFydGJlYXQ6IHN0cmluZztcbiAgc3RhdHVzOiAnYWN0aXZlJyB8ICdlbmRlZCc7XG4gIGlzTGVhZGVyOiBib29sZWFuO1xufVxuXG4vKipcbiAqIFBheWxvYWQgZm9yIFBPU1QgL2FwaS9pbmdlc3QvbG9nc1xuICovXG5leHBvcnQgaW50ZXJmYWNlIEluZ2VzdExvZ1BheWxvYWQge1xuICBzZXNzaW9uSWQ6IHN0cmluZztcbiAgZW50cmllczogVW5pZmllZExvZ0VudHJ5W107XG59XG5cbi8qKlxuICogUGF5bG9hZCBmb3IgUE9TVCAvYXBpL2luZ2VzdC9tZXRyaWNzXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSW5nZXN0TWV0cmljc1BheWxvYWQge1xuICBzZXNzaW9uSWQ6IHN0cmluZztcbiAgc25hcHNob3Q6IE1ldHJpY1NuYXBzaG90O1xufVxuXG4vKipcbiAqIFBheWxvYWQgZm9yIFBPU1QgL2FwaS9pbmdlc3Qvc2Vzc2lvblxuICovXG5leHBvcnQgaW50ZXJmYWNlIFNlc3Npb25FdmVudFBheWxvYWQge1xuICBzZXNzaW9uSWQ6IHN0cmluZztcbiAgZXZlbnQ6ICdzdGFydGVkJyB8ICdzdG9wcGVkJyB8ICdoZWFydGJlYXQnO1xuICBwaWQ6IG51bWJlcjtcbiAgc3RhcnRlZEF0OiBzdHJpbmc7XG59XG5cbi8qKlxuICogQ2FsbGJhY2tzIHByb3ZpZGVkIGJ5IHRoZSB1bmlmaWVkIGNvbnNvbGUgb3JjaGVzdHJhdG9yIGZvciBicm9hZGNhc3RpbmdcbiAqIGluZ2VzdGVkIGV2ZW50cyB0aHJvdWdoIHRoZSBleGlzdGluZyBTU0UgaW5mcmFzdHJ1Y3R1cmUuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSW5nZXN0QnJvYWRjYXN0cyB7XG4gIGxvZ0Jyb2FkY2FzdDogKGVudHJ5OiBVbmlmaWVkTG9nRW50cnkpID0+IHZvaWQ7XG4gIG1ldHJpY3NPblNuYXBzaG90PzogKHNuYXBzaG90OiBNZXRyaWNTbmFwc2hvdCkgPT4gdm9pZDtcbiAgc2Vzc2lvbkJyb2FkY2FzdD86IChldmVudDogU2Vzc2lvbkluZm8pID0+IHZvaWQ7XG59XG5cbi8qKlxuICogUmVzdWx0IG9mIGNyZWF0aW5nIGluZ2VzdCByb3V0ZXMuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSW5nZXN0Um91dGVzUmVzdWx0IHtcbiAgcm91dGVyOiBSb3V0ZXI7XG4gIC8qKiBHZXQgYWxsIHRyYWNrZWQgc2Vzc2lvbnMgKi9cbiAgZ2V0U2Vzc2lvbnM6ICgpID0+IFNlc3Npb25JbmZvW107XG4gIC8qKiBSZWdpc3RlciB0aGUgbGVhZGVyIGFzIGEgc2Vzc2lvbiAqL1xuICByZWdpc3RlckxlYWRlclNlc3Npb246IChzZXNzaW9uSWQ6IHN0cmluZywgcGlkOiBudW1iZXIpID0+IHZvaWQ7XG59XG5cbi8qKiBOb3JtYWxpemUgYSBzdHJpbmcgdmlhIFVuaWNvZGVWYWxpZGF0b3IgKERNQ1AtU0VDLTAwNCkgKi9cbmZ1bmN0aW9uIG5vcm1hbGl6ZUlucHV0KHM6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBVbmljb2RlVmFsaWRhdG9yLm5vcm1hbGl6ZShzKS5ub3JtYWxpemVkQ29udGVudDtcbn1cblxuLyoqXG4gKiBDcmVhdGUgdGhlIGluZ2VzdGlvbiByb3V0ZXMgYW5kIHNlc3Npb24gcmVnaXN0cnkuXG4gKlxuICogQHBhcmFtIGJyb2FkY2FzdHMgLSBDYWxsYmFja3MgdG8gZm9yd2FyZCBpbmdlc3RlZCBldmVudHMgdG8gU1NFIGNsaWVudHNcbiAqIEByZXR1cm5zIFJvdXRlciBhbmQgc2Vzc2lvbiBtYW5hZ2VtZW50IGZ1bmN0aW9uc1xuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlSW5nZXN0Um91dGVzKGJyb2FkY2FzdHM6IEluZ2VzdEJyb2FkY2FzdHMpOiBJbmdlc3RSb3V0ZXNSZXN1bHQge1xuICBjb25zdCByb3V0ZXIgPSBSb3V0ZXIoKTtcbiAgY29uc3Qgc2Vzc2lvbnMgPSBuZXcgTWFwPHN0cmluZywgU2Vzc2lvbkluZm8+KCk7XG4gIGNvbnN0IG5hbWVQb29sID0gbmV3IFNlc3Npb25OYW1lUG9vbCgpO1xuICBjb25zdCByYXRlTGltaXRlciA9IG5ldyBTbGlkaW5nV2luZG93UmF0ZUxpbWl0ZXIoUkFURV9MSU1JVF9NQVgsIFJBVEVfTElNSVRfV0lORE9XX01TKTtcblxuICAvLyBKU09OIGJvZHkgcGFyc2luZyB3aXRoIHNpemUgbGltaXRcbiAgcm91dGVyLnVzZShleHByZXNzLmpzb24oeyBsaW1pdDogTUFYX1BBWUxPQURfU0laRSB9KSk7XG5cbiAgLyoqXG4gICAqIFBPU1QgL2FwaS9pbmdlc3QvbG9ncyDigJQgUmVjZWl2ZSBiYXRjaGVkIGxvZyBlbnRyaWVzIGZyb20gYSBmb2xsb3dlci5cbiAgICovXG4gIHJvdXRlci5wb3N0KCcvYXBpL2luZ2VzdC9sb2dzJywgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIGlmICghcmF0ZUxpbWl0ZXIudHJ5QWNxdWlyZSgpKSB7XG4gICAgICByZXMuc3RhdHVzKDQyOSkuanNvbih7IGVycm9yOiAnUmF0ZSBsaW1pdCBleGNlZWRlZCcgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgcGF5bG9hZCA9IHJlcS5ib2R5IGFzIEluZ2VzdExvZ1BheWxvYWQ7XG4gICAgaWYgKCFwYXlsb2FkPy5zZXNzaW9uSWQgfHwgIUFycmF5LmlzQXJyYXkocGF5bG9hZC5lbnRyaWVzKSkge1xuICAgICAgY29uc3QgcmVjZWl2ZWQgPSBwYXlsb2FkID8gT2JqZWN0LmtleXMocGF5bG9hZCkgOiBbXTtcbiAgICAgIGxvZ2dlci53YXJuKCdbSW5nZXN0Um91dGVzXSBJbnZhbGlkIGxvZyBwYXlsb2FkJywgeyByZWNlaXZlZCwgaGFzU2Vzc2lvbklkOiAhIXBheWxvYWQ/LnNlc3Npb25JZCwgaGFzRW50cmllczogQXJyYXkuaXNBcnJheShwYXlsb2FkPy5lbnRyaWVzKSB9KTtcbiAgICAgIHJlcy5zdGF0dXMoNDAwKS5qc29uKHsgZXJyb3I6ICdJbnZhbGlkIHBheWxvYWQnLCByZXF1aXJlZDogWydzZXNzaW9uSWQnLCAnZW50cmllcyddLCByZWNlaXZlZCB9KTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgcGF5bG9hZC5zZXNzaW9uSWQgPSBub3JtYWxpemVJbnB1dChwYXlsb2FkLnNlc3Npb25JZCk7XG5cbiAgICBsZXQgY291bnQgPSAwO1xuICAgIGxldCBza2lwcGVkID0gMDtcbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIHBheWxvYWQuZW50cmllcykge1xuICAgICAgaWYgKCFlbnRyeSB8fCB0eXBlb2YgZW50cnkubWVzc2FnZSAhPT0gJ3N0cmluZycpIHsgc2tpcHBlZCsrOyBjb250aW51ZTsgfVxuICAgICAgY29uc3Qgc3RhbXBlZDogVW5pZmllZExvZ0VudHJ5ID0ge1xuICAgICAgICAuLi5lbnRyeSxcbiAgICAgICAgZGF0YTogeyAuLi5lbnRyeS5kYXRhLCBfc2Vzc2lvbklkOiBwYXlsb2FkLnNlc3Npb25JZCB9LFxuICAgICAgfTtcbiAgICAgIGJyb2FkY2FzdHMubG9nQnJvYWRjYXN0KHN0YW1wZWQpO1xuICAgICAgY291bnQrKztcbiAgICB9XG5cbiAgICAvLyBVcGRhdGUgc2Vzc2lvbiBoZWFydGJlYXRcbiAgICBjb25zdCBzZXNzaW9uID0gc2Vzc2lvbnMuZ2V0KHBheWxvYWQuc2Vzc2lvbklkKTtcbiAgICBpZiAoc2Vzc2lvbikge1xuICAgICAgc2Vzc2lvbi5sYXN0SGVhcnRiZWF0ID0gbmV3IERhdGUoKS50b0lTT1N0cmluZygpO1xuICAgIH1cblxuICAgIGlmIChza2lwcGVkID4gMCkge1xuICAgICAgbG9nZ2VyLmRlYnVnKGBbSW5nZXN0Um91dGVzXSBMb2cgaW5nZXN0IGZyb20gJHtzZXNzaW9uPy5kaXNwbGF5TmFtZSA/PyBwYXlsb2FkLnNlc3Npb25JZH06IGFjY2VwdGVkPSR7Y291bnR9LCBza2lwcGVkPSR7c2tpcHBlZH1gKTtcbiAgICB9XG5cbiAgICByZXMuc3RhdHVzKDIwMCkuanNvbih7IGFjY2VwdGVkOiBjb3VudCwgc2tpcHBlZCB9KTtcbiAgfSk7XG5cbiAgLyoqXG4gICAqIFBPU1QgL2FwaS9pbmdlc3QvbWV0cmljcyDigJQgUmVjZWl2ZSBtZXRyaWMgc25hcHNob3RzIGZyb20gYSBmb2xsb3dlci5cbiAgICovXG4gIHJvdXRlci5wb3N0KCcvYXBpL2luZ2VzdC9tZXRyaWNzJywgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIGlmICghcmF0ZUxpbWl0ZXIudHJ5QWNxdWlyZSgpKSB7XG4gICAgICByZXMuc3RhdHVzKDQyOSkuanNvbih7IGVycm9yOiAnUmF0ZSBsaW1pdCBleGNlZWRlZCcgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgY29uc3QgcGF5bG9hZCA9IHJlcS5ib2R5IGFzIEluZ2VzdE1ldHJpY3NQYXlsb2FkO1xuICAgIGlmICghcGF5bG9hZD8uc2Vzc2lvbklkIHx8ICFwYXlsb2FkLnNuYXBzaG90KSB7XG4gICAgICBjb25zdCByZWNlaXZlZCA9IHBheWxvYWQgPyBPYmplY3Qua2V5cyhwYXlsb2FkKSA6IFtdO1xuICAgICAgbG9nZ2VyLndhcm4oJ1tJbmdlc3RSb3V0ZXNdIEludmFsaWQgbWV0cmljcyBwYXlsb2FkJywgeyByZWNlaXZlZCB9KTtcbiAgICAgIHJlcy5zdGF0dXMoNDAwKS5qc29uKHsgZXJyb3I6ICdJbnZhbGlkIHBheWxvYWQnLCByZXF1aXJlZDogWydzZXNzaW9uSWQnLCAnc25hcHNob3QnXSwgcmVjZWl2ZWQgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHBheWxvYWQuc2Vzc2lvbklkID0gbm9ybWFsaXplSW5wdXQocGF5bG9hZC5zZXNzaW9uSWQpO1xuXG4gICAgaWYgKGJyb2FkY2FzdHMubWV0cmljc09uU25hcHNob3QpIHtcbiAgICAgIGJyb2FkY2FzdHMubWV0cmljc09uU25hcHNob3QocGF5bG9hZC5zbmFwc2hvdCk7XG4gICAgfVxuXG4gICAgY29uc3Qgc2Vzc2lvbiA9IHNlc3Npb25zLmdldChwYXlsb2FkLnNlc3Npb25JZCk7XG4gICAgbG9nZ2VyLmRlYnVnKGBbSW5nZXN0Um91dGVzXSBNZXRyaWNzIGluZ2VzdGVkIGZyb20gJHtzZXNzaW9uPy5kaXNwbGF5TmFtZSA/PyBwYXlsb2FkLnNlc3Npb25JZH1gKTtcbiAgICByZXMuc3RhdHVzKDIwMCkuanNvbih7IGFjY2VwdGVkOiB0cnVlIH0pO1xuICB9KTtcblxuICAvKipcbiAgICogUE9TVCAvYXBpL2luZ2VzdC9zZXNzaW9uIOKAlCBTZXNzaW9uIGxpZmVjeWNsZSBldmVudHMuXG4gICAqL1xuICByb3V0ZXIucG9zdCgnL2FwaS9pbmdlc3Qvc2Vzc2lvbicsIChyZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UpID0+IHtcbiAgICBjb25zdCBwYXlsb2FkID0gcmVxLmJvZHkgYXMgU2Vzc2lvbkV2ZW50UGF5bG9hZDtcbiAgICBpZiAoIXBheWxvYWQ/LnNlc3Npb25JZCB8fCAhcGF5bG9hZC5ldmVudCkge1xuICAgICAgY29uc3QgcmVjZWl2ZWQgPSBwYXlsb2FkID8gT2JqZWN0LmtleXMocGF5bG9hZCkgOiBbXTtcbiAgICAgIGxvZ2dlci53YXJuKCdbSW5nZXN0Um91dGVzXSBJbnZhbGlkIHNlc3Npb24gZXZlbnQgcGF5bG9hZCcsIHsgcmVjZWl2ZWQgfSk7XG4gICAgICByZXMuc3RhdHVzKDQwMCkuanNvbih7IGVycm9yOiAnSW52YWxpZCBwYXlsb2FkJywgcmVxdWlyZWQ6IFsnc2Vzc2lvbklkJywgJ2V2ZW50J10sIHJlY2VpdmVkIH0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBwYXlsb2FkLnNlc3Npb25JZCA9IG5vcm1hbGl6ZUlucHV0KHBheWxvYWQuc2Vzc2lvbklkKTtcblxuICAgIGNvbnN0IG5vdyA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcblxuICAgIHN3aXRjaCAocGF5bG9hZC5ldmVudCkge1xuICAgICAgY2FzZSAnc3RhcnRlZCc6IHtcbiAgICAgICAgY29uc3QgZGlzcGxheU5hbWUgPSBuYW1lUG9vbC5hc3NpZ24ocGF5bG9hZC5zZXNzaW9uSWQpO1xuICAgICAgICBjb25zdCBjb2xvciA9IG5hbWVQb29sLmdldENvbG9yKHBheWxvYWQuc2Vzc2lvbklkKSA/PyAnIzNiODJmNic7XG4gICAgICAgIGNvbnN0IGluZm86IFNlc3Npb25JbmZvID0ge1xuICAgICAgICAgIHNlc3Npb25JZDogcGF5bG9hZC5zZXNzaW9uSWQsXG4gICAgICAgICAgZGlzcGxheU5hbWUsXG4gICAgICAgICAgY29sb3IsXG4gICAgICAgICAgcGlkOiBwYXlsb2FkLnBpZCxcbiAgICAgICAgICBzdGFydGVkQXQ6IHBheWxvYWQuc3RhcnRlZEF0IHx8IG5vdyxcbiAgICAgICAgICBsYXN0SGVhcnRiZWF0OiBub3csXG4gICAgICAgICAgc3RhdHVzOiAnYWN0aXZlJyxcbiAgICAgICAgICBpc0xlYWRlcjogZmFsc2UsXG4gICAgICAgIH07XG4gICAgICAgIHNlc3Npb25zLnNldChwYXlsb2FkLnNlc3Npb25JZCwgaW5mbyk7XG4gICAgICAgIGxvZ2dlci5pbmZvKCdbSW5nZXN0Um91dGVzXSBTZXNzaW9uIHJlZ2lzdGVyZWQnLCB7XG4gICAgICAgICAgZGlzcGxheU5hbWUsIHNlc3Npb25JZDogcGF5bG9hZC5zZXNzaW9uSWQsIHBpZDogcGF5bG9hZC5waWQsIGNvbG9yLFxuICAgICAgICAgIGFjdGl2ZVNlc3Npb25zOiBBcnJheS5mcm9tKHNlc3Npb25zLnZhbHVlcygpKS5maWx0ZXIocyA9PiBzLnN0YXR1cyA9PT0gJ2FjdGl2ZScpLmxlbmd0aCxcbiAgICAgICAgfSk7XG4gICAgICAgIGJyb2FkY2FzdHMuc2Vzc2lvbkJyb2FkY2FzdD8uKGluZm8pO1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICAgIGNhc2UgJ3N0b3BwZWQnOiB7XG4gICAgICAgIGNvbnN0IGV4aXN0aW5nID0gc2Vzc2lvbnMuZ2V0KHBheWxvYWQuc2Vzc2lvbklkKTtcbiAgICAgICAgaWYgKGV4aXN0aW5nKSB7XG4gICAgICAgICAgZXhpc3Rpbmcuc3RhdHVzID0gJ2VuZGVkJztcbiAgICAgICAgICBleGlzdGluZy5sYXN0SGVhcnRiZWF0ID0gbm93O1xuICAgICAgICAgIG5hbWVQb29sLnJlbGVhc2UocGF5bG9hZC5zZXNzaW9uSWQpO1xuICAgICAgICAgIGxvZ2dlci5pbmZvKCdbSW5nZXN0Um91dGVzXSBTZXNzaW9uIHN0b3BwZWQnLCB7XG4gICAgICAgICAgICBkaXNwbGF5TmFtZTogZXhpc3RpbmcuZGlzcGxheU5hbWUsIHNlc3Npb25JZDogcGF5bG9hZC5zZXNzaW9uSWQsIHBpZDogZXhpc3RpbmcucGlkLFxuICAgICAgICAgICAgYWN0aXZlU2Vzc2lvbnM6IEFycmF5LmZyb20oc2Vzc2lvbnMudmFsdWVzKCkpLmZpbHRlcihzID0+IHMuc3RhdHVzID09PSAnYWN0aXZlJykubGVuZ3RoIC0gMSxcbiAgICAgICAgICB9KTtcbiAgICAgICAgICBicm9hZGNhc3RzLnNlc3Npb25Ccm9hZGNhc3Q/LihleGlzdGluZyk7XG4gICAgICAgIH1cbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICBjYXNlICdoZWFydGJlYXQnOiB7XG4gICAgICAgIGNvbnN0IGV4aXN0aW5nID0gc2Vzc2lvbnMuZ2V0KHBheWxvYWQuc2Vzc2lvbklkKTtcbiAgICAgICAgaWYgKGV4aXN0aW5nKSB7XG4gICAgICAgICAgZXhpc3RpbmcubGFzdEhlYXJ0YmVhdCA9IG5vdztcbiAgICAgICAgfVxuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXMuc3RhdHVzKDIwMCkuanNvbih7IG9rOiB0cnVlIH0pO1xuICB9KTtcblxuICAvKipcbiAgICogR0VUIC9hcGkvc2Vzc2lvbnMg4oCUIExpc3QgYWxsIHRyYWNrZWQgc2Vzc2lvbnMuXG4gICAqL1xuICByb3V0ZXIuZ2V0KCcvYXBpL3Nlc3Npb25zJywgKF9yZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UpID0+IHtcbiAgICBjb25zdCBhbGxTZXNzaW9ucyA9IEFycmF5LmZyb20oc2Vzc2lvbnMudmFsdWVzKCkpO1xuICAgIHJlcy5qc29uKHsgc2Vzc2lvbnM6IGFsbFNlc3Npb25zIH0pO1xuICB9KTtcblxuICAvKipcbiAgICogUE9TVCAvYXBpL3Nlc3Npb25zLzpzZXNzaW9uSWQva2lsbCDigJQgVGVybWluYXRlIGEgc2Vzc2lvbidzIHNlcnZlciBwcm9jZXNzLlxuICAgKi9cbiAgcm91dGVyLnBvc3QoJy9hcGkvc2Vzc2lvbnMvOnNlc3Npb25JZC9raWxsJywgKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSkgPT4ge1xuICAgIGNvbnN0IHNlc3Npb25JZCA9IHJlcS5wYXJhbXNbJ3Nlc3Npb25JZCddIGFzIHN0cmluZztcbiAgICBjb25zdCBzZXNzaW9uID0gc2Vzc2lvbnMuZ2V0KHNlc3Npb25JZCk7XG5cbiAgICBpZiAoIXNlc3Npb24pIHtcbiAgICAgIGxvZ2dlci53YXJuKCdbSW5nZXN0Um91dGVzXSBLaWxsIHJlcXVlc3RlZCBmb3IgdW5rbm93biBzZXNzaW9uJywgeyBzZXNzaW9uSWQgfSk7XG4gICAgICByZXMuc3RhdHVzKDQwNCkuanNvbih7IGVycm9yOiAnU2Vzc2lvbiBub3QgZm91bmQnLCBzZXNzaW9uSWQgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKCFzZXNzaW9uLnBpZCkge1xuICAgICAgcmVzLnN0YXR1cyg0MDApLmpzb24oeyBlcnJvcjogJ05vIFBJRCBmb3Igc2Vzc2lvbicsIHNlc3Npb25JZCwgZGlzcGxheU5hbWU6IHNlc3Npb24uZGlzcGxheU5hbWUgfSk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdHJ5IHtcbiAgICAgIHByb2Nlc3Mua2lsbChzZXNzaW9uLnBpZCwgJ1NJR1RFUk0nKTtcbiAgICAgIHNlc3Npb24uc3RhdHVzID0gJ2VuZGVkJztcbiAgICAgIG5hbWVQb29sLnJlbGVhc2Uoc2Vzc2lvbklkKTtcbiAgICAgIGxvZ2dlci5pbmZvKCdbSW5nZXN0Um91dGVzXSBTZXNzaW9uIGtpbGxlZCcsIHtcbiAgICAgICAgZGlzcGxheU5hbWU6IHNlc3Npb24uZGlzcGxheU5hbWUsIHNlc3Npb25JZCwgcGlkOiBzZXNzaW9uLnBpZCxcbiAgICAgICAgYWN0aXZlU2Vzc2lvbnM6IEFycmF5LmZyb20oc2Vzc2lvbnMudmFsdWVzKCkpLmZpbHRlcihzID0+IHMuc3RhdHVzID09PSAnYWN0aXZlJykubGVuZ3RoIC0gMSxcbiAgICAgIH0pO1xuICAgICAgcmVzLmpzb24oeyBvazogdHJ1ZSwga2lsbGVkOiBzZXNzaW9uLmRpc3BsYXlOYW1lLCBwaWQ6IHNlc3Npb24ucGlkIH0pO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgY29uc3QgbWVzc2FnZSA9IChlcnIgYXMgRXJyb3IpLm1lc3NhZ2U7XG4gICAgICBsb2dnZXIuZXJyb3IoJ1tJbmdlc3RSb3V0ZXNdIEZhaWxlZCB0byBraWxsIHNlc3Npb24nLCB7XG4gICAgICAgIGRpc3BsYXlOYW1lOiBzZXNzaW9uLmRpc3BsYXlOYW1lLCBzZXNzaW9uSWQsIHBpZDogc2Vzc2lvbi5waWQsIGVycm9yOiBtZXNzYWdlLFxuICAgICAgfSk7XG4gICAgICByZXMuc3RhdHVzKDUwMCkuanNvbih7IGVycm9yOiAnRmFpbGVkIHRvIGtpbGwgc2Vzc2lvbicsIHNlc3Npb25JZCwgZGlzcGxheU5hbWU6IHNlc3Npb24uZGlzcGxheU5hbWUsIHBpZDogc2Vzc2lvbi5waWQsIGRldGFpbDogbWVzc2FnZSB9KTtcbiAgICB9XG4gIH0pO1xuXG4gIC8vIFJlYXBlcjogcGVyaW9kaWNhbGx5IGNoZWNrIGZvciBzdGFsZSBzZXNzaW9ucyB3aG9zZSBoZWFydGJlYXQgaGFzIGV4cGlyZWRcbiAgY29uc3QgcmVhcGVySW50ZXJ2YWwgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgICBmb3IgKGNvbnN0IFtpZCwgc2Vzc2lvbl0gb2Ygc2Vzc2lvbnMpIHtcbiAgICAgIGlmIChzZXNzaW9uLnN0YXR1cyAhPT0gJ2FjdGl2ZScpIGNvbnRpbnVlO1xuICAgICAgaWYgKHNlc3Npb24uaXNMZWFkZXIpIGNvbnRpbnVlOyAvLyBsZWFkZXIgbWFuYWdlcyBpdHNlbGZcbiAgICAgIGNvbnN0IGFnZSA9IG5vdyAtIG5ldyBEYXRlKHNlc3Npb24ubGFzdEhlYXJ0YmVhdCkuZ2V0VGltZSgpO1xuICAgICAgaWYgKGFnZSA+IFNFU1NJT05fU1RBTEVfTVMpIHtcbiAgICAgICAgc2Vzc2lvbi5zdGF0dXMgPSAnZW5kZWQnO1xuICAgICAgICBuYW1lUG9vbC5yZWxlYXNlKGlkKTtcbiAgICAgICAgbG9nZ2VyLmluZm8oJ1tJbmdlc3RSb3V0ZXNdIFJlYXBlZCBzdGFsZSBzZXNzaW9uJywge1xuICAgICAgICAgIGRpc3BsYXlOYW1lOiBzZXNzaW9uLmRpc3BsYXlOYW1lLCBzZXNzaW9uSWQ6IGlkLCBwaWQ6IHNlc3Npb24ucGlkLFxuICAgICAgICAgIGxhc3RIZWFydGJlYXRBZ286IGAke01hdGgucm91bmQoYWdlIC8gMTAwMCl9c2AsXG4gICAgICAgICAgYWN0aXZlU2Vzc2lvbnM6IEFycmF5LmZyb20oc2Vzc2lvbnMudmFsdWVzKCkpLmZpbHRlcihzID0+IHMuc3RhdHVzID09PSAnYWN0aXZlJykubGVuZ3RoIC0gMSxcbiAgICAgICAgfSk7XG4gICAgICAgIGJyb2FkY2FzdHMuc2Vzc2lvbkJyb2FkY2FzdD8uKHNlc3Npb24pO1xuICAgICAgfVxuICAgIH1cbiAgfSwgUkVBUEVSX0lOVEVSVkFMX01TKTtcbiAgcmVhcGVySW50ZXJ2YWwudW5yZWYoKTtcblxuICBmdW5jdGlvbiBnZXRTZXNzaW9ucygpOiBTZXNzaW9uSW5mb1tdIHtcbiAgICByZXR1cm4gQXJyYXkuZnJvbShzZXNzaW9ucy52YWx1ZXMoKSkuZmlsdGVyKHMgPT4gcy5zdGF0dXMgPT09ICdhY3RpdmUnKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHJlZ2lzdGVyTGVhZGVyU2Vzc2lvbihzZXNzaW9uSWQ6IHN0cmluZywgcGlkOiBudW1iZXIpOiB2b2lkIHtcbiAgICBjb25zdCBkaXNwbGF5TmFtZSA9IG5hbWVQb29sLmFzc2lnbihzZXNzaW9uSWQsIHRydWUpO1xuICAgIGNvbnN0IGNvbG9yID0gbmFtZVBvb2wuZ2V0Q29sb3Ioc2Vzc2lvbklkKSA/PyAnIzNiODJmNic7XG4gICAgc2Vzc2lvbnMuc2V0KHNlc3Npb25JZCwge1xuICAgICAgc2Vzc2lvbklkLFxuICAgICAgZGlzcGxheU5hbWUsXG4gICAgICBjb2xvcixcbiAgICAgIHBpZCxcbiAgICAgIHN0YXJ0ZWRBdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgbGFzdEhlYXJ0YmVhdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgc3RhdHVzOiAnYWN0aXZlJyxcbiAgICAgIGlzTGVhZGVyOiB0cnVlLFxuICAgIH0pO1xuICAgIGxvZ2dlci5pbmZvKCdbSW5nZXN0Um91dGVzXSBMZWFkZXIgc2Vzc2lvbiByZWdpc3RlcmVkJywgeyBkaXNwbGF5TmFtZSwgc2Vzc2lvbklkLCBwaWQsIGNvbG9yIH0pO1xuICB9XG5cbiAgcmV0dXJuIHsgcm91dGVyLCBnZXRTZXNzaW9ucywgcmVnaXN0ZXJMZWFkZXJTZXNzaW9uIH07XG59XG4iXX0=
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leader election for the unified web console.
|
|
3
|
+
*
|
|
4
|
+
* When multiple MCP server instances run concurrently, only one should host
|
|
5
|
+
* the web console (the "leader"). Others become "followers" that forward
|
|
6
|
+
* events to the leader. This module handles:
|
|
7
|
+
*
|
|
8
|
+
* 1. Reading/writing a leader lock file at ~/.dollhouse/run/console-leader.lock
|
|
9
|
+
* 2. Atomic claim via temp+rename to prevent TOCTOU races
|
|
10
|
+
* 3. PID-based stale detection (signal-0 liveness check)
|
|
11
|
+
* 4. Heartbeat updates (10s interval) so followers can detect hung leaders
|
|
12
|
+
* 5. Cleanup on process exit
|
|
13
|
+
*
|
|
14
|
+
* The port 3939 binding is the ultimate tiebreaker: even if two processes
|
|
15
|
+
* both write the lock file, only one can bind the port.
|
|
16
|
+
*
|
|
17
|
+
* @since v2.1.0 — Issue #1700
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Information stored in the leader lock file.
|
|
21
|
+
*/
|
|
22
|
+
export interface ConsoleLeaderInfo {
|
|
23
|
+
version: number;
|
|
24
|
+
pid: number;
|
|
25
|
+
port: number;
|
|
26
|
+
sessionId: string;
|
|
27
|
+
startedAt: string;
|
|
28
|
+
heartbeat: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Result of a leader election attempt.
|
|
32
|
+
*/
|
|
33
|
+
export interface ElectionResult {
|
|
34
|
+
role: 'leader' | 'follower';
|
|
35
|
+
/** Leader info — for followers, this is the existing leader's info */
|
|
36
|
+
leaderInfo: ConsoleLeaderInfo;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check whether a process with the given PID is alive.
|
|
40
|
+
* Uses signal 0 which checks existence without sending a signal.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Read and parse the leader lock file.
|
|
45
|
+
* Returns null if the file doesn't exist, is unreadable, or has invalid content.
|
|
46
|
+
*/
|
|
47
|
+
export declare function readLeaderLock(): Promise<ConsoleLeaderInfo | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Check if a leader lock is stale (dead process or expired heartbeat).
|
|
50
|
+
*/
|
|
51
|
+
export declare function isLockStale(info: ConsoleLeaderInfo): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Attempt to atomically claim leadership.
|
|
54
|
+
*
|
|
55
|
+
* Writes to a temp file then renames to the lock path. On POSIX systems
|
|
56
|
+
* rename is atomic, so only one writer wins. After renaming, re-reads the
|
|
57
|
+
* lock to verify our PID won.
|
|
58
|
+
*
|
|
59
|
+
* @returns true if this process successfully claimed leadership
|
|
60
|
+
*/
|
|
61
|
+
export declare function claimLeadership(info: ConsoleLeaderInfo): Promise<boolean>;
|
|
62
|
+
/**
|
|
63
|
+
* Delete the leader lock file (for cleanup or takeover).
|
|
64
|
+
*/
|
|
65
|
+
export declare function deleteLeaderLock(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Run the leader election protocol.
|
|
68
|
+
*
|
|
69
|
+
* 1. If no lock exists or lock is stale → claim leadership
|
|
70
|
+
* 2. If lock exists with a live, responsive leader → become follower
|
|
71
|
+
*
|
|
72
|
+
* @param sessionId - This process's unique session identifier
|
|
73
|
+
* @param port - The port this process would use as leader (typically 3939)
|
|
74
|
+
* @returns Election result with role and leader info
|
|
75
|
+
*/
|
|
76
|
+
export declare function electLeader(sessionId: string, port: number): Promise<ElectionResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Start the leader heartbeat loop.
|
|
79
|
+
* Updates the lock file every HEARTBEAT_INTERVAL_MS so followers know the leader is alive.
|
|
80
|
+
*
|
|
81
|
+
* @returns A stop function to clear the interval
|
|
82
|
+
*/
|
|
83
|
+
export declare function startHeartbeat(info: ConsoleLeaderInfo): () => void;
|
|
84
|
+
/**
|
|
85
|
+
* Register cleanup handlers to remove the leader lock on process exit.
|
|
86
|
+
* Should only be called by the leader.
|
|
87
|
+
*/
|
|
88
|
+
export declare function registerLeaderCleanup(): void;
|
|
89
|
+
//# sourceMappingURL=LeaderElection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LeaderElection.d.ts","sourceRoot":"","sources":["../../../src/web/console/LeaderElection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAuBH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,sEAAsE;IACtE,UAAU,EAAE,iBAAiB,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAWxE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAM5D;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAe/E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEtD;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAyD1F;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAgBlE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leader election for the unified web console.
|
|
3
|
+
*
|
|
4
|
+
* When multiple MCP server instances run concurrently, only one should host
|
|
5
|
+
* the web console (the "leader"). Others become "followers" that forward
|
|
6
|
+
* events to the leader. This module handles:
|
|
7
|
+
*
|
|
8
|
+
* 1. Reading/writing a leader lock file at ~/.dollhouse/run/console-leader.lock
|
|
9
|
+
* 2. Atomic claim via temp+rename to prevent TOCTOU races
|
|
10
|
+
* 3. PID-based stale detection (signal-0 liveness check)
|
|
11
|
+
* 4. Heartbeat updates (10s interval) so followers can detect hung leaders
|
|
12
|
+
* 5. Cleanup on process exit
|
|
13
|
+
*
|
|
14
|
+
* The port 3939 binding is the ultimate tiebreaker: even if two processes
|
|
15
|
+
* both write the lock file, only one can bind the port.
|
|
16
|
+
*
|
|
17
|
+
* @since v2.1.0 — Issue #1700
|
|
18
|
+
*/
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { mkdir, readFile, writeFile, rename, unlink } from 'node:fs/promises';
|
|
22
|
+
import { UnicodeValidator } from '../../security/validators/unicodeValidator.js';
|
|
23
|
+
import { logger } from '../../utils/logger.js';
|
|
24
|
+
/** Directory for runtime state files */
|
|
25
|
+
const RUN_DIR = join(homedir(), '.dollhouse', 'run');
|
|
26
|
+
/** Path to the leader lock file */
|
|
27
|
+
const LOCK_FILE = join(RUN_DIR, 'console-leader.lock');
|
|
28
|
+
/** How often the leader updates its heartbeat (ms) */
|
|
29
|
+
const HEARTBEAT_INTERVAL_MS = 10_000;
|
|
30
|
+
/** How long before a heartbeat is considered stale (ms) */
|
|
31
|
+
const HEARTBEAT_STALE_MS = 30_000;
|
|
32
|
+
/** Current lock file schema version */
|
|
33
|
+
const LOCK_VERSION = 1;
|
|
34
|
+
/**
|
|
35
|
+
* Check whether a process with the given PID is alive.
|
|
36
|
+
* Uses signal 0 which checks existence without sending a signal.
|
|
37
|
+
*/
|
|
38
|
+
export function isProcessAlive(pid) {
|
|
39
|
+
try {
|
|
40
|
+
process.kill(pid, 0);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Read and parse the leader lock file.
|
|
49
|
+
* Returns null if the file doesn't exist, is unreadable, or has invalid content.
|
|
50
|
+
*/
|
|
51
|
+
export async function readLeaderLock() {
|
|
52
|
+
try {
|
|
53
|
+
const content = await readFile(LOCK_FILE, 'utf-8');
|
|
54
|
+
const data = JSON.parse(content);
|
|
55
|
+
if (data.version !== LOCK_VERSION || !data.pid || !data.port || !data.sessionId) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return data;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check if a leader lock is stale (dead process or expired heartbeat).
|
|
66
|
+
*/
|
|
67
|
+
export function isLockStale(info) {
|
|
68
|
+
if (!isProcessAlive(info.pid)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const heartbeatAge = Date.now() - new Date(info.heartbeat).getTime();
|
|
72
|
+
return heartbeatAge > HEARTBEAT_STALE_MS;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Attempt to atomically claim leadership.
|
|
76
|
+
*
|
|
77
|
+
* Writes to a temp file then renames to the lock path. On POSIX systems
|
|
78
|
+
* rename is atomic, so only one writer wins. After renaming, re-reads the
|
|
79
|
+
* lock to verify our PID won.
|
|
80
|
+
*
|
|
81
|
+
* @returns true if this process successfully claimed leadership
|
|
82
|
+
*/
|
|
83
|
+
export async function claimLeadership(info) {
|
|
84
|
+
await mkdir(RUN_DIR, { recursive: true });
|
|
85
|
+
const tmpFile = join(RUN_DIR, `console-leader.lock.${process.pid}.tmp`);
|
|
86
|
+
try {
|
|
87
|
+
await writeFile(tmpFile, JSON.stringify(info, null, 2), 'utf-8');
|
|
88
|
+
await rename(tmpFile, LOCK_FILE);
|
|
89
|
+
// Verify we won the race
|
|
90
|
+
const written = await readLeaderLock();
|
|
91
|
+
return written !== null && written.pid === info.pid;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Clean up temp file on failure
|
|
95
|
+
try {
|
|
96
|
+
await unlink(tmpFile);
|
|
97
|
+
}
|
|
98
|
+
catch { /* ignore */ }
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Delete the leader lock file (for cleanup or takeover).
|
|
104
|
+
*/
|
|
105
|
+
export async function deleteLeaderLock() {
|
|
106
|
+
try {
|
|
107
|
+
await unlink(LOCK_FILE);
|
|
108
|
+
}
|
|
109
|
+
catch { /* already gone */ }
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Run the leader election protocol.
|
|
113
|
+
*
|
|
114
|
+
* 1. If no lock exists or lock is stale → claim leadership
|
|
115
|
+
* 2. If lock exists with a live, responsive leader → become follower
|
|
116
|
+
*
|
|
117
|
+
* @param sessionId - This process's unique session identifier
|
|
118
|
+
* @param port - The port this process would use as leader (typically 3939)
|
|
119
|
+
* @returns Election result with role and leader info
|
|
120
|
+
*/
|
|
121
|
+
export async function electLeader(sessionId, port) {
|
|
122
|
+
sessionId = UnicodeValidator.normalize(sessionId).normalizedContent;
|
|
123
|
+
const existingLock = await readLeaderLock();
|
|
124
|
+
if (existingLock && !isLockStale(existingLock)) {
|
|
125
|
+
logger.info('[LeaderElection] Existing leader found — becoming follower', {
|
|
126
|
+
leaderSession: existingLock.sessionId, leaderPid: existingLock.pid,
|
|
127
|
+
leaderPort: existingLock.port, mySession: sessionId, myPid: process.pid,
|
|
128
|
+
});
|
|
129
|
+
return { role: 'follower', leaderInfo: existingLock };
|
|
130
|
+
}
|
|
131
|
+
// No valid leader — try to claim
|
|
132
|
+
if (existingLock) {
|
|
133
|
+
const alive = isProcessAlive(existingLock.pid);
|
|
134
|
+
const heartbeatAge = Date.now() - new Date(existingLock.heartbeat).getTime();
|
|
135
|
+
logger.info('[LeaderElection] Stale leader lock — taking over', {
|
|
136
|
+
stalePid: existingLock.pid, alive, heartbeatAgeMs: heartbeatAge,
|
|
137
|
+
staleSession: existingLock.sessionId, mySession: sessionId,
|
|
138
|
+
});
|
|
139
|
+
await deleteLeaderLock();
|
|
140
|
+
}
|
|
141
|
+
const now = new Date().toISOString();
|
|
142
|
+
const myInfo = {
|
|
143
|
+
version: LOCK_VERSION,
|
|
144
|
+
pid: process.pid,
|
|
145
|
+
port,
|
|
146
|
+
sessionId,
|
|
147
|
+
startedAt: now,
|
|
148
|
+
heartbeat: now,
|
|
149
|
+
};
|
|
150
|
+
const claimed = await claimLeadership(myInfo);
|
|
151
|
+
if (claimed) {
|
|
152
|
+
logger.info('[LeaderElection] Claimed leadership', { sessionId, port, pid: process.pid });
|
|
153
|
+
return { role: 'leader', leaderInfo: myInfo };
|
|
154
|
+
}
|
|
155
|
+
// Another process won the race — re-read and become follower
|
|
156
|
+
const winner = await readLeaderLock();
|
|
157
|
+
if (winner) {
|
|
158
|
+
logger.info('[LeaderElection] Lost election — becoming follower', {
|
|
159
|
+
winnerPid: winner.pid, winnerSession: winner.sessionId, mySession: sessionId, myPid: process.pid,
|
|
160
|
+
});
|
|
161
|
+
return { role: 'follower', leaderInfo: winner };
|
|
162
|
+
}
|
|
163
|
+
// Extremely unlikely: lock disappeared between our claim and re-read. Retry once.
|
|
164
|
+
logger.warn('[LeaderElection] Lock vanished after failed claim. Retrying.');
|
|
165
|
+
const retryInfo = { ...myInfo, heartbeat: new Date().toISOString() };
|
|
166
|
+
const retryClaimed = await claimLeadership(retryInfo);
|
|
167
|
+
if (retryClaimed) {
|
|
168
|
+
return { role: 'leader', leaderInfo: retryInfo };
|
|
169
|
+
}
|
|
170
|
+
const actualLeader = await readLeaderLock();
|
|
171
|
+
return { role: 'follower', leaderInfo: actualLeader ?? retryInfo };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Start the leader heartbeat loop.
|
|
175
|
+
* Updates the lock file every HEARTBEAT_INTERVAL_MS so followers know the leader is alive.
|
|
176
|
+
*
|
|
177
|
+
* @returns A stop function to clear the interval
|
|
178
|
+
*/
|
|
179
|
+
export function startHeartbeat(info) {
|
|
180
|
+
const interval = setInterval(async () => {
|
|
181
|
+
try {
|
|
182
|
+
const updated = { ...info, heartbeat: new Date().toISOString() };
|
|
183
|
+
const tmpFile = join(RUN_DIR, `console-leader.lock.${process.pid}.tmp`);
|
|
184
|
+
await writeFile(tmpFile, JSON.stringify(updated, null, 2), 'utf-8');
|
|
185
|
+
await rename(tmpFile, LOCK_FILE);
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
logger.debug('[LeaderElection] Heartbeat write failed:', err);
|
|
189
|
+
}
|
|
190
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
191
|
+
// Don't let the heartbeat interval keep the process alive
|
|
192
|
+
interval.unref();
|
|
193
|
+
return () => clearInterval(interval);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Register cleanup handlers to remove the leader lock on process exit.
|
|
197
|
+
* Should only be called by the leader.
|
|
198
|
+
*/
|
|
199
|
+
export function registerLeaderCleanup() {
|
|
200
|
+
const cleanup = () => { deleteLeaderLock().catch(() => { }); };
|
|
201
|
+
process.once('exit', cleanup);
|
|
202
|
+
process.once('SIGTERM', cleanup);
|
|
203
|
+
process.once('SIGINT', cleanup);
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTGVhZGVyRWxlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvd2ViL2NvbnNvbGUvTGVhZGVyRWxlY3Rpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHO0FBRUgsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUNsQyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2pDLE9BQU8sRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDOUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sK0NBQStDLENBQUM7QUFDakYsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRS9DLHdDQUF3QztBQUN4QyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxFQUFFLEVBQUUsWUFBWSxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBRXJELG1DQUFtQztBQUNuQyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsT0FBTyxFQUFFLHFCQUFxQixDQUFDLENBQUM7QUFFdkQsc0RBQXNEO0FBQ3RELE1BQU0scUJBQXFCLEdBQUcsTUFBTSxDQUFDO0FBRXJDLDJEQUEyRDtBQUMzRCxNQUFNLGtCQUFrQixHQUFHLE1BQU0sQ0FBQztBQUVsQyx1Q0FBdUM7QUFDdkMsTUFBTSxZQUFZLEdBQUcsQ0FBQyxDQUFDO0FBdUJ2Qjs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLEdBQVc7SUFDeEMsSUFBSSxDQUFDO1FBQ0gsT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDckIsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0FBQ0gsQ0FB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QyxJQUFJLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQ2xDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIExlYWRlciBlbGVjdGlvbiBmb3IgdGhlIHVuaWZpZWQgd2ViIGNvbnNvbGUuXG4gKlxuICogV2hlbiBtdWx0aXBsZSBNQ1Agc2VydmVyIGluc3RhbmNlcyBydW4gY29uY3VycmVudGx5LCBvbmx5IG9uZSBzaG91bGQgaG9zdFxuICogdGhlIHdlYiBjb25zb2xlICh0aGUgXCJsZWFkZXJcIikuIE90aGVycyBiZWNvbWUgXCJmb2xsb3dlcnNcIiB0aGF0IGZvcndhcmRcbiAqIGV2ZW50cyB0byB0aGUgbGVhZGVyLiBUaGlzIG1vZHVsZSBoYW5kbGVzOlxuICpcbiAqIDEuIFJlYWRpbmcvd3JpdGluZyBhIGxlYWRlciBsb2NrIGZpbGUgYXQgfi8uZG9sbGhvdXNlL3J1bi9jb25zb2xlLWxlYWRlci5sb2NrXG4gKiAyLiBBdG9taWMgY2xhaW0gdmlhIHRlbXArcmVuYW1lIHRvIHByZXZlbnQgVE9DVE9VIHJhY2VzXG4gKiAzLiBQSUQtYmFzZWQgc3RhbGUgZGV0ZWN0aW9uIChzaWduYWwtMCBsaXZlbmVzcyBjaGVjaylcbiAqIDQuIEhlYXJ0YmVhdCB1cGRhdGVzICgxMHMgaW50ZXJ2YWwpIHNvIGZvbGxvd2VycyBjYW4gZGV0ZWN0IGh1bmcgbGVhZGVyc1xuICogNS4gQ2xlYW51cCBvbiBwcm9jZXNzIGV4aXRcbiAqXG4gKiBUaGUgcG9ydCAzOTM5IGJpbmRpbmcgaXMgdGhlIHVsdGltYXRlIHRpZWJyZWFrZXI6IGV2ZW4gaWYgdHdvIHByb2Nlc3Nlc1xuICogYm90aCB3cml0ZSB0aGUgbG9jayBmaWxlLCBvbmx5IG9uZSBjYW4gYmluZCB0aGUgcG9ydC5cbiAqXG4gKiBAc2luY2UgdjIuMS4wIOKAlCBJc3N1ZSAjMTcwMFxuICovXG5cbmltcG9ydCB7IGhvbWVkaXIgfSBmcm9tICdub2RlOm9zJztcbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgbWtkaXIsIHJlYWRGaWxlLCB3cml0ZUZpbGUsIHJlbmFtZSwgdW5saW5rIH0gZnJvbSAnbm9kZTpmcy9wcm9taXNlcyc7XG5pbXBvcnQgeyBVbmljb2RlVmFsaWRhdG9yIH0gZnJvbSAnLi4vLi4vc2VjdXJpdHkvdmFsaWRhdG9ycy91bmljb2RlVmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbi8qKiBEaXJlY3RvcnkgZm9yIHJ1bnRpbWUgc3RhdGUgZmlsZXMgKi9cbmNvbnN0IFJVTl9ESVIgPSBqb2luKGhvbWVkaXIoKSwgJy5kb2xsaG91c2UnLCAncnVuJyk7XG5cbi8qKiBQYXRoIHRvIHRoZSBsZWFkZXIgbG9jayBmaWxlICovXG5jb25zdCBMT0NLX0ZJTEUgPSBqb2luKFJVTl9ESVIsICdjb25zb2xlLWxlYWRlci5sb2NrJyk7XG5cbi8qKiBIb3cgb2Z0ZW4gdGhlIGxlYWRlciB1cGRhdGVzIGl0cyBoZWFydGJlYXQgKG1zKSAqL1xuY29uc3QgSEVBUlRCRUFUX0lOVEVSVkFMX01TID0gMTBfMDAwO1xuXG4vKiogSG93IGxvbmcgYmVmb3JlIGEgaGVhcnRiZWF0IGlzIGNvbnNpZGVyZWQgc3RhbGUgKG1zKSAqL1xuY29uc3QgSEVBUlRCRUFUX1NUQUxFX01TID0gMzBfMDAwO1xuXG4vKiogQ3VycmVudCBsb2NrIGZpbGUgc2NoZW1hIHZlcnNpb24gKi9cbmNvbnN0IExPQ0tfVkVSU0lPTiA9IDE7XG5cbi8qKlxuICogSW5mb3JtYXRpb24gc3RvcmVkIGluIHRoZSBsZWFkZXIgbG9jayBmaWxlLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIENvbnNvbGVMZWFkZXJJbmZvIHtcbiAgdmVyc2lvbjogbnVtYmVyO1xuICBwaWQ6IG51bWJlcjtcbiAgcG9ydDogbnVtYmVyO1xuICBzZXNzaW9uSWQ6IHN0cmluZztcbiAgc3RhcnRlZEF0OiBzdHJpbmc7XG4gIGhlYXJ0YmVhdDogc3RyaW5nO1xufVxuXG4vKipcbiAqIFJlc3VsdCBvZiBhIGxlYWRlciBlbGVjdGlvbiBhdHRlbXB0LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEVsZWN0aW9uUmVzdWx0IHtcbiAgcm9sZTogJ2xlYWRlcicgfCAnZm9sbG93ZXInO1xuICAvKiogTGVhZGVyIGluZm8g4oCUIGZvciBmb2xsb3dlcnMsIHRoaXMgaXMgdGhlIGV4aXN0aW5nIGxlYWRlcidzIGluZm8gKi9cbiAgbGVhZGVySW5mbzogQ29uc29sZUxlYWRlckluZm87XG59XG5cbi8qKlxuICogQ2hlY2sgd2hldGhlciBhIHByb2Nlc3Mgd2l0aCB0aGUgZ2l2ZW4gUElEIGlzIGFsaXZlLlxuICogVXNlcyBzaWduYWwgMCB3aGljaCBjaGVja3MgZXhpc3RlbmNlIHdpdGhvdXQgc2VuZGluZyBhIHNpZ25hbC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzUHJvY2Vzc0FsaXZlKHBpZDogbnVtYmVyKTogYm9vbGVhbiB7XG4gIHRyeSB7XG4gICAgcHJvY2Vzcy5raWxsKHBpZCwgMCk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxufVxuXG4vKipcbiAqIFJlYWQgYW5kIHBhcnNlIHRoZSBsZWFkZXIgbG9jayBmaWxlLlxuICogUmV0dXJucyBudWxsIGlmIHRoZSBmaWxlIGRvZXNuJ3QgZXhpc3QsIGlzIHVucmVhZGFibGUsIG9yIGhhcyBpbnZhbGlkIGNvbnRlbnQuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZWFkTGVhZGVyTG9jaygpOiBQcm9taXNlPENvbnNvbGVMZWFkZXJJbmZvIHwgbnVsbD4ge1xuICB0cnkge1xuICAgIGNvbnN0IGNvbnRlbnQgPSBhd2FpdCByZWFkRmlsZShMT0NLX0ZJTEUsICd1dGYtOCcpO1xuICAgIGNvbnN0IGRhdGEgPSBKU09OLnBhcnNlKGNvbnRlbnQpIGFzIENvbnNvbGVMZWFkZXJJbmZvO1xuICAgIGlmIChkYXRhLnZlcnNpb24gIT09IExPQ0tfVkVSU0lPTiB8fCAhZGF0YS5waWQgfHwgIWRhdGEucG9ydCB8fCAhZGF0YS5zZXNzaW9uSWQpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICByZXR1cm4gZGF0YTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cblxuLyoqXG4gKiBDaGVjayBpZiBhIGxlYWRlciBsb2NrIGlzIHN0YWxlIChkZWFkIHByb2Nlc3Mgb3IgZXhwaXJlZCBoZWFydGJlYXQpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaXNMb2NrU3RhbGUoaW5mbzogQ29uc29sZUxlYWRlckluZm8pOiBib29sZWFuIHtcbiAgaWYgKCFpc1Byb2Nlc3NBbGl2ZShpbmZvLnBpZCkpIHtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuICBjb25zdCBoZWFydGJlYXRBZ2UgPSBEYXRlLm5vdygpIC0gbmV3IERhdGUoaW5mby5oZWFydGJlYXQpLmdldFRpbWUoKTtcbiAgcmV0dXJuIGhlYXJ0YmVhdEFnZSA+IEhFQVJUQkVBVF9TVEFMRV9NUztcbn1cblxuLyoqXG4gKiBBdHRlbXB0IHRvIGF0b21pY2FsbHkgY2xhaW0gbGVhZGVyc2hpcC5cbiAqXG4gKiBXcml0ZXMgdG8gYSB0ZW1wIGZpbGUgdGhlbiByZW5hbWVzIHRvIHRoZSBsb2NrIHBhdGguIE9uIFBPU0lYIHN5c3RlbXNcbiAqIHJlbmFtZSBpcyBhdG9taWMsIHNvIG9ubHkgb25lIHdyaXRlciB3aW5zLiBBZnRlciByZW5hbWluZywgcmUtcmVhZHMgdGhlXG4gKiBsb2NrIHRvIHZlcmlmeSBvdXIgUElEIHdvbi5cbiAqXG4gKiBAcmV0dXJucyB0cnVlIGlmIHRoaXMgcHJvY2VzcyBzdWNjZXNzZnVsbHkgY2xhaW1lZCBsZWFkZXJzaGlwXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjbGFpbUxlYWRlcnNoaXAoaW5mbzogQ29uc29sZUxlYWRlckluZm8pOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgYXdhaXQgbWtkaXIoUlVOX0RJUiwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gIGNvbnN0IHRtcEZpbGUgPSBqb2luKFJVTl9ESVIsIGBjb25zb2xlLWxlYWRlci5sb2NrLiR7cHJvY2Vzcy5waWR9LnRtcGApO1xuICB0cnkge1xuICAgIGF3YWl0IHdyaXRlRmlsZSh0bXBGaWxlLCBKU09OLnN0cmluZ2lmeShpbmZvLCBudWxsLCAyKSwgJ3V0Zi04Jyk7XG4gICAgYXdhaXQgcmVuYW1lKHRtcEZpbGUsIExPQ0tfRklMRSk7XG5cbiAgICAvLyBWZXJpZnkgd2Ugd29uIHRoZSByYWNlXG4gICAgY29uc3Qgd3JpdHRlbiA9IGF3YWl0IHJlYWRMZWFkZXJMb2NrKCk7XG4gICAgcmV0dXJuIHdyaXR0ZW4gIT09IG51bGwgJiYgd3JpdHRlbi5waWQgPT09IGluZm8ucGlkO1xuICB9IGNhdGNoIHtcbiAgICAvLyBDbGVhbiB1cCB0ZW1wIGZpbGUgb24gZmFpbHVyZVxuICAgIHRyeSB7IGF3YWl0IHVubGluayh0bXBGaWxlKTsgfSBjYXRjaCB7IC8qIGlnbm9yZSAqLyB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbi8qKlxuICogRGVsZXRlIHRoZSBsZWFkZXIgbG9jayBmaWxlIChmb3IgY2xlYW51cCBvciB0YWtlb3ZlcikuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBkZWxldGVMZWFkZXJMb2NrKCk6IFByb21pc2U8dm9pZD4ge1xuICB0cnkgeyBhd2FpdCB1bmxpbmsoTE9DS19GSUxFKTsgfSBjYXRjaCB7IC8qIGFscmVhZHkgZ29uZSAqLyB9XG59XG5cbi8qKlxuICogUnVuIHRoZSBsZWFkZXIgZWxlY3Rpb24gcHJvdG9jb2wuXG4gKlxuICogMS4gSWYgbm8gbG9jayBleGlzdHMgb3IgbG9jayBpcyBzdGFsZSDihpIgY2xhaW0gbGVhZGVyc2hpcFxuICogMi4gSWYgbG9jayBleGlzdHMgd2l0aCBhIGxpdmUsIHJlc3BvbnNpdmUgbGVhZGVyIOKGkiBiZWNvbWUgZm9sbG93ZXJcbiAqXG4gKiBAcGFyYW0gc2Vzc2lvbklkIC0gVGhpcyBwcm9jZXNzJ3MgdW5pcXVlIHNlc3Npb24gaWRlbnRpZmllclxuICogQHBhcmFtIHBvcnQgLSBUaGUgcG9ydCB0aGlzIHByb2Nlc3Mgd291bGQgdXNlIGFzIGxlYWRlciAodHlwaWNhbGx5IDM5MzkpXG4gKiBAcmV0dXJucyBFbGVjdGlvbiByZXN1bHQgd2l0aCByb2xlIGFuZCBsZWFkZXIgaW5mb1xuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZWxlY3RMZWFkZXIoc2Vzc2lvbklkOiBzdHJpbmcsIHBvcnQ6IG51bWJlcik6IFByb21pc2U8RWxlY3Rpb25SZXN1bHQ+IHtcbiAgc2Vzc2lvbklkID0gVW5pY29kZVZhbGlkYXRvci5ub3JtYWxpemUoc2Vzc2lvbklkKS5ub3JtYWxpemVkQ29udGVudDtcbiAgY29uc3QgZXhpc3RpbmdMb2NrID0gYXdhaXQgcmVhZExlYWRlckxvY2soKTtcblxuICBpZiAoZXhpc3RpbmdMb2NrICYmICFpc0xvY2tTdGFsZShleGlzdGluZ0xvY2spKSB7XG4gICAgbG9nZ2VyLmluZm8oJ1tMZWFkZXJFbGVjdGlvbl0gRXhpc3RpbmcgbGVhZGVyIGZvdW5kIOKAlCBiZWNvbWluZyBmb2xsb3dlcicsIHtcbiAgICAgIGxlYWRlclNlc3Npb246IGV4aXN0aW5nTG9jay5zZXNzaW9uSWQsIGxlYWRlclBpZDogZXhpc3RpbmdMb2NrLnBpZCxcbiAgICAgIGxlYWRlclBvcnQ6IGV4aXN0aW5nTG9jay5wb3J0LCBteVNlc3Npb246IHNlc3Npb25JZCwgbXlQaWQ6IHByb2Nlc3MucGlkLFxuICAgIH0pO1xuICAgIHJldHVybiB7IHJvbGU6ICdmb2xsb3dlcicsIGxlYWRlckluZm86IGV4aXN0aW5nTG9jayB9O1xuICB9XG5cbiAgLy8gTm8gdmFsaWQgbGVhZGVyIOKAlCB0cnkgdG8gY2xhaW1cbiAgaWYgKGV4aXN0aW5nTG9jaykge1xuICAgIGNvbnN0IGFsaXZlID0gaXNQcm9jZXNzQWxpdmUoZXhpc3RpbmdMb2NrLnBpZCk7XG4gICAgY29uc3QgaGVhcnRiZWF0QWdlID0gRGF0ZS5ub3coKSAtIG5ldyBEYXRlKGV4aXN0aW5nTG9jay5oZWFydGJlYXQpLmdldFRpbWUoKTtcbiAgICBsb2dnZXIuaW5mbygnW0xlYWRlckVsZWN0aW9uXSBTdGFsZSBsZWFkZXIgbG9jayDigJQgdGFraW5nIG92ZXInLCB7XG4gICAgICBzdGFsZVBpZDogZXhpc3RpbmdMb2NrLnBpZCwgYWxpdmUsIGhlYXJ0YmVhdEFnZU1zOiBoZWFydGJlYXRBZ2UsXG4gICAgICBzdGFsZVNlc3Npb246IGV4aXN0aW5nTG9jay5zZXNzaW9uSWQsIG15U2Vzc2lvbjogc2Vzc2lvbklkLFxuICAgIH0pO1xuICAgIGF3YWl0IGRlbGV0ZUxlYWRlckxvY2soKTtcbiAgfVxuXG4gIGNvbnN0IG5vdyA9IG5ldyBEYXRlKCkudG9JU09TdHJpbmcoKTtcbiAgY29uc3QgbXlJbmZvOiBDb25zb2xlTGVhZGVySW5mbyA9IHtcbiAgICB2ZXJzaW9uOiBMT0NLX1ZFUlNJT04sXG4gICAgcGlkOiBwcm9jZXNzLnBpZCxcbiAgICBwb3J0LFxuICAgIHNlc3Npb25JZCxcbiAgICBzdGFydGVkQXQ6IG5vdyxcbiAgICBoZWFydGJlYXQ6IG5vdyxcbiAgfTtcblxuICBjb25zdCBjbGFpbWVkID0gYXdhaXQgY2xhaW1MZWFkZXJzaGlwKG15SW5mbyk7XG4gIGlmIChjbGFpbWVkKSB7XG4gICAgbG9nZ2VyLmluZm8oJ1tMZWFkZXJFbGVjdGlvbl0gQ2xhaW1lZCBsZWFkZXJzaGlwJywgeyBzZXNzaW9uSWQsIHBvcnQsIHBpZDogcHJvY2Vzcy5waWQgfSk7XG4gICAgcmV0dXJuIHsgcm9sZTogJ2xlYWRlcicsIGxlYWRlckluZm86IG15SW5mbyB9O1xuICB9XG5cbiAgLy8gQW5vdGhlciBwcm9jZXNzIHdvbiB0aGUgcmFjZSDigJQgcmUtcmVhZCBhbmQgYmVjb21lIGZvbGxvd2VyXG4gIGNvbnN0IHdpbm5lciA9IGF3YWl0IHJlYWRMZWFkZXJMb2NrKCk7XG4gIGlmICh3aW5uZXIpIHtcbiAgICBsb2dnZXIuaW5mbygnW0xlYWRlckVsZWN0aW9uXSBMb3N0IGVsZWN0aW9uIOKAlCBiZWNvbWluZyBmb2xsb3dlcicsIHtcbiAgICAgIHdpbm5lclBpZDogd2lubmVyLnBpZCwgd2lubmVyU2Vzc2lvbjogd2lubmVyLnNlc3Npb25JZCwgbXlTZXNzaW9uOiBzZXNzaW9uSWQsIG15UGlkOiBwcm9jZXNzLnBpZCxcbiAgICB9KTtcbiAgICByZXR1cm4geyByb2xlOiAnZm9sbG93ZXInLCBsZWFkZXJJbmZvOiB3aW5uZXIgfTtcbiAgfVxuXG4gIC8vIEV4dHJlbWVseSB1bmxpa2VseTogbG9jayBkaXNhcHBlYXJlZCBiZXR3ZWVuIG91ciBjbGFpbSBhbmQgcmUtcmVhZC4gUmV0cnkgb25jZS5cbiAgbG9nZ2VyLndhcm4oJ1tMZWFkZXJFbGVjdGlvbl0gTG9jayB2YW5pc2hlZCBhZnRlciBmYWlsZWQgY2xhaW0uIFJldHJ5aW5nLicpO1xuICBjb25zdCByZXRyeUluZm86IENvbnNvbGVMZWFkZXJJbmZvID0geyAuLi5teUluZm8sIGhlYXJ0YmVhdDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpIH07XG4gIGNvbnN0IHJldHJ5Q2xhaW1lZCA9IGF3YWl0IGNsYWltTGVhZGVyc2hpcChyZXRyeUluZm8pO1xuICBpZiAocmV0cnlDbGFpbWVkKSB7XG4gICAgcmV0dXJuIHsgcm9sZTogJ2xlYWRlcicsIGxlYWRlckluZm86IHJldHJ5SW5mbyB9O1xuICB9XG4gIGNvbnN0IGFjdHVhbExlYWRlciA9IGF3YWl0IHJlYWRMZWFkZXJMb2NrKCk7XG4gIHJldHVybiB7IHJvbGU6ICdmb2xsb3dlcicsIGxlYWRlckluZm86IGFjdHVhbExlYWRlciA/PyByZXRyeUluZm8gfTtcbn1cblxuLyoqXG4gKiBTdGFydCB0aGUgbGVhZGVyIGhlYXJ0YmVhdCBsb29wLlxuICogVXBkYXRlcyB0aGUgbG9jayBmaWxlIGV2ZXJ5IEhFQVJUQkVBVF9JTlRFUlZBTF9NUyBzbyBmb2xsb3dlcnMga25vdyB0aGUgbGVhZGVyIGlzIGFsaXZlLlxuICpcbiAqIEByZXR1cm5zIEEgc3RvcCBmdW5jdGlvbiB0byBjbGVhciB0aGUgaW50ZXJ2YWxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0YXJ0SGVhcnRiZWF0KGluZm86IENvbnNvbGVMZWFkZXJJbmZvKTogKCkgPT4gdm9pZCB7XG4gIGNvbnN0IGludGVydmFsID0gc2V0SW50ZXJ2YWwoYXN5bmMgKCkgPT4ge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCB1cGRhdGVkOiBDb25zb2xlTGVhZGVySW5mbyA9IHsgLi4uaW5mbywgaGVhcnRiZWF0OiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCkgfTtcbiAgICAgIGNvbnN0IHRtcEZpbGUgPSBqb2luKFJVTl9ESVIsIGBjb25zb2xlLWxlYWRlci5sb2NrLiR7cHJvY2Vzcy5waWR9LnRtcGApO1xuICAgICAgYXdhaXQgd3JpdGVGaWxlKHRtcEZpbGUsIEpTT04uc3RyaW5naWZ5KHVwZGF0ZWQsIG51bGwsIDIpLCAndXRmLTgnKTtcbiAgICAgIGF3YWl0IHJlbmFtZSh0bXBGaWxlLCBMT0NLX0ZJTEUpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgbG9nZ2VyLmRlYnVnKCdbTGVhZGVyRWxlY3Rpb25dIEhlYXJ0YmVhdCB3cml0ZSBmYWlsZWQ6JywgZXJyKTtcbiAgICB9XG4gIH0sIEhFQVJUQkVBVF9JTlRFUlZBTF9NUyk7XG5cbiAgLy8gRG9uJ3QgbGV0IHRoZSBoZWFydGJlYXQgaW50ZXJ2YWwga2VlcCB0aGUgcHJvY2VzcyBhbGl2ZVxuICBpbnRlcnZhbC51bnJlZigpO1xuXG4gIHJldHVybiAoKSA9PiBjbGVhckludGVydmFsKGludGVydmFsKTtcbn1cblxuLyoqXG4gKiBSZWdpc3RlciBjbGVhbnVwIGhhbmRsZXJzIHRvIHJlbW92ZSB0aGUgbGVhZGVyIGxvY2sgb24gcHJvY2VzcyBleGl0LlxuICogU2hvdWxkIG9ubHkgYmUgY2FsbGVkIGJ5IHRoZSBsZWFkZXIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWdpc3RlckxlYWRlckNsZWFudXAoKTogdm9pZCB7XG4gIGNvbnN0IGNsZWFudXAgPSAoKSA9PiB7IGRlbGV0ZUxlYWRlckxvY2soKS5jYXRjaCgoKSA9PiB7fSk7IH07XG4gIHByb2Nlc3Mub25jZSgnZXhpdCcsIGNsZWFudXApO1xuICBwcm9jZXNzLm9uY2UoJ1NJR1RFUk0nLCBjbGVhbnVwKTtcbiAgcHJvY2Vzcy5vbmNlKCdTSUdJTlQnLCBjbGVhbnVwKTtcbn1cbiJdfQ==
|