@blokjs/trigger-mcp 0.6.12
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/dist/McpTrigger.d.ts +114 -0
- package/dist/McpTrigger.js +423 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +38 -0
- package/package.json +38 -0
- package/src/McpTrigger.integration.test.ts +277 -0
- package/src/McpTrigger.ts +555 -0
- package/src/index.ts +41 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* McpTrigger — Model Context Protocol trigger. Exposes Blok workflows as
|
|
3
|
+
* MCP **tools** (and **resources**) to external clients (Cursor, Claude
|
|
4
|
+
* Code, …) over two transports multiplexed on the shared Hono port:
|
|
5
|
+
*
|
|
6
|
+
* - **SSE (legacy 2024-11-05)** — `GET <path>/sse` opens the stream and
|
|
7
|
+
* announces `POST <path>/messages?sessionId=…` for JSON-RPC. This is the
|
|
8
|
+
* 2-endpoint shape existing IDE configs expect (drop-in parity).
|
|
9
|
+
* - **Streamable-HTTP** — a single `<path>` endpoint (the current official
|
|
10
|
+
* remote transport), served statelessly via the SDK's web-standard
|
|
11
|
+
* transport directly off `c.req.raw`.
|
|
12
|
+
*
|
|
13
|
+
* **Authoring surface** — a workflow opts in with `trigger.mcp`:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* export default workflow({
|
|
17
|
+
* name: "search_code",
|
|
18
|
+
* version: "1.0.0",
|
|
19
|
+
* input: z.object({ query: z.string(), limit: z.number().optional() }),
|
|
20
|
+
* trigger: { mcp: { path: "/mcp", serverName: "tetrix-platform",
|
|
21
|
+
* tool: { description: "Full-text search the indexed code" } } },
|
|
22
|
+
* steps: [ { id: "search", use: "@tetrix/meili-search", inputs: { query: $.req.body.query } } ],
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* Every workflow sharing the same `path` + `serverName` is aggregated into one
|
|
27
|
+
* MCP server. Each tool's `inputSchema` is generated from the workflow's `input`
|
|
28
|
+
* Zod schema (via zod-to-json-schema). On `tools/call` the trigger runs the
|
|
29
|
+
* mapped workflow through the runner — so every call is a Blok Studio run with
|
|
30
|
+
* tracing / retries / idempotency for free.
|
|
31
|
+
*
|
|
32
|
+
* **Hono integration:** identical to `SSETrigger`/`WebhookTrigger` — accepts the
|
|
33
|
+
* shared `Hono` app + an optional `HttpTriggerLike` exposing `addPreCatchAllHook`
|
|
34
|
+
* so routes register AFTER the workflow registry is populated but BEFORE the
|
|
35
|
+
* legacy `/:workflow{.+}` catch-all.
|
|
36
|
+
*
|
|
37
|
+
* **Identity:** an `x-user-context` header (or `?user_context=`) carrying base64
|
|
38
|
+
* `{userId,email}` is parsed per connection and passed to the workflow ctx for
|
|
39
|
+
* credential injection. It is NOT access control — there is no scoping here
|
|
40
|
+
* (that's the app's call). Tokens are never logged (the global sanitizer middleware
|
|
41
|
+
* handles redaction).
|
|
42
|
+
*/
|
|
43
|
+
import { DefaultLogger, type GlobalOptions as RunnerGlobalOptions, TriggerBase } from "@blokjs/runner";
|
|
44
|
+
import type { Hono } from "hono";
|
|
45
|
+
/** Parsed `x-user-context` identity (credential injection only — never scoping). */
|
|
46
|
+
export interface McpUserContext {
|
|
47
|
+
userId: string;
|
|
48
|
+
email: string;
|
|
49
|
+
}
|
|
50
|
+
type McpTransportKind = "sse" | "streamable-http";
|
|
51
|
+
interface McpToolMeta {
|
|
52
|
+
name?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
}
|
|
55
|
+
interface McpResourceMeta {
|
|
56
|
+
uri: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
mimeType?: string;
|
|
60
|
+
}
|
|
61
|
+
/** Loosely-read `trigger.mcp` config (validated at workflow load by the helper). */
|
|
62
|
+
interface McpTriggerConfig {
|
|
63
|
+
path: string;
|
|
64
|
+
serverName?: string;
|
|
65
|
+
serverVersion?: string;
|
|
66
|
+
transports?: McpTransportKind[];
|
|
67
|
+
tool?: McpToolMeta;
|
|
68
|
+
resource?: McpResourceMeta;
|
|
69
|
+
middleware?: string[];
|
|
70
|
+
}
|
|
71
|
+
interface HttpTriggerLike {
|
|
72
|
+
addPreCatchAllHook(cb: () => void | Promise<void>): void;
|
|
73
|
+
}
|
|
74
|
+
/** Parse a base64-encoded `{userId,email}` identity string. Returns null on any failure. */
|
|
75
|
+
export declare function parseUserContext(value: string | undefined | null): McpUserContext | null;
|
|
76
|
+
export default class McpTrigger extends TriggerBase {
|
|
77
|
+
protected nodeMap: RunnerGlobalOptions;
|
|
78
|
+
protected readonly logger: DefaultLogger;
|
|
79
|
+
protected readonly tracer: import("@opentelemetry/api").Tracer;
|
|
80
|
+
private readonly meter;
|
|
81
|
+
private readonly counterToolCalls;
|
|
82
|
+
private readonly counterSessions;
|
|
83
|
+
private readonly app;
|
|
84
|
+
private readonly httpTrigger;
|
|
85
|
+
private wired;
|
|
86
|
+
/** Live SSE sessions: sessionId → { transport, server, userContext }. */
|
|
87
|
+
private sseSessions;
|
|
88
|
+
constructor(app: Hono<any, any, any>, httpTrigger?: HttpTriggerLike);
|
|
89
|
+
/** Inject the runner's GlobalOptions (nodes + workflows). Called before `listen()`. */
|
|
90
|
+
setNodeMap(nodeMap: RunnerGlobalOptions): void;
|
|
91
|
+
listen(): Promise<number>;
|
|
92
|
+
stop(): Promise<void>;
|
|
93
|
+
getStats(): {
|
|
94
|
+
sessions: number;
|
|
95
|
+
};
|
|
96
|
+
private registerRoutesFromRegistry;
|
|
97
|
+
private getServerGroups;
|
|
98
|
+
private buildServer;
|
|
99
|
+
private dispatchTool;
|
|
100
|
+
private dispatchResource;
|
|
101
|
+
/**
|
|
102
|
+
* Run a workflow through the runner and return `ctx.response.data`. Mirrors
|
|
103
|
+
* the WebhookTrigger request→response dispatch (init → context → middleware →
|
|
104
|
+
* run). MCP tool calls within a session are serial, matching the shared
|
|
105
|
+
* `this.configuration` lifecycle the other triggers use.
|
|
106
|
+
*/
|
|
107
|
+
private runWorkflow;
|
|
108
|
+
private registerGroupRoutes;
|
|
109
|
+
private registerSseRoutes;
|
|
110
|
+
private registerStreamableHttpRoute;
|
|
111
|
+
}
|
|
112
|
+
export declare function _setActiveMcpTrigger(trigger: McpTrigger | null): void;
|
|
113
|
+
export declare function _getActiveMcpTrigger(): McpTrigger | null;
|
|
114
|
+
export type { McpTriggerConfig };
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* McpTrigger — Model Context Protocol trigger. Exposes Blok workflows as
|
|
3
|
+
* MCP **tools** (and **resources**) to external clients (Cursor, Claude
|
|
4
|
+
* Code, …) over two transports multiplexed on the shared Hono port:
|
|
5
|
+
*
|
|
6
|
+
* - **SSE (legacy 2024-11-05)** — `GET <path>/sse` opens the stream and
|
|
7
|
+
* announces `POST <path>/messages?sessionId=…` for JSON-RPC. This is the
|
|
8
|
+
* 2-endpoint shape existing IDE configs expect (drop-in parity).
|
|
9
|
+
* - **Streamable-HTTP** — a single `<path>` endpoint (the current official
|
|
10
|
+
* remote transport), served statelessly via the SDK's web-standard
|
|
11
|
+
* transport directly off `c.req.raw`.
|
|
12
|
+
*
|
|
13
|
+
* **Authoring surface** — a workflow opts in with `trigger.mcp`:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* export default workflow({
|
|
17
|
+
* name: "search_code",
|
|
18
|
+
* version: "1.0.0",
|
|
19
|
+
* input: z.object({ query: z.string(), limit: z.number().optional() }),
|
|
20
|
+
* trigger: { mcp: { path: "/mcp", serverName: "tetrix-platform",
|
|
21
|
+
* tool: { description: "Full-text search the indexed code" } } },
|
|
22
|
+
* steps: [ { id: "search", use: "@tetrix/meili-search", inputs: { query: $.req.body.query } } ],
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* Every workflow sharing the same `path` + `serverName` is aggregated into one
|
|
27
|
+
* MCP server. Each tool's `inputSchema` is generated from the workflow's `input`
|
|
28
|
+
* Zod schema (via zod-to-json-schema). On `tools/call` the trigger runs the
|
|
29
|
+
* mapped workflow through the runner — so every call is a Blok Studio run with
|
|
30
|
+
* tracing / retries / idempotency for free.
|
|
31
|
+
*
|
|
32
|
+
* **Hono integration:** identical to `SSETrigger`/`WebhookTrigger` — accepts the
|
|
33
|
+
* shared `Hono` app + an optional `HttpTriggerLike` exposing `addPreCatchAllHook`
|
|
34
|
+
* so routes register AFTER the workflow registry is populated but BEFORE the
|
|
35
|
+
* legacy `/:workflow{.+}` catch-all.
|
|
36
|
+
*
|
|
37
|
+
* **Identity:** an `x-user-context` header (or `?user_context=`) carrying base64
|
|
38
|
+
* `{userId,email}` is parsed per connection and passed to the workflow ctx for
|
|
39
|
+
* credential injection. It is NOT access control — there is no scoping here
|
|
40
|
+
* (that's the app's call). Tokens are never logged (the global sanitizer middleware
|
|
41
|
+
* handles redaction).
|
|
42
|
+
*/
|
|
43
|
+
import { DefaultLogger, TriggerBase, WorkflowRegistry, } from "@blokjs/runner";
|
|
44
|
+
import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
|
|
45
|
+
import { Server as McpSdkServer } from "@modelcontextprotocol/sdk/server/index.js";
|
|
46
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
47
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
48
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
49
|
+
import { SpanStatusCode, metrics, trace } from "@opentelemetry/api";
|
|
50
|
+
import { v4 as uuid } from "uuid";
|
|
51
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
52
|
+
const DEFAULT_SERVER_NAME = "blok-mcp";
|
|
53
|
+
const DEFAULT_SERVER_VERSION = "1.0.0";
|
|
54
|
+
const DEFAULT_TRANSPORTS = ["sse", "streamable-http"];
|
|
55
|
+
// -----------------------------------------------------------------------------
|
|
56
|
+
// Helpers
|
|
57
|
+
// -----------------------------------------------------------------------------
|
|
58
|
+
/** Parse a base64-encoded `{userId,email}` identity string. Returns null on any failure. */
|
|
59
|
+
export function parseUserContext(value) {
|
|
60
|
+
if (!value || typeof value !== "string")
|
|
61
|
+
return null;
|
|
62
|
+
try {
|
|
63
|
+
const decoded = Buffer.from(value, "base64").toString("utf-8");
|
|
64
|
+
if (!decoded || decoded.trim() === "null")
|
|
65
|
+
return null;
|
|
66
|
+
const ctx = JSON.parse(decoded);
|
|
67
|
+
if (!ctx || typeof ctx.userId !== "string")
|
|
68
|
+
return null;
|
|
69
|
+
return { userId: ctx.userId, email: typeof ctx.email === "string" ? ctx.email : "unknown" };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Convert a Zod schema to a JSON-Schema object suitable for an MCP tool `inputSchema`. */
|
|
76
|
+
// biome-ignore lint/suspicious/noExplicitAny: zod schema is opaque here
|
|
77
|
+
function toInputJsonSchema(inputZod) {
|
|
78
|
+
const empty = { type: "object", properties: {}, additionalProperties: true };
|
|
79
|
+
if (!inputZod || typeof inputZod !== "object" || typeof inputZod.safeParse !== "function") {
|
|
80
|
+
return empty;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const json = zodToJsonSchema(inputZod, { target: "jsonSchema7", $refStrategy: "none" });
|
|
84
|
+
// biome-ignore lint/performance/noDelete: strip JSON-Schema meta the MCP client doesn't need
|
|
85
|
+
delete json.$schema;
|
|
86
|
+
if (json.type !== "object") {
|
|
87
|
+
return { ...empty, _wrapped: json };
|
|
88
|
+
}
|
|
89
|
+
return json;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return empty;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// -----------------------------------------------------------------------------
|
|
96
|
+
// Trigger
|
|
97
|
+
// -----------------------------------------------------------------------------
|
|
98
|
+
export default class McpTrigger extends TriggerBase {
|
|
99
|
+
nodeMap = {};
|
|
100
|
+
logger = new DefaultLogger();
|
|
101
|
+
tracer = trace.getTracer(process.env.PROJECT_NAME || "trigger-mcp-workflow", process.env.PROJECT_VERSION || "0.0.1");
|
|
102
|
+
meter = metrics.getMeter("blok");
|
|
103
|
+
counterToolCalls = this.meter.createCounter("blok_mcp_tool_calls_total", {
|
|
104
|
+
description: "MCP tools/call dispatches.",
|
|
105
|
+
unit: "1",
|
|
106
|
+
});
|
|
107
|
+
counterSessions = this.meter.createCounter("blok_mcp_sse_sessions_total", {
|
|
108
|
+
description: "MCP SSE sessions opened.",
|
|
109
|
+
unit: "1",
|
|
110
|
+
});
|
|
111
|
+
// biome-ignore lint/suspicious/noExplicitAny: Hono generic propagation (matches SSE/WS triggers)
|
|
112
|
+
app;
|
|
113
|
+
httpTrigger;
|
|
114
|
+
wired = false;
|
|
115
|
+
/** Live SSE sessions: sessionId → { transport, server, userContext }. */
|
|
116
|
+
sseSessions = new Map();
|
|
117
|
+
// biome-ignore lint/suspicious/noExplicitAny: matches `app` field generic
|
|
118
|
+
constructor(app, httpTrigger) {
|
|
119
|
+
super();
|
|
120
|
+
this.app = app;
|
|
121
|
+
this.httpTrigger = httpTrigger ?? null;
|
|
122
|
+
_setActiveMcpTrigger(this);
|
|
123
|
+
}
|
|
124
|
+
/** Inject the runner's GlobalOptions (nodes + workflows). Called before `listen()`. */
|
|
125
|
+
setNodeMap(nodeMap) {
|
|
126
|
+
this.nodeMap = nodeMap;
|
|
127
|
+
}
|
|
128
|
+
async listen() {
|
|
129
|
+
const startTime = this.startCounter();
|
|
130
|
+
if (this.wired) {
|
|
131
|
+
this.logger.log("[blok][mcp] listen() called twice; ignoring");
|
|
132
|
+
return this.endCounter(startTime);
|
|
133
|
+
}
|
|
134
|
+
this.wired = true;
|
|
135
|
+
if (this.httpTrigger) {
|
|
136
|
+
this.httpTrigger.addPreCatchAllHook(() => this.registerRoutesFromRegistry());
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.registerRoutesFromRegistry();
|
|
140
|
+
}
|
|
141
|
+
return this.endCounter(startTime);
|
|
142
|
+
}
|
|
143
|
+
async stop() {
|
|
144
|
+
for (const { transport } of this.sseSessions.values()) {
|
|
145
|
+
try {
|
|
146
|
+
await transport.close();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
/* ignore */
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
this.sseSessions.clear();
|
|
153
|
+
this.wired = false;
|
|
154
|
+
if (_getActiveMcpTrigger() === this)
|
|
155
|
+
_setActiveMcpTrigger(null);
|
|
156
|
+
this.destroyMonitoring();
|
|
157
|
+
this.logger.log("[blok][mcp] stopped");
|
|
158
|
+
}
|
|
159
|
+
getStats() {
|
|
160
|
+
return { sessions: this.sseSessions.size };
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Registry scan + grouping
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
registerRoutesFromRegistry() {
|
|
166
|
+
const groups = this.getServerGroups();
|
|
167
|
+
if (groups.length === 0) {
|
|
168
|
+
this.logger.log("[blok][mcp] no workflows with trigger.mcp found");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
for (const group of groups) {
|
|
172
|
+
this.registerGroupRoutes(group);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
getServerGroups() {
|
|
176
|
+
const registry = WorkflowRegistry.getInstance();
|
|
177
|
+
const byPath = new Map();
|
|
178
|
+
for (const entry of registry.list()) {
|
|
179
|
+
// Workflows registered as builders expose config on `_config`; plain
|
|
180
|
+
// objects (tests / JSON) expose it at the top level.
|
|
181
|
+
const wf = entry.workflow?._config ?? entry.workflow;
|
|
182
|
+
const cfg = wf?.trigger?.mcp;
|
|
183
|
+
if (!cfg || typeof cfg.path !== "string")
|
|
184
|
+
continue;
|
|
185
|
+
const path = cfg.path;
|
|
186
|
+
let group = byPath.get(path);
|
|
187
|
+
if (!group) {
|
|
188
|
+
group = {
|
|
189
|
+
path,
|
|
190
|
+
serverName: cfg.serverName || DEFAULT_SERVER_NAME,
|
|
191
|
+
serverVersion: cfg.serverVersion || DEFAULT_SERVER_VERSION,
|
|
192
|
+
transports: Array.isArray(cfg.transports) && cfg.transports.length > 0 ? cfg.transports : DEFAULT_TRANSPORTS,
|
|
193
|
+
tools: [],
|
|
194
|
+
resources: [],
|
|
195
|
+
};
|
|
196
|
+
byPath.set(path, group);
|
|
197
|
+
}
|
|
198
|
+
if (cfg.resource && typeof cfg.resource.uri === "string") {
|
|
199
|
+
group.resources.push({
|
|
200
|
+
workflowName: entry.name,
|
|
201
|
+
uri: cfg.resource.uri,
|
|
202
|
+
name: cfg.resource.name || entry.name,
|
|
203
|
+
description: cfg.resource.description,
|
|
204
|
+
mimeType: cfg.resource.mimeType || "application/json",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const inputZod = wf?.input;
|
|
209
|
+
group.tools.push({
|
|
210
|
+
workflowName: entry.name,
|
|
211
|
+
toolName: cfg.tool?.name || entry.name,
|
|
212
|
+
description: cfg.tool?.description || `Run the "${entry.name}" workflow.`,
|
|
213
|
+
inputZod,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return [...byPath.values()];
|
|
218
|
+
}
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// MCP server factory (one per request/session, bound to a user context)
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
buildServer(group, getUserContext) {
|
|
223
|
+
const server = new McpSdkServer({ name: group.serverName, version: group.serverVersion }, { capabilities: { tools: {}, resources: {} } });
|
|
224
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
225
|
+
tools: group.tools.map((t) => ({
|
|
226
|
+
name: t.toolName,
|
|
227
|
+
description: t.description,
|
|
228
|
+
inputSchema: toInputJsonSchema(t.inputZod),
|
|
229
|
+
})),
|
|
230
|
+
}));
|
|
231
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
232
|
+
const toolName = req.params.name;
|
|
233
|
+
const tool = group.tools.find((t) => t.toolName === toolName);
|
|
234
|
+
if (!tool) {
|
|
235
|
+
return { content: [{ type: "text", text: `Unknown tool: ${toolName}` }], isError: true };
|
|
236
|
+
}
|
|
237
|
+
const args = (req.params.arguments ?? {});
|
|
238
|
+
return this.dispatchTool(tool, args, getUserContext());
|
|
239
|
+
});
|
|
240
|
+
if (group.resources.length > 0) {
|
|
241
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
242
|
+
resources: group.resources.map((r) => ({
|
|
243
|
+
uri: r.uri,
|
|
244
|
+
name: r.name,
|
|
245
|
+
description: r.description,
|
|
246
|
+
mimeType: r.mimeType,
|
|
247
|
+
})),
|
|
248
|
+
}));
|
|
249
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
250
|
+
const uri = req.params.uri;
|
|
251
|
+
const resource = group.resources.find((r) => r.uri === uri);
|
|
252
|
+
if (!resource)
|
|
253
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
254
|
+
const result = await this.dispatchResource(resource, getUserContext());
|
|
255
|
+
const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
256
|
+
return { contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }] };
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return server;
|
|
260
|
+
}
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// Workflow dispatch (tools/call + resources/read run through the runner)
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
async dispatchTool(tool, args, userContext) {
|
|
265
|
+
this.counterToolCalls.add(1, { tool: tool.toolName });
|
|
266
|
+
try {
|
|
267
|
+
const data = await this.runWorkflow(tool.workflowName, args, userContext, `mcp.tool:${tool.toolName}`);
|
|
268
|
+
const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
269
|
+
return { content: [{ type: "text", text }] };
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
273
|
+
this.logger.error(`[blok][mcp] tool "${tool.toolName}" failed: ${msg}`);
|
|
274
|
+
// Tool failure → MCP tool error result, NOT a transport crash.
|
|
275
|
+
return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async dispatchResource(resource, userContext) {
|
|
279
|
+
return this.runWorkflow(resource.workflowName, {}, userContext, `mcp.resource:${resource.uri}`);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Run a workflow through the runner and return `ctx.response.data`. Mirrors
|
|
283
|
+
* the WebhookTrigger request→response dispatch (init → context → middleware →
|
|
284
|
+
* run). MCP tool calls within a session are serial, matching the shared
|
|
285
|
+
* `this.configuration` lifecycle the other triggers use.
|
|
286
|
+
*/
|
|
287
|
+
async runWorkflow(workflowName, args, userContext, spanLabel) {
|
|
288
|
+
const requestId = uuid();
|
|
289
|
+
return this.tracer.startActiveSpan(`mcp:${workflowName}`, async (span) => {
|
|
290
|
+
try {
|
|
291
|
+
const registry = WorkflowRegistry.getInstance();
|
|
292
|
+
const entry = registry.get(workflowName);
|
|
293
|
+
if (!entry)
|
|
294
|
+
throw new Error(`workflow "${workflowName}" not found in registry`);
|
|
295
|
+
await this.configuration.init(workflowName, this.nodeMap, entry.workflow);
|
|
296
|
+
const headers = {};
|
|
297
|
+
if (userContext) {
|
|
298
|
+
// Carry identity to the workflow exactly as HTTP would (base64 header).
|
|
299
|
+
headers["x-user-context"] = Buffer.from(JSON.stringify(userContext), "utf-8").toString("base64");
|
|
300
|
+
}
|
|
301
|
+
const ctx = this.createContext(undefined, workflowName, requestId);
|
|
302
|
+
ctx.request = {
|
|
303
|
+
body: args,
|
|
304
|
+
headers,
|
|
305
|
+
params: {},
|
|
306
|
+
query: {},
|
|
307
|
+
};
|
|
308
|
+
ctx._mcp = { userContext };
|
|
309
|
+
await this.applyMiddlewareChain(ctx, this.nodeMap);
|
|
310
|
+
await this.run(ctx);
|
|
311
|
+
span.setAttribute("workflow_name", workflowName);
|
|
312
|
+
span.setAttribute("mcp_label", spanLabel);
|
|
313
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
314
|
+
return ctx.response?.data;
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
318
|
+
span.recordException(err);
|
|
319
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: msg });
|
|
320
|
+
throw err;
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
span.end();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
// Route registration (SSE + Streamable-HTTP)
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
registerGroupRoutes(group) {
|
|
331
|
+
this.logger.log(`[blok][mcp] server "${group.serverName}" at ${group.path} — ${group.tools.length} tool(s), ${group.resources.length} resource(s), transports=[${group.transports.join(",")}]`);
|
|
332
|
+
if (group.transports.includes("sse")) {
|
|
333
|
+
this.registerSseRoutes(group);
|
|
334
|
+
}
|
|
335
|
+
if (group.transports.includes("streamable-http")) {
|
|
336
|
+
this.registerStreamableHttpRoute(group);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
registerSseRoutes(group) {
|
|
340
|
+
const ssePath = `${group.path}/sse`;
|
|
341
|
+
const messagesPath = `${group.path}/messages`;
|
|
342
|
+
this.logger.log(`[blok][mcp] GET ${ssePath} POST ${messagesPath} (sse)`);
|
|
343
|
+
// GET <path>/sse — open the SSE stream; SDK announces the messages endpoint.
|
|
344
|
+
this.app.get(ssePath, async (c) => {
|
|
345
|
+
const env = c.env;
|
|
346
|
+
if (!env?.outgoing) {
|
|
347
|
+
return c.text("MCP SSE transport requires the Node server (@hono/node-server).", 500);
|
|
348
|
+
}
|
|
349
|
+
const rawUserCtx = c.req.header("x-user-context") || (new URL(c.req.url).searchParams.get("user_context") ?? undefined);
|
|
350
|
+
const userContext = parseUserContext(rawUserCtx);
|
|
351
|
+
const transport = new SSEServerTransport(messagesPath, env.outgoing);
|
|
352
|
+
const sessionId = transport.sessionId;
|
|
353
|
+
const server = this.buildServer(group, () => this.sseSessions.get(sessionId)?.userContext ?? null);
|
|
354
|
+
this.sseSessions.set(sessionId, { transport, server, userContext });
|
|
355
|
+
this.counterSessions.add(1, { server: group.serverName });
|
|
356
|
+
transport.onclose = () => {
|
|
357
|
+
this.sseSessions.delete(sessionId);
|
|
358
|
+
};
|
|
359
|
+
try {
|
|
360
|
+
await server.connect(transport);
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
this.sseSessions.delete(sessionId);
|
|
364
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
365
|
+
this.logger.error(`[blok][mcp] sse connect failed: ${msg}`);
|
|
366
|
+
}
|
|
367
|
+
return RESPONSE_ALREADY_SENT;
|
|
368
|
+
});
|
|
369
|
+
// POST <path>/messages?sessionId=… — JSON-RPC messages for an open stream.
|
|
370
|
+
this.app.post(messagesPath, async (c) => {
|
|
371
|
+
const env = c.env;
|
|
372
|
+
const sessionId = new URL(c.req.url).searchParams.get("sessionId") ?? undefined;
|
|
373
|
+
if (!sessionId)
|
|
374
|
+
return c.text("Missing sessionId query parameter", 400);
|
|
375
|
+
const session = this.sseSessions.get(sessionId);
|
|
376
|
+
if (!session)
|
|
377
|
+
return c.text("Session not found", 404);
|
|
378
|
+
const body = await c.req.json().catch(() => undefined);
|
|
379
|
+
try {
|
|
380
|
+
await session.transport.handlePostMessage(env.incoming, env.outgoing, body);
|
|
381
|
+
}
|
|
382
|
+
catch (err) {
|
|
383
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
384
|
+
this.logger.error(`[blok][mcp] handlePostMessage failed: ${msg}`);
|
|
385
|
+
if (!env.outgoing.headersSent)
|
|
386
|
+
return c.text("Error handling MCP message", 500);
|
|
387
|
+
}
|
|
388
|
+
return RESPONSE_ALREADY_SENT;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
registerStreamableHttpRoute(group) {
|
|
392
|
+
this.logger.log(`[blok][mcp] ALL ${group.path} (streamable-http)`);
|
|
393
|
+
// Stateless Streamable-HTTP: a fresh server + transport per request, served
|
|
394
|
+
// directly off the Fetch `Request` (works on any Hono runtime).
|
|
395
|
+
this.app.all(group.path, async (c) => {
|
|
396
|
+
const rawUserCtx = c.req.header("x-user-context") || (new URL(c.req.url).searchParams.get("user_context") ?? undefined);
|
|
397
|
+
const userContext = parseUserContext(rawUserCtx);
|
|
398
|
+
const server = this.buildServer(group, () => userContext);
|
|
399
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
400
|
+
try {
|
|
401
|
+
await server.connect(transport);
|
|
402
|
+
const res = await transport.handleRequest(c.req.raw);
|
|
403
|
+
return res;
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
407
|
+
this.logger.error(`[blok][mcp] streamable-http request failed: ${msg}`);
|
|
408
|
+
return c.json({ jsonrpc: "2.0", error: { code: -32603, message: msg }, id: null }, 500);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// -----------------------------------------------------------------------------
|
|
414
|
+
// Singleton accessor (parity with SSE/Webhook triggers)
|
|
415
|
+
// -----------------------------------------------------------------------------
|
|
416
|
+
let activeTrigger = null;
|
|
417
|
+
export function _setActiveMcpTrigger(trigger) {
|
|
418
|
+
activeTrigger = trigger;
|
|
419
|
+
}
|
|
420
|
+
export function _getActiveMcpTrigger() {
|
|
421
|
+
return activeTrigger;
|
|
422
|
+
}
|
|
423
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWNwVHJpZ2dlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9NY3BUcmlnZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXlDRztBQUdILE9BQU8sRUFDTixhQUFhLEVBRWIsV0FBVyxFQUNYLGdCQUFnQixHQUNoQixNQUFNLGdCQUFnQixDQUFDO0FBRXhCLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQ3pFLE9BQU8sRUFBRSxNQUFNLElBQUksWUFBWSxFQUFFLE1BQU0sMkNBQTJDLENBQUM7QUFDbkYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDN0UsT0FBTyxFQUFFLHdDQUF3QyxFQUFFLE1BQU0sK0RBQStELENBQUM7QUFDekgsT0FBTyxFQUNOLHFCQUFxQixFQUNyQiwwQkFBMEIsRUFDMUIsc0JBQXNCLEVBQ3RCLHlCQUF5QixHQUN6QixNQUFNLG9DQUFvQyxDQUFDO0FBQzVDLE9BQU8sRUFBYSxjQUFjLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRS9FLE9BQU8sRUFBRSxFQUFFLElBQUksSUFBSSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ2xDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQW9FckQsTUFBTSxtQkFBbUIsR0FBRyxVQUFVLENBQUM7QUFDdkMsTUFBTSxzQkFBc0IsR0FBRyxPQUFPLENBQUM7QUFDdkMsTUFBTSxrQkFBa0IsR0FBdUIsQ0FBQyxLQUFLLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztBQUUxRSxnRkFBZ0Y7QUFDaEYsVUFBVTtBQUNWLGdGQUFnRjtBQUVoRiw0RkFBNEY7QUFDNUYsTUFBTSxVQUFVLGdCQUFnQixDQUFDLEtBQWdDO0lBQ2hFLElBQUksQ0FBQyxLQUFLLElBQUksT0FBTyxLQUFLLEtB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TUFBTSxVQUFVLG9CQUFvQjtJQUNuQyxPQUFPLGFBQWEsQ0FBQztBQUN0QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBNY3BUcmlnZ2VyIOKAlCBNb2RlbCBDb250ZXh0IFByb3RvY29sIHRyaWdnZXIuIEV4cG9zZXMgQmxvayB3b3JrZmxvd3MgYXNcbiAqIE1DUCAqKnRvb2xzKiogKGFuZCAqKnJlc291cmNlcyoqKSB0byBleHRlcm5hbCBjbGllbnRzIChDdXJzb3IsIENsYXVkZVxuICogQ29kZSwg4oCmKSBvdmVyIHR3byB0cmFuc3BvcnRzIG11bHRpcGxleGVkIG9uIHRoZSBzaGFyZWQgSG9ubyBwb3J0OlxuICpcbiAqICAgLSAqKlNTRSAobGVnYWN5IDIwMjQtMTEtMDUpKiog4oCUIGBHRVQgPHBhdGg+L3NzZWAgb3BlbnMgdGhlIHN0cmVhbSBhbmRcbiAqICAgICBhbm5vdW5jZXMgYFBPU1QgPHBhdGg+L21lc3NhZ2VzP3Nlc3Npb25JZD3igKZgIGZvciBKU09OLVJQQy4gVGhpcyBpcyB0aGVcbiAqICAgICAyLWVuZHBvaW50IHNoYXBlIGV4aXN0aW5nIElERSBjb25maWdzIGV4cGVjdCAoZHJvcC1pbiBwYXJpdHkpLlxuICogICAtICoqU3RyZWFtYWJsZS1IVFRQKiog4oCUIGEgc2luZ2xlIGA8cGF0aD5gIGVuZHBvaW50ICh0aGUgY3VycmVudCBvZmZpY2lhbFxuICogICAgIHJlbW90ZSB0cmFuc3BvcnQpLCBzZXJ2ZWQgc3RhdGVsZXNzbHkgdmlhIHRoZSBTREsncyB3ZWItc3RhbmRhcmRcbiAqICAgICB0cmFuc3BvcnQgZGlyZWN0bHkgb2ZmIGBjLnJlcS5yYXdgLlxuICpcbiAqICoqQXV0aG9yaW5nIHN1cmZhY2UqKiDigJQgYSB3b3JrZmxvdyBvcHRzIGluIHdpdGggYHRyaWdnZXIubWNwYDpcbiAqXG4gKiBgYGB0c1xuICogZXhwb3J0IGRlZmF1bHQgd29ya2Zsb3coe1xuICogICBuYW1lOiBcInNlYXJjaF9jb2RlXCIsXG4gKiAgIHZlcnNpb246IFwiMS4wLjBcIixcbiAqICAgaW5wdXQ6IHoub2JqZWN0KHsgcXVlcnk6IHouc3RyaW5nKCksIGxpbWl0OiB6Lm51bWJlcigpLm9wdGlvbmFsKCkgfSksXG4gKiAgIHRyaWdnZXI6IHsgbWNwOiB7IHBhdGg6IFwiL21jcFwiLCBzZXJ2ZXJOYW1lOiBcInRldHJpeC1wbGF0Zm9ybVwiLFxuICogICAgICAgICAgICAgICAgICAgICB0b29sOiB7IGRlc2NyaXB0aW9uOiBcIkZ1bGwtdGV4dCBzZWFyY2ggdGhlIGluZGV4ZWQgY29kZVwiIH0gfSB9LFxuICogICBzdGVwczogWyB7IGlkOiBcInNlYXJjaFwiLCB1c2U6IFwiQHRldHJpeC9tZWlsaS1zZWFyY2hcIiwgaW5wdXRzOiB7IHF1ZXJ5OiAkLnJlcS5ib2R5LnF1ZXJ5IH0gfSBdLFxuICogfSk7XG4gKiBgYGBcbiAqXG4gKiBFdmVyeSB3b3JrZmxvdyBzaGFyaW5nIHRoZSBzYW1lIGBwYXRoYCArIGBzZXJ2ZXJOYW1lYCBpcyBhZ2dyZWdhdGVkIGludG8gb25lXG4gKiBNQ1Agc2VydmVyLiBFYWNoIHRvb2wncyBgaW5wdXRTY2hlbWFgIGlzIGdlbmVyYXRlZCBmcm9tIHRoZSB3b3JrZmxvdydzIGBpbnB1dGBcbiAqIFpvZCBzY2hlbWEgKHZpYSB6b2QtdG8tanNvbi1zY2hlbWEpLiBPbiBgdG9vbHMvY2FsbGAgdGhlIHRyaWdnZXIgcnVucyB0aGVcbiAqIG1hcHBlZCB3b3JrZmxvdyB0aHJvdWdoIHRoZSBydW5uZXIg4oCUIHNvIGV2ZXJ5IGNhbGwgaXMgYSBCbG9rIFN0dWRpbyBydW4gd2l0aFxuICogdHJhY2luZyAvIHJldHJpZXMgLyBpZGVtcG90ZW5jeSBmb3IgZnJlZS5cbiAqXG4gKiAqKkhvbm8gaW50ZWdyYXRpb246KiogaWRlbnRpY2FsIHRvIGBTU0VUcmlnZ2VyYC9gV2ViaG9va1RyaWdnZXJgIOKAlCBhY2NlcHRzIHRoZVxuICogc2hhcmVkIGBIb25vYCBhcHAgKyBhbiBvcHRpb25hbCBgSHR0cFRyaWdnZXJMaWtlYCBleHBvc2luZyBgYWRkUHJlQ2F0Y2hBbGxIb29rYFxuICogc28gcm91dGVzIHJlZ2lzdGVyIEFGVEVSIHRoZSB3b3JrZmxvdyByZWdpc3RyeSBpcyBwb3B1bGF0ZWQgYnV0IEJFRk9SRSB0aGVcbiAqIGxlZ2FjeSBgLzp3b3JrZmxvd3suK31gIGNhdGNoLWFsbC5cbiAqXG4gKiAqKklkZW50aXR5OioqIGFuIGB4LXVzZXItY29udGV4dGAgaGVhZGVyIChvciBgP3VzZXJfY29udGV4dD1gKSBjYXJyeWluZyBiYXNlNjRcbiAqIGB7dXNlcklkLGVtYWlsfWAgaXMgcGFyc2VkIHBlciBjb25uZWN0aW9uIGFuZCBwYXNzZWQgdG8gdGhlIHdvcmtmbG93IGN0eCBmb3JcbiAqIGNyZWRlbnRpYWwgaW5qZWN0aW9uLiBJdCBpcyBOT1QgYWNjZXNzIGNvbnRyb2wg4oCUIHRoZXJlIGlzIG5vIHNjb3BpbmcgaGVyZVxuICogKHRoYXQncyB0aGUgYXBwJ3MgY2FsbCkuIFRva2VucyBhcmUgbmV2ZXIgbG9nZ2VkICh0aGUgZ2xvYmFsIHNhbml0aXplciBtaWRkbGV3YXJlXG4gKiBoYW5kbGVzIHJlZGFjdGlvbikuXG4gKi9cblxuaW1wb3J0IHR5cGUgeyBJbmNvbWluZ01lc3NhZ2UsIFNlcnZlclJlc3BvbnNlIH0gZnJvbSBcIm5vZGU6aHR0cFwiO1xuaW1wb3J0IHtcblx0RGVmYXVsdExvZ2dlcixcblx0dHlwZSBHbG9iYWxPcHRpb25zIGFzIFJ1bm5lckdsb2JhbE9wdGlvbnMsXG5cdFRyaWdnZXJCYXNlLFxuXHRXb3JrZmxvd1JlZ2lzdHJ5LFxufSBmcm9tIFwiQGJsb2tqcy9ydW5uZXJcIjtcbmltcG9ydCB0eXBlIHsgQ29udGV4dCwgUmVxdWVzdENvbnRleHQgfSBmcm9tIFwiQGJsb2tqcy9zaGFyZWRcIjtcbmltcG9ydCB7IFJFU1BPTlNFX0FMUkVBRFlfU0VOVCB9IGZyb20gXCJAaG9uby9ub2RlLXNlcnZlci91dGlscy9yZXNwb25zZVwiO1xuaW1wb3J0IHsgU2VydmVyIGFzIE1jcFNka1NlcnZlciB9IGZyb20gXCJAbW9kZWxjb250ZXh0cHJvdG9jb2wvc2RrL3NlcnZlci9pbmRleC5qc1wiO1xuaW1wb3J0IHsgU1NFU2VydmVyVHJhbnNwb3J0IH0gZnJvbSBcIkBtb2RlbGNvbnRleHRwcm90b2NvbC9zZGsvc2VydmVyL3NzZS5qc1wiO1xuaW1wb3J0IHsgV2ViU3RhbmRhcmRTdHJlYW1hYmxlSFRUUFNlcnZlclRyYW5zcG9ydCB9IGZyb20gXCJAbW9kZWxjb250ZXh0cHJvdG9jb2wvc2RrL3NlcnZlci93ZWJTdGFuZGFyZFN0cmVhbWFibGVIdHRwLmpzXCI7XG5pbXBvcnQge1xuXHRDYWxsVG9vbFJlcXVlc3RTY2hlbWEsXG5cdExpc3RSZXNvdXJjZXNSZXF1ZXN0U2NoZW1hLFxuXHRMaXN0VG9vbHNSZXF1ZXN0U2NoZW1hLFxuXHRSZWFkUmVzb3VyY2VSZXF1ZXN0U2NoZW1hLFxufSBmcm9tIFwiQG1vZGVsY29udGV4dHByb3RvY29sL3Nkay90eXBlcy5qc1wiO1xuaW1wb3J0IHsgdHlwZSBTcGFuLCBTcGFuU3RhdHVzQ29kZSwgbWV0cmljcywgdHJhY2UgfSBmcm9tIFwiQG9wZW50ZWxlbWV0cnkvYXBpXCI7XG5pbXBvcnQgdHlwZSB7IEhvbm8sIENvbnRleHQgYXMgSG9ub0NvbnRleHQgfSBmcm9tIFwiaG9ub1wiO1xuaW1wb3J0IHsgdjQgYXMgdXVpZCB9IGZyb20gXCJ1dWlkXCI7XG5pbXBvcnQgeyB6b2RUb0pzb25TY2hlbWEgfSBmcm9tIFwiem9kLXRvLWpzb24tc2NoZW1hXCI7XG5cbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4vLyBUeXBlc1xuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLyoqIFBhcnNlZCBgeC11c2VyLWNvbnRleHRgIGlkZW50aXR5IChjcmVkZW50aWFsIGluamVjdGlvbiBvbmx5IOKAlCBuZXZlciBzY29waW5nKS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgTWNwVXNlckNvbnRleHQge1xuXHR1c2VySWQ6IHN0cmluZztcblx0ZW1haWw6IHN0cmluZztcbn1cblxudHlwZSBNY3BUcmFuc3BvcnRLaW5kID0gXCJzc2VcIiB8IFwic3RyZWFtYWJsZS1odHRwXCI7XG5cbmludGVyZmFjZSBNY3BUb29sTWV0YSB7XG5cdG5hbWU/OiBzdHJpbmc7XG5cdGRlc2NyaXB0aW9uPzogc3RyaW5nO1xufVxuaW50ZXJmYWNlIE1jcFJlc291cmNlTWV0YSB7XG5cdHVyaTogc3RyaW5nO1xuXHRuYW1lPzogc3RyaW5nO1xuXHRkZXNjcmlwdGlvbj86IHN0cmluZztcblx0bWltZVR5cGU/OiBzdHJpbmc7XG59XG5cbi8qKiBMb29zZWx5LXJlYWQgYHRyaWdnZXIubWNwYCBjb25maWcgKHZhbGlkYXRlZCBhdCB3b3JrZmxvdyBsb2FkIGJ5IHRoZSBoZWxwZXIpLiAqL1xuaW50ZXJmYWNlIE1jcFRyaWdnZXJDb25maWcge1xuXHRwYXRoOiBzdHJpbmc7XG5cdHNlcnZlck5hbWU/OiBzdHJpbmc7XG5cdHNlcnZlclZlcnNpb24/OiBzdHJpbmc7XG5cdHRyYW5zcG9ydHM/OiBNY3BUcmFuc3BvcnRLaW5kW107XG5cdHRvb2w/OiBNY3BUb29sTWV0YTtcblx0cmVzb3VyY2U/OiBNY3BSZXNvdXJjZU1ldGE7XG5cdG1pZGRsZXdhcmU/OiBzdHJpbmdbXTtcbn1cblxuaW50ZXJmYWNlIEh0dHBUcmlnZ2VyTGlrZSB7XG5cdGFkZFByZUNhdGNoQWxsSG9vayhjYjogKCkgPT4gdm9pZCB8IFByb21pc2U8dm9pZD4pOiB2b2lkO1xufVxuXG4vKiogQSB3b3JrZmxvdyBleHBvc2VkIGFzIGFuIE1DUCB0b29sLiAqL1xuaW50ZXJmYWNlIFRvb2xFbnRyeSB7XG5cdHdvcmtmbG93TmFtZTogc3RyaW5nO1xuXHR0b29sTmFtZTogc3RyaW5nO1xuXHRkZXNjcmlwdGlvbjogc3RyaW5nO1xuXHQvLyBiaW9tZS1pZ25vcmUgbGludC9zdXNwaWNpb3VzL25vRXhwbGljaXRBbnk6IHdvcmtmbG93IGBpbnB1dGAgaXMgYW4gb3BhcXVlIFpvZFR5cGVcblx0aW5wdXRab2Q6IGFueSB8IHVuZGVmaW5lZDtcbn1cblxuLyoqIEEgd29ya2Zsb3cgZXhwb3NlZCBhcyBhbiBNQ1AgcmVzb3VyY2UuICovXG5pbnRlcmZhY2UgUmVzb3VyY2VFbnRyeSB7XG5cdHdvcmtmbG93TmFtZTogc3RyaW5nO1xuXHR1cmk6IHN0cmluZztcblx0bmFtZTogc3RyaW5nO1xuXHRkZXNjcmlwdGlvbj86IHN0cmluZztcblx0bWltZVR5cGU6IHN0cmluZztcbn1cblxuLyoqIEFsbCB3b3JrZmxvd3MgdGhhdCBzaGFyZSBhIChwYXRoLCBzZXJ2ZXJOYW1lKSBmb3JtIG9uZSBNQ1Agc2VydmVyLiAqL1xuaW50ZXJmYWNlIFNlcnZlckdyb3VwIHtcblx0cGF0aDogc3RyaW5nO1xuXHRzZXJ2ZXJOYW1lOiBzdHJpbmc7XG5cdHNlcnZlclZlcnNpb246IHN0cmluZztcblx0dHJhbnNwb3J0czogTWNwVHJhbnNwb3J0S2luZFtdO1xuXHR0b29sczogVG9vbEVudHJ5W107XG5cdHJlc291cmNlczogUmVzb3VyY2VFbnRyeVtdO1xufVxuXG5jb25zdCBERUZBVUxUX1NFUlZFUl9OQU1FID0gXCJibG9rLW1jcFwiO1xuY29uc3QgREVGQVVMVF9TRVJWRVJfVkVSU0lPTiA9IFwiMS4wLjBcIjtcbmNvbnN0IERFRkFVTFRfVFJBTlNQT1JUUzogTWNwVHJhbnNwb3J0S2luZFtdID0gW1wic3NlXCIsIFwic3RyZWFtYWJsZS1odHRwXCJdO1xuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gSGVscGVyc1xuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuLyoqIFBhcnNlIGEgYmFzZTY0LWVuY29kZWQgYHt1c2VySWQsZW1haWx9YCBpZGVudGl0eSBzdHJpbmcuIFJldHVybnMgbnVsbCBvbiBhbnkgZmFpbHVyZS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVVzZXJDb250ZXh0KHZhbHVlOiBzdHJpbmcgfCB1bmRlZmluZWQgfCBudWxsKTogTWNwVXNlckNvbnRleHQgfCBudWxsIHtcblx0aWYgKCF2YWx1ZSB8fCB0eXBlb2YgdmFsdWUgIT09IFwic3RyaW5nXCIpIHJldHVybiBudWxsO1xuXHR0cnkge1xuXHRcdGNvbnN0IGRlY29kZWQgPSBCdWZmZXIuZnJvbSh2YWx1ZSwgXCJiYXNlNjRcIikudG9TdHJpbmcoXCJ1dGYtOFwiKTtcblx0XHRpZiAoIWRlY29kZWQgfHwgZGVjb2RlZC50cmltKCkgPT09IFwibnVsbFwiKSByZXR1cm4gbnVsbDtcblx0XHRjb25zdCBjdHggPSBKU09OLnBhcnNlKGRlY29kZWQpIGFzIHsgdXNlcklkPzogc3RyaW5nOyBlbWFpbD86IHN0cmluZyB9O1xuXHRcdGlmICghY3R4IHx8IHR5cGVvZiBjdHgudXNlcklkICE9PSBcInN0cmluZ1wiKSByZXR1cm4gbnVsbDtcblx0XHRyZXR1cm4geyB1c2VySWQ6IGN0eC51c2VySWQsIGVtYWlsOiB0eXBlb2YgY3R4LmVtYWlsID09PSBcInN0cmluZ1wiID8gY3R4LmVtYWlsIDogXCJ1bmtub3duXCIgfTtcblx0fSBjYXRjaCB7XG5cdFx0cmV0dXJuIG51bGw7XG5cdH1cbn1cblxuLyoqIENvbnZlcnQgYSBab2Qgc2NoZW1hIHRvIGEgSlNPTi1TY2hlbWEgb2JqZWN0IHN1aXRhYmxlIGZvciBhbiBNQ1AgdG9vbCBgaW5wdXRTY2hlbWFgLiAqL1xuLy8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiB6b2Qgc2NoZW1hIGlzIG9wYXF1ZSBoZXJlXG5mdW5jdGlvbiB0b0lucHV0SnNvblNjaGVtYShpbnB1dFpvZDogYW55IHwgdW5kZWZpbmVkKTogeyB0eXBlOiBcIm9iamVjdFwiOyBbazogc3RyaW5nXTogdW5rbm93biB9IHtcblx0Y29uc3QgZW1wdHkgPSB7IHR5cGU6IFwib2JqZWN0XCIgYXMgY29uc3QsIHByb3BlcnRpZXM6IHt9LCBhZGRpdGlvbmFsUHJvcGVydGllczogdHJ1ZSB9O1xuXHRpZiAoIWlucHV0Wm9kIHx8IHR5cGVvZiBpbnB1dFpvZCAhPT0gXCJvYmplY3RcIiB8fCB0eXBlb2YgaW5wdXRab2Quc2FmZVBhcnNlICE9PSBcImZ1bmN0aW9uXCIpIHtcblx0XHRyZXR1cm4gZW1wdHk7XG5cdH1cblx0dHJ5IHtcblx0XHRjb25zdCBqc29uID0gem9kVG9Kc29uU2NoZW1hKGlucHV0Wm9kLCB7IHRhcmdldDogXCJqc29uU2NoZW1hN1wiLCAkcmVmU3RyYXRlZ3k6IFwibm9uZVwiIH0pIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuXHRcdC8vIGJpb21lLWlnbm9yZSBsaW50L3BlcmZvcm1hbmNlL25vRGVsZXRlOiBzdHJpcCBKU09OLVNjaGVtYSBtZXRhIHRoZSBNQ1AgY2xpZW50IGRvZXNuJ3QgbmVlZFxuXHRcdGRlbGV0ZSBqc29uLiRzY2hlbWE7XG5cdFx0aWYgKGpzb24udHlwZSAhPT0gXCJvYmplY3RcIikge1xuXHRcdFx0cmV0dXJuIHsgLi4uZW1wdHksIF93cmFwcGVkOiBqc29uIH0gYXMgeyB0eXBlOiBcIm9iamVjdFwiOyBbazogc3RyaW5nXTogdW5rbm93biB9O1xuXHRcdH1cblx0XHRyZXR1cm4ganNvbiBhcyB7IHR5cGU6IFwib2JqZWN0XCI7IFtrOiBzdHJpbmddOiB1bmtub3duIH07XG5cdH0gY2F0Y2gge1xuXHRcdHJldHVybiBlbXB0eTtcblx0fVxufVxuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gVHJpZ2dlclxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgTWNwVHJpZ2dlciBleHRlbmRzIFRyaWdnZXJCYXNlIHtcblx0cHJvdGVjdGVkIG5vZGVNYXA6IFJ1bm5lckdsb2JhbE9wdGlvbnMgPSB7fSBhcyBSdW5uZXJHbG9iYWxPcHRpb25zO1xuXHRwcm90ZWN0ZWQgcmVhZG9ubHkgbG9nZ2VyID0gbmV3IERlZmF1bHRMb2dnZXIoKTtcblx0cHJvdGVjdGVkIHJlYWRvbmx5IHRyYWNlciA9IHRyYWNlLmdldFRyYWNlcihcblx0XHRwcm9jZXNzLmVudi5QUk9KRUNUX05BTUUgfHwgXCJ0cmlnZ2VyLW1jcC13b3JrZmxvd1wiLFxuXHRcdHByb2Nlc3MuZW52LlBST0pFQ1RfVkVSU0lPTiB8fCBcIjAuMC4xXCIsXG5cdCk7XG5cdHByaXZhdGUgcmVhZG9ubHkgbWV0ZXIgPSBtZXRyaWNzLmdldE1ldGVyKFwiYmxva1wiKTtcblx0cHJpdmF0ZSByZWFkb25seSBjb3VudGVyVG9vbENhbGxzID0gdGhpcy5tZXRlci5jcmVhdGVDb3VudGVyKFwiYmxva19tY3BfdG9vbF9jYWxsc190b3RhbFwiLCB7XG5cdFx0ZGVzY3JpcHRpb246IFwiTUNQIHRvb2xzL2NhbGwgZGlzcGF0Y2hlcy5cIixcblx0XHR1bml0OiBcIjFcIixcblx0fSk7XG5cdHByaXZhdGUgcmVhZG9ubHkgY291bnRlclNlc3Npb25zID0gdGhpcy5tZXRlci5jcmVhdGVDb3VudGVyKFwiYmxva19tY3Bfc3NlX3Nlc3Npb25zX3RvdGFsXCIsIHtcblx0XHRkZXNjcmlwdGlvbjogXCJNQ1AgU1NFIHNlc3Npb25zIG9wZW5lZC5cIixcblx0XHR1bml0OiBcIjFcIixcblx0fSk7XG5cblx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiBIb25vIGdlbmVyaWMgcHJvcGFnYXRpb24gKG1hdGNoZXMgU1NFL1dTIHRyaWdnZXJzKVxuXHRwcml2YXRlIHJlYWRvbmx5IGFwcDogSG9ubzxhbnksIGFueSwgYW55Pjtcblx0cHJpdmF0ZSByZWFkb25seSBodHRwVHJpZ2dlcjogSHR0cFRyaWdnZXJMaWtlIHwgbnVsbDtcblx0cHJpdmF0ZSB3aXJlZCA9IGZhbHNlO1xuXG5cdC8qKiBMaXZlIFNTRSBzZXNzaW9uczogc2Vzc2lvbklkIOKGkiB7IHRyYW5zcG9ydCwgc2VydmVyLCB1c2VyQ29udGV4dCB9LiAqL1xuXHRwcml2YXRlIHNzZVNlc3Npb25zID0gbmV3IE1hcDxcblx0XHRzdHJpbmcsXG5cdFx0eyB0cmFuc3BvcnQ6IFNTRVNlcnZlclRyYW5zcG9ydDsgc2VydmVyOiBNY3BTZGtTZXJ2ZXI7IHVzZXJDb250ZXh0OiBNY3BVc2VyQ29udGV4dCB8IG51bGwgfVxuXHQ+KCk7XG5cblx0Ly8gYmlvbWUtaWdub3JlIGxpbnQvc3VzcGljaW91cy9ub0V4cGxpY2l0QW55OiBtYXRjaGVzIGBhcHBgIGZpZWxkIGdlbmVyaWNcblx0Y29uc3RydWN0b3IoYXBwOiBIb25vPGFueSwgYW55LCBhbnk+LCBodHRwVHJpZ2dlcj86IEh0dHBUcmlnZ2VyTGlrZSkge1xuXHRcdHN1cGVyKCk7XG5cdFx0dGhpcy5hcHAgPSBhcHA7XG5cdFx0dGhpcy5odHRwVHJpZ2dlciA9IGh0dHBUcmlnZ2VyID8/IG51bGw7XG5cdFx0X3NldEFjdGl2ZU1jcFRyaWdnZXIodGhpcyk7XG5cdH1cblxuXHQvKiogSW5qZWN0IHRoZSBydW5uZXIncyBHbG9iYWxPcHRpb25zIChub2RlcyArIHdvcmtmbG93cykuIENhbGxlZCBiZWZvcmUgYGxpc3RlbigpYC4gKi9cblx0c2V0Tm9kZU1hcChub2RlTWFwOiBSdW5uZXJHbG9iYWxPcHRpb25zKTogdm9pZCB7XG5cdFx0dGhpcy5ub2RlTWFwID0gbm9kZU1hcDtcblx0fVxuXG5cdGFzeW5jIGxpc3RlbigpOiBQcm9taXNlPG51bWJlcj4ge1xuXHRcdGNvbnN0IHN0YXJ0VGltZSA9IHRoaXMuc3RhcnRDb3VudGVyKCk7XG5cdFx0aWYgKHRoaXMud2lyZWQpIHtcblx0XHRcdHRoaXMubG9nZ2VyLmxvZyhcIltibG9rXVttY3BdIGxpc3RlbigpIGNhbGxlZCB0d2ljZTsgaWdub3JpbmdcIik7XG5cdFx0XHRyZXR1cm4gdGhpcy5lbmRDb3VudGVyKHN0YXJ0VGltZSk7XG5cdFx0fVxuXHRcdHRoaXMud2lyZWQgPSB0cnVlO1xuXG5cdFx0aWYgKHRoaXMuaHR0cFRyaWdnZXIpIHtcblx0XHRcdHRoaXMuaHR0cFRyaWdnZXIuYWRkUHJlQ2F0Y2hBbGxIb29rKCgpID0+IHRoaXMucmVnaXN0ZXJSb3V0ZXNGcm9tUmVnaXN0cnkoKSk7XG5cdFx0fSBlbHNlIHtcblx0XHRcdHRoaXMucmVnaXN0ZXJSb3V0ZXNGcm9tUmVnaXN0cnkoKTtcblx0XHR9XG5cdFx0cmV0dXJuIHRoaXMuZW5kQ291bnRlcihzdGFydFRpbWUpO1xuXHR9XG5cblx0YXN5bmMgc3RvcCgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRmb3IgKGNvbnN0IHsgdHJhbnNwb3J0IH0gb2YgdGhpcy5zc2VTZXNzaW9ucy52YWx1ZXMoKSkge1xuXHRcdFx0dHJ5IHtcblx0XHRcdFx0YXdhaXQgdHJhbnNwb3J0LmNsb3NlKCk7XG5cdFx0XHR9IGNhdGNoIHtcblx0XHRcdFx0LyogaWdub3JlICovXG5cdFx0XHR9XG5cdFx0fVxuXHRcdHRoaXMuc3NlU2Vzc2lvbnMuY2xlYXIoKTtcblx0XHR0aGlzLndpcmVkID0gZmFsc2U7XG5cdFx0aWYgKF9nZXRBY3RpdmVNY3BUcmlnZ2VyKCkgPT09IHRoaXMpIF9zZXRBY3RpdmVNY3BUcmlnZ2VyKG51bGwpO1xuXHRcdHRoaXMuZGVzdHJveU1vbml0b3JpbmcoKTtcblx0XHR0aGlzLmxvZ2dlci5sb2coXCJbYmxva11bbWNwXSBzdG9wcGVkXCIpO1xuXHR9XG5cblx0Z2V0U3RhdHMoKTogeyBzZXNzaW9uczogbnVtYmVyIH0ge1xuXHRcdHJldHVybiB7IHNlc3Npb25zOiB0aGlzLnNzZVNlc3Npb25zLnNpemUgfTtcblx0fVxuXG5cdC8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHQvLyBSZWdpc3RyeSBzY2FuICsgZ3JvdXBpbmdcblx0Ly8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cblx0cHJpdmF0ZSByZWdpc3RlclJvdXRlc0Zyb21SZWdpc3RyeSgpOiB2b2lkIHtcblx0XHRjb25zdCBncm91cHMgPSB0aGlzLmdldFNlcnZlckdyb3VwcygpO1xuXHRcdGlmIChncm91cHMubGVuZ3RoID09PSAwKSB7XG5cdFx0XHR0aGlzLmxvZ2dlci5sb2coXCJbYmxva11bbWNwXSBubyB3b3JrZmxvd3Mgd2l0aCB0cmlnZ2VyLm1jcCBmb3VuZFwiKTtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0Zm9yIChjb25zdCBncm91cCBvZiBncm91cHMpIHtcblx0XHRcdHRoaXMucmVnaXN0ZXJHcm91cFJvdXRlcyhncm91cCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBnZXRTZXJ2ZXJHcm91cHMoKTogU2VydmVyR3JvdXBbXSB7XG5cdFx0Y29uc3QgcmVnaXN0cnkgPSBXb3JrZmxvd1JlZ2lzdHJ5LmdldEluc3RhbmNlKCk7XG5cdFx0Y29uc3QgYnlQYXRoID0gbmV3IE1hcDxzdHJpbmcsIFNlcnZlckdyb3VwPigpO1xuXG5cdFx0Zm9yIChjb25zdCBlbnRyeSBvZiByZWdpc3RyeS5saXN0KCkpIHtcblx0XHRcdC8vIFdvcmtmbG93cyByZWdpc3RlcmVkIGFzIGJ1aWxkZXJzIGV4cG9zZSBjb25maWcgb24gYF9jb25maWdgOyBwbGFpblxuXHRcdFx0Ly8gb2JqZWN0cyAodGVzdHMgLyBKU09OKSBleHBvc2UgaXQgYXQgdGhlIHRvcCBsZXZlbC5cblx0XHRcdGNvbnN0IHdmID0gKGVudHJ5LndvcmtmbG93IGFzIHsgX2NvbmZpZz86IHVua25vd24gfSk/Ll9jb25maWcgPz8gZW50cnkud29ya2Zsb3c7XG5cdFx0XHRjb25zdCBjZmcgPSAod2YgYXMgeyB0cmlnZ2VyPzogeyBtY3A/OiBNY3BUcmlnZ2VyQ29uZmlnIH0gfSk/LnRyaWdnZXI/Lm1jcDtcblx0XHRcdGlmICghY2ZnIHx8IHR5cGVvZiBjZmcucGF0aCAhPT0gXCJzdHJpbmdcIikgY29udGludWU7XG5cblx0XHRcdGNvbnN0IHBhdGggPSBjZmcucGF0aDtcblx0XHRcdGxldCBncm91cCA9IGJ5UGF0aC5nZXQocGF0aCk7XG5cdFx0XHRpZiAoIWdyb3VwKSB7XG5cdFx0XHRcdGdyb3VwID0ge1xuXHRcdFx0XHRcdHBhdGgsXG5cdFx0XHRcdFx0c2VydmVyTmFtZTogY2ZnLnNlcnZlck5hbWUgfHwgREVGQVVMVF9TRVJWRVJfTkFNRSxcblx0XHRcdFx0XHRzZXJ2ZXJWZXJzaW9uOiBjZmcuc2VydmVyVmVyc2lvbiB8fCBERUZBVUxUX1NFUlZFUl9WRVJTSU9OLFxuXHRcdFx0XHRcdHRyYW5zcG9ydHM6IEFycmF5LmlzQXJyYXkoY2ZnLnRyYW5zcG9ydHMpICYmIGNmZy50cmFuc3BvcnRzLmxlbmd0aCA+IDAgPyBjZmcudHJhbnNwb3J0cyA6IERFRkFVTFRfVFJBTlNQT1JUUyxcblx0XHRcdFx0XHR0b29sczogW10sXG5cdFx0XHRcdFx0cmVzb3VyY2VzOiBbXSxcblx0XHRcdFx0fTtcblx0XHRcdFx0YnlQYXRoLnNldChwYXRoLCBncm91cCk7XG5cdFx0XHR9XG5cblx0XHRcdGlmIChjZmcucmVzb3VyY2UgJiYgdHlwZW9mIGNmZy5yZXNvdXJjZS51cmkgPT09IFwic3RyaW5nXCIpIHtcblx0XHRcdFx0Z3JvdXAucmVzb3VyY2VzLnB1c2goe1xuXHRcdFx0XHRcdHdvcmtmbG93TmFtZTogZW50cnkubmFtZSxcblx0XHRcdFx0XHR1cmk6IGNmZy5yZXNvdXJjZS51cmksXG5cdFx0XHRcdFx0bmFtZTogY2ZnLnJlc291cmNlLm5hbWUgfHwgZW50cnkubmFtZSxcblx0XHRcdFx0XHRkZXNjcmlwdGlvbjogY2ZnLnJlc291cmNlLmRlc2NyaXB0aW9uLFxuXHRcdFx0XHRcdG1pbWVUeXBlOiBjZmcucmVzb3VyY2UubWltZVR5cGUgfHwgXCJhcHBsaWNhdGlvbi9qc29uXCIsXG5cdFx0XHRcdH0pO1xuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0Y29uc3QgaW5wdXRab2QgPSAod2YgYXMgeyBpbnB1dD86IHVua25vd24gfSk/LmlucHV0O1xuXHRcdFx0XHRncm91cC50b29scy5wdXNoKHtcblx0XHRcdFx0XHR3b3JrZmxvd05hbWU6IGVudHJ5Lm5hbWUsXG5cdFx0XHRcdFx0dG9vbE5hbWU6IGNmZy50b29sPy5uYW1lIHx8IGVudHJ5Lm5hbWUsXG5cdFx0XHRcdFx0ZGVzY3JpcHRpb246IGNmZy50b29sPy5kZXNjcmlwdGlvbiB8fCBgUnVuIHRoZSBcIiR7ZW50cnkubmFtZX1cIiB3b3JrZmxvdy5gLFxuXHRcdFx0XHRcdGlucHV0Wm9kLFxuXHRcdFx0XHR9KTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRyZXR1cm4gWy4uLmJ5UGF0aC52YWx1ZXMoKV07XG5cdH1cblxuXHQvLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0Ly8gTUNQIHNlcnZlciBmYWN0b3J5IChvbmUgcGVyIHJlcXVlc3Qvc2Vzc2lvbiwgYm91bmQgdG8gYSB1c2VyIGNvbnRleHQpXG5cdC8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5cdHByaXZhdGUgYnVpbGRTZXJ2ZXIoZ3JvdXA6IFNlcnZlckdyb3VwLCBnZXRVc2VyQ29udGV4dDogKCkgPT4gTWNwVXNlckNvbnRleHQgfCBudWxsKTogTWNwU2RrU2VydmVyIHtcblx0XHRjb25zdCBzZXJ2ZXIgPSBuZXcgTWNwU2RrU2VydmVyKFxuXHRcdFx0eyBuYW1lOiBncm91cC5zZXJ2ZXJOYW1lLCB2ZXJzaW9uOiBncm91cC5zZXJ2ZXJWZXJzaW9uIH0sXG5cdFx0XHR7IGNhcGFiaWxpdGllczogeyB0b29sczoge30sIHJlc291cmNlczoge30gfSB9LFxuXHRcdCk7XG5cblx0XHRzZXJ2ZXIuc2V0UmVxdWVzdEhhbmRsZXIoTGlzdFRvb2xzUmVxdWVzdFNjaGVtYSwgYXN5bmMgKCkgPT4gKHtcblx0XHRcdHRvb2xzOiBncm91cC50b29scy5tYXAoKHQpID0+ICh7XG5cdFx0XHRcdG5hbWU6IHQudG9vbE5hbWUsXG5cdFx0XHRcdGRlc2NyaXB0aW9uOiB0LmRlc2NyaXB0aW9uLFxuXHRcdFx0XHRpbnB1dFNjaGVtYTogdG9JbnB1dEpzb25TY2hlbWEodC5pbnB1dFpvZCksXG5cdFx0XHR9KSksXG5cdFx0fSkpO1xuXG5cdFx0c2VydmVyLnNldFJlcXVlc3RIYW5kbGVyKENhbGxUb29sUmVxdWVzdFNjaGVtYSwgYXN5bmMgKHJlcSkgPT4ge1xuXHRcdFx0Y29uc3QgdG9vbE5hbWUgPSByZXEucGFyYW1zLm5hbWU7XG5cdFx0XHRjb25zdCB0b29sID0gZ3JvdXAudG9vbHMuZmluZCgodCkgPT4gdC50b29sTmFtZSA9PT0gdG9vbE5hbWUpO1xuXHRcdFx0aWYgKCF0b29sKSB7XG5cdFx0XHRcdHJldHVybiB7IGNvbnRlbnQ6IFt7IHR5cGU6IFwidGV4dFwiLCB0ZXh0OiBgVW5rbm93biB0b29sOiAke3Rvb2xOYW1lfWAgfV0sIGlzRXJyb3I6IHRydWUgfTtcblx0XHRcdH1cblx0XHRcdGNvbnN0IGFyZ3MgPSAocmVxLnBhcmFtcy5hcmd1bWVudHMgPz8ge30pIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuXHRcdFx0cmV0dXJuIHRoaXMuZGlzcGF0Y2hUb29sKHRvb2wsIGFyZ3MsIGdldFVzZXJDb250ZXh0KCkpO1xuXHRcdH0pO1xuXG5cdFx0aWYgKGdyb3VwLnJlc291cmNlcy5sZW5ndGggPiAwKSB7XG5cdFx0XHRzZXJ2ZXIuc2V0UmVxdWVzdEhhbmRsZXIoTGlzdFJlc291cmNlc1JlcXVlc3RTY2hlbWEsIGFzeW5jICgpID0+ICh7XG5cdFx0XHRcdHJlc291cmNlczogZ3JvdXAucmVzb3VyY2VzLm1hcCgocikgPT4gKHtcblx0XHRcdFx0XHR1cmk6IHIudXJpLFxuXHRcdFx0XHRcdG5hbWU6IHIubmFtZSxcblx0XHRcdFx0XHRkZXNjcmlwdGlvbjogci5kZXNjcmlwdGlvbixcblx0XHRcdFx0XHRtaW1lVHlwZTogci5taW1lVHlwZSxcblx0XHRcdFx0fSkpLFxuXHRcdFx0fSkpO1xuXG5cdFx0XHRzZXJ2ZXIuc2V0UmVxdWVzdEhhbmRsZXIoUmVhZFJlc291cmNlUmVxdWVzdFNjaGVtYSwgYXN5bmMgKHJlcSkgPT4ge1xuXHRcdFx0XHRjb25zdCB1cmkgPSByZXEucGFyYW1zLnVyaTtcblx0XHRcdFx0Y29uc3QgcmVzb3VyY2UgPSBncm91cC5yZXNvdXJjZXMuZmluZCgocikgPT4gci51cmkgPT09IHVyaSk7XG5cdFx0XHRcdGlmICghcmVzb3VyY2UpIHRocm93IG5ldyBFcnJvcihgVW5rbm93biByZXNvdXJjZTogJHt1cml9YCk7XG5cdFx0XHRcdGNvbnN0IHJlc3VsdCA9IGF3YWl0IHRoaXMuZGlzcGF0Y2hSZXNvdXJjZShyZXNvdXJjZSwgZ2V0VXNlckNvbnRleHQoKSk7XG5cdFx0XHRcdGNvbnN0IHRleHQgPSB0eXBlb2YgcmVzdWx0ID09PSBcInN0cmluZ1wiID8gcmVzdWx0IDogSlNPTi5zdHJpbmdpZnkocmVzdWx0LCBudWxsLCAyKTtcblx0XHRcdFx0cmV0dXJuIHsgY29udGVudHM6IFt7IHVyaTogcmVzb3VyY2UudXJpLCBtaW1lVHlwZTogcmVzb3VyY2UubWltZVR5cGUsIHRleHQgfV0gfTtcblx0XHRcdH0pO1xuXHRcdH1cblxuXHRcdHJldHVybiBzZXJ2ZXI7XG5cdH1cblxuXHQvLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0Ly8gV29ya2Zsb3cgZGlzcGF0Y2ggKHRvb2xzL2NhbGwgKyByZXNvdXJjZXMvcmVhZCBydW4gdGhyb3VnaCB0aGUgcnVubmVyKVxuXHQvLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuXHRwcml2YXRlIGFzeW5jIGRpc3BhdGNoVG9vbChcblx0XHR0b29sOiBUb29sRW50cnksXG5cdFx0YXJnczogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG5cdFx0dXNlckNvbnRleHQ6IE1jcFVzZXJDb250ZXh0IHwgbnVsbCxcblx0KTogUHJvbWlzZTx7IGNvbnRlbnQ6IEFycmF5PHsgdHlwZTogXCJ0ZXh0XCI7IHRleHQ6IHN0cmluZyB9PjsgaXNFcnJvcj86IGJvb2xlYW4gfT4ge1xuXHRcdHRoaXMuY291bnRlclRvb2xDYWxscy5hZGQoMSwgeyB0b29sOiB0b29sLnRvb2xOYW1lIH0pO1xuXHRcdHRyeSB7XG5cdFx0XHRjb25zdCBkYXRhID0gYXdhaXQgdGhpcy5ydW5Xb3JrZmxvdyh0b29sLndvcmtmbG93TmFtZSwgYXJncywgdXNlckNvbnRleHQsIGBtY3AudG9vbDoke3Rvb2wudG9vbE5hbWV9YCk7XG5cdFx0XHRjb25zdCB0ZXh0ID0gdHlwZW9mIGRhdGEgPT09IFwic3RyaW5nXCIgPyBkYXRhIDogSlNPTi5zdHJpbmdpZnkoZGF0YSwgbnVsbCwgMik7XG5cdFx0XHRyZXR1cm4geyBjb250ZW50OiBbeyB0eXBlOiBcInRleHRcIiwgdGV4dCB9XSB9O1xuXHRcdH0gY2F0Y2ggKGVycikge1xuXHRcdFx0Y29uc3QgbXNnID0gZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpO1xuXHRcdFx0dGhpcy5sb2dnZXIuZXJyb3IoYFtibG9rXVttY3BdIHRvb2wgXCIke3Rvb2wudG9vbE5hbWV9XCIgZmFpbGVkOiAke21zZ31gKTtcblx0XHRcdC8vIFRvb2wgZmFpbHVyZSDihpIgTUNQIHRvb2wgZXJyb3IgcmVzdWx0LCBOT1QgYSB0cmFuc3BvcnQgY3Jhc2guXG5cdFx0XHRyZXR1cm4geyBjb250ZW50OiBbeyB0eXBlOiBcInRleHRcIiwgdGV4dDogYEVycm9yOiAke21zZ31gIH1dLCBpc0Vycm9yOiB0cnVlIH07XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBhc3luYyBkaXNwYXRjaFJlc291cmNlKHJlc291cmNlOiBSZXNvdXJjZUVudHJ5LCB1c2VyQ29udGV4dDogTWNwVXNlckNvbnRleHQgfCBudWxsKTogUHJvbWlzZTx1bmtub3duPiB7XG5cdFx0cmV0dXJuIHRoaXMucnVuV29ya2Zsb3cocmVzb3VyY2Uud29ya2Zsb3dOYW1lLCB7fSwgdXNlckNvbnRleHQsIGBtY3AucmVzb3VyY2U6JHtyZXNvdXJjZS51cml9YCk7XG5cdH1cblxuXHQvKipcblx0ICogUnVuIGEgd29ya2Zsb3cgdGhyb3VnaCB0aGUgcnVubmVyIGFuZCByZXR1cm4gYGN0eC5yZXNwb25zZS5kYXRhYC4gTWlycm9yc1xuXHQgKiB0aGUgV2ViaG9va1RyaWdnZXIgcmVxdWVzdOKGknJlc3BvbnNlIGRpc3BhdGNoIChpbml0IOKGkiBjb250ZXh0IOKGkiBtaWRkbGV3YXJlIOKGklxuXHQgKiBydW4pLiBNQ1AgdG9vbCBjYWxscyB3aXRoaW4gYSBzZXNzaW9uIGFyZSBzZXJpYWwsIG1hdGNoaW5nIHRoZSBzaGFyZWRcblx0ICogYHRoaXMuY29uZmlndXJhdGlvbmAgbGlmZWN5Y2xlIHRoZSBvdGhlciB0cmlnZ2VycyB1c2UuXG5cdCAqL1xuXHRwcml2YXRlIGFzeW5jIHJ1bldvcmtmbG93KFxuXHRcdHdvcmtmbG93TmFtZTogc3RyaW5nLFxuXHRcdGFyZ3M6IFJlY29yZDxzdHJpbmcsIHVua25vd24+LFxuXHRcdHVzZXJDb250ZXh0OiBNY3BVc2VyQ29udGV4dCB8IG51bGwsXG5cdFx0c3BhbkxhYmVsOiBzdHJpbmcsXG5cdCk6IFByb21pc2U8dW5rbm93bj4ge1xuXHRcdGNvbnN0IHJlcXVlc3RJZCA9IHV1aWQoKTtcblx0XHRyZXR1cm4gdGhpcy50cmFjZXIuc3RhcnRBY3RpdmVTcGFuKGBtY3A6JHt3b3JrZmxvd05hbWV9YCwgYXN5bmMgKHNwYW46IFNwYW4pID0+IHtcblx0XHRcdHRyeSB7XG5cdFx0XHRcdGNvbnN0IHJlZ2lzdHJ5ID0gV29ya2Zsb3dSZWdpc3RyeS5nZXRJbnN0YW5jZSgpO1xuXHRcdFx0XHRjb25zdCBlbnRyeSA9IHJlZ2lzdHJ5LmdldCh3b3JrZmxvd05hbWUpO1xuXHRcdFx0XHRpZiAoIWVudHJ5KSB0aHJvdyBuZXcgRXJyb3IoYHdvcmtmbG93IFwiJHt3b3JrZmxvd05hbWV9XCIgbm90IGZvdW5kIGluIHJlZ2lzdHJ5YCk7XG5cdFx0XHRcdGF3YWl0IHRoaXMuY29uZmlndXJhdGlvbi5pbml0KHdvcmtmbG93TmFtZSwgdGhpcy5ub2RlTWFwLCBlbnRyeS53b3JrZmxvdyk7XG5cblx0XHRcdFx0Y29uc3QgaGVhZGVyczogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHt9O1xuXHRcdFx0XHRpZiAodXNlckNvbnRleHQpIHtcblx0XHRcdFx0XHQvLyBDYXJyeSBpZGVudGl0eSB0byB0aGUgd29ya2Zsb3cgZXhhY3RseSBhcyBIVFRQIHdvdWxkIChiYXNlNjQgaGVhZGVyKS5cblx0XHRcdFx0XHRoZWFkZXJzW1wieC11c2VyLWNvbnRleHRcIl0gPSBCdWZmZXIuZnJvbShKU09OLnN0cmluZ2lmeSh1c2VyQ29udGV4dCksIFwidXRmLThcIikudG9TdHJpbmcoXCJiYXNlNjRcIik7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRjb25zdCBjdHg6IENvbnRleHQgPSB0aGlzLmNyZWF0ZUNvbnRleHQodW5kZWZpbmVkLCB3b3JrZmxvd05hbWUsIHJlcXVlc3RJZCk7XG5cdFx0XHRcdGN0eC5yZXF1ZXN0ID0ge1xuXHRcdFx0XHRcdGJvZHk6IGFyZ3MsXG5cdFx0XHRcdFx0aGVhZGVycyxcblx0XHRcdFx0XHRwYXJhbXM6IHt9LFxuXHRcdFx0XHRcdHF1ZXJ5OiB7fSxcblx0XHRcdFx0fSBhcyB1bmtub3duIGFzIFJlcXVlc3RDb250ZXh0O1xuXHRcdFx0XHQoY3R4IGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KS5fbWNwID0geyB1c2VyQ29udGV4dCB9O1xuXG5cdFx0XHRcdGF3YWl0IHRoaXMuYXBwbHlNaWRkbGV3YXJlQ2hhaW4oY3R4LCB0aGlzLm5vZGVNYXApO1xuXHRcdFx0XHRhd2FpdCB0aGlzLnJ1bihjdHgpO1xuXG5cdFx0XHRcdHNwYW4uc2V0QXR0cmlidXRlKFwid29ya2Zsb3dfbmFtZVwiLCB3b3JrZmxvd05hbWUpO1xuXHRcdFx0XHRzcGFuLnNldEF0dHJpYnV0ZShcIm1jcF9sYWJlbFwiLCBzcGFuTGFiZWwpO1xuXHRcdFx0XHRzcGFuLnNldFN0YXR1cyh7IGNvZGU6IFNwYW5TdGF0dXNDb2RlLk9LIH0pO1xuXHRcdFx0XHRyZXR1cm4gY3R4LnJlc3BvbnNlPy5kYXRhO1xuXHRcdFx0fSBjYXRjaCAoZXJyKSB7XG5cdFx0XHRcdGNvbnN0IG1zZyA9IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKTtcblx0XHRcdFx0c3Bhbi5yZWNvcmRFeGNlcHRpb24oZXJyIGFzIEVycm9yKTtcblx0XHRcdFx0c3Bhbi5zZXRTdGF0dXMoeyBjb2RlOiBTcGFuU3RhdHVzQ29kZS5FUlJPUiwgbWVzc2FnZTogbXNnIH0pO1xuXHRcdFx0XHR0aHJvdyBlcnI7XG5cdFx0XHR9IGZpbmFsbHkge1xuXHRcdFx0XHRzcGFuLmVuZCgpO1xuXHRcdFx0fVxuXHRcdH0pO1xuXHR9XG5cblx0Ly8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdC8vIFJvdXRlIHJlZ2lzdHJhdGlvbiAoU1NFICsgU3RyZWFtYWJsZS1IVFRQKVxuXHQvLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuXHRwcml2YXRlIHJlZ2lzdGVyR3JvdXBSb3V0ZXMoZ3JvdXA6IFNlcnZlckdyb3VwKTogdm9pZCB7XG5cdFx0dGhpcy5sb2dnZXIubG9nKFxuXHRcdFx0YFtibG9rXVttY3BdIHNlcnZlciBcIiR7Z3JvdXAuc2VydmVyTmFtZX1cIiBhdCAke2dyb3VwLnBhdGh9IOKAlCAke2dyb3VwLnRvb2xzLmxlbmd0aH0gdG9vbChzKSwgJHtncm91cC5yZXNvdXJjZXMubGVuZ3RofSByZXNvdXJjZShzKSwgdHJhbnNwb3J0cz1bJHtncm91cC50cmFuc3BvcnRzLmpvaW4oXCIsXCIpfV1gLFxuXHRcdCk7XG5cblx0XHRpZiAoZ3JvdXAudHJhbnNwb3J0cy5pbmNsdWRlcyhcInNzZVwiKSkge1xuXHRcdFx0dGhpcy5yZWdpc3RlclNzZVJvdXRlcyhncm91cCk7XG5cdFx0fVxuXHRcdGlmIChncm91cC50cmFuc3BvcnRzLmluY2x1ZGVzKFwic3RyZWFtYWJsZS1odHRwXCIpKSB7XG5cdFx0XHR0aGlzLnJlZ2lzdGVyU3RyZWFtYWJsZUh0dHBSb3V0ZShncm91cCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSByZWdpc3RlclNzZVJvdXRlcyhncm91cDogU2VydmVyR3JvdXApOiB2b2lkIHtcblx0XHRjb25zdCBzc2VQYXRoID0gYCR7Z3JvdXAucGF0aH0vc3NlYDtcblx0XHRjb25zdCBtZXNzYWdlc1BhdGggPSBgJHtncm91cC5wYXRofS9tZXNzYWdlc2A7XG5cdFx0dGhpcy5sb2dnZXIubG9nKGBbYmxva11bbWNwXSAgIEdFVCAgJHtzc2VQYXRofSAgIFBPU1QgJHttZXNzYWdlc1BhdGh9ICAoc3NlKWApO1xuXG5cdFx0Ly8gR0VUIDxwYXRoPi9zc2Ug4oCUIG9wZW4gdGhlIFNTRSBzdHJlYW07IFNESyBhbm5vdW5jZXMgdGhlIG1lc3NhZ2VzIGVuZHBvaW50LlxuXHRcdHRoaXMuYXBwLmdldChzc2VQYXRoLCBhc3luYyAoYzogSG9ub0NvbnRleHQpID0+IHtcblx0XHRcdGNvbnN0IGVudiA9IGMuZW52IGFzIHVua25vd24gYXMgeyBpbmNvbWluZzogSW5jb21pbmdNZXNzYWdlOyBvdXRnb2luZzogU2VydmVyUmVzcG9uc2UgfTtcblx0XHRcdGlmICghZW52Py5vdXRnb2luZykge1xuXHRcdFx0XHRyZXR1cm4gYy50ZXh0KFwiTUNQIFNTRSB0cmFuc3BvcnQgcmVxdWlyZXMgdGhlIE5vZGUgc2VydmVyIChAaG9uby9ub2RlLXNlcnZlcikuXCIsIDUwMCk7XG5cdFx0XHR9XG5cdFx0XHRjb25zdCByYXdVc2VyQ3R4ID1cblx0XHRcdFx0Yy5yZXEuaGVhZGVyKFwieC11c2VyLWNvbnRleHRcIikgfHwgKG5ldyBVUkwoYy5yZXEudXJsKS5zZWFyY2hQYXJhbXMuZ2V0KFwidXNlcl9jb250ZXh0XCIpID8/IHVuZGVmaW5lZCk7XG5cdFx0XHRjb25zdCB1c2VyQ29udGV4dCA9IHBhcnNlVXNlckNvbnRleHQocmF3VXNlckN0eCk7XG5cblx0XHRcdGNvbnN0IHRyYW5zcG9ydCA9IG5ldyBTU0VTZXJ2ZXJUcmFuc3BvcnQobWVzc2FnZXNQYXRoLCBlbnYub3V0Z29pbmcpO1xuXHRcdFx0Y29uc3Qgc2Vzc2lvbklkID0gdHJhbnNwb3J0LnNlc3Npb25JZDtcblx0XHRcdGNvbnN0IHNlcnZlciA9IHRoaXMuYnVpbGRTZXJ2ZXIoZ3JvdXAsICgpID0+IHRoaXMuc3NlU2Vzc2lvbnMuZ2V0KHNlc3Npb25JZCk/LnVzZXJDb250ZXh0ID8/IG51bGwpO1xuXHRcdFx0dGhpcy5zc2VTZXNzaW9ucy5zZXQoc2Vzc2lvbklkLCB7IHRyYW5zcG9ydCwgc2VydmVyLCB1c2VyQ29udGV4dCB9KTtcblx0XHRcdHRoaXMuY291bnRlclNlc3Npb25zLmFkZCgxLCB7IHNlcnZlcjogZ3JvdXAuc2VydmVyTmFtZSB9KTtcblxuXHRcdFx0dHJhbnNwb3J0Lm9uY2xvc2UgPSAoKSA9PiB7XG5cdFx0XHRcdHRoaXMuc3NlU2Vzc2lvbnMuZGVsZXRlKHNlc3Npb25JZCk7XG5cdFx0XHR9O1xuXG5cdFx0XHR0cnkge1xuXHRcdFx0XHRhd2FpdCBzZXJ2ZXIuY29ubmVjdCh0cmFuc3BvcnQpO1xuXHRcdFx0fSBjYXRjaCAoZXJyKSB7XG5cdFx0XHRcdHRoaXMuc3NlU2Vzc2lvbnMuZGVsZXRlKHNlc3Npb25JZCk7XG5cdFx0XHRcdGNvbnN0IG1zZyA9IGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKTtcblx0XHRcdFx0dGhpcy5sb2dnZXIuZXJyb3IoYFtibG9rXVttY3BdIHNzZSBjb25uZWN0IGZhaWxlZDogJHttc2d9YCk7XG5cdFx0XHR9XG5cdFx0XHRyZXR1cm4gUkVTUE9OU0VfQUxSRUFEWV9TRU5UO1xuXHRcdH0pO1xuXG5cdFx0Ly8gUE9TVCA8cGF0aD4vbWVzc2FnZXM/c2Vzc2lvbklkPeKApiDigJQgSlNPTi1SUEMgbWVzc2FnZXMgZm9yIGFuIG9wZW4gc3RyZWFtLlxuXHRcdHRoaXMuYXBwLnBvc3QobWVzc2FnZXNQYXRoLCBhc3luYyAoYzogSG9ub0NvbnRleHQpID0+IHtcblx0XHRcdGNvbnN0IGVudiA9IGMuZW52IGFzIHVua25vd24gYXMgeyBpbmNvbWluZzogSW5jb21pbmdNZXNzYWdlOyBvdXRnb2luZzogU2VydmVyUmVzcG9uc2UgfTtcblx0XHRcdGNvbnN0IHNlc3Npb25JZCA9IG5ldyBVUkwoYy5yZXEudXJsKS5zZWFyY2hQYXJhbXMuZ2V0KFwic2Vzc2lvbklkXCIpID8/IHVuZGVmaW5lZDtcblx0XHRcdGlmICghc2Vzc2lvbklkKSByZXR1cm4gYy50ZXh0KFwiTWlzc2luZyBzZXNzaW9uSWQgcXVlcnkgcGFyYW1ldGVyXCIsIDQwMCk7XG5cdFx0XHRjb25zdCBzZXNzaW9uID0gdGhpcy5zc2VTZXNzaW9ucy5nZXQoc2Vzc2lvbklkKTtcblx0XHRcdGlmICghc2Vzc2lvbikgcmV0dXJuIGMudGV4dChcIlNlc3Npb24gbm90IGZvdW5kXCIsIDQwNCk7XG5cdFx0XHRjb25zdCBib2R5ID0gYXdhaXQgYy5yZXEuanNvbigpLmNhdGNoKCgpID0+IHVuZGVmaW5lZCk7XG5cdFx0XHR0cnkge1xuXHRcdFx0XHRhd2FpdCBzZXNzaW9uLnRyYW5zcG9ydC5oYW5kbGVQb3N0TWVzc2FnZShlbnYuaW5jb21pbmcsIGVudi5vdXRnb2luZywgYm9keSk7XG5cdFx0XHR9IGNhdGNoIChlcnIpIHtcblx0XHRcdFx0Y29uc3QgbXNnID0gZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpO1xuXHRcdFx0XHR0aGlzLmxvZ2dlci5lcnJvcihgW2Jsb2tdW21jcF0gaGFuZGxlUG9zdE1lc3NhZ2UgZmFpbGVkOiAke21zZ31gKTtcblx0XHRcdFx0aWYgKCFlbnYub3V0Z29pbmcuaGVhZGVyc1NlbnQpIHJldHVybiBjLnRleHQoXCJFcnJvciBoYW5kbGluZyBNQ1AgbWVzc2FnZVwiLCA1MDApO1xuXHRcdFx0fVxuXHRcdFx0cmV0dXJuIFJFU1BPTlNFX0FMUkVBRFlfU0VOVDtcblx0XHR9KTtcblx0fVxuXG5cdHByaXZhdGUgcmVnaXN0ZXJTdHJlYW1hYmxlSHR0cFJvdXRlKGdyb3VwOiBTZXJ2ZXJHcm91cCk6IHZvaWQge1xuXHRcdHRoaXMubG9nZ2VyLmxvZyhgW2Jsb2tdW21jcF0gICBBTEwgICR7Z3JvdXAucGF0aH0gIChzdHJlYW1hYmxlLWh0dHApYCk7XG5cblx0XHQvLyBTdGF0ZWxlc3MgU3RyZWFtYWJsZS1IVFRQOiBhIGZyZXNoIHNlcnZlciArIHRyYW5zcG9ydCBwZXIgcmVxdWVzdCwgc2VydmVkXG5cdFx0Ly8gZGlyZWN0bHkgb2ZmIHRoZSBGZXRjaCBgUmVxdWVzdGAgKHdvcmtzIG9uIGFueSBIb25vIHJ1bnRpbWUpLlxuXHRcdHRoaXMuYXBwLmFsbChncm91cC5wYXRoLCBhc3luYyAoYzogSG9ub0NvbnRleHQpID0+IHtcblx0XHRcdGNvbnN0IHJhd1VzZXJDdHggPVxuXHRcdFx0XHRjLnJlcS5oZWFkZXIoXCJ4LXVzZXItY29udGV4dFwiKSB8fCAobmV3IFVSTChjLnJlcS51cmwpLnNlYXJjaFBhcmFtcy5nZXQoXCJ1c2VyX2NvbnRleHRcIikgPz8gdW5kZWZpbmVkKTtcblx0XHRcdGNvbnN0IHVzZXJDb250ZXh0ID0gcGFyc2VVc2VyQ29udGV4dChyYXdVc2VyQ3R4KTtcblxuXHRcdFx0Y29uc3Qgc2VydmVyID0gdGhpcy5idWlsZFNlcnZlcihncm91cCwgKCkgPT4gdXNlckNvbnRleHQpO1xuXHRcdFx0Y29uc3QgdHJhbnNwb3J0ID0gbmV3IFdlYlN0YW5kYXJkU3RyZWFtYWJsZUhUVFBTZXJ2ZXJUcmFuc3BvcnQoeyBzZXNzaW9uSWRHZW5lcmF0b3I6IHVuZGVmaW5lZCB9KTtcblx0XHRcdHRyeSB7XG5cdFx0XHRcdGF3YWl0IHNlcnZlci5jb25uZWN0KHRyYW5zcG9ydCk7XG5cdFx0XHRcdGNvbnN0IHJlcyA9IGF3YWl0IHRyYW5zcG9ydC5oYW5kbGVSZXF1ZXN0KGMucmVxLnJhdyk7XG5cdFx0XHRcdHJldHVybiByZXM7XG5cdFx0XHR9IGNhdGNoIChlcnIpIHtcblx0XHRcdFx0Y29uc3QgbXNnID0gZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6IFN0cmluZyhlcnIpO1xuXHRcdFx0XHR0aGlzLmxvZ2dlci5lcnJvcihgW2Jsb2tdW21jcF0gc3RyZWFtYWJsZS1odHRwIHJlcXVlc3QgZmFpbGVkOiAke21zZ31gKTtcblx0XHRcdFx0cmV0dXJuIGMuanNvbih7IGpzb25ycGM6IFwiMi4wXCIsIGVycm9yOiB7IGNvZGU6IC0zMjYwMywgbWVzc2FnZTogbXNnIH0sIGlkOiBudWxsIH0sIDUwMCk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cbn1cblxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbi8vIFNpbmdsZXRvbiBhY2Nlc3NvciAocGFyaXR5IHdpdGggU1NFL1dlYmhvb2sgdHJpZ2dlcnMpXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5sZXQgYWN0aXZlVHJpZ2dlcjogTWNwVHJpZ2dlciB8IG51bGwgPSBudWxsO1xuZXhwb3J0IGZ1bmN0aW9uIF9zZXRBY3RpdmVNY3BUcmlnZ2VyKHRyaWdnZXI6IE1jcFRyaWdnZXIgfCBudWxsKTogdm9pZCB7XG5cdGFjdGl2ZVRyaWdnZXIgPSB0cmlnZ2VyO1xufVxuZXhwb3J0IGZ1bmN0aW9uIF9nZXRBY3RpdmVNY3BUcmlnZ2VyKCk6IE1jcFRyaWdnZXIgfCBudWxsIHtcblx0cmV0dXJuIGFjdGl2ZVRyaWdnZXI7XG59XG5cbmV4cG9ydCB0eXBlIHsgTWNwVHJpZ2dlckNvbmZpZyB9O1xuIl19
|