@nlabs/rip-hunter
Version:
JS utilities for AJAX and GraphQL
550 lines (549 loc) • 72.8 kB
JavaScript
/**
* Copyright (c) 2017-Present, Nitrogen Labs, Inc.
* Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/ import { ApiError } from './errors/ApiError.js';
const isArray = (value)=>Array.isArray(value);
const isString = (value)=>typeof value === 'string';
const isPlainObject = (value)=>value !== null && typeof value === 'object' && !isArray(value);
const isEmpty = (value)=>value === null || value === undefined || value === '';
const isNull = (value)=>value === null;
const isUndefined = (value)=>value === undefined;
// Cache for compiled regex patterns
const JSON_CONTENT_TYPE_REGEX = /application\/json/i;
// Request cache for deduplication
const requestCache = new Map();
export const removeSpaces = (str)=>str.replace(/\s+(?=(?:[^'"]*['"][^'"]*['"])*[^'"]*$)/gm, '');
export const queryString = (json)=>Object.keys(json).map((key)=>`${encodeURIComponent(key)}=${encodeURIComponent(json[key])}`).join('&');
// Create a timeout promise
const createTimeout = (ms)=>new Promise((_, reject)=>setTimeout(()=>reject(new Error('Request timeout')), ms));
// Generate cache key for request deduplication
const generateCacheKey = (url, method, params, options)=>{
const key = JSON.stringify({
method,
options,
params,
url
});
return btoa(key).slice(0, 50); // Truncate to reasonable length
};
// SSE EventSource polyfill for Node.js
const createEventSource = (url, options)=>{
if (typeof EventSource !== 'undefined') {
return new EventSource(url);
}
// For Node.js, we'll throw an error suggesting the user install eventsource
throw new Error('EventSource not available in this environment. For Node.js support, install the "eventsource" package and import it manually.');
};
// WebSocket client for GraphQL subscriptions (graphql-ws protocol)
class GraphQLSubscriptionClient {
ws = null;
url;
options;
reconnectAttempts = 0;
reconnectTimer = null;
messageQueue = [];
isConnecting = false;
isConnected = false;
subscriptions = new Map();
messageHandlers = new Map();
constructor(url, options = {}){
this.url = url;
this.options = options;
}
convertToWebSocketUrl(url) {
return url.replace(/^http/, 'ws').replace(/^https/, 'wss');
}
connect() {
return new Promise((resolve, reject)=>{
if (this.isConnecting) {
return;
}
this.isConnecting = true;
try {
const wsUrl = this.convertToWebSocketUrl(this.url);
if (typeof WebSocket === 'undefined') {
this.isConnecting = false;
reject(new Error('WebSocket not available in this environment. For Node.js support, you may need to install a WebSocket library.'));
return;
}
this.ws = new WebSocket(wsUrl, 'graphql-ws');
this.ws.onopen = ()=>{
this.isConnecting = false;
this.isConnected = true;
this.reconnectAttempts = 0;
const initMessage = {
type: 'connection_init',
payload: this.options.connectionParams || {}
};
this.send(JSON.stringify(initMessage));
while(this.messageQueue.length > 0){
const message = this.messageQueue.shift();
if (message && this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(message);
}
}
resolve();
};
this.ws.onerror = (error)=>{
this.isConnecting = false;
if (!this.isConnected) {
reject(error);
}
};
this.ws.onclose = ()=>{
this.isConnecting = false;
this.isConnected = false;
this.handleReconnect();
};
this.ws.onmessage = (event)=>{
try {
const message = JSON.parse(event.data);
if (message.type === 'connection_ack') {
return;
}
if (message.type === 'connection_error') {
const error = new Error(message.payload?.message || 'Connection error');
this.subscriptions.forEach((callbacks)=>{
if (callbacks.onError) {
callbacks.onError(error);
}
});
return;
}
const subscriptionId = message.id;
const callbacks = this.subscriptions.get(subscriptionId);
if (callbacks) {
switch(message.type){
case 'data':
if (callbacks.onNext) {
callbacks.onNext(message.payload?.data || message.payload);
}
break;
case 'error':
if (callbacks.onError) {
const error = new ApiError(Array.isArray(message.payload) ? message.payload : [
message.payload
], new Error('GraphQL subscription error'));
callbacks.onError(error);
}
break;
case 'complete':
if (callbacks.onComplete) {
callbacks.onComplete();
}
this.subscriptions.delete(subscriptionId);
const handler = this.messageHandlers.get(subscriptionId);
if (handler && this.ws) {
this.ws.removeEventListener('message', handler);
this.messageHandlers.delete(subscriptionId);
}
break;
}
}
} catch (error) {
this.subscriptions.forEach((callbacks)=>{
if (callbacks.onError) {
callbacks.onError(error instanceof Error ? error : new Error('Parse error'));
}
});
}
};
} catch (error) {
this.isConnecting = false;
reject(error);
}
});
}
handleReconnect() {
const { maxReconnectAttempts = 5, reconnectInterval = 1000 } = this.options;
if (this.reconnectAttempts < maxReconnectAttempts && this.subscriptions.size > 0) {
this.reconnectAttempts++;
this.subscriptions.forEach((callbacks)=>{
if (callbacks.onReconnect) {
callbacks.onReconnect(this.reconnectAttempts);
}
});
this.reconnectTimer = setTimeout(()=>{
this.connect().catch(()=>{});
}, reconnectInterval);
} else if (this.subscriptions.size > 0) {
const error = new Error('WebSocket reconnection failed after max attempts');
this.subscriptions.forEach((callbacks)=>{
if (callbacks.onError) {
callbacks.onError(error);
}
});
}
}
send(message) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(message);
} else {
this.messageQueue.push(message);
if (!this.isConnecting && !this.isConnected) {
this.connect().catch(()=>{});
}
}
}
subscribe(query, variables, callbacks) {
const subscriptionId = `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.subscriptions.set(subscriptionId, callbacks);
const startMessage = {
id: subscriptionId,
type: 'start',
payload: {
query,
variables: variables || {}
}
};
const ensureConnection = ()=>{
if (!this.isConnected && !this.isConnecting) {
this.connect().then(()=>{
this.send(JSON.stringify(startMessage));
}).catch((error)=>{
if (callbacks.onError) {
callbacks.onError(error);
}
});
} else if (this.isConnected) {
this.send(JSON.stringify(startMessage));
}
};
ensureConnection();
return ()=>{
const stopMessage = {
id: subscriptionId,
type: 'stop'
};
this.send(JSON.stringify(stopMessage));
this.subscriptions.delete(subscriptionId);
const handler = this.messageHandlers.get(subscriptionId);
if (handler && this.ws) {
this.ws.removeEventListener('message', handler);
this.messageHandlers.delete(subscriptionId);
}
};
}
close() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.messageQueue = [];
this.subscriptions.clear();
this.messageHandlers.clear();
this.isConnected = false;
this.isConnecting = false;
}
}
// Subscription client cache
const subscriptionClients = new Map();
// AJAX
export const ajax = (url, method, params, options = {})=>{
const { headers, token, timeout = 30000, cache = false } = options;
let formatUrl = (url || '').trim();
const formatToken = (token || '').trim();
const formatHeaders = headers || new Headers();
// Method
const formatMethod = (method || 'GET').toUpperCase();
let formatParams;
// Parameters
if (params && formatMethod === 'GET') {
formatUrl = `${formatUrl}?${queryString(params)}`;
formatParams = null;
} else if (params) {
formatHeaders.set('Accept', 'application/json');
formatHeaders.set('Content-Type', 'application/json');
formatParams = JSON.stringify(params);
} else {
formatParams = params;
}
// Authentication token
if (!isEmpty(formatToken)) {
formatHeaders.set('Authorization', `Bearer ${formatToken}`);
}
// Check cache for GET requests
if (cache && formatMethod === 'GET') {
const cacheKey = generateCacheKey(formatUrl, formatMethod, params, options);
const cached = requestCache.get(cacheKey);
if (cached) {
return cached;
}
}
// Create the request promise
const requestPromise = fetch(formatUrl, {
body: formatParams,
headers: formatHeaders,
method: formatMethod
}).then((response)=>{
// Check if response is json using cached regex
const isResponseJson = JSON_CONTENT_TYPE_REGEX.test(response.headers.get('Content-Type') || '');
if (isResponseJson) {
return response.json();
}
return response.text();
}).then((results)=>results) // Simplified - no need for redundant check
.catch((error)=>{
if ((error || {}).message === 'only absolute urls are supported') {
return Promise.reject(new ApiError([
{
message: 'invalid_url'
}
], error));
}
return Promise.reject(new ApiError([
{
message: 'network_error'
}
], error));
});
// Add timeout if specified
const finalPromise = timeout > 0 ? Promise.race([
requestPromise,
createTimeout(timeout)
]) : requestPromise;
// Cache the promise for GET requests
if (cache && formatMethod === 'GET') {
const cacheKey = generateCacheKey(formatUrl, formatMethod, params, options);
requestCache.set(cacheKey, finalPromise);
// Clean up cache after 5 minutes
setTimeout(()=>requestCache.delete(cacheKey), 300000);
}
return finalPromise;
};
export const get = (url, params, options)=>ajax(url, 'GET', params, options);
export const post = (url, params, options)=>ajax(url, 'POST', params, options);
export const put = (url, params, options)=>ajax(url, 'PUT', params, options);
export const del = (url, params, options)=>ajax(url, 'DELETE', params, options);
// Optimized toGql function
export const toGql = (obj)=>{
if (isString(obj)) {
return JSON.stringify(obj);
} else if (isPlainObject(obj)) {
// Filter out undefined and null values in one pass
const gqlProps = [];
for(const key in obj){
if (obj.hasOwnProperty(key)) {
const item = obj[key];
// Skip undefined and null values
if (isUndefined(item) || isNull(item)) {
continue;
}
if (isPlainObject(item)) {
gqlProps.push(`${key}: ${toGql(item)}`);
} else if (isArray(item)) {
const list = item.map((listItem)=>toGql(listItem));
gqlProps.push(`${key}: [${list.join(', ')}]`);
} else {
const val = JSON.stringify(item);
if (val) {
gqlProps.push(`${key}: ${val}`);
}
}
}
}
const values = gqlProps.join(', ');
return values === '' ? '""' : `{${gqlProps.join(', ')}}`;
} else if (isArray(obj)) {
return `[${obj.map((objItem)=>toGql(objItem)).join(', ')}]`;
}
return String(obj);
};
export const graphqlQuery = (url, query, options = {})=>{
const { headers, token, timeout = 30000 } = options;
const formatUrl = url ? url.trim() : '';
const formatToken = (token || '').trim();
const formatHeaders = headers || new Headers({
'Content-Type': 'application/json'
});
if (!isEmpty(formatToken)) {
formatHeaders.set('Authorization', `Bearer ${formatToken}`);
}
const requestPromise = fetch(formatUrl, {
body: JSON.stringify(query),
headers: formatHeaders,
method: 'post'
}).then((response)=>{
const isJson = JSON_CONTENT_TYPE_REGEX.test(response.headers.get('Content-Type') || '');
if (isJson && response.body) {
return response.json();
}
return null;
}).catch((error)=>{
if ((error || {}).message === 'only absolute urls are supported') {
return Promise.reject(new ApiError([
{
message: 'invalid_url'
}
], error));
}
return Promise.reject(new ApiError([
{
message: 'network_error'
}
], error));
}).then((json)=>{
if (!json || json.errors) {
if (!json) {
return Promise.reject(new ApiError([
{
message: 'api_error'
}
], new Error()));
} else if ((json.errors || []).some((error)=>error.message === 'Must provide query string.')) {
return Promise.reject(new ApiError([
{
message: 'required_query'
}
], new Error()));
}
return json.errors ? Promise.reject(new ApiError(json.errors, new Error())) : {};
}
return json.data || {};
}).catch((error)=>// Simplified error handling - no need to create new error if source exists
Promise.reject(error.source ? error : new ApiError([
{
message: 'network_error'
}
], error)));
// Add timeout if specified
return timeout > 0 ? Promise.race([
requestPromise,
createTimeout(timeout)
]) : requestPromise;
};
// Add query and mutation functions for better API consistency
export const query = (url, body, options = {})=>graphqlQuery(url, {
query: body
}, options);
export const mutation = (url, body, options = {})=>graphqlQuery(url, {
query: body
}, options);
// GraphQL Subscriptions
export const subscribe = (url, query, callbacks, options = {})=>{
const { token, variables, connectionParams, headers } = options;
const formatUrl = (url || '').trim();
const formatToken = (token || '').trim();
if (!formatUrl) {
if (callbacks.onError) {
callbacks.onError(new Error('Subscription URL is required'));
}
return ()=>{};
}
let client = subscriptionClients.get(formatUrl);
if (!client) {
const clientOptions = {
...options,
connectionParams: {
...connectionParams,
...formatToken ? {
authorization: `Bearer ${formatToken}`
} : {}
}
};
client = new GraphQLSubscriptionClient(formatUrl, clientOptions);
subscriptionClients.set(formatUrl, client);
}
return client.subscribe(query, variables, callbacks);
};
// SSE Support
export const subscribeSSE = (url, callbacks, options = {})=>{
const { headers, token, timeout = 30000, retryInterval = 1000, maxRetries = 5 } = options;
const formatUrl = (url || '').trim();
const formatToken = (token || '').trim();
const formatHeaders = headers || new Headers();
// Add authorization header if token provided
if (!isEmpty(formatToken)) {
formatHeaders.set('Authorization', `Bearer ${formatToken}`);
}
let retryCount = 0;
let eventSource = null;
let timeoutId = null;
const connect = ()=>{
try {
eventSource = createEventSource(formatUrl, {
headers: formatHeaders
});
// Set up timeout
if (timeout > 0) {
timeoutId = setTimeout(()=>{
if (eventSource) {
eventSource.close();
if (callbacks.onError) {
callbacks.onError(new Error('SSE connection timeout'));
}
}
}, timeout);
}
// Event handlers
eventSource.onopen = (event)=>{
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
retryCount = 0; // Reset retry count on successful connection
if (callbacks.onOpen) {
callbacks.onOpen(event);
}
};
eventSource.onmessage = (event)=>{
if (callbacks.onMessage) {
const sseEvent = {
data: event.data,
id: event.lastEventId,
retry: event.data.includes('retry:') ? parseInt(event.data.split('retry:')[1]) : undefined,
type: event.type
};
callbacks.onMessage(sseEvent);
}
};
eventSource.onerror = (event)=>{
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
if (eventSource && eventSource.readyState === EventSource.CLOSED) {
// Connection closed, attempt retry if within limits
if (retryCount < maxRetries) {
retryCount++;
if (callbacks.onRetry) {
callbacks.onRetry(retryCount, retryInterval);
}
setTimeout(connect, retryInterval);
} else {
if (callbacks.onError) {
callbacks.onError(new Error('SSE connection failed after max retries'));
}
}
} else {
if (callbacks.onError) {
callbacks.onError(event);
}
}
};
} catch (error) {
if (callbacks.onError) {
callbacks.onError(error instanceof Error ? error : new Error('Failed to create SSE connection'));
}
}
};
// Start the connection
connect();
// Return cleanup function
return ()=>{
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
if (eventSource) {
eventSource.close();
eventSource = null;
}
};
};
export { ApiError } from './errors/ApiError.js';
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9Vc2Vycy9uaXRyb2c3L0RldmVsb3BtZW50L3JpcC1odW50ZXIvc3JjL2luZGV4LnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQ29weXJpZ2h0IChjKSAyMDE3LVByZXNlbnQsIE5pdHJvZ2VuIExhYnMsIEluYy5cbiAqIENvcHlyaWdodHMgbGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgdGhlIGFjY29tcGFueWluZyBMSUNFTlNFIGZpbGUgZm9yIHRlcm1zLlxuICovXG5cbmltcG9ydCB7QXBpRXJyb3J9IGZyb20gJy4vZXJyb3JzL0FwaUVycm9yLmpzJztcblxuY29uc3QgaXNBcnJheSA9ICh2YWx1ZTogYW55KTogdmFsdWUgaXMgYW55W10gPT4gQXJyYXkuaXNBcnJheSh2YWx1ZSk7XG5jb25zdCBpc1N0cmluZyA9ICh2YWx1ZTogYW55KTogdmFsdWUgaXMgc3RyaW5nID0+IHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZyc7XG5jb25zdCBpc1BsYWluT2JqZWN0ID0gKHZhbHVlOiBhbnkpOiB2YWx1ZSBpcyBSZWNvcmQ8c3RyaW5nLCBhbnk+ID0+XG4gIHZhbHVlICE9PSBudWxsICYmIHR5cGVvZiB2YWx1ZSA9PT0gJ29iamVjdCcgJiYgIWlzQXJyYXkodmFsdWUpO1xuY29uc3QgaXNFbXB0eSA9ICh2YWx1ZTogYW55KTogYm9vbGVhbiA9PlxuICB2YWx1ZSA9PT0gbnVsbCB8fCB2YWx1ZSA9PT0gdW5kZWZpbmVkIHx8IHZhbHVlID09PSAnJztcbmNvbnN0IGlzTnVsbCA9ICh2YWx1ZTogYW55KTogYm9vbGVhbiA9PiB2YWx1ZSA9PT0gbnVsbDtcbmNvbnN0IGlzVW5kZWZpbmVkID0gKHZhbHVlOiBhbnkpOiBib29sZWFuID0+IHZhbHVlID09PSB1bmRlZmluZWQ7XG5cbi8vIENhY2hlIGZvciBjb21waWxlZCByZWdleCBwYXR0ZXJuc1xuY29uc3QgSlNPTl9DT05URU5UX1RZUEVfUkVHRVggPSAvYXBwbGljYXRpb25cXC9qc29uL2k7XG5cbi8vIFJlcXVlc3QgY2FjaGUgZm9yIGRlZHVwbGljYXRpb25cbmNvbnN0IHJlcXVlc3RDYWNoZSA9IG5ldyBNYXA8c3RyaW5nLCBQcm9taXNlPGFueT4+KCk7XG5cbmV4cG9ydCBpbnRlcmZhY2UgSHVudGVyT3B0aW9uc1R5cGUge1xuICByZWFkb25seSBoZWFkZXJzPzogSGVhZGVycztcbiAgcmVhZG9ubHkgdG9rZW4/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHZhcmlhYmxlcz86IGFueTtcbiAgcmVhZG9ubHkgdGltZW91dD86IG51bWJlcjtcbiAgcmVhZG9ubHkgY2FjaGU/OiBib29sZWFuO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEh1bnRlclF1ZXJ5VHlwZSB7XG4gIHJlYWRvbmx5IHF1ZXJ5OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHZhcmlhYmxlcz86IGFueTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBIdW50ZXJTU0VPcHRpb25zVHlwZSB7XG4gIHJlYWRvbmx5IGhlYWRlcnM/OiBIZWFkZXJzO1xuICByZWFkb25seSB0b2tlbj86IHN0cmluZztcbiAgcmVhZG9ubHkgdGltZW91dD86IG51bWJlcjtcbiAgcmVhZG9ubHkgcmV0cnlJbnRlcnZhbD86IG51bWJlcjtcbiAgcmVhZG9ubHkgbWF4UmV0cmllcz86IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBIdW50ZXJTU0VFdmVudFR5cGUge1xuICByZWFkb25seSBkYXRhOiBzdHJpbmc7XG4gIHJlYWRvbmx5IHR5cGU6IHN0cmluZztcbiAgcmVhZG9ubHkgaWQ/OiBzdHJpbmc7XG4gIHJlYWRvbmx5IHJldHJ5PzogbnVtYmVyIHwgdW5kZWZpbmVkO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEh1bnRlclNTRUNhbGxiYWNrVHlwZSB7XG4gIG9uTWVzc2FnZT86IChldmVudDogSHVudGVyU1NFRXZlbnRUeXBlKSA9PiB2b2lkO1xuICBvbk9wZW4/OiAoZXZlbnQ6IEV2ZW50KSA9PiB2b2lkO1xuICBvbkVycm9yPzogKGVycm9yOiBFcnJvciB8IEV2ZW50KSA9PiB2b2lkO1xuICBvblJldHJ5PzogKGF0dGVtcHQ6IG51bWJlciwgZGVsYXk6IG51bWJlcikgPT4gdm9pZDtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBIdW50ZXJTdWJzY3JpcHRpb25PcHRpb25zVHlwZSB7XG4gIHJlYWRvbmx5IGhlYWRlcnM/OiBIZWFkZXJzO1xuICByZWFkb25seSB0b2tlbj86IHN0cmluZztcbiAgcmVhZG9ubHkgdmFyaWFibGVzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIHJlYWRvbmx5IGNvbm5lY3Rpb25QYXJhbXM/OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgcmVhZG9ubHkgcmVjb25uZWN0SW50ZXJ2YWw/OiBudW1iZXI7XG4gIHJlYWRvbmx5IG1heFJlY29ubmVjdEF0dGVtcHRzPzogbnVtYmVyO1xuICByZWFkb25seSB0aW1lb3V0PzogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIEh1bnRlclN1YnNjcmlwdGlvbkNhbGxiYWNrVHlwZSB7XG4gIG9uTmV4dD86IChkYXRhOiBhbnkpID0+IHZvaWQ7XG4gIG9uRXJyb3I/OiAoZXJyb3I6IEVycm9yIHwgRXZlbnQpID0+IHZvaWQ7XG4gIG9uQ29tcGxldGU/OiAoKSA9PiB2b2lkO1xuICBvblJlY29ubmVjdD86IChhdHRlbXB0OiBudW1iZXIpID0+IHZvaWQ7XG59XG5cbmV4cG9ydCBjb25zdCByZW1vdmVTcGFjZXMgPSAoc3RyOiBzdHJpbmcpOiBzdHJpbmcgPT5cbiAgc3RyLnJlcGxhY2UoL1xccysoPz0oPzpbXidcIl0qWydcIl1bXidcIl0qWydcIl0pKlteJ1wiXSokKS9nbSwgJycpO1xuXG5leHBvcnQgY29uc3QgcXVlcnlTdHJpbmcgPSAoanNvbjogYW55KTogc3RyaW5nID0+XG4gIE9iamVjdC5rZXlzKGpzb24pXG4gICAgLm1hcChcbiAgICAgIChrZXk6IHN0cmluZykgPT5cbiAgICAgICAgYCR7ZW5jb2RlVVJJQ29tcG9uZW50KGtleSl9PSR7ZW5jb2RlVVJJQ29tcG9uZW50KGpzb25ba2V5XSl9YFxuICAgIClcbiAgICAuam9pbignJicpO1xuXG4vLyBDcmVhdGUgYSB0aW1lb3V0IHByb21pc2VcbmNvbnN0IGNyZWF0ZVRpbWVvdXQgPSAobXM6IG51bWJlcik6IFByb21pc2U8bmV2ZXI+ID0+XG4gIG5ldyBQcm9taXNlKChfLCByZWplY3QpID0+XG4gICAgc2V0VGltZW91dCgoKSA9PiByZWplY3QobmV3IEVycm9yKCdSZXF1ZXN0IHRpbWVvdXQnKSksIG1zKVxuICApO1xuXG4vLyBHZW5lcmF0ZSBjYWNoZSBrZXkgZm9yIHJlcXVlc3QgZGVkdXBsaWNhdGlvblxuY29uc3QgZ2VuZXJhdGVDYWNoZUtleSA9ICh1cmw6IHN0cmluZywgbWV0aG9kOiBzdHJpbmcsIHBhcmFtcz86IGFueSwgb3B0aW9ucz86IEh1bnRlck9wdGlvbnNUeXBlKTogc3RyaW5nID0+IHtcbiAgY29uc3Qga2V5ID0gSlNPTi5zdHJpbmdpZnkoe21ldGhvZCwgb3B0aW9ucywgcGFyYW1zLCB1cmx9KTtcbiAgcmV0dXJuIGJ0b2Eoa2V5KS5zbGljZSgwLCA1MCk7IC8vIFRydW5jYXRlIHRvIHJlYXNvbmFibGUgbGVuZ3RoXG59O1xuXG4vLyBTU0UgRXZlbnRTb3VyY2UgcG9seWZpbGwgZm9yIE5vZGUuanNcbmNvbnN0IGNyZWF0ZUV2ZW50U291cmNlID0gKHVybDogc3RyaW5nLCBvcHRpb25zPzogSHVudGVyU1NFT3B0aW9uc1R5cGUpOiBFdmVudFNvdXJjZSA9PiB7XG4gIGlmKHR5cGVvZiBFdmVudFNvdXJjZSAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICByZXR1cm4gbmV3IEV2ZW50U291cmNlKHVybCk7XG4gIH1cblxuICAvLyBGb3IgTm9kZS5qcywgd2UnbGwgdGhyb3cgYW4gZXJyb3Igc3VnZ2VzdGluZyB0aGUgdXNlciBpbnN0YWxsIGV2ZW50c291cmNlXG4gIHRocm93IG5ldyBFcnJvcignRXZlbnRTb3VyY2Ugbm90IGF2YWlsYWJsZSBpbiB0aGlzIGVudmlyb25tZW50LiBGb3IgTm9kZS5qcyBzdXBwb3J0LCBpbnN0YWxsIHRoZSBcImV2ZW50c291cmNlXCIgcGFja2FnZSBhbmQgaW1wb3J0IGl0IG1hbnVhbGx5LicpO1xufTtcblxuLy8gV2ViU29ja2V0IGNsaWVudCBmb3IgR3JhcGhRTCBzdWJzY3JpcHRpb25zIChncmFwaHFsLXdzIHByb3RvY29sKVxuY2xhc3MgR3JhcGhRTFN1YnNjcmlwdGlvbkNsaWVudCB7XG4gIHByaXZhdGUgd3M6IFdlYlNvY2tldCB8IG51bGwgPSBudWxsO1xuICBwcml2YXRlIHVybDogc3RyaW5nO1xuICBwcml2YXRlIG9wdGlvbnM6IEh1bnRlclN1YnNjcmlwdGlvbk9wdGlvbnNUeXBlO1xuICBwcml2YXRlIHJlY29ubmVjdEF0dGVtcHRzID0gMDtcbiAgcHJpdmF0ZSByZWNvbm5lY3RUaW1lcjogTm9kZUpTLlRpbWVvdXQgfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSBtZXNzYWdlUXVldWU6IHN0cmluZ1tdID0gW107XG4gIHByaXZhdGUgaXNDb25uZWN0aW5nID0gZmFsc2U7XG4gIHByaXZhdGUgaXNDb25uZWN0ZWQgPSBmYWxzZTtcbiAgcHJpdmF0ZSBzdWJzY3JpcHRpb25zID0gbmV3IE1hcDxzdHJpbmcsIEh1bnRlclN1YnNjcmlwdGlvbkNhbGxiYWNrVHlwZT4oKTtcbiAgcHJpdmF0ZSBtZXNzYWdlSGFuZGxlcnMgPSBuZXcgTWFwPHN0cmluZywgKGV2ZW50OiBNZXNzYWdlRXZlbnQpID0+IHZvaWQ+KCk7XG5cbiAgY29uc3RydWN0b3IodXJsOiBzdHJpbmcsIG9wdGlvbnM6IEh1bnRlclN1YnNjcmlwdGlvbk9wdGlvbnNUeXBlID0ge30pIHtcbiAgICB0aGlzLnVybCA9IHVybDtcbiAgICB0aGlzLm9wdGlvbnMgPSBvcHRpb25zO1xuICB9XG5cbiAgcHJpdmF0ZSBjb252ZXJ0VG9XZWJTb2NrZXRVcmwodXJsOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiB1cmwucmVwbGFjZSgvXmh0dHAvLCAnd3MnKS5yZXBsYWNlKC9eaHR0cHMvLCAnd3NzJyk7XG4gIH1cblxuICBwcml2YXRlIGNvbm5lY3QoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIGlmKHRoaXMuaXNDb25uZWN0aW5nKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIHRoaXMuaXNDb25uZWN0aW5nID0gdHJ1ZTtcblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3Qgd3NVcmwgPSB0aGlzLmNvbnZlcnRUb1dlYlNvY2tldFVybCh0aGlzLnVybCk7XG5cbiAgICAgICAgaWYodHlwZW9mIFdlYlNvY2tldCA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgICB0aGlzLmlzQ29ubmVjdGluZyA9IGZhbHNlO1xuICAgICAgICAgIHJlamVjdChuZXcgRXJyb3IoJ1dlYlNvY2tldCBub3QgYXZhaWxhYmxlIGluIHRoaXMgZW52aXJvbm1lbnQuIEZvciBOb2RlLmpzIHN1cHBvcnQsIHlvdSBtYXkgbmVlZCB0byBpbnN0YWxsIGEgV2ViU29ja2V0IGxpYnJhcnkuJykpO1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMud3MgPSBuZXcgV2ViU29ja2V0KHdzVXJsLCAnZ3JhcGhxbC13cycpO1xuXG4gICAgICAgIHRoaXMud3Mub25vcGVuID0gKCkgPT4ge1xuICAgICAgICAgIHRoaXMuaXNDb25uZWN0aW5nID0gZmFsc2U7XG4gICAgICAgICAgdGhpcy5pc0Nvbm5lY3RlZCA9IHRydWU7XG4gICAgICAgICAgdGhpcy5yZWNvbm5lY3RBdHRlbXB0cyA9IDA7XG5cbiAgICAgICAgICBjb25zdCBpbml0TWVzc2FnZSA9IHtcbiAgICAgICAgICAgIHR5cGU6ICdjb25uZWN0aW9uX2luaXQnLFxuICAgICAgICAgICAgcGF5bG9hZDogdGhpcy5vcHRpb25zLmNvbm5lY3Rpb25QYXJhbXMgfHwge31cbiAgICAgICAgICB9O1xuICAgICAgICAgIHRoaXMuc2VuZChKU09OLnN0cmluZ2lmeShpbml0TWVzc2FnZSkpO1xuXG4gICAgICAgICAgd2hpbGUodGhpcy5tZXNzYWdlUXVldWUubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgY29uc3QgbWVzc2FnZSA9IHRoaXMubWVzc2FnZVF1ZXVlLnNoaWZ0KCk7XG4gICAgICAgICAgICBpZihtZXNzYWdlICYmIHRoaXMud3M/LnJlYWR5U3RhdGUgPT09IFdlYlNvY2tldC5PUEVOKSB7XG4gICAgICAgICAgICAgIHRoaXMud3Muc2VuZChtZXNzYWdlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG5cbiAgICAgICAgICByZXNvbHZlKCk7XG4gICAgICAgIH07XG5cbiAgICAgICAgdGhpcy53cy5vbmVycm9yID0gKGVycm9yKSA9PiB7XG4gICAgICAgICAgdGhpcy5pc0Nvbm5lY3RpbmcgPSBmYWxzZTtcbiAgICAgICAgICBpZighdGhpcy5pc0Nvbm5lY3RlZCkge1xuICAgICAgICAgICAgcmVqZWN0KGVycm9yKTtcbiAgICAgICAgICB9XG4gICAgICAgIH07XG5cbiAgICAgICAgdGhpcy53cy5vbmNsb3NlID0gKCkgPT4ge1xuICAgICAgICAgIHRoaXMuaXNDb25uZWN0aW5nID0gZmFsc2U7XG4gICAgICAgICAgdGhpcy5pc0Nvbm5lY3RlZCA9IGZhbHNlO1xuICAgICAgICAgIHRoaXMuaGFuZGxlUmVjb25uZWN0KCk7XG4gICAgICAgIH07XG5cbiAgICAgICAgdGhpcy53cy5vbm1lc3NhZ2UgPSAoZXZlbnQ6IE1lc3NhZ2VFdmVudCkgPT4ge1xuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjb25zdCBtZXNzYWdlID0gSlNPTi5wYXJzZShldmVudC5kYXRhKTtcblxuICAgICAgICAgICAgaWYobWVzc2FnZS50eXBlID09PSAnY29ubmVjdGlvbl9hY2snKSB7XG4gICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYobWVzc2FnZS50eXBlID09PSAnY29ubmVjdGlvbl9lcnJvcicpIHtcbiAgICAgICAgICAgICAgY29uc3QgZXJyb3IgPSBuZXcgRXJyb3IobWVzc2FnZS5wYXlsb2FkPy5tZXNzYWdlIHx8ICdDb25uZWN0aW9uIGVycm9yJyk7XG4gICAgICAgICAgICAgIHRoaXMuc3Vic2NyaXB0aW9ucy5mb3JFYWNoKChjYWxsYmFja3MpID0+IHtcbiAgICAgICAgICAgICAgICBpZihjYWxsYmFja3Mub25FcnJvcikge1xuICAgICAgICAgICAgICAgICAgY2FsbGJhY2tzLm9uRXJyb3IoZXJyb3IpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY29uc3Qgc3Vic2NyaXB0aW9uSWQgPSBtZXNzYWdlLmlkO1xuICAgICAgICAgICAgY29uc3QgY2FsbGJhY2tzID0gdGhpcy5zdWJzY3JpcHRpb25zLmdldChzdWJzY3JpcHRpb25JZCk7XG5cbiAgICAgICAgICAgIGlmKGNhbGxiYWNrcykge1xuICAgICAgICAgICAgICBzd2l0Y2gobWVzc2FnZS50eXBlKSB7XG4gICAgICAgICAgICAgICAgY2FzZSAnZGF0YSc6XG4gICAgICAgICAgICAgICAgICBpZihjYWxsYmFja3Mub25OZXh0KSB7XG4gICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcy5vbk5leHQobWVzc2FnZS5wYXlsb2FkPy5kYXRhIHx8IG1lc3NhZ2UucGF5bG9hZCk7XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICBjYXNlICdlcnJvcic6XG4gICAgICAgICAgICAgICAgICBpZihjYWxsYmFja3Mub25FcnJvcikge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBlcnJvciA9IG5ldyBBcGlFcnJvcihcbiAgICAgICAgICAgICAgICAgICAgICBBcnJheS5pc0FycmF5KG1lc3NhZ2UucGF5bG9hZCkgPyBtZXNzYWdlLnBheWxvYWQgOiBbbWVzc2FnZS5wYXlsb2FkXSxcbiAgICAgICAgICAgICAgICAgICAgICBuZXcgRXJyb3IoJ0dyYXBoUUwgc3Vic2NyaXB0aW9uIGVycm9yJylcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2tzLm9uRXJyb3IoZXJyb3IpO1xuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgY2FzZSAnY29tcGxldGUnOlxuICAgICAgICAgICAgICAgICAgaWYoY2FsbGJhY2tzLm9uQ29tcGxldGUpIHtcbiAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2tzLm9uQ29tcGxldGUoKTtcbiAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgIHRoaXMuc3Vic2NyaXB0aW9ucy5kZWxldGUoc3Vic2NyaXB0aW9uSWQpO1xuICAgICAgICAgICAgICAgICAgY29uc3QgaGFuZGxlciA9IHRoaXMubWVzc2FnZUhhbmRsZXJzLmdldChzdWJzY3JpcHRpb25JZCk7XG4gICAgICAgICAgICAgICAgICBpZihoYW5kbGVyICYmIHRoaXMud3MpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy53cy5yZW1vdmVFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgaGFuZGxlcik7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMubWVzc2FnZUhhbmRsZXJzLmRlbGV0ZShzdWJzY3JpcHRpb25JZCk7XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gY2F0Y2goZXJyb3IpIHtcbiAgICAgICAgICAgIHRoaXMuc3Vic2NyaXB0aW9ucy5mb3JFYWNoKChjYWxsYmFja3MpID0+IHtcbiAgICAgICAgICAgICAgaWYoY2FsbGJhY2tzLm9uRXJyb3IpIHtcbiAgICAgICAgICAgICAgICBjYWxsYmFja3Mub25FcnJvcihlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IgOiBuZXcgRXJyb3IoJ1BhcnNlIGVycm9yJykpO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICB9IGNhdGNoKGVycm9yKSB7XG4gICAgICAgIHRoaXMuaXNDb25uZWN0aW5nID0gZmFsc2U7XG4gICAgICAgIHJlamVjdChlcnJvcik7XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBwcml2YXRlIGhhbmRsZVJlY29ubmVjdCgpOiB2b2lkIHtcbiAgICBjb25zdCB7bWF4UmVjb25uZWN0QXR0ZW1wdHMgPSA1LCByZWNvbm5lY3RJbnRlcnZhbCA9IDEwMDB9ID0gdGhpcy5vcHRpb25zO1xuXG4gICAgaWYodGhpcy5yZWNvbm5lY3RBdHRlbXB0cyA8IG1heFJlY29ubmVjdEF0dGVtcHRzICYmIHRoaXMuc3Vic2NyaXB0aW9ucy5zaXplID4gMCkge1xuICAgICAgdGhpcy5yZWNvbm5lY3RBdHRlbXB0cysrO1xuXG4gICAgICB0aGlzLnN1YnNjcmlwdGlvbnMuZm9yRWFjaCgoY2FsbGJhY2tzKSA9PiB7XG4gICAgICAgIGlmKGNhbGxiYWNrcy5vblJlY29ubmVjdCkge1xuICAgICAgICAgIGNhbGxiYWNrcy5vblJlY29ubmVjdCh0aGlzLnJlY29ubmVjdEF0dGVtcHRzKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG5cbiAgICAgIHRoaXMucmVjb25uZWN0VGltZXIgPSBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgdGhpcy5jb25uZWN0KCkuY2F0Y2goKCkgPT4ge1xuICAgICAgICB9KTtcbiAgICAgIH0sIHJlY29ubmVjdEludGVydmFsKTtcbiAgICB9IGVsc2UgaWYodGhpcy5zdWJzY3JpcHRpb25zLnNpemUgPiAwKSB7XG4gICAgICBjb25zdCBlcnJvciA9IG5ldyBFcnJvcignV2ViU29ja2V0IHJlY29ubmVjdGlvbiBmYWlsZWQgYWZ0ZXIgbWF4IGF0dGVtcHRzJyk7XG4gICAgICB0aGlzLnN1YnNjcmlwdGlvbnMuZm9yRWFjaCgoY2FsbGJhY2tzKSA9PiB7XG4gICAgICAgIGlmKGNhbGxiYWNrcy5vbkVycm9yKSB7XG4gICAgICAgICAgY2FsbGJhY2tzLm9uRXJyb3IoZXJyb3IpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIHNlbmQobWVzc2FnZTogc3RyaW5nKTogdm9pZCB7XG4gICAgaWYodGhpcy53cz8ucmVhZHlTdGF0ZSA9PT0gV2ViU29ja2V0Lk9QRU4pIHtcbiAgICAgIHRoaXMud3Muc2VuZChtZXNzYWdlKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5tZXNzYWdlUXVldWUucHVzaChtZXNzYWdlKTtcbiAgICAgIGlmKCF0aGlzLmlzQ29ubmVjdGluZyAmJiAhdGhpcy5pc0Nvbm5lY3RlZCkge1xuICAgICAgICB0aGlzLmNvbm5lY3QoKS5jYXRjaCgoKSA9PiB7XG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHN1YnNjcmliZShcbiAgICBxdWVyeTogc3RyaW5nLFxuICAgIHZhcmlhYmxlczogUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfCB1bmRlZmluZWQsXG4gICAgY2FsbGJhY2tzOiBIdW50ZXJTdWJzY3JpcHRpb25DYWxsYmFja1R5cGVcbiAgKTogKCkgPT4gdm9pZCB7XG4gICAgY29uc3Qgc3Vic2NyaXB0aW9uSWQgPSBgc3ViXyR7RGF0ZS5ub3coKX1fJHtNYXRoLnJhbmRvbSgpLnRvU3RyaW5nKDM2KS5zdWJzdHIoMiwgOSl9YDtcblxuICAgIHRoaXMuc3Vic2NyaXB0aW9ucy5zZXQoc3Vic2NyaXB0aW9uSWQsIGNhbGxiYWNrcyk7XG5cbiAgICBjb25zdCBzdGFydE1lc3NhZ2UgPSB7XG4gICAgICBpZDogc3Vic2NyaXB0aW9uSWQsXG4gICAgICB0eXBlOiAnc3RhcnQnLFxuICAgICAgcGF5bG9hZDoge1xuICAgICAgICBxdWVyeSxcbiAgICAgICAgdmFyaWFibGVzOiB2YXJpYWJsZXMgfHwge31cbiAgICAgIH1cbiAgICB9O1xuXG4gICAgY29uc3QgZW5zdXJlQ29ubmVjdGlvbiA9ICgpOiB2b2lkID0+IHtcbiAgICAgIGlmKCF0aGlzLmlzQ29ubmVjdGVkICYmICF0aGlzLmlzQ29ubmVjdGluZykge1xuICAgICAgICB0aGlzLmNvbm5lY3QoKS50aGVuKCgpID0+IHtcbiAgICAgICAgICB0aGlzLnNlbmQoSlNPTi5zdHJpbmdpZnkoc3RhcnRNZXNzYWdlKSk7XG4gICAgICAgIH0pLmNhdGNoKChlcnJvcikgPT4ge1xuICAgICAgICAgIGlmKGNhbGxiYWNrcy5vbkVycm9yKSB7XG4gICAgICAgICAgICBjYWxsYmFja3Mub25FcnJvcihlcnJvcik7XG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgIH0gZWxzZSBpZih0aGlzLmlzQ29ubmVjdGVkKSB7XG4gICAgICAgIHRoaXMuc2VuZChKU09OLnN0cmluZ2lmeShzdGFydE1lc3NhZ2UpKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gICAgZW5zdXJlQ29ubmVjdGlvbigpO1xuXG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgIGNvbnN0IHN0b3BNZXNzYWdlID0ge1xuICAgICAgICBpZDogc3Vic2NyaXB0aW9uSWQsXG4gICAgICAgIHR5cGU6ICdzdG9wJ1xuICAgICAgfTtcbiAgICAgIHRoaXMuc2VuZChKU09OLnN0cmluZ2lmeShzdG9wTWVzc2FnZSkpO1xuICAgICAgdGhpcy5zdWJzY3JpcHRpb25zLmRlbGV0ZShzdWJzY3JpcHRpb25JZCk7XG4gICAgICBjb25zdCBoYW5kbGVyID0gdGhpcy5tZXNzYWdlSGFuZGxlcnMuZ2V0KHN1YnNjcmlwdGlvbklkKTtcbiAgICAgIGlmKGhhbmRsZXIgJiYgdGhpcy53cykge1xuICAgICAgICB0aGlzLndzLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ21lc3NhZ2UnLCBoYW5kbGVyKTtcbiAgICAgICAgdGhpcy5tZXNzYWdlSGFuZGxlcnMuZGVsZXRlKHN1YnNjcmlwdGlvbklkKTtcbiAgICAgIH1cbiAgICB9O1xuICB9XG5cbiAgY2xvc2UoKTogdm9pZCB7XG4gICAgaWYodGhpcy5yZWNvbm5lY3RUaW1lcikge1xuICAgICAgY2xlYXJUaW1lb3V0KHRoaXMucmVjb25uZWN0VGltZXIpO1xuICAgICAgdGhpcy5yZWNvbm5lY3RUaW1lciA9IG51bGw7XG4gICAgfVxuICAgIGlmKHRoaXMud3MpIHtcbiAgICAgIHRoaXMud3MuY2xvc2UoKTtcbiAgICAgIHRoaXMud3MgPSBudWxsO1xuICAgIH1cbiAgICB0aGlzLm1lc3NhZ2VRdWV1ZSA9IFtdO1xuICAgIHRoaXMuc3Vic2NyaXB0aW9ucy5jbGVhcigpO1xuICAgIHRoaXMubWVzc2FnZUhhbmRsZXJzLmNsZWFyKCk7XG4gICAgdGhpcy5pc0Nvbm5lY3RlZCA9IGZhbHNlO1xuICAgIHRoaXMuaXNDb25uZWN0aW5nID0gZmFsc2U7XG4gIH1cbn1cblxuLy8gU3Vic2NyaXB0aW9uIGNsaWVudCBjYWNoZVxuY29uc3Qgc3Vic2NyaXB0aW9uQ2xpZW50cyA9IG5ldyBNYXA8c3RyaW5nLCBHcmFwaFFMU3Vic2NyaXB0aW9uQ2xpZW50PigpO1xuXG4vLyBBSkFYXG5leHBvcnQgY29uc3QgYWpheCA9IChcbiAgdXJsOiBzdHJpbmcsXG4gIG1ldGhvZDogc3RyaW5nLFxuICBwYXJhbXM/LFxuICBvcHRpb25zOiBIdW50ZXJPcHRpb25zVHlwZSA9IHt9XG4pOiBQcm9taXNlPGFueT4gPT4ge1xuICBjb25zdCB7aGVhZGVycywgdG9rZW4sIHRpbWVvdXQgPSAzMDAwMCwgY2FjaGUgPSBmYWxzZX0gPSBvcHRpb25zO1xuXG4gIGxldCBmb3JtYXRVcmw6IHN0cmluZyA9ICh1cmwgfHwgJycpLnRyaW0oKTtcbiAgY29uc3QgZm9ybWF0VG9rZW46IHN0cmluZyA9ICh0b2tlbiB8fCAnJykudHJpbSgpO1xuICBjb25zdCBmb3JtYXRIZWFkZXJzOiBIZWFkZXJzID0gaGVhZGVycyB8fCBuZXcgSGVhZGVycygpO1xuXG4gIC8vIE1ldGhvZFxuICBjb25zdCBmb3JtYXRNZXRob2Q6IHN0cmluZyA9IChtZXRob2QgfHwgJ0dFVCcpLnRvVXBwZXJDYXNlKCk7XG4gIGxldCBmb3JtYXRQYXJhbXM7XG5cbiAgLy8gUGFyYW1ldGVyc1xuICBpZihwYXJhbXMgJiYgZm9ybWF0TWV0aG9kID09PSAnR0VUJykge1xuICAgIGZvcm1hdFVybCA9IGAke2Zvcm1hdFVybH0/JHtxdWVyeVN0cmluZyhwYXJhbXMpfWA7XG4gICAgZm9ybWF0UGFyYW1zID0gbnVsbDtcbiAgfSBlbHNlIGlmKHBhcmFtcykge1xuICAgIGZvcm1hdEhlYWRlcnMuc2V0KCdBY2NlcHQnLCAnYXBwbGljYXRpb24vanNvbicpO1xuICAgIGZvcm1hdEhlYWRlcnMuc2V0KCdDb250ZW50LVR5cGUnLCAnYXBwbGljYXRpb24vanNvbicpO1xuICAgIGZvcm1hdFBhcmFtcyA9IEpTT04uc3RyaW5naWZ5KHBhcmFtcyk7XG4gIH0gZWxzZSB7XG4gICAgZm9ybWF0UGFyYW1zID0gcGFyYW1zO1xuICB9XG5cbiAgLy8gQXV0aGVudGljYXRpb24gdG9rZW5cbiAgaWYoIWlzRW1wdHkoZm9ybWF0VG9rZW4pKSB7XG4gICAgZm9ybWF0SGVhZGVycy5zZXQoJ0F1dGhvcml6YXRpb24nLCBgQmVhcmVyICR7Zm9ybWF0VG9rZW59YCk7XG4gIH1cblxuICAvLyBDaGVjayBjYWNoZSBmb3IgR0VUIHJlcXVlc3RzXG4gIGlmKGNhY2hlICYmIGZvcm1hdE1ldGhvZCA9PT0gJ0dFVCcpIHtcbiAgICBjb25zdCBjYWNoZUtleSA9IGdlbmVyYXRlQ2FjaGVLZXkoZm9ybWF0VXJsLCBmb3JtYXRNZXRob2QsIHBhcmFtcywgb3B0aW9ucyk7XG4gICAgY29uc3QgY2FjaGVkID0gcmVxdWVzdENhY2hlLmdldChjYWNoZUtleSk7XG4gICAgaWYoY2FjaGVkKSB7XG4gICAgICByZXR1cm4gY2FjaGVkO1xuICAgIH1cbiAgfVxuXG4gIC8vIENyZWF0ZSB0aGUgcmVxdWVzdCBwcm9taXNlXG4gIGNvbnN0IHJlcXVlc3RQcm9taXNlID0gZmV0Y2goZm9ybWF0VXJsLCB7XG4gICAgYm9keTogZm9ybWF0UGFyYW1zLFxuICAgIGhlYWRlcnM6IGZvcm1hdEhlYWRlcnMsXG4gICAgbWV0aG9kOiBmb3JtYXRNZXRob2RcbiAgfSlcbiAgICAudGhlbigocmVzcG9uc2U6IFJlc3BvbnNlKSA9PiB7XG4gICAgICAvLyBDaGVjayBpZiByZXNwb25zZSBpcyBqc29uIHVzaW5nIGNhY2hlZCByZWdleFxuICAgICAgY29uc3QgaXNSZXNwb25zZUpzb24gPSBKU09OX0NPTlRFTlRfVFlQRV9SRUdFWC50ZXN0KFxuICAgICAgICByZXNwb25zZS5oZWFkZXJzLmdldCgnQ29udGVudC1UeXBlJykgfHwgJydcbiAgICAgICk7XG5cbiAgICAgIGlmKGlzUmVzcG9uc2VKc29uKSB7XG4gICAgICAgIHJldHVybiByZXNwb25zZS5qc29uKCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiByZXNwb25zZS50ZXh0KCk7XG4gICAgfSlcbiAgICAudGhlbigocmVzdWx0cykgPT4gcmVzdWx0cykgLy8gU2ltcGxpZmllZCAtIG5vIG5lZWQgZm9yIHJlZHVuZGFudCBjaGVja1xuICAgIC5jYXRjaCgoZXJyb3IpID0+IHtcbiAgICAgIGlmKChlcnJvciB8fCB7fSkubWVzc2FnZSA9PT0gJ29ubHkgYWJzb2x1dGUgdXJscyBhcmUgc3VwcG9ydGVkJykge1xuICAgICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QoXG4gICAgICAgICAgbmV3IEFwaUVycm9yKFt7bWVzc2FnZTogJ2ludmFsaWRfdXJsJ31dLCBlcnJvcilcbiAgICAgICAgKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIFByb21pc2UucmVqZWN0KFxuICAgICAgICBuZXcgQXBpRXJyb3IoW3ttZXNzYWdlOiAnbmV0d29ya19lcnJvcid9XSwgZXJyb3IpXG4gICAgICApO1xuICAgIH0pO1xuXG4gIC8vIEFkZCB0aW1lb3V0IGlmIHNwZWNpZmllZFxuICBjb25zdCBmaW5hbFByb21pc2UgPSB0aW1lb3V0ID4gMFxuICAgID8gUHJvbWlzZS5yYWNlKFtyZXF1ZXN0UHJvbWlzZSwgY3JlYXRlVGltZW91dCh0aW1lb3V0KV0pXG4gICAgOiByZXF1ZXN0UHJvbWlzZTtcblxuICAvLyBDYWNoZSB0aGUgcHJvbWlzZSBmb3IgR0VUIHJlcXVlc3RzXG4gIGlmKGNhY2hlICYmIGZvcm1hdE1ldGhvZCA9PT0gJ0dFVCcpIHtcbiAgICBjb25zdCBjYWNoZUtleSA9IGdlbmVyYXRlQ2FjaGVLZXkoZm9ybWF0VXJsLCBmb3JtYXRNZXRob2QsIHBhcmFtcywgb3B0aW9ucyk7XG4gICAgcmVxdWVzdENhY2hlLnNldChjYWNoZUtleSwgZmluYWxQcm9taXNlKTtcblxuICAgIC8vIENsZWFuIHVwIGNhY2hlIGFmdGVyIDUgbWludXRlc1xuICAgIHNldFRpbWVvdXQoKCkgPT4gcmVxdWVzdENhY2hlLmRlbGV0ZShjYWNoZUtleSksIDMwMDAwMCk7XG4gIH1cblxuICByZXR1cm4gZmluYWxQcm9taXNlO1xufTtcblxuZXhwb3J0IGNvbnN0IGdldCA9IChcbiAgdXJsOiBzdHJpbmcsXG4gIHBhcmFtcz8sXG4gIG9wdGlvbnM/OiBIdW50ZXJPcHRpb25zVHlwZVxuKTogUHJvbWlzZTxhbnk+ID0+IGFqYXgodXJsLCAnR0VUJywgcGFyYW1zLCBvcHRpb25zKTtcblxuZXhwb3J0IGNvbnN0IHBvc3QgPSAoXG4gIHVybDogc3RyaW5nLFxuICBwYXJhbXM/LFxuICBvcHRpb25zPzogSHVudGVyT3B0aW9uc1R5cGVcbik6IFByb21pc2U8YW55PiA9PiBhamF4KHVybCwgJ1BPU1QnLCBwYXJhbXMsIG9wdGlvbnMpO1xuXG5leHBvcnQgY29uc3QgcHV0ID0gKFxuICB1cmw6IHN0cmluZyxcbiAgcGFyYW1zPyxcbiAgb3B0aW9ucz86IEh1bnRlck9wdGlvbnNUeXBlXG4pOiBQcm9taXNlPGFueT4gPT4gYWpheCh1cmwsICdQVVQnLCBwYXJhbXMsIG9wdGlvbnMpO1xuXG5leHBvcnQgY29uc3QgZGVsID0gKFxuICB1cmw6IHN0cmluZyxcbiAgcGFyYW1zPyxcbiAgb3B0aW9ucz86IEh1bnRlck9wdGlvbnNUeXBlXG4pOiBQcm9taXNlPGFueT4gPT4gYWpheCh1cmwsICdERUxFVEUnLCBwYXJhbXMsIG9wdGlvbnMpO1xuXG4vLyBPcHRpbWl6ZWQgdG9HcWwgZnVuY3Rpb25cbmV4cG9ydCBjb25zdCB0b0dxbCA9IChvYmo6IGFueSk6IHN0cmluZyA9PiB7XG4gIGlmKGlzU3RyaW5nKG9iaikpIHtcbiAgICByZXR1cm4gSlNPTi5zdHJpbmdpZnkob2JqKTtcbiAgfSBlbHNlIGlmKGlzUGxhaW5PYmplY3Qob2JqKSkge1xuICAgIC8vIEZpbHRlciBvdXQgdW5kZWZpbmVkIGFuZCBudWxsIHZhbHVlcyBpbiBvbmUgcGFzc1xuICAgIGNvbnN0IGdxbFByb3BzOiBzdHJpbmdbXSA9IFtdO1xuXG4gICAgZm9yKGNvbnN0IGtleSBpbiBvYmopIHtcbiAgICAgIGlmKG9iai5oYXNPd25Qcm9wZXJ0eShrZXkpKSB7XG4gICAgICAgIGNvbnN0IGl0ZW0gPSBvYmpba2V5XTtcblxuICAgICAgICAvLyBTa2lwIHVuZGVmaW5lZCBhbmQgbnVsbCB2YWx1ZXNcbiAgICAgICAgaWYoaXNVbmRlZmluZWQoaXRlbSkgfHwgaXNOdWxsKGl0ZW0pKSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cblxuICAgICAgICBpZihpc1BsYWluT2JqZWN0KGl0ZW0pKSB7XG4gICAgICAgICAgZ3FsUHJvcHMucHVzaChgJHtrZXl9OiAke3RvR3FsKGl0ZW0pfWApO1xuICAgICAgICB9IGVsc2UgaWYoaXNBcnJheShpdGVtKSkge1xuICAgICAgICAgIGNvbnN0IGxpc3QgPSBpdGVtLm1hcCgobGlzdEl0ZW0pID0+IHRvR3FsKGxpc3RJdGVtKSk7XG4gICAgICAgICAgZ3FsUHJvcHMucHVzaChgJHtrZXl9OiBbJHtsaXN0LmpvaW4oJywgJyl9XWApO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGNvbnN0IHZhbCA9IEpTT04uc3RyaW5naWZ5KGl0ZW0pO1xuICAgICAgICAgIGlmKHZhbCkge1xuICAgICAgICAgICAgZ3FsUHJvcHMucHVzaChgJHtrZXl9OiAke3ZhbH1gKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBjb25zdCB2YWx1ZXMgPSBncWxQcm9wcy5qb2luKCcsICcpO1xuICAgIHJldHVybiB2YWx1ZXMgPT09ICcnID8gJ1wiXCInIDogYHske2dxbFByb3BzLmpvaW4oJywgJyl9fWA7XG4gIH0gZWxzZSBpZihpc0FycmF5KG9iaikpIHtcbiAgICByZXR1cm4gYFske29iai5tYXAoKG9iakl0ZW0pID0+IHRvR3FsKG9iakl0ZW0pKS5qb2luKCcsICcpfV1gO1xuICB9XG5cbiAgcmV0dXJuIFN0cmluZyhvYmopO1xufTtcblxuZXhwb3J0IGNvbnN0IGdyYXBocWxRdWVyeSA9IChcbiAgdXJsOiBzdHJpbmcsXG4gIHF1ZXJ5OiBIdW50ZXJRdWVyeVR5cGUgfCBIdW50ZXJRdWVyeVR5cGVbXSxcbiAgb3B0aW9uczogSHVudGVyT3B0aW9uc1R5cGUgPSB7fVxuKTogUHJvbWlzZTxhbnk+ID0+IHtcbiAgY29uc3Qge2hlYWRlcnMsIHRva2VuLCB0aW1lb3V0ID0gMzAwMDB9ID0gb3B0aW9ucztcbiAgY29uc3QgZm9ybWF0VXJsOiBzdHJpbmcgPSB1cmwgPyB1cmwudHJpbSgpIDogJyc7XG4gIGNvbnN0IGZvcm1hdFRva2VuOiBzdHJpbmcgPSAodG9rZW4gfHwgJycpLnRyaW0oKTtcbiAgY29uc3QgZm9ybWF0SGVhZGVyczogSGVhZGVycyA9IGhlYWRlcnMgfHwgbmV3IEhlYWRlcnMoeydDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbid9KTtcblxuICBpZighaXNFbXB0eShmb3JtYXRUb2tlbikpIHtcbiAgICBmb3JtYXRIZWFkZXJzLnNldCgnQXV0aG9yaXphdGlvbicsIGBCZWFyZXIgJHtmb3JtYXRUb2tlbn1gKTtcbiAgfVxuXG4gIGNvbnN0IHJlcXVlc3RQcm9taXNlID0gZmV0Y2goZm9ybWF0VXJsLCB7XG4gICAgYm9keTogSlNPTi5zdHJpbmdpZnkocXVlcnkpLFxuICAgIGhlYWRlcnM6IGZvcm1hdEhlYWRlcnMsXG4gICAgbWV0aG9kOiAncG9zdCdcbiAgfSlcbiAgICAudGhlbigocmVzcG9uc2U6IFJlc3BvbnNlKSA9PiB7XG4gICAgICBjb25zdCBpc0pzb246IGJvb2xlYW4gPSBKU09OX0NPTlRFTlRfVFlQRV9SRUdFWC50ZXN0KFxuICAgICAgICByZXNwb25zZS5oZWFkZXJzLmdldCgnQ29udGVudC1UeXBlJykgfHwgJydcbiAgICAgICk7XG5cbiAgICAgIGlmKGlzSnNvbiAmJiByZXNwb25zZS5ib2R5KSB7XG4gICAgICAgIHJldHVybiByZXNwb25zZS5qc29uKCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBudWxsO1xuICAgIH0pXG4gICAgLmNhdGNoKChlcnJvcikgPT4ge1xuICAgICAgaWYoKGVycm9yIHx8IHt9KS5tZXNzYWdlID09PSAnb25seSBhYnNvbHV0ZSB1cmxzIGFyZSBzdXBwb3J0ZWQnKSB7XG4gICAgICAgIHJldHVybiBQcm9taXNlLnJlamVjdChcbiAgICAgICAgICBuZXcgQXBpRXJyb3IoW3ttZXNzYWdlOiAnaW52YWxpZF91cmwnfV0sIGVycm9yKVxuICAgICAgICApO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QoXG4gICAgICAgIG5ldyBBcGlFcnJvcihbe21lc3NhZ2U6ICduZXR3b3JrX2Vycm9yJ31dLCBlcnJvcilcbiAgICAgICk7XG4gICAgfSlcbiAgICAudGhlbigoanNvbikgPT4ge1xuICAgICAgaWYoIWpzb24gfHwganNvbi5lcnJvcnMpIHtcbiAgICAgICAgaWYoIWpzb24pIHtcbiAgICAgICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QoXG4gICAgICAgICAgICBuZXcgQXBpRXJyb3IoW3ttZXNzYWdlOiAnYXBpX2Vycm9yJ31dLCBuZXcgRXJyb3IoKSlcbiAgICAgICAgICApO1xuICAgICAgICB9IGVsc2UgaWYoXG4gICAgICAgICAgKGpzb24uZXJyb3JzIHx8IFtdKS5zb21lKFxuICAgICAgICAgICAgKGVycm9yKSA9PiBlcnJvci5tZXNzYWdlID09PSAnTXVzdCBwcm92aWRlIHF1ZXJ5IHN0cmluZy4nXG4gICAgICAgICAgKVxuICAgICAgICApIHtcbiAgICAgICAgICByZXR1cm4gUHJvbWlzZS5yZWplY3QoXG4gICAgICAgICAgICBuZXcgQXBpRXJyb3IoW3ttZXNzYWdlOiAncmVxdWlyZWRfcXVlcnknfV0sIG5ldyBFcnJvcigpKVxuICAgICAgICAgICk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4ganNvbi5lcnJvcnNcbiAgICAgICAgICA/IFByb21pc2UucmVqZWN0KG5ldyBBcGlFcnJvcihqc29uLmVycm9ycywgbmV3IEVycm9yKCkpKVxuICAgICAgICAgIDoge307XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBqc29uLmRhdGEgfHwge307XG4gICAgfSlcbiAgICAuY2F0Y2goKGVycm9yOiBBcGlFcnJvcikgPT5cbiAgICAgIC8vIFNpbXBsaWZpZWQgZXJyb3IgaGFuZGxpbmcgLSBubyBuZWVkIHRvIGNyZWF0ZSBuZXcgZXJyb3IgaWYgc291cmNlIGV4aXN0c1xuICAgICAgUHJvbWlzZS5yZWplY3QoZXJyb3Iuc291cmNlID8gZXJyb3IgOiBuZXcgQXBpRXJyb3IoW3ttZXNzYWdlOiAnbmV0d29ya19lcnJvcid9XSwgZXJyb3IpKVxuICAgICk7XG5cbiAgLy8gQWRkIHRpbWVvdXQgaWYgc3BlY2lmaWVkXG4gIHJldHVybiB0aW1lb3V0ID4gMFxuICAgID8gUHJvbWlzZS5yYWNlKFtyZXF1ZXN0UHJvbWlzZSwgY3JlYXRlVGltZW91dCh0aW1lb3V0KV0pXG4gICAgOiByZXF1ZXN0UHJvbWlzZTtcbn07XG5cbi8vIEFkZCBxdWVyeSBhbmQgbXV0YXRpb24gZnVuY3Rpb25zIGZvciBiZXR0ZXIgQVBJIGNvbnNpc3RlbmN5XG5leHBvcnQgY29uc3QgcXVlcnkgPSAoXG4gIHVybDogc3RyaW5nLFxuICBib2R5OiBzdHJpbmcsXG4gIG9wdGlvbnM6IEh1bnRlck9wdGlvbnNUeXBlID0ge31cbik6IFByb21pc2U8YW55PiA9PiBncmFwaHFsUXVlcnkodXJsLCB7cXVlcnk6IGJvZHl9LCBvcHRpb25zKTtcblxuZXhwb3J0IGNvbnN0IG11dGF0aW9uID0gKFxuICB1cmw6IHN0cmluZyxcbiAgYm9keTogc3RyaW5nLFxuICBvcHRpb25zOiBIdW50ZXJPcHRpb25zVHlwZSA9IHt9XG4pOiBQcm9taXNlPGFueT4gPT4gZ3JhcGhxbFF1ZXJ5KHVybCwge3F1ZXJ5OiBib2R5fSwgb3B0aW9ucyk7XG5cbi8vIEdyYXBoUUwgU3Vic2NyaXB0aW9uc1xuZXhwb3J0IGNvbnN0IHN1YnNjcmliZSA9IChcbiAgdXJsOiBzdHJpbmcsXG4gIHF1ZXJ5OiBzdHJpbmcsXG4gIGNhbGxiYWNrczogSHVudGVyU3Vic2NyaXB0aW9uQ2FsbGJhY2tUeXBlLFxuICBvcHRpb25zOiBIdW50ZXJTdWJzY3JpcHRpb25PcHRpb25zVHlwZSA9IHt9XG4pOiAoKCkgPT4gdm9pZCkgPT4ge1xuICBjb25zdCB7dG9rZW4sIHZhcmlhYmxlcywgY29ubmVjdGlvblBhcmFtcywgaGVhZGVyc30gPSBvcHRpb25zO1xuICBjb25zdCBmb3JtYXRVcmw6IHN0cmluZyA9ICh1cmwgfHwgJycpLnRyaW0oKTtcbiAgY29uc3QgZm9ybWF0VG9rZW46IHN0cmluZyA9ICh0b2tlbiB8fCAnJykudHJpbSgpO1xuXG4gIGlmKCFmb3JtYXRVcmwpIHtcbiAgICBpZihjYWxsYmFja3Mub25FcnJvcikge1xuICAgICAgY2FsbGJhY2tzLm9uRXJyb3IobmV3IEVycm9yKCdTdWJzY3JpcHRpb24gVVJMIGlzIHJlcXVpcmVkJykpO1xuICAgIH1cbiAgICByZXR1cm4gKCkgPT4ge307XG4gIH1cblxuICBsZXQgY2xpZW50ID0gc3Vic2NyaXB0aW9uQ2xpZW50cy5nZXQoZm9ybWF0VXJsKTtcbiAgaWYoIWNsaWVudCkge1xuICAgIGNvbnN0IGNsaWVudE9wdGlvbnM6IEh1bnRlclN1YnNjcmlwdGlvbk9wdGlvbnNUeXBlID0ge1xuICAgICAgLi4ub3B0aW9ucyxcbiAgICAgIGNvbm5lY3Rpb25QYXJhbXM6IHtcbiAgICAgICAgLi4uY29ubmVjdGlvblBhcmFtcyxcbiAgICAgICAgLi4uKGZvcm1hdFRva2VuID8ge2F1dGhvcml6YXRpb246IGBCZWFyZXIgJHtmb3JtYXRUb2tlbn1gfSA6IHt9KVxuICAgICAgfVxuICAgIH07XG4gICAgY2xpZW50ID0gbmV3IEdyYXBoUUxTdWJzY3JpcHRpb25DbGllbnQoZm9ybWF0VXJsLCBjbGllbnRPcHRpb25zKTtcbiAgICBzdWJzY3JpcHRpb25DbGllbnRzLnNldChmb3JtYXRVcmwsIGNsaWVudCk7XG4gIH1cblxuICByZXR1cm4gY2xpZW50LnN1YnNjcmliZShxdWVyeSwgdmFyaWFibGVzLCBjYWxsYmFja3MpO1xufTtcblxuLy8gU1NFIFN1cHBvcnRcbmV4cG9ydCBjb25zdCBzdWJzY3JpYmVTU0UgPSAoXG4gIHVybDogc3RyaW5nLFxuICBjYWxsYmFja3M6IEh1bnRlclNTRUNhbGxiYWNrVHlwZSxcbiAgb3B0aW9uczogSHVudGVyU1NFT3B0aW9uc1R5cGUgPSB7fVxuKTogKCgpID0+IHZvaWQpID0+IHtcbiAgY29uc3Qge2hlYWRlcnMsIHRva2VuLCB0aW1lb3V0ID0gMzAwMDAsIHJldHJ5SW50ZXJ2YWwgPSAxMDAwLCBtYXhSZXRyaWVzID0gNX0gPSBvcHRpb25zO1xuICBjb25zdCBmb3JtYXRVcmw6IHN0cmluZyA9ICh1cmwgfHwgJycpLnRyaW0oKTtcbiAgY29uc3QgZm9ybWF0VG9rZW46IHN0cmluZyA9ICh0b2tlbiB8fCAnJykudHJpbSgpO1xuICBjb25zdCBmb3JtYXRIZWFkZXJzOiBIZWFkZXJzID0gaGVhZGVycyB8fCBuZXcgSGVhZGVycygpO1xuXG4gIC8vIEFkZCBhdXRob3JpemF0aW9uIGhlYWRlciBpZiB0b2tlbiBwcm92aWRlZFxuICBpZighaXNFbXB0eShmb3JtYXRUb2tlbikpIHtcbiAgICBmb3JtYXRIZWFkZXJzLnNldCgnQXV0aG9yaXphdGlvbicsIGBCZWFyZXIgJHtmb3JtYXRUb2tlbn1gKTtcbiAgfVxuXG4gIGxldCByZXRyeUNvdW50ID0gMDtcbiAgbGV0IGV2ZW50U291cmNlOiBFdmVudFNvdXJjZSB8IG51bGwgPSBudWxsO1xuICBsZXQgdGltZW91dElkOiBOb2RlSlMuVGltZW91dCB8IG51bGwgPSBudWxsO1xuXG4gIGNvbnN0IGNvbm5lY3QgPSAoKTogdm9pZCA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIGV2ZW50U291cmNlID0gY3JlYXRlRXZlbnRTb3VyY2UoZm9ybWF0VXJsLCB7aGVhZGVyczogZm9ybWF0SGVhZGVyc30pO1xuXG4gICAgICAvLyBTZXQgdXAgdGltZW91dFxuICAgICAgaWYodGltZW91dCA+IDApIHtcbiAgICAgICAgdGltZW91dElkID0gc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgaWYoZXZlbnRTb3VyY2UpIHtcbiAgICAgICAgICAgIGV2ZW50U291cmNlLmNsb3NlKCk7XG4gICAgICAgICAgICBpZihjYWxsYmFja3Mub25FcnJvcikge1xuICAgICAgICAgICAgICBjYWxsYmFja3Mub25FcnJvcihuZXcgRXJyb3IoJ1NTRSBjb25uZWN0aW9uIHRpbWVvdXQnKSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9LCB0aW1lb3V0KTtcbiAgICAgIH1cblxuICAgICAgLy8gRXZlbnQgaGFuZGxlcnNcbiAgICAgIGV2ZW50U291cmNlLm9ub3BlbiA9IChldmVudCkgPT4ge1xuICAgICAgICBpZih0aW1lb3V0SWQpIHtcbiAgICAgICAgICBjbGVhclRpbWVvdXQodGltZW91dElkKTtcbiAgICAgICAgICB0aW1lb3V0SWQgPSBudWxsO1xuICAgICAgICB9XG4gICAgICAgIHJldHJ5Q291bnQgPSAwOyAvLyBSZXNldCByZXRyeSBjb3VudCBvbiBzdWNjZXNzZnVsIGNvbm5lY3Rpb25cbiAgICAgICAgaWYoY2FsbGJhY2tzLm9uT3Blbikge1xuICAgICAgICAgIGNhbGxiYWNrcy5vbk9wZW4oZXZlbnQpO1xuICAgICAgICB9XG4gICAgICB9O1xuXG4gICAgICBldmVudFNvdXJjZS5vbm1lc3NhZ2UgPSAoZXZlbnQpID0+IHtcbiAgICAgICAgaWYoY2FsbGJhY2tzLm9uTWVzc2FnZSkge1xuICAgICAgICAgIGNvbnN0IHNzZUV2ZW50OiBIdW50ZXJTU0VFdmVudFR5cGUgPSB7XG4gICAgICAgICAgICBkYXRhOiBldmVudC5kYXRhLFxuICAgICAgICAgICAgaWQ6IGV2ZW50Lmxhc3RFdmVudElkLFxuICAgICAgICAgICAgcmV0cnk6IGV2ZW50LmRhdGEuaW5jbHVkZXMoJ3JldHJ5OicpID8gcGFyc2VJbnQoZXZlbnQuZGF0YS5zcGxpdCgncmV0cnk6JylbMV0pIDogdW5kZWZpbmVkLFxuICAgICAgICAgICAgdHlwZTogZXZlbnQudHlwZVxuICAgICAgICAgIH07XG4gICAgICAgICAgY2FsbGJhY2tzLm9uTWVzc2FnZShzc2VFdmVudCk7XG4gICAgICAgIH1cbiAgICAgIH07XG5cbiAgICAgIGV2ZW50U291cmNlLm9uZXJyb3IgPSAoZXZlbnQpID0+IHtcbiAgICAgICAgaWYod