@dbx-tools/auth 0.9.31 → 0.9.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/lifecycle.ts CHANGED
@@ -16,9 +16,9 @@ export class TokenLifecycle {
16
16
  logger.debug("created authentication lifecycle", {
17
17
  credential: credentialId(key),
18
18
  storage: store.name(),
19
- refreshBufferSeconds: options.refreshBufferSeconds,
20
- lockTimeoutSeconds: options.lockTimeoutSeconds,
21
- loginTimeoutSeconds: options.loginTimeoutSeconds,
19
+ refreshBufferMs: options.refreshBufferMs,
20
+ lockTimeoutMs: options.lockTimeoutMs,
21
+ loginTimeoutMs: options.loginTimeoutMs,
22
22
  silentProvider: provider.canAuthenticateSilently(),
23
23
  });
24
24
  }
@@ -32,7 +32,7 @@ export class TokenLifecycle {
32
32
  return this.withLock(async () => {
33
33
  await this.store.prepareWrite();
34
34
  const token = validateToken(
35
- await this.provider.login(this.options.loginTimeoutSeconds * 1000),
35
+ await this.provider.login(this.options.loginTimeoutMs),
36
36
  );
37
37
  await this.store.save(this.key, token);
38
38
  logger.debug("interactive login stored credential", {
@@ -137,7 +137,7 @@ export class TokenLifecycle {
137
137
  });
138
138
  if (!login) throw error;
139
139
  logger.debug("falling back to interactive login", this.context());
140
- token = await this.provider.login(this.options.loginTimeoutSeconds * 1000);
140
+ token = await this.provider.login(this.options.loginTimeoutMs);
141
141
  }
142
142
  } else if (this.provider.canAuthenticateSilently()) {
143
143
  logger.debug("attempting silent credential acquisition", {
@@ -145,7 +145,7 @@ export class TokenLifecycle {
145
145
  loginFallback: login,
146
146
  });
147
147
  try {
148
- token = await this.provider.authenticate(this.options.loginTimeoutSeconds * 1000);
148
+ token = await this.provider.authenticate(this.options.loginTimeoutMs);
149
149
  } catch (error) {
150
150
  logger.debug("silent credential acquisition failed", {
151
151
  ...this.context(),
@@ -154,11 +154,11 @@ export class TokenLifecycle {
154
154
  });
155
155
  if (!login) throw error;
156
156
  logger.debug("falling back to interactive login", this.context());
157
- token = await this.provider.login(this.options.loginTimeoutSeconds * 1000);
157
+ token = await this.provider.login(this.options.loginTimeoutMs);
158
158
  }
159
159
  } else if (login) {
160
160
  logger.debug("provider requires interactive login", this.context());
161
- token = await this.provider.login(this.options.loginTimeoutSeconds * 1000);
161
+ token = await this.provider.login(this.options.loginTimeoutMs);
162
162
  } else {
163
163
  throw new AuthError(
164
164
  "oauth",
@@ -180,13 +180,17 @@ export class TokenLifecycle {
180
180
  if (!isValid(token, this.now())) return false;
181
181
  if (!token.expiry) return true;
182
182
  return (
183
- Date.parse(token.expiry) - this.now().getTime() > this.options.refreshBufferSeconds * 1000
183
+ Date.parse(token.expiry) - this.now().getTime() > this.options.refreshBufferMs
184
184
  );
185
185
  }
186
186
 
187
187
  private async withLock<T>(action: () => Promise<T>): Promise<T> {
188
188
  logger.debug("waiting for credential lock", this.context());
189
- const lease = await this.store.acquireLock(this.key, this.options.lockTimeoutSeconds * 1000);
189
+ // Omit the timeout (wait indefinitely) unless a positive cap is configured.
190
+ // The holder may run for minutes; the caller owns the overall request budget
191
+ // and the lock is always released when `action` settles (success or error).
192
+ const lockTimeoutMs = this.options.lockTimeoutMs > 0 ? this.options.lockTimeoutMs : undefined;
193
+ const lease = await this.store.acquireLock(this.key, lockTimeoutMs);
190
194
  logger.debug("credential lock acquired", this.context());
191
195
  let failure: unknown;
192
196
  try {
package/src/storage.ts CHANGED
@@ -3,44 +3,94 @@ import type { CredentialStore, LockAdapter, Token } from "./types.ts";
3
3
 
4
4
  const logger = authLogger("memory-storage");
5
5
 
6
- /** Process-local generic lease registry used by the in-memory store. */
6
+ /** One queued waiter for a held key: resolved when promoted, rejected on timeout. */
7
+ interface MemoryLockWaiter {
8
+ resolve: (lease: string) => void;
9
+ reject: (error: unknown) => void;
10
+ }
11
+
12
+ /**
13
+ * A key's current owner lease plus its FIFO queue of waiters.
14
+ *
15
+ * Owner and queue live in one entry so the key is never half-present: it is
16
+ * dropped only when it is both unowned and unwanted. A waiter that times out
17
+ * removes only itself from `queue` and never disturbs `lease`, so a timed-out
18
+ * waiter can no longer let a later caller bypass the active holder.
19
+ */
20
+ interface MemoryLockState {
21
+ lease: string;
22
+ queue: MemoryLockWaiter[];
23
+ }
24
+
25
+ /**
26
+ * Process-local keyed mutex used by the in-memory store.
27
+ *
28
+ * Modeled on `@dbx-tools/core`'s `withProcessLock` coordinator: one holder per
29
+ * key, the rest queue in arrival order, distinct keys proceed concurrently.
30
+ * Waiting is purely promise-based, so a waiter never blocks the event loop and,
31
+ * by default, waits indefinitely - a holder may legitimately run for minutes
32
+ * (e.g. an interactive `databricks auth login` that opens a browser). A finite
33
+ * `timeoutMs` is opt-in for callers that prefer to fail fast.
34
+ */
7
35
  export class MemoryLockAdapter implements LockAdapter {
8
- readonly #tails = new Map<string, Promise<void>>();
9
- readonly #leases = new Map<string, () => void>();
36
+ readonly #states = new Map<string, MemoryLockState>();
10
37
  #sequence = 0;
11
38
 
12
- async acquire(key: string, timeoutMs: number): Promise<string> {
39
+ acquire(key: string, timeoutMs?: number): Promise<string> {
13
40
  const credential = credentialId(key);
14
- const previous = this.#tails.get(key) ?? Promise.resolve();
15
- let release!: () => void;
16
- const current = new Promise<void>((resolve) => {
17
- release = resolve;
18
- });
19
- const tail = previous.then(() => current);
20
- this.#tails.set(key, tail);
21
- try {
22
- logger.debug("waiting for memory lock", { credential, timeoutMs });
23
- await withTimeout(previous, timeoutMs, `Timed out waiting for lock ${key}`);
24
- } catch (error) {
25
- release();
26
- if (this.#tails.get(key) === tail) this.#tails.delete(key);
27
- throw error;
41
+ const state = this.#states.get(key);
42
+ if (!state) {
43
+ const lease = this.#nextLease(key);
44
+ this.#states.set(key, { lease, queue: [] });
45
+ logger.debug("memory lock acquired", { credential });
46
+ return Promise.resolve(lease);
28
47
  }
29
- const lease = `${++this.#sequence}:${key}`;
30
- this.#leases.set(lease, () => {
31
- release();
32
- this.#leases.delete(lease);
33
- if (this.#tails.get(key) === tail) this.#tails.delete(key);
48
+ logger.debug("waiting for memory lock", { credential, timeoutMs });
49
+ return new Promise<string>((resolve, reject) => {
50
+ let timer: ReturnType<typeof setTimeout> | undefined;
51
+ const waiter: MemoryLockWaiter = {
52
+ resolve: (lease) => {
53
+ if (timer) clearTimeout(timer);
54
+ logger.debug("memory lock acquired", { credential });
55
+ resolve(lease);
56
+ },
57
+ reject: (error) => {
58
+ if (timer) clearTimeout(timer);
59
+ reject(error);
60
+ },
61
+ };
62
+ state.queue.push(waiter);
63
+ // A timer is created only for a finite positive budget. Omitted, Infinity,
64
+ // or non-positive all mean "wait forever" - no timer to leak or fire.
65
+ if (timeoutMs !== undefined && Number.isFinite(timeoutMs) && timeoutMs > 0) {
66
+ timer = setTimeout(() => {
67
+ const index = state.queue.indexOf(waiter);
68
+ if (index >= 0) state.queue.splice(index, 1);
69
+ waiter.reject(new Error(`Timed out waiting for lock ${key}`));
70
+ }, timeoutMs);
71
+ }
34
72
  });
35
- logger.debug("memory lock acquired", { credential });
36
- return lease;
37
73
  }
38
74
 
39
- async release(lease: string): Promise<void> {
40
- logger.debug("releasing memory lock", {
41
- credential: credentialId(lease.split(":").slice(1).join(":")),
42
- });
43
- this.#leases.get(lease)?.();
75
+ release(lease: string): Promise<void> {
76
+ const key = lease.split(":").slice(1).join(":");
77
+ logger.debug("releasing memory lock", { credential: credentialId(key) });
78
+ const state = this.#states.get(key);
79
+ // Ignore a stale release from anyone who no longer owns the key (a timed-out
80
+ // or superseded lease). Honoring it would revoke the lock from the holder.
81
+ if (!state || state.lease !== lease) return Promise.resolve();
82
+ const next = state.queue.shift();
83
+ if (!next) {
84
+ this.#states.delete(key);
85
+ return Promise.resolve();
86
+ }
87
+ state.lease = this.#nextLease(key);
88
+ next.resolve(state.lease);
89
+ return Promise.resolve();
90
+ }
91
+
92
+ #nextLease(key: string): string {
93
+ return `${++this.#sequence}:${key}`;
44
94
  }
45
95
  }
46
96
 
@@ -74,7 +124,7 @@ export class MemoryCredentialStore implements CredentialStore {
74
124
  logger.debug("removed memory credential", { credential: credentialId(key) });
75
125
  }
76
126
 
77
- acquireLock(key: string, timeoutMs: number): Promise<string> {
127
+ acquireLock(key: string, timeoutMs?: number): Promise<string> {
78
128
  return this.locks.acquire(key, timeoutMs);
79
129
  }
80
130
 
@@ -86,19 +136,3 @@ export class MemoryCredentialStore implements CredentialStore {
86
136
  return "memory";
87
137
  }
88
138
  }
89
-
90
- async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
91
- if (!Number.isFinite(timeoutMs) || timeoutMs < 0)
92
- throw new TypeError("timeoutMs must be non-negative");
93
- let timeout: ReturnType<typeof setTimeout> | undefined;
94
- try {
95
- return await Promise.race([
96
- promise,
97
- new Promise<never>((_, reject) => {
98
- timeout = setTimeout(() => reject(new Error(message)), timeoutMs);
99
- }),
100
- ]);
101
- } finally {
102
- if (timeout) clearTimeout(timeout);
103
- }
104
- }
package/src/types.ts CHANGED
@@ -44,17 +44,24 @@ export interface AccessToken {
44
44
  scopes: string[];
45
45
  }
46
46
 
47
- /** Shared lifecycle configuration embedded by every provider. */
47
+ /** Shared lifecycle configuration embedded by every provider. All times in milliseconds. */
48
48
  export interface AuthOptions {
49
- refreshBufferSeconds: number;
50
- lockTimeoutSeconds: number;
51
- loginTimeoutSeconds: number;
49
+ refreshBufferMs: number;
50
+ /**
51
+ * Opt-in cap, in milliseconds, on waiting to acquire the per-credential
52
+ * refresh lock. `0` (the default) means wait indefinitely: a holder may
53
+ * legitimately run for minutes (an interactive login that opens a browser),
54
+ * and the caller owns the overall request budget. Set a positive value to
55
+ * fail fast instead.
56
+ */
57
+ lockTimeoutMs: number;
58
+ loginTimeoutMs: number;
52
59
  }
53
60
 
54
61
  const AUTH_DEFAULTS: AuthOptions = {
55
- refreshBufferSeconds: 300,
56
- lockTimeoutSeconds: 30,
57
- loginTimeoutSeconds: 900,
62
+ refreshBufferMs: 300_000,
63
+ lockTimeoutMs: 0,
64
+ loginTimeoutMs: 900_000,
58
65
  };
59
66
 
60
67
  /** Record-style factory retained for straightforward Node/Python bridging. */
@@ -131,7 +138,8 @@ export interface DatabricksProfile extends DatabricksProfileSummary {
131
138
 
132
139
  /** Cross-language lease adapter; implementations may use files, databases, or FFI. */
133
140
  export interface LockAdapter {
134
- acquire(key: string, timeoutMs: number): Promise<string>;
141
+ /** Acquire the key's lease. Omit `timeoutMs` (or pass `0`) to wait indefinitely. */
142
+ acquire(key: string, timeoutMs?: number): Promise<string>;
135
143
  release(lease: string): Promise<void>;
136
144
  }
137
145
 
@@ -141,7 +149,7 @@ export interface CredentialStore {
141
149
  prepareWrite(): Promise<void>;
142
150
  save(key: string, token: Token): Promise<void>;
143
151
  remove(key: string): Promise<void>;
144
- acquireLock(key: string, timeoutMs: number): Promise<string>;
152
+ acquireLock(key: string, timeoutMs?: number): Promise<string>;
145
153
  releaseLock(lease: string): Promise<void>;
146
154
  name(): string;
147
155
  }
@@ -1,207 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { basename, dirname, join } from "node:path";
5
- import { acquireFileLock } from "@dbx-tools/core/file-lock";
6
- import { authLogger, credentialId, tokenMetadata } from "./_logging.js";
7
- import { AuthError } from "./errors.js";
8
- import { FileLayout } from "./types.js";
9
- const logger = authLogger("file-storage");
10
- class NodeFileLocks {
11
- lockDirectory;
12
- leases = new Map();
13
- constructor(lockDirectory) {
14
- this.lockDirectory = lockDirectory;
15
- }
16
- async acquire(key, timeoutMs) {
17
- logger.debug("waiting for file lock", {
18
- credential: credentialId(key),
19
- timeoutMs,
20
- });
21
- const lease = await acquireFileLock(key, { dir: this.lockDirectory, timeoutMs });
22
- const id = randomUUID();
23
- this.leases.set(id, lease);
24
- logger.debug("file lock acquired", {
25
- credential: credentialId(key),
26
- backend: lease.backend,
27
- });
28
- return id;
29
- }
30
- async release(id) {
31
- const lease = this.leases.get(id);
32
- if (!lease)
33
- return;
34
- this.leases.delete(id);
35
- await lease.release();
36
- logger.debug("file lock released", { backend: lease.backend });
37
- }
38
- }
39
- /** File-backed credential store preserving unrelated Databricks CLI entries. */
40
- export class FileCredentialStore {
41
- root;
42
- layout;
43
- locks;
44
- constructor(root = join(homedir(), ".databricks"), layout = FileLayout.Single, locks) {
45
- this.root = root;
46
- this.layout = layout;
47
- this.locks = locks ?? new NodeFileLocks(join(root, "locks"));
48
- }
49
- async load(key) {
50
- const store = this.forKey(key);
51
- return store.withCacheLock(async () => {
52
- const token = deserializeToken((await store.readCache()).tokens[key]);
53
- logger.debug("loaded file credential", {
54
- credential: credentialId(key),
55
- layout: this.layout,
56
- token: tokenMetadata(token),
57
- });
58
- return token;
59
- });
60
- }
61
- async prepareWrite() {
62
- await ensureDirectory(this.root, 0o700);
63
- logger.debug("prepared credential directory", { layout: this.layout });
64
- }
65
- async save(key, token) {
66
- const store = this.forKey(key);
67
- await store.withCacheLock(async () => {
68
- const cache = await store.readCache();
69
- cache.tokens[key] = serializeToken(token);
70
- await store.writeCache(cache);
71
- logger.debug("saved file credential", {
72
- credential: credentialId(key),
73
- layout: this.layout,
74
- token: tokenMetadata(token),
75
- });
76
- });
77
- }
78
- async remove(key) {
79
- const store = this.forKey(key);
80
- await store.withCacheLock(async () => {
81
- const cache = await store.readCache();
82
- delete cache.tokens[key];
83
- await store.writeCache(cache);
84
- logger.debug("removed file credential", {
85
- credential: credentialId(key),
86
- layout: this.layout,
87
- });
88
- });
89
- }
90
- acquireLock(key, timeoutMs) {
91
- return this.locks.acquire(this.layout === FileLayout.Single ? `${this.root}:refresh` : `${this.root}:${key}:refresh`, timeoutMs);
92
- }
93
- releaseLock(lease) {
94
- return this.locks.release(lease);
95
- }
96
- name() {
97
- return "file";
98
- }
99
- forKey(key) {
100
- if (this.layout === FileLayout.Single)
101
- return this;
102
- const digest = createHash("sha256").update(key).digest("hex");
103
- return new FileCredentialStore(join(this.root, digest), FileLayout.Single, this.locks);
104
- }
105
- async withCacheLock(action) {
106
- await ensureDirectory(this.root, 0o700);
107
- const lease = await this.locks.acquire(`${this.root}:cache`, 30_000);
108
- try {
109
- return await action();
110
- }
111
- finally {
112
- await this.locks.release(lease);
113
- }
114
- }
115
- async readCache() {
116
- try {
117
- const source = await readTextFile(join(this.root, "token-cache.json"));
118
- const cache = source ? JSON.parse(source) : { version: 1, tokens: {} };
119
- if (cache.version !== 1 || typeof cache.tokens !== "object" || !cache.tokens) {
120
- throw new AuthError("storage", "Token cache must use version 1");
121
- }
122
- logger.debug("read token cache", {
123
- exists: Boolean(source),
124
- credentialCount: Object.keys(cache.tokens).length,
125
- });
126
- return cache;
127
- }
128
- catch (cause) {
129
- if (cause instanceof AuthError)
130
- throw cause;
131
- throw new AuthError("storage", "Could not read Databricks token cache", { cause });
132
- }
133
- }
134
- async writeCache(cache) {
135
- try {
136
- await atomicWriteTextFile(join(this.root, "token-cache.json"), `${JSON.stringify(cache, null, 2)}\n`, 0o600);
137
- logger.debug("wrote token cache", { credentialCount: Object.keys(cache.tokens).length });
138
- }
139
- catch (cause) {
140
- throw new AuthError("storage", "Could not write Databricks token cache", { cause });
141
- }
142
- }
143
- }
144
- async function ensureDirectory(path, mode) {
145
- await mkdir(path, { recursive: true, mode });
146
- await chmod(path, mode);
147
- }
148
- async function readTextFile(path) {
149
- try {
150
- return await readFile(path, "utf8");
151
- }
152
- catch (cause) {
153
- if (cause.code === "ENOENT")
154
- return undefined;
155
- throw cause;
156
- }
157
- }
158
- async function atomicWriteTextFile(path, content, mode) {
159
- const parent = dirname(path);
160
- await ensureDirectory(parent, 0o700);
161
- const temporary = join(parent, `.${basename(path)}-${randomUUID()}.tmp`);
162
- try {
163
- await writeFile(temporary, content, { mode, flag: "wx" });
164
- await chmod(temporary, mode);
165
- await rename(temporary, path);
166
- }
167
- finally {
168
- await unlink(temporary).catch((cause) => {
169
- if (cause.code !== "ENOENT")
170
- throw cause;
171
- });
172
- }
173
- }
174
- function serializeToken(token) {
175
- return {
176
- access_token: token.accessToken,
177
- token_type: token.tokenType,
178
- ...(token.refreshToken ? { refresh_token: token.refreshToken } : {}),
179
- ...(token.expiry ? { expiry: token.expiry } : {}),
180
- ...(token.scopes.length ? { scopes: token.scopes } : {}),
181
- };
182
- }
183
- function deserializeToken(value) {
184
- if (!value || typeof value !== "object")
185
- return undefined;
186
- const record = value;
187
- const accessToken = stringValue(record.access_token ?? record.accessToken);
188
- if (!accessToken)
189
- return undefined;
190
- return {
191
- accessToken,
192
- tokenType: stringValue(record.token_type ?? record.tokenType) ?? "Bearer",
193
- ...(stringValue(record.refresh_token ?? record.refreshToken)
194
- ? { refreshToken: stringValue(record.refresh_token ?? record.refreshToken) }
195
- : {}),
196
- ...(stringValue(record.expiry ?? record.expires_at)
197
- ? { expiry: stringValue(record.expiry ?? record.expires_at) }
198
- : {}),
199
- scopes: Array.isArray(record.scopes)
200
- ? record.scopes.filter((scope) => typeof scope === "string")
201
- : [],
202
- };
203
- }
204
- function stringValue(value) {
205
- return typeof value === "string" && value.trim() ? value : undefined;
206
- }
207
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZS1zdG9yYWdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL25vZGUtc3RvcmFnZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLFVBQVUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNyRCxPQUFPLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNyRixPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ2xDLE9BQU8sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUVwRCxPQUFPLEVBQUUsZUFBZSxFQUFzQixNQUFNLDJCQUEyQixDQUFDO0FBRWhGLE9BQU8sRUFBRSxVQUFVLEVBQUUsWUFBWSxFQUFFLGFBQWEsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN4RSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRXhDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFFeEMsTUFBTSxNQUFNLEdBQUcsVUFBVSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0FBTzFDLE1BQU0sYUFBYTtJQUdZO0lBRlosTUFBTSxHQUFHLElBQUksR0FBRyxFQUF5QixDQUFDO0lBRTNELFlBQTZCLGFBQXFCO1FBQXJCLGtCQUFhLEdBQWIsYUFBYSxDQUFRO0lBQUcsQ0FBQztJQUV0RCxLQUFLLENBQUMsT0FBTyxDQUFDLEdBQVcsRUFBRSxTQUFpQjtRQUMxQyxNQUFNLENBQUMsS0FBSyxDQUFDLHVCQUF1QixFQUFFO1lBQ3BDLFVBQVUsRUFBRSxZQUFZLENBQUMsR0FBRyxDQUFDO1lBQzdCLFNBQVM7U0FDVixDQUFDLENBQUM7UUFDSCxNQUFNLEtBQUssR0FBRyxNQUFNLGVBQWUsQ0FBQyxHQUFHLEVBQUUsRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLGFBQWEsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO1FBQ2pGLE1BQU0sRUFBRSxHQUFHLFVBQVUsRUFBRSxDQUFDO1FBQ3hCLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUMzQixNQUFNLENBQUMsS0FBSyxDQUFDLG9CQUFvQixFQUFFO1lBQ2pDLFVBQVUsRUFBRSxZQUFZLENBQUMsR0FBRyxDQUFDO1lBQzdCLE9BQU8sRUFBRSxLQUFLLENBQUMsT0FBTztTQUN2QixDQUFDLENBQUM7UUFDSCxPQUFPLEVBQUUsQ0FBQztJQUNaLENBQUM7SUFFRCxLQUFLLENBQUMsT0FBTyxDQUFDLEVBQVU7UUFDdEIsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDbEMsSUFBSSxDQUFDLEtBQUs7WUFBRSxPQUFPO1FBQ25CLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ3ZCLE1BQU0sS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3RCLE1BQU0sQ0FBQyxLQUFLLENBQUMsb0JBQW9CLEVBQUUsRUFBRSxPQUFPLEVBQUUsS0FBSyxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFDakUsQ0FBQztDQUNGO0FBRUQsZ0ZBQWdGO0FBQ2hGLE1BQU0sT0FBTyxtQkFBbUI7SUFJWDtJQUNBO0lBSkYsS0FBSyxDQUFjO0lBRXBDLFlBQ21CLE9BQU8sSUFBSSxDQUFDLE9BQU8sRUFBRSxFQUFFLGFBQWEsQ0FBQyxFQUNyQyxTQUFTLFVBQVUsQ0FBQyxNQUFNLEVBQzNDLEtBQW1CO1FBRkYsU0FBSSxHQUFKLElBQUksQ0FBaUM7UUFDckMsV0FBTSxHQUFOLE1BQU0sQ0FBb0I7UUFHM0MsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLElBQUksSUFBSSxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQy9ELENBQUM7SUFFRCxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQVc7UUFDcEIsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMvQixPQUFPLEtBQUssQ0FBQyxhQUFhLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDcEMsTUFBTSxLQUFLLEdBQUcsZ0JBQWdCLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQ3RFLE1BQU0sQ0FBQyxLQUFLLENBQUMsd0JBQXdCLEVBQUU7Z0JBQ3JDLFVBQVUsRUFBRSxZQUFZLENBQUMsR0FBRyxDQUFDO2dCQUM3QixNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU07Z0JBQ25CLEtBQUssRUFBRSxhQUFhLENBQUMsS0FBSyxDQUFDO2FBQzVCLENBQUMsQ0FBQztZQUNILE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQsS0FBSyxDQUFDLFlBQVk7UUFDaEIsTUFBTSxlQUFlLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztRQUN4QyxNQUFNLENBQUMsS0FBSyxDQUFDLCtCQUErQixFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQ3pFLENBQUM7SUFFRCxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQVcsRUFBRSxLQUFZO1FBQ2xDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDL0IsTUFBTSxLQUFLLENBQUMsYUFBYSxDQUFDLEtBQUssSUFBSSxFQUFFO1lBQ25DLE1BQU0sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ3RDLEtBQUssQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQzFDLE1BQU0sS0FBSyxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUM5QixNQUFNLENBQUMsS0FBSyxDQUFDLHVCQUF1QixFQUFFO2dCQUNwQyxVQUFVLEVBQUUsWUFBWSxDQUFDLEdBQUcsQ0FBQztnQkFDN0IsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNO2dCQUNuQixLQUFLLEVBQUUsYUFBYSxDQUFDLEtBQUssQ0FBQzthQUM1QixDQUFDLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxLQUFLLENBQUMsTUFBTSxDQUFDLEdBQVc7UUFDdEIsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMvQixNQUFNLEtBQUssQ0FBQyxhQUFhLENBQUMsS0FBSyxJQUFJLEVBQUU7WUFDbkMsTUFBTSxLQUFLLEdBQUcsTUFBTSxLQUFLLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDdEMsT0FBTyxLQUFLLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3pCLE1BQU0sS0FBSyxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUM5QixNQUFNLENBQUMsS0FBSyxDQUFDLHlCQUF5QixFQUFFO2dCQUN0QyxVQUFVLEVBQUUsWUFBWSxDQUFDLEdBQUcsQ0FBQztnQkFDN0IsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNO2FBQ3BCLENBQUMsQ0FBQztRQUNMLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVELFdBQVcsQ0FBQyxHQUFXLEVBQUUsU0FBaUI7UUFDeEMsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FDdkIsSUFBSSxDQUFDLE1BQU0sS0FBSyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxJQUFJLFVBQVUsQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxJQUFJLEdBQUcsVUFBVSxFQUMxRixTQUFTLENBQ1YsQ0FBQztJQUNKLENBQUM7SUFFRCxXQUFXLENBQUMsS0FBYTtRQUN2QixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ25DLENBQUM7SUFFRCxJQUFJO1FBQ0YsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVPLE1BQU0sQ0FBQyxHQUFXO1FBQ3hCLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxVQUFVLENBQUMsTUFBTTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBQ25ELE1BQU0sTUFBTSxHQUFHLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzlELE9BQU8sSUFBSSxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsRUFBRSxVQUFVLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN6RixDQUFDO0lBRU8sS0FBSyxDQUFDLGFBQWEsQ0FBSSxNQUF3QjtRQUNyRCxNQUFNLGVBQWUsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDO1FBQ3hDLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDckUsSUFBSSxDQUFDO1lBQ0gsT0FBTyxNQUFNLE1BQU0sRUFBRSxDQUFDO1FBQ3hCLENBQUM7Z0JBQVMsQ0FBQztZQUNULE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDbEMsQ0FBQztJQUNILENBQUM7SUFFTyxLQUFLLENBQUMsU0FBUztRQUNyQixJQUFJLENBQUM7WUFDSCxNQUFNLE1BQU0sR0FBRyxNQUFNLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxDQUFDLENBQUM7WUFDdkUsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBRSxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBZ0IsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxFQUFFLEVBQUUsQ0FBQztZQUN2RixJQUFJLEtBQUssQ0FBQyxPQUFPLEtBQUssQ0FBQyxJQUFJLE9BQU8sS0FBSyxDQUFDLE1BQU0sS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQzdFLE1BQU0sSUFBSSxTQUFTLENBQUMsU0FBUyxFQUFFLGdDQUFnQyxDQUFDLENBQUM7WUFDbkUsQ0FBQztZQUNELE1BQU0sQ0FBQyxLQUFLLENBQUMsa0JBQWtCLEVBQUU7Z0JBQy9CLE1BQU0sRUFBRSxPQUFPLENBQUMsTUFBTSxDQUFDO2dCQUN2QixlQUFlLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTTthQUNsRCxDQUFDLENBQUM7WUFDSCxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsSUFBSSxLQUFLLFlBQVksU0FBUztnQkFBRSxNQUFNLEtBQUssQ0FBQztZQUM1QyxNQUFNLElBQUksU0FBUyxDQUFDLFNBQVMsRUFBRSx1Q0FBdUMsRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7UUFDckYsQ0FBQztJQUNILENBQUM7SUFFTyxLQUFLLENBQUMsVUFBVSxDQUFDLEtBQWlCO1FBQ3hDLElBQUksQ0FBQztZQUNILE1BQU0sbUJBQW1CLENBQ3ZCLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLGtCQUFrQixDQUFDLEVBQ25DLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQ3JDLEtBQUssQ0FDTixDQUFDO1lBQ0YsTUFBTSxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsRUFBRSxFQUFFLGVBQWUsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQzNGLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsTUFBTSxJQUFJLFNBQVMsQ0FBQyxTQUFTLEVBQUUsd0NBQXdDLEVBQUUsRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO1FBQ3RGLENBQUM7SUFDSCxDQUFDO0NBQ0Y7QUFFRCxLQUFLLFVBQVUsZUFBZSxDQUFDLElBQVksRUFBRSxJQUFZO0lBQ3ZELE1BQU0sS0FBSyxDQUFDLElBQUksRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUM3QyxNQUFNLEtBQUssQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDMUIsQ0FBQztBQUVELEtBQUssVUFBVSxZQUFZLENBQUMsSUFBWTtJQUN0QyxJQUFJLENBQUM7UUFDSCxPQUFPLE1BQU0sUUFBUSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztJQUN0QyxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLElBQUssS0FBK0IsQ0FBQyxJQUFJLEtBQUssUUFBUTtZQUFFLE9BQU8sU0FBUyxDQUFDO1FBQ3pFLE1BQU0sS0FBSyxDQUFDO0lBQ2QsQ0FBQztBQUNILENBQUM7QUFFRCxLQUFLLFVBQVUsbUJBQW1CLENBQUMsSUFBWSxFQUFFLE9BQWUsRUFBRSxJQUFZO0lBQzVFLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM3QixNQUFNLGVBQWUsQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFDckMsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxVQUFVLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDekUsSUFBSSxDQUFDO1FBQ0gsTUFBTSxTQUFTLENBQUMsU0FBUyxFQUFFLE9BQU8sRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUMxRCxNQUFNLEtBQUssQ0FBQyxTQUFTLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDN0IsTUFBTSxNQUFNLENBQUMsU0FBUyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ2hDLENBQUM7WUFBUyxDQUFDO1FBQ1QsTUFBTSxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBNEIsRUFBRSxFQUFFO1lBQzdELElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxRQUFRO2dCQUFFLE1BQU0sS0FBSyxDQUFDO1FBQzNDLENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztBQUNILENBQUM7QUFFRCxTQUFTLGNBQWMsQ0FBQyxLQUFZO0lBQ2xDLE9BQU87UUFDTCxZQUFZLEVBQUUsS0FBSyxDQUFDLFdBQVc7UUFDL0IsVUFBVSxFQUFFLEtBQUssQ0FBQyxTQUFTO1FBQzNCLEdBQUcsQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxFQUFFLGFBQWEsRUFBRSxLQUFLLENBQUMsWUFBWSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNwRSxHQUFHLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDakQsR0FBRyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztLQUN6RCxDQUFDO0FBQ0osQ0FBQztBQUVELFNBQVMsZ0JBQWdCLENBQUMsS0FBYztJQUN0QyxJQUFJLENBQUMsS0FBSyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVE7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUMxRCxNQUFNLE1BQU0sR0FBRyxLQUFnQyxDQUFDO0lBQ2hELE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUMsWUFBWSxJQUFJLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUMzRSxJQUFJLENBQUMsV0FBVztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ25DLE9BQU87UUFDTCxXQUFXO1FBQ1gsU0FBUyxFQUFFLFdBQVcsQ0FBQyxNQUFNLENBQUMsVUFBVSxJQUFJLE1BQU0sQ0FBQyxTQUFTLENBQUMsSUFBSSxRQUFRO1FBQ3pFLEdBQUcsQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLGFBQWEsSUFBSSxNQUFNLENBQUMsWUFBWSxDQUFDO1lBQzFELENBQUMsQ0FBQyxFQUFFLFlBQVksRUFBRSxXQUFXLENBQUMsTUFBTSxDQUFDLGFBQWEsSUFBSSxNQUFNLENBQUMsWUFBWSxDQUFDLEVBQUU7WUFDNUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNQLEdBQUcsQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDO1lBQ2pELENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxXQUFXLENBQUMsTUFBTSxDQUFDLE1BQU0sSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLEVBQUU7WUFDN0QsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNQLE1BQU0sRUFBRSxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUM7WUFDbEMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsS0FBSyxFQUFtQixFQUFFLENBQUMsT0FBTyxLQUFLLEtBQUssUUFBUSxDQUFDO1lBQzdFLENBQUMsQ0FBQyxFQUFFO0tBQ1AsQ0FBQztBQUNKLENBQUM7QUFFRCxTQUFTLFdBQVcsQ0FBQyxLQUFjO0lBQ2pDLE9BQU8sT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7QUFDdkUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGNyZWF0ZUhhc2gsIHJhbmRvbVVVSUQgfSBmcm9tIFwibm9kZTpjcnlwdG9cIjtcbmltcG9ydCB7IGNobW9kLCBta2RpciwgcmVhZEZpbGUsIHJlbmFtZSwgdW5saW5rLCB3cml0ZUZpbGUgfSBmcm9tIFwibm9kZTpmcy9wcm9taXNlc1wiO1xuaW1wb3J0IHsgaG9tZWRpciB9IGZyb20gXCJub2RlOm9zXCI7XG5pbXBvcnQgeyBiYXNlbmFtZSwgZGlybmFtZSwgam9pbiB9IGZyb20gXCJub2RlOnBhdGhcIjtcblxuaW1wb3J0IHsgYWNxdWlyZUZpbGVMb2NrLCB0eXBlIEZpbGVMb2NrTGVhc2UgfSBmcm9tIFwiQGRieC10b29scy9jb3JlL2ZpbGUtbG9ja1wiO1xuXG5pbXBvcnQgeyBhdXRoTG9nZ2VyLCBjcmVkZW50aWFsSWQsIHRva2VuTWV0YWRhdGEgfSBmcm9tIFwiLi9fbG9nZ2luZy50c1wiO1xuaW1wb3J0IHsgQXV0aEVycm9yIH0gZnJvbSBcIi4vZXJyb3JzLnRzXCI7XG5pbXBvcnQgdHlwZSB7IENyZWRlbnRpYWxTdG9yZSwgTG9ja0FkYXB0ZXIsIFRva2VuIH0gZnJvbSBcIi4vdHlwZXMudHNcIjtcbmltcG9ydCB7IEZpbGVMYXlvdXQgfSBmcm9tIFwiLi90eXBlcy50c1wiO1xuXG5jb25zdCBsb2dnZXIgPSBhdXRoTG9nZ2VyKFwiZmlsZS1zdG9yYWdlXCIpO1xuXG5pbnRlcmZhY2UgVG9rZW5DYWNoZSB7XG4gIHZlcnNpb246IG51bWJlcjtcbiAgdG9rZW5zOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbn1cblxuY2xhc3MgTm9kZUZpbGVMb2NrcyBpbXBsZW1lbnRzIExvY2tBZGFwdGVyIHtcbiAgcHJpdmF0ZSByZWFkb25seSBsZWFzZXMgPSBuZXcgTWFwPHN0cmluZywgRmlsZUxvY2tMZWFzZT4oKTtcblxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJlYWRvbmx5IGxvY2tEaXJlY3Rvcnk6IHN0cmluZykge31cblxuICBhc3luYyBhY3F1aXJlKGtleTogc3RyaW5nLCB0aW1lb3V0TXM6IG51bWJlcik6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgbG9nZ2VyLmRlYnVnKFwid2FpdGluZyBmb3IgZmlsZSBsb2NrXCIsIHtcbiAgICAgIGNyZWRlbnRpYWw6IGNyZWRlbnRpYWxJZChrZXkpLFxuICAgICAgdGltZW91dE1zLFxuICAgIH0pO1xuICAgIGNvbnN0IGxlYXNlID0gYXdhaXQgYWNxdWlyZUZpbGVMb2NrKGtleSwgeyBkaXI6IHRoaXMubG9ja0RpcmVjdG9yeSwgdGltZW91dE1zIH0pO1xuICAgIGNvbnN0IGlkID0gcmFuZG9tVVVJRCgpO1xuICAgIHRoaXMubGVhc2VzLnNldChpZCwgbGVhc2UpO1xuICAgIGxvZ2dlci5kZWJ1ZyhcImZpbGUgbG9jayBhY3F1aXJlZFwiLCB7XG4gICAgICBjcmVkZW50aWFsOiBjcmVkZW50aWFsSWQoa2V5KSxcbiAgICAgIGJhY2tlbmQ6IGxlYXNlLmJhY2tlbmQsXG4gICAgfSk7XG4gICAgcmV0dXJuIGlkO1xuICB9XG5cbiAgYXN5bmMgcmVsZWFzZShpZDogc3RyaW5nKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgbGVhc2UgPSB0aGlzLmxlYXNlcy5nZXQoaWQpO1xuICAgIGlmICghbGVhc2UpIHJldHVybjtcbiAgICB0aGlzLmxlYXNlcy5kZWxldGUoaWQpO1xuICAgIGF3YWl0IGxlYXNlLnJlbGVhc2UoKTtcbiAgICBsb2dnZXIuZGVidWcoXCJmaWxlIGxvY2sgcmVsZWFzZWRcIiwgeyBiYWNrZW5kOiBsZWFzZS5iYWNrZW5kIH0pO1xuICB9XG59XG5cbi8qKiBGaWxlLWJhY2tlZCBjcmVkZW50aWFsIHN0b3JlIHByZXNlcnZpbmcgdW5yZWxhdGVkIERhdGFicmlja3MgQ0xJIGVudHJpZXMuICovXG5leHBvcnQgY2xhc3MgRmlsZUNyZWRlbnRpYWxTdG9yZSBpbXBsZW1lbnRzIENyZWRlbnRpYWxTdG9yZSB7XG4gIHByaXZhdGUgcmVhZG9ubHkgbG9ja3M6IExvY2tBZGFwdGVyO1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgcmVhZG9ubHkgcm9vdCA9IGpvaW4oaG9tZWRpcigpLCBcIi5kYXRhYnJpY2tzXCIpLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgbGF5b3V0ID0gRmlsZUxheW91dC5TaW5nbGUsXG4gICAgbG9ja3M/OiBMb2NrQWRhcHRlcixcbiAgKSB7XG4gICAgdGhpcy5sb2NrcyA9IGxvY2tzID8/IG5ldyBOb2RlRmlsZUxvY2tzKGpvaW4ocm9vdCwgXCJsb2Nrc1wiKSk7XG4gIH1cblxuICBhc3luYyBsb2FkKGtleTogc3RyaW5nKTogUHJvbWlzZTxUb2tlbiB8IHVuZGVmaW5lZD4ge1xuICAgIGNvbnN0IHN0b3JlID0gdGhpcy5mb3JLZXkoa2V5KTtcbiAgICByZXR1cm4gc3RvcmUud2l0aENhY2hlTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCB0b2tlbiA9IGRlc2VyaWFsaXplVG9rZW4oKGF3YWl0IHN0b3JlLnJlYWRDYWNoZSgpKS50b2tlbnNba2V5XSk7XG4gICAgICBsb2dnZXIuZGVidWcoXCJsb2FkZWQgZmlsZSBjcmVkZW50aWFsXCIsIHtcbiAgICAgICAgY3JlZGVudGlhbDogY3JlZGVudGlhbElkKGtleSksXG4gICAgICAgIGxheW91dDogdGhpcy5sYXlvdXQsXG4gICAgICAgIHRva2VuOiB0b2tlbk1ldGFkYXRhKHRva2VuKSxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIHRva2VuO1xuICAgIH0pO1xuICB9XG5cbiAgYXN5bmMgcHJlcGFyZVdyaXRlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IGVuc3VyZURpcmVjdG9yeSh0aGlzLnJvb3QsIDBvNzAwKTtcbiAgICBsb2dnZXIuZGVidWcoXCJwcmVwYXJlZCBjcmVkZW50aWFsIGRpcmVjdG9yeVwiLCB7IGxheW91dDogdGhpcy5sYXlvdXQgfSk7XG4gIH1cblxuICBhc3luYyBzYXZlKGtleTogc3RyaW5nLCB0b2tlbjogVG9rZW4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBjb25zdCBzdG9yZSA9IHRoaXMuZm9yS2V5KGtleSk7XG4gICAgYXdhaXQgc3RvcmUud2l0aENhY2hlTG9jayhhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBjYWNoZSA9IGF3YWl0IHN0b3JlLnJlYWRDYWNoZSgpO1xuICAgICAgY2FjaGUudG9rZW5zW2tleV0gPSBzZXJpYWxpemVUb2tlbih0b2tlbik7XG4gICAgICBhd2FpdCBzdG9yZS53cml0ZUNhY2hlKGNhY2hlKTtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhcInNhdmVkIGZpbGUgY3JlZGVudGlhbFwiLCB7XG4gICAgICAgIGNyZWRlbnRpYWw6IGNyZWRlbnRpYWxJZChrZXkpLFxuICAgICAgICBsYXlvdXQ6IHRoaXMubGF5b3V0LFxuICAgICAgICB0b2tlbjogdG9rZW5NZXRhZGF0YSh0b2tlbiksXG4gICAgICB9KTtcbiAgICB9KTtcbiAgfVxuXG4gIGFzeW5jIHJlbW92ZShrZXk6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHN0b3JlID0gdGhpcy5mb3JLZXkoa2V5KTtcbiAgICBhd2FpdCBzdG9yZS53aXRoQ2FjaGVMb2NrKGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGNhY2hlID0gYXdhaXQgc3RvcmUucmVhZENhY2hlKCk7XG4gICAgICBkZWxldGUgY2FjaGUudG9rZW5zW2tleV07XG4gICAgICBhd2FpdCBzdG9yZS53cml0ZUNhY2hlKGNhY2hlKTtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhcInJlbW92ZWQgZmlsZSBjcmVkZW50aWFsXCIsIHtcbiAgICAgICAgY3JlZGVudGlhbDogY3JlZGVudGlhbElkKGtleSksXG4gICAgICAgIGxheW91dDogdGhpcy5sYXlvdXQsXG4gICAgICB9KTtcbiAgICB9KTtcbiAgfVxuXG4gIGFjcXVpcmVMb2NrKGtleTogc3RyaW5nLCB0aW1lb3V0TXM6IG51bWJlcik6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgcmV0dXJuIHRoaXMubG9ja3MuYWNxdWlyZShcbiAgICAgIHRoaXMubGF5b3V0ID09PSBGaWxlTGF5b3V0LlNpbmdsZSA/IGAke3RoaXMucm9vdH06cmVmcmVzaGAgOiBgJHt0aGlzLnJvb3R9OiR7a2V5fTpyZWZyZXNoYCxcbiAgICAgIHRpbWVvdXRNcyxcbiAgICApO1xuICB9XG5cbiAgcmVsZWFzZUxvY2sobGVhc2U6IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICAgIHJldHVybiB0aGlzLmxvY2tzLnJlbGVhc2UobGVhc2UpO1xuICB9XG5cbiAgbmFtZSgpOiBzdHJpbmcge1xuICAgIHJldHVybiBcImZpbGVcIjtcbiAgfVxuXG4gIHByaXZhdGUgZm9yS2V5KGtleTogc3RyaW5nKTogRmlsZUNyZWRlbnRpYWxTdG9yZSB7XG4gICAgaWYgKHRoaXMubGF5b3V0ID09PSBGaWxlTGF5b3V0LlNpbmdsZSkgcmV0dXJuIHRoaXM7XG4gICAgY29uc3QgZGlnZXN0ID0gY3JlYXRlSGFzaChcInNoYTI1NlwiKS51cGRhdGUoa2V5KS5kaWdlc3QoXCJoZXhcIik7XG4gICAgcmV0dXJuIG5ldyBGaWxlQ3JlZGVudGlhbFN0b3JlKGpvaW4odGhpcy5yb290LCBkaWdlc3QpLCBGaWxlTGF5b3V0LlNpbmdsZSwgdGhpcy5sb2Nrcyk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIHdpdGhDYWNoZUxvY2s8VD4oYWN0aW9uOiAoKSA9PiBQcm9taXNlPFQ+KTogUHJvbWlzZTxUPiB7XG4gICAgYXdhaXQgZW5zdXJlRGlyZWN0b3J5KHRoaXMucm9vdCwgMG83MDApO1xuICAgIGNvbnN0IGxlYXNlID0gYXdhaXQgdGhpcy5sb2Nrcy5hY3F1aXJlKGAke3RoaXMucm9vdH06Y2FjaGVgLCAzMF8wMDApO1xuICAgIHRyeSB7XG4gICAgICByZXR1cm4gYXdhaXQgYWN0aW9uKCk7XG4gICAgfSBmaW5hbGx5IHtcbiAgICAgIGF3YWl0IHRoaXMubG9ja3MucmVsZWFzZShsZWFzZSk7XG4gICAgfVxuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyByZWFkQ2FjaGUoKTogUHJvbWlzZTxUb2tlbkNhY2hlPiB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHNvdXJjZSA9IGF3YWl0IHJlYWRUZXh0RmlsZShqb2luKHRoaXMucm9vdCwgXCJ0b2tlbi1jYWNoZS5qc29uXCIpKTtcbiAgICAgIGNvbnN0IGNhY2hlID0gc291cmNlID8gKEpTT04ucGFyc2Uoc291cmNlKSBhcyBUb2tlbkNhY2hlKSA6IHsgdmVyc2lvbjogMSwgdG9rZW5zOiB7fSB9O1xuICAgICAgaWYgKGNhY2hlLnZlcnNpb24gIT09IDEgfHwgdHlwZW9mIGNhY2hlLnRva2VucyAhPT0gXCJvYmplY3RcIiB8fCAhY2FjaGUudG9rZW5zKSB7XG4gICAgICAgIHRocm93IG5ldyBBdXRoRXJyb3IoXCJzdG9yYWdlXCIsIFwiVG9rZW4gY2FjaGUgbXVzdCB1c2UgdmVyc2lvbiAxXCIpO1xuICAgICAgfVxuICAgICAgbG9nZ2VyLmRlYnVnKFwicmVhZCB0b2tlbiBjYWNoZVwiLCB7XG4gICAgICAgIGV4aXN0czogQm9vbGVhbihzb3VyY2UpLFxuICAgICAgICBjcmVkZW50aWFsQ291bnQ6IE9iamVjdC5rZXlzKGNhY2hlLnRva2VucykubGVuZ3RoLFxuICAgICAgfSk7XG4gICAgICByZXR1cm4gY2FjaGU7XG4gICAgfSBjYXRjaCAoY2F1c2UpIHtcbiAgICAgIGlmIChjYXVzZSBpbnN0YW5jZW9mIEF1dGhFcnJvcikgdGhyb3cgY2F1c2U7XG4gICAgICB0aHJvdyBuZXcgQXV0aEVycm9yKFwic3RvcmFnZVwiLCBcIkNvdWxkIG5vdCByZWFkIERhdGFicmlja3MgdG9rZW4gY2FjaGVcIiwgeyBjYXVzZSB9KTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIHdyaXRlQ2FjaGUoY2FjaGU6IFRva2VuQ2FjaGUpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICB0cnkge1xuICAgICAgYXdhaXQgYXRvbWljV3JpdGVUZXh0RmlsZShcbiAgICAgICAgam9pbih0aGlzLnJvb3QsIFwidG9rZW4tY2FjaGUuanNvblwiKSxcbiAgICAgICAgYCR7SlNPTi5zdHJpbmdpZnkoY2FjaGUsIG51bGwsIDIpfVxcbmAsXG4gICAgICAgIDBvNjAwLFxuICAgICAgKTtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhcIndyb3RlIHRva2VuIGNhY2hlXCIsIHsgY3JlZGVudGlhbENvdW50OiBPYmplY3Qua2V5cyhjYWNoZS50b2tlbnMpLmxlbmd0aCB9KTtcbiAgICB9IGNhdGNoIChjYXVzZSkge1xuICAgICAgdGhyb3cgbmV3IEF1dGhFcnJvcihcInN0b3JhZ2VcIiwgXCJDb3VsZCBub3Qgd3JpdGUgRGF0YWJyaWNrcyB0b2tlbiBjYWNoZVwiLCB7IGNhdXNlIH0pO1xuICAgIH1cbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBlbnN1cmVEaXJlY3RvcnkocGF0aDogc3RyaW5nLCBtb2RlOiBudW1iZXIpOiBQcm9taXNlPHZvaWQ+IHtcbiAgYXdhaXQgbWtkaXIocGF0aCwgeyByZWN1cnNpdmU6IHRydWUsIG1vZGUgfSk7XG4gIGF3YWl0IGNobW9kKHBhdGgsIG1vZGUpO1xufVxuXG5hc3luYyBmdW5jdGlvbiByZWFkVGV4dEZpbGUocGF0aDogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmcgfCB1bmRlZmluZWQ+IHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gYXdhaXQgcmVhZEZpbGUocGF0aCwgXCJ1dGY4XCIpO1xuICB9IGNhdGNoIChjYXVzZSkge1xuICAgIGlmICgoY2F1c2UgYXMgTm9kZUpTLkVycm5vRXhjZXB0aW9uKS5jb2RlID09PSBcIkVOT0VOVFwiKSByZXR1cm4gdW5kZWZpbmVkO1xuICAgIHRocm93IGNhdXNlO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGF0b21pY1dyaXRlVGV4dEZpbGUocGF0aDogc3RyaW5nLCBjb250ZW50OiBzdHJpbmcsIG1vZGU6IG51bWJlcik6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCBwYXJlbnQgPSBkaXJuYW1lKHBhdGgpO1xuICBhd2FpdCBlbnN1cmVEaXJlY3RvcnkocGFyZW50LCAwbzcwMCk7XG4gIGNvbnN0IHRlbXBvcmFyeSA9IGpvaW4ocGFyZW50LCBgLiR7YmFzZW5hbWUocGF0aCl9LSR7cmFuZG9tVVVJRCgpfS50bXBgKTtcbiAgdHJ5IHtcbiAgICBhd2FpdCB3cml0ZUZpbGUodGVtcG9yYXJ5LCBjb250ZW50LCB7IG1vZGUsIGZsYWc6IFwid3hcIiB9KTtcbiAgICBhd2FpdCBjaG1vZCh0ZW1wb3JhcnksIG1vZGUpO1xuICAgIGF3YWl0IHJlbmFtZSh0ZW1wb3JhcnksIHBhdGgpO1xuICB9IGZpbmFsbHkge1xuICAgIGF3YWl0IHVubGluayh0ZW1wb3JhcnkpLmNhdGNoKChjYXVzZTogTm9kZUpTLkVycm5vRXhjZXB0aW9uKSA9PiB7XG4gICAgICBpZiAoY2F1c2UuY29kZSAhPT0gXCJFTk9FTlRcIikgdGhyb3cgY2F1c2U7XG4gICAgfSk7XG4gIH1cbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplVG9rZW4odG9rZW46IFRva2VuKTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4ge1xuICByZXR1cm4ge1xuICAgIGFjY2Vzc190b2tlbjogdG9rZW4uYWNjZXNzVG9rZW4sXG4gICAgdG9rZW5fdHlwZTogdG9rZW4udG9rZW5UeXBlLFxuICAgIC4uLih0b2tlbi5yZWZyZXNoVG9rZW4gPyB7IHJlZnJlc2hfdG9rZW46IHRva2VuLnJlZnJlc2hUb2tlbiB9IDoge30pLFxuICAgIC4uLih0b2tlbi5leHBpcnkgPyB7IGV4cGlyeTogdG9rZW4uZXhwaXJ5IH0gOiB7fSksXG4gICAgLi4uKHRva2VuLnNjb3Blcy5sZW5ndGggPyB7IHNjb3BlczogdG9rZW4uc2NvcGVzIH0gOiB7fSksXG4gIH07XG59XG5cbmZ1bmN0aW9uIGRlc2VyaWFsaXplVG9rZW4odmFsdWU6IHVua25vd24pOiBUb2tlbiB8IHVuZGVmaW5lZCB7XG4gIGlmICghdmFsdWUgfHwgdHlwZW9mIHZhbHVlICE9PSBcIm9iamVjdFwiKSByZXR1cm4gdW5kZWZpbmVkO1xuICBjb25zdCByZWNvcmQgPSB2YWx1ZSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcbiAgY29uc3QgYWNjZXNzVG9rZW4gPSBzdHJpbmdWYWx1ZShyZWNvcmQuYWNjZXNzX3Rva2VuID8/IHJlY29yZC5hY2Nlc3NUb2tlbik7XG4gIGlmICghYWNjZXNzVG9rZW4pIHJldHVybiB1bmRlZmluZWQ7XG4gIHJldHVybiB7XG4gICAgYWNjZXNzVG9rZW4sXG4gICAgdG9rZW5UeXBlOiBzdHJpbmdWYWx1ZShyZWNvcmQudG9rZW5fdHlwZSA/PyByZWNvcmQudG9rZW5UeXBlKSA/PyBcIkJlYXJlclwiLFxuICAgIC4uLihzdHJpbmdWYWx1ZShyZWNvcmQucmVmcmVzaF90b2tlbiA/PyByZWNvcmQucmVmcmVzaFRva2VuKVxuICAgICAgPyB7IHJlZnJlc2hUb2tlbjogc3RyaW5nVmFsdWUocmVjb3JkLnJlZnJlc2hfdG9rZW4gPz8gcmVjb3JkLnJlZnJlc2hUb2tlbikgfVxuICAgICAgOiB7fSksXG4gICAgLi4uKHN0cmluZ1ZhbHVlKHJlY29yZC5leHBpcnkgPz8gcmVjb3JkLmV4cGlyZXNfYXQpXG4gICAgICA/IHsgZXhwaXJ5OiBzdHJpbmdWYWx1ZShyZWNvcmQuZXhwaXJ5ID8/IHJlY29yZC5leHBpcmVzX2F0KSB9XG4gICAgICA6IHt9KSxcbiAgICBzY29wZXM6IEFycmF5LmlzQXJyYXkocmVjb3JkLnNjb3BlcylcbiAgICAgID8gcmVjb3JkLnNjb3Blcy5maWx0ZXIoKHNjb3BlKTogc2NvcGUgaXMgc3RyaW5nID0+IHR5cGVvZiBzY29wZSA9PT0gXCJzdHJpbmdcIilcbiAgICAgIDogW10sXG4gIH07XG59XG5cbmZ1bmN0aW9uIHN0cmluZ1ZhbHVlKHZhbHVlOiB1bmtub3duKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PT0gXCJzdHJpbmdcIiAmJiB2YWx1ZS50cmltKCkgPyB2YWx1ZSA6IHVuZGVmaW5lZDtcbn1cbiJdfQ==