angular-in-memory-web-api
Version:
An in-memory web api for Angular demos and tests
563 lines • 81.6 kB
JavaScript
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { concatMap, first } from 'rxjs/operators';
import { delayResponse } from './delay-response';
import { getStatusText, isSuccess, STATUS } from './http-status-codes';
import { InMemoryBackendConfig, parseUri, removeTrailingSlash, } from './interfaces';
/**
* Base class for in-memory web api back-ends
* Simulate the behavior of a RESTy web api
* backed by the simple in-memory data store provided by the injected `InMemoryDbService` service.
* Conforms mostly to behavior described here:
* http://www.restapitutorial.com/lessons/httpmethods.html
*/
export class BackendService {
constructor(inMemDbService, config = {}) {
this.inMemDbService = inMemDbService;
this.config = new InMemoryBackendConfig();
this.db = {};
this.requestInfoUtils = this.getRequestInfoUtils();
const loc = this.getLocation('/');
this.config.host = loc.host; // default to app web server host
this.config.rootPath = loc.path; // default to path when app is served (e.g.'/')
Object.assign(this.config, config);
}
get dbReady() {
if (!this.dbReadySubject) {
// first time the service is called.
this.dbReadySubject = new BehaviorSubject(false);
this.resetDb();
}
return this.dbReadySubject.asObservable().pipe(first((r) => r));
}
/**
* Process Request and return an Observable of Http Response object
* in the manner of a RESTy web api.
*
* Expect URI pattern in the form :base/:collectionName/:id?
* Examples:
* // for store with a 'customers' collection
* GET api/customers // all customers
* GET api/customers/42 // the character with id=42
* GET api/customers?name=^j // 'j' is a regex; returns customers whose name starts with 'j' or
* 'J' GET api/customers.json/42 // ignores the ".json"
*
* Also accepts direct commands to the service in which the last segment of the apiBase is the
* word "commands" Examples: POST commands/resetDb, GET/POST commands/config - get or (re)set the
* config
*
* HTTP overrides:
* If the injected inMemDbService defines an HTTP method (lowercase)
* The request is forwarded to that method as in
* `inMemDbService.get(requestInfo)`
* which must return either an Observable of the response type
* for this http library or null|undefined (which means "keep processing").
*/
handleRequest(req) {
// handle the request when there is an in-memory database
return this.dbReady.pipe(concatMap(() => this.handleRequest_(req)));
}
handleRequest_(req) {
const url = req.urlWithParams ? req.urlWithParams : req.url;
// Try override parser
// If no override parser or it returns nothing, use default parser
const parser = this.bind('parseRequestUrl');
const parsed = (parser && parser(url, this.requestInfoUtils)) || this.parseRequestUrl(url);
const collectionName = parsed.collectionName;
const collection = this.db[collectionName];
const reqInfo = {
req: req,
apiBase: parsed.apiBase,
collection: collection,
collectionName: collectionName,
headers: this.createHeaders({ 'Content-Type': 'application/json' }),
id: this.parseId(collection, collectionName, parsed.id),
method: this.getRequestMethod(req),
query: parsed.query,
resourceUrl: parsed.resourceUrl,
url: url,
utils: this.requestInfoUtils,
};
let resOptions;
if (/commands\/?$/i.test(reqInfo.apiBase)) {
return this.commands(reqInfo);
}
const methodInterceptor = this.bind(reqInfo.method);
if (methodInterceptor) {
// InMemoryDbService intercepts this HTTP method.
// if interceptor produced a response, return it.
// else InMemoryDbService chose not to intercept; continue processing.
const interceptorResponse = methodInterceptor(reqInfo);
if (interceptorResponse) {
return interceptorResponse;
}
}
if (this.db[collectionName]) {
// request is for a known collection of the InMemoryDbService
return this.createResponse$(() => this.collectionHandler(reqInfo));
}
if (this.config.passThruUnknownUrl) {
// unknown collection; pass request thru to a "real" backend.
return this.getPassThruBackend().handle(req);
}
// 404 - can't handle this request
resOptions = this.createErrorResponseOptions(url, STATUS.NOT_FOUND, `Collection '${collectionName}' not found`);
return this.createResponse$(() => resOptions);
}
/**
* Add configured delay to response observable unless delay === 0
*/
addDelay(response) {
const d = this.config.delay;
return d === 0 ? response : delayResponse(response, d || 500);
}
/**
* Apply query/search parameters as a filter over the collection
* This impl only supports RegExp queries on string properties of the collection
* ANDs the conditions together
*/
applyQuery(collection, query) {
// extract filtering conditions - {propertyName, RegExps) - from query/search parameters
const conditions = [];
const caseSensitive = this.config.caseSensitiveSearch ? undefined : 'i';
query.forEach((value, name) => {
value.forEach((v) => conditions.push({ name, rx: new RegExp(decodeURI(v), caseSensitive) }));
});
const len = conditions.length;
if (!len) {
return collection;
}
// AND the RegExp conditions
return collection.filter((row) => {
let ok = true;
let i = len;
while (ok && i) {
i -= 1;
const cond = conditions[i];
ok = cond.rx.test(row[cond.name]);
}
return ok;
});
}
/**
* Get a method from the `InMemoryDbService` (if it exists), bound to that service
*/
bind(methodName) {
const fn = this.inMemDbService[methodName];
return fn ? fn.bind(this.inMemDbService) : undefined;
}
bodify(data) {
return this.config.dataEncapsulation ? { data } : data;
}
clone(data) {
return JSON.parse(JSON.stringify(data));
}
collectionHandler(reqInfo) {
// const req = reqInfo.req;
let resOptions;
switch (reqInfo.method) {
case 'get':
resOptions = this.get(reqInfo);
break;
case 'post':
resOptions = this.post(reqInfo);
break;
case 'put':
resOptions = this.put(reqInfo);
break;
case 'delete':
resOptions = this.delete(reqInfo);
break;
default:
resOptions = this.createErrorResponseOptions(reqInfo.url, STATUS.METHOD_NOT_ALLOWED, 'Method not allowed');
break;
}
// If `inMemDbService.responseInterceptor` exists, let it morph the response options
const interceptor = this.bind('responseInterceptor');
return interceptor ? interceptor(resOptions, reqInfo) : resOptions;
}
/**
* Commands reconfigure the in-memory web api service or extract information from it.
* Commands ignore the latency delay and respond ASAP.
*
* When the last segment of the `apiBase` path is "commands",
* the `collectionName` is the command.
*
* Example URLs:
* commands/resetdb (POST) // Reset the "database" to its original state
* commands/config (GET) // Return this service's config object
* commands/config (POST) // Update the config (e.g. the delay)
*
* Usage:
* http.post('commands/resetdb', undefined);
* http.get('commands/config');
* http.post('commands/config', '{"delay":1000}');
*/
commands(reqInfo) {
const command = reqInfo.collectionName.toLowerCase();
const method = reqInfo.method;
let resOptions = { url: reqInfo.url };
switch (command) {
case 'resetdb':
resOptions.status = STATUS.NO_CONTENT;
return this.resetDb(reqInfo).pipe(concatMap(() => this.createResponse$(() => resOptions, false /* no latency delay */)));
case 'config':
if (method === 'get') {
resOptions.status = STATUS.OK;
resOptions.body = this.clone(this.config);
// any other HTTP method is assumed to be a config update
}
else {
const body = this.getJsonBody(reqInfo.req);
Object.assign(this.config, body);
this.passThruBackend = undefined; // re-create when needed
resOptions.status = STATUS.NO_CONTENT;
}
break;
default:
resOptions = this.createErrorResponseOptions(reqInfo.url, STATUS.INTERNAL_SERVER_ERROR, `Unknown command "${command}"`);
}
return this.createResponse$(() => resOptions, false /* no latency delay */);
}
createErrorResponseOptions(url, status, message) {
return {
body: { error: `${message}` },
url: url,
headers: this.createHeaders({ 'Content-Type': 'application/json' }),
status: status,
};
}
/**
* Create a cold response Observable from a factory for ResponseOptions
* @param resOptionsFactory - creates ResponseOptions when observable is subscribed
* @param withDelay - if true (default), add simulated latency delay from configuration
*/
createResponse$(resOptionsFactory, withDelay = true) {
const resOptions$ = this.createResponseOptions$(resOptionsFactory);
let resp$ = this.createResponse$fromResponseOptions$(resOptions$);
return withDelay ? this.addDelay(resp$) : resp$;
}
/**
* Create a cold Observable of ResponseOptions.
* @param resOptionsFactory - creates ResponseOptions when observable is subscribed
*/
createResponseOptions$(resOptionsFactory) {
return new Observable((responseObserver) => {
let resOptions;
try {
resOptions = resOptionsFactory();
}
catch (error) {
const err = error.message || error;
resOptions = this.createErrorResponseOptions('', STATUS.INTERNAL_SERVER_ERROR, `${err}`);
}
const status = resOptions.status;
try {
resOptions.statusText = status != null ? getStatusText(status) : undefined;
}
catch (e) {
/* ignore failure */
}
if (status != null && isSuccess(status)) {
responseObserver.next(resOptions);
responseObserver.complete();
}
else {
responseObserver.error(resOptions);
}
return () => { }; // unsubscribe function
});
}
delete({ collection, collectionName, headers, id, url }) {
if (id == null) {
return this.createErrorResponseOptions(url, STATUS.NOT_FOUND, `Missing "${collectionName}" id`);
}
const exists = this.removeById(collection, id);
return {
headers: headers,
status: exists || !this.config.delete404 ? STATUS.NO_CONTENT : STATUS.NOT_FOUND,
};
}
/**
* Find first instance of item in collection by `item.id`
* @param collection
* @param id
*/
findById(collection, id) {
return collection.find((item) => item.id === id);
}
/**
* Generate the next available id for item in this collection
* Use method from `inMemDbService` if it exists and returns a value,
* else delegates to `genIdDefault`.
* @param collection - collection of items with `id` key property
*/
genId(collection, collectionName) {
const genId = this.bind('genId');
if (genId) {
const id = genId(collection, collectionName);
if (id != null) {
return id;
}
}
return this.genIdDefault(collection, collectionName);
}
/**
* Default generator of the next available id for item in this collection
* This default implementation works only for numeric ids.
* @param collection - collection of items with `id` key property
* @param collectionName - name of the collection
*/
genIdDefault(collection, collectionName) {
if (!this.isCollectionIdNumeric(collection, collectionName)) {
throw new Error(`Collection '${collectionName}' id type is non-numeric or unknown. Can only generate numeric ids.`);
}
let maxId = 0;
collection.reduce((prev, item) => {
maxId = Math.max(maxId, typeof item.id === 'number' ? item.id : maxId);
}, undefined);
return maxId + 1;
}
get({ collection, collectionName, headers, id, query, url, }) {
let data = collection;
if (id != null && id !== '') {
data = this.findById(collection, id);
}
else if (query) {
data = this.applyQuery(collection, query);
}
if (!data) {
return this.createErrorResponseOptions(url, STATUS.NOT_FOUND, `'${collectionName}' with id='${id}' not found`);
}
return { body: this.bodify(this.clone(data)), headers: headers, status: STATUS.OK };
}
/**
* Get location info from a url, even on server where `document` is not defined
*/
getLocation(url) {
if (!url.startsWith('http')) {
// get the document iff running in browser
const doc = typeof document === 'undefined' ? undefined : document;
// add host info to url before parsing. Use a fake host when not in browser.
const base = doc ? doc.location.protocol + '//' + doc.location.host : 'http://fake';
url = url.startsWith('/') ? base + url : base + '/' + url;
}
return parseUri(url);
}
/**
* get or create the function that passes unhandled requests
* through to the "real" backend.
*/
getPassThruBackend() {
return this.passThruBackend
? this.passThruBackend
: (this.passThruBackend = this.createPassThruBackend());
}
/**
* Get utility methods from this service instance.
* Useful within an HTTP method override
*/
getRequestInfoUtils() {
return {
createResponse$: this.createResponse$.bind(this),
findById: this.findById.bind(this),
isCollectionIdNumeric: this.isCollectionIdNumeric.bind(this),
getConfig: () => this.config,
getDb: () => this.db,
getJsonBody: this.getJsonBody.bind(this),
getLocation: this.getLocation.bind(this),
getPassThruBackend: this.getPassThruBackend.bind(this),
parseRequestUrl: this.parseRequestUrl.bind(this),
};
}
indexOf(collection, id) {
return collection.findIndex((item) => item.id === id);
}
/** Parse the id as a number. Return original value if not a number. */
parseId(collection, collectionName, id) {
if (!this.isCollectionIdNumeric(collection, collectionName)) {
// Can't confirm that `id` is a numeric type; don't parse as a number
// or else `'42'` -> `42` and _get by id_ fails.
return id;
}
const idNum = parseFloat(id);
return isNaN(idNum) ? id : idNum;
}
/**
* return true if can determine that the collection's `item.id` is a number
* This implementation can't tell if the collection is empty so it assumes NO
* */
isCollectionIdNumeric(collection, collectionName) {
// collectionName not used now but override might maintain collection type information
// so that it could know the type of the `id` even when the collection is empty.
return !!(collection && collection[0]) && typeof collection[0].id === 'number';
}
/**
* Parses the request URL into a `ParsedRequestUrl` object.
* Parsing depends upon certain values of `config`: `apiBase`, `host`, and `urlRoot`.
*
* Configuring the `apiBase` yields the most interesting changes to `parseRequestUrl` behavior:
* When apiBase=undefined and url='http://localhost/api/collection/42'
* {base: 'api/', collectionName: 'collection', id: '42', ...}
* When apiBase='some/api/root/' and url='http://localhost/some/api/root/collection'
* {base: 'some/api/root/', collectionName: 'collection', id: undefined, ...}
* When apiBase='/' and url='http://localhost/collection'
* {base: '/', collectionName: 'collection', id: undefined, ...}
*
* The actual api base segment values are ignored. Only the number of segments matters.
* The following api base strings are considered identical: 'a/b' ~ 'some/api/' ~ `two/segments'
*
* To replace this default method, assign your alternative to your
* InMemDbService['parseRequestUrl']
*/
parseRequestUrl(url) {
try {
const loc = this.getLocation(url);
let drop = (this.config.rootPath || '').length;
let urlRoot = '';
if (loc.host !== this.config.host) {
// url for a server on a different host!
// assume it's collection is actually here too.
drop = 1; // the leading slash
urlRoot = loc.protocol + '//' + loc.host + '/';
}
const path = loc.path.substring(drop);
const pathSegments = path.split('/');
let segmentIndex = 0;
// apiBase: the front part of the path devoted to getting to the api route
// Assumes first path segment if no config.apiBase
// else ignores as many path segments as are in config.apiBase
// Does NOT care what the api base chars actually are.
let apiBase;
if (this.config.apiBase == null) {
apiBase = pathSegments[segmentIndex++];
}
else {
apiBase = removeTrailingSlash(this.config.apiBase.trim());
if (apiBase) {
segmentIndex = apiBase.split('/').length;
}
else {
segmentIndex = 0; // no api base at all; unwise but allowed.
}
}
apiBase += '/';
let collectionName = pathSegments[segmentIndex++];
// ignore anything after a '.' (e.g.,the "json" in "customers.json")
collectionName = collectionName && collectionName.split('.')[0];
const id = pathSegments[segmentIndex++];
const query = this.createQueryMap(loc.query);
const resourceUrl = urlRoot + apiBase + collectionName + '/';
return { apiBase, collectionName, id, query, resourceUrl };
}
catch (err) {
const msg = `unable to parse url '${url}'; original error: ${err.message}`;
throw new Error(msg);
}
}
// Create entity
// Can update an existing entity too if post409 is false.
post({ collection, collectionName, headers, id, req, resourceUrl, url, }) {
const item = this.clone(this.getJsonBody(req));
if (item.id == null) {
try {
item.id = id || this.genId(collection, collectionName);
}
catch (err) {
const emsg = err.message || '';
if (/id type is non-numeric/.test(emsg)) {
return this.createErrorResponseOptions(url, STATUS.UNPROCESSABLE_ENTRY, emsg);
}
else {
return this.createErrorResponseOptions(url, STATUS.INTERNAL_SERVER_ERROR, `Failed to generate new id for '${collectionName}'`);
}
}
}
if (id && id !== item.id) {
return this.createErrorResponseOptions(url, STATUS.BAD_REQUEST, `Request id does not match item.id`);
}
else {
id = item.id;
}
const existingIx = this.indexOf(collection, id);
const body = this.bodify(item);
if (existingIx === -1) {
collection.push(item);
headers.set('Location', resourceUrl + '/' + id);
return { headers, body, status: STATUS.CREATED };
}
else if (this.config.post409) {
return this.createErrorResponseOptions(url, STATUS.CONFLICT, `'${collectionName}' item with id='${id} exists and may not be updated with POST; use PUT instead.`);
}
else {
collection[existingIx] = item;
return this.config.post204
? { headers, status: STATUS.NO_CONTENT } // successful; no content
: { headers, body, status: STATUS.OK }; // successful; return entity
}
}
// Update existing entity
// Can create an entity too if put404 is false.
put({ collection, collectionName, headers, id, req, url }) {
const item = this.clone(this.getJsonBody(req));
if (item.id == null) {
return this.createErrorResponseOptions(url, STATUS.NOT_FOUND, `Missing '${collectionName}' id`);
}
if (id && id !== item.id) {
return this.createErrorResponseOptions(url, STATUS.BAD_REQUEST, `Request for '${collectionName}' id does not match item.id`);
}
else {
id = item.id;
}
const existingIx = this.indexOf(collection, id);
const body = this.bodify(item);
if (existingIx > -1) {
collection[existingIx] = item;
return this.config.put204
? { headers, status: STATUS.NO_CONTENT } // successful; no content
: { headers, body, status: STATUS.OK }; // successful; return entity
}
else if (this.config.put404) {
// item to update not found; use POST to create new item for this id.
return this.createErrorResponseOptions(url, STATUS.NOT_FOUND, `'${collectionName}' item with id='${id} not found and may not be created with PUT; use POST instead.`);
}
else {
// create new item for id not found
collection.push(item);
return { headers, body, status: STATUS.CREATED };
}
}
removeById(collection, id) {
const ix = this.indexOf(collection, id);
if (ix > -1) {
collection.splice(ix, 1);
return true;
}
return false;
}
/**
* Tell your in-mem "database" to reset.
* returns Observable of the database because resetting it could be async
*/
resetDb(reqInfo) {
this.dbReadySubject && this.dbReadySubject.next(false);
const db = this.inMemDbService.createDb(reqInfo);
const db$ = db instanceof Observable
? db
: typeof db.then === 'function'
? from(db)
: of(db);
db$.pipe(first()).subscribe((d) => {
this.db = d;
this.dbReadySubject && this.dbReadySubject.next(true);
});
return this.dbReady;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFja2VuZC1zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvbWlzYy9hbmd1bGFyLWluLW1lbW9yeS13ZWItYXBpL3NyYy9iYWNrZW5kLXNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1HO0FBR0gsT0FBTyxFQUFDLGVBQWUsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFZLEVBQUUsRUFBQyxNQUFNLE1BQU0sQ0FBQztBQUNyRSxPQUFPLEVBQUMsU0FBUyxFQUFFLEtBQUssRUFBQyxNQUFNLGdCQUFnQixDQUFDO0FBRWhELE9BQU8sRUFBQyxhQUFhLEVBQUMsTUFBTSxrQkFBa0IsQ0FBQztBQUMvQyxPQUFPLEVBQUMsYUFBYSxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUMsTUFBTSxxQkFBcUIsQ0FBQztBQUNyRSxPQUFPLEVBQ0wscUJBQXFCLEVBSXJCLFFBQVEsRUFFUixtQkFBbUIsR0FNcEIsTUFBTSxjQUFjLENBQUM7QUFFdEI7Ozs7OztHQU1HO0FBQ0gsTUFBTSxPQUFnQixjQUFjO0lBT2xDLFlBQ1ksY0FBaUMsRUFDM0MsU0FBb0MsRUFBRTtRQUQ1QixtQkFBYyxHQUFkLGNBQWMsQ0FBbUI7UUFQbkMsV0FBTSxHQUE4QixJQUFJLHFCQUFxQixFQUFFLENBQUM7UUFDaEUsT0FBRSxHQUF5QixFQUFFLENBQUM7UUFHOUIscUJBQWdCLEdBQUcsSUFBSSxDQUFDLG1CQUFtQixFQUFFLENBQUM7UUFNdEQsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNsQyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsaUNBQWlDO1FBQzlELElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQywrQ0FBK0M7UUFDaEYsTUFB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QUksQ0FBQyxjQUFjLElBQUksSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDeEQsQ0FBQyxDQUFDLENBQUM7UUFDSCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDdEIsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAbGljZW5zZVxuICogQ29weXJpZ2h0IEdvb2dsZSBMTEMgQWxsIFJpZ2h0cyBSZXNlcnZlZC5cbiAqXG4gKiBVc2Ugb2YgdGhpcyBzb3VyY2UgY29kZSBpcyBnb3Zlcm5lZCBieSBhbiBNSVQtc3R5bGUgbGljZW5zZSB0aGF0IGNhbiBiZVxuICogZm91bmQgaW4gdGhlIExJQ0VOU0UgZmlsZSBhdCBodHRwczovL2FuZ3VsYXIuaW8vbGljZW5zZVxuICovXG5cbmltcG9ydCB7SHR0cEhlYWRlcnN9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbi9odHRwJztcbmltcG9ydCB7QmVoYXZpb3JTdWJqZWN0LCBmcm9tLCBPYnNlcnZhYmxlLCBPYnNlcnZlciwgb2Z9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHtjb25jYXRNYXAsIGZpcnN0fSBmcm9tICdyeGpzL29wZXJhdG9ycyc7XG5cbmltcG9ydCB7ZGVsYXlSZXNwb25zZX0gZnJvbSAnLi9kZWxheS1yZXNwb25zZSc7XG5pbXBvcnQge2dldFN0YXR1c1RleHQsIGlzU3VjY2VzcywgU1RBVFVTfSBmcm9tICcuL2h0dHAtc3RhdHVzLWNvZGVzJztcbmltcG9ydCB7XG4gIEluTWVtb3J5QmFja2VuZENvbmZpZyxcbiAgSW5NZW1vcnlCYWNrZW5kQ29uZmlnQXJncyxcbiAgSW5NZW1vcnlEYlNlcnZpY2UsXG4gIFBhcnNlZFJlcXVlc3RVcmwsXG4gIHBhcnNlVXJpLFxuICBQYXNzVGhydUJhY2tlbmQsXG4gIHJlbW92ZVRyYWlsaW5nU2xhc2gsXG4gIFJlcXVlc3RDb3JlLFxuICBSZXF1ZXN0SW5mbyxcbiAgUmVxdWVzdEluZm9VdGlsaXRpZXMsXG4gIFJlc3BvbnNlT3B0aW9ucyxcbiAgVXJpSW5mbyxcbn0gZnJvbSAnLi9pbnRlcmZhY2VzJztcblxuLyoqXG4gKiBCYXNlIGNsYXNzIGZvciBpbi1tZW1vcnkgd2ViIGFwaSBiYWNrLWVuZHNcbiAqIFNpbXVsYXRlIHRoZSBiZWhhdmlvciBvZiBhIFJFU1R5IHdlYiBhcGlcbiAqIGJhY2tlZCBieSB0aGUgc2ltcGxlIGluLW1lbW9yeSBkYXRhIHN0b3JlIHByb3ZpZGVkIGJ5IHRoZSBpbmplY3RlZCBgSW5NZW1vcnlEYlNlcnZpY2VgIHNlcnZpY2UuXG4gKiBDb25mb3JtcyBtb3N0bHkgdG8gYmVoYXZpb3IgZGVzY3JpYmVkIGhlcmU6XG4gKiBodHRwOi8vd3d3LnJlc3RhcGl0dXRvcmlhbC5jb20vbGVzc29ucy9odHRwbWV0aG9kcy5odG1sXG4gKi9cbmV4cG9ydCBhYnN0cmFjdCBjbGFzcyBCYWNrZW5kU2VydmljZSB7XG4gIHByb3RlY3RlZCBjb25maWc6IEluTWVtb3J5QmFja2VuZENvbmZpZ0FyZ3MgPSBuZXcgSW5NZW1vcnlCYWNrZW5kQ29uZmlnKCk7XG4gIHByb3RlY3RlZCBkYjoge1trZXk6IHN0cmluZ106IGFueX0gPSB7fTtcbiAgcHJvdGVjdGVkIGRiUmVhZHlTdWJqZWN0OiBCZWhhdmlvclN1YmplY3Q8Ym9vbGVhbj4gfCB1bmRlZmluZWQ7XG4gIHByaXZhdGUgcGFzc1RocnVCYWNrZW5kOiBQYXNzVGhydUJhY2tlbmQgfCB1bmRlZmluZWQ7XG4gIHByb3RlY3RlZCByZXF1ZXN0SW5mb1V0aWxzID0gdGhpcy5nZXRSZXF1ZXN0SW5mb1V0aWxzKCk7XG5cbiAgY29uc3RydWN0b3IoXG4gICAgcHJvdGVjdGVkIGluTWVtRGJTZXJ2aWNlOiBJbk1lbW9yeURiU2VydmljZSxcbiAgICBjb25maWc6IEluTWVtb3J5QmFja2VuZENvbmZpZ0FyZ3MgPSB7fSxcbiAgKSB7XG4gICAgY29uc3QgbG9jID0gdGhpcy5nZXRMb2NhdGlvbignLycpO1xuICAgIHRoaXMuY29uZmlnLmhvc3QgPSBsb2MuaG9zdDsgLy8gZGVmYXVsdCB0byBhcHAgd2ViIHNlcnZlciBob3N0XG4gICAgdGhpcy5jb25maWcucm9vdFBhdGggPSBsb2MucGF0aDsgLy8gZGVmYXVsdCB0byBwYXRoIHdoZW4gYXBwIGlzIHNlcnZlZCAoZS5nLicvJylcbiAgICBPYmplY3QuYXNzaWduKHRoaXMuY29uZmlnLCBjb25maWcpO1xuICB9XG5cbiAgcHJvdGVjdGVkIGdldCBkYlJlYWR5KCk6IE9ic2VydmFibGU8Ym9vbGVhbj4ge1xuICAgIGlmICghdGhpcy5kYlJlYWR5U3ViamVjdCkge1xuICAgICAgLy8gZmlyc3QgdGltZSB0aGUgc2VydmljZSBpcyBjYWxsZWQuXG4gICAgICB0aGlzLmRiUmVhZHlTdWJqZWN0ID0gbmV3IEJlaGF2aW9yU3ViamVjdDxib29sZWFuPihmYWxzZSk7XG4gICAgICB0aGlzLnJlc2V0RGIoKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuZGJSZWFkeVN1YmplY3QuYXNPYnNlcnZhYmxlKCkucGlwZShmaXJzdCgocjogYm9vbGVhbikgPT4gcikpO1xuICB9XG5cbiAgLyoqXG4gICAqIFByb2Nlc3MgUmVxdWVzdCBhbmQgcmV0dXJuIGFuIE9ic2VydmFibGUgb2YgSHR0cCBSZXNwb25zZ