metaapi.cloud-sdk
Version:
SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)
293 lines (292 loc) • 40.1 kB
JavaScript
'use strict';
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _async_to_generator(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
import TimeoutError from '../timeoutError';
import OptionsValidator from '../optionsValidator';
import LoggerManager from '../../logger';
let SynchronizationThrottler = class SynchronizationThrottler {
/**
* Initializes the synchronization throttler
*/ start() {
if (!this._removeOldSyncIdsInterval) {
this._removeOldSyncIdsInterval = setInterval(()=>this._removeOldSyncIdsJob(), 1000);
this._processQueueInterval = setInterval(()=>this._processQueueJob(), 1000);
}
}
/**
* Deinitializes the throttler
*/ stop() {
clearInterval(this._removeOldSyncIdsInterval);
this._removeOldSyncIdsInterval = null;
clearInterval(this._processQueueInterval);
this._processQueueInterval = null;
}
_removeOldSyncIdsJob() {
var _this = this;
return _async_to_generator(function*() {
const now = Date.now();
for (let key of Object.keys(_this._synchronizationIds)){
if (now - _this._synchronizationIds[key] > _this._synchronizationTimeoutInSeconds * 1000) {
delete _this._synchronizationIds[key];
}
}
while(_this._synchronizationQueue.length && Date.now() - _this._synchronizationQueue[0].queueTime > _this._queueTimeoutInSeconds * 1000){
_this._removeFromQueue(_this._synchronizationQueue[0].synchronizationId, 'timeout');
}
_this._advanceQueue();
})();
}
/**
* Fills a synchronization slot with synchronization id
* @param {String} synchronizationId synchronization id
*/ updateSynchronizationId(synchronizationId) {
if (this._accountsBySynchronizationIds[synchronizationId]) {
this._synchronizationIds[synchronizationId] = Date.now();
}
}
/**
* Returns the list of currently synchronizing account ids
*/ get synchronizingAccounts() {
const synchronizingAccounts = [];
Object.keys(this._synchronizationIds).forEach((key)=>{
const accountData = this._accountsBySynchronizationIds[key];
if (accountData && !synchronizingAccounts.includes(accountData.accountId)) {
synchronizingAccounts.push(accountData.accountId);
}
});
return synchronizingAccounts;
}
/**
* Returns the list of currenly active synchronization ids
* @return {String[]} synchronization ids
*/ get activeSynchronizationIds() {
return Object.keys(this._accountsBySynchronizationIds);
}
/**
* Returns the amount of maximum allowed concurrent synchronizations
* @return {number} maximum allowed concurrent synchronizations
*/ get maxConcurrentSynchronizations() {
const calculatedMax = Math.max(Math.ceil(this._client.subscribedAccountIds(this._instanceNumber, this._socketInstanceIndex, this._region).length / 10), 1);
return Math.min(calculatedMax, this._maxConcurrentSynchronizations);
}
/**
* Returns flag whether there are free slots for synchronization requests
* @return {Boolean} flag whether there are free slots for synchronization requests
*/ get isSynchronizationAvailable() {
if (this._client.socketInstances[this._region][this._instanceNumber].reduce((acc, socketInstance)=>acc + socketInstance.synchronizationThrottler.synchronizingAccounts.length, 0) >= this._maxConcurrentSynchronizations) {
return false;
}
return this.synchronizingAccounts.length < this.maxConcurrentSynchronizations;
}
/**
* Removes synchronizations from queue and from the list by parameters
* @param {String} accountId account id
* @param {Number} instanceIndex account instance index
* @param {String} host account host name
*/ removeIdByParameters(accountId, instanceIndex, host) {
for (let key of Object.keys(this._accountsBySynchronizationIds)){
if (this._accountsBySynchronizationIds[key].accountId === accountId && this._accountsBySynchronizationIds[key].instanceIndex === instanceIndex && this._accountsBySynchronizationIds[key].host === host) {
this.removeSynchronizationId(key);
}
}
}
/**
* Removes synchronization id from slots and removes ids for the same account from the queue
* @param {String} synchronizationId synchronization id
*/ removeSynchronizationId(synchronizationId) {
if (this._accountsBySynchronizationIds[synchronizationId]) {
const accountId = this._accountsBySynchronizationIds[synchronizationId].accountId;
const instanceIndex = this._accountsBySynchronizationIds[synchronizationId].instanceIndex;
const host = this._accountsBySynchronizationIds[synchronizationId].host;
for (let key of Object.keys(this._accountsBySynchronizationIds)){
if (this._accountsBySynchronizationIds[key].accountId === accountId && this._accountsBySynchronizationIds[key].instanceIndex === instanceIndex && this._accountsBySynchronizationIds[key].host === host) {
this._removeFromQueue(key, 'cancel');
delete this._accountsBySynchronizationIds[key];
}
}
}
if (this._synchronizationIds[synchronizationId]) {
delete this._synchronizationIds[synchronizationId];
}
this._advanceQueue();
}
/**
* Clears synchronization ids on disconnect
*/ onDisconnect() {
this._synchronizationQueue.forEach((synchronization)=>{
synchronization.resolve('cancel');
});
this._synchronizationIds = {};
this._accountsBySynchronizationIds = {};
this._synchronizationQueue = [];
this.stop();
this.start();
}
_advanceQueue() {
let index = 0;
while(this.isSynchronizationAvailable && this._synchronizationQueue.length && index < this._synchronizationQueue.length){
const queueItem = this._synchronizationQueue[index];
queueItem.resolve('synchronize');
this.updateSynchronizationId(queueItem.synchronizationId);
index++;
}
}
_removeFromQueue(synchronizationId, result) {
this._synchronizationQueue.forEach((syncItem, i)=>{
if (syncItem.synchronizationId === synchronizationId) {
syncItem.resolve(result);
}
});
this._synchronizationQueue = this._synchronizationQueue.filter((item)=>item.synchronizationId !== synchronizationId);
}
_processQueueJob() {
var _this = this;
return _async_to_generator(function*() {
try {
while(_this._synchronizationQueue.length){
const queueItem = _this._synchronizationQueue[0];
yield _this._synchronizationQueue[0].promise;
if (_this._synchronizationQueue.length && _this._synchronizationQueue[0].synchronizationId === queueItem.synchronizationId) {
_this._synchronizationQueue.shift();
}
}
} catch (err) {
_this._logger.error('Error processing queue job', err);
}
})();
}
/**
* Schedules to send a synchronization request for account
* @param {String} accountId account id
* @param {Object} request request to send
* @param {Object} hashes terminal state hashes
*/ scheduleSynchronize(accountId, request, hashes) {
var _this = this;
return _async_to_generator(function*() {
const synchronizationId = request.requestId;
for (let key of Object.keys(_this._accountsBySynchronizationIds)){
if (_this._accountsBySynchronizationIds[key].accountId === accountId && _this._accountsBySynchronizationIds[key].instanceIndex === request.instanceIndex && _this._accountsBySynchronizationIds[key].host === request.host) {
_this.removeSynchronizationId(key);
}
}
_this._accountsBySynchronizationIds[synchronizationId] = {
accountId,
instanceIndex: request.instanceIndex,
host: request.host
};
if (!_this.isSynchronizationAvailable) {
let resolve;
let requestResolve = new Promise((res)=>{
resolve = res;
});
_this._synchronizationQueue.push({
synchronizationId: synchronizationId,
promise: requestResolve,
resolve,
queueTime: Date.now()
});
const result = yield requestResolve;
if (result === 'cancel') {
return false;
} else if (result === 'timeout') {
throw new TimeoutError(`Account ${accountId} synchronization ${synchronizationId}` + ' timed out in synchronization queue');
}
}
_this.updateSynchronizationId(synchronizationId);
request.specificationsHashes = hashes.specificationsHashes;
request.positionsHashes = hashes.positionsHashes;
request.ordersHashes = hashes.ordersHashes;
yield _this._client.rpcRequest(accountId, request);
return true;
})();
}
/**
* Constructs the synchronization throttler
* @param {MetaApiWebsocketClient} client MetaApi websocket client
* @param {Number} socketInstanceIndex index of socket instance that uses the throttler
* @param {Number} instanceNumber instance index number
* @param {String} region server region
* @param {SynchronizationThrottlerOpts} opts synchronization throttler options
*/ constructor(client, socketInstanceIndex, instanceNumber, region, opts){
_define_property(this, "_maxConcurrentSynchronizations", void 0);
_define_property(this, "_queueTimeoutInSeconds", void 0);
_define_property(this, "_synchronizationTimeoutInSeconds", void 0);
_define_property(this, "_client", void 0);
_define_property(this, "_region", void 0);
_define_property(this, "_socketInstanceIndex", void 0);
_define_property(this, "_synchronizationIds", void 0);
_define_property(this, "_accountsBySynchronizationIds", void 0);
_define_property(this, "_synchronizationQueue", void 0);
_define_property(this, "_removeOldSyncIdsInterval", void 0);
_define_property(this, "_processQueueInterval", void 0);
_define_property(this, "_instanceNumber", void 0);
_define_property(this, "_logger", void 0);
const validator = new OptionsValidator();
opts = opts || {};
this._maxConcurrentSynchronizations = validator.validateNonZero(opts.maxConcurrentSynchronizations, 15, 'synchronizationThrottler.maxConcurrentSynchronizations');
this._queueTimeoutInSeconds = validator.validateNonZero(opts.queueTimeoutInSeconds, 300, 'synchronizationThrottler.queueTimeoutInSeconds');
this._synchronizationTimeoutInSeconds = validator.validateNonZero(opts.synchronizationTimeoutInSeconds, 10, 'synchronizationThrottler.synchronizationTimeoutInSeconds');
this._client = client;
this._region = region;
this._socketInstanceIndex = socketInstanceIndex;
this._synchronizationIds = {};
this._accountsBySynchronizationIds = {};
this._synchronizationQueue = [];
this._removeOldSyncIdsInterval = null;
this._processQueueInterval = null;
this._instanceNumber = instanceNumber;
this._logger = LoggerManager.getLogger('SynchronizationThrottler');
}
};
/**
* Options for synchronization throttler
* @typedef {Object} SynchronizationThrottlerOpts
* @property {Number} [maxConcurrentSynchronizations] amount of maximum allowed concurrent synchronizations
* @property {Number} [queueTimeoutInSeconds] allowed time for a synchronization in queue
* @property {Number} [synchronizationTimeoutInSeconds] time after which a synchronization slot
* is freed to be used by another synchronization
*/ /**
* Synchronization throttler used to limit the amount of concurrent synchronizations to prevent application
* from being overloaded due to excessive number of synchronisation responses being sent.
*/ export { SynchronizationThrottler as default };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBUaW1lb3V0RXJyb3IgZnJvbSAnLi4vdGltZW91dEVycm9yJztcbmltcG9ydCBPcHRpb25zVmFsaWRhdG9yIGZyb20gJy4uL29wdGlvbnNWYWxpZGF0b3InO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIsIHtMb2dnZXJ9IGZyb20gJy4uLy4uL2xvZ2dlcic7XG5pbXBvcnQgTWV0YUFwaVdlYnNvY2tldENsaWVudCBmcm9tICcuL21ldGFBcGlXZWJzb2NrZXQuY2xpZW50JztcblxuLyoqXG4gKiBPcHRpb25zIGZvciBzeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyXG4gKiBAdHlwZWRlZiB7T2JqZWN0fSBTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzXG4gKiBAcHJvcGVydHkge051bWJlcn0gW21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zXSBhbW91bnQgb2YgbWF4aW11bSBhbGxvd2VkIGNvbmN1cnJlbnQgc3luY2hyb25pemF0aW9uc1xuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtxdWV1ZVRpbWVvdXRJblNlY29uZHNdIGFsbG93ZWQgdGltZSBmb3IgYSBzeW5jaHJvbml6YXRpb24gaW4gcXVldWVcbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kc10gdGltZSBhZnRlciB3aGljaCBhIHN5bmNocm9uaXphdGlvbiBzbG90XG4gKiBpcyBmcmVlZCB0byBiZSB1c2VkIGJ5IGFub3RoZXIgc3luY2hyb25pemF0aW9uXG4gKi9cblxuLyoqXG4gKiBTeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyIHVzZWQgdG8gbGltaXQgdGhlIGFtb3VudCBvZiBjb25jdXJyZW50IHN5bmNocm9uaXphdGlvbnMgdG8gcHJldmVudCBhcHBsaWNhdGlvblxuICogZnJvbSBiZWluZyBvdmVybG9hZGVkIGR1ZSB0byBleGNlc3NpdmUgbnVtYmVyIG9mIHN5bmNocm9uaXNhdGlvbiByZXNwb25zZXMgYmVpbmcgc2VudC5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU3luY2hyb25pemF0aW9uVGhyb3R0bGVyIHtcbiAgXG4gIHByaXZhdGUgX21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zOiBhbnk7XG4gIHByaXZhdGUgX3F1ZXVlVGltZW91dEluU2Vjb25kczogYW55O1xuICBwcml2YXRlIF9zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzOiBhbnk7XG4gIHByaXZhdGUgX2NsaWVudDogTWV0YUFwaVdlYnNvY2tldENsaWVudDtcbiAgcHJpdmF0ZSBfcmVnaW9uOiBhbnk7XG4gIHByaXZhdGUgX3NvY2tldEluc3RhbmNlSW5kZXg6IGFueTtcbiAgcHJpdmF0ZSBfc3luY2hyb25pemF0aW9uSWRzOiB7fTtcbiAgcHJpdmF0ZSBfYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkczoge307XG4gIHByaXZhdGUgX3N5bmNocm9uaXphdGlvblF1ZXVlOiBhbnlbXTtcbiAgcHJpdmF0ZSBfcmVtb3ZlT2xkU3luY0lkc0ludGVydmFsOiBhbnk7XG4gIHByaXZhdGUgX3Byb2Nlc3NRdWV1ZUludGVydmFsOiBhbnk7XG4gIHByaXZhdGUgX2luc3RhbmNlTnVtYmVyOiBhbnk7XG4gIHByaXZhdGUgX2xvZ2dlcjogTG9nZ2VyO1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIHRoZSBzeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyXG4gICAqIEBwYXJhbSB7TWV0YUFwaVdlYnNvY2tldENsaWVudH0gY2xpZW50IE1ldGFBcGkgd2Vic29ja2V0IGNsaWVudFxuICAgKiBAcGFyYW0ge051bWJlcn0gc29ja2V0SW5zdGFuY2VJbmRleCBpbmRleCBvZiBzb2NrZXQgaW5zdGFuY2UgdGhhdCB1c2VzIHRoZSB0aHJvdHRsZXJcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGluc3RhbmNlTnVtYmVyIGluc3RhbmNlIGluZGV4IG51bWJlclxuICAgKiBAcGFyYW0ge1N0cmluZ30gcmVnaW9uIHNlcnZlciByZWdpb25cbiAgICogQHBhcmFtIHtTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzfSBvcHRzIHN5bmNocm9uaXphdGlvbiB0aHJvdHRsZXIgb3B0aW9uc1xuICAgKi9cbiAgY29uc3RydWN0b3IoY2xpZW50LCBzb2NrZXRJbnN0YW5jZUluZGV4LCBpbnN0YW5jZU51bWJlciwgcmVnaW9uLCBvcHRzOiBTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzKSB7XG4gICAgY29uc3QgdmFsaWRhdG9yID0gbmV3IE9wdGlvbnNWYWxpZGF0b3IoKTtcbiAgICBvcHRzID0gb3B0cyB8fCB7fTtcbiAgICB0aGlzLl9tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucyA9IHZhbGlkYXRvci52YWxpZGF0ZU5vblplcm8ob3B0cy5tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucywgMTUsXG4gICAgICAnc3luY2hyb25pemF0aW9uVGhyb3R0bGVyLm1heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zJyk7XG4gICAgdGhpcy5fcXVldWVUaW1lb3V0SW5TZWNvbmRzID0gdmFsaWRhdG9yLnZhbGlkYXRlTm9uWmVybyhvcHRzLnF1ZXVlVGltZW91dEluU2Vjb25kcywgMzAwLFxuICAgICAgJ3N5bmNocm9uaXphdGlvblRocm90dGxlci5xdWV1ZVRpbWVvdXRJblNlY29uZHMnKTtcbiAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzID0gdmFsaWRhdG9yLnZhbGlkYXRlTm9uWmVybyhvcHRzLnN5bmNocm9uaXphdGlvblRpbWVvdXRJblNlY29uZHMsIDEwLFxuICAgICAgJ3N5bmNocm9uaXphdGlvblRocm90dGxlci5zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzJyk7XG4gICAgdGhpcy5fY2xpZW50ID0gY2xpZW50O1xuICAgIHRoaXMuX3JlZ2lvbiA9IHJlZ2lvbjtcbiAgICB0aGlzLl9zb2NrZXRJbnN0YW5jZUluZGV4ID0gc29ja2V0SW5zdGFuY2VJbmRleDtcbiAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25JZHMgPSB7fTtcbiAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzID0ge307XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUgPSBbXTtcbiAgICB0aGlzLl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwgPSBudWxsO1xuICAgIHRoaXMuX3Byb2Nlc3NRdWV1ZUludGVydmFsID0gbnVsbDtcbiAgICB0aGlzLl9pbnN0YW5jZU51bWJlciA9IGluc3RhbmNlTnVtYmVyO1xuICAgIHRoaXMuX2xvZ2dlciA9IExvZ2dlck1hbmFnZXIuZ2V0TG9nZ2VyKCdTeW5jaHJvbml6YXRpb25UaHJvdHRsZXInKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplcyB0aGUgc3luY2hyb25pemF0aW9uIHRocm90dGxlclxuICAgKi9cbiAgc3RhcnQoKSB7XG4gICAgaWYoIXRoaXMuX3JlbW92ZU9sZFN5bmNJZHNJbnRlcnZhbCkge1xuICAgICAgdGhpcy5fcmVtb3ZlT2xkU3luY0lkc0ludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4gdGhpcy5fcmVtb3ZlT2xkU3luY0lkc0pvYigpLCAxMDAwKTtcbiAgICAgIHRoaXMuX3Byb2Nlc3NRdWV1ZUludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4gdGhpcy5fcHJvY2Vzc1F1ZXVlSm9iKCksIDEwMDApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBEZWluaXRpYWxpemVzIHRoZSB0aHJvdHRsZXJcbiAgICovXG4gIHN0b3AoKSB7XG4gICAgY2xlYXJJbnRlcnZhbCh0aGlzLl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwpO1xuICAgIHRoaXMuX3JlbW92ZU9sZFN5bmNJZHNJbnRlcnZhbCA9IG51bGw7XG4gICAgY2xlYXJJbnRlcnZhbCh0aGlzLl9wcm9jZXNzUXVldWVJbnRlcnZhbCk7XG4gICAgdGhpcy5fcHJvY2Vzc1F1ZXVlSW50ZXJ2YWwgPSBudWxsO1xuICB9XG5cbiAgYXN5bmMgX3JlbW92ZU9sZFN5bmNJZHNKb2IoKSB7XG4gICAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgICBmb3IgKGxldCBrZXkgb2YgT2JqZWN0LmtleXModGhpcy5fc3luY2hyb25pemF0aW9uSWRzKSkge1xuICAgICAgaWYgKChub3cgLSB0aGlzLl9zeW5jaHJvbml6YXRpb25JZHNba2V5XSkgPiB0aGlzLl9zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzICogMTAwMCkge1xuICAgICAgICBkZWxldGUgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzW2tleV07XG4gICAgICB9XG4gICAgfVxuICAgIHdoaWxlICh0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5sZW5ndGggJiYgKERhdGUubm93KCkgLSB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVswXS5xdWV1ZVRpbWUpID4gXG4gICAgICAgIHRoaXMuX3F1ZXVlVGltZW91dEluU2Vjb25kcyAqIDEwMDApIHtcbiAgICAgIHRoaXMuX3JlbW92ZUZyb21RdWV1ZSh0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVswXS5zeW5jaHJvbml6YXRpb25JZCwgJ3RpbWVvdXQnKTtcbiAgICB9XG4gICAgdGhpcy5fYWR2YW5jZVF1ZXVlKCk7XG4gIH1cblxuICAvKipcbiAgICogRmlsbHMgYSBzeW5jaHJvbml6YXRpb24gc2xvdCB3aXRoIHN5bmNocm9uaXphdGlvbiBpZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gc3luY2hyb25pemF0aW9uSWQgc3luY2hyb25pemF0aW9uIGlkXG4gICAqL1xuICB1cGRhdGVTeW5jaHJvbml6YXRpb25JZChzeW5jaHJvbml6YXRpb25JZCkge1xuICAgIGlmKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdKSB7XG4gICAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdID0gRGF0ZS5ub3coKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgbGlzdCBvZiBjdXJyZW50bHkgc3luY2hyb25pemluZyBhY2NvdW50IGlkc1xuICAgKi9cbiAgZ2V0IHN5bmNocm9uaXppbmdBY2NvdW50cygpIHtcbiAgICBjb25zdCBzeW5jaHJvbml6aW5nQWNjb3VudHMgPSBbXTtcbiAgICBPYmplY3Qua2V5cyh0aGlzLl9zeW5jaHJvbml6YXRpb25JZHMpLmZvckVhY2goa2V5ID0+IHtcbiAgICAgIGNvbnN0IGFjY291bnREYXRhID0gdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldO1xuICAgICAgaWYoYWNjb3VudERhdGEgJiYgIXN5bmNocm9uaXppbmdBY2NvdW50cy5pbmNsdWRlcyhhY2NvdW50RGF0YS5hY2NvdW50SWQpKSB7XG4gICAgICAgIHN5bmNocm9uaXppbmdBY2NvdW50cy5wdXNoKGFjY291bnREYXRhLmFjY291bnRJZCk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgcmV0dXJuIHN5bmNocm9uaXppbmdBY2NvdW50cztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBsaXN0IG9mIGN1cnJlbmx5IGFjdGl2ZSBzeW5jaHJvbml6YXRpb24gaWRzXG4gICAqIEByZXR1cm4ge1N0cmluZ1tdfSBzeW5jaHJvbml6YXRpb24gaWRzXG4gICAqL1xuICBnZXQgYWN0aXZlU3luY2hyb25pemF0aW9uSWRzKCkge1xuICAgIHJldHVybiBPYmplY3Qua2V5cyh0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBhbW91bnQgb2YgbWF4aW11bSBhbGxvd2VkIGNvbmN1cnJlbnQgc3luY2hyb25pemF0aW9uc1xuICAgKiBAcmV0dXJuIHtudW1iZXJ9IG1heGltdW0gYWxsb3dlZCBjb25jdXJyZW50IHN5bmNocm9uaXphdGlvbnNcbiAgICovXG4gIGdldCBtYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucygpIHtcbiAgICBjb25zdCBjYWxjdWxhdGVkTWF4ID0gTWF0aC5tYXgoTWF0aC5jZWlsKFxuICAgICAgdGhpcy5fY2xpZW50LnN1YnNjcmliZWRBY2NvdW50SWRzKHRoaXMuX2luc3RhbmNlTnVtYmVyLCB0aGlzLl9zb2NrZXRJbnN0YW5jZUluZGV4LCB0aGlzLl9yZWdpb24pLmxlbmd0aCAvIDEwKSwgMSk7XG4gICAgcmV0dXJuIE1hdGgubWluKGNhbGN1bGF0ZWRNYXgsIHRoaXMuX21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGZsYWcgd2hldGhlciB0aGVyZSBhcmUgZnJlZSBzbG90cyBmb3Igc3luY2hyb25pemF0aW9uIHJlcXVlc3RzXG4gICAqIEByZXR1cm4ge0Jvb2xlYW59IGZsYWcgd2hldGhlciB0aGVyZSBhcmUgZnJlZSBzbG90cyBmb3Igc3luY2hyb25pemF0aW9uIHJlcXVlc3RzXG4gICAqL1xuICBnZXQgaXNTeW5jaHJvbml6YXRpb25BdmFpbGFibGUoKSB7XG4gICAgaWYgKHRoaXMuX2NsaWVudC5zb2NrZXRJbnN0YW5jZXNbdGhpcy5fcmVnaW9uXVt0aGlzLl9pbnN0YW5jZU51bWJlcl0ucmVkdWNlKChhY2MsIHNvY2tldEluc3RhbmNlKSA9PiBcbiAgICAgIGFjYyArIHNvY2tldEluc3RhbmNlLnN5bmNocm9uaXphdGlvblRocm90dGxlci5zeW5jaHJvbml6aW5nQWNjb3VudHMubGVuZ3RoLCAwKSA+PVxuICAgICAgdGhpcy5fbWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnMpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuc3luY2hyb25pemluZ0FjY291bnRzLmxlbmd0aCA8IHRoaXMubWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnM7XG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyBzeW5jaHJvbml6YXRpb25zIGZyb20gcXVldWUgYW5kIGZyb20gdGhlIGxpc3QgYnkgcGFyYW1ldGVyc1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGluc3RhbmNlSW5kZXggYWNjb3VudCBpbnN0YW5jZSBpbmRleFxuICAgKiBAcGFyYW0ge1N0cmluZ30gaG9zdCBhY2NvdW50IGhvc3QgbmFtZVxuICAgKi9cbiAgcmVtb3ZlSWRCeVBhcmFtZXRlcnMoYWNjb3VudElkLCBpbnN0YW5jZUluZGV4LCBob3N0KSB7XG4gICAgZm9yIChsZXQga2V5IG9mIE9iamVjdC5rZXlzKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHMpKSB7XG4gICAgICBpZih0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uYWNjb3VudElkID09PSBhY2NvdW50SWQgJiZcbiAgICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaW5zdGFuY2VJbmRleCA9PT0gaW5zdGFuY2VJbmRleCAmJlxuICAgICAgICAgIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5ob3N0ID09PSBob3N0KSB7XG4gICAgICAgIHRoaXMucmVtb3ZlU3luY2hyb25pemF0aW9uSWQoa2V5KTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyBzeW5jaHJvbml6YXRpb24gaWQgZnJvbSBzbG90cyBhbmQgcmVtb3ZlcyBpZHMgZm9yIHRoZSBzYW1lIGFjY291bnQgZnJvbSB0aGUgcXVldWVcbiAgICogQHBhcmFtIHtTdHJpbmd9IHN5bmNocm9uaXphdGlvbklkIHN5bmNocm9uaXphdGlvbiBpZFxuICAgKi9cbiAgcmVtb3ZlU3luY2hyb25pemF0aW9uSWQoc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICBpZiAodGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1tzeW5jaHJvbml6YXRpb25JZF0pIHtcbiAgICAgIGNvbnN0IGFjY291bnRJZCA9IHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdLmFjY291bnRJZDtcbiAgICAgIGNvbnN0IGluc3RhbmNlSW5kZXggPSB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXS5pbnN0YW5jZUluZGV4O1xuICAgICAgY29uc3QgaG9zdCA9IHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdLmhvc3Q7XG4gICAgICBmb3IgKGxldCBrZXkgb2YgT2JqZWN0LmtleXModGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcykpIHtcbiAgICAgICAgaWYodGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmFjY291bnRJZCA9PT0gYWNjb3VudElkICYmIFxuICAgICAgICAgIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5pbnN0YW5jZUluZGV4ID09PSBpbnN0YW5jZUluZGV4ICYmXG4gICAgICAgICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmhvc3QgPT09IGhvc3QpIHtcbiAgICAgICAgICB0aGlzLl9yZW1vdmVGcm9tUXVldWUoa2V5LCAnY2FuY2VsJyk7XG4gICAgICAgICAgZGVsZXRlIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBpZih0aGlzLl9zeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdKSB7XG4gICAgICBkZWxldGUgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXTtcbiAgICB9XG4gICAgdGhpcy5fYWR2YW5jZVF1ZXVlKCk7XG4gIH1cblxuICAvKipcbiAgICogQ2xlYXJzIHN5bmNocm9uaXphdGlvbiBpZHMgb24gZGlzY29ubmVjdFxuICAgKi9cbiAgb25EaXNjb25uZWN0KCkge1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlLmZvckVhY2goc3luY2hyb25pemF0aW9uID0+IHtcbiAgICAgIHN5bmNocm9uaXphdGlvbi5yZXNvbHZlKCdjYW5jZWwnKTtcbiAgICB9KTtcbiAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25JZHMgPSB7fTtcbiAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzID0ge307XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUgPSBbXTtcbiAgICB0aGlzLnN0b3AoKTtcbiAgICB0aGlzLnN0YXJ0KCk7XG4gIH1cblxuICBfYWR2YW5jZVF1ZXVlKCkge1xuICAgIGxldCBpbmRleCA9IDA7XG4gICAgd2hpbGUodGhpcy5pc1N5bmNocm9uaXphdGlvbkF2YWlsYWJsZSAmJiB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5sZW5ndGggJiYgXG4gICAgICAgIGluZGV4IDwgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUubGVuZ3RoKSB7XG4gICAgICBjb25zdCBxdWV1ZUl0ZW0gPSB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVtpbmRleF07XG4gICAgICBxdWV1ZUl0ZW0ucmVzb2x2ZSgnc3luY2hyb25pemUnKTtcbiAgICAgIHRoaXMudXBkYXRlU3luY2hyb25pemF0aW9uSWQocXVldWVJdGVtLnN5bmNocm9uaXphdGlvbklkKTtcbiAgICAgIGluZGV4Kys7XG4gICAgfVxuICB9XG5cbiAgX3JlbW92ZUZyb21RdWV1ZShzeW5jaHJvbml6YXRpb25JZCwgcmVzdWx0KSB7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUuZm9yRWFjaCgoc3luY0l0ZW0sIGkpID0+IHtcbiAgICAgIGlmKHN5bmNJdGVtLnN5bmNocm9uaXphdGlvbklkID09PSBzeW5jaHJvbml6YXRpb25JZCkge1xuICAgICAgICBzeW5jSXRlbS5yZXNvbHZlKHJlc3VsdCk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUgPSB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5maWx0ZXIoaXRlbSA9PiBcbiAgICAgIGl0ZW0uc3luY2hyb25pemF0aW9uSWQgIT09IHN5bmNocm9uaXphdGlvbklkKTtcbiAgfVxuXG4gIGFzeW5jIF9wcm9jZXNzUXVldWVKb2IoKSB7XG4gICAgdHJ5IHtcbiAgICAgIHdoaWxlICh0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5sZW5ndGgpIHtcbiAgICAgICAgY29uc3QgcXVldWVJdGVtID0gdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbMF07XG4gICAgICAgIGF3YWl0IHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlWzBdLnByb21pc2U7XG4gICAgICAgIGlmKHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlLmxlbmd0aCAmJiB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVswXS5zeW5jaHJvbml6YXRpb25JZCA9PT0gXG4gICAgICAgICAgICBxdWV1ZUl0ZW0uc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICAgICAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5zaGlmdCgpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICB0aGlzLl9sb2dnZXIuZXJyb3IoJ0Vycm9yIHByb2Nlc3NpbmcgcXVldWUgam9iJywgZXJyKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU2NoZWR1bGVzIHRvIHNlbmQgYSBzeW5jaHJvbml6YXRpb24gcmVxdWVzdCBmb3IgYWNjb3VudFxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtPYmplY3R9IHJlcXVlc3QgcmVxdWVzdCB0byBzZW5kXG4gICAqIEBwYXJhbSB7T2JqZWN0fSBoYXNoZXMgdGVybWluYWwgc3RhdGUgaGFzaGVzXG4gICAqL1xuICBhc3luYyBzY2hlZHVsZVN5bmNocm9uaXplKGFjY291bnRJZCwgcmVxdWVzdCwgaGFzaGVzKSB7XG4gICAgY29uc3Qgc3luY2hyb25pemF0aW9uSWQgPSByZXF1ZXN0LnJlcXVlc3RJZDtcbiAgICBmb3IgKGxldCBrZXkgb2YgT2JqZWN0LmtleXModGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcykpIHtcbiAgICAgIGlmKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5hY2NvdW50SWQgPT09IGFjY291bnRJZCAmJlxuICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaW5zdGFuY2VJbmRleCA9PT0gcmVxdWVzdC5pbnN0YW5jZUluZGV4ICYmXG4gICAgICAgIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5ob3N0ID09PSByZXF1ZXN0Lmhvc3QpIHtcbiAgICAgICAgdGhpcy5yZW1vdmVTeW5jaHJvbml6YXRpb25JZChrZXkpO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXSA9IHthY2NvdW50SWQsIGluc3RhbmNlSW5kZXg6IHJlcXVlc3QuaW5zdGFuY2VJbmRleCxcbiAgICAgIGhvc3Q6IHJlcXVlc3QuaG9zdH07XG4gICAgaWYoIXRoaXMuaXNTeW5jaHJvbml6YXRpb25BdmFpbGFibGUpIHtcbiAgICAgIGxldCByZXNvbHZlO1xuICAgICAgbGV0IHJlcXVlc3RSZXNvbHZlID0gbmV3IFByb21pc2UoKHJlcykgPT4ge1xuICAgICAgICByZXNvbHZlID0gcmVzO1xuICAgICAgfSk7XG4gICAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5wdXNoKHtcbiAgICAgICAgc3luY2hyb25pemF0aW9uSWQ6IHN5bmNocm9uaXphdGlvbklkLFxuICAgICAgICBwcm9taXNlOiByZXF1ZXN0UmVzb2x2ZSxcbiAgICAgICAgcmVzb2x2ZSxcbiAgICAgICAgcXVldWVUaW1lOiBEYXRlLm5vdygpXG4gICAgICB9KTtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHJlcXVlc3RSZXNvbHZlO1xuICAgICAgaWYocmVzdWx0ID09PSAnY2FuY2VsJykge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9IGVsc2UgaWYocmVzdWx0ID09PSAndGltZW91dCcpIHtcbiAgICAgICAgdGhyb3cgbmV3IFRpbWVvdXRFcnJvcihgQWNjb3VudCAke2FjY291bnRJZH0gc3luY2hyb25pemF0aW9uICR7c3luY2hyb25pemF0aW9uSWR9YCArXG4gICAgICAgICcgdGltZWQgb3V0IGluIHN5bmNocm9uaXphdGlvbiBxdWV1ZScpO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLnVwZGF0ZVN5bmNocm9uaXphdGlvbklkKHN5bmNocm9uaXphdGlvbklkKTtcbiAgICByZXF1ZXN0LnNwZWNpZmljYXRpb25zSGFzaGVzID0gaGFzaGVzLnNwZWNpZmljYXRpb25zSGFzaGVzO1xuICAgIHJlcXVlc3QucG9zaXRpb25zSGFzaGVzID0gaGFzaGVzLnBvc2l0aW9uc0hhc2hlcztcbiAgICByZXF1ZXN0Lm9yZGVyc0hhc2hlcyA9IGhhc2hlcy5vcmRlcnNIYXNoZXM7XG4gICAgYXdhaXQgdGhpcy5fY2xpZW50LnJwY1JlcXVlc3QoYWNjb3VudElkLCByZXF1ZXN0KTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuXG59XG5cbi8qKlxuICogT3B0aW9ucyBmb3Igc3luY2hyb25pemF0aW9uIHRocm90dGxlclxuICovXG5leHBvcnQgdHlwZSBTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzID0ge1xuXG4gIC8qKlxuICAgKiBhbW91bnQgb2YgbWF4aW11bSBhbGxvd2VkIGNvbmN1cnJlbnQgc3luY2hyb25pemF0aW9uc1xuICAgKi9cbiAgbWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnM/OiBudW1iZXIsXG5cbiAgLyoqXG4gICAqIGFsbG93ZWQgdGltZSBmb3IgYSBzeW5jaHJvbml6YXRpb24gaW4gcXVldWVcbiAgICovXG4gIHF1ZXVlVGltZW91dEluU2Vjb25kcz86IG51bWJlcixcblxuICAvKipcbiAgICogdGltZSBhZnRlciB3aGljaCBhIHN5bmNocm9uaXphdGlvbiBzbG90XG4gICAqIGlzIGZyZWVkIHRvIGJlIHVzZWQgYnkgYW5vdGhlciBzeW5jaHJvbml6YXRpb25cbiAgICovXG4gIHN5bmNocm9uaXphdGlvblRpbWVvdXRJblNlY29uZHM/OiBudW1iZXJcbn1cbiJdLCJuYW1lcyI6WyJUaW1lb3V0RXJyb3IiLCJPcHRpb25zVmFsaWRhdG9yIiwiTG9nZ2VyTWFuYWdlciIsIlN5bmNocm9uaXphdGlvblRocm90dGxlciIsInN0YXJ0IiwiX3JlbW92ZU9sZFN5bmNJZHNJbnRlcnZhbCIsInNldEludGVydmFsIiwiX3JlbW92ZU9sZFN5bmNJZHNKb2IiLCJfcHJvY2Vzc1F1ZXVlSW50ZXJ2YWwiLCJfcHJvY2Vzc1F1ZXVlSm9iIiwic3RvcCIsImNsZWFySW50ZXJ2YWwiLCJub3ciLCJEYXRlIiwia2V5IiwiT2JqZWN0Iiwia2V5cyIsIl9zeW5jaHJvbml6YXRpb25JZHMiLCJfc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcyIsIl9zeW5jaHJvbml6YXRpb25RdWV1ZSIsImxlbmd0aCIsInF1ZXVlVGltZSIsIl9xdWV1ZVRpbWVvdXRJblNlY29uZHMiLCJfcmVtb3ZlRnJvbVF1ZXVlIiwic3luY2hyb25pemF0aW9uSWQiLCJfYWR2YW5jZVF1ZXVlIiwidXBkYXRlU3luY2hyb25pemF0aW9uSWQiLCJfYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcyIsInN5bmNocm9uaXppbmdBY2NvdW50cyIsImZvckVhY2giLCJhY2NvdW50RGF0YSIsImluY2x1ZGVzIiwiYWNjb3VudElkIiwicHVzaCIsImFjdGl2ZVN5bmNocm9uaXphdGlvbklkcyIsIm1heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zIiwiY2FsY3VsYXRlZE1heCIsIk1hdGgiLCJtYXgiLCJjZWlsIiwiX2NsaWVudCIsInN1YnNjcmliZWRBY2NvdW50SWRzIiwiX2luc3RhbmNlTnVtYmVyIiwiX3NvY2tldEluc3RhbmNlSW5kZXgiLCJfcmVnaW9uIiwibWluIiwiX21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zIiwiaXNTeW5jaHJvbml6YXRpb25BdmFpbGFibGUiLCJzb2NrZXRJbnN0YW5jZXMiLCJyZWR1Y2UiLCJhY2MiLCJzb2NrZXRJbnN0YW5jZSIsInN5bmNocm9uaXphdGlvblRocm90dGxlciIsInJlbW92ZUlkQnlQYXJhbWV0ZXJzIiwiaW5zdGFuY2VJbmRleCIsImhvc3QiLCJyZW1vdmVTeW5jaHJvbml6YXRpb25JZCIsIm9uRGlzY29ubmVjdCIsInN5bmNocm9uaXphdGlvbiIsInJlc29sdmUiLCJpbmRleCIsInF1ZXVlSXRlbSIsInJlc3VsdCIsInN5bmNJdGVtIiwiaSIsImZpbHRlciIsIml0ZW0iLCJwcm9taXNlIiwic2hpZnQiLCJlcnIiLCJfbG9nZ2VyIiwiZXJyb3IiLCJzY2hlZHVsZVN5bmNocm9uaXplIiwicmVxdWVzdCIsImhhc2hlcyIsInJlcXVlc3RJZCIsInJlcXVlc3RSZXNvbHZlIiwiUHJvbWlzZSIsInJlcyIsInNwZWNpZmljYXRpb25zSGFzaGVzIiwicG9zaXRpb25zSGFzaGVzIiwib3JkZXJzSGFzaGVzIiwicnBjUmVxdWVzdCIsImNvbnN0cnVjdG9yIiwiY2xpZW50Iiwic29ja2V0SW5zdGFuY2VJbmRleCIsImluc3RhbmNlTnVtYmVyIiwicmVnaW9uIiwib3B0cyIsInZhbGlkYXRvciIsInZhbGlkYXRlTm9uWmVybyIsInF1ZXVlVGltZW91dEluU2Vjb25kcyIsInN5bmNocm9uaXphdGlvblRpbWVvdXRJblNlY29uZHMiLCJnZXRMb2dnZXIiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRUEsT0FBT0Esa0JBQWtCLGtCQUFrQjtBQUMzQyxPQUFPQyxzQkFBc0Isc0JBQXNCO0FBQ25ELE9BQU9DLG1CQUE2QixlQUFl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