@saeris/hanko 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE.md +21 -0
- package/README.md +605 -0
- package/dist/approve/index.d.mts +318 -0
- package/dist/approve/index.d.mts.map +1 -0
- package/dist/approve/index.mjs +393 -0
- package/dist/approve/index.mjs.map +1 -0
- package/dist/client/index.d.mts +101 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +215 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/codes-Ba_qYH6u.mjs +93 -0
- package/dist/codes-Ba_qYH6u.mjs.map +1 -0
- package/dist/handlers.d.mts +113 -0
- package/dist/handlers.d.mts.map +1 -0
- package/dist/handlers.mjs +194 -0
- package/dist/handlers.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +345 -0
- package/dist/index.mjs.map +1 -0
- package/dist/linking-DcQSMgem.mjs +177 -0
- package/dist/linking-DcQSMgem.mjs.map +1 -0
- package/dist/linking-nKoayyHf.d.mts +133 -0
- package/dist/linking-nKoayyHf.d.mts.map +1 -0
- package/dist/machine-CRHKjtoP.d.mts +223 -0
- package/dist/machine-CRHKjtoP.d.mts.map +1 -0
- package/dist/machine-D_5DAFxi.mjs +155 -0
- package/dist/machine-D_5DAFxi.mjs.map +1 -0
- package/dist/qr.d.mts +58 -0
- package/dist/qr.d.mts.map +1 -0
- package/dist/qr.mjs +27 -0
- package/dist/qr.mjs.map +1 -0
- package/dist/scan/index.d.mts +381 -0
- package/dist/scan/index.d.mts.map +1 -0
- package/dist/scan/index.mjs +409 -0
- package/dist/scan/index.mjs.map +1 -0
- package/dist/scan/worker.d.mts +2 -0
- package/dist/scan/worker.mjs +2 -0
- package/dist/server-BhoYRkCm.d.mts +257 -0
- package/dist/server-BhoYRkCm.d.mts.map +1 -0
- package/dist/stores/kv.d.mts +64 -0
- package/dist/stores/kv.d.mts.map +1 -0
- package/dist/stores/kv.mjs +87 -0
- package/dist/stores/kv.mjs.map +1 -0
- package/dist/stores/memory.d.mts +22 -0
- package/dist/stores/memory.d.mts.map +1 -0
- package/dist/stores/memory.mjs +42 -0
- package/dist/stores/memory.mjs.map +1 -0
- package/dist/types-BvBIFPH6.mjs +7 -0
- package/dist/types-BvBIFPH6.mjs.map +1 -0
- package/dist/types-C82lb-zX.d.mts +82 -0
- package/dist/types-C82lb-zX.d.mts.map +1 -0
- package/dist/worker-BdwaK1uX.mjs +5291 -0
- package/dist/worker-BdwaK1uX.mjs.map +1 -0
- package/dist/worker-DxbdBA2z.d.mts +164 -0
- package/dist/worker-DxbdBA2z.d.mts.map +1 -0
- package/package.json +116 -3
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { n as ApprovalState } from "../machine-CRHKjtoP.mjs";
|
|
2
|
+
import { a as buildAppSchemeUrl, c as digitalAssetLinks, d as pwaLaunchHandler, i as appleAppSiteAssociation, l as expoLinkingConfig, n as LinkSource, o as buildApprovalUrl, r as ParsedApprovalLink, s as consumeLaunchTarget, t as LinkConfig, u as parseApprovalLink } from "../linking-nKoayyHf.mjs";
|
|
3
|
+
//#region src/scanner.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* QR reading for the approving device.
|
|
6
|
+
*
|
|
7
|
+
* Generation is dependency-free; decoding cannot be. hanko defines the
|
|
8
|
+
* {@link QrScanner} interface — two methods — and lets the host bring a
|
|
9
|
+
* decoder rather than picking one for everybody.
|
|
10
|
+
*
|
|
11
|
+
* That indirection is not ceremony. `BarcodeDetector` looks like the obvious
|
|
12
|
+
* answer and is NOT a web standard: it is a WICG incubation that MDN flags as
|
|
13
|
+
* outside Baseline, Safari has never shipped it, and no vendor has committed
|
|
14
|
+
* to it. Building against it directly means the scanner silently does nothing
|
|
15
|
+
* on an iPhone — the device most people approve from.
|
|
16
|
+
*
|
|
17
|
+
* What works today, in rough order of preference:
|
|
18
|
+
*
|
|
19
|
+
* - **`qr-scanner`** — ~6 kB gzipped, self-contained, uses a native
|
|
20
|
+
* `BarcodeDetector` where one exists and its own worker otherwise. It owns
|
|
21
|
+
* the camera too, which is most of the work. See the Astro example.
|
|
22
|
+
* - **`barcode-detector`** — a ponyfill over ZXing-C++/WASM. More formats and
|
|
23
|
+
* actively maintained, but it fetches its WASM from a CDN at runtime, which
|
|
24
|
+
* is a poor default on an authentication screen.
|
|
25
|
+
* - **`expo-camera`** on React Native, where no web API exists at all.
|
|
26
|
+
*
|
|
27
|
+
* {@link createBarcodeDetectorScanner} remains for the native path and for
|
|
28
|
+
* ponyfills mirroring that API.
|
|
29
|
+
*/
|
|
30
|
+
/** Minimal shape of a detected barcode. Mirrors the Barcode Detection API. */
|
|
31
|
+
/**
|
|
32
|
+
* A camera frame, as an opaque handle.
|
|
33
|
+
*
|
|
34
|
+
* The DOM's `ImageBitmapSource` in a browser — a `<video>`, `ImageBitmap`,
|
|
35
|
+
* `Blob`, or canvas — but declared locally rather than pulled from `lib.dom`.
|
|
36
|
+
* This package compiles without the DOM lib on purpose, so server code cannot
|
|
37
|
+
* reach a browser-only global by accident, and so the same types work for a
|
|
38
|
+
* React Native frame that is not an `ImageBitmapSource` at all.
|
|
39
|
+
*
|
|
40
|
+
* hanko never inspects the frame; it passes it straight to the detector.
|
|
41
|
+
*/
|
|
42
|
+
type CameraFrame = unknown;
|
|
43
|
+
/** Minimal shape of a detected barcode. Mirrors the Barcode Detection API. */
|
|
44
|
+
interface DetectedBarcode {
|
|
45
|
+
rawValue: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* What hanko needs from a scanner: turn a frame into strings.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately narrower than `BarcodeDetector` — no bounding boxes, no
|
|
51
|
+
* formats — so an `expo-camera` or jsQR adapter is trivial to write.
|
|
52
|
+
*/
|
|
53
|
+
interface QrScanner {
|
|
54
|
+
detect(source: CameraFrame): Promise<DetectedBarcode[]>;
|
|
55
|
+
}
|
|
56
|
+
/** The subset of the global `BarcodeDetector` this module uses. */
|
|
57
|
+
interface BarcodeDetectorLike {
|
|
58
|
+
detect(source: CameraFrame): Promise<DetectedBarcode[]>;
|
|
59
|
+
}
|
|
60
|
+
interface BarcodeDetectorConstructor {
|
|
61
|
+
new (options?: {
|
|
62
|
+
formats?: string[];
|
|
63
|
+
}): BarcodeDetectorLike;
|
|
64
|
+
}
|
|
65
|
+
/** Whether this runtime has a native `BarcodeDetector`. */
|
|
66
|
+
declare const hasNativeBarcodeDetector: () => boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Scanner backed by the platform's `BarcodeDetector`.
|
|
69
|
+
*
|
|
70
|
+
* Pass the ponyfill's constructor where the native one is missing:
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { BarcodeDetector } from "barcode-detector/ponyfill";
|
|
74
|
+
* const scanner = createBarcodeDetectorScanner({ detector: BarcodeDetector });
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* Restricted to `qr_code`: a taplist beer can carries an EAN-13 barcode, and a
|
|
78
|
+
* scanner that reported it here would send a product code to the approval
|
|
79
|
+
* endpoint as though it were a user code.
|
|
80
|
+
*/
|
|
81
|
+
declare const createBarcodeDetectorScanner: ({ detector }?: {
|
|
82
|
+
/** Constructor to use. Defaults to the global when present. */
|
|
83
|
+
detector?: BarcodeDetectorConstructor;
|
|
84
|
+
}) => QrScanner;
|
|
85
|
+
/** Everything hanko can pull out of a scanned payload. */
|
|
86
|
+
interface ScannedPayload {
|
|
87
|
+
/** The code to send to the approval endpoint. */
|
|
88
|
+
userCode: string;
|
|
89
|
+
/** The URL the QR encoded, when it was a URL. */
|
|
90
|
+
verificationUri?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Read a `user_code` out of a scanned string.
|
|
94
|
+
*
|
|
95
|
+
* QRs in this flow carry `verification_uri_complete` — a URL with the code in
|
|
96
|
+
* it — but a scanner may also see a bare code from a hand-typed fallback, so
|
|
97
|
+
* both are accepted.
|
|
98
|
+
*
|
|
99
|
+
* Returns `null` rather than throwing: a camera pointed at the world sees
|
|
100
|
+
* unrelated codes constantly, and each one is a normal non-event, not an
|
|
101
|
+
* error.
|
|
102
|
+
*/
|
|
103
|
+
declare const parseScannedCode: (raw: string, { param }?: {
|
|
104
|
+
param?: string;
|
|
105
|
+
}) => ScannedPayload | null;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/challenge.d.ts
|
|
108
|
+
/**
|
|
109
|
+
* Confirmation challenges for the approving device.
|
|
110
|
+
*
|
|
111
|
+
* RFC 8628 §5.4: the approval screen SHOULD show the code and ask the user to
|
|
112
|
+
* verify it matches the device in front of them. Scanning a QR skips the
|
|
113
|
+
* typing, which also skips the moment where the user would have noticed the
|
|
114
|
+
* code was wrong — so the check has to be reintroduced deliberately.
|
|
115
|
+
*
|
|
116
|
+
* How much friction that check deserves is a product decision, not a protocol
|
|
117
|
+
* one. Discord and Steam ship a bare "Approve?"; GitHub's sudo flow makes you
|
|
118
|
+
* type a code; Google shows three numbers and asks you to pick the one on the
|
|
119
|
+
* other screen. All are legitimate for different risk levels, so the strategy
|
|
120
|
+
* is pluggable and the machine only cares whether it passed.
|
|
121
|
+
*
|
|
122
|
+
* These run on the ALREADY AUTHENTICATED device. They do not authenticate the
|
|
123
|
+
* user — they establish that the user can see the screen requesting access.
|
|
124
|
+
*/
|
|
125
|
+
/** Outcome of a challenge attempt. */
|
|
126
|
+
interface ChallengeResult {
|
|
127
|
+
ok: boolean;
|
|
128
|
+
/** Present when `ok` is false, for a precise message. */
|
|
129
|
+
reason?: `mismatch` | `cancelled` | `unavailable`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A confirmation strategy.
|
|
133
|
+
*
|
|
134
|
+
* `present` gives the UI whatever it needs to render (the decoys for a
|
|
135
|
+
* triplet, say); `verify` checks the user's answer. Async because a biometric
|
|
136
|
+
* or WebAuthn prompt is inherently asynchronous.
|
|
137
|
+
*/
|
|
138
|
+
interface ChallengeStrategy<TPrompt = unknown, TAnswer = unknown> {
|
|
139
|
+
/** Stable name, so a UI can switch on which challenge it is rendering. */
|
|
140
|
+
readonly kind: string;
|
|
141
|
+
/** What the UI should show. Called once per confirmation. */
|
|
142
|
+
present(userCode: string): TPrompt;
|
|
143
|
+
/** Check the user's answer. */
|
|
144
|
+
verify(answer: TAnswer, userCode: string): Promise<ChallengeResult>;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* No challenge — approving is a single tap.
|
|
148
|
+
*
|
|
149
|
+
* What Discord and Steam ship. Appropriate when the QR was scanned inside your
|
|
150
|
+
* own authenticated app, which already proves possession of the phone. Note
|
|
151
|
+
* this is weaker than RFC 8628 §5.4 asks for: nothing stops a user approving a
|
|
152
|
+
* code they never actually looked at.
|
|
153
|
+
*/
|
|
154
|
+
declare const noChallenge: () => ChallengeStrategy<null, void>;
|
|
155
|
+
/**
|
|
156
|
+
* The user types the code shown on the device.
|
|
157
|
+
*
|
|
158
|
+
* The RFC's own suggestion, and GitHub's sudo pattern. Highest friction,
|
|
159
|
+
* strongest guarantee: an attacker who phished the QR cannot supply a code the
|
|
160
|
+
* user can read off their own screen.
|
|
161
|
+
*/
|
|
162
|
+
declare const codeEntryChallenge: () => ChallengeStrategy<{
|
|
163
|
+
kind: `code-entry`;
|
|
164
|
+
}, string>;
|
|
165
|
+
/** What a triplet challenge hands the UI. */
|
|
166
|
+
interface TripletPrompt {
|
|
167
|
+
kind: `triplet`;
|
|
168
|
+
/** The real code plus decoys, already shuffled. Render as-is. */
|
|
169
|
+
choices: string[];
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The user picks the real code from a small set of decoys.
|
|
173
|
+
*
|
|
174
|
+
* Google's mobile approval pattern. Most of code-entry's guarantee at a
|
|
175
|
+
* fraction of the friction — one tap instead of eight keystrokes — which
|
|
176
|
+
* matters on a phone. A wrong pick is a real signal: the user is not looking
|
|
177
|
+
* at the device they are authorizing.
|
|
178
|
+
*/
|
|
179
|
+
declare const tripletChallenge: ({ decoys, generate, shuffle }: {
|
|
180
|
+
/** Decoys alongside the real code. Two gives the classic three-up. */
|
|
181
|
+
decoys?: number;
|
|
182
|
+
/** Produces a decoy. Must match the real code's shape to be plausible. */
|
|
183
|
+
generate: () => string;
|
|
184
|
+
/** Injectable for deterministic tests. */
|
|
185
|
+
shuffle?: <T>(items: T[]) => T[];
|
|
186
|
+
} & {
|
|
187
|
+
generate: () => string;
|
|
188
|
+
}) => ChallengeStrategy<TripletPrompt, string>;
|
|
189
|
+
/**
|
|
190
|
+
* Delegate to something the platform provides — FaceID, Touch ID, WebAuthn, a
|
|
191
|
+
* device passcode.
|
|
192
|
+
*
|
|
193
|
+
* hanko cannot implement these: they are platform APIs with no common
|
|
194
|
+
* interface. What it can do is give them a slot in the same machine, so a host
|
|
195
|
+
* that has one is not forced outside the flow to use it.
|
|
196
|
+
*
|
|
197
|
+
* A biometric proves possession of the phone, NOT that the user looked at the
|
|
198
|
+
* device screen — so for a public taproom TV, pair it with `codeEntry` or
|
|
199
|
+
* `triplet` rather than using it alone.
|
|
200
|
+
*/
|
|
201
|
+
declare const platformChallenge: ({ kind, authenticate }: {
|
|
202
|
+
kind?: string;
|
|
203
|
+
/** Resolves true when the platform accepted the user. */
|
|
204
|
+
authenticate: (userCode: string) => Promise<boolean>;
|
|
205
|
+
}) => ChallengeStrategy<{
|
|
206
|
+
kind: string;
|
|
207
|
+
}, void>;
|
|
208
|
+
/**
|
|
209
|
+
* Require several challenges in order.
|
|
210
|
+
*
|
|
211
|
+
* The composition that makes sense in practice: a biometric proves the phone,
|
|
212
|
+
* a code check proves the screen. Neither alone covers both.
|
|
213
|
+
*
|
|
214
|
+
* Short-circuits on the first failure, and its `present` returns every prompt
|
|
215
|
+
* so a UI can render them as steps.
|
|
216
|
+
*/
|
|
217
|
+
declare const allOf: (strategies: ChallengeStrategy[]) => ChallengeStrategy<unknown[], unknown[]>;
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/approve/index.d.ts
|
|
220
|
+
/** What the approval endpoint tells us about a code. */
|
|
221
|
+
interface ResolvedGrant {
|
|
222
|
+
/** The code, as the server has it. Display this for the user to check. */
|
|
223
|
+
userCode: string;
|
|
224
|
+
/** Optional detail a host may show: which client is asking, and for what. */
|
|
225
|
+
clientId?: string;
|
|
226
|
+
scope?: string;
|
|
227
|
+
}
|
|
228
|
+
interface ApprovalHooks {
|
|
229
|
+
/** Any successful transition. The main binding point for a UI. */
|
|
230
|
+
onTransition?: (from: ApprovalState, to: ApprovalState) => void;
|
|
231
|
+
/** A code was read. Fires before the server is asked about it. */
|
|
232
|
+
onCode?: (userCode: string) => void;
|
|
233
|
+
/** The server resolved the code. Carries what to show the user. */
|
|
234
|
+
onResolved?: (grant: ResolvedGrant, prompt: unknown) => void;
|
|
235
|
+
/** The challenge was answered incorrectly. The user may retry. */
|
|
236
|
+
onChallengeFailed?: (reason: string | undefined, attempts: number) => void;
|
|
237
|
+
/** Terminal: the decision was recorded. */
|
|
238
|
+
onSettled?: (state: ApprovalState) => void;
|
|
239
|
+
/** Recoverable failure — camera or network. */
|
|
240
|
+
onError?: (error: unknown, state: ApprovalState) => void;
|
|
241
|
+
}
|
|
242
|
+
interface ApprovalClientOptions {
|
|
243
|
+
/** Resolve a code to a grant. Usually `GET /link?user_code=…`. */
|
|
244
|
+
resolve: (userCode: string) => Promise<ResolvedGrant | null>;
|
|
245
|
+
/** Record the decision. Usually `POST /link`. */
|
|
246
|
+
submit: (userCode: string, approved: boolean) => Promise<void>;
|
|
247
|
+
/**
|
|
248
|
+
* How the user proves they are looking at the requesting screen.
|
|
249
|
+
*
|
|
250
|
+
* Defaults to {@link noChallenge} — one tap, what Discord and Steam ship.
|
|
251
|
+
* For a public screen prefer `codeEntryChallenge` or `tripletChallenge`,
|
|
252
|
+
* which is what RFC 8628 §5.4 actually asks for.
|
|
253
|
+
*/
|
|
254
|
+
challenge?: ChallengeStrategy;
|
|
255
|
+
/** Needed only for the in-app scanning path. */
|
|
256
|
+
scanner?: QrScanner;
|
|
257
|
+
/** Query parameter carrying the code in scanned URLs. */
|
|
258
|
+
codeParam?: string;
|
|
259
|
+
hooks?: ApprovalHooks;
|
|
260
|
+
}
|
|
261
|
+
declare class ApprovalClient {
|
|
262
|
+
#private;
|
|
263
|
+
constructor({ resolve, submit, challenge, scanner, codeParam, hooks }: ApprovalClientOptions);
|
|
264
|
+
get state(): ApprovalState;
|
|
265
|
+
get settled(): boolean;
|
|
266
|
+
/** The resolved grant, once known. Show its code for the user to verify. */
|
|
267
|
+
get grant(): ResolvedGrant | undefined;
|
|
268
|
+
/** Whatever the challenge wants rendered — triplet choices, a prompt kind. */
|
|
269
|
+
get challengePrompt(): unknown;
|
|
270
|
+
get challengeKind(): string;
|
|
271
|
+
/** Whether the challenge has been satisfied, so approval may proceed. */
|
|
272
|
+
get confirmed(): boolean;
|
|
273
|
+
/** Open the scanner (in-app path). */
|
|
274
|
+
startScanning(): void;
|
|
275
|
+
/**
|
|
276
|
+
* Offer a camera frame to the scanner.
|
|
277
|
+
*
|
|
278
|
+
* Returns the code when this frame contained one. Call it per frame and
|
|
279
|
+
* ignore the nulls — a camera pointed at a room sees unrelated codes
|
|
280
|
+
* constantly, and each is a non-event rather than an error.
|
|
281
|
+
*/
|
|
282
|
+
scan(source: CameraFrame): Promise<string | null>;
|
|
283
|
+
/**
|
|
284
|
+
* Hand over a code from the URL (Plex path) or a manual entry field.
|
|
285
|
+
*
|
|
286
|
+
* The same entry point as a successful scan, so both topologies converge on
|
|
287
|
+
* one flow from here.
|
|
288
|
+
*/
|
|
289
|
+
submitCode(userCode: string): Promise<boolean>;
|
|
290
|
+
/**
|
|
291
|
+
* Answer the confirmation challenge.
|
|
292
|
+
*
|
|
293
|
+
* Separate from {@link approve} so a UI can verify as the user types (or as
|
|
294
|
+
* a biometric returns) and enable the approve button only once it passes.
|
|
295
|
+
*/
|
|
296
|
+
confirm(answer?: unknown): Promise<boolean>;
|
|
297
|
+
/**
|
|
298
|
+
* Approve the sign-in.
|
|
299
|
+
*
|
|
300
|
+
* Refuses while the challenge is unsatisfied. That guard is the point of the
|
|
301
|
+
* challenge existing: a UI bug that wires the button straight to this method
|
|
302
|
+
* must not be able to skip the check.
|
|
303
|
+
*/
|
|
304
|
+
approve(): Promise<boolean>;
|
|
305
|
+
/**
|
|
306
|
+
* Refuse the sign-in.
|
|
307
|
+
*
|
|
308
|
+
* Deliberately NOT gated on the challenge. A user who cannot confirm a code
|
|
309
|
+
* is exactly the user most likely to be looking at a phishing attempt, and
|
|
310
|
+
* they must always be able to say no.
|
|
311
|
+
*/
|
|
312
|
+
deny(): Promise<boolean>;
|
|
313
|
+
/** Start over after a failure or an unrecognized code. */
|
|
314
|
+
reset(): void;
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
export { ApprovalClient, ApprovalClientOptions, ApprovalHooks, type CameraFrame, type ChallengeResult, type ChallengeStrategy, type DetectedBarcode, type LinkConfig, type LinkSource, type ParsedApprovalLink, type QrScanner, ResolvedGrant, type ScannedPayload, type TripletPrompt, allOf, appleAppSiteAssociation, buildAppSchemeUrl, buildApprovalUrl, codeEntryChallenge, consumeLaunchTarget, createBarcodeDetectorScanner, digitalAssetLinks, expoLinkingConfig, hasNativeBarcodeDetector, noChallenge, parseApprovalLink, parseScannedCode, platformChallenge, pwaLaunchHandler, tripletChallenge };
|
|
318
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/scanner.ts","../../src/challenge.ts","../../src/approve/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuCY;;UAGK;EACf;;;;;;;;UASe;EACf,OAAO,QAAQ,cAAc,QAAQ;;;UAI7B;EACR,OAAO,QAAQ,cAAc,QAAQ;;UAG7B;OACH;IAAY;MAAuB;;;cAI7B;;;;;;;;;;;;;;;cAiBA,iCAAgC;;EAI3C,WAAW;MACJ;;UA2BQ;;EAEf;;EAEA;;;;;;;;;;;;;cAcW,mBACX,eACA;EAA2B;MAC1B;;;;;;;;;;;;;;;;;;;;;UCnHc;EACf;;EAEA;;;;;;;;;UAUe,kBAAkB,mBAAmB;;WAE3C;;EAET,QAAQ,mBAAmB;;EAE3B,OAAO,QAAQ,SAAS,mBAAmB,QAAQ;;;;;;;;;;cAWxC,mBAAkB;;;;;;;;cAalB,0BAAyB;EAClC;;;UAgBa;EACf;;EAEA;;;;;;;;;;cAWW,qBAAoB,QAAA,UAAA;;EAM/B;;EAEA;;EAEA,WAAW,GAAG,OAAO,QAAQ;;EACzB;MAA2B,kBAAkB;;;;;;;;;;;;;cAoCtC,sBAAqB,MAAA;EAIhC;;EAEA,eAAe,qBAAqB;MAClC;EAAoB;;;;;;;;;;;cAyBX,QACX,YAAY,wBACX;;;;UClJc;;EAEf;;EAEA;EACA;;UAGe;;EAEf,gBAAgB,MAAM,eAAe,IAAI;;EAEzC,UAAU;;EAEV,cAAc,OAAO,eAAe;;EAEpC,qBAAqB,4BAA4B;;EAEjD,aAAa,OAAO;;EAEpB,WAAW,gBAAgB,OAAO;;UAGnB;;EAEf,UAAU,qBAAqB,QAAQ;;EAEvC,SAAS,kBAAkB,sBAAsB;;;;;;;;EAQjD,YAAY;;EAEZ,UAAU;;EAEV;EACA,QAAQ;;cAGG;;EAcC,cACV,SACA,QACA,WACA,SACA,WACA,SACC;MASC,SAAS;MAIT;;MAKA,SAAS;;MAKT;MAIA;;MAKA;;EAgBJ;;;;;;;;EAWM,KAAK,QAAQ,cAAc;;;;;;;EA6B3B,WAAW,mBAAmB;;;;;;;EAoC9B,QAAQ,mBAAmB;;;;;;;;EAyB3B,WAAW;;;;;;;;EAYX,QAAQ;;EAqBd"}
|