@bravely-studios/account-web 0.3.4
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/LICENSE +10 -0
- package/README.md +262 -0
- package/dist/ActivationStateMachine.d.ts +50 -0
- package/dist/ActivationStateMachine.d.ts.map +1 -0
- package/dist/ActivationStateMachine.js +141 -0
- package/dist/ActivationStateMachine.js.map +1 -0
- package/dist/BravelyAccountManager.d.ts +156 -0
- package/dist/BravelyAccountManager.d.ts.map +1 -0
- package/dist/BravelyAccountManager.js +621 -0
- package/dist/BravelyAccountManager.js.map +1 -0
- package/dist/EntitlementCache.d.ts +50 -0
- package/dist/EntitlementCache.d.ts.map +1 -0
- package/dist/EntitlementCache.js +116 -0
- package/dist/EntitlementCache.js.map +1 -0
- package/dist/components/ActivationLadder.d.ts +78 -0
- package/dist/components/ActivationLadder.d.ts.map +1 -0
- package/dist/components/ActivationLadder.js +145 -0
- package/dist/components/ActivationLadder.js.map +1 -0
- package/dist/components/CrossAppCard.d.ts +48 -0
- package/dist/components/CrossAppCard.d.ts.map +1 -0
- package/dist/components/CrossAppCard.js +45 -0
- package/dist/components/CrossAppCard.js.map +1 -0
- package/dist/deprecation.d.ts +19 -0
- package/dist/deprecation.d.ts.map +1 -0
- package/dist/deprecation.js +83 -0
- package/dist/deprecation.js.map +1 -0
- package/dist/displayName.d.ts +15 -0
- package/dist/displayName.d.ts.map +1 -0
- package/dist/displayName.js +41 -0
- package/dist/displayName.js.map +1 -0
- package/dist/dpop.d.ts +30 -0
- package/dist/dpop.d.ts.map +1 -0
- package/dist/dpop.js +87 -0
- package/dist/dpop.js.map +1 -0
- package/dist/hooks/useActivationLaneFromUrl.d.ts +54 -0
- package/dist/hooks/useActivationLaneFromUrl.d.ts.map +1 -0
- package/dist/hooks/useActivationLaneFromUrl.js +105 -0
- package/dist/hooks/useActivationLaneFromUrl.js.map +1 -0
- package/dist/hooks/useFreshLaunchRestoration.d.ts +62 -0
- package/dist/hooks/useFreshLaunchRestoration.d.ts.map +1 -0
- package/dist/hooks/useFreshLaunchRestoration.js +135 -0
- package/dist/hooks/useFreshLaunchRestoration.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +50 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +107 -0
- package/dist/oauth.js.map +1 -0
- package/dist/storage.d.ts +48 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +153 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Copyright (c) 2026 Bravely Studios LLC. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software is the proprietary work of Bravely Studios LLC. No part of this
|
|
4
|
+
package may be reproduced, distributed, transmitted, modified, or otherwise
|
|
5
|
+
exploited in any form or by any means without the prior written permission of
|
|
6
|
+
Bravely Studios LLC.
|
|
7
|
+
|
|
8
|
+
Unauthorized use, copying, or distribution is strictly prohibited.
|
|
9
|
+
|
|
10
|
+
For licensing inquiries, contact jeff@bravely.dev.
|
package/README.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# @bravely-studios/account-web
|
|
2
|
+
|
|
3
|
+
Thin TypeScript facade over the Bravely identity API for browser-based
|
|
4
|
+
Bravely Studios apps. Used internally across the Bravely web app family.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
- **OAuth 2.1 + PKCE** sign-in via `auth.bravely.dev` — RFC 7636 S256.
|
|
9
|
+
- **BAS lifecycle** — exchange / refresh against `identity.bravely.dev`.
|
|
10
|
+
- **Entitlement cache** — 72h offline fallback.
|
|
11
|
+
- **Activation state machine** — checkout-to-active flow.
|
|
12
|
+
- **Paddle account actions** — BAS-authed checkout session and
|
|
13
|
+
customer-portal session helpers through `identity.bravely.dev`.
|
|
14
|
+
- **`Bravely-Deprecation` handling** — soft warnings + hard
|
|
15
|
+
`BravelyClientKilledError` on HTTP 426 kill-switch.
|
|
16
|
+
- **DPoP-ready** — RFC 9449 proof generation.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @bravely-studios/account-web
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { BravelyAccountManager } from "@bravely-studios/account-web";
|
|
28
|
+
|
|
29
|
+
const manager = new BravelyAccountManager({
|
|
30
|
+
authority: "https://auth.bravely.dev",
|
|
31
|
+
appSlug: "diskaroo",
|
|
32
|
+
clientVersion: "1.3.1",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// On page load
|
|
36
|
+
await manager.restore();
|
|
37
|
+
manager.onStateChange((state) => {
|
|
38
|
+
if (state.kind === "signed_in") renderApp(state);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// On a sign-in button click
|
|
42
|
+
await manager.signIn();
|
|
43
|
+
|
|
44
|
+
// React 19? Subscribe via useSyncExternalStore — `getState` returns a stable
|
|
45
|
+
// reference between updates (0.2.1+), so no `_cachedState` workaround needed.
|
|
46
|
+
//
|
|
47
|
+
// const state = useSyncExternalStore(
|
|
48
|
+
// manager.onStateChange.bind(manager),
|
|
49
|
+
// manager.getState.bind(manager),
|
|
50
|
+
// );
|
|
51
|
+
|
|
52
|
+
// Entitlement gate
|
|
53
|
+
if (await manager.hasEntitlement("diskaroo_pro")) {
|
|
54
|
+
showProFeatures();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Paid upgrade
|
|
58
|
+
await manager.openCheckout("annual");
|
|
59
|
+
|
|
60
|
+
// Manage subscription
|
|
61
|
+
const portal = await manager.createPaddlePortalSession();
|
|
62
|
+
window.open(portal.url, "_blank");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Module map
|
|
66
|
+
|
|
67
|
+
| File | Responsibility |
|
|
68
|
+
|-----------------------------------------|-----------------------------------------------------------------------------------|
|
|
69
|
+
| `BravelyAccountManager.ts` | Public facade. Sign-in, sign-out, entitlements, checkout, portal, activation. |
|
|
70
|
+
| `EntitlementCache.ts` | 72h offline cache with TTL + invalidation. |
|
|
71
|
+
| `ActivationStateMachine.ts` | Port of the canonical activation machine. |
|
|
72
|
+
| `oauth.ts` | PKCE S256 helpers + authorize URL builder. |
|
|
73
|
+
| `storage.ts` | sessionStorage / IndexedDB / memory adapters. |
|
|
74
|
+
| `dpop.ts` | WebCrypto ES256 keypair + RFC 9449 proofs (Gate 2-ready). |
|
|
75
|
+
| `deprecation.ts` | `Bravely-Deprecation` header parser + error classes. |
|
|
76
|
+
| `types.ts` | TS types mirroring the OpenAPI 3.1 schemas. |
|
|
77
|
+
| `displayName.ts` | Slug → display-name lookup (`diskaroo` → `Diskaroo`). |
|
|
78
|
+
| `components/ActivationLadder.tsx` | **M3** — post-checkout 4-phase ladder (`Activating <App> Pro…`). |
|
|
79
|
+
| `components/CrossAppCard.tsx` | **M4** — third-quadrant card (`You own N Bravely Pro apps on this account.`). |
|
|
80
|
+
| `hooks/useActivationLaneFromUrl.ts` | **M3** — detect `?upgraded` / `?checkout` / `?subscription` return params. |
|
|
81
|
+
| `hooks/useFreshLaunchRestoration.ts` | **M2** — silent rehydrate + brief `Synced N items from your <device>.` toast. |
|
|
82
|
+
|
|
83
|
+
## C-ux M2/M3/M4 exports (0.2.0)
|
|
84
|
+
|
|
85
|
+
Wave A of the C-ux M2-M4 rollout (`cux-m2-m4-rollout-plan.md`). New exports
|
|
86
|
+
let the four D-Web variants — `prodjectly`, `scry-web`, `printscreenly-web`,
|
|
87
|
+
`todoingly-web` — mount the foundational surfaces in Wave B-D.
|
|
88
|
+
|
|
89
|
+
### `<ActivationLadder>` (M3)
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { ActivationLadder } from "@bravely-studios/account-web";
|
|
93
|
+
|
|
94
|
+
<ActivationLadder
|
|
95
|
+
state={manager.getActivationState()}
|
|
96
|
+
appSlug="diskaroo"
|
|
97
|
+
orderId={paddleOrderId ?? null}
|
|
98
|
+
onRetry={() => manager.pollForActivation()}
|
|
99
|
+
onContactSupport={() => window.open("mailto:jeff@bravely.dev")}
|
|
100
|
+
/>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Renders nothing unless the manager is in `post_checkout_activation`.
|
|
104
|
+
Auto-ticks elapsed every second; rolls through the 4 locked phases at
|
|
105
|
+
0/15s/60s/120s. Phase copy is byte-identical to
|
|
106
|
+
`bravely-commerce-router/docs/activation-state-machine.json` — the
|
|
107
|
+
drift-test in `__tests__/ActivationLadder.test.tsx` enforces it.
|
|
108
|
+
|
|
109
|
+
### `<CrossAppCard>` (M4)
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { CrossAppCard } from "@bravely-studios/account-web";
|
|
113
|
+
|
|
114
|
+
<CrossAppCard
|
|
115
|
+
entitlements={state.entitlements}
|
|
116
|
+
currentAppSlug="diskaroo"
|
|
117
|
+
variant="card" // or "footer-chip"
|
|
118
|
+
dismissible={false} // journey-doc default = persistent
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Renders nothing when the user has zero cross-app entitlements. Excludes
|
|
123
|
+
the current app's own `<slug>_pro` from the count; treats
|
|
124
|
+
`bravely_premium` as a single bundle token.
|
|
125
|
+
|
|
126
|
+
### `useActivationLaneFromUrl()` (M3)
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
const { inActivationLane, source, clearUrlParam } = useActivationLaneFromUrl({
|
|
130
|
+
manager,
|
|
131
|
+
autoStartPolling: true,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (inActivationLane) clearUrlParam();
|
|
136
|
+
}, [inActivationLane]);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Detects the three observed post-checkout return URL patterns:
|
|
140
|
+
`?upgraded=true` (prodjectly), `?checkout=complete` (scry-web),
|
|
141
|
+
`?subscription=success` (todoingly-web). Auto-calls
|
|
142
|
+
`manager.notifyCheckoutCompleted()` and, if `autoStartPolling`, kicks
|
|
143
|
+
off `manager.pollForActivation()`.
|
|
144
|
+
|
|
145
|
+
### `useFreshLaunchRestoration()` (M2)
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const fresh = useFreshLaunchRestoration({
|
|
149
|
+
manager,
|
|
150
|
+
itemsLabel: "tasks",
|
|
151
|
+
resolveOtherDeviceName: () => null, // Gate 1 fallback
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
fresh.setSyncedCount(myCollection.length);
|
|
156
|
+
}, [myCollection.length]);
|
|
157
|
+
|
|
158
|
+
return fresh.shouldShowToast ? <Toast>{fresh.toastText}</Toast> : null;
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
First-launch detector. UI is silent for 3 s after sign-in; then `Synced
|
|
162
|
+
N items from your <device>.` shows for the host page to dismiss. The
|
|
163
|
+
banned phrase family (`Welcome back. Restoring your Pro features`) is
|
|
164
|
+
absent by design. Gate 1 device-name resolver is null; the hook drops
|
|
165
|
+
the `from your <device>` anchor automatically.
|
|
166
|
+
|
|
167
|
+
### Manager additions
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// Cross-app filter — excludes <currentApp>_pro, includes bravely_premium.
|
|
171
|
+
const others = manager.crossAppEntitlements();
|
|
172
|
+
|
|
173
|
+
// Post-checkout polling runner — 30 retries × 1s..8s capped backoff.
|
|
174
|
+
const result = await manager.pollForActivation();
|
|
175
|
+
// result.outcome: "active" | "exhausted" | "timeout" | "not_signed_in"
|
|
176
|
+
|
|
177
|
+
// Paddle customer-portal session — callers decide how to open the URL.
|
|
178
|
+
const portal = await manager.createPaddlePortalSession();
|
|
179
|
+
// portal.url is the hosted customer-portal URL.
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Storage adapters
|
|
183
|
+
|
|
184
|
+
- **`sessionStorage`** — BAS, PKCE verifier, state. Wipes on tab close.
|
|
185
|
+
- **`IndexedDB`** — refresh_token, entitlement cache, DPoP key handle.
|
|
186
|
+
Survives reload; key bytes never leave the browser (extractable=false).
|
|
187
|
+
- **In-memory** — SSR / test fallback.
|
|
188
|
+
|
|
189
|
+
Host pages can swap in their own ServiceWorker-backed adapter by passing
|
|
190
|
+
`storage` into the manager config.
|
|
191
|
+
|
|
192
|
+
## Activation state machine
|
|
193
|
+
|
|
194
|
+
`getActivationState()` returns the current state from the canonical machine.
|
|
195
|
+
Host pages render UI off the `name` (`restoring_session`, `verifying_entitlement`,
|
|
196
|
+
`entitlement_cached_valid`, `post_checkout_activation`, etc.) and the
|
|
197
|
+
`busy` flag (whether to show a spinner). CLAUDE.md hard rule
|
|
198
|
+
`feedback_no_etas`: never render a predicted ETA — always elapsed time.
|
|
199
|
+
|
|
200
|
+
## DPoP gate transition
|
|
201
|
+
|
|
202
|
+
- **Gate 1 (today):** BAS-authed manager requests keep
|
|
203
|
+
`Authorization: Bearer <bas>` for router compatibility and also attach a
|
|
204
|
+
valid `DPoP` proof header with `ath`. The server runs in `off` mode and
|
|
205
|
+
accepts the Bearer scheme without verifying the proof.
|
|
206
|
+
- **Gate 2 (next):** server enforcement can start from real client traffic
|
|
207
|
+
because `getAppDataToken()`, `openCheckout()`,
|
|
208
|
+
`createPaddlePortalSession()`, entitlement refreshes, and activation polls
|
|
209
|
+
already carry proof headers.
|
|
210
|
+
|
|
211
|
+
## Changelog
|
|
212
|
+
|
|
213
|
+
### 0.3.4 — 2026-05-13
|
|
214
|
+
|
|
215
|
+
- **Infra:** package now publishes to `registry.npmjs.org` (public scope).
|
|
216
|
+
Previously hosted on GitHub Packages. No code changes; behavior is
|
|
217
|
+
identical to `0.3.3`.
|
|
218
|
+
|
|
219
|
+
### 0.3.3 — 2026-05-13
|
|
220
|
+
|
|
221
|
+
- **Feature:** `BravelyAccountManager` enters `fresh_launch_restoration`
|
|
222
|
+
when a BAS exists but the entitlement cache is missing, smoothing the
|
|
223
|
+
cold-start UX before the first entitlement read completes.
|
|
224
|
+
|
|
225
|
+
### 0.3.2 — 2026-05-13
|
|
226
|
+
|
|
227
|
+
- **Fix:** BAS-authenticated manager requests now attach an RFC 9449 `DPoP`
|
|
228
|
+
proof header, including `ath`, while preserving the Gate 1
|
|
229
|
+
`Authorization: Bearer <bas>` scheme required by current router endpoints.
|
|
230
|
+
- **Fix:** fresh entitlement cache writes carry the generated DPoP JKT
|
|
231
|
+
thumbprint so Gate 2 cache binding can inspect the local key identity.
|
|
232
|
+
|
|
233
|
+
### 0.3.0 — 2026-05-13
|
|
234
|
+
|
|
235
|
+
- **Feature:** added `createPaddlePortalSession()`, a BAS-authed manager
|
|
236
|
+
primitive for `POST /api/paddle-portal`. It returns the hosted Paddle
|
|
237
|
+
customer-portal URL DTO and leaves checkout activation behavior unchanged.
|
|
238
|
+
- **Types:** exported `PaddlePortalSession` and aligned the API baseline note
|
|
239
|
+
to `bravely-commerce-router` OpenAPI `1.2.1`.
|
|
240
|
+
|
|
241
|
+
### 0.2.1 — 2026-05-12
|
|
242
|
+
|
|
243
|
+
- **Fix:** `getState()` now returns a **stable reference** between writes.
|
|
244
|
+
Previously it cloned on every read, which broke React 19's
|
|
245
|
+
`useSyncExternalStore` (snapshot identity changed every render → React
|
|
246
|
+
error #185 / "Maximum update depth exceeded"). The clone now happens
|
|
247
|
+
once, inside `setState()`, before listeners fire. Listener payloads and
|
|
248
|
+
the next `getState()` call return the same object reference. Three
|
|
249
|
+
consumers (`prodjectly`, `printscreenly-web`, `todoingly-web`) carry
|
|
250
|
+
module-level `_cachedState` workarounds that become redundant with this
|
|
251
|
+
release — they can be dropped in a follow-up sweep.
|
|
252
|
+
- **Fix:** internal `libVersion` default aligned to package version
|
|
253
|
+
(was hard-coded to `"0.2.0"`).
|
|
254
|
+
- **Docs:** install snippet uses the correct `@bravely-studios` scope.
|
|
255
|
+
|
|
256
|
+
### 0.2.0 — 2026-05-11
|
|
257
|
+
|
|
258
|
+
- Initial C-ux M2/M3/M4 facade shipped.
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
Proprietary. (c) 2026 Bravely Studios LLC.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ActivationState, ActivationStateName } from "./types.js";
|
|
2
|
+
/** Canonical event names accepted by `transition()`. */
|
|
3
|
+
export type ActivationEvent = "app_launched" | "app_launched_no_session" | "bas_found" | "bas_missing" | "bas_invalid" | "entitlement_check_success_pro" | "entitlement_check_success_none" | "entitlement_check_failure" | "checkout_completed" | "activation_polled_active" | "activation_polled_inactive" | "activation_timed_out" | "user_signed_in" | "user_skipped" | "user_clicked_retry";
|
|
4
|
+
interface TransitionContext {
|
|
5
|
+
bas_present?: boolean;
|
|
6
|
+
entitlement_cache_present?: boolean;
|
|
7
|
+
}
|
|
8
|
+
type ConditionalTarget = {
|
|
9
|
+
when: TransitionContext;
|
|
10
|
+
to: ActivationStateName;
|
|
11
|
+
else?: ActivationStateName;
|
|
12
|
+
};
|
|
13
|
+
type TransitionTarget = ActivationStateName | ConditionalTarget;
|
|
14
|
+
/**
|
|
15
|
+
* The retry budget for `post_checkout_activation` — matches the JSON spec.
|
|
16
|
+
* Host pages use this to schedule entitlement polling.
|
|
17
|
+
*/
|
|
18
|
+
export declare const POST_CHECKOUT_RETRY: {
|
|
19
|
+
readonly maxRetries: 30;
|
|
20
|
+
readonly initialBackoffMs: 1000;
|
|
21
|
+
readonly maxBackoffMs: 8000;
|
|
22
|
+
readonly backoffMultiplier: 1.5;
|
|
23
|
+
};
|
|
24
|
+
/** Auto-advance timeouts (ms). Matches the JSON `auto_advance_after_ms`. */
|
|
25
|
+
export declare const AUTO_ADVANCE_MS: Partial<Record<ActivationStateName, number>>;
|
|
26
|
+
/** The state name a given state auto-advances to on timeout. */
|
|
27
|
+
export declare const AUTO_ADVANCE_TO: Partial<Record<ActivationStateName, ActivationStateName>>;
|
|
28
|
+
export declare class ActivationStateMachine {
|
|
29
|
+
private state;
|
|
30
|
+
private subscribers;
|
|
31
|
+
constructor(initial?: ActivationStateName);
|
|
32
|
+
current(): ActivationState;
|
|
33
|
+
/**
|
|
34
|
+
* Attempt a transition. Returns the new state (or the unchanged state if
|
|
35
|
+
* the event is not a valid transition from the current state).
|
|
36
|
+
*/
|
|
37
|
+
transition(event: ActivationEvent, ctx?: TransitionContext): ActivationState;
|
|
38
|
+
/** Force the machine into a specific state (used by activation pollers). */
|
|
39
|
+
force(name: ActivationStateName): ActivationState;
|
|
40
|
+
/** Subscribe to state changes; returns an unsubscribe function. */
|
|
41
|
+
onStateChange(cb: (s: ActivationState) => void): () => void;
|
|
42
|
+
/** Elapsed ms since the current state was entered. */
|
|
43
|
+
elapsedMs(): number;
|
|
44
|
+
/** Lookup table introspection. */
|
|
45
|
+
static transitions: Record<ActivationStateName, Partial<Record<ActivationEvent, TransitionTarget>>>;
|
|
46
|
+
static busyStates: Set<ActivationStateName>;
|
|
47
|
+
private enter;
|
|
48
|
+
}
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=ActivationStateMachine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActivationStateMachine.d.ts","sourceRoot":"","sources":["../src/ActivationStateMachine.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEvE,wDAAwD;AACxD,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,yBAAyB,GACzB,WAAW,GACX,aAAa,GACb,aAAa,GACb,+BAA+B,GAC/B,gCAAgC,GAChC,2BAA2B,GAC3B,oBAAoB,GACpB,0BAA0B,GAC1B,4BAA4B,GAC5B,sBAAsB,GACtB,gBAAgB,GAChB,cAAc,GACd,oBAAoB,CAAC;AAEzB,UAAU,iBAAiB;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,iBAAiB,CAAC;IACxB,EAAE,EAAE,mBAAmB,CAAC;IACxB,IAAI,CAAC,EAAE,mBAAmB,CAAC;CAC5B,CAAC;AAEF,KAAK,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC;AAwDhE;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;CAKtB,CAAC;AAEX,4EAA4E;AAC5E,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAKxE,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,mBAAmB,CAAC,CAKrF,CAAC;AAEF,qBAAa,sBAAsB;IACjC,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,WAAW,CAA2C;gBAElD,OAAO,GAAE,mBAA4B;IAIjD,OAAO,IAAI,eAAe;IAI1B;;;OAGG;IACH,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,GAAE,iBAAsB,GAAG,eAAe;IAShF,4EAA4E;IAC5E,KAAK,CAAC,IAAI,EAAE,mBAAmB,GAAG,eAAe;IAIjD,mEAAmE;IACnE,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,eAAe,KAAK,IAAI,GAAG,MAAM,IAAI;IAK3D,sDAAsD;IACtD,SAAS,IAAI,MAAM;IAInB,kCAAkC;IAClC,MAAM,CAAC,WAAW,kFAAe;IACjC,MAAM,CAAC,UAAU,2BAAe;IAEhC,OAAO,CAAC,KAAK;CAKd"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Port of bravely-commerce-router/docs/activation-state-machine.json (v1.0.0).
|
|
2
|
+
//
|
|
3
|
+
// Keep aligned by hand. The canonical doc is the source of truth; this port
|
|
4
|
+
// is a thin TS surface so host pages can render UI off `getActivationState()`.
|
|
5
|
+
//
|
|
6
|
+
// CLAUDE.md feedback_no_etas: every state surfaces ELAPSED time, never a
|
|
7
|
+
// predicted ETA. Anti-patterns from M5 enforced in the banned-phrases CI
|
|
8
|
+
// check (in bravely-commerce-router); the strings here MUST match the JSON.
|
|
9
|
+
const TRANSITIONS = {
|
|
10
|
+
idle: {
|
|
11
|
+
app_launched: { when: { bas_present: true }, to: "restoring_session" },
|
|
12
|
+
app_launched_no_session: { when: { bas_present: false }, to: "pending_user_action" },
|
|
13
|
+
},
|
|
14
|
+
restoring_session: {
|
|
15
|
+
bas_found: "verifying_entitlement",
|
|
16
|
+
bas_missing: "fresh_launch_restoration",
|
|
17
|
+
bas_invalid: "pending_user_action",
|
|
18
|
+
},
|
|
19
|
+
verifying_entitlement: {
|
|
20
|
+
entitlement_check_success_pro: "entitlement_fresh_valid",
|
|
21
|
+
entitlement_check_success_none: "entitlement_none",
|
|
22
|
+
entitlement_check_failure: {
|
|
23
|
+
when: { entitlement_cache_present: true },
|
|
24
|
+
to: "entitlement_cached_valid",
|
|
25
|
+
else: "failed",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
entitlement_cached_valid: {
|
|
29
|
+
entitlement_check_success_pro: "entitlement_fresh_valid",
|
|
30
|
+
entitlement_check_success_none: "entitlement_none",
|
|
31
|
+
entitlement_check_failure: "entitlement_cached_valid",
|
|
32
|
+
},
|
|
33
|
+
entitlement_fresh_valid: {},
|
|
34
|
+
entitlement_none: {
|
|
35
|
+
checkout_completed: "post_checkout_activation",
|
|
36
|
+
},
|
|
37
|
+
post_checkout_activation: {
|
|
38
|
+
activation_polled_active: "entitlement_fresh_valid",
|
|
39
|
+
activation_polled_inactive: "post_checkout_activation",
|
|
40
|
+
activation_timed_out: "failed",
|
|
41
|
+
},
|
|
42
|
+
fresh_launch_restoration: {
|
|
43
|
+
entitlement_check_success_pro: "entitlement_fresh_valid",
|
|
44
|
+
entitlement_check_success_none: "entitlement_none",
|
|
45
|
+
entitlement_check_failure: "failed",
|
|
46
|
+
},
|
|
47
|
+
pending_user_action: {
|
|
48
|
+
user_signed_in: "restoring_session",
|
|
49
|
+
user_skipped: "entitlement_none",
|
|
50
|
+
},
|
|
51
|
+
failed: {
|
|
52
|
+
user_clicked_retry: "pending_user_action",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const BUSY_STATES = new Set([
|
|
56
|
+
"restoring_session",
|
|
57
|
+
"verifying_entitlement",
|
|
58
|
+
"post_checkout_activation",
|
|
59
|
+
"fresh_launch_restoration",
|
|
60
|
+
]);
|
|
61
|
+
/**
|
|
62
|
+
* The retry budget for `post_checkout_activation` — matches the JSON spec.
|
|
63
|
+
* Host pages use this to schedule entitlement polling.
|
|
64
|
+
*/
|
|
65
|
+
export const POST_CHECKOUT_RETRY = {
|
|
66
|
+
maxRetries: 30,
|
|
67
|
+
initialBackoffMs: 1000,
|
|
68
|
+
maxBackoffMs: 8000,
|
|
69
|
+
backoffMultiplier: 1.5,
|
|
70
|
+
};
|
|
71
|
+
/** Auto-advance timeouts (ms). Matches the JSON `auto_advance_after_ms`. */
|
|
72
|
+
export const AUTO_ADVANCE_MS = {
|
|
73
|
+
restoring_session: 8000,
|
|
74
|
+
verifying_entitlement: 8000,
|
|
75
|
+
post_checkout_activation: 120_000,
|
|
76
|
+
fresh_launch_restoration: 10_000,
|
|
77
|
+
};
|
|
78
|
+
/** The state name a given state auto-advances to on timeout. */
|
|
79
|
+
export const AUTO_ADVANCE_TO = {
|
|
80
|
+
restoring_session: "failed",
|
|
81
|
+
verifying_entitlement: "failed",
|
|
82
|
+
post_checkout_activation: "failed",
|
|
83
|
+
fresh_launch_restoration: "failed",
|
|
84
|
+
};
|
|
85
|
+
export class ActivationStateMachine {
|
|
86
|
+
state;
|
|
87
|
+
subscribers = new Set();
|
|
88
|
+
constructor(initial = "idle") {
|
|
89
|
+
this.state = { name: initial, enteredAt: new Date(), busy: BUSY_STATES.has(initial) };
|
|
90
|
+
}
|
|
91
|
+
current() {
|
|
92
|
+
return { ...this.state };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Attempt a transition. Returns the new state (or the unchanged state if
|
|
96
|
+
* the event is not a valid transition from the current state).
|
|
97
|
+
*/
|
|
98
|
+
transition(event, ctx = {}) {
|
|
99
|
+
const fromTable = TRANSITIONS[this.state.name];
|
|
100
|
+
const target = fromTable[event];
|
|
101
|
+
if (!target)
|
|
102
|
+
return this.current(); // no-op
|
|
103
|
+
const next = resolveTarget(target, ctx);
|
|
104
|
+
if (!next)
|
|
105
|
+
return this.current();
|
|
106
|
+
return this.enter(next);
|
|
107
|
+
}
|
|
108
|
+
/** Force the machine into a specific state (used by activation pollers). */
|
|
109
|
+
force(name) {
|
|
110
|
+
return this.enter(name);
|
|
111
|
+
}
|
|
112
|
+
/** Subscribe to state changes; returns an unsubscribe function. */
|
|
113
|
+
onStateChange(cb) {
|
|
114
|
+
this.subscribers.add(cb);
|
|
115
|
+
return () => this.subscribers.delete(cb);
|
|
116
|
+
}
|
|
117
|
+
/** Elapsed ms since the current state was entered. */
|
|
118
|
+
elapsedMs() {
|
|
119
|
+
return Date.now() - this.state.enteredAt.getTime();
|
|
120
|
+
}
|
|
121
|
+
/** Lookup table introspection. */
|
|
122
|
+
static transitions = TRANSITIONS;
|
|
123
|
+
static busyStates = BUSY_STATES;
|
|
124
|
+
enter(name) {
|
|
125
|
+
this.state = { name, enteredAt: new Date(), busy: BUSY_STATES.has(name) };
|
|
126
|
+
for (const cb of this.subscribers)
|
|
127
|
+
cb({ ...this.state });
|
|
128
|
+
return this.current();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function resolveTarget(target, ctx) {
|
|
132
|
+
if (typeof target === "string")
|
|
133
|
+
return target;
|
|
134
|
+
for (const [k, v] of Object.entries(target.when)) {
|
|
135
|
+
if (ctx[k] !== v) {
|
|
136
|
+
return target.else ?? null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return target.to;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=ActivationStateMachine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActivationStateMachine.js","sourceRoot":"","sources":["../src/ActivationStateMachine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { BravelyAccountState, ManagerConfig, Entitlement, CheckoutPlan, PaddlePortalSession, ActivationState, LibVersionPolicy } from "./types.js";
|
|
2
|
+
import { BravelyClientKilledError, OAuthError } from "./deprecation.js";
|
|
3
|
+
/** Result of `pollForActivation()` — the manager's M3 activation runner. */
|
|
4
|
+
export type ActivationResult = {
|
|
5
|
+
outcome: "active";
|
|
6
|
+
entitlements: Entitlement[];
|
|
7
|
+
attempts: number;
|
|
8
|
+
} | {
|
|
9
|
+
outcome: "timeout";
|
|
10
|
+
attempts: number;
|
|
11
|
+
} | {
|
|
12
|
+
outcome: "exhausted";
|
|
13
|
+
attempts: number;
|
|
14
|
+
} | {
|
|
15
|
+
outcome: "not_signed_in";
|
|
16
|
+
};
|
|
17
|
+
type StateListener = (state: BravelyAccountState) => void;
|
|
18
|
+
export declare class BravelyAccountManager {
|
|
19
|
+
private readonly cfg;
|
|
20
|
+
private readonly storage;
|
|
21
|
+
private readonly cache;
|
|
22
|
+
private readonly activation;
|
|
23
|
+
private state;
|
|
24
|
+
private listeners;
|
|
25
|
+
private libVersionPolicy;
|
|
26
|
+
private dpopKeypairPromise;
|
|
27
|
+
private dpopJktThumbprint;
|
|
28
|
+
constructor(config: ManagerConfig);
|
|
29
|
+
/**
|
|
30
|
+
* Return the current state. The returned reference is **stable across
|
|
31
|
+
* calls** until `setState()` is invoked — i.e. while the state has not
|
|
32
|
+
* changed, two consecutive `getState()` calls produce values for which
|
|
33
|
+
* `Object.is(a, b)` is true.
|
|
34
|
+
*
|
|
35
|
+
* This is the contract React 19's `useSyncExternalStore` requires: the
|
|
36
|
+
* `getSnapshot` callback must return `===` identical values until the
|
|
37
|
+
* `subscribe` callback fires. Returning a fresh clone on every read (which
|
|
38
|
+
* an earlier version of this facade did) triggers React error #185 —
|
|
39
|
+
* "Maximum update depth exceeded" — because the snapshot keeps "changing"
|
|
40
|
+
* even when nothing has happened.
|
|
41
|
+
*
|
|
42
|
+
* The clone happens once, at write time, inside `setState()`. Listeners
|
|
43
|
+
* receive the same reference this method returns until the next
|
|
44
|
+
* `setState()` swaps in a new canonical clone.
|
|
45
|
+
*/
|
|
46
|
+
getState(): BravelyAccountState;
|
|
47
|
+
onStateChange(cb: StateListener): () => void;
|
|
48
|
+
getActivationState(): ActivationState;
|
|
49
|
+
getLibVersionPolicy(): LibVersionPolicy | null;
|
|
50
|
+
/**
|
|
51
|
+
* Hydrate from persistent storage and (if a BAS is present) verify it
|
|
52
|
+
* against the identity API. Call once on page load before rendering UI
|
|
53
|
+
* that depends on `getState()`.
|
|
54
|
+
*/
|
|
55
|
+
restore(): Promise<BravelyAccountState>;
|
|
56
|
+
/**
|
|
57
|
+
* Begin the OAuth 2.1 + PKCE sign-in dance. Resolves to the new state. If
|
|
58
|
+
* the browser is at the start of the dance, this triggers a redirect
|
|
59
|
+
* (and the promise effectively never resolves before unload). If the
|
|
60
|
+
* browser is on the callback URL with `?code=...&state=...`, this finishes
|
|
61
|
+
* the exchange.
|
|
62
|
+
*/
|
|
63
|
+
signIn(opts?: {
|
|
64
|
+
loginHint?: string;
|
|
65
|
+
}): Promise<BravelyAccountState>;
|
|
66
|
+
/** Sign out: drop local state. Server-side revoke is best-effort. */
|
|
67
|
+
signOut(): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Read entitlements, serving the cached row if fresh and refreshing
|
|
70
|
+
* opportunistically. Always returns an array (possibly empty).
|
|
71
|
+
*/
|
|
72
|
+
getEntitlements(): Promise<Entitlement[]>;
|
|
73
|
+
/** Convenience: check a single entitlement (e.g. `diskaroo_pro`). */
|
|
74
|
+
hasEntitlement(lookupKey: string): Promise<boolean>;
|
|
75
|
+
/** Mint an app-data-token for apps that need direct Firebase SDK access. */
|
|
76
|
+
getAppDataToken(): Promise<string>;
|
|
77
|
+
/**
|
|
78
|
+
* Open Paddle hosted-checkout for the given plan. Hits
|
|
79
|
+
* `/api/checkout/sessions` (BAS-authed), opens the returned URL via the
|
|
80
|
+
* system browser (`window.open(url, '_blank')`). The promise resolves once
|
|
81
|
+
* the request returns; the caller is responsible for polling
|
|
82
|
+
* `getEntitlements()` (or watching `getActivationState()`) for the
|
|
83
|
+
* post-checkout activation.
|
|
84
|
+
*/
|
|
85
|
+
openCheckout(plan: CheckoutPlan): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Mint a Paddle customer-portal session for the signed-in account.
|
|
88
|
+
* Consumers decide how to open `session.url`; checkout activation state is
|
|
89
|
+
* intentionally unchanged.
|
|
90
|
+
*/
|
|
91
|
+
createPaddlePortalSession(): Promise<PaddlePortalSession>;
|
|
92
|
+
/** Inform the manager that checkout completion was observed. */
|
|
93
|
+
notifyCheckoutCompleted(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Return the active entitlements that count as "cross-app" for the M4
|
|
96
|
+
* `<CrossAppCard>`. Excludes the current app's own `<slug>_pro` token.
|
|
97
|
+
* `bravely_premium` (the bundle entitlement) is included — it's the
|
|
98
|
+
* unifying signal that drives the M4 copy ("You own N Bravely Pro
|
|
99
|
+
* apps").
|
|
100
|
+
*
|
|
101
|
+
* Reads from the in-memory signed-in state (which mirrors the
|
|
102
|
+
* `EntitlementCache`); does not hit the network. Host pages that need a
|
|
103
|
+
* fresh fetch should call `getEntitlements()` first.
|
|
104
|
+
*/
|
|
105
|
+
crossAppEntitlements(): Entitlement[];
|
|
106
|
+
/**
|
|
107
|
+
* Run the canonical M3 post-checkout activation polling loop against
|
|
108
|
+
* `/api/entitlements?app=<slug>`. Uses the retry budget locked in
|
|
109
|
+
* `POST_CHECKOUT_RETRY` (30 retries, 1s..8s capped exponential
|
|
110
|
+
* backoff). Fires the right activation state events as it runs.
|
|
111
|
+
*
|
|
112
|
+
* Returns:
|
|
113
|
+
* - `{ outcome: "active" }` as soon as an entitlement is observed
|
|
114
|
+
* (typically `<currentApp>_pro` or `bravely_premium`).
|
|
115
|
+
* - `{ outcome: "exhausted" }` after `maxRetries` attempts without an
|
|
116
|
+
* active entitlement.
|
|
117
|
+
* - `{ outcome: "timeout" }` if total elapsed time hits the auto-
|
|
118
|
+
* advance ceiling for `post_checkout_activation` (120s).
|
|
119
|
+
* - `{ outcome: "not_signed_in" }` if no BAS is present.
|
|
120
|
+
*
|
|
121
|
+
* The promise resolves regardless of network failures. Each failure
|
|
122
|
+
* is treated as an inactive poll (the user's purchase is still safe;
|
|
123
|
+
* the activation state machine surfaces this via the ladder copy).
|
|
124
|
+
*/
|
|
125
|
+
pollForActivation(): Promise<ActivationResult>;
|
|
126
|
+
private completeAuthorization;
|
|
127
|
+
private persistToken;
|
|
128
|
+
private refreshEntitlements;
|
|
129
|
+
/**
|
|
130
|
+
* Gate 1 compatibility path: keep `Authorization: Bearer <bas>` because
|
|
131
|
+
* the current router accepts Bearer on shared BAS endpoints, but attach a
|
|
132
|
+
* real RFC 9449 proof in the `DPoP` header so Gate 2 traffic is already
|
|
133
|
+
* exercising proof generation.
|
|
134
|
+
*/
|
|
135
|
+
private fetchWithBas;
|
|
136
|
+
private dpopProof;
|
|
137
|
+
private getDpopKeypair;
|
|
138
|
+
/** Fetch wrapper that processes the `Bravely-Deprecation` header on every response. */
|
|
139
|
+
private fetchWithDeprecation;
|
|
140
|
+
private requireBas;
|
|
141
|
+
/**
|
|
142
|
+
* Replace the canonical in-memory state.
|
|
143
|
+
*
|
|
144
|
+
* Clones `next` once and stores the clone as `this.state` BEFORE notifying
|
|
145
|
+
* listeners. Listeners receive the same reference subsequent `getState()`
|
|
146
|
+
* calls will return — required for `useSyncExternalStore` consumers (see
|
|
147
|
+
* `getState()` for the React #185 background).
|
|
148
|
+
*
|
|
149
|
+
* Cloning at write time also seals off any external references the caller
|
|
150
|
+
* still holds to `next`: later mutations of `next` (or its nested
|
|
151
|
+
* `entitlements` array) cannot leak into `this.state`.
|
|
152
|
+
*/
|
|
153
|
+
private setState;
|
|
154
|
+
}
|
|
155
|
+
export { BravelyClientKilledError, OAuthError };
|
|
156
|
+
//# sourceMappingURL=BravelyAccountManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BravelyAccountManager.d.ts","sourceRoot":"","sources":["../src/BravelyAccountManager.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EACb,WAAW,EAEX,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAMpB,OAAO,EAEL,wBAAwB,EACxB,UAAU,EACX,MAAM,kBAAkB,CAAC;AAE1B,4EAA4E;AAC5E,MAAM,MAAM,gBAAgB,GACxB;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,YAAY,EAAE,WAAW,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,OAAO,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,OAAO,EAAE,eAAe,CAAA;CAAE,CAAC;AAgBjC,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAE1D,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CACmF;IACvG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,kBAAkB,CAA4C;IACtE,OAAO,CAAC,iBAAiB,CAAuB;gBAEpC,MAAM,EAAE,aAAa;IA0BjC;;;;;;;;;;;;;;;;OAgBG;IACH,QAAQ,IAAI,mBAAmB;IAI/B,aAAa,CAAC,EAAE,EAAE,aAAa,GAAG,MAAM,IAAI;IAK5C,kBAAkB,IAAI,eAAe;IAIrC,mBAAmB,IAAI,gBAAgB,GAAG,IAAI;IAI9C;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,mBAAmB,CAAC;IA4B7C;;;;;;OAMG;IACG,MAAM,CAAC,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA6B7E,qEAAqE;IAC/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B9B;;;OAGG;IACG,eAAe,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAU/C,qEAAqE;IAC/D,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKzD,4EAA4E;IACtE,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IAYxC;;;;;;;OAOG;IACG,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBrD;;;;OAIG;IACG,yBAAyB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAW/D,gEAAgE;IAChE,uBAAuB,IAAI,IAAI;IAI/B;;;;;;;;;;OAUG;IACH,oBAAoB,IAAI,WAAW,EAAE;IAMrC;;;;;;;;;;;;;;;;;;OAkBG;IACG,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,CAAC;YAuEtC,qBAAqB;YAoCrB,YAAY;YA4BZ,mBAAmB;IA+CjC;;;;;OAKG;YACW,YAAY;YASZ,SAAS;YAgBT,cAAc;IAmB5B,uFAAuF;YACzE,oBAAoB;YAkCpB,UAAU;IAMxB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,QAAQ;CAMjB;AA4CD,OAAO,EAAE,wBAAwB,EAAE,UAAU,EAAE,CAAC"}
|