@pipeline-builder/api-server 0.0.1

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.
Files changed (61) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +79 -0
  3. package/lib/api/app-factory.d.ts +95 -0
  4. package/lib/api/app-factory.js +328 -0
  5. package/lib/api/check-quota.d.ts +13 -0
  6. package/lib/api/check-quota.js +74 -0
  7. package/lib/api/context-middleware.d.ts +34 -0
  8. package/lib/api/context-middleware.js +42 -0
  9. package/lib/api/etag-middleware.d.ts +7 -0
  10. package/lib/api/etag-middleware.js +44 -0
  11. package/lib/api/get-context.d.ts +22 -0
  12. package/lib/api/get-context.js +28 -0
  13. package/lib/api/health-checks.d.ts +62 -0
  14. package/lib/api/health-checks.js +87 -0
  15. package/lib/api/idempotency-middleware.d.ts +80 -0
  16. package/lib/api/idempotency-middleware.js +321 -0
  17. package/lib/api/index.d.ts +20 -0
  18. package/lib/api/index.js +34 -0
  19. package/lib/api/meter-quota.d.ts +32 -0
  20. package/lib/api/meter-quota.js +70 -0
  21. package/lib/api/metrics.d.ts +47 -0
  22. package/lib/api/metrics.js +221 -0
  23. package/lib/api/middleware-factory.d.ts +56 -0
  24. package/lib/api/middleware-factory.js +80 -0
  25. package/lib/api/middleware.d.ts +1 -0
  26. package/lib/api/middleware.js +8 -0
  27. package/lib/api/mongo-connect.d.ts +19 -0
  28. package/lib/api/mongo-connect.js +41 -0
  29. package/lib/api/quota-helpers.d.ts +23 -0
  30. package/lib/api/quota-helpers.js +22 -0
  31. package/lib/api/rate-limit-by-org.d.ts +29 -0
  32. package/lib/api/rate-limit-by-org.js +64 -0
  33. package/lib/api/readiness.d.ts +29 -0
  34. package/lib/api/readiness.js +65 -0
  35. package/lib/api/request-types.d.ts +55 -0
  36. package/lib/api/request-types.js +65 -0
  37. package/lib/api/require-org-id.d.ts +14 -0
  38. package/lib/api/require-org-id.js +28 -0
  39. package/lib/api/route-wrapper.d.ts +50 -0
  40. package/lib/api/route-wrapper.js +84 -0
  41. package/lib/api/server.d.ts +79 -0
  42. package/lib/api/server.js +234 -0
  43. package/lib/api/tenant-context.d.ts +28 -0
  44. package/lib/api/tenant-context.js +48 -0
  45. package/lib/api/tracing.d.ts +25 -0
  46. package/lib/api/tracing.js +69 -0
  47. package/lib/http/index.d.ts +4 -0
  48. package/lib/http/index.js +7 -0
  49. package/lib/http/sse-connection-manager.d.ts +357 -0
  50. package/lib/http/sse-connection-manager.js +665 -0
  51. package/lib/http/sse-relay.d.ts +63 -0
  52. package/lib/http/sse-relay.js +70 -0
  53. package/lib/http/sse-ticket-channel.d.ts +31 -0
  54. package/lib/http/sse-ticket-channel.js +52 -0
  55. package/lib/http/sse-ticket-store.d.ts +75 -0
  56. package/lib/http/sse-ticket-store.js +160 -0
  57. package/lib/index.d.ts +30 -0
  58. package/lib/index.js +35 -0
  59. package/lib/otel-bootstrap.d.ts +1 -0
  60. package/lib/otel-bootstrap.js +75 -0
  61. package/package.json +183 -0
