@pipeline-builder/api-server 4.4.3 → 4.4.5

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.
@@ -1,33 +1,160 @@
1
1
  // Copyright 2026 Pipeline Builder Contributors
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { ErrorCode, createLogger, emitCounter, getQuotaServiceAuthHeader, sendError, sendQuotaExceeded, errorMessage } from '@pipeline-builder/api-core';
3
+ import { armDeferredQuotaCheck, callQuotaExemption, createLogger, emitCounter, enforcedQuotaLimit, envInt, getQuotaServiceAuthHeader, isQuotaExemptRoute, meteredQuotaTypeFor, sendQuotaExceeded, errorMessage, tagRouteGate } from '@pipeline-builder/api-core';
4
4
  import { getContext } from './get-context.js';
5
+ import { hasCompletedIdempotentResponse } from './idempotency-middleware.js';
5
6
  const logger = createLogger('check-quota');
6
- /** Human-readable labels for quota exceeded messages. */
7
- const QUOTA_LABELS = {
8
- apiCalls: 'API call',
9
- aiCalls: 'AI call',
10
- pipelines: 'Pipeline',
11
- plugins: 'Plugin',
12
- storageBytes: 'Registry storage',
13
- dashboards: 'Dashboard',
14
- alertRules: 'Alert rule',
15
- alertDestinations: 'Alert destination',
16
- idpConfigs: 'IdP configuration',
17
- listings: 'Plugin listing',
18
- };
7
+ // PER-POD CHECK CACHE.
8
+ //
9
+ // The gate used to ask the quota service on EVERY metered request — a GET, a
10
+ // JWT verify and a hierarchy resolve over there, per request here. A reading is
11
+ // now reused per (org, quota type):
12
+ // - an ALLOWED reading for `QUOTA_CHECK_CACHE_TTL_MS` (default 5000; 0 turns
13
+ // the cache off), and only while this pod has passed fewer requests on it
14
+ // than the reading had headroom for (`enforcedQuotaLimit − used`), so one
15
+ // pod cannot run far past a nearly-full counter on a stale "yes";
16
+ // - a DENIED reading until the period resets, capped at
17
+ // `QUOTA_CHECK_DENIED_CACHE_MS` (default 60000) so an upgrade, a bundle
18
+ // purchase or an operator reset is honoured within a minute;
19
+ // - a fail-open sentinel is NEVER cached — recovery is seen on the next request.
20
+ // Staleness is bounded and accepted: the counter itself never exceeds the cap
21
+ // (the metered increment admits only what fits and records the rest as
22
+ // overshoot), so a stale "yes" costs at most a few unbilled-over-cap requests
23
+ // per pod per TTL, never an unbounded run. Concurrent misses for one key share
24
+ // a single in-flight read.
25
+ /** TTL (ms) of a cached ALLOWED check. `0` disables the cache. */
26
+ export const QUOTA_CHECK_CACHE_TTL_MS = envInt('QUOTA_CHECK_CACHE_TTL_MS', 5000, { min: 0 });
27
+ /** Longest (ms) a DENIED check is reused before re-asking (it never outlives `resetAt`). */
28
+ export const QUOTA_CHECK_DENIED_CACHE_MS = envInt('QUOTA_CHECK_DENIED_CACHE_MS', 60000, { min: 0 });
29
+ /** Bound on cached (org, type) readings per pod. */
30
+ const CHECK_CACHE_MAX_ENTRIES = 10_000;
31
+ const checkCache = new Map();
32
+ const inFlightChecks = new Map();
33
+ /** Drop every cached check (tests; also a safe operational reset). */
34
+ export function clearQuotaCheckCache() {
35
+ checkCache.clear();
36
+ inFlightChecks.clear();
37
+ }
38
+ /** A usable cached reading for `key`, counting this request against its headroom. */
39
+ function cachedCheck(key, quotaType) {
40
+ const hit = checkCache.get(key);
41
+ if (!hit)
42
+ return null;
43
+ if (hit.expiresAt <= Date.now()) {
44
+ checkCache.delete(key);
45
+ return null;
46
+ }
47
+ if (!hit.status.allowed)
48
+ return hit.status;
49
+ if (hit.status.limit !== -1) {
50
+ const headroom = enforcedQuotaLimit(quotaType, hit.status.limit) - hit.status.used;
51
+ if (hit.passes >= headroom) {
52
+ checkCache.delete(key);
53
+ return null;
54
+ }
55
+ }
56
+ hit.passes += 1;
57
+ return hit.status;
58
+ }
59
+ function rememberCheck(key, status) {
60
+ if (!status || status.failOpen)
61
+ return;
62
+ const now = Date.now();
63
+ let expiresAt;
64
+ if (status.allowed) {
65
+ if (QUOTA_CHECK_CACHE_TTL_MS === 0)
66
+ return;
67
+ expiresAt = now + QUOTA_CHECK_CACHE_TTL_MS;
68
+ }
69
+ else {
70
+ if (QUOTA_CHECK_DENIED_CACHE_MS === 0)
71
+ return;
72
+ const resetAt = Date.parse(status.resetAt);
73
+ expiresAt = Math.min(now + QUOTA_CHECK_DENIED_CACHE_MS, Number.isFinite(resetAt) ? resetAt : now);
74
+ if (expiresAt <= now)
75
+ return;
76
+ }
77
+ if (checkCache.size >= CHECK_CACHE_MAX_ENTRIES) {
78
+ // Map preserves insertion order, so the first key is the oldest reading.
79
+ const oldest = checkCache.keys().next();
80
+ if (!oldest.done)
81
+ checkCache.delete(oldest.value);
82
+ }
83
+ // The request that fetched the reading is its first pass.
84
+ checkCache.set(key, { status, expiresAt, passes: status.allowed ? 1 : 0 });
85
+ }
86
+ /**
87
+ * One quota read for (org, type), shared by every concurrent miss on this pod.
88
+ * A request that waited on someone else's read then goes back through the
89
+ * cache, so it is counted against the reading's headroom like any other pass
90
+ * (`joined` stops it from waiting a second time).
91
+ */
92
+ async function readCheck(quotaService, orgId, quotaType, joined = false) {
93
+ const key = `${orgId}\u0000${quotaType}`;
94
+ const cached = cachedCheck(key, quotaType);
95
+ if (cached)
96
+ return cached;
97
+ const pending = inFlightChecks.get(key);
98
+ if (pending) {
99
+ if (joined)
100
+ return pending;
101
+ const status = await pending;
102
+ // A fail-open (or uncacheable) reading has nothing to account against.
103
+ return checkCache.has(key) ? readCheck(quotaService, orgId, quotaType, true) : status;
104
+ }
105
+ // Authenticate as THIS service, not by forwarding the user's token: the user
106
+ // token 403s at the quota read route for custom roles without `quotas:read`,
107
+ // and (being non-service) counts against the quota service's per-IP limiter
108
+ // keyed on THIS pod's IP — both of which silently fail the gate open under load.
109
+ const read = quotaService.check(orgId, quotaType, getQuotaServiceAuthHeader(orgId))
110
+ .then((status) => {
111
+ rememberCheck(key, status);
112
+ return status;
113
+ })
114
+ .finally(() => inFlightChecks.delete(key));
115
+ inFlightChecks.set(key, read);
116
+ return read;
117
+ }
19
118
  /**
20
119
  * Create middleware that checks a specific quota type before proceeding.
21
120
  *
22
- * On quota exceeded, returns a 429 response with quota details.
121
+ * On quota exceeded, returns a 429 response with quota details (quotaType,
122
+ * limit, used, resetAt, upgradeUrl — see api-core `sendQuotaExceeded`).
23
123
  * On quota service failure, fails open (allows the request).
24
124
  *
125
+ * `apiCalls` is split per request by principal: an org service account or a
126
+ * personal access key is checked against `automationCalls` instead
127
+ * ({@link meteredQuotaTypeFor}), the
128
+ * same type `meterQuotaOnSuccess` increments — so mount both with `'apiCalls'`.
129
+ *
130
+ * Skipped (passes through) exactly where the meter skips, so the gate never
131
+ * refuses a call the meter would not bill (`callQuotaExemption`):
132
+ * - a SERVICE PRINCIPAL (peer-service traffic is never metered, so a tenant at
133
+ * its limit must not break the internal calls its own earlier actions caused);
134
+ * - for `apiCalls`: an IMPERSONATION session (vendor staff diagnosing an
135
+ * exhausted org must not be locked out by it) and an ON-BEHALF hop (a user
136
+ * token forwarded by ask/pipeline with a signed marker — the originating
137
+ * request was already gated and metered);
138
+ * - an `apiCalls` request on the fleet-wide `QUOTA_EXEMPT_ROUTES` (console
139
+ * polling — an exhausted org must still render the banner saying so);
140
+ * - an idempotent RETRY whose original already completed (the replay is served
141
+ * from the idempotency cache, does no work and is never metered; refusing it
142
+ * would 429 a client for asking what its own earlier request returned).
143
+ *
144
+ * Readings are cached per pod (see the cache notes above), and the read itself
145
+ * has a short budget (api-core `QUOTA_CHECK_TIMEOUT_MS`, no retry) — a slow
146
+ * quota service fails open quickly instead of stalling every request.
147
+ *
148
+ * Mountable at the app-routes level (`app.use(prefix, …auth, checkQuota(...))`):
149
+ * the org comes from the request context when one is attached, else from the
150
+ * verified `req.user.organizationId` — the same fallback the meter uses.
151
+ *
25
152
  * @param quotaService - Quota service client
26
153
  * @param quotaType - Which quota to check (e.g. 'apiCalls', 'pipelines', 'plugins')
27
154
  * @returns Express middleware
28
155
  */
