@bravely-studios/account-web 0.3.9 → 0.4.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/README.md +134 -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 +132 -2
- package/dist/BravelyAccountManager.d.ts.map +1 -1
- package/dist/BravelyAccountManager.js +429 -30
- 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 +55 -0
- package/dist/components/BravelySignInScreen.d.ts.map +1 -0
- package/dist/components/BravelySignInScreen.js +141 -0
- package/dist/components/BravelySignInScreen.js.map +1 -0
- package/dist/components/OfferSlotGrid.d.ts +204 -0
- package/dist/components/OfferSlotGrid.d.ts.map +1 -0
- package/dist/components/OfferSlotGrid.js +490 -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/oauth.d.ts +18 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +3 -0
- package/dist/oauth.js.map +1 -1
- 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 +23 -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,23 @@ 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
|
+
- **3-slot offer grid (0.4.0, D3)** — `<OfferSlotGrid>` + the locked
|
|
17
|
+
slot-copy factory (`buildOfferSlots`), per-app themes, full-viewport
|
|
18
|
+
scale-to-fit INCLUDING scale-up (clamp [0.5, 3.0], scroll fallback below,
|
|
19
|
+
no max-width column cap), `slot_viewed`/`slot_selected` hooks.
|
|
20
|
+
- **Install metrics (0.4.0, D6)** — `getOrMintInstallId()` +
|
|
21
|
+
`emitAppFirstOpenedIfNeeded()` (persist-first fire-once sentinel; the
|
|
22
|
+
host fires the actual analytics event).
|
|
10
23
|
- **Entitlement cache** — 72h offline fallback.
|
|
11
|
-
- **Activation state machine** — checkout-to-active flow.
|
|
24
|
+
- **Activation state machine** — checkout-to-active flow (v1.1.0: the
|
|
25
|
+
`user_skipped` lane is removed — no skip/guest affordance).
|
|
12
26
|
- **Paddle account actions** — BAS-authed checkout session and
|
|
13
27
|
customer-portal session helpers through `identity.bravely.dev`.
|
|
14
28
|
- **`Bravely-Deprecation` handling** — soft warnings + hard
|
|
@@ -82,6 +96,9 @@ window.open(portal.url, "_blank");
|
|
|
82
96
|
| `components/ActivationLadder.tsx` | **M3** — post-checkout 4-phase ladder (`Activating <App> Pro…`). |
|
|
83
97
|
| `components/CrossAppCard.tsx` | **M4** — third-quadrant card (`You own N Bravely Pro apps on this account.`). |
|
|
84
98
|
| `components/BravelyProviderButtons.tsx` | Canonical Apple/Google/Email picker — mirrors the Swift SwiftUI surface. |
|
|
99
|
+
| `components/BravelySignInScreen.tsx` | **D1** — the login-first first screen (icon + name + value prop + picker). |
|
|
100
|
+
| `components/OfferSlotGrid.tsx` | **D3** — locked 3-slot offer grid + copy factory + viewport fit (scale-up). |
|
|
101
|
+
| `installMetrics.ts` | **D6** — install-id mint + fire-once `app_first_opened` sentinel helpers. |
|
|
85
102
|
| `hooks/useActivationLaneFromUrl.ts` | **M3** — detect `?upgraded` / `?checkout` / `?subscription` return params. |
|
|
86
103
|
| `hooks/useFreshLaunchRestoration.ts` | **M2** — silent rehydrate + brief `Synced N items from your <device>.` toast. |
|
|
87
104
|
|
|
@@ -213,9 +230,14 @@ const portal = await manager.createPaddlePortalSession();
|
|
|
213
230
|
|
|
214
231
|
## Storage adapters
|
|
215
232
|
|
|
216
|
-
- **`sessionStorage`** —
|
|
217
|
-
|
|
218
|
-
|
|
233
|
+
- **`sessionStorage`** — PKCE verifier + state ONLY (single OAuth-dance
|
|
234
|
+
secrets; they die with the tab by design).
|
|
235
|
+
- **`IndexedDB`** — the durable session (`bas`, `ba_id`, `email`,
|
|
236
|
+
`expires_at`, `refresh_token`), entitlement cache, DPoP key handle, and
|
|
237
|
+
the install metrics keys. Survives tab close, browser restart, and app
|
|
238
|
+
updates (D4 session-forever). Key bytes never leave the browser
|
|
239
|
+
(extractable=false). Pre-0.4.0 sessionStorage session rows migrate to
|
|
240
|
+
IndexedDB on first read.
|
|
219
241
|
- **In-memory** — SSR / test fallback.
|
|
220
242
|
|
|
221
243
|
Host pages can swap in their own ServiceWorker-backed adapter by passing
|
|
@@ -240,8 +262,115 @@ Host pages render UI off the `name` (`restoring_session`, `verifying_entitlement
|
|
|
240
262
|
`createPaddlePortalSession()`, entitlement refreshes, and activation polls
|
|
241
263
|
already carry proof headers.
|
|
242
264
|
|
|
265
|
+
## Login-first cutover surfaces (0.4.0)
|
|
266
|
+
|
|
267
|
+
### `<BravelySignInScreen>` (D1)
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
import { BravelySignInScreen, getOrMintInstallId } from "@bravely-studios/account-web";
|
|
271
|
+
|
|
272
|
+
<BravelySignInScreen
|
|
273
|
+
appName="Todoing.ly"
|
|
274
|
+
appIconSrc="/icon-256.png"
|
|
275
|
+
valueProp="Every task, every device, always in sync."
|
|
276
|
+
style={{ variant: "dark", accent: "#3B6EF0" }}
|
|
277
|
+
isBusy={isAuthorizing}
|
|
278
|
+
onContinue={(provider) => manager.signIn({ provider })}
|
|
279
|
+
offline={cantReach}
|
|
280
|
+
onRetry={() => retryProbe()}
|
|
281
|
+
onShown={async () => {
|
|
282
|
+
posthog.capture("login_screen_shown", { install_id: await getOrMintInstallId() });
|
|
283
|
+
}}
|
|
284
|
+
/>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The first screen on first launch (spec `onboarding.login_first`). No skip,
|
|
288
|
+
no guest lane, no sign-in/sign-up fork — the screen IS both. `onShown`
|
|
289
|
+
fires once per mount; the host MUST emit `login_screen_shown` there.
|
|
290
|
+
|
|
291
|
+
### `<OfferSlotGrid>` + `buildOfferSlots()` (D3)
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import {
|
|
295
|
+
OfferSlotGrid, buildOfferSlots, offerThemeForSlug,
|
|
296
|
+
planTokenForSlot, telemetryValueForSlot,
|
|
297
|
+
} from "@bravely-studios/account-web";
|
|
298
|
+
|
|
299
|
+
const slots = buildOfferSlots({ appName: "Scry", appSlug: "scry", type: "RVA" });
|
|
300
|
+
|
|
301
|
+
<OfferSlotGrid
|
|
302
|
+
slots={slots} // provisioned slots only — filter before passing
|
|
303
|
+
theme={offerThemeForSlug("scry")}
|
|
304
|
+
onSlotViewed={(s) => posthog.capture("slot_viewed", { offer_slot: telemetryValueForSlot(s) })}
|
|
305
|
+
onSelect={(s) => {
|
|
306
|
+
posthog.capture("slot_selected", { offer_slot: telemetryValueForSlot(s) });
|
|
307
|
+
manager.openCheckout(planTokenForSlot(s, "RVA"));
|
|
308
|
+
}}
|
|
309
|
+
/>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The web sibling of Swift `OfferSlotGrid` / C# `BravelyOfferGrid`: the locked
|
|
313
|
+
3-slot copy (HOOK → GSO → FRONT_LTV) with full-viewport scale-to-fit
|
|
314
|
+
including scale-up (`scale = min(availW/naturalW, availH/naturalH)` clamped
|
|
315
|
+
to [0.5, 3.0]; scroll fallback below 0.5; single-column stack under 901px;
|
|
316
|
+
NO max-width column cap). The host fires `paywall_shown` when it presents
|
|
317
|
+
the page.
|
|
318
|
+
|
|
319
|
+
### Install metrics (D6)
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { getOrMintInstallId, emitAppFirstOpenedIfNeeded } from "@bravely-studios/account-web";
|
|
323
|
+
|
|
324
|
+
// At app entry, before any UI gating:
|
|
325
|
+
const { installId } = await emitAppFirstOpenedIfNeeded({
|
|
326
|
+
emit: ({ installId }) => posthog.capture("app_first_opened", { install_id: installId }),
|
|
327
|
+
});
|
|
328
|
+
manager.setInstallId(installId); // every auth exchange now carries install_id
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Persist-first sentinel: the durable IndexedDB sentinel row is written BEFORE
|
|
332
|
+
the emit, so a crash can only under-count — never double-fire.
|
|
333
|
+
|
|
243
334
|
## Changelog
|
|
244
335
|
|
|
336
|
+
### 0.4.0 — 2026-06-11
|
|
337
|
+
|
|
338
|
+
Login-first cutover W1 (plan §§2, 4, 5, 7 — D1/D3/D4/D6):
|
|
339
|
+
|
|
340
|
+
- **Session forever (D4):** `bas` / `ba_id` / `email` moved from
|
|
341
|
+
sessionStorage to IndexedDB (PKCE verifier/state stay session-scoped;
|
|
342
|
+
legacy rows migrate on first read); `expires_at` persisted from every
|
|
343
|
+
token response; the `grant_type=refresh_token` rotation loop implemented
|
|
344
|
+
against `{authority}/oauth/token` (router ≥1.6.0) with single-flight
|
|
345
|
+
dedupe; pre-expiry background refresh (48h window) on `restore()`;
|
|
346
|
+
401 → refresh-then-retry-once on every BAS-authed call; session
|
|
347
|
+
resurrection from a durable refresh token when the BAS row is missing;
|
|
348
|
+
destructive sign-out ONLY on definitive `invalid_grant` /
|
|
349
|
+
401-after-refresh — transport/5xx/429 failures keep every token.
|
|
350
|
+
- **`<BravelySignInScreen>` (D1):** the canonical login-first first screen.
|
|
351
|
+
- **`<OfferSlotGrid>` (D3):** the locked 3-slot offer grid + copy factory,
|
|
352
|
+
per-app themes, viewport fit with scale-up.
|
|
353
|
+
- **Install metrics (D6):** `getOrMintInstallId()` +
|
|
354
|
+
`emitAppFirstOpenedIfNeeded()`.
|
|
355
|
+
- **Activation machine v1.1.0:** `user_skipped` removed in lockstep with
|
|
356
|
+
`bravely-commerce-router` (legacy event strings are a no-op).
|
|
357
|
+
- **Types:** `CheckoutPlan` gains `"onetime"` (interval-true IVA Slot-1
|
|
358
|
+
token per the 2026-06-10 catalog rename); API baseline note → 1.6.0.
|
|
359
|
+
- **Fix:** stale "magic-link only" comment on the email provider button
|
|
360
|
+
(password is primary per ADR 0014).
|
|
361
|
+
|
|
362
|
+
### 0.3.10 — 2026-06-05
|
|
363
|
+
|
|
364
|
+
- **Feature:** optional `install_id` passthrough for PHASE-2 install→account
|
|
365
|
+
reconcile. `ManagerConfig` gains an optional `installId`; the manager exposes
|
|
366
|
+
`setInstallId()` / `getInstallId()`, and threads the value onto both legs of
|
|
367
|
+
sign-in — the `/oauth/authorize` query (persisted to the app-auth code) and
|
|
368
|
+
the `/oauth/token` exchange body. The wire field name is `install_id`.
|
|
369
|
+
Strictly additive: with no `install_id`, the authorize URL and token body are
|
|
370
|
+
byte-identical to prior releases (asserted by an equality test). Server-side
|
|
371
|
+
persistence/reconcile already live in the commerce router; this release only
|
|
372
|
+
emits the field.
|
|
373
|
+
|
|
245
374
|
### 0.3.9 — 2026-06-03
|
|
246
375
|
|
|
247
376
|
- **Feature:** added an optional host-injected `log` seam to `ManagerConfig`
|
|
@@ -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":"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"}
|
|
@@ -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;
|
|
@@ -33,6 +51,23 @@ export declare class BravelyAccountManager {
|
|
|
33
51
|
private libVersionPolicy;
|
|
34
52
|
private dpopKeypairPromise;
|
|
35
53
|
private dpopJktThumbprint;
|
|
54
|
+
/**
|
|
55
|
+
* PHASE-2 install→account reconciliation id (optional). When set, it is
|
|
56
|
+
* forwarded as `install_id` on BOTH the OAuth authorize URL and the
|
|
57
|
+
* `/oauth/token` exchange so the auth server can reconcile the install onto
|
|
58
|
+
* the resolved `ba_id`. `null` ⇒ nothing is sent and both legs stay
|
|
59
|
+
* byte-identical to the pre-`install_id` flow. Settable post-construction
|
|
60
|
+
* via `setInstallId()` because the host often learns the id asynchronously
|
|
61
|
+
* (e.g. after reading it from native bridge / first-launch storage).
|
|
62
|
+
*/
|
|
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;
|
|
36
71
|
constructor(config: ManagerConfig);
|
|
37
72
|
/**
|
|
38
73
|
* Return the current state. The returned reference is **stable across
|
|
@@ -55,10 +90,34 @@ export declare class BravelyAccountManager {
|
|
|
55
90
|
onStateChange(cb: StateListener): () => void;
|
|
56
91
|
getActivationState(): ActivationState;
|
|
57
92
|
getLibVersionPolicy(): LibVersionPolicy | null;
|
|
93
|
+
/**
|
|
94
|
+
* Set (or clear) the PHASE-2 install→account reconciliation id. Idempotent.
|
|
95
|
+
* Pass the per-install identifier the native client minted at
|
|
96
|
+
* `app_first_opened`; the manager forwards it as `install_id` on the next
|
|
97
|
+
* OAuth authorize redirect AND on the token exchange, so the auth server's
|
|
98
|
+
* existing reconcile path can stamp the resolved `ba_id` onto that install.
|
|
99
|
+
*
|
|
100
|
+
* Pass `undefined`/`null`/empty to clear it — after which the flow is again
|
|
101
|
+
* byte-identical to the pre-`install_id` behavior (no param sent). Purely
|
|
102
|
+
* additive: existing consumers that never call this send no `install_id`.
|
|
103
|
+
*/
|
|
104
|
+
setInstallId(installId: string | null | undefined): void;
|
|
105
|
+
/** Current install→account reconciliation id, or `null` if none is set. */
|
|
106
|
+
getInstallId(): string | null;
|
|
58
107
|
/**
|
|
59
108
|
* Hydrate from persistent storage and (if a BAS is present) verify it
|
|
60
109
|
* against the identity API. Call once on page load before rendering UI
|
|
61
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).
|
|
62
121
|
*/
|
|
63
122
|
restore(): Promise<BravelyAccountState>;
|
|
64
123
|
/**
|
|
@@ -90,6 +149,14 @@ export declare class BravelyAccountManager {
|
|
|
90
149
|
}): Promise<BravelyAccountState>;
|
|
91
150
|
/** Sign out: drop local state. Server-side revoke is best-effort. */
|
|
92
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;
|
|
93
160
|
/**
|
|
94
161
|
* Read entitlements, serving the cached row if fresh and refreshing
|
|
95
162
|
* opportunistically. Always returns an array (possibly empty).
|
|
@@ -138,8 +205,9 @@ export declare class BravelyAccountManager {
|
|
|
138
205
|
* introduced in the Mac/iOS v2 onboarding port.
|
|
139
206
|
*
|
|
140
207
|
* Throws if the user is not signed in (`not_signed_in`) or if the server
|
|
141
|
-
* returns a non-2xx status.
|
|
142
|
-
*
|
|
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.
|
|
143
211
|
*/
|
|
144
212
|
getAccountEntitlements(): Promise<AccountEntitlements>;
|
|
145
213
|
/**
|
|
@@ -163,15 +231,77 @@ export declare class BravelyAccountManager {
|
|
|
163
231
|
*/
|
|
164
232
|
pollForActivation(): Promise<ActivationResult>;
|
|
165
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;
|
|
166
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;
|
|
167
279
|
private refreshEntitlements;
|
|
168
280
|
/**
|
|
169
281
|
* Gate 1 compatibility path: keep `Authorization: Bearer <bas>` because
|
|
170
282
|
* the current router accepts Bearer on shared BAS endpoints, but attach a
|
|
171
283
|
* real RFC 9449 proof in the `DPoP` header so Gate 2 traffic is already
|
|
172
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).
|
|
173
295
|
*/
|
|
174
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;
|
|
175
305
|
private dpopProof;
|
|
176
306
|
private getDpopKeypair;
|
|
177
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":"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;AAiBjC;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,QAAsB,CAAC;AAEhE;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,kBAAkB,GAClB,oBAAoB,GACpB,mBAAmB,CAAC;AAExB,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;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwD;IAC5E,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;IAChD;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS,CAAuB;IACxC;;;;;OAKG;IACH,OAAO,CAAC,eAAe,CAAwC;gBAEnD,MAAM,EAAE,aAAa;IAmCjC;;;;;;;;;;;;;;;;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;;;;;;;;;;OAUG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAIxD,2EAA2E;IAC3E,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B;;;;;;;;;;;;;;OAcG;IACG,OAAO,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAqD7C;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACG,MAAM,CAAC,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;KAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAgCtG,qEAAqE;IAC/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgD9B;;;;;;OAMG;YACW,iBAAiB;IAa/B;;;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;;;;;;;;;;;;;OAaG;IACG,sBAAsB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAkB5D;;;;;;;;;;;;;;;;;;OAkBG;IACG,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,CAAC;YAyFtC,qBAAqB;IAyCnC;;;;OAIG;YACW,kBAAkB;YAqBlB,YAAY;IAmB1B;;;;;;;;;;OAUG;YACW,qBAAqB;IA+BnC;;;;OAIG;YACW,wBAAwB;IAQtC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,cAAc;YASR,sBAAsB;YAgCtB,gBAAgB;YAyDhB,mBAAmB;IA0EjC;;;;;;;;;;;;;;;OAeG;YACW,YAAY;IA6C1B;;;;;;OAMG;YACW,iBAAiB;YAIjB,SAAS;YAgBT,cAAc;IAmB5B,uFAAuF;YACzE,oBAAoB;YAkCpB,UAAU;IAMxB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,QAAQ;CAMjB;AAwDD,OAAO,EAAE,wBAAwB,EAAE,UAAU,EAAE,CAAC"}
|