mage-module-shard
Version:
Server-side sharding and broadcasting module for MAGE apps.
652 lines • 75.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const cluster = require("cluster");
const crypto = require("crypto");
const mage = require("mage");
const { msgServer, serviceDiscovery } = mage.core;
const isFunction = require('is-function-x');
const errorToObject = require('serialize-error');
const shortid = require('shortid');
/**
* RemoteError class
*
* RemoteErrors encapsulate an error that occured
* either while sending a ShardedRequest, or
* while executing the request on the remote MAGE node.
*
* @export
* @class RemoteError
*/
class RemoteError extends Error {
constructor(data) {
super(data.message);
Object.assign(this, data);
this.name = 'RemoteError';
}
}
exports.RemoteError = RemoteError;
/**
* ShardedRequests
*
* @export
* @class ShardedRequest
*/
class ShardedRequest {
constructor(id, mmrpNode, eventName, target, method, args) {
this.id = id;
this.mmrpNode = mmrpNode;
this.eventName = eventName;
this.target = target;
this.method = method;
this.args = args;
}
/**
* Send the request
*
* Note that the sending module is responsible for listening for the
* response, and passing it to this request through `resolve` or
* `reject`.
*
* The sending module may also call `reject` should it judge a request
* has timed out.
*
* @returns
* @memberof ShardedRequest
*/
async send() {
const Envelope = msgServer.mmrp.Envelope;
const messages = [this.id, this.method, JSON.stringify(this.args)];
const source = [this.mmrpNode.clusterId, this.mmrpNode.identity];
const requestEnvelope = new Envelope(this.eventName, messages, this.target, source, 'TRACK_ROUTE');
return new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
this.mmrpNode.send(requestEnvelope, 1, (error) => {
if (error) {
return reject(error);
}
});
});
}
}
exports.ShardedRequest = ShardedRequest;
function serializeError(data) {
return JSON.stringify(errorToObject(data));
}
/**
* AbstractShardedModule
*
* This abstract module will take care of maintaining
* a consistent view of the cluster, and of creating
* proxy objects (or shards) which can then be used to
* consistently forward requests to a server.
*
* See Readme.md for more details on how to use this class.
*
* @export
* @class AbstractShardedModule
*/
class AbstractShardedModule {
/**
* Creates an instance of AbstractShardedModul
*
* @param {string} [name]
* @memberof AbstractShardedModule
*/
constructor(name, hashingAlgorithm = 'md5', gcTimeoutTime = 5 * 1000) {
/**
* Hashing algorithm to for sharding
*/
this.hashingAlgorithm = 'md5';
/**
* Hashes of available workers in the clusters
*
* A given node in the cluster may be running more than
* one worker; each worker will have its own accessible address.
*
* @type {string[]}
* @memberof AbstractShardedModule
*/
this.addressHashes = [];
/**
* Hash to MMRP address key-value map
*
* @type {{ [hash: string]: string[] }}
* @memberof AbstractShardedModule
*/
this.clusterAddressMap = {};
/**
* Number of nodes in the cluster
*
* @type {number}
* @memberof AbstractShardedModule
*/
this.clusterSize = 0;
/**
* Pendng requests that are being executed on a remote MAGE node
*
* When requests are forwarded to a remote MAGE node, a reference to
* the original request will be placed here; upon reception of
* the response (an error or a value), the code execution
* will then continue locally.
*
* Requests will timeout after a certain amount of time
*
* @type {Map<IShardedRequestMeta, ShardedRequest>}
* @memberof AbstractShardedModule
*/
this.pendingRequests = new Map();
/**
* Key-value for fetching the pendingRequests
* map key
*
* This is used to allow quick by-reference access to
* pending requests stored in this.pendingRequests
*
* ```typescript
* const key = this.pendingRequestsKeyMap['some-id']
* const request = this.pendingRequests(key)
* ```
*
* @memberof AbstractShardedModule
*/
this.pendingRequestsKeyMap = {};
if (!name) {
name = this.getClassName();
}
this.name = name;
this.hashingAlgorithm = hashingAlgorithm;
this.REQUEST_EVENT_NAME = `sharded.${name}.request`;
this.RESPONSE_EVENT_NAME = `sharded.${name}.response`;
// Stalled requests garbage collection
this.scheduleGarbageCollection(gcTimeoutTime);
}
/**
* Setup method called by MAGE during initialization
*
* @param {mage.core.IState} _state
* @param {(error?: Error) => void} callback
* @memberof AbstractShardedModule
*/
async setup(_state, callback) {
this.logger = mage.logger.context('ShardedModule', this.name);
const { name, REQUEST_EVENT_NAME, RESPONSE_EVENT_NAME } = this;
// See https://github.com/dylang/shortid#shortidworkerinteger
/* istanbul ignore if */
if (cluster.isWorker) {
const id = cluster.worker.id;
shortid.worker(id % 16);
}
const mmrpNode = this.getMmrpNode();
/* istanbul ignore if */
if (!mmrpNode) {
return callback(new Error('mmrpNode does not exist. Did you configure mmrp and service discovery in your config file ?'));
}
// Cluster communication - run module method locally, and forward the response
mmrpNode.on(`delivery.${REQUEST_EVENT_NAME}`, async (requestEnvelope) => {
const request = requestEnvelope.messages;
const requestId = request.shift().toString();
let responseError;
let responseData;
try {
responseData = await this.onRequest(request);
}
catch (e) {
responseError = e;
}
finally {
const Envelope = msgServer.mmrp.Envelope;
const response = responseError ? 'false' : JSON.stringify(responseData);
const error = responseError ? serializeError(responseError) : 'false';
const messages = [requestId, response, error];
const responseEnvelope = new Envelope(this.RESPONSE_EVENT_NAME, messages, requestEnvelope.returnRoute);
mmrpNode.send(responseEnvelope, 1, (mmrpError) => {
/* istanbul ignore if */
if (mmrpError) {
this.logger.error('Error sending reply', error);
}
});
}
});
mmrpNode.on(`delivery.${RESPONSE_EVENT_NAME}`, async (envelope) => this.onResponse(envelope.messages));
// Service information tracking
const service = this.service = this.getServiceDiscovery().createService(name, 'tcp');
const address = [mmrpNode.clusterId, mmrpNode.identity];
service.on('up', (node) => this.registerNodeAddress(node));
service.on('down', (node) => this.unregisterNodeAddress(node));
service.announce(this.getPseudoPort(), address, (error) => {
/* istanbul ignore if */
if (error) {
return callback(error);
}
service.discover();
callback();
});
this.localNodeHash = this.hash(address.join(''));
}
/**
* Teardown method called by MAGE during shutdown
*
* @param {mage.core.IState} _state
* @param {(error?: Error) => void} callback
* @memberof AbstractShardedModule
*/
/* istanbul ignore next */
teardown(_state, callback) {
if (!this.service) {
callback();
}
this.service.close(callback);
}
/**
* Retrieve a shard using a deserialized shard instance (IShard)
*
* @param {IShard} shard
*/
getShard(shard) {
const { id } = shard;
const address = this.clusterAddressMap[id];
const toJson = function () {
return { id };
};
return new Proxy(this, {
get: (target, name) => {
// Return shard ID if requested
if (name === 'toJSON') {
return toJson;
}
if (name === 'id') {
return id;
}
if (name === 'inspect') {
return function () {
return { id };
};
}
// Only functions are made available by the proxy
const val = target[name];
if (isFunction(val) !== true) {
// Do not send local requests over the network
if (id === this.localNodeHash) {
return Promise.resolve(val);
}
this.assertClusterId(id);
return this.addPendingRequest(address, name).send();
}
// Encapsulate request
return async (...args) => {
if (id === this.localNodeHash) {
return val.bind(this)(...args);
}
this.assertClusterId(id);
return this.addPendingRequest(address, name, args).send();
};
},
has(_target, key) {
return key === 'id';
},
ownKeys() {
return ['id'];
}
});
}
/**
* Retrieve a shard using a deserialized shard instance (IShard)
*
*
*/
createBroadcast() {
return new Proxy(this, {
get: (target, name) => {
const val = target[name];
const hashes = Object.keys(this.clusterAddressMap);
const promises = [];
const responses = {};
const errors = {};
const data = async () => {
await Promise.all(promises);
if (Object.keys(errors).length > 0) {
return [errors, responses];
}
else {
return [null, responses];
}
};
if (isFunction(val) !== true) {
for (const id of hashes) {
const shard = target.getShard({ id });
const promise = shard[name]
.then((response) => responses[id] = response)
.catch((error) => errors[id] = error);
promises.push(promise);
}
return data();
}
// Encapsulate request
return async (...args) => {
for (const id of hashes) {
const shard = target.getShard({ id });
const promise = shard[name](...args)
.then((response) => responses[id] = response)
.catch((error) => errors[id] = error);
promises.push(promise);
}
return data();
};
},
/* istanbul ignore next */
has(_target, _key) {
return false;
},
/* istanbul ignore next */
ownKeys() {
return [];
}
});
}
/**
* Retrieve the local shard value
*
* Useful when you wish to make it so that future requests
* be routed to the current local server.
*
* Note that this does NOT return a proxy; this returns just the forwardable
* data of a proxy.
*/
getLocalShard() {
return { id: this.localNodeHash };
}
/**
* Create a new shard from a shard key
*
* You will want to call this to receive the shard
* reference. Then, for subsequent related calls,
* you will want to make sure to use the returned
* reference to forward your requests.
*
* @param {string} shardKey
*/
createShard(shardKey) {
const shardId = this.getShardId(shardKey);
return this.getShard(shardId);
}
/**
*
* @param {string} shardKey
*/
getShardId(shardKey) {
const hash = this.hash(shardKey, 'buffer');
let sum = 0;
// Fastest way I could find to walk through the Buffer
// See: https://stackoverflow.com/a/3762735/262831
for (let i = hash.length; i--;) {
sum += hash[i];
}
return {
id: this.addressHashes[sum % this.clusterSize]
};
}
/**
* Hash a string using the configured algorithm
*
* Inspired by https://github.com/3rd-Eden/node-hashring/blob/master/index.js#L13
*
* @param str
*/
hash(str, encoding = 'hex') {
return crypto.createHash(this.hashingAlgorithm).update(str).digest(encoding);
}
/* istanbul ignore next */
/* tslint:disable-next-line:prefer-function-over-method */
getMmrpNode() {
return msgServer.getMmrpNode();
}
/* istanbul ignore next */
/* tslint:disable-next-line:prefer-function-over-method */
getServiceDiscovery() {
return serviceDiscovery;
}
/**
* Make up a fake port
*
* The current serviceDiscovery will consider an announced service to
* be the same if the hostnames and ports are the same; this is an issue in
* our case, since each workers on a single server will be announced,
* and one might want to run more than one MAGE instance on a single server.
*
* To palliate to this issue, we pretend the PID is a port. Since PID's, depending
* on the subsystem, can possibly be very large (Linux's default is 32768, but is
* configurable through /proc/sys/kernel/pid_max), we use a modulo operator to limit
* how big the port can be.
*
* 2017/07 (stelcheck): This is a huge hack, and I am aware this may break under specific
* circumstances; namely, if other external services are announced on the same host,
* on the same port. If you believe you are hitting this case, please let me know.
*/
/* istanbul ignore next */
/* tslint:disable-next-line:prefer-function-over-method */
getPseudoPort() {
return (process.pid % 65535) + 1;
}
/**
* Retrieve the current classes's name
*
* @returns {string}
* @memberof AbstractShardedModule
*/
getClassName() {
return this.constructor.name;
}
/**
* Schedule garbage collection
*
* Note that this will cancel any previously scheduled GC, the timeout
* time value passed as an argument will be used for all future GC schedulings
*
* @private
* @param {number} gcTimeoutTime
* @memberof AbstractShardedModule
*/
scheduleGarbageCollection(gcTimeoutTime) {
if (this.gcTimer) {
clearTimeout(this.gcTimer);
}
this.gcTimer = setTimeout(() => {
// Clean up the timer reference
this.gcTimer = null;
const now = Date.now();
const keys = this.pendingRequests.keys();
for (const key of keys) {
// All other requests are valid, cancel iteration
if (key.timestamp >= now - gcTimeoutTime) {
break;
}
// Reject the request
const request = this.getAndDeletePendingRequest(key.id);
request.reject(new Error('Request timed out'));
}
// Schedule next GC
this.scheduleGarbageCollection(gcTimeoutTime);
}, gcTimeoutTime);
}
/**
*
*
* @private
* @param {mage.core.IServiceNode} node
* @memberof AbstractShardedModule
*/
registerNodeAddress(node) {
const address = node.data;
const hash = this.hash(address.join(''));
if (this.clusterAddressMap[hash]) {
this.logger.warning.data(node).log('Tried to re-register a known node');
return;
}
this.logger.notice.data(node).log('Registering new node');
// Add adress to our map
// これではなかろうか
this.clusterAddressMap[hash] = address;
// Add hash to our list of accessible addresses
this.addressHashes.push(hash);
this.addressHashes.sort();
this.clusterSize += 1;
}
/**
*
*
* @private
* @param {mage.core.IServiceNode} node
* @memberof AbstractShardedModule
*/
unregisterNodeAddress(node) {
const address = node.data;
const hash = this.hash(address.join(''));
// Remove hash from list of accessible address
const index = this.addressHashes.indexOf(hash);
if (index === -1) {
this.logger.warning.data(node).log('Tried to unregister an unknown node');
return;
}
this.logger.notice.data(node).log('Unegistering node');
this.addressHashes.splice(index, 1);
// Remove address from map
delete this.clusterAddressMap[hash];
// Reduce cluster size
this.clusterSize -= 1;
}
/**
*
*
* @private
* @param {MmrpEnvelopeMessage[]} messages
* @returns
* @memberof AbstractShardedModule
*/
async onRequest(messages) {
const rawAttributeName = messages.shift();
const rawArgs = messages.shift();
if (!rawAttributeName) {
throw new Error('Method name is missing');
}
const attributeName = rawAttributeName.toString();
if (!rawArgs) {
return this[attributeName];
}
const method = this[attributeName];
const args = JSON.parse(rawArgs.toString());
if (!method) {
throw new Error(`Method is not locally available (requested method: ${attributeName})`);
}
return method.apply(this, args);
}
/**
* Process a response
*
* @private
* @param {MmrpEnvelopeMessage[]} messages
* @returns
* @memberof AbstractShardedModule
*/
async onResponse(messages) {
const [requestId, rawData, rawError] = messages;
const request = this.getAndDeletePendingRequest(requestId.toString());
// For void methods (no return, or undefined attributes)
if (messages.length < 3) {
return request.resolve(undefined);
}
if (rawError && rawError.toString() !== 'false') {
const data = JSON.parse(rawError.toString());
const error = new RemoteError(data);
return request.reject(error);
}
request.resolve(JSON.parse(rawData.toString()));
}
/**
* Create a request, and add it to our list of pending requests
*
* The request is returned so that the calling code may
* call the request's `send` method and `await` a response.
*
* @private
* @param {string[]} target
* @param {string} method
* @param {any[]} args
* @returns
* @memberof AbstractShardedModule
*/
addPendingRequest(target, method, args) {
const id = shortid.generate();
const timestamp = Date.now();
const key = { id, timestamp };
this.pendingRequestsKeyMap[id] = key;
const request = new ShardedRequest(id, this.getMmrpNode(), this.REQUEST_EVENT_NAME, target, method, args);
this.pendingRequests.set(key, request);
return request;
}
/**
* Retrieve a pending response by request ID
*
* @private
* @param {string} id
* @returns
* @memberof AbstractShardedModule
*/
getPendingRequest(id) {
const key = this.pendingRequestsKeyMap[id];
if (!key) {
throw new Error(`Key not found in request key map (id: ${id})`);
}
const request = this.pendingRequests.get(key);
if (!request) {
throw new Error(`Pending request not found (id: ${key.id}, timestamp: ${key.timestamp})`);
}
return request;
}
/**
* Delete a pending response
*
* This is normally called once a request has been completed,
* or when a request has timed out.
*
* @private
* @param {string} id
* @memberof AbstractShardedModule
*/
deletePendingRequest(id) {
const key = this.pendingRequestsKeyMap[id];
if (!key) {
throw new Error(`Key not found in request key map (id: ${id})`);
}
delete this.pendingRequestsKeyMap[id];
this.pendingRequests.delete(key);
}
/**
* Retrieve a request, by ID, then delete it from the
* list of pending requests
*
* @private
* @param {string} id
* @returns
* @memberof AbstractShardedModule
*/
getAndDeletePendingRequest(id) {
const request = this.getPendingRequest(id);
this.deletePendingRequest(id);
return request;
}
/**
* Ensure that we know this cluster ID
*
* @param id
*/
assertClusterId(id) {
if (!this.clusterAddressMap[id]) {
throw new RemoteError({
message: 'Remote node is no longer available'
});
}
}
}
exports.default = AbstractShardedModule;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxtQ0FBa0M7QUFDbEMsaUNBQWdDO0FBQ2hDLDZCQUE0QjtBQUU1QixNQUFNLEVBQ0osU0FBUyxFQUNULGdCQUFnQixFQUNqQixHQUFHLElBQUksQ0FBQyxJQUFJLENBQUE7QUFFYixNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUE7QUFDM0MsTUFBTSxhQUFhLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixDQUFDLENBQUE7QUFDaEQsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFBO0FBOERsQzs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFhLFdBQVksU0FBUSxLQUFLO0lBQ3BDLFlBQVksSUFBUztRQUNuQixLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQ25CLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFBO1FBQ3pCLElBQUksQ0FBQyxJQUFJLEdBQUcsYUFBYSxDQUFBO0lBQzNCLENBQUM7Q0FDRjtBQU5ELGtDQU1DO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFhLGNBQWM7SUFzRXpCLFlBQVksRUFBVSxFQUFFLFFBQWEsRUFBRSxTQUFpQixFQUFFLE1BQWdCLEVBQUUsTUFBYyxFQUFFLElBQVk7UUFDdEcsSUFBSSxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUE7UUFDWixJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQTtRQUN4QixJQUFJLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQTtRQUMxQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtRQUNwQixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQTtRQUNwQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQTtJQUNsQixDQUFDO0lBRUQ7Ozs7Ozs7Ozs7OztP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QUMxQyxJQUFJLENBQUMsb0JBQW9CLENBQUMsRUFBRSxDQUFDLENBQUE7UUFFN0IsT0FBTyxPQUFPLENBQUE7SUFDaEIsQ0FBQztJQUVEOzs7O09BSUc7SUFDSyxlQUFlLENBQUMsRUFBVTtRQUNoQyxJQUFJLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEVBQUUsQ0FBQyxFQUFFO1lBQy9CLE1BQU0sSUFBSSxXQUFXLENBQUM7Z0JBQ3BCLE9BQU8sRUFBRSxvQ0FBb0M7YUFDOUMsQ0FBQyxDQUFBO1NBQ0g7SUFDSCxDQUFDO0NBQ0Y7QUF6dUJELHdDQXl1QkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBjbHVzdGVyIGZyb20gJ2NsdXN0ZXInXG5pbXBvcnQgKiBhcyBjcnlwdG8gZnJvbSAnY3J5cHRvJ1xuaW1wb3J0ICogYXMgbWFnZSBmcm9tICdtYWdlJ1xuXG5jb25zdCB7XG4gIG1zZ1NlcnZlcixcbiAgc2VydmljZURpc2NvdmVyeVxufSA9IG1hZ2UuY29yZVxuXG5jb25zdCBpc0Z1bmN0aW9uID0gcmVxdWlyZSgnaXMtZnVuY3Rpb24teCcpXG5jb25zdCBlcnJvclRvT2JqZWN0ID0gcmVxdWlyZSgnc2VyaWFsaXplLWVycm9yJylcbmNvbnN0IHNob3J0aWQgPSByZXF1aXJlKCdzaG9ydGlkJylcblxuLyoqXG4gKiBUaGlzIHR5cGUgaXMgbm90IGV4cG9zZWQgYXQgdGhlIG1vbWVudCBieSBNQUdFLCBzb1xuICogd2UgaW1wb3J0ZWQgaXRzIGRlZmluaXRpb24gYmFjayBpbnRvIHRoaXMgcHJvamVjdC5cbiAqL1xudHlwZSBNbXJwRW52ZWxvcGVNZXNzYWdlID0gc3RyaW5nIHwgQnVmZmVyXG5cbi8qKlxuICogU2hhcmQgbW9kdWxlIGF0dHJpYnV0ZSBtYXBwaW5nXG4gKi9cbmV4cG9ydCB0eXBlIFNoYXJkQXR0cmlidXRlPFQ+ID1cbiAgVCBleHRlbmRzICguLi5hcmdzOiBpbmZlciBQKSA9PiBQcm9taXNlPGluZmVyIFI+ID8gKC4uLmFyZ3M6IFApID0+IFByb21pc2U8Uj4gOlxuICBUIGV4dGVuZHMgKC4uLmFyZ3M6IGluZmVyIFApID0+IGluZmVyIFIgPyAoLi4uYXJnczogUCkgPT4gUHJvbWlzZTxSPiA6XG4gIFByb21pc2U8VD5cblxuLyoqXG4gKiBTaGFyZCBwcm94eSB0eXBlIHJldHVybmVkIHdpdGggbW9kdWxlLmNyZWF0ZVNoYXJkKClcbiAqL1xuZXhwb3J0IHR5cGUgU2hhcmQ8VCBleHRlbmRzIEFic3RyYWN0U2hhcmRlZE1vZHVsZT4gPSBJU2hhcmQgJiB7XG4gIHJlYWRvbmx5IFtBIGluIGtleW9mIFRdOiBTaGFyZEF0dHJpYnV0ZTxUW0FdPlxufVxuXG4vKipcbiAqIFJlc3BvbnNlcyBmb3IgYnJvYWRjYXN0XG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSUhhc2hNYXA8VD4ge1xuICBba2V5OiBzdHJpbmddOiBUXG59XG5cbi8qKlxuICogQnJvYWRjYXN0IG1vZHVsZSBhdHRyaWJ1dGUgbWFwcGluZ1xuICovXG5leHBvcnQgdHlwZSBCcm9hZGNhc3RBdHRyaWJ1dGU8VD4gPVxuICBUIGV4dGVuZHMgKC4uLmFyZ3M6IGluZmVyIFApID0+IFByb21pc2U8aW5mZXIgUz4gPyAoLi4uYXJnczogUCkgPT4gUHJvbWlzZTxbSUhhc2hNYXA8RXJyb3I+LCBJSGFzaE1hcDxTPl0+IDpcbiAgVCBleHRlbmRzICguLi5hcmdzOiBpbmZlciBQKSA9PiBpbmZlciBSID8gKC4uLmFyZ3M6IFApID0+IFByb21pc2U8W0lIYXNoTWFwPEVycm9yPiwgSUhhc2hNYXA8Uj5dPiA6XG4gIFByb21pc2U8W0lIYXNoTWFwPEVycm9yPiwgSUhhc2hNYXA8VD5dPlxuXG4vKipcbiAqIEJyb2FkY2FzdCBwcm94eSB0eXBlIHJldHVybmVkIHdpdGggbW9kdWxlLmNyZWF0ZUJyb2FkY2FzdCgpXG4gKi9cbmV4cG9ydCB0eXBlIEJyb2FkY2FzdDxUIGV4dGVuZHMgQWJzdHJhY3RTaGFyZGVkTW9kdWxlPiA9IHtcbiAgcmVhZG9ubHkgW0EgaW4ga2V5b2YgVF06IEJyb2FkY2FzdEF0dHJpYnV0ZTxUW0FdPlxufVxuXG4vKipcbiAqIElTaGFyZGVkUmVxdWVzdE1ldGFcbiAqXG4gKiBJU2hhcmRlZFJlcXVlc3RNZXRhIGFyZSB1c2VkIGFzIGtleXMgdG8gYWNjZXNzIFNoYXJkZWRSZXF1ZXN0XG4gKiBpbnN0YW5jZXMgc3RvcmVkIGluIGEgbWFwOyB0aGV5IGNvbnRhaW4gdGhlIGluZm9ybWF0aW9uIHJlcXVpcmVkIHRvIGlkZW50aWZ5XG4gKiB0aGUgcmVsYXRlZCByZXF1ZXN0LCBhbmQgYSB0aW1lc3RhbXAgdXNlZCB0byBkZXRlcm1pbmUgd2hldGhlciBhIHJlcXVlc3RcbiAqIGhhcyB0aW1lZCBvdXQgb3Igbm90LlxuICpcbiAqIEBleHBvcnRcbiAqIEBpbnRlcmZhY2UgSVNoYXJkZWRSZXF1ZXN0TWV0YVxuICovXG5leHBvcnQgaW50ZXJmYWNlIElTaGFyZGVkUmVxdWVzdE1ldGEge1xuICBpZDogc3RyaW5nXG4gIHRpbWVzdGFtcDogbnVtYmVyXG5cbn1cblxuLyoqXG4gKiBSZW1vdGVFcnJvciBjbGFzc1xuICpcbiAqIFJlbW90ZUVycm9ycyBlbmNhcHN1bGF0ZSBhbiBlcnJvciB0aGF0IG9jY3VyZWRcbiAqIGVpdGhlciB3aGlsZSBzZW5kaW5nIGEgU2hhcmRlZFJlcXVlc3QsIG9yXG4gKiB3aGlsZSBleGVjdXRpbmcgdGhlIHJlcXVlc3Qgb24gdGhlIHJlbW90ZSBNQUdFIG5vZGUuXG4gKlxuICogQGV4cG9ydFxuICogQGNsYXNzIFJlbW90ZUVycm9yXG4gKi9cbmV4cG9ydCBjbGFzcyBSZW1vdGVFcnJvciBleHRlbmRzIEVycm9yIHtcbiAgY29uc3RydWN0b3IoZGF0YTogYW55KSB7XG4gICAgc3VwZXIoZGF0YS5tZXNzYWdlKVxuICAgIE9iamVjdC5hc3NpZ24odGhpcywgZGF0YSlcbiAgICB0aGlzLm5hbWUgPSAnUmVtb3RlRXJyb3InXG4gIH1cbn1cblxuLyoqXG4gKiBTaGFyZGVkUmVxdWVzdHNcbiAqXG4gKiBAZXhwb3J0XG4gKiBAY2xhc3MgU2hhcmRlZFJlcXVlc3RcbiAqL1xuZXhwb3J0IGNsYXNzIFNoYXJkZWRSZXF1ZXN0IHtcbiAgLyoqXG4gICAqIFJlcXVlc3QgSURcbiAgICpcbiAgICogVGhpcyBJRCBpcyB1c2VkIGJ5IHRoZSByZW1vdGUgc2VydmVyIHdoZW4gc2VuZGluZyBiYWNrIGEgcmVzcG9uc2UsXG4gICAqIGFuZCBieSB0aGUgbG9jYWwgc2VydmVyIHRvIHJvdXRlIHRoZSByZXNwb25zZSBiYWNrIHRvIHRoZSBjb3JyZWN0IFNoYXJkZWRSZXF1ZXN0XG4gICAqIGluc3RhbmNlLlxuICAgKlxuICAgKiBAdHlwZSB7c3RyaW5nfVxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyBpZDogc3RyaW5nXG5cbiAgLyoqXG4gICAqIFRoZSBtbXJwTm9kZSBpbnN0YW5jZSB0byB1c2UgdG8gc2VuZCB0aGUgbWVzc2FnZVxuICAgKlxuICAgKiBAdHlwZSB7YW55fVxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyBtbXJwTm9kZTogYW55XG5cbiAgLyoqXG4gICAqIFRoZSBldmVudCBuYW1lXG4gICAqXG4gICAqIFNob3VsZCBub3JtYWxseSBiZSBgQWJzdHJhY3RTaGFyZGVkTW9kdWxlLlJFUVVFU1RfRVZFTlRfTkFNRWBcbiAgICogZm9yIHRoZSBjbGFzcyB3aGljaCB3aWxsIGluc3RhbmNpYXRlIGl0XG4gICAqXG4gICAqIEB0eXBlIHtzdHJpbmd9XG4gICAqIEBtZW1iZXJvZiBTaGFyZGVkUmVxdWVzdFxuICAgKi9cbiAgcHVibGljIGV2ZW50TmFtZTogc3RyaW5nXG5cbiAgLyoqXG4gICAqIFRhcmdldCBNQUdFIG5vZGVcbiAgICpcbiAgICogQHR5cGUge3N0cmluZ1tdfVxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyB0YXJnZXQ6IHN0cmluZ1tdXG5cbiAgLyoqXG4gICAqIE1ldGhvZCB0byBiZSBleGVjdXRlZFxuICAgKlxuICAgKiBAdHlwZSB7c3RyaW5nfVxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyBtZXRob2Q6IHN0cmluZ1xuXG4gIC8qKlxuICAgKiBBcmd1bWVudHMgdG8gZmVlZCB0byB0aGUgbWV0aG9kXG4gICAqXG4gICAqIEB0eXBlIHthbnlbXX1cbiAgICogQG1lbWJlcm9mIFNoYXJkZWRSZXF1ZXN0XG4gICAqL1xuICBwdWJsaWMgYXJncz86IGFueVtdXG5cbiAgLyoqXG4gICAqIFJlc29sdmUgZnVuY3Rpb24gKHNlZSBgc2VuZGApXG4gICAqXG4gICAqIEBtZW1iZXJvZiBTaGFyZGVkUmVxdWVzdFxuICAgKi9cbiAgcHVibGljIHJlc29sdmU6IChkYXRhOiBhbnkpID0+IHZvaWRcblxuICAvKipcbiAgICogUmVqZWN0IGZ1bmN0aW9uIChzZWUgYHNlbmRgKVxuICAgKlxuICAgKiBAbWVtYmVyb2YgU2hhcmRlZFJlcXVlc3RcbiAgICovXG4gIHB1YmxpYyByZWplY3Q6IChlcnJvcjogRXJyb3IpID0+IHZvaWRcblxuICBjb25zdHJ1Y3RvcihpZDogc3RyaW5nLCBtbXJwTm9kZTogYW55LCBldmVudE5hbWU6IHN0cmluZywgdGFyZ2V0OiBzdHJpbmdbXSwgbWV0aG9kOiBzdHJpbmcsIGFyZ3M/OiBhbnlbXSkge1xuICAgIHRoaXMuaWQgPSBpZFxuICAgIHRoaXMubW1ycE5vZGUgPSBtbXJwTm9kZVxuICAgIHRoaXMuZXZlbnROYW1lID0gZXZlbnROYW1lXG4gICAgdGhpcy50YXJnZXQgPSB0YXJnZXRcbiAgICB0aGlzLm1ldGhvZCA9IG1ldGhvZFxuICAgIHRoaXMuYXJncyA9IGFyZ3NcbiAgfVxuXG4gIC8qKlxuICAgKiBTZW5kIHRoZSByZXF1ZXN0XG4gICAqXG4gICAqIE5vdGUgdGhhdCB0aGUgc2VuZGluZyBtb2R1bGUgaXMgcmVzcG9uc2libGUgZm9yIGxpc3RlbmluZyBmb3IgdGhlXG4gICAqIHJlc3BvbnNlLCBhbmQgcGFzc2luZyBpdCB0byB0aGlzIHJlcXVlc3QgdGhyb3VnaCBgcmVzb2x2ZWAgb3JcbiAgICogYHJlamVjdGAuXG4gICAqXG4gICAqIFRoZSBzZW5kaW5nIG1vZHVsZSBtYXkgYWxzbyBjYWxsIGByZWplY3RgIHNob3VsZCBpdCBqdWRnZSBhIHJlcXVlc3RcbiAgICogaGFzIHRpbWVkIG91dC5cbiAgICpcbiAgICogQHJldHVybnNcbiAgICogQG1lbWJlcm9mIFNoYXJkZWRSZXF1ZXN0XG4gICAqL1xuICBwdWJsaWMgYXN5bmMgc2VuZCgpIHtcbiAgICBjb25zdCBFbnZlbG9wZSA9IG1zZ1NlcnZlci5tbXJwLkVudmVsb3BlXG4gICAgY29uc3QgbWVzc2FnZXMgPSBbdGhpcy5pZCwgdGhpcy5tZXRob2QsIEpTT04uc3RyaW5naWZ5KHRoaXMuYXJncyldXG4gICAgY29uc3Qgc291cmNlID0gW3RoaXMubW1ycE5vZGUuY2x1c3RlcklkLCB0aGlzLm1tcnBOb2RlLmlkZW50aXR5XVxuICAgIGNvbnN0IHJlcXVlc3RFbnZlbG9wZSA9IG5ldyBFbnZlbG9wZSh0aGlzLmV2ZW50TmFtZSwgbWVzc2FnZXMsIHRoaXMudGFyZ2V0LCBzb3VyY2UsICdUUkFDS19ST1VURScpXG5cbiAgICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgdGhpcy5yZXNvbHZlID0gcmVzb2x2ZVxuICAgICAgdGhpcy5yZWplY3QgPSByZWplY3RcblxuICAgICAgdGhpcy5tbXJwTm9kZS5zZW5kKHJlcXVlc3RFbnZlbG9wZSwgMSwgKGVycm9yPzogRXJyb3IpID0+IHtcbiAgICAgICAgaWYgKGVycm9yKSB7XG4gICAgICAgICAgcmV0dXJuIHJlamVjdChlcnJvcilcbiAgICAgICAgfVxuICAgICAgfSlcbiAgICB9KVxuICB9XG59XG5cbi8qKlxuICogSVNoYXJkIGludGVyZmFjZVxuICpcbiAqIFRoaXMgcmVwcmVzZW50cyB0aGUgZGF0YSBzdHJ1Y3R1cmUgb2ZcbiAqIGEgc2hhcmQuXG4gKlxuICogQGV4cG9ydFxuICovXG5leHBvcnQgaW50ZXJmYWNlIElTaGFyZCB7XG4gIGlkOiBzdHJpbmdcbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplRXJyb3IoZGF0YTogYW55KSB7XG4gIHJldHVybiBKU09OLnN0cmluZ2lmeShlcnJvclRvT2JqZWN0KGRhdGEpKVxufVxuXG4vKipcbiAqIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICpcbiAqIFRoaXMgYWJzdHJhY3QgbW9kdWxlIHdpbGwgdGFrZSBjYXJlIG9mIG1haW50YWluaW5nXG4gKiBhIGNvbnNpc3RlbnQgdmlldyBvZiB0aGUgY2x1c3RlciwgYW5kIG9mIGNyZWF0aW5nXG4gKiBwcm94eSBvYmplY3RzIChvciBzaGFyZHMpIHdoaWNoIGNhbiB0aGVuIGJlIHVzZWQgdG9cbiAqIGNvbnNpc3RlbnRseSBmb3J3YXJkIHJlcXVlc3RzIHRvIGEgc2VydmVyLlxuICpcbiAqIFNlZSBSZWFkbWUubWQgZm9yIG1vcmUgZGV0YWlscyBvbiBob3cgdG8gdXNlIHRoaXMgY2xhc3MuXG4gKlxuICogQGV4cG9ydFxuICogQGNsYXNzIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICovXG5leHBvcnQgZGVmYXVsdCBhYnN0cmFjdCBjbGFzcyBBYnN0cmFjdFNoYXJkZWRNb2R1bGUge1xuICAvKipcbiAgICogU2VydmljZSBuYW1lXG4gICAqXG4gICAqIEJ5IGRlZmF1bHQsIHRoZSBzZXJ2aWNlIG5hbWUgd2lsbCBiZSB0aGUgbmFtZSBvZiB0aGUgY2xhc3NcbiAgICpcbiAgICogQHR5cGUge3N0cmluZ31cbiAgICogQG1lbWJlcm9mIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICAgKi9cbiAgcHVibGljIG5hbWU6IHN0cmluZ1xuXG4gIC8qKlxuICAgKiBIYXNoaW5nIGFsZ29yaXRobSB0byBmb3Igc2hhcmRpbmdcbiAgICovXG4gIHByaXZhdGUgaGFzaGluZ0FsZ29yaXRobTogc3RyaW5nID0gJ21kNSdcblxuICAvKipcbiAgICogTW9kdWxlIGxvZ2dlciBpbnN0YW5jZVxuICAgKi9cbiAgcHJpdmF0ZSBsb2dnZXI6IG1hZ2UuY29yZS5JTG9nZ2VyXG5cbiAgLyoqXG4gICAqIFJlcXVlc3QgZXZlbnQgbGFiZWxcbiAgICpcbiAgICogUmVxdWVzdCBtZXNzYWdlcyBmb2xsb3cgdGhlIGZvbGxvd2luZyBmb3JtYXQ6XG4gICAqXG4gICAqIFtyZXF1ZXN0SWQsIG1ldGhvZCwgLi4uYXJnc11cbiAgICpcbiAgICogQHR5cGUge3N0cmluZ31cbiAgICogQG1lbWJlcm9mIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICAgKi9cbiAgcHJpdmF0ZSBSRVFVRVNUX0VWRU5UX05BTUU6IHN0cmluZ1xuXG4gIC8qKlxuICAgKiBSZXNwb25zZSBldmVudCBsYWJlbFxuICAgKlxuICAgKiBSZXNwb25zZSBtZXNzYWdlcyBmb2xsb3cgdGhlIGZvbGxvd2luZyBmb3JtYXQ6XG4gICAqXG4gICAqIFtyZXF1ZXN0SWQsIHJlc3BvbnNlLCBlcnJvcl1cbiAgICpcbiAgICogQHR5cGUge3N0cmluZ31cbiAgICogQG1lbWJlcm9mIEFic3RyYWN0U2hhcmRlZE1vZHVsZVxuICAgKi9cbiAgcHJpdmF0ZSBSRVNQT05TRV9FVkVOVF9OQU1FOiBzdHJpbmdcblxuICAvKipcbiAgICogU2VydmljZSBpbnN0YW5jZVxuICAgKlxuICAgKiBAdHlwZSB7bWFnZS5jb3JlLklTZXJ2aWNlfVxuICAgKiBAbWVtYmVyb2YgQWJzdHJhY3RTaGFyZGVkTW9kdWxlXG4gICAqL1xuICBwcml2YXRlIHNlcnZpY2U6IG1hZ2UuY29yZS5JU2VydmljZVxuXG4gIC8qKlxuICAgKiBIYXNoZXMgb2YgYXZhaWxhYmxlIHdvcmtlcnMgaW4gdGhlIGNsdXN0ZXJzXG4gICAqXG4gICAqIEEgZ2l2ZW4gbm9kZSBpbiB0aGUgY2x1c3RlciBtYXkgYmUgcnVubmluZyBtb3JlIHRoYW5cbiAgICogb25lIHdvcmtlcjsgZWFjaCB3b3JrZXIgd2lsbCBoYXZlIGl0cyBvd24gYWNjZXNzaWJsZSBhZGRyZXNzLlxuICAgKlxuICAgKiBAdHlwZSB7c3RyaW5nW119XG4gICAqIEBtZW1iZXJvZiBBYnN0cmFjdFNoYXJkZWRNb2R1bGVcbiAgICovXG4gIHByaXZhdGUgYWRkcmVzc0hhc2hlczogc3RyaW5nW10gPSBbXVxuXG4gIC8qKlxuICAgKiBUaGUgYWRkcmVzcyBoYXNoIGZvciB0aGlzIGxvY2FsIHNlcnZlclxuICAgKlxuICAgKiBUaGlzIGlzIHVzZWQgZm9yIH