@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.
Files changed (59) hide show
  1. package/LICENSE +10 -0
  2. package/README.md +262 -0
  3. package/dist/ActivationStateMachine.d.ts +50 -0
  4. package/dist/ActivationStateMachine.d.ts.map +1 -0
  5. package/dist/ActivationStateMachine.js +141 -0
  6. package/dist/ActivationStateMachine.js.map +1 -0
  7. package/dist/BravelyAccountManager.d.ts +156 -0
  8. package/dist/BravelyAccountManager.d.ts.map +1 -0
  9. package/dist/BravelyAccountManager.js +621 -0
  10. package/dist/BravelyAccountManager.js.map +1 -0
  11. package/dist/EntitlementCache.d.ts +50 -0
  12. package/dist/EntitlementCache.d.ts.map +1 -0
  13. package/dist/EntitlementCache.js +116 -0
  14. package/dist/EntitlementCache.js.map +1 -0
  15. package/dist/components/ActivationLadder.d.ts +78 -0
  16. package/dist/components/ActivationLadder.d.ts.map +1 -0
  17. package/dist/components/ActivationLadder.js +145 -0
  18. package/dist/components/ActivationLadder.js.map +1 -0
  19. package/dist/components/CrossAppCard.d.ts +48 -0
  20. package/dist/components/CrossAppCard.d.ts.map +1 -0
  21. package/dist/components/CrossAppCard.js +45 -0
  22. package/dist/components/CrossAppCard.js.map +1 -0
  23. package/dist/deprecation.d.ts +19 -0
  24. package/dist/deprecation.d.ts.map +1 -0
  25. package/dist/deprecation.js +83 -0
  26. package/dist/deprecation.js.map +1 -0
  27. package/dist/displayName.d.ts +15 -0
  28. package/dist/displayName.d.ts.map +1 -0
  29. package/dist/displayName.js +41 -0
  30. package/dist/displayName.js.map +1 -0
  31. package/dist/dpop.d.ts +30 -0
  32. package/dist/dpop.d.ts.map +1 -0
  33. package/dist/dpop.js +87 -0
  34. package/dist/dpop.js.map +1 -0
  35. package/dist/hooks/useActivationLaneFromUrl.d.ts +54 -0
  36. package/dist/hooks/useActivationLaneFromUrl.d.ts.map +1 -0
  37. package/dist/hooks/useActivationLaneFromUrl.js +105 -0
  38. package/dist/hooks/useActivationLaneFromUrl.js.map +1 -0
  39. package/dist/hooks/useFreshLaunchRestoration.d.ts +62 -0
  40. package/dist/hooks/useFreshLaunchRestoration.d.ts.map +1 -0
  41. package/dist/hooks/useFreshLaunchRestoration.js +135 -0
  42. package/dist/hooks/useFreshLaunchRestoration.js.map +1 -0
  43. package/dist/index.d.ts +16 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +15 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/oauth.d.ts +50 -0
  48. package/dist/oauth.d.ts.map +1 -0
  49. package/dist/oauth.js +107 -0
  50. package/dist/oauth.js.map +1 -0
  51. package/dist/storage.d.ts +48 -0
  52. package/dist/storage.d.ts.map +1 -0
  53. package/dist/storage.js +153 -0
  54. package/dist/storage.js.map +1 -0
  55. package/dist/types.d.ts +172 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +7 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +61 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"displayName.d.ts","sourceRoot":"","sources":["../src/displayName.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAUjD,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD"}