29
- export function checkQuota(quotaService, quotaType) {
30
- return async (req, res, next) => {
156
+ export function checkQuota(quotaService, baseQuotaType) {
157
+ return tagRouteGate(async (req, res, next) => {
31
158
  // The check is sent with THIS service's credentials, so it must only run for
32
159
  // a VERIFIED caller — otherwise the org would come from the spoofable
33
160
  // header-derived identity. Mounted after requireAuth everywhere; an
@@ -36,41 +163,35 @@ export function checkQuota(quotaService, quotaType) {
36
163
  logger.warn('Quota check skipped: request is not authenticated');
37
164
  return next();
38
165
  }
166
+ const exemption = callQuotaExemption(req);
167
+ if (exemption === 'service' || (exemption && baseQuotaType === 'apiCalls'))
168
+ return next();
169
+ if (baseQuotaType === 'apiCalls' && isQuotaExemptRoute(req.method, requestPath(req)))
170
+ return next();
171
+ const quotaType = meteredQuotaTypeFor(req, baseQuotaType);
39
172
  let orgId;
40
173
  try {
41
- const ctx = getContext(req);
42
- orgId = ctx.identity.orgId;
174
+ orgId = getContext(req).identity.orgId;
43
175
  }
44
176
  catch {
45
- // Context middleware not applied — fail open (log and continue)
46
- logger.warn('Quota check skipped: request context not initialized');
47
- return next();
48
- }
49
- if (!orgId) {
50
- sendError(res, 400, 'Organization ID is required for quota check', ErrorCode.VALIDATION_ERROR);
51
- return;
177
+ // No request context (platform attaches none): the verified auth org — the
178
+ // same fallback `meterQuotaOnSuccess` uses, so both read one counter.
179
+ orgId = req.user.organizationId;
52
180
  }
181
+ // No org, nothing to bill (the meter skips it too). Not a 400: run deferred
182
+ // from `requireAuth` this precedes the route's own `requireOrgId`, and a
183
+ // route that needs no org (a person between orgs switching into one) must
184
+ // not be refused by a quota gate.
185
+ if (!orgId)
186
+ return next();
187
+ // A retry of a completed idempotent mutation is answered from the replay
188
+ // cache without running the handler — nothing to gate (one store read, only
189
+ // for requests that carry an Idempotency-Key).
190
+ if (await hasCompletedIdempotentResponse(req))
191
+ return next();
192
+ let quotaStatus;
53
193
  try {
54
- // Authenticate as THIS service, not by forwarding the user's token: the
55
- // user token 403s at the quota read route for custom roles without
56
- // `quotas:read`, and (being non-service) counts against the quota
57
- // service's per-IP limiter keyed on THIS pod's IP — both of which
58
- // silently fail the gate open under load.
59
- const quotaStatus = await quotaService.check(orgId, quotaType, getQuotaServiceAuthHeader(orgId));
60
- if (!quotaStatus.allowed) {
61
- logger.warn(`${quotaType} quota exceeded`, {
62
- orgId,
63
- limit: quotaStatus.limit,
64
- used: quotaStatus.used,
65
- });
66
- // Route through the shared helper so the check-gate 429 emits the same
67
- // Retry-After + X-Quota-* headers as the reserve-path 429 (the inline
68
- // 429 this replaced omitted them). Custom message keeps the
69
- // "contact your administrator" copy.
70
- sendQuotaExceeded(res, quotaType, { type: quotaType, limit: quotaStatus.limit, used: quotaStatus.used, remaining: quotaStatus.remaining }, quotaStatus.resetAt, `${QUOTA_LABELS[quotaType]} quota exceeded. Please contact your administrator to increase your quota.`);
71
- return;
72
- }
73
- next();
194
+ quotaStatus = await readCheck(quotaService, orgId, quotaType);
74
195
  }
75
196
  catch (error) {
76
197
  // Fail open — allow the request if quota service is unavailable
@@ -83,8 +204,41 @@ export function checkQuota(quotaService, quotaType) {
83
204
  // is ALERTABLE from one series regardless of which layer fell open. Only
84
205
  // a log line here meant every org was silently un-billed for the duration.
85
206
  emitCounter('quota_fail_open_total', { operation: 'check', reason: 'exception', quotaType });
86
- next();
207
+ return next();
87
208
  }
88
- };
209
+ if (!quotaStatus.allowed) {
210
+ logger.warn(`${quotaType} quota exceeded`, {
211
+ orgId,
212
+ limit: quotaStatus.limit,
213
+ used: quotaStatus.used,
214
+ });
215
+ // Route through the shared helper so the check-gate 429 emits the same
216
+ // Retry-After + X-Quota-* headers and the same body (which quota, the
217
+ // reset date, the upgrade page) as the reserve-path 429.
218
+ sendQuotaExceeded(res, quotaType, { type: quotaType, limit: quotaStatus.limit, used: quotaStatus.used, remaining: quotaStatus.remaining }, quotaStatus.resetAt);
219
+ return;
220
+ }
221
+ // Outside the try: a synchronous throw downstream must reach Express's error
222
+ // handling, not be mistaken for a quota-service failure and run next() twice.
223
+ next();
224
+ }, { kind: 'quota', mode: 'check', quotaType: baseQuotaType });
225
+ }
226
+ /** The service-local request path, without the query string. */
227
+ export function requestPath(req) {
228
+ return (req.originalUrl ?? req.url ?? '').split('?')[0] ?? '';
229
+ }
230
+ /**
231
+ * The DEFERRED form of {@link checkQuota}: arms the check at mount time and lets
232
+ * `requireAuth` run it once the caller is verified (api-core
233
+ * `armDeferredQuotaCheck`). For a prefix mount ahead of routers that
234
+ * authenticate per route — use {@link apiCallsQuota}, which pairs it with the
235
+ * meter. Tagged as the check gate, so the route table records it.
236
+ */
237
+ export function deferredCheckQuota(quotaService, quotaType) {
238
+ const check = checkQuota(quotaService, quotaType);
239
+ return tagRouteGate((_req, res, next) => {
240
+ armDeferredQuotaCheck(res, check);
241
+ next();
242
+ }, { kind: 'quota', mode: 'check', quotaType });
89
243
  }
90
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2stcXVvdGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL2NoZWNrLXF1b3RhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsT0FBTyxFQUFFLFNBQVMsRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFFLHlCQUF5QixFQUFFLFNBQVMsRUFBRSxpQkFBaUIsRUFBRSxZQUFZLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUd6SixPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFOUMsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0FBRTNDLHlEQUF5RDtBQUN6RCxNQUFNLFlBQVksR0FBOEI7SUFDOUMsUUFBUSxFQUFFLFVBQVU7SUFDcEIsT0FBTyxFQUFFLFNBQVM7SUFDbEIsU0FBUyxFQUFFLFVBQVU7SUFDckIsT0FBTyxFQUFFLFFBQVE7SUFDakIsWUFBWSxFQUFFLGtCQUFrQjtJQUNoQyxVQUFVLEVBQUUsV0FBVztJQUN2QixVQUFVLEVBQUUsWUFBWTtJQUN4QixpQkFBaUIsRUFBRSxtQkFBbUI7SUFDdEMsVUFBVSxFQUFFLG1CQUFtQjtJQUMvQixRQUFRLEVBQUUsZ0JBQWdCO0NBQzNCLENBQUM7QUFFRjs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFNLFVBQVUsVUFBVSxDQUN4QixZQUEwQixFQUMxQixTQUFvQjtJQUVwQixPQUFPLEtBQUssRUFBRSxHQUFZLEVBQUUsR0FBYSxFQUFFLElBQWtCLEVBQWlCLEVBQUU7UUFDOUUsNkVBQTZFO1FBQzdFLHNFQUFzRTtRQUN0RSxvRUFBb0U7UUFDcEUsNEVBQTRFO1FBQzVFLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDZCxNQUFNLENBQUMsSUFBSSxDQUFDLG1EQUFtRCxDQUFDLENBQUM7WUFDakUsT0FBTyxJQUFJLEVBQUUsQ0FBQztRQUNoQixDQUFDO1FBRUQsSUFBSSxLQUF5QixDQUFDO1FBRTlCLElBQUksQ0FBQztZQUNILE1BQU0sR0FBRyxHQUFHLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUM1QixLQUFLLEdBQUcsR0FBRyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUM7UUFDN0IsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLGdFQUFnRTtZQUNoRSxNQUFNLENBQUMsSUFBSSxDQUFDLHNEQUFzRCxDQUFDLENBQUM7WUFDcEUsT0FBTyxJQUFJLEVBQUUsQ0FBQztRQUNoQixDQUFDO1FBRUQsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1lBQ1gsU0FBUyxDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsNkNBQTZDLEVBQUUsU0FBUyxDQUFDLGdCQUFnQixDQUFDLENBQUM7WUFDL0YsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLENBQUM7WUFDSCx3RUFBd0U7WUFDeEUsbUVBQW1FO1lBQ25FLGtFQUFrRTtZQUNsRSxrRUFBa0U7WUFDbEUsMENBQTBDO1lBQzFDLE1BQU0sV0FBVyxHQUFHLE1BQU0sWUFBWSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsU0FBUyxFQUFFLHlCQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7WUFFakcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDekIsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLFNBQVMsaUJBQWlCLEVBQUU7b0JBQ3pDLEtBQUs7b0JBQ0wsS0FBSyxFQUFFLFdBQVcsQ0FBQyxLQUFLO29CQUN4QixJQUFJLEVBQUUsV0FBVyxDQUFDLElBQUk7aUJBQ3ZCLENBQUMsQ0FBQztnQkFFSCx1RUFBdUU7Z0JBQ3ZFLHNFQUFzRTtnQkFDdEUsNERBQTREO2dCQUM1RCxxQ0FBcUM7Z0JBQ3JDLGlCQUFpQixDQUNmLEdBQUcsRUFDSCxTQUFTLEVBQ1QsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxXQUFXLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxXQUFXLENBQUMsSUFBSSxFQUFFLFNBQVMsRUFBRSxXQUFXLENBQUMsU0FBUyxFQUFFLEVBQ3ZHLFdBQVcsQ0FBQyxPQUFPLEVBQ25CLEdBQUcsWUFBWSxDQUFDLFNBQVMsQ0FBQyw0RUFBNEUsQ0FDdkcsQ0FBQztnQkFDRixPQUFPO1lBQ1QsQ0FBQztZQUVELElBQUksRUFBRSxDQUFDO1FBQ1QsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixnRUFBZ0U7WUFDaEUsTUFBTSxDQUFDLElBQUksQ0FBQywwREFBMEQsRUFBRTtnQkFDdEUsS0FBSztnQkFDTCxTQUFTO2dCQUNULEtBQUssRUFBRSxZQUFZLENBQUMsS0FBSyxDQUFDO2FBQzNCLENBQUMsQ0FBQztZQUNILDBFQUEwRTtZQUMxRSx5RUFBeUU7WUFDekUsMkVBQTJFO1lBQzNFLFdBQVcsQ0FBQyx1QkFBdUIsRUFBRSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLFdBQVcsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO1lBQzdGLElBQUksRUFBRSxDQUFDO1FBQ1QsQ0FBQztJQUNILENBQUMsQ0FBQztBQUNKLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgRXJyb3JDb2RlLCBjcmVhdGVMb2dnZXIsIGVtaXRDb3VudGVyLCBnZXRRdW90YVNlcnZpY2VBdXRoSGVhZGVyLCBzZW5kRXJyb3IsIHNlbmRRdW90YUV4Y2VlZGVkLCBlcnJvck1lc3NhZ2UgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgdHlwZSB7IFF1b3RhVHlwZSwgUXVvdGFTZXJ2aWNlIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuaW1wb3J0IHR5cGUgeyBSZXF1ZXN0LCBSZXNwb25zZSwgTmV4dEZ1bmN0aW9uIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBnZXRDb250ZXh0IH0gZnJvbSAnLi9nZXQtY29udGV4dC5qcyc7XG5cbmNvbnN0IGxvZ2dlciA9IGNyZWF0ZUxvZ2dlcignY2hlY2stcXVvdGEnKTtcblxuLyoqIEh1bWFuLXJlYWRhYmxlIGxhYmVscyBmb3IgcXVvdGEgZXhjZWVkZWQgbWVzc2FnZXMuICovXG5jb25zdCBRVU9UQV9MQUJFTFM6IFJlY29yZDxRdW90YVR5cGUsIHN0cmluZz4gPSB7XG4gIGFwaUNhbGxzOiAnQVBJIGNhbGwnLFxuICBhaUNhbGxzOiAnQUkgY2FsbCcsXG4gIHBpcGVsaW5lczogJ1BpcGVsaW5lJyxcbiAgcGx1Z2luczogJ1BsdWdpbicsXG4gIHN0b3JhZ2VCeXRlczogJ1JlZ2lzdHJ5IHN0b3JhZ2UnLFxuICBkYXNoYm9hcmRzOiAnRGFzaGJvYXJkJyxcbiAgYWxlcnRSdWxlczogJ0FsZXJ0IHJ1bGUnLFxuICBhbGVydERlc3RpbmF0aW9uczogJ0FsZXJ0IGRlc3RpbmF0aW9uJyxcbiAgaWRwQ29uZmlnczogJ0lkUCBjb25maWd1cmF0aW9uJyxcbiAgbGlzdGluZ3M6ICdQbHVnaW4gbGlzdGluZycsXG59O1xuXG4vKipcbiAqIENyZWF0ZSBtaWRkbGV3YXJlIHRoYXQgY2hlY2tzIGEgc3BlY2lmaWMgcXVvdGEgdHlwZSBiZWZvcmUgcHJvY2VlZGluZy5cbiAqXG4gKiBPbiBxdW90YSBleGNlZWRlZCwgcmV0dXJucyBhIDQyOSByZXNwb25zZSB3aXRoIHF1b3RhIGRldGFpbHMuXG4gKiBPbiBxdW90YSBzZXJ2aWNlIGZhaWx1cmUsIGZhaWxzIG9wZW4gKGFsbG93cyB0aGUgcmVxdWVzdCkuXG4gKlxuICogQHBhcmFtIHF1b3RhU2VydmljZSAtIFF1b3RhIHNlcnZpY2UgY2xpZW50XG4gKiBAcGFyYW0gcXVvdGFUeXBlIC0gV2hpY2ggcXVvdGEgdG8gY2hlY2sgKGUuZy4gJ2FwaUNhbGxzJywgJ3BpcGVsaW5lcycsICdwbHVnaW5zJylcbiAqIEByZXR1cm5zIEV4cHJlc3MgbWlkZGxld2FyZVxuICovXG5leHBvcnQgZnVuY3Rpb24gY2hlY2tRdW90YShcbiAgcXVvdGFTZXJ2aWNlOiBRdW90YVNlcnZpY2UsXG4gIHF1b3RhVHlwZTogUXVvdGFUeXBlLFxuKSB7XG4gIHJldHVybiBhc3luYyAocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlLCBuZXh0OiBOZXh0RnVuY3Rpb24pOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICAvLyBUaGUgY2hlY2sgaXMgc2VudCB3aXRoIFRISVMgc2VydmljZSdzIGNyZWRlbnRpYWxzLCBzbyBpdCBtdXN0IG9ubHkgcnVuIGZvclxuICAgIC8vIGEgVkVSSUZJRUQgY2FsbGVyIOKAlCBvdGhlcndpc2UgdGhlIG9yZyB3b3VsZCBjb21lIGZyb20gdGhlIHNwb29mYWJsZVxuICAgIC8vIGhlYWRlci1kZXJpdmVkIGlkZW50aXR5LiBNb3VudGVkIGFmdGVyIHJlcXVpcmVBdXRoIGV2ZXJ5d2hlcmU7IGFuXG4gICAgLy8gdW5hdXRoZW50aWNhdGVkIHJlcXVlc3QgaGVyZSBpcyBhIHdpcmluZyBidWcsIHNvIHNraXAgKGZhaWwgb3BlbikgbG91ZGx5LlxuICAgIGlmICghcmVxLnVzZXIpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdRdW90YSBjaGVjayBza2lwcGVkOiByZXF1ZXN0IGlzIG5vdCBhdXRoZW50aWNhdGVkJyk7XG4gICAgICByZXR1cm4gbmV4dCgpO1xuICAgIH1cblxuICAgIGxldCBvcmdJZDogc3RyaW5nIHwgdW5kZWZpbmVkO1xuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGN0eCA9IGdldENvbnRleHQocmVxKTtcbiAgICAgIG9yZ0lkID0gY3R4LmlkZW50aXR5Lm9yZ0lkO1xuICAgIH0gY2F0Y2gge1xuICAgICAgLy8gQ29udGV4dCBtaWRkbGV3YXJlIG5vdCBhcHBsaWVkIOKAlCBmYWlsIG9wZW4gKGxvZyBhbmQgY29udGludWUpXG4gICAgICBsb2dnZXIud2FybignUXVvdGEgY2hlY2sgc2tpcHBlZDogcmVxdWVzdCBjb250ZXh0IG5vdCBpbml0aWFsaXplZCcpO1xuICAgICAgcmV0dXJuIG5leHQoKTtcbiAgICB9XG5cbiAgICBpZiAoIW9yZ0lkKSB7XG4gICAgICBzZW5kRXJyb3IocmVzLCA0MDAsICdPcmdhbml6YXRpb24gSUQgaXMgcmVxdWlyZWQgZm9yIHF1b3RhIGNoZWNrJywgRXJyb3JDb2RlLlZBTElEQVRJT05fRVJST1IpO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHRyeSB7XG4gICAgICAvLyBBdXRoZW50aWNhdGUgYXMgVEhJUyBzZXJ2aWNlLCBub3QgYnkgZm9yd2FyZGluZyB0aGUgdXNlcidzIHRva2VuOiB0aGVcbiAgICAgIC8vIHVzZXIgdG9rZW4gNDAzcyBhdCB0aGUgcXVvdGEgcmVhZCByb3V0ZSBmb3IgY3VzdG9tIHJvbGVzIHdpdGhvdXRcbiAgICAgIC8vIGBxdW90YXM6cmVhZGAsIGFuZCAoYmVpbmcgbm9uLXNlcnZpY2UpIGNvdW50cyBhZ2FpbnN0IHRoZSBxdW90YVxuICAgICAgLy8gc2VydmljZSdzIHBlci1JUCBsaW1pdGVyIGtleWVkIG9uIFRISVMgcG9kJ3MgSVAg4oCUIGJvdGggb2Ygd2hpY2hcbiAgICAgIC8vIHNpbGVudGx5IGZhaWwgdGhlIGdhdGUgb3BlbiB1bmRlciBsb2FkLlxuICAgICAgY29uc3QgcXVvdGFTdGF0dXMgPSBhd2FpdCBxdW90YVNlcnZpY2UuY2hlY2sob3JnSWQsIHF1b3RhVHlwZSwgZ2V0UXVvdGFTZXJ2aWNlQXV0aEhlYWRlcihvcmdJZCkpO1xuXG4gICAgICBpZiAoIXF1b3RhU3RhdHVzLmFsbG93ZWQpIHtcbiAgICAgICAgbG9nZ2VyLndhcm4oYCR7cXVvdGFUeXBlfSBxdW90YSBleGNlZWRlZGAsIHtcbiAgICAgICAgICBvcmdJZCxcbiAgICAgICAgICBsaW1pdDogcXVvdGFTdGF0dXMubGltaXQsXG4gICAgICAgICAgdXNlZDogcXVvdGFTdGF0dXMudXNlZCxcbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gUm91dGUgdGhyb3VnaCB0aGUgc2hhcmVkIGhlbHBlciBzbyB0aGUgY2hlY2stZ2F0ZSA0MjkgZW1pdHMgdGhlIHNhbWVcbiAgICAgICAgLy8gUmV0cnktQWZ0ZXIgKyBYLVF1b3RhLSogaGVhZGVycyBhcyB0aGUgcmVzZXJ2ZS1wYXRoIDQyOSAodGhlIGlubGluZVxuICAgICAgICAvLyA0MjkgdGhpcyByZXBsYWNlZCBvbWl0dGVkIHRoZW0pLiBDdXN0b20gbWVzc2FnZSBrZWVwcyB0aGVcbiAgICAgICAgLy8gXCJjb250YWN0IHlvdXIgYWRtaW5pc3RyYXRvclwiIGNvcHkuXG4gICAgICAgIHNlbmRRdW90YUV4Y2VlZGVkKFxuICAgICAgICAgIHJlcyxcbiAgICAgICAgICBxdW90YVR5cGUsXG4gICAgICAgICAgeyB0eXBlOiBxdW90YVR5cGUsIGxpbWl0OiBxdW90YVN0YXR1cy5saW1pdCwgdXNlZDogcXVvdGFTdGF0dXMudXNlZCwgcmVtYWluaW5nOiBxdW90YVN0YXR1cy5yZW1haW5pbmcgfSxcbiAgICAgICAgICBxdW90YVN0YXR1cy5yZXNldEF0LFxuICAgICAgICAgIGAke1FVT1RBX0xBQkVMU1txdW90YVR5cGVdfSBxdW90YSBleGNlZWRlZC4gUGxlYXNlIGNvbnRhY3QgeW91ciBhZG1pbmlzdHJhdG9yIHRvIGluY3JlYXNlIHlvdXIgcXVvdGEuYCxcbiAgICAgICAgKTtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICBuZXh0KCk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIC8vIEZhaWwgb3BlbiDigJQgYWxsb3cgdGhlIHJlcXVlc3QgaWYgcXVvdGEgc2VydmljZSBpcyB1bmF2YWlsYWJsZVxuICAgICAgbG9nZ2VyLndhcm4oJ1FVT1RBX0ZBSUxfT1BFTjogUXVvdGEgY2hlY2sgZXhjZXB0aW9uLCBhbGxvd2luZyByZXF1ZXN0Jywge1xuICAgICAgICBvcmdJZCxcbiAgICAgICAgcXVvdGFUeXBlLFxuICAgICAgICBlcnJvcjogZXJyb3JNZXNzYWdlKGVycm9yKSxcbiAgICAgIH0pO1xuICAgICAgLy8gU2FtZSBjb3VudGVyIHRoZSBhcGktY29yZSBxdW90YSBjbGllbnQgZW1pdHMsIHNvIGEgcXVvdGEtc2VydmljZSBvdXRhZ2VcbiAgICAgIC8vIGlzIEFMRVJUQUJMRSBmcm9tIG9uZSBzZXJpZXMgcmVnYXJkbGVzcyBvZiB3aGljaCBsYXllciBmZWxsIG9wZW4uIE9ubHlcbiAgICAgIC8vIGEgbG9nIGxpbmUgaGVyZSBtZWFudCBldmVyeSBvcmcgd2FzIHNpbGVudGx5IHVuLWJpbGxlZCBmb3IgdGhlIGR1cmF0aW9uLlxuICAgICAgZW1pdENvdW50ZXIoJ3F1b3RhX2ZhaWxfb3Blbl90b3RhbCcsIHsgb3BlcmF0aW9uOiAnY2hlY2snLCByZWFzb246ICdleGNlcHRpb24nLCBxdW90YVR5cGUgfSk7XG4gICAgICBuZXh0KCk7XG4gICAgfVxuICB9O1xufVxuIl19
244
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2stcXVvdGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXBpL2NoZWNrLXF1b3RhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEMsT0FBTyxFQUFFLHFCQUFxQixFQUFFLGtCQUFrQixFQUFFLFlBQVksRUFBRSxXQUFXLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxFQUFFLHlCQUF5QixFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLGlCQUFpQixFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUdqUSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDOUMsT0FBTyxFQUFFLDhCQUE4QixFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFFN0UsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0FBRTNDLHVCQUF1QjtBQUN2QixFQUFFO0FBQ0YsNkVBQTZFO0FBQzdFLGdGQUFnRjtBQUNoRixvQ0FBb0M7QUFDcEMsOEVBQThFO0FBQzlFLDZFQUE2RTtBQUM3RSw2RUFBNkU7QUFDN0UscUVBQXFFO0FBQ3JFLHlEQUF5RDtBQUN6RCwyRUFBMkU7QUFDM0UsZ0VBQWdFO0FBQ2hFLGtGQUFrRjtBQUNsRiw4RUFBOEU7QUFDOUUsdUVBQXVFO0FBQ3ZFLDhFQUE4RTtBQUM5RSwrRUFBK0U7QUFDL0UsMkJBQTJCO0FBRTNCLGtFQUFrRTtBQUNsRSxNQUFNLENBQUMsTUFBTSx3QkFBd0IsR0FBRyxNQUFNLENBQUMsMEJBQTBCLEVBQUUsSUFBSSxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7QUFFN0YsNEZBQTRGO0FBQzVGLE1BQU0sQ0FBQyxNQUFNLDJCQUEyQixHQUFHLE1BQU0sQ0FBQyw2QkFBNkIsRUFBRSxLQUFLLEVBQUUsRUFBRSxHQUFHLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FB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O0FBQ2xELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuaW1wb3J0IHsgYXJtRGVmZXJyZWRRdW90YUNoZWNrLCBjYWxsUXVvdGFFeGVtcHRpb24sIGNyZWF0ZUxvZ2dlciwgZW1pdENvdW50ZXIsIGVuZm9yY2VkUXVvdGFMaW1pdCwgZW52SW50LCBnZXRRdW90YVNlcnZpY2VBdXRoSGVhZGVyLCBpc1F1b3RhRXhlbXB0Um91dGUsIG1ldGVyZWRRdW90YVR5cGVGb3IsIHNlbmRRdW90YUV4Y2VlZGVkLCBlcnJvck1lc3NhZ2UsIHRhZ1JvdXRlR2F0ZSB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL2FwaS1jb3JlJztcbmltcG9ydCB0eXBlIHsgUXVvdGFDaGVja1Jlc3VsdCwgUXVvdGFUeXBlLCBRdW90YVNlcnZpY2UgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgdHlwZSB7IFJlcXVlc3QsIFJlcXVlc3RIYW5kbGVyLCBSZXNwb25zZSwgTmV4dEZ1bmN0aW9uIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBnZXRDb250ZXh0IH0gZnJvbSAnLi9nZXQtY29udGV4dC5qcyc7XG5pbXBvcnQgeyBoYXNDb21wbGV0ZWRJZGVtcG90ZW50UmVzcG9uc2UgfSBmcm9tICcuL2lkZW1wb3RlbmN5LW1pZGRsZXdhcmUuanMnO1xuXG5jb25zdCBsb2dnZXIgPSBjcmVhdGVMb2dnZXIoJ2NoZWNrLXF1b3RhJyk7XG5cbi8vIFBFUi1QT0QgQ0hFQ0sgQ0FDSEUuXG4vL1xuLy8gVGhlIGdhdGUgdXNlZCB0byBhc2sgdGhlIHF1b3RhIHNlcnZpY2Ugb24gRVZFUlkgbWV0ZXJlZCByZXF1ZXN0IOKAlCBhIEdFVCwgYVxuLy8gSldUIHZlcmlmeSBhbmQgYSBoaWVyYXJjaHkgcmVzb2x2ZSBvdmVyIHRoZXJlLCBwZXIgcmVxdWVzdCBoZXJlLiBBIHJlYWRpbmcgaXNcbi8vIG5vdyByZXVzZWQgcGVyIChvcmcsIHF1b3RhIHR5cGUpOlxuLy8gIC0gYW4gQUxMT1dFRCByZWFkaW5nIGZvciBgUVVPVEFfQ0hFQ0tfQ0FDSEVfVFRMX01TYCAoZGVmYXVsdCA1MDAwOyAwIHR1cm5zXG4vLyAgICB0aGUgY2FjaGUgb2ZmKSwgYW5kIG9ubHkgd2hpbGUgdGhpcyBwb2QgaGFzIHBhc3NlZCBmZXdlciByZXF1ZXN0cyBvbiBpdFxuLy8gICAgdGhhbiB0aGUgcmVhZGluZyBoYWQgaGVhZHJvb20gZm9yIChgZW5mb3JjZWRRdW90YUxpbWl0IOKIkiB1c2VkYCksIHNvIG9uZVxuLy8gICAgcG9kIGNhbm5vdCBydW4gZmFyIHBhc3QgYSBuZWFybHktZnVsbCBjb3VudGVyIG9uIGEgc3RhbGUgXCJ5ZXNcIjtcbi8vICAtIGEgREVOSUVEIHJlYWRpbmcgdW50aWwgdGhlIHBlcmlvZCByZXNldHMsIGNhcHBlZCBhdFxuLy8gICAgYFFVT1RBX0NIRUNLX0RFTklFRF9DQUNIRV9NU2AgKGRlZmF1bHQgNjAwMDApIHNvIGFuIHVwZ3JhZGUsIGEgYnVuZGxlXG4vLyAgICBwdXJjaGFzZSBvciBhbiBvcGVyYXRvciByZXNldCBpcyBob25vdXJlZCB3aXRoaW4gYSBtaW51dGU7XG4vLyAgLSBhIGZhaWwtb3BlbiBzZW50aW5lbCBpcyBORVZFUiBjYWNoZWQg4oCUIHJlY292ZXJ5IGlzIHNlZW4gb24gdGhlIG5leHQgcmVxdWVzdC5cbi8vIFN0YWxlbmVzcyBpcyBib3VuZGVkIGFuZCBhY2NlcHRlZDogdGhlIGNvdW50ZXIgaXRzZWxmIG5ldmVyIGV4Y2VlZHMgdGhlIGNhcFxuLy8gKHRoZSBtZXRlcmVkIGluY3JlbWVudCBhZG1pdHMgb25seSB3aGF0IGZpdHMgYW5kIHJlY29yZHMgdGhlIHJlc3QgYXNcbi8vIG92ZXJzaG9vdCksIHNvIGEgc3RhbGUgXCJ5ZXNcIiBjb3N0cyBhdCBtb3N0IGEgZmV3IHVuYmlsbGVkLW92ZXItY2FwIHJlcXVlc3RzXG4vLyBwZXIgcG9kIHBlciBUVEwsIG5ldmVyIGFuIHVuYm91bmRlZCBydW4uIENvbmN1cnJlbnQgbWlzc2VzIGZvciBvbmUga2V5IHNoYXJlXG4vLyBhIHNpbmdsZSBpbi1mbGlnaHQgcmVhZC5cblxuLyoqIFRUTCAobXMpIG9mIGEgY2FjaGVkIEFMTE9XRUQgY2hlY2suIGAwYCBkaXNhYmxlcyB0aGUgY2FjaGUuICovXG5leHBvcnQgY29uc3QgUVVPVEFfQ0hFQ0tfQ0FDSEVfVFRMX01TID0gZW52SW50KCdRVU9UQV9DSEVDS19DQUNIRV9UVExfTVMnLCA1MDAwLCB7IG1pbjogMCB9KTtcblxuLyoqIExvbmdlc3QgKG1zKSBhIERFTklFRCBjaGVjayBpcyByZXVzZWQgYmVmb3JlIHJlLWFza2luZyAoaXQgbmV2ZXIgb3V0bGl2ZXMgYHJlc2V0QXRgKS4gKi9cbmV4cG9ydCBjb25zdCBRVU9UQV9DSEVDS19ERU5JRURfQ0FDSEVfTVMgPSBlbnZJbnQoJ1FVT1RBX0NIRUNLX0RFTklFRF9DQUNIRV9NUycsIDYwMDAwLCB7IG1pbjogMCB9KTtcblxuLyoqIEJvdW5kIG9uIGNhY2hlZCAob3JnLCB0eXBlKSByZWFkaW5ncyBwZXIgcG9kLiAqL1xuY29uc3QgQ0hFQ0tfQ0FDSEVfTUFYX0VOVFJJRVMgPSAxMF8wMDA7XG5cbmludGVyZmFjZSBDYWNoZWRDaGVjayB7XG4gIHN0YXR1czogUXVvdGFDaGVja1Jlc3VsdDtcbiAgZXhwaXJlc0F0OiBudW1iZXI7XG4gIC8qKiBSZXF1ZXN0cyB0aGlzIHBvZCBsZXQgdGhyb3VnaCBvbiB0aGlzIHJlYWRpbmcuICovXG4gIHBhc3NlczogbnVtYmVyO1xufVxuXG5jb25zdCBjaGVja0NhY2hlID0gbmV3IE1hcDxzdHJpbmcsIENhY2hlZENoZWNrPigpO1xuY29uc3QgaW5GbGlnaHRDaGVja3MgPSBuZXcgTWFwPHN0cmluZywgUHJvbWlzZTxRdW90YUNoZWNrUmVzdWx0Pj4oKTtcblxuLyoqIERyb3AgZXZlcnkgY2FjaGVkIGNoZWNrICh0ZXN0czsgYWxzbyBhIHNhZmUgb3BlcmF0aW9uYWwgcmVzZXQpLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNsZWFyUXVvdGFDaGVja0NhY2hlKCk6IHZvaWQge1xuICBjaGVja0NhY2hlLmNsZWFyKCk7XG4gIGluRmxpZ2h0Q2hlY2tzLmNsZWFyKCk7XG59XG5cbi8qKiBBIHVzYWJsZSBjYWNoZWQgcmVhZGluZyBmb3IgYGtleWAsIGNvdW50aW5nIHRoaXMgcmVxdWVzdCBhZ2FpbnN0IGl0cyBoZWFkcm9vbS4gKi9cbmZ1bmN0aW9uIGNhY2hlZENoZWNrKGtleTogc3RyaW5nLCBxdW90YVR5cGU6IFF1b3RhVHlwZSk6IFF1b3RhQ2hlY2tSZXN1bHQgfCBudWxsIHtcbiAgY29uc3QgaGl0ID0gY2hlY2tDYWNoZS5nZXQoa2V5KTtcbiAgaWYgKCFoaXQpIHJldHVybiBudWxsO1xuICBpZiAoaGl0LmV4cGlyZXNBdCA8PSBEYXRlLm5vdygpKSB7XG4gICAgY2hlY2tDYWNoZS5kZWxldGUoa2V5KTtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICBpZiAoIWhpdC5zdGF0dXMuYWxsb3dlZCkgcmV0dXJuIGhpdC5zdGF0dXM7XG4gIGlmIChoaXQuc3RhdHVzLmxpbWl0ICE9PSAtMSkge1xuICAgIGNvbnN0IGhlYWRyb29tID0gZW5mb3JjZWRRdW90YUxpbWl0KHF1b3RhVHlwZSwgaGl0LnN0YXR1cy5saW1pdCkgLSBoaXQuc3RhdHVzLnVzZWQ7XG4gICAgaWYgKGhpdC5wYXNzZXMgPj0gaGVhZHJvb20pIHtcbiAgICAgIGNoZWNrQ2FjaGUuZGVsZXRlKGtleSk7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gIH1cbiAgaGl0LnBhc3NlcyArPSAxO1xuICByZXR1cm4gaGl0LnN0YXR1cztcbn1cblxuZnVuY3Rpb24gcmVtZW1iZXJDaGVjayhrZXk6IHN0cmluZywgc3RhdHVzOiBRdW90YUNoZWNrUmVzdWx0KTogdm9pZCB7XG4gIGlmICghc3RhdHVzIHx8IHN0YXR1cy5mYWlsT3BlbikgcmV0dXJuO1xuICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICBsZXQgZXhwaXJlc0F0OiBudW1iZXI7XG4gIGlmIChzdGF0dXMuYWxsb3dlZCkge1xuICAgIGlmIChRVU9UQV9DSEVDS19DQUNIRV9UVExfTVMgPT09IDApIHJldHVybjtcbiAgICBleHBpcmVzQXQgPSBub3cgKyBRVU9UQV9DSEVDS19DQUNIRV9UVExfTVM7XG4gIH0gZWxzZSB7XG4gICAgaWYgKFFVT1RBX0NIRUNLX0RFTklFRF9DQUNIRV9NUyA9PT0gMCkgcmV0dXJuO1xuICAgIGNvbnN0IHJlc2V0QXQgPSBEYXRlLnBhcnNlKHN0YXR1cy5yZXNldEF0KTtcbiAgICBleHBpcmVzQXQgPSBNYXRoLm1pbihub3cgKyBRVU9UQV9DSEVDS19ERU5JRURfQ0FDSEVfTVMsIE51bWJlci5pc0Zpbml0ZShyZXNldEF0KSA/IHJlc2V0QXQgOiBub3cpO1xuICAgIGlmIChleHBpcmVzQXQgPD0gbm93KSByZXR1cm47XG4gIH1cbiAgaWYgKGNoZWNrQ2FjaGUuc2l6ZSA+PSBDSEVDS19DQUNIRV9NQVhfRU5UUklFUykge1xuICAgIC8vIE1hcCBwcmVzZXJ2ZXMgaW5zZXJ0aW9uIG9yZGVyLCBzbyB0aGUgZmlyc3Qga2V5IGlzIHRoZSBvbGRlc3QgcmVhZGluZy5cbiAgICBjb25zdCBvbGRlc3QgPSBjaGVja0NhY2hlLmtleXMoKS5uZXh0KCk7XG4gICAgaWYgKCFvbGRlc3QuZG9uZSkgY2hlY2tDYWNoZS5kZWxldGUob2xkZXN0LnZhbHVlKTtcbiAgfVxuICAvLyBUaGUgcmVxdWVzdCB0aGF0IGZldGNoZWQgdGhlIHJlYWRpbmcgaXMgaXRzIGZpcnN0IHBhc3MuXG4gIGNoZWNrQ2FjaGUuc2V0KGtleSwgeyBzdGF0dXMsIGV4cGlyZXNBdCwgcGFzc2VzOiBzdGF0dXMuYWxsb3dlZCA/IDEgOiAwIH0pO1xufVxuXG4vKipcbiAqIE9uZSBxdW90YSByZWFkIGZvciAob3JnLCB0eXBlKSwgc2hhcmVkIGJ5IGV2ZXJ5IGNvbmN1cnJlbnQgbWlzcyBvbiB0aGlzIHBvZC5cbiAqIEEgcmVxdWVzdCB0aGF0IHdhaXRlZCBvbiBzb21lb25lIGVsc2UncyByZWFkIHRoZW4gZ29lcyBiYWNrIHRocm91Z2ggdGhlXG4gKiBjYWNoZSwgc28gaXQgaXMgY291bnRlZCBhZ2FpbnN0IHRoZSByZWFkaW5nJ3MgaGVhZHJvb20gbGlrZSBhbnkgb3RoZXIgcGFzc1xuICogKGBqb2luZWRgIHN0b3BzIGl0IGZyb20gd2FpdGluZyBhIHNlY29uZCB0aW1lKS5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gcmVhZENoZWNrKHF1b3RhU2VydmljZTogUXVvdGFTZXJ2aWNlLCBvcmdJZDogc3RyaW5nLCBxdW90YVR5cGU6IFF1b3RhVHlwZSwgam9pbmVkID0gZmFsc2UpOiBQcm9taXNlPFF1b3RhQ2hlY2tSZXN1bHQ+IHtcbiAgY29uc3Qga2V5ID0gYCR7b3JnSWR9XFx1MDAwMCR7cXVvdGFUeXBlfWA7XG4gIGNvbnN0IGNhY2hlZCA9IGNhY2hlZENoZWNrKGtleSwgcXVvdGFUeXBlKTtcbiAgaWYgKGNhY2hlZCkgcmV0dXJuIGNhY2hlZDtcbiAgY29uc3QgcGVuZGluZyA9IGluRmxpZ2h0Q2hlY2tzLmdldChrZXkpO1xuICBpZiAocGVuZGluZykge1xuICAgIGlmIChqb2luZWQpIHJldHVybiBwZW5kaW5nO1xuICAgIGNvbnN0IHN0YXR1cyA9IGF3YWl0IHBlbmRpbmc7XG4gICAgLy8gQSBmYWlsLW9wZW4gKG9yIHVuY2FjaGVhYmxlKSByZWFkaW5nIGhhcyBub3RoaW5nIHRvIGFjY291bnQgYWdhaW5zdC5cbiAgICByZXR1cm4gY2hlY2tDYWNoZS5oYXMoa2V5KSA/IHJlYWRDaGVjayhxdW90YVNlcnZpY2UsIG9yZ0lkLCBxdW90YVR5cGUsIHRydWUpIDogc3RhdHVzO1xuICB9XG4gIC8vIEF1dGhlbnRpY2F0ZSBhcyBUSElTIHNlcnZpY2UsIG5vdCBieSBmb3J3YXJkaW5nIHRoZSB1c2VyJ3MgdG9rZW46IHRoZSB1c2VyXG4gIC8vIHRva2VuIDQwM3MgYXQgdGhlIHF1b3RhIHJlYWQgcm91dGUgZm9yIGN1c3RvbSByb2xlcyB3aXRob3V0IGBxdW90YXM6cmVhZGAsXG4gIC8vIGFuZCAoYmVpbmcgbm9uLXNlcnZpY2UpIGNvdW50cyBhZ2FpbnN0IHRoZSBxdW90YSBzZXJ2aWNlJ3MgcGVyLUlQIGxpbWl0ZXJcbiAgLy8ga2V5ZWQgb24gVEhJUyBwb2QncyBJUCDigJQgYm90aCBvZiB3aGljaCBzaWxlbnRseSBmYWlsIHRoZSBnYXRlIG9wZW4gdW5kZXIgbG9hZC5cbiAgY29uc3QgcmVhZCA9IHF1b3RhU2VydmljZS5jaGVjayhvcmdJZCwgcXVvdGFUeXBlLCBnZXRRdW90YVNlcnZpY2VBdXRoSGVhZGVyKG9yZ0lkKSlcbiAgICAudGhlbigoc3RhdHVzKSA9PiB7XG4gICAgICByZW1lbWJlckNoZWNrKGtleSwgc3RhdHVzKTtcbiAgICAgIHJldHVybiBzdGF0dXM7XG4gICAgfSlcbiAgICAuZmluYWxseSgoKSA9PiBpbkZsaWdodENoZWNrcy5kZWxldGUoa2V5KSk7XG4gIGluRmxpZ2h0Q2hlY2tzLnNldChrZXksIHJlYWQpO1xuICByZXR1cm4gcmVhZDtcbn1cblxuLyoqXG4gKiBDcmVhdGUgbWlkZGxld2FyZSB0aGF0IGNoZWNrcyBhIHNwZWNpZmljIHF1b3RhIHR5cGUgYmVmb3JlIHByb2NlZWRpbmcuXG4gKlxuICogT24gcXVvdGEgZXhjZWVkZWQsIHJldHVybnMgYSA0MjkgcmVzcG9uc2Ugd2l0aCBxdW90YSBkZXRhaWxzIChxdW90YVR5cGUsXG4gKiBsaW1pdCwgdXNlZCwgcmVzZXRBdCwgdXBncmFkZVVybCDigJQgc2VlIGFwaS1jb3JlIGBzZW5kUXVvdGFFeGNlZWRlZGApLlxuICogT24gcXVvdGEgc2VydmljZSBmYWlsdXJlLCBmYWlscyBvcGVuIChhbGxvd3MgdGhlIHJlcXVlc3QpLlxuICpcbiAqIGBhcGlDYWxsc2AgaXMgc3BsaXQgcGVyIHJlcXVlc3QgYnkgcHJpbmNpcGFsOiBhbiBvcmcgc2VydmljZSBhY2NvdW50IG9yIGFcbiAqIHBlcnNvbmFsIGFjY2VzcyBrZXkgaXMgY2hlY2tlZCBhZ2FpbnN0IGBhdXRvbWF0aW9uQ2FsbHNgIGluc3RlYWRcbiAqICh7QGxpbmsgbWV0ZXJlZFF1b3RhVHlwZUZvcn0pLCB0aGVcbiAqIHNhbWUgdHlwZSBgbWV0ZXJRdW90YU9uU3VjY2Vzc2AgaW5jcmVtZW50cyDigJQgc28gbW91bnQgYm90aCB3aXRoIGAnYXBpQ2FsbHMnYC5cbiAqXG4gKiBTa2lwcGVkIChwYXNzZXMgdGhyb3VnaCkgZXhhY3RseSB3aGVyZSB0aGUgbWV0ZXIgc2tpcHMsIHNvIHRoZSBnYXRlIG5ldmVyXG4gKiByZWZ1c2VzIGEgY2FsbCB0aGUgbWV0ZXIgd291bGQgbm90IGJpbGwgKGBjYWxsUXVvdGFFeGVtcHRpb25gKTpcbiAqIC0gYSBTRVJWSUNFIFBSSU5DSVBBTCAocGVlci1zZXJ2aWNlIHRyYWZmaWMgaXMgbmV2ZXIgbWV0ZXJlZCwgc28gYSB0ZW5hbnQgYXRcbiAqICAgaXRzIGxpbWl0IG11c3Qgbm90IGJyZWFrIHRoZSBpbnRlcm5hbCBjYWxscyBpdHMgb3duIGVhcmxpZXIgYWN0aW9ucyBjYXVzZWQpO1xuICogLSBmb3IgYGFwaUNhbGxzYDogYW4gSU1QRVJTT05BVElPTiBzZXNzaW9uICh2ZW5kb3Igc3RhZmYgZGlhZ25vc2luZyBhblxuICogICBleGhhdXN0ZWQgb3JnIG11c3Qgbm90IGJlIGxvY2tlZCBvdXQgYnkgaXQpIGFuZCBhbiBPTi1CRUhBTEYgaG9wIChhIHVzZXJcbiAqICAgdG9rZW4gZm9yd2FyZGVkIGJ5IGFzay9waXBlbGluZSB3aXRoIGEgc2lnbmVkIG1hcmtlciDigJQgdGhlIG9yaWdpbmF0aW5nXG4gKiAgIHJlcXVlc3Qgd2FzIGFscmVhZHkgZ2F0ZWQgYW5kIG1ldGVyZWQpO1xuICogLSBhbiBgYXBpQ2FsbHNgIHJlcXVlc3Qgb24gdGhlIGZsZWV0LXdpZGUgYFFVT1RBX0VYRU1QVF9ST1VURVNgIChjb25zb2xlXG4gKiAgIHBvbGxpbmcg4oCUIGFuIGV4aGF1c3RlZCBvcmcgbXVzdCBzdGlsbCByZW5kZXIgdGhlIGJhbm5lciBzYXlpbmcgc28pO1xuICogLSBhbiBpZGVtcG90ZW50IFJFVFJZIHdob3NlIG9yaWdpbmFsIGFscmVhZHkgY29tcGxldGVkICh0aGUgcmVwbGF5IGlzIHNlcnZlZFxuICogICBmcm9tIHRoZSBpZGVtcG90ZW5jeSBjYWNoZSwgZG9lcyBubyB3b3JrIGFuZCBpcyBuZXZlciBtZXRlcmVkOyByZWZ1c2luZyBpdFxuICogICB3b3VsZCA0MjkgYSBjbGllbnQgZm9yIGFza2luZyB3aGF0IGl0cyBvd24gZWFybGllciByZXF1ZXN0IHJldHVybmVkKS5cbiAqXG4gKiBSZWFkaW5ncyBhcmUgY2FjaGVkIHBlciBwb2QgKHNlZSB0aGUgY2FjaGUgbm90ZXMgYWJvdmUpLCBhbmQgdGhlIHJlYWQgaXRzZWxmXG4gKiBoYXMgYSBzaG9ydCBidWRnZXQgKGFwaS1jb3JlIGBRVU9UQV9DSEVDS19USU1FT1VUX01TYCwgbm8gcmV0cnkpIOKAlCBhIHNsb3dcbiAqIHF1b3RhIHNlcnZpY2UgZmFpbHMgb3BlbiBxdWlja2x5IGluc3RlYWQgb2Ygc3RhbGxpbmcgZXZlcnkgcmVxdWVzdC5cbiAqXG4gKiBNb3VudGFibGUgYXQgdGhlIGFwcC1yb3V0ZXMgbGV2ZWwgKGBhcHAudXNlKHByZWZpeCwg4oCmYXV0aCwgY2hlY2tRdW90YSguLi4pKWApOlxuICogdGhlIG9yZyBjb21lcyBmcm9tIHRoZSByZXF1ZXN0IGNvbnRleHQgd2hlbiBvbmUgaXMgYXR0YWNoZWQsIGVsc2UgZnJvbSB0aGVcbiAqIHZlcmlmaWVkIGByZXEudXNlci5vcmdhbml6YXRpb25JZGAg4oCUIHRoZSBzYW1lIGZhbGxiYWNrIHRoZSBtZXRlciB1c2VzLlxuICpcbiAqIEBwYXJhbSBxdW90YVNlcnZpY2UgLSBRdW90YSBzZXJ2aWNlIGNsaWVudFxuICogQHBhcmFtIHF1b3RhVHlwZSAtIFdoaWNoIHF1b3RhIHRvIGNoZWNrIChlLmcuICdhcGlDYWxscycsICdwaXBlbGluZXMnLCAncGx1Z2lucycpXG4gKiBAcmV0dXJucyBFeHByZXNzIG1pZGRsZXdhcmVcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNoZWNrUXVvdGEoXG4gIHF1b3RhU2VydmljZTogUXVvdGFTZXJ2aWNlLFxuICBiYXNlUXVvdGFUeXBlOiBRdW90YVR5cGUsXG4pIHtcbiAgcmV0dXJuIHRhZ1JvdXRlR2F0ZShhc3luYyAocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlLCBuZXh0OiBOZXh0RnVuY3Rpb24pOiBQcm9taXNlPHZvaWQ+ID0+IHtcbiAgICAvLyBUaGUgY2hlY2sgaXMgc2VudCB3aXRoIFRISVMgc2VydmljZSdzIGNyZWRlbnRpYWxzLCBzbyBpdCBtdXN0IG9ubHkgcnVuIGZvclxuICAgIC8vIGEgVkVSSUZJRUQgY2FsbGVyIOKAlCBvdGhlcndpc2UgdGhlIG9yZyB3b3VsZCBjb21lIGZyb20gdGhlIHNwb29mYWJsZVxuICAgIC8vIGhlYWRlci1kZXJpdmVkIGlkZW50aXR5LiBNb3VudGVkIGFmdGVyIHJlcXVpcmVBdXRoIGV2ZXJ5d2hlcmU7IGFuXG4gICAgLy8gdW5hdXRoZW50aWNhdGVkIHJlcXVlc3QgaGVyZSBpcyBhIHdpcmluZyBidWcsIHNvIHNraXAgKGZhaWwgb3BlbikgbG91ZGx5LlxuICAgIGlmICghcmVxLnVzZXIpIHtcbiAgICAgIGxvZ2dlci53YXJuKCdRdW90YSBjaGVjayBza2lwcGVkOiByZXF1ZXN0IGlzIG5vdCBhdXRoZW50aWNhdGVkJyk7XG4gICAgICByZXR1cm4gbmV4dCgpO1xuICAgIH1cblxuICAgIGNvbnN0IGV4ZW1wdGlvbiA9IGNhbGxRdW90YUV4ZW1wdGlvbihyZXEpO1xuICAgIGlmIChleGVtcHRpb24gPT09ICdzZXJ2aWNlJyB8fCAoZXhlbXB0aW9uICYmIGJhc2VRdW90YVR5cGUgPT09ICdhcGlDYWxscycpKSByZXR1cm4gbmV4dCgpO1xuICAgIGlmIChiYXNlUXVvdGFUeXBlID09PSAnYXBpQ2FsbHMnICYmIGlzUXVvdGFFeGVtcHRSb3V0ZShyZXEubWV0aG9kLCByZXF1ZXN0UGF0aChyZXEpKSkgcmV0dXJuIG5leHQoKTtcblxuICAgIGNvbnN0IHF1b3RhVHlwZSA9IG1ldGVyZWRRdW90YVR5cGVGb3IocmVxLCBiYXNlUXVvdGFUeXBlKTtcbiAgICBsZXQgb3JnSWQ6IHN0cmluZyB8IHVuZGVmaW5lZDtcblxuICAgIHRyeSB7XG4gICAgICBvcmdJZCA9IGdldENvbnRleHQocmVxKS5pZGVudGl0eS5vcmdJZDtcbiAgICB9IGNhdGNoIHtcbiAgICAgIC8vIE5vIHJlcXVlc3QgY29udGV4dCAocGxhdGZvcm0gYXR0YWNoZXMgbm9uZSk6IHRoZSB2ZXJpZmllZCBhdXRoIG9yZyDigJQgdGhlXG4gICAgICAvLyBzYW1lIGZhbGxiYWNrIGBtZXRlclF1b3RhT25TdWNjZXNzYCB1c2VzLCBzbyBib3RoIHJlYWQgb25lIGNvdW50ZXIuXG4gICAgICBvcmdJZCA9IHJlcS51c2VyLm9yZ2FuaXphdGlvbklkO1xuICAgIH1cblxuICAgIC8vIE5vIG9yZywgbm90aGluZyB0byBiaWxsICh0aGUgbWV0ZXIgc2tpcHMgaXQgdG9vKS4gTm90IGEgNDAwOiBydW4gZGVmZXJyZWRcbiAgICAvLyBmcm9tIGByZXF1aXJlQXV0aGAgdGhpcyBwcmVjZWRlcyB0aGUgcm91dGUncyBvd24gYHJlcXVpcmVPcmdJZGAsIGFuZCBhXG4gICAgLy8gcm91dGUgdGhhdCBuZWVkcyBubyBvcmcgKGEgcGVyc29uIGJldHdlZW4gb3JncyBzd2l0Y2hpbmcgaW50byBvbmUpIG11c3RcbiAgICAvLyBub3QgYmUgcmVmdXNlZCBieSBhIHF1b3RhIGdhdGUuXG4gICAgaWYgKCFvcmdJZCkgcmV0dXJuIG5leHQoKTtcblxuICAgIC8vIEEgcmV0cnkgb2YgYSBjb21wbGV0ZWQgaWRlbXBvdGVudCBtdXRhdGlvbiBpcyBhbnN3ZXJlZCBmcm9tIHRoZSByZXBsYXlcbiAgICAvLyBjYWNoZSB3aXRob3V0IHJ1bm5pbmcgdGhlIGhhbmRsZXIg4oCUIG5vdGhpbmcgdG8gZ2F0ZSAob25lIHN0b3JlIHJlYWQsIG9ubHlcbiAgICAvLyBmb3IgcmVxdWVzdHMgdGhhdCBjYXJyeSBhbiBJZGVtcG90ZW5jeS1LZXkpLlxuICAgIGlmIChhd2FpdCBoYXNDb21wbGV0ZWRJZGVtcG90ZW50UmVzcG9uc2UocmVxKSkgcmV0dXJuIG5leHQoKTtcblxuICAgIGxldCBxdW90YVN0YXR1czogUXVvdGFDaGVja1Jlc3VsdDtcbiAgICB0cnkge1xuICAgICAgcXVvdGFTdGF0dXMgPSBhd2FpdCByZWFkQ2hlY2socXVvdGFTZXJ2aWNlLCBvcmdJZCwgcXVvdGFUeXBlKTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgLy8gRmFpbCBvcGVuIOKAlCBhbGxvdyB0aGUgcmVxdWVzdCBpZiBxdW90YSBzZXJ2aWNlIGlzIHVuYXZhaWxhYmxlXG4gICAgICBsb2dnZXIud2FybignUVVPVEFfRkFJTF9PUEVOOiBRdW90YSBjaGVjayBleGNlcHRpb24sIGFsbG93aW5nIHJlcXVlc3QnLCB7XG4gICAgICAgIG9yZ0lkLFxuICAgICAgICBxdW90YVR5cGUsXG4gICAgICAgIGVycm9yOiBlcnJvck1lc3NhZ2UoZXJyb3IpLFxuICAgICAgfSk7XG4gICAgICAvLyBTYW1lIGNvdW50ZXIgdGhlIGFwaS1jb3JlIHF1b3RhIGNsaWVudCBlbWl0cywgc28gYSBxdW90YS1zZXJ2aWNlIG91dGFnZVxuICAgICAgLy8gaXMgQUxFUlRBQkxFIGZyb20gb25lIHNlcmllcyByZWdhcmRsZXNzIG9mIHdoaWNoIGxheWVyIGZlbGwgb3Blbi4gT25seVxuICAgICAgLy8gYSBsb2cgbGluZSBoZXJlIG1lYW50IGV2ZXJ5IG9yZyB3YXMgc2lsZW50bHkgdW4tYmlsbGVkIGZvciB0aGUgZHVyYXRpb24uXG4gICAgICBlbWl0Q291bnRlcigncXVvdGFfZmFpbF9vcGVuX3RvdGFsJywgeyBvcGVyYXRpb246ICdjaGVjaycsIHJlYXNvbjogJ2V4Y2VwdGlvbicsIHF1b3RhVHlwZSB9KTtcbiAgICAgIHJldHVybiBuZXh0KCk7XG4gICAgfVxuXG4gICAgaWYgKCFxdW90YVN0YXR1cy5hbGxvd2VkKSB7XG4gICAgICBsb2dnZXIud2FybihgJHtxdW90YVR5cGV9IHF1b3RhIGV4Y2VlZGVkYCwge1xuICAgICAgICBvcmdJZCxcbiAgICAgICAgbGltaXQ6IHF1b3RhU3RhdHVzLmxpbWl0LFxuICAgICAgICB1c2VkOiBxdW90YVN0YXR1cy51c2VkLFxuICAgICAgfSk7XG5cbiAgICAgIC8vIFJvdXRlIHRocm91Z2ggdGhlIHNoYXJlZCBoZWxwZXIgc28gdGhlIGNoZWNrLWdhdGUgNDI5IGVtaXRzIHRoZSBzYW1lXG4gICAgICAvLyBSZXRyeS1BZnRlciArIFgtUXVvdGEtKiBoZWFkZXJzIGFuZCB0aGUgc2FtZSBib2R5ICh3aGljaCBxdW90YSwgdGhlXG4gICAgICAvLyByZXNldCBkYXRlLCB0aGUgdXBncmFkZSBwYWdlKSBhcyB0aGUgcmVzZXJ2ZS1wYXRoIDQyOS5cbiAgICAgIHNlbmRRdW90YUV4Y2VlZGVkKFxuICAgICAgICByZXMsXG4gICAgICAgIHF1b3RhVHlwZSxcbiAgICAgICAgeyB0eXBlOiBxdW90YVR5cGUsIGxpbWl0OiBxdW90YVN0YXR1cy5saW1pdCwgdXNlZDogcXVvdGFTdGF0dXMudXNlZCwgcmVtYWluaW5nOiBxdW90YVN0YXR1cy5yZW1haW5pbmcgfSxcbiAgICAgICAgcXVvdGFTdGF0dXMucmVzZXRBdCxcbiAgICAgICk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgLy8gT3V0c2lkZSB0aGUgdHJ5OiBhIHN5bmNocm9ub3VzIHRocm93IGRvd25zdHJlYW0gbXVzdCByZWFjaCBFeHByZXNzJ3MgZXJyb3JcbiAgICAvLyBoYW5kbGluZywgbm90IGJlIG1pc3Rha2VuIGZvciBhIHF1b3RhLXNlcnZpY2UgZmFpbHVyZSBhbmQgcnVuIG5leHQoKSB0d2ljZS5cbiAgICBuZXh0KCk7XG4gIH0sIHsga2luZDogJ3F1b3RhJywgbW9kZTogJ2NoZWNrJywgcXVvdGFUeXBlOiBiYXNlUXVvdGFUeXBlIH0pO1xufVxuXG4vKiogVGhlIHNlcnZpY2UtbG9jYWwgcmVxdWVzdCBwYXRoLCB3aXRob3V0IHRoZSBxdWVyeSBzdHJpbmcuICovXG5leHBvcnQgZnVuY3Rpb24gcmVxdWVzdFBhdGgocmVxOiBSZXF1ZXN0KTogc3RyaW5nIHtcbiAgcmV0dXJuIChyZXEub3JpZ2luYWxVcmwgPz8gcmVxLnVybCA/PyAnJykuc3BsaXQoJz8nKVswXSA/PyAnJztcbn1cblxuLyoqXG4gKiBUaGUgREVGRVJSRUQgZm9ybSBvZiB7QGxpbmsgY2hlY2tRdW90YX06IGFybXMgdGhlIGNoZWNrIGF0IG1vdW50IHRpbWUgYW5kIGxldHNcbiAqIGByZXF1aXJlQXV0aGAgcnVuIGl0IG9uY2UgdGhlIGNhbGxlciBpcyB2ZXJpZmllZCAoYXBpLWNvcmVcbiAqIGBhcm1EZWZlcnJlZFF1b3RhQ2hlY2tgKS4gRm9yIGEgcHJlZml4IG1vdW50IGFoZWFkIG9mIHJvdXRlcnMgdGhhdFxuICogYXV0aGVudGljYXRlIHBlciByb3V0ZSDigJQgdXNlIHtAbGluayBhcGlDYWxsc1F1b3RhfSwgd2hpY2ggcGFpcnMgaXQgd2l0aCB0aGVcbiAqIG1ldGVyLiBUYWdnZWQgYXMgdGhlIGNoZWNrIGdhdGUsIHNvIHRoZSByb3V0ZSB0YWJsZSByZWNvcmRzIGl0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gZGVmZXJyZWRDaGVja1F1b3RhKHF1b3RhU2VydmljZTogUXVvdGFTZXJ2aWNlLCBxdW90YVR5cGU6IFF1b3RhVHlwZSk6IFJlcXVlc3RIYW5kbGVyIHtcbiAgY29uc3QgY2hlY2sgPSBjaGVja1F1b3RhKHF1b3RhU2VydmljZSwgcXVvdGFUeXBlKSBhcyBSZXF1ZXN0SGFuZGxlcjtcbiAgcmV0dXJuIHRhZ1JvdXRlR2F0ZSgoX3JlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKTogdm9pZCA9PiB7XG4gICAgYXJtRGVmZXJyZWRRdW90YUNoZWNrKHJlcywgY2hlY2spO1xuICAgIG5leHQoKTtcbiAgfSwgeyBraW5kOiAncXVvdGEnLCBtb2RlOiAnY2hlY2snLCBxdW90YVR5cGUgfSk7XG59XG4iXX0=
@@ -60,6 +60,28 @@ export declare function createEnvRedisIdempotencyStore(): IdempotencyStore | nul
60
60
  export declare function setIdempotencyStore(store: IdempotencyStore): void;
61
61
  /** The current process-wide default store (in-memory unless overridden). */
62
62
  export declare function getIdempotencyStore(): IdempotencyStore;
63
+ /**
64
+ * `res.locals` key set to `true` when the response was REPLAYED from the
65
+ * idempotency cache instead of running the handler. A replay did no work, so
66
+ * anything that bills per successful response (`meterQuotaOnSuccess`) must skip
67
+ * it — the original request already paid. Use {@link isIdempotentReplay}.
68
+ */
69
+ export declare const IDEMPOTENT_REPLAY_LOCAL = "idempotentReplay";
70
+ /** True when `res` is a replay of a cached idempotent response. */
71
+ export declare function isIdempotentReplay(res: Response): boolean;
72
+ /**
73
+ * True when this request is a RETRY of an idempotent mutation whose original
74
+ * has COMPLETED (a cached 2xx on record) — i.e. the idempotency middleware will
75
+ * replay it without running the handler. The apiCalls check uses it to skip
76
+ * the gate for such a retry: the replay does no work and is never metered, so
77
+ * refusing it at the quota gate (which runs in requireAuth, BEFORE replay
78
+ * detection in most chains) would 429 a client for re-reading its own result.
79
+ *
80
+ * One store read, and only for a mutation carrying `Idempotency-Key`; every
81
+ * failure answers `false` (the gate then runs as usual). Uses the process-wide
82
+ * store — the same one the route chains' middleware resolves.
83
+ */
84
+ export declare function hasCompletedIdempotentResponse(req: Request): Promise<boolean>;
63
85
  export interface IdempotencyMiddlewareOptions {
64
86
  /** Custom store backend. Defaults to the process-wide store set via
65
87
  * {@link setIdempotencyStore} (in-memory until `createApp` wires Redis). */