ribaunt 0.1.6 → 0.2.1

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 (58) hide show
  1. package/README.md +39 -6
  2. package/dist/cjs/index.d.ts +52 -24
  3. package/dist/cjs/index.d.ts.map +1 -1
  4. package/dist/cjs/index.js +267 -237
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/cjs/redis.d.ts +13 -0
  7. package/dist/cjs/redis.d.ts.map +1 -0
  8. package/dist/cjs/redis.js +53 -0
  9. package/dist/cjs/redis.js.map +1 -0
  10. package/dist/index.d.ts +52 -24
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +264 -236
  13. package/dist/index.js.map +1 -1
  14. package/dist/redis.d.ts +13 -0
  15. package/dist/redis.d.ts.map +1 -0
  16. package/dist/redis.js +49 -0
  17. package/dist/redis.js.map +1 -0
  18. package/dist/solver-worker.d.ts +2 -0
  19. package/dist/solver-worker.d.ts.map +1 -0
  20. package/dist/solver-worker.js +15 -0
  21. package/dist/solver-worker.js.map +1 -0
  22. package/dist/solver.d.ts +11 -0
  23. package/dist/solver.d.ts.map +1 -1
  24. package/dist/solver.js +15 -0
  25. package/dist/solver.js.map +1 -1
  26. package/dist/widget-browser.d.ts +1 -0
  27. package/dist/widget-browser.d.ts.map +1 -1
  28. package/dist/widget-browser.js +1 -0
  29. package/dist/widget-browser.js.map +1 -1
  30. package/dist/widget-react.d.ts +9 -11
  31. package/dist/widget-react.d.ts.map +1 -1
  32. package/dist/widget-react.js +44 -7
  33. package/dist/widget-react.js.map +1 -1
  34. package/dist/widget.d.ts +31 -1
  35. package/dist/widget.d.ts.map +1 -1
  36. package/dist/widget.js +220 -33
  37. package/dist/widget.js.map +1 -1
  38. package/dist/worker-client.d.ts +8 -0
  39. package/dist/worker-client.d.ts.map +1 -0
  40. package/dist/worker-client.js +78 -0
  41. package/dist/worker-client.js.map +1 -0
  42. package/package.json +23 -2
  43. package/dist/cjs/solver.d.ts +0 -16
  44. package/dist/cjs/solver.d.ts.map +0 -1
  45. package/dist/cjs/solver.js +0 -89
  46. package/dist/cjs/solver.js.map +0 -1
  47. package/dist/cjs/widget-browser.d.ts +0 -7
  48. package/dist/cjs/widget-browser.d.ts.map +0 -1
  49. package/dist/cjs/widget-browser.js +0 -12
  50. package/dist/cjs/widget-browser.js.map +0 -1
  51. package/dist/cjs/widget-react.d.ts +0 -40
  52. package/dist/cjs/widget-react.d.ts.map +0 -1
  53. package/dist/cjs/widget-react.js +0 -182
  54. package/dist/cjs/widget-react.js.map +0 -1
  55. package/dist/cjs/widget.d.ts +0 -97
  56. package/dist/cjs/widget.d.ts.map +0 -1
  57. package/dist/cjs/widget.js +0 -519
  58. package/dist/cjs/widget.js.map +0 -1
package/README.md CHANGED
@@ -37,11 +37,14 @@ pnpm add ribaunt
37
37
  ## Quick Start
38
38
 
39
39
  Set a strong secret in your server environment. Keep this server-only.
40
+ Ribaunt requires at least 32 UTF-8 bytes; generate a random value rather than a memorable password.
40
41
 
41
42
  ```env
42
43
  RIBAUNT_SECRET="replace-with-a-long-random-secret"
43
44
  ```
44
45
 
46
+ > **Upgrading from v0.1:** `verifySolution()` now returns a structured result object, not a boolean. Always check `result.valid`; using the result object directly in an `if` condition is unsafe because JavaScript objects are truthy.
47
+
45
48
  Create two endpoints: one to issue challenges and one to verify solutions.
46
49
 
