@dbx-tools/cli-tunnel 0.6.59 → 0.6.85
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/README.md +1 -364
- package/index.ts +2 -19
- package/lib/index.d.ts +2 -19
- package/lib/index.js +2 -15
- package/lib/src/app.d.ts +12 -123
- package/lib/src/app.js +22 -250
- package/lib/src/cli.d.ts +19 -22
- package/lib/src/cli.js +143 -128
- package/lib/src/options.d.ts +46 -0
- package/lib/src/options.js +51 -0
- package/lib/src/proxy.d.ts +27 -37
- package/lib/src/proxy.js +115 -220
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -81
- package/src/app.ts +20 -282
- package/src/cli.ts +152 -162
- package/src/options.ts +85 -0
- package/src/proxy.ts +139 -261
- package/bin/dbx-tools-tunnel.ts +0 -13
- package/lib/bin/dbx-tools-tunnel.d.ts +0 -2
- package/lib/bin/dbx-tools-tunnel.js +0 -14
- package/lib/src/allowlist.d.ts +0 -32
- package/lib/src/allowlist.js +0 -57
- package/lib/src/env.d.ts +0 -57
- package/lib/src/env.js +0 -60
- package/lib/src/headers.d.ts +0 -108
- package/lib/src/headers.js +0 -140
- package/lib/src/otp.d.ts +0 -49
- package/lib/src/otp.js +0 -124
- package/lib/src/plugin.d.ts +0 -147
- package/lib/src/plugin.js +0 -138
- package/lib/src/portr.d.ts +0 -40
- package/lib/src/portr.js +0 -93
- package/lib/src/rate-limit.d.ts +0 -35
- package/lib/src/rate-limit.js +0 -53
- package/lib/src/signing-key.d.ts +0 -86
- package/lib/src/signing-key.js +0 -170
- package/src/allowlist.ts +0 -60
- package/src/env.ts +0 -72
- package/src/headers.ts +0 -155
- package/src/otp.ts +0 -137
- package/src/plugin.ts +0 -269
- package/src/portr.ts +0 -113
- package/src/rate-limit.ts +0 -59
- package/src/signing-key.ts +0 -201
package/lib/src/app.js
CHANGED
|
@@ -1,258 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The GATE half of the wrapper: a server-less AppKit app whose only job is to
|
|
3
|
+
* expose the `authGate` handlers the proxy calls.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* deliver the one-time code;
|
|
11
|
-
* - dbx-tools auto-configuration + branding via `@dbx-tools/appkit`.
|
|
5
|
+
* The in-process plugin path (`authGate` in the app's own `plugins`) has a real
|
|
6
|
+
* HTTP server to mount routes on. A wrapper does not - the app it fronts is a
|
|
7
|
+
* separate process it must not reach into - so the gate's login routes live on
|
|
8
|
+
* the proxy instead, and this app exists purely to give the plugin the runtime it
|
|
9
|
+
* needs: a `CacheManager` for the one-time-code store and signing key, and the
|
|
10
|
+
* sibling `email` plugin's transport for delivering a code.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* transport. A sign-in code is SYSTEM mail - no on-behalf-of user asked for it
|
|
16
|
-
* and no reply to it reaches anyone - so it sends from the email config's
|
|
17
|
-
* do-not-reply address (`no-reply@EMAIL_DOMAIN` unless EMAIL_SYSTEM_FROM names
|
|
18
|
-
* another).
|
|
19
|
-
*
|
|
20
|
-
* FAIL FAST: a gate that can't email a code is useless, so if email does not
|
|
21
|
-
* resolve to SMTP mode (real delivery), this throws - unless `insecure` is set
|
|
22
|
-
* (`--insecure` / `TUNNEL_INSECURE=true`), in which case the caller runs the
|
|
23
|
-
* tunnel OPEN with no gate.
|
|
12
|
+
* Lazily imported by `cli.ts`, so `dbx tunnel --insecure` (and `install` /
|
|
13
|
+
* `status`) never load AppKit, the Databricks SDK, or the SMTP stack.
|
|
24
14
|
*
|
|
25
15
|
* @module
|
|
26
16
|
*/
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
import { authGate } from "./plugin.js";
|
|
32
|
-
const logger = log.logger("tunnel:app");
|
|
33
|
-
/**
|
|
34
|
-
* A code TTL as the plain phrase the email states ("10 minutes", "45 seconds").
|
|
35
|
-
*
|
|
36
|
-
* Whole minutes read as minutes; anything else stays in seconds rather than
|
|
37
|
-
* rounding, so a 90-second TTL is not advertised as "1 minute" and a recipient is
|
|
38
|
-
* never told the code lives longer than it does.
|
|
39
|
-
*/
|
|
40
|
-
export function expiresIn(seconds) {
|
|
41
|
-
return seconds >= 60 && seconds % 60 === 0
|
|
42
|
-
? string.pluralize(seconds / 60, "minute")
|
|
43
|
-
: string.pluralize(seconds, "second");
|
|
44
|
-
}
|
|
45
|
-
/** The reassurance line closing both parts. */
|
|
46
|
-
const IGNORE_LINE = "If you did not request this code, you can ignore this email.";
|
|
47
|
-
/**
|
|
48
|
-
* The HTML part's source: the full branded template, with the code as a large
|
|
49
|
-
* styled heading (`## ` is what makes it prominent in an inbox).
|
|
50
|
-
*/
|
|
51
|
-
export function codeEmailHtmlBody(code, opts) {
|
|
52
|
-
return [
|
|
53
|
-
opts.message,
|
|
54
|
-
"",
|
|
55
|
-
`## ${code}`,
|
|
56
|
-
"",
|
|
57
|
-
`This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
|
|
58
|
-
"",
|
|
59
|
-
IGNORE_LINE,
|
|
60
|
-
].join("\n");
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* The `text/plain` part, supplied EXPLICITLY rather than rendered from the tree
|
|
64
|
-
* above. Both parts say the same thing; only the line layout differs.
|
|
65
|
-
*
|
|
66
|
-
* The prompt and the code share ONE line ("Your verification code is: 123456").
|
|
67
|
-
* That single-line shape is what iOS, Gmail, Outlook, and Android code detection
|
|
68
|
-
* keys on most reliably - the heuristics look for a code in the same sentence as
|
|
69
|
-
* a recognized prompt, so splitting them across lines makes detection dependent
|
|
70
|
-
* on the client, and any blank line between them defeats it outright.
|
|
71
|
-
*
|
|
72
|
-
* The GENERATED text part cannot hold that shape at all: it is a rendering of the
|
|
73
|
-
* HTML, so it carries the brand header/footer and turns the code heading's CSS
|
|
74
|
-
* margin into blank lines, arriving as `prompt\n\n\ncode`.
|
|
75
|
-
*
|
|
76
|
-
* The code is visible text in BOTH parts, never an image, so a client scraping
|
|
77
|
-
* either one finds it. No trailer line follows the copy - Apple's domain-bound
|
|
78
|
-
* `@domain #code` footer is deliberately NOT emitted, since it constrains the
|
|
79
|
-
* code to one origin and is not what the broadly-compatible shape needs.
|
|
80
|
-
*/
|
|
81
|
-
export function codeEmailTextBody(code, opts) {
|
|
82
|
-
return [
|
|
83
|
-
`${opts.message} ${code}`,
|
|
84
|
-
`This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
|
|
85
|
-
"",
|
|
86
|
-
IGNORE_LINE,
|
|
87
|
-
].join("\n");
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* The SUBJECT line, with the code in it: `"123456 is your verification code"`.
|
|
91
|
-
*
|
|
92
|
-
* The code has to be here, not only in the body, because of what mobile autofill
|
|
93
|
-
* actually reads. iOS offers a code from an incoming NOTIFICATION - natively for
|
|
94
|
-
* Messages and Mail, and since iOS 26 for any app's notification text, which is
|
|
95
|
-
* what finally made Gmail work - and a notification contains the sender, the
|
|
96
|
-
* subject, and a short snippet. Nothing else. A code that lives in the body is
|
|
97
|
-
* invisible to it, however cleanly the body is formatted, which is why a perfectly
|
|
98
|
-
* shaped `text/plain` part still produced no autofill prompt in Gmail.
|
|
99
|
-
*
|
|
100
|
-
* `<code> is your <thing>` rather than `<thing>: <code>` because the leading code
|
|
101
|
-
* survives TRUNCATION: a notification and an inbox list both cut the subject, and
|
|
102
|
-
* the platform heuristics want the code in the same sentence as a recognized
|
|
103
|
-
* prompt ("code", "verification"). Putting it first keeps both intact no matter
|
|
104
|
-
* where the cut lands.
|
|
105
|
-
*
|
|
106
|
-
* `subject` is the configured line ("Your verification code"), lower-cased at its
|
|
107
|
-
* first word so the sentence reads naturally, and left ALONE when it does not look
|
|
108
|
-
* like the conventional phrasing - an operator who set a deliberate subject gets
|
|
109
|
-
* theirs with the code prefixed, not a mangled hybrid.
|
|
110
|
-
*/
|
|
111
|
-
export function codeEmailSubject(code, subject) {
|
|
112
|
-
const trimmed = subject.trim();
|
|
113
|
-
const conventional = /^your\s+/i.exec(trimmed);
|
|
114
|
-
const rest = conventional ? trimmed.slice(conventional[0].length) : trimmed;
|
|
115
|
-
return conventional ? `${code} is your ${rest}` : `${code} - ${trimmed}`;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* The PREHEADER: the snippet beside the subject in an inbox list, and the body of
|
|
119
|
-
* the push notification. Carries the code for the same reason the subject does -
|
|
120
|
-
* it is the other half of what a notification shows - and repeats the prompt
|
|
121
|
-
* wording so a heuristic scanning the snippet alone finds a code next to a phrase
|
|
122
|
-
* it recognizes.
|
|
123
|
-
*/
|
|
124
|
-
export function codeEmailPreview(code, opts) {
|
|
125
|
-
return `${opts.message} ${code}`;
|
|
126
|
-
}
|
|
127
|
-
const { createApp } = createAppNs;
|
|
128
|
-
const { applyLakebaseEnv } = lakebaseResolver;
|
|
129
|
-
const { resolveSystemSenderAddress } = sender;
|
|
130
|
-
const { getEmailRuntime, sendEmail } = transport;
|
|
131
|
-
const { emailBrandFromContext } = emailBrand;
|
|
132
|
-
const { loadBrandContext } = nodeBrand;
|
|
133
|
-
/**
|
|
134
|
-
* Env vars whose presence means a Lakebase database was bound to this deployment.
|
|
135
|
-
* `LAKEBASE_ENDPOINT` is what a Databricks App `postgres` resource binding sets;
|
|
136
|
-
* `PGHOST` is the local/manual spelling.
|
|
137
|
-
*/
|
|
138
|
-
const LAKEBASE_ENV = ["LAKEBASE_ENDPOINT", "PGHOST"];
|
|
139
|
-
/**
|
|
140
|
-
* Upper bound on resolving the Lakebase connection. The gate is in the request
|
|
141
|
-
* path for every visitor, so a slow workspace API must not hold up boot
|
|
142
|
-
* indefinitely; a memory cache is a degraded gate, an unbooted one is no gate.
|
|
143
|
-
*/
|
|
144
|
-
const LAKEBASE_RESOLVE_TIMEOUT_MS = 60_000;
|
|
145
|
-
/**
|
|
146
|
-
* Fill in the Lakebase connection env AppKit's cache needs, so `CacheManager`
|
|
147
|
-
* chooses PERSISTENT storage instead of memory.
|
|
148
|
-
*
|
|
149
|
-
* This is what makes the gate's session signing key and outstanding one-time
|
|
150
|
-
* codes survive a restart. `applyLakebaseEnv` is the SHARED helper AppKit
|
|
151
|
-
* auto-configuration uses, so the gate gets exactly the env a pool needs -
|
|
152
|
-
* `LAKEBASE_ENDPOINT`, `PGHOST`, `PGDATABASE`, and `PGUSER` - rather than a
|
|
153
|
-
* hand-rolled subset. All four matter: `createLakebasePool()` throws without any
|
|
154
|
-
* one of them, and a Databricks App `postgres` resource binding supplies only the
|
|
155
|
-
* first. Without them the pool cannot be built, the cache silently degrades to
|
|
156
|
-
* in-memory, and every redeploy signs out every user (the exact symptom this
|
|
157
|
-
* exists to prevent).
|
|
158
|
-
*
|
|
159
|
-
* It is called here rather than left to `createApp`'s `autoConfigure` because that
|
|
160
|
-
* gates on a `lakebase()` plugin being registered - and this app registers none,
|
|
161
|
-
* having no server to mount Lakebase routes on. Calling the helper directly also
|
|
162
|
-
* lets the gate be stricter than `autoConfigure` is:
|
|
163
|
-
*
|
|
164
|
-
* - It runs ONLY when a Lakebase env var is present. A tunnel is a wrapper
|
|
165
|
-
* around someone else's app and must not invent infrastructure, so with
|
|
166
|
-
* nothing bound it skips rather than falling through the resolver's
|
|
167
|
-
* list-or-CREATE-a-project path.
|
|
168
|
-
* - `autoCreate: false` for the same reason, in case a project happens to exist.
|
|
169
|
-
* - Every failure is a WARNING, never a throw. The cache is an optimization for
|
|
170
|
-
* session durability; admission still requires a code delivered to an
|
|
171
|
-
* allow-listed address, so a gate with a memory cache is safe, just forgetful.
|
|
172
|
-
*/
|
|
173
|
-
export async function resolveCacheStorageEnv() {
|
|
174
|
-
if (!env.text(LAKEBASE_ENV)) {
|
|
175
|
-
logger.info(`no ${env.name(LAKEBASE_ENV)} - the gate cache stays in memory; sessions and codes will not survive a restart`);
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
const { resolved, user } = await applyLakebaseEnv({ autoCreate: false }, AbortSignal.timeout(LAKEBASE_RESOLVE_TIMEOUT_MS));
|
|
180
|
-
logger.info("lakebase resolved for the gate cache", {
|
|
181
|
-
host: resolved.host,
|
|
182
|
-
database: resolved.database,
|
|
183
|
-
endpoint: resolved.endpoint,
|
|
184
|
-
user,
|
|
185
|
-
});
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
logger.warn("could not resolve lakebase - the gate cache stays in memory; sessions and codes will not survive a restart", { error });
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Boot the gate app and return the API the proxy calls. Throws when email is not
|
|
195
|
-
* in SMTP mode (no way to deliver a code) so a misconfigured gate fails fast at
|
|
196
|
-
* startup rather than silently accepting nobody; the caller may catch this and
|
|
197
|
-
* fall back to insecure/open mode when the operator opted in.
|
|
198
|
-
*/
|
|
17
|
+
import { appkit } from "@dbx-tools/appkit";
|
|
18
|
+
import { email } from "@dbx-tools/email";
|
|
19
|
+
import { authGate } from "@dbx-tools/tunnel";
|
|
20
|
+
/** Boot the gate app and return the handlers the proxy authenticates against. */
|
|
199
21
|
export async function startGateApp(config) {
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// reads the connection out of `process.env` when it does. See the function's
|
|
208
|
-
// docs for why the gate resolves this itself.
|
|
209
|
-
await resolveCacheStorageEnv();
|
|
210
|
-
// `sendCode` delivers the OTP through the email plugin's SHARED transport, which
|
|
211
|
-
// the `email()` plugin primes during its `setup()` (awaited by `createApp`
|
|
212
|
-
// below). Using the module-level `sendEmail` avoids a circular dependency on the
|
|
213
|
-
// app handle's inferred type. The `From` is the SYSTEM sender: a verification
|
|
214
|
-
// code is machine-generated and unanswerable, so it must not arrive from a
|
|
215
|
-
// person's address inviting a reply.
|
|
216
|
-
const sendCode = async (to, code, opts) => {
|
|
217
|
-
const from = resolveSystemSenderAddress(getEmailRuntime().config);
|
|
218
|
-
await sendEmail({
|
|
219
|
-
to: [to],
|
|
220
|
-
// The code rides in the SUBJECT and the preheader, not just the body:
|
|
221
|
-
// those two strings are the whole of a push notification, and a
|
|
222
|
-
// notification is what mobile autofill reads. See `codeEmailSubject`.
|
|
223
|
-
subject: codeEmailSubject(code, opts.subject),
|
|
224
|
-
body: codeEmailHtmlBody(code, opts),
|
|
225
|
-
}, from, undefined, { text: codeEmailTextBody(code, opts), preview: codeEmailPreview(code, opts) });
|
|
226
|
-
};
|
|
227
|
-
// `createApp` namespaces each plugin's exports on the handle by manifest name;
|
|
228
|
-
// `handle.authGate` is the in-process gate API the proxy drives.
|
|
229
|
-
//
|
|
230
|
-
// `autoConfigure: false` because {@link resolveCacheStorageEnv} above already
|
|
231
|
-
// ran the one piece of it this app wants (`applyLakebaseEnv`), on this app's
|
|
232
|
-
// terms - no auto-create, and a warning instead of a throw.
|
|
233
|
-
const handle = await createApp({
|
|
234
|
-
autoConfigure: false,
|
|
235
|
-
plugins: [
|
|
236
|
-
// Brand the code email from the resolved context, the same bridge every
|
|
237
|
-
// other dbx-tools email surface uses (accent + font inlined, logo only when
|
|
238
|
-
// it is a fetchable URL - a package-export path cannot load in an inbox).
|
|
239
|
-
email({ brand: emailBrandFromContext(context) }),
|
|
240
|
-
// `brandName` falls back to the resolved context's display name, so the
|
|
241
|
-
// email copy names the app rather than a generic placeholder. Spelled as an
|
|
242
|
-
// explicit `??` rather than a key before `...config`: commander sets
|
|
243
|
-
// `brandName` on the options object whether or not the flag was passed, so
|
|
244
|
-
// spreading it would clobber the context name with `undefined`.
|
|
245
|
-
authGate({ ...config, brandName: config.brandName ?? context.name, sendCode }),
|
|
246
|
-
],
|
|
247
|
-
});
|
|
248
|
-
// Fail fast: the gate needs SMTP to email codes. `getEmailRuntime()` resolves to
|
|
249
|
-
// `mode: "file"` (outbox) or throws when no SMTP creds are configured - neither
|
|
250
|
-
// can deliver to a real inbox, so refuse to bring up a gate that lets nobody in.
|
|
251
|
-
if (getEmailRuntime().config.mode !== "smtp") {
|
|
252
|
-
throw new Error("email is not configured for SMTP delivery - the OTP gate cannot send codes. " +
|
|
253
|
-
"Set SMTP_HOST/SMTP_USER/SMTP_PASSWORD, or pass --insecure / TUNNEL_INSECURE=true to run the tunnel open.");
|
|
254
|
-
}
|
|
255
|
-
logger.info("gate app ready (no server; proxy-driven)");
|
|
256
|
-
return handle.authGate;
|
|
22
|
+
// No `server()` plugin: nothing here listens. `plugins` order matters - `email`
|
|
23
|
+
// registers its transport before `authGate` looks for a sibling to send with.
|
|
24
|
+
const plugins = await appkit.createApp({ plugins: [email(), authGate(config)] });
|
|
25
|
+
const api = plugins.authGate;
|
|
26
|
+
if (!api)
|
|
27
|
+
throw new Error("the authGate plugin exposed no api");
|
|
28
|
+
return api;
|
|
257
29
|
}
|
|
258
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXlCRztBQUVILE9BQU8sRUFBRSxTQUFTLElBQUksV0FBVyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDL0UsT0FBTyxFQUFFLEtBQUssSUFBSSxTQUFTLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsS0FBSyxJQUFJLFVBQVUsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pGLE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzFELE9BQU8sRUFBRSxRQUFRLEVBQStELE1BQU0sYUFBYSxDQUFDO0FBRXBHLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7QUFFeEM7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLFNBQVMsQ0FBQyxPQUFlO0lBQ3ZDLE9BQU8sT0FBTyxJQUFJLEVBQUUsSUFBSSxPQUFPLEdBQUcsRUFBRSxLQUFLLENBQUM7UUFDeEMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsT0FBTyxHQUFHLEVBQUUsRUFBRSxRQUFRLENBQUM7UUFDMUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0FBQzFDLENBQUM7QUFLRCwrQ0FBK0M7QUFDL0MsTUFBTSxXQUFXLEdBQUcsOERBQThELENBQUM7QUFFbkY7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUFDLElBQVksRUFBRSxJQUFjO0lBQzVELE9BQU87UUFDTCxJQUFJLENBQUMsT0FBTztRQUNaLEVBQUU7UUFDRixNQUFNLElBQUksRUFBRTtRQUNa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RSxJQUFJLENBQUMsRUFBRSxDQUMvRSxDQUFDO0lBQ0osQ0FBQyxDQUFDO0lBRUYsK0VBQStFO0lBQy9FLGlFQUFpRTtJQUNqRSxFQUFFO0lBQ0YsOEVBQThFO0lBQzlFLDZFQUE2RTtJQUM3RSw0REFBNEQ7SUFDNUQsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLENBQUM7UUFDN0IsYUFBYSxFQUFFLEtBQUs7UUFDcEIsT0FBTyxFQUFFO1lBQ1Asd0VBQXdFO1lBQ3hFLDRFQUE0RTtZQUM1RSwwRUFBMEU7WUFDMUUsS0FBSyxDQUFDLEVBQUUsS0FBSyxFQUFFLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDaEQsd0VBQXdFO1lBQ3hFLDRFQUE0RTtZQUM1RSxxRUFBcUU7WUFDckUsMkVBQTJFO1lBQzNFLGdFQUFnRTtZQUNoRSxRQUFRLENBQUMsRUFBRSxHQUFHLE1BQU0sRUFBRSxTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQVMsSUFBSSxPQUFPLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDO1NBQy9FO0tBQ0YsQ0FBQyxDQUFDO0lBRUgsaUZBQWlGO0lBQ2pGLGdGQUFnRjtJQUNoRixpRkFBaUY7SUFDakYsSUFBSSxlQUFlLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxLQUFLLE1BQU0sRUFBRSxDQUFDO1FBQzdDLE1BQU0sSUFBSSxLQUFLLENBQ2IsOEVBQThFO1lBQzVFLDBHQUEwRyxDQUM3RyxDQUFDO0lBQ0osQ0FBQztJQUVELE1BQU0sQ0FBQyxJQUFJLENBQUMsMENBQTBDLENBQUMsQ0FBQztJQUN4RCxPQUFPLE1BQU0sQ0FBQyxRQUF1QixDQUFDO0FBQ3hDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRoZSB0dW5uZWwgZ2F0ZSdzIHRpbnkgQXBwS2l0IFwiYXBwXCIgLSBgY3JlYXRlQXBwYCBXSVRIT1VUIGEgYHNlcnZlcigpYCBwbHVnaW4uXG4gKlxuICogVGhlcmUgaXMgbm8gSFRUUCBzZXJ2ZXIgaGVyZTogdGhlIHR1bm5lbCBwcm94eSBpcyB0aGUgc2VydmVyLCBhbmQgaXQgY2FsbHMgdGhlXG4gKiBnYXRlIGhhbmRsZXJzIGluLXByb2Nlc3MuIGBjcmVhdGVBcHBgIGlzIHVzZWQgb25seSBmb3Igd2hhdCBpdCBhdXRvLXdpcmVzOlxuICogICAtIGBDYWNoZU1hbmFnZXJgIChMYWtlYmFzZSB3aGVuIHRoaXMgcHJvY2VzcyBjYW4gcmVhY2ggaXQsIGVsc2UgbWVtb3J5KSAtXG4gKiAgICAgd2hpY2ggdGhlIE9UUCBgQ29kZVN0b3JlYCBhbmQgdGhlIHNlc3Npb24gc2lnbmluZyBrZXkgdXNlIGZvciBzdG9yYWdlICsgVFRMXG4gKiAgICAgZXZpY3Rpb247XG4gKiAgIC0gdGhlIGBlbWFpbCgpYCB0cmFuc3BvcnQgKFNNVFAgLyBvdXRib3gpLCBwcmltZWQgZnJvbSBlbnYsIHNvIGBzZW5kQ29kZWAgY2FuXG4gKiAgICAgZGVsaXZlciB0aGUgb25lLXRpbWUgY29kZTtcbiAqICAgLSBkYngtdG9vbHMgYXV0by1jb25maWd1cmF0aW9uICsgYnJhbmRpbmcgdmlhIGBAZGJ4LXRvb2xzL2FwcGtpdGAuXG4gKlxuICogSXQgcmV0dXJucyB0aGUge0BsaW5rIEF1dGhHYXRlQXBpfSB0aGUgcHJveHkgZHJpdmVzLiBgc2VuZENvZGVgIGlzIHdpcmVkIGhlcmVcbiAqICh0aGUgcGx1Z2luIGNhbid0IHJlc29sdmUgSE9XIHRvIHNlbmQgb24gaXRzIG93bikgdG8gdGhlIGVtYWlsIHBsdWdpbidzXG4gKiB0cmFuc3BvcnQuIEEgc2lnbi1pbiBjb2RlIGlzIFNZU1RFTSBtYWlsIC0gbm8gb24tYmVoYWxmLW9mIHVzZXIgYXNrZWQgZm9yIGl0XG4gKiBhbmQgbm8gcmVwbHkgdG8gaXQgcmVhY2hlcyBhbnlvbmUgLSBzbyBpdCBzZW5kcyBmcm9tIHRoZSBlbWFpbCBjb25maWcnc1xuICogZG8tbm90LXJlcGx5IGFkZHJlc3MgKGBuby1yZXBseUBFTUFJTF9ET01BSU5gIHVubGVzcyBFTUFJTF9TWVNURU1fRlJPTSBuYW1lc1xuICogYW5vdGhlcikuXG4gKlxuICogRkFJTCBGQVNUOiBhIGdhdGUgdGhhdCBjYW4ndCBlbWFpbCBhIGNvZGUgaXMgdXNlbGVzcywgc28gaWYgZW1haWwgZG9lcyBub3RcbiAqIHJlc29sdmUgdG8gU01UUCBtb2RlIChyZWFsIGRlbGl2ZXJ5KSwgdGhpcyB0aHJvd3MgLSB1bmxlc3MgYGluc2VjdXJlYCBpcyBzZXRcbiAqIChgLS1pbnNlY3VyZWAgLyBgVFVOTkVMX0lOU0VDVVJFPXRydWVgKSwgaW4gd2hpY2ggY2FzZSB0aGUgY2FsbGVyIHJ1bnMgdGhlXG4gKiB0dW5uZWwgT1BFTiB3aXRoIG5vIGdhdGUuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IGNyZWF0ZUFwcCBhcyBjcmVhdGVBcHBOcywgbGFrZWJhc2VSZXNvbHZlciB9IGZyb20gXCJAZGJ4LXRvb2xzL2FwcGtpdFwiO1xuaW1wb3J0IHsgYnJhbmQgYXMgbm9kZUJyYW5kIH0gZnJvbSBcIkBkYngtdG9vbHMvY29yZVwiO1xuaW1wb3J0IHsgYnJhbmQgYXMgZW1haWxCcmFuZCwgZW1haWwsIHNlbmRlciwgdHJhbnNwb3J0IH0gZnJvbSBcIkBkYngtdG9vbHMvZW1haWxcIjtcbmltcG9ydCB7IGVudiwgbG9nLCBzdHJpbmcgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgYXV0aEdhdGUsIHR5cGUgQXV0aEdhdGVBcGksIHR5cGUgQXV0aEdhdGVDb25maWcsIHR5cGUgU2VuZENvZGVPcHRpb25zIH0gZnJvbSBcIi4vcGx1Z2luLnRzXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJ0dW5uZWw6YXBwXCIpO1xuXG4vKipcbiAqIEEgY29kZSBUVEwgYXMgdGhlIHBsYWluIHBocmFzZSB0aGUgZW1haWwgc3RhdGVzIChcIjEwIG1pbnV0ZXNcIiwgXCI0NSBzZWNvbmRzXCIpLlxuICpcbiAqIFdob2xlIG1pbnV0ZXMgcmVhZCBhcyBtaW51dGVzOyBhbnl0aGluZyBlbHNlIHN0YXlzIGluIHNlY29uZHMgcmF0aGVyIHRoYW5cbiAqIHJvdW5kaW5nLCBzbyBhIDkwLXNlY29uZCBUVEwgaXMgbm90IGFkdmVydGlzZWQgYXMgXCIxIG1pbnV0ZVwiIGFuZCBhIHJlY2lwaWVudCBpc1xuICogbmV2ZXIgdG9sZCB0aGUgY29kZSBsaXZlcyBsb25nZXIgdGhhbiBpdCBkb2VzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZXhwaXJlc0luKHNlY29uZHM6IG51bWJlcik6IHN0cmluZyB7XG4gIHJldHVybiBzZWNvbmRzID49IDYwICYmIHNlY29uZHMgJSA2MCA9PT0gMFxuICAgID8gc3RyaW5nLnBsdXJhbGl6ZShzZWNvbmRzIC8gNjAsIFwibWludXRlXCIpXG4gICAgOiBzdHJpbmcucGx1cmFsaXplKHNlY29uZHMsIFwic2Vjb25kXCIpO1xufVxuXG4vKiogVGhlIHBhcnRzIG9mIHtAbGluayBTZW5kQ29kZU9wdGlvbnN9IHRoZSBjb2RlIGVtYWlsJ3MgY29weSBpcyBidWlsdCBmcm9tLiAqL1xudHlwZSBDb2RlQ29weSA9IFBpY2s8U2VuZENvZGVPcHRpb25zLCBcIm1lc3NhZ2VcIiB8IFwiY29kZVR0bFNlY29uZHNcIj47XG5cbi8qKiBUaGUgcmVhc3N1cmFuY2UgbGluZSBjbG9zaW5nIGJvdGggcGFydHMuICovXG5jb25zdCBJR05PUkVfTElORSA9IFwiSWYgeW91IGRpZCBub3QgcmVxdWVzdCB0aGlzIGNvZGUsIHlvdSBjYW4gaWdub3JlIHRoaXMgZW1haWwuXCI7XG5cbi8qKlxuICogVGhlIEhUTUwgcGFydCdzIHNvdXJjZTogdGhlIGZ1bGwgYnJhbmRlZCB0ZW1wbGF0ZSwgd2l0aCB0aGUgY29kZSBhcyBhIGxhcmdlXG4gKiBzdHlsZWQgaGVhZGluZyAoYCMjIGAgaXMgd2hhdCBtYWtlcyBpdCBwcm9taW5lbnQgaW4gYW4gaW5ib3gpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY29kZUVtYWlsSHRtbEJvZHkoY29kZTogc3RyaW5nLCBvcHRzOiBDb2RlQ29weSk6IHN0cmluZyB7XG4gIHJldHVybiBbXG4gICAgb3B0cy5tZXNzYWdlLFxuICAgIFwiXCIsXG4gICAgYCMjICR7Y29kZX1gLFxuICAgIFwiXCIsXG4gICAgYFRoaXMgY29kZSBleHBpcmVzIGluICR7ZXhwaXJlc0luKG9wdHMuY29kZVR0bFNlY29uZHMpfS5gLFxuICAgIFwiXCIsXG4gICAgSUdOT1JFX0xJTkUsXG4gIF0uam9pbihcIlxcblwiKTtcbn1cblxuLyoqXG4gKiBUaGUgYHRleHQvcGxhaW5gIHBhcnQsIHN1cHBsaWVkIEVYUExJQ0lUTFkgcmF0aGVyIHRoYW4gcmVuZGVyZWQgZnJvbSB0aGUgdHJlZVxuICogYWJvdmUuIEJvdGggcGFydHMgc2F5IHRoZSBzYW1lIHRoaW5nOyBvbmx5IHRoZSBsaW5lIGxheW91dCBkaWZmZXJzLlxuICpcbiAqIFRoZSBwcm9tcHQgYW5kIHRoZSBjb2RlIHNoYXJlIE9ORSBsaW5lIChcIllvdXIgdmVyaWZpY2F0aW9uIGNvZGUgaXM6IDEyMzQ1NlwiKS5cbiAqIFRoYXQgc2luZ2xlLWxpbmUgc2hhcGUgaXMgd2hhdCBpT1MsIEdtYWlsLCBPdXRsb29rLCBhbmQgQW5kcm9pZCBjb2RlIGRldGVjdGlvblxuICoga2V5cyBvbiBtb3N0IHJlbGlhYmx5IC0gdGhlIGhldXJpc3RpY3MgbG9vayBmb3IgYSBjb2RlIGluIHRoZSBzYW1lIHNlbnRlbmNlIGFzXG4gKiBhIHJlY29nbml6ZWQgcHJvbXB0LCBzbyBzcGxpdHRpbmcgdGhlbSBhY3Jvc3MgbGluZXMgbWFrZXMgZGV0ZWN0aW9uIGRlcGVuZGVudFxuICogb24gdGhlIGNsaWVudCwgYW5kIGFueSBibGFuayBsaW5lIGJldHdlZW4gdGhlbSBkZWZlYXRzIGl0IG91dHJpZ2h0LlxuICpcbiAqIFRoZSBHRU5FUkFURUQgdGV4dCBwYXJ0IGNhbm5vdCBob2xkIHRoYXQgc2hhcGUgYXQgYWxsOiBpdCBpcyBhIHJlbmRlcmluZyBvZiB0aGVcbiAqIEhUTUwsIHNvIGl0IGNhcnJpZXMgdGhlIGJyYW5kIGhlYWRlci9mb290ZXIgYW5kIHR1cm5zIHRoZSBjb2RlIGhlYWRpbmcncyBDU1NcbiAqIG1hcmdpbiBpbnRvIGJsYW5rIGxpbmVzLCBhcnJpdmluZyBhcyBgcHJvbXB0XFxuXFxuXFxuY29kZWAuXG4gKlxuICogVGhlIGNvZGUgaXMgdmlzaWJsZSB0ZXh0IGluIEJPVEggcGFydHMsIG5ldmVyIGFuIGltYWdlLCBzbyBhIGNsaWVudCBzY3JhcGluZ1xuICogZWl0aGVyIG9uZSBmaW5kcyBpdC4gTm8gdHJhaWxlciBsaW5lIGZvbGxvd3MgdGhlIGNvcHkgLSBBcHBsZSdzIGRvbWFpbi1ib3VuZFxuICogYEBkb21haW4gI2NvZGVgIGZvb3RlciBpcyBkZWxpYmVyYXRlbHkgTk9UIGVtaXR0ZWQsIHNpbmNlIGl0IGNvbnN0cmFpbnMgdGhlXG4gKiBjb2RlIHRvIG9uZSBvcmlnaW4gYW5kIGlzIG5vdCB3aGF0IHRoZSBicm9hZGx5LWNvbXBhdGlibGUgc2hhcGUgbmVlZHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjb2RlRW1haWxUZXh0Qm9keShjb2RlOiBzdHJpbmcsIG9wdHM6IENvZGVDb3B5KTogc3RyaW5nIHtcbiAgcmV0dXJuIFtcbiAgICBgJHtvcHRzLm1lc3NhZ2V9ICR7Y29kZX1gLFxuICAgIGBUaGlzIGNvZGUgZXhwaXJlcyBpbiAke2V4cGlyZXNJbihvcHRzLmNvZGVUdGxTZWNvbmRzKX0uYCxcbiAgICBcIlwiLFxuICAgIElHTk9SRV9MSU5FLFxuICBdLmpvaW4oXCJcXG5cIik7XG59XG5cbi8qKlxuICogVGhlIFNVQkpFQ1QgbGluZSwgd2l0aCB0aGUgY29kZSBpbiBpdDogYFwiMTIzNDU2IGlzIHlvdXIgdmVyaWZpY2F0aW9uIGNvZGVcImAuXG4gKlxuICogVGhlIGNvZGUgaGFzIHRvIGJlIGhlcmUsIG5vdCBvbmx5IGluIHRoZSBib2R5LCBiZWNhdXNlIG9mIHdoYXQgbW9iaWxlIGF1dG9maWxsXG4gKiBhY3R1YWxseSByZWFkcy4gaU9TIG9mZmVycyBhIGNvZGUgZnJvbSBhbiBpbmNvbWluZyBOT1RJRklDQVRJT04gLSBuYXRpdmVseSBmb3JcbiAqIE1lc3NhZ2VzIGFuZCBNYWlsLCBhbmQgc2luY2UgaU9TIDI2IGZvciBhbnkgYXBwJ3Mgbm90aWZpY2F0aW9uIHRleHQsIHdoaWNoIGlzXG4gKiB3aGF0IGZpbmFsbHkgbWFkZSBHbWFpbCB3b3JrIC0gYW5kIGEgbm90aWZpY2F0aW9uIGNvbnRhaW5zIHRoZSBzZW5kZXIsIHRoZVxuICogc3ViamVjdCwgYW5kIGEgc2hvcnQgc25pcHBldC4gTm90aGluZyBlbHNlLiBBIGNvZGUgdGhhdCBsaXZlcyBpbiB0aGUgYm9keSBpc1xuICogaW52aXNpYmxlIHRvIGl0LCBob3dldmVyIGNsZWFubHkgdGhlIGJvZHkgaXMgZm9ybWF0dGVkLCB3aGljaCBpcyB3aHkgYSBwZXJmZWN0bHlcbiAqIHNoYXBlZCBgdGV4dC9wbGFpbmAgcGFydCBzdGlsbCBwcm9kdWNlZCBubyBhdXRvZmlsbCBwcm9tcHQgaW4gR21haWwuXG4gKlxuICogYDxjb2RlPiBpcyB5b3VyIDx0aGluZz5gIHJhdGhlciB0aGFuIGA8dGhpbmc+OiA8Y29kZT5gIGJlY2F1c2UgdGhlIGxlYWRpbmcgY29kZVxuICogc3Vydml2ZXMgVFJVTkNBVElPTjogYSBub3RpZmljYXRpb24gYW5kIGFuIGluYm94IGxpc3QgYm90aCBjdXQgdGhlIHN1YmplY3QsIGFuZFxuICogdGhlIHBsYXRmb3JtIGhldXJpc3RpY3Mgd2FudCB0aGUgY29kZSBpbiB0aGUgc2FtZSBzZW50ZW5jZSBhcyBhIHJlY29nbml6ZWRcbiAqIHByb21wdCAoXCJjb2RlXCIsIFwidmVyaWZpY2F0aW9uXCIpLiBQdXR0aW5nIGl0IGZpcnN0IGtlZXBzIGJvdGggaW50YWN0IG5vIG1hdHRlclxuICogd2hlcmUgdGhlIGN1dCBsYW5kcy5cbiAqXG4gKiBgc3ViamVjdGAgaXMgdGhlIGNvbmZpZ3VyZWQgbGluZSAoXCJZb3VyIHZlcmlmaWNhdGlvbiBjb2RlXCIpLCBsb3dlci1jYXNlZCBhdCBpdHNcbiAqIGZpcnN0IHdvcmQgc28gdGhlIHNlbnRlbmNlIHJlYWRzIG5hdHVyYWxseSwgYW5kIGxlZnQgQUxPTkUgd2hlbiBpdCBkb2VzIG5vdCBsb29rXG4gKiBsaWtlIHRoZSBjb252ZW50aW9uYWwgcGhyYXNpbmcgLSBhbiBvcGVyYXRvciB3aG8gc2V0IGEgZGVsaWJlcmF0ZSBzdWJqZWN0IGdldHNcbiAqIHRoZWlycyB3aXRoIHRoZSBjb2RlIHByZWZpeGVkLCBub3QgYSBtYW5nbGVkIGh5YnJpZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNvZGVFbWFpbFN1YmplY3QoY29kZTogc3RyaW5nLCBzdWJqZWN0OiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCB0cmltbWVkID0gc3ViamVjdC50cmltKCk7XG4gIGNvbnN0IGNvbnZlbnRpb25hbCA9IC9eeW91clxccysvaS5leGVjKHRyaW1tZWQpO1xuICBjb25zdCByZXN0ID0gY29udmVudGlvbmFsID8gdHJpbW1lZC5zbGljZShjb252ZW50aW9uYWxbMF0ubGVuZ3RoKSA6IHRyaW1tZWQ7XG4gIHJldHVybiBjb252ZW50aW9uYWwgPyBgJHtjb2RlfSBpcyB5b3VyICR7cmVzdH1gIDogYCR7Y29kZX0gLSAke3RyaW1tZWR9YDtcbn1cblxuLyoqXG4gKiBUaGUgUFJFSEVBREVSOiB0aGUgc25pcHBldCBiZXNpZGUgdGhlIHN1YmplY3QgaW4gYW4gaW5ib3ggbGlzdCwgYW5kIHRoZSBib2R5IG9mXG4gKiB0aGUgcHVzaCBub3RpZmljYXRpb24uIENhcnJpZXMgdGhlIGNvZGUgZm9yIHRoZSBzYW1lIHJlYXNvbiB0aGUgc3ViamVjdCBkb2VzIC1cbiAqIGl0IGlzIHRoZSBvdGhlciBoYWxmIG9mIHdoYXQgYSBub3RpZmljYXRpb24gc2hvd3MgLSBhbmQgcmVwZWF0cyB0aGUgcHJvbXB0XG4gKiB3b3JkaW5nIHNvIGEgaGV1cmlzdGljIHNjYW5uaW5nIHRoZSBzbmlwcGV0IGFsb25lIGZpbmRzIGEgY29kZSBuZXh0IHRvIGEgcGhyYXNlXG4gKiBpdCByZWNvZ25pemVzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY29kZUVtYWlsUHJldmlldyhjb2RlOiBzdHJpbmcsIG9wdHM6IENvZGVDb3B5KTogc3RyaW5nIHtcbiAgcmV0dXJuIGAke29wdHMubWVzc2FnZX0gJHtjb2RlfWA7XG59XG5cbmNvbnN0IHsgY3JlYXRlQXBwIH0gPSBjcmVhdGVBcHBOcztcbmNvbnN0IHsgYXBwbHlMYWtlYmFzZUVudiB9ID0gbGFrZWJhc2VSZXNvbHZlcjtcbmNvbnN0IHsgcmVzb2x2ZVN5c3RlbVNlbmRlckFkZHJlc3MgfSA9IHNlbmRlcjtcbmNvbnN0IHsgZ2V0RW1haWxSdW50aW1lLCBzZW5kRW1haWwgfSA9IHRyYW5zcG9ydDtcbmNvbnN0IHsgZW1haWxCcmFuZEZyb21Db250ZXh0IH0gPSBlbWFpbEJyYW5kO1xuY29uc3QgeyBsb2FkQnJhbmRDb250ZXh0IH0gPSBub2RlQnJhbmQ7XG5cbi8qKlxuICogRW52IHZhcnMgd2hvc2UgcHJlc2VuY2UgbWVhbnMgYSBMYWtlYmFzZSBkYXRhYmFzZSB3YXMgYm91bmQgdG8gdGhpcyBkZXBsb3ltZW50LlxuICogYExBS0VCQVNFX0VORFBPSU5UYCBpcyB3aGF0IGEgRGF0YWJyaWNrcyBBcHAgYHBvc3RncmVzYCByZXNvdXJjZSBiaW5kaW5nIHNldHM7XG4gKiBgUEdIT1NUYCBpcyB0aGUgbG9jYWwvbWFudWFsIHNwZWxsaW5nLlxuICovXG5jb25zdCBMQUtFQkFTRV9FTlYgPSBbXCJMQUtFQkFTRV9FTkRQT0lOVFwiLCBcIlBHSE9TVFwiXSBhcyBjb25zdDtcblxuLyoqXG4gKiBVcHBlciBib3VuZCBvbiByZXNvbHZpbmcgdGhlIExha2ViYXNlIGNvbm5lY3Rpb24uIFRoZSBnYXRlIGlzIGluIHRoZSByZXF1ZXN0XG4gKiBwYXRoIGZvciBldmVyeSB2aXNpdG9yLCBzbyBhIHNsb3cgd29ya3NwYWNlIEFQSSBtdXN0IG5vdCBob2xkIHVwIGJvb3RcbiAqIGluZGVmaW5pdGVseTsgYSBtZW1vcnkgY2FjaGUgaXMgYSBkZWdyYWRlZCBnYXRlLCBhbiB1bmJvb3RlZCBvbmUgaXMgbm8gZ2F0ZS5cbiAqL1xuY29uc3QgTEFLRUJBU0VfUkVTT0xWRV9USU1FT1VUX01TID0gNjBfMDAwO1xuXG4vKipcbiAqIEZpbGwgaW4gdGhlIExha2ViYXNlIGNvbm5lY3Rpb24gZW52IEFwcEtpdCdzIGNhY2hlIG5lZWRzLCBzbyBgQ2FjaGVNYW5hZ2VyYFxuICogY2hvb3NlcyBQRVJTSVNURU5UIHN0b3JhZ2UgaW5zdGVhZCBvZiBtZW1vcnkuXG4gKlxuICogVGhpcyBpcyB3aGF0IG1ha2VzIHRoZSBnYXRlJ3Mgc2Vzc2lvbiBzaWduaW5nIGtleSBhbmQgb3V0c3RhbmRpbmcgb25lLXRpbWVcbiAqIGNvZGVzIHN1cnZpdmUgYSByZXN0YXJ0LiBgYXBwbHlMYWtlYmFzZUVudmAgaXMgdGhlIFNIQVJFRCBoZWxwZXIgQXBwS2l0XG4gKiBhdXRvLWNvbmZpZ3VyYXRpb24gdXNlcywgc28gdGhlIGdhdGUgZ2V0cyBleGFjdGx5IHRoZSBlbnYgYSBwb29sIG5lZWRzIC1cbiAqIGBMQUtFQkFTRV9FTkRQT0lOVGAsIGBQR0hPU1RgLCBgUEdEQVRBQkFTRWAsIGFuZCBgUEdVU0VSYCAtIHJhdGhlciB0aGFuIGFcbiAqIGhhbmQtcm9sbGVkIHN1YnNldC4gQWxsIGZvdXIgbWF0dGVyOiBgY3JlYXRlTGFrZWJhc2VQb29sKClgIHRocm93cyB3aXRob3V0IGFueVxuICogb25lIG9mIHRoZW0sIGFuZCBhIERhdGFicmlja3MgQXBwIGBwb3N0Z3Jlc2AgcmVzb3VyY2UgYmluZGluZyBzdXBwbGllcyBvbmx5IHRoZVxuICogZmlyc3QuIFdpdGhvdXQgdGhlbSB0aGUgcG9vbCBjYW5ub3QgYmUgYnVpbHQsIHRoZSBjYWNoZSBzaWxlbnRseSBkZWdyYWRlcyB0b1xuICogaW4tbWVtb3J5LCBhbmQgZXZlcnkgcmVkZXBsb3kgc2lnbnMgb3V0IGV2ZXJ5IHVzZXIgKHRoZSBleGFjdCBzeW1wdG9tIHRoaXNcbiAqIGV4aXN0cyB0byBwcmV2ZW50KS5cbiAqXG4gKiBJdCBpcyBjYWxsZWQgaGVyZSByYXRoZXIgdGhhbiBsZWZ0IHRvIGBjcmVhdGVBcHBgJ3MgYGF1dG9Db25maWd1cmVgIGJlY2F1c2UgdGhhdFxuICogZ2F0ZXMgb24gYSBgbGFrZWJhc2UoKWAgcGx1Z2luIGJlaW5nIHJlZ2lzdGVyZWQgLSBhbmQgdGhpcyBhcHAgcmVnaXN0ZXJzIG5vbmUsXG4gKiBoYXZpbmcgbm8gc2VydmVyIHRvIG1vdW50IExha2ViYXNlIHJvdXRlcyBvbi4gQ2FsbGluZyB0aGUgaGVscGVyIGRpcmVjdGx5IGFsc29cbiAqIGxldHMgdGhlIGdhdGUgYmUgc3RyaWN0ZXIgdGhhbiBgYXV0b0NvbmZpZ3VyZWAgaXM6XG4gKlxuICogICAtIEl0IHJ1bnMgT05MWSB3aGVuIGEgTGFrZWJhc2UgZW52IHZhciBpcyBwcmVzZW50LiBBIHR1bm5lbCBpcyBhIHdyYXBwZXJcbiAqICAgICBhcm91bmQgc29tZW9uZSBlbHNlJ3MgYXBwIGFuZCBtdXN0IG5vdCBpbnZlbnQgaW5mcmFzdHJ1Y3R1cmUsIHNvIHdpdGhcbiAqICAgICBub3RoaW5nIGJvdW5kIGl0IHNraXBzIHJhdGhlciB0aGFuIGZhbGxpbmcgdGhyb3VnaCB0aGUgcmVzb2x2ZXInc1xuICogICAgIGxpc3Qtb3ItQ1JFQVRFLWEtcHJvamVjdCBwYXRoLlxuICogICAtIGBhdXRvQ3JlYXRlOiBmYWxzZWAgZm9yIHRoZSBzYW1lIHJlYXNvbiwgaW4gY2FzZSBhIHByb2plY3QgaGFwcGVucyB0byBleGlzdC5cbiAqICAgLSBFdmVyeSBmYWlsdXJlIGlzIGEgV0FSTklORywgbmV2ZXIgYSB0aHJvdy4gVGhlIGNhY2hlIGlzIGFuIG9wdGltaXphdGlvbiBmb3JcbiAqICAgICBzZXNzaW9uIGR1cmFiaWxpdHk7IGFkbWlzc2lvbiBzdGlsbCByZXF1aXJlcyBhIGNvZGUgZGVsaXZlcmVkIHRvIGFuXG4gKiAgICAgYWxsb3ctbGlzdGVkIGFkZHJlc3MsIHNvIGEgZ2F0ZSB3aXRoIGEgbWVtb3J5IGNhY2hlIGlzIHNhZmUsIGp1c3QgZm9yZ2V0ZnVsLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVzb2x2ZUNhY2hlU3RvcmFnZUVudigpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgaWYgKCFlbnYudGV4dChMQUtFQkFTRV9FTlYpKSB7XG4gICAgbG9nZ2VyLmluZm8oXG4gICAgICBgbm8gJHtlbnYubmFtZShMQUtFQkFTRV9FTlYpfSAtIHRoZSBnYXRlIGNhY2hlIHN0YXlzIGluIG1lbW9yeTsgc2Vzc2lvbnMgYW5kIGNvZGVzIHdpbGwgbm90IHN1cnZpdmUgYSByZXN0YXJ0YCxcbiAgICApO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICB0cnkge1xuICAgIGNvbnN0IHsgcmVzb2x2ZWQsIHVzZXIgfSA9IGF3YWl0IGFwcGx5TGFrZWJhc2VFbnYoXG4gICAgICB7IGF1dG9DcmVhdGU6IGZhbHNlIH0sXG4gICAgICBBYm9ydFNpZ25hbC50aW1lb3V0KExBS0VCQVNFX1JFU09MVkVfVElNRU9VVF9NUyksXG4gICAgKTtcbiAgICBsb2dnZXIuaW5mbyhcImxha2ViYXNlIHJlc29sdmVkIGZvciB0aGUgZ2F0ZSBjYWNoZVwiLCB7XG4gICAgICBob3N0OiByZXNvbHZlZC5ob3N0LFxuICAgICAgZGF0YWJhc2U6IHJlc29sdmVkLmRhdGFiYXNlLFxuICAgICAgZW5kcG9pbnQ6IHJlc29sdmVkLmVuZHBvaW50LFxuICAgICAgdXNlcixcbiAgICB9KTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBsb2dnZXIud2FybihcbiAgICAgIFwiY291bGQgbm90IHJlc29sdmUgbGFrZWJhc2UgLSB0aGUgZ2F0ZSBjYWNoZSBzdGF5cyBpbiBtZW1vcnk7IHNlc3Npb25zIGFuZCBjb2RlcyB3aWxsIG5vdCBzdXJ2aXZlIGEgcmVzdGFydFwiLFxuICAgICAgeyBlcnJvciB9LFxuICAgICk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbi8qKlxuICogQm9vdCB0aGUgZ2F0ZSBhcHAgYW5kIHJldHVybiB0aGUgQVBJIHRoZSBwcm94eSBjYWxscy4gVGhyb3dzIHdoZW4gZW1haWwgaXMgbm90XG4gKiBpbiBTTVRQIG1vZGUgKG5vIHdheSB0byBkZWxpdmVyIGEgY29kZSkgc28gYSBtaXNjb25maWd1cmVkIGdhdGUgZmFpbHMgZmFzdCBhdFxuICogc3RhcnR1cCByYXRoZXIgdGhhbiBzaWxlbnRseSBhY2NlcHRpbmcgbm9ib2R5OyB0aGUgY2FsbGVyIG1heSBjYXRjaCB0aGlzIGFuZFxuICogZmFsbCBiYWNrIHRvIGluc2VjdXJlL29wZW4gbW9kZSB3aGVuIHRoZSBvcGVyYXRvciBvcHRlZCBpbi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHN0YXJ0R2F0ZUFwcChjb25maWc6IEF1dGhHYXRlQ29uZmlnKTogUHJvbWlzZTxBdXRoR2F0ZUFwaT4ge1xuICAvLyBUaGUgaG9zdCBhcHAncyBvd24gYnJhbmQgKGBicmFuZGluZy9icmFuZC55YW1sYCBkaXNjb3ZlcmVkIGZyb20gY3dkKSBvciB0aGVcbiAgLy8gZGJ4LXRvb2xzIGRlZmF1bHQuIFRoaXMgaXMgdGhlIE9ORSBicmFuZCBzb3VyY2UgZm9yIHRoZSBnYXRlOiBpdCBzdHlsZXMgdGhlXG4gIC8vIGNvZGUgZW1haWwgKGFjY2VudCBiYW5kLCBmb250LCBsb2dvKSB2aWEgdGhlIGVtYWlsIHBsdWdpbiBBTkQgc3VwcGxpZXMgdGhlXG4gIC8vIGRpc3BsYXkgbmFtZSB0aGUgY29weSB1c2VzLCBzbyBhIGRlcGxveW1lbnQgdGhhdCB0aGVtZXMgaXRzIGFwcCB0aGVtZXMgaXRzXG4gIC8vIHNpZ24taW4gZW1haWwgd2l0aCBpdC4gQW4gZXhwbGljaXQgYGJyYW5kTmFtZWAgc3RpbGwgd2lucyAoc2VlIGJlbG93KS5cbiAgY29uc3QgY29udGV4dCA9IGF3YWl0IGxvYWRCcmFuZENvbnRleHQoKTtcblxuICAvLyBCZWZvcmUgYGNyZWF0ZUFwcGAsIGJlY2F1c2UgQXBwS2l0IHJlc29sdmVzIHRoZSBjYWNoZSdzIHN0b3JhZ2UgZHVyaW5nIGl0IGFuZFxuICAvLyByZWFkcyB0aGUgY29ubmVjdGlvbiBvdXQgb2YgYHByb2Nlc3MuZW52YCB3aGVuIGl0IGRvZXMuIFNlZSB0aGUgZnVuY3Rpb24nc1xuICAvLyBkb2NzIGZvciB3aHkgdGhlIGdhdGUgcmVzb2x2ZXMgdGhpcyBpdHNlbGYuXG4gIGF3YWl0IHJlc29sdmVDYWNoZVN0b3JhZ2VFbnYoKTtcblxuICAvLyBgc2VuZENvZGVgIGRlbGl2ZXJzIHRoZSBPVFAgdGhyb3VnaCB0aGUgZW1haWwgcGx1Z2luJ3MgU0hBUkVEIHRyYW5zcG9ydCwgd2hpY2hcbiAgLy8gdGhlIGBlbWFpbCgpYCBwbHVnaW4gcHJpbWVzIGR1cmluZyBpdHMgYHNldHVwKClgIChhd2FpdGVkIGJ5IGBjcmVhdGVBcHBgXG4gIC8vIGJlbG93KS4gVXNpbmcgdGhlIG1vZHVsZS1sZXZlbCBgc2VuZEVtYWlsYCBhdm9pZHMgYSBjaXJjdWxhciBkZXBlbmRlbmN5IG9uIHRoZVxuICAvLyBhcHAgaGFuZGxlJ3MgaW5mZXJyZWQgdHlwZS4gVGhlIGBGcm9tYCBpcyB0aGUgU1lTVEVNIHNlbmRlcjogYSB2ZXJpZmljYXRpb25cbiAgLy8gY29kZSBpcyBtYWNoaW5lLWdlbmVyYXRlZCBhbmQgdW5hbnN3ZXJhYmxlLCBzbyBpdCBtdXN0IG5vdCBhcnJpdmUgZnJvbSBhXG4gIC8vIHBlcnNvbidzIGFkZHJlc3MgaW52aXRpbmcgYSByZXBseS5cbiAgY29uc3Qgc2VuZENvZGU6IE5vbk51bGxhYmxlPEF1dGhHYXRlQ29uZmlnW1wic2VuZENvZGVcIl0+ID0gYXN5bmMgKHRvLCBjb2RlLCBvcHRzKSA9PiB7XG4gICAgY29uc3QgZnJvbSA9IHJlc29sdmVTeXN0ZW1TZW5kZXJBZGRyZXNzKGdldEVtYWlsUnVudGltZSgpLmNvbmZpZyk7XG4gICAgYXdhaXQgc2VuZEVtYWlsKFxuICAgICAge1xuICAgICAgICB0bzogW3RvXSxcbiAgICAgICAgLy8gVGhlIGNvZGUgcmlkZXMgaW4gdGhlIFNVQkpFQ1QgYW5kIHRoZSBwcmVoZWFkZXIsIG5vdCBqdXN0IHRoZSBib2R5OlxuICAgICAgICAvLyB0aG9zZSB0d28gc3RyaW5ncyBhcmUgdGhlIHdob2xlIG9mIGEgcHVzaCBub3RpZmljYXRpb24sIGFuZCBhXG4gICAgICAgIC8vIG5vdGlmaWNhdGlvbiBpcyB3aGF0IG1vYmlsZSBhdXRvZmlsbCByZWFkcy4gU2VlIGBjb2RlRW1haWxTdWJqZWN0YC5cbiAgICAgICAgc3ViamVjdDogY29kZUVtYWlsU3ViamVjdChjb2RlLCBvcHRzLnN1YmplY3QpLFxuICAgICAgICBib2R5OiBjb2RlRW1haWxIdG1sQm9keShjb2RlLCBvcHRzKSxcbiAgICAgIH0sXG4gICAgICBmcm9tLFxuICAgICAgdW5kZWZpbmVkLFxuICAgICAgeyB0ZXh0OiBjb2RlRW1haWxUZXh0Qm9keShjb2RlLCBvcHRzKSwgcHJldmlldzogY29kZUVtYWlsUHJldmlldyhjb2RlLCBvcHRzKSB9LFxuICAgICk7XG4gIH07XG5cbiAgLy8gYGNyZWF0ZUFwcGAgbmFtZXNwYWNlcyBlYWNoIHBsdWdpbidzIGV4cG9ydHMgb24gdGhlIGhhbmRsZSBieSBtYW5pZmVzdCBuYW1lO1xuICAvLyBgaGFuZGxlLmF1dGhHYXRlYCBpcyB0aGUgaW4tcHJvY2VzcyBnYXRlIEFQSSB0aGUgcHJveHkgZHJpdmVzLlxuICAvL1xuICAvLyBgYXV0b0NvbmZpZ3VyZTogZmFsc2VgIGJlY2F1c2Uge0BsaW5rIHJlc29sdmVDYWNoZVN0b3JhZ2VFbnZ9IGFib3ZlIGFscmVhZHlcbiAgLy8gcmFuIHRoZSBvbmUgcGllY2Ugb2YgaXQgdGhpcyBhcHAgd2FudHMgKGBhcHBseUxha2ViYXNlRW52YCksIG9uIHRoaXMgYXBwJ3NcbiAgLy8gdGVybXMgLSBubyBhdXRvLWNyZWF0ZSwgYW5kIGEgd2FybmluZyBpbnN0ZWFkIG9mIGEgdGhyb3cuXG4gIGNvbnN0IGhhbmRsZSA9IGF3YWl0IGNyZWF0ZUFwcCh7XG4gICAgYXV0b0NvbmZpZ3VyZTogZmFsc2UsXG4gICAgcGx1Z2luczogW1xuICAgICAgLy8gQnJhbmQgdGhlIGNvZGUgZW1haWwgZnJvbSB0aGUgcmVzb2x2ZWQgY29udGV4dCwgdGhlIHNhbWUgYnJpZGdlIGV2ZXJ5XG4gICAgICAvLyBvdGhlciBkYngtdG9vbHMgZW1haWwgc3VyZmFjZSB1c2VzIChhY2NlbnQgKyBmb250IGlubGluZWQsIGxvZ28gb25seSB3aGVuXG4gICAgICAvLyBpdCBpcyBhIGZldGNoYWJsZSBVUkwgLSBhIHBhY2thZ2UtZXhwb3J0IHBhdGggY2Fubm90IGxvYWQgaW4gYW4gaW5ib3gpLlxuICAgICAgZW1haWwoeyBicmFuZDogZW1haWxCcmFuZEZyb21Db250ZXh0KGNvbnRleHQpIH0pLFxuICAgICAgLy8gYGJyYW5kTmFtZWAgZmFsbHMgYmFjayB0byB0aGUgcmVzb2x2ZWQgY29udGV4dCdzIGRpc3BsYXkgbmFtZSwgc28gdGhlXG4gICAgICAvLyBlbWFpbCBjb3B5IG5hbWVzIHRoZSBhcHAgcmF0aGVyIHRoYW4gYSBnZW5lcmljIHBsYWNlaG9sZGVyLiBTcGVsbGVkIGFzIGFuXG4gICAgICAvLyBleHBsaWNpdCBgPz9gIHJhdGhlciB0aGFuIGEga2V5IGJlZm9yZSBgLi4uY29uZmlnYDogY29tbWFuZGVyIHNldHNcbiAgICAgIC8vIGBicmFuZE5hbWVgIG9uIHRoZSBvcHRpb25zIG9iamVjdCB3aGV0aGVyIG9yIG5vdCB0aGUgZmxhZyB3YXMgcGFzc2VkLCBzb1xuICAgICAgLy8gc3ByZWFkaW5nIGl0IHdvdWxkIGNsb2JiZXIgdGhlIGNvbnRleHQgbmFtZSB3aXRoIGB1bmRlZmluZWRgLlxuICAgICAgYXV0aEdhdGUoeyAuLi5jb25maWcsIGJyYW5kTmFtZTogY29uZmlnLmJyYW5kTmFtZSA/PyBjb250ZXh0Lm5hbWUsIHNlbmRDb2RlIH0pLFxuICAgIF0sXG4gIH0pO1xuXG4gIC8vIEZhaWwgZmFzdDogdGhlIGdhdGUgbmVlZHMgU01UUCB0byBlbWFpbCBjb2Rlcy4gYGdldEVtYWlsUnVudGltZSgpYCByZXNvbHZlcyB0b1xuICAvLyBgbW9kZTogXCJmaWxlXCJgIChvdXRib3gpIG9yIHRocm93cyB3aGVuIG5vIFNNVFAgY3JlZHMgYXJlIGNvbmZpZ3VyZWQgLSBuZWl0aGVyXG4gIC8vIGNhbiBkZWxpdmVyIHRvIGEgcmVhbCBpbmJveCwgc28gcmVmdXNlIHRvIGJyaW5nIHVwIGEgZ2F0ZSB0aGF0IGxldHMgbm9ib2R5IGluLlxuICBpZiAoZ2V0RW1haWxSdW50aW1lKCkuY29uZmlnLm1vZGUgIT09IFwic210cFwiKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgXCJlbWFpbCBpcyBub3QgY29uZmlndXJlZCBmb3IgU01UUCBkZWxpdmVyeSAtIHRoZSBPVFAgZ2F0ZSBjYW5ub3Qgc2VuZCBjb2Rlcy4gXCIgK1xuICAgICAgICBcIlNldCBTTVRQX0hPU1QvU01UUF9VU0VSL1NNVFBfUEFTU1dPUkQsIG9yIHBhc3MgLS1pbnNlY3VyZSAvIFRVTk5FTF9JTlNFQ1VSRT10cnVlIHRvIHJ1biB0aGUgdHVubmVsIG9wZW4uXCIsXG4gICAgKTtcbiAgfVxuXG4gIGxvZ2dlci5pbmZvKFwiZ2F0ZSBhcHAgcmVhZHkgKG5vIHNlcnZlcjsgcHJveHktZHJpdmVuKVwiKTtcbiAgcmV0dXJuIGhhbmRsZS5hdXRoR2F0ZSBhcyBBdXRoR2F0ZUFwaTtcbn1cbiJdfQ==
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFFSCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDM0MsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ3pDLE9BQU8sRUFBRSxRQUFRLEVBQXlDLE1BQU0sbUJBQW1CLENBQUM7QUFFcEYsaUZBQWlGO0FBQ2pGLE1BQU0sQ0FBQyxLQUFLLFVBQVUsWUFBWSxDQUFDLE1BQXNCO0lBQ3ZELGdGQUFnRjtJQUNoRiw4RUFBOEU7SUFDOUUsTUFBTSxPQUFPLEdBQUcsTUFBTSxNQUFNLENBQUMsU0FBUyxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUUsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ2pGLE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxRQUFtQyxDQUFDO0lBQ3hELElBQUksQ0FBQyxHQUFHO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxvQ0FBb0MsQ0FBQyxDQUFDO0lBQ2hFLE9BQU8sR0FBRyxDQUFDO0FBQ2IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVGhlIEdBVEUgaGFsZiBvZiB0aGUgd3JhcHBlcjogYSBzZXJ2ZXItbGVzcyBBcHBLaXQgYXBwIHdob3NlIG9ubHkgam9iIGlzIHRvXG4gKiBleHBvc2UgdGhlIGBhdXRoR2F0ZWAgaGFuZGxlcnMgdGhlIHByb3h5IGNhbGxzLlxuICpcbiAqIFRoZSBpbi1wcm9jZXNzIHBsdWdpbiBwYXRoIChgYXV0aEdhdGVgIGluIHRoZSBhcHAncyBvd24gYHBsdWdpbnNgKSBoYXMgYSByZWFsXG4gKiBIVFRQIHNlcnZlciB0byBtb3VudCByb3V0ZXMgb24uIEEgd3JhcHBlciBkb2VzIG5vdCAtIHRoZSBhcHAgaXQgZnJvbnRzIGlzIGFcbiAqIHNlcGFyYXRlIHByb2Nlc3MgaXQgbXVzdCBub3QgcmVhY2ggaW50byAtIHNvIHRoZSBnYXRlJ3MgbG9naW4gcm91dGVzIGxpdmUgb25cbiAqIHRoZSBwcm94eSBpbnN0ZWFkLCBhbmQgdGhpcyBhcHAgZXhpc3RzIHB1cmVseSB0byBnaXZlIHRoZSBwbHVnaW4gdGhlIHJ1bnRpbWUgaXRcbiAqIG5lZWRzOiBhIGBDYWNoZU1hbmFnZXJgIGZvciB0aGUgb25lLXRpbWUtY29kZSBzdG9yZSBhbmQgc2lnbmluZyBrZXksIGFuZCB0aGVcbiAqIHNpYmxpbmcgYGVtYWlsYCBwbHVnaW4ncyB0cmFuc3BvcnQgZm9yIGRlbGl2ZXJpbmcgYSBjb2RlLlxuICpcbiAqIExhemlseSBpbXBvcnRlZCBieSBgY2xpLnRzYCwgc28gYGRieCB0dW5uZWwgLS1pbnNlY3VyZWAgKGFuZCBgaW5zdGFsbGAgL1xuICogYHN0YXR1c2ApIG5ldmVyIGxvYWQgQXBwS2l0LCB0aGUgRGF0YWJyaWNrcyBTREssIG9yIHRoZSBTTVRQIHN0YWNrLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBhcHBraXQgfSBmcm9tIFwiQGRieC10b29scy9hcHBraXRcIjtcbmltcG9ydCB7IGVtYWlsIH0gZnJvbSBcIkBkYngtdG9vbHMvZW1haWxcIjtcbmltcG9ydCB7IGF1dGhHYXRlLCB0eXBlIEF1dGhHYXRlQXBpLCB0eXBlIEF1dGhHYXRlQ29uZmlnIH0gZnJvbSBcIkBkYngtdG9vbHMvdHVubmVsXCI7XG5cbi8qKiBCb290IHRoZSBnYXRlIGFwcCBhbmQgcmV0dXJuIHRoZSBoYW5kbGVycyB0aGUgcHJveHkgYXV0aGVudGljYXRlcyBhZ2FpbnN0LiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHN0YXJ0R2F0ZUFwcChjb25maWc6IEF1dGhHYXRlQ29uZmlnKTogUHJvbWlzZTxBdXRoR2F0ZUFwaT4ge1xuICAvLyBObyBgc2VydmVyKClgIHBsdWdpbjogbm90aGluZyBoZXJlIGxpc3RlbnMuIGBwbHVnaW5zYCBvcmRlciBtYXR0ZXJzIC0gYGVtYWlsYFxuICAvLyByZWdpc3RlcnMgaXRzIHRyYW5zcG9ydCBiZWZvcmUgYGF1dGhHYXRlYCBsb29rcyBmb3IgYSBzaWJsaW5nIHRvIHNlbmQgd2l0aC5cbiAgY29uc3QgcGx1Z2lucyA9IGF3YWl0IGFwcGtpdC5jcmVhdGVBcHAoeyBwbHVnaW5zOiBbZW1haWwoKSwgYXV0aEdhdGUoY29uZmlnKV0gfSk7XG4gIGNvbnN0IGFwaSA9IHBsdWdpbnMuYXV0aEdhdGUgYXMgQXV0aEdhdGVBcGkgfCB1bmRlZmluZWQ7XG4gIGlmICghYXBpKSB0aHJvdyBuZXcgRXJyb3IoXCJ0aGUgYXV0aEdhdGUgcGx1Z2luIGV4cG9zZWQgbm8gYXBpXCIpO1xuICByZXR1cm4gYXBpO1xufVxuIl19
|
package/lib/src/cli.d.ts
CHANGED
|
@@ -1,30 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `dbx
|
|
2
|
+
* `dbx tunnel` - front a command with a public portr tunnel and an email-OTP gate.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* This is the WRAPPER path, and it exists for one case: a project that does not
|
|
5
|
+
* use `@dbx-tools/appkit`'s `createApp`, and therefore cannot register
|
|
6
|
+
* `tunnelInterceptor()` + the `authGate` plugin in-process. An AppKit app should
|
|
7
|
+
* still take the plugin path - one process, no proxy hop, no duplicated header
|
|
8
|
+
* handling.
|
|
6
9
|
*
|
|
7
|
-
*
|
|
10
|
+
* The wrapper claims the PUBLIC port (`DATABRICKS_APP_PORT`, the port the
|
|
11
|
+
* platform and portr route to), moves the wrapped app to a private one, and
|
|
12
|
+
* reverse-proxies between them so the gate sits in front of traffic it would
|
|
13
|
+
* otherwise have no way to intercept. Everything else is delegated: the gate
|
|
14
|
+
* config comes from `plugin.resolveAuthGateConfig`, the portr lifecycle from
|
|
15
|
+
* `portr.*` - both the same functions the in-process path uses.
|
|
8
16
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* platform-neutral and honours whatever port it finds there.
|
|
14
|
-
* 2. Boot the tiny gate AppKit app (no server): inits `CacheManager` + the
|
|
15
|
-
* email transport, yields the in-process gate API.
|
|
16
|
-
* 3. Start the gate PROXY on the ORIGINAL public port, forwarding to the app.
|
|
17
|
-
* 4. Install + run portr pointed at the public port (when a tunnel is
|
|
18
|
-
* configured; otherwise the proxy still gates nothing and forwards).
|
|
19
|
-
*
|
|
20
|
-
* Supervision: the app child, portr child, and this process are tied together -
|
|
21
|
-
* if ANY exits, everything comes down (concurrently-style `killOthers`).
|
|
22
|
-
*
|
|
23
|
-
* Options come from flags OR env; see the option definitions below.
|
|
17
|
+
* Ships no bin. `@dbx-tools/cli` mounts `buildProgram()` as `dbx tunnel` lazily,
|
|
18
|
+
* so `dbx dev` pays for none of this, and `--insecure` / `status` / `install`
|
|
19
|
+
* never load AppKit or the SMTP stack either (the gate app is behind a dynamic
|
|
20
|
+
* import).
|
|
24
21
|
*
|
|
25
22
|
* @module
|
|
26
23
|
*/
|
|
27
|
-
import { CommanderError } from "commander";
|
|
24
|
+
import { Command, CommanderError } from "commander";
|
|
28
25
|
export { CommanderError };
|
|
29
|
-
/**
|
|
30
|
-
export declare function
|
|
26
|
+
/** The `dbx tunnel` program. No side effects until parsed. */
|
|
27
|
+
export declare function buildProgram(name?: string): Command;
|