@@ -0,0 +1,665 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { randomBytes } from 'node:crypto';
4
+ import { createLogger, SSE_TICKET_TTL_MS, writeSseHeaders } from '@pipeline-builder/api-core';
5
+ import { CoreConstants } from '@pipeline-builder/pipeline-core';
6
+ import { v7 as uuid } from 'uuid';
7
+ import { createMemoryTicketStore } from './sse-ticket-store.js';
8
+ import { incCounter, setGauge } from '../api/metrics.js';
9
+ const logger = createLogger('sse-manager');
10
+ /**
11
+ * A build-log stream subject id: either a dashed UUID (api-server's `uuid()`)
12
+ * or nginx's `$request_id` — 16 random bytes rendered as 32 hex chars with NO
13
+ * dashes. Accept both by making the group separators optional; still strictly
14
+ * 32 hex chars so it can't carry an injection / path-traversal payload into the
15
+ * SSE subject. Shared by the ticket-mint route and the stream middleware so
16
+ * both validate the subject identically.
17
+ */
18
+ export const SSE_REQUEST_ID_RE = /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i;
19
+ /**
20
+ * SSE helper class with memory leak protection
21
+ *
22
+ * Features:
23
+ * - Client limits per request ID
24
+ * - Automatic timeout for idle connections
25
+ * - Periodic cleanup of stale connections
26
+ * - Connection statistics
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const sseManager = new SSEManager({ maxClientsPerRequest: 5 });
31
+ * app.get('/logs/:requestId', sseManager.middleware());
32
+ *
33
+ * // Send events
34
+ * sseManager.send('request-123', 'INFO', 'Processing...');
35
+ * ```
36
+ */
37
+ export class SSEManager {
38
+ clients = new Map();
39
+ /** Per-org open-client counters. Decremented on removeClient/cleanup so the
40
+ * map mirrors `clients[].orgId` counts at all times. Orgs reach zero are
41
+ * deleted to keep the map bounded. */
42
+ orgClientCounts = new Map();
43
+ /** Ticket + stream-ownership backend (in-memory default; Redis for multi-pod).
44
+ * Bounded by `maxTotalTickets` / `maxTicketsPerOrg`, enforced in createTicket;
45
+ * expiry is validated at consume time and swept on the cleanup interval. */
46
+ ticketStore;
47
+ maxClientsPerRequest;
48
+ maxTotalClients;
49
+ maxClientsPerOrg;
50
+ maxTotalTickets;
51
+ maxTicketsPerOrg;
52
+ ticketTtlMs;
53
+ streamOwnerTtlMs;
54
+ clientTimeoutMs;
55
+ cleanupInterval = null;
56
+ /** Cross-pod fan-out bus (undefined ⇒ local-only delivery). */
57
+ relay;
58
+ /** This manager instance's id — tags relayed frames so we ignore our own echo. */
59
+ nodeId = randomBytes(8).toString('hex');
60
+ constructor(options = {}) {
61
+ this.maxClientsPerRequest = options.maxClientsPerRequest ?? parseInt(process.env.SSE_MAX_CLIENTS_PER_REQUEST || '10', 10);
62
+ this.maxTotalClients = options.maxTotalClients ?? parseInt(process.env.SSE_MAX_TOTAL_CLIENTS || '1000', 10);
63
+ this.maxClientsPerOrg = options.maxClientsPerOrg ?? parseInt(process.env.SSE_MAX_CLIENTS_PER_ORG || '50', 10);
64
+ this.maxTotalTickets = options.maxTotalTickets ?? parseInt(process.env.SSE_MAX_TOTAL_TICKETS || '1000', 10);
65
+ this.maxTicketsPerOrg = options.maxTicketsPerOrg ?? parseInt(process.env.SSE_MAX_TICKETS_PER_ORG || '10', 10);
66
+ this.ticketTtlMs = options.ticketTtlMs ?? SSE_TICKET_TTL_MS;
67
+ this.streamOwnerTtlMs = options.streamOwnerTtlMs ?? parseInt(process.env.SSE_STREAM_OWNER_TTL_MS || '3600000', 10); // 1 hour
68
+ this.ticketStore = options.ticketStore ?? createMemoryTicketStore();
69
+ this.clientTimeoutMs = options.clientTimeoutMs ?? parseInt(process.env.SSE_CLIENT_TIMEOUT_MS || '1800000', 10); // 30 minutes
70
+ const cleanupIntervalMs = options.cleanupIntervalMs ?? parseInt(process.env.SSE_CLEANUP_INTERVAL_MS || '300000', 10); // 5 minutes
71
+ this.startCleanupInterval(cleanupIntervalMs);
72
+ // Wire the cross-pod relay: subscribe once on startup so frames PUBLISHED by
73
+ // any other pod are re-emitted to THIS pod's local clients. Frames we
74
+ // published ourselves are ignored (origin === nodeId) — we already wrote them
75
+ // locally. Fail-safe: no relay ⇒ local-only delivery (single-replica default).
76
+ this.relay = options.relay;
77
+ this.relay?.subscribe((msg) => this.onRelayMessage(msg));
78
+ }
79
+ /**
80
+ * Handle a frame received from another pod via the relay. We already delivered
81
+ * our OWN frames to local clients before publishing, so ignore our echo. A
82
+ * `send` re-emits to the subject's local clients; a `broadcast` to all of them.
83
+ * Purely local — never re-publishes, so there is no fan-out loop.
84
+ */
85
+ onRelayMessage(msg) {
86
+ if (!msg || msg.origin === this.nodeId)
87
+ return;
88
+ if (msg.kind === 'broadcast') {
89
+ this.broadcastLocal(msg.payload);
90
+ }
91
+ else if (msg.requestId) {
92
+ this.sendLocal(msg.requestId, msg.payload);
93
+ }
94
+ }
95
+ /** Total open connections across all requests. */
96
+ totalClients() {
97
+ let n = 0;
98
+ for (const clients of this.clients.values())
99
+ n += clients.length;
100
+ return n;
101
+ }
102
+ /**
103
+ * Publish the current live-connection count as a gauge so on-call can see SSE
104
+ * saturation (approach to the per-process cap) on a dashboard — previously
105
+ * these numbers lived only in `getStats()`/logs. Called after every add/remove/
106
+ * close/cleanup. Cheap (O(R)); metric helpers never throw.
107
+ */
108
+ updateActiveGauge() {
109
+ setGauge('sse_active_connections', {}, this.totalClients());
110
+ }
111
+ /** Current open-stream count for an org (0 if unseen). */
112
+ getOrgClientCount(orgId) {
113
+ return this.orgClientCounts.get(orgId) ?? 0;
114
+ }
115
+ /**
116
+ * Canonical form of a requestId for equality checks: dashes stripped and
117
+ * lowercased, so the dashed (api-server `uuid()`) and undashed (nginx
118
+ * `$request_id`) renderings of the same id compare equal. Callers are
119
+ * expected to have already format-validated against {@link SSE_REQUEST_ID_RE}.
120
+ */
121
+ static normalizeRequestId(requestId) {
122
+ return requestId.replace(/-/g, '').toLowerCase();
123
+ }
124
+ /**
125
+ * Canonical form of an orgId for ownership equality checks: trimmed and
126
+ * lowercased. The stream PRODUCER (bindStreamOwner) and the ticket-minting
127
+ * CONSUMER (createTicket) run in different services, so a casing / whitespace
128
+ * drift between how each renders the same org would otherwise make the owner
129
+ * comparison in {@link createTicket} false-`forbidden` the real owner.
130
+ * Normalizing both sides — mirroring {@link normalizeRequestId} — closes that.
131
+ */
132
+ static normalizeOrgId(orgId) {
133
+ return orgId.trim().toLowerCase();
134
+ }
135
+ /**
136
+ * Record the org that OWNS a stream subject. The stream PRODUCER calls this
137
+ * when it creates a build-log stream so that ticket minting can assert the
138
+ * caller's org owns the subject (see {@link createTicket}). Backed by the
139
+ * injected ticket store, so when a Redis store is wired the platform producer
140
+ * (a different service sharing the same Redis) can bind ownership that this
141
+ * service reads — closing the cross-tenant attach gap where any org could mint
142
+ * a ticket for a guessed requestId.
143
+ *
144
+ * @param requestId - The stream subject (format-validated by the caller).
145
+ * @param orgId - The owning org (normalized internally via normalizeOrgId).
146
+ */
147
+ async bindStreamOwner(requestId, orgId) {
148
+ await this.ticketStore.bindStreamOwner(SSEManager.normalizeRequestId(requestId), SSEManager.normalizeOrgId(orgId), this.streamOwnerTtlMs);
149
+ }
150
+ /**
151
+ * Mint a short-lived, single-use SSE ticket bound to `orgId` AND a specific
152
+ * stream subject (`requestId`). Clients POST to obtain one (JWT-authenticated)
153
+ * for the exact stream they intend to open, then open the EventSource with
154
+ * `?ticket=<t>` so the JWT never lands in a query string / access log. The
155
+ * ticket can then only be consumed to open that one subject's stream — see
156
+ * {@link consumeTicket} — which is what enforces per-subject authorization.
157
+ *
158
+ * ORG-OWNERSHIP: if a stream owner has been bound for this subject (via
159
+ * {@link bindStreamOwner}) and it is a DIFFERENT org, minting is refused
160
+ * (`reason: 'forbidden'`) — an org cannot mint a ticket for another org's
161
+ * stream even if it guesses the requestId. When no owner is bound (producer
162
+ * wiring not yet present), minting falls back to binding the ticket to the
163
+ * caller's own org, preserving current behavior.
164
+ *
165
+ * Bounded by `maxTotalTickets` (process-wide) and `maxTicketsPerOrg`
166
+ * (per-tenant). Mirrors the message-service notifications ticket store.
167
+ *
168
+ * @param orgId - Owning org (normalized internally via normalizeOrgId).
169
+ * @param requestId - The build-log stream subject this ticket authorizes.
170
+ * The caller must have format-validated it (see {@link SSE_REQUEST_ID_RE}).
171
+ * @returns `{ ok: true, ticket }` on success, or `{ ok: false, reason }`
172
+ * where reason is `'org-limit'`, `'capacity'`, or `'forbidden'`.
173
+ */
174
+ async createTicket(orgId, requestId) {
175
+ // Ownership gate first — a cross-tenant mint attempt should never even
176
+ // consume cap budget. Only enforced when an owner is actually bound. Both
177
+ // sides are org-normalized so a producer/consumer casing drift can't
178
+ // false-`forbidden` the real owner (mirrors requestId normalization).
179
+ const normalized = SSEManager.normalizeRequestId(requestId);
180
+ const normalizedOrg = SSEManager.normalizeOrgId(orgId);
181
+ const owner = await this.ticketStore.getStreamOwner(normalized);
182
+ if (owner && owner !== normalizedOrg) {
183
+ logger.warn(`SSE ticket refused: org ${normalizedOrg} does not own stream subject`);
184
+ incCounter('sse_ticket_rejected_total', { reason: 'forbidden' });
185
+ return { ok: false, reason: 'forbidden' };
186
+ }
187
+ // Total cap next — protects process memory even when a single org is
188
+ // the offender. Then the per-org cap for fair-share.
189
+ if ((await this.ticketStore.total()) >= this.maxTotalTickets) {
190
+ logger.warn(`SSE ticket cap reached (max: ${this.maxTotalTickets}); rejecting ticket request`);
191
+ incCounter('sse_ticket_rejected_total', { reason: 'capacity' });
192
+ return { ok: false, reason: 'capacity' };
193
+ }
194
+ if ((await this.ticketStore.countForOrg(normalizedOrg)) >= this.maxTicketsPerOrg) {
195
+ logger.warn(`Per-org SSE ticket cap reached for ${normalizedOrg} (max: ${this.maxTicketsPerOrg})`);
196
+ incCounter('sse_ticket_rejected_total', { reason: 'org-limit' });
197
+ return { ok: false, reason: 'org-limit' };
198
+ }
199
+ const ticket = randomBytes(24).toString('base64url');
200
+ await this.ticketStore.put(ticket, { orgId: normalizedOrg, requestId: normalized }, this.ticketTtlMs);
201
+ return { ok: true, ticket };
202
+ }
203
+ /**
204
+ * Validate and CONSUME a ticket for a specific stream subject. Single-use:
205
+ * the ticket is deleted whether or not it turns out to be valid, so a replay
206
+ * of the same value always fails. Returns the bound org on success, or null
207
+ * when the ticket is unknown / already-used / expired / OR was minted for a
208
+ * DIFFERENT `requestId` than the one being opened.
209
+ *
210
+ * The subject check is the authorization fix: without it, a ticket minted for
211
+ * one stream could be presented to open ANY stream the holder's org could
212
+ * name, letting an authenticated org attach to another org's log stream by
213
+ * guessing its requestId. The generic 401 the caller returns does not
214
+ * distinguish "unknown ticket" from "wrong subject", so it leaks nothing
215
+ * about which requestIds exist.
216
+ *
217
+ * @param ticketId - The opaque ticket value from `?ticket=`.
218
+ * @param requestId - The stream subject from the URL path (`:requestId`),
219
+ * already format-validated by the caller.
220
+ */
221
+ async consumeTicket(ticketId, requestId) {
222
+ const ticket = await this.ticketStore.consume(ticketId); // single-use
223
+ if (!ticket)
224
+ return null;
225
+ // Subject binding — reject a ticket presented for a subject it was not
226
+ // minted for. Both sides are normalized so dashed/undashed forms match.
227
+ if (ticket.requestId !== SSEManager.normalizeRequestId(requestId))
228
+ return null;
229
+ return { orgId: ticket.orgId };
230
+ }
231
+ /**
232
+ * Adds a client to the SSE manager
233
+ *
234
+ * @param requestId - Unique request ID
235
+ * @param res - Express Response object
236
+ * @param orgId - Authenticated org id (optional). When set, enforces the
237
+ * per-org cap and the counter is decremented on disconnect/cleanup.
238
+ * @returns true if client was added, false if rejected (limit reached)
239
+ */
240
+ addClient(requestId, res, orgId) {
241
+ // Normalize the map key: the subject id is allowed in two forms (dashed
242
+ // api-server uuid vs undashed nginx $request_id), and a producer/consumer that
243
+ // render it differently would otherwise key different Map entries — the client
244
+ // registers under one and every send()/relay writes to another, dropping all
245
+ // frames. Normalizing here (and in send/sendLocal/closeRequest/etc.) unifies them.
246
+ requestId = SSEManager.normalizeRequestId(requestId);
247
+ const existing = this.clients.get(requestId) || [];
248
+ // Check client limit
249
+ if (existing.length >= this.maxClientsPerRequest) {
250
+ logger.warn(`Client limit reached for request ${requestId} (max: ${this.maxClientsPerRequest})`);
251
+ incCounter('sse_connection_rejected_total', { reason: 'request-limit' });
252
+ return false;
253
+ }
254
+ // Process-wide cap: protects fd table + memory from runaway dashboards.
255
+ if (this.totalClients() >= this.maxTotalClients) {
256
+ logger.warn(`Total SSE client cap reached (max: ${this.maxTotalClients}); rejecting new connection`);
257
+ incCounter('sse_connection_rejected_total', { reason: 'total-cap' });
258
+ return false;
259
+ }
260
+ // Per-org cap: prevents one noisy org from monopolizing process-wide
261
+ // slots. Enforced only when orgId is known — service-internal / anonymous
262
+ // streams that never identify with an orgId stay subject to the global
263
+ // ceilings above. Reserve atomically before insert so two concurrent
264
+ // requests at the limit can't both succeed.
265
+ if (orgId) {
266
+ const orgCurrent = this.orgClientCounts.get(orgId) ?? 0;
267
+ if (orgCurrent >= this.maxClientsPerOrg) {
268
+ logger.warn(`Per-org SSE client cap reached for ${orgId} (max: ${this.maxClientsPerOrg}); rejecting new connection`);
269
+ incCounter('sse_connection_rejected_total', { reason: 'org-cap' });
270
+ return false;
271
+ }
272
+ this.orgClientCounts.set(orgId, orgCurrent + 1);
273
+ }
274
+ // Create timeout for this client
275
+ const clientId = uuid();
276
+ const timeout = setTimeout(() => {
277
+ logger.debug(`Client ${clientId} timed out for request ${requestId}`);
278
+ this.removeClient(requestId, clientId);
279
+ try {
280
+ res.end();
281
+ }
282
+ catch (err) {
283
+ logger.debug('Response already closed on timeout', { requestId, clientId, error: err instanceof Error ? err.message : String(err) });
284
+ }
285
+ }, this.clientTimeoutMs);
286
+ const client = {
287
+ id: clientId,
288
+ res,
289
+ connectedAt: Date.now(),
290
+ timeout,
291
+ backpressureCount: 0,
292
+ orgId,
293
+ };
294
+ // Handle disconnection
295
+ res.on('close', () => {
296
+ clearTimeout(timeout);
297
+ this.removeClient(requestId, clientId);
298
+ });
299
+ res.on('error', (err) => {
300
+ logger.error(`SSE client error for request ${requestId}:`, err);
301
+ clearTimeout(timeout);
302
+ this.removeClient(requestId, clientId);
303
+ });
304
+ existing.push(client);
305
+ this.clients.set(requestId, existing);
306
+ incCounter('sse_connections_total', {});
307
+ this.updateActiveGauge();
308
+ logger.debug(`Client ${clientId} connected for request ${requestId} (total: ${existing.length})`);
309
+ return true;
310
+ }
311
+ /** Drop one from the per-org counter. Idempotent: a counter at 0 stays at 0
312
+ * and the org key is removed from the map. */
313
+ decrementOrgCount(orgId) {
314
+ if (!orgId)
315
+ return;
316
+ const current = this.orgClientCounts.get(orgId) ?? 0;
317
+ if (current <= 1)
318
+ this.orgClientCounts.delete(orgId);
319
+ else
320
+ this.orgClientCounts.set(orgId, current - 1);
321
+ }
322
+ /**
323
+ * Removes a client from the manager
324
+ */
325
+ removeClient(requestId, clientId) {
326
+ const clients = this.clients.get(requestId);
327
+ if (!clients)
328
+ return;
329
+ const remaining = clients.filter(c => {
330
+ if (c.id === clientId) {
331
+ clearTimeout(c.timeout);
332
+ // Mirror the per-org counter — must decrement here, not just in the
333
+ // explicit close paths, since 'close'/'error' event handlers are the
334
+ // primary disconnect signal in practice.
335
+ this.decrementOrgCount(c.orgId);
336
+ return false;
337
+ }
338
+ return true;
339
+ });
340
+ if (remaining.length === 0) {
341
+ this.clients.delete(requestId);
342
+ logger.debug(`All clients disconnected for request ${requestId}`);
343
+ }
344
+ else {
345
+ this.clients.set(requestId, remaining);
346
+ }
347
+ this.updateActiveGauge();
348
+ }
349
+ /**
350
+ * Sends a message to all SSE clients for a requestId
351
+ *
352
+ * @param requestId - Request ID
353
+ * @param type - Event type
354
+ * @param message - Message string
355
+ * @param data - Optional additional data
356
+ * @returns Number of clients the message was sent to
357
+ */
358
+ send(requestId, type, message, data) {
359
+ requestId = SSEManager.normalizeRequestId(requestId); // unify producer/consumer key forms
360
+ const payload = {
361
+ ts: new Date().toISOString(),
362
+ type,
363
+ message,
364
+ data,
365
+ };
366
+ // Deliver to LOCAL clients first, then relay the SAME payload to other pods
367
+ // so an EventSource attached on a different replica also receives it. The
368
+ // relay is fire-and-forget and fail-safe: no relay / Redis down ⇒ local-only.
369
+ const sentCount = this.sendLocal(requestId, payload);
370
+ this.publishRelay({ origin: this.nodeId, kind: 'send', requestId, payload });
371
+ return sentCount;
372
+ }
373
+ /**
374
+ * Write a pre-built payload to this pod's LOCAL clients for `requestId`. Shared
375
+ * by {@link send} (local half) and the relay re-emit path ({@link onRelayMessage}),
376
+ * so a relayed frame reuses the producer's original `ts` rather than a new one.
377
+ * Never relays — callers decide whether to publish.
378
+ */
379
+ sendLocal(requestId, payload) {
380
+ requestId = SSEManager.normalizeRequestId(requestId); // idempotent; covers the relay re-emit path
381
+ const clients = [...(this.clients.get(requestId) || [])];
382
+ let sentCount = 0;
383
+ const serialized = `data: ${JSON.stringify(payload)}\n\n`;
384
+ for (const client of clients) {
385
+ try {
386
+ // Backpressure: skip clients whose write buffer is full
387
+ if (client.res.writableEnded) {
388
+ this.removeClient(requestId, client.id);
389
+ continue;
390
+ }
391
+ const canWrite = client.res.write(serialized);
392
+ if (!canWrite) {
393
+ client.backpressureCount++;
394
+ // Disconnect clients that consistently can't keep up (10 consecutive backpressure events)
395
+ if (client.backpressureCount >= CoreConstants.SSE_BACKPRESSURE_THRESHOLD) {
396
+ logger.warn(`Disconnecting slow client ${client.id} for request ${requestId} (${client.backpressureCount} backpressure events)`);
397
+ incCounter('sse_backpressure_disconnects_total', {});
398
+ this.removeClient(requestId, client.id);
399
+ try {
400
+ client.res.end();
401
+ }
402
+ catch { /* already closed */ }
403
+ continue;
404
+ }
405
+ }
406
+ else {
407
+ client.backpressureCount = 0; // Reset on successful write
408
+ }
409
+ sentCount++;
410
+ }
411
+ catch (error) {
412
+ logger.error(`Failed to send to client ${client.id}:`, error);
413
+ this.removeClient(requestId, client.id);
414
+ }
415
+ }
416
+ return sentCount;
417
+ }
418
+ /**
419
+ * Broadcast a message to all connected clients across all requests
420
+ *
421
+ * @param type - Event type
422
+ * @param message - Message string
423
+ * @param data - Optional additional data
424
+ * @returns Total number of clients the message was sent to
425
+ */
426
+ broadcast(type, message, data) {
427
+ const payload = {
428
+ ts: new Date().toISOString(),
429
+ type,
430
+ message,
431
+ data,
432
+ };
433
+ // Deliver to local clients, then relay a single broadcast frame so EVERY pod
434
+ // re-emits to ALL of its own clients — a per-requestId relay would miss
435
+ // subjects that only exist on other replicas.
436
+ const totalSent = this.broadcastLocal(payload);
437
+ this.publishRelay({ origin: this.nodeId, kind: 'broadcast', payload });
438
+ return totalSent;
439
+ }
440
+ /** Write a pre-built payload to all LOCAL clients across every request. */
441
+ broadcastLocal(payload) {
442
+ let totalSent = 0;
443
+ for (const requestId of [...this.clients.keys()]) {
444
+ totalSent += this.sendLocal(requestId, payload);
445
+ }
446
+ return totalSent;
447
+ }
448
+ /** Fire-and-forget relay publish. No-op (local-only) when no relay is wired. */
449
+ publishRelay(msg) {
450
+ this.relay?.publish(msg);
451
+ }
452
+ /**
453
+ * Close all clients for a specific request
454
+ *
455
+ * @param requestId - Request ID to close
456
+ * @param finalMessage - Optional final message to send before closing
457
+ */
458
+ closeRequest(requestId, finalMessage) {
459
+ requestId = SSEManager.normalizeRequestId(requestId);
460
+ const clients = this.clients.get(requestId);
461
+ if (!clients)
462
+ return;
463
+ if (finalMessage) {
464
+ this.send(requestId, 'COMPLETED', finalMessage);
465
+ }
466
+ for (const client of clients) {
467
+ clearTimeout(client.timeout);
468
+ this.decrementOrgCount(client.orgId);
469
+ try {
470
+ client.res.end();
471
+ }
472
+ catch (err) {
473
+ logger.debug('Response already closed on request close', { requestId, clientId: client.id, error: err instanceof Error ? err.message : String(err) });
474
+ }
475
+ }
476
+ this.clients.delete(requestId);
477
+ this.updateActiveGauge();
478
+ logger.debug(`Closed all clients for request ${requestId}`);
479
+ }
480
+ /**
481
+ * Get statistics about current connections
482
+ */
483
+ getStats() {
484
+ let totalClients = 0;
485
+ let oldestConnection = null;
486
+ const now = Date.now();
487
+ for (const clients of this.clients.values()) {
488
+ totalClients += clients.length;
489
+ for (const client of clients) {
490
+ const age = now - client.connectedAt;
491
+ if (oldestConnection === null || age > oldestConnection) {
492
+ oldestConnection = age;
493
+ }
494
+ }
495
+ }
496
+ return {
497
+ totalRequests: this.clients.size,
498
+ totalClients,
499
+ oldestConnectionMs: oldestConnection,
500
+ };
501
+ }
502
+ /**
503
+ * Check if a request has any connected clients
504
+ */
505
+ hasClients(requestId) {
506
+ const clients = this.clients.get(SSEManager.normalizeRequestId(requestId));
507
+ return clients !== undefined && clients.length > 0;
508
+ }
509
+ /**
510
+ * Get the number of clients for a specific request
511
+ */
512
+ getClientCount(requestId) {
513
+ return this.clients.get(SSEManager.normalizeRequestId(requestId))?.length ?? 0;
514
+ }
515
+ /**
516
+ * Middleware to initialize a ticket-authenticated SSE connection.
517
+ *
518
+ * The stream is NOT open to the world: the caller must first exchange its
519
+ * JWT for a short-lived single-use ticket (see {@link createTicket}, wired to
520
+ * `POST /logs/ticket`) and pass it as `?ticket=<t>`. The middleware validates
521
+ * + consumes the ticket, binds the connection to the ticket's org, and
522
+ * enforces the per-org connection cap. A missing / invalid / expired /
523
+ * already-used ticket is rejected with 401 before any SSE headers flush.
524
+ *
525
+ * @example
526
+ * ```typescript
527
+ * app.get('/logs/:requestId', sseManager.middleware());
528
+ * ```
529
+ */
530
+ middleware() {
531
+ return async (req, res) => {
532
+ const { requestId } = req.params;
533
+ if (!SSE_REQUEST_ID_RE.test(requestId)) {
534
+ res.status(400).end('Invalid requestId format');
535
+ return;
536
+ }
537
+ // Ticket auth — required. Resolve `?ticket=<t>`, then validate + consume
538
+ // it (single-use) FOR THIS SUBJECT. An anonymous, malformed, unknown,
539
+ // expired, already-consumed, or wrong-subject ticket is a 401. The org is
540
+ // taken from the ticket, so the stream is always bound to a verified
541
+ // tenant AND to the exact subject the ticket was minted for (no anonymous
542
+ // streams, no cross-subject attach).
543
+ const ticketId = req.query?.ticket;
544
+ if (typeof ticketId !== 'string' || ticketId.length === 0) {
545
+ res.status(401).end('Missing ticket');
546
+ return;
547
+ }
548
+ const consumed = await this.consumeTicket(ticketId, requestId);
549
+ if (!consumed) {
550
+ res.status(401).end('Invalid or expired ticket');
551
+ return;
552
+ }
553
+ const orgId = consumed.orgId;
554
+ // Pre-flight per-request cap so we can return 429 before flushing
555
+ // SSE headers (after headers are flushed the client can't read a 429).
556
+ const existing = this.clients.get(requestId) || [];
557
+ if (existing.length >= this.maxClientsPerRequest) {
558
+ logger.warn(`Client limit reached for request ${requestId} (max: ${this.maxClientsPerRequest})`);
559
+ res.status(429).end('Too many connections for this request');
560
+ return;
561
+ }
562
+ // Same for the per-org cap — better to send 429 than open the stream and
563
+ // immediately close it. The ticket guarantees a verified org id, so the
564
+ // per-org ceiling is always enforced for the /logs stream.
565
+ if ((this.orgClientCounts.get(orgId) ?? 0) >= this.maxClientsPerOrg) {
566
+ logger.warn(`Per-org SSE client cap reached for ${orgId} (max: ${this.maxClientsPerOrg})`);
567
+ res.status(429).end('Too many SSE connections for this organization');
568
+ return;
569
+ }
570
+ writeSseHeaders(res);
571
+ res.flushHeaders();
572
+ // Headers are flushed — we can no longer send a 429. addClient re-checks
573
+ // the caps (including the process-wide total-clients cap, which the
574
+ // pre-flight above does not) and can still reject on a race: a concurrent
575
+ // connection may have filled a cap between the pre-flight check and here.
576
+ // On rejection, close the now-dangling response instead of leaving an
577
+ // open socket with no client record (which would leak an fd + never emit
578
+ // stream data).
579
+ const added = this.addClient(requestId, res, orgId);
580
+ if (!added) {
581
+ logger.warn(`SSE addClient rejected after headers flushed for request ${requestId}; closing dangling response`);
582
+ try {
583
+ res.end();
584
+ }
585
+ catch { /* already closed */ }
586
+ }
587
+ };
588
+ }
589
+ /**
590
+ * Start periodic cleanup of stale connections
591
+ */
592
+ startCleanupInterval(intervalMs) {
593
+ this.cleanupInterval = setInterval(() => {
594
+ this.cleanup();
595
+ }, intervalMs);
596
+ // Don't prevent process exit
597
+ this.cleanupInterval.unref();
598
+ }
599
+ /**
600
+ * Clean up stale connections using single-pass partition.
601
+ * O(R × C) instead of O(R × C²), and avoids mutation-during-iteration.
602
+ */
603
+ cleanup() {
604
+ const now = Date.now();
605
+ let cleaned = 0;
606
+ // Sweep expired tickets/owners so an in-memory store stays bounded even when
607
+ // tickets are minted but never consumed (client closed the tab before
608
+ // connecting). Consume-time still re-checks expiry, so this is memory
609
+ // hygiene only; the Redis store relies on native key TTL (sweep is a no-op).
610
+ void this.ticketStore.sweep?.();
611
+ for (const [requestId, clients] of this.clients.entries()) {
612
+ const stale = [];
613
+ const active = [];
614
+ for (const client of clients) {
615
+ // Time-based eviction OR a socket that silently closed (no 'close'
616
+ // event) — Node sometimes drops sockets without firing the event,
617
+ // so we explicitly check writableEnded/destroyed here.
618
+ const ageStale = now - client.connectedAt > this.clientTimeoutMs;
619
+ const socketDead = client.res.writableEnded || client.res.destroyed === true;
620
+ if (ageStale || socketDead) {
621
+ stale.push(client);
622
+ }
623
+ else {
624
+ active.push(client);
625
+ }
626
+ }
627
+ for (const client of stale) {
628
+ clearTimeout(client.timeout);
629
+ this.decrementOrgCount(client.orgId);
630
+ try {
631
+ client.res.end();
632
+ }
633
+ catch { /* already closed */ }
634
+ cleaned++;
635
+ }
636
+ if (active.length === 0) {
637
+ this.clients.delete(requestId);
638
+ }
639
+ else if (stale.length > 0) {
640
+ this.clients.set(requestId, active);
641
+ }
642
+ }
643
+ if (cleaned > 0) {
644
+ incCounter('sse_stale_cleaned_total', {}, cleaned);
645
+ this.updateActiveGauge();
646
+ logger.info(`Cleaned up ${cleaned} stale SSE connections`);
647
+ }
648
+ }
649
+ /**
650
+ * Shutdown the SSE manager and close all connections
651
+ */
652
+ shutdown() {
653
+ if (this.cleanupInterval) {
654
+ clearInterval(this.cleanupInterval);
655
+ this.cleanupInterval = null;
656
+ }
657
+ for (const requestId of [...this.clients.keys()]) {
658
+ this.closeRequest(requestId, 'Server shutting down');
659
+ }
660
+ // Tear down the relay's Redis connections (best-effort; never throws).
661
+ void this.relay?.close().catch(() => { });
662
+ logger.info('SSE Manager shut down');
663
+ }
664
+ }
665
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3NlLWNvbm5lY3Rpb24tbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9odHRwL3NzZS1jb25uZWN0aW9uLW1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQzFDLE9BQU8sRUFBRSxZQUFZLEVBQUUsaUJBQWlCLEVBQUUsZUFBZSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDOUYsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRWhFLE9BQU8sRUFBRSxFQUFFLElBQUksSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRWxDLE9BQU8sRUFBRSx1QkFBdUIsRUFBNkIsTUFBTSx1QkFBdUIsQ0FBQztBQUMzRixPQUFPLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRXpELE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxhQUFhLENBQUMsQ0FBQztBQWlHM0M7Ozs7Ozs7R0FPRztBQUNILE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFHLHFFQUFxRSxDQUFDO0FBZ0J2Rzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpQkc7QUFDSCxNQUFNLE9BQU8sVUFBVTtJQUNiLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBdUIsQ0FBQztJQUNqRDs7MkNBRXVDO0lBQy9CLGVBQWUsR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztJQUNwRDs7aUZBRTZFO0lBQzVELFdBQVcsQ0FBdUI7SUFDbEMsb0JBQW9CLENBQVM7SUFDN0IsZUFBZSxDQUFTO0lBQ3hCLGdCQUFnQixDQUFTO0lBQ3pCLGVBQWUsQ0FBUztJQUN4QixnQkFBZ0IsQ0FBUztJQUN6QixXQUFXLENBQVM7SUFDcEIsZ0JBQWdCLENBQVM7SUFDekIsZUFBZSxDQUFTO0lBQ2pDLGVBQWUsR0FBMEIsSUFBSSxD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dkQsQ0FBQztRQUVELHVFQUF1RTtRQUN2RSxLQUFLLElBQUksQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxHQUF5QixDQUFDLENBQUMsQ0FBQztRQUVoRSxNQUFNLENBQUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLENBQUM7SUFDdkMsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmltcG9ydCB7IHJhbmRvbUJ5dGVzIH0gZnJvbSAnbm9kZTpjcnlwdG8nO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyLCBTU0VfVElDS0VUX1RUTF9NUywgd3JpdGVTc2VIZWFkZXJzIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHsgQ29yZUNvbnN0YW50cyB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWNvcmUnO1xuaW1wb3J0IHR5cGUgeyBSZXNwb25zZSB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHsgdjcgYXMgdXVpZCB9IGZyb20gJ3V1aWQnO1xuaW1wb3J0IHR5cGUgeyBTU0VSZWxheSwgU1NFUmVsYXlNZXNzYWdlIH0gZnJvbSAnLi9zc2UtcmVsYXkuanMnO1xuaW1wb3J0IHsgY3JlYXRlTWVtb3J5VGlja2V0U3RvcmUsIHR5cGUgU1NFU3RyZWFtVGlja2V0U3RvcmUgfSBmcm9tICcuL3NzZS10aWNrZXQtc3RvcmUuanMnO1xuaW1wb3J0IHsgaW5jQ291bnRlciwgc2V0R2F1Z2UgfSBmcm9tICcuLi9hcGkvbWV0cmljcy5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignc3NlLW1hbmFnZXInKTtcblxuLyoqXG4gKiBFdmVudCB0eXBlcyBmb3IgU1NFIGxvZ2dpbmdcbiAqL1xuZXhwb3J0IHR5cGUgU1NFRXZlbnRUeXBlID0gJ0lORk8nIHwgJ1dBUk4nIHwgJ0VSUk9SJyB8ICdDT01QTEVURUQnIHwgJ1JPTExCQUNLJyB8ICdNRVNTQUdFJztcblxuLyoqXG4gKiBTU0UgcGF5bG9hZCBzdHJ1Y3R1cmVcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTU0VQYXlsb2FkIHtcbiAgdHM6IHN0cmluZztcbiAgdHlwZTogU1NFRXZlbnRUeXBlO1xuICBtZXNzYWdlOiBzdHJpbmc7XG4gIGRhdGE/OiB1bmtub3duO1xufVxuXG4vKipcbiAqIFNTRSBjbGllbnQgd2l0aCBjb25uZWN0aW9uIHRyYWNraW5nXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU1NFQ2xpZW50IHtcbiAgaWQ6IHN0cmluZztcbiAgcmVzOiBSZXNwb25zZTtcbiAgY29ubmVjdGVkQXQ6IG51bWJlcjtcbiAgdGltZW91dDogTm9kZUpTLlRpbWVvdXQ7XG4gIC8qKiBOdW1iZXIgb2YgY29uc2VjdXRpdmUgYmFja3ByZXNzdXJlIGV2ZW50cyAod3JpdGUgcmV0dXJuZWQgZmFsc2UpLiAqL1xuICBiYWNrcHJlc3N1cmVDb3VudDogbnVtYmVyO1xuICAvKiogT3duaW5nIG9yZyBpZCDigJQgcG9wdWxhdGVkIHdoZW4gdGhlIHJlcXVlc3Qgd2FzIGF1dGhlbnRpY2F0ZWQuIFVzZWQgdG9cbiAgICogIGRlY3JlbWVudCB0aGUgcGVyLW9yZyBjb3VudGVyIG9uIGRpc2Nvbm5lY3QgLyBjbGVhbnVwLiAqL1xuICBvcmdJZD86IHN0cmluZztcbn1cblxuLyoqXG4gKiBTU0UgTWFuYWdlciBjb25maWd1cmF0aW9uIG9wdGlvbnNcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTU0VNYW5hZ2VyT3B0aW9ucyB7XG4gIC8qKiBNYXhpbXVtIGNsaWVudHMgYWxsb3dlZCBwZXIgcmVxdWVzdCBJRCAoZGVmYXVsdDogMTApICovXG4gIG1heENsaWVudHNQZXJSZXF1ZXN0PzogbnVtYmVyO1xuICAvKiogQ2xpZW50IHRpbWVvdXQgaW4gbWlsbGlzZWNvbmRzIChkZWZhdWx0OiAzMCBtaW51dGVzKSAqL1xuICBjbGllbnRUaW1lb3V0TXM/OiBudW1iZXI7XG4gIC8qKiBJbnRlcnZhbCBmb3IgY2xlYW51cCBjaGVja3MgaW4gbWlsbGlzZWNvbmRzIChkZWZhdWx0OiA1IG1pbnV0ZXMpICovXG4gIGNsZWFudXBJbnRlcnZhbE1zPzogbnVtYmVyO1xuICAvKipcbiAgICogSGFyZCBjYXAgb24gdG90YWwgb3BlbiBjb25uZWN0aW9ucyBwZXIgcHJvY2Vzcy4gRGVmYXVsdHMgdG8gMTAwMCBmcm9tXG4gICAqIGBTU0VfTUFYX1RPVEFMX0NMSUVOVFNgLiBOZXcgY29ubmVjdGlvbnMgYmV5b25kIHRoaXMgYXJlIHJlamVjdGVkIGF0XG4gICAqIGBhZGRDbGllbnQoKWAuIFR1bmUgdXAgaWYgeW91ciBzZXJ2aWNlIHNlcnZlcyA+IDEwMDAgY29uY3VycmVudCBTU0VcbiAgICogZGFzaGJvYXJkcywgYnV0IGJlIGF3YXJlIE5vZGUuanMgZmQgbGltaXRzIGRvbWluYXRlIGFib3ZlIH41MDAwLlxuICAgKi9cbiAgbWF4VG90YWxDbGllbnRzPzogbnVtYmVyO1xuICAvKipcbiAgICogUGVyLW9yZyBjZWlsaW5nIG9uIGNvbmN1cnJlbnQgU1NFIHN0cmVhbXMuIERlZmF1bHRzIHRvIDUwIGZyb21cbiAgICogYFNTRV9NQVhfQ0xJRU5UU19QRVJfT1JHYC4gUHJvdGVjdHMgYWdhaW5zdCBvbmUgbm9pc3kgb3JnIGNvbnN1bWluZyB0aGVcbiAgICogcHJvY2Vzcy13aWRlIGJ1ZGdldCDigJQgd2l0aG91dCBpdCwgYW4gYXV0aGVudGljYXRlZCB1c2VyIGNhbiBmYW4gb3V0XG4gICAqIHRob3VzYW5kcyBvZiBzdHJlYW1zIChvbmUgcGVyIGRhc2hib2FyZCAvIGxvZyBzdHJlYW0pIGFuZCBzdGFydmUgb3RoZXJcbiAgICogb3JncyBvZiBjb25uZWN0aW9uIHNsb3RzLiBUaGUgY2FwIGlzIGVuZm9yY2VkIG9ubHkgb24gYGFkZENsaWVudGAgY2FsbHNcbiAgICogdGhhdCBjYXJyeSBhbiBgb3JnSWRgOyBzZXJ2aWNlLWludGVybmFsIC8gYW5vbnltb3VzIFNTRSB0cmFmZmljIHNraXBzIGl0LlxuICAgKi9cbiAgbWF4Q2xpZW50c1Blck9yZz86IG51bWJlcjtcbiAgLyoqXG4gICAqIEhhcmQgY2FwIG9uIHRvdGFsIGxpdmUgKHVuY29uc3VtZWQsIHVuZXhwaXJlZCkgU1NFIHRpY2tldHMgYWNyb3NzIHRoZVxuICAgKiBwcm9jZXNzLiBEZWZhdWx0cyB0byAxMDAwIGZyb20gYFNTRV9NQVhfVE9UQUxfVElDS0VUU2AuIEJvdW5kcyBtZW1vcnlcbiAgICogdW5kZXIgYWJ1c2Ug4oCUIGBjcmVhdGVUaWNrZXRgIHJldHVybnMgYHsgb2s6IGZhbHNlLCByZWFzb246ICdjYXBhY2l0eScgfWBcbiAgICogb25jZSByZWFjaGVkLlxuICAgKi9cbiAgbWF4VG90YWxUaWNrZXRzPzogbnVtYmVyO1xuICAvKipcbiAgICogUGVyLW9yZyBjYXAgb24gbGl2ZSBTU0UgdGlja2V0cy4gRGVmYXVsdHMgdG8gMTAgZnJvbVxuICAgKiBgU1NFX01BWF9USUNLRVRTX1BFUl9PUkdgLiBQcmV2ZW50cyBhIHNpbmdsZSB0ZW5hbnQgZnJvbSBzYXR1cmF0aW5nIHRoZVxuICAgKiB0aWNrZXQgdGFibGUg4oCUIGBjcmVhdGVUaWNrZXRgIHJldHVybnMgYHsgb2s6IGZhbHNlLCByZWFzb246ICdvcmctbGltaXQnIH1gXG4gICAqIG9uY2UgcmVhY2hlZC5cbiAgICovXG4gIG1heFRpY2tldHNQZXJPcmc/OiBudW1iZXI7XG4gIC8qKiBUaWNrZXQgVFRMIGluIG1zIChkZWZhdWx0OiBgU1NFX1RJQ0tFVF9UVExfTVNgIGZyb20gYXBpLWNvcmUpLiAqL1xuICB0aWNrZXRUdGxNcz86IG51bWJlcjtcbiAgLyoqXG4gICAqIFRpY2tldCArIHN0cmVhbS1vd25lcnNoaXAgYmFja2VuZC4gRGVmYXVsdHMgdG8gYW4gaW4tbWVtb3J5IHN0b3JlIChjb3JyZWN0XG4gICAqIHNpbmdsZS1yZXBsaWNhIG9ubHkpLiBJbmplY3QgYGNyZWF0ZUVudlJlZGlzVGlja2V0U3RvcmUoKWAgKG9yIGEgYmVzcG9rZVxuICAgKiBgU1NFU3RyZWFtVGlja2V0U3RvcmVgKSBzbyB0aWNrZXQgbWludC9yZWRlZW0gQU5EIHN0cmVhbSBvd25lcnNoaXAgd29yayBhY3Jvc3NcbiAgICogaG9yaXpvbnRhbGx5LXNjYWxlZCBwb2RzIGFuZCDigJQgY3JpdGljYWxseSDigJQgc28gdGhlIHBsYXRmb3JtIHN0cmVhbSBwcm9kdWNlclxuICAgKiAoYSBkaWZmZXJlbnQgc2VydmljZSkgY2FuIGJpbmQgb3duZXJzaGlwIHRoYXQgdGhpcyBzZXJ2aWNlIHJlYWRzLlxuICAgKi9cbiAgdGlja2V0U3RvcmU/OiBTU0VTdHJlYW1UaWNrZXRTdG9yZTtcbiAgLyoqXG4gICAqIFRUTCBmb3IgYSBzdHJlYW0tb3duZXJzaGlwIGJpbmRpbmcgKGRlZmF1bHQ6IGBTU0VfU1RSRUFNX09XTkVSX1RUTF9NU2AgZW52IG9yXG4gICAqIDFoKS4gTG9uZyBlbm91Z2ggdG8gb3V0bGl2ZSBhIGJ1aWxkOyB0aGUgcHJvZHVjZXIgcmUtYmluZHMgYXMgbmVlZGVkLlxuICAgKi9cbiAgc3RyZWFtT3duZXJUdGxNcz86IG51bWJlcjtcbiAgLyoqXG4gICAqIENyb3NzLXBvZCBmYW4tb3V0IGJ1cyBmb3IgYHNlbmQoKWAvYGJyb2FkY2FzdCgpYC4gT3B0aW9uYWwg4oCUIHdoZW4gb21pdHRlZCxcbiAgICogZGVsaXZlcnkgaXMgTE9DQUwtT05MWSAoY29ycmVjdCBzaW5nbGUtcmVwbGljYTsgdG9kYXkncyBiZWhhdmlvcikuIEluamVjdFxuICAgKiBgY3JlYXRlRW52UmVkaXNTU0VSZWxheSgpYCBzbyBhIGZyYW1lIHByb2R1Y2VkIG9uIG9uZSBwb2QgcmVhY2hlcyBhblxuICAgKiBFdmVudFNvdXJjZSBhdHRhY2hlZCBvbiBhIERJRkZFUkVOVCBwb2QuIERlZ3JhZGVzIHRvIGxvY2FsLW9ubHkgYXV0b21hdGljYWxseVxuICAgKiB3aGVuIFJlZGlzIGlzIHVuc2V0ICh0aGUgZmFjdG9yeSByZXR1cm5zIG51bGwpIG9yIGR1cmluZyBhIFJlZGlzIG91dGFnZS5cbiAgICovXG4gIHJlbGF5PzogU1NFUmVsYXk7XG59XG5cbi8qKlxuICogQSBidWlsZC1sb2cgc3RyZWFtIHN1YmplY3QgaWQ6IGVpdGhlciBhIGRhc2hlZCBVVUlEIChhcGktc2VydmVyJ3MgYHV1aWQoKWApXG4gKiBvciBuZ2lueCdzIGAkcmVxdWVzdF9pZGAg4oCUIDE2IHJhbmRvbSBieXRlcyByZW5kZXJlZCBhcyAzMiBoZXggY2hhcnMgd2l0aCBOT1xuICogZGFzaGVzLiBBY2NlcHQgYm90aCBieSBtYWtpbmcgdGhlIGdyb3VwIHNlcGFyYXRvcnMgb3B0aW9uYWw7IHN0aWxsIHN0cmljdGx5XG4gKiAzMiBoZXggY2hhcnMgc28gaXQgY2FuJ3QgY2FycnkgYW4gaW5qZWN0aW9uIC8gcGF0aC10cmF2ZXJzYWwgcGF5bG9hZCBpbnRvIHRoZVxuICogU1NFIHN1YmplY3QuIFNoYXJlZCBieSB0aGUgdGlja2V0LW1pbnQgcm91dGUgYW5kIHRoZSBzdHJlYW0gbWlkZGxld2FyZSBzb1xuICogYm90aCB2YWxpZGF0ZSB0aGUgc3ViamVjdCBpZGVudGljYWxseS5cbiAqL1xuZXhwb3J0IGNvbnN0IFNTRV9SRVFVRVNUX0lEX1JFID0gL15bMC05YS1mXXs4fS0/WzAtOWEtZl17NH0tP1swLTlhLWZdezR9LT9bMC05YS1mXXs0fS0/WzAtOWEtZl17MTJ9JC9pO1xuXG4vKiogUmVzdWx0IG9mIHtAbGluayBTU0VNYW5hZ2VyLmNyZWF0ZVRpY2tldH0uICovXG5leHBvcnQgdHlwZSBDcmVhdGVUaWNrZXRSZXN1bHQgPVxuICB8IHsgb2s6IHRydWU7IHRpY2tldDogc3RyaW5nIH1cbiAgfCB7IG9rOiBmYWxzZTsgcmVhc29uOiAnb3JnLWxpbWl0JyB8ICdjYXBhY2l0eScgfCAnZm9yYmlkZGVuJyB9O1xuXG4vKipcbiAqIFNTRSBNYW5hZ2VyIHN0YXRpc3RpY3NcbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTU0VNYW5hZ2VyU3RhdHMge1xuICB0b3RhbFJlcXVlc3RzOiBudW1iZXI7XG4gIHRvdGFsQ2xpZW50czogbnVtYmVyO1xuICBvbGRlc3RDb25uZWN0aW9uTXM6IG51bWJlciB8IG51bGw7XG59XG5cbi8qKlxuICogU1NFIGhlbHBlciBjbGFzcyB3aXRoIG1lbW9yeSBsZWFrIHByb3RlY3Rpb25cbiAqXG4gKiBGZWF0dXJlczpcbiAqIC0gQ2xpZW50IGxpbWl0cyBwZXIgcmVxdWVzdCBJRFxuICogLSBBdXRvbWF0aWMgdGltZW91dCBmb3IgaWRsZSBjb25uZWN0aW9uc1xuICogLSBQZXJpb2RpYyBjbGVhbnVwIG9mIHN0YWxlIGNvbm5lY3Rpb25zXG4gKiAtIENvbm5lY3Rpb24gc3RhdGlzdGljc1xuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBjb25zdCBzc2VNYW5hZ2VyID0gbmV3IFNTRU1hbmFnZXIoeyBtYXhDbGllbnRzUGVyUmVxdWVzdDogNSB9KTtcbiAqIGFwcC5nZXQoJy9sb2dzLzpyZXF1ZXN0SWQnLCBzc2VNYW5hZ2VyLm1pZGRsZXdhcmUoKSk7XG4gKlxuICogLy8gU2VuZCBldmVudHNcbiAqIHNzZU1hbmFnZXIuc2VuZCgncmVxdWVzdC0xMjMnLCAnSU5GTycsICdQcm9jZXNzaW5nLi4uJyk7XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNsYXNzIFNTRU1hbmFnZXIge1xuICBwcml2YXRlIGNsaWVudHMgPSBuZXcgTWFwPHN0cmluZywgU1NFQ2xpZW50W10+KCk7XG4gIC8qKiBQZXItb3JnIG9wZW4tY2xpZW50IGNvdW50ZXJzLiBEZWNyZW1lbnRlZCBvbiByZW1vdmVDbGllbnQvY2xlYW51cCBzbyB0aGVcbiAgICogIG1hcCBtaXJyb3JzIGBjbGllbnRzW10ub3JnSWRgIGNvdW50cyBhdCBhbGwgdGltZXMuIE9yZ3MgcmVhY2ggemVybyBhcmVcbiAgICogIGRlbGV0ZWQgdG8ga2VlcCB0aGUgbWFwIGJvdW5kZWQuICovXG4gIHByaXZhdGUgb3JnQ2xpZW50Q291bnRzID0gbmV3IE1hcDxzdHJpbmcsIG51bWJlcj4oKTtcbiAgLyoqIFRpY2tldCArIHN0cmVhbS1vd25lcnNoaXAgYmFja2VuZCAoaW4tbWVtb3J5IGRlZmF1bHQ7IFJlZGlzIGZvciBtdWx0aS1wb2QpLlxuICAgKiAgQm91bmRlZCBieSBgbWF4VG90YWxUaWNrZXRzYCAvIGBtYXhUaWNrZXRzUGVyT3JnYCwgZW5mb3JjZWQgaW4gY3JlYXRlVGlja2V0O1xuICAgKiAgZXhwaXJ5IGlzIHZhbGlkYXRlZCBhdCBjb25zdW1lIHRpbWUgYW5kIHN3ZXB0IG9uIHRoZSBjbGVhbnVwIGludGVydmFsLiAqL1xuICBwcml2YXRlIHJlYWRvbmx5IHRpY2tldFN0b3JlOiBTU0VTdHJlYW1UaWNrZXRTdG9yZTtcbiAgcHJpdmF0ZSByZWFkb25seSBtYXhDbGllbnRzUGVyUmVxdWVzdDogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IG1heFRvdGFsQ2xpZW50czogbnVtYmVyO1xuICBwcml2YXRlIHJlYWRvbmx5IG1heENsaWVudHNQZXJPcmc6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBtYXhUb3RhbFRpY2tldHM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBtYXhUaWNrZXRzUGVyT3JnOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgdGlja2V0VHRsTXM6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSBzdHJlYW1Pd25lclR0bE1zOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgY2xpZW50VGltZW91dE1zOiBudW1iZXI7XG4gIHByaXZhdGUgY2xlYW51cEludGVydmFsOiBOb2RlSlMuVGltZW91dCB8IG51bGwgPSBudWxsO1xuICAvKiogQ3Jvc3MtcG9kIGZhbi1vdXQgYnVzICh1bmRlZmluZWQg4oeSIGxvY2FsLW9ubHkgZGVsaXZlcnkpLiAqL1xuICBwcml2YXRlIHJlYWRvbmx5IHJlbGF5PzogU1NFUmVsYXk7XG4gIC8qKiBUaGlzIG1hbmFnZXIgaW5zdGFuY2UncyBpZCDigJQgdGFncyByZWxheWVkIGZyYW1lcyBzbyB3ZSBpZ25vcmUgb3VyIG93biBlY2hvLiAqL1xuICBwcml2YXRlIHJlYWRvbmx5IG5vZGVJZCA9IHJhbmRvbUJ5dGVzKDgpLnRvU3RyaW5nKCdoZXgnKTtcblxuICBjb25zdHJ1Y3RvcihvcHRpb25zOiBTU0VNYW5hZ2VyT3B0aW9ucyA9IHt9KSB7XG4gICAgdGhpcy5tYXhDbGllbnRzUGVyUmVxdWVzdCA9IG9wdGlvbnMubWF4Q2xpZW50c1BlclJlcXVlc3QgPz8gcGFyc2VJbnQocHJvY2Vzcy5lbnYuU1NFX01BWF9DTElFTlRTX1BFUl9SRVFVRVNUIHx8ICcxMCcsIDEwKTtcbiAgICB0aGlzLm1heFRvdGFsQ2xpZW50cyA9IG9wdGlvbnMubWF4VG90YWxDbGllbnRzID8/IHBhcnNlSW50KHByb2Nlc3MuZW52LlNTRV9NQVhfVE9UQUxfQ0xJRU5UUyB8fCAnMTAwMCcsIDEwKTtcbiAgICB0aGlzLm1heENsaWVudHNQZXJPcmcgPSBvcHRpb25zLm1heENsaWVudHNQZXJPcmcgPz8gcGFyc2VJbnQocHJvY2Vzcy5lbnYuU1NFX01BWF9DTElFTlRTX1BFUl9PUkcgfHwgJzUwJywgMTApO1xuICAgIHRoaXMubWF4VG90YWxUaWNrZXRzID0gb3B0aW9ucy5tYXhUb3RhbFRpY2tldHMgPz8gcGFyc2VJbnQocHJvY2Vzcy5lbnYuU1NFX01BWF9UT1RBTF9USUNLRVRTIHx8ICcxMDAwJywgMTApO1xuICAgIHRoaXMubWF4VGlja2V0c1Blck9yZyA9IG9wdGlvbnMubWF4VGlja2V0c1Blck9yZyA/PyBwYXJzZUludChwcm9jZXNzLmVudi5TU0VfTUFYX1RJQ0tFVFNfUEVSX09SRyB8fCAnMTAnLCAxMCk7XG4gICAgdGhpcy50aWNrZXRUdGxNcyA9IG9wdGlvbnMudGlja2V0VHRsTXMgPz8gU1NFX1RJQ0tFVF9UVExfTVM7XG4gICAgdGhpcy5zdHJlYW1Pd25lclR0bE1zID0gb3B0aW9ucy5zdHJlYW1Pd25lclR0bE1zID8/IHBhcnNlSW50KHByb2Nlc3MuZW52LlNTRV9TVFJFQU1fT1dORVJfVFRMX01TIHx8ICczNjAwMDAwJywgMTApOyAvLyAxIGhvdXJcbiAgICB0aGlzLnRpY2tldFN0b3JlID0gb3B0aW9ucy50aWNrZXRTdG9yZSA/PyBjcmVhdGVNZW1vcnlUaWNrZXRTdG9yZSgpO1xuICAgIHRoaXMuY2xpZW50VGltZW91dE1zID0gb3B0aW9ucy5jbGllbnRUaW1lb3V0TXMgPz8gcGFyc2VJbnQocHJvY2Vzcy5lbnYuU1NFX0NMSUVOVF9USU1FT1VUX01TIHx8ICcxODAwMDAwJywgMTApOyAvLyAzMCBtaW51dGVzXG5cbiAgICBjb25zdCBjbGVhbnVwSW50ZXJ2YWxNcyA9IG9wdGlvbnMuY2xlYW51cEludGVydmFsTXMgPz8gcGFyc2VJbnQocHJvY2Vzcy5lbnYuU1NFX0NMRUFOVVBfSU5URVJWQUxfTVMgfHwgJzMwMDAwMCcsIDEwKTsgLy8gNSBtaW51dGVzXG4gICAgdGhpcy5zdGFydENsZWFudXBJbnRlcnZhbChjbGVhbnVwSW50ZXJ2YWxNcyk7XG5cbiAgICAvLyBXaXJlIHRoZSBjcm9zcy1wb2QgcmVsYXk6IHN1YnNjcmliZSBvbmNlIG9uIHN0YXJ0dXAgc28gZnJhbWVzIFBVQkxJU0hFRCBieVxuICAgIC8vIGFueSBvdGhlciBwb2QgYXJlIHJlLWVtaXR0ZWQgdG8gVEhJUyBwb2QncyBsb2NhbCBjbGllbnRzLiBGcmFtZXMgd2VcbiAgICAvLyBwdWJsaXNoZWQgb3Vyc2VsdmVzIGFyZSBpZ25vcmVkIChvcmlnaW4gPT09IG5vZGVJZCkg4oCUIHdlIGFscmVhZHkgd3JvdGUgdGhlbVxuICAgIC8vIGxvY2FsbHkuIEZhaWwtc2FmZTogbm8gcmVsYXkg4oeSIGxvY2FsLW9ubHkgZGVsaXZlcnkgKHNpbmdsZS1yZXBsaWNhIGRlZmF1bHQpLlxuICAgIHRoaXMucmVsYXkgPSBvcHRpb25zLnJlbGF5O1xuICAgIHRoaXMucmVsYXk/LnN1YnNjcmliZSgobXNnKSA9PiB0aGlzLm9uUmVsYXlNZXNzYWdlKG1zZykpO1xuICB9XG5cbiAgLyoqXG4gICAqIEhhbmRsZSBhIGZyYW1lIHJlY2VpdmVkIGZyb20gYW5vdGhlciBwb2QgdmlhIHRoZSByZWxheS4gV2UgYWxyZWFkeSBkZWxpdmVyZWRcbiAgICogb3VyIE9XTiBmcmFtZXMgdG8gbG9jYWwgY2xpZW50cyBiZWZvcmUgcHVibGlzaGluZywgc28gaWdub3JlIG91ciBlY2hvLiBBXG4gICAqIGBzZW5kYCByZS1lbWl0cyB0byB0aGUgc3ViamVjdCdzIGxvY2FsIGNsaWVudHM7IGEgYGJyb2FkY2FzdGAgdG8gYWxsIG9mIHRoZW0uXG4gICAqIFB1cmVseSBsb2NhbCDigJQgbmV2ZXIgcmUtcHVibGlzaGVzLCBzbyB0aGVyZSBpcyBubyBmYW4tb3V0IGxvb3AuXG4gICAqL1xuICBwcml2YXRlIG9uUmVsYXlNZXNzYWdlKG1zZzogU1NFUmVsYXlNZXNzYWdlKTogdm9pZCB7XG4gICAgaWYgKCFtc2cgfHwgbXNnLm9yaWdpbiA9PT0gdGhpcy5ub2RlSWQpIHJldHVybjtcbiAgICBpZiAobXNnLmtpbmQgPT09ICdicm9hZGNhc3QnKSB7XG4gICAgICB0aGlzLmJyb2FkY2FzdExvY2FsKG1zZy5wYXlsb2FkKTtcbiAgICB9IGVsc2UgaWYgKG1zZy5yZXF1ZXN0SWQpIHtcbiAgICAgIHRoaXMuc2VuZExvY2FsKG1zZy5yZXF1ZXN0SWQsIG1zZy5wYXlsb2FkKTtcbiAgICB9XG4gIH1cblxuICAvKiogVG90YWwgb3BlbiBjb25uZWN0aW9ucyBhY3Jvc3MgYWxsIHJlcXVlc3RzLiAqL1xuICBwcml2YXRlIHRvdGFsQ2xpZW50cygpOiBudW1iZXIge1xuICAgIGxldCBuID0gMDtcbiAgICBmb3IgKGNvbnN0IGNsaWVudHMgb2YgdGhpcy5jbGllbnRzLnZhbHVlcygpKSBuICs9IGNsaWVudHMubGVuZ3RoO1xuICAgIHJldHVybiBuO1xuICB9XG5cbiAgLyoqXG4gICAqIFB1Ymxpc2ggdGhlIGN1cnJlbnQgbGl2ZS1jb25uZWN0aW9uIGNvdW50IGFzIGEgZ2F1Z2Ugc28gb24tY2FsbCBjYW4gc2VlIFNTRVxuICAgKiBzYXR1cmF0aW9uIChhcHByb2FjaCB0byB0aGUgcGVyLXByb2Nlc3MgY2FwKSBvbiBhIGRhc2hib2FyZCDigJQgcHJldmlvdXNseVxuICAgKiB0aGVzZSBudW1iZXJzIGxpdmVkIG9ubHkgaW4gYGdldFN0YXRzKClgL2xvZ3MuIENhbGxlZCBhZnRlciBldmVyeSBhZGQvcmVtb3ZlL1xuICAgKiBjbG9zZS9jbGVhbnVwLiBDaGVhcCAoTyhSKSk7IG1ldHJpYyBoZWxwZXJzIG5ldmVyIHRocm93LlxuICAgKi9cbiAgcHJpdmF0ZSB1cGRhdGVBY3RpdmVHYXVnZSgpOiB2b2lkIHtcbiAgICBzZXRHYXVnZSgnc3NlX2FjdGl2ZV9jb25uZWN0aW9ucycsIHt9LCB0aGlzLnRvdGFsQ2xpZW50cygpKTtcbiAgfVxuXG4gIC8qKiBDdXJyZW50IG9wZW4tc3RyZWFtIGNvdW50IGZvciBhbiBvcmcgKDAgaWYgdW5zZWVuKS4gKi9cbiAgZ2V0T3JnQ2xpZW50Q291bnQob3JnSWQ6IHN0cmluZyk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMub3JnQ2xpZW50Q291bnRzLmdldChvcmdJZCkgPz8gMDtcbiAgfVxuXG4gIC8qKlxuICAgKiBDYW5vbmljYWwgZm9ybSBvZiBhIHJlcXVlc3RJZCBmb3IgZXF1YWxpdHkgY2hlY2tzOiBkYXNoZXMgc3RyaXBwZWQgYW5kXG4gICAqIGxvd2VyY2FzZWQsIHNvIHRoZSBkYXNoZWQgKGFwaS1zZXJ2ZXIgYHV1aWQoKWApIGFuZCB1bmRhc2hlZCAobmdpbnhcbiAgICogYCRyZXF1ZXN0X2lkYCkgcmVuZGVyaW5ncyBvZiB0aGUgc2FtZSBpZCBjb21wYXJlIGVxdWFsLiBDYWxsZXJzIGFyZVxuICAgKiBleHBlY3RlZCB0byBoYXZlIGFscmVhZHkgZm9ybWF0LXZhbGlkYXRlZCBhZ2FpbnN0IHtAbGluayBTU0VfUkVRVUVTVF9JRF9SRX0uXG4gICAqL1xuICBwcml2YXRlIHN0YXRpYyBub3JtYWxpemVSZXF1ZXN0SWQocmVxdWVzdElkOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiByZXF1ZXN0SWQucmVwbGFjZSgvLS9nLCAnJykudG9Mb3dlckNhc2UoKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDYW5vbmljYWwgZm9ybSBvZiBhbiBvcmdJZCBmb3Igb3duZXJzaGlwIGVxdWFsaXR5IGNoZWNrczogdHJpbW1lZCBhbmRcbiAgICogbG93ZXJjYXNlZC4gVGhlIHN0cmVhbSBQUk9EVUNFUiAoYmluZFN0cmVhbU93bmVyKSBhbmQgdGhlIHRpY2tldC1taW50aW5nXG4gICAqIENPTlNVTUVSIChjcmVhdGVUaWNrZXQpIHJ1biBpbiBkaWZmZXJlbnQgc2VydmljZXMsIHNvIGEgY2FzaW5nIC8gd2hpdGVzcGFjZVxuICAgKiBkcmlmdCBiZXR3ZWVuIGhvdyBlYWNoIHJlbmRlcnMgdGhlIHNhbWUgb3JnIHdvdWxkIG90aGVyd2lzZSBtYWtlIHRoZSBvd25lclxuICAgKiBjb21wYXJpc29uIGluIHtAbGluayBjcmVhdGVUaWNrZXR9IGZhbHNlLWBmb3JiaWRkZW5gIHRoZSByZWFsIG93bmVyLlxuICAgKiBOb3JtYWxpemluZyBib3RoIHNpZGVzIOKAlCBtaXJyb3Jpbmcge0BsaW5rIG5vcm1hbGl6ZVJlcXVlc3RJZH0g4oCUIGNsb3NlcyB0aGF0LlxuICAgKi9cbiAgcHJpdmF0ZSBzdGF0aWMgbm9ybWFsaXplT3JnSWQob3JnSWQ6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgcmV0dXJuIG9yZ0lkLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlY29yZCB0aGUgb3JnIHRoYXQgT1dOUyBhIHN0cmVhbSBzdWJqZWN0LiBUaGUgc3RyZWFtIFBST0RVQ0VSIGNhbGxzIHRoaXNcbiAgICogd2hlbiBpdCBjcmVhdGVzIGEgYnVpbGQtbG9nIHN0cmVhbSBzbyB0aGF0IHRpY2tldCBtaW50aW5nIGNhbiBhc3NlcnQgdGhlXG4gICAqIGNhbGxlcidzIG9yZyBvd25zIHRoZSBzdWJqZWN0IChzZWUge0BsaW5rIGNyZWF0ZVRpY2tldH0pLiBCYWNrZWQgYnkgdGhlXG4gICAqIGluamVjdGVkIHRpY2tldCBzdG9yZSwgc28gd2hlbiBhIFJlZGlzIHN0b3JlIGlzIHdpcmVkIHRoZSBwbGF0Zm9ybSBwcm9kdWNlclxuICAgKiAoYSBkaWZmZXJlbnQgc2VydmljZSBzaGFyaW5nIHRoZSBzYW1lIFJlZGlzKSBjYW4gYmluZCBvd25lcnNoaXAgdGhhdCB0aGlzXG4gICAqIHNlcnZpY2UgcmVhZHMg4oCUIGNsb3NpbmcgdGhlIGNyb3NzLXRlbmFudCBhdHRhY2ggZ2FwIHdoZXJlIGFueSBvcmcgY291bGQgbWludFxuICAgKiBhIHRpY2tldCBmb3IgYSBndWVzc2VkIHJlcXVlc3RJZC5cbiAgICpcbiAgICogQHBhcmFtIHJlcXVlc3RJZCAtIFRoZSBzdHJlYW0gc3ViamVjdCAoZm9ybWF0LXZhbGlkYXRlZCBieSB0aGUgY2FsbGVyKS5cbiAgICogQHBhcmFtIG9yZ0lkIC0gVGhlIG93bmluZyBvcmcgKG5vcm1hbGl6ZWQgaW50ZXJuYWxseSB2aWEgbm9ybWFsaXplT3JnSWQpLlxuICAgKi9cbiAgYXN5bmMgYmluZFN0cmVhbU93bmVyKHJlcXVlc3RJZDogc3RyaW5nLCBvcmdJZDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgYXdhaXQgdGhpcy50aWNrZXRTdG9yZS5iaW5kU3RyZWFtT3duZXIoXG4gICAgICBTU0VNYW5hZ2VyLm5vcm1hbGl6ZVJlcXVlc3RJZChyZXF1ZXN0SWQpLFxuICAgICAgU1NFTWFuYWdlci5ub3JtYWxpemVPcmdJZChvcmdJZCksXG4gICAgICB0aGlzLnN0cmVhbU93bmVyVHRsTXMsXG4gICAgKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBNaW50IGEgc2hvcnQtbGl2ZWQsIHNpbmdsZS11c2UgU1NFIHRpY2tldCBib3VuZCB0byBgb3JnSWRgIEFORCBhIHNwZWNpZmljXG4gICAqIHN0cmVhbSBzdWJqZWN0IChgcmVxdWVzdElkYCkuIENsaWVudHMgUE9TVCB0byBvYnRhaW4gb25lIChKV1QtYXV0aGVudGljYXRlZClcbiAgICogZm9yIHRoZSBleGFjdCBzdHJlYW0gdGhleSBpbnRlbmQgdG8gb3BlbiwgdGhlbiBvcGVuIHRoZSBFdmVudFNvdXJjZSB3aXRoXG4gICAqIGA/dGlja2V0PTx0PmAgc28gdGhlIEpXVCBuZXZlciBsYW5kcyBpbiBhIHF1ZXJ5IHN0cmluZyAvIGFjY2VzcyBsb2cuIFRoZVxuICAgKiB0aWNrZXQgY2FuIHRoZW4gb25seSBiZSBjb25zdW1lZCB0byBvcGVuIHRoYXQgb25lIHN1YmplY3QncyBzdHJlYW0g4oCUIHNlZVxuICAgKiB7QGxpbmsgY29uc3VtZVRpY2tldH0g4oCUIHdoaWNoIGlzIHdoYXQgZW5mb3JjZXMgcGVyLXN1YmplY3QgYXV0aG9yaXphdGlvbi5cbiAgICpcbiAgICogT1JHLU9XTkVSU0hJUDogaWYgYSBzdHJlYW0gb3duZXIgaGFzIGJlZW4gYm91bmQgZm9yIHRoaXMgc3ViamVjdCAodmlhXG4gICAqIHtAbGluayBiaW5kU3RyZWFtT3duZXJ9KSBhbmQgaXQgaXMgYSBESUZGRVJFTlQgb3JnLCBtaW50aW5nIGlzIHJlZnVzZWRcbiAgICogKGByZWFzb246ICdmb3JiaWRkZW4nYCkg4oCUIGFuIG9yZyBjYW5ub3QgbWludCBhIHRpY2tldCBmb3IgYW5vdGhlciBvcmcnc1xuICAgKiBzdHJlYW0gZXZlbiBpZiBpdCBndWVzc2VzIHRoZSByZXF1ZXN0SWQuIFdoZW4gbm8gb3duZXIgaXMgYm91bmQgKHByb2R1Y2VyXG4gICAqIHdpcmluZyBub3QgeWV0IHByZXNlbnQpLCBtaW50aW5nIGZhbGxzIGJhY2sgdG8gYmluZGluZyB0aGUgdGlja2V0IHRvIHRoZVxuICAgKiBjYWxsZXIncyBvd24gb3JnLCBwcmVzZXJ2aW5nIGN1cnJlbnQgYmVoYXZpb3IuXG4gICAqXG4gICAqIEJvdW5kZWQgYnkgYG1heFRvdGFsVGlja2V0c2AgKHByb2Nlc3Mtd2lkZSkgYW5kIGBtYXhUaWNrZXRzUGVyT3JnYFxuICAgKiAocGVyLXRlbmFudCkuIE1pcnJvcnMgdGhlIG1lc3NhZ2Utc2VydmljZSBub3RpZmljYXRpb25zIHRpY2tldCBzdG9yZS5cbiAgICpcbiAgICogQHBhcmFtIG9yZ0lkIC0gT3duaW5nIG9yZyAobm9ybWFsaXplZCBpbnRlcm5hbGx5IHZpYSBub3JtYWxpemVPcmdJZCkuXG4gICAqIEBwYXJhbSByZXF1ZXN0SWQgLSBUaGUgYnVpbGQtbG9nIHN0cmVhbSBzdWJqZWN0IHRoaXMgdGlja2V0IGF1dGhvcml6ZXMuXG4gICAqICAgVGhlIGNhbGxlciBtdXN0IGhhdmUgZm9ybWF0LXZhbGlkYXRlZCBpdCAoc2VlIHtAbGluayBTU0VfUkVRVUVTVF9JRF9SRX0pLlxuICAgKiBAcmV0dXJucyBgeyBvazogdHJ1ZSwgdGlja2V0IH1gIG9uIHN1Y2Nlc3MsIG9yIGB7IG9rOiBmYWxzZSwgcmVhc29uIH1gXG4gICAqICAgd2hlcmUgcmVhc29uIGlzIGAnb3JnLWxpbWl0J2AsIGAnY2FwYWNpdHknYCwgb3IgYCdmb3JiaWRkZW4nYC5cbiAgICovXG4gIGFzeW5jIGNyZWF0ZVRpY2tldChvcmdJZDogc3RyaW5nLCByZXF1ZXN0SWQ6IHN0cmluZyk6IFByb21pc2U8Q3JlYXRlVGlja2V0UmVzdWx0PiB7XG4gICAgLy8gT3duZXJzaGlwIGdhdGUgZmlyc3Qg4oCUIGEgY3Jvc3MtdGVuYW50IG1pbnQgYXR0ZW1wdCBzaG91bGQgbmV2ZXIgZXZlblxuICAgIC8vIGNvbnN1bWUgY2FwIGJ1ZGdldC4gT25seSBlbmZvcmNlZCB3aGVuIGFuIG93bmVyIGlzIGFjdHVhbGx5IGJvdW5kLiBCb3RoXG4gICAgLy8gc2lkZXMgYXJlIG9yZy1ub3JtYWxpemVkIHNvIGEgcHJvZHVjZXIvY29uc3VtZXIgY2FzaW5nIGRyaWZ0IGNhbid0XG4gICAgLy8gZmFsc2UtYGZvcmJpZGRlbmAgdGhlIHJlYWwgb3duZXIgKG1pcnJvcnMgcmVxdWVzdElkIG5vcm1hbGl6YXRpb24pLlxuICAgIGNvbnN0IG5vcm1hbGl6ZWQgPSBTU0VNYW5hZ2VyLm5vcm1hbGl6ZVJlcXVlc3RJZChyZXF1ZXN0SWQpO1xuICAgIGNvbnN0IG5vcm1hbGl6ZWRPcmcgPSBTU0VNYW5hZ2VyLm5vcm1hbGl6ZU9yZ0lkKG9yZ0lkKTtcbiAgICBjb25zdCBvd25lciA9IGF3YWl0IHRoaXMudGlja2V0U3RvcmUuZ2V0U3RyZWFtT3duZXIobm9ybWFsaXplZCk7XG4gICAgaWYgKG93bmVyICYmIG93bmVyICE9PSBub3JtYWxpemVkT3JnKSB7XG4gICAgICBsb2dnZXIud2FybihgU1NFIHRpY2tldCByZWZ1c2VkOiBvcmcgJHtub3JtYWxpemVkT3JnfSBkb2VzIG5vdCBvd24gc3RyZWFtIHN1YmplY3RgKTtcbiAgICAgIGluY0NvdW50ZXIoJ3NzZV90aWNrZXRfcmVqZWN0ZWRfdG90YWwnLCB7IHJlYXNvbjogJ2ZvcmJpZGRlbicgfSk7XG4gICAgICByZXR1cm4geyBvazogZmFsc2UsIHJlYXNvbjogJ2ZvcmJpZGRlbicgfTtcbiAgICB9XG5cbiAgICAvLyBUb3RhbCBjYXAgbmV4dCDigJQgcHJvdGVjdHMgcHJvY2VzcyBtZW1vcnkgZXZlbiB3aGVuIGEgc2luZ2xlIG9yZyBpc1xuICAgIC8vIHRoZSBvZmZlbmRlci4gVGhlbiB0aGUgcGVyLW9yZyBjYXAgZm9yIGZhaXItc2hhcmUuXG4gICAgaWYgKChhd2FpdCB0aGlzLnRpY2tldFN0b3JlLnRvdGFsKCkpID49IHRoaXMubWF4VG90YWxUaWNrZXRzKSB7XG4gICAgICBsb2dnZXIud2FybihgU1NFIHRpY2tldCBjYXAgcmVhY2hlZCAobWF4OiAke3RoaXMubWF4VG90YWxUaWNrZXRzfSk7IHJlamVjdGluZyB0aWNrZXQgcmVxdWVzdGApO1xuICAgICAgaW5jQ291bnRlcignc3NlX3RpY2tldF9yZWplY3RlZF90b3RhbCcsIHsgcmVhc29uOiAnY2FwYWNpdHknIH0pO1xuICAgICAgcmV0dXJuIHsgb2s6IGZhbHNlLCByZWFzb246ICdjYXBhY2l0eScgfTtcbiAgICB9XG4gICAgaWYgKChhd2FpdCB0aGlzLnRpY2tldFN0b3JlLmNvdW50Rm9yT3JnKG5vcm1hbGl6ZWRPcmcpKSA+PSB0aGlzLm1heFRpY2tldHNQZXJPcmcpIHtcbiAgICAgIGxvZ2dlci53YXJuKGBQZXItb3JnIFNTRSB0aWNrZXQgY2FwIHJlYWNoZWQgZm9yICR7bm9ybWFsaXplZE9yZ30gKG1heDogJHt0aGlzLm1heFRpY2tldHNQZXJPcmd9KWApO1xuICAgICAgaW5jQ291bnRlcignc3NlX3RpY2tldF9yZWplY3RlZF90b3RhbCcsIHsgcmVhc29uOiAnb3JnLWxpbWl0JyB9KTtcbiAgICAgIHJldHVybiB7IG9rOiBmYWxzZSwgcmVhc29uOiAnb3JnLWxpbWl0JyB9O1xuICAgIH1cblxuICAgIGNvbnN0IHRpY2tldCA9IHJhbmRvbUJ5dGVzKDI0KS50b1N0cmluZygnYmFzZTY0dXJsJyk7XG4gICAgYXdhaXQgdGhpcy50aWNrZXRTdG9yZS5wdXQodGlja2V0LCB7IG9yZ0lkOiBub3JtYWxpemVkT3JnLCByZXF1ZXN0SWQ6IG5vcm1hbGl6ZWQgfSwgdGhpcy50aWNrZXRUdGxNcyk7XG4gICAgcmV0dXJuIHsgb2s6IHRydWUsIHRpY2tldCB9O1xuICB9XG5cbiAgLyoqXG4gICAqIFZhbGlkYXRlIGFuZCBDT05TVU1FIGEgdGlja2V0IGZvciBhIHNwZWNpZmljIHN0cmVhbSBzdWJqZWN0LiBTaW5nbGUtdXNlOlxuICAgKiB0aGUgdGlja2V0IGlzIGRlbGV0ZWQgd2hldGhlciBvciBub3QgaXQgdHVybnMgb3V0IHRvIGJlIHZhbGlkLCBzbyBhIHJlcGxheVxuICAgKiBvZiB0aGUgc2FtZSB2YWx1ZSBhbHdheXMgZmFpbHMuIFJldHVybnMgdGhlIGJvdW5kIG9yZyBvbiBzdWNjZXNzLCBvciBudWxsXG4gICAqIHdoZW4gdGhlIHRpY2tldCBpcyB1bmtub3duIC8gYWxyZWFkeS11c2VkIC8gZXhwaXJlZCAvIE9SIHdhcyBtaW50ZWQgZm9yIGFcbiAgICogRElGRkVSRU5UIGByZXF1ZXN0SWRgIHRoYW4gdGhlIG9uZSBiZWluZyBvcGVuZWQuXG4gICAqXG4gICAqIFRoZSBzdWJqZWN0IGNoZWNrIGlzIHRoZSBhdXRob3JpemF0aW9uIGZpeDogd2l0aG91dCBpdCwgYSB0aWNrZXQgbWludGVkIGZvclxuICAgKiBvbmUgc3RyZWFtIGNvdWxkIGJlIHByZXNlbnRlZCB0byBvcGVuIEFOWSBzdHJlYW0gdGhlIGhvbGRlcidzIG9yZyBjb3VsZFxuICAgKiBuYW1lLCBsZXR0aW5nIGFuIGF1dGhlbnRpY2F0ZWQgb3JnIGF0dGFjaCB0byBhbm90aGVyIG9yZydzIGxvZyBzdHJlYW0gYnlcbiAgICogZ3Vlc3NpbmcgaXRzIHJlcXVlc3RJZC4gVGhlIGdlbmVyaWMgNDAxIHRoZSBjYWxsZXIgcmV0dXJucyBkb2VzIG5vdFxuICAgKiBkaXN0aW5ndWlzaCBcInVua25vd24gdGlja2V0XCIgZnJvbSBcIndyb25nIHN1YmplY3RcIiwgc28gaXQgbGVha3Mgbm90aGluZ1xuICAgKiBhYm91dCB3aGljaCByZXF1ZXN0SWRzIGV4aXN0LlxuICAgKlxuICAgKiBAcGFyYW0gdGlja2V0SWQgLSBUaGUgb3BhcXVlIHRpY2tldCB2YWx1ZSBmcm9tIGA/dGlja2V0PWAuXG4gICAqIEBwYXJhbSByZXF1ZXN0SWQgLSBUaGUgc3RyZWFtIHN1YmplY3QgZnJvbSB0aGUgVVJMIHBhdGggKGA6cmVxdWVzdElkYCksXG4gICAqICAgYWxyZWFkeSBmb3JtYXQtdmFsaWRhdGVkIGJ5IHRoZSBjYWxsZXIuXG4gICAqL1xuICBhc3luYyBjb25zdW1lVGlja2V0KHRpY2tldElkOiBzdHJpbmcsIHJlcXVlc3RJZDogc3RyaW5nKTogUHJvbWlzZTx7IG9yZ0lkOiBzdHJpbmcgfSB8IG51bGw+IHtcbiAgICBjb25zdCB0aWNrZXQgPSBhd2FpdCB0aGlzLnRpY2tldFN0b3JlLmNvbnN1bWUodGlja2V0SWQpOyAvLyBzaW5nbGUtdXNlXG4gICAgaWYgKCF0aWNrZXQpIHJldHVybiBudWxsO1xuICAgIC8vIFN1YmplY3QgYmluZGluZyDigJQgcmVqZWN0IGEgdGlja2V0IHByZXNlbnRlZCBmb3IgYSBzdWJqZWN0IGl0IHdhcyBub3RcbiAgICAvLyBtaW50ZWQgZm9yLiBCb3RoIHNpZGVzIGFyZSBub3JtYWxpemVkIHNvIGRhc2hlZC91bmRhc2hlZCBmb3JtcyBtYXRjaC5cbiAgICBpZiAodGlja2V0LnJlcXVlc3RJZCAhPT0gU1NFTWFuYWdlci5ub3JtYWxpemVSZXF1ZXN0SWQocmVxdWVzdElkKSkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIHsgb3JnSWQ6IHRpY2tldC5vcmdJZCB9O1xuICB9XG5cbiAgLyoqXG4gICAqIEFkZHMgYSBjbGllbnQgdG8gdGhlIFNTRSBtYW5hZ2VyXG4gICAqXG4gICAqIEBwYXJhbSByZXF1ZXN0SWQgLSBVbmlxdWUgcmVxdWVzdCBJRFxuICAgKiBAcGFyYW0gcmVzIC0gRXhwcmVzcyBSZXNwb25zZSBvYmplY3RcbiAgICogQHBhcmFtIG9yZ0lkIC0gQXV0aGVudGljYXRlZCBvcmcgaWQgKG9wdGlvbmFsKS4gV2hlbiBzZXQsIGVuZm9yY2VzIHRoZVxuICAgKiAgIHBlci1vcmcgY2FwIGFuZCB0aGUgY291bnRlciBpcyBkZWNyZW1lbnRlZCBvbiBkaXNjb25uZWN0L2NsZWFudXAuXG4gICAqIEByZXR1cm5zIHRydWUgaWYgY2xpZW50IHdhcyBhZGRlZCwgZmFsc2UgaWYgcmVqZWN0ZWQgKGxpbWl0IHJlYWNoZWQpXG4gICAqL1xuICBhZGRDbGllbnQocmVxdWVzdElkOiBzdHJpbmcsIHJlczogUmVzcG9uc2UsIG9yZ0lkPzogc3RyaW5nKTogYm9vbGVhbiB7XG4gICAgLy8gTm9ybWFsaXplIHRoZSBtYXAga2V5OiB0aGUgc3ViamVjdCBpZCBpcyBhbGxvd2VkIGluIHR3byBmb3JtcyAoZGFzaGVkXG4gICAgLy8gYXBpLXNlcnZlciB1dWlkIHZzIHVuZGFzaGVkIG5naW54ICRyZXF1ZXN0X2lkKSwgYW5kIGEgcHJvZHVjZXIvY29uc3VtZXIgdGhhdFxuICAgIC8vIHJlbmRlciBpdCBkaWZmZXJlbnRseSB3b3VsZCBvdGhlcndpc2Uga2V5IGRpZmZlcmVudCBNYXAgZW50cmllcyDigJQgdGhlIGNsaWVudFxuICAgIC8vIHJlZ2lzdGVycyB1bmRlciBvbmUgYW5kIGV2ZXJ5IHNlbmQoKS9yZWxheSB3cml0ZXMgdG8gYW5vdGhlciwgZHJvcHBpbmcgYWxsXG4gICAgLy8gZnJhbWVzLiBOb3JtYWxpemluZyBoZXJlIChhbmQgaW4gc2VuZC9zZW5kTG9jYWwvY2xvc2VSZXF1ZXN0L2V0Yy4pIHVuaWZpZXMgdGhlbS5cbiAgICByZXF1ZXN0SWQgPSBTU0VNYW5hZ2VyLm5vcm1hbGl6ZVJlcXVlc3RJZChyZXF1ZXN0SWQpO1xuICAgIGNvbnN0IGV4aXN0aW5nID0gdGhpcy5jbGllbnRzLmdldChyZXF1ZXN0SWQpIHx8IFtdO1xuXG4gICAgLy8gQ2hlY2sgY2xpZW50IGxpbWl0XG4gICAgaWYgKGV4aXN0aW5nLmxlbmd0aCA+PSB0aGlzLm1heENsaWVudHNQZXJSZXF1ZXN0KSB7XG4gICAgICBsb2dnZXIud2FybihgQ2xpZW50IGxpbWl0IHJlYWNoZWQgZm9yIHJlcXVlc3QgJHtyZXF1ZXN0SWR9IChtYXg6ICR7dGhpcy5tYXhDbGllbnRzUGVyUmVxdWVzdH0pYCk7XG4gICAgICBpbmNDb3VudGVyKCdzc2VfY29ubmVjdGlvbl9yZWplY3RlZF90b3RhbCcsIHsgcmVhc29uOiAncmVxdWVzdC1saW1pdCcgfSk7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgLy8gUHJvY2Vzcy13aWRlIGNhcDogcHJvdGVjdHMgZmQgdGFibGUgKyBtZW1vcnkgZnJvbSBydW5hd2F5IGRhc2hib2FyZHMuXG4gICAgaWYgKHRoaXMudG90YWxDbGllbnRzKCkgPj0gdGhpcy5tYXhUb3RhbENsaWVudHMpIHtcbiAgICAgIGxvZ2dlci53YXJuKGBUb3RhbCBTU0UgY2xpZW50IGNhcCByZWFjaGVkIChtYXg6ICR7dGhpcy5tYXhUb3RhbENsaWVudHN9KTsgcmVqZWN0aW5nIG5ldyBjb25uZWN0aW9uYCk7XG4gICAgICBpbmNDb3VudGVyKCdzc2VfY29ubmVjdGlvbl9yZWplY3RlZF90b3RhbCcsIHsgcmVhc29uOiAndG90YWwtY2FwJyB9KTtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG5cbiAgICAvLyBQZXItb3JnIGNhcDogcHJldmVudHMgb25lIG5vaXN5IG9yZyBmcm9tIG1vbm9wb2xpemluZyBwcm9jZXNzLXdpZGVcbiAgICAvLyBzbG90cy4gRW5mb3JjZWQgb25seSB3aGVuIG9yZ0lkIGlzIGtub3duIOKAlCBzZXJ2aWNlLWludGVybmFsIC8gYW5vbnltb3VzXG4gICAgLy8gc3RyZWFtcyB0aGF0IG5ldmVyIGlkZW50aWZ5IHdpdGggYW4gb3JnSWQgc3RheSBzdWJqZWN0IHRvIHRoZSBnbG9iYWxcbiAgICAvLyBjZWlsaW5ncyBhYm92ZS4gUmVzZXJ2ZSBhdG9taWNhbGx5IGJlZm9yZSBpbnNlcnQgc28gdHdvIGNvbmN1cnJlbnRcbiAgICAvLyByZXF1ZXN0cyBhdCB0aGUgbGltaXQgY2FuJ3QgYm90aCBzdWNjZWVkLlxuICAgIGlmIChvcmdJZCkge1xuICAgICAgY29uc3Qgb3JnQ3VycmVudCA9IHRoaXMub3JnQ2xpZW50Q291bnRzLmdldChvcmdJZCkgPz8gMDtcbiAgICAgIGlmIChvcmdDdXJyZW50ID49IHRoaXMubWF4Q2xpZW50c1Blck9yZykge1xuICAgICAgICBsb2dnZXIud2FybihgUGVyLW9yZyBTU0UgY2xpZW50IGNhcCByZWFjaGVkIGZvciAke29yZ0lkfSAobWF4OiAke3RoaXMubWF4Q2xpZW50c1Blck9yZ30pOyByZWplY3RpbmcgbmV3IGNvbm5lY3Rpb25gKTtcbiAgICAgICAgaW5jQ291bnRlcignc3NlX2Nvbm5lY3Rpb25fcmVqZWN0ZWRfdG90YWwnLCB7IHJlYXNvbjogJ29yZy1jYXAnIH0pO1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgICB0aGlzLm9yZ0NsaWVudENvdW50cy5zZXQob3JnSWQsIG9yZ0N1cnJlbnQgKyAxKTtcbiAgICB9XG5cbiAgICAvLyBDcmVhdGUgdGltZW91dCBmb3IgdGhpcyBjbGllbnRcbiAgICBjb25zdCBjbGllbnRJZCA9IHV1aWQoKTtcbiAgICBjb25zdCB0aW1lb3V0ID0gc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICBsb2dnZXIuZGVidWcoYENsaWVudCAke2NsaWVudElkfSB0aW1lZCBvdXQgZm9yIHJlcXVlc3QgJHtyZXF1ZXN0SWR9YCk7XG4gICAgICB0aGlzLnJlbW92ZUNsaWVudChyZXF1ZXN0SWQsIGNsaWVudElkKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJlcy5lbmQoKTtcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2dnZXIuZGVidWcoJ1Jlc3BvbnNlIGFscmVhZHkgY2xvc2VkIG9uIHRpbWVvdXQnLCB7IHJlcXVlc3RJZCwgY2xpZW50SWQsIGVycm9yOiBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogU3RyaW5nKGVycikgfSk7XG4gICAgICB9XG4gICAgfSwgdGhpcy5jbGllbnRUaW1lb3V0TXMpO1xuXG4gICAgY29uc3QgY2xpZW50OiBTU0VDbGllbnQgPSB7XG4gICAgICBpZDogY2xpZW50SWQsXG4gICAgICByZXMsXG4gICAgICBjb25uZWN0ZWRBdDogRGF0ZS5ub3coKSxcbiAgICAgIHRpbWVvdXQsXG4gICAgICBiYWNrcHJlc3N1cmVDb3VudDogMCxcbiAgICAgIG9yZ0lkLFxuICAgIH07XG5cbiAgICAvLyBIYW5kbGUgZGlzY29ubmVjdGlvblxuICAgIHJlcy5vbignY2xvc2UnLCAoKSA9PiB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgICB0aGlzLnJlbW92ZUNsaWVudChyZXF1ZXN0SWQsIGNsaWVudElkKTtcbiAgICB9KTtcblxuICAgIHJlcy5vbignZXJyb3InLCAoZXJyKSA9PiB7XG4gICAgICBsb2dnZXIuZXJyb3IoYFNTRSBjbGllbnQgZXJyb3IgZm9yIHJlcXVlc3QgJHtyZXF1ZXN0SWR9OmAsIGVycik7XG4gICAgICBjbGVhclRpbWVvdXQodGltZW91dCk7XG4gICAgICB0aGlzLnJlbW92ZUNsaWVudChyZXF1ZXN0SWQsIGNsaWVudElkKTtcbiAgICB9KTtcblxuICAgIGV4aXN0aW5nLnB1c2goY2xpZW50KTtcbiAgICB0aGlzLmNsaWVudHMuc2V0KHJlcXVlc3RJZCwgZXhpc3RpbmcpO1xuICAgIGluY0NvdW50ZXIoJ3NzZV9jb25uZWN0aW9uc190b3RhbCcsIHt9KTtcbiAgICB0aGlzLnVwZGF0ZUFjdGl2ZUdhdWdlKCk7XG5cbiAgICBsb2dnZXIuZGVidWcoYENsaWVudCAke2NsaWVudElkfSBjb25uZWN0ZWQgZm9yIHJlcXVlc3QgJHtyZXF1ZXN0SWR9ICh0b3RhbDogJHtleGlzdGluZy5sZW5ndGh9KWApO1xuICAgIHJldHVybiB0cnVlO1xuICB9XG5cbiAgLyoqIERyb3Agb25lIGZyb20gdGhlIHBlci1vcmcgY291bnRlci4gSWRlbXBvdGVudDogYSBjb3VudGVyIGF0IDAgc3RheXMgYXQgMFxuICAgKiAgYW5kIHRoZSBvcmcga2V5IGlzIHJlbW92ZWQgZnJvbSB0aGUgbWFwLiAqL1xuICBwcml2YXRlIGRlY3JlbWVudE9yZ0NvdW50KG9yZ0lkPzogc3RyaW5nKTogdm9pZCB7XG4gICAgaWYgKCFvcmdJZCkgcmV0dXJuO1xuICAgIGNvbnN0IGN1cnJlbnQgPSB0aGlzLm9yZ0NsaWVudENvdW50cy5nZXQob3JnSWQpID8/IDA7XG4gICAgaWYgKGN1cnJlbnQgPD0gMSkgdGhpcy5vcmdDbGllbnRDb3VudHMuZGVsZXRlKG9yZ0lkKTtcbiAgICBlbHNlIHRoaXMub3JnQ2xpZW50Q291bnRzLnNldChvcmdJZCwgY3VycmVudCAtIDEpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgYSBjbGllbnQgZnJvbSB0aGUgbWFuYWdlclxuICAgKi9cbiAgcHJpdmF0ZSByZW1vdmVDbGllbnQocmVxdWVzdElkOiBzdHJpbmcsIGNsaWVudElkOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBjb25zdCBjbGllbnRzID0gdGhpcy5jbGllbnRzLmdldChyZXF1ZXN0SWQpO1xuICAgIGlmICghY2xpZW50cykgcmV0dXJuO1xuXG4gICAgY29uc3QgcmVtYWluaW5nID0gY2xpZW50cy5maWx0ZXIoYyA9PiB7XG4gICAgICBpZiAoYy5pZCA9PT0gY2xpZW50SWQpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KGMudGltZW91dCk7XG4gICAgICAgIC8vIE1pcnJvciB0aGUgcGVyLW9yZyBjb3VudGVyIOKAlCBtdXN0IGRlY3JlbWVudCBoZXJlLCBub3QganVzdCBpbiB0aGVcbiAgICAgICAgLy8gZXhwbGljaXQgY2xvc2UgcGF0aHMsIHNpbmNlICdjbG9zZScvJ2Vycm9yJyBldmVudCBoYW5kbGVycyBhcmUgdGhlXG4gICAgICAgIC8vIHByaW1hcnkgZGlzY29ubmVjdCBzaWduYWwgaW4gcHJhY3RpY2UuXG4gICAgICAgIHRoaXMuZGVjcmVtZW50T3JnQ291bnQoYy5vcmdJZCk7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH0pO1xuXG4gICAgaWYgKHJlbWFpbmluZy5sZW5ndGggPT09IDApIHtcbiAgICAgIHRoaXMuY2xpZW50cy5kZWxldGUocmVxdWVzdElkKTtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhgQWxsIGNsaWVudHMgZGlzY29ubmVjdGVkIGZvciByZXF1ZXN0ICR7cmVxdWVzdElkfWApO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmNsaWVudHMuc2V0KHJlcXVlc3RJZCwgcmVtYWluaW5nKTtcbiAgICB9XG4gICAgdGhpcy51cGRhdGVBY3RpdmVHYXVnZSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIFNlbmRzIGEgbWVzc2FnZSB0byBhbGwgU1NFIGNsaWVudHMgZm9yIGEgcmVxdWVzdElkXG4gICAqXG4gICAqIEBwYXJhbSByZXF1ZXN0SWQgLSBSZXF1ZXN0IElEXG4gICAqIEBwYXJhbSB0eXBlIC0gRXZlbnQgdHlwZVxuICAgKiBAcGFyYW0gbWVzc2FnZSAtIE1lc3NhZ2Ugc3RyaW5nXG4gICAqIEBwYXJhbSBkYXRhIC0gT3B0aW9uYWwgYWRkaXRpb25hbCBkYXRhXG4gICAqIEByZXR1cm5zIE51bWJlciBvZiBjbGllbnRzIHRoZSBtZXNzYWdlIHdhcyBzZW50IHRvXG4gICAqL1xuICBzZW5kKHJlcXVlc3RJZDogc3RyaW5nLCB0eXBlOiBTU0VFdmVudFR5cGUsIG1lc3NhZ2U6IHN0cmluZywgZGF0YT86IHVua25vd24pOiBudW1iZXIge1xuICAgIHJlcXVlc3RJZCA9IFNTRU1hbmFnZXIubm9ybWFsaXplUmVxdWVzdElkKHJlcXVlc3RJZCk7IC8vIHVuaWZ5IHByb2R1Y2VyL2NvbnN1bWVyIGtleSBmb3Jtc1xuICAgIGNvbnN0IHBheWxvYWQ6IFNTRVBheWxvYWQgPSB7XG4gICAgICB0czogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgdHlwZSxcbiAgICAgIG1lc3NhZ2UsXG4gICAgICBkYXRhLFxuICAgIH07XG5cbiAgICAvLyBEZWxpdmVyIHRvIExPQ0FMIGNsaWVudHMgZmlyc3QsIHRoZW4gcmVsYXkgdGhlIFNBTUUgcGF5bG9hZCB0byBvdGhlciBwb2RzXG4gICAgLy8gc28gYW4gRXZlbnRTb3VyY2UgYXR0YWNoZWQgb24gYSBkaWZmZXJlbnQgcmVwbGljYSBhbHNvIHJlY2VpdmVzIGl0LiBUaGVcbiAgICAvLyByZWxheSBpcyBmaXJlLWFuZC1mb3JnZXQgYW5kIGZhaWwtc2FmZTogbm8gcmVsYXkgLyBSZWRpcyBkb3duIOKHkiBsb2NhbC1vbmx5LlxuICAgIGNvbnN0IHNlbnRDb3VudCA9IHRoaXMuc2VuZExvY2FsKHJlcXVlc3RJZCwgcGF5bG9hZCk7XG4gICAgdGhpcy5wdWJsaXNoUmVsYXkoeyBvcmlnaW46IHRoaXMubm9kZUlkLCBraW5kOiAnc2VuZCcsIHJlcXVlc3RJZCwgcGF5bG9hZCB9KTtcbiAgICByZXR1cm4gc2VudENvdW50O1xuICB9XG5cbiAgLyoqXG4gICAqIFdyaXRlIGEgcHJlLWJ1aWx0IHBheWxvYWQgdG8gdGhpcyBwb2QncyBMT0NBTCBjbGllbnRzIGZvciBgcmVxdWVzdElkYC4gU2hhcmVkXG4gICAqIGJ5IHtAbGluayBzZW5kfSAobG9jYWwgaGFsZikgYW5kIHRoZSByZWxheSByZS1lbWl0IHBhdGggKHtAbGluayBvblJlbGF5TWVzc2FnZX0pLFxuICAgKiBzbyBhIHJlbGF5ZWQgZnJhbWUgcmV1c2VzIHRoZSBwcm9kdWNlcidzIG9yaWdpbmFsIGB0c2AgcmF0aGVyIHRoYW4gYSBuZXcgb25lLlxuICAgKiBOZXZlciByZWxheXMg4oCUIGNhbGxlcnMgZGVjaWRlIHdoZXRoZXIgdG8gcHVibGlzaC5cbiAgICovXG4gIHByaXZhdGUgc2VuZExvY2FsKHJlcXVlc3RJZDogc3RyaW5nLCBwYXlsb2FkOiBTU0VQYXlsb2FkKTogbnVtYmVyIHtcbiAgICByZXF1ZXN0SWQgPSBTU0VNYW5hZ2VyLm5vcm1hbGl6ZVJlcXVlc3RJZChyZXF1ZXN0SWQpOyAvLyBpZGVtcG90ZW50OyBjb3ZlcnMgdGhlIHJlbGF5IHJlLWVtaXQgcGF0aFxuICAgIGNvbnN0IGNsaWVudHMgPSBbLi4uKHRoaXMuY2xpZW50cy5nZXQocmVxdWVzdElkKSB8fCBbXSldO1xuICAgIGxldCBzZW50Q291bnQgPSAwO1xuICAgIGNvbnN0IHNlcmlhbGl6ZWQgPSBgZGF0YTogJHtKU09OLnN0cmluZ2lmeShwYXlsb2FkKX1cXG5cXG5gO1xuXG4gICAgZm9yIChjb25zdCBjbGllbnQgb2YgY2xpZW50cykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgLy8gQmFja3ByZXNzdXJlOiBza2lwIGNsaWVudHMgd2hvc2Ugd3JpdGUgYnVmZmVyIGlzIGZ1bGxcbiAgICAgICAgaWYgKGNsaWVudC5yZXMud3JpdGFibGVFbmRlZCkge1xuICAgICAgICAgIHRoaXMucmVtb3ZlQ2xpZW50KHJlcXVlc3RJZCwgY2xpZW50LmlkKTtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBjYW5Xcml0ZSA9IGNsaWVudC5yZXMud3JpdGUoc2VyaWFsaXplZCk7XG4gICAgICAgIGlmICghY2FuV3JpdGUpIHtcbiAgICAgICAgICBjbGllbnQuYmFja3ByZXNzdXJlQ291bnQrKztcbiAgICAgICAgICAvLyBEaXNjb25uZWN0IGNsaWVudHMgdGhhdCBjb25zaXN0ZW50bHkgY2FuJ3Qga2VlcCB1cCAoMTAgY29uc2VjdXRpdmUgYmFja3ByZXNzdXJlIGV2ZW50cylcbiAgICAgICAgICBpZiAoY2xpZW50LmJhY2twcmVzc3VyZUNvdW50ID49IENvcmVDb25zdGFudHMuU1NFX0JBQ0tQUkVTU1VSRV9USFJFU0hPTEQpIHtcbiAgICAgICAgICAgIGxvZ2dlci53YXJuKGBEaXNjb25uZWN0aW5nIHNsb3cgY2xpZW50ICR7Y2xpZW50LmlkfSBmb3IgcmVxdWVzdCAke3JlcXVlc3RJZH0gKCR7Y2xpZW50LmJhY2twcmVzc3VyZUNvdW50fSBiYWNrcHJlc3N1cmUgZXZlbnRzKWApO1xuICAgICAgICAgICAgaW5jQ291bnRlcignc3NlX2JhY2twcmVzc3VyZV9kaXNjb25uZWN0c190b3RhbCcsIHt9KTtcbiAgICAgICAgICAgIHRoaXMucmVtb3ZlQ2xpZW50KHJlcXVlc3RJZCwgY2xpZW50LmlkKTtcbiAgICAgICAgICAgIHRyeSB7IGNsaWVudC5yZXMuZW5kKCk7IH0gY2F0Y2ggeyAvKiBhbHJlYWR5IGNsb3NlZCAqLyB9XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgY2xpZW50LmJhY2twcmVzc3VyZUNvdW50ID0gMDsgLy8gUmVzZXQgb24gc3VjY2Vzc2Z1bCB3cml0ZVxuICAgICAgICB9XG4gICAgICAgIHNlbnRDb3VudCsrO1xuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbG9nZ2VyLmVycm9yKGBGYWlsZWQgdG8gc2VuZCB0byBjbGllbnQgJHtjbGllbnQuaWR9OmAsIGVycm9yKTtcbiAgICAgICAgdGhpcy5yZW1vdmVDbGllbnQocmVxdWVzdElkLCBjbGllbnQuaWQpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBzZW50Q291bnQ7XG4gIH1cblxuICAvKipcbiAgICogQnJvYWRjYXN0IGEgbWVzc2FnZSB0byBhbGwgY29ubmVjdGVkIGNsaWVudHMgYWNyb3NzIGFsbCByZXF1ZXN0c1xuICAgKlxuICAgKiBAcGFyYW0gdHlwZSAtIEV2ZW50IHR5cGVcbiAgICogQHBhcmFtIG1lc3NhZ2UgLSBNZXNzYWdlIHN0cmluZ1xuICAgKiBAcGFyYW0gZGF0YSAtIE9wdGlvbmFsIGFkZGl0aW9uYWwgZGF0YVxuICAgKiBAcmV0dXJucyBUb3RhbCBudW1iZXIgb2YgY2xpZW50cyB0aGUgbWVzc2FnZSB3YXMgc2VudCB0b1xuICAgKi9cbiAgYnJvYWRjYXN0KHR5cGU6IFNTRUV2ZW50VHlwZSwgbWVzc2FnZTogc3RyaW5nLCBkYXRhPzogdW5rbm93bik6IG51bWJlciB7XG4gICAgY29uc3QgcGF5bG9hZDogU1NFUGF5bG9hZCA9IHtcbiAgICAgIHRzOiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gICAgICB0eXBlLFxuICAgICAgbWVzc2FnZSxcbiAgICAgIGRhdGEsXG4gICAgfTtcbiAgICAvLyBEZWxpdmVyIHRvIGxvY2FsIGNsaWVudHMsIHRoZW4gcmVsYXkgYSBzaW5nbGUgYnJvYWRjYXN0IGZyYW1lIHNvIEVWRVJZIHBvZFxuICAgIC8vIHJlLWVtaXRzIHRvIEFMTCBvZiBpdHMgb3duIGNsaWVudHMg4oCUIGEgcGVyLXJlcXVlc3RJZCByZWxheSB3b3VsZCBtaXNzXG4gICAgLy8gc3ViamVjdHMgdGhhdCBvbmx5IGV4aXN0IG9uIG90aGVyIHJlcGxpY2FzLlxuICAgIGNvbnN0IHRvdGFsU2VudCA9IHRoaXMuYnJvYWRjYXN0TG9jYWwocGF5bG9hZCk7XG4gICAgdGhpcy5wdWJsaXNoUmVsYXkoeyBvcmlnaW46IHRoaXMubm9kZUlkLCBraW5kOiAnYnJvYWRjYXN0JywgcGF5bG9hZCB9KTtcbiAgICByZXR1cm4gdG90YWxTZW50O1xuICB9XG5cbiAgLyoqIFdyaXRlIGEgcHJlLWJ1aWx0IHBheWxvYWQgdG8gYWxsIExPQ0FMIGNsaWVudHMgYWNyb3NzIGV2ZXJ5IHJlcXVlc3QuICovXG4gIHByaXZhdGUgYnJvYWRjYXN0TG9jYWwocGF5bG9hZDogU1NFUGF5bG9hZCk6IG51bWJlciB7XG4gICAgbGV0IHRvdGFsU2VudCA9IDA7XG4gICAgZm9yIChjb25zdCByZXF1ZXN0SWQgb2YgWy4uLnRoaXMuY2xpZW50cy5rZXlzKCldKSB7XG4gICAgICB0b3RhbFNlbnQgKz0gdGhpcy5zZW5kTG9jYWwocmVxdWVzdElkLCBwYXlsb2FkKTtcbiAgICB9XG4gICAgcmV0dXJuIHRvdGFsU2VudDtcbiAgfVxuXG4gIC8qKiBGaXJlLWFuZC1mb3JnZXQgcmVsYXkgcHVibGlzaC4gTm8tb3AgKGxvY2FsLW9ubHkpIHdoZW4gbm8gcmVsYXkgaXMgd2lyZWQuICovXG4gIHByaXZhdGUgcHVibGlzaFJlbGF5KG1zZzogU1NFUmVsYXlNZXNzYWdlKTogdm9pZCB7XG4gICAgdGhpcy5yZWxheT8ucHVibGlzaChtc2cpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsb3NlIGFsbCBjbGllbnRzIGZvciBhIHNwZWNpZmljIHJlcXVlc3RcbiAgICpcbiAgICogQHBhcmFtIHJlcXVlc3RJZCAtIFJlcXVlc3QgSUQgdG8gY2xvc2VcbiAgICogQHBhcmFtIGZpbmFsTWVzc2FnZSAtIE9wdGlvbmFsIGZpbmFsIG1lc3NhZ2UgdG8gc2VuZCBiZWZvcmUgY2xvc2luZ1xuICAgKi9cbiAgY2xvc2VSZXF1ZXN0KHJlcXVlc3RJZDogc3RyaW5nLCBmaW5hbE1lc3NhZ2U/OiBzdHJpbmcpOiB2b2lkIHtcbiAgICByZXF1ZXN0SWQgPSBTU0VNYW5hZ2VyLm5vcm1hbGl6ZVJlcXVlc3RJZChyZXF1ZXN0SWQpO1xuICAgIGNvbnN0IGNsaWVudHMgPSB0aGlzLmNsaWVudHMuZ2V0KHJlcXVlc3RJZCk7XG4gICAgaWYgKCFjbGllbnRzKSByZXR1cm47XG5cbiAgICBpZiAoZmluYWxNZXNzYWdlKSB7XG4gICAgICB0aGlzLnNlbmQocmVxdWVzdElkLCAnQ09NUExFVEVEJywgZmluYWxNZXNzYWdlKTtcbiAgICB9XG5cbiAgICBmb3IgKGNvbnN0IGNsaWVudCBvZiBjbGllbnRzKSB7XG4gICAgICBjbGVhclRpbWVvdXQoY2xpZW50LnRpbWVvdXQpO1xuICAgICAgdGhpcy5kZWNyZW1lbnRPcmdDb3VudChjbGllbnQub3JnSWQpO1xuICAgICAgdHJ5IHtcbiAgICAgICAgY2xpZW50LnJlcy5lbmQoKTtcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBsb2dnZXIuZGVidWcoJ1Jlc3BvbnNlIGFscmVhZHkgY2xvc2VkIG9uIHJlcXVlc3QgY2xvc2UnLCB7IHJlcXVlc3RJZCwgY2xpZW50SWQ6IGNsaWVudC5pZCwgZXJyb3I6IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKSB9KTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICB0aGlzLmNsaWVudHMuZGVsZXRlKHJlcXVlc3RJZCk7XG4gICAgdGhpcy51cGRhdGVBY3RpdmVHYXVnZSgpO1xuICAgIGxvZ2dlci5kZWJ1ZyhgQ2xvc2VkIGFsbCBjbGllbnRzIGZvciByZXF1ZXN0ICR7cmVxdWVzdElkfWApO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCBzdGF0aXN0aWNzIGFib3V0IGN1cnJlbnQgY29ubmVjdGlvbnNcbiAgICovXG4gIGdldFN0YXRzKCk6IFNTRU1hbmFnZXJTdGF0cyB7XG4gICAgbGV0IHRvdGFsQ2xpZW50cyA9IDA7XG4gICAgbGV0IG9sZGVzdENvbm5lY3Rpb246IG51bWJlciB8IG51bGwgPSBudWxsO1xuICAgIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG5cbiAgICBmb3IgKGNvbnN0IGNsaWVudHMgb2YgdGhpcy5jbGllbnRzLnZhbHVlcygpKSB7XG4gICAgICB0b3RhbENsaWVudHMgKz0gY2xpZW50cy5sZW5ndGg7XG4gICAgICBmb3IgKGNvbnN0IGNsaWVudCBvZiBjbGllbnRzKSB7XG4gICAgICAgIGNvbnN0IGFnZSA9IG5vdyAtIGNsaWVudC5jb25uZWN0ZWRBdDtcbiAgICAgICAgaWYgKG9sZGVzdENvbm5lY3Rpb24gPT09IG51bGwgfHwgYWdlID4gb2xkZXN0Q29ubmVjdGlvbikge1xuICAgICAgICAgIG9sZGVzdENvbm5lY3Rpb24gPSBhZ2U7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4ge1xuICAgICAgdG90YWxSZXF1ZXN0czogdGhpcy5jbGllbnRzLnNpemUsXG4gICAgICB0b3RhbENsaWVudHMsXG4gICAgICBvbGRlc3RDb25uZWN0aW9uTXM6IG9sZGVzdENvbm5lY3Rpb24sXG4gICAgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVjayBpZiBhIHJlcXVlc3QgaGFzIGFueSBjb25uZWN0ZWQgY2xpZW50c1xuICAgKi9cbiAgaGFzQ2xpZW50cyhyZXF1ZXN0SWQ6IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgIGNvbnN0IGNsaWVudHMgPSB0aGlzLmNsaWVudHMuZ2V0KFNTRU1hbmFnZXIubm9ybWFsaXplUmVxdWVzdElkKHJlcXVlc3RJZCkpO1xuICAgIHJldHVybiBjbGllbnRzICE9PSB1bmRlZmluZWQgJiYgY2xpZW50cy5sZW5ndGggPiAwO1xuICB9XG5cbiAgLyoqXG4gICAqIEdldCB0aGUgbnVtYmVyIG9mIGNsaWVudHMgZm9yIGEgc3BlY2lmaWMgcmVxdWVzdFxuICAgKi9cbiAgZ2V0Q2xpZW50Q291bnQocmVxdWVzdElkOiBzdHJpbmcpOiBudW1iZXIge1xuICAgIHJldHVybiB0aGlzLmNsaWVudHMuZ2V0KFNTRU1hbmFnZXIubm9ybWFsaXplUmVxdWVzdElkKHJlcXVlc3RJZCkpPy5sZW5ndGggPz8gMDtcbiAgfVxuXG4gIC8qKlxuICAgKiBNaWRkbGV3YXJlIHRvIGluaXRpYWxpemUgYSB0aWNrZXQtYXV0aGVudGljYXRlZCBTU0UgY29ubmVjdGlvbi5cbiAgICpcbiAgICogVGhlIHN0cmVhbSBpcyBOT1Qgb3BlbiB0byB0aGUgd29ybGQ6IHRoZSBjYWxsZXIgbXVzdCBmaXJzdCBleGNoYW5nZSBpdHNcbiAgICogSldUIGZvciBhIHNob3J0LWxpdmVkIHNpbmdsZS11c2UgdGlja2V0IChzZWUge0BsaW5rIGNyZWF0ZVRpY2tldH0sIHdpcmVkIHRvXG4gICAqIGBQT1NUIC9sb2dzL3RpY2tldGApIGFuZCBwYXNzIGl0IGFzIGA/dGlja2V0PTx0PmAuIFRoZSBtaWRkbGV3YXJlIHZhbGlkYXRlc1xuICAgKiArIGNvbnN1bWVzIHRoZSB0aWNrZXQsIGJpbmRzIHRoZSBjb25uZWN0aW9uIHRvIHRoZSB0aWNrZXQncyBvcmcsIGFuZFxuICAgKiBlbmZvcmNlcyB0aGUgcGVyLW9yZyBjb25uZWN0aW9uIGNhcC4gQSBtaXNzaW5nIC8gaW52YWxpZCAvIGV4cGlyZWQgL1xuICAgKiBhbHJlYWR5LXVzZWQgdGlja2V0IGlzIHJlamVjdGVkIHdpdGggNDAxIGJlZm9yZSBhbnkgU1NFIGhlYWRlcnMgZmx1c2guXG4gICAqXG4gICAqIEBleGFtcGxlXG4gICAqIGBgYHR5cGVzY3JpcHRcbiAgICogYXBwLmdldCgnL2xvZ3MvOnJlcXVlc3RJZCcsIHNzZU1hbmFnZXIubWlkZGxld2FyZSgpKTtcbiAgICogYGBgXG4gICAqL1xuICBtaWRkbGV3YXJlKCkge1xuICAgIHJldHVybiBhc3luYyAocmVxOiB7IHBhcmFtczogeyByZXF1ZXN0SWQ6IHN0cmluZyB9OyBxdWVyeT86IHsgdGlja2V0PzogdW5rbm93biB9IH0sIHJlczogUmVzcG9uc2UpID0+IHtcbiAgICAgIGNvbnN0IHsgcmVxdWVzdElkIH0gPSByZXEucGFyYW1zO1xuXG4gICAgICBpZiAoIVNTRV9SRVFVRVNUX0lEX1JFLnRlc3QocmVxdWVzdElkKSkge1xuICAgICAgICByZXMuc3RhdHVzKDQwMCkuZW5kKCdJbnZhbGlkIHJlcXVlc3RJZCBmb3JtYXQnKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICAvLyBUaWNrZXQgYXV0aCDigJQgcmVxdWlyZWQuIFJlc29sdmUgYD90aWNrZXQ9PHQ+YCwgdGhlbiB2YWxpZGF0ZSArIGNvbnN1bWVcbiAgICAgIC8vIGl0IChzaW5nbGUtdXNlKSBGT1IgVEhJUyBTVUJKRUNULiBBbiBhbm9ueW1vdXMsIG1hbGZvcm1lZCwgdW5rbm93bixcbiAgICAgIC8vIGV4cGlyZWQsIGFscmVhZHktY29uc3VtZWQsIG9yIHdyb25nLXN1YmplY3QgdGlja2V0IGlzIGEgNDAxLiBUaGUgb3JnIGlzXG4gICAgICAvLyB0YWtlbiBmcm9tIHRoZSB0aWNrZXQsIHNvIHRoZSBzdHJlYW0gaXMgYWx3YXlzIGJvdW5kIHRvIGEgdmVyaWZpZWRcbiAgICAgIC8vIHRlbmFudCBBTkQgdG8gdGhlIGV4YWN0IHN1YmplY3QgdGhlIHRpY2tldCB3YXMgbWludGVkIGZvciAobm8gYW5vbnltb3VzXG4gICAgICAvLyBzdHJlYW1zLCBubyBjcm9zcy1zdWJqZWN0IGF0dGFjaCkuXG4gICAgICBjb25zdCB0aWNrZXRJZCA9IHJlcS5xdWVyeT8udGlja2V0O1xuICAgICAgaWYgKHR5cGVvZiB0aWNrZXRJZCAhPT0gJ3N0cmluZycgfHwgdGlja2V0SWQubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHJlcy5zdGF0dXMoNDAxKS5lbmQoJ01pc3NpbmcgdGlja2V0Jyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IGNvbnN1bWVkID0gYXdhaXQgdGhpcy5jb25zdW1lVGlja2V0KHRpY2tldElkLCByZXF1ZXN0SWQpO1xuICAgICAgaWYgKCFjb25zdW1lZCkge1xuICAgICAgICByZXMuc3RhdHVzKDQwMSkuZW5kKCdJbnZhbGlkIG9yIGV4cGlyZWQgdGlja2V0Jyk7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGNvbnN0IG9yZ0lkID0gY29uc3VtZWQub3JnSWQ7XG5cbiAgICAgIC8vIFByZS1mbGlnaHQgcGVyLXJlcXVlc3QgY2FwIHNvIHdlIGNhbiByZXR1cm4gNDI5IGJlZm9yZSBmbHVzaGluZ1xuICAgICAgLy8gU1NFIGhlYWRlcnMgKGFmdGVyIGhlYWRlcnMgYXJlIGZsdXNoZWQgdGhlIGNsaWVudCBjYW4ndCByZWFkIGEgNDI5KS5cbiAgICAgIGNvbnN0IGV4aXN0aW5nID0gdGhpcy5jbGllbnRzLmdldChyZXF1ZXN0SWQpIHx8IFtdO1xuICAgICAgaWYgKGV4aXN0aW5nLmxlbmd0aCA+PSB0aGlzLm1heENsaWVudHNQZXJSZXF1ZXN0KSB7XG4gICAgICAgIGxvZ2dlci53YXJuKGBDbGllbnQgbGltaXQgcmVhY2hlZCBmb3IgcmVxdWVzdCAke3JlcXVlc3RJZH0gKG1heDogJHt0aGlzLm1heENsaWVudHNQZXJSZXF1ZXN0fSlgKTtcbiAgICAgICAgcmVzLnN0YXR1cyg0MjkpLmVuZCgnVG9vIG1hbnkgY29ubmVjdGlvbnMgZm9yIHRoaXMgcmVxdWVzdCcpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG5cbiAgICAgIC8vIFNhbWUgZm9yIHRoZSBwZXItb3JnIGNhcCDigJQgYmV0dGVyIHRvIHNlbmQgNDI5IHRoYW4gb3BlbiB0aGUgc3RyZWFtIGFuZFxuICAgICAgLy8gaW1tZWRpYXRlbHkgY2xvc2UgaXQuIFRoZSB0aWNrZXQgZ3VhcmFudGVlcyBhIHZlcmlmaWVkIG9yZyBpZCwgc28gdGhlXG4gICAgICAvLyBwZXItb3JnIGNlaWxpbmcgaXMgYWx3YXlzIGVuZm9yY2VkIGZvciB0aGUgL2xvZ3Mgc3RyZWFtLlxuICAgICAgaWYgKCh0aGlzLm9yZ0NsaWVudENvdW50cy5nZXQob3JnSWQpID8/IDApID49IHRoaXMubWF4Q2xpZW50c1Blck9yZykge1xuICAgICAgICBsb2dnZXIud2FybihgUGVyLW9yZyBTU0UgY2xpZW50IGNhcCByZWFjaGVkIGZvciAke29yZ0lkfSAobWF4OiAke3RoaXMubWF4Q2xpZW50c1Blck9yZ30pYCk7XG4gICAgICAgIHJlcy5zdGF0dXMoNDI5KS5lbmQoJ1RvbyBtYW55IFNTRSBjb25uZWN0aW9ucyBmb3IgdGhpcyBvcmdhbml6YXRpb24nKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICB3cml0ZVNzZUhlYWRlcnMocmVzKTtcbiAgICAgIHJlcy5mbHVzaEhlYWRlcnMoKTtcblxuICAgICAgLy8gSGVhZGVycyBhcmUgZmx1c2hlZCDigJQgd2UgY2FuIG5vIGxvbmdlciBzZW5kIGEgNDI5LiBhZGRDbGllbnQgcmUtY2hlY2tzXG4gICAgICAvLyB0aGUgY2FwcyAoaW5jbHVkaW5nIHRoZSBwcm9jZXNzLXdpZGUgdG90YWwtY2xpZW50cyBjYXAsIHdoaWNoIHRoZVxuICAgICAgLy8gcHJlLWZsaWdodCBhYm92ZSBkb2VzIG5vdCkgYW5kIGNhbiBzdGlsbCByZWplY3Qgb24gYSByYWNlOiBhIGNvbmN1cnJlbnRcbiAgICAgIC8vIGNvbm5lY3Rpb24gbWF5IGhhdmUgZmlsbGVkIGEgY2FwIGJldHdlZW4gdGhlIHByZS1mbGlnaHQgY2hlY2sgYW5kIGhlcmUuXG4gICAgICAvLyBPbiByZWplY3Rpb24sIGNsb3NlIHRoZSBub3ctZGFuZ2xpbmcgcmVzcG9uc2UgaW5zdGVhZCBvZiBsZWF2aW5nIGFuXG4gICAgICAvLyBvcGVuIHNvY2tldCB3aXRoIG5vIGNsaWVudCByZWNvcmQgKHdoaWNoIHdvdWxkIGxlYWsgYW4gZmQgKyBuZXZlciBlbWl0XG4gICAgICAvLyBzdHJlYW0gZGF0YSkuXG4gICAgICBjb25zdCBhZGRlZCA9IHRoaXMuYWRkQ2xpZW50KHJlcXVlc3RJZCwgcmVzLCBvcmdJZCk7XG4gICAgICBpZiAoIWFkZGVkKSB7XG4gICAgICAgIGxvZ2dlci53YXJuKGBTU0UgYWRkQ2xpZW50IHJlamVjdGVkIGFmdGVyIGhlYWRlcnMgZmx1c2hlZCBmb3IgcmVxdWVzdCAke3JlcXVlc3RJZH07IGNsb3NpbmcgZGFuZ2xpbmcgcmVzcG9uc2VgKTtcbiAgICAgICAgdHJ5IHsgcmVzLmVuZCgpOyB9IGNhdGNoIHsgLyogYWxyZWFkeSBjbG9zZWQgKi8gfVxuICAgICAgfVxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogU3RhcnQgcGVyaW9kaWMgY2xlYW51cCBvZiBzdGFsZSBjb25uZWN0aW9uc1xuICAgKi9cbiAgcHJpdmF0ZSBzdGFydENsZWFudXBJbnRlcnZhbChpbnRlcnZhbE1zOiBudW1iZXIpOiB2b2lkIHtcbiAgICB0aGlzLmNsZWFudXBJbnRlcnZhbCA9IHNldEludGVydmFsKCgpID0+IHtcbiAgICAgIHRoaXMuY2xlYW51cCgpO1xuICAgIH0sIGludGVydmFsTXMpO1xuXG4gICAgLy8gRG9uJ3QgcHJldmVudCBwcm9jZXNzIGV4aXRcbiAgICB0aGlzLmNsZWFudXBJbnRlcnZhbC51bnJlZigpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFuIHVwIHN0YWxlIGNvbm5lY3Rpb25zIHVzaW5nIHNpbmdsZS1wYXNzIHBhcnRpdGlvbi5cbiAgICogTyhSIMOXIEMpIGluc3RlYWQgb2YgTyhSIMOXIEPCsiksIGFuZCBhdm9pZHMgbXV0YXRpb24tZHVyaW5nLWl0ZXJhdGlvbi5cbiAgICovXG4gIHByaXZhdGUgY2xlYW51cCgpOiB2b2lkIHtcbiAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgIGxldCBjbGVhbmVkID0gMDtcblxuICAgIC8vIFN3ZWVwIGV4cGlyZWQgdGlja2V0cy9vd25lcnMgc28gYW4gaW4tbWVtb3J5IHN0b3JlIHN0YXlzIGJvdW5kZWQgZXZlbiB3aGVuXG4gICAgLy8gdGlja2V0cyBhcmUgbWludGVkIGJ1dCBuZXZlciBjb25zdW1lZCAoY2xpZW50IGNsb3NlZCB0aGUgdGFiIGJlZm9yZVxuICAgIC8vIGNvbm5lY3RpbmcpLiBDb25zdW1lLXRpbWUgc3RpbGwgcmUtY2hlY2tzIGV4cGlyeSwgc28gdGhpcyBpcyBtZW1vcnlcbiAgICAvLyBoeWdpZW5lIG9ubHk7IHRoZSBSZWRpcyBzdG9yZSByZWxpZXMgb24gbmF0aXZlIGtleSBUVEwgKHN3ZWVwIGlzIGEgbm8tb3ApLlxuICAgIHZvaWQgdGhpcy50aWNrZXRTdG9yZS5zd2VlcD8uKCk7XG5cbiAgICBmb3IgKGNvbnN0IFtyZXF1ZXN0SWQsIGNsaWVudHNdIG9mIHRoaXMuY2xpZW50cy5lbnRyaWVzKCkpIHtcbiAgICAgIGNvbnN0IHN0YWxlOiBTU0VDbGllbnRbXSA9IFtdO1xuICAgICAgY29uc3QgYWN0aXZlOiBTU0VDbGllbnRbXSA9IFtdO1xuXG4gICAgICBmb3IgKGNvbnN0IGNsaWVudCBvZiBjbGllbnRzKSB7XG4gICAgICAgIC8vIFRpbWUtYmFzZWQgZXZpY3Rpb24gT1IgYSBzb2NrZXQgdGhhdCBzaWxlbnRseSBjbG9zZWQgKG5vICdjbG9zZSdcbiAgICAgICAgLy8gZXZlbnQpIOKAlCBOb2RlIHNvbWV0aW1lcyBkcm9wcyBzb2NrZXRzIHdpdGhvdXQgZmlyaW5nIHRoZSBldmVudCxcbiAgICAgICAgLy8gc28gd2UgZXhwbGljaXRseSBjaGVjayB3cml0YWJsZUVuZGVkL2Rlc3Ryb3llZCBoZXJlLlxuICAgICAgICBjb25zdCBhZ2VTdGFsZSA9IG5vdyAtIGNsaWVudC5jb25uZWN0ZWRBdCA+IHRoaXMuY2xpZW50VGltZW91dE1zO1xuICAgICAgICBjb25zdCBzb2NrZXREZWFkID0gY2xpZW50LnJlcy53cml0YWJsZUVuZGVkIHx8IChjbGllbnQucmVzIGFzIHsgZGVzdHJveWVkPzogYm9vbGVhbiB9KS5kZXN0cm95ZWQgPT09IHRydWU7XG4gICAgICAgIGlmIChhZ2VTdGFsZSB8fCBzb2NrZXREZWFkKSB7XG4gICAgICAgICAgc3RhbGUucHVzaChjbGllbnQpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGFjdGl2ZS5wdXNoKGNsaWVudCk7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgZm9yIChjb25zdCBjbGllbnQgb2Ygc3RhbGUpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KGNsaWVudC50aW1lb3V0KTtcbiAgICAgICAgdGhpcy5kZWNyZW1lbnRPcmdDb3VudChjbGllbnQub3JnSWQpO1xuICAgICAgICB0cnkgeyBjbGllbnQucmVzLmVuZCgpOyB9IGNhdGNoIHsgLyogYWxyZWFkeSBjbG9zZWQgKi8gfVxuICAgICAgICBjbGVhbmVkKys7XG4gICAgICB9XG5cbiAgICAgIGlmIChhY3RpdmUubGVuZ3RoID09PSAwKSB7XG4gICAgICAgIHRoaXMuY2xpZW50cy5kZWxldGUocmVxdWVzdElkKTtcbiAgICAgIH0gZWxzZSBpZiAoc3RhbGUubGVuZ3RoID4gMCkge1xuICAgICAgICB0aGlzLmNsaWVudHMuc2V0KHJlcXVlc3RJZCwgYWN0aXZlKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoY2xlYW5lZCA+IDApIHtcbiAgICAgIGluY0NvdW50ZXIoJ3NzZV9zdGFsZV9jbGVhbmVkX3RvdGFsJywge30sIGNsZWFuZWQpO1xuICAgICAgdGhpcy51cGRhdGVBY3RpdmVHYXVnZSgpO1xuICAgICAgbG9nZ2VyLmluZm8oYENsZWFuZWQgdXAgJHtjbGVhbmVkfSBzdGFsZSBTU0UgY29ubmVjdGlvbnNgKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU2h1dGRvd24gdGhlIFNTRSBtYW5hZ2VyIGFuZCBjbG9zZSBhbGwgY29ubmVjdGlvbnNcbiAgICovXG4gIHNodXRkb3duKCk6IHZvaWQge1xuICAgIGlmICh0aGlzLmNsZWFudXBJbnRlcnZhbCkge1xuICAgICAgY2xlYXJJbnRlcnZhbCh0aGlzLmNsZWFudXBJbnRlcnZhbCk7XG4gICAgICB0aGlzLmNsZWFudXBJbnRlcnZhbCA9IG51bGw7XG4gICAgfVxuXG4gICAgZm9yIChjb25zdCByZXF1ZXN0SWQgb2YgWy4uLnRoaXMuY2xpZW50cy5rZXlzKCldKSB7XG4gICAgICB0aGlzLmNsb3NlUmVxdWVzdChyZXF1ZXN0SWQsICdTZXJ2ZXIgc2h1dHRpbmcgZG93bicpO1xuICAgIH1cblxuICAgIC8vIFRlYXIgZG93biB0aGUgcmVsYXkncyBSZWRpcyBjb25uZWN0aW9ucyAoYmVzdC1lZmZvcnQ7IG5ldmVyIHRocm93cykuXG4gICAgdm9pZCB0aGlzLnJlbGF5Py5jbG9zZSgpLmNhdGNoKCgpID0+IHsgLyogYWxyZWFkeSBjbG9zaW5nICovIH0pO1xuXG4gICAgbG9nZ2VyLmluZm8oJ1NTRSBNYW5hZ2VyIHNodXQgZG93bicpO1xuICB9XG59XG4iXX0=