@@ -0,0 +1,41 @@
1
+ // Slug → user-facing display name lookup.
2
+ //
3
+ // Source of truth: the H1 of each `<app>-free-vs-pro.md` in
4
+ // `00 - AI Instructions/`. The display name is what the user sees in the
5
+ // activation ladder copy (`Activating PrintScreen.ly Pro…`), in the cross-app
6
+ // card (`You own 3 Bravely Pro apps on this account.`), and any other
7
+ // product-facing string the facade renders.
8
+ //
9
+ // Lookup is exhaustive over the `AppSlug` union — adding a new utility means
10
+ // adding a row here. Unknown slugs fall through to a Title-cased fallback so
11
+ // host pages never crash; observability surface logs the lookup miss.
12
+ /**
13
+ * Canonical slug → display name map. Sourced from the H1 of each
14
+ * `<app>-free-vs-pro.md`. Branding owners: keep this in lockstep with the
15
+ * marketing pages on `bravely.dev/<slug>` and the Settings strings inside
16
+ * each utility.
17
+ */
18
+ export const DISPLAY_NAMES = {
19
+ diskaroo: "Diskaroo",
20
+ markdly: "Markd.ly",
21
+ printscreenly: "PrintScreen.ly",
22
+ prodjectly: "Prodject.ly",
23
+ saycopypaste: "SayCopyPaste",
24
+ scry: "Scry",
25
+ stickily: "Sticki.ly",
26
+ terminaltags: "Terminal Tags",
27
+ todoingly: "Todoing.ly",
28
+ };
29
+ /**
30
+ * Returns the display name for a slug. Unknown slugs fall back to a
31
+ * naive Title-case rendering so host pages never crash on an unrecognized
32
+ * slug (e.g. a new utility added before this lib version ships).
33
+ */
34
+ export function displayNameFor(slug) {
35
+ if (slug in DISPLAY_NAMES)
36
+ return DISPLAY_NAMES[slug];
37
+ if (slug.length === 0)
38
+ return slug;
39
+ return slug.charAt(0).toUpperCase() + slug.slice(1);
40
+ }
41
+ //# sourceMappingURL=displayName.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"displayName.js","sourceRoot":"","sources":["../src/displayName.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,4DAA4D;AAC5D,yEAAyE;AACzE,8EAA8E;AAC9E,sEAAsE;AACtE,4CAA4C;AAC5C,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,sEAAsE;AAItE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAA4B;IACpD,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,UAAU;IACnB,aAAa,EAAE,gBAAgB;IAC/B,UAAU,EAAE,aAAa;IACzB,YAAY,EAAE,cAAc;IAC5B,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,WAAW;IACrB,YAAY,EAAE,eAAe;IAC7B,SAAS,EAAE,YAAY;CACxB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,IAAI,IAAI,aAAa;QAAE,OAAO,aAAa,CAAC,IAAe,CAAC,CAAC;IACjE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC"}
package/dist/dpop.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ export interface DPoPKeypair {
2
+ publicJwk: JsonWebKey;
3
+ /** RFC 7638 thumbprint of the public JWK. */
4
+ jktThumbprint: string;
5
+ /** Private key handle. Not serialisable. */
6
+ privateKey: CryptoKey;
7
+ }
8
+ /** Generate a fresh ES256 keypair with `extractable: false` on the private key. */
9
+ export declare function generateKeypair(): Promise<DPoPKeypair>;
10
+ /**
11
+ * RFC 7638 JWK thumbprint. EC keys hash the canonical-JSON of
12
+ * `{ crv, kty, x, y }` (lexicographically sorted, no whitespace).
13
+ */
14
+ export declare function computeJwkThumbprint(jwk: JsonWebKey): Promise<string>;
15
+ /** SHA-256(access_token), base64url-encoded — for the `ath` claim. */
16
+ export declare function accessTokenHash(accessToken: string): Promise<string>;
17
+ /**
18
+ * Build a DPoP proof JWT for the given HTTP method + URL. If an access token
19
+ * is supplied, includes the `ath` claim (access-token hash) per §4.2.
20
+ *
21
+ * Gate 1: function returns a real JWT; the server simply does not require
22
+ * the `DPoP` header. Host pages may still attach the header to seed traffic.
23
+ */
24
+ export declare function generateProof(args: {
25
+ keypair: DPoPKeypair;
26
+ htm: string;
27
+ htu: string;
28
+ accessToken?: string;
29
+ }): Promise<string>;
30
+ //# sourceMappingURL=dpop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,UAAU,CAAC;IACtB,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,UAAU,EAAE,SAAS,CAAC;CACvB;AAED,mFAAmF;AACnF,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CAU5D;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAM3E;AAED,sEAAsE;AACtE,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI1E;AAQD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,OAAO,EAAE,WAAW,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBlB"}
package/dist/dpop.js ADDED
@@ -0,0 +1,87 @@
1
+ // RFC 9449 DPoP proof helpers.
2
+ //
3
+ // **Gate status:** lib SHIPS this code at Gate 1 but the server runs in
4
+ // `off` mode and ignores DPoP. The function call surface is finalised here
5
+ // so the Gate 2 transition is a one-line server flip; client code does not
6
+ // rewrite.
7
+ //
8
+ // Key lifecycle:
9
+ // - ES256 (P-256 ECDSA) keypair, `extractable: false`. Private key is a
10
+ // WebCrypto handle stored in IndexedDB (handle reference; bytes never
11
+ // leave the subtle worker).
12
+ // - Public key JWK + RFC 7638 thumbprint = `jkt` claim baked into BAS rows
13
+ // for binding.
14
+ //
15
+ // Proof JWT (per §4.2):
16
+ // header: { alg: "ES256", typ: "dpop+jwt", jwk: <public JWK> }
17
+ // payload: { jti: <random>, htm: <method>, htu: <url>, iat: <epoch> [, ath: <hash(at)>] }
18
+ // signature: ES256 over `${b64(header)}.${b64(payload)}`
19
+ import { base64urlEncode } from "./oauth.js";
20
+ const ALG = { name: "ECDSA", namedCurve: "P-256" };
21
+ const SIGN_ALG = { name: "ECDSA", hash: "SHA-256" };
22
+ /** Generate a fresh ES256 keypair with `extractable: false` on the private key. */
23
+ export async function generateKeypair() {
24
+ const kp = await crypto.subtle.generateKey(ALG, false, ["sign", "verify"]);
25
+ const publicJwk = await crypto.subtle.exportKey("jwk", kp.publicKey);
26
+ // Make sure only the public-portion fields are kept (sanity strip; private
27
+ // can't actually be exported since extractable=false).
28
+ delete publicJwk.d;
29
+ delete publicJwk.key_ops;
30
+ delete publicJwk.ext;
31
+ const jktThumbprint = await computeJwkThumbprint(publicJwk);
32
+ return { publicJwk, jktThumbprint, privateKey: kp.privateKey };
33
+ }
34
+ /**
35
+ * RFC 7638 JWK thumbprint. EC keys hash the canonical-JSON of
36
+ * `{ crv, kty, x, y }` (lexicographically sorted, no whitespace).
37
+ */
38
+ export async function computeJwkThumbprint(jwk) {
39
+ if (jwk.kty !== "EC")
40
+ throw new Error(`Unsupported jwk.kty: ${jwk.kty}`);
41
+ const canonical = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
42
+ const bytes = new TextEncoder().encode(JSON.stringify(canonical));
43
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
44
+ return base64urlEncode(new Uint8Array(digest));
45
+ }
46
+ /** SHA-256(access_token), base64url-encoded — for the `ath` claim. */
47
+ export async function accessTokenHash(accessToken) {
48
+ const bytes = new TextEncoder().encode(accessToken);
49
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
50
+ return base64urlEncode(new Uint8Array(digest));
51
+ }
52
+ function randomJti() {
53
+ const bytes = new Uint8Array(16);
54
+ crypto.getRandomValues(bytes);
55
+ return base64urlEncode(bytes);
56
+ }
57
+ /**
58
+ * Build a DPoP proof JWT for the given HTTP method + URL. If an access token
59
+ * is supplied, includes the `ath` claim (access-token hash) per §4.2.
60
+ *
61
+ * Gate 1: function returns a real JWT; the server simply does not require
62
+ * the `DPoP` header. Host pages may still attach the header to seed traffic.
63
+ */
64
+ export async function generateProof(args) {
65
+ const header = {
66
+ alg: "ES256",
67
+ typ: "dpop+jwt",
68
+ jwk: args.keypair.publicJwk,
69
+ };
70
+ const payload = {
71
+ jti: randomJti(),
72
+ htm: args.htm.toUpperCase(),
73
+ htu: args.htu,
74
+ iat: Math.floor(Date.now() / 1000),
75
+ };
76
+ if (args.accessToken)
77
+ payload.ath = await accessTokenHash(args.accessToken);
78
+ const encH = base64urlEncode(new TextEncoder().encode(JSON.stringify(header)));
79
+ const encP = base64urlEncode(new TextEncoder().encode(JSON.stringify(payload)));
80
+ const signingInput = `${encH}.${encP}`;
81
+ const sig = await crypto.subtle.sign(SIGN_ALG, args.keypair.privateKey, new TextEncoder().encode(signingInput));
82
+ // WebCrypto returns raw r||s for ECDSA; that's already the JOSE-mandated
83
+ // shape (no DER-unwrap needed).
84
+ const encS = base64urlEncode(new Uint8Array(sig));
85
+ return `${signingInput}.${encS}`;
86
+ }
87
+ //# sourceMappingURL=dpop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpop.js","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { BravelyAccountManager } from "../BravelyAccountManager.js";
2
+ export type ActivationLaneSource = "upgraded" | "checkout" | "subscription" | null;
3
+ export interface ActivationLaneResult {
4
+ /** True iff any recognized post-checkout return param is present. */
5
+ inActivationLane: boolean;
6
+ /** Which param was detected (or null). */
7
+ source: ActivationLaneSource;
8
+ /** The raw query param value (e.g. `"true"`, `"complete"`, `"success"`). */
9
+ rawValue: string | null;
10
+ /**
11
+ * Removes the recognized post-checkout query param via
12
+ * `history.replaceState`. Idempotent. Host pages should call this once
13
+ * they have observed the activation lane is engaged.
14
+ */
15
+ clearUrlParam: () => void;
16
+ }
17
+ interface DetectedLane {
18
+ source: Exclude<ActivationLaneSource, null>;
19
+ paramName: string;
20
+ rawValue: string;
21
+ }
22
+ /** Pure detector — used by the hook and unit-testable on its own. */
23
+ export declare function detectActivationLane(search: string | URLSearchParams): DetectedLane | null;
24
+ export interface UseActivationLaneFromUrlOptions {
25
+ /**
26
+ * If supplied, the hook will call `manager.notifyCheckoutCompleted()`
27
+ * once when the lane is engaged.
28
+ */
29
+ manager?: BravelyAccountManager | null;
30
+ /**
31
+ * If true (default false) AND a manager is supplied AND it exposes
32
+ * `pollForActivation()`, the hook will start the polling loop in the
33
+ * background once the lane is engaged. Failures are swallowed; the
34
+ * caller is responsible for surfacing them via the manager's state.
35
+ */
36
+ autoStartPolling?: boolean;
37
+ /**
38
+ * Override the URL source. Defaults to `window.location.search`. Useful
39
+ * for SSR / testing.
40
+ */
41
+ urlSearch?: string;
42
+ /**
43
+ * Override the URL clearer. Defaults to `window.history.replaceState`.
44
+ */
45
+ onClearParam?: (paramName: string) => void;
46
+ }
47
+ /**
48
+ * React hook surfacing the post-checkout activation lane state. Stable
49
+ * across re-renders. Calls `manager.notifyCheckoutCompleted()` exactly
50
+ * once per detected lane.
51
+ */
52
+ export declare function useActivationLaneFromUrl(opts?: UseActivationLaneFromUrlOptions): ActivationLaneResult;
53
+ export {};
54
+ //# sourceMappingURL=useActivationLaneFromUrl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useActivationLaneFromUrl.d.ts","sourceRoot":"","sources":["../../src/hooks/useActivationLaneFromUrl.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAC5B,UAAU,GACV,UAAU,GACV,cAAc,GACd,IAAI,CAAC;AAET,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0CAA0C;IAC1C,MAAM,EAAE,oBAAoB,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;OAIG;IACH,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,UAAU,YAAY;IACpB,MAAM,EAAE,OAAO,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;IAC5C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AASD,qEAAqE;AACrE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,YAAY,GAAG,IAAI,CAc1F;AAED,MAAM,WAAW,+BAA+B;IAC9C;;;OAGG;IACH,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACvC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,GAAE,+BAAoC,GACzC,oBAAoB,CAuEtB"}
@@ -0,0 +1,105 @@
1
+ // `useActivationLaneFromUrl()` — detect post-checkout return URL params.
2
+ //
3
+ // Three patterns observed across the 4 web variants per the rollout plan:
4
+ // - `?upgraded=true` (prodjectly)
5
+ // - `?checkout=complete` (scry-web)
6
+ // - `?subscription=success` (todoingly-web)
7
+ //
8
+ // The hook reports whether any of those signals is present, which one fired,
9
+ // and exposes a `clearUrlParam()` callback that removes the param via
10
+ // `window.history.replaceState` so a refresh doesn't re-trigger the
11
+ // activation lane. As a side effect, when `inActivationLane === true`, it
12
+ // also fires `manager.notifyCheckoutCompleted()` once, then optionally
13
+ // starts the `pollForActivation()` runner if `autoStartPolling` is set.
14
+ import { useEffect, useMemo, useRef, useState } from "react";
15
+ /** Maps each recognized param to its `source` label. */
16
+ const PATTERNS = [
17
+ { param: "upgraded", source: "upgraded" },
18
+ { param: "checkout", source: "checkout" },
19
+ { param: "subscription", source: "subscription" },
20
+ ];
21
+ /** Pure detector — used by the hook and unit-testable on its own. */
22
+ export function detectActivationLane(search) {
23
+ const params = typeof search === "string"
24
+ ? new URLSearchParams(search.startsWith("?") ? search.slice(1) : search)
25
+ : search;
26
+ for (const { param, source } of PATTERNS) {
27
+ const value = params.get(param);
28
+ if (value === null)
29
+ continue;
30
+ // Treat any truthy / completion-style value as engaged. The three
31
+ // variants use `true`, `complete`, `success` respectively. Empty string
32
+ // is treated as "engaged" since some checkout providers send `?foo=`.
33
+ return { source, paramName: param, rawValue: value };
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * React hook surfacing the post-checkout activation lane state. Stable
39
+ * across re-renders. Calls `manager.notifyCheckoutCompleted()` exactly
40
+ * once per detected lane.
41
+ */
42
+ export function useActivationLaneFromUrl(opts = {}) {
43
+ const { manager, autoStartPolling, urlSearch, onClearParam } = opts;
44
+ const detected = useMemo(() => {
45
+ const source = urlSearch !== undefined
46
+ ? urlSearch
47
+ : typeof window !== "undefined"
48
+ ? window.location.search
49
+ : "";
50
+ return detectActivationLane(source);
51
+ }, [urlSearch]);
52
+ // We hold a separate piece of state so `clearUrlParam()` calls can reset
53
+ // it (the hook should report `inActivationLane === false` after the
54
+ // caller has cleared the param). But re-deriving from `urlSearch` makes
55
+ // the hook deterministic.
56
+ const [lane, setLane] = useState(detected);
57
+ // Track whether we've already fired the notify side-effect for this lane.
58
+ const notifiedRef = useRef(null);
59
+ useEffect(() => {
60
+ setLane(detected);
61
+ }, [detected]);
62
+ useEffect(() => {
63
+ if (!lane)
64
+ return;
65
+ if (notifiedRef.current === lane)
66
+ return;
67
+ notifiedRef.current = lane;
68
+ if (manager) {
69
+ try {
70
+ manager.notifyCheckoutCompleted();
71
+ }
72
+ catch {
73
+ // Manager state machine may reject the event from the current
74
+ // state; that's a no-op by design.
75
+ }
76
+ if (autoStartPolling) {
77
+ // Fire-and-forget; manager surfaces results via state change.
78
+ const m = manager;
79
+ if (typeof m.pollForActivation === "function") {
80
+ void m.pollForActivation().catch(() => undefined);
81
+ }
82
+ }
83
+ }
84
+ }, [lane, manager, autoStartPolling]);
85
+ const clearUrlParam = () => {
86
+ if (!lane)
87
+ return;
88
+ if (onClearParam) {
89
+ onClearParam(lane.paramName);
90
+ }
91
+ else if (typeof window !== "undefined" && window.history?.replaceState) {
92
+ const url = new URL(window.location.href);
93
+ url.searchParams.delete(lane.paramName);
94
+ window.history.replaceState(window.history.state, "", url.pathname + (url.search ? url.search : "") + url.hash);
95
+ }
96
+ setLane(null);
97
+ };
98
+ return {
99
+ inActivationLane: lane !== null,
100
+ source: lane?.source ?? null,
101
+ rawValue: lane?.rawValue ?? null,
102
+ clearUrlParam,
103
+ };
104
+ }
105
+ //# sourceMappingURL=useActivationLaneFromUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useActivationLaneFromUrl.js","sourceRoot":"","sources":["../../src/hooks/useActivationLaneFromUrl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { BravelyAccountManager } from "../BravelyAccountManager.js";
2
+ /**
3
+ * Returned shape from `useFreshLaunchRestoration()`. `shouldShowToast`
4
+ * flips true after the 3 s timer when this is a first launch with a
5
+ * successfully-hydrated entitlement; flips back to false when the host
6
+ * page calls `dismissToast()`.
7
+ */
8
+ export interface FreshLaunchRestorationResult {
9
+ /** Count of items synced (host page provides via `setSyncedCount`). */
10
+ syncedCount: number | null;
11
+ /** Resolved name of the other device (Gate 2). Always null in Gate 1. */
12
+ otherDeviceName: string | null;
13
+ /** True iff the toast should currently render. */
14
+ shouldShowToast: boolean;
15
+ /** Plural-aware item label. Defaults to `items`. */
16
+ itemsLabel: string;
17
+ /**
18
+ * Fully-rendered toast copy ready to mount. Drops the `[other-device]`
19
+ * anchor when null (Gate 1 fallback per plan doc decision #2). Empty
20
+ * string when nothing should render.
21
+ */
22
+ toastText: string;
23
+ /** Host page reports how many items were synced (tasks, notes, etc.). */
24
+ setSyncedCount: (n: number) => void;
25
+ /** Host page closes the toast (UI dismiss). */
26
+ dismissToast: () => void;
27
+ }
28
+ export interface UseFreshLaunchRestorationOptions {
29
+ /** Manager singleton. Required for the entitlement-cache check. */
30
+ manager: BravelyAccountManager;
31
+ /**
32
+ * Storage key holding the boolean first-launch marker. Defaults to
33
+ * `bravely:fresh_launch_marker_v1`. Bump the suffix if the meaning of
34
+ * "first launch" changes.
35
+ */
36
+ markerKey?: string;
37
+ /** Item-label shown in the toast (e.g. "tasks", "stickies"). */
38
+ itemsLabel?: string;
39
+ /**
40
+ * ms after detection until the toast fires. Defaults to 3000 per the
41
+ * state machine `fresh_launch_restoration` 0-3s window.
42
+ */
43
+ toastDelayMs?: number;
44
+ /**
45
+ * Resolver for the "other device" string (Gate 2). Pass a function that
46
+ * returns the most-recent OTHER device fingerprint -> human name (e.g.
47
+ * "iPad" / "Mac mini"). When null, copy drops the anchor.
48
+ */
49
+ resolveOtherDeviceName?: () => string | null;
50
+ /** Override the localStorage / sessionStorage accessor for tests. */
51
+ storage?: {
52
+ getItem(k: string): string | null;
53
+ setItem(k: string, v: string): void;
54
+ };
55
+ }
56
+ /**
57
+ * Pure-function copy renderer for the M2 toast. Drops the `[other-device]`
58
+ * anchor when null. Banned: the entire "Welcome back" phrase family.
59
+ */
60
+ export declare function renderFreshLaunchToastCopy(syncedCount: number | null, otherDeviceName: string | null, itemsLabel?: string): string;
61
+ export declare function useFreshLaunchRestoration(opts: UseFreshLaunchRestorationOptions): FreshLaunchRestorationResult;
62
+ //# sourceMappingURL=useFreshLaunchRestoration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFreshLaunchRestoration.d.ts","sourceRoot":"","sources":["../../src/hooks/useFreshLaunchRestoration.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAGzE;;;;;GAKG;AACH,MAAM,WAAW,4BAA4B;IAC3C,uEAAuE;IACvE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,yEAAyE;IACzE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kDAAkD;IAClD,eAAe,EAAE,OAAO,CAAC;IACzB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,cAAc,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,+CAA+C;IAC/C,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,gCAAgC;IAC/C,mEAAmE;IACnE,OAAO,EAAE,qBAAqB,CAAC;IAC/B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC7C,qEAAqE;IACrE,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;QAClC,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACrC,CAAC;CACH;AAMD;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,UAAU,GAAE,MAA4B,GACvC,MAAM,CAOR;AA2BD,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,gCAAgC,GACrC,4BAA4B,CAuF9B"}
@@ -0,0 +1,135 @@
1
+ // `useFreshLaunchRestoration()` — M2 silent rehydrate + brief toast.
2
+ //
3
+ // Detects a "fresh launch on this device" — i.e. the user signed in on a
4
+ // new device (or cleared the cache) and we just hydrated an entitlement
5
+ // from the server. UI is silent for the first 3 s while data loads; after
6
+ // 3 s a bottom-edge toast surfaces with `Synced [N] [items] from your
7
+ // [other-device].` (or the device-anchor-less variant when the resolver
8
+ // is not available yet — Gate 1 fallback per plan doc decision #2).
9
+ //
10
+ // Banned: NO "Welcome back. Restoring your Pro features." Anywhere. Anti-
11
+ // pattern #3 from the journey doc.
12
+ //
13
+ // First-launch detection uses a single boolean key in storage (set on first
14
+ // hydrate). Subsequent launches see `first-launch-marker !== null` and the
15
+ // hook returns `shouldShowToast = false`.
16
+ import { useEffect, useRef, useState } from "react";
17
+ const DEFAULT_MARKER_KEY = "bravely:fresh_launch_marker_v1";
18
+ const DEFAULT_TOAST_DELAY_MS = 3000;
19
+ const DEFAULT_ITEMS_LABEL = "items";
20
+ /**
21
+ * Pure-function copy renderer for the M2 toast. Drops the `[other-device]`
22
+ * anchor when null. Banned: the entire "Welcome back" phrase family.
23
+ */
24
+ export function renderFreshLaunchToastCopy(syncedCount, otherDeviceName, itemsLabel = DEFAULT_ITEMS_LABEL) {
25
+ if (syncedCount === null || syncedCount <= 0)
26
+ return "";
27
+ const label = itemsLabel || DEFAULT_ITEMS_LABEL;
28
+ if (otherDeviceName && otherDeviceName.length > 0) {
29
+ return `Synced ${syncedCount} ${label} from your ${otherDeviceName}.`;
30
+ }
31
+ return `Synced ${syncedCount} ${label}.`;
32
+ }
33
+ /**
34
+ * Choose which storage to use. localStorage on browser; in-memory shim on
35
+ * SSR/test. The host page's preferences in `manager.storage` are ignored
36
+ * here because the marker is a UI-only signal (resetting it on sign-out
37
+ * is undesirable — the user is still on a "fresh device" mathematically).
38
+ */
39
+ function pickStorage(override) {
40
+ if (override)
41
+ return override;
42
+ if (typeof window !== "undefined" && window.localStorage) {
43
+ return {
44
+ getItem: (k) => window.localStorage.getItem(k),
45
+ setItem: (k, v) => window.localStorage.setItem(k, v),
46
+ };
47
+ }
48
+ const m = new Map();
49
+ return {
50
+ getItem: (k) => m.get(k) ?? null,
51
+ setItem: (k, v) => {
52
+ m.set(k, v);
53
+ },
54
+ };
55
+ }
56
+ export function useFreshLaunchRestoration(opts) {
57
+ const { manager, markerKey = DEFAULT_MARKER_KEY, itemsLabel = DEFAULT_ITEMS_LABEL, toastDelayMs = DEFAULT_TOAST_DELAY_MS, resolveOtherDeviceName, storage, } = opts;
58
+ const store = pickStorage(storage);
59
+ const [syncedCount, setSyncedCountState] = useState(null);
60
+ const [shouldShowToast, setShouldShowToast] = useState(false);
61
+ const [otherDeviceName, setOtherDeviceName] = useState(() => {
62
+ try {
63
+ return resolveOtherDeviceName ? resolveOtherDeviceName() ?? null : null;
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ });
69
+ // Track whether we've armed the toast for this hook lifetime so we don't
70
+ // double-fire on re-render.
71
+ const armedRef = useRef(false);
72
+ const timerRef = useRef(null);
73
+ // Track whether we've already marked first-launch so subsequent calls
74
+ // (e.g. React strict mode double-mount in dev) don't re-arm.
75
+ const markedRef = useRef(false);
76
+ useEffect(() => {
77
+ // Listen for the manager state to flip to `signed_in`. The first time
78
+ // that happens on a "fresh" device (no marker in storage), we arm the
79
+ // toast for `toastDelayMs` ms.
80
+ const handleState = (state) => {
81
+ if (state.kind !== "signed_in")
82
+ return;
83
+ if (markedRef.current)
84
+ return;
85
+ const marker = store.getItem(markerKey);
86
+ if (marker !== null) {
87
+ // Not a fresh launch; do nothing.
88
+ markedRef.current = true;
89
+ return;
90
+ }
91
+ // Fresh launch. Mark it now so a subsequent sign-out + sign-in in
92
+ // the same tab doesn't re-trigger.
93
+ store.setItem(markerKey, new Date().toISOString());
94
+ markedRef.current = true;
95
+ if (armedRef.current)
96
+ return;
97
+ armedRef.current = true;
98
+ timerRef.current = setTimeout(() => {
99
+ setShouldShowToast(true);
100
+ }, toastDelayMs);
101
+ };
102
+ // Fire once for the current state in case we mounted post-restore.
103
+ handleState(manager.getState());
104
+ const off = manager.onStateChange(handleState);
105
+ return () => {
106
+ off();
107
+ if (timerRef.current !== null)
108
+ clearTimeout(timerRef.current);
109
+ };
110
+ }, [manager, markerKey, store, toastDelayMs]);
111
+ // Re-resolve the device name if the resolver itself changes (e.g. Gate
112
+ // 2 plug-in mounts after first paint).
113
+ useEffect(() => {
114
+ try {
115
+ setOtherDeviceName(resolveOtherDeviceName ? resolveOtherDeviceName() ?? null : null);
116
+ }
117
+ catch {
118
+ setOtherDeviceName(null);
119
+ }
120
+ }, [resolveOtherDeviceName]);
121
+ const toastText = renderFreshLaunchToastCopy(syncedCount, otherDeviceName, itemsLabel);
122
+ // Suppress the toast surface when the copy is empty (no items, or
123
+ // negative count) — there is nothing to say.
124
+ const effectiveShow = shouldShowToast && toastText.length > 0;
125
+ return {
126
+ syncedCount,
127
+ otherDeviceName,
128
+ shouldShowToast: effectiveShow,
129
+ itemsLabel,
130
+ toastText,
131
+ setSyncedCount: setSyncedCountState,
132
+ dismissToast: () => setShouldShowToast(false),
133
+ };
134
+ }
135
+ //# sourceMappingURL=useFreshLaunchRestoration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFreshLaunchRestoration.js","sourceRoot":"","sources":["../../src/hooks/useFreshLaunchRestoration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export { BravelyAccountManager, BravelyClientKilledError, OAuthError } from "./BravelyAccountManager.js";
2
+ export type { ActivationResult } from "./BravelyAccountManager.js";
3
+ export { EntitlementCache, ENTITLEMENT_CACHE_TTL_SECONDS } from "./EntitlementCache.js";
4
+ export { ActivationStateMachine, AUTO_ADVANCE_MS, AUTO_ADVANCE_TO, POST_CHECKOUT_RETRY, type ActivationEvent, } from "./ActivationStateMachine.js";
5
+ export { parseDeprecation } from "./deprecation.js";
6
+ export { generateCodeVerifier, generateCodeChallenge, generateState, buildAuthorizeUrl, parseCallback, preparePkce, base64urlEncode, base64urlDecode, } from "./oauth.js";
7
+ export { defaultStorage, memoryStorage, sessionStorageBacked, indexedDbBacked, CompositeStorage } from "./storage.js";
8
+ export type { Storage } from "./storage.js";
9
+ export { generateKeypair, computeJwkThumbprint, generateProof, accessTokenHash, type DPoPKeypair, } from "./dpop.js";
10
+ export type { AppSlug, Entitlement, EntitlementSnapshot, OAuthTokenResponse, BasResponse, BravelyAccountState, ActivationState, ActivationStateName, ManagerConfig, LibVersionPolicy, DeprecationDirective, DeprecationEndpoint, DeprecationClient, CheckoutPlan, PaddlePortalSession, CachedEntitlements, DPoPProof, } from "./types.js";
11
+ export { ActivationLadder, ACTIVATION_LADDER_PHASE_TEMPLATES, ladderPhaseForElapsed, renderLadderCopy, buildResolvedLadder, type ActivationLadderProps, type ActivationLadderPhase, } from "./components/ActivationLadder.js";
12
+ export { CrossAppCard, filterCrossAppEntitlements, countCrossAppEntitlements, renderCrossAppCopy, type CrossAppCardProps, } from "./components/CrossAppCard.js";
13
+ export { useActivationLaneFromUrl, detectActivationLane, type ActivationLaneResult, type ActivationLaneSource, type UseActivationLaneFromUrlOptions, } from "./hooks/useActivationLaneFromUrl.js";
14
+ export { useFreshLaunchRestoration, renderFreshLaunchToastCopy, type FreshLaunchRestorationResult, type UseFreshLaunchRestorationOptions, } from "./hooks/useFreshLaunchRestoration.js";
15
+ export { displayNameFor, DISPLAY_NAMES } from "./displayName.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACzG,YAAY,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACtH,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,gBAAgB,EAChB,iCAAiC,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,YAAY,EACZ,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,+BAA+B,GACrC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,4BAA4B,EACjC,KAAK,gCAAgC,GACtC,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ // Barrel re-exports for `@bravely-studios/account-web`.
2
+ export { BravelyAccountManager, BravelyClientKilledError, OAuthError } from "./BravelyAccountManager.js";
3
+ export { EntitlementCache, ENTITLEMENT_CACHE_TTL_SECONDS } from "./EntitlementCache.js";
4
+ export { ActivationStateMachine, AUTO_ADVANCE_MS, AUTO_ADVANCE_TO, POST_CHECKOUT_RETRY, } from "./ActivationStateMachine.js";
5
+ export { parseDeprecation } from "./deprecation.js";
6
+ export { generateCodeVerifier, generateCodeChallenge, generateState, buildAuthorizeUrl, parseCallback, preparePkce, base64urlEncode, base64urlDecode, } from "./oauth.js";
7
+ export { defaultStorage, memoryStorage, sessionStorageBacked, indexedDbBacked, CompositeStorage } from "./storage.js";
8
+ export { generateKeypair, computeJwkThumbprint, generateProof, accessTokenHash, } from "./dpop.js";
9
+ // ---- C-ux M2/M3/M4 surfaces (Wave A) ---------------------------------------
10
+ export { ActivationLadder, ACTIVATION_LADDER_PHASE_TEMPLATES, ladderPhaseForElapsed, renderLadderCopy, buildResolvedLadder, } from "./components/ActivationLadder.js";
11
+ export { CrossAppCard, filterCrossAppEntitlements, countCrossAppEntitlements, renderCrossAppCopy, } from "./components/CrossAppCard.js";
12
+ export { useActivationLaneFromUrl, detectActivationLane, } from "./hooks/useActivationLaneFromUrl.js";
13
+ export { useFreshLaunchRestoration, renderFreshLaunchToastCopy, } from "./hooks/useFreshLaunchRestoration.js";
14
+ export { displayNameFor, DISPLAY_NAMES } from "./displayName.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAExD,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAEzG,OAAO,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AACxF,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,mBAAmB,GAEpB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEtH,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,eAAe,GAEhB,MAAM,WAAW,CAAC;AAqBnB,+EAA+E;AAC/E,OAAO,EACL,gBAAgB,EAChB,iCAAiC,EACjC,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,GAGpB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,YAAY,EACZ,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,GAEnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,wBAAwB,EACxB,oBAAoB,GAIrB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,yBAAyB,EACzB,0BAA0B,GAG3B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}