sliftutils 1.7.128 → 1.7.130

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.
Files changed (55) hide show
  1. package/bin/addmachine.js +9 -0
  2. package/bin/machineclient.js +9 -0
  3. package/bin/machineserver.js +10 -0
  4. package/index.d.ts +15 -0
  5. package/misc/dist/ownIPs.ts.cache +69 -0
  6. package/misc/ownIPs.d.ts +11 -0
  7. package/misc/ownIPs.ts +62 -0
  8. package/package.json +9 -3
  9. package/security/authorizedKeys/authorizedKeys.ts +34 -0
  10. package/security/authorizedKeys/daemon/authLog.ts +12 -5
  11. package/security/authorizedKeys/daemon/changes.ts +16 -1
  12. package/security/authorizedKeys/daemon/daemon.ts +43 -34
  13. package/security/authorizedKeys/daemon/dist/changes.ts.cache +40 -0
  14. package/security/authorizedKeys/daemon/dist/git.ts.cache +121 -0
  15. package/security/authorizedKeys/daemon/dist/notify.ts.cache +39 -0
  16. package/security/authorizedKeys/daemon/dist/paths.ts.cache +26 -0
  17. package/security/authorizedKeys/daemon/dist/repoFiles.ts.cache +114 -0
  18. package/security/authorizedKeys/daemon/dist/revocation.ts.cache +0 -0
  19. package/security/authorizedKeys/daemon/dist/sessions.ts.cache +147 -0
  20. package/security/authorizedKeys/daemon/dist/state.ts.cache +76 -0
  21. package/security/authorizedKeys/daemon/dist/trust.ts.cache +295 -0
  22. package/security/authorizedKeys/daemon/notify.ts +8 -2
  23. package/security/authorizedKeys/daemon/paths.ts +0 -3
  24. package/security/authorizedKeys/daemon/repoFiles.ts +118 -0
  25. package/security/authorizedKeys/daemon/revocation.ts +0 -0
  26. package/security/authorizedKeys/daemon/rootKeys.ts +77 -10
  27. package/security/authorizedKeys/daemon/state.ts +21 -7
  28. package/security/authorizedKeys/daemon/trust.ts +37 -16
  29. package/security/authorizedKeys/daemon/userKeys.ts +5 -4
  30. package/security/authorizedKeys/dist/authorizedKeys.ts.cache +37 -3
  31. package/security/authorizedKeys/dist/keysRepo.ts.cache +111 -0
  32. package/security/authorizedKeys/dist/revokeSource.ts.cache +17 -4
  33. package/security/authorizedKeys/dist/secureSSH.ts.cache +147 -71
  34. package/security/authorizedKeys/dist/sources.ts.cache +52 -8
  35. package/security/authorizedKeys/dist/unrevoke.ts.cache +159 -44
  36. package/security/authorizedKeys/keysRepo.ts +111 -0
  37. package/security/authorizedKeys/secureSSH.ts +66 -58
  38. package/security/authorizedKeys/unrevoke.ts +160 -36
  39. package/security/helpers/dist/remoteSSH.ts.cache +19 -9
  40. package/security/machines/addMachine.ts +84 -0
  41. package/security/machines/dist/addMachine.ts.cache +88 -0
  42. package/security/machines/dist/identity.ts.cache +176 -0
  43. package/security/machines/dist/machines.ts.cache +264 -0
  44. package/security/machines/dist/trustClient.ts.cache +48 -0
  45. package/security/machines/dist/trustState.ts.cache +57 -0
  46. package/security/machines/identity.ts +199 -0
  47. package/security/machines/machines.ts +304 -0
  48. package/security/machines/trustClient.ts +51 -0
  49. package/security/machines/trustServer.ts +87 -0
  50. package/security/notifications/discord.ts +14 -5
  51. package/security/notifications/dist/discord.ts.cache +17 -8
  52. package/security/notifications/dist/remoteWebhook.ts.cache +78 -0
  53. package/security/notifications/dist/setupNotify.ts.cache +39 -0
  54. package/security/signedFiles/dist/signFiles.ts.cache +8 -10
  55. package/security/signedFiles/signFiles.ts +6 -10
@@ -0,0 +1,304 @@
1
+ import fs from "fs/promises";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { resolveKeysRepo } from "../authorizedKeys/keysRepo";
5
+ import { revokeRepo, syncRepoFiles } from "../authorizedKeys/daemon/repoFiles";
6
+ import { listRepoDir, readRepoFile } from "../authorizedKeys/daemon/repoFiles";
7
+ import { newRevocationId, pairKey, readRevocationFiles, readUnrevokes } from "../authorizedKeys/daemon/revocation";
8
+ import { runGit } from "../authorizedKeys/daemon/git";
9
+ import { ensureRevokeKey } from "../authorizedKeys/daemon/repoFiles";
10
+ import { verifyCheckout } from "../authorizedKeys/daemon/trust";
11
+ import { revokeRepoPath, revokeRepoURL } from "../authorizedKeys/revokeSource";
12
+ import { notify } from "../authorizedKeys/daemon/notify";
13
+ import { areDiscordNotificationsConfigured, configureDiscordNotifications, DEFAULT_WEBHOOK_FILE_PATH } from "../notifications/discord";
14
+ import { spawnPromise } from "../helpers/spawn";
15
+
16
+ // Which machines this system talks to, kept in the same repo as the ssh keys. That repo is already
17
+ // signed, already distributed to every machine, and already has somewhere to record a rejection,
18
+ // so a second one would only be a second thing to keep in step.
19
+ export const MACHINES_DIR = "machines";
20
+ const REVOCATIONS_DIR = "revocations";
21
+ const REVOCATION_REASON = "a machine talked to us from an unapproved IP";
22
+ // The revoke repo is pulled at most this often. A check happens per request, and a request must
23
+ // not cost a round trip to github.
24
+ const REVOKE_SYNC_INTERVAL = 60 * 1000;
25
+
26
+ /** One machine we are willing to talk to, and the addresses it may talk to us from. */
27
+ export type MachineState = {
28
+ machineId: string;
29
+ ips: string[];
30
+ addedAt: string;
31
+ };
32
+
33
+ async function pathExists(filePath: string) {
34
+ try {
35
+ await fs.access(filePath);
36
+ return true;
37
+ } catch (e) {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ function machineFilePath(repoPath: string, machineId: string) {
43
+ return path.join(repoPath, MACHINES_DIR, `${machineId}.json`);
44
+ }
45
+
46
+ /** Every machine a checkout lists. */
47
+ export async function getMachines(repoPath: string): Promise<MachineState[]> {
48
+ return [...(await readMachines(repoPath)).values()];
49
+ }
50
+
51
+ /** Makes a checkout list exactly these machines: those named are written with the addresses given,
52
+ and anything not named is removed. So read them, change what you want, and write the result.
53
+
54
+ The addresses are part of setting rather than a separate step, because a machine with no
55
+ address it may talk from is not a machine that would ever be accepted - there is no state where
56
+ naming one without them means anything.
57
+
58
+ Nothing here signs or commits. The signature is what every other machine checks before
59
+ believing any of this, and it takes the hardware key, so `yarn signfiles git` is still yours to
60
+ run afterwards. */
61
+ export async function setMachines(config: {
62
+ repoPath: string;
63
+ machines: { machineId: string; ips: string[] }[];
64
+ }): Promise<MachineState[]> {
65
+ let { repoPath, machines } = config;
66
+ let existing = await readMachines(repoPath);
67
+ let directory = path.join(repoPath, MACHINES_DIR);
68
+ let written: MachineState[] = [];
69
+ for (let machine of machines) {
70
+ if (!machine.machineId) {
71
+ throw new Error(`Expected a machineId, was ${JSON.stringify(machine.machineId)}`);
72
+ }
73
+ if (!machine.ips.length) {
74
+ throw new Error(
75
+ `Expected addresses for ${machine.machineId}, was none. A machine is trusted from`
76
+ + ` the addresses it may talk from, so leave it out of the list to remove it.`
77
+ );
78
+ }
79
+ let ips = machine.ips.filter((ip, index) => ip && machine.ips.indexOf(ip) === index);
80
+ let state: MachineState = {
81
+ machineId: machine.machineId,
82
+ // In the order given, without duplicates, so the file reads the way it was set.
83
+ ips,
84
+ addedAt: existing.get(machine.machineId)?.addedAt || new Date().toISOString(),
85
+ };
86
+ await fs.mkdir(directory, { recursive: true });
87
+ await fs.writeFile(machineFilePath(repoPath, state.machineId), JSON.stringify(state, undefined, 4) + "\n");
88
+ written.push(state);
89
+ }
90
+
91
+ // Whatever the repo had and the caller did not name is no longer trusted.
92
+ for (let machineId of existing.keys()) {
93
+ if (!machines.some(machine => machine.machineId === machineId)) {
94
+ await fs.rm(machineFilePath(repoPath, machineId), { force: true });
95
+ }
96
+ }
97
+ return written;
98
+ }
99
+
100
+ /** Every machine the repo lists. */
101
+ async function readMachines(repoPath: string) {
102
+ let machines = new Map<string, MachineState>();
103
+ let directory = path.join(repoPath, MACHINES_DIR);
104
+ let names = await fs.readdir(directory).catch(() => [] as string[]);
105
+ for (let name of names.sort()) {
106
+ if (!name.endsWith(".json")) {
107
+ continue;
108
+ }
109
+ try {
110
+ let parsed = JSON.parse(await fs.readFile(path.join(directory, name), "utf8"));
111
+ let machineId = parsed.machineId || name.replace(/\.json$/, "");
112
+ machines.set(machineId, { machineId, ips: parsed.ips || [], addedAt: parsed.addedAt || "" });
113
+ } catch (e) {
114
+ console.log(`Ignoring unreadable machine file ${name}. ${e}`);
115
+ }
116
+ }
117
+ return machines;
118
+ }
119
+
120
+ let lastRevokeSync = 0;
121
+
122
+ /** Pulled at most once a REVOKE_SYNC_INTERVAL, because this is asked per request. A sync that
123
+ fails leaves the checkout we already have, which is the safe direction: revocations we know
124
+ about stay known. */
125
+ async function syncRevocations(sourceURL: string) {
126
+ if (Date.now() - lastRevokeSync < REVOKE_SYNC_INTERVAL) {
127
+ return;
128
+ }
129
+ lastRevokeSync = Date.now();
130
+ try {
131
+ await syncRepoFiles(revokeRepo(sourceURL));
132
+ } catch (e) {
133
+ console.log(`Could not read ${revokeRepoURL(sourceURL)}, using the revocations already here. ${e}`);
134
+ }
135
+ }
136
+
137
+ /** Machine revocations, read out of the revoke repo the same way key revocations are. */
138
+ async function readMachineRevocations(sourceURL: string) {
139
+ let repo = revokeRepo(sourceURL);
140
+ let revocations: { revocationId: string; machineId: string; ip: string }[] = [];
141
+ for (let name of await listRepoDir(repo, REVOCATIONS_DIR)) {
142
+ if (!name.endsWith(".json")) {
143
+ continue;
144
+ }
145
+ try {
146
+ let parsed = JSON.parse(await readRepoFile(repo, path.join(REVOCATIONS_DIR, name)) || "");
147
+ if (parsed.machineId) {
148
+ revocations.push({
149
+ revocationId: parsed.revocationId || name.replace(/\.json$/, ""),
150
+ machineId: parsed.machineId,
151
+ ip: parsed.ip || "",
152
+ });
153
+ }
154
+ } catch (e) {
155
+ console.log(`Ignoring unreadable revocation ${name}. ${e}`);
156
+ }
157
+ }
158
+ return revocations;
159
+ }
160
+
161
+ /** Sends the one notification this file is allowed to send, when it can.
162
+
163
+ The daemon configures notifications on startup and would simply send. This can also run in some
164
+ other process, which has not, so the webhook is picked up here if it is readable. A machine
165
+ with no webhook set up still records the revocation - being unable to tell anyone is not a
166
+ reason to keep accepting a machine that is being misused. */
167
+ async function notifyBestEffort(headline: string, body: string) {
168
+ try {
169
+ if (!areDiscordNotificationsConfigured()) {
170
+ // Read first: configureDiscordNotifications exits the process when the file is missing,
171
+ // which is right for the daemon at startup and wrong for a library call.
172
+ await fs.readFile(DEFAULT_WEBHOOK_FILE_PATH, "utf8");
173
+ await configureDiscordNotifications({ filePath: DEFAULT_WEBHOOK_FILE_PATH });
174
+ }
175
+ await notify(headline, body);
176
+ } catch (e) {
177
+ console.log(`Could not send a notification about this, it is only in the log. ${e}`);
178
+ }
179
+ }
180
+
181
+ /** Records that a machine we accept talked to us from an address it is not allowed from. One per
182
+ machine and address, so a second address is a second revocation and an unrevoke of the first
183
+ does not cover it. */
184
+ async function recordMachineRevocation(config: {
185
+ sourceURL: string;
186
+ machineId: string;
187
+ ip: string;
188
+ hostLabel: string;
189
+ }) {
190
+ let { sourceURL, machineId, ip, hostLabel } = config;
191
+ let repoPath = revokeRepoPath(sourceURL);
192
+ let keyPath = await ensureRevokeKey(sourceURL);
193
+ let revocationId = newRevocationId(machineId);
194
+ let directory = path.join(repoPath, REVOCATIONS_DIR);
195
+ await fs.mkdir(directory, { recursive: true });
196
+ await fs.writeFile(path.join(directory, `${revocationId}.json`), JSON.stringify({
197
+ revocationId,
198
+ machineId,
199
+ ip,
200
+ revokedAt: new Date().toISOString(),
201
+ revokedBy: hostLabel,
202
+ reason: REVOCATION_REASON,
203
+ }, undefined, 4) + "\n");
204
+
205
+ await runGit({ args: ["add", "-A"], cwd: repoPath, keyPath });
206
+ await runGit({
207
+ args: ["-c", "user.email=portsecure@localhost", "-c", "user.name=portsecure", "commit", "-m", `revoke ${revocationId}`],
208
+ cwd: repoPath, keyPath,
209
+ });
210
+ let push = await runGit({ args: ["push", "origin", "HEAD"], cwd: repoPath, keyPath, allowFailure: true });
211
+ if (push.status !== 0) {
212
+ // Another machine most likely recorded the same thing first, and the next read picks it up.
213
+ console.log(`Could not push the revocation of ${machineId} from ${ip}. ${(push.stdout + push.stderr).trim()}`);
214
+ return;
215
+ }
216
+ console.log(`Revoked ${machineId} from ${ip}, ${revocationId}`);
217
+
218
+ // Said by whoever wrote the revocation, once, the same as for an ssh key. Machines that only
219
+ // read it later say nothing, or one event would be reported by every machine that saw it.
220
+ await notifyBestEffort(
221
+ `SUSPICIOUS IP ${ip} FROZE MACHINE ${machineId}`,
222
+ `A machine we trust talked to us from ${ip}, which is not an address it is allowed to talk`
223
+ + ` from. It proved it holds that machine's key, so either someone else has a copy of it,`
224
+ + ` or that machine's address changed.`
225
+ + `\n\nIt is frozen everywhere now, and nothing accepts it.`
226
+ + `\n\nIf this was an attack, remove \`machines/${machineId}.json\` from \`${sourceURL}\` now.`
227
+ + `\nIf it was legitimate, run \`yarn unrevoke git\` in that repo. It allows ${ip} for that`
228
+ + ` machine, and takes effect as each machine picks it up.`
229
+ + `\n\nmachine: \`${machineId}\``
230
+ + `\nfrozen by: \`${hostLabel}\``
231
+ );
232
+ }
233
+
234
+ /** Whether we will talk to this machine, coming from this address.
235
+
236
+ Both halves are required and neither is a guess: the caller knows the machine id because the
237
+ connection proved it, and knows the address because the packets came from there. A machine we
238
+ do not list is simply not accepted. A machine we do list, arriving from an address it does not
239
+ have, is treated as the same kind of event as a stolen ssh key - it is revoked everywhere, and
240
+ stays revoked until an unrevoke allows that machine from that address.
241
+
242
+ Says why when the answer is no, because the three ways to be refused are entirely different
243
+ things to a person reading it: never trusted at all, trusted but frozen, or trusted and talking
244
+ from somewhere it should not be.
245
+
246
+ Throws when the repo itself cannot be read or is not signed, rather than answering false: that
247
+ is a broken installation, not a rejected machine, and the two deserve different handling. */
248
+ export type MachineVerdict = { accepted: boolean; reason: string };
249
+
250
+ export async function isMachineAccepted(config: { machineId: string; ip: string }): Promise<MachineVerdict> {
251
+ let { machineId, ip } = config;
252
+ if (!machineId || !ip) {
253
+ throw new Error(`Expected a machineId and an ip, was ${JSON.stringify(machineId)} and ${JSON.stringify(ip)}`);
254
+ }
255
+ let { repoPath, sourceURL } = await resolveKeysRepo();
256
+ // Unsigned, or signed over different contents, means the machine list is not evidence of
257
+ // anything. Same gate the ssh keys go through.
258
+ await verifyCheckout(repoPath);
259
+
260
+ let machine = (await readMachines(repoPath)).get(machineId);
261
+ if (!machine) {
262
+ return { accepted: false, reason: "that machine is not trusted" };
263
+ }
264
+
265
+ await syncRevocations(sourceURL);
266
+ let unrevokes = await readUnrevokes(sourceURL).catch(() => ({ pairs: new Map(), legacyIds: new Map() }));
267
+ // An unrevoke counts as soon as it is seen. It is signed, so writing one already takes the
268
+ // hardware key, and anyone holding that can sign anything at all - a wait before honouring it
269
+ // guards against an attacker who has no need of it.
270
+ let allowedAgain = (pair: string) => !!unrevokes.pairs.get(pair)?.length;
271
+ let revocations = await readMachineRevocations(sourceURL);
272
+ // Any revocation nothing has undone keeps the machine out, from everywhere, the way a revoked
273
+ // ssh key is out everywhere rather than only from the address it was misused from.
274
+ let frozenFrom = "";
275
+ for (let revocation of revocations) {
276
+ if (revocation.machineId !== machineId) {
277
+ continue;
278
+ }
279
+ if (!allowedAgain(pairKey({ fingerprint: revocation.machineId, ip: revocation.ip }))) {
280
+ frozenFrom = revocation.ip;
281
+ }
282
+ }
283
+ if (frozenFrom) {
284
+ return {
285
+ accepted: false,
286
+ reason: `that machine is frozen, it was used from ${frozenFrom} which it is not allowed from`,
287
+ };
288
+ }
289
+
290
+ if (machine.ips.includes(ip)) {
291
+ return { accepted: true, reason: "" };
292
+ }
293
+
294
+ // Listed, but talking to us from somewhere it should not be. Recorded once for this machine
295
+ // and address, so being talked to repeatedly does not write repeatedly.
296
+ let pair = pairKey({ fingerprint: machineId, ip });
297
+ let alreadyRecorded = revocations.some(revocation =>
298
+ pairKey({ fingerprint: revocation.machineId, ip: revocation.ip }) === pair);
299
+ if (!alreadyRecorded && !allowedAgain(pair)) {
300
+ await recordMachineRevocation({ sourceURL, machineId, ip, hostLabel: os.hostname() });
301
+ return { accepted: false, reason: `that machine is not allowed from ${ip}, so it is now frozen everywhere` };
302
+ }
303
+ return { accepted: false, reason: `that machine is not allowed from ${ip}` };
304
+ }
@@ -0,0 +1,51 @@
1
+ import { signTrustPacket } from "./identity";
2
+
3
+ // The other half of the demonstration: asks a server whether it trusts this machine. The answer
4
+ // depends on the address this machine reaches the server from, which is why the client cannot
5
+ // work it out alone.
6
+ const TRUST_PATH = "/trusted";
7
+ const USAGE = `Usage: yarn machineclient <url> <domain>
8
+
9
+ Asks that server whether this machine is trusted, proving which machine it is with a signed packet.
10
+ The server decides using the address we reach it from, so the answer is about how we are talking to
11
+ it, not only about who we are.
12
+
13
+ The domain is required: this machine has a different identity under each one, and the answer is
14
+ about the identity we sign with.`;
15
+
16
+ export type TrustAnswer = {
17
+ trusted: boolean;
18
+ machineId?: string;
19
+ ip?: string;
20
+ reason?: string;
21
+ };
22
+
23
+ /** Asks one server whether it trusts this machine, as it is under this domain. */
24
+ export async function askIfTrusted(config: { baseURL: string; domain: string }): Promise<TrustAnswer> {
25
+ let { baseURL, domain } = config;
26
+ let packet = await signTrustPacket(domain);
27
+ let url = `${baseURL.replace(/\/+$/, "")}${TRUST_PATH}`;
28
+ let response = await fetch(url, {
29
+ method: "POST",
30
+ headers: { "Content-Type": "application/json" },
31
+ body: JSON.stringify(packet),
32
+ });
33
+ return await response.json() as TrustAnswer;
34
+ }
35
+
36
+ export async function main() {
37
+ let [baseURL, domain] = process.argv.slice(2);
38
+ if (!baseURL || !domain) {
39
+ throw new Error(USAGE);
40
+ }
41
+ let answer = await askIfTrusted({ baseURL, domain });
42
+ console.log(`${answer.trusted && "TRUSTED" || "NOT TRUSTED"} by ${baseURL}`);
43
+ console.log(` machine ${answer.machineId || "(not established)"}`);
44
+ console.log(` seen coming from ${answer.ip || "(unknown)"}`);
45
+ if (answer.reason) {
46
+ console.log(` ${answer.reason}`);
47
+ }
48
+ if (!answer.trusted) {
49
+ process.exitCode = 1;
50
+ }
51
+ }
@@ -0,0 +1,87 @@
1
+ import http from "http";
2
+ import { verifyTrustPacket } from "./identity";
3
+ import { isMachineAccepted } from "./machines";
4
+
5
+ // A demonstration of machine trust, and the smallest thing that exercises it end to end: a client
6
+ // proves which machine it is, and this answers whether that machine is trusted from the address it
7
+ // is actually talking from.
8
+ //
9
+ // Plain http on purpose. Nothing here is secret - the packet proves who the client is by signature
10
+ // rather than by the channel, and the answer is a yes or no about a machine the caller already
11
+ // knows the id of.
12
+ const DEFAULT_PORT = 47812;
13
+ const TRUST_PATH = "/trusted";
14
+ const MAX_BODY_LENGTH = 64 * 1024;
15
+
16
+ /** The address the client is actually talking from. Not read from anything the client sent,
17
+ because that is the one half of the pair it must not get to choose. */
18
+ function addressOf(request: http.IncomingMessage) {
19
+ let address = request.socket.remoteAddress || "";
20
+ // Node reports IPv4 over a dual stack socket this way.
21
+ return address.replace(/^::ffff:/, "");
22
+ }
23
+
24
+ async function readBody(request: http.IncomingMessage) {
25
+ let chunks: Buffer[] = [];
26
+ let length = 0;
27
+ for await (let chunk of request) {
28
+ length += chunk.length;
29
+ if (length > MAX_BODY_LENGTH) {
30
+ throw new Error(`Expected at most ${MAX_BODY_LENGTH} bytes, was more`);
31
+ }
32
+ chunks.push(chunk);
33
+ }
34
+ return Buffer.concat(chunks).toString("utf8");
35
+ }
36
+
37
+ export function startTrustServer(config: { port: number }) {
38
+ let server = http.createServer((request, response) => {
39
+ let answer = async () => {
40
+ if (request.url !== TRUST_PATH) {
41
+ return { status: 404, body: { error: `POST a trust packet to ${TRUST_PATH}` } };
42
+ }
43
+ let packet = JSON.parse(await readBody(request));
44
+ let ip = addressOf(request);
45
+ let verified = verifyTrustPacket(packet);
46
+ if (!verified.valid) {
47
+ // Not a rejection of the machine, a packet that does not prove anything about one.
48
+ return { status: 400, body: { trusted: false, ip, reason: verified.problem } };
49
+ }
50
+ let verdict = await isMachineAccepted({ machineId: packet.machineId, ip });
51
+ return {
52
+ status: 200,
53
+ body: {
54
+ trusted: verdict.accepted,
55
+ machineId: packet.machineId,
56
+ ip,
57
+ // Whatever the check itself said. It is the only thing that knows which of the
58
+ // ways to be refused this was.
59
+ reason: verdict.reason,
60
+ },
61
+ };
62
+ };
63
+ answer().then(result => {
64
+ console.log(`${request.method} ${request.url} from ${addressOf(request)}: ${JSON.stringify(result.body)}`);
65
+ response.writeHead(result.status, { "Content-Type": "application/json" });
66
+ response.end(JSON.stringify(result.body, undefined, 4));
67
+ }).catch(e => {
68
+ console.log(`${request.method} ${request.url} from ${addressOf(request)} failed. ${e}`);
69
+ response.writeHead(500, { "Content-Type": "application/json" });
70
+ response.end(JSON.stringify({ trusted: false, error: `${e}` }, undefined, 4));
71
+ });
72
+ });
73
+ server.listen(config.port);
74
+ return server;
75
+ }
76
+
77
+ export async function main() {
78
+ let port = Number(process.argv[2] || DEFAULT_PORT);
79
+ if (!Number.isFinite(port)) {
80
+ throw new Error(`Usage: yarn machineserver [port]`);
81
+ }
82
+ startTrustServer({ port });
83
+ console.log(`Answering trust checks on http://0.0.0.0:${port}${TRUST_PATH}`);
84
+ console.log(`A client asks with: yarn machineclient http://<this machine>:${port}`);
85
+ // Left running. Nothing else to do here, the server is the program.
86
+ await new Promise(() => { });
87
+ }
@@ -89,8 +89,9 @@ export function messageTimestamp(now: Date) {
89
89
 
90
90
  async function postToWebhook(webhookURL: string, message: string) {
91
91
  // Stamped once rather than per attempt, so a retry says when the thing happened rather than
92
- // when we last managed to get it out.
93
- let content = `\`${messageTimestamp(new Date())}\` ${message}`;
92
+ // when we last managed to get it out. At the end, because the first line of a message is the
93
+ // notification preview and a timestamp there would spend it saying nothing.
94
+ let content = `${message} · \`${messageTimestamp(new Date())}\``;
94
95
  if (content.length > DISCORD_MESSAGE_LIMIT) {
95
96
  content = content.slice(0, DISCORD_MESSAGE_LIMIT - 3) + "...";
96
97
  }
@@ -149,9 +150,11 @@ async function checkWebhookFileChanged() {
149
150
  try {
150
151
  await queueSend(
151
152
  state.webhookURL,
152
- `**portsecure**: the Discord webhook in \`${state.filePath}\` changed to`
153
- + ` \`${redactWebhookURL(newWebhookURL)}\`.`
154
- + ` Notifications are moving to the new webhook and this channel will stop receiving them.`
153
+ `**portsecure**: **NOTIFICATIONS ARE MOVING TO ANOTHER CHANNEL**`
154
+ + `\n\nThis channel stops receiving them now. If that was not you, somebody with access`
155
+ + ` to this machine just redirected its alerts.`
156
+ + `\n\nnew webhook: \`${redactWebhookURL(newWebhookURL)}\``
157
+ + `\nset in: \`${state.filePath}\``
155
158
  );
156
159
  } catch (e) {
157
160
  console.error(`portsecure: failed to warn the old Discord webhook about the change. ${e}`);
@@ -198,6 +201,12 @@ export async function sendDiscordNotification(message: string) {
198
201
  await queueSend(state.webhookURL, message);
199
202
  }
200
203
 
204
+ /** Whether notifications are set up in this process. For code that may run outside the daemon,
205
+ where sending is worth doing if it can be and not worth failing over if it cannot. */
206
+ export function areDiscordNotificationsConfigured() {
207
+ return !!notificationState;
208
+ }
209
+
201
210
  export function stopDiscordNotifications() {
202
211
  if (!notificationState) {
203
212
  return;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
6
- //exports.stopDiscordNotifications = exports.sendDiscordNotification = exports.configureDiscordNotifications = exports.sendToWebhookURL = exports.messageTimestamp = exports.formatWebhookFile = exports.redactWebhookURL = exports.parseWebhookFile = exports.DEFAULT_WEBHOOK_FILE_PATH = void 0;
6
+ //exports.stopDiscordNotifications = exports.areDiscordNotificationsConfigured = exports.sendDiscordNotification = exports.configureDiscordNotifications = exports.sendToWebhookURL = exports.messageTimestamp = exports.formatWebhookFile = exports.redactWebhookURL = exports.parseWebhookFile = exports.DEFAULT_WEBHOOK_FILE_PATH = void 0;
7
7
  const promises_1 = __importDefault(require("fs/promises"));
8
8
  // PORTED CODE: security/authorizedKeys/daemon/portsecureDaemon.js embeds a hand port of this file in plain JS, so the
9
9
  // daemon can run with no dependencies. The two are expected to behave identically - if you change
@@ -84,8 +84,9 @@ function messageTimestamp(now) {
84
84
  exports.messageTimestamp = messageTimestamp;
85
85
  async function postToWebhook(webhookURL, message) {
86
86
  // Stamped once rather than per attempt, so a retry says when the thing happened rather than
87
- // when we last managed to get it out.
88
- let content = `\`${messageTimestamp(new Date())}\` ${message}`;
87
+ // when we last managed to get it out. At the end, because the first line of a message is the
88
+ // notification preview and a timestamp there would spend it saying nothing.
89
+ let content = `${message} · \`${messageTimestamp(new Date())}\``;
89
90
  if (content.length > DISCORD_MESSAGE_LIMIT) {
90
91
  content = content.slice(0, DISCORD_MESSAGE_LIMIT - 3) + "...";
91
92
  }
@@ -139,9 +140,11 @@ async function checkWebhookFileChanged() {
139
140
  // Warn the old channel first, with the new webhook redacted, so a stolen old webhook does not
140
141
  // hand the attacker a usable new one.
141
142
  try {
142
- await queueSend(state.webhookURL, `**portsecure**: the Discord webhook in \`${state.filePath}\` changed to`
143
- + ` \`${redactWebhookURL(newWebhookURL)}\`.`
144
- + ` Notifications are moving to the new webhook and this channel will stop receiving them.`);
143
+ await queueSend(state.webhookURL, `**portsecure**: **NOTIFICATIONS ARE MOVING TO ANOTHER CHANNEL**`
144
+ + `\n\nThis channel stops receiving them now. If that was not you, somebody with access`
145
+ + ` to this machine just redirected its alerts.`
146
+ + `\n\nnew webhook: \`${redactWebhookURL(newWebhookURL)}\``
147
+ + `\nset in: \`${state.filePath}\``);
145
148
  }
146
149
  catch (e) {
147
150
  console.error(`portsecure: failed to warn the old Discord webhook about the change. ${e}`);
@@ -185,6 +188,12 @@ async function sendDiscordNotification(message) {
185
188
  await queueSend(state.webhookURL, message);
186
189
  }
187
190
  exports.sendDiscordNotification = sendDiscordNotification;
191
+ /** Whether notifications are set up in this process. For code that may run outside the daemon,
192
+ where sending is worth doing if it can be and not worth failing over if it cannot. */
193
+ function areDiscordNotificationsConfigured() {
194
+ return !!notificationState;
195
+ }
196
+ exports.areDiscordNotificationsConfigured = areDiscordNotificationsConfigured;
188
197
  function stopDiscordNotifications() {
189
198
  if (!notificationState) {
190
199
  return;
@@ -193,5 +202,5 @@ function stopDiscordNotifications() {
193
202
  notificationState = undefined;
194
203
  }
195
204
  exports.stopDiscordNotifications = stopDiscordNotifications;
196
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlzY29yZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImRpc2NvcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMkRBQTZCO0FBRTdCLHNIQUFzSDtBQUN0SCxrR0FBa0c7QUFDbEcsOENBQThDO0FBRWpDLFFBQUEseUJBQXlCLEdBQUcsaUNBQWlDLENBQUM7QUFDM0UsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUM3QyxNQUFNLHNCQUFzQixHQUFHO0lBQzNCLG1DQUFtQztJQUNuQyxzQ0FBc0M7SUFDdEMsMENBQTBDO0NBQzdDLENBQUM7QUFDRixNQUFNLHFCQUFxQixHQUFHLElBQUksQ0FBQztBQUNuQyxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQztBQUNsQyxNQUFNLGlCQUFpQixHQUFHLENBQUMsQ0FBQztBQUM1QixNQUFNLHNCQUFzQixHQUFHLENBQUMsQ0FBQztBQUNqQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7QUFFekMsSUFBSSxpQkFJUyxDQUFDO0FBRWQsc0ZBQXNGO0FBQ3RGLDRDQUE0QztBQUM1QyxJQUFJLFNBQVMsR0FBcUIsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0FBRXBEO3NDQUNzQztBQUN0QyxTQUFnQixnQkFBZ0IsQ0FBQyxNQUFnRDtJQUM3RSxJQUFJLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUN0QyxJQUFJLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN4RyxJQUFJLFVBQVUsR0FBRyxL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RUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsa0RBQWtELENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUMvRyxDQUFDLEVBQUUsYUFBYSxDQUFDLENBQUM7SUFDbEIsZ0VBQWdFO0lBQ2hFLFVBQVUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUNuQixpQkFBaUIsR0FBRyxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLENBQUM7SUFDekQsT0FBTyxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsQ0FBQztBQUN2QyxDQUFDO0FBdkJELHNFQXVCQztBQUVELGdHQUFnRztBQUNoRyxpR0FBaUc7QUFDakcsdUVBQXVFO0FBQ2hFLEtBQUssVUFBVSx1QkFBdUIsQ0FBQyxPQUFlO0lBQ3pELElBQUksS0FBSyxHQUFHLGlCQUFpQixDQUFDO0lBQzlCLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNULE1BQU0sSUFBSSxLQUFLLENBQUMsK0ZBQStGLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLHFCQUFxQixDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzlKLENBQUM7SUFDRCxNQUFNLFNBQVMsQ0FBQyxLQUFLLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQy9DLENBQUM7QUFORCwwREFNQztBQUVELFNBQWdCLHdCQUF3QjtJQUNwQyxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztRQUNyQixPQUFPO0lBQ1gsQ0FBQztJQUNELGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUM1QyxpQkFBaUIsR0FBRyxTQUFTLENBQUM7QUFDbEMsQ0FBQztBQU5ELDREQU1DIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGZzIGZyb20gXCJmcy9wcm9taXNlc1wiO1xuXG4vLyBQT1JURUQgQ09ERTogc2VjdXJpdHkvYXV0aG9yaXplZEtleXMvZGFlbW9uL3BvcnRzZWN1cmVEYWVtb24uanMgZW1iZWRzIGEgaGFuZCBwb3J0IG9mIHRoaXMgZmlsZSBpbiBwbGFpbiBKUywgc28gdGhlXG4vLyBkYWVtb24gY2FuIHJ1biB3aXRoIG5vIGRlcGVuZGVuY2llcy4gVGhlIHR3byBhcmUgZXhwZWN0ZWQgdG8gYmVoYXZlIGlkZW50aWNhbGx5IC0gaWYgeW91IGNoYW5nZVxuLy8gb25lLCBtYWtlIHRoZSBtYXRjaGluZyBjaGFuZ2UgaW4gdGhlIG90aGVyLlxuXG5leHBvcnQgY29uc3QgREVGQVVMVF9XRUJIT09LX0ZJTEVfUEFUSCA9IFwiL2V0Yy9wb3J0c2VjdXJlL2Rpc2NvcmQtd2ViaG9va1wiO1xuY29uc3QgREVGQVVMVF9DSEVDS19JTlRFUlZBTCA9IDUgKiA2MCAqIDEwMDA7XG5jb25zdCBWQUxJRF9XRUJIT09LX1BSRUZJWEVTID0gW1xuICAgIFwiaHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvXCIsXG4gICAgXCJodHRwczovL2Rpc2NvcmRhcHAuY29tL2FwaS93ZWJob29rcy9cIixcbiAgICBcImh0dHBzOi8vY2FuYXJ5LmRpc2NvcmQuY29tL2FwaS93ZWJob29rcy9cIixcbl07XG5jb25zdCBESVNDT1JEX01FU1NBR0VfTElNSVQgPSAyMDAwO1xuY29uc3QgTUFYX0VSUk9SX0JPRFlfTEVOR1RIID0gNTAwO1xuY29uc3QgTUFYX1NFTkRfQVRURU1QVFMgPSAzO1xuY29uc3QgUkVEQUNURURfVE9LRU5fVklTSUJMRSA9IDg7XG5jb25zdCBERUZBVUxUX1JBVEVfTElNSVRfV0FJVCA9IDIgKiAxMDAwO1xuXG5sZXQgbm90aWZpY2F0aW9uU3RhdGU6IHtcbiAgICBmaWxlUGF0aDogc3RyaW5nO1xuICAgIHdlYmhvb2tVUkw6IHN0cmluZztcbiAgICBjaGVja1RpbWVyOiBOb2RlSlMuVGltZW91dDtcbn0gfCB1bmRlZmluZWQ7XG5cbi8vIERpc2NvcmQgd2ViaG9va3MgYXJlIHJhdGUgbGltaXRlZCAocm91Z2hseSA1IHJlcXVlc3RzIHBlciAyIHNlY29uZHMpLCBzbyBldmVyeSBzZW5kXG4vLyBnb2VzIHRocm91Z2ggb25lIGNoYWluIGluc3RlYWQgb2YgcmFjaW5nLlxubGV0IHNlbmRDaGFpbjogUHJvbWlzZTx1bmtub3duPiA9IFByb21pc2UucmVzb2x2ZSgpO1xuXG4vKiogUHVsbHMgdGhlIHdlYmhvb2sgVVJMIG91dCBvZiB3ZWJob29rIGZpbGUgY29udGVudHMuIGBzb3VyY2VOYW1lYCBvbmx5IGFwcGVhcnMgaW4gZXJyb3JzLCBzb1xuICAgIGNhbGxlcnMgY2FuIG5hbWUgYSByZW1vdGUgcGF0aC4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVdlYmhvb2tGaWxlKGNvbmZpZzogeyBjb250ZW50czogc3RyaW5nOyBzb3VyY2VOYW1lOiBzdHJpbmcgfSkge1xuICAgIGxldCB7IGNvbnRlbnRzLCBzb3VyY2VOYW1lIH0gPSBjb25maWc7XG4gICAgbGV0IGxpbmVzID0gY29udGVudHMuc3BsaXQoXCJcXG5cIikubWFwKGxpbmUgPT4gbGluZS50cmltKCkpLmZpbHRlcihsaW5lID0+IGxpbmUgJiYgIWxpbmUuc3RhcnRzV2l0aChcIiNcIikpO1xuICAgIGxldCB3ZWJob29rVVJMID0gbGluZXNbMF07XG4gICAgaWYgKCF3ZWJob29rVVJMKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSBEaXNjb3JkIHdlYmhvb2sgVVJMIGluICR7c291cmNlTmFtZX0sIHRoZSBmaWxlIGhhcyBubyB1c2FibGUgbGluZXNgKTtcbiAgICB9XG4gICAgaWYgKCFWQUxJRF9XRUJIT09LX1BSRUZJWEVTLnNvbWUocHJlZml4ID0+IHdlYmhvb2tVUkwuc3RhcnRzV2l0aChwcmVmaXgpKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgYSBEaXNjb3JkIHdlYmhvb2sgVVJMIHN0YXJ0aW5nIHdpdGggb25lIG9mICR7VkFMSURfV0VCSE9PS19QUkVGSVhFUy5qb2luKFwiLCBcIil9LCBgXG4gICAgICAgICAgICArIGB3YXMgJHt3ZWJob29rVVJMLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9IChpbiAke3NvdXJjZU5hbWV9KWBcbiAgICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHdlYmhvb2tVUkw7XG59XG5cbi8qKiBLZWVwcyB0aGUgaWQgYW5kIGJvdGggZW5kcyBvZiB0aGUgdG9rZW4sIHNvIGEgcmVhZGVyIGNhbiBjb25maXJtIHdoaWNoIHdlYmhvb2sgcmVwbGFjZWQgdGhlaXJzXG4gICAgd2l0aG91dCByZWNlaXZpbmcgb25lIHRoZXkgY291bGQgcG9zdCB0by4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWRhY3RXZWJob29rVVJMKHdlYmhvb2tVUkw6IHN0cmluZykge1xuICAgIGxldCBzZXBhcmF0b3JJbmRleCA9IHdlYmhvb2tVUkwubGFzdEluZGV4T2YoXCIvXCIpO1xuICAgIGxldCBiYXNlID0gd2ViaG9va1VSTC5zbGljZSgwLCBzZXBhcmF0b3JJbmRleCArIDEpO1xuICAgIGxldCB0b2tlbiA9IHdlYmhvb2tVUkwuc2xpY2Uoc2VwYXJhdG9ySW5kZXggKyAxKTtcbiAgICBpZiAodG9rZW4ubGVuZ3RoIDw9IFJFREFDVEVEX1RPS0VOX1ZJU0lCTEUgKiAyKSB7XG4gICAgICAgIC8vIFRvbyBzaG9ydCB0byBzaG93IGJvdGggZW5kcyB3aXRob3V0IGdpdmluZyBhd2F5IHRoZSB3aG9sZSB0b2tlbi5cbiAgICAgICAgcmV0dXJuIGAke2Jhc2V9JHt0b2tlbi5zbGljZSgwLCBSRURBQ1RFRF9UT0tFTl9WSVNJQkxFKX0uLi5gO1xuICAgIH1cbiAgICByZXR1cm4gYCR7YmFzZX0ke3Rva2VuLnNsaWNlKDAsIFJFREFDVEVEX1RPS0VOX1ZJU0lCTEUpfS4uLiR7dG9rZW4uc2xpY2UoLVJFREFDVEVEX1RPS0VOX1ZJU0lCTEUpfWA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRXZWJob29rRmlsZSh3ZWJob29rVVJMOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gYCMgcG9ydHNlY3VyZSBEaXNjb3JkIHdlYmhvb2suIEZpcnN0IG5vbi1jb21tZW50IGxpbmUgaXMgdXNlZC5cXG4ke3dlYmhvb2tVUkx9XFxuYDtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcmVhZFdlYmhvb2tGaWxlKGZpbGVQYXRoOiBzdHJpbmcpIHtcbiAgICBsZXQgY29udGVudHM7XG4gICAgdHJ5IHtcbiAgICAgICAgY29udGVudHMgPSBhd2FpdCBmcy5yZWFkRmlsZShmaWxlUGF0aCwgXCJ1dGY4XCIpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBhIHJlYWRhYmxlIERpc2NvcmQgd2ViaG9vayBmaWxlIGF0ICR7ZmlsZVBhdGh9LCAke2V9YCk7XG4gICAgfVxuICAgIHJldHVybiBwYXJzZVdlYmhvb2tGaWxlKHsgY29udGVudHMsIHNvdXJjZU5hbWU6IGZpbGVQYXRoIH0pO1xufVxuXG4vKiogV2hlbiB0aGUgbWVzc2FnZSB3YXMgc2VudCwgaW4gdGhlIHNlbmRpbmcgbWFjaGluZSdzIG93biB0aW1lIHpvbmUuIERpc2NvcmQgc2hvd3Mgd2hlbiBpdFxuICAgIHJlY2VpdmVkIHNvbWV0aGluZywgd2hpY2ggaXMgbm90IHRoZSBzYW1lIHRoaW5nIHdoZW4gYSBtYWNoaW5lIGhhcyBiZWVuIG9mZmxpbmUgb3IgYSBzZW5kIGhhc1xuICAgIGJlZW4gcmV0cmllZCwgYW5kIHRoZSB6b25lIG1hdHRlcnMgd2hlbiB0aGUgbWFjaGluZXMgYXJlIG5vdCBhbGwgaW4gb25lIHBsYWNlLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1lc3NhZ2VUaW1lc3RhbXAobm93OiBEYXRlKSB7XG4gICAgbGV0IGRhdGUgPSBub3cudG9Mb2NhbGVEYXRlU3RyaW5nKFwiZW4tQ0FcIik7XG4gICAgbGV0IHRpbWUgPSBub3cudG9Mb2NhbGVUaW1lU3RyaW5nKFwiZW4tVVNcIiwge1xuICAgICAgICBob3VyOiBcIjItZGlnaXRcIixcbiAgICAgICAgbWludXRlOiBcIjItZGlnaXRcIixcbiAgICAgICAgc2Vjb25kOiBcIjItZGlnaXRcIixcbiAgICAgICAgaG91cjEyOiB0cnVlLFxuICAgICAgICB0aW1lWm9uZU5hbWU6IFwic2hvcnRcIixcbiAgICB9KTtcbiAgICByZXR1cm4gYCR7ZGF0ZX0gJHt0aW1lfWA7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHBvc3RUb1dlYmhvb2sod2ViaG9va1VSTDogc3RyaW5nLCBtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICAvLyBTdGFtcGVkIG9uY2UgcmF0aGVyIHRoYW4gcGVyIGF0dGVtcHQsIHNvIGEgcmV0cnkgc2F5cyB3aGVuIHRoZSB0aGluZyBoYXBwZW5lZCByYXRoZXIgdGhhblxuICAgIC8vIHdoZW4gd2UgbGFzdCBtYW5hZ2VkIHRvIGdldCBpdCBvdXQuXG4gICAgbGV0IGNvbnRlbnQgPSBgXFxgJHttZXNzYWdlVGltZXN0YW1wKG5ldyBEYXRlKCkpfVxcYCAke21lc3NhZ2V9YDtcbiAgICBpZiAoY29udGVudC5sZW5ndGggPiBESVNDT1JEX01FU1NBR0VfTElNSVQpIHtcbiAgICAgICAgY29udGVudCA9IGNvbnRlbnQuc2xpY2UoMCwgRElTQ09SRF9NRVNTQUdFX0xJTUlUIC0gMykgKyBcIi4uLlwiO1xuICAgIH1cbiAgICBmb3IgKGxldCBhdHRlbXB0ID0gMTsgYXR0ZW1wdCA8PSBNQVhfU0VORF9BVFRFTVBUUzsgYXR0ZW1wdCsrKSB7XG4gICAgICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHdlYmhvb2tVUkwsIHtcbiAgICAgICAgICAgIG1ldGhvZDogXCJQT1NUXCIsXG4gICAgICAgICAgICBoZWFkZXJzOiB7IFwiQ29udGVudC1UeXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0sXG4gICAgICAgICAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7IGNvbnRlbnQgfSksXG4gICAgICAgIH0pO1xuICAgICAgICBpZiAocmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBsZXQgYm9keSA9IChhd2FpdCByZXNwb25zZS50ZXh0KCkpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCk7XG4gICAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQyOSAmJiBhdHRlbXB0IDwgTUFYX1NFTkRfQVRURU1QVFMpIHtcbiAgICAgICAgICAgIGxldCByZXRyeUFmdGVyID0gTnVtYmVyKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwicmV0cnktYWZ0ZXJcIikpO1xuICAgICAgICAgICAgbGV0IHdhaXRUaW1lID0gcmV0cnlBZnRlciAmJiByZXRyeUFmdGVyICogMTAwMCB8fCBERUZBVUxUX1JBVEVfTElNSVRfV0FJVDtcbiAgICAgICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKHJlc29sdmUgPT4gc2V0VGltZW91dChyZXNvbHZlLCB3YWl0VGltZSkpO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkIGEgMnh4IHJlc3BvbnNlIGZyb20gdGhlIERpc2NvcmQgd2ViaG9vaywgd2FzICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9LCBib2R5ICR7Ym9keX1gXG4gICAgICAgICk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBxdWV1ZVNlbmQod2ViaG9va1VSTDogc3RyaW5nLCBtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICBsZXQgc2VuZCA9ICgpID0+IHBvc3RUb1dlYmhvb2sod2ViaG9va1VSTCwgbWVzc2FnZSk7XG4gICAgbGV0IHJlc3VsdCA9IHNlbmRDaGFpbi50aGVuKHNlbmQsIHNlbmQpO1xuICAgIHNlbmRDaGFpbiA9IHJlc3VsdC5jYXRjaCgoKSA9PiB1bmRlZmluZWQpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKiBTZW5kcyB0byBhbiBleHBsaWNpdCB3ZWJob29rIFVSTCwgZm9yIHRvb2xpbmcgdGhhdCBhY3RzIG9uIGEgd2ViaG9vayBiZWZvcmUgKG9yIGluc3RlYWQgb2YpXG4gICAgY29uZmlndXJlRGlzY29yZE5vdGlmaWNhdGlvbnMgLSBzZXR1cCwgbWlncmF0aW9ucywgY29ubmVjdGl2aXR5IGNoZWNrcy4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZW5kVG9XZWJob29rVVJMKGNvbmZpZzogeyB3ZWJob29rVVJMOiBzdHJpbmc7IG1lc3NhZ2U6IHN0cmluZyB9KSB7XG4gICAgcmV0dXJuIHF1ZXVlU2VuZChjb25maWcud2ViaG9va1VSTCwgY29uZmlnLm1lc3NhZ2UpO1xufVxuXG5hc3luYyBmdW5jdGlvbiBjaGVja1dlYmhvb2tGaWxlQ2hhbmdlZCgpIHtcbiAgICBsZXQgc3RhdGUgPSBub3RpZmljYXRpb25TdGF0ZTtcbiAgICBpZiAoIXN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IG5ld1dlYmhvb2tVUkw7XG4gICAgdHJ5IHtcbiAgICAgICAgbmV3V2ViaG9va1VSTCA9IGF3YWl0IHJlYWRXZWJob29rRmlsZShzdGF0ZS5maWxlUGF0aCk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiBEaXNjb3JkIHdlYmhvb2sgZmlsZSBpcyBubyBsb25nZXIgcmVhZGFibGUsIHN0aWxsIHVzaW5nIHRoZSBsb2FkZWQgd2ViaG9vay4gJHtlfWApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChuZXdXZWJob29rVVJMID09PSBzdGF0ZS53ZWJob29rVVJMKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgLy8gV2FybiB0aGUgb2xkIGNoYW5uZWwgZmlyc3QsIHdpdGggdGhlIG5ldyB3ZWJob29rIHJlZGFjdGVkLCBzbyBhIHN0b2xlbiBvbGQgd2ViaG9vayBkb2VzIG5vdFxuICAgIC8vIGhhbmQgdGhlIGF0dGFja2VyIGEgdXNhYmxlIG5ldyBvbmUuXG4gICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgcXVldWVTZW5kKFxuICAgICAgICAgICAgc3RhdGUud2ViaG9va1VSTCxcbiAgICAgICAgICAgIGAqKnBvcnRzZWN1cmUqKjogdGhlIERpc2NvcmQgd2ViaG9vayBpbiBcXGAke3N0YXRlLmZpbGVQYXRofVxcYCBjaGFuZ2VkIHRvYFxuICAgICAgICAgICAgKyBgIFxcYCR7cmVkYWN0V2ViaG9va1VSTChuZXdXZWJob29rVVJMKX1cXGAuYFxuICAgICAgICAgICAgKyBgIE5vdGlmaWNhdGlvbnMgYXJlIG1vdmluZyB0byB0aGUgbmV3IHdlYmhvb2sgYW5kIHRoaXMgY2hhbm5lbCB3aWxsIHN0b3AgcmVjZWl2aW5nIHRoZW0uYFxuICAgICAgICApO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihgcG9ydHNlY3VyZTogZmFpbGVkIHRvIHdhcm4gdGhlIG9sZCBEaXNjb3JkIHdlYmhvb2sgYWJvdXQgdGhlIGNoYW5nZS4gJHtlfWApO1xuICAgIH1cbiAgICBzdGF0ZS53ZWJob29rVVJMID0gbmV3V2ViaG9va1VSTDtcbn1cblxuLyoqIE11c3QgYmUgY2FsbGVkIG9uY2Ugb24gc3RhcnR1cCwgYmVmb3JlIGFueSBub3RpZmljYXRpb24gaXMgc2VudC4gQWJvcnRzIHRoZSBwcm9jZXNzIGlmIHRoZVxuICAgIHdlYmhvb2sgZmlsZSBpcyBtaXNzaW5nIG9yIGludmFsaWQsIHRoZW4gcmUtY2hlY2tzIHRoZSBmaWxlIG9uIGFuIGludGVydmFsIGFuZCB3YXJucyB0aGUgb2xkXG4gICAgd2ViaG9vayB3aGVuZXZlciBpdCBjaGFuZ2VzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zKGNvbmZpZz86IHtcbiAgICBmaWxlUGF0aD86IHN0cmluZztcbiAgICBjaGVja0ludGVydmFsPzogbnVtYmVyO1xufSkge1xuICAgIGlmIChub3RpZmljYXRpb25TdGF0ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIHRvIGJlIGNhbGxlZCBvbmNlLCB3YXMgY2FsbGVkIGFnYWluIChhbHJlYWR5IHVzaW5nICR7bm90aWZpY2F0aW9uU3RhdGUuZmlsZVBhdGh9KWApO1xuICAgIH1cbiAgICBsZXQgZmlsZVBhdGggPSBjb25maWc/LmZpbGVQYXRoIHx8IERFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEg7XG4gICAgbGV0IGNoZWNrSW50ZXJ2YWwgPSBjb25maWc/LmNoZWNrSW50ZXJ2YWwgfHwgREVGQVVMVF9DSEVDS19JTlRFUlZBTDtcbiAgICBsZXQgd2ViaG9va1VSTDtcbiAgICB0cnkge1xuICAgICAgICB3ZWJob29rVVJMID0gYXdhaXQgcmVhZFdlYmhvb2tGaWxlKGZpbGVQYXRoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYHBvcnRzZWN1cmU6IHJlZnVzaW5nIHRvIHN0YXJ0IHdpdGhvdXQgYSB2YWxpZCBEaXNjb3JkIHdlYmhvb2sgZmlsZS5cXG4ke2V9YCk7XG4gICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICB9XG4gICAgbGV0IGNoZWNrVGltZXIgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgICAgIGNoZWNrV2ViaG9va0ZpbGVDaGFuZ2VkKCkuY2F0Y2goZSA9PiBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiBEaXNjb3JkIHdlYmhvb2sgZmlsZSBjaGVjayBmYWlsZWQuICR7ZX1gKSk7XG4gICAgfSwgY2hlY2tJbnRlcnZhbCk7XG4gICAgLy8gVGhlIGNoZWNrIHNob3VsZCBuZXZlciBiZSB0aGUgcmVhc29uIHRoZSBwcm9jZXNzIHN0YXlzIGFsaXZlLlxuICAgIGNoZWNrVGltZXIudW5yZWYoKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHsgZmlsZVBhdGgsIHdlYmhvb2tVUkwsIGNoZWNrVGltZXIgfTtcbiAgICByZXR1cm4geyBmaWxlUGF0aCwgY2hlY2tJbnRlcnZhbCB9O1xufVxuXG4vLyBETyBOT1QgYWRkIG5ldyBjYWxscyB0byB0aGlzLiBFdmVyeSBtZXNzYWdlIGdvZXMgdG8gYSByZWFsIERpc2NvcmQgc2VydmVyIHNvbWVvbmUgcmVhZHMsIHNvIGFcbi8vIG5vdGlmaWNhdGlvbiBpcyBvbmx5IGV2ZXIgYWRkZWQgd2hlbiB0aGUgdXNlciBleHBsaWNpdGx5IGFza3MgZm9yIHRoYXQgc3BlY2lmaWMgY2FzZS4gU3RhcnR1cCxcbi8vIHN1Y2Nlc3MsIGVycm9ycywgcmV0cmllcyBhbmQgcmVjb3ZlcmllcyBhbGwgYmVsb25nIGluIGEgbG9nIGluc3RlYWQuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VuZERpc2NvcmROb3RpZmljYXRpb24obWVzc2FnZTogc3RyaW5nKSB7XG4gICAgbGV0IHN0YXRlID0gbm90aWZpY2F0aW9uU3RhdGU7XG4gICAgaWYgKCFzdGF0ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIHRvIGJlIGNhbGxlZCBiZWZvcmUgc2VuZGluZywgd2FzIGNhbGxlZCB3aXRoIG1lc3NhZ2UgJHttZXNzYWdlLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YCk7XG4gICAgfVxuICAgIGF3YWl0IHF1ZXVlU2VuZChzdGF0ZS53ZWJob29rVVJMLCBtZXNzYWdlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN0b3BEaXNjb3JkTm90aWZpY2F0aW9ucygpIHtcbiAgICBpZiAoIW5vdGlmaWNhdGlvblN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY2xlYXJJbnRlcnZhbChub3RpZmljYXRpb25TdGF0ZS5jaGVja1RpbWVyKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHVuZGVmaW5lZDtcbn1cbiJdfQ==
197
- /* _JS_SOURCE_HASH = "be450922cec9e8a268e2a78f817bfedb88063a0b59db5b1a954d81c96169543a"; */
205
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlzY29yZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImRpc2NvcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMkRBQTZCO0FBRTdCLHNIQUFzSDtBQUN0SCxrR0FBa0c7QUFDbEcsOENBQThDO0FBRWpDLFFBQUEseUJBQXlCLEdBQUcsaUNBQWlDLENBQUM7QUFDM0UsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUM3QyxNQUFNLHNCQUFzQixHQUFHO0lBQzNCLG1DQUFtQztJQUNuQyxzQ0FBc0M7SUFDdEMsMENBQTBDO0NBQzdDLENBQUM7QUFDRixNQUFNLHFCQUFxQixHQUFHLElBQUksQ0FBQztBQUNuQyxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQztBQUNsQyxNQUFNLGlCQUFpQixHQUFHLENBQUMsQ0FBQztBQUM1QixNQUFNLHNCQUFzQixHQUFHLENBQUMsQ0FBQztBQUNqQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7QUFFekMsSUFBSSxpQkFJUyxDQUFDO0FBRWQsc0ZBQXNGO0FBQ3RGLDRDQUE0QztBQUM1QyxJQUFJLFNBQVMsR0FBcUIsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0FBRXBEO3NDQUNzQztBQUN0QyxTQUFnQixnQkFBZ0IsQ0FBQyxNQUFnRDtJQUM3RSxJQUFJLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUN0QyxJQUFJLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN4RyxJQUFJLFVBQVUsR0FBRyxL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Q29udGVudCI6WyJpbXBvcnQgZnMgZnJvbSBcImZzL3Byb21pc2VzXCI7XG5cbi8vIFBPUlRFRCBDT0RFOiBzZWN1cml0eS9hdXRob3JpemVkS2V5cy9kYWVtb24vcG9ydHNlY3VyZURhZW1vbi5qcyBlbWJlZHMgYSBoYW5kIHBvcnQgb2YgdGhpcyBmaWxlIGluIHBsYWluIEpTLCBzbyB0aGVcbi8vIGRhZW1vbiBjYW4gcnVuIHdpdGggbm8gZGVwZW5kZW5jaWVzLiBUaGUgdHdvIGFyZSBleHBlY3RlZCB0byBiZWhhdmUgaWRlbnRpY2FsbHkgLSBpZiB5b3UgY2hhbmdlXG4vLyBvbmUsIG1ha2UgdGhlIG1hdGNoaW5nIGNoYW5nZSBpbiB0aGUgb3RoZXIuXG5cbmV4cG9ydCBjb25zdCBERUZBVUxUX1dFQkhPT0tfRklMRV9QQVRIID0gXCIvZXRjL3BvcnRzZWN1cmUvZGlzY29yZC13ZWJob29rXCI7XG5jb25zdCBERUZBVUxUX0NIRUNLX0lOVEVSVkFMID0gNSAqIDYwICogMTAwMDtcbmNvbnN0IFZBTElEX1dFQkhPT0tfUFJFRklYRVMgPSBbXG4gICAgXCJodHRwczovL2Rpc2NvcmQuY29tL2FwaS93ZWJob29rcy9cIixcbiAgICBcImh0dHBzOi8vZGlzY29yZGFwcC5jb20vYXBpL3dlYmhvb2tzL1wiLFxuICAgIFwiaHR0cHM6Ly9jYW5hcnkuZGlzY29yZC5jb20vYXBpL3dlYmhvb2tzL1wiLFxuXTtcbmNvbnN0IERJU0NPUkRfTUVTU0FHRV9MSU1JVCA9IDIwMDA7XG5jb25zdCBNQVhfRVJST1JfQk9EWV9MRU5HVEggPSA1MDA7XG5jb25zdCBNQVhfU0VORF9BVFRFTVBUUyA9IDM7XG5jb25zdCBSRURBQ1RFRF9UT0tFTl9WSVNJQkxFID0gODtcbmNvbnN0IERFRkFVTFRfUkFURV9MSU1JVF9XQUlUID0gMiAqIDEwMDA7XG5cbmxldCBub3RpZmljYXRpb25TdGF0ZToge1xuICAgIGZpbGVQYXRoOiBzdHJpbmc7XG4gICAgd2ViaG9va1VSTDogc3RyaW5nO1xuICAgIGNoZWNrVGltZXI6IE5vZGVKUy5UaW1lb3V0O1xufSB8IHVuZGVmaW5lZDtcblxuLy8gRGlzY29yZCB3ZWJob29rcyBhcmUgcmF0ZSBsaW1pdGVkIChyb3VnaGx5IDUgcmVxdWVzdHMgcGVyIDIgc2Vjb25kcyksIHNvIGV2ZXJ5IHNlbmRcbi8vIGdvZXMgdGhyb3VnaCBvbmUgY2hhaW4gaW5zdGVhZCBvZiByYWNpbmcuXG5sZXQgc2VuZENoYWluOiBQcm9taXNlPHVua25vd24+ID0gUHJvbWlzZS5yZXNvbHZlKCk7XG5cbi8qKiBQdWxscyB0aGUgd2ViaG9vayBVUkwgb3V0IG9mIHdlYmhvb2sgZmlsZSBjb250ZW50cy4gYHNvdXJjZU5hbWVgIG9ubHkgYXBwZWFycyBpbiBlcnJvcnMsIHNvXG4gICAgY2FsbGVycyBjYW4gbmFtZSBhIHJlbW90ZSBwYXRoLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlV2ViaG9va0ZpbGUoY29uZmlnOiB7IGNvbnRlbnRzOiBzdHJpbmc7IHNvdXJjZU5hbWU6IHN0cmluZyB9KSB7XG4gICAgbGV0IHsgY29udGVudHMsIHNvdXJjZU5hbWUgfSA9IGNvbmZpZztcbiAgICBsZXQgbGluZXMgPSBjb250ZW50cy5zcGxpdChcIlxcblwiKS5tYXAobGluZSA9PiBsaW5lLnRyaW0oKSkuZmlsdGVyKGxpbmUgPT4gbGluZSAmJiAhbGluZS5zdGFydHNXaXRoKFwiI1wiKSk7XG4gICAgbGV0IHdlYmhvb2tVUkwgPSBsaW5lc1swXTtcbiAgICBpZiAoIXdlYmhvb2tVUkwpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBhIERpc2NvcmQgd2ViaG9vayBVUkwgaW4gJHtzb3VyY2VOYW1lfSwgdGhlIGZpbGUgaGFzIG5vIHVzYWJsZSBsaW5lc2ApO1xuICAgIH1cbiAgICBpZiAoIVZBTElEX1dFQkhPT0tfUFJFRklYRVMuc29tZShwcmVmaXggPT4gd2ViaG9va1VSTC5zdGFydHNXaXRoKHByZWZpeCkpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCBhIERpc2NvcmQgd2ViaG9vayBVUkwgc3RhcnRpbmcgd2l0aCBvbmUgb2YgJHtWQUxJRF9XRUJIT09LX1BSRUZJWEVTLmpvaW4oXCIsIFwiKX0sIGBcbiAgICAgICAgICAgICsgYHdhcyAke3dlYmhvb2tVUkwuc2xpY2UoMCwgTUFYX0VSUk9SX0JPRFlfTEVOR1RIKX0gKGluICR7c291cmNlTmFtZX0pYFxuICAgICAgICApO1xuICAgIH1cbiAgICByZXR1cm4gd2ViaG9va1VSTDtcbn1cblxuLyoqIEtlZXBzIHRoZSBpZCBhbmQgYm90aCBlbmRzIG9mIHRoZSB0b2tlbiwgc28gYSByZWFkZXIgY2FuIGNvbmZpcm0gd2hpY2ggd2ViaG9vayByZXBsYWNlZCB0aGVpcnNcbiAgICB3aXRob3V0IHJlY2VpdmluZyBvbmUgdGhleSBjb3VsZCBwb3N0IHRvLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlZGFjdFdlYmhvb2tVUkwod2ViaG9va1VSTDogc3RyaW5nKSB7XG4gICAgbGV0IHNlcGFyYXRvckluZGV4ID0gd2ViaG9va1VSTC5sYXN0SW5kZXhPZihcIi9cIik7XG4gICAgbGV0IGJhc2UgPSB3ZWJob29rVVJMLnNsaWNlKDAsIHNlcGFyYXRvckluZGV4ICsgMSk7XG4gICAgbGV0IHRva2VuID0gd2ViaG9va1VSTC5zbGljZShzZXBhcmF0b3JJbmRleCArIDEpO1xuICAgIGlmICh0b2tlbi5sZW5ndGggPD0gUkVEQUNURURfVE9LRU5fVklTSUJMRSAqIDIpIHtcbiAgICAgICAgLy8gVG9vIHNob3J0IHRvIHNob3cgYm90aCBlbmRzIHdpdGhvdXQgZ2l2aW5nIGF3YXkgdGhlIHdob2xlIHRva2VuLlxuICAgICAgICByZXR1cm4gYCR7YmFzZX0ke3Rva2VuLnNsaWNlKDAsIFJFREFDVEVEX1RPS0VOX1ZJU0lCTEUpfS4uLmA7XG4gICAgfVxuICAgIHJldHVybiBgJHtiYXNlfSR7dG9rZW4uc2xpY2UoMCwgUkVEQUNURURfVE9LRU5fVklTSUJMRSl9Li4uJHt0b2tlbi5zbGljZSgtUkVEQUNURURfVE9LRU5fVklTSUJMRSl9YDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFdlYmhvb2tGaWxlKHdlYmhvb2tVUkw6IHN0cmluZykge1xuICAgIHJldHVybiBgIyBwb3J0c2VjdXJlIERpc2NvcmQgd2ViaG9vay4gRmlyc3Qgbm9uLWNvbW1lbnQgbGluZSBpcyB1c2VkLlxcbiR7d2ViaG9va1VSTH1cXG5gO1xufVxuXG5hc3luYyBmdW5jdGlvbiByZWFkV2ViaG9va0ZpbGUoZmlsZVBhdGg6IHN0cmluZykge1xuICAgIGxldCBjb250ZW50cztcbiAgICB0cnkge1xuICAgICAgICBjb250ZW50cyA9IGF3YWl0IGZzLnJlYWRGaWxlKGZpbGVQYXRoLCBcInV0ZjhcIik7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgcmVhZGFibGUgRGlzY29yZCB3ZWJob29rIGZpbGUgYXQgJHtmaWxlUGF0aH0sICR7ZX1gKTtcbiAgICB9XG4gICAgcmV0dXJuIHBhcnNlV2ViaG9va0ZpbGUoeyBjb250ZW50cywgc291cmNlTmFtZTogZmlsZVBhdGggfSk7XG59XG5cbi8qKiBXaGVuIHRoZSBtZXNzYWdlIHdhcyBzZW50LCBpbiB0aGUgc2VuZGluZyBtYWNoaW5lJ3Mgb3duIHRpbWUgem9uZS4gRGlzY29yZCBzaG93cyB3aGVuIGl0XG4gICAgcmVjZWl2ZWQgc29tZXRoaW5nLCB3aGljaCBpcyBub3QgdGhlIHNhbWUgdGhpbmcgd2hlbiBhIG1hY2hpbmUgaGFzIGJlZW4gb2ZmbGluZSBvciBhIHNlbmQgaGFzXG4gICAgYmVlbiByZXRyaWVkLCBhbmQgdGhlIHpvbmUgbWF0dGVycyB3aGVuIHRoZSBtYWNoaW5lcyBhcmUgbm90IGFsbCBpbiBvbmUgcGxhY2UuICovXG5leHBvcnQgZnVuY3Rpb24gbWVzc2FnZVRpbWVzdGFtcChub3c6IERhdGUpIHtcbiAgICBsZXQgZGF0ZSA9IG5vdy50b0xvY2FsZURhdGVTdHJpbmcoXCJlbi1DQVwiKTtcbiAgICBsZXQgdGltZSA9IG5vdy50b0xvY2FsZVRpbWVTdHJpbmcoXCJlbi1VU1wiLCB7XG4gICAgICAgIGhvdXI6IFwiMi1kaWdpdFwiLFxuICAgICAgICBtaW51dGU6IFwiMi1kaWdpdFwiLFxuICAgICAgICBzZWNvbmQ6IFwiMi1kaWdpdFwiLFxuICAgICAgICBob3VyMTI6IHRydWUsXG4gICAgICAgIHRpbWVab25lTmFtZTogXCJzaG9ydFwiLFxuICAgIH0pO1xuICAgIHJldHVybiBgJHtkYXRlfSAke3RpbWV9YDtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcG9zdFRvV2ViaG9vayh3ZWJob29rVVJMOiBzdHJpbmcsIG1lc3NhZ2U6IHN0cmluZykge1xuICAgIC8vIFN0YW1wZWQgb25jZSByYXRoZXIgdGhhbiBwZXIgYXR0ZW1wdCwgc28gYSByZXRyeSBzYXlzIHdoZW4gdGhlIHRoaW5nIGhhcHBlbmVkIHJhdGhlciB0aGFuXG4gICAgLy8gd2hlbiB3ZSBsYXN0IG1hbmFnZWQgdG8gZ2V0IGl0IG91dC4gQXQgdGhlIGVuZCwgYmVjYXVzZSB0aGUgZmlyc3QgbGluZSBvZiBhIG1lc3NhZ2UgaXMgdGhlXG4gICAgLy8gbm90aWZpY2F0aW9uIHByZXZpZXcgYW5kIGEgdGltZXN0YW1wIHRoZXJlIHdvdWxkIHNwZW5kIGl0IHNheWluZyBub3RoaW5nLlxuICAgIGxldCBjb250ZW50ID0gYCR7bWVzc2FnZX0gwrcgXFxgJHttZXNzYWdlVGltZXN0YW1wKG5ldyBEYXRlKCkpfVxcYGA7XG4gICAgaWYgKGNvbnRlbnQubGVuZ3RoID4gRElTQ09SRF9NRVNTQUdFX0xJTUlUKSB7XG4gICAgICAgIGNvbnRlbnQgPSBjb250ZW50LnNsaWNlKDAsIERJU0NPUkRfTUVTU0FHRV9MSU1JVCAtIDMpICsgXCIuLi5cIjtcbiAgICB9XG4gICAgZm9yIChsZXQgYXR0ZW1wdCA9IDE7IGF0dGVtcHQgPD0gTUFYX1NFTkRfQVRURU1QVFM7IGF0dGVtcHQrKykge1xuICAgICAgICBsZXQgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh3ZWJob29rVVJMLCB7XG4gICAgICAgICAgICBtZXRob2Q6IFwiUE9TVFwiLFxuICAgICAgICAgICAgaGVhZGVyczogeyBcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIiB9LFxuICAgICAgICAgICAgYm9keTogSlNPTi5zdHJpbmdpZnkoeyBjb250ZW50IH0pLFxuICAgICAgICB9KTtcbiAgICAgICAgaWYgKHJlc3BvbnNlLm9rKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgbGV0IGJvZHkgPSAoYXdhaXQgcmVzcG9uc2UudGV4dCgpKS5zbGljZSgwLCBNQVhfRVJST1JfQk9EWV9MRU5HVEgpO1xuICAgICAgICBpZiAocmVzcG9uc2Uuc3RhdHVzID09PSA0MjkgJiYgYXR0ZW1wdCA8IE1BWF9TRU5EX0FUVEVNUFRTKSB7XG4gICAgICAgICAgICBsZXQgcmV0cnlBZnRlciA9IE51bWJlcihyZXNwb25zZS5oZWFkZXJzLmdldChcInJldHJ5LWFmdGVyXCIpKTtcbiAgICAgICAgICAgIGxldCB3YWl0VGltZSA9IHJldHJ5QWZ0ZXIgJiYgcmV0cnlBZnRlciAqIDEwMDAgfHwgREVGQVVMVF9SQVRFX0xJTUlUX1dBSVQ7XG4gICAgICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgd2FpdFRpbWUpKTtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgIGBFeHBlY3RlZCBhIDJ4eCByZXNwb25zZSBmcm9tIHRoZSBEaXNjb3JkIHdlYmhvb2ssIHdhcyAke3Jlc3BvbnNlLnN0YXR1c30gJHtyZXNwb25zZS5zdGF0dXNUZXh0fSwgYm9keSAke2JvZHl9YFxuICAgICAgICApO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gcXVldWVTZW5kKHdlYmhvb2tVUkw6IHN0cmluZywgbWVzc2FnZTogc3RyaW5nKSB7XG4gICAgbGV0IHNlbmQgPSAoKSA9PiBwb3N0VG9XZWJob29rKHdlYmhvb2tVUkwsIG1lc3NhZ2UpO1xuICAgIGxldCByZXN1bHQgPSBzZW5kQ2hhaW4udGhlbihzZW5kLCBzZW5kKTtcbiAgICBzZW5kQ2hhaW4gPSByZXN1bHQuY2F0Y2goKCkgPT4gdW5kZWZpbmVkKTtcbiAgICByZXR1cm4gcmVzdWx0O1xufVxuXG4vKiogU2VuZHMgdG8gYW4gZXhwbGljaXQgd2ViaG9vayBVUkwsIGZvciB0b29saW5nIHRoYXQgYWN0cyBvbiBhIHdlYmhvb2sgYmVmb3JlIChvciBpbnN0ZWFkIG9mKVxuICAgIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIC0gc2V0dXAsIG1pZ3JhdGlvbnMsIGNvbm5lY3Rpdml0eSBjaGVja3MuICovXG5leHBvcnQgZnVuY3Rpb24gc2VuZFRvV2ViaG9va1VSTChjb25maWc6IHsgd2ViaG9va1VSTDogc3RyaW5nOyBtZXNzYWdlOiBzdHJpbmcgfSkge1xuICAgIHJldHVybiBxdWV1ZVNlbmQoY29uZmlnLndlYmhvb2tVUkwsIGNvbmZpZy5tZXNzYWdlKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gY2hlY2tXZWJob29rRmlsZUNoYW5nZWQoKSB7XG4gICAgbGV0IHN0YXRlID0gbm90aWZpY2F0aW9uU3RhdGU7XG4gICAgaWYgKCFzdGF0ZSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGxldCBuZXdXZWJob29rVVJMO1xuICAgIHRyeSB7XG4gICAgICAgIG5ld1dlYmhvb2tVUkwgPSBhd2FpdCByZWFkV2ViaG9va0ZpbGUoc3RhdGUuZmlsZVBhdGgpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihgcG9ydHNlY3VyZTogRGlzY29yZCB3ZWJob29rIGZpbGUgaXMgbm8gbG9uZ2VyIHJlYWRhYmxlLCBzdGlsbCB1c2luZyB0aGUgbG9hZGVkIHdlYmhvb2suICR7ZX1gKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAobmV3V2ViaG9va1VSTCA9PT0gc3RhdGUud2ViaG9va1VSTCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIC8vIFdhcm4gdGhlIG9sZCBjaGFubmVsIGZpcnN0LCB3aXRoIHRoZSBuZXcgd2ViaG9vayByZWRhY3RlZCwgc28gYSBzdG9sZW4gb2xkIHdlYmhvb2sgZG9lcyBub3RcbiAgICAvLyBoYW5kIHRoZSBhdHRhY2tlciBhIHVzYWJsZSBuZXcgb25lLlxuICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHF1ZXVlU2VuZChcbiAgICAgICAgICAgIHN0YXRlLndlYmhvb2tVUkwsXG4gICAgICAgICAgICBgKipwb3J0c2VjdXJlKio6ICoqTk9USUZJQ0FUSU9OUyBBUkUgTU9WSU5HIFRPIEFOT1RIRVIgQ0hBTk5FTCoqYFxuICAgICAgICAgICAgKyBgXFxuXFxuVGhpcyBjaGFubmVsIHN0b3BzIHJlY2VpdmluZyB0aGVtIG5vdy4gSWYgdGhhdCB3YXMgbm90IHlvdSwgc29tZWJvZHkgd2l0aCBhY2Nlc3NgXG4gICAgICAgICAgICArIGAgdG8gdGhpcyBtYWNoaW5lIGp1c3QgcmVkaXJlY3RlZCBpdHMgYWxlcnRzLmBcbiAgICAgICAgICAgICsgYFxcblxcbm5ldyB3ZWJob29rOiBcXGAke3JlZGFjdFdlYmhvb2tVUkwobmV3V2ViaG9va1VSTCl9XFxgYFxuICAgICAgICAgICAgKyBgXFxuc2V0IGluOiBcXGAke3N0YXRlLmZpbGVQYXRofVxcYGBcbiAgICAgICAgKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYHBvcnRzZWN1cmU6IGZhaWxlZCB0byB3YXJuIHRoZSBvbGQgRGlzY29yZCB3ZWJob29rIGFib3V0IHRoZSBjaGFuZ2UuICR7ZX1gKTtcbiAgICB9XG4gICAgc3RhdGUud2ViaG9va1VSTCA9IG5ld1dlYmhvb2tVUkw7XG59XG5cbi8qKiBNdXN0IGJlIGNhbGxlZCBvbmNlIG9uIHN0YXJ0dXAsIGJlZm9yZSBhbnkgbm90aWZpY2F0aW9uIGlzIHNlbnQuIEFib3J0cyB0aGUgcHJvY2VzcyBpZiB0aGVcbiAgICB3ZWJob29rIGZpbGUgaXMgbWlzc2luZyBvciBpbnZhbGlkLCB0aGVuIHJlLWNoZWNrcyB0aGUgZmlsZSBvbiBhbiBpbnRlcnZhbCBhbmQgd2FybnMgdGhlIG9sZFxuICAgIHdlYmhvb2sgd2hlbmV2ZXIgaXQgY2hhbmdlcy4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjb25maWd1cmVEaXNjb3JkTm90aWZpY2F0aW9ucyhjb25maWc/OiB7XG4gICAgZmlsZVBhdGg/OiBzdHJpbmc7XG4gICAgY2hlY2tJbnRlcnZhbD86IG51bWJlcjtcbn0pIHtcbiAgICBpZiAobm90aWZpY2F0aW9uU3RhdGUpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBjb25maWd1cmVEaXNjb3JkTm90aWZpY2F0aW9ucyB0byBiZSBjYWxsZWQgb25jZSwgd2FzIGNhbGxlZCBhZ2FpbiAoYWxyZWFkeSB1c2luZyAke25vdGlmaWNhdGlvblN0YXRlLmZpbGVQYXRofSlgKTtcbiAgICB9XG4gICAgbGV0IGZpbGVQYXRoID0gY29uZmlnPy5maWxlUGF0aCB8fCBERUZBVUxUX1dFQkhPT0tfRklMRV9QQVRIO1xuICAgIGxldCBjaGVja0ludGVydmFsID0gY29uZmlnPy5jaGVja0ludGVydmFsIHx8IERFRkFVTFRfQ0hFQ0tfSU5URVJWQUw7XG4gICAgbGV0IHdlYmhvb2tVUkw7XG4gICAgdHJ5IHtcbiAgICAgICAgd2ViaG9va1VSTCA9IGF3YWl0IHJlYWRXZWJob29rRmlsZShmaWxlUGF0aCk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiByZWZ1c2luZyB0byBzdGFydCB3aXRob3V0IGEgdmFsaWQgRGlzY29yZCB3ZWJob29rIGZpbGUuXFxuJHtlfWApO1xuICAgICAgICBwcm9jZXNzLmV4aXQoMSk7XG4gICAgfVxuICAgIGxldCBjaGVja1RpbWVyID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgICAgICBjaGVja1dlYmhvb2tGaWxlQ2hhbmdlZCgpLmNhdGNoKGUgPT4gY29uc29sZS5lcnJvcihgcG9ydHNlY3VyZTogRGlzY29yZCB3ZWJob29rIGZpbGUgY2hlY2sgZmFpbGVkLiAke2V9YCkpO1xuICAgIH0sIGNoZWNrSW50ZXJ2YWwpO1xuICAgIC8vIFRoZSBjaGVjayBzaG91bGQgbmV2ZXIgYmUgdGhlIHJlYXNvbiB0aGUgcHJvY2VzcyBzdGF5cyBhbGl2ZS5cbiAgICBjaGVja1RpbWVyLnVucmVmKCk7XG4gICAgbm90aWZpY2F0aW9uU3RhdGUgPSB7IGZpbGVQYXRoLCB3ZWJob29rVVJMLCBjaGVja1RpbWVyIH07XG4gICAgcmV0dXJuIHsgZmlsZVBhdGgsIGNoZWNrSW50ZXJ2YWwgfTtcbn1cblxuLy8gRE8gTk9UIGFkZCBuZXcgY2FsbHMgdG8gdGhpcy4gRXZlcnkgbWVzc2FnZSBnb2VzIHRvIGEgcmVhbCBEaXNjb3JkIHNlcnZlciBzb21lb25lIHJlYWRzLCBzbyBhXG4vLyBub3RpZmljYXRpb24gaXMgb25seSBldmVyIGFkZGVkIHdoZW4gdGhlIHVzZXIgZXhwbGljaXRseSBhc2tzIGZvciB0aGF0IHNwZWNpZmljIGNhc2UuIFN0YXJ0dXAsXG4vLyBzdWNjZXNzLCBlcnJvcnMsIHJldHJpZXMgYW5kIHJlY292ZXJpZXMgYWxsIGJlbG9uZyBpbiBhIGxvZyBpbnN0ZWFkLlxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNlbmREaXNjb3JkTm90aWZpY2F0aW9uKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIGxldCBzdGF0ZSA9IG5vdGlmaWNhdGlvblN0YXRlO1xuICAgIGlmICghc3RhdGUpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBjb25maWd1cmVEaXNjb3JkTm90aWZpY2F0aW9ucyB0byBiZSBjYWxsZWQgYmVmb3JlIHNlbmRpbmcsIHdhcyBjYWxsZWQgd2l0aCBtZXNzYWdlICR7bWVzc2FnZS5zbGljZSgwLCBNQVhfRVJST1JfQk9EWV9MRU5HVEgpfWApO1xuICAgIH1cbiAgICBhd2FpdCBxdWV1ZVNlbmQoc3RhdGUud2ViaG9va1VSTCwgbWVzc2FnZSk7XG59XG5cbi8qKiBXaGV0aGVyIG5vdGlmaWNhdGlvbnMgYXJlIHNldCB1cCBpbiB0aGlzIHByb2Nlc3MuIEZvciBjb2RlIHRoYXQgbWF5IHJ1biBvdXRzaWRlIHRoZSBkYWVtb24sXG4gICAgd2hlcmUgc2VuZGluZyBpcyB3b3J0aCBkb2luZyBpZiBpdCBjYW4gYmUgYW5kIG5vdCB3b3J0aCBmYWlsaW5nIG92ZXIgaWYgaXQgY2Fubm90LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFyZURpc2NvcmROb3RpZmljYXRpb25zQ29uZmlndXJlZCgpIHtcbiAgICByZXR1cm4gISFub3RpZmljYXRpb25TdGF0ZTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN0b3BEaXNjb3JkTm90aWZpY2F0aW9ucygpIHtcbiAgICBpZiAoIW5vdGlmaWNhdGlvblN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY2xlYXJJbnRlcnZhbChub3RpZmljYXRpb25TdGF0ZS5jaGVja1RpbWVyKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHVuZGVmaW5lZDtcbn1cbiJdfQ==
206
+ /* _JS_SOURCE_HASH = "abb6e0b243d1d85cb4a3599020263d0b3164ce4bf2aef2aa81489e304cd43b4c"; */