47
50
  ```ts
@@ -60,10 +63,10 @@ app.get('/api/captcha/challenge', (_req, res) => {
60
63
 
61
64
  app.post('/api/captcha/verify', async (req, res) => {
62
65
  const { tokens, solutions } = req.body;
63
- const valid = await verifySolution(tokens, solutions);
66
+ const result = await verifySolution(tokens, solutions);
64
67
 
65
- if (!valid) {
66
- return res.status(400).json({ success: false, error: 'Invalid CAPTCHA solution' });
68
+ if (!result.valid) {
69
+ return res.status(400).json({ success: false, error: result.reason });
67
70
  }
68
71
 
69
72
  return res.json({ success: true });
@@ -140,12 +143,38 @@ const challenges = createChallenge(5, 4, 120);
140
143
 
141
144
  Validate user- or config-controlled values before passing them to `createChallenge()`. Invalid values throw.
142
145
 
146
+ ### `createChallenge({ difficulty: "auto", ... })`
147
+
148
+ Creates adaptive challenge tokens from a server-bounded calibration hint. Calibration is untrusted and raise-only: it can increase work for fast clients, but it never lowers the server baseline.
149
+
150
+ ```ts
151
+ const challenges = createChallenge({
152
+ difficulty: 'auto',
153
+ calibration: requestBody.calibration,
154
+ targetDurationMs: 750,
155
+ minDifficulty: 3,
156
+ maxDifficulty: 6,
157
+ minAmount: 1,
158
+ maxAmount: 8,
159
+ ttlSeconds: 60,
160
+ });
161
+ ```
162
+
163
+ For machine-to-machine checks, benchmark the Node client and send the same calibration shape:
164
+
165
+ ```ts
166
+ import { calibrateNode } from 'ribaunt';
167
+
168
+ const calibration = calibrateNode();
169
+ ```
170
+
143
171
  ### `verifySolution(tokens, solutions, options?)`
144
172
 
145
- Verifies submitted solutions and returns `Promise<boolean>`.
173
+ Verifies submitted solutions and returns a structured result:
174
+ `{ valid: true }` or `{ valid: false, reason, message }`.
146
175
 
147
176
  ```ts
148
- const valid = await verifySolution(tokens, solutions, {
177
+ const result = await verifySolution(tokens, solutions, {
149
178
  onWarning: (warning) => {
150
179
  console.log('captcha warning', warning.reason, warning.message);
151
180
  },
@@ -155,7 +184,7 @@ const valid = await verifySolution(tokens, solutions, {
155
184
  Replay prevention defaults to `local`, which blocks token reuse in the current process. For serverless or horizontally scaled deployments, use `replayPrevention: 'remote'` with an atomic distributed store.
156
185
 
157
186
  ```ts
158
- const valid = await verifySolution(tokens, solutions, {
187
+ const result = await verifySolution(tokens, solutions, {
159
188
  replayPrevention: 'remote',
160
189
  replayStore: {
161
190
  consume: async (jti, expiresAt) => {
@@ -184,6 +213,8 @@ const solutions = solveChallenge(challenges, {
184
213
  challenge-endpoint="/api/captcha/challenge"
185
214
  verify-endpoint="/api/captcha/verify"
186
215
  auto-verify="true"
216
+ challenge-method="POST"
217
+ calibrate="true"
187
218
  show-warning="false"
188
219
  warning-message="Verification may take longer on this device."
189
220
  solve-timeout="15000"
@@ -196,6 +227,8 @@ const solutions = solveChallenge(challenges, {
196
227
  | `challenge-endpoint` | `challengeEndpoint` | Endpoint that returns `{ challenges: string[] }`. |
197
228
  | `verify-endpoint` | `verifyEndpoint` | Endpoint that accepts `{ tokens, solutions }`. |
198
229
  | `auto-verify` | `autoVerify` | Starts verification when the widget loads. |
230
+ | `challenge-method` | `challengeMethod` | Use `POST` when sending calibration to an auto-hardness endpoint. |
231
+ | `calibrate` | `calibrate` | Benchmarks the browser and sends `{ calibration }` with POST challenge requests. |
199
232
  | `show-warning` | `showWarning` | Shows a warning banner. |
200
233
  | `warning-message` | `warningMessage` | Custom warning text. |
201
234
  | `solve-timeout` | `solveTimeout` | Optional solve timeout in milliseconds. |
@@ -3,22 +3,66 @@ export interface ChallengeSolution {
3
3
  nonce: string;
4
4
  hash: string;
5
5
  }
6
+ export interface ClientCalibration {
7
+ iterations: number;
8
+ durationMs: number;
9
+ }
10
+ export interface WorkloadBounds {
11
+ minDifficulty?: number;
12
+ maxDifficulty?: number;
13
+ minAmount?: number;
14
+ maxAmount?: number;
15
+ }
16
+ export interface AdaptiveWorkloadOptions extends WorkloadBounds {
17
+ riskScore?: number;
18
+ targetDurationMs?: number;
19
+ calibration?: ClientCalibration;
20
+ }
21
+ export interface Workload {
22
+ difficulty: number;
23
+ amount: number;
24
+ estimatedAttempts: number;
25
+ }
26
+ export interface ChallengeOptions {
27
+ difficulty?: number | 'auto';
28
+ amount?: number;
29
+ ttlSeconds?: number;
30
+ context?: string;
31
+ workload?: Pick<Workload, 'difficulty' | 'amount'>;
32
+ riskScore?: number;
33
+ targetDurationMs?: number;
34
+ calibration?: ClientCalibration;
35
+ minDifficulty?: number;
36
+ maxDifficulty?: number;
37
+ minAmount?: number;
38
+ maxAmount?: number;
39
+ }
6
40
  export interface ReplayStore {
7
41
  consume(jti: string, expiresAt: number): Promise<boolean>;
42
+ consumeMany?(jtis: string[], expiresAt: number): Promise<boolean>;
8
43
  }
9
44
  export type ReplayPreventionMode = 'disabled' | 'local' | 'remote';
10
45
  export interface VerifySolutionOptions {
11
46
  replayPrevention?: ReplayPreventionMode;
12
47
  replayStore?: ReplayStore;
48
+ context?: string;
13
49
  debug?: boolean;
14
50
  onWarning?: (warning: VerifyWarning) => void;
15
51
  }
16
- export type VerifyWarningReason = 'invalid-token' | 'expired-token' | 'invalid-solution' | 'replay-detected' | 'configuration-error';
52
+ export type VerifyFailureReason = 'invalid-token' | 'expired-token' | 'invalid-solution' | 'context-mismatch' | 'replay-detected' | 'configuration-error';
53
+ export type VerifyWarningReason = VerifyFailureReason;
17
54
  export interface VerifyWarning {
18
55
  reason: VerifyWarningReason;
19
56
  message: string;
20
57
  error?: unknown;
21
58
  }
59
+ export type VerifySolutionResult = {
60
+ valid: true;
61
+ } | {
62
+ valid: false;
63
+ reason: VerifyFailureReason;
64
+ message: string;
65
+ };
22
66
  export interface SolveChallengeOptions {
23
67
  maxIterations?: number;
24
68
  maxDurationMs?: number;
@@ -26,34 +70,18 @@ export interface SolveChallengeOptions {
26
70
  export declare class LocalReplayStore implements ReplayStore {
27
71
  private usedTokens;
28
72
  consume(jti: string, expiresAt: number): Promise<boolean>;
73
+ consumeMany(jtis: string[], expiresAt: number): Promise<boolean>;
29
74
  private cleanup;
30
75
  }
31
76
  /**
32
- * Creates one or more PoW challenges and returns them as signed JWT tokens.
33
- *
34
- * @param difficulty - Number of leading zeros required in the hash (default 5)
35
- * @param amount - Number of challenges to create (default 4)
36
- * @param ttlSeconds - Time to live for each challenge in seconds (default 30)
37
- * @returns An array of JWT challenge tokens
77
+ * Selects bounded proof-of-work using a server-owned risk floor and untrusted timing calibration.
38
78
  */
79
+ export declare function selectWorkload(options?: AdaptiveWorkloadOptions): Workload;
80
+ export declare function calibrateNode(iterations?: number): ClientCalibration;
81
+ export declare const calibrateClient: typeof calibrateNode;
39
82
  export declare function createChallenge(difficulty?: number, amount?: number, ttlSeconds?: number): ChallengeToken[];
40
- /**
41
- * Solves one or more PoW challenges encoded in JWT tokens.
42
- *
43
- * @param token - The JWT challenge token or an array of tokens
44
- * @param options - Optional guardrails for test/debug usage (`maxIterations`, `maxDurationMs`)
45
- * @returns The nonce/hash pair for single input or an array of them for multiple tokens
46
- */
47
- export declare function solveChallenge(token: ChallengeToken): ChallengeSolution | undefined;
83
+ export declare function createChallenge(options: ChallengeOptions): ChallengeToken[];
48
84
  export declare function solveChallenge(token: ChallengeToken, options?: SolveChallengeOptions): ChallengeSolution | undefined;
49
- export declare function solveChallenge(token: ChallengeToken[]): ChallengeSolution[] | undefined;
50
85
  export declare function solveChallenge(token: ChallengeToken[], options?: SolveChallengeOptions): ChallengeSolution[] | undefined;
51
- /**
52
- * Verifies a PoW solution returned by the client.
53
- *
54
- * @param token - The original JWT issued as the challenge (single token or array of tokens)
55
- * @param nonce - The nonce/answer submitted by the client (single nonce, array of nonces, or array of solution objects)
56
- * @returns true only if every provided solution is valid; otherwise false
57
- */
58
- export declare function verifySolution(token: ChallengeToken | ChallengeToken[], nonce: number | string | Array<number | string> | ChallengeSolution | ChallengeSolution[], options?: VerifySolutionOptions): Promise<boolean>;
86
+ export declare function verifySolution(token: ChallengeToken | ChallengeToken[], nonce: number | string | Array<number | string> | ChallengeSolution | ChallengeSolution[], options?: VerifySolutionOptions): Promise<VerifySolutionResult>;
59
87
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;CAC9C;AAED,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,iBAAiB,GACjB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,UAAU,CAA6B;IAEzC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW/D,OAAO,CAAC,OAAO;CAShB;AA0LD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,UAAU,GAAE,MAAU,EACtB,MAAM,GAAE,MAAU,EAClB,UAAU,GAAE,MAAW,GACtB,cAAc,EAAE,CAUlB;AA0FD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,GAAG,SAAS,CAAC;AACrF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,EACrB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,iBAAiB,GAAG,SAAS,CAAC;AACjC,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,iBAAiB,EAAE,GAAG,SAAS,CAAC;AACzF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,iBAAiB,EAAE,GAAG,SAAS,CAAC;AAwBnC;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,EACxC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,iBAAiB,GAAG,iBAAiB,EAAE,EACzF,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,OAAO,CAAC,CAuDlB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,QAAQ,CAAC,CAAC;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;CAC9C;AAED,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,kBAAkB,GAClB,iBAAiB,GACjB,qBAAqB,CAAC;AAE1B,MAAM,MAAM,mBAAmB,GAAG,mBAAmB,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,UAAU,CAA6B;IAEzC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIzD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAatE,OAAO,CAAC,OAAO;CAMhB;AA+ED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,uBAA4B,GAAG,QAAQ,CAuB9E;AAED,wBAAgB,aAAa,CAAC,UAAU,SAAM,GAAG,iBAAiB,CAejE;AAED,eAAO,MAAM,eAAe,sBAAgB,CAAC;AA8C7C,wBAAgB,eAAe,CAC7B,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,GAClB,cAAc,EAAE,CAAC;AACpB,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,cAAc,EAAE,CAAC;AAqE7E,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,iBAAiB,GAAG,SAAS,CAAC;AACtH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,iBAAiB,EAAE,GAAG,SAAS,CAAC;AA2E1H,wBAAsB,cAAc,CAClC,KAAK,EAAE,cAAc,GAAG,cAAc,EAAE,EACxC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,iBAAiB,GAAG,iBAAiB,EAAE,EACzF,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,oBAAoB,CAAC,CA4E/B"}