UNPKG

mobx-keystone-yjs

Version:

Yjs bindings for mobx-keystone

742 lines 97.8 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { createAtom, reaction, observe, computed, action, remove } from "mobx"; import { createContext, Model, tProp, types, frozen, getParentToChildPath, onSnapshot, model, frozenKey, modelTypeKey, DeepChangeType, modelSnapshotOutWithMetadata, resolvePath, runUnprotected, isModel, getSnapshot, getSnapshotModelId, isFrozenSnapshot, fromSnapshot, onGlobalDeepChange, onDeepChange, isTreeNode } from "mobx-keystone"; import * as Y from "yjs"; class MobxKeystoneYjsError extends Error { constructor(msg) { super(msg); Object.setPrototypeOf(this, MobxKeystoneYjsError.prototype); } } function failure(msg) { return new MobxKeystoneYjsError(msg); } const yjsCollectionAtoms = /* @__PURE__ */ new WeakMap(); const getYjsCollectionAtom = (yjsCollection) => { return yjsCollectionAtoms.get(yjsCollection); }; const getOrCreateYjsCollectionAtom = (yjsCollection) => { let atom = yjsCollectionAtoms.get(yjsCollection); if (!atom) { atom = createAtom(`yjsCollectionAtom`); yjsCollectionAtoms.set(yjsCollection, atom); } return atom; }; function isYjsValueDeleted(yjsValue) { var _a, _b; if (yjsValue instanceof Y.AbstractType) { return !!((_a = yjsValue._item) == null ? void 0 : _a.deleted) || !!((_b = yjsValue.doc) == null ? void 0 : _b.isDestroyed); } return false; } function resolveYjsPath(yjsObject, path) { let currentYjsObject = yjsObject; let i = -1; for (const pathPart of path) { i++; if (currentYjsObject instanceof Y.Text) { return currentYjsObject; } if (currentYjsObject instanceof Y.Map) { getOrCreateYjsCollectionAtom(currentYjsObject).reportObserved(); const key = String(pathPart); currentYjsObject = currentYjsObject.get(key); } else if (currentYjsObject instanceof Y.Array) { getOrCreateYjsCollectionAtom(currentYjsObject).reportObserved(); const key = Number(pathPart); currentYjsObject = currentYjsObject.get(key); } else { throw failure( `Y.Map or Y.Array was expected at path ${JSON.stringify( path.slice(0, i) )} in order to resolve path ${JSON.stringify(path)}, but got ${currentYjsObject} instead` ); } } return currentYjsObject; } const yjsBindingContext = createContext(void 0); var __defProp2 = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp2(target, key, result); return result; }; const deltaListType = types.array(types.frozen(types.unchecked())); const yjsTextModelId = "mobx-keystone-yjs/YjsTextModel"; let YjsTextModel = class extends Model({ deltaList: tProp(deltaListType, () => []) }) { constructor() { super(...arguments); /** * Atom that gets changed when the associated Y.js text changes. */ __publicField(this, "yjsTextChangedAtom", createAtom("yjsTextChangedAtom")); } /** * Helper function to create a YjsTextModel instance with a simple text. */ static withText(text) { return new DecoratedYjsTextModel({ deltaList: [ frozen([ { insert: text } ]) ] }); } get _yjsObjectPath() { const ctx = yjsBindingContext.get(this); if ((ctx == null ? void 0 : ctx.boundObject) == null) { throw failure( "the YjsTextModel instance must be part of a bound object before it can be accessed" ); } const path = getParentToChildPath(ctx.boundObject, this); if (!path) { throw failure("a path from the bound object to the YjsTextModel instance is not available"); } return path; } get _yjsObjectAtPath() { const path = this._yjsObjectPath; const ctx = yjsBindingContext.get(this); return resolveYjsPath(ctx.yjsObject, path); } get yjsText() { const yjsObject = this._yjsObjectAtPath; if (!(yjsObject instanceof Y.Text)) { throw failure(`Y.Text was expected at path ${JSON.stringify(this._yjsObjectPath)}`); } return yjsObject; } get text() { this.yjsTextChangedAtom.reportObserved(); const ctx = yjsBindingContext.get(this); if ((ctx == null ? void 0 : ctx.boundObject) != null) { try { const yjsTextString = this.yjsText.toString(); if (yjsTextString !== "" || this.deltaList.length === 0) { return yjsTextString; } } catch { } } return this.deltaListToText(); } deltaListToText() { const doc = new Y.Doc(); const text = doc.getText(); this.deltaList.forEach((d) => { text.applyDelta(d.data); }); return text.toString(); } onInit() { const shouldReplicateToYjs = (ctx) => { return !!ctx && !!ctx.boundObject && !ctx.isApplyingYjsChangesToMobxKeystone; }; let reapplyDeltasToYjsText = false; const newDeltas = []; let disposeObserveDeltaList; const disposeReactionToDeltaListRefChange = reaction( () => this.$.deltaList, (deltaList) => { disposeObserveDeltaList == null ? void 0 : disposeObserveDeltaList(); disposeObserveDeltaList = void 0; disposeObserveDeltaList = observe(deltaList, (change) => { if (reapplyDeltasToYjsText) { return; } if (!shouldReplicateToYjs(yjsBindingContext.get(this))) { return; } if (change.type === "splice" && change.removedCount === 0 && change.addedCount > 0 && change.index === this.deltaList.length) { newDeltas.push(...change.added); } else { reapplyDeltasToYjsText = true; } }); }, { fireImmediately: true } ); const disposeOnSnapshot = onSnapshot(this, () => { try { if (reapplyDeltasToYjsText) { const ctx = yjsBindingContext.get(this); if (shouldReplicateToYjs(ctx)) { const { yjsText } = this; if (isYjsValueDeleted(yjsText)) { throw failure("cannot reapply deltas to deleted Yjs.Text"); } ctx.yjsDoc.transact(() => { if (yjsText.length > 0) { yjsText.delete(0, yjsText.length); } this.deltaList.forEach((frozenDeltas) => { yjsText.applyDelta(frozenDeltas.data); }); }, ctx.yjsOrigin); } } else if (newDeltas.length > 0) { const ctx = yjsBindingContext.get(this); if (shouldReplicateToYjs(ctx)) { const { yjsText } = this; if (isYjsValueDeleted(yjsText)) { throw failure("cannot reapply deltas to deleted Yjs.Text"); } ctx.yjsDoc.transact(() => { newDeltas.forEach((frozenDeltas) => { yjsText.applyDelta(frozenDeltas.data); }); }, ctx.yjsOrigin); } } } finally { reapplyDeltasToYjsText = false; newDeltas.length = 0; } }); const diposeYjsTextChangedAtom = hookYjsTextChangedAtom( () => this.yjsText, this.yjsTextChangedAtom ); return () => { disposeOnSnapshot(); disposeReactionToDeltaListRefChange(); disposeObserveDeltaList == null ? void 0 : disposeObserveDeltaList(); disposeObserveDeltaList = void 0; diposeYjsTextChangedAtom(); }; } }; __decorateClass([ computed ], YjsTextModel.prototype, "_yjsObjectPath", 1); __decorateClass([ computed ], YjsTextModel.prototype, "_yjsObjectAtPath", 1); __decorateClass([ computed ], YjsTextModel.prototype, "yjsText", 1); __decorateClass([ computed ], YjsTextModel.prototype, "text", 1); YjsTextModel = __decorateClass([ model(yjsTextModelId) ], YjsTextModel); const DecoratedYjsTextModel = YjsTextModel; function hookYjsTextChangedAtom(getYjsText, textChangedAtom) { let disposeObserveYjsText; const observeFn = () => { textChangedAtom.reportChanged(); }; const disposeReactionToYTextChange = reaction( () => { try { const yjsText = getYjsText(); return isYjsValueDeleted(yjsText) ? void 0 : yjsText; } catch { return void 0; } }, (yjsText) => { disposeObserveYjsText == null ? void 0 : disposeObserveYjsText(); disposeObserveYjsText = void 0; if (yjsText) { yjsText.observe(observeFn); disposeObserveYjsText = () => { yjsText.unobserve(observeFn); }; } textChangedAtom.reportChanged(); }, { fireImmediately: true } ); return () => { disposeReactionToYTextChange(); disposeObserveYjsText == null ? void 0 : disposeObserveYjsText(); disposeObserveYjsText = void 0; }; } const yjsContainerToSnapshot = /* @__PURE__ */ new WeakMap(); function setYjsContainerSnapshot(container, snapshot) { yjsContainerToSnapshot.set(container, snapshot); } function isYjsContainerUpToDate(container, snapshot) { return yjsContainerToSnapshot.get(container) === snapshot; } function isPlainPrimitive(v) { const t = typeof v; return t === "string" || t === "number" || t === "boolean" || v === null || v === void 0; } function isPlainArray(v) { return Array.isArray(v); } function isPlainObject(v) { return typeof v === "object" && v !== null && !Array.isArray(v); } function convertJsonToYjsData(v) { if (isPlainPrimitive(v)) { return v; } if (isPlainArray(v)) { const arr = new Y.Array(); applyJsonArrayToYArray(arr, v); return arr; } if (isPlainObject(v)) { if (v[frozenKey] === true) { return v; } if (v[modelTypeKey] === yjsTextModelId) { const text = new Y.Text(); const yjsTextModel = v; yjsTextModel.deltaList.forEach((frozenDeltas) => { text.applyDelta(frozenDeltas.data); }); return text; } const map = new Y.Map(); applyJsonObjectToYMap(map, v); return map; } throw new Error(`unsupported value type: ${v}`); } const applyJsonArrayToYArray = (dest, source, options = {}) => { const { mode = "add" } = options; if (mode === "merge" && isYjsContainerUpToDate(dest, source)) { return; } const srcLen = source.length; if (mode === "add") { for (let i = 0; i < srcLen; i++) { dest.push([convertJsonToYjsData(source[i])]); } return; } const destLen = dest.length; if (destLen > srcLen) { dest.delete(srcLen, destLen - srcLen); } const minLen = Math.min(destLen, srcLen); for (let i = 0; i < minLen; i++) { const srcItem = source[i]; const destItem = dest.get(i); if (isPlainObject(srcItem) && destItem instanceof Y.Map) { applyJsonObjectToYMap(destItem, srcItem, options); continue; } if (isPlainArray(srcItem) && destItem instanceof Y.Array) { applyJsonArrayToYArray(destItem, srcItem, options); continue; } if (isPlainPrimitive(srcItem) && destItem === srcItem) { continue; } dest.delete(i, 1); dest.insert(i, [convertJsonToYjsData(srcItem)]); } for (let i = destLen; i < srcLen; i++) { dest.push([convertJsonToYjsData(source[i])]); } setYjsContainerSnapshot(dest, source); }; const applyJsonObjectToYMap = (dest, source, options = {}) => { const { mode = "add" } = options; if (mode === "merge" && isYjsContainerUpToDate(dest, source)) { return; } if (mode === "add") { for (const k of Object.keys(source)) { const v = source[k]; if (v !== void 0) { dest.set(k, convertJsonToYjsData(v)); } } return; } const sourceKeysWithValues = new Set(Object.keys(source).filter((k) => source[k] !== void 0)); for (const key of dest.keys()) { if (!sourceKeysWithValues.has(key)) { dest.delete(key); } } for (const k of Object.keys(source)) { const v = source[k]; if (v === void 0) { continue; } const existing = dest.get(k); if (isPlainObject(v) && existing instanceof Y.Map) { applyJsonObjectToYMap(existing, v, options); continue; } if (isPlainArray(v) && existing instanceof Y.Array) { applyJsonArrayToYArray(existing, v, options); continue; } if (isPlainPrimitive(v) && existing === v) { continue; } dest.set(k, convertJsonToYjsData(v)); } setYjsContainerSnapshot(dest, source); }; function convertValue(v) { if (v === null || v === void 0 || typeof v !== "object") { return v; } if (Array.isArray(v) && v.length === 0) { return new Y.Array(); } return convertJsonToYjsData(v); } function applyMobxChangeToYjsObject(change, yjsObject) { if (isYjsValueDeleted(yjsObject)) { throw failure("cannot apply patch to deleted Yjs value"); } const yjsContainer = resolveYjsPath(yjsObject, change.path); if (!yjsContainer) { return; } if (yjsContainer instanceof Y.Array) { if (change.type === DeepChangeType.ArraySplice) { yjsContainer.delete(change.index, change.removedValues.length); if (change.addedValues.length > 0) { const valuesToInsert = change.addedValues.map(convertValue); yjsContainer.insert(change.index, valuesToInsert); } } else if (change.type === DeepChangeType.ArrayUpdate) { yjsContainer.delete(change.index, 1); yjsContainer.insert(change.index, [convertValue(change.newValue)]); } else { throw failure(`unsupported array change type: ${change.type}`); } } else if (yjsContainer instanceof Y.Map) { if (change.type === DeepChangeType.ObjectAdd || change.type === DeepChangeType.ObjectUpdate) { const key = change.key; if (change.newValue === void 0) { yjsContainer.delete(key); } else { yjsContainer.set(key, convertValue(change.newValue)); } } else if (change.type === DeepChangeType.ObjectRemove) { const key = change.key; yjsContainer.delete(key); } else { throw failure(`unsupported object change type: ${change.type}`); } } else if (yjsContainer instanceof Y.Text) { return; } else { throw failure(`unsupported Yjs container type: ${yjsContainer}`); } } const convertYjsDataToJson = action((yjsData) => { if (yjsData instanceof Y.Array) { return yjsData.map((v) => convertYjsDataToJson(v)); } if (yjsData instanceof Y.Map) { const obj = {}; yjsData.forEach((v, k) => { obj[k] = convertYjsDataToJson(v); }); return obj; } if (yjsData instanceof Y.Text) { const deltas = yjsData.toDelta(); return modelSnapshotOutWithMetadata(YjsTextModel, { deltaList: deltas.length > 0 ? [{ $frozen: true, data: deltas }] : [] }); } return yjsData; }); function applyYjsEventToMobx(event, boundObject, reconciliationMap) { const path = event.path; const { value: target } = resolvePath(boundObject, path); if (!target) { throw failure(`cannot resolve path ${JSON.stringify(path)}`); } runUnprotected(() => { if (event instanceof Y.YMapEvent) { applyYMapEventToMobx(event, target, reconciliationMap); } else if (event instanceof Y.YArrayEvent) { applyYArrayEventToMobx(event, target, reconciliationMap); } else if (event instanceof Y.YTextEvent) { applyYTextEventToMobx(event, target); } }); } function processDeletedValue(val, reconciliationMap) { if (val && typeof val === "object" && isModel(val)) { const sn = getSnapshot(val); const id = getSnapshotModelId(sn); if (id) { reconciliationMap.set(id, val); } } } function reviveValue(jsonValue, reconciliationMap) { if (jsonValue === null || typeof jsonValue !== "object") { return jsonValue; } if (isFrozenSnapshot(jsonValue)) { return frozen(jsonValue.data); } if (reconciliationMap && jsonValue && typeof jsonValue === "object") { const modelId = getSnapshotModelId(jsonValue); if (modelId) { const existing = reconciliationMap.get(modelId); if (existing) { reconciliationMap.delete(modelId); return existing; } } } return fromSnapshot(jsonValue); } function applyYMapEventToMobx(event, target, reconciliationMap) { const source = event.target; event.changes.keys.forEach((change, key) => { switch (change.action) { case "add": case "update": { const yjsValue = source.get(key); const jsonValue = convertYjsDataToJson(yjsValue); if (change.action === "update") { processDeletedValue(target[key], reconciliationMap); } target[key] = reviveValue(jsonValue, reconciliationMap); break; } case "delete": { processDeletedValue(target[key], reconciliationMap); if (isModel(target)) { remove(target.$, key); } else { remove(target, key); } break; } default: throw failure(`unsupported Yjs map event action: ${change.action}`); } }); } function applyYArrayEventToMobx(event, target, reconciliationMap) { let currentIndex = 0; for (const change of event.changes.delta) { if (change.retain) { currentIndex += change.retain; } if (change.delete) { const deletedItems = target.slice(currentIndex, currentIndex + change.delete); deletedItems.forEach((item) => { processDeletedValue(item, reconciliationMap); }); target.splice(currentIndex, change.delete); } if (change.insert) { const insertedItems = Array.isArray(change.insert) ? change.insert : [change.insert]; const values = insertedItems.map((yjsValue) => { const jsonValue = convertYjsDataToJson(yjsValue); return reviveValue(jsonValue, reconciliationMap); }); target.splice(currentIndex, 0, ...values); currentIndex += values.length; } } } function applyYTextEventToMobx(event, target) { if (target == null ? void 0 : target.deltaList) { target.deltaList.push(frozen(event.delta)); } } function captureChangeSnapshots(change) { if (change.type === DeepChangeType.ArraySplice && change.addedValues.length > 0) { const snapshots = change.addedValues.map((v) => isTreeNode(v) ? getSnapshot(v) : v); return { ...change, addedValues: snapshots }; } else if (change.type === DeepChangeType.ArrayUpdate) { const snapshot = isTreeNode(change.newValue) ? getSnapshot(change.newValue) : change.newValue; return { ...change, newValue: snapshot }; } else if (change.type === DeepChangeType.ObjectAdd || change.type === DeepChangeType.ObjectUpdate) { const snapshot = isTreeNode(change.newValue) ? getSnapshot(change.newValue) : change.newValue; return { ...change, newValue: snapshot }; } return change; } function bindYjsToMobxKeystone({ yjsDoc, yjsObject, mobxKeystoneType }) { const yjsOrigin = /* @__PURE__ */ Symbol("bindYjsToMobxKeystoneTransactionOrigin"); let applyingYjsChangesToMobxKeystone = 0; const bindingContext = { yjsDoc, yjsObject, mobxKeystoneType, yjsOrigin, boundObject: void 0, // not yet created get isApplyingYjsChangesToMobxKeystone() { return applyingYjsChangesToMobxKeystone > 0; } }; if (isYjsValueDeleted(yjsObject)) { throw failure("cannot apply patch to deleted Yjs value"); } const yjsJson = convertYjsDataToJson(yjsObject); let boundObject; let hasInitChanges = false; const createBoundObject = () => { const disposeGlobalListener = onGlobalDeepChange((_target, change) => { if (change.isInit) { hasInitChanges = true; } }); try { const result = yjsBindingContext.apply( () => fromSnapshot(mobxKeystoneType, yjsJson), bindingContext ); yjsBindingContext.set(result, { ...bindingContext, boundObject: result }); return result; } finally { disposeGlobalListener(); } }; boundObject = createBoundObject(); const observeDeepCb = action((events) => { const eventsToApply = []; events.forEach((event) => { var _a; if (event.transaction.origin !== yjsOrigin) { eventsToApply.push(event); } if (event.target instanceof Y.Map || event.target instanceof Y.Array) { (_a = getYjsCollectionAtom(event.target)) == null ? void 0 : _a.reportChanged(); } }); if (eventsToApply.length > 0) { applyingYjsChangesToMobxKeystone++; try { const reconciliationMap = /* @__PURE__ */ new Map(); const initChanges = []; const disposeGlobalListener = onGlobalDeepChange((target, change) => { if (change.isInit) { initChanges.push({ target, change: captureChangeSnapshots(change) }); } }); try { eventsToApply.forEach((event) => { applyYjsEventToMobx(event, boundObject, reconciliationMap); }); } finally { disposeGlobalListener(); } if (initChanges.length > 0 && !isYjsValueDeleted(yjsObject)) { yjsDoc.transact(() => { for (const { target, change } of initChanges) { const pathToTarget = getParentToChildPath(boundObject, target); if (pathToTarget !== void 0) { const changeWithCorrectPath = { ...change, path: [...pathToTarget, ...change.path] }; applyMobxChangeToYjsObject(changeWithCorrectPath, yjsObject); } } }, yjsOrigin); } if (yjsObject instanceof Y.Map || yjsObject instanceof Y.Array) { setYjsContainerSnapshot(yjsObject, getSnapshot(boundObject)); } } finally { applyingYjsChangesToMobxKeystone--; } } }); yjsObject.observeDeep(observeDeepCb); let pendingChanges = []; const disposeOnDeepChange = onDeepChange(boundObject, (change) => { if (applyingYjsChangesToMobxKeystone > 0) { return; } if (change.isInit) { return; } pendingChanges.push(captureChangeSnapshots(change)); }); const disposeOnSnapshot = onSnapshot(boundObject, (boundObjectSnapshot) => { if (pendingChanges.length === 0) { return; } const changesToApply = pendingChanges; pendingChanges = []; if (isYjsValueDeleted(yjsObject)) { return; } yjsDoc.transact(() => { changesToApply.forEach((change) => { applyMobxChangeToYjsObject(change, yjsObject); }); }, yjsOrigin); if (yjsObject instanceof Y.Map || yjsObject instanceof Y.Array) { setYjsContainerSnapshot(yjsObject, boundObjectSnapshot); } }); const finalSnapshot = getSnapshot(boundObject); if (hasInitChanges) { yjsDoc.transact(() => { if (yjsObject instanceof Y.Map) { applyJsonObjectToYMap(yjsObject, finalSnapshot, { mode: "merge" }); } else if (yjsObject instanceof Y.Array) { applyJsonArrayToYArray(yjsObject, finalSnapshot, { mode: "merge" }); } }, yjsOrigin); } if (yjsObject instanceof Y.Map || yjsObject instanceof Y.Array) { setYjsContainerSnapshot(yjsObject, finalSnapshot); } const dispose = () => { yjsDoc.off("destroy", dispose); disposeOnDeepChange(); disposeOnSnapshot(); yjsObject.unobserveDeep(observeDeepCb); }; yjsDoc.on("destroy", dispose); return { boundObject, dispose, yjsOrigin }; } export { MobxKeystoneYjsError, YjsTextModel, applyJsonArrayToYArray, applyJsonObjectToYMap, bindYjsToMobxKeystone, convertJsonToYjsData, yjsBindingContext, yjsTextModelId }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9ieC1rZXlzdG9uZS15anMuZXNtLm1qcyIsInNvdXJjZXMiOlsiLi4vc3JjL3V0aWxzL2Vycm9yLnRzIiwiLi4vc3JjL3V0aWxzL2dldE9yQ3JlYXRlWWpzQ29sbGVjdGlvbkF0b20udHMiLCIuLi9zcmMvdXRpbHMvaXNZanNWYWx1ZURlbGV0ZWQudHMiLCIuLi9zcmMvYmluZGluZy9yZXNvbHZlWWpzUGF0aC50cyIsIi4uL3NyYy9iaW5kaW5nL3lqc0JpbmRpbmdDb250ZXh0LnRzIiwiLi4vc3JjL2JpbmRpbmcvWWpzVGV4dE1vZGVsLnRzIiwiLi4vc3JjL2JpbmRpbmcveWpzU25hcHNob3RUcmFja2luZy50cyIsIi4uL3NyYy9iaW5kaW5nL2NvbnZlcnRKc29uVG9ZanNEYXRhLnRzIiwiLi4vc3JjL2JpbmRpbmcvYXBwbHlNb2J4Q2hhbmdlVG9ZanNPYmplY3QudHMiLCIuLi9zcmMvYmluZGluZy9jb252ZXJ0WWpzRGF0YVRvSnNvbi50cyIsIi4uL3NyYy9iaW5kaW5nL2FwcGx5WWpzRXZlbnRUb01vYngudHMiLCIuLi9zcmMvYmluZGluZy9iaW5kWWpzVG9Nb2J4S2V5c3RvbmUudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXHJcbiAqIEEgbW9ieC1rZXlzdG9uZS15anMgZXJyb3IuXHJcbiAqL1xyXG5leHBvcnQgY2xhc3MgTW9ieEtleXN0b25lWWpzRXJyb3IgZXh0ZW5kcyBFcnJvciB7XHJcbiAgY29uc3RydWN0b3IobXNnOiBzdHJpbmcpIHtcclxuICAgIHN1cGVyKG1zZylcclxuXHJcbiAgICAvLyBTZXQgdGhlIHByb3RvdHlwZSBleHBsaWNpdGx5LlxyXG4gICAgT2JqZWN0LnNldFByb3RvdHlwZU9mKHRoaXMsIE1vYnhLZXlzdG9uZVlqc0Vycm9yLnByb3RvdHlwZSlcclxuICB9XHJcbn1cclxuXHJcbi8qKlxyXG4gKiBAaW50ZXJuYWxcclxuICovXHJcbmV4cG9ydCBmdW5jdGlvbiBmYWlsdXJlKG1zZzogc3RyaW5nKSB7XHJcbiAgcmV0dXJuIG5ldyBNb2J4S2V5c3RvbmVZanNFcnJvcihtc2cpXHJcbn1cclxuIiwiaW1wb3J0IHsgY3JlYXRlQXRvbSwgSUF0b20gfSBmcm9tIFwibW9ieFwiXG5pbXBvcnQgKiBhcyBZIGZyb20gXCJ5anNcIlxuXG5jb25zdCB5anNDb2xsZWN0aW9uQXRvbXMgPSBuZXcgV2Vha01hcDxZLk1hcDx1bmtub3duPiB8IFkuQXJyYXk8dW5rbm93bj4sIElBdG9tPigpXG5cbi8qKlxuICogQGludGVybmFsXG4gKi9cbmV4cG9ydCBjb25zdCBnZXRZanNDb2xsZWN0aW9uQXRvbSA9IChcbiAgeWpzQ29sbGVjdGlvbjogWS5NYXA8dW5rbm93bj4gfCBZLkFycmF5PHVua25vd24+XG4pOiBJQXRvbSB8IHVuZGVmaW5lZCA9PiB7XG4gIHJldHVybiB5anNDb2xsZWN0aW9uQXRvbXMuZ2V0KHlqc0NvbGxlY3Rpb24pXG59XG5cbi8qKlxuICogQGludGVybmFsXG4gKi9cbmV4cG9ydCBjb25zdCBnZXRPckNyZWF0ZVlqc0NvbGxlY3Rpb25BdG9tID0gKFxuICB5anNDb2xsZWN0aW9uOiBZLk1hcDx1bmtub3duPiB8IFkuQXJyYXk8dW5rbm93bj5cbik6IElBdG9tID0+IHtcbiAgbGV0IGF0b20gPSB5anNDb2xsZWN0aW9uQXRvbXMuZ2V0KHlqc0NvbGxlY3Rpb24pXG4gIGlmICghYXRvbSkge1xuICAgIGF0b20gPSBjcmVhdGVBdG9tKGB5anNDb2xsZWN0aW9uQXRvbWApXG4gICAgeWpzQ29sbGVjdGlvbkF0b21zLnNldCh5anNDb2xsZWN0aW9uLCBhdG9tKVxuICB9XG4gIHJldHVybiBhdG9tXG59XG4iLCJpbXBvcnQgKiBhcyBZIGZyb20gXCJ5anNcIlxuXG4vKipcbiAqIENoZWNrcyBpZiBhIFkuanMgdmFsdWUgaGFzIGJlZW4gZGVsZXRlZCBvciBpdHMgZG9jdW1lbnQgZGVzdHJveWVkLlxuICpcbiAqIEBwYXJhbSB5anNWYWx1ZSBUaGUgWS5qcyB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIGB0cnVlYCBpZiB0aGUgdmFsdWUgaXMgZGVsZXRlZCBvciBkZXN0cm95ZWQsIGBmYWxzZWAgb3RoZXJ3aXNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaXNZanNWYWx1ZURlbGV0ZWQoeWpzVmFsdWU6IHVua25vd24pOiBib29sZWFuIHtcbiAgaWYgKHlqc1ZhbHVlIGluc3RhbmNlb2YgWS5BYnN0cmFjdFR5cGUpIHtcbiAgICByZXR1cm4gISEoeWpzVmFsdWUgYXMgYW55KS5faXRlbT8uZGVsZXRlZCB8fCAhIXlqc1ZhbHVlLmRvYz8uaXNEZXN0cm95ZWRcbiAgfVxuICByZXR1cm4gZmFsc2Vcbn1cbiIsImltcG9ydCAqIGFzIFkgZnJvbSBcInlqc1wiXG5pbXBvcnQgeyBmYWlsdXJlIH0gZnJvbSBcIi4uL3V0aWxzL2Vycm9yXCJcbmltcG9ydCB7IGdldE9yQ3JlYXRlWWpzQ29sbGVjdGlvbkF0b20gfSBmcm9tIFwiLi4vdXRpbHMvZ2V0T3JDcmVhdGVZanNDb2xsZWN0aW9uQXRvbVwiXG5cbi8qKlxuICogUmVzb2x2ZXMgYSBwYXRoIHdpdGhpbiBhIFlqcyBvYmplY3Qgc3RydWN0dXJlLlxuICogUmV0dXJucyB0aGUgWWpzIGNvbnRhaW5lciBhdCB0aGUgc3BlY2lmaWVkIHBhdGguXG4gKlxuICogV2hlbiBhIFkuVGV4dCBpcyBlbmNvdW50ZXJlZCBkdXJpbmcgcGF0aCByZXNvbHV0aW9uIChlaXRoZXIgYXQgdGhlIHN0YXJ0XG4gKiBvciBtaWQtcGF0aCksIGl0IGlzIHJldHVybmVkIGltbWVkaWF0ZWx5IHNpbmNlIFkuVGV4dCBkb2Vzbid0IHN1cHBvcnRcbiAqIG5lc3RlZCBwYXRoIHRyYXZlcnNhbC5cbiAqXG4gKiBAcGFyYW0geWpzT2JqZWN0IFRoZSByb290IFlqcyBvYmplY3RcbiAqIEBwYXJhbSBwYXRoIEFycmF5IG9mIGtleXMvaW5kaWNlcyB0byB0cmF2ZXJzZVxuICogQHJldHVybnMgVGhlIFlqcyBjb250YWluZXIgYXQgdGhlIHBhdGgsIG9yIFkuVGV4dCBpZiBlbmNvdW50ZXJlZCBkdXJpbmcgdHJhdmVyc2FsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlWWpzUGF0aChcbiAgeWpzT2JqZWN0OiBZLk1hcDx1bmtub3duPiB8IFkuQXJyYXk8dW5rbm93bj4gfCBZLlRleHQsXG4gIHBhdGg6IHJlYWRvbmx5IChzdHJpbmcgfCBudW1iZXIpW11cbik6IHVua25vd24ge1xuICBsZXQgY3VycmVudFlqc09iamVjdDogdW5rbm93biA9IHlqc09iamVjdFxuXG4gIGxldCBpID0gLTFcbiAgZm9yIChjb25zdCBwYXRoUGFydCBvZiBwYXRoKSB7XG4gICAgaSsrXG4gICAgLy8gSWYgd2UgZW5jb3VudGVyIGEgWS5UZXh0IGR1cmluZyBwYXRoIHJlc29sdXRpb24sIHJldHVybiBpdCBpbW1lZGlhdGVseS5cbiAgICAvLyBZLlRleHQgb2JqZWN0cyBkb24ndCBzdXBwb3J0IG5lc3RlZCBwYXRoIHRyYXZlcnNhbCwgYW5kIHRoZWlyIHVwZGF0ZXNcbiAgICAvLyBhcmUgaGFuZGxlZCBzZXBhcmF0ZWx5IGJ5IFlqc1RleHRNb2RlbCdzIG93biBzeW5jaHJvbml6YXRpb24gbWVjaGFuaXNtLlxuICAgIGlmIChjdXJyZW50WWpzT2JqZWN0IGluc3RhbmNlb2YgWS5UZXh0KSB7XG4gICAgICByZXR1cm4gY3VycmVudFlqc09iamVjdFxuICAgIH1cblxuICAgIGlmIChjdXJyZW50WWpzT2JqZWN0IGluc3RhbmNlb2YgWS5NYXApIHtcbiAgICAgIGdldE9yQ3JlYXRlWWpzQ29sbGVjdGlvbkF0b20oY3VycmVudFlqc09iamVjdCkucmVwb3J0T2JzZXJ2ZWQoKVxuICAgICAgY29uc3Qga2V5ID0gU3RyaW5nKHBhdGhQYXJ0KVxuICAgICAgY3VycmVudFlqc09iamVjdCA9IGN1cnJlbnRZanNPYmplY3QuZ2V0KGtleSlcbiAgICB9IGVsc2UgaWYgKGN1cnJlbnRZanNPYmplY3QgaW5zdGFuY2VvZiBZLkFycmF5KSB7XG4gICAgICBnZXRPckNyZWF0ZVlqc0NvbGxlY3Rpb25BdG9tKGN1cnJlbnRZanNPYmplY3QpLnJlcG9ydE9ic2VydmVkKClcbiAgICAgIGNvbnN0IGtleSA9IE51bWJlcihwYXRoUGFydClcbiAgICAgIGN1cnJlbnRZanNPYmplY3QgPSBjdXJyZW50WWpzT2JqZWN0LmdldChrZXkpXG4gICAgfSBlbHNlIHtcbiAgICAgIHRocm93IGZhaWx1cmUoXG4gICAgICAgIGBZLk1hcCBvciBZLkFycmF5IHdhcyBleHBlY3RlZCBhdCBwYXRoICR7SlNPTi5zdHJpbmdpZnkoXG4gICAgICAgICAgcGF0aC5zbGljZSgwLCBpKVxuICAgICAgICApfSBpbiBvcmRlciB0byByZXNvbHZlIHBhdGggJHtKU09OLnN0cmluZ2lmeShwYXRoKX0sIGJ1dCBnb3QgJHtjdXJyZW50WWpzT2JqZWN0fSBpbnN0ZWFkYFxuICAgICAgKVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiBjdXJyZW50WWpzT2JqZWN0XG59XG4iLCJpbXBvcnQgeyBBbnlUeXBlLCBjcmVhdGVDb250ZXh0IH0gZnJvbSBcIm1vYngta2V5c3RvbmVcIlxuaW1wb3J0ICogYXMgWSBmcm9tIFwieWpzXCJcblxuLyoqXG4gKiBDb250ZXh0IHdpdGggaW5mbyBvbiBob3cgYSBtb2J4LWtleXN0b25lIG1vZGVsIGlzIGJvdW5kIHRvIGEgWS5qcyBkYXRhIHN0cnVjdHVyZS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBZanNCaW5kaW5nQ29udGV4dCB7XG4gIC8qKlxuICAgKiBUaGUgWS5qcyBkb2N1bWVudC5cbiAgICovXG4gIHlqc0RvYzogWS5Eb2NcblxuICAvKipcbiAgICogVGhlIGJvdW5kIFkuanMgZGF0YSBzdHJ1Y3R1cmUuXG4gICAqL1xuICB5anNPYmplY3Q6IFkuTWFwPHVua25vd24+IHwgWS5BcnJheTx1bmtub3duPiB8IFkuVGV4dFxuXG4gIC8qKlxuICAgKiBUaGUgbW9ieC1rZXlzdG9uZSBtb2RlbCB0eXBlLlxuICAgKi9cbiAgbW9ieEtleXN0b25lVHlwZTogQW55VHlwZVxuXG4gIC8qKlxuICAgKiBUaGUgb3JpZ2luIHN5bWJvbCB1c2VkIGZvciB0cmFuc2FjdGlvbnMuXG4gICAqL1xuICB5anNPcmlnaW46IHN5bWJvbFxuXG4gIC8qKlxuICAgKiBUaGUgYm91bmQgbW9ieC1rZXlzdG9uZSBpbnN0YW5jZS5cbiAgICovXG4gIGJvdW5kT2JqZWN0OiB1bmtub3duXG5cbiAgLyoqXG4gICAqIFdoZXRoZXIgd2UgYXJlIGN1cnJlbnRseSBhcHBseWluZyBZLmpzIGNoYW5nZXMgdG8gdGhlIG1vYngta2V5c3RvbmUgbW9kZWwuXG4gICAqL1xuICBpc0FwcGx5aW5nWWpzQ2hhbmdlc1RvTW9ieEtleXN0b25lOiBib29sZWFuXG59XG5cbi8qKlxuICogQ29udGV4dCB3aXRoIGluZm8gb24gaG93IGEgbW9ieC1rZXlzdG9uZSBtb2RlbCBpcyBib3VuZCB0byBhIFkuanMgZGF0YSBzdHJ1Y3R1cmUuXG4gKi9cbmV4cG9ydCBjb25zdCB5anNCaW5kaW5nQ29udGV4dCA9IGNyZWF0ZUNvbnRleHQ8WWpzQmluZGluZ0NvbnRleHQgfCB1bmRlZmluZWQ+KHVuZGVmaW5lZClcbiIsImltcG9ydCB7IGNvbXB1dGVkLCBjcmVhdGVBdG9tLCBJQXRvbSwgb2JzZXJ2ZSwgcmVhY3Rpb24gfSBmcm9tIFwibW9ieFwiXG5pbXBvcnQge1xuICBGcm96ZW4sXG4gIGZyb3plbixcbiAgZ2V0UGFyZW50VG9DaGlsZFBhdGgsXG4gIE1vZGVsLFxuICBtb2RlbCxcbiAgb25TbmFwc2hvdCxcbiAgdFByb3AsXG4gIHR5cGVzLFxufSBmcm9tIFwibW9ieC1rZXlzdG9uZVwiXG5pbXBvcnQgKiBhcyBZIGZyb20gXCJ5anNcIlxuaW1wb3J0IHsgZmFpbHVyZSB9IGZyb20gXCIuLi91dGlscy9lcnJvclwiXG5pbXBvcnQgeyBpc1lqc1ZhbHVlRGVsZXRlZCB9IGZyb20gXCIuLi91dGlscy9pc1lqc1ZhbHVlRGVsZXRlZFwiXG5pbXBvcnQgeyByZXNvbHZlWWpzUGF0aCB9IGZyb20gXCIuL3Jlc29sdmVZanNQYXRoXCJcbmltcG9ydCB7IFlqc0JpbmRpbmdDb250ZXh0LCB5anNCaW5kaW5nQ29udGV4dCB9IGZyb20gXCIuL3lqc0JpbmRpbmdDb250ZXh0XCJcblxuLy8gRGVsdGFbXVtdLCBzaW5jZSBlYWNoIHNpbmdsZSBjaGFuZ2UgaXMgYSBEZWx0YVtdXG4vLyB3ZSB1c2UgZnJvemVuIHNvIHRoYXQgd2UgY2FuIHJldXNlIGVhY2ggZGVsdGEgY2hhbmdlXG5jb25zdCBkZWx0YUxpc3RUeXBlID0gdHlwZXMuYXJyYXkodHlwZXMuZnJvemVuKHR5cGVzLnVuY2hlY2tlZDx1bmtub3duW10+KCkpKVxuXG5leHBvcnQgY29uc3QgeWpzVGV4dE1vZGVsSWQgPSBcIm1vYngta2V5c3RvbmUteWpzL1lqc1RleHRNb2RlbFwiXG5cbi8qKlxuICogQSBtb2J4LWtleXN0b25lIG1vZGVsIHRoYXQgcmVwcmVzZW50cyBhIFlqcy5UZXh0IG9iamVjdC5cbiAqL1xuQG1vZGVsKHlqc1RleHRNb2RlbElkKVxuZXhwb3J0IGNsYXNzIFlqc1RleHRNb2RlbCBleHRlbmRzIE1vZGVsKHtcbiAgZGVsdGFMaXN0OiB0UHJvcChkZWx0YUxpc3RUeXBlLCAoKSA9PiBbXSksXG59KSB7XG4gIC8qKlxuICAgKiBIZWxwZXIgZnVuY3Rpb24gdG8gY3JlYXRlIGEgWWpzVGV4dE1vZGVsIGluc3RhbmNlIHdpdGggYSBzaW1wbGUgdGV4dC5cbiAgICovXG4gIHN0YXRpYyB3aXRoVGV4dCh0ZXh0OiBzdHJpbmcpOiBZanNUZXh0TW9kZWwge1xuICAgIHJldHVybiBuZXcgRGVjb3JhdGVkWWpzVGV4dE1vZGVsKHtcbiAgICAgIGRlbHRhTGlzdDogW1xuICAgICAgICBmcm96ZW4oW1xuICAgICAgICAgIHtcbiAgICAgICAgICAgIGluc2VydDogdGV4dCxcbiAgICAgICAgICB9LFxuICAgICAgICBdKSxcbiAgICAgIF0sXG4gICAgfSlcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgWS5qcyBwYXRoIGZyb20gdGhlIGJvdW5kIG9iamVjdCB0byB0aGUgWWpzVGV4dE1vZGVsIGluc3RhbmNlLlxuICAgKi9cbiAgQGNvbXB1dGVkXG4gIHByaXZhdGUgZ2V0IF95anNPYmplY3RQYXRoKCkge1xuICAgIGNvbnN0IGN0eCA9IHlqc0JpbmRpbmdDb250ZXh0LmdldCh0aGlzKVxuICAgIGlmIChjdHg/LmJvdW5kT2JqZWN0ID09IG51bGwpIHtcbiAgICAgIHRocm93IGZhaWx1cmUoXG4gICAgICAgIFwidGhlIFlqc1RleHRNb2RlbCBpbnN0YW5jZSBtdXN0IGJlIHBhcnQgb2YgYSBib3VuZCBvYmplY3QgYmVmb3JlIGl0IGNhbiBiZSBhY2Nlc3NlZFwiXG4gICAgICApXG4gICAgfVxuXG4gICAgY29uc3QgcGF0aCA9IGdldFBhcmVudFRvQ2hpbGRQYXRoKGN0eC5ib3VuZE9iamVjdCwgdGhpcylcbiAgICBpZiAoIXBhdGgpIHtcbiAgICAgIHRocm93IGZhaWx1cmUoXCJhIHBhdGggZnJvbSB0aGUgYm91bmQgb2JqZWN0IHRvIHRoZSBZanNUZXh0TW9kZWwgaW5zdGFuY2UgaXMgbm90IGF2YWlsYWJsZVwiKVxuICAgIH1cblxuICAgIHJldHVybiBwYXRoXG4gIH1cblxuICAvKipcbiAgICogVGhlIFlqcy5UZXh0IG9iamVjdCBwcmVzZW50IGF0IHRoaXMgbW9ieC1rZXlzdG9uZSBub2RlJ3MgcGF0aC5cbiAgICovXG4gIEBjb21wdXRlZFxuICBwcml2YXRlIGdldCBfeWpzT2JqZWN0QXRQYXRoKCk6IHVua25vd24ge1xuICAgIGNvbnN0IHBhdGggPSB0aGlzLl95anNPYmplY3RQYXRoXG5cbiAgICBjb25zdCBjdHggPSB5anNCaW5kaW5nQ29udGV4dC5nZXQodGhpcykhXG5cbiAgICByZXR1cm4gcmVzb2x2ZVlqc1BhdGgoY3R4Lnlqc09iamVjdCwgcGF0aClcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgWWpzLlRleHQgb2JqZWN0IHJlcHJlc2VudGVkIGJ5IHRoaXMgbW9ieC1rZXlzdG9uZSBub2RlLlxuICAgKi9cbiAgQGNvbXB1dGVkXG4gIGdldCB5anNUZXh0KCk6IFkuVGV4dCB7XG4gICAgY29uc3QgeWpzT2JqZWN0ID0gdGhpcy5feWpzT2JqZWN0QXRQYXRoXG5cbiAgICBpZiAoISh5anNPYmplY3QgaW5zdGFuY2VvZiBZLlRleHQpKSB7XG4gICAgICB0aHJvdyBmYWlsdXJlKGBZLlRleHQgd2FzIGV4cGVjdGVkIGF0IHBhdGggJHtKU09OLnN0cmluZ2lmeSh0aGlzLl95anNPYmplY3RQYXRoKX1gKVxuICAgIH1cblxuICAgIHJldHVybiB5anNPYmplY3RcbiAgfVxuXG4gIC8qKlxuICAgKiBBdG9tIHRoYXQgZ2V0cyBjaGFuZ2VkIHdoZW4gdGhlIGFzc29jaWF0ZWQgWS5qcyB0ZXh0IGNoYW5nZXMuXG4gICAqL1xuICB5anNUZXh0Q2hhbmdlZEF0b20gPSBjcmVhdGVBdG9tKFwieWpzVGV4dENoYW5nZWRBdG9tXCIpXG5cbiAgLyoqXG4gICAqIFRoZSB0ZXh0IHZhbHVlIG9mIHRoZSBZanMuVGV4dCBvYmplY3QuXG4gICAqIFNob3J0Y3V0IGZvciBgeWpzVGV4dC50b1N0cmluZygpYCwgYnV0IGNvbXB1dGVkLlxuICAgKi9cbiAgQGNvbXB1dGVkXG4gIGdldCB0ZXh0KCk6IHN0cmluZyB7XG4gICAgdGhpcy55anNUZXh0Q2hhbmdlZEF0b20ucmVwb3J0T2JzZXJ2ZWQoKVxuXG4gICAgY29uc3QgY3R4ID0geWpzQmluZGluZ0NvbnRleHQuZ2V0KHRoaXMpXG4gICAgaWYgKGN0eD8uYm91bmRPYmplY3QgIT0gbnVsbCkge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgeWpzVGV4dFN0cmluZyA9IHRoaXMueWpzVGV4dC50b1N0cmluZygpXG4gICAgICAgIC8vIGlmIHRoZSB5anNUZXh0IGlzIGRldGFjaGVkLCB0b1N0cmluZygpIHJldHVybnMgYW4gZW1wdHkgc3RyaW5nXG4gICAgICAgIC8vIGluIHRoYXQgY2FzZSB3ZSBzaG91bGQgdXNlIHRoZSBkZWx0YUxpc3QgYXMgYSBmYWxsYmFja1xuICAgICAgICBpZiAoeWpzVGV4dFN0cmluZyAhPT0gXCJcIiB8fCB0aGlzLmRlbHRhTGlzdC5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICByZXR1cm4geWpzVGV4dFN0cmluZ1xuICAgICAgICB9XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gZmFsbCBiYWNrXG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gZmFsbCBiYWNrIHRvIGRlbHRhTGlzdFxuICAgIHJldHVybiB0aGlzLmRlbHRhTGlzdFRvVGV4dCgpXG4gIH1cblxuICBwcml2YXRlIGRlbHRhTGlzdFRvVGV4dCgpOiBzdHJpbmcge1xuICAgIGNvbnN0IGRvYyA9IG5ldyBZLkRvYygpXG4gICAgY29uc3QgdGV4dCA9IGRvYy5nZXRUZXh0KClcbiAgICB0aGlzLmRlbHRhTGlzdC5mb3JFYWNoKChkKSA9PiB7XG4gICAgICB0ZXh0LmFwcGx5RGVsdGEoZC5kYXRhKVxuICAgIH0pXG4gICAgcmV0dXJuIHRleHQudG9TdHJpbmcoKVxuICB9XG5cbiAgcHJvdGVjdGVkIG9uSW5pdCgpIHtcbiAgICBjb25zdCBzaG91bGRSZXBsaWNhdGVUb1lqcyA9IChjdHg6IFlqc0JpbmRpbmdDb250ZXh0IHwgdW5kZWZpbmVkKTogY3R4IGlzIFlqc0JpbmRpbmdDb250ZXh0ID0+IHtcbiAgICAgIHJldHVybiAhIWN0eCAmJiAhIWN0eC5ib3VuZE9iamVjdCAmJiAhY3R4LmlzQXBwbHlpbmdZanNDaGFuZ2VzVG9Nb2J4S2V5c3RvbmVcbiAgICB9XG5cbiAgICBsZXQgcmVhcHBseURlbHRhc1RvWWpzVGV4dCA9IGZhbHNlXG4gICAgY29uc3QgbmV3RGVsdGFzOiBGcm96ZW48dW5rbm93bltdPltdID0gW11cblxuICAgIGxldCBkaXNwb3NlT2JzZXJ2ZURlbHRhTGlzdDogKCgpID0+IHZvaWQpIHwgdW5kZWZpbmVkXG5cbiAgICBjb25zdCBkaXNwb3NlUmVhY3Rpb25Ub0RlbHRhTGlzdFJlZkNoYW5nZSA9IHJlYWN0aW9uKFxuICAgICAgKCkgPT4gdGhpcy4kLmRlbHRhTGlzdCxcbiAgICAgIChkZWx0YUxpc3QpID0+IHtcbiAgICAgICAgZGlzcG9zZU9ic2VydmVEZWx0YUxpc3Q/LigpXG4gICAgICAgIGRpc3Bvc2VPYnNlcnZlRGVsdGFMaXN0ID0gdW5kZWZpbmVkXG5cbiAgICAgICAgZGlzcG9zZU9ic2VydmVEZWx0YUxpc3QgPSBvYnNlcnZlKGRlbHRhTGlzdCwgKGNoYW5nZSkgPT4ge1xuICAgICAgICAgIGlmIChyZWFwcGx5RGVsdGFzVG9ZanNUZXh0KSB7XG4gICAgICAgICAgICAvLyBhbHJlYWR5IGdvbm5hIHJlcGxhY2UgdGhlbSBhbGxcbiAgICAgICAgICAgIHJldHVyblxuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAoIXNob3VsZFJlcGxpY2F0ZVRvWWpzKHlqc0JpbmRpbmdDb250ZXh0LmdldCh0aGlzKSkpIHtcbiAgICAgICAgICAgIC8vIHlqcyB0ZXh0IGlzIGFscmVhZHkgdXAgdG8gZGF0ZSB3aXRoIHRoZXNlIGNoYW5nZXNcbiAgICAgICAgICAgIHJldHVyblxuICAgICAgICAgIH1cblxuICAgICAgICAgIGlmIChcbiAgICAgICAgICAgIGNoYW5nZS50eXBlID09PSBcInNwbGljZVwiICYmXG4gICAgICAgICAgICBjaGFuZ2UucmVtb3ZlZENvdW50ID09PSAwICYmXG4gICAgICAgICAgICBjaGFuZ2UuYWRkZWRDb3VudCA+IDAgJiZcbiAgICAgICAgICAgIGNoYW5nZS5pbmRleCA9PT0gdGhpcy5kZWx0YUxpc3QubGVuZ3RoXG4gICAgICAgICAgKSB7XG4gICAgICAgICAgICAvLyBvcHRpbWl6YXRpb24sIGp1c3QgYWRkaW5nIG5ldyBvbmVzIHRvIHRoZSBlbmRcbiAgICAgICAgICAgIG5ld0RlbHRhcy5wdXNoKC4uLmNoYW5nZS5hZGRlZClcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gYW55IG90aGVyIGNoYW5nZSwgd2UgbmVlZCB0byByZWFwcGx5IGFsbCBkZWx0YXNcbiAgICAgICAgICAgIHJlYXBwbHlEZWx0YXNUb1lqc1RleHQgPSB0cnVlXG4gICAgICAgICAgfVxuICAgICAgICB9KVxuICAgICAgfSxcbiAgICAgIHsgZmlyZUltbWVkaWF0ZWx5OiB0cnVlIH1cbiAgICApXG5cbiAgICBjb25zdCBkaXNwb3NlT25TbmFwc2hvdCA9IG9uU25hcHNob3QodGhpcywgKCkgPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgaWYgKHJlYXBwbHlEZWx0YXNUb1lqc1RleHQpIHtcbiAgICAgICAgICBjb25zdCBjdHggPSB5anNCaW5kaW5nQ29udGV4dC5nZXQodGhpcylcblxuICAgICAgICAgIGlmIChzaG91bGRSZXBsaWNhdGVUb1lqcyhjdHgpKSB7XG4gICAgICAgICAgICBjb25zdCB7IHlqc1RleHQgfSA9IHRoaXNcbiAgICAgICAgICAgIGlmIChpc1lqc1ZhbHVlRGVsZXRlZCh5anNUZXh0KSkge1xuICAgICAgICAgICAgICB0aHJvdyBmYWlsdXJlKFwiY2Fubm90IHJlYXBwbHkgZGVsdGFzIHRvIGRlbGV0ZWQgWWpzLlRleHRcIilcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY3R4Lnlqc0RvYy50cmFuc2FjdCgoKSA9PiB7XG4gICAgICAgICAgICAgIC8vIGRpZG4ndCBmaW5kIGEgYmV0dGVyIHdheSB0aGFuIHRoaXMgdG8gcmVhcHBseSBhbGwgZGVsdGFzXG4gICAgICAgICAgICAgIC8vIHdpdGhvdXQgaGF2aW5nIHRvIHJlLWNyZWF0ZSB0aGUgWS5UZXh0IG9iamVjdFxuICAgICAgICAgICAgICBpZiAoeWpzVGV4dC5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICAgICAgeWpzVGV4dC5kZWxldGUoMCwgeWpzVGV4dC5sZW5ndGgpXG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICB0aGlzLmRlbHRhTGlzdC5mb3JFYWNoKChmcm96ZW5EZWx0YXMpID0+IHtcbiAgICAgICAgICAgICAgICB5anNUZXh0LmFwcGx5RGVsdGEoZnJvemVuRGVsdGFzLmRhdGEpXG4gICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICB9LCBjdHgueWpzT3JpZ2luKVxuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIGlmIChuZXdEZWx0YXMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIGNvbnN0IGN0eCA9IHlqc0JpbmRpbmdDb250ZXh0LmdldCh0aGlzKVxuXG4gICAgICAgICAgaWYgKHNob3VsZFJlcGxpY2F0ZVRvWWpzKGN0eCkpIHtcbiAgICAgICAgICAgIGNvbnN0IHsgeWpzVGV4dCB9ID0gdGhpc1xuICAgICAgICAgICAgaWYgKGlzWWpzVmFsdWVEZWxldGVkKHlqc1RleHQpKSB7XG4gICAgICAgICAgICAgIHRocm93IGZhaWx1cmUoXCJjYW5ub3QgcmVhcHBseSBkZWx0YXMgdG8gZGVsZXRlZCBZanMuVGV4dFwiKVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjdHgueWpzRG9jLnRyYW5zYWN0KCgpID0+IHtcbiAgICAgICAgICAgICAgbmV3RGVsdGFzLmZvckVhY2goKGZyb3plbkRlbHRhcykgPT4ge1xuICAgICAgICAgICAgICAgIHlqc1RleHQuYXBwbHlEZWx0YShmcm96ZW5EZWx0YXMuZGF0YSlcbiAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIH0sIGN0eC55anNPcmlnaW4pXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGZpbmFsbHkge1xuICAgICAgICByZWFwcGx5RGVsdGFzVG9ZanNUZXh0ID0gZmFsc2VcbiAgICAgICAgbmV3RGVsdGFzLmxlbmd0aCA9IDBcbiAgICAgIH1cbiAgICB9KVxuXG4gICAgY29uc3QgZGlwb3NlWWpzVGV4dENoYW5nZWRBdG9tID0gaG9va1lqc1RleHRDaGFuZ2VkQXRvbShcbiAgICAgICgpID0+IHRoaXMueWpzVGV4dCxcbiAgICAgIHRoaXMueWpzVGV4dENoYW5nZWRBdG9tXG4gICAgKVxuXG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgIGRpc3Bvc2VPblNuYXBzaG90KClcbiAgICAgIGRpc3Bvc2VSZWFjdGlvblRvRGVsdGFMaXN0UmVmQ2hhbmdlKClcbiAgICAgIGRpc3Bvc2VPYnNlcnZlRGVsdGFMaXN0Py4oKVxuICAgICAgZGlzcG9zZU9ic2VydmVEZWx0YUxpc3QgPSB1bmRlZmluZWRcblxuICAgICAgZGlwb3NlWWpzVGV4dENoYW5nZWRBdG9tKClcbiAgICB9XG4gIH1cbn1cblxuLy8gd2UgdXNlIHRoaXMgdHJpY2sganVzdCB0byBhdm9pZCBhIGJhYmVsIGJ1ZyB0aGF0IGNhdXNlcyBjbGFzc2VzIHVzZWQgaW5zaWRlIGNsYXNzZXMgbm90IHRvIGJlIG92ZXJyaWRlblxuLy8gYnkgdGhlIGRlY29yYXRvclxuY29uc3QgRGVjb3JhdGVkWWpzVGV4dE1vZGVsID0gWWpzVGV4dE1vZGVsXG5cbmZ1bmN0aW9uIGhvb2tZanNUZXh0Q2hhbmdlZEF0b20oZ2V0WWpzVGV4dDogKCkgPT4gWS5UZXh0LCB0ZXh0Q2hhbmdlZEF0b206IElBdG9tKSB7XG4gIGxldCBkaXNwb3NlT2JzZXJ2ZVlqc1RleHQ6ICgoKSA9PiB2b2lkKSB8IHVuZGVmaW5lZFxuXG4gIGNvbnN0IG9ic2VydmVGbiA9ICgpID0+IHtcbiAgICB0ZXh0Q2hhbmdlZEF0b20ucmVwb3J0Q2hhbmdlZCgpXG4gIH1cblxuICBjb25zdCBkaXNwb3NlUmVhY3Rpb25Ub1lUZXh0Q2hhbmdlID0gcmVhY3Rpb24oXG4gICAgKCkgPT4ge1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgeWpzVGV4dCA9IGdldFlqc1RleHQoKVxuICAgICAgICByZXR1cm4gaXNZanNWYWx1ZURlbGV0ZWQoeWpzVGV4dCkgPyB1bmRlZmluZWQgOiB5anNUZXh0XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgcmV0dXJuIHVuZGVmaW5lZFxuICAgICAgfVxuICAgIH0sXG4gICAgKHlqc1RleHQpID0+IHtcbiAgICAgIGRpc3Bvc2VPYnNlcnZlWWpzVGV4dD8uKClcbiAgICAgIGRpc3Bvc2VPYnNlcnZlWWpzVGV4dCA9IHVuZGVmaW5lZFxuXG4gICAgICBpZiAoeWpzVGV4dCkge1xuICAgICAgICB5anNUZXh0Lm9ic2VydmUob2JzZXJ2ZUZuKVxuXG4gICAgICAgIGRpc3Bvc2VPYnNlcnZlWWpzVGV4dCA9ICgpID0+IHtcbiAgICAgICAgICB5anNUZXh0LnVub2JzZXJ2ZShvYnNlcnZlRm4pXG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgdGV4dENoYW5nZWRBdG9tLnJlcG9ydENoYW5nZWQoKVxuICAgIH0sXG4gICAge1xuICAgICAgZmlyZUltbWVkaWF0ZWx5OiB0cnVlLFxuICAgIH1cbiAgKVxuXG4gIHJldHVybiAoKSA9PiB7XG4gICAgZGlzcG9zZVJlYWN0aW9uVG9ZVGV4dENoYW5nZSgpXG4gICAgZGlzcG9zZU9ic2VydmVZanNUZXh0Py4oKVxuICAgIGRpc3Bvc2VPYnNlcnZlWWpzVGV4dCA9IHVuZGVmaW5lZFxuICB9XG59XG4iLCJpbXBvcnQgKiBhcyBZIGZyb20gXCJ5anNcIlxuXG4vKipcbiAqIFdlYWtNYXAgdGhhdCB0cmFja3Mgd2hpY2ggc25hcHNob3QgZWFjaCBZLmpzIGNvbnRhaW5lciB3YXMgbGFzdCBzeW5jZWQgZnJvbS5cbiAqIFRoaXMgaXMgdXNlZCBkdXJpbmcgcmVjb25jaWxpYXRpb24gdG8gc2tpcCBjb250YWluZXJzIHRoYXQgYXJlIGFscmVhZHkgdXAtdG8tZGF0ZS5cbiAqXG4gKiBUaGUga2V5IGlzIHRoZSBZLmpzIGNvbnRhaW5lciAoWS5NYXAgb3IgWS5BcnJheSkuXG4gKiBUaGUgdmFsdWUgaXMgdGhlIHNuYXBzaG90IChwbGFpbiBvYmplY3Qgb3IgYXJyYXkpIHRoYXQgd2FzIGxhc3Qgc3luY2VkIHRvIGl0LlxuICovXG5leHBvcnQgY29uc3QgeWpzQ29udGFpbmVyVG9TbmFwc2hvdCA9IG5ldyBXZWFrTWFwPFkuTWFwPGFueT4gfCBZLkFycmF5PGFueT4sIHVua25vd24+KClcblxuLyoqXG4gKiBVcGRhdGVzIHRoZSBzbmFwc2hvdCB0cmFja2luZyBmb3IgYSBZLmpzIGNvbnRhaW5lci5cbiAqIENhbGwgdGhpcyBhZnRlciBzeW5jaW5nIGEgc25hcHNob3QgdG8gYSBZLmpzIGNvbnRhaW5lci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNldFlqc0NvbnRhaW5lclNuYXBzaG90KFxuICBjb250YWluZXI6IFkuTWFwPGFueT4gfCBZLkFycmF5PGFueT4sXG4gIHNuYXBzaG90OiB1bmtub3duXG4pOiB2b2lkIHtcbiAgeWpzQ29udGFpbmVyVG9TbmFwc2hvdC5zZXQoY29udGFpbmVyLCBzbmFwc2hvdClcbn1cblxuLyoqXG4gKiBHZXRzIHRoZSBsYXN0IHN5bmNlZCBzbmFwc2hvdCBmb3IgYSBZLmpzIGNvbnRhaW5lci5cbiAqIFJldHVybnMgdW5kZWZpbmVkIGlmIHRoZSBjb250YWluZXIgaGFzIG5ldmVyIGJlZW4gc3luY2VkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0WWpzQ29udGFpbmVyU25hcHNob3QoY29udGFpbmVyOiBZLk1hcDxhbnk+IHwgWS5BcnJheTxhbnk+KTogdW5rbm93biB7XG4gIHJldHVybiB5anNDb250YWluZXJUb1NuYXBzaG90LmdldChjb250YWluZXIpXG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGEgWS5qcyBjb250YWluZXIgaXMgdXAtdG8tZGF0ZSB3aXRoIHRoZSBnaXZlbiBzbmFwc2hvdC5cbiAqIFVzZXMgcmVmZXJlbmNlIGVxdWFsaXR5IHRvIGNoZWNrIGlmIHRoZSBzbmFwc2hvdCBpcyB0aGUgc2FtZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzWWpzQ29udGFpbmVyVXBUb0RhdGUoXG4gIGNvbnRhaW5lcjogWS5NYXA8YW55PiB8IFkuQXJyYXk8YW55PixcbiAgc25hcHNob3Q6IHVua25vd25cbik6IGJvb2xlYW4ge1xuICByZXR1cm4geWpzQ29udGFpbmVyVG9TbmFwc2hvdC5nZXQoY29udGFpbmVyKSA9PT0gc25hcHNob3Rcbn1cbiIsImltcG9ydCB7IGZyb3plbktleSwgbW9kZWxUeXBlS2V5LCBTbmFwc2hvdE91dE9mIH0gZnJvbSBcIm1vYngta2V5c3RvbmVcIlxuaW1wb3J0ICogYXMgWSBmcm9tIFwieWpzXCJcbmltcG9ydCB7IFBsYWluQXJyYXksIFBsYWluT2JqZWN0LCBQbGFpblByaW1pdGl2ZSwgUGxhaW5WYWx1ZSB9IGZyb20gXCIuLi9wbGFpblR5cGVzXCJcbmltcG9ydCB7IFlqc0RhdGEgfSBmcm9tIFwiLi9jb252ZXJ0WWpzRGF0YVRvSnNvblwiXG5pbXBvcnQgeyBZanNUZXh0TW9kZWwsIHlqc1RleHRNb2RlbElkIH0gZnJvbSBcIi4vWWpzVGV4dE1vZGVsXCJcbmltcG9ydCB7IGlzWWpzQ29udGFpbmVyVXBUb0RhdGUsIHNldFlqc0NvbnRhaW5lclNuYXBzaG90IH0gZnJvbSBcIi4veWpzU25hcHNob3RUcmFja2luZ1wiXG5cbi8qKlxuICogT3B0aW9ucyBmb3IgYXBwbHlpbmcgSlNPTiBkYXRhIHRvIFkuanMgZGF0YSBzdHJ1Y3R1cmVzLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEFwcGx5SnNvblRvWWpzT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBUaGUgbW9kZSB0byB1c2Ugd2hlbiBhcHBseWluZyBKU09OIGRhdGEgdG8gWS5qcyBkYXRhIHN0cnVjdHVyZXMuXG4gICAqIC0gYGFkZGA6IENyZWF0ZXMgbmV3IFkuanMgY29udGFpbmVycyBmb3Igb2JqZWN0cy9hcnJheXMgKGRlZmF1bHQsIGJhY2t3YXJkcyBjb21wYXRpYmxlKVxuICAgKiAtIGBtZXJnZWA6IFJlY3Vyc2l2ZWx5IG1lcmdlcyB2YWx1ZXMsIHByZXNlcnZpbmcgZXhpc3RpbmcgY29udGFpbmVyIHJlZmVyZW5jZXMgd2hlcmUgcG9zc2libGVcbiAgICovXG4gIG1vZGU/OiBcImFkZFwiIHwgXCJtZXJnZVwiXG59XG5cbmZ1bmN0aW9uIGlzUGxhaW5QcmltaXRpdmUodjogUGxhaW5WYWx1ZSk6IHYgaXMgUGxhaW5QcmltaXRpdmUge1xuICBjb25zdCB0ID0gdHlwZW9mIHZcbiAgcmV0dXJuIHQgPT09IFwic3RyaW5nXCIgfHwgdCA9PT0gXCJudW1iZXJcIiB8fCB0ID09PSBcImJvb2xlYW5cIiB8fCB2ID09PSBudWxsIHx8IHYgPT09IHVuZGVmaW5lZFxufVxuXG5mdW5jdGlvbiBpc1BsYWluQXJyYXkodjogUGxhaW5WYWx1ZSk6IHYgaXMgUGxhaW5BcnJheSB7XG4gIHJldHVybiBBcnJheS5pc0FycmF5KHYpXG59XG5cbmZ1bmN0aW9uIGlzUGxhaW5PYmplY3QodjogUGxhaW5WYWx1ZSk6IHYgaXMgUGxhaW5PYmplY3Qge1xuICByZXR1cm4gdHlwZW9mIHYgPT09IFwib2JqZWN0XCIgJiYgdiAhPT0gbnVsbCAmJiAhQXJyYXkuaXNBcnJheSh2KVxufVxuXG4vKipcbiAqIENvbnZlcnRzIGEgcGxhaW4gdmFsdWUgdG8gYSBZLmpzIGRhdGEgc3RydWN0dXJlLlxuICogT2JqZWN0cyBhcmUgY29udmVydGVkIHRvIFkuTWFwcywgYXJyYXlzIHRvIFkuQXJyYXlzLCBwcmltaXRpdmVzIGFyZSB1bnRvdWNoZWQuXG4gKiBGcm96ZW4gdmFsdWVzIGFyZSBhIHNwZWNpYWwgY2FzZSBhbmQgdGhleSBhcmUga2VwdCBhcyBpbW11dGFibGUgcGxhaW4gdmFsdWVzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY29udmVydEpzb25Ub1lqc0RhdGEodjogUGxhaW5WYWx1ZSk6IFlqc0RhdGEge1xuICBpZiAoaXNQbGFpblByaW1pdGl2ZSh2KSkge1xuICAgIHJldHVybiB2XG4gIH1cblxuICBpZiAoaXNQbGFpbkFycmF5KHYpKSB7XG4gICAgY29uc3QgYXJyID0gbmV3IFkuQXJyYXkoKVxuICAgIGFwcGx5SnNvbkFycmF5VG9ZQXJyYXkoYXJyLCB2KVxuICAgIHJldHVybiBhcnJcbiAgfVxuXG4gIGlmIChpc1BsYWluT2JqZWN0KHYpKSB7XG4gICAgaWYgKHZbZnJvemVuS2V5XSA9PT0gdHJ1ZSkge1xuICAgICAgLy8gZnJvemVuIHZhbHVlLCBzYXZlIGFzIGltbXV0YWJsZSBvYmplY3RcbiAgICAgIHJldHVybiB2XG4gICAgfVxuXG4gICAgaWYgKHZbbW9kZWxUeXBlS2V5XSA9PT0geWpzVGV4dE1vZGVsSWQpIHtcbiAgICAgIGNvbnN0IHRleHQgPSBuZXcgWS5UZXh0KClcbiAgICAgIGNvbnN0IHlqc1RleHRNb2RlbCA9IHYgYXMgdW5rbm93biBhcyBTbmFwc2hvdE91dE9mPFlqc1RleHRNb2RlbD5cbiAgICAgIHlqc1RleHRNb2RlbC5kZWx0YUxpc3QuZm9yRWFjaCgoZnJvemVuRGVsdGFzKSA9PiB7XG4gICAgICAgIHRleHQuYXBwbHlEZWx0YShmcm96ZW5EZWx0YXMuZGF0YSlcbiAgICAgIH0pXG4gICAgICByZXR1cm4gdGV4dFxuICAgIH1cblxuICAgIGNvbnN0IG1hcCA9IG5ldyBZLk1hcCgpXG4gICAgYXBwbHlKc29uT2JqZWN0VG9ZTWFwKG1hcCwgdilcbiAgICByZXR1cm4gbWFwXG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoYHVuc3VwcG9ydGVkIHZhbHVlIHR5cGU6ICR7dn1gKVxufVxuXG4vKipcbiAqIEFwcGxpZXMgYSBKU09OIGFycmF5IHRvIGEgWS5BcnJheSwgdXNpbmcgdGhlIGNvbnZlcnRKc29uVG9ZanNEYXRhIHRvIGNvbnZlcnQgdGhlIHZhbHVlcy5cbiAqXG4gKiBAcGFyYW0gZGVzdCBUaGUgZGVzdGluYXRpb24gWS5BcnJheS5cbiAqIEBwYXJhbSBzb3VyY2UgVGhlIHNvdXJjZSBKU09OIGFycmF5LlxuICogQHBhcmFtIG9wdGlvbnMgT3B0aW9ucyBmb3IgYXBwbHlpbmcgdGhlIEpTT04gZGF0YS5cbiAqL1xuZXhwb3J0IGNvbnN0IGFwcGx5SnNvbkFycmF5VG9ZQXJyYXkgPSAoXG4gIGRlc3Q6IFkuQXJyYXk8YW55PixcbiAgc291cmNlOiBQbGFpbkFycmF5LFxuICBvcHRpb25zOiBBcHBseUpzb25Ub1lqc09wdGlvbnMgPSB7fVxuKSA9PiB7XG4gIGNvbnN0IHsgbW9kZSA9IFwiYWRkXCIgfSA9IG9wdGlvbnNcblxuICAvLyBJbiBtZXJnZSBtb2RlLCBjaGVjayBpZiB0aGUgY29udGFpbmVyIGlzIGFscmVhZHkgdXAtdG8tZGF0ZSB3aXRoIHRoaXMgc25hcHNob3RcbiAgaWYgKG1vZGUgPT09IFwibWVyZ2VcIiAmJiBpc1lqc0NvbnRhaW5lclVwVG9EYXRlKGRlc3QsIHNvdXJjZSkpIHtcbiAgICByZXR1cm5cbiAgfVxuXG4gIGNvbnN0IHNyY0xlbiA9IHNvdXJjZS5sZW5ndGhcblxuICBpZiAobW9kZSA9PT0gXCJhZGRcIikge1xuICAgIC8vIEFkZCBtb2RlOiBqdXN0IHB1c2ggYWxsIGl0ZW1zIHRvIHRoZSBlbmRcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNyY0xlbjsgaSsrKSB7XG4gICAgICBkZXN0LnB1c2goW2NvbnZlcnRKc29uVG9ZanNEYXRhKHNvdXJjZVtpXSldKVxuICAgIH1cbiAgICByZXR1cm5cbiAgfVxuXG4gIC8vIE1lcmdlIG1vZGU6IHJlY3Vyc2l2ZWx5IG1lcmdlIHZhbHVlcywgcHJlc2VydmluZyBleGlzdGluZyBjb250YWluZXIgcmVmZXJlbmNlc1xuICBjb25zdCBkZXN0TGVuID0gZGVzdC5sZW5ndGhcblxuICAvLyBSZW1vdmUgZXh0cmEgaXRlbXMgZnJvbSB0aGUgZW5kXG4gIGlmIChkZXN0TGVuID4gc3JjTGVuKSB7XG4gICAgZGVzdC5kZWxldGUoc3JjTGVuLCBkZXN0TGVuIC0gc3JjTGVuKVxuICB9XG5cbiAgLy8gVXBkYXRlIGV4aXN0aW5nIGl0ZW1zXG4gIGNvbnN0IG1pbkxlbiA9IE1hdGgubWluKGRlc3RMZW4sIHNyY0xlbilcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBtaW5MZW47IGkrKykge1xuICAgIGNvbnN0IHNyY0l0ZW0gPSBzb3VyY2VbaV1cbiAgICBjb25zdCBkZXN0SXRlbSA9IGRlc3QuZ2V0KGkpXG5cbiAgICAvLyBJZiBib3RoIGFyZSBvYmplY3RzLCBtZXJnZSByZWN1cnNpdmVseVxuICAgIGlmIChpc1BsYWluT2JqZWN0KHNyY0l0ZW0pICYmIGRlc3RJdGVtIGluc3RhbmNlb2YgWS5NYXApIHtcbiAgICAgIGFwcGx5SnNvbk9iamVjdFRvWU1hcChkZXN0SXRlbSwgc3JjSXRlbSwgb3B0aW9ucylcbiAgICAgIGNvbnRpbnVlXG4gICAgfVxuXG4gICAgLy8gSWYgYm90aCBhcmUgYXJyYXlzLCBtZXJnZSByZWN1cnNpdmVseVxuICAgIGlmIChpc1BsYWluQXJyYXkoc3JjSXRlbSkgJiYgZGVzdEl0ZW0gaW5zdGFuY2VvZiBZLkFycmF5KSB7XG4gICAgICBhcHBseUpzb25BcnJheVRvWUFycmF5KGRlc3RJdGVtLCBzcmNJdGVtLCBvcHRpb25zKVxuICAgICAgY29udGludWVcbiAgICB9XG5cbiAgICAvLyBTa2lwIGlmIHByaW1pdGl2ZSB2YWx1ZSBpcyB1bmNoYW5nZWQgKG9wdGltaXphdGlvbilcbiAgICBpZiAoaXNQbGFpblByaW1pdGl2ZShzcmNJdGVtKSAmJiBkZXN0SXRlbSA9PT0gc3JjSXRlbSkge1xuICAgICAgY29udGludWVcbiAgICB9XG5cbiAgICAvLyBPdGhlcndpc2UsIHJlcGxhY2UgdGhlIGl0ZW1cbiAgICBkZXN0LmRlbGV0ZShpLCAxKVxuICAgIGRlc3QuaW5zZXJ0KGksIFtjb252ZXJ0SnNvblRvWWpzRGF0YShzcmNJdGVtKV0pXG4gIH1cblxuICAvLyBBZGQgbmV3IGl0ZW1zIGF0IHRoZSBlbmRcbiAgZm9yIChsZXQgaSA9IGRlc3RMZW47IGkgPCBzcmNMZW47IGkrKykge1xuICAgIGRlc3QucHVzaChbY29udmVydEpzb25Ub1lqc0RhdGEoc291cmNlW2ldKV0pXG4gIH1cblxuICAvLyBVcGRhdGUgc25hcHNob3QgdHJhY2tpbmcgYWZ0ZXIgc3VjY2Vzc2Z1bCBtZXJnZVxuICBzZXRZanNDb250YWluZXJTbmFwc2hvdChkZXN0LCBzb3VyY2UpXG59XG5cbi8qKlxuICogQXBwbGllcyBhIEpTT04gb2JqZWN0IHRvIGEgWS5NYXAsIHVzaW5nIHRoZSBjb252ZXJ0SnNvblRvWWpzRGF0YSB0byBjb252ZXJ0IHRoZSB2YWx1ZXMuXG4gKlxuICogQHBhcmFtIGRlc3QgVGhlIGRlc3RpbmF0aW9uIFkuTWFwLlxuICogQHBhcmFtIHNvdXJjZSBUaGUgc291cmNlIEpTT04gb2JqZWN0LlxuICogQHBhcmFtIG9wdGlvbnMgT3B0aW9ucyBmb3IgYXBwbHlpbmcgdGhlIEpTT04gZGF0YS5cbiAqL1xuZXhwb3J0IGNvbnN0IGFwcGx5SnNvbk9iamVjdFRvWU1hcCA9IChcbiAgZGVzdDogWS5NYXA8YW55PixcbiAgc291cmNlOiBQbGFpbk9iamVjdCxcbiAgb3B0aW9uczogQXBwbHlKc29uVG9ZanNPcHRpb25zID0ge31cbikgPT4ge1xuICBjb25zdCB7IG1vZGUgPSBcImFkZFwiIH0gPSBvcHRpb25zXG5cbiAgLy8gSW4gbWVyZ2UgbW9kZSwgY2hlY2sgaWYgdGhlIGNvbnRhaW5lciBpcyBhbHJlYWR5IHVwLXRvLWRhdGUgd2l0aCB0aGlzIHNuYXBzaG90XG4gIGlmIChtb2RlID09PSBcIm1lcmdlXCIgJiYgaXNZanNDb250YWluZXJVcFRvRGF0ZShkZXN0LCBzb3VyY2UpKSB7XG4gICAgcmV0dXJuXG4gIH1cblxuICBpZiAobW9kZSA9PT0gXCJhZGRcIikge1xuICAgIC8vIEFkZCBtb2RlOiBqdXN0IHNldCBhbGwgdmFsdWVzXG4gICAgZm9yIChjb25zdCBrIG9mIE9iamVjdC5rZXlzKHNvdXJjZSkpIHtcbiAgICAgIGNvbnN0IHYgPSBzb3VyY2Vba11cbiAgICAgIGlmICh2ICE9PSB1bmRlZmluZWQpIHtc