UNPKG

@decaf-ts/for-nano

Version:

decaf-ts persistence adapter for CouchDB via nano

856 lines (850 loc) 102 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@decaf-ts/db-decorators'), require('reflect-metadata'), require('@decaf-ts/for-couchdb'), require('nano'), require('@decaf-ts/decorator-validation'), require('@decaf-ts/core')) : typeof define === 'function' && define.amd ? define(['exports', '@decaf-ts/db-decorators', 'reflect-metadata', '@decaf-ts/for-couchdb', 'nano', '@decaf-ts/decorator-validation', '@decaf-ts/core'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["for-nano"] = {}, global.dbDecorators, null, global.forCouchdb, global.Nano, global.decoratorValidation, global.core)); })(this, (function (exports, dbDecorators, reflectMetadata, forCouchdb, Nano, decoratorValidation, core) { 'use strict'; /** * @description Identifier for the Nano database flavor * @summary Constant string that identifies the database type as "nano" for use in adapter selection and configuration * @const NanoFlavour * @memberOf module:for-nano */ const NanoFlavour = "nano"; /** * @description Dispatcher for Nano database change events * @summary Handles the subscription to and processing of database change events from a Nano database, * notifying observers when documents are created, updated, or deleted * @template DocumentScope - The Nano document scope type * @param {number} [timeout=5000] - Timeout in milliseconds for change feed requests * @class NanoDispatch * @example * ```typescript * // Create a dispatcher for a Nano database * const db = server.db.use('my_database'); * const adapter = new NanoAdapter(db); * const dispatch = new NanoDispatch(); * * // The dispatcher will automatically subscribe to changes * // and notify observers when documents change * ``` * @mermaid * classDiagram * class Dispatch { * +initialize() * +updateObservers() * } * class NanoDispatch { * -observerLastUpdate?: string * -attemptCounter: number * -timeout: number * +constructor(timeout) * #changeHandler() * #initialize() * } * Dispatch <|-- NanoDispatch */ class NanoDispatch extends core.Dispatch { constructor(timeout = 5000) { super(); this.timeout = timeout; this.attemptCounter = 0; } /** * @description Processes database change events * @summary Handles the response from the Nano changes feed, processes the changes, * and notifies observers about document changes * @param {RequestError | null} error - Error object if the request failed * @param response - The changes response from Nano * @param {any} [headers] - Response headers (unused) * @return {Promise<void>} A promise that resolves when all changes have been processed * @mermaid * sequenceDiagram * participant D as NanoDispatch * participant L as Logger * participant O as Observers * Note over D: Receive changes from Nano * alt Error in response * D->>L: Log error * D-->>D: Return early * end * alt Response is string * D->>D: Parse JSON from string * end * D->>D: Process changes * D->>D: Group changes by table and operation * loop For each table * loop For each operation * D->>O: updateObservers(table, operation, ids) * D->>D: Update observerLastUpdate * D->>L: Log successful dispatch * end * end */ async changeHandler(error, response, // eslint-disable-next-line @typescript-eslint/no-unused-vars headers) { const log = this.log.for(this.changeHandler); if (error) return log.error(`Error in change request: ${error}`); try { response = (typeof response === "string" ? response .split("\n") .filter((r) => !!r) .map((r) => JSON.parse(r)) : response); } catch (e) { return log.error(`Error parsing couchdb change feed: ${e}`); } const count = response.length; if (count > 0) { log.debug(`Received ${count} changes. processing...`); const changes = response .map((rec, i) => { if (i === count - 1) { if (this.observerLastUpdate === rec.last_seq) log.error(`Invalid last update check: ${this.observerLastUpdate} !== ${rec.last_seq}`); return; } const r = rec; const [table, id] = r.id.split(forCouchdb.CouchDBKeys.SEPARATOR); return { table: table, id: id, operation: r.deleted ? dbDecorators.OperationKeys.DELETE : r.changes[r.changes.length - 1].rev.split("-")[0] === "1" ? dbDecorators.OperationKeys.CREATE : dbDecorators.OperationKeys.UPDATE, step: r.changes[r.changes.length - 1].rev, }; }) .reduce((accum, r) => { if (!r) return accum; const { table, id, operation, step } = r; if (!accum[table]) accum[table] = {}; if (!accum[table][operation]) accum[table][operation] = { ids: new Set(), step: step }; accum[table][operation].ids.add(id); accum[table][operation].step = step; return accum; }, {}); for (const table of Object.keys(changes)) { for (const op of Object.keys(changes[table])) { try { await this.updateObservers(table, op, [ ...changes[table][op].ids.values(), ]); this.observerLastUpdate = changes[table][op].step; log.verbose(`Observer refresh dispatched by ${op} for ${table}`); log.debug(`pks: ${Array.from(changes[table][op].ids.values())}`); } catch (e) { log.error(`Failed to dispatch observer refresh for ${table}, op ${op}: ${e}`); } } } } } /** * @description Initializes the dispatcher and subscribes to database changes * @summary Sets up the continuous changes feed subscription to the Nano database * and handles reconnection attempts if the connection fails * @return {Promise<void>} A promise that resolves when the subscription is established * @mermaid * sequenceDiagram * participant D as NanoDispatch * participant S as subscribeToCouch * participant DB as Nano Database * participant L as Logger * D->>S: Call subscribeToCouch * S->>S: Check adapter and native * alt No adapter or native * S-->>S: throw InternalError * end * S->>DB: changes(options, changeHandler) * alt Success * DB-->>S: Subscription established * S-->>D: Promise resolves * D->>L: Log successful subscription * else Error * DB-->>S: Error * S->>S: Increment attemptCounter * alt attemptCounter > 3 * S->>L: Log error * S-->>D: Promise rejects * else attemptCounter <= 3 * S->>L: Log retry * S->>S: Wait timeout * S->>S: Recursive call to subscribeToCouch * end * end */ async initialize() { const log = this.log.for(this.initialize); const subLog = log.for(subscribeToCouch); async function subscribeToCouch() { if (!this.adapter || !this.native) throw new dbDecorators.InternalError(`No adapter/native observed for dispatch`); try { this.native.changes({ feed: "continuous", include_docs: false, since: this.observerLastUpdate || "now", timeout: this.timeout, }, this.changeHandler.bind(this)); } catch (e) { if (++this.attemptCounter > 3) return subLog.error(`Failed to subscribe to couchdb changes: ${e}`); subLog.info(`Failed to subscribe to couchdb changes: ${e}. Retrying in 5 seconds...`); await new Promise((resolve) => setTimeout(resolve, this.timeout)); return subscribeToCouch.call(this); } } subscribeToCouch .call(this) .then(() => { this.log.info(`Subscribed to couchdb changes`); }) .catch((e) => { throw new dbDecorators.InternalError(`Failed to subscribe to couchdb changes: ${e}`); }); } } /** * @description Sets the creator or updater field in a model based on the user in the context * @summary Callback function used in decorators to automatically set the created_by or updated_by fields * with the username from the context when a document is created or updated * @template M - Type extending Model * @template R - Type extending NanoRepository<M> * @template V - Type extending RelationsMetadata * @param {R} this - The repository instance * @param {Context<NanoFlags>} context - The operation context containing user information * @param {V} data - The relation metadata * @param key - The property key to set with the username * @param {M} model - The model instance being created or updated * @return {Promise<void>} A promise that resolves when the operation is complete * @function createdByOnNanoCreateUpdate * @memberOf module:for-nano * @mermaid * sequenceDiagram * participant F as createdByOnNanoCreateUpdate * participant C as Context * participant M as Model * F->>C: get("user") * C-->>F: user object * F->>M: set key to user.name * Note over F: If no user in context * F-->>F: throw UnsupportedError */ async function createdByOnNanoCreateUpdate(context, data, key, model) { try { const user = context.get("user"); model[key] = user.name; // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new core.UnsupportedError("No User found in context. Please provide a user in the context"); } } /** * @description Adapter for interacting with Nano databases * @summary Provides a standardized interface for performing CRUD operations on Nano databases, * extending the CouchDB adapter with Nano-specific functionality. This adapter handles document * creation, reading, updating, and deletion, as well as bulk operations and index management. * @template DocumentScope - The Nano document scope type * @template NanoFlags - Configuration flags for Nano operations * @template Context - Context type for operations * @param {DocumentScope<any>} scope - The Nano document scope to use for database operations * @param {string} [alias] - Optional alias for the adapter * @class NanoAdapter * @example * ```typescript * // Connect to a Nano database * const server = NanoAdapter.connect('admin', 'password', 'localhost:5984'); * const db = server.db.use('my_database'); * * // Create an adapter instance * const adapter = new NanoAdapter(db); * * // Use the adapter for database operations * const document = await adapter.read('users', '123'); * ``` * @mermaid * classDiagram * class CouchDBAdapter { * +flags() * +Dispatch() * +index() * +create() * +read() * +update() * +delete() * } * class NanoAdapter { * +flags() * +Dispatch() * +index() * +create() * +createAll() * +read() * +readAll() * +update() * +updateAll() * +delete() * +deleteAll() * +raw() * +static connect() * +static createDatabase() * +static deleteDatabase() * +static createUser() * +static deleteUser() * +static decoration() * } * CouchDBAdapter <|-- NanoAdapter */ class NanoAdapter extends forCouchdb.CouchDBAdapter { constructor(scope, alias) { super(scope, NanoFlavour, alias); } /** * @description Generates flags for database operations * @summary Creates a set of flags for a specific operation, including user information * @template M - Type extending Model * @param {OperationKeys} operation - The operation being performed (create, read, update, delete) * @param {Constructor<M>} model - The model constructor * @param {Partial<NanoFlags>} flags - Partial flags to be merged * @return {Promise<NanoFlags>} Complete flags for the operation */ async flags(operation, model, flags) { return Object.assign(await super.flags(operation, model, flags), { user: { name: this.native.config.url.split("@")[0].split(":")[0], }, }); } /** * @description Creates a new NanoDispatch instance * @summary Returns a dispatcher for handling Nano-specific operations * @return {NanoDispatch} A new NanoDispatch instance */ Dispatch() { return new NanoDispatch(); } /** * @description Creates database indexes for models * @summary Generates and creates indexes in the Nano database based on the provided models * @template M - Type extending Model * @param models - Model constructors to create indexes for * @return {Promise<void>} A promise that resolves when all indexes are created * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant G as generateIndexes * participant DB as Nano Database * A->>G: generateIndexes(models) * G-->>A: indexes * loop For each index * A->>DB: createIndex(index) * DB-->>A: response * Note over A: Check if index already exists * alt Index exists * A-->>A: throw ConflictError * end * end */ async index(...models) { const indexes = forCouchdb.generateIndexes(models); for (const index of indexes) { const res = await this.native.createIndex(index); const { result, id, name } = res; if (result === "existing") throw new dbDecorators.ConflictError(`Index for table ${name} with id ${id}`); } } /** * @description Creates a new document in the database * @summary Inserts a new document into the Nano database with the provided data * @param {string} tableName - The name of the table/collection * @param {string | number} id - The document identifier * @param {Record<string, any>} model - The document data to insert * @return {Promise<Record<string, any>>} A promise that resolves to the created document with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>DB: insert(model) * alt Success * DB-->>A: response with ok=true * A->>A: assignMetadata(model, response.rev) * A-->>A: return document with metadata * else Error * DB-->>A: error * A-->>A: throw parseError(e) * else Not OK * DB-->>A: response with ok=false * A-->>A: throw InternalError * end */ async create(tableName, id, model) { let response; try { response = await this.native.insert(model); } catch (e) { throw this.parseError(e); } if (!response.ok) throw new dbDecorators.InternalError(`Failed to insert doc id: ${id} in table ${tableName}`); return this.assignMetadata(model, response.rev); } /** * @description Creates multiple documents in the database * @summary Inserts multiple documents into the Nano database in a single bulk operation * @param {string} tableName - The name of the table/collection * @param {string[] | number[]} ids - Array of document identifiers * @param models - Array of document data to insert * @return A promise that resolves to an array of created documents with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>DB: bulk({docs: models}) * alt Success * DB-->>A: response array * A->>A: Check if all responses have no errors * alt All OK * A->>A: assignMultipleMetadata(models, revs) * A-->>A: return documents with metadata * else Some errors * A->>A: Collect error messages * A-->>A: throw InternalError with collected messages * end * else Error * DB-->>A: error * A-->>A: throw parseError(e) * end */ async createAll(tableName, ids, models) { let response; try { response = await this.native.bulk({ docs: models }); } catch (e) { throw this.parseError(e); } if (!response.every((r) => !r.error)) { const errors = response.reduce((accum, el, i) => { if (el.error) accum.push(`el ${i}: ${el.error}${el.reason ? ` - ${el.reason}` : ""}`); return accum; }, []); throw new dbDecorators.InternalError(errors.join("\n")); } return this.assignMultipleMetadata(models, response.map((r) => r.rev)); } /** * @description Retrieves a document from the database * @summary Fetches a single document from the Nano database by its ID * @param {string} tableName - The name of the table/collection * @param {string | number} id - The document identifier * @return {Promise<Record<string, any>>} A promise that resolves to the retrieved document with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>A: generateId(tableName, id) * A->>DB: get(_id) * alt Success * DB-->>A: record * A->>A: assignMetadata(record, record._rev) * A-->>A: return document with metadata * else Error * DB-->>A: error * A-->>A: throw parseError(e) * end */ async read(tableName, id) { const _id = this.generateId(tableName, id); let record; try { record = await this.native.get(_id); } catch (e) { throw this.parseError(e); } return this.assignMetadata(record, record._rev); } /** * @description Retrieves multiple documents from the database * @summary Fetches multiple documents from the Nano database by their IDs in a single operation * @param {string} tableName - The name of the table/collection * @param {Array<string | number | bigint>} ids - Array of document identifiers * @return A promise that resolves to an array of retrieved documents with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>A: Map ids to generateId(tableName, id) * A->>DB: fetch({keys: mappedIds}, {}) * DB-->>A: results * A->>A: Process each result row * loop For each row * alt Row has error * A-->>A: throw InternalError * else Row has document * A->>A: assignMetadata(doc, doc._rev) * else No document * A-->>A: throw InternalError * end * end * A-->>A: return documents with metadata */ async readAll(tableName, ids) { const results = await this.native.fetch({ keys: ids.map((id) => this.generateId(tableName, id)) }, {}); return results.rows.map((r) => { if (r.error) throw new dbDecorators.InternalError(r.error); if (r.doc) { const res = Object.assign({}, r.doc); return this.assignMetadata(res, r.doc[forCouchdb.CouchDBKeys.REV]); } throw new dbDecorators.InternalError("Should be impossible"); }); } /** * @description Updates a document in the database * @summary Updates an existing document in the Nano database with the provided data * @param {string} tableName - The name of the table/collection * @param {string | number} id - The document identifier * @param {Record<string, any>} model - The updated document data * @return {Promise<Record<string, any>>} A promise that resolves to the updated document with metadata * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Database * A->>DB: insert(model) * alt Success * DB-->>A: response with ok=true * A->>A: assignMetadata(model, response.rev) * A-->>A: return document with metadata * else Error * DB-->>A: error * A-->>A: throw parseError(e) * else Not OK * DB-->>A: response with ok=false * A-->>A: throw InternalError * end */ async update(tableName, id, model) { let response; try { response = await this.native.insert(model); } catch (e) { throw this.parseError(e); } if (!response.ok) throw new dbDecorators.InternalError(`Failed to update doc id: ${id} in table ${tableName}`); return this.assignMetadata(model, response.rev); } async updateAll(tableName, ids, models) { let response; try { response = await this.native.bulk({ docs: models }); } catch (e) { throw this.parseError(e); } if (!response.every((r) => !r.error)) { const errors = response.reduce((accum, el, i) => { if (el.error) accum.push(`el ${i}: ${el.error}${el.reason ? ` - ${el.reason}` : ""}`); return accum; }, []); throw new dbDecorators.InternalError(errors.join("\n")); } return this.assignMultipleMetadata(models, response.map((r) => r.rev)); } async delete(tableName, id) { const _id = this.generateId(tableName, id); let record; try { record = await this.native.get(_id); await this.native.destroy(_id, record._rev); } catch (e) { throw this.parseError(e); } return this.assignMetadata(record, record._rev); } async deleteAll(tableName, ids) { const results = await this.native.fetch({ keys: ids.map((id) => this.generateId(tableName, id)) }, {}); const deletion = await this.native.bulk({ docs: results.rows.map((r) => { r[forCouchdb.CouchDBKeys.DELETED] = true; return r; }), }); deletion.forEach((d) => { if (d.error) console.error(d.error); }); return results.rows.map((r) => { if (r.error) throw new dbDecorators.InternalError(r.error); if (r.doc) { const res = Object.assign({}, r.doc); return this.assignMetadata(res, r.doc[forCouchdb.CouchDBKeys.REV]); } throw new dbDecorators.InternalError("Should be impossible"); }); } async raw(rawInput, docsOnly = true) { try { const response = await this.native.find(rawInput); if (response.warning) console.warn(response.warning); if (docsOnly) return response.docs; return response; } catch (e) { throw this.parseError(e); } } static connect(user, pass, host = "localhost:5984", protocol = "http") { return Nano(`${protocol}://${user}:${pass}@${host}`); } /** * @description Creates a new database on the Nano server * @summary Creates a new database with the specified name on the connected Nano server * @param {ServerScope} con - The Nano server connection * @param {string} name - The name of the database to create * @return {Promise<void>} A promise that resolves when the database is created * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Server * A->>DB: db.create(name) * alt Success * DB-->>A: result with ok=true * else Error * DB-->>A: error * A-->>A: throw parseError(e) * else Not OK * DB-->>A: result with ok=false * A-->>A: throw parseError(error, reason) * end */ static async createDatabase(con, name) { let result; try { result = await con.db.create(name); } catch (e) { throw forCouchdb.CouchDBAdapter.parseError(e); } const { ok, error, reason } = result; if (!ok) throw forCouchdb.CouchDBAdapter.parseError(error, reason); } /** * @description Deletes a database from the Nano server * @summary Removes an existing database with the specified name from the connected Nano server * @param {ServerScope} con - The Nano server connection * @param {string} name - The name of the database to delete * @return {Promise<void>} A promise that resolves when the database is deleted * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant DB as Nano Server * A->>DB: db.destroy(name) * alt Success * DB-->>A: result with ok=true * else Error * DB-->>A: error * A-->>A: throw parseError(e) * else Not OK * DB-->>A: result with ok=false * A-->>A: throw InternalError * end */ static async deleteDatabase(con, name) { let result; try { result = await con.db.destroy(name); } catch (e) { throw forCouchdb.CouchDBAdapter.parseError(e); } const { ok } = result; if (!ok) throw new dbDecorators.InternalError(`Failed to delete database with name ${name}`); } /** * @description Creates a new user and grants access to a database * @summary Creates a new user in the Nano server and configures security to grant the user access to a specific database * @param {ServerScope} con - The Nano server connection * @param {string} dbName - The name of the database to grant access to * @param {string} user - The username to create * @param {string} pass - The password for the new user * @param {string[]} [roles=["reader", "writer"]] - The roles to assign to the user * @return {Promise<void>} A promise that resolves when the user is created and granted access * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant U as _users Database * participant S as Security API * A->>A: Create user object * A->>U: insert(user) * alt Success * U-->>A: response with ok=true * A->>S: PUT _security with user permissions * alt Security Success * S-->>A: security response with ok=true * else Security Failure * S-->>A: security response with ok=false * A-->>A: throw InternalError * end * else Error * U-->>A: error * A-->>A: throw parseError(e) * else Not OK * U-->>A: response with ok=false * A-->>A: throw InternalError * end */ static async createUser(con, dbName, user, pass, roles = ["reader", "writer"]) { const users = con.db.use("_users"); const usr = { _id: "org.couchdb.user:" + user, name: user, password: pass, roles: roles, type: "user", }; try { const created = await users.insert(usr); const { ok } = created; if (!ok) throw new dbDecorators.InternalError(`Failed to create user ${user}`); const security = await con.request({ db: dbName, method: "put", path: "_security", // headers: { // // }, body: { admins: { names: [user], roles: [], }, members: { names: [user], roles: roles, }, }, }); if (!security.ok) throw new dbDecorators.InternalError(`Failed to authorize user ${user} to db ${dbName}`); } catch (e) { throw forCouchdb.CouchDBAdapter.parseError(e); } } /** * @description Deletes a user from the Nano server * @summary Removes an existing user from the Nano server * @param {ServerScope} con - The Nano server connection * @param {string} dbName - The name of the database (used for logging purposes) * @param {string} user - The username to delete * @return {Promise<void>} A promise that resolves when the user is deleted * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant U as _users Database * A->>A: Generate user ID * A->>U: get(id) * U-->>A: user document * A->>U: destroy(id, user._rev) * alt Success * U-->>A: success response * else Error * U-->>A: error * A-->>A: throw parseError(e) * end */ static async deleteUser(con, dbName, user) { const users = con.db.use("_users"); const id = "org.couchdb.user:" + user; try { const usr = await users.get(id); await users.destroy(id, usr._rev); } catch (e) { throw forCouchdb.CouchDBAdapter.parseError(e); } } /** * @description Sets up decorations for Nano-specific model properties * @summary Configures decorators for created_by and updated_by fields in models to be automatically * populated with the user from the context when documents are created or updated * @return {void} * @mermaid * sequenceDiagram * participant A as NanoAdapter * participant D as Decoration * participant R as Repository * A->>R: key(PersistenceKeys.CREATED_BY) * R-->>A: createdByKey * A->>D: flavouredAs("nano") * A->>D: for(createdByKey) * A->>D: define(onCreate(createdByOnNanoCreateUpdate), propMetadata) * A->>D: apply() * A->>R: key(PersistenceKeys.UPDATED_BY) * R-->>A: updatedByKey * A->>D: flavouredAs("nano") * A->>D: for(updatedByKey) * A->>D: define(onCreate(createdByOnNanoCreateUpdate), propMetadata) * A->>D: apply() */ static decoration() { const createdByKey = core.Repository.key(core.PersistenceKeys.CREATED_BY); const updatedByKey = core.Repository.key(core.PersistenceKeys.UPDATED_BY); decoratorValidation.Decoration.flavouredAs("nano") .for(createdByKey) .define(dbDecorators.onCreate(createdByOnNanoCreateUpdate), decoratorValidation.propMetadata(createdByKey, {})) .apply(); decoratorValidation.Decoration.flavouredAs("nano") .for(updatedByKey) .define(dbDecorators.onCreate(createdByOnNanoCreateUpdate), decoratorValidation.propMetadata(updatedByKey, {})) .apply(); } } // Forces override for Nano Decoration NanoAdapter.decoration(); /** * @description A TypeScript module for interacting with Nano databases * @summary This module provides a set of utilities, classes, and types for working with Nano databases. It includes repository patterns, adapters, and type definitions to simplify database operations. * @module for-nano */ /** * @description Package version identifier * @summary Stores the current package version string for the for-nano module * @const VERSION * @memberOf module:for-nano */ const VERSION = "0.1.6"; exports.NanoAdapter = NanoAdapter; exports.NanoFlavour = NanoFlavour; exports.VERSION = VERSION; exports.createdByOnNanoCreateUpdate = createdByOnNanoCreateUpdate; })); //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9yLW5hbm8uY2pzIiwic291cmNlcyI6WyIuLi9zcmMvY29uc3RhbnRzLnRzIiwiLi4vc3JjL05hbm9EaXNwYXRjaC50cyIsIi4uL3NyYy9hZGFwdGVyLnRzIiwiLi4vc3JjL2luZGV4LnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGRlc2NyaXB0aW9uIElkZW50aWZpZXIgZm9yIHRoZSBOYW5vIGRhdGFiYXNlIGZsYXZvclxuICogQHN1bW1hcnkgQ29uc3RhbnQgc3RyaW5nIHRoYXQgaWRlbnRpZmllcyB0aGUgZGF0YWJhc2UgdHlwZSBhcyBcIm5hbm9cIiBmb3IgdXNlIGluIGFkYXB0ZXIgc2VsZWN0aW9uIGFuZCBjb25maWd1cmF0aW9uXG4gKiBAY29uc3QgTmFub0ZsYXZvdXJcbiAqIEBtZW1iZXJPZiBtb2R1bGU6Zm9yLW5hbm9cbiAqL1xuZXhwb3J0IGNvbnN0IE5hbm9GbGF2b3VyID0gXCJuYW5vXCI7XG4iLCJpbXBvcnQgeyBEaXNwYXRjaCB9IGZyb20gXCJAZGVjYWYtdHMvY29yZVwiO1xuaW1wb3J0IHtcbiAgRGF0YWJhc2VDaGFuZ2VzUmVzcG9uc2UsXG4gIERhdGFiYXNlQ2hhbmdlc1Jlc3VsdEl0ZW0sXG4gIERvY3VtZW50U2NvcGUsXG4gIFJlcXVlc3RFcnJvcixcbn0gZnJvbSBcIm5hbm9cIjtcbmltcG9ydCB7IEludGVybmFsRXJyb3IsIE9wZXJhdGlvbktleXMgfSBmcm9tIFwiQGRlY2FmLXRzL2RiLWRlY29yYXRvcnNcIjtcbmltcG9ydCB7IENvdWNoREJLZXlzIH0gZnJvbSBcIkBkZWNhZi10cy9mb3ItY291Y2hkYlwiO1xuXG4vKipcbiAqIEBkZXNjcmlwdGlvbiBEaXNwYXRjaGVyIGZvciBOYW5vIGRhdGFiYXNlIGNoYW5nZSBldmVudHNcbiAqIEBzdW1tYXJ5IEhhbmRsZXMgdGhlIHN1YnNjcmlwdGlvbiB0byBhbmQgcHJvY2Vzc2luZyBvZiBkYXRhYmFzZSBjaGFuZ2UgZXZlbnRzIGZyb20gYSBOYW5vIGRhdGFiYXNlLFxuICogbm90aWZ5aW5nIG9ic2VydmVycyB3aGVuIGRvY3VtZW50cyBhcmUgY3JlYXRlZCwgdXBkYXRlZCwgb3IgZGVsZXRlZFxuICogQHRlbXBsYXRlIERvY3VtZW50U2NvcGUgLSBUaGUgTmFubyBkb2N1bWVudCBzY29wZSB0eXBlXG4gKiBAcGFyYW0ge251bWJlcn0gW3RpbWVvdXQ9NTAwMF0gLSBUaW1lb3V0IGluIG1pbGxpc2Vjb25kcyBmb3IgY2hhbmdlIGZlZWQgcmVxdWVzdHNcbiAqIEBjbGFzcyBOYW5vRGlzcGF0Y2hcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBDcmVhdGUgYSBkaXNwYXRjaGVyIGZvciBhIE5hbm8gZGF0YWJhc2VcbiAqIGNvbnN0IGRiID0gc2VydmVyLmRiLnVzZSgnbXlfZGF0YWJhc2UnKTtcbiAqIGNvbnN0IGFkYXB0ZXIgPSBuZXcgTmFub0FkYXB0ZXIoZGIpO1xuICogY29uc3QgZGlzcGF0Y2ggPSBuZXcgTmFub0Rpc3BhdGNoKCk7XG4gKlxuICogLy8gVGhlIGRpc3BhdGNoZXIgd2lsbCBhdXRvbWF0aWNhbGx5IHN1YnNjcmliZSB0byBjaGFuZ2VzXG4gKiAvLyBhbmQgbm90aWZ5IG9ic2VydmVycyB3aGVuIGRvY3VtZW50cyBjaGFuZ2VcbiAqIGBgYFxuICogQG1lcm1haWRcbiAqIGNsYXNzRGlhZ3JhbVxuICogICBjbGFzcyBEaXNwYXRjaCB7XG4gKiAgICAgK2luaXRpYWxpemUoKVxuICogICAgICt1cGRhdGVPYnNlcnZlcnMoKVxuICogICB9XG4gKiAgIGNsYXNzIE5hbm9EaXNwYXRjaCB7XG4gKiAgICAgLW9ic2VydmVyTGFzdFVwZGF0ZT86IHN0cmluZ1xuICogICAgIC1hdHRlbXB0Q291bnRlcjogbnVtYmVyXG4gKiAgICAgLXRpbWVvdXQ6IG51bWJlclxuICogICAgICtjb25zdHJ1Y3Rvcih0aW1lb3V0KVxuICogICAgICNjaGFuZ2VIYW5kbGVyKClcbiAqICAgICAjaW5pdGlhbGl6ZSgpXG4gKiAgIH1cbiAqICAgRGlzcGF0Y2ggPHwtLSBOYW5vRGlzcGF0Y2hcbiAqL1xuZXhwb3J0IGNsYXNzIE5hbm9EaXNwYXRjaCBleHRlbmRzIERpc3BhdGNoPERvY3VtZW50U2NvcGU8YW55Pj4ge1xuICBwcml2YXRlIG9ic2VydmVyTGFzdFVwZGF0ZT86IHN0cmluZztcbiAgcHJpdmF0ZSBhdHRlbXB0Q291bnRlcjogbnVtYmVyID0gMDtcbiAgY29uc3RydWN0b3IocHJpdmF0ZSB0aW1lb3V0ID0gNTAwMCkge1xuICAgIHN1cGVyKCk7XG4gIH1cblxuICAvKipcbiAgICogQGRlc2NyaXB0aW9uIFByb2Nlc3NlcyBkYXRhYmFzZSBjaGFuZ2UgZXZlbnRzXG4gICAqIEBzdW1tYXJ5IEhhbmRsZXMgdGhlIHJlc3BvbnNlIGZyb20gdGhlIE5hbm8gY2hhbmdlcyBmZWVkLCBwcm9jZXNzZXMgdGhlIGNoYW5nZXMsXG4gICAqIGFuZCBub3RpZmllcyBvYnNlcnZlcnMgYWJvdXQgZG9jdW1lbnQgY2hhbmdlc1xuICAgKiBAcGFyYW0ge1JlcXVlc3RFcnJvciB8IG51bGx9IGVycm9yIC0gRXJyb3Igb2JqZWN0IGlmIHRoZSByZXF1ZXN0IGZhaWxlZFxuICAgKiBAcGFyYW0gcmVzcG9uc2UgLSBUaGUgY2hhbmdlcyByZXNwb25zZSBmcm9tIE5hbm9cbiAgICogQHBhcmFtIHthbnl9IFtoZWFkZXJzXSAtIFJlc3BvbnNlIGhlYWRlcnMgKHVudXNlZClcbiAgICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn0gQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiBhbGwgY2hhbmdlcyBoYXZlIGJlZW4gcHJvY2Vzc2VkXG4gICAqIEBtZXJtYWlkXG4gICAqIHNlcXVlbmNlRGlhZ3JhbVxuICAgKiAgIHBhcnRpY2lwYW50IEQgYXMgTmFub0Rpc3BhdGNoXG4gICAqICAgcGFydGljaXBhbnQgTCBhcyBMb2dnZXJcbiAgICogICBwYXJ0aWNpcGFudCBPIGFzIE9ic2VydmVyc1xuICAgKiAgIE5vdGUgb3ZlciBEOiBSZWNlaXZlIGNoYW5nZXMgZnJvbSBOYW5vXG4gICAqICAgYWx0IEVycm9yIGluIHJlc3BvbnNlXG4gICAqICAgICBELT4+TDogTG9nIGVycm9yXG4gICAqICAgICBELS0+PkQ6IFJldHVybiBlYXJseVxuICAgKiAgIGVuZFxuICAgKiAgIGFsdCBSZXNwb25zZSBpcyBzdHJpbmdcbiAgICogICAgIEQtPj5EOiBQYXJzZSBKU09OIGZyb20gc3RyaW5nXG4gICAqICAgZW5kXG4gICAqICAgRC0+PkQ6IFByb2Nlc3MgY2hhbmdlc1xuICAgKiAgIEQtPj5EOiBHcm91cCBjaGFuZ2VzIGJ5IHRhYmxlIGFuZCBvcGVyYXRpb25cbiAgICogICBsb29wIEZvciBlYWNoIHRhYmxlXG4gICAqICAgICBsb29wIEZvciBlYWNoIG9wZXJhdGlvblxuICAgKiAgICAgICBELT4+TzogdXBkYXRlT2JzZXJ2ZXJzKHRhYmxlLCBvcGVyYXRpb24sIGlkcylcbiAgICogICAgICAgRC0+PkQ6IFVwZGF0ZSBvYnNlcnZlckxhc3RVcGRhdGVcbiAgICogICAgICAgRC0+Pkw6IExvZyBzdWNjZXNzZnVsIGRpc3BhdGNoXG4gICAqICAgICBlbmRcbiAgICogICBlbmRcbiAgICovXG4gIHByb3RlY3RlZCBhc3luYyBjaGFuZ2VIYW5kbGVyKFxuICAgIGVycm9yOiBSZXF1ZXN0RXJyb3IgfCBudWxsLFxuICAgIHJlc3BvbnNlOiAoRGF0YWJhc2VDaGFuZ2VzUmVzcG9uc2UgfCBEYXRhYmFzZUNoYW5nZXNSZXN1bHRJdGVtKVtdIHwgc3RyaW5nLFxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdW51c2VkLXZhcnNcbiAgICBoZWFkZXJzPzogYW55XG4gICkge1xuICAgIGNvbnN0IGxvZyA9IHRoaXMubG9nLmZvcih0aGlzLmNoYW5nZUhhbmRsZXIpO1xuICAgIGlmIChlcnJvcikgcmV0dXJuIGxvZy5lcnJvcihgRXJyb3IgaW4gY2hhbmdlIHJlcXVlc3Q6ICR7ZXJyb3J9YCk7XG4gICAgdHJ5IHtcbiAgICAgIHJlc3BvbnNlID0gKFxuICAgICAgICB0eXBlb2YgcmVzcG9uc2UgPT09IFwic3RyaW5nXCJcbiAgICAgICAgICA/IHJlc3BvbnNlXG4gICAgICAgICAgICAgIC5zcGxpdChcIlxcblwiKVxuICAgICAgICAgICAgICAuZmlsdGVyKChyKSA9PiAhIXIpXG4gICAgICAgICAgICAgIC5tYXAoKHIpID0+IEpTT04ucGFyc2UocikpXG4gICAgICAgICAgOiByZXNwb25zZVxuICAgICAgKSBhcyBEYXRhYmFzZUNoYW5nZXNSZXNwb25zZVtdO1xuICAgIH0gY2F0Y2ggKGU6IHVua25vd24pIHtcbiAgICAgIHJldHVybiBsb2cuZXJyb3IoYEVycm9yIHBhcnNpbmcgY291Y2hkYiBjaGFuZ2UgZmVlZDogJHtlfWApO1xuICAgIH1cbiAgICBjb25zdCBjb3VudCA9IHJlc3BvbnNlLmxlbmd0aDtcbiAgICBpZiAoY291bnQgPiAwKSB7XG4gICAgICBsb2cuZGVidWcoYFJlY2VpdmVkICR7Y291bnR9IGNoYW5nZXMuIHByb2Nlc3NpbmcuLi5gKTtcbiAgICAgIGNvbnN0IGNoYW5nZXMgPSByZXNwb25zZVxuICAgICAgICAubWFwKChyZWMsIGkpID0+IHtcbiAgICAgICAgICBpZiAoaSA9PT0gY291bnQgLSAxKSB7XG4gICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgIHRoaXMub2JzZXJ2ZXJMYXN0VXBkYXRlID09PVxuICAgICAgICAgICAgICAocmVjIGFzIERhdGFiYXNlQ2hhbmdlc1Jlc3BvbnNlKS5sYXN0X3NlcVxuICAgICAgICAgICAgKVxuICAgICAgICAgICAgICBsb2cuZXJyb3IoXG4gICAgICAgICAgICAgICAgYEludmFsaWQgbGFzdCB1cGRhdGUgY2hlY2s6ICR7dGhpcy5vYnNlcnZlckxhc3RVcGRhdGV9ICE9PSAkeyhyZWMgYXMgRGF0YWJhc2VDaGFuZ2VzUmVzcG9uc2UpLmxhc3Rfc2VxfWBcbiAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgICAgY29uc3QgciA9IHJlYyBhcyBEYXRhYmFzZUNoYW5nZXNSZXN1bHRJdGVtO1xuICAgICAgICAgIGNvbnN0IFt0YWJsZSwgaWRdID0gci5pZC5zcGxpdChDb3VjaERCS2V5cy5TRVBBUkFUT1IpO1xuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICB0YWJsZTogdGFibGUsXG4gICAgICAgICAgICBpZDogaWQsXG4gICAgICAgICAgICBvcGVyYXRpb246IHIuZGVsZXRlZFxuICAgICAgICAgICAgICA/IE9wZXJhdGlvbktleXMuREVMRVRFXG4gICAgICAgICAgICAgIDogci5jaGFuZ2VzW3IuY2hhbmdlcy5sZW5ndGggLSAxXS5yZXYuc3BsaXQoXCItXCIpWzBdID09PSBcIjFcIlxuICAgICAgICAgICAgICAgID8gT3BlcmF0aW9uS2V5cy5DUkVBVEVcbiAgICAgICAgICAgICAgICA6IE9wZXJhdGlvbktleXMuVVBEQVRFLFxuICAgICAgICAgICAgc3RlcDogci5jaGFuZ2VzW3IuY2hhbmdlcy5sZW5ndGggLSAxXS5yZXYsXG4gICAgICAgICAgfTtcbiAgICAgICAgfSlcbiAgICAgICAgLnJlZHVjZShcbiAgICAgICAgICAoXG4gICAgICAgICAgICBhY2N1bTogUmVjb3JkPFxuICAgICAgICAgICAgICBzdHJpbmcsXG4gICAgICAgICAgICAgIFJlY29yZDxcbiAgICAgICAgICAgICAgICBzdHJpbmcsXG4gICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgaWRzOiBTZXQ8YW55PjtcbiAgICAgICAgICAgICAgICAgIHN0ZXA6IHN0cmluZztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgID5cbiAgICAgICAgICAgID4sXG4gICAgICAgICAgICByXG4gICAgICAgICAgKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXIpIHJldHVybiBhY2N1bTtcbiAgICAgICAgICAgIGNvbnN0IHsgdGFibGUsIGlkLCBvcGVyYXRpb24sIHN0ZXAgfSA9IHIgYXMge1xuICAgICAgICAgICAgICB0YWJsZTogc3RyaW5nO1xuICAgICAgICAgICAgICBpZDogc3RyaW5nO1xuICAgICAgICAgICAgICBvcGVyYXRpb246IE9wZXJhdGlvbktleXM7XG4gICAgICAgICAgICAgIHN0ZXA6IHN0cmluZztcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBpZiAoIWFjY3VtW3RhYmxlXSkgYWNjdW1bdGFibGVdID0ge307XG4gICAgICAgICAgICBpZiAoIWFjY3VtW3RhYmxlXVtvcGVyYXRpb25dKVxuICAgICAgICAgICAgICBhY2N1bVt0YWJsZV1bb3BlcmF0aW9uXSA9IHsgaWRzOiBuZXcgU2V0KCksIHN0ZXA6IHN0ZXAgfTtcbiAgICAgICAgICAgIGFjY3VtW3RhYmxlXVtvcGVyYXRpb25dLmlkcy5hZGQoaWQpO1xuICAgICAgICAgICAgYWNjdW1bdGFibGVdW29wZXJhdGlvbl0uc3RlcCA9IHN0ZXA7XG4gICAgICAgICAgICByZXR1cm4gYWNjdW07XG4gICAgICAgICAgfSxcbiAgICAgICAgICB7fVxuICAgICAgICApO1xuXG4gICAgICBmb3IgKGNvbnN0IHRhYmxlIG9mIE9iamVjdC5rZXlzKGNoYW5nZXMpKSB7XG4gICAgICAgIGZvciAoY29uc3Qgb3Agb2YgT2JqZWN0LmtleXMoY2hhbmdlc1t0YWJsZV0pKSB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IHRoaXMudXBkYXRlT2JzZXJ2ZXJzKHRhYmxlLCBvcCwgW1xuICAgICAgICAgICAgICAuLi5jaGFuZ2VzW3RhYmxlXVtvcF0uaWRzLnZhbHVlcygpLFxuICAgICAgICAgICAgXSk7XG4gICAgICAgICAgICB0aGlzLm9ic2VydmVyTGFzdFVwZGF0ZSA9IGNoYW5nZXNbdGFibGVdW29wXS5zdGVwO1xuICAgICAgICAgICAgbG9nLnZlcmJvc2UoYE9ic2VydmVyIHJlZnJlc2ggZGlzcGF0Y2hlZCBieSAke29wfSBmb3IgJHt0YWJsZX1gKTtcbiAgICAgICAgICAgIGxvZy5kZWJ1ZyhgcGtzOiAke0FycmF5LmZyb20oY2hhbmdlc1t0YWJsZV1bb3BdLmlkcy52YWx1ZXMoKSl9YCk7XG4gICAgICAgICAgfSBjYXRjaCAoZTogdW5rbm93bikge1xuICAgICAgICAgICAgbG9nLmVycm9yKFxuICAgICAgICAgICAgICBgRmFpbGVkIHRvIGRpc3BhdGNoIG9ic2VydmVyIHJlZnJlc2ggZm9yICR7dGFibGV9LCBvcCAke29wfTogJHtlfWBcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEBkZXNjcmlwdGlvbiBJbml0aWFsaXplcyB0aGUgZGlzcGF0Y2hlciBhbmQgc3Vic2NyaWJlcyB0byBkYXRhYmFzZSBjaGFuZ2VzXG4gICAqIEBzdW1tYXJ5IFNldHMgdXAgdGhlIGNvbnRpbnVvdXMgY2hhbmdlcyBmZWVkIHN1YnNjcmlwdGlvbiB0byB0aGUgTmFubyBkYXRhYmFzZVxuICAgKiBhbmQgaGFuZGxlcyByZWNvbm5lY3Rpb24gYXR0ZW1wdHMgaWYgdGhlIGNvbm5lY3Rpb24gZmFpbHNcbiAgICogQHJldHVybiB7UHJvbWlzZTx2b2lkPn0gQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiB0aGUgc3Vic2NyaXB0aW9uIGlzIGVzdGFibGlzaGVkXG4gICAqIEBtZXJtYWlkXG4gICAqIHNlcXVlbmNlRGlhZ3JhbVxuICAgKiAgIHBhcnRpY2lwYW50IEQgYXMgTmFub0Rpc3BhdGNoXG4gICAqICAgcGFydGljaXBhbnQgUyBhcyBzdWJzY3JpYmVUb0NvdWNoXG4gICAqICAgcGFydGljaXBhbnQgREIgYXMgTmFubyBEYXRhYmFzZVxuICAgKiAgIHBhcnRpY2lwYW50IEwgYXMgTG9nZ2VyXG4gICAqICAgRC0+PlM6IENhbGwgc3Vic2NyaWJlVG9Db3VjaFxuICAgKiAgIFMtPj5TOiBDaGVjayBhZGFwdGVyIGFuZCBuYXRpdmVcbiAgICogICBhbHQgTm8gYWRhcHRlciBvciBuYXRpdmVcbiAgICogICAgIFMtLT4+UzogdGhyb3cgSW50ZXJuYWxFcnJvclxuICAgKiAgIGVuZFxuICAgKiAgIFMtPj5EQjogY2hhbmdlcyhvcHRpb25zLCBjaGFuZ2VIYW5kbGVyKVxuICAgKiAgIGFsdCBTdWNjZXNzXG4gICAqICAgICBEQi0tPj5TOiBTdWJzY3JpcHRpb24gZXN0YWJsaXNoZWRcbiAgICogICAgIFMtLT4+RDogUHJvbWlzZSByZXNvbHZlc1xuICAgKiAgICAgRC0+Pkw6IExvZyBzdWNjZXNzZnVsIHN1YnNjcmlwdGlvblxuICAgKiAgIGVsc2UgRXJyb3JcbiAgICogICAgIERCLS0+PlM6IEVycm9yXG4gICAqICAgICBTLT4+UzogSW5jcmVtZW50IGF0dGVtcHRDb3VudGVyXG4gICAqICAgICBhbHQgYXR0ZW1wdENvdW50ZXIgPiAzXG4gICAqICAgICAgIFMtPj5MOiBMb2cgZXJyb3JcbiAgICogICAgICAgUy0tPj5EOiBQcm9taXNlIHJlamVjdHNcbiAgICogICAgIGVsc2UgYXR0ZW1wdENvdW50ZXIgPD0gM1xuICAgKiAgICAgICBTLT4+TDogTG9nIHJldHJ5XG4gICAqICAgICAgIFMtPj5TOiBXYWl0IHRpbWVvdXRcbiAgICogICAgICAgUy0+PlM6IFJlY3Vyc2l2ZSBjYWxsIHRvIHN1YnNjcmliZVRvQ291Y2hcbiAgICogICAgIGVuZFxuICAgKiAgIGVuZFxuICAgKi9cbiAgcHJvdGVjdGVkIG92ZXJyaWRlIGFzeW5jIGluaXRpYWxpemUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgbG9nID0gdGhpcy5sb2cuZm9yKHRoaXMuaW5pdGlhbGl6ZSk7XG4gICAgY29uc3Qgc3ViTG9nID0gbG9nLmZvcihzdWJzY3JpYmVUb0NvdWNoKTtcbiAgICBhc3luYyBmdW5jdGlvbiBzdWJzY3JpYmVUb0NvdWNoKHRoaXM6IE5hbm9EaXNwYXRjaCk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgaWYgKCF0aGlzLmFkYXB0ZXIgfHwgIXRoaXMubmF0aXZlKVxuICAgICAgICB0aHJvdyBuZXcgSW50ZXJuYWxFcnJvcihgTm8gYWRhcHRlci9uYXRpdmUgb2JzZXJ2ZWQgZm9yIGRpc3BhdGNoYCk7XG5cbiAgICAgIHRyeSB7XG4gICAgICAgIHRoaXMubmF0aXZlLmNoYW5nZXMoXG4gICAgICAgICAge1xuICAgICAgICAgICAgZmVlZDogXCJjb250aW51b3VzXCIsXG4gICAgICAgICAgICBpbmNsdWRlX2RvY3M6IGZhbHNlLFxuICAgICAgICAgICAgc2luY2U6IHRoaXMub2JzZXJ2ZXJMYXN0VXBkYXRlIHx8IFwibm93XCIsXG4gICAgICAgICAgICB0aW1lb3V0OiB0aGlzLnRpbWVvdXQsXG4gICAgICAgICAgfSxcbiAgICAgICAgICB0aGlzLmNoYW5nZUhhbmRsZXIuYmluZCh0aGlzKSBhcyBhbnlcbiAgICAgICAgKTtcbiAgICAgIH0gY2F0Y2ggKGU6IHVua25vd24pIHtcbiAgICAgICAgaWYgKCsrdGhpcy5hdHRlbXB0Q291bnRlciA+IDMpXG4gICAgICAgICAgcmV0dXJuIHN1YkxvZy5lcnJvcihgRmFpbGVkIHRvIHN1YnNjcmliZSB0byBjb3VjaGRiIGNoYW5nZXM6ICR7ZX1gKTtcbiAgICAgICAgc3ViTG9nLmluZm8oXG4gICAgICAgICAgYEZhaWxlZCB0byBzdWJzY3JpYmUgdG8gY291Y2hkYiBjaGFuZ2VzOiAke2V9LiBSZXRyeWluZyBpbiA1IHNlY29uZHMuLi5gXG4gICAgICAgICk7XG4gICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIHRoaXMudGltZW91dCkpO1xuICAgICAgICByZXR1cm4gc3Vic2NyaWJlVG9Db3VjaC5jYWxsKHRoaXMpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHN1YnNjcmliZVRvQ291Y2hcbiAgICAgIC5jYWxsKHRoaXMpXG4gICAgICAudGhlbigoKSA9PiB7XG4gICAgICAgIHRoaXMubG9nLmluZm8oYFN1YnNjcmliZWQgdG8gY291Y2hkYiBjaGFuZ2VzYCk7XG4gICAgICB9KVxuICAgICAgLmNhdGNoKChlOiB1bmtub3duKSA9PiB7XG4gICAgICAgIHRocm93IG5ldyBJbnRlcm5hbEVycm9yKGBGYWlsZWQgdG8gc3Vic2NyaWJlIHRvIGNvdWNoZGIgY2hhbmdlczogJHtlfWApO1xuICAgICAgfSk7XG4gIH1cbn1cbiIsImltcG9ydCB7XG4gIENvbmZsaWN0RXJyb3IsXG4gIENvbnRleHQsXG4gIEludGVybmFsRXJyb3IsXG4gIG9uQ3JlYXRlLFxuICBPcGVyYXRpb25LZXlzLFxufSBmcm9tIFwiQGRlY2FmLXRzL2RiLWRlY29yYXRvcnNcIjtcbmltcG9ydCBcInJlZmxlY3QtbWV0YWRhdGFcIjtcbmltcG9ydCB7XG4gIENvdWNoREJBZGFwdGVyLFxuICBDb3VjaERCS2V5cyxcbiAgQ3JlYXRlSW5kZXhSZXF1ZXN0LFxuICBnZW5lcmF0ZUluZGV4ZXMsXG4gIE1hbmdvUXVlcnksXG4gIE1hbmdvUmVzcG9uc2UsXG59IGZyb20gXCJAZGVjYWYtdHMvZm9yLWNvdWNoZGJcIjtcbmltcG9ydCBOYW5vIGZyb20gXCJuYW5vXCI7XG5pbXBvcnQge1xuICBEb2N1bWVudEJ1bGtSZXNwb25zZSxcbiAgRG9jdW1lbnRHZXRSZXNwb25zZSxcbiAgRG9jdW1lbnRJbnNlcnRSZXNwb25zZSxcbiAgRG9jdW1lbnRTY29wZSxcbiAgTWF5YmVEb2N1bWVudCxcbiAgU2VydmVyU2NvcGUsXG59IGZyb20gXCJuYW5vXCI7XG5pbXBvcnQge1xuICBDb25zdHJ1Y3RvcixcbiAgRGVjb3JhdGlvbixcbiAgTW9kZWwsXG4gIHByb3BNZXRhZGF0YSxcbn0gZnJvbSBcIkBkZWNhZi10cy9kZWNvcmF0b3ItdmFsaWRhdGlvblwiO1xuaW1wb3J0IHsgTmFub0ZsYWdzIH0gZnJvbSBcIi4vdHlwZXNcIjtcbmltcG9ydCB7XG4gIFBlcnNpc3RlbmNlS2V5cyxcbiAgUmVsYXRpb25zTWV0YWRhdGEsXG4gIFJlcG9zaXRvcnksXG4gIFVuc3VwcG9ydGVkRXJyb3IsXG59IGZyb20gXCJAZGVjYWYtdHMvY29yZVwiO1xuaW1wb3J0IHsgTmFub0ZsYXZvdXIgfSBmcm9tIFwiLi9jb25zdGFudHNcIjtcbmltcG9ydCB7IE5hbm9SZXBvc2l0b3J5IH0gZnJvbSBcIi4vTmFub1JlcG9zaXRvcnlcIjtcbmltcG9ydCB7IE5hbm9EaXNwYXRjaCB9IGZyb20gXCIuL05hbm9EaXNwYXRjaFwiO1xuXG4vKipcbiAqIEBkZXNjcmlwdGlvbiBTZXRzIHRoZSBjcmVhdG9yIG9yIHVwZGF0ZXIgZmllbGQgaW4gYSBtb2RlbCBiYXNlZCBvbiB0aGUgdXNlciBpbiB0aGUgY29udGV4dFxuICogQHN1bW1hcnkgQ2FsbGJhY2sgZnVuY3Rpb24gdXNlZCBpbiBkZ