matrix-react-sdk
Version:
SDK for matrix.org using React
512 lines (499 loc) • 78 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.OwnBeaconStoreEvent = exports.OwnBeaconStore = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _lodash = require("lodash");
var _matrix = require("matrix-js-sdk/src/matrix");
var _types = require("matrix-js-sdk/src/types");
var _logger = require("matrix-js-sdk/src/logger");
var _dispatcher = _interopRequireDefault(require("../dispatcher/dispatcher"));
var _AsyncStoreWithClient = require("./AsyncStoreWithClient");
var _arrays = require("../utils/arrays");
var _beacon = require("../utils/beacon");
var _localRoom = require("../utils/local-room");
var _SettingsStore = _interopRequireDefault(require("../settings/SettingsStore"));
var _OwnBeaconStore;
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
const isOwnBeacon = (beacon, userId) => beacon.beaconInfoOwner === userId;
let OwnBeaconStoreEvent = exports.OwnBeaconStoreEvent = /*#__PURE__*/function (OwnBeaconStoreEvent) {
OwnBeaconStoreEvent["LivenessChange"] = "OwnBeaconStore.LivenessChange";
OwnBeaconStoreEvent["MonitoringLivePosition"] = "OwnBeaconStore.MonitoringLivePosition";
OwnBeaconStoreEvent["LocationPublishError"] = "LocationPublishError";
OwnBeaconStoreEvent["BeaconUpdateError"] = "BeaconUpdateError";
return OwnBeaconStoreEvent;
}({});
const MOVING_UPDATE_INTERVAL = 5000;
const STATIC_UPDATE_INTERVAL = 30000;
const BAIL_AFTER_CONSECUTIVE_ERROR_COUNT = 2;
const CREATED_BEACONS_KEY = "mx_live_beacon_created_id";
const removeLocallyCreateBeaconEventId = eventId => {
const ids = getLocallyCreatedBeaconEventIds();
window.localStorage.setItem(CREATED_BEACONS_KEY, JSON.stringify(ids.filter(id => id !== eventId)));
};
const storeLocallyCreateBeaconEventId = eventId => {
const ids = getLocallyCreatedBeaconEventIds();
window.localStorage.setItem(CREATED_BEACONS_KEY, JSON.stringify([...ids, eventId]));
};
const getLocallyCreatedBeaconEventIds = () => {
let ids;
try {
ids = JSON.parse(window.localStorage.getItem(CREATED_BEACONS_KEY) ?? "[]");
if (!Array.isArray(ids)) {
throw new Error("Invalid stored value");
}
} catch (error) {
_logger.logger.error("Failed to retrieve locally created beacon event ids", error);
ids = [];
}
return ids;
};
class OwnBeaconStore extends _AsyncStoreWithClient.AsyncStoreWithClient {
constructor() {
super(_dispatcher.default);
// users beacons, keyed by event type
(0, _defineProperty2.default)(this, "beacons", new Map());
(0, _defineProperty2.default)(this, "beaconsByRoomId", new Map());
/**
* Track over the wire errors for published positions
* Counts consecutive wire errors per beacon
* Reset on successful publish of location
*/
(0, _defineProperty2.default)(this, "beaconLocationPublishErrorCounts", new Map());
(0, _defineProperty2.default)(this, "beaconUpdateErrors", new Map());
/**
* ids of live beacons
* ordered by creation time descending
*/
(0, _defineProperty2.default)(this, "liveBeaconIds", []);
(0, _defineProperty2.default)(this, "locationInterval", void 0);
(0, _defineProperty2.default)(this, "clearPositionWatch", void 0);
/**
* Track when the last position was published
* So we can manually get position on slow interval
* when the target is stationary
*/
(0, _defineProperty2.default)(this, "lastPublishedPositionTimestamp", void 0);
/**
* Ref returned from watchSetting for the MSC3946 labs flag
*/
(0, _defineProperty2.default)(this, "dynamicWatcherRef", void 0);
(0, _defineProperty2.default)(this, "hasLiveBeacons", roomId => {
return !!this.getLiveBeaconIds(roomId).length;
});
/**
* Some live beacon has a wire error
* Optionally filter by room
*/
(0, _defineProperty2.default)(this, "hasLocationPublishErrors", roomId => {
return this.getLiveBeaconIds(roomId).some(this.beaconHasLocationPublishError);
});
/**
* If a beacon has failed to publish position
* past the allowed consecutive failure count (BAIL_AFTER_CONSECUTIVE_ERROR_COUNT)
* Then consider it to have an error
*/
(0, _defineProperty2.default)(this, "beaconHasLocationPublishError", beaconId => {
const counts = this.beaconLocationPublishErrorCounts.get(beaconId);
return counts !== undefined && counts >= BAIL_AFTER_CONSECUTIVE_ERROR_COUNT;
});
(0, _defineProperty2.default)(this, "resetLocationPublishError", beaconId => {
this.incrementBeaconLocationPublishErrorCount(beaconId, false);
// always publish to all live beacons together
// instead of just one that was changed
// to keep lastPublishedTimestamp simple
// and extra published locations don't hurt
this.publishCurrentLocationToBeacons();
});
(0, _defineProperty2.default)(this, "getLiveBeaconIds", roomId => {
if (!roomId) {
return this.liveBeaconIds;
}
return this.liveBeaconIds.filter(beaconId => this.beaconsByRoomId.get(roomId)?.has(beaconId));
});
(0, _defineProperty2.default)(this, "getLiveBeaconIdsWithLocationPublishError", roomId => {
return this.getLiveBeaconIds(roomId).filter(this.beaconHasLocationPublishError);
});
(0, _defineProperty2.default)(this, "getBeaconById", beaconId => {
return this.beacons.get(beaconId);
});
(0, _defineProperty2.default)(this, "stopBeacon", async beaconIdentifier => {
const beacon = this.beacons.get(beaconIdentifier);
// if no beacon, or beacon is already explicitly set isLive: false
// do nothing
if (!beacon?.beaconInfo?.live) {
return;
}
await this.updateBeaconEvent(beacon, {
live: false
});
// prune from local store
removeLocallyCreateBeaconEventId(beacon.beaconInfoId);
});
/**
* Listeners
*/
(0, _defineProperty2.default)(this, "onNewBeacon", (_event, beacon) => {
if (!this.matrixClient || !isOwnBeacon(beacon, this.matrixClient.getUserId())) {
return;
}
this.addBeacon(beacon);
this.checkLiveness();
});
/**
* This will be called when a beacon is replaced
*/
(0, _defineProperty2.default)(this, "onUpdateBeacon", (_event, beacon) => {
if (!this.matrixClient || !isOwnBeacon(beacon, this.matrixClient.getUserId())) {
return;
}
this.checkLiveness();
beacon.monitorLiveness();
});
(0, _defineProperty2.default)(this, "onDestroyBeacon", beaconIdentifier => {
// check if we care about this beacon
if (!this.beacons.has(beaconIdentifier)) {
return;
}
this.checkLiveness();
});
(0, _defineProperty2.default)(this, "onBeaconLiveness", (isLive, beacon) => {
// check if we care about this beacon
if (!this.beacons.has(beacon.identifier)) {
return;
}
// beacon expired, update beacon to un-alive state
if (!isLive) {
this.stopBeacon(beacon.identifier);
}
this.checkLiveness();
this.emit(OwnBeaconStoreEvent.LivenessChange, this.getLiveBeaconIds());
});
/**
* Check for changes in membership in rooms with beacons
* and stop monitoring beacons in rooms user is no longer member of
*/
(0, _defineProperty2.default)(this, "onRoomStateMembers", (_event, roomState, member) => {
// no beacons for this room, ignore
if (!this.matrixClient || !this.beaconsByRoomId.has(roomState.roomId) || member.userId !== this.matrixClient.getUserId()) {
return;
}
// TODO check powerlevels here
// in PSF-797
// stop watching beacons in rooms where user is no longer a member
if (member.membership === _types.KnownMembership.Leave || member.membership === _types.KnownMembership.Ban) {
this.beaconsByRoomId.get(roomState.roomId)?.forEach(this.removeBeacon);
this.beaconsByRoomId.delete(roomState.roomId);
}
});
/**
* @internal public for test only
*/
(0, _defineProperty2.default)(this, "reinitialiseBeaconState", () => {
this.clearBeacons();
this.initialiseBeaconState();
});
(0, _defineProperty2.default)(this, "initialiseBeaconState", () => {
if (!this.matrixClient) return;
const userId = this.matrixClient.getSafeUserId();
const visibleRooms = this.matrixClient.getVisibleRooms(_SettingsStore.default.getValue("feature_dynamic_room_predecessors"));
visibleRooms.forEach(room => {
const roomState = room.currentState;
const beacons = roomState.beacons;
const ownBeaconsArray = [...beacons.values()].filter(beacon => isOwnBeacon(beacon, userId));
ownBeaconsArray.forEach(beacon => this.addBeacon(beacon));
});
this.checkLiveness();
});
(0, _defineProperty2.default)(this, "addBeacon", beacon => {
this.beacons.set(beacon.identifier, beacon);
if (!this.beaconsByRoomId.has(beacon.roomId)) {
this.beaconsByRoomId.set(beacon.roomId, new Set());
}
this.beaconsByRoomId.get(beacon.roomId).add(beacon.identifier);
beacon.monitorLiveness();
});
/**
* Remove listeners for a given beacon
* remove from state
* and update liveness if changed
*/
(0, _defineProperty2.default)(this, "removeBeacon", beaconId => {
if (!this.beacons.has(beaconId)) {
return;
}
this.beacons.get(beaconId).destroy();
this.beacons.delete(beaconId);
this.checkLiveness();
});
(0, _defineProperty2.default)(this, "checkLiveness", () => {
const locallyCreatedBeaconEventIds = getLocallyCreatedBeaconEventIds();
const prevLiveBeaconIds = this.getLiveBeaconIds();
this.liveBeaconIds = [...this.beacons.values()].filter(beacon => beacon.isLive &&
// only beacons created on this device should be shared to
locallyCreatedBeaconEventIds.includes(beacon.beaconInfoId)).sort(_beacon.sortBeaconsByLatestCreation).map(beacon => beacon.identifier);
const diff = (0, _arrays.arrayDiff)(prevLiveBeaconIds, this.liveBeaconIds);
if (diff.added.length || diff.removed.length) {
this.emit(OwnBeaconStoreEvent.LivenessChange, this.liveBeaconIds);
}
// publish current location immediately
// when there are new live beacons
// and we already have a live monitor
// so first position is published quickly
// even when target is stationary
//
// when there is no existing live monitor
// it will be created below by togglePollingLocation
// and publish first position quickly
if (diff.added.length && this.isMonitoringLiveLocation) {
this.publishCurrentLocationToBeacons();
}
// if overall liveness changed
if (!!prevLiveBeaconIds?.length !== !!this.liveBeaconIds.length) {
this.togglePollingLocation();
}
});
(0, _defineProperty2.default)(this, "createLiveBeacon", async (roomId, beaconInfoContent) => {
if (!this.matrixClient) return;
// explicitly stop any live beacons this user has
// to ensure they remain stopped
// if the new replacing beacon is redacted
const existingLiveBeaconIdsForRoom = this.getLiveBeaconIds(roomId);
await Promise.all(existingLiveBeaconIdsForRoom.map(beaconId => this.stopBeacon(beaconId)));
// eslint-disable-next-line camelcase
const {
event_id
} = await (0, _localRoom.doMaybeLocalRoomAction)(roomId, actualRoomId => this.matrixClient.unstable_createLiveBeacon(actualRoomId, beaconInfoContent), this.matrixClient);
storeLocallyCreateBeaconEventId(event_id);
});
/**
* Geolocation
*/
(0, _defineProperty2.default)(this, "togglePollingLocation", () => {
if (!!this.liveBeaconIds.length) {
this.startPollingLocation();
} else {
this.stopPollingLocation();
}
});
(0, _defineProperty2.default)(this, "startPollingLocation", async () => {
// clear any existing interval
this.stopPollingLocation();
try {
this.clearPositionWatch = (0, _beacon.watchPosition)(this.onWatchedPosition, this.onGeolocationError);
} catch (error) {
if (error instanceof Error) {
this.onGeolocationError(error.message);
} else {
console.error("Unexpected error", error);
}
// don't set locationInterval if geolocation failed to setup
return;
}
this.locationInterval = window.setInterval(() => {
if (!this.lastPublishedPositionTimestamp) {
return;
}
// if position was last updated STATIC_UPDATE_INTERVAL ms ago or more
// get our position and publish it
if (this.lastPublishedPositionTimestamp <= Date.now() - STATIC_UPDATE_INTERVAL) {
this.publishCurrentLocationToBeacons();
}
}, STATIC_UPDATE_INTERVAL);
this.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
});
(0, _defineProperty2.default)(this, "stopPollingLocation", () => {
clearInterval(this.locationInterval);
this.locationInterval = undefined;
this.lastPublishedPositionTimestamp = undefined;
if (this.clearPositionWatch) {
this.clearPositionWatch();
this.clearPositionWatch = undefined;
}
this.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
});
(0, _defineProperty2.default)(this, "onWatchedPosition", position => {
const timedGeoPosition = (0, _beacon.mapGeolocationPositionToTimedGeo)(position);
// if this is our first position, publish immediately
if (!this.lastPublishedPositionTimestamp) {
this.publishLocationToBeacons(timedGeoPosition);
} else {
this.debouncedPublishLocationToBeacons(timedGeoPosition);
}
});
(0, _defineProperty2.default)(this, "onGeolocationError", async error => {
_logger.logger.error("Geolocation failed", error);
// other errors are considered non-fatal
// and self recovering
if (![_beacon.GeolocationError.Unavailable, _beacon.GeolocationError.PermissionDenied].includes(error)) {
return;
}
this.stopPollingLocation();
// kill live beacons when location permissions are revoked
await Promise.all(this.liveBeaconIds.map(this.stopBeacon));
});
/**
* Gets the current location
* (as opposed to using watched location)
* and publishes it to all live beacons
*/
(0, _defineProperty2.default)(this, "publishCurrentLocationToBeacons", async () => {
try {
const position = await (0, _beacon.getCurrentPosition)();
this.publishLocationToBeacons((0, _beacon.mapGeolocationPositionToTimedGeo)(position));
} catch (error) {
if (error instanceof Error) {
this.onGeolocationError(error.message);
} else {
console.error("Unexpected error", error);
}
}
});
/**
* MatrixClient api
*/
/**
* Updates beacon with provided content update
* Records error in beaconUpdateErrors
* rethrows
*/
(0, _defineProperty2.default)(this, "updateBeaconEvent", async (beacon, update) => {
const {
description,
timeout,
timestamp,
live,
assetType
} = _objectSpread(_objectSpread({}, beacon.beaconInfo), update);
const updateContent = _matrix.ContentHelpers.makeBeaconInfoContent(timeout, live, description, assetType, timestamp);
try {
await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, updateContent);
// cleanup any errors
const hadError = this.beaconUpdateErrors.has(beacon.identifier);
if (hadError) {
this.beaconUpdateErrors.delete(beacon.identifier);
this.emit(OwnBeaconStoreEvent.BeaconUpdateError, beacon.identifier, false);
}
} catch (error) {
_logger.logger.error("Failed to update beacon", error);
this.beaconUpdateErrors.set(beacon.identifier, error);
this.emit(OwnBeaconStoreEvent.BeaconUpdateError, beacon.identifier, true);
throw error;
}
});
/**
* Sends m.location events to all live beacons
* Sets last published beacon
*/
(0, _defineProperty2.default)(this, "publishLocationToBeacons", async position => {
this.lastPublishedPositionTimestamp = Date.now();
await Promise.all(this.healthyLiveBeaconIds.map(beaconId => this.beacons.has(beaconId) ? this.sendLocationToBeacon(this.beacons.get(beaconId), position) : null));
});
(0, _defineProperty2.default)(this, "debouncedPublishLocationToBeacons", (0, _lodash.debounce)(this.publishLocationToBeacons, MOVING_UPDATE_INTERVAL));
/**
* Sends m.location event to referencing given beacon
*/
(0, _defineProperty2.default)(this, "sendLocationToBeacon", async (beacon, {
geoUri,
timestamp
}) => {
const content = _matrix.ContentHelpers.makeBeaconContent(geoUri, timestamp, beacon.beaconInfoId);
try {
await this.matrixClient.sendEvent(beacon.roomId, _matrix.M_BEACON.name, content);
this.incrementBeaconLocationPublishErrorCount(beacon.identifier, false);
} catch (error) {
_logger.logger.error(error);
this.incrementBeaconLocationPublishErrorCount(beacon.identifier, true);
}
});
/**
* Manage beacon wire error count
* - clear count for beacon when not error
* - increment count for beacon when is error
* - emit if beacon error count crossed threshold
*/
(0, _defineProperty2.default)(this, "incrementBeaconLocationPublishErrorCount", (beaconId, isError) => {
const hadError = this.beaconHasLocationPublishError(beaconId);
if (isError) {
// increment error count
this.beaconLocationPublishErrorCounts.set(beaconId, (this.beaconLocationPublishErrorCounts.get(beaconId) ?? 0) + 1);
} else {
// clear any error count
this.beaconLocationPublishErrorCounts.delete(beaconId);
}
if (this.beaconHasLocationPublishError(beaconId) !== hadError) {
this.emit(OwnBeaconStoreEvent.LocationPublishError, beaconId);
}
});
}
static get instance() {
return OwnBeaconStore.internalInstance;
}
/**
* True when we have live beacons
* and geolocation.watchPosition is active
*/
get isMonitoringLiveLocation() {
return !!this.clearPositionWatch;
}
async onNotReady() {
if (this.matrixClient) {
this.matrixClient.removeListener(_matrix.BeaconEvent.LivenessChange, this.onBeaconLiveness);
this.matrixClient.removeListener(_matrix.BeaconEvent.New, this.onNewBeacon);
this.matrixClient.removeListener(_matrix.BeaconEvent.Update, this.onUpdateBeacon);
this.matrixClient.removeListener(_matrix.BeaconEvent.Destroy, this.onDestroyBeacon);
this.matrixClient.removeListener(_matrix.RoomStateEvent.Members, this.onRoomStateMembers);
}
_SettingsStore.default.unwatchSetting(this.dynamicWatcherRef ?? "");
this.clearBeacons();
}
clearBeacons() {
this.beacons.forEach(beacon => beacon.destroy());
this.stopPollingLocation();
this.beacons.clear();
this.beaconsByRoomId.clear();
this.liveBeaconIds = [];
this.beaconLocationPublishErrorCounts.clear();
this.beaconUpdateErrors.clear();
}
async onReady() {
if (this.matrixClient) {
this.matrixClient.on(_matrix.BeaconEvent.LivenessChange, this.onBeaconLiveness);
this.matrixClient.on(_matrix.BeaconEvent.New, this.onNewBeacon);
this.matrixClient.on(_matrix.BeaconEvent.Update, this.onUpdateBeacon);
this.matrixClient.on(_matrix.BeaconEvent.Destroy, this.onDestroyBeacon);
this.matrixClient.on(_matrix.RoomStateEvent.Members, this.onRoomStateMembers);
}
this.dynamicWatcherRef = _SettingsStore.default.watchSetting("feature_dynamic_room_predecessors", null, this.reinitialiseBeaconState);
this.initialiseBeaconState();
}
async onAction(payload) {
// we don't actually do anything here
}
/**
* State management
*/
/**
* Live beacon ids that do not have wire errors
*/
get healthyLiveBeaconIds() {
return this.liveBeaconIds.filter(beaconId => !this.beaconHasLocationPublishError(beaconId) && !this.beaconUpdateErrors.has(beaconId));
}
}
exports.OwnBeaconStore = OwnBeaconStore;
_OwnBeaconStore = OwnBeaconStore;
(0, _defineProperty2.default)(OwnBeaconStore, "internalInstance", (() => {
const instance = new _OwnBeaconStore();
instance.start();
return instance;
})());
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJfbG9kYXNoIiwicmVxdWlyZSIsIl9tYXRyaXgiLCJfdHlwZXMiLCJfbG9nZ2VyIiwiX2Rpc3BhdGNoZXIiLCJfaW50ZXJvcFJlcXVpcmVEZWZhdWx0IiwiX0FzeW5jU3RvcmVXaXRoQ2xpZW50IiwiX2FycmF5cyIsIl9iZWFjb24iLCJfbG9jYWxSb29tIiwiX1NldHRpbmdzU3RvcmUiLCJfT3duQmVhY29uU3RvcmUiLCJvd25LZXlzIiwiZSIsInIiLCJ0IiwiT2JqZWN0Iiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsIm8iLCJmaWx0ZXIiLCJnZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IiLCJlbnVtZXJhYmxlIiwicHVzaCIsImFwcGx5IiwiX29iamVjdFNwcmVhZCIsImFyZ3VtZW50cyIsImxlbmd0aCIsImZvckVhY2giLCJfZGVmaW5lUHJvcGVydHkyIiwiZGVmYXVsdCIsImdldE93blByb3BlcnR5RGVzY3JpcHRvcnMiLCJkZWZpbmVQcm9wZXJ0aWVzIiwiZGVmaW5lUHJvcGVydHkiLCJpc093bkJlYWNvbiIsImJlYWNvbiIsInVzZXJJZCIsImJlYWNvbkluZm9Pd25lciIsIk93bkJlYWNvblN0b3JlRXZlbnQiLCJleHBvcnRzIiwiTU9WSU5HX1VQREFURV9JTlRFUlZBTCIsIlNUQVRJQ19VUERBVEVfSU5URVJWQUwiLCJCQUlMX0FGVEVSX0NPTlNFQ1VUSVZFX0VSUk9SX0NPVU5UIiwiQ1JFQVRFRF9CRUFDT05TX0tFWSIsInJlbW92ZUxvY2FsbHlDcmVhdGVCZWFjb25FdmVudElkIiwiZXZlbnRJZCIsImlkcyIsImdldExvY2FsbHlDcmVhdGVkQmVhY29uRXZlbnRJZHMiLCJ3aW5kb3ciLCJsb2NhbFN0b3JhZ2UiLCJzZXRJdGVtIiwiSlNPTiIsInN0cmluZ2lmeSIsImlkIiwic3RvcmVMb2NhbGx5Q3JlYXRlQmVhY29uRXZlbnRJZCIsInBhcnNlIiwiZ2V0SXRlbSIsIkFycmF5IiwiaXNBcnJheSIsIkVycm9yIiwiZXJyb3IiLCJsb2dnZXIiLCJPd25CZWFjb25TdG9yZSIsIkFzeW5jU3RvcmVXaXRoQ2xpZW50IiwiY29uc3RydWN0b3IiLCJkZWZhdWx0RGlzcGF0Y2hlciIsIk1hcCIsInJvb21JZCIsImdldExpdmVCZWFjb25JZHMiLCJzb21lIiwiYmVhY29uSGFzTG9jYXRpb25QdWJsaXNoRXJyb3IiLCJiZWFjb25JZCIsImNvdW50cyIsImJlYWNvbkxvY2F0aW9uUHVibGlzaEVycm9yQ291bnRzIiwiZ2V0IiwidW5kZWZpbmVkIiwiaW5jcmVtZW50QmVhY29uTG9jYXRpb25QdWJsaXNoRXJyb3JDb3VudCIsInB1Ymxpc2hDdXJyZW50TG9jYXRpb25Ub0JlYWNvbnMiLCJsaXZlQmVhY29uSWRzIiwiYmVhY29uc0J5Um9vbUlkIiwiaGFzIiwiYmVhY29ucyIsImJlYWNvbklkZW50aWZpZXIiLCJiZWFjb25JbmZvIiwibGl2ZSIsInVwZGF0ZUJlYWNvbkV2ZW50IiwiYmVhY29uSW5mb0lkIiwiX2V2ZW50IiwibWF0cml4Q2xpZW50IiwiZ2V0VXNlcklkIiwiYWRkQmVhY29uIiwiY2hlY2tMaXZlbmVzcyIsIm1vbml0b3JMaXZlbmVzcyIsImlzTGl2ZSIsImlkZW50aWZpZXIiLCJzdG9wQmVhY29uIiwiZW1pdCIsIkxpdmVuZXNzQ2hhbmdlIiwicm9vbVN0YXRlIiwibWVtYmVyIiwibWVtYmVyc2hpcCIsIktub3duTWVtYmVyc2hpcCIsIkxlYXZlIiwiQmFuIiwicmVtb3ZlQmVhY29uIiwiZGVsZXRlIiwiY2xlYXJCZWFjb25zIiwiaW5pdGlhbGlzZUJlYWNvblN0YXRlIiwiZ2V0U2FmZVVzZXJJZCIsInZpc2libGVSb29tcyIsImdldFZpc2libGVSb29tcyIsIlNldHRpbmdzU3RvcmUiLCJnZXRWYWx1ZSIsInJvb20iLCJjdXJyZW50U3RhdGUiLCJvd25CZWFjb25zQXJyYXkiLCJ2YWx1ZXMiLCJzZXQiLCJTZXQiLCJhZGQiLCJkZXN0cm95IiwibG9jYWxseUNyZWF0ZWRCZWFjb25FdmVudElkcyIsInByZXZMaXZlQmVhY29uSWRzIiwiaW5jbHVkZXMiLCJzb3J0Iiwic29ydEJlYWNvbnNCeUxhdGVzdENyZWF0aW9uIiwibWFwIiwiZGlmZiIsImFycmF5RGlmZiIsImFkZGVkIiwicmVtb3ZlZCIsImlzTW9uaXRvcmluZ0xpdmVMb2NhdGlvbiIsInRvZ2dsZVBvbGxpbmdMb2NhdGlvbiIsImJlYWNvbkluZm9Db250ZW50IiwiZXhpc3RpbmdMaXZlQmVhY29uSWRzRm9yUm9vbSIsIlByb21pc2UiLCJhbGwiLCJldmVudF9pZCIsImRvTWF5YmVMb2NhbFJvb21BY3Rpb24iLCJhY3R1YWxSb29tSWQiLCJ1bnN0YWJsZV9jcmVhdGVMaXZlQmVhY29uIiwic3RhcnRQb2xsaW5nTG9jYXRpb24iLCJzdG9wUG9sbGluZ0xvY2F0aW9uIiwiY2xlYXJQb3NpdGlvbldhdGNoIiwid2F0Y2hQb3NpdGlvbiIsIm9uV2F0Y2hlZFBvc2l0aW9uIiwib25HZW9sb2NhdGlvbkVycm9yIiwibWVzc2FnZSIsImNvbnNvbGUiLCJsb2NhdGlvbkludGVydmFsIiwic2V0SW50ZXJ2YWwiLCJsYXN0UHVibGlzaGVkUG9zaXRpb25UaW1lc3RhbXAiLCJEYXRlIiwibm93IiwiTW9uaXRvcmluZ0xpdmVQb3NpdGlvbiIsImNsZWFySW50ZXJ2YWwiLCJwb3NpdGlvbiIsInRpbWVkR2VvUG9zaXRpb24iLCJtYXBHZW9sb2NhdGlvblBvc2l0aW9uVG9UaW1lZEdlbyIsInB1Ymxpc2hMb2NhdGlvblRvQmVhY29ucyIsImRlYm91bmNlZFB1Ymxpc2hMb2NhdGlvblRvQmVhY29ucyIsIkdlb2xvY2F0aW9uRXJyb3IiLCJVbmF2YWlsYWJsZSIsIlBlcm1pc3Npb25EZW5pZWQiLCJnZXRDdXJyZW50UG9zaXRpb24iLCJ1cGRhdGUiLCJkZXNjcmlwdGlvbiIsInRpbWVvdXQiLCJ0aW1lc3RhbXAiLCJhc3NldFR5cGUiLCJ1cGRhdGVDb250ZW50IiwiQ29udGVudEhlbHBlcnMiLCJtYWtlQmVhY29uSW5mb0NvbnRlbnQiLCJ1bnN0YWJsZV9zZXRMaXZlQmVhY29uIiwiaGFkRXJyb3IiLCJiZWFjb25VcGRhdGVFcnJvcnMiLCJCZWFjb25VcGRhdGVFcnJvciIsImhlYWx0aHlMaXZlQmVhY29uSWRzIiwic2VuZExvY2F0aW9uVG9CZWFjb24iLCJkZWJvdW5jZSIsImdlb1VyaSIsImNvbnRlbnQiLCJtYWtlQmVhY29uQ29udGVudCIsInNlbmRFdmVudCIsIk1fQkVBQ09OIiwibmFtZSIsImlzRXJyb3IiLCJMb2NhdGlvblB1Ymxpc2hFcnJvciIsImluc3RhbmNlIiwiaW50ZXJuYWxJbnN0YW5jZSIsIm9uTm90UmVhZHkiLCJyZW1vdmVMaXN0ZW5lciIsIkJlYWNvbkV2ZW50Iiwib25CZWFjb25MaXZlbmVzcyIsIk5ldyIsIm9uTmV3QmVhY29uIiwiVXBkYXRlIiwib25VcGRhdGVCZWFjb24iLCJEZXN0cm95Iiwib25EZXN0cm95QmVhY29uIiwiUm9vbVN0YXRlRXZlbnQiLCJNZW1iZXJzIiwib25Sb29tU3RhdGVNZW1iZXJzIiwidW53YXRjaFNldHRpbmciLCJkeW5hbWljV2F0Y2hlclJlZiIsImNsZWFyIiwib25SZWFkeSIsIm9uIiwid2F0Y2hTZXR0aW5nIiwicmVpbml0aWFsaXNlQmVhY29uU3RhdGUiLCJvbkFjdGlvbiIsInBheWxvYWQiLCJzdGFydCJdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zdG9yZXMvT3duQmVhY29uU3RvcmUudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLypcbkNvcHlyaWdodCAyMDI0IE5ldyBWZWN0b3IgTHRkLlxuQ29weXJpZ2h0IDIwMjIgVGhlIE1hdHJpeC5vcmcgRm91bmRhdGlvbiBDLkkuQy5cblxuU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFHUEwtMy4wLW9ubHkgT1IgR1BMLTMuMC1vbmx5XG5QbGVhc2Ugc2VlIExJQ0VOU0UgZmlsZXMgaW4gdGhlIHJlcG9zaXRvcnkgcm9vdCBmb3IgZnVsbCBkZXRhaWxzLlxuKi9cblxuaW1wb3J0IHsgZGVib3VuY2UgfSBmcm9tIFwibG9kYXNoXCI7XG5pbXBvcnQge1xuICAgIEJlYWNvbixcbiAgICBCZWFjb25JZGVudGlmaWVyLFxuICAgIEJlYWNvbkV2ZW50LFxuICAgIE1hdHJpeEV2ZW50LFxuICAgIFJvb20sXG4gICAgUm9vbU1lbWJlcixcbiAgICBSb29tU3RhdGUsXG4gICAgUm9vbVN0YXRlRXZlbnQsXG4gICAgQ29udGVudEhlbHBlcnMsXG4gICAgTUJlYWNvbkluZm9FdmVudENvbnRlbnQsXG4gICAgTV9CRUFDT04sXG59IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy9tYXRyaXhcIjtcbmltcG9ydCB7IEtub3duTWVtYmVyc2hpcCB9IGZyb20gXCJtYXRyaXgtanMtc2RrL3NyYy90eXBlc1wiO1xuaW1wb3J0IHsgbG9nZ2VyIH0gZnJvbSBcIm1hdHJpeC1qcy1zZGsvc3JjL2xvZ2dlclwiO1xuXG5pbXBvcnQgZGVmYXVsdERpc3BhdGNoZXIgZnJvbSBcIi4uL2Rpc3BhdGNoZXIvZGlzcGF0Y2hlclwiO1xuaW1wb3J0IHsgQWN0aW9uUGF5bG9hZCB9IGZyb20gXCIuLi9kaXNwYXRjaGVyL3BheWxvYWRzXCI7XG5pbXBvcnQgeyBBc3luY1N0b3JlV2l0aENsaWVudCB9IGZyb20gXCIuL0FzeW5jU3RvcmVXaXRoQ2xpZW50XCI7XG5pbXBvcnQgeyBhcnJheURpZmYgfSBmcm9tIFwiLi4vdXRpbHMvYXJyYXlzXCI7XG5pbXBvcnQge1xuICAgIENsZWFyV2F0Y2hDYWxsYmFjayxcbiAgICBHZW9sb2NhdGlvbkVycm9yLFxuICAgIG1hcEdlb2xvY2F0aW9uUG9zaXRpb25Ub1RpbWVkR2VvLFxuICAgIHNvcnRCZWFjb25zQnlMYXRlc3RDcmVhdGlvbixcbiAgICBUaW1lZEdlb1VyaSxcbiAgICB3YXRjaFBvc2l0aW9uLFxuICAgIGdldEN1cnJlbnRQb3NpdGlvbixcbn0gZnJvbSBcIi4uL3V0aWxzL2JlYWNvblwiO1xuaW1wb3J0IHsgZG9NYXliZUxvY2FsUm9vbUFjdGlvbiB9IGZyb20gXCIuLi91dGlscy9sb2NhbC1yb29tXCI7XG5pbXBvcnQgU2V0dGluZ3NTdG9yZSBmcm9tIFwiLi4vc2V0dGluZ3MvU2V0dGluZ3NTdG9yZVwiO1xuXG5jb25zdCBpc093bkJlYWNvbiA9IChiZWFjb246IEJlYWNvbiwgdXNlcklkOiBzdHJpbmcpOiBib29sZWFuID0+IGJlYWNvbi5iZWFjb25JbmZvT3duZXIgPT09IHVzZXJJZDtcblxuZXhwb3J0IGVudW0gT3duQmVhY29uU3RvcmVFdmVudCB7XG4gICAgTGl2ZW5lc3NDaGFuZ2UgPSBcIk93bkJlYWNvblN0b3JlLkxpdmVuZXNzQ2hhbmdlXCIsXG4gICAgTW9uaXRvcmluZ0xpdmVQb3NpdGlvbiA9IFwiT3duQmVhY29uU3RvcmUuTW9uaXRvcmluZ0xpdmVQb3NpdGlvblwiLFxuICAgIExvY2F0aW9uUHVibGlzaEVycm9yID0gXCJMb2NhdGlvblB1Ymxpc2hFcnJvclwiLFxuICAgIEJlYWNvblVwZGF0ZUVycm9yID0gXCJCZWFjb25VcGRhdGVFcnJvclwiLFxufVxuXG5jb25zdCBNT1ZJTkdfVVBEQVRFX0lOVEVSVkFMID0gNTAwMDtcbmNvbnN0IFNUQVRJQ19VUERBVEVfSU5URVJWQUwgPSAzMDAwMDtcblxuY29uc3QgQkFJTF9BRlRFUl9DT05TRUNVVElWRV9FUlJPUl9DT1VOVCA9IDI7XG5cbnR5cGUgT3duQmVhY29uU3RvcmVTdGF0ZSA9IHtcbiAgICBiZWFjb25zOiBNYXA8QmVhY29uSWRlbnRpZmllciwgQmVhY29uPjtcbiAgICBiZWFjb25Mb2NhdGlvblB1Ymxpc2hFcnJvckNvdW50czogTWFwPEJlYWNvbklkZW50aWZpZXIsIG51bWJlcj47XG4gICAgYmVhY29uVXBkYXRlRXJyb3JzOiBNYXA8QmVhY29uSWRlbnRpZmllciwgRXJyb3I+O1xuICAgIGJlYWNvbnNCeVJvb21JZDogTWFwPFJvb21bXCJyb29tSWRcIl0sIFNldDxCZWFjb25JZGVudGlmaWVyPj47XG4gICAgbGl2ZUJlYWNvbklkczogQmVhY29uSWRlbnRpZmllcltdO1xufTtcblxuY29uc3QgQ1JFQVRFRF9CRUFDT05TX0tFWSA9IFwibXhfbGl2ZV9iZWFjb25fY3JlYXRlZF9pZFwiO1xuY29uc3QgcmVtb3ZlTG9jYWxseUNyZWF0ZUJlYWNvbkV2ZW50SWQgPSAoZXZlbnRJZDogc3RyaW5nKTogdm9pZCA9PiB7XG4gICAgY29uc3QgaWRzID0gZ2V0TG9jYWxseUNyZWF0ZWRCZWFjb25FdmVudElkcygpO1xuICAgIHdpbmRvdy5sb2NhbFN0b3JhZ2Uuc2V0SXRlbShDUkVBVEVEX0JFQUNPTlNfS0VZLCBKU09OLnN0cmluZ2lmeShpZHMuZmlsdGVyKChpZCkgPT4gaWQgIT09IGV2ZW50SWQpKSk7XG59O1xuY29uc3Qgc3RvcmVMb2NhbGx5Q3JlYXRlQmVhY29uRXZlbnRJZCA9IChldmVudElkOiBzdHJpbmcpOiB2b2lkID0+IHtcbiAgICBjb25zdCBpZHMgPSBnZXRMb2NhbGx5Q3JlYXRlZEJlYWNvbkV2ZW50SWRzKCk7XG4gICAgd2luZG93LmxvY2FsU3RvcmFnZS5zZXRJdGVtKENSRUFURURfQkVBQ09OU19LRVksIEpTT04uc3RyaW5naWZ5KFsuLi5pZHMsIGV2ZW50SWRdKSk7XG59O1xuXG5jb25zdCBnZXRMb2NhbGx5Q3JlYXRlZEJlYWNvbkV2ZW50SWRzID0gKCk6IHN0cmluZ1tdID0+IHtcbiAgICBsZXQgaWRzOiBzdHJpbmdbXTtcbiAgICB0cnkge1xuICAgICAgICBpZHMgPSBKU09OLnBhcnNlKHdpbmRvdy5sb2NhbFN0b3JhZ2UuZ2V0SXRlbShDUkVBVEVEX0JFQUNPTlNfS0VZKSA/PyBcIltdXCIpO1xuICAgICAgICBpZiAoIUFycmF5LmlzQXJyYXkoaWRzKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiSW52YWxpZCBzdG9yZWQgdmFsdWVcIik7XG4gICAgICAgIH1cbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBsb2dnZXIuZXJyb3IoXCJGYWlsZWQgdG8gcmV0cmlldmUgbG9jYWxseSBjcmVhdGVkIGJlYWNvbiBldmVudCBpZHNcIiwgZXJyb3IpO1xuICAgICAgICBpZHMgPSBbXTtcbiAgICB9XG4gICAgcmV0dXJuIGlkcztcbn07XG5leHBvcnQgY2xhc3MgT3duQmVhY29uU3RvcmUgZXh0ZW5kcyBBc3luY1N0b3JlV2l0aENsaWVudDxPd25CZWFjb25TdG9yZVN0YXRlPiB7XG4gICAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgaW50ZXJuYWxJbnN0YW5jZSA9ICgoKSA9PiB7XG4gICAgICAgIGNvbnN0IGluc3RhbmNlID0gbmV3IE93bkJlYWNvblN0b3JlKCk7XG4gICAgICAgIGluc3RhbmNlLnN0YXJ0KCk7XG4gICAgICAgIHJldHVybiBpbnN0YW5jZTtcbiAgICB9KSgpO1xuICAgIC8vIHVzZXJzIGJlYWNvbnMsIGtleWVkIGJ5IGV2ZW50IHR5cGVcbiAgICBwdWJsaWMgcmVhZG9ubHkgYmVhY29ucyA9IG5ldyBNYXA8QmVhY29uSWRlbnRpZmllciwgQmVhY29uPigpO1xuICAgIHB1YmxpYyByZWFkb25seSBiZWFjb25zQnlSb29tSWQgPSBuZXcgTWFwPFJvb21bXCJyb29tSWRcIl0sIFNldDxCZWFjb25JZGVudGlmaWVyPj4oKTtcbiAgICAvKipcbiAgICAgKiBUcmFjayBvdmVyIHRoZSB3aXJlIGVycm9ycyBmb3IgcHVibGlzaGVkIHBvc2l0aW9uc1xuICAgICAqIENvdW50cyBjb25zZWN1dGl2ZSB3aXJlIGVycm9ycyBwZXIgYmVhY29uXG4gICAgICogUmVzZXQgb24gc3VjY2Vzc2Z1bCBwdWJsaXNoIG9mIGxvY2F0aW9uXG4gICAgICovXG4gICAgcHVibGljIHJlYWRvbmx5IGJlYWNvbkxvY2F0aW9uUHVibGlzaEVycm9yQ291bnRzID0gbmV3IE1hcDxCZWFjb25JZGVudGlmaWVyLCBudW1iZXI+KCk7XG4gICAgcHVibGljIHJlYWRvbmx5IGJlYWNvblVwZGF0ZUVycm9ycyA9IG5ldyBNYXA8QmVhY29uSWRlbnRpZmllciwgdW5rbm93bj4oKTtcbiAgICAvKipcbiAgICAgKiBpZHMgb2YgbGl2ZSBiZWFjb25zXG4gICAgICogb3JkZXJlZCBieSBjcmVhdGlvbiB0aW1lIGRlc2NlbmRpbmdcbiAgICAgKi9cbiAgICBwcml2YXRlIGxpdmVCZWFjb25JZHM6IEJlYWNvbklkZW50aWZpZXJbXSA9IFtdO1xuICAgIHByaXZhdGUgbG9jYXRpb25JbnRlcnZhbD86IG51bWJlcjtcbiAgICBwcml2YXRlIGNsZWFyUG9zaXRpb25XYXRjaD86IENsZWFyV2F0Y2hDYWxsYmFjaztcbiAgICAvKipcbiAgICAgKiBUcmFjayB3aGVuIHRoZSBsYXN0IHBvc2l0aW9uIHdhcyBwdWJsaXNoZWRcbiAgICAgKiBTbyB3ZSBjYW4gbWFudWFsbHkgZ2V0IHBvc2l0aW9uIG9uIHNsb3cgaW50ZXJ2YWxcbiAgICAgKiB3aGVuIHRoZSB0YXJnZXQgaXMgc3RhdGlvbmFyeVxuICAgICAqL1xuICAgIHByaXZhdGUgbGFzdFB1Ymxpc2hlZFBvc2l0aW9uVGltZXN0YW1wPzogbnVtYmVyO1xuICAgIC8qKlxuICAgICAqIFJlZiByZXR1cm5lZCBmcm9tIHdhdGNoU2V0dGluZyBmb3IgdGhlIE1TQzM5NDYgbGFicyBmbGFnXG4gICAgICovXG4gICAgcHJpdmF0ZSBkeW5hbWljV2F0Y2hlclJlZjogc3RyaW5nIHwgdW5kZWZpbmVkO1xuXG4gICAgcHVibGljIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICBzdXBlcihkZWZhdWx0RGlzcGF0Y2hlcik7XG4gICAgfVxuXG4gICAgcHVibGljIHN0YXRpYyBnZXQgaW5zdGFuY2UoKTogT3duQmVhY29uU3RvcmUge1xuICAgICAgICByZXR1cm4gT3duQmVhY29uU3RvcmUuaW50ZXJuYWxJbnN0YW5jZTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUcnVlIHdoZW4gd2UgaGF2ZSBsaXZlIGJlYWNvbnNcbiAgICAgKiBhbmQgZ2VvbG9jYXRpb24ud2F0Y2hQb3NpdGlvbiBpcyBhY3RpdmVcbiAgICAgKi9cbiAgICBwdWJsaWMgZ2V0IGlzTW9uaXRvcmluZ0xpdmVMb2NhdGlvbigpOiBib29sZWFuIHtcbiAgICAgICAgcmV0dXJuICEhdGhpcy5jbGVhclBvc2l0aW9uV2F0Y2g7XG4gICAgfVxuXG4gICAgcHJvdGVjdGVkIGFzeW5jIG9uTm90UmVhZHkoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGlmICh0aGlzLm1hdHJpeENsaWVudCkge1xuICAgICAgICAgICAgdGhpcy5tYXRyaXhDbGllbnQucmVtb3ZlTGlzdGVuZXIoQmVhY29uRXZlbnQuTGl2ZW5lc3NDaGFuZ2UsIHRoaXMub25CZWFjb25MaXZlbmVzcyk7XG4gICAgICAgICAgICB0aGlzLm1hdHJpeENsaWVudC5yZW1vdmVMaXN0ZW5lcihCZWFjb25FdmVudC5OZXcsIHRoaXMub25OZXdCZWFjb24pO1xuICAgICAgICAgICAgdGhpcy5tYXRyaXhDbGllbnQucmVtb3ZlTGlzdGVuZXIoQmVhY29uRXZlbnQuVXBkYXRlLCB0aGlzLm9uVXBkYXRlQmVhY29uKTtcbiAgICAgICAgICAgIHRoaXMubWF0cml4Q2xpZW50LnJlbW92ZUxpc3RlbmVyKEJlYWNvbkV2ZW50LkRlc3Ryb3ksIHRoaXMub25EZXN0cm95QmVhY29uKTtcbiAgICAgICAgICAgIHRoaXMubWF0cml4Q2xpZW50LnJlbW92ZUxpc3RlbmVyKFJvb21TdGF0ZUV2ZW50Lk1lbWJlcnMsIHRoaXMub25Sb29tU3RhdGVNZW1iZXJzKTtcbiAgICAgICAgfVxuICAgICAgICBTZXR0aW5nc1N0b3JlLnVud2F0Y2hTZXR0aW5nKHRoaXMuZHluYW1pY1dhdGNoZXJSZWYgPz8gXCJcIik7XG5cbiAgICAgICAgdGhpcy5jbGVhckJlYWNvbnMoKTtcbiAgICB9XG5cbiAgICBwcml2YXRlIGNsZWFyQmVhY29ucygpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5iZWFjb25zLmZvckVhY2goKGJlYWNvbikgPT4gYmVhY29uLmRlc3Ryb3koKSk7XG5cbiAgICAgICAgdGhpcy5zdG9wUG9sbGluZ0xvY2F0aW9uKCk7XG4gICAgICAgIHRoaXMuYmVhY29ucy5jbGVhcigpO1xuICAgICAgICB0aGlzLmJlYWNvbnNCeVJvb21JZC5jbGVhcigpO1xuICAgICAgICB0aGlzLmxpdmVCZWFjb25JZHMgPSBbXTtcbiAgICAgICAgdGhpcy5iZWFjb25Mb2NhdGlvblB1Ymxpc2hFcnJvckNvdW50cy5jbGVhcigpO1xuICAgICAgICB0aGlzLmJlYWNvblVwZGF0ZUVycm9ycy5jbGVhcigpO1xuICAgIH1cblxuICAgIHByb3RlY3RlZCBhc3luYyBvblJlYWR5KCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBpZiAodGhpcy5tYXRyaXhDbGllbnQpIHtcbiAgICAgICAgICAgIHRoaXMubWF0cml4Q2xpZW50Lm9uKEJlYWNvbkV2ZW50LkxpdmVuZXNzQ2hhbmdlLCB0aGlzLm9uQmVhY29uTGl2ZW5lc3MpO1xuICAgICAgICAgICAgdGhpcy5tYXRyaXhDbGllbnQub24oQmVhY29uRXZlbnQuTmV3LCB0aGlzLm9uTmV3QmVhY29uKTtcbiAgICAgICAgICAgIHRoaXMubWF0cml4Q2xpZW50Lm9uKEJlYWNvbkV2ZW50LlVwZGF0ZSwgdGhpcy5vblVwZGF0ZUJlYWNvbik7XG4gICAgICAgICAgICB0aGlzLm1hdHJpeENsaWVudC5vbihCZWFjb25FdmVudC5EZXN0cm95LCB0aGlzLm9uRGVzdHJveUJlYWNvbik7XG4gICAgICAgICAgICB0aGlzLm1hdHJpeENsaWVudC5vbihSb29tU3RhdGVFdmVudC5NZW1iZXJzLCB0aGlzLm9uUm9vbVN0YXRlTWVtYmVycyk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5keW5hbWljV2F0Y2hlclJlZiA9IFNldHRpbmdzU3RvcmUud2F0Y2hTZXR0aW5nKFxuICAgICAgICAgICAgXCJmZWF0dXJlX2R5bmFtaWNfcm9vbV9wcmVkZWNlc3NvcnNcIixcbiAgICAgICAgICAgIG51bGwsXG4gICAgICAgICAgICB0aGlzLnJlaW5pdGlhbGlzZUJlYWNvblN0YXRlLFxuICAgICAgICApO1xuXG4gICAgICAgIHRoaXMuaW5pdGlhbGlzZUJlYWNvblN0YXRlKCk7XG4gICAgfVxuXG4gICAgcHJvdGVjdGVkIGFzeW5jIG9uQWN0aW9uKHBheWxvYWQ6IEFjdGlvblBheWxvYWQpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgICAgLy8gd2UgZG9uJ3QgYWN0dWFsbHkgZG8gYW55dGhpbmcgaGVyZVxuICAgIH1cblxuICAgIHB1YmxpYyBoYXNMaXZlQmVhY29ucyA9IChyb29tSWQ/OiBzdHJpbmcpOiBib29sZWFuID0+IHtcbiAgICAgICAgcmV0dXJuICEhdGhpcy5nZXRMaXZlQmVhY29uSWRzKHJvb21JZCkubGVuZ3RoO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTb21lIGxpdmUgYmVhY29uIGhhcyBhIHdpcmUgZXJyb3JcbiAgICAgKiBPcHRpb25hbGx5IGZpbHRlciBieSByb29tXG4gICAgICovXG4gICAgcHVibGljIGhhc0xvY2F0aW9uUHVibGlzaEVycm9ycyA9IChyb29tSWQ/OiBzdHJpbmcpOiBib29sZWFuID0+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0TGl2ZUJlYWNvbklkcyhyb29tSWQpLnNvbWUodGhpcy5iZWFjb25IYXNMb2NhdGlvblB1Ymxpc2hFcnJvcik7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIElmIGEgYmVhY29uIGhhcyBmYWlsZWQgdG8gcHVibGlzaCBwb3NpdGlvblxuICAgICAqIHBhc3QgdGhlIGFsbG93ZWQgY29uc2VjdXRpdmUgZmFpbHVyZSBjb3VudCAoQkFJTF9BRlRFUl9DT05TRUNVVElWRV9FUlJPUl9DT1VOVClcbiAgICAgKiBUaGVuIGNvbnNpZGVyIGl0IHRvIGhhdmUgYW4gZXJyb3JcbiAgICAgKi9cbiAgICBwdWJsaWMgYmVhY29uSGFzTG9jYXRpb25QdWJsaXNoRXJyb3IgPSAoYmVhY29uSWQ6IHN0cmluZyk6IGJvb2xlYW4gPT4ge1xuICAgICAgICBjb25zdCBjb3VudHMgPSB0aGlzLmJlYWNvbkxvY2F0aW9uUHVibGlzaEVycm9yQ291bnRzLmdldChiZWFjb25JZCk7XG4gICAgICAgIHJldHVybiBjb3VudHMgIT09IHVuZGVmaW5lZCAmJiBjb3VudHMgPj0gQkFJTF9BRlRFUl9DT05TRUNVVElWRV9FUlJPUl9DT1VOVDtcbiAgICB9O1xuXG4gICAgcHVibGljIHJlc2V0TG9jYXRpb25QdWJsaXNoRXJyb3IgPSAoYmVhY29uSWQ6IHN0cmluZyk6IHZvaWQgPT4ge1xuICAgICAgICB0aGlzLmluY3JlbWVudEJlYWNvbkxvY2F0aW9uUHVibGlzaEVycm9yQ291bnQoYmVhY29uSWQsIGZhbHNlKTtcblxuICAgICAgICAvLyBhbHdheXMgcHVibGlzaCB0byBhbGwgbGl2ZSBiZWFjb25zIHRvZ2V0aGVyXG4gICAgICAgIC8vIGluc3RlYWQgb2YganVzdCBvbmUgdGhhdCB3YXMgY2hhbmdlZFxuICAgICAgICAvLyB0byBrZWVwIGxhc3RQdWJsaXNoZWRUaW1lc3RhbXAgc2ltcGxlXG4gICAgICAgIC8vIGFuZCBleHRyYSBwdWJsaXNoZWQgbG9jYXRpb25zIGRvbid0IGh1cnRcbiAgICAgICAgdGhpcy5wdWJsaXNoQ3VycmVudExvY2F0aW9uVG9CZWFjb25zKCk7XG4gICAgfTtcblxuICAgIHB1YmxpYyBnZXRMaXZlQmVhY29uSWRzID0gKHJvb21JZD86IHN0cmluZyk6IHN0cmluZ1tdID0+IHtcbiAgICAgICAgaWYgKCFyb29tSWQpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLmxpdmVCZWFjb25JZHM7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMubGl2ZUJlYWNvbklkcy5maWx0ZXIoKGJlYWNvbklkKSA9PiB0aGlzLmJlYWNvbnNCeVJvb21JZC5nZXQocm9vbUlkKT8uaGFzKGJlYWNvbklkKSk7XG4gICAgfTtcblxuICAgIHB1YmxpYyBnZXRMaXZlQmVhY29uSWRzV2l0aExvY2F0aW9uUHVibGlzaEVycm9yID0gKHJvb21JZD86IHN0cmluZyk6IHN0cmluZ1tdID0+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZ2V0TGl2ZUJlYWNvbklkcyhyb29tSWQpLmZpbHRlcih0aGlzLmJlYWNvbkhhc0xvY2F0aW9uUHVibGlzaEVycm9yKTtcbiAgICB9O1xuXG4gICAgcHVibGljIGdldEJlYWNvbkJ5SWQgPSAoYmVhY29uSWQ6IHN0cmluZyk6IEJlYWNvbiB8IHVuZGVmaW5lZCA9PiB7XG4gICAgICAgIHJldHVybiB0aGlzLmJlYWNvbnMuZ2V0KGJlYWNvbklkKTtcbiAgICB9O1xuXG4gICAgcHVibGljIHN0b3BCZWFjb24gPSBhc3luYyAoYmVhY29uSWRlbnRpZmllcjogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiA9PiB7XG4gICAgICAgIGNvbnN0IGJlYWNvbiA9IHRoaXMuYmVhY29ucy5nZXQoYmVhY29uSWRlbnRpZmllcik7XG4gICAgICAgIC8vIGlmIG5vIGJlYWNvbiwgb3IgYmVhY29uIGlzIGFscmVhZHkgZXhwbGljaXRseSBzZXQgaXNMaXZlOiBmYWxzZVxuICAgICAgICAvLyBkbyBub3RoaW5nXG4gICAgICAgIGlmICghYmVhY29uPy5iZWFjb25JbmZvPy5saXZlKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBhd2FpdCB0aGlzLnVwZGF0ZUJlYWNvbkV2ZW50KGJlYWNvbiwgeyBsaXZlOiBmYWxzZSB9KTtcbiAgICAgICAgLy8gcHJ1bmUgZnJvbSBsb2NhbCBzdG9yZVxuICAgICAgICByZW1vdmVMb2NhbGx5Q3JlYXRlQmVhY29uRXZlbnRJZChiZWFjb24uYmVhY29uSW5mb0lkKTtcbiAgICB9O1xuXG4gICAgLyoqXG4gICAgICogTGlzdGVuZXJzXG4gICAgICovXG5cbiAgICBwcml2YXRlIG9uTmV3QmVhY29uID0gKF9ldmVudDogTWF0cml4RXZlbnQsIGJlYWNvbjogQmVhY29uKTogdm9pZCA9PiB7XG4gICAgICAgIGlmICghdGhpcy5tYXRyaXhDbGllbnQgfHwgIWlzT3duQmVhY29uKGJlYWNvbiwgdGhpcy5tYXRyaXhDbGllbnQuZ2V0VXNlcklkKCkhKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuYWRkQmVhY29uKGJlYWNvbik7XG4gICAgICAgIHRoaXMuY2hlY2tMaXZlbmVzcygpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBUaGlzIHdpbGwgYmUgY2FsbGVkIHdoZW4gYSBiZWFjb24gaXMgcmVwbGFjZWRcbiAgICAgKi9cbiAgICBwcml2YXRlIG9uVXBkYXRlQmVhY29uID0gKF9ldmVudDogTWF0cml4RXZlbnQsIGJlYWNvbjogQmVhY29uKTogdm9pZCA9PiB7XG4gICAgICAgIGlmICghdGhpcy5tYXRyaXhDbGllbnQgfHwgIWlzT3duQmVhY29uKGJlYWNvbiwgdGhpcy5tYXRyaXhDbGllbnQuZ2V0VXNlcklkKCkhKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5jaGVja0xpdmVuZXNzKCk7XG4gICAgICAgIGJlYWNvbi5tb25pdG9yTGl2ZW5lc3MoKTtcbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBvbkRlc3Ryb3lCZWFjb24gPSAoYmVhY29uSWRlbnRpZmllcjogQmVhY29uSWRlbnRpZmllcik6IHZvaWQgPT4ge1xuICAgICAgICAvLyBjaGVjayBpZiB3ZSBjYXJlIGFib3V0IHRoaXMgYmVhY29uXG4gICAgICAgIGlmICghdGhpcy5iZWFjb25zLmhhcyhiZWFjb25JZGVudGlmaWVyKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5jaGVja0xpdmVuZXNzKCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25CZWFjb25MaXZlbmVzcyA9IChpc0xpdmU6IGJvb2xlYW4sIGJlYWNvbjogQmVhY29uKTogdm9pZCA9PiB7XG4gICAgICAgIC8vIGNoZWNrIGlmIHdlIGNhcmUgYWJvdXQgdGhpcyBiZWFjb25cbiAgICAgICAgaWYgKCF0aGlzLmJlYWNvbnMuaGFzKGJlYWNvbi5pZGVudGlmaWVyKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gYmVhY29uIGV4cGlyZWQsIHVwZGF0ZSBiZWFjb24gdG8gdW4tYWxpdmUgc3RhdGVcbiAgICAgICAgaWYgKCFpc0xpdmUpIHtcbiAgICAgICAgICAgIHRoaXMuc3RvcEJlYWNvbihiZWFjb24uaWRlbnRpZmllcik7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLmNoZWNrTGl2ZW5lc3MoKTtcblxuICAgICAgICB0aGlzLmVtaXQoT3duQmVhY29uU3RvcmVFdmVudC5MaXZlbmVzc0NoYW5nZSwgdGhpcy5nZXRMaXZlQmVhY29uSWRzKCkpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBDaGVjayBmb3IgY2hhbmdlcyBpbiBtZW1iZXJzaGlwIGluIHJvb21zIHdpdGggYmVhY29uc1xuICAgICAqIGFuZCBzdG9wIG1vbml0b3JpbmcgYmVhY29ucyBpbiByb29tcyB1c2VyIGlzIG5vIGxvbmdlciBtZW1iZXIgb2ZcbiAgICAgKi9cbiAgICBwcml2YXRlIG9uUm9vbVN0YXRlTWVtYmVycyA9IChfZXZlbnQ6IE1hdHJpeEV2ZW50LCByb29tU3RhdGU6IFJvb21TdGF0ZSwgbWVtYmVyOiBSb29tTWVtYmVyKTogdm9pZCA9PiB7XG4gICAgICAgIC8vIG5vIGJlYWNvbnMgZm9yIHRoaXMgcm9vbSwgaWdub3JlXG4gICAgICAgIGlmIChcbiAgICAgICAgICAgICF0aGlzLm1hdHJpeENsaWVudCB8fFxuICAgICAgICAgICAgIXRoaXMuYmVhY29uc0J5Um9vbUlkLmhhcyhyb29tU3RhdGUucm9vbUlkKSB8fFxuICAgICAgICAgICAgbWVtYmVyLnVzZXJJZCAhPT0gdGhpcy5tYXRyaXhDbGllbnQuZ2V0VXNlcklkKClcbiAgICAgICAgKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUT0RPIGNoZWNrIHBvd2VybGV2ZWxzIGhlcmVcbiAgICAgICAgLy8gaW4gUFNGLTc5N1xuXG4gICAgICAgIC8vIHN0b3Agd2F0Y2hpbmcgYmVhY29ucyBpbiByb29tcyB3aGVyZSB1c2VyIGlzIG5vIGxvbmdlciBhIG1lbWJlclxuICAgICAgICBpZiAobWVtYmVyLm1lbWJlcnNoaXAgPT09IEtub3duTWVtYmVyc2hpcC5MZWF2ZSB8fCBtZW1iZXIubWVtYmVyc2hpcCA9PT0gS25vd25NZW1iZXJzaGlwLkJhbikge1xuICAgICAgICAgICAgdGhpcy5iZWFjb25zQnlSb29tSWQuZ2V0KHJvb21TdGF0ZS5yb29tSWQpPy5mb3JFYWNoKHRoaXMucmVtb3ZlQmVhY29uKTtcbiAgICAgICAgICAgIHRoaXMuYmVhY29uc0J5Um9vbUlkLmRlbGV0ZShyb29tU3RhdGUucm9vbUlkKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBTdGF0ZSBtYW5hZ2VtZW50XG4gICAgICovXG5cbiAgICAvKipcbiAgICAgKiBMaXZlIGJlYWNvbiBpZHMgdGhhdCBkbyBub3QgaGF2ZSB3aXJlIGVycm9yc1xuICAgICAqL1xuICAgIHByaXZhdGUgZ2V0IGhlYWx0aHlMaXZlQmVhY29uSWRzKCk6IHN0cmluZ1tdIHtcbiAgICAgICAgcmV0dXJuIHRoaXMubGl2ZUJlYWNvbklkcy5maWx0ZXIoXG4gICAgICAgICAgICAoYmVhY29uSWQpID0+ICF0aGlzLmJlYWNvbkhhc0xvY2F0aW9uUHVibGlzaEVycm9yKGJlYWNvbklkKSAmJiAhdGhpcy5iZWFjb25VcGRhdGVFcnJvcnMuaGFzKGJlYWNvbklkKSxcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBAaW50ZXJuYWwgcHVibGljIGZvciB0ZXN0IG9ubHlcbiAgICAgKi9cbiAgICBwdWJsaWMgcmVpbml0aWFsaXNlQmVhY29uU3RhdGUgPSAoKTogdm9pZCA9PiB7XG4gICAgICAgIHRoaXMuY2xlYXJCZWFjb25zKCk7XG4gICAgICAgIHRoaXMuaW5pdGlhbGlzZUJlYWNvblN0YXRlKCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgaW5pdGlhbGlzZUJlYWNvblN0YXRlID0gKCk6IHZvaWQgPT4ge1xuICAgICAgICBpZiAoIXRoaXMubWF0cml4Q2xpZW50KSByZXR1cm47XG4gICAgICAgIGNvbnN0IHVzZXJJZCA9IHRoaXMubWF0cml4Q2xpZW50LmdldFNhZmVVc2VySWQoKTtcbiAgICAgICAgY29uc3QgdmlzaWJsZVJvb21zID0gdGhpcy5tYXRyaXhDbGllbnQuZ2V0VmlzaWJsZVJvb21zKFxuICAgICAgICAgICAgU2V0dGluZ3NTdG9yZS5nZXRWYWx1ZShcImZlYXR1cmVfZHluYW1pY19yb29tX3ByZWRlY2Vzc29yc1wiKSxcbiAgICAgICAgKTtcblxuICAgICAgICB2aXNpYmxlUm9vbXMuZm9yRWFjaCgocm9vbSkgPT4ge1xuICAgICAgICAgICAgY29uc3Qgcm9vbVN0YXRlID0gcm9vbS5jdXJyZW50U3RhdGU7XG4gICAgICAgICAgICBjb25zdCBiZWFjb25zID0gcm9vbVN0YXRlLmJlYWNvbnM7XG4gICAgICAgICAgICBjb25zdCBvd25CZWFjb25zQXJyYXkgPSBbLi4uYmVhY29ucy52YWx1ZXMoKV0uZmlsdGVyKChiZWFjb24pID0+IGlzT3duQmVhY29uKGJlYWNvbiwgdXNlcklkKSk7XG4gICAgICAgICAgICBvd25CZWFjb25zQXJyYXkuZm9yRWFjaCgoYmVhY29uKSA9PiB0aGlzLmFkZEJlYWNvbihiZWFjb24pKTtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgdGhpcy5jaGVja0xpdmVuZXNzKCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgYWRkQmVhY29uID0gKGJlYWNvbjogQmVhY29uKTogdm9pZCA9PiB7XG4gICAgICAgIHRoaXMuYmVhY29ucy5zZXQoYmVhY29uLmlkZW50aWZpZXIsIGJlYWNvbik7XG5cbiAgICAgICAgaWYgKCF0aGlzLmJlYWNvbnNCeVJvb21JZC5oYXMoYmVhY29uLnJvb21JZCkpIHtcbiAgICAgICAgICAgIHRoaXMuYmVhY29uc0J5Um9vbUlkLnNldChiZWFjb24ucm9vbUlkLCBuZXcgU2V0PHN0cmluZz4oKSk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLmJlYWNvbnNCeVJvb21JZC5nZXQoYmVhY29uLnJvb21JZCkhLmFkZChiZWFjb24uaWRlbnRpZmllcik7XG5cbiAgICAgICAgYmVhY29uLm1vbml0b3JMaXZlbmVzcygpO1xuICAgIH07XG5cbiAgICAvKipcbiAgICAgKiBSZW1vdmUgbGlzdGVuZXJzIGZvciBhIGdpdmVuIGJlYWNvblxuICAgICAqIHJlbW92ZSBmcm9tIHN0YXRlXG4gICAgICogYW5kIHVwZGF0ZSBsaXZlbmVzcyBpZiBjaGFuZ2VkXG4gICAgICovXG4gICAgcHJpdmF0ZSByZW1vdmVCZWFjb24gPSAoYmVhY29uSWQ6IHN0cmluZyk6IHZvaWQgPT4ge1xuICAgICAgICBpZiAoIXRoaXMuYmVhY29ucy5oYXMoYmVhY29uSWQpKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5iZWFjb25zLmdldChiZWFjb25JZCkhLmRlc3Ryb3koKTtcbiAgICAgICAgdGhpcy5iZWFjb25zLmRlbGV0ZShiZWFjb25JZCk7XG5cbiAgICAgICAgdGhpcy5jaGVja0xpdmVuZXNzKCk7XG4gICAgfTtcblxuICAgIHByaXZhdGUgY2hlY2tMaXZlbmVzcyA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgY29uc3QgbG9jYWxseUNyZWF0ZWRCZWFjb25FdmVudElkcyA9IGdldExvY2FsbHlDcmVhdGVkQmVhY29uRXZlbnRJZHMoKTtcbiAgICAgICAgY29uc3QgcHJldkxpdmVCZWFjb25JZHMgPSB0aGlzLmdldExpdmVCZWFjb25JZHMoKTtcbiAgICAgICAgdGhpcy5saXZlQmVhY29uSWRzID0gWy4uLnRoaXMuYmVhY29ucy52YWx1ZXMoKV1cbiAgICAgICAgICAgIC5maWx0ZXIoXG4gICAgICAgICAgICAgICAgKGJlYWNvbikgPT5cbiAgICAgICAgICAgICAgICAgICAgYmVhY29uLmlzTGl2ZSAmJlxuICAgICAgICAgICAgICAgICAgICAvLyBvbmx5IGJlYWNvbnMgY3JlYXRlZCBvbiB0aGlzIGRldmljZSBzaG91bGQgYmUgc2hhcmVkIHRvXG4gICAgICAgICAgICAgICAgICAgIGxvY2FsbHlDcmVhdGVkQmVhY29uRXZlbnRJZHMuaW5jbHVkZXMoYmVhY29uLmJlYWNvbkluZm9JZCksXG4gICAgICAgICAgICApXG4gICAgICAgICAgICAuc29ydChzb3J0QmVhY29uc0J5TGF0ZXN0Q3JlYXRpb24pXG4gICAgICAgICAgICAubWFwKChiZWFjb24pID0+IGJlYWNvbi5pZGVudGlmaWVyKTtcblxuICAgICAgICBjb25zdCBkaWZmID0gYXJyYXlEaWZmKHByZXZMaXZlQmVhY29uSWRzLCB0aGlzLmxpdmVCZWFjb25JZHMpO1xuXG4gICAgICAgIGlmIChkaWZmLmFkZGVkLmxlbmd0aCB8fCBkaWZmLnJlbW92ZWQubGVuZ3RoKSB7XG4gICAgICAgICAgICB0aGlzLmVtaXQoT3duQmVhY29uU3RvcmVFdmVudC5MaXZlbmVzc0NoYW5nZSwgdGhpcy5saXZlQmVhY29uSWRzKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHB1Ymxpc2ggY3VycmVudCBsb2NhdGlvbiBpbW1lZGlhdGVseVxuICAgICAgICAvLyB3aGVuIHRoZXJlIGFyZSBuZXcgbGl2ZSBiZWFjb25zXG4gICAgICAgIC8vIGFuZCB3ZSBhbHJlYWR5IGhhdmUgYSBsaXZlIG1vbml0b3JcbiAgICAgICAgLy8gc28gZmlyc3QgcG9zaXRpb24gaXMgcHVibGlzaGVkIHF1aWNrbHlcbiAgICAgICAgLy8gZXZlbiB3aGVuIHRhcmdldCBpcyBzdGF0aW9uYXJ5XG4gICAgICAgIC8vXG4gICAgICAgIC8vIHdoZW4gdGhlcmUgaXMgbm8gZXhpc3RpbmcgbGl2ZSBtb25pdG9yXG4gICAgICAgIC8vIGl0IHdpbGwgYmUgY3JlYXRlZCBiZWxvdyBieSB0b2dnbGVQb2xsaW5nTG9jYXRpb25cbiAgICAgICAgLy8gYW5kIHB1Ymxpc2ggZmlyc3QgcG9zaXRpb24gcXVpY2tseVxuICAgICAgICBpZiAoZGlmZi5hZGRlZC5sZW5ndGggJiYgdGhpcy5pc01vbml0b3JpbmdMaXZlTG9jYXRpb24pIHtcbiAgICAgICAgICAgIHRoaXMucHVibGlzaEN1cnJlbnRMb2NhdGlvblRvQmVhY29ucygpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gaWYgb3ZlcmFsbCBsaXZlbmVzcyBjaGFuZ2VkXG4gICAgICAgIGlmICghIXByZXZMaXZlQmVhY29uSWRzPy5sZW5ndGggIT09ICEhdGhpcy5saXZlQmVhY29uSWRzLmxlbmd0aCkge1xuICAgICAgICAgICAgdGhpcy50b2dnbGVQb2xsaW5nTG9jYXRpb24oKTtcbiAgICAgICAgfVxuICAgIH07XG5cbiAgICBwdWJsaWMgY3JlYXRlTGl2ZUJlYWNvbiA9IGFzeW5jIChcbiAgICAgICAgcm9vbUlkOiBSb29tW1wicm9vbUlkXCJdLFxuICAgICAgICBiZWFjb25JbmZvQ29udGVudDogTUJlYWNvbkluZm9FdmVudENvbnRlbnQsXG4gICAgKTogUHJvbWlzZTx2b2lkPiA9PiB7XG4gICAgICAgIGlmICghdGhpcy5tYXRyaXhDbGllbnQpIHJldHVybjtcbiAgICAgICAgLy8gZXhwbGljaXRseSBzdG9wIGFueSBsaXZlIGJlYWNvbnMgdGhpcyB1c2VyIGhhc1xuICAgICAgICAvLyB0byBlbnN1cmUgdGhleSByZW1haW4gc3RvcHBlZFxuICAgICAgICAvLyBpZiB0aGUgbmV3IHJlcGxhY2luZyBiZWFjb24gaXMgcmVkYWN0ZWRcbiAgICAgICAgY29uc3QgZXhpc3RpbmdMaXZlQmVhY29uSWRzRm9yUm9vbSA9IHRoaXMuZ2V0TGl2ZUJlYWNvbklkcyhyb29tSWQpO1xuICAgICAgICBhd2FpdCBQcm9taXNlLmFsbChleGlzdGluZ0xpdmVCZWFjb25JZHNGb3JSb29tLm1hcCgoYmVhY29uSWQpID0+IHRoaXMuc3RvcEJlYWNvbihiZWFjb25JZCkpKTtcblxuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY2FtZWxjYXNlXG4gICAgICAgIGNvbnN0IHsgZXZlbnRfaWQgfSA9IGF3YWl0IGRvTWF5YmVMb2NhbFJvb21BY3Rpb24oXG4gICAgICAgICAgICByb29tSWQsXG4gICAgICAgICAgICAoYWN0dWFsUm9vbUlkOiBzdHJpbmcpID0+IHRoaXMubWF0cml4Q2xpZW50IS51bnN0YWJsZV9jcmVhdGVMaXZlQmVhY29uKGFjdHVhbFJvb21JZCwgYmVhY29uSW5mb0NvbnRlbnQpLFxuICAgICAgICAgICAgdGhpcy5tYXRyaXhDbGllbnQsXG4gICAgICAgICk7XG5cbiAgICAgICAgc3RvcmVMb2NhbGx5Q3JlYXRlQmVhY29uRXZlbnRJZChldmVudF9pZCk7XG4gICAgfTtcblxuICAgIC8qKlxuICAgICAqIEdlb2xvY2F0aW9uXG4gICAgICovXG5cbiAgICBwcml2YXRlIHRvZ2dsZVBvbGxpbmdMb2NhdGlvbiA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgaWYgKCEhdGhpcy5saXZlQmVhY29uSWRzLmxlbmd0aCkge1xuICAgICAgICAgICAgdGhpcy5zdGFydFBvbGxpbmdMb2NhdGlvbigpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5zdG9wUG9sbGluZ0xvY2F0aW9uKCk7XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBzdGFydFBvbGxpbmdMb2NhdGlvbiA9IGFzeW5jICgpOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICAgICAgLy8gY2xlYXIgYW55IGV4aXN0aW5nIGludGVydmFsXG4gICAgICAgIHRoaXMuc3RvcFBvbGxpbmdMb2NhdGlvbigpO1xuXG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0aGlzLmNsZWFyUG9zaXRpb25XYXRjaCA9IHdhdGNoUG9zaXRpb24odGhpcy5vbldhdGNoZWRQb3NpdGlvbiwgdGhpcy5vbkdlb2xvY2F0aW9uRXJyb3IpO1xuICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uR2VvbG9jYXRpb25FcnJvcihlcnJvci5tZXNzYWdlIGFzIEdlb2xvY2F0aW9uRXJyb3IpO1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKFwiVW5leHBlY3RlZCBlcnJvclwiLCBlcnJvcik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBkb24ndCBzZXQgbG9jYXRpb25JbnRlcnZhbCBpZiBnZW9sb2NhdGlvbiBmYWlsZWQgdG8gc2V0dXBcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMubG9jYXRpb25JbnRlcnZhbCA9IHdpbmRvdy5zZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXRoaXMubGFzdFB1Ymxpc2hlZFBvc2l0aW9uVGltZXN0YW1wKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gaWYgcG9zaXRpb24gd2FzIGxhc3QgdXBkYXRlZCBTVEFUSUNfVVBEQVRFX0lOVEVSVkFMIG1zIGFnbyBvciBtb3JlXG4gICAgICAgICAgICAvLyBnZXQgb3VyIHBvc2l0aW9uIGFuZCBwdWJsaXNoIGl0XG4gICAgICAgICAgICBpZiAodGhpcy5sYXN0UHVibGlzaGVkUG9zaXRpb25UaW1lc3RhbXAgPD0gRGF0ZS5ub3coKSAtIFNUQVRJQ19VUERBVEVfSU5URVJWQUwpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnB1Ymxpc2hDdXJyZW50TG9jYXRpb25Ub0JlYWNvbnMoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSwgU1RBVElDX1VQREFURV9JTlRFUlZBTCk7XG5cbiAgICAgICAgdGhpcy5lbWl0KE93bkJlYWNvblN0b3JlRXZlbnQuTW9uaXRvcmluZ0xpdmVQb3NpdGlvbik7XG4gICAgfTtcblxuICAgIHByaXZhdGUgc3RvcFBvbGxpbmdMb2NhdGlvbiA9ICgpOiB2b2lkID0+IHtcbiAgICAgICAgY2xlYXJJbnRlcnZhbCh0aGlzLmxvY2F0aW9uSW50ZXJ2YWwpO1xuICAgICAgICB0aGlzLmxvY2F0aW9uSW50ZXJ2YWwgPSB1bmRlZmluZWQ7XG4gICAgICAgIHRoaXMubGFzdFB1Ymxpc2hlZFBvc2l0aW9uVGltZXN0YW1wID0gdW5kZWZpbmVkO1xuXG4gICAgICAgIGlmICh0aGlzLmNsZWFyUG9zaXRpb25XYXRjaCkge1xuICAgICAgICAgICAgdGhpcy5jbGVhclBvc2l0aW9uV2F0Y2goKTtcbiAgICAgICAgICAgIHRoaXMuY2xlYXJQb3NpdGlvbldhdGNoID0gdW5kZWZpbmVkO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5lbWl0KE93bkJlYWNvblN0b3JlRXZlbnQuTW9uaXRvcmluZ0xpdmVQb3NpdGlvbik7XG4gICAgfTtcblxuICAgIHByaXZhdGUgb25XYXRjaGVkUG9zaXRpb24gPSAocG9zaXRpb246IEdlb2xvY2F0aW9uUG9zaXRpb24pOiB2b2lkID0+IHtcbiAgICAgICAgY29uc3QgdGltZWRHZW9Qb3NpdGlvbiA9IG1hcEdlb2xvY2F0aW9uUG9zaXRpb25Ub1RpbWVkR2VvKHBvc2l0aW9uKTtcblxuICAgICAgICAvLyBpZiB0aGlzIGlzIG91ciBmaXJzdCBwb3NpdGlvbiwgcHVibGlzaCBpbW1lZGlhdGVseVxuICAgICAgICBpZiAoIXRoaXMubGFzdFB1Ymxpc2hlZFBvc2l0aW9uVGltZXN0YW1wKSB7XG4gICAgICAgICAgICB0aGlzLnB1Ymxpc2hMb2NhdGlvblRvQmVhY29ucyh0aW1lZEdlb1Bvc2l0aW9uKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuZGVib3VuY2VkUHVibGlzaExvY2F0aW9uVG9