package.dist.src.internal.pubsub-client.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sdk Show documentation
Show all versions of sdk Show documentation
Client SDK for Momento services
The newest version!
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PubsubClient = void 0;
const generated_types_1 = require("@gomomento/generated-types");
var grpcPubsub = generated_types_1.pubsub.cache_client.pubsub;
// older versions of node don't have the global util variables https://github.com/nodejs/node/issues/20365
const headers_interceptor_1 = require("./grpc/headers-interceptor");
const cache_service_error_mapper_1 = require("../errors/cache-service-error-mapper");
const grpc_js_1 = require("@grpc/grpc-js");
const package_json_1 = require("../../package.json");
const middlewares_interceptor_1 = require("./grpc/middlewares-interceptor");
const __1 = require("../");
const utils_1 = require("@gomomento/sdk-core/dist/src/internal/utils");
const AbstractPubsubClient_1 = require("@gomomento/sdk-core/dist/src/internal/clients/pubsub/AbstractPubsubClient");
const grpc_channel_options_1 = require("./grpc/grpc-channel-options");
const retry_interceptor_1 = require("./grpc/retry-interceptor");
const utils_2 = require("@gomomento/sdk-core/dist/src/utils");
class PubsubClient extends AbstractPubsubClient_1.AbstractPubsubClient {
// private static readonly RST_STREAM_NO_ERROR_MESSAGE =
// 'Received RST_STREAM with code 0';
/**
* @param {TopicClientProps} props
*/
constructor(props) {
super(props.configuration.getLoggerFactory(), props.configuration.getLoggerFactory().getLogger(PubsubClient.name), new cache_service_error_mapper_1.CacheServiceErrorMapper(props.configuration.getThrowOnErrors()));
this.credentialProvider = props.credentialProvider;
this.unaryRequestTimeoutMs = PubsubClient.DEFAULT_REQUEST_TIMEOUT_MS;
this.getLogger().debug(`Creating topic client using endpoint: '${this.credentialProvider.getCacheEndpoint()}'`);
const topicGrpcConfig = props.configuration
.getTransportStrategy()
.getGrpcConfig();
// NOTE: This is hard-coded for now but we may want to expose it via TopicConfiguration in the
// future, as we do with some of the other clients.
const grpcConfig = new __1.StaticGrpcConfiguration({
deadlineMillis: this.unaryRequestTimeoutMs,
maxSessionMemoryMb: PubsubClient.DEFAULT_MAX_SESSION_MEMORY_MB,
keepAlivePermitWithoutCalls: topicGrpcConfig.getKeepAlivePermitWithoutCalls(),
keepAliveTimeMs: topicGrpcConfig.getKeepAliveTimeMS(),
keepAliveTimeoutMs: topicGrpcConfig.getKeepAliveTimeoutMS(),
});
const channelOptions = (0, grpc_channel_options_1.grpcChannelOptionsFromGrpcConfig)(grpcConfig);
this.getLogger().debug(`Creating pubsub client with channel options: ${JSON.stringify(channelOptions, null, 2)}`);
this.client = new grpcPubsub.PubsubClient(this.credentialProvider.getCacheEndpoint(), this.credentialProvider.isCacheEndpointSecure()
? grpc_js_1.ChannelCredentials.createSsl()
: grpc_js_1.ChannelCredentials.createInsecure(), channelOptions);
const headers = [
new headers_interceptor_1.Header('Authorization', this.credentialProvider.getAuthToken()),
new headers_interceptor_1.Header('agent', `nodejs:topic:${package_json_1.version}`),
new headers_interceptor_1.Header('runtime-version', `nodejs:${process.versions.node}`),
];
this.unaryInterceptors = PubsubClient.initializeUnaryInterceptors(headers, props.configuration, this.unaryRequestTimeoutMs);
this.streamingInterceptors =
PubsubClient.initializeStreamingInterceptors(headers);
}
getEndpoint() {
const endpoint = this.credentialProvider.getCacheEndpoint();
this.getLogger().debug(`Using cache endpoint: ${endpoint}`);
return endpoint;
}
async sendPublish(cacheName, topicName, value) {
const topicValue = new grpcPubsub._TopicValue();
if (typeof value === 'string') {
topicValue.text = value;
}
else {
topicValue.binary = value;
}
const request = new grpcPubsub._PublishRequest({
cache_name: cacheName,
topic: topicName,
value: topicValue,
});
return await new Promise((resolve, reject) => {
this.client.Publish(request, {
interceptors: this.unaryInterceptors,
}, (err, resp) => {
if (resp) {
resolve(new __1.TopicPublish.Success());
}
else {
this.getCacheServiceErrorMapper().resolveOrRejectError({
err: err,
errorResponseFactoryFn: e => new __1.TopicPublish.Error(e),
resolveFn: resolve,
rejectFn: reject,
});
}
});
});
}
/**
* @remark This method is responsible for restarting the stream if it ends unexpectedly.
* Since we return a single subscription object to the user, we need to update it with the
* unsubscribe function should we restart the stream. This is why we pass the subscription
* state and subscription object to this method.
*
* Handling a cache not exists requires special care as well. In the most likely case,
* when the subscription starts and the cache does not exist, we receive an error immediately.
* We return an error from the subscribe method and do immediately unsubscribe. In a distinct,
* unlikely but possible case, the user deletes the cache while the stream is running. In this
* case we already returned a subscription object to the user, so we instead cancel the stream and
* propagate an error to the user via the error handler.
*/
sendSubscribe(options) {
const request = new grpcPubsub._SubscriptionRequest({
cache_name: options.cacheName,
topic: options.topicName,
resume_at_topic_sequence_number: options.subscriptionState.resumeAtTopicSequenceNumber,
sequence_page: options.subscriptionState.lastTopicSequencePage,
});
this.getLogger().trace('Subscribing to topic with resume_at_topic_sequence_number %s and sequence_page %s', options.subscriptionState.resumeAtTopicSequenceNumber, options.subscriptionState.resumeAtTopicSequencePage);
const call = this.client.Subscribe(request, {
interceptors: this.streamingInterceptors,
});
options.subscriptionState.setSubscribed();
// Allow the caller to cancel the stream.
// Note that because we restart the stream on error or stream end,
// we need to ensure we keep the same subscription object. That way
// stream restarts are transparent to the caller.
options.subscriptionState.unsubscribeFn = () => {
call.cancel();
};
return new Promise((resolve, _reject) => {
const prepareCallbackOptions = {
...options,
resolve,
};
call.on('data', this.prepareDataCallback(prepareCallbackOptions));
call.on('error', this.prepareErrorCallback(prepareCallbackOptions));
call.on('end', this.prepareEndCallback(prepareCallbackOptions));
});
}
prepareDataCallback(options) {
return (resp) => {
if (options.firstMessage) {
options.resolve(options.subscription);
}
options.firstMessage = false;
if (resp.item) {
const sequenceNumber = resp.item.topic_sequence_number;
const sequencePage = resp.item.sequence_page;
options.subscriptionState.lastTopicSequenceNumber = sequenceNumber;
options.subscriptionState.lastTopicSequencePage = sequencePage;
this.getLogger().trace('Received an item on subscription stream; topic: %s; sequence number: %s; sequence page: %s', (0, utils_1.truncateString)(options.topicName), sequenceNumber, sequencePage);
if (resp.item.value.text) {
options.onItem(new __1.TopicItem(resp.item.value.text, sequenceNumber, {
tokenId: resp.item.publisher_id,
}));
}
else if (resp.item.value.binary) {
options.onItem(new __1.TopicItem(resp.item.value.binary, sequenceNumber, {
tokenId: resp.item.publisher_id,
}));
}
else {
this.getLogger().error('Received subscription item with unknown type; topic: %s', (0, utils_1.truncateString)(options.topicName));
options.onError(new __1.TopicSubscribe.Error(new __1.UnknownError('Unknown item value type')), options.subscription);
}
}
else if (resp.heartbeat) {
this.getLogger().trace('Received heartbeat from subscription stream; topic: %s', (0, utils_1.truncateString)(options.topicName));
options.onHeartbeat(new __1.TopicHeartbeat());
}
else if (resp.discontinuity) {
const topicDiscontinuity = new __1.TopicDiscontinuity(resp.discontinuity.last_topic_sequence, resp.discontinuity.new_topic_sequence, resp.discontinuity.new_sequence_page);
this.getLogger().trace('Received discontinuity from subscription stream; topic: %s; %s', (0, utils_1.truncateString)(options.topicName), topicDiscontinuity.toString());
options.subscriptionState.lastTopicSequenceNumber =
resp.discontinuity.new_topic_sequence;
options.subscriptionState.lastTopicSequencePage =
resp.discontinuity.new_sequence_page;
options.onDiscontinuity(topicDiscontinuity);
}
else {
this.getLogger().error('Received unknown subscription item; topic: %s', (0, utils_1.truncateString)(options.topicName));
options.onError(new __1.TopicSubscribe.Error(new __1.UnknownError('Unknown item type')), options.subscription);
}
};
}
prepareErrorCallback(options) {
return (err) => {
// When the caller unsubscribes, we may get a follow on error, which we ignore.
if (!options.subscriptionState.isSubscribed) {
return;
}
const serviceError = err;
this.getLogger().trace(`Subscription encountered an error: ${serviceError.code}: ${serviceError.message}: ${serviceError.details}`);
const shouldReconnectSubscription =
// previously, we were only attempting a reconnect on this one very specific case, but our current expectation is that
// we should err on the side of retrying. This may become a sort of "deny list" of error types to *not* retry on
// in the future, but for now we will be aggressive about retrying.
// // serviceError.code === Status.INTERNAL &&
// // serviceError.details === PubsubClient.RST_STREAM_NO_ERROR_MESSAGE;
true;
const momentoError = new __1.TopicSubscribe.Error(this.getCacheServiceErrorMapper().convertError(serviceError));
this.handleSubscribeError(options, momentoError, shouldReconnectSubscription);
};
}
static initializeUnaryInterceptors(headers, configuration, requestTimeoutMs) {
return [
(0, middlewares_interceptor_1.middlewaresInterceptor)(configuration.getLoggerFactory(), [], {}),
headers_interceptor_1.HeaderInterceptor.createHeadersInterceptor(headers),
retry_interceptor_1.RetryInterceptor.createRetryInterceptor({
clientName: 'PubSubClient',
loggerFactory: configuration.getLoggerFactory(),
overallRequestTimeoutMs: requestTimeoutMs,
}),
];
}
// TODO https://github.com/momentohq/client-sdk-nodejs/issues/349
// decide on streaming interceptors and middlewares
static initializeStreamingInterceptors(headers) {
return [headers_interceptor_1.HeaderInterceptor.createHeadersInterceptor(headers)];
}
}
exports.PubsubClient = PubsubClient;
PubsubClient.DEFAULT_REQUEST_TIMEOUT_MS = (0, utils_2.secondsToMilliseconds)(5);
PubsubClient.DEFAULT_MAX_SESSION_MEMORY_MB = 256;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVic3ViLWNsaWVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9pbnRlcm5hbC9wdWJzdWItY2xpZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGdFQUFrRDtBQUNsRCxJQUFPLFVBQVUsR0FBRyx3QkFBTSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUM7QUFDL0MsMEdBQTBHO0FBQzFHLG9FQUFxRTtBQUNyRSxxRkFBNkU7QUFDN0UsMkNBQTRFO0FBQzVFLHFEQUEyQztBQUMzQyw0RUFBc0U7QUFDdEUsMkJBVWE7QUFDYix1RUFBMkU7QUFDM0Usb0hBSW1GO0FBR25GLHNFQUE2RTtBQUM3RSxnRUFBMEQ7QUFDMUQsOERBQXlFO0FBRXpFLE1BQWEsWUFBYSxTQUFRLDJDQUFrQztJQVVsRSx3REFBd0Q7SUFDeEQsdUNBQXVDO0lBRXZDOztPQUVHO0lBQ0gsWUFBWSxLQUEwQjtRQUNwQyxLQUFLLENBQ0gsS0FBSyxDQUFDLGFBQWEsQ0FBQyxnQkFBZ0IsRUFBRSxFQUN0QyxLQUFLLENBQUMsYUFBYSxDQUFDLGdCQUFnQixFQUFFLENBQUMsU0FBUyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsRUFDbkUsSUFBSSxvREFBdUIsQ0FBQyxLQUFLLENBQUMsYUFBYSxDQUFDLGdCQUFnQixFQUFFLENBQUMsQ0FDcEUsQ0FBQztRQUNGLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxLQUFLLENBQUMsa0JBQWtCLENBQUM7UUFDbkQsSUFBSSxDQUFDLHFCQUFxQixHQUFHLFlBQVksQ0FBQywwQkFBMEIsQ0FBQztRQUNyRSxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUMsS0FBSyxDQUNwQiwwQ0FBMEMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLGdCQUFnQixFQUFFLEdBQUcsQ0FDeEYsQ0FBQztRQUVGLE1BQU0sZUFBZSxHQUEyQixLQUFLLENBQUMsYUFBYTthQUNoRSxvQkFBb0IsRUFBRTthQUN0QixhQUFhLEVBQUUsQ0FBQztR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QkFBK0IsQ0FDNUMsT0FBaUI7UUFFakIsT0FBTyxDQUFDLHVDQUFpQixDQUFDLHdCQUF3QixDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDL0QsQ0FBQzs7QUFsVEgsb0NBbVRDO0FBL1N5Qix1Q0FBMEIsR0FDaEQsSUFBQSw2QkFBcUIsRUFBQyxDQUFDLENBQUMsQ0FBQztBQUNILDBDQUE2QixHQUFXLEdBQUcsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7cHVic3VifSBmcm9tICdAZ29tb21lbnRvL2dlbmVyYXRlZC10eXBlcyc7XG5pbXBvcnQgZ3JwY1B1YnN1YiA9IHB1YnN1Yi5jYWNoZV9jbGllbnQucHVic3ViO1xuLy8gb2xkZXIgdmVyc2lvbnMgb2Ygbm9kZSBkb24ndCBoYXZlIHRoZSBnbG9iYWwgdXRpbCB2YXJpYWJsZXMgaHR0cHM6Ly9naXRodWIuY29tL25vZGVqcy9ub2RlL2lzc3Vlcy8yMDM2NVxuaW1wb3J0IHtIZWFkZXIsIEhlYWRlckludGVyY2VwdG9yfSBmcm9tICcuL2dycGMvaGVhZGVycy1pbnRlcmNlcHRvcic7XG5pbXBvcnQge0NhY2hlU2VydmljZUVycm9yTWFwcGVyfSBmcm9tICcuLi9lcnJvcnMvY2FjaGUtc2VydmljZS1lcnJvci1tYXBwZXInO1xuaW1wb3J0IHtDaGFubmVsQ3JlZGVudGlhbHMsIEludGVyY2VwdG9yLCBTZXJ2aWNlRXJyb3J9IGZyb20gJ0BncnBjL2dycGMtanMnO1xuaW1wb3J0IHt2ZXJzaW9ufSBmcm9tICcuLi8uLi9wYWNrYWdlLmpzb24nO1xuaW1wb3J0IHttaWRkbGV3YXJlc0ludGVyY2VwdG9yfSBmcm9tICcuL2dycGMvbWlkZGxld2FyZXMtaW50ZXJjZXB0b3InO1xuaW1wb3J0IHtcbiAgQ3JlZGVudGlhbFByb3ZpZGVyLFxuICBTdGF0aWNHcnBjQ29uZmlndXJhdGlvbixcbiAgVG9waWNEaXNjb250aW51aXR5LFxuICBUb3BpY0hlYXJ0YmVhdCxcbiAgVG9waWNHcnBjQ29uZmlndXJhdGlvbixcbiAgVG9waWNJdGVtLFxuICBUb3BpY1B1Ymxpc2gsXG4gIFRvcGljU3Vic2NyaWJlLFxuICBVbmtub3duRXJyb3IsXG59IGZyb20gJy4uLyc7XG5pbXBvcnQge3RydW5jYXRlU3RyaW5nfSBmcm9tICdAZ29tb21lbnRvL3Nkay1jb3JlL2Rpc3Qvc3JjL2ludGVybmFsL3V0aWxzJztcbmltcG9ydCB7XG4gIEFic3RyYWN0UHVic3ViQ2xpZW50LFxuICBTZW5kU3Vic2NyaWJlT3B0aW9ucyxcbiAgUHJlcGFyZVN1YnNjcmliZUNhbGxiYWNrT3B0aW9ucyxcbn0gZnJvbSAnQGdvbW9tZW50by9zZGstY29yZS9kaXN0L3NyYy9pbnRlcm5hbC9jbGllbnRzL3B1YnN1Yi9BYnN0cmFjdFB1YnN1YkNsaWVudCc7XG5pbXBvcnQge1RvcGljQ29uZmlndXJhdGlvbn0gZnJvbSAnLi4vY29uZmlnL3RvcGljLWNvbmZpZ3VyYXRpb24nO1xuaW1wb3J0IHtUb3BpY0NsaWVudEFsbFByb3BzfSBmcm9tICcuL3RvcGljLWNsaWVudC1hbGwtcHJvcHMnO1xuaW1wb3J0IHtncnBjQ2hhbm5lbE9wdGlvbnNGcm9tR3JwY0NvbmZpZ30gZnJvbSAnLi9ncnBjL2dycGMtY2hhbm5lbC1vcHRpb25zJztcbmltcG9ydCB7UmV0cnlJbnRlcmNlcHRvcn0gZnJvbSAnLi9ncnBjL3JldHJ5LWludGVyY2VwdG9yJztcbmltcG9ydCB7c2Vjb25kc1RvTWlsbGlzZWNvbmRzfSBmcm9tICdAZ29tb21lbnRvL3Nkay1jb3JlL2Rpc3Qvc3JjL3V0aWxzJztcblxuZXhwb3J0IGNsYXNzIFB1YnN1YkNsaWVudCBleHRlbmRzIEFic3RyYWN0UHVic3ViQ2xpZW50PFNlcnZpY2VFcnJvcj4ge1xuICBwcml2YXRlIHJlYWRvbmx5IGNsaWVudDogZ3JwY1B1YnN1Yi5QdWJzdWJDbGllbnQ7XG4gIHByb3RlY3RlZCByZWFkb25seSBjcmVkZW50aWFsUHJvdmlkZXI6IENyZWRlbnRpYWxQcm92aWRlcjtcbiAgcHJpdmF0ZSByZWFkb25seSB1bmFyeVJlcXVlc3RUaW1lb3V0TXM6IG51bWJlcjtcbiAgcHJpdmF0ZSBzdGF0aWMgcmVhZG9ubHkgREVGQVVMVF9SRVFVRVNUX1RJTUVPVVRfTVM6IG51bWJlciA9XG4gICAgc2Vjb25kc1RvTWlsbGlzZWNvbmRzKDUpO1xuICBwcml2YXRlIHN0YXRpYyByZWFkb25seSBERUZBVUxUX01BWF9TRVNTSU9OX01FTU9SWV9NQjogbnVtYmVyID0gMjU2O1xuICBwcml2YXRlIHJlYWRvbmx5IHVuYXJ5SW50ZXJjZXB0b3JzOiBJbnRlcmNlcHRvcltdO1xuICBwcml2YXRlIHJlYWRvbmx5IHN0cmVhbWluZ0ludGVyY2VwdG9yczogSW50ZXJjZXB0b3JbXTtcblxuICAvLyBwcml2YXRlIHN0YXRpYyByZWFkb25seSBSU1RfU1RSRUFNX05PX0VSUk9SX01FU1NBR0UgPVxuICAvLyAgICdSZWNlaXZlZCBSU1RfU1RSRUFNIHdpdGggY29kZSAwJztcblxuICAvKipcbiAgICogQHBhcmFtIHtUb3BpY0NsaWVudFByb3BzfSBwcm9wc1xuICAgKi9cbiAgY29uc3RydWN0b3IocHJvcHM6IFRvcGljQ2xpZW50QWxsUHJvcHMpIHtcbiAgICBzdXBlcihcbiAgICAgIHByb3BzLmNvbmZpZ3VyYXRpb24uZ2V0TG9nZ2VyRmFjdG9yeSgpLFxuICAgICAgcHJvcHMuY29uZmlndXJhdGlvbi5nZXRMb2dnZXJGYWN0b3J5KCkuZ2V0TG9nZ2VyKFB1YnN1YkNsaWVudC5uYW1lKSxcbiAgICAgIG5ldyBDYWNoZVNlcnZpY2VFcnJvck1hcHBlcihwcm9wcy5jb25maWd1cmF0aW9uLmdldFRocm93T25FcnJvcnMoKSlcbiAgICApO1xuICAgIHRoaXMuY3JlZGVudGlhbFByb3ZpZGVyID0gcHJvcHMuY3JlZGVudGlhbFByb3ZpZGVyO1xuICAgIHRoaXMudW5hcnlSZXF1ZXN0VGltZW91dE1zID0gUHVic3ViQ2xpZW50LkRFRkFVTFRfUkVRVUVTVF9USU1FT1VUX01TO1xuICAgIHRoaXMuZ2V0TG9nZ2VyKCkuZGVidWcoXG4gICAgICBgQ3JlYXRpbmcgdG9waWMgY2xpZW50IHVzaW5nIGVuZHBvaW50OiAnJHt0aGlzLmNyZWRlbnRpYWxQcm92aWRlci5nZXRDYWNoZUVuZHBvaW50KCl9J2BcbiAgICApO1xuXG4gICAgY29uc3QgdG9waWNHcnBjQ29uZmlnOiBUb3BpY0dycGNDb25maWd1cmF0aW9uID0gcHJvcHMuY29uZmlndXJhdGlvblxuICAgICAgLmdldFRyYW5zcG9ydFN0cmF0ZWd5KClcbiAgICAgIC5nZXRHcnBjQ29uZmlnKCk7XG5cbiAgICAvLyBOT1RFOiBUaGlzIGlzIGhhcmQtY29kZWQgZm9yIG5vdyBidXQgd2UgbWF5IHdhbnQgdG8gZXhwb3NlIGl0IHZpYSBUb3BpY0NvbmZpZ3VyYXRpb24gaW4gdGhlXG4gICAgLy8gZnV0dXJlLCBhcyB3ZSBkbyB3aXRoIHNvbWUgb2YgdGhlIG90aGVyIGNsaWVudHMuXG4gICAgY29uc3QgZ3JwY0NvbmZpZyA9IG5ldyBTdGF0aWNHcnBjQ29uZmlndXJhdGlvbih7XG4gICAgICBkZWFkbGluZU1pbGxpczogdGhpcy51bmFyeVJlcXVlc3RUaW1lb3V0TXMsXG4gICAgICBtYXhTZXNzaW9uTWVtb3J5TWI6IFB1YnN1YkNsaWVudC5ERUZBVUxUX01BWF9TRVNTSU9OX01FTU9SWV9NQixcbiAgICAgIGtlZXBBbGl2ZVBlcm1pdFdpdGhvdXRDYWxsczpcbiAgICAgICAgdG9waWNHcnBjQ29uZmlnLmdldEtlZXBBbGl2ZVBlcm1pdFdpdGhvdXRDYWxscygpLFxuICAgICAga2VlcEFsaXZlVGltZU1zOiB0b3BpY0dycGNDb25maWcuZ2V0S2VlcEFsaXZlVGltZU1TKCksXG4gICAgICBrZWVwQWxpdmVUaW1lb3V0TXM6IHRvcGljR3JwY0NvbmZpZy5nZXRLZWVwQWxpdmVUaW1lb3V0TVMoKSxcbiAgICB9KTtcblxuICAgIGNvbnN0IGNoYW5uZWxPcHRpb25zID0gZ3JwY0NoYW5uZWxPcHRpb25zRnJvbUdycGNDb25maWcoZ3JwY0NvbmZpZyk7XG5cbiAgICB0aGlzLmdldExvZ2dlcigpLmRlYnVnKFxuICAgICAgYENyZWF0aW5nIHB1YnN1YiBjbGllbnQgd2l0aCBjaGFubmVsIG9wdGlvbnM6ICR7SlNPTi5zdHJpbmdpZnkoXG4gICAgICAgIGNoYW5uZWxPcHRpb25zLFxuICAgICAgICBudWxsLFxuICAgICAgICAyXG4gICAgICApfWBcbiAgICApO1xuXG4gICAgdGhpcy5jbGllbnQgPSBuZXcgZ3JwY1B1YnN1Yi5QdWJzdWJDbGllbnQoXG4gICAgICB0aGlzLmNyZWRlbnRpYWxQcm92aWRlci5nZXRDYWNoZUVuZHBvaW50KCksXG4gICAgICB0aGlzLmNyZWRlbnRpYWxQcm92aWRlci5pc0NhY2hlRW5kcG9pbnRTZWN1cmUoKVxuICAgICAgICA/IENoYW5uZWxDcmVkZW50aWFscy5jcmVhdGVTc2woKVxuICAgICAgICA6IENoYW5uZWxDcmVkZW50aWFscy5jcmVhdGVJbnNlY3VyZSgpLFxuICAgICAgY2hhbm5lbE9wdGlvbnNcbiAgICApO1xuXG4gICAgY29uc3QgaGVhZGVyczogSGVhZGVyW10gPSBbXG4gICAgICBuZXcgSGVhZGVyKCdBdXRob3JpemF0aW9uJywgdGhpcy5jcmVkZW50aWFsUHJvdmlkZXIuZ2V0QXV0aFRva2VuKCkpLFxuICAgICAgbmV3IEhlYWRlcignYWdlbnQnLCBgbm9kZWpzOnRvcGljOiR7dmVyc2lvbn1gKSxcbiAgICAgIG5ldyBIZWFkZXIoJ3J1bnRpbWUtdmVyc2lvbicsIGBub2RlanM6JHtwcm9jZXNzLnZlcnNpb25zLm5vZGV9YCksXG4gICAgXTtcbiAgICB0aGlzLnVuYXJ5SW50ZXJjZXB0b3JzID0gUHVic3ViQ2xpZW50LmluaXRpYWxpemVVbmFyeUludGVyY2VwdG9ycyhcbiAgICAgIGhlYWRlcnMsXG4gICAgICBwcm9wcy5jb25maWd1cmF0aW9uLFxuICAgICAgdGhpcy51bmFyeVJlcXVlc3RUaW1lb3V0TXNcbiAgICApO1xuICAgIHRoaXMuc3RyZWFtaW5nSW50ZXJjZXB0b3JzID1cbiAgICAgIFB1YnN1YkNsaWVudC5pbml0aWFsaXplU3RyZWFtaW5nSW50ZXJjZXB0b3JzKGhlYWRlcnMpO1xuICB9XG5cbiAgcHVibGljIGdldEVuZHBvaW50KCk6IHN0cmluZyB7XG4gICAgY29uc3QgZW5kcG9pbnQgPSB0aGlzLmNyZWRlbnRpYWxQcm92aWRlci5nZXRDYWNoZUVuZHBvaW50KCk7XG4gICAgdGhpcy5nZXRMb2dnZXIoKS5kZWJ1ZyhgVXNpbmcgY2FjaGUgZW5kcG9pbnQ6ICR7ZW5kcG9pbnR9YCk7XG4gICAgcmV0dXJuIGVuZHBvaW50O1xuICB9XG5cbiAgcHJvdGVjdGVkIGFzeW5jIHNlbmRQdWJsaXNoKFxuICAgIGNhY2hlTmFtZTogc3RyaW5nLFxuICAgIHRvcGljTmFtZTogc3RyaW5nLFxuICAgIHZhbHVlOiBzdHJpbmcgfCBVaW50OEFycmF5XG4gICk6IFByb21pc2U8VG9waWNQdWJsaXNoLlJlc3BvbnNlPiB7XG4gICAgY29uc3QgdG9waWNWYWx1ZSA9IG5ldyBncnBjUHVic3ViLl9Ub3BpY1ZhbHVlKCk7XG4gICAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRvcGljVmFsdWUudGV4dCA9IHZhbHVlO1xuICAgIH0gZWxzZSB7XG4gICAgICB0b3BpY1ZhbHVlLmJpbmFyeSA9IHZhbHVlO1xuICAgIH1cblxuICAgIGNvbnN0IHJlcXVlc3QgPSBuZXcgZ3JwY1B1YnN1Yi5fUHVibGlzaFJlcXVlc3Qoe1xuICAgICAgY2FjaGVfbmFtZTogY2FjaGVOYW1lLFxuICAgICAgdG9waWM6IHRvcGljTmFtZSxcbiAgICAgIHZhbHVlOiB0b3BpY1ZhbHVlLFxuICAgIH0pO1xuXG4gICAgcmV0dXJuIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIHRoaXMuY2xpZW50LlB1Ymxpc2goXG4gICAgICAgIHJlcXVlc3QsXG4gICAgICAgIHtcbiAgICAgICAgICBpbnRlcmNlcHRvcnM6IHRoaXMudW5hcnlJbnRlcmNlcHRvcnMsXG4gICAgICAgIH0sXG4gICAgICAgIChlcnIsIHJlc3ApID0+IHtcbiAgICAgICAgICBpZiAocmVzcCkge1xuICAgICAgICAgICAgcmVzb2x2ZShuZXcgVG9waWNQdWJsaXNoLlN1Y2Nlc3MoKSk7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuZ2V0Q2FjaGVTZXJ2aWNlRXJyb3JNYXBwZXIoKS5yZXNvbHZlT3JSZWplY3RFcnJvcih7XG4gICAgICAgICAgICAgIGVycjogZXJyLFxuICAgICAgICAgICAgICBlcnJvclJlc3BvbnNlRmFjdG9yeUZuOiBlID0+IG5ldyBUb3BpY1B1Ymxpc2guRXJyb3IoZSksXG4gICAgICAgICAgICAgIHJlc29sdmVGbjogcmVzb2x2ZSxcbiAgICAgICAgICAgICAgcmVqZWN0Rm46IHJlamVjdCxcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgKTtcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBAcmVtYXJrIFRoaXMgbWV0aG9kIGlzIHJlc3BvbnNpYmxlIGZvciByZXN0YXJ0aW5nIHRoZSBzdHJlYW0gaWYgaXQgZW5kcyB1bmV4cGVjdGVkbHkuXG4gICAqIFNpbmNlIHdlIHJldHVybiBhIHNpbmdsZSBzdWJzY3JpcHRpb24gb2JqZWN0IHRvIHRoZSB1c2VyLCB3ZSBuZWVkIHRvIHVwZGF0ZSBpdCB3aXRoIHRoZVxuICAgKiB1bnN1YnNjcmliZSBmdW5jdGlvbiBzaG91bGQgd2UgcmVzdGFydCB0aGUgc3RyZWFtLiBUaGlzIGlzIHdoeSB3ZSBwYXNzIHRoZSBzdWJzY3JpcHRpb25cbiAgICogc3RhdGUgYW5kIHN1YnNjcmlwdGlvbiBvYmplY3QgdG8gdGhpcyBtZXRob2QuXG4gICAqXG4gICAqIEhhbmRsaW5nIGEgY2FjaGUgbm90IGV4aXN0cyByZXF1aXJlcyBzcGVjaWFsIGNhcmUgYXMgd2VsbC4gSW4gdGhlIG1vc3QgbGlrZWx5IGNhc2UsXG4gICAqIHdoZW4gdGhlIHN1YnNjcmlwdGlvbiBzdGFydHMgYW5kIHRoZSBjYWNoZSBkb2VzIG5vdCBleGlzdCwgd2UgcmVjZWl2ZSBhbiBlcnJvciBpbW1lZGlhdGVseS5cbiAgICogV2UgcmV0dXJuIGFuIGVycm9yIGZyb20gdGhlIHN1YnNjcmliZSBtZXRob2QgYW5kIGRvIGltbWVkaWF0ZWx5IHVuc3Vic2NyaWJlLiBJbiBhIGRpc3RpbmN0LFxuICAgKiB1bmxpa2VseSBidXQgcG9zc2libGUgY2FzZSwgdGhlIHVzZXIgZGVsZXRlcyB0aGUgY2FjaGUgd2hpbGUgdGhlIHN0cmVhbSBpcyBydW5uaW5nLiBJbiB0aGlzXG4gICAqIGNhc2Ugd2UgYWxyZWFkeSByZXR1cm5lZCBhIHN1YnNjcmlwdGlvbiBvYmplY3QgdG8gdGhlIHVzZXIsIHNvIHdlIGluc3RlYWQgY2FuY2VsIHRoZSBzdHJlYW0gYW5kXG4gICAqIHByb3BhZ2F0ZSBhbiBlcnJvciB0byB0aGUgdXNlciB2aWEgdGhlIGVycm9yIGhhbmRsZXIuXG4gICAqL1xuICBwcm90ZWN0ZWQgb3ZlcnJpZGUgc2VuZFN1YnNjcmliZShcbiAgICBvcHRpb25zOiBTZW5kU3Vic2NyaWJlT3B0aW9uc1xuICApOiBQcm9taXNlPFRvcGljU3Vic2NyaWJlLlJlc3BvbnNlPiB7XG4gICAgY29uc3QgcmVxdWVzdCA9IG5ldyBncnBjUHVic3ViLl9TdWJzY3JpcHRpb25SZXF1ZXN0KHtcbiAgICAgIGNhY2hlX25hbWU6IG9wdGlvbnMuY2FjaGVOYW1lLFxuICAgICAgdG9waWM6IG9wdGlvbnMudG9waWNOYW1lLFxuICAgICAgcmVzdW1lX2F0X3RvcGljX3NlcXVlbmNlX251bWJlcjpcbiAgICAgICAgb3B0aW9ucy5zdWJzY3JpcHRpb25TdGF0ZS5yZXN1bWVBdFRvcGljU2VxdWVuY2VOdW1iZXIsXG4gICAgICBzZXF1ZW5jZV9wYWdlOiBvcHRpb25zLnN1YnNjcmlwdGlvblN0YXRlLmxhc3RUb3BpY1NlcXVlbmNlUGFnZSxcbiAgICB9KTtcblxuICAgIHRoaXMuZ2V0TG9nZ2VyKCkudHJhY2UoXG4gICAgICAnU3Vic2NyaWJpbmcgdG8gdG9waWMgd2l0aCByZXN1bWVfYXRfdG9waWNfc2VxdWVuY2VfbnVtYmVyICVzIGFuZCBzZXF1ZW5jZV9wYWdlICVzJyxcbiAgICAgIG9wdGlvbnMuc3Vic2NyaXB0aW9uU3RhdGUucmVzdW1lQXRUb3BpY1NlcXVlbmNlTnVtYmVyLFxuICAgICAgb3B0aW9ucy5zdWJzY3JpcHRpb25TdGF0ZS5yZXN1bWVBdFRvcGljU2VxdWVuY2VQYWdlXG4gICAgKTtcbiAgICBjb25zdCBjYWxsID0gdGhpcy5jbGllbnQuU3Vic2NyaWJlKHJlcXVlc3QsIHtcbiAgICAgIGludGVyY2VwdG9yczogdGhpcy5zdHJlYW1pbmdJbnRlcmNlcHRvcnMsXG4gICAgfSk7XG4gICAgb3B0aW9ucy5zdWJzY3JpcHRpb25TdGF0ZS5zZXRTdWJzY3JpYmVkKCk7XG5cbiAgICAvLyBBbGxvdyB0aGUgY2FsbGVyIHRvIGNhbmNlbCB0aGUgc3RyZWFtLlxuICAgIC8vIE5vdGUgdGhhdCBiZWNhdXNlIHdlIHJlc3RhcnQgdGhlIHN0cmVhbSBvbiBlcnJvciBvciBzdHJlYW0gZW5kLFxuICAgIC8vIHdlIG5lZWQgdG8gZW5zdXJlIHdlIGtlZXAgdGhlIHNhbWUgc3Vic2NyaXB0aW9uIG9iamVjdC4gVGhhdCB3YXlcbiAgICAvLyBzdHJlYW0gcmVzdGFydHMgYXJlIHRyYW5zcGFyZW50IHRvIHRoZSBjYWxsZXIuXG4gICAgb3B0aW9ucy5zdWJzY3JpcHRpb25TdGF0ZS51bnN1YnNjcmliZUZuID0gKCkgPT4ge1xuICAgICAgY2FsbC5jYW5jZWwoKTtcbiAgICB9O1xuXG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCBfcmVqZWN0KSA9PiB7XG4gICAgICBjb25zdCBwcmVwYXJlQ2FsbGJhY2tPcHRpb25zOiBQcmVwYXJlU3Vic2NyaWJlQ2FsbGJhY2tPcHRpb25zID0ge1xuICAgICAgICAuLi5vcHRpb25zLFxuICAgICAgICByZXNvbHZlLFxuICAgICAgfTtcbiAgICAgIGNhbGwub24oJ2RhdGEnLCB0aGlzLnByZXBhcmVEYXRhQ2FsbGJhY2socHJlcGFyZUNhbGxiYWNrT3B0aW9ucykpO1xuICAgICAgY2FsbC5vbignZXJyb3InLCB0aGlzLnByZXBhcmVFcnJvckNhbGxiYWNrKHByZXBhcmVDYWxsYmFja09wdGlvbnMpKTtcbiAgICAgIGNhbGwub24oJ2VuZCcsIHRoaXMucHJlcGFyZUVuZENhbGxiYWNrKHByZXBhcmVDYWxsYmFja09wdGlvbnMpKTtcbiAgICB9KTtcbiAgfVxuXG4gIHByaXZhdGUgcHJlcGFyZURhdGFDYWxsYmFjayhcbiAgICBvcHRpb25zOiBQcmVwYXJlU3Vic2NyaWJlQ2FsbGJhY2tPcHRpb25zXG4gICk6IChyZXNwOiBncnBjUHVic3ViLl9TdWJzY3JpcHRpb25JdGVtKSA9PiB2b2lkIHtcbiAgICByZXR1cm4gKHJlc3A6IGdycGNQdWJzdWIuX1N1YnNjcmlwdGlvbkl0ZW0pID0+IHtcbiAgICAgIGlmIChvcHRpb25zLmZpcnN0TWVzc2FnZSkge1xuICAgICAgICBvcHRpb25zLnJlc29sdmUob3B0aW9ucy5zdWJzY3JpcHRpb24pO1xuICAgICAgfVxuICAgICAgb3B0aW9ucy5maXJzdE1lc3NhZ2UgPSBmYWxzZTtcblxuICAgICAgaWYgKHJlc3AuaXRlbSkge1xuICAgICAgICBjb25zdCBzZXF1ZW5jZU51bWJlciA9IHJlc3AuaXRlbS50b3BpY19zZXF1ZW5jZV9udW1iZXI7XG4gICAgICAgIGNvbnN0IHNlcXVlbmNlUGFnZSA9IHJlc3AuaXRlbS5zZXF1ZW5jZV9wYWdlO1xuICAgICAgICBvcHRpb25zLnN1YnNjcmlwdGlvblN0YXRlLmxhc3RUb3BpY1NlcXVlbmNlTnVtYmVyID0gc2VxdWVuY2VOdW1iZXI7XG4gICAgICAgIG9wdGlvbnMuc3Vic2NyaXB0aW9uU3RhdGUubGFzdFRvcGljU2VxdWVuY2VQYWdlID0gc2VxdWVuY2VQYWdlO1xuICAgICAgICB0aGlzLmdldExvZ2dlcigpLnRyYWNlKFxuICAgICAgICAgICdSZWNlaXZlZCBhbiBpdGVtIG9uIHN1YnNjcmlwdGlvbiBzdHJlYW07IHRvcGljOiAlczsgc2VxdWVuY2UgbnVtYmVyOiAlczsgc2VxdWVuY2UgcGFnZTogJXMnLFxuICAgICAgICAgIHRydW5jYXRlU3RyaW5nKG9wdGlvbnMudG9waWNOYW1lKSxcbiAgICAgICAgICBzZXF1ZW5jZU51bWJlcixcbiAgICAgICAgICBzZXF1ZW5jZVBhZ2VcbiAgICAgICAgKTtcbiAgICAgICAgaWYgKHJlc3AuaXRlbS52YWx1ZS50ZXh0KSB7XG4gICAgICAgICAgb3B0aW9ucy5vbkl0ZW0oXG4gICAgICAgICAgICBuZXcgVG9waWNJdGVtKHJlc3AuaXRlbS52YWx1ZS50ZXh0LCBzZXF1ZW5jZU51bWJlciwge1xuICAgICAgICAgICAgICB0b2tlbklkOiByZXNwLml0ZW0ucHVibGlzaGVyX2lkLFxuICAgICAgICAgICAgfSlcbiAgICAgICAgICApO1xuICAgICAgICB9IGVsc2UgaWYgKHJlc3AuaXRlbS52YWx1ZS5iaW5hcnkpIHtcbiAgICAgICAgICBvcHRpb25zLm9uSXRlbShcbiAgICAgICAgICAgIG5ldyBUb3BpY0l0ZW0ocmVzcC5pdGVtLnZhbHVlLmJpbmFyeSwgc2VxdWVuY2VOdW1iZXIsIHtcbiAgICAgICAgICAgICAgdG9rZW5JZDogcmVzcC5pdGVtLnB1Ymxpc2hlcl9pZCxcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLmdldExvZ2dlcigpLmVycm9yKFxuICAgICAgICAgICAgJ1JlY2VpdmVkIHN1YnNjcmlwdGlvbiBpdGVtIHdpdGggdW5rbm93biB0eXBlOyB0b3BpYzogJXMnLFxuICAgICAgICAgICAgdHJ1bmNhdGVTdHJpbmcob3B0aW9ucy50b3BpY05hbWUpXG4gICAgICAgICAgKTtcbiAgICAgICAgICBvcHRpb25zLm9uRXJyb3IoXG4gICAgICAgICAgICBuZXcgVG9waWNTdWJzY3JpYmUuRXJyb3IoXG4gICAgICAgICAgICAgIG5ldyBVbmtub3duRXJyb3IoJ1Vua25vd24gaXRlbSB2YWx1ZSB0eXBlJylcbiAgICAgICAgICAgICksXG4gICAgICAgICAgICBvcHRpb25zLnN1YnNjcmlwdGlvblxuICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSBpZiAocmVzcC5oZWFydGJlYXQpIHtcbiAgICAgICAgdGhpcy5nZXRMb2dnZXIoKS50cmFjZShcbiAgICAgICAgICAnUmVjZWl2ZWQgaGVhcnRiZWF0IGZyb20gc3Vic2NyaXB0aW9uIHN0cmVhbTsgdG9waWM6ICVzJyxcbiAgICAgICAgICB0cnVuY2F0ZVN0cmluZyhvcHRpb25zLnRvcGljTmFtZSlcbiAgICAgICAgKTtcbiAgICAgICAgb3B0aW9ucy5vbkhlYXJ0YmVhdChuZXcgVG9waWNIZWFydGJlYXQoKSk7XG4gICAgICB9IGVsc2UgaWYgKHJlc3AuZGlzY29udGludWl0eSkge1xuICAgICAgICBjb25zdCB0b3BpY0Rpc2NvbnRpbnVpdHkgPSBuZXcgVG9waWNEaXNjb250aW51aXR5KFxuICAgICAgICAgIHJlc3AuZGlzY29udGludWl0eS5sYXN0X3RvcGljX3NlcXVlbmNlLFxuICAgICAgICAgIHJlc3AuZGlzY29udGludWl0eS5uZXdfdG9waWNfc2VxdWVuY2UsXG4gICAgICAgICAgcmVzcC5kaXNjb250aW51aXR5Lm5ld19zZXF1ZW5jZV9wYWdlXG4gICAgICAgICk7XG4gICAgICAgIHRoaXMuZ2V0TG9nZ2VyKCkudHJhY2UoXG4gICAgICAgICAgJ1JlY2VpdmVkIGRpc2NvbnRpbnVpdHkgZnJvbSBzdWJzY3JpcHRpb24gc3RyZWFtOyB0b3BpYzogJXM7ICVzJyxcbiAgICAgICAgICB0cnVuY2F0ZVN0cmluZyhvcHRpb25zLnRvcGljTmFtZSksXG4gICAgICAgICAgdG9waWNEaXNjb250aW51aXR5LnRvU3RyaW5nKClcbiAgICAgICAgKTtcbiAgICAgICAgb3B0aW9ucy5zdWJzY3JpcHRpb25TdGF0ZS5sYXN0VG9waWNTZXF1ZW5jZU51bWJlciA9XG4gICAgICAgICAgcmVzcC5kaXNjb250aW51aXR5Lm5ld190b3BpY19zZXF1ZW5jZTtcbiAgICAgICAgb3B0aW9ucy5zdWJzY3JpcHRpb25TdGF0ZS5sYXN0VG9waWNTZXF1ZW5jZVBhZ2UgPVxuICAgICAgICAgIHJlc3AuZGlzY29udGludWl0eS5uZXdfc2VxdWVuY2VfcGFnZTtcbiAgICAgICAgb3B0aW9ucy5vbkRpc2NvbnRpbnVpdHkodG9waWNEaXNjb250aW51aXR5KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuZ2V0TG9nZ2VyKCkuZXJyb3IoXG4gICAgICAgICAgJ1JlY2VpdmVkIHVua25vd24gc3Vic2NyaXB0aW9uIGl0ZW07IHRvcGljOiAlcycsXG4gICAgICAgICAgdHJ1bmNhdGVTdHJpbmcob3B0aW9ucy50b3BpY05hbWUpXG4gICAgICAgICk7XG4gICAgICAgIG9wdGlvbnMub25FcnJvcihcbiAgICAgICAgICBuZXcgVG9waWNTdWJzY3JpYmUuRXJyb3IobmV3IFVua25vd25FcnJvcignVW5rbm93biBpdGVtIHR5cGUnKSksXG4gICAgICAgICAgb3B0aW9ucy5zdWJzY3JpcHRpb25cbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICB9O1xuICB9XG5cbiAgcHJpdmF0ZSBwcmVwYXJlRXJyb3JDYWxsYmFjayhcbiAgICBvcHRpb25zOiBQcmVwYXJlU3Vic2NyaWJlQ2FsbGJhY2tPcHRpb25zXG4gICk6IChlcnI6IEVycm9yKSA9PiB2b2lkIHtcbiAgICByZXR1cm4gKGVycjogRXJyb3IpID0+IHtcbiAgICAgIC8vIFdoZW4gdGhlIGNhbGxlciB1bnN1YnNjcmliZXMsIHdlIG1heSBnZXQgYSBmb2xsb3cgb24gZXJyb3IsIHdoaWNoIHdlIGlnbm9yZS5cbiAgICAgIGlmICghb3B0aW9ucy5zdWJzY3JpcHRpb25TdGF0ZS5pc1N1YnNjcmliZWQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICBjb25zdCBzZXJ2aWNlRXJyb3IgPSBlcnIgYXMgdW5rbm93biBhcyBTZXJ2aWNlRXJyb3I7XG4gICAgICB0aGlzLmdldExvZ2dlcigpLnRyYWNlKFxuICAgICAgICBgU3Vic2NyaXB0aW9uIGVuY291bnRlcmVkIGFuIGVycm9yOiAke3NlcnZpY2VFcnJvci5jb2RlfTogJHtzZXJ2aWNlRXJyb3IubWVzc2FnZX06ICR7c2VydmljZUVycm9yLmRldGFpbHN9YFxuICAgICAgKTtcbiAgICAgIGNvbnN0IHNob3VsZFJlY29ubmVjdFN1YnNjcmlwdGlvbiA9XG4gICAgICAgIC8vIHByZXZpb3VzbHksIHdlIHdlcmUgb25seSBhdHRlbXB0aW5nIGEgcmVjb25uZWN0IG9uIHRoaXMgb25lIHZlcnkgc3BlY2lmaWMgY2FzZSwgYnV0IG91ciBjdXJyZW50IGV4cGVjdGF0aW9uIGlzIHRoYXRcbiAgICAgICAgLy8gd2Ugc2hvdWxkIGVyciBvbiB0aGUgc2lkZSBvZiByZXRyeWluZy4gVGhpcyBtYXkgYmVjb21lIGEgc29ydCBvZiBcImRlbnkgbGlzdFwiIG9mIGVycm9yIHR5cGVzIHRvICpub3QqIHJldHJ5IG9uXG4gICAgICAgIC8vIGluIHRoZSBmdXR1cmUsIGJ1dCBmb3Igbm93IHdlIHdpbGwgYmUgYWdncmVzc2l2ZSBhYm91dCByZXRyeWluZy5cbiAgICAgICAgLy8gLy8gc2VydmljZUVycm9yLmNvZGUgPT09IFN0YXR1cy5JTlRFUk5BTCAmJlxuICAgICAgICAvLyAgLy8gc2VydmljZUVycm9yLmRldGFpbHMgPT09IFB1YnN1YkNsaWVudC5SU1RfU1RSRUFNX05PX0VSUk9SX01FU1NBR0U7XG4gICAgICAgIHRydWU7XG4gICAgICBjb25zdCBtb21lbnRvRXJyb3IgPSBuZXcgVG9waWNTdWJzY3JpYmUuRXJyb3IoXG4gICAgICAgIHRoaXMuZ2V0Q2FjaGVTZXJ2aWNlRXJyb3JNYXBwZXIoKS5jb252ZXJ0RXJyb3Ioc2VydmljZUVycm9yKVxuICAgICAgKTtcbiAgICAgIHRoaXMuaGFuZGxlU3Vic2NyaWJlRXJyb3IoXG4gICAgICAgIG9wdGlvbnMsXG4gICAgICAgIG1vbWVudG9FcnJvcixcbiAgICAgICAgc2hvdWxkUmVjb25uZWN0U3Vic2NyaXB0aW9uXG4gICAgICApO1xuICAgIH07XG4gIH1cblxuICBwcml2YXRlIHN0YXRpYyBpbml0aWFsaXplVW5hcnlJbnRlcmNlcHRvcnMoXG4gICAgaGVhZGVyczogSGVhZGVyW10sXG4gICAgY29uZmlndXJhdGlvbjogVG9waWNDb25maWd1cmF0aW9uLFxuICAgIHJlcXVlc3RUaW1lb3V0TXM6IG51bWJlclxuICApOiBJbnRlcmNlcHRvcltdIHtcbiAgICByZXR1cm4gW1xuICAgICAgbWlkZGxld2FyZXNJbnRlcmNlcHRvcihjb25maWd1cmF0aW9uLmdldExvZ2dlckZhY3RvcnkoKSwgW10sIHt9KSxcbiAgICAgIEhlYWRlckludGVyY2VwdG9yLmNyZWF0ZUhlYWRlcnNJbnRlcmNlcHRvcihoZWFkZXJzKSxcbiAgICAgIFJldHJ5SW50ZXJjZXB0b3IuY3JlYXRlUmV0cnlJbnRlcmNlcHRvcih7XG4gICAgICAgIGNsaWVudE5hbWU6ICdQdWJTdWJDbGllbnQnLFxuICAgICAgICBsb2dnZXJGYWN0b3J5OiBjb25maWd1cmF0aW9uLmdldExvZ2dlckZhY3RvcnkoKSxcbiAgICAgICAgb3ZlcmFsbFJlcXVlc3RUaW1lb3V0TXM6IHJlcXVlc3RUaW1lb3V0TXMsXG4gICAgICB9KSxcbiAgICBdO1xuICB9XG5cbiAgLy8gVE9ETyBodHRwczovL2dpdGh1Yi5jb20vbW9tZW50b2hxL2NsaWVudC1zZGstbm9kZWpzL2lzc3Vlcy8zNDlcbiAgLy8gZGVjaWRlIG9uIHN0cmVhbWluZyBpbnRlcmNlcHRvcnMgYW5kIG1pZGRsZXdhcmVzXG4gIHByaXZhdGUgc3RhdGljIGluaXRpYWxpemVTdHJlYW1pbmdJbnRlcmNlcHRvcnMoXG4gICAgaGVhZGVyczogSGVhZGVyW11cbiAgKTogSW50ZXJjZXB0b3JbXSB7XG4gICAgcmV0dXJuIFtIZWFkZXJJbnRlcmNlcHRvci5jcmVhdGVIZWFkZXJzSW50ZXJjZXB0b3IoaGVhZGVycyldO1xuICB9XG59XG4iXX0=
© 2015 - 2025 Weber Informatics LLC | Privacy Policy