@bravely-studios/account-web 0.3.10 → 0.4.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.
- package/README.md +159 -5
- package/dist/ActivationStateMachine.d.ts +1 -1
- package/dist/ActivationStateMachine.d.ts.map +1 -1
- package/dist/ActivationStateMachine.js +6 -2
- package/dist/ActivationStateMachine.js.map +1 -1
- package/dist/BravelyAccountManager.d.ts +108 -2
- package/dist/BravelyAccountManager.d.ts.map +1 -1
- package/dist/BravelyAccountManager.js +376 -29
- package/dist/BravelyAccountManager.js.map +1 -1
- package/dist/components/BravelyProviderButtons.d.ts.map +1 -1
- package/dist/components/BravelyProviderButtons.js.map +1 -1
- package/dist/components/BravelySignInScreen.d.ts +65 -0
- package/dist/components/BravelySignInScreen.d.ts.map +1 -0
- package/dist/components/BravelySignInScreen.js +156 -0
- package/dist/components/BravelySignInScreen.js.map +1 -0
- package/dist/components/OfferSlotGrid.d.ts +228 -0
- package/dist/components/OfferSlotGrid.d.ts.map +1 -0
- package/dist/components/OfferSlotGrid.js +554 -0
- package/dist/components/OfferSlotGrid.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/installMetrics.d.ts +40 -0
- package/dist/installMetrics.d.ts.map +1 -0
- package/dist/installMetrics.js +128 -0
- package/dist/installMetrics.js.map +1 -0
- package/dist/storage.d.ts +23 -10
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +52 -16
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,9 +6,29 @@ Bravely Studios apps. Used internally across the Bravely web app family.
|
|
|
6
6
|
## What it does
|
|
7
7
|
|
|
8
8
|
- **OAuth 2.1 + PKCE** sign-in via `auth.bravely.dev` — RFC 7636 S256.
|
|
9
|
-
- **
|
|
9
|
+
- **Session forever (0.4.0, D4)** — durable IndexedDB session plus the
|
|
10
|
+
`grant_type=refresh_token` rotation loop: pre-expiry background refresh,
|
|
11
|
+
401 refresh-then-retry-once, and destructive sign-out ONLY on a definitive
|
|
12
|
+
`invalid_grant` / 401-after-refresh. Transport failures never wipe tokens.
|
|
13
|
+
- **Login-first screen (0.4.0, D1)** — `<BravelySignInScreen>`: icon + name
|
|
14
|
+
+ ONE value-prop line + the locked provider picker + passive legal links;
|
|
15
|
+
recoverable offline state; `onShown` seam for `login_screen_shown`.
|
|
16
|
+
Compact per-step geometry (0.4.1, D3 v1.2): the column caps at
|
|
17
|
+
`SIGN_IN_COMPACT_MAX_WIDTH` (480px) — sign-in never inherits offer
|
|
18
|
+
geometry.
|
|
19
|
+
- **3-slot offer grid (0.4.0, D3; 0.4.1 v1.2)** — `<OfferSlotGrid>` + the
|
|
20
|
+
locked slot-copy factory (`buildOfferSlots`), per-app themes, HEIGHT-FIRST
|
|
21
|
+
full-viewport scale-to-fit INCLUDING scale-up (scale fills
|
|
22
|
+
`viewportHeight × 0.92`, width capped at `viewportWidth × 0.95`; clamp
|
|
23
|
+
[0.5, 3.0], scroll fallback below, no max-width column cap), slot-card
|
|
24
|
+
baseline row alignment (subgrid; CTA bottom-pinned),
|
|
25
|
+
`slot_viewed`/`slot_selected` hooks.
|
|
26
|
+
- **Install metrics (0.4.0, D6)** — `getOrMintInstallId()` +
|
|
27
|
+
`emitAppFirstOpenedIfNeeded()` (persist-first fire-once sentinel; the
|
|
28
|
+
host fires the actual analytics event).
|
|
10
29
|
- **Entitlement cache** — 72h offline fallback.
|
|
11
|
-
- **Activation state machine** — checkout-to-active flow.
|
|
30
|
+
- **Activation state machine** — checkout-to-active flow (v1.1.0: the
|
|
31
|
+
`user_skipped` lane is removed — no skip/guest affordance).
|
|
12
32
|
- **Paddle account actions** — BAS-authed checkout session and
|
|
13
33
|
customer-portal session helpers through `identity.bravely.dev`.
|
|
14
34
|
- **`Bravely-Deprecation` handling** — soft warnings + hard
|
|
@@ -82,6 +102,9 @@ window.open(portal.url, "_blank");
|
|
|
82
102
|
| `components/ActivationLadder.tsx` | **M3** — post-checkout 4-phase ladder (`Activating <App> Pro…`). |
|
|
83
103
|
| `components/CrossAppCard.tsx` | **M4** — third-quadrant card (`You own N Bravely Pro apps on this account.`). |
|
|
84
104
|
| `components/BravelyProviderButtons.tsx` | Canonical Apple/Google/Email picker — mirrors the Swift SwiftUI surface. |
|
|
105
|
+
| `components/BravelySignInScreen.tsx` | **D1** — the login-first first screen (icon + name + value prop + picker). |
|
|
106
|
+
| `components/OfferSlotGrid.tsx` | **D3** — locked 3-slot offer grid + copy factory + viewport fit (scale-up). |
|
|
107
|
+
| `installMetrics.ts` | **D6** — install-id mint + fire-once `app_first_opened` sentinel helpers. |
|
|
85
108
|
| `hooks/useActivationLaneFromUrl.ts` | **M3** — detect `?upgraded` / `?checkout` / `?subscription` return params. |
|
|
86
109
|
| `hooks/useFreshLaunchRestoration.ts` | **M2** — silent rehydrate + brief `Synced N items from your <device>.` toast. |
|
|
87
110
|
|
|
@@ -213,9 +236,14 @@ const portal = await manager.createPaddlePortalSession();
|
|
|
213
236
|
|
|
214
237
|
## Storage adapters
|
|
215
238
|
|
|
216
|
-
- **`sessionStorage`** —
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
- **`sessionStorage`** — PKCE verifier + state ONLY (single OAuth-dance
|
|
240
|
+
secrets; they die with the tab by design).
|
|
241
|
+
- **`IndexedDB`** — the durable session (`bas`, `ba_id`, `email`,
|
|
242
|
+
`expires_at`, `refresh_token`), entitlement cache, DPoP key handle, and
|
|
243
|
+
the install metrics keys. Survives tab close, browser restart, and app
|
|
244
|
+
updates (D4 session-forever). Key bytes never leave the browser
|
|
245
|
+
(extractable=false). Pre-0.4.0 sessionStorage session rows migrate to
|
|
246
|
+
IndexedDB on first read.
|
|
219
247
|
- **In-memory** — SSR / test fallback.
|
|
220
248
|
|
|
221
249
|
Host pages can swap in their own ServiceWorker-backed adapter by passing
|
|
@@ -240,8 +268,134 @@ Host pages render UI off the `name` (`restoring_session`, `verifying_entitlement
|
|
|
240
268
|
`createPaddlePortalSession()`, entitlement refreshes, and activation polls
|
|
241
269
|
already carry proof headers.
|
|
242
270
|
|
|
271
|
+
## Login-first cutover surfaces (0.4.0)
|
|
272
|
+
|
|
273
|
+
### `<BravelySignInScreen>` (D1)
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
import { BravelySignInScreen, getOrMintInstallId } from "@bravely-studios/account-web";
|
|
277
|
+
|
|
278
|
+
<BravelySignInScreen
|
|
279
|
+
appName="Todoing.ly"
|
|
280
|
+
appIconSrc="/icon-256.png"
|
|
281
|
+
valueProp="Every task, every device, always in sync."
|
|
282
|
+
style={{ variant: "dark", accent: "#3B6EF0" }}
|
|
283
|
+
isBusy={isAuthorizing}
|
|
284
|
+
onContinue={(provider) => manager.signIn({ provider })}
|
|
285
|
+
offline={cantReach}
|
|
286
|
+
onRetry={() => retryProbe()}
|
|
287
|
+
onShown={async () => {
|
|
288
|
+
posthog.capture("login_screen_shown", { install_id: await getOrMintInstallId() });
|
|
289
|
+
}}
|
|
290
|
+
/>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The first screen on first launch (spec `onboarding.login_first`). No skip,
|
|
294
|
+
no guest lane, no sign-in/sign-up fork — the screen IS both. `onShown`
|
|
295
|
+
fires once per mount; the host MUST emit `login_screen_shown` there.
|
|
296
|
+
|
|
297
|
+
### `<OfferSlotGrid>` + `buildOfferSlots()` (D3)
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
import {
|
|
301
|
+
OfferSlotGrid, buildOfferSlots, offerThemeForSlug,
|
|
302
|
+
planTokenForSlot, telemetryValueForSlot,
|
|
303
|
+
} from "@bravely-studios/account-web";
|
|
304
|
+
|
|
305
|
+
const slots = buildOfferSlots({ appName: "Scry", appSlug: "scry", type: "RVA" });
|
|
306
|
+
|
|
307
|
+
<OfferSlotGrid
|
|
308
|
+
slots={slots} // provisioned slots only — filter before passing
|
|
309
|
+
theme={offerThemeForSlug("scry")}
|
|
310
|
+
onSlotViewed={(s) => posthog.capture("slot_viewed", { offer_slot: telemetryValueForSlot(s) })}
|
|
311
|
+
onSelect={(s) => {
|
|
312
|
+
posthog.capture("slot_selected", { offer_slot: telemetryValueForSlot(s) });
|
|
313
|
+
manager.openCheckout(planTokenForSlot(s, "RVA"));
|
|
314
|
+
}}
|
|
315
|
+
/>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The web sibling of Swift `OfferSlotGrid` / C# `BravelyOfferGrid`: the locked
|
|
319
|
+
3-slot copy (HOOK → GSO → FRONT_LTV) with HEIGHT-FIRST full-viewport
|
|
320
|
+
scale-to-fit including scale-up (v1.2: the scale fills
|
|
321
|
+
`viewportHeight × 0.92`; width binds only as a cap at
|
|
322
|
+
`viewportWidth × 0.95`; clamped to [0.5, 3.0]; scroll fallback below 0.5;
|
|
323
|
+
single-column stack under 901px; NO max-width column cap). In the 3-up
|
|
324
|
+
layout the cards share row tracks (CSS subgrid) so every section row
|
|
325
|
+
baseline-aligns to its tallest sibling and the CTAs pin to the card bottom.
|
|
326
|
+
The host fires `paywall_shown` when it presents the page.
|
|
327
|
+
|
|
328
|
+
### Install metrics (D6)
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import { getOrMintInstallId, emitAppFirstOpenedIfNeeded } from "@bravely-studios/account-web";
|
|
332
|
+
|
|
333
|
+
// At app entry, before any UI gating:
|
|
334
|
+
const { installId } = await emitAppFirstOpenedIfNeeded({
|
|
335
|
+
emit: ({ installId }) => posthog.capture("app_first_opened", { install_id: installId }),
|
|
336
|
+
});
|
|
337
|
+
manager.setInstallId(installId); // every auth exchange now carries install_id
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Persist-first sentinel: the durable IndexedDB sentinel row is written BEFORE
|
|
341
|
+
the emit, so a crash can only under-count — never double-fire.
|
|
342
|
+
|
|
243
343
|
## Changelog
|
|
244
344
|
|
|
345
|
+
### 0.4.1 — 2026-06-11
|
|
346
|
+
|
|
347
|
+
D3 v1.2 patch (Jeff field feedback 2026-06-11; plan §4):
|
|
348
|
+
|
|
349
|
+
- **Per-step sizing (a):** `<BravelySignInScreen>` renders COMPACT — the
|
|
350
|
+
column caps at `SIGN_IN_COMPACT_MAX_WIDTH` (480px) and self-centers;
|
|
351
|
+
sign-in never inherits offer geometry. `computeOfferFit` is now
|
|
352
|
+
HEIGHT-FIRST: the scale fills `viewportHeight × 0.92`; width binds only
|
|
353
|
+
as a cap at `viewportWidth × 0.95` (`widthCapBound` reports when it
|
|
354
|
+
does). The single-arg signature is unchanged (it IS the offer-step
|
|
355
|
+
formula); the `pad` field + `OFFER_FIT_PAD` are deprecated and ignored —
|
|
356
|
+
breathing room now comes from the fractions
|
|
357
|
+
(`OFFER_FIT_HEIGHT_FRACTION` / `OFFER_FIT_WIDTH_CAP_FRACTION`).
|
|
358
|
+
- **Slot-card baseline alignment (b):** in the 3-up layout the cards share
|
|
359
|
+
the parent's row tracks via CSS subgrid — every section row
|
|
360
|
+
(name/tagline/price/price-sub/bullets/guarantee/bonus/nudge/CTA)
|
|
361
|
+
equalizes to its tallest sibling; a missing section (the LTV "/mo", the
|
|
362
|
+
HOOK bonus, the RVA HOOK guarantee) leaves its track empty instead of
|
|
363
|
+
pulling content up; the CTA pins to the shared bottom row. Non-subgrid
|
|
364
|
+
browsers gracefully keep the flex-column card; stacked mode unchanged.
|
|
365
|
+
- **Locked copy (c):** bonus headlines are now
|
|
366
|
+
`FREE full access to [the 8 other premium utilities] — 9 apps in all`
|
|
367
|
+
(GSO) / `FREE [lifetime] access to [the 8 other premium utilities] — 9
|
|
368
|
+
apps in all` (LTV; accent `lifetime` span kept); slot-1 nudge is
|
|
369
|
+
`Want the other 8 apps free? Go Annual →`. The "all 9 apps, every
|
|
370
|
+
platform, every device, + the bonus bundle" tagline STAYS (total count is
|
|
371
|
+
correct). Byte-identical with the Swift/C#/get.bravely.dev surfaces.
|
|
372
|
+
|
|
373
|
+
### 0.4.0 — 2026-06-11
|
|
374
|
+
|
|
375
|
+
Login-first cutover W1 (plan §§2, 4, 5, 7 — D1/D3/D4/D6):
|
|
376
|
+
|
|
377
|
+
- **Session forever (D4):** `bas` / `ba_id` / `email` moved from
|
|
378
|
+
sessionStorage to IndexedDB (PKCE verifier/state stay session-scoped;
|
|
379
|
+
legacy rows migrate on first read); `expires_at` persisted from every
|
|
380
|
+
token response; the `grant_type=refresh_token` rotation loop implemented
|
|
381
|
+
against `{authority}/oauth/token` (router ≥1.6.0) with single-flight
|
|
382
|
+
dedupe; pre-expiry background refresh (48h window) on `restore()`;
|
|
383
|
+
401 → refresh-then-retry-once on every BAS-authed call; session
|
|
384
|
+
resurrection from a durable refresh token when the BAS row is missing;
|
|
385
|
+
destructive sign-out ONLY on definitive `invalid_grant` /
|
|
386
|
+
401-after-refresh — transport/5xx/429 failures keep every token.
|
|
387
|
+
- **`<BravelySignInScreen>` (D1):** the canonical login-first first screen.
|
|
388
|
+
- **`<OfferSlotGrid>` (D3):** the locked 3-slot offer grid + copy factory,
|
|
389
|
+
per-app themes, viewport fit with scale-up.
|
|
390
|
+
- **Install metrics (D6):** `getOrMintInstallId()` +
|
|
391
|
+
`emitAppFirstOpenedIfNeeded()`.
|
|
392
|
+
- **Activation machine v1.1.0:** `user_skipped` removed in lockstep with
|
|
393
|
+
`bravely-commerce-router` (legacy event strings are a no-op).
|
|
394
|
+
- **Types:** `CheckoutPlan` gains `"onetime"` (interval-true IVA Slot-1
|
|
395
|
+
token per the 2026-06-10 catalog rename); API baseline note → 1.6.0.
|
|
396
|
+
- **Fix:** stale "magic-link only" comment on the email provider button
|
|
397
|
+
(password is primary per ADR 0014).
|
|
398
|
+
|
|
245
399
|
### 0.3.10 — 2026-06-05
|
|
246
400
|
|
|
247
401
|
- **Feature:** optional `install_id` passthrough for PHASE-2 install→account
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ActivationState, ActivationStateName } from "./types.js";
|
|
2
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" | "
|
|
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_clicked_retry";
|
|
4
4
|
interface TransitionContext {
|
|
5
5
|
bas_present?: boolean;
|
|
6
6
|
entitlement_cache_present?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivationStateMachine.d.ts","sourceRoot":"","sources":["../src/ActivationStateMachine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ActivationStateMachine.d.ts","sourceRoot":"","sources":["../src/ActivationStateMachine.ts"],"names":[],"mappings":"AAcA,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,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;AAuDhE;;;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"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
// Port of bravely-commerce-router/docs/activation-state-machine.json (v1.
|
|
1
|
+
// Port of bravely-commerce-router/docs/activation-state-machine.json (v1.1.0).
|
|
2
2
|
//
|
|
3
3
|
// Keep aligned by hand. The canonical doc is the source of truth; this port
|
|
4
4
|
// is a thin TS surface so host pages can render UI off `getActivationState()`.
|
|
5
5
|
//
|
|
6
|
+
// v1.1.0 (login-first cutover D1/D2): the `user_skipped` lane is REMOVED —
|
|
7
|
+
// there is no skip/guest affordance; sign-in is the only way forward from
|
|
8
|
+
// `pending_user_action`. A stale client firing the legacy `"user_skipped"`
|
|
9
|
+
// string is a NO-OP (state unchanged), never a silent route into a free lane.
|
|
10
|
+
//
|
|
6
11
|
// CLAUDE.md feedback_no_etas: every state surfaces ELAPSED time, never a
|
|
7
12
|
// predicted ETA. Anti-patterns from M5 enforced in the banned-phrases CI
|
|
8
13
|
// check (in bravely-commerce-router); the strings here MUST match the JSON.
|
|
@@ -46,7 +51,6 @@ const TRANSITIONS = {
|
|
|
46
51
|
},
|
|
47
52
|
pending_user_action: {
|
|
48
53
|
user_signed_in: "restoring_session",
|
|
49
|
-
user_skipped: "entitlement_none",
|
|
50
54
|
},
|
|
51
55
|
failed: {
|
|
52
56
|
user_clicked_retry: "pending_user_action",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivationStateMachine.js","sourceRoot":"","sources":["../src/ActivationStateMachine.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,4EAA4E;
|
|
1
|
+
{"version":3,"file":"ActivationStateMachine.js","sourceRoot":"","sources":["../src/ActivationStateMachine.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,2EAA2E;AAC3E,8EAA8E;AAC9E,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,4EAA4E;AAkC5E,MAAM,WAAW,GAAoF;IACnG,IAAI,EAAE;QACJ,YAAY,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,mBAAmB,EAAE;QACtE,uBAAuB,EAAE,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,qBAAqB,EAAE;KACrF;IACD,iBAAiB,EAAE;QACjB,SAAS,EAAE,uBAAuB;QAClC,WAAW,EAAE,0BAA0B;QACvC,WAAW,EAAE,qBAAqB;KACnC;IACD,qBAAqB,EAAE;QACrB,6BAA6B,EAAE,yBAAyB;QACxD,8BAA8B,EAAE,kBAAkB;QAClD,yBAAyB,EAAE;YACzB,IAAI,EAAE,EAAE,yBAAyB,EAAE,IAAI,EAAE;YACzC,EAAE,EAAE,0BAA0B;YAC9B,IAAI,EAAE,QAAQ;SACf;KACF;IACD,wBAAwB,EAAE;QACxB,6BAA6B,EAAE,yBAAyB;QACxD,8BAA8B,EAAE,kBAAkB;QAClD,yBAAyB,EAAE,0BAA0B;KACtD;IACD,uBAAuB,EAAE,EAAE;IAC3B,gBAAgB,EAAE;QAChB,kBAAkB,EAAE,0BAA0B;KAC/C;IACD,wBAAwB,EAAE;QACxB,wBAAwB,EAAE,yBAAyB;QACnD,0BAA0B,EAAE,0BAA0B;QACtD,oBAAoB,EAAE,QAAQ;KAC/B;IACD,wBAAwB,EAAE;QACxB,6BAA6B,EAAE,yBAAyB;QACxD,8BAA8B,EAAE,kBAAkB;QAClD,yBAAyB,EAAE,QAAQ;KACpC;IACD,mBAAmB,EAAE;QACnB,cAAc,EAAE,mBAAmB;KACpC;IACD,MAAM,EAAE;QACN,kBAAkB,EAAE,qBAAqB;KAC1C;CACF,CAAC;AAEF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAsB;IAC/C,mBAAmB;IACnB,uBAAuB;IACvB,0BAA0B;IAC1B,0BAA0B;CAC3B,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,UAAU,EAAE,EAAE;IACd,gBAAgB,EAAE,IAAI;IACtB,YAAY,EAAE,IAAI;IAClB,iBAAiB,EAAE,GAAG;CACd,CAAC;AAEX,4EAA4E;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAiD;IAC3E,iBAAiB,EAAE,IAAI;IACvB,qBAAqB,EAAE,IAAI;IAC3B,wBAAwB,EAAE,OAAO;IACjC,wBAAwB,EAAE,MAAM;CACjC,CAAC;AAEF,gEAAgE;AAChE,MAAM,CAAC,MAAM,eAAe,GAA8D;IACxF,iBAAiB,EAAE,QAAQ;IAC3B,qBAAqB,EAAE,QAAQ;IAC/B,wBAAwB,EAAE,QAAQ;IAClC,wBAAwB,EAAE,QAAQ;CACnC,CAAC;AAEF,MAAM,OAAO,sBAAsB;IACzB,KAAK,CAAkB;IACvB,WAAW,GAAG,IAAI,GAAG,EAAgC,CAAC;IAE9D,YAAY,UAA+B,MAAM;QAC/C,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;IACxF,CAAC;IAED,OAAO;QACL,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,KAAsB,EAAE,MAAyB,EAAE;QAC5D,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ;QAC5C,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,IAAyB;QAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,mEAAmE;IACnE,aAAa,CAAC,EAAgC;QAC5C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC3C,CAAC;IAED,sDAAsD;IACtD,SAAS;QACP,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC;IAED,kCAAkC;IAClC,MAAM,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,MAAM,CAAC,UAAU,GAAG,WAAW,CAAC;IAExB,KAAK,CAAC,IAAyB;QACrC,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1E,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,WAAW;YAAE,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;;AAGH,SAAS,aAAa,CAAC,MAAwB,EAAE,GAAsB;IACrE,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,IAAI,GAAG,CAAC,CAA4B,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5C,OAAO,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAC;AACnB,CAAC"}
|
|
@@ -15,6 +15,24 @@ export type ActivationResult = {
|
|
|
15
15
|
} | {
|
|
16
16
|
outcome: "not_signed_in";
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Proactive refresh window (D4): when the BAS has less than this much life
|
|
20
|
+
* left (or is already past `expires_at`), the manager rotates the session in
|
|
21
|
+
* the background via `grant_type=refresh_token` instead of waiting for a 401.
|
|
22
|
+
* BAS access tokens live 14 days; 48h is comfortably inside that while still
|
|
23
|
+
* rotating well before expiry for any returning visitor.
|
|
24
|
+
*/
|
|
25
|
+
export declare const PRE_EXPIRY_REFRESH_WINDOW_MS: number;
|
|
26
|
+
/**
|
|
27
|
+
* Outcome of a `grant_type=refresh_token` rotation attempt.
|
|
28
|
+
*
|
|
29
|
+
* - `refreshed` — new BAS + rotated refresh token persisted.
|
|
30
|
+
* - `no_refresh_token` — nothing to present (legacy session or never granted).
|
|
31
|
+
* - `definitive_failure` — server said `invalid_grant` (rotation/reuse/revoked)
|
|
32
|
+
* or 401; the local session has been cleared.
|
|
33
|
+
* - `transient_failure` — network / 5xx / 429; session and tokens KEPT.
|
|
34
|
+
*/
|
|
35
|
+
export type RefreshOutcome = "refreshed" | "no_refresh_token" | "definitive_failure" | "transient_failure";
|
|
18
36
|
type StateListener = (state: BravelyAccountState) => void;
|
|
19
37
|
export declare class BravelyAccountManager {
|
|
20
38
|
private readonly cfg;
|
|
@@ -43,6 +61,13 @@ export declare class BravelyAccountManager {
|
|
|
43
61
|
* (e.g. after reading it from native bridge / first-launch storage).
|
|
44
62
|
*/
|
|
45
63
|
private installId;
|
|
64
|
+
/**
|
|
65
|
+
* Single-flight guard for the refresh grant: concurrent 401s / proactive
|
|
66
|
+
* refreshes join the in-flight rotation instead of racing the one-time-use
|
|
67
|
+
* refresh token (a second concurrent presentation of the same token would
|
|
68
|
+
* trip the server's family reuse detection and revoke the session).
|
|
69
|
+
*/
|
|
70
|
+
private refreshInFlight;
|
|
46
71
|
constructor(config: ManagerConfig);
|
|
47
72
|
/**
|
|
48
73
|
* Return the current state. The returned reference is **stable across
|
|
@@ -83,6 +108,16 @@ export declare class BravelyAccountManager {
|
|
|
83
108
|
* Hydrate from persistent storage and (if a BAS is present) verify it
|
|
84
109
|
* against the identity API. Call once on page load before rendering UI
|
|
85
110
|
* that depends on `getState()`.
|
|
111
|
+
*
|
|
112
|
+
* D4 session-forever behavior:
|
|
113
|
+
* - A stored BAS yields `signed_in` immediately (cache-backed); the
|
|
114
|
+
* entitlement check runs in the background. Transport failures NEVER
|
|
115
|
+
* wipe tokens or flip the state to signed_out.
|
|
116
|
+
* - A BAS near/past `expires_at` triggers a background refresh-grant
|
|
117
|
+
* rotation (`PRE_EXPIRY_REFRESH_WINDOW_MS`).
|
|
118
|
+
* - No BAS but a stored refresh token → the session is resurrected via
|
|
119
|
+
* `grant_type=refresh_token` before giving up (sessions survive even
|
|
120
|
+
* if the access token row was lost).
|
|
86
121
|
*/
|
|
87
122
|
restore(): Promise<BravelyAccountState>;
|
|
88
123
|
/**
|
|
@@ -114,6 +149,14 @@ export declare class BravelyAccountManager {
|
|
|
114
149
|
}): Promise<BravelyAccountState>;
|
|
115
150
|
/** Sign out: drop local state. Server-side revoke is best-effort. */
|
|
116
151
|
signOut(): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Drop every locally-persisted session artifact and flip to `signed_out`.
|
|
154
|
+
* The DESTRUCTIVE half of sign-out — only ever called from explicit user
|
|
155
|
+
* sign-out or a DEFINITIVE auth failure (`invalid_grant` on the refresh
|
|
156
|
+
* grant, or a 401 that survived a successful refresh-and-retry). Transport
|
|
157
|
+
* failures must never reach this.
|
|
158
|
+
*/
|
|
159
|
+
private clearLocalSession;
|
|
117
160
|
/**
|
|
118
161
|
* Read entitlements, serving the cached row if fresh and refreshing
|
|
119
162
|
* opportunistically. Always returns an array (possibly empty).
|
|
@@ -162,8 +205,9 @@ export declare class BravelyAccountManager {
|
|
|
162
205
|
* introduced in the Mac/iOS v2 onboarding port.
|
|
163
206
|
*
|
|
164
207
|
* Throws if the user is not signed in (`not_signed_in`) or if the server
|
|
165
|
-
* returns a non-2xx status.
|
|
166
|
-
*
|
|
208
|
+
* returns a non-2xx status. A definitive 401 (refresh-and-retry exhausted)
|
|
209
|
+
* is surfaced as `not_signed_in`; a 401 riding a transient refresh failure
|
|
210
|
+
* keeps the session and throws a plain request failure.
|
|
167
211
|
*/
|
|
168
212
|
getAccountEntitlements(): Promise<AccountEntitlements>;
|
|
169
213
|
/**
|
|
@@ -187,15 +231,77 @@ export declare class BravelyAccountManager {
|
|
|
187
231
|
*/
|
|
188
232
|
pollForActivation(): Promise<ActivationResult>;
|
|
189
233
|
private completeAuthorization;
|
|
234
|
+
/**
|
|
235
|
+
* Persist the token + identity rows shared by the sign-in and refresh
|
|
236
|
+
* legs (BAS, rotated refresh token, ba_id, email, expires_at, lib version
|
|
237
|
+
* policy). Returns the inline entitlement snapshot.
|
|
238
|
+
*/
|
|
239
|
+
private persistSessionCore;
|
|
190
240
|
private persistToken;
|
|
241
|
+
/**
|
|
242
|
+
* Persist a `grant_type=refresh_token` rotation result. Differs from the
|
|
243
|
+
* sign-in leg deliberately:
|
|
244
|
+
* - The inline entitlement snapshot is best-effort on the server (an RC
|
|
245
|
+
* hiccup yields `[]` so rotation never fails) — so an EMPTY snapshot
|
|
246
|
+
* must not downgrade the 72h offline cache mid-session. Non-empty
|
|
247
|
+
* snapshots update cache + state; empty ones leave the authoritative
|
|
248
|
+
* `/api/entitlements` check (which runs on every restore) in charge.
|
|
249
|
+
* - No `entitlement_check_*` activation events are fired here for the
|
|
250
|
+
* same reason; only `bas_found` advances the restore lane.
|
|
251
|
+
*/
|
|
252
|
+
private persistRefreshedToken;
|
|
253
|
+
/**
|
|
254
|
+
* True when the stored BAS is inside the proactive-refresh window (or past
|
|
255
|
+
* expiry). Unknown expiry (legacy rows) ⇒ false — the 401-retry path
|
|
256
|
+
* covers those sessions.
|
|
257
|
+
*/
|
|
258
|
+
private shouldProactivelyRefresh;
|
|
259
|
+
/**
|
|
260
|
+
* Rotate the session via `POST {authority}/oauth/token` with
|
|
261
|
+
* `grant_type=refresh_token` (router ≥1.6.0). Single-flight: concurrent
|
|
262
|
+
* callers join the in-flight rotation (the refresh token is one-time-use;
|
|
263
|
+
* racing it would trip server-side reuse detection).
|
|
264
|
+
*
|
|
265
|
+
* Cross-TAB races are handled by two layers: (a) where available, a Web
|
|
266
|
+
* Lock (`bravely:session_refresh:<slug>`) serializes rotations across tabs
|
|
267
|
+
* sharing the same IndexedDB token; (b) the token is re-read inside the
|
|
268
|
+
* critical section, and if another tab already rotated (stored BAS changed
|
|
269
|
+
* while we waited) the rotation is skipped. Browsers without Web Locks
|
|
270
|
+
* fall back to the server's 30s idempotent-retry grace for near-
|
|
271
|
+
* simultaneous presentations of the same token.
|
|
272
|
+
*
|
|
273
|
+
* Destructive ONLY on a definitive rejection (`invalid_grant` / 401):
|
|
274
|
+
* transport failures, 5xx, and 429 keep every token in place.
|
|
275
|
+
*/
|
|
276
|
+
private refreshSession;
|
|
277
|
+
private refreshSessionCrossTab;
|
|
278
|
+
private doRefreshSession;
|
|
191
279
|
private refreshEntitlements;
|
|
192
280
|
/**
|
|
193
281
|
* Gate 1 compatibility path: keep `Authorization: Bearer <bas>` because
|
|
194
282
|
* the current router accepts Bearer on shared BAS endpoints, but attach a
|
|
195
283
|
* real RFC 9449 proof in the `DPoP` header so Gate 2 traffic is already
|
|
196
284
|
* exercising proof generation.
|
|
285
|
+
*
|
|
286
|
+
* D4 401 handling — refresh-then-retry-once: a 401 triggers ONE refresh
|
|
287
|
+
* grant; on success the request is retried with the new BAS. Destructive
|
|
288
|
+
* sign-out happens ONLY when the failure is definitive:
|
|
289
|
+
* - the refresh grant itself was rejected (`invalid_grant`/401), or
|
|
290
|
+
* - there is no refresh token to present, or
|
|
291
|
+
* - the retried request STILL came back 401 after a successful refresh.
|
|
292
|
+
* A transient refresh failure (offline/5xx/429) returns the original 401
|
|
293
|
+
* response WITHOUT touching the stored session — callers must treat a 401
|
|
294
|
+
* with the session still present as a transient failure (serve cache).
|
|
197
295
|
*/
|
|
198
296
|
private fetchWithBas;
|
|
297
|
+
/**
|
|
298
|
+
* True when a 401 response should be treated as a signed-out verdict:
|
|
299
|
+
* `fetchWithBas` has already run the refresh-then-retry dance and cleared
|
|
300
|
+
* local storage iff the failure was definitive. If the BAS row is still
|
|
301
|
+
* present, the 401 rode a TRANSIENT refresh failure — the session is kept
|
|
302
|
+
* and callers must degrade gracefully (serve cache) instead of signing out.
|
|
303
|
+
*/
|
|
304
|
+
private sessionWasCleared;
|
|
199
305
|
private dpopProof;
|
|
200
306
|
private getDpopKeypair;
|
|
201
307
|
/** Fetch wrapper that processes the `Bravely-Deprecation` header on every response. */
|
|
@@ -1 +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,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAA8B,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAK3E,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;
|
|
1
|
+
{"version":3,"file":"BravelyAccountManager.d.ts","sourceRoot":"","sources":["../src/BravelyAccountManager.ts"],"names":[],"mappings":"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"}
|