@monetize.software/sdk-react 3.0.0-alpha.8 → 3.0.0-alpha.9

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 CHANGED
@@ -46,8 +46,10 @@ function App() {
46
46
  }
47
47
 
48
48
  function UpgradeCTA() {
49
- const user = usePaywallUser();
50
- return <p>Hi, {user?.email ?? 'guest'}! Unlock full access.</p>;
49
+ const account = usePaywallUser();
50
+ if (account.status === 'loading') return <p>…</p>;
51
+ if (account.status === 'guest') return <p>Hi guest! Unlock full access.</p>;
52
+ return <p>Hi, {account.user?.email ?? 'there'}! Unlock full access.</p>;
51
53
  }
52
54
  ```
53
55
 
@@ -79,7 +81,7 @@ intentionally not performed.
79
81
  |---|---|---|
80
82
  | `usePaywall()` | `PaywallUI \| null` | instance change (rare) |
81
83
  | `usePaywallState()` | `{ open, view, error }` | any state-machine change |
82
- | `usePaywallUser()` | `PaywallUser \| null` | `userChange` event |
84
+ | `usePaywallUser()` | `PaywallUserState` (`loading` \| `guest` \| `signed_in`) | `userChange` / `authChange` |
83
85
  | `usePaywallAccess(opts?)` | `{ status, result }` | `userChange` / `purchase_completed` |
84
86
  | `usePaywallPrices()` | `{ prices, loading, error }` | bootstrap refresh |
85
87
  | `usePaywallTrial()` | `TrialStatus \| null` | `trial_blocked` / `trial_expired` |
@@ -1,30 +1,50 @@
1
- import { PaywallUser } from '@monetize.software/sdk';
1
+ import { AuthSession, PaywallUser } from '@monetize.software/sdk';
2
2
  /**
3
- * Подписка на текущего юзера пейвола (sync snapshot + автоматический ре-рендер
4
- * на любой userChange — bootstrap, /me refresh, после-checkout watcher).
3
+ * Состояние «кто такой текущий пользователь» с точки зрения хоста.
5
4
  *
6
- * Возвращает `null` до первого ответа сети или когда инстанс ещё не готов
7
- * (SSR / до useEffect Provider / Provider не оборачивает дерево с инстансом).
5
+ * Discriminated union намеренно совмещает три источника: готовность инстанса
6
+ * PaywallUI (Provider mount), наличие session у managed-auth и `getCachedUser()`
7
+ * от bootstrap'а. Это убирает у хоста нужду различать «пейвол ещё грузится»
8
+ * vs «никого нет» вручную — типы сужают каждый случай.
8
9
  *
9
- * Удобно для подсветки текущего плана / e-mail юзера в собственном UI без
10
- * необходимости держать дублирующий state и руками подписываться на
11
- * `paywall.on('userChange', ...)`.
10
+ * - `loading` Provider ещё не смонтировал PaywallUI (SSR / pre-mount /
11
+ * dev-double-mount cleanup). На этом этапе показывать skeleton.
12
+ * - `guest` — у пейвола нет identity:
13
+ * • managed-auth: `auth.getCachedSession()` вернул null;
14
+ * • hybrid (без managed-auth): bootstrap прошёл, но user-snapshot пуст.
15
+ * В этом состоянии валидно показать CTA «Sign in» / `<PaywallButton mode="signin">`.
16
+ * - `signed_in` — есть identity. `user` — последний снимок из BillingClient
17
+ * (может быть `null`, пока /me-refresh после signIn в полёте — UI должен
18
+ * показать skeleton, не «sign-in» CTA). `session` — managed-auth session
19
+ * или `null` для hybrid-режима.
12
20
  *
21
+ * Хост обычно делает три проверки подряд:
13
22
  * ```tsx
14
- * const user = usePaywallUser();
15
- * if (user?.has_active_subscription) {
16
- * return <ProBadge plan={user.active_subscription?.plan_name} />;
17
- * }
23
+ * const account = usePaywallUser();
24
+ * if (account.status === 'loading') return <Skeleton />;
25
+ * if (account.status === 'guest') return <SignInCTA />;
26
+ * // account.user может быть null, пока /me грузится — показать skeleton тут же.
27
+ * if (!account.user) return <Skeleton />;
28
+ * return <Profile user={account.user} />;
18
29
  * ```
19
30
  *
20
- * Реализация поверх `paywall.on('userChange', cb)` + `billing.getCachedUser()`.
21
- * `paywall.on` не делает initial replay'я, поэтому useSyncExternalStore сам
22
- * читает старт-snapshot через getSnapshot без лишних cb-вызовов.
23
- *
24
- * Ссылочная стабильность: BillingClient сравнивает user shape перед update'ом
25
- * (`sameUser`), так что между неизменными обновлениями `getCachedUser()`
26
- * возвращает ===-равный объект. Это гарантирует, что useSyncExternalStore
27
- * не дёргает ре-рендер при no-op refresh'ах.
31
+ * Реализация подписана и на `userChange`, и на `authChange` любой источник
32
+ * меняющий status триггерит rerender. Snapshot reference закеширован через
33
+ * useRef, чтобы useSyncExternalStore не словил infinite-loop на новых
34
+ * объектах при каждом getSnapshot.
28
35
  */
29
- export declare function usePaywallUser(): PaywallUser | null;
36
+ export type PaywallUserState = {
37
+ status: 'loading';
38
+ user: null;
39
+ session: null;
40
+ } | {
41
+ status: 'guest';
42
+ user: null;
43
+ session: null;
44
+ } | {
45
+ status: 'signed_in';
46
+ user: PaywallUser | null;
47
+ session: AuthSession | null;
48
+ };
49
+ export declare function usePaywallUser(): PaywallUserState;
30
50
  //# sourceMappingURL=usePaywallUser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usePaywallUser.d.ts","sourceRoot":"","sources":["../../src/hooks/usePaywallUser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,IAAI,WAAW,GAAG,IAAI,CAgBnD"}
1
+ {"version":3,"file":"usePaywallUser.d.ts","sourceRoot":"","sources":["../../src/hooks/usePaywallUser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAGvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAChD;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAC9C;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CAC7B,CAAC;AAKN,wBAAgB,cAAc,IAAI,gBAAgB,CA8EjD"}
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const i=require("react/jsx-runtime"),l=require("react"),m=require("@monetize.software/sdk"),b=l.createContext(null);b.displayName="PaywallContext";const g=l.createContext(!1);g.displayName="PaywallProviderMarker";function T(e){const n="instance"in e?e.instance:void 0,t="options"in e?e.options:void 0,[r,a]=l.useState(n??null);return l.useEffect(()=>{if(n){a(n);return}if(!t)return;const s=new m.PaywallUI(t);return a(s),()=>{s.destroy(),a(null)}},[n]),i.jsx(g.Provider,{value:!0,children:i.jsx(b.Provider,{value:r,children:e.children})})}function o(){const e=l.useContext(g),n=l.useContext(b);if(!e)throw new Error("[sdk-react] usePaywall() called outside <PaywallProvider>. Wrap your tree with <PaywallProvider options={...}> or pass an externally-created instance via <PaywallProvider instance={paywall}>.");return n}const h={open:!1,view:null,error:null};function j(){const e=o(),n=l.useCallback(r=>e?e.onStateChange(r,{immediate:"none"}):()=>{},[e]),t=l.useCallback(()=>e?e.getState():h,[e]);return l.useSyncExternalStore(n,t,()=>h)}function B(){const e=o(),n=l.useCallback(r=>e?e.on("userChange",()=>r()):()=>{},[e]),t=l.useCallback(()=>e?e.billing.getCachedUser():null,[e]);return l.useSyncExternalStore(n,t,A)}function A(){return null}function V(e,n){const t=o(),r=l.useRef(n);r.current=n,l.useEffect(()=>{if(t)return t.on(e,a=>{r.current(a)})},[t,e])}const k={status:"loading",result:null};function C(e={}){const n=o(),[t,r]=l.useState(k),a=e.skipTrial===!0,s=e.skipVisibility===!0;return l.useEffect(()=>{if(!n){r(k);return}const c=new AbortController;let y=!1;const u=()=>{n.getAccess({skipTrial:a,skipVisibility:s,signal:c.signal}).then(p=>{y||c.signal.aborted||r({status:"ready",result:p})}).catch(()=>{})};u();const d=n.on("userChange",u),w=n.on("purchase_completed",u);return()=>{y=!0,c.abort(),d(),w()}},[n,a,s]),t}function R(){const e=o(),[n,t]=l.useState(()=>({prices:e?.getCachedPrices()??null,loading:!0,error:null}));return l.useEffect(()=>{if(!e){t({prices:null,loading:!0,error:null});return}const r=e.getCachedPrices();t({prices:r,loading:r===null,error:null});const a=new AbortController;let s=!1;(()=>{e.getPrices({signal:a.signal}).then(u=>{s||t({prices:u,loading:!1,error:null})}).catch(u=>{s||a.signal.aborted||t(d=>({prices:d.prices,loading:!1,error:u instanceof Error?u:new Error(String(u))}))})})();const y=e.on("ready",()=>{const u=e.getCachedPrices();u&&t({prices:u,loading:!1,error:null})});return()=>{s=!0,a.abort(),y()}},[e]),n}function O(){const e=o(),[n,t]=l.useState(()=>e?.getTrialStatus()??null),r=l.useCallback(()=>{if(!e){t(null);return}t(e.getTrialStatus())},[e]);return l.useEffect(()=>{if(!e){t(null);return}r();const a=e.on("trial_blocked",r),s=e.on("trial_expired",r);return()=>{a(),s()}},[e,r]),n}function _(){const e=o(),[n,t]=l.useState(()=>e?.getVisibility()??null),r=l.useCallback(()=>{if(!e){t(null);return}t(e.getVisibility())},[e]);return l.useEffect(()=>{if(!e){t(null);return}r();const a=e.on("ready",r),s=e.on("visibility_blocked",r);return()=>{a(),s()}},[e,r]),n}function U(e){const n=o(),t=C(),r=t.status==="ready"&&t.result.access==="blocked",a=e.openOnBlocked===!0&&r;if(l.useEffect(()=>{a&&n&&n.open()},[a,n]),t.status==="loading")return i.jsx(i.Fragment,{children:e.loading??null});if(t.result.access==="granted")return i.jsx(i.Fragment,{children:e.children});const s=e.fallback;return typeof s=="function"?i.jsx(i.Fragment,{children:s({result:t.result,open:()=>n?.open()})}):i.jsx(i.Fragment,{children:s??null})}const v=l.forwardRef(function(n,t){const r=o(),{mode:a="paywall",identity:s,renew:c,skipTrial:y,skipVisibility:u,render:d,onClick:w,disabled:p,...x}=n,P=r!==null,f={identity:s,renew:c,skipTrial:y,skipVisibility:u},S=()=>{if(r)switch(a){case"support":r.openSupport(f);return;case"auth":case"signin":r.openSignin(f);return;case"signup":r.openSignup(f);return;default:r.open(f)}};return d?d({open:S,ready:P}):i.jsx("button",{ref:t,type:"button",disabled:p||!P,"aria-busy":P?void 0:!0,onClick:E=>{S(),w?.(E)},...x})}),F=l.forwardRef(function(n,t){return i.jsx(v,{...n,mode:"support",ref:t})});exports.PaywallButton=v;exports.PaywallGate=U;exports.PaywallProvider=T;exports.PaywallSupportButton=F;exports.usePaywall=o;exports.usePaywallAccess=C;exports.usePaywallEvent=V;exports.usePaywallPrices=R;exports.usePaywallState=j;exports.usePaywallTrial=O;exports.usePaywallUser=B;exports.usePaywallVisibility=_;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react/jsx-runtime"),a=require("react"),A=require("@monetize.software/sdk"),h=a.createContext(null);h.displayName="PaywallContext";const S=a.createContext(!1);S.displayName="PaywallProviderMarker";function j(e){const t="instance"in e?e.instance:void 0,n="options"in e?e.options:void 0,[s,r]=a.useState(t??null);return a.useEffect(()=>{if(t){r(t);return}if(!n)return;const l=new A.PaywallUI(n);return r(l),()=>{l.destroy(),r(null)}},[t]),o.jsx(S.Provider,{value:!0,children:o.jsx(h.Provider,{value:s,children:e.children})})}function c(){const e=a.useContext(S),t=a.useContext(h);if(!e)throw new Error("[sdk-react] usePaywall() called outside <PaywallProvider>. Wrap your tree with <PaywallProvider options={...}> or pass an externally-created instance via <PaywallProvider instance={paywall}>.");return t}const C={open:!1,view:null,error:null};function B(){const e=c(),t=a.useCallback(s=>e?e.onStateChange(s,{immediate:"none"}):()=>{},[e]),n=a.useCallback(()=>e?e.getState():C,[e]);return a.useSyncExternalStore(t,n,()=>C)}const p={status:"loading",user:null,session:null},w={status:"guest",user:null,session:null};function _(){const e=c(),t=a.useRef(p),n=a.useCallback(r=>{if(!e)return()=>{};const l=e.on("userChange",()=>r()),u=e.auth?e.on("authChange",()=>r()):null;return()=>{l(),u?.()}},[e]),s=a.useCallback(()=>{if(!e)return t.current=p,p;const r=e.billing.getCachedUser();if(e.auth){const l=e.auth.getCachedSession();if(!l)return t.current=w,w;const u=t.current;if(u.status==="signed_in"&&u.user===r&&u.session===l)return u;const d={status:"signed_in",user:r,session:l};return t.current=d,d}if(r){const l=t.current;if(l.status==="signed_in"&&l.user===r&&l.session===null)return l;const u={status:"signed_in",user:r,session:null};return t.current=u,u}return t.current=w,w},[e]);return a.useSyncExternalStore(n,s,R)}function R(){return p}function V(e,t){const n=c(),s=a.useRef(t);s.current=t,a.useEffect(()=>{if(n)return n.on(e,r=>{s.current(r)})},[n,e])}const v={status:"loading",result:null};function x(e={}){const t=c(),[n,s]=a.useState(v),r=e.skipTrial===!0,l=e.skipVisibility===!0;return a.useEffect(()=>{if(!t){s(v);return}const u=new AbortController;let d=!1;const i=()=>{t.getAccess({skipTrial:r,skipVisibility:l,signal:u.signal}).then(g=>{d||u.signal.aborted||s({status:"ready",result:g})}).catch(()=>{})};i();const f=t.on("userChange",i),P=t.on("purchase_completed",i);return()=>{d=!0,u.abort(),f(),P()}},[t,r,l]),n}function O(){const e=c(),[t,n]=a.useState(()=>({prices:e?.getCachedPrices()??null,loading:!0,error:null}));return a.useEffect(()=>{if(!e){n({prices:null,loading:!0,error:null});return}const s=e.getCachedPrices();n({prices:s,loading:s===null,error:null});const r=new AbortController;let l=!1;(()=>{e.getPrices({signal:r.signal}).then(i=>{l||n({prices:i,loading:!1,error:null})}).catch(i=>{l||r.signal.aborted||n(f=>({prices:f.prices,loading:!1,error:i instanceof Error?i:new Error(String(i))}))})})();const d=e.on("ready",()=>{const i=e.getCachedPrices();i&&n({prices:i,loading:!1,error:null})});return()=>{l=!0,r.abort(),d()}},[e]),t}function U(){const e=c(),[t,n]=a.useState(()=>e?.getTrialStatus()??null),s=a.useCallback(()=>{if(!e){n(null);return}n(e.getTrialStatus())},[e]);return a.useEffect(()=>{if(!e){n(null);return}s();const r=e.on("trial_blocked",s),l=e.on("trial_expired",s);return()=>{r(),l()}},[e,s]),t}function G(){const e=c(),[t,n]=a.useState(()=>e?.getVisibility()??null),s=a.useCallback(()=>{if(!e){n(null);return}n(e.getVisibility())},[e]);return a.useEffect(()=>{if(!e){n(null);return}s();const r=e.on("ready",s),l=e.on("visibility_blocked",s);return()=>{r(),l()}},[e,s]),t}function N(e){const t=c(),n=x(),s=n.status==="ready"&&n.result.access==="blocked",r=e.openOnBlocked===!0&&s;if(a.useEffect(()=>{r&&t&&t.open()},[r,t]),n.status==="loading")return o.jsx(o.Fragment,{children:e.loading??null});if(n.result.access==="granted")return o.jsx(o.Fragment,{children:e.children});const l=e.fallback;return typeof l=="function"?o.jsx(o.Fragment,{children:l({result:n.result,open:()=>t?.open()})}):o.jsx(o.Fragment,{children:l??null})}const E=a.forwardRef(function(t,n){const s=c(),{mode:r="paywall",identity:l,renew:u,skipTrial:d,skipVisibility:i,render:f,onClick:P,disabled:g,...m}=t,b=s!==null,y={identity:l,renew:u,skipTrial:d,skipVisibility:i},k=()=>{if(s)switch(r){case"support":s.openSupport(y);return;case"auth":case"signin":s.openSignin(y);return;case"signup":s.openSignup(y);return;default:s.open(y)}};return f?f({open:k,ready:b}):o.jsx("button",{ref:n,type:"button",disabled:g||!b,"aria-busy":b?void 0:!0,onClick:T=>{k(),P?.(T)},...m})}),F=a.forwardRef(function(t,n){return o.jsx(E,{...t,mode:"support",ref:n})});exports.PaywallButton=E;exports.PaywallGate=N;exports.PaywallProvider=j;exports.PaywallSupportButton=F;exports.usePaywall=c;exports.usePaywallAccess=x;exports.usePaywallEvent=V;exports.usePaywallPrices=O;exports.usePaywallState=B;exports.usePaywallTrial=U;exports.usePaywallUser=_;exports.usePaywallVisibility=G;
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/context.ts","../src/PaywallProvider.tsx","../src/hooks/usePaywall.ts","../src/hooks/usePaywallState.ts","../src/hooks/usePaywallUser.ts","../src/hooks/usePaywallEvent.ts","../src/hooks/usePaywallAccess.ts","../src/hooks/usePaywallPrices.ts","../src/hooks/usePaywallTrial.ts","../src/hooks/usePaywallVisibility.ts","../src/components/PaywallGate.tsx","../src/components/PaywallButton.tsx","../src/components/PaywallSupportButton.tsx"],"sourcesContent":["import { createContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\n\n/**\n * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.\n *\n * value === null до того, как Provider успел смонтировать инстанс (SSR,\n * первый render до useEffect, дев double-mount в StrictMode после cleanup).\n * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,\n * а не падать.\n *\n * defaultValue intentionally `null`, а не `undefined` — это позволяет\n * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция\n * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,\n * но инстанс ещё не создан» (null).\n */\nexport const PaywallContext = createContext<PaywallUI | null>(null);\nPaywallContext.displayName = 'PaywallContext';\n\n/**\n * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».\n *\n * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.\n * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /\n * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда\n * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall\n * проверяет первым.\n */\nexport const PaywallProviderMarker = createContext<boolean>(false);\nPaywallProviderMarker.displayName = 'PaywallProviderMarker';\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { PaywallUI, type PaywallUIOptions } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from './context';\n\n/**\n * Два взаимоисключающих режима использования:\n *\n * - `options` — Provider сам конструирует `PaywallUI` в useEffect и\n * прибирает в cleanup. Самый частый кейс — обычный сайт.\n * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для\n * extension'ов (`@monetize.software/sdk-extension` поставляет structurally\n * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между\n * несколькими React-деревьями и для тестов.\n *\n * Discriminated union на уровне типов — TS не даст передать оба сразу.\n */\nexport type PaywallProviderProps =\n | {\n options: PaywallUIOptions;\n instance?: never;\n children: ReactNode;\n }\n | {\n instance: PaywallUI;\n options?: never;\n children: ReactNode;\n };\n\n/**\n * Корневой Provider для всех React-биндингов SDK.\n *\n * ```tsx\n * // вариант 1: Provider сам создаёт инстанс\n * <PaywallProvider options={{ paywallId: '...', auth: true }}>\n * <App />\n * </PaywallProvider>\n *\n * // вариант 2: готовый инстанс снаружи (extension / shared)\n * const paywall = createPaywallUI({ paywallId: '...' });\n * <PaywallProvider instance={paywall}>\n * <App />\n * </PaywallProvider>\n * ```\n *\n * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все\n * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что\n * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-\n * ограничений на дерево потомков.\n *\n * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не\n * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-\n * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op\n * после destroy.\n *\n * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс\n * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),\n * следует менять `key` у Provider'а — это идиоматичный React-способ форсить\n * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:\n * структурный equality глубоких options всегда ломается на функциях-колбеках\n * и live-обновлениях storage'а.\n */\nexport function PaywallProvider(props: PaywallProviderProps): JSX.Element {\n const externalInstance = 'instance' in props ? props.instance : undefined;\n const options = 'options' in props ? props.options : undefined;\n\n // Внешний инстанс → синхронно кладём его в state, чтобы первый render\n // потомков уже видел реальный PaywallUI (хосту он доступен мгновенно после\n // вызова createPaywallUI). Свой инстанс → null до useEffect, потому что\n // конструктор PaywallUI трогает window/queueMicrotask и не должен крутиться\n // на сервере.\n const [paywall, setPaywall] = useState<PaywallUI | null>(\n externalInstance ?? null\n );\n\n // Сам инстанс создаём в useEffect (только клиент). Если хост даёт готовый —\n // useEffect просто sync'ит state на случай, если ref поменялся между\n // рендерами без unmount'а.\n useEffect(() => {\n if (externalInstance) {\n setPaywall(externalInstance);\n // Externally-owned lifecycle — destroy() не наш.\n return;\n }\n\n if (!options) return;\n\n const created = new PaywallUI(options);\n setPaywall(created);\n return () => {\n created.destroy();\n // null на cleanup — потомки на следующем render'е увидят «инстанс ещё\n // не готов» вместо обращения к destroyed-объекту. В обычной жизни\n // unmount Provider'а сразу размонтирует и потомков, поэтому это\n // подстраховка для редких manual-remount-сценариев и StrictMode'а.\n setPaywall(null);\n };\n // options/instance меняются по reference. Реактивная пересборка инстанса\n // на каждый ре-рендер хост-компонента — не то, что нужно (см. JSDoc выше).\n // Для пересоздания используется React `key`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalInstance]);\n\n return (\n <PaywallProviderMarker.Provider value={true}>\n <PaywallContext.Provider value={paywall}>\n {props.children}\n </PaywallContext.Provider>\n </PaywallProviderMarker.Provider>\n );\n}\n","import { useContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from '../context';\n\n/**\n * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.\n *\n * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,\n * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`\n * (Provider есть, но инстанс ещё не смонтирован).\n *\n * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,\n * `paywall.openSupport()`, подписки на события — для всего этого\n * usePaywall() самый прямой путь:\n *\n * ```tsx\n * const paywall = usePaywall();\n * <button onClick={() => paywall?.open()}>Upgrade</button>\n * ```\n *\n * Для типичных кейсов (gating, state-driven UI) обычно удобнее\n * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},\n * {@link usePaywallUser}.\n */\nexport function usePaywall(): PaywallUI | null {\n const hasProvider = useContext(PaywallProviderMarker);\n const paywall = useContext(PaywallContext);\n\n if (!hasProvider) {\n throw new Error(\n '[sdk-react] usePaywall() called outside <PaywallProvider>. ' +\n 'Wrap your tree with <PaywallProvider options={...}> or pass an ' +\n 'externally-created instance via <PaywallProvider instance={paywall}>.'\n );\n }\n\n return paywall;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallStateSnapshot } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Зеркалит CLOSED_STATE из PaywallUI.ts. Хранится локально, чтобы getSnapshot\n// при paywall=null отдавал стабильную ссылку (та же ссылка между рендерами →\n// useSyncExternalStore не дёргает лишний re-render). Не экспортируется\n// наружу: для public API публичная форма доступна через usePaywallState().\n//\n// Shape проверяется в contract.ts — если PaywallStateSnapshot в SDK обзаведётся\n// новым полем, TS-build sdk-react падает раньше, чем кто-то заметит расхождение.\nconst SSR_SNAPSHOT: PaywallStateSnapshot = { open: false, view: null, error: null };\n\n/**\n * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,\n * последняя ошибка.\n *\n * Реализована поверх `paywall.onStateChange` + `paywall.getState` через\n * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику\n * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и\n * минимум re-render'ов (snapshot равенство по `Object.is`).\n *\n * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,\n * error: null }` — это та же форма, что PaywallUI кладёт во внутренний\n * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».\n *\n * ```tsx\n * const { open, view } = usePaywallState();\n * useEffect(() => {\n * if (open) analytics.track('paywall_seen');\n * }, [open]);\n * ```\n */\nexport function usePaywallState(): PaywallStateSnapshot {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n // immediate: 'none' — useSyncExternalStore сам читает snapshot через\n // getSnapshot. Реплей initial-state'а через subscribe был бы лишним\n // вызовом cb, не приносящим новой информации.\n return paywall.onStateChange(cb, { immediate: 'none' });\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallStateSnapshot => {\n return paywall ? paywall.getState() : SSR_SNAPSHOT;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT);\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallUser } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Подписка на текущего юзера пейвола (sync snapshot + автоматический ре-рендер\n * на любой userChange — bootstrap, /me refresh, после-checkout watcher).\n *\n * Возвращает `null` до первого ответа сети или когда инстанс ещё не готов\n * (SSR / до useEffect Provider'а / Provider не оборачивает дерево с инстансом).\n *\n * Удобно для подсветки текущего плана / e-mail юзера в собственном UI без\n * необходимости держать дублирующий state и руками подписываться на\n * `paywall.on('userChange', ...)`.\n *\n * ```tsx\n * const user = usePaywallUser();\n * if (user?.has_active_subscription) {\n * return <ProBadge plan={user.active_subscription?.plan_name} />;\n * }\n * ```\n *\n * Реализация поверх `paywall.on('userChange', cb)` + `billing.getCachedUser()`.\n * `paywall.on` не делает initial replay'я, поэтому useSyncExternalStore сам\n * читает старт-snapshot через getSnapshot — без лишних cb-вызовов.\n *\n * Ссылочная стабильность: BillingClient сравнивает user shape перед update'ом\n * (`sameUser`), так что между неизменными обновлениями `getCachedUser()`\n * возвращает ===-равный объект. Это гарантирует, что useSyncExternalStore\n * не дёргает ре-рендер при no-op refresh'ах.\n */\nexport function usePaywallUser(): PaywallUser | null {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n return paywall.on('userChange', () => cb());\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallUser | null => {\n return paywall ? paywall.billing.getCachedUser() : null;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallUser | null {\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport type { PaywallEvent, PaywallEventHandler } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Payload-тип конкретного события достаём через `Parameters<PaywallEventHandler<E>>[0]`,\n// потому что сам `PaywallEventPayloads` в SDK объявлен локально и не экспортируется.\n// Подход через `Parameters<>` устойчив к этому: пока `PaywallEventHandler` есть в\n// public surface, payload-тип SDK мы выводим корректно — TS-сборка sdk-react\n// упадёт, если сигнатура `PaywallEventHandler` поедет.\ntype EventPayload<E extends PaywallEvent> = Parameters<PaywallEventHandler<E>>[0];\n\n/**\n * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`\n * с двумя важными отличиями от ручного useEffect:\n *\n * 1. handler не нужно мемоизировать через `useCallback` — внутри храним\n * последнюю версию в `useRef`, само subscription пересоздаётся только\n * при смене `event` или инстанса paywall'а. Это убирает класс багов с\n * «забыл useCallback → подписка отписывается-переподписывается на каждый\n * рендер → события теряются».\n *\n * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):\n * подписка просто не создаётся, ждёт пока инстанс появится.\n *\n * ```tsx\n * usePaywallEvent('purchase_completed', (payload) => {\n * toast.success(`Покупка ${payload.priceId} прошла`);\n * queryClient.invalidateQueries(['user']);\n * });\n * ```\n *\n * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации\n * локального стейта) это самый прямой паттерн — компонент гарантированно\n * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.\n */\nexport function usePaywallEvent<E extends PaywallEvent>(\n event: E,\n handler: PaywallEventHandler<E>\n): void {\n const paywall = usePaywall();\n const handlerRef = useRef(handler);\n\n // Обновляем ref на каждом render'е — следующее срабатывание события\n // подхватит свежий handler. Без отдельного useEffect, потому что синхронный\n // assign в render-фазе для ref'а корректен и не нарушает rules-of-hooks.\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!paywall) return;\n return paywall.on(event, (payload) => {\n // Cast необходим, потому что общий вариант `PaywallEventHandler` теряет\n // narrowing по `E`. handlerRef.current типизирован под конкретный E,\n // но `on()` принимает union — рантайм-shape гарантирован SDK'шным emit'ом.\n (handlerRef.current as (p: EventPayload<E>) => void)(payload);\n });\n }, [paywall, event]);\n}\n","import { useEffect, useState } from 'react';\nimport type {\n GetAccessOptions,\n PaywallAccessResult\n} from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `loading` — первый fetch ещё в полёте (или Provider не готов).\n * `ready` — есть свежий ответ; `result` гарантированно non-null.\n *\n * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:\n *\n * `if (access.status === 'ready') access.result.access === 'granted'`\n */\nexport type PaywallAccessState =\n | { status: 'loading'; result: null }\n | { status: 'ready'; result: PaywallAccessResult };\n\nconst LOADING_STATE: PaywallAccessState = { status: 'loading', result: null };\n\n/**\n * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».\n *\n * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не\n * монтируется, trial-storage не двигается). На каждый `userChange` событие\n * автоматически рефетчится — после успешной покупки `has_subscription`\n * сработает мгновенно, и хост перерендерит UI без feature-lock'а.\n *\n * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать\n * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,\n * если кеш свежий).\n *\n * ```tsx\n * const access = usePaywallAccess();\n * const paywall = usePaywall();\n *\n * if (access.status === 'loading') return <Skeleton />;\n * if (access.result.access === 'blocked') {\n * return <button onClick={() => paywall?.open()}>Upgrade</button>;\n * }\n * return <PremiumFeature />;\n * ```\n *\n * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность\n * ссылки `opts` не требуется, эффект перезапустится только при реальном\n * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него\n * новый ref) — отмена inflight-запроса делается локально через AbortController\n * в cleanup-эффекте.\n */\nexport function usePaywallAccess(opts: GetAccessOptions = {}): PaywallAccessState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallAccessState>(LOADING_STATE);\n\n const skipTrial = opts.skipTrial === true;\n const skipVisibility = opts.skipVisibility === true;\n\n useEffect(() => {\n if (!paywall) {\n // Инстанс ушёл (Provider unmount / StrictMode cleanup) — честно\n // вернуть loading, чтобы хост не показывал устаревший result от\n // прошлого инстанса.\n setState(LOADING_STATE);\n return;\n }\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getAccess({ skipTrial, skipVisibility, signal: ctrl.signal })\n .then((result) => {\n if (cancelled || ctrl.signal.aborted) return;\n // Каждый refresh даёт новый объект — useState увидит !== и\n // ререндерит. Это ок: для гейтинга интерес представляет именно\n // `access` поле, остальное (visibility/trial snapshot'ы) — auxiliary\n // данные, которые не должны бы менять решение хоста на тех же входах.\n setState({ status: 'ready', result });\n })\n .catch(() => {\n // getAccess() имеет собственный offline-fallback и не throw'ит на\n // failed network'е — сюда мы попадаем только при abort'е, который\n // прилетает в cleanup-эффекте. Молча игнорим.\n });\n };\n\n refresh();\n\n // userChange покрывает оба источника обновления decision'а:\n // - после-checkout watcher эмит'ит userChange когда has_subscription=true\n // - manual /me refresh из хоста (paywall.billing.getUser())\n // Дополнительно слушаем purchase_completed для symmetric'ности — на\n // некоторых платежных провайдерах userChange может задержаться, а\n // purchase_completed летит мгновенно по URL-маркеру/postMessage.\n const unsubUser = paywall.on('userChange', refresh);\n const unsubPurchase = paywall.on('purchase_completed', refresh);\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsubUser();\n unsubPurchase();\n };\n }, [paywall, skipTrial, skipVisibility]);\n\n return state;\n}\n","import { useEffect, useState } from 'react';\nimport type { PaywallPrice } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а\n * или когда инстанс ещё не готов).\n * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.\n * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).\n *\n * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`\n * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три\n * независимые величины одновременно (показать предыдущий список + skeleton +\n * сообщение об ошибке поверх) — discriminated union тут только усложняет.\n */\nexport interface PaywallPricesState {\n prices: PaywallPrice[] | null;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Загружает и подписывается на цены пейвола. Подходит для отдельной\n * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,\n * что и в модалке, без открытия paywall'а.\n *\n * Реализация:\n * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше\n * BillingClient'а — например, после `paywall.preload()` или предыдущего\n * open'а — цены доступны мгновенно);\n * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;\n * - subscription на `ready` event — рефетч bootstrap'а на новом open()\n * может принести обновлённые цены, мы обновляем snapshot.\n *\n * ```tsx\n * const { prices, loading } = usePaywallPrices();\n * if (loading && !prices) return <Skeleton />;\n * return prices?.map((p) => <PriceCard key={p.id} price={p} />);\n * ```\n */\nexport function usePaywallPrices(): PaywallPricesState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallPricesState>(() => ({\n prices: paywall?.getCachedPrices() ?? null,\n loading: true,\n error: null\n }));\n\n useEffect(() => {\n if (!paywall) {\n setState({ prices: null, loading: true, error: null });\n return;\n }\n\n // Sync-доступ через cached snapshot — если bootstrap уже загружен,\n // показываем цены немедленно (без флеша «loading → ready»).\n const cached = paywall.getCachedPrices();\n setState({ prices: cached, loading: cached === null, error: null });\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getPrices({ signal: ctrl.signal })\n .then((prices) => {\n if (cancelled) return;\n setState({ prices, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (cancelled || ctrl.signal.aborted) return;\n setState((prev) => ({\n prices: prev.prices,\n loading: false,\n error: error instanceof Error ? error : new Error(String(error))\n }));\n });\n };\n\n refresh();\n\n // `ready` event фаерится из открытого paywall'а с финальным bootstrap'ом —\n // если хост открыл/закрыл модалку, цены могли обновиться через\n // stale-while-revalidate. Слушаем чтобы в pricing-странице цифры не\n // расходились с тем, что юзер увидит в модалке.\n const unsub = paywall.on('ready', () => {\n const fresh = paywall.getCachedPrices();\n if (fresh) setState({ prices: fresh, loading: false, error: null });\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsub();\n };\n }, [paywall]);\n\n return state;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `TrialStatus` локально не экспортируется из SDK, но мы его получаем\n// через ReturnType-инференцию по публичному методу `getTrialStatus()`. Так\n// тип всегда совпадает с тем, что реально возвращает PaywallUI, без зависимости\n// от непубличного namespace'а SDK.\ntype TrialStatus = NonNullable<ReturnType<PaywallUI['getTrialStatus']>>;\n\n/**\n * Текущий статус триала ({@link TrialStatus}) с автоматическим ре-рендером на\n * `trial_blocked` события.\n *\n * Возвращает `null`, пока триал не проверялся (хост не вызывал\n * `paywall.open()` / `paywall.getAccess()`) либо триал отключён в конфиге\n * пейвола. Сам триал-стейт живёт в storage (localStorage / chrome.storage),\n * проверяется в `paywall.open()` и в `paywall.getAccess()` — оба пути обновляют\n * in-memory snapshot, который мы здесь и читаем.\n *\n * Использовать чтобы рисовать собственный UI:\n * - «У тебя осталось 3 показа» (mode `opens`) — `status.remainingActions`;\n * - «Триал истечёт через 2 часа» (mode `time`) — `status.remainingMs`;\n * - «Триал заблокирован, оплати чтобы продолжить» — `status.blocked === true`.\n *\n * ```tsx\n * const trial = usePaywallTrial();\n * if (trial?.mode === 'opens') {\n * return <Banner>Showings left: {trial.remainingActions}</Banner>;\n * }\n * ```\n */\nexport function usePaywallTrial(): TrialStatus | null {\n const paywall = usePaywall();\n const [status, setStatus] = useState<TrialStatus | null>(() =>\n paywall?.getTrialStatus() ?? null\n );\n\n // Стабильный refresh для эффекта — отдельная функция, чтобы deps массив\n // эффекта был чистым (`[paywall]`), без useCallback-цепочек.\n const sync = useCallback(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n setStatus(paywall.getTrialStatus());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n // Sync read на mount-е — getTrialStatus() мог обновиться между прошлым\n // рендером и effect'ом (например, hook вызван после первого open()-а).\n sync();\n\n // `trial_blocked` — единственный event, после которого snapshot реально\n // меняется. `trial_expired` фаерится один раз за жизнь инстанса и не\n // меняет shape статуса (статус становится `mode: 'none'` ИЛИ переходит\n // в un-blocked-режим, что и так читается через sync()).\n const unsubBlock = paywall.on('trial_blocked', sync);\n const unsubExpired = paywall.on('trial_expired', sync);\n\n return () => {\n unsubBlock();\n unsubExpired();\n };\n }, [paywall, sync]);\n\n return status;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `VisibilityStatus` локально не экспортируется из SDK — получаем через\n// ReturnType от публичного `getVisibility()`. См. usePaywallTrial для тех же\n// соображений.\ntype VisibilityStatus = NonNullable<ReturnType<PaywallUI['getVisibility']>>;\n\n/**\n * Server-computed visibility-снимок ({@link VisibilityStatus}): попадает ли\n * юзер в monetization-scope пейвола (страна, девайс, ручной visibility-флаг).\n *\n * Возвращает `null`, пока bootstrap не загружен или сервер не отдал\n * `settings.visibility` (старый online без targeting-патча).\n *\n * Использовать чтобы:\n * - показать собственный fallback («сервис недоступен в вашей стране») вместо\n * модалки, когда `visible === false`;\n * - залогировать impression для аналитики страны/tier'а юзера;\n * - принять решение какой CTA рисовать, не дёргая open() и не дожидаясь\n * visibility_blocked event.\n *\n * ```tsx\n * const visibility = usePaywallVisibility();\n * if (visibility && !visibility.visible) {\n * return <SoftBlock reason={visibility.reason} />;\n * }\n * ```\n */\nexport function usePaywallVisibility(): VisibilityStatus | null {\n const paywall = usePaywall();\n const [visibility, setVisibility] = useState<VisibilityStatus | null>(() =>\n paywall?.getVisibility() ?? null\n );\n\n const sync = useCallback(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n setVisibility(paywall.getVisibility());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n sync();\n\n // `ready` event летит после успешного bootstrap'а — там обновляется\n // `lastVisibility` в PaywallUI. `visibility_blocked` — когда блокировка\n // реально срабатывает на gate'е. Оба меняют snapshot.\n const unsubReady = paywall.on('ready', sync);\n const unsubBlocked = paywall.on('visibility_blocked', sync);\n\n return () => {\n unsubReady();\n unsubBlocked();\n };\n }, [paywall, sync]);\n\n return visibility;\n}\n","import { useEffect, type ReactNode } from 'react';\nimport type { PaywallAccessResult } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\nimport { usePaywallAccess } from '../hooks/usePaywallAccess';\n\nexport interface PaywallGateProps {\n /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */\n loading?: ReactNode;\n /**\n * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо\n * статичный ReactNode, либо render-функцию, получающую callback\n * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:\n *\n * ```tsx\n * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}\n * ```\n *\n * Если не передан — компонент рендерит `null` для blocked (host\n * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).\n */\n fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);\n /**\n * Автоматически дёргать `paywall.open()` сразу как только access перешёл в\n * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на\n * paywall»: компонент сам открывает модалку, не нужно писать onClick.\n *\n * По умолчанию `false` — большинство хостов хотят сначала показать\n * объясняющий CTA, а модалку открывать по клику. Включать осознанно.\n */\n openOnBlocked?: boolean;\n /** Премиум-контент. Рендерится только когда access=granted. */\n children: ReactNode;\n}\n\nexport interface BlockedRenderArgs {\n result: Extract<PaywallAccessResult, { access: 'blocked' }>;\n open: () => void;\n}\n\n/**\n * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().\n *\n * Три состояния:\n * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;\n * - `granted` (есть подписка / visibility / trial) — рендерим `children`;\n * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем\n * `paywall.open()` при `openOnBlocked={true}`.\n *\n * ```tsx\n * <PaywallGate\n * loading={<Skeleton />}\n * fallback={({ open }) => <button onClick={open}>Upgrade</button>}\n * >\n * <PremiumFeature />\n * </PaywallGate>\n * ```\n *\n * Для нестандартных сценариев (показать \"Try free trial\" вместо upgrade,\n * комбинировать с собственным auth-flow'ом) использовать\n * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь\n * стать конфигурируемым на каждый чих.\n */\nexport function PaywallGate(props: PaywallGateProps): JSX.Element | null {\n const paywall = usePaywall();\n const access = usePaywallAccess();\n\n // `openOnBlocked` — side-effect, поэтому в useEffect. Зависим от access\n // через идентификатор `result.access`, а не от объекта целиком, чтобы\n // не дёргать open() на каждом refresh-е getAccess'а с тем же blocked-итогом.\n const isBlocked =\n access.status === 'ready' && access.result.access === 'blocked';\n const shouldAutoOpen = props.openOnBlocked === true && isBlocked;\n\n useEffect(() => {\n if (shouldAutoOpen && paywall) paywall.open();\n }, [shouldAutoOpen, paywall]);\n\n if (access.status === 'loading') {\n return <>{props.loading ?? null}</>;\n }\n\n if (access.result.access === 'granted') {\n return <>{props.children}</>;\n }\n\n // blocked\n const fallback = props.fallback;\n if (typeof fallback === 'function') {\n return (\n <>\n {fallback({\n result: access.result,\n open: () => paywall?.open()\n })}\n </>\n );\n }\n return <>{fallback ?? null}</>;\n}\n","import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactElement,\n type ReactNode\n} from 'react';\nimport type { OpenOptions } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\n\n/**\n * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.\n * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,\n * `skipVisibility`.\n */\ntype OpenProps = OpenOptions;\n\ninterface CommonProps extends OpenProps {\n /** Что открывать: layout (default), support, auth-gate (signin),\n * signup-форма. 'auth' эквивалентен 'signin' (исторически — openAuth\n * дефолтит в signin-mode). Для анонимного signin используй\n * `usePaywall().signInAnonymously()` напрямую — headless без модалки. */\n mode?: 'paywall' | 'support' | 'auth' | 'signin' | 'signup';\n /** Render-prop для полного контроля над элементом-триггером. Когда задан,\n * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */\n render?: (args: PaywallButtonRenderArgs) => ReactElement;\n}\n\nexport interface PaywallButtonRenderArgs {\n /** Открыть пейвол согласно `mode` + переданным opts. */\n open: () => void;\n /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */\n ready: boolean;\n}\n\n/**\n * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,\n * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный\n * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем\n * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).\n */\ntype ButtonRenderProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof OpenProps | 'children'\n> & {\n children?: ReactNode;\n};\n\nexport type PaywallButtonProps = CommonProps & ButtonRenderProps;\n\n/**\n * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как\n * нативный `<button>` со всеми твоими className/style/disabled, но при нужде\n * можно передать `render` для произвольного элемента (Radix-style asChild\n * паттерн через render-prop).\n *\n * ```tsx\n * // обычный кейс\n * <PaywallButton className=\"btn-primary\" renew>\n * Renew subscription\n * </PaywallButton>\n *\n * // custom-элемент\n * <PaywallButton render={({ open, ready }) => (\n * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>\n * )} />\n *\n * // саппорт-форма вместо тарифов\n * <PaywallButton mode=\"support\">Need help?</PaywallButton>\n * ```\n *\n * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`\n * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —\n * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.\n */\nexport const PaywallButton = forwardRef<HTMLButtonElement, PaywallButtonProps>(\n function PaywallButton(props, ref) {\n const paywall = usePaywall();\n const {\n mode = 'paywall',\n identity,\n renew,\n skipTrial,\n skipVisibility,\n render,\n onClick,\n disabled,\n ...buttonProps\n } = props;\n\n const ready = paywall !== null;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n switch (mode) {\n case 'support':\n paywall.openSupport(openOpts);\n return;\n case 'auth':\n case 'signin':\n paywall.openSignin(openOpts);\n return;\n case 'signup':\n paywall.openSignup(openOpts);\n return;\n default:\n paywall.open(openOpts);\n }\n };\n\n if (render) {\n return render({ open, ready });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready}\n aria-busy={!ready ? true : undefined}\n onClick={(event) => {\n // Наш handler первым — host через event.preventDefault() ничего\n // не остановит, потому что open() уже стрельнул. Это намеренно:\n // открытие пейвола не должно зависеть от того, забыл ли хост\n // вернуть `false` из своего analytics-handler'а. Если нужен\n // префлайт-чек — паттерн через `render`-prop, там полный контроль.\n open();\n onClick?.(event);\n }}\n {...buttonProps}\n />\n );\n }\n);\n","import { forwardRef } from 'react';\nimport { PaywallButton, type PaywallButtonProps } from './PaywallButton';\n\nexport type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;\n\n/**\n * Сахар над `<PaywallButton mode=\"support\">`. Самостоятельная компонента, а\n * не пресет prop'а, для discoverability — название говорит за себя, и в\n * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.\n *\n * ```tsx\n * <PaywallSupportButton className=\"link\">Help</PaywallSupportButton>\n * ```\n */\nexport const PaywallSupportButton = forwardRef<\n HTMLButtonElement,\n PaywallSupportButtonProps\n>(function PaywallSupportButton(props, ref) {\n return <PaywallButton {...props} mode=\"support\" ref={ref} />;\n});\n"],"names":["PaywallContext","createContext","PaywallProviderMarker","PaywallProvider","props","externalInstance","options","paywall","setPaywall","useState","useEffect","created","PaywallUI","jsx","usePaywall","hasProvider","useContext","SSR_SNAPSHOT","usePaywallState","subscribe","useCallback","cb","getSnapshot","useSyncExternalStore","usePaywallUser","getServerSnapshot","usePaywallEvent","event","handler","handlerRef","useRef","payload","LOADING_STATE","usePaywallAccess","opts","state","setState","skipTrial","skipVisibility","ctrl","cancelled","refresh","result","unsubUser","unsubPurchase","usePaywallPrices","cached","prices","error","prev","unsub","fresh","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubReady","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","openOpts","open","PaywallSupportButton"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/context.ts","../src/PaywallProvider.tsx","../src/hooks/usePaywall.ts","../src/hooks/usePaywallState.ts","../src/hooks/usePaywallUser.ts","../src/hooks/usePaywallEvent.ts","../src/hooks/usePaywallAccess.ts","../src/hooks/usePaywallPrices.ts","../src/hooks/usePaywallTrial.ts","../src/hooks/usePaywallVisibility.ts","../src/components/PaywallGate.tsx","../src/components/PaywallButton.tsx","../src/components/PaywallSupportButton.tsx"],"sourcesContent":["import { createContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\n\n/**\n * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.\n *\n * value === null до того, как Provider успел смонтировать инстанс (SSR,\n * первый render до useEffect, дев double-mount в StrictMode после cleanup).\n * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,\n * а не падать.\n *\n * defaultValue intentionally `null`, а не `undefined` — это позволяет\n * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция\n * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,\n * но инстанс ещё не создан» (null).\n */\nexport const PaywallContext = createContext<PaywallUI | null>(null);\nPaywallContext.displayName = 'PaywallContext';\n\n/**\n * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».\n *\n * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.\n * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /\n * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда\n * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall\n * проверяет первым.\n */\nexport const PaywallProviderMarker = createContext<boolean>(false);\nPaywallProviderMarker.displayName = 'PaywallProviderMarker';\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { PaywallUI, type PaywallUIOptions } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from './context';\n\n/**\n * Два взаимоисключающих режима использования:\n *\n * - `options` — Provider сам конструирует `PaywallUI` в useEffect и\n * прибирает в cleanup. Самый частый кейс — обычный сайт.\n * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для\n * extension'ов (`@monetize.software/sdk-extension` поставляет structurally\n * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между\n * несколькими React-деревьями и для тестов.\n *\n * Discriminated union на уровне типов — TS не даст передать оба сразу.\n */\nexport type PaywallProviderProps =\n | {\n options: PaywallUIOptions;\n instance?: never;\n children: ReactNode;\n }\n | {\n instance: PaywallUI;\n options?: never;\n children: ReactNode;\n };\n\n/**\n * Корневой Provider для всех React-биндингов SDK.\n *\n * ```tsx\n * // вариант 1: Provider сам создаёт инстанс\n * <PaywallProvider options={{ paywallId: '...', auth: true }}>\n * <App />\n * </PaywallProvider>\n *\n * // вариант 2: готовый инстанс снаружи (extension / shared)\n * const paywall = createPaywallUI({ paywallId: '...' });\n * <PaywallProvider instance={paywall}>\n * <App />\n * </PaywallProvider>\n * ```\n *\n * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все\n * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что\n * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-\n * ограничений на дерево потомков.\n *\n * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не\n * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-\n * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op\n * после destroy.\n *\n * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс\n * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),\n * следует менять `key` у Provider'а — это идиоматичный React-способ форсить\n * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:\n * структурный equality глубоких options всегда ломается на функциях-колбеках\n * и live-обновлениях storage'а.\n */\nexport function PaywallProvider(props: PaywallProviderProps): JSX.Element {\n const externalInstance = 'instance' in props ? props.instance : undefined;\n const options = 'options' in props ? props.options : undefined;\n\n // Внешний инстанс → синхронно кладём его в state, чтобы первый render\n // потомков уже видел реальный PaywallUI (хосту он доступен мгновенно после\n // вызова createPaywallUI). Свой инстанс → null до useEffect, потому что\n // конструктор PaywallUI трогает window/queueMicrotask и не должен крутиться\n // на сервере.\n const [paywall, setPaywall] = useState<PaywallUI | null>(\n externalInstance ?? null\n );\n\n // Сам инстанс создаём в useEffect (только клиент). Если хост даёт готовый —\n // useEffect просто sync'ит state на случай, если ref поменялся между\n // рендерами без unmount'а.\n useEffect(() => {\n if (externalInstance) {\n setPaywall(externalInstance);\n // Externally-owned lifecycle — destroy() не наш.\n return;\n }\n\n if (!options) return;\n\n const created = new PaywallUI(options);\n setPaywall(created);\n return () => {\n created.destroy();\n // null на cleanup — потомки на следующем render'е увидят «инстанс ещё\n // не готов» вместо обращения к destroyed-объекту. В обычной жизни\n // unmount Provider'а сразу размонтирует и потомков, поэтому это\n // подстраховка для редких manual-remount-сценариев и StrictMode'а.\n setPaywall(null);\n };\n // options/instance меняются по reference. Реактивная пересборка инстанса\n // на каждый ре-рендер хост-компонента — не то, что нужно (см. JSDoc выше).\n // Для пересоздания используется React `key`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalInstance]);\n\n return (\n <PaywallProviderMarker.Provider value={true}>\n <PaywallContext.Provider value={paywall}>\n {props.children}\n </PaywallContext.Provider>\n </PaywallProviderMarker.Provider>\n );\n}\n","import { useContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from '../context';\n\n/**\n * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.\n *\n * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,\n * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`\n * (Provider есть, но инстанс ещё не смонтирован).\n *\n * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,\n * `paywall.openSupport()`, подписки на события — для всего этого\n * usePaywall() самый прямой путь:\n *\n * ```tsx\n * const paywall = usePaywall();\n * <button onClick={() => paywall?.open()}>Upgrade</button>\n * ```\n *\n * Для типичных кейсов (gating, state-driven UI) обычно удобнее\n * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},\n * {@link usePaywallUser}.\n */\nexport function usePaywall(): PaywallUI | null {\n const hasProvider = useContext(PaywallProviderMarker);\n const paywall = useContext(PaywallContext);\n\n if (!hasProvider) {\n throw new Error(\n '[sdk-react] usePaywall() called outside <PaywallProvider>. ' +\n 'Wrap your tree with <PaywallProvider options={...}> or pass an ' +\n 'externally-created instance via <PaywallProvider instance={paywall}>.'\n );\n }\n\n return paywall;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallStateSnapshot } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Зеркалит CLOSED_STATE из PaywallUI.ts. Хранится локально, чтобы getSnapshot\n// при paywall=null отдавал стабильную ссылку (та же ссылка между рендерами →\n// useSyncExternalStore не дёргает лишний re-render). Не экспортируется\n// наружу: для public API публичная форма доступна через usePaywallState().\n//\n// Shape проверяется в contract.ts — если PaywallStateSnapshot в SDK обзаведётся\n// новым полем, TS-build sdk-react падает раньше, чем кто-то заметит расхождение.\nconst SSR_SNAPSHOT: PaywallStateSnapshot = { open: false, view: null, error: null };\n\n/**\n * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,\n * последняя ошибка.\n *\n * Реализована поверх `paywall.onStateChange` + `paywall.getState` через\n * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику\n * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и\n * минимум re-render'ов (snapshot равенство по `Object.is`).\n *\n * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,\n * error: null }` — это та же форма, что PaywallUI кладёт во внутренний\n * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».\n *\n * ```tsx\n * const { open, view } = usePaywallState();\n * useEffect(() => {\n * if (open) analytics.track('paywall_seen');\n * }, [open]);\n * ```\n */\nexport function usePaywallState(): PaywallStateSnapshot {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n // immediate: 'none' — useSyncExternalStore сам читает snapshot через\n // getSnapshot. Реплей initial-state'а через subscribe был бы лишним\n // вызовом cb, не приносящим новой информации.\n return paywall.onStateChange(cb, { immediate: 'none' });\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallStateSnapshot => {\n return paywall ? paywall.getState() : SSR_SNAPSHOT;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT);\n}\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\nimport type { AuthSession, PaywallUser } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Состояние «кто такой текущий пользователь» с точки зрения хоста.\n *\n * Discriminated union намеренно совмещает три источника: готовность инстанса\n * PaywallUI (Provider mount), наличие session у managed-auth и `getCachedUser()`\n * от bootstrap'а. Это убирает у хоста нужду различать «пейвол ещё грузится»\n * vs «никого нет» вручную — типы сужают каждый случай.\n *\n * - `loading` — Provider ещё не смонтировал PaywallUI (SSR / pre-mount /\n * dev-double-mount cleanup). На этом этапе показывать skeleton.\n * - `guest` — у пейвола нет identity:\n * • managed-auth: `auth.getCachedSession()` вернул null;\n * • hybrid (без managed-auth): bootstrap прошёл, но user-snapshot пуст.\n * В этом состоянии валидно показать CTA «Sign in» / `<PaywallButton mode=\"signin\">`.\n * - `signed_in` — есть identity. `user` — последний снимок из BillingClient\n * (может быть `null`, пока /me-refresh после signIn в полёте — UI должен\n * показать skeleton, не «sign-in» CTA). `session` — managed-auth session\n * или `null` для hybrid-режима.\n *\n * Хост обычно делает три проверки подряд:\n * ```tsx\n * const account = usePaywallUser();\n * if (account.status === 'loading') return <Skeleton />;\n * if (account.status === 'guest') return <SignInCTA />;\n * // account.user может быть null, пока /me грузится — показать skeleton тут же.\n * if (!account.user) return <Skeleton />;\n * return <Profile user={account.user} />;\n * ```\n *\n * Реализация подписана и на `userChange`, и на `authChange` — любой источник\n * меняющий status триггерит rerender. Snapshot reference закеширован через\n * useRef, чтобы useSyncExternalStore не словил infinite-loop на новых\n * объектах при каждом getSnapshot.\n */\nexport type PaywallUserState =\n | { status: 'loading'; user: null; session: null }\n | { status: 'guest'; user: null; session: null }\n | {\n status: 'signed_in';\n user: PaywallUser | null;\n session: AuthSession | null;\n };\n\nconst LOADING: PaywallUserState = { status: 'loading', user: null, session: null };\nconst GUEST: PaywallUserState = { status: 'guest', user: null, session: null };\n\nexport function usePaywallUser(): PaywallUserState {\n const paywall = usePaywall();\n // useRef-кэш предыдущего snapshot'а — обязателен для useSyncExternalStore.\n // Если каждый getSnapshot возвращает новый объект с теми же components,\n // React воспринимает это как изменение состояния и ловит infinite-loop\n // (или, в строгом режиме, валит warning'ом «getSnapshot should be cached»).\n const cacheRef = useRef<PaywallUserState>(LOADING);\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n const unsubUser = paywall.on('userChange', () => cb());\n // authChange слушаем только в managed-auth режиме. В hybrid-режиме\n // authChange всё равно не эмитится — but defensive: paywall.auth\n // отсутствует, так что подписка просто пропускается.\n const unsubAuth = paywall.auth ? paywall.on('authChange', () => cb()) : null;\n return () => {\n unsubUser();\n unsubAuth?.();\n };\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallUserState => {\n if (!paywall) {\n cacheRef.current = LOADING;\n return LOADING;\n }\n\n const user = paywall.billing.getCachedUser();\n\n if (paywall.auth) {\n const session = paywall.auth.getCachedSession();\n if (!session) {\n cacheRef.current = GUEST;\n return GUEST;\n }\n const prev = cacheRef.current;\n if (\n prev.status === 'signed_in' &&\n prev.user === user &&\n prev.session === session\n ) {\n return prev;\n }\n const next: PaywallUserState = { status: 'signed_in', user, session };\n cacheRef.current = next;\n return next;\n }\n\n // hybrid (no managed-auth). identity приходит через open({identity}); до\n // этого момента billing.getCachedUser() вернёт null. Без session отличать\n // «host передал identity, user ещё грузится» от «гость» невозможно — так\n // что наличие user используем как сигнал signed-in.\n if (user) {\n const prev = cacheRef.current;\n if (\n prev.status === 'signed_in' &&\n prev.user === user &&\n prev.session === null\n ) {\n return prev;\n }\n const next: PaywallUserState = {\n status: 'signed_in',\n user,\n session: null\n };\n cacheRef.current = next;\n return next;\n }\n\n cacheRef.current = GUEST;\n return GUEST;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallUserState {\n return LOADING;\n}\n","import { useEffect, useRef } from 'react';\nimport type { PaywallEvent, PaywallEventHandler } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Payload-тип конкретного события достаём через `Parameters<PaywallEventHandler<E>>[0]`,\n// потому что сам `PaywallEventPayloads` в SDK объявлен локально и не экспортируется.\n// Подход через `Parameters<>` устойчив к этому: пока `PaywallEventHandler` есть в\n// public surface, payload-тип SDK мы выводим корректно — TS-сборка sdk-react\n// упадёт, если сигнатура `PaywallEventHandler` поедет.\ntype EventPayload<E extends PaywallEvent> = Parameters<PaywallEventHandler<E>>[0];\n\n/**\n * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`\n * с двумя важными отличиями от ручного useEffect:\n *\n * 1. handler не нужно мемоизировать через `useCallback` — внутри храним\n * последнюю версию в `useRef`, само subscription пересоздаётся только\n * при смене `event` или инстанса paywall'а. Это убирает класс багов с\n * «забыл useCallback → подписка отписывается-переподписывается на каждый\n * рендер → события теряются».\n *\n * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):\n * подписка просто не создаётся, ждёт пока инстанс появится.\n *\n * ```tsx\n * usePaywallEvent('purchase_completed', (payload) => {\n * toast.success(`Покупка ${payload.priceId} прошла`);\n * queryClient.invalidateQueries(['user']);\n * });\n * ```\n *\n * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации\n * локального стейта) это самый прямой паттерн — компонент гарантированно\n * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.\n */\nexport function usePaywallEvent<E extends PaywallEvent>(\n event: E,\n handler: PaywallEventHandler<E>\n): void {\n const paywall = usePaywall();\n const handlerRef = useRef(handler);\n\n // Обновляем ref на каждом render'е — следующее срабатывание события\n // подхватит свежий handler. Без отдельного useEffect, потому что синхронный\n // assign в render-фазе для ref'а корректен и не нарушает rules-of-hooks.\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!paywall) return;\n return paywall.on(event, (payload) => {\n // Cast необходим, потому что общий вариант `PaywallEventHandler` теряет\n // narrowing по `E`. handlerRef.current типизирован под конкретный E,\n // но `on()` принимает union — рантайм-shape гарантирован SDK'шным emit'ом.\n (handlerRef.current as (p: EventPayload<E>) => void)(payload);\n });\n }, [paywall, event]);\n}\n","import { useEffect, useState } from 'react';\nimport type {\n GetAccessOptions,\n PaywallAccessResult\n} from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `loading` — первый fetch ещё в полёте (или Provider не готов).\n * `ready` — есть свежий ответ; `result` гарантированно non-null.\n *\n * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:\n *\n * `if (access.status === 'ready') access.result.access === 'granted'`\n */\nexport type PaywallAccessState =\n | { status: 'loading'; result: null }\n | { status: 'ready'; result: PaywallAccessResult };\n\nconst LOADING_STATE: PaywallAccessState = { status: 'loading', result: null };\n\n/**\n * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».\n *\n * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не\n * монтируется, trial-storage не двигается). На каждый `userChange` событие\n * автоматически рефетчится — после успешной покупки `has_subscription`\n * сработает мгновенно, и хост перерендерит UI без feature-lock'а.\n *\n * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать\n * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,\n * если кеш свежий).\n *\n * ```tsx\n * const access = usePaywallAccess();\n * const paywall = usePaywall();\n *\n * if (access.status === 'loading') return <Skeleton />;\n * if (access.result.access === 'blocked') {\n * return <button onClick={() => paywall?.open()}>Upgrade</button>;\n * }\n * return <PremiumFeature />;\n * ```\n *\n * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность\n * ссылки `opts` не требуется, эффект перезапустится только при реальном\n * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него\n * новый ref) — отмена inflight-запроса делается локально через AbortController\n * в cleanup-эффекте.\n */\nexport function usePaywallAccess(opts: GetAccessOptions = {}): PaywallAccessState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallAccessState>(LOADING_STATE);\n\n const skipTrial = opts.skipTrial === true;\n const skipVisibility = opts.skipVisibility === true;\n\n useEffect(() => {\n if (!paywall) {\n // Инстанс ушёл (Provider unmount / StrictMode cleanup) — честно\n // вернуть loading, чтобы хост не показывал устаревший result от\n // прошлого инстанса.\n setState(LOADING_STATE);\n return;\n }\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getAccess({ skipTrial, skipVisibility, signal: ctrl.signal })\n .then((result) => {\n if (cancelled || ctrl.signal.aborted) return;\n // Каждый refresh даёт новый объект — useState увидит !== и\n // ререндерит. Это ок: для гейтинга интерес представляет именно\n // `access` поле, остальное (visibility/trial snapshot'ы) — auxiliary\n // данные, которые не должны бы менять решение хоста на тех же входах.\n setState({ status: 'ready', result });\n })\n .catch(() => {\n // getAccess() имеет собственный offline-fallback и не throw'ит на\n // failed network'е — сюда мы попадаем только при abort'е, который\n // прилетает в cleanup-эффекте. Молча игнорим.\n });\n };\n\n refresh();\n\n // userChange покрывает оба источника обновления decision'а:\n // - после-checkout watcher эмит'ит userChange когда has_subscription=true\n // - manual /me refresh из хоста (paywall.billing.getUser())\n // Дополнительно слушаем purchase_completed для symmetric'ности — на\n // некоторых платежных провайдерах userChange может задержаться, а\n // purchase_completed летит мгновенно по URL-маркеру/postMessage.\n const unsubUser = paywall.on('userChange', refresh);\n const unsubPurchase = paywall.on('purchase_completed', refresh);\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsubUser();\n unsubPurchase();\n };\n }, [paywall, skipTrial, skipVisibility]);\n\n return state;\n}\n","import { useEffect, useState } from 'react';\nimport type { PaywallPrice } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а\n * или когда инстанс ещё не готов).\n * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.\n * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).\n *\n * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`\n * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три\n * независимые величины одновременно (показать предыдущий список + skeleton +\n * сообщение об ошибке поверх) — discriminated union тут только усложняет.\n */\nexport interface PaywallPricesState {\n prices: PaywallPrice[] | null;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Загружает и подписывается на цены пейвола. Подходит для отдельной\n * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,\n * что и в модалке, без открытия paywall'а.\n *\n * Реализация:\n * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше\n * BillingClient'а — например, после `paywall.preload()` или предыдущего\n * open'а — цены доступны мгновенно);\n * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;\n * - subscription на `ready` event — рефетч bootstrap'а на новом open()\n * может принести обновлённые цены, мы обновляем snapshot.\n *\n * ```tsx\n * const { prices, loading } = usePaywallPrices();\n * if (loading && !prices) return <Skeleton />;\n * return prices?.map((p) => <PriceCard key={p.id} price={p} />);\n * ```\n */\nexport function usePaywallPrices(): PaywallPricesState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallPricesState>(() => ({\n prices: paywall?.getCachedPrices() ?? null,\n loading: true,\n error: null\n }));\n\n useEffect(() => {\n if (!paywall) {\n setState({ prices: null, loading: true, error: null });\n return;\n }\n\n // Sync-доступ через cached snapshot — если bootstrap уже загружен,\n // показываем цены немедленно (без флеша «loading → ready»).\n const cached = paywall.getCachedPrices();\n setState({ prices: cached, loading: cached === null, error: null });\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getPrices({ signal: ctrl.signal })\n .then((prices) => {\n if (cancelled) return;\n setState({ prices, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (cancelled || ctrl.signal.aborted) return;\n setState((prev) => ({\n prices: prev.prices,\n loading: false,\n error: error instanceof Error ? error : new Error(String(error))\n }));\n });\n };\n\n refresh();\n\n // `ready` event фаерится из открытого paywall'а с финальным bootstrap'ом —\n // если хост открыл/закрыл модалку, цены могли обновиться через\n // stale-while-revalidate. Слушаем чтобы в pricing-странице цифры не\n // расходились с тем, что юзер увидит в модалке.\n const unsub = paywall.on('ready', () => {\n const fresh = paywall.getCachedPrices();\n if (fresh) setState({ prices: fresh, loading: false, error: null });\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsub();\n };\n }, [paywall]);\n\n return state;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `TrialStatus` локально не экспортируется из SDK, но мы его получаем\n// через ReturnType-инференцию по публичному методу `getTrialStatus()`. Так\n// тип всегда совпадает с тем, что реально возвращает PaywallUI, без зависимости\n// от непубличного namespace'а SDK.\ntype TrialStatus = NonNullable<ReturnType<PaywallUI['getTrialStatus']>>;\n\n/**\n * Текущий статус триала ({@link TrialStatus}) с автоматическим ре-рендером на\n * `trial_blocked` события.\n *\n * Возвращает `null`, пока триал не проверялся (хост не вызывал\n * `paywall.open()` / `paywall.getAccess()`) либо триал отключён в конфиге\n * пейвола. Сам триал-стейт живёт в storage (localStorage / chrome.storage),\n * проверяется в `paywall.open()` и в `paywall.getAccess()` — оба пути обновляют\n * in-memory snapshot, который мы здесь и читаем.\n *\n * Использовать чтобы рисовать собственный UI:\n * - «У тебя осталось 3 показа» (mode `opens`) — `status.remainingActions`;\n * - «Триал истечёт через 2 часа» (mode `time`) — `status.remainingMs`;\n * - «Триал заблокирован, оплати чтобы продолжить» — `status.blocked === true`.\n *\n * ```tsx\n * const trial = usePaywallTrial();\n * if (trial?.mode === 'opens') {\n * return <Banner>Showings left: {trial.remainingActions}</Banner>;\n * }\n * ```\n */\nexport function usePaywallTrial(): TrialStatus | null {\n const paywall = usePaywall();\n const [status, setStatus] = useState<TrialStatus | null>(() =>\n paywall?.getTrialStatus() ?? null\n );\n\n // Стабильный refresh для эффекта — отдельная функция, чтобы deps массив\n // эффекта был чистым (`[paywall]`), без useCallback-цепочек.\n const sync = useCallback(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n setStatus(paywall.getTrialStatus());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n // Sync read на mount-е — getTrialStatus() мог обновиться между прошлым\n // рендером и effect'ом (например, hook вызван после первого open()-а).\n sync();\n\n // `trial_blocked` — единственный event, после которого snapshot реально\n // меняется. `trial_expired` фаерится один раз за жизнь инстанса и не\n // меняет shape статуса (статус становится `mode: 'none'` ИЛИ переходит\n // в un-blocked-режим, что и так читается через sync()).\n const unsubBlock = paywall.on('trial_blocked', sync);\n const unsubExpired = paywall.on('trial_expired', sync);\n\n return () => {\n unsubBlock();\n unsubExpired();\n };\n }, [paywall, sync]);\n\n return status;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `VisibilityStatus` локально не экспортируется из SDK — получаем через\n// ReturnType от публичного `getVisibility()`. См. usePaywallTrial для тех же\n// соображений.\ntype VisibilityStatus = NonNullable<ReturnType<PaywallUI['getVisibility']>>;\n\n/**\n * Server-computed visibility-снимок ({@link VisibilityStatus}): попадает ли\n * юзер в monetization-scope пейвола (страна, девайс, ручной visibility-флаг).\n *\n * Возвращает `null`, пока bootstrap не загружен или сервер не отдал\n * `settings.visibility` (старый online без targeting-патча).\n *\n * Использовать чтобы:\n * - показать собственный fallback («сервис недоступен в вашей стране») вместо\n * модалки, когда `visible === false`;\n * - залогировать impression для аналитики страны/tier'а юзера;\n * - принять решение какой CTA рисовать, не дёргая open() и не дожидаясь\n * visibility_blocked event.\n *\n * ```tsx\n * const visibility = usePaywallVisibility();\n * if (visibility && !visibility.visible) {\n * return <SoftBlock reason={visibility.reason} />;\n * }\n * ```\n */\nexport function usePaywallVisibility(): VisibilityStatus | null {\n const paywall = usePaywall();\n const [visibility, setVisibility] = useState<VisibilityStatus | null>(() =>\n paywall?.getVisibility() ?? null\n );\n\n const sync = useCallback(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n setVisibility(paywall.getVisibility());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n sync();\n\n // `ready` event летит после успешного bootstrap'а — там обновляется\n // `lastVisibility` в PaywallUI. `visibility_blocked` — когда блокировка\n // реально срабатывает на gate'е. Оба меняют snapshot.\n const unsubReady = paywall.on('ready', sync);\n const unsubBlocked = paywall.on('visibility_blocked', sync);\n\n return () => {\n unsubReady();\n unsubBlocked();\n };\n }, [paywall, sync]);\n\n return visibility;\n}\n","import { useEffect, type ReactNode } from 'react';\nimport type { PaywallAccessResult } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\nimport { usePaywallAccess } from '../hooks/usePaywallAccess';\n\nexport interface PaywallGateProps {\n /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */\n loading?: ReactNode;\n /**\n * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо\n * статичный ReactNode, либо render-функцию, получающую callback\n * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:\n *\n * ```tsx\n * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}\n * ```\n *\n * Если не передан — компонент рендерит `null` для blocked (host\n * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).\n */\n fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);\n /**\n * Автоматически дёргать `paywall.open()` сразу как только access перешёл в\n * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на\n * paywall»: компонент сам открывает модалку, не нужно писать onClick.\n *\n * По умолчанию `false` — большинство хостов хотят сначала показать\n * объясняющий CTA, а модалку открывать по клику. Включать осознанно.\n */\n openOnBlocked?: boolean;\n /** Премиум-контент. Рендерится только когда access=granted. */\n children: ReactNode;\n}\n\nexport interface BlockedRenderArgs {\n result: Extract<PaywallAccessResult, { access: 'blocked' }>;\n open: () => void;\n}\n\n/**\n * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().\n *\n * Три состояния:\n * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;\n * - `granted` (есть подписка / visibility / trial) — рендерим `children`;\n * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем\n * `paywall.open()` при `openOnBlocked={true}`.\n *\n * ```tsx\n * <PaywallGate\n * loading={<Skeleton />}\n * fallback={({ open }) => <button onClick={open}>Upgrade</button>}\n * >\n * <PremiumFeature />\n * </PaywallGate>\n * ```\n *\n * Для нестандартных сценариев (показать \"Try free trial\" вместо upgrade,\n * комбинировать с собственным auth-flow'ом) использовать\n * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь\n * стать конфигурируемым на каждый чих.\n */\nexport function PaywallGate(props: PaywallGateProps): JSX.Element | null {\n const paywall = usePaywall();\n const access = usePaywallAccess();\n\n // `openOnBlocked` — side-effect, поэтому в useEffect. Зависим от access\n // через идентификатор `result.access`, а не от объекта целиком, чтобы\n // не дёргать open() на каждом refresh-е getAccess'а с тем же blocked-итогом.\n const isBlocked =\n access.status === 'ready' && access.result.access === 'blocked';\n const shouldAutoOpen = props.openOnBlocked === true && isBlocked;\n\n useEffect(() => {\n if (shouldAutoOpen && paywall) paywall.open();\n }, [shouldAutoOpen, paywall]);\n\n if (access.status === 'loading') {\n return <>{props.loading ?? null}</>;\n }\n\n if (access.result.access === 'granted') {\n return <>{props.children}</>;\n }\n\n // blocked\n const fallback = props.fallback;\n if (typeof fallback === 'function') {\n return (\n <>\n {fallback({\n result: access.result,\n open: () => paywall?.open()\n })}\n </>\n );\n }\n return <>{fallback ?? null}</>;\n}\n","import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactElement,\n type ReactNode\n} from 'react';\nimport type { OpenOptions } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\n\n/**\n * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.\n * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,\n * `skipVisibility`.\n */\ntype OpenProps = OpenOptions;\n\ninterface CommonProps extends OpenProps {\n /** Что открывать: layout (default), support, auth-gate (signin),\n * signup-форма. 'auth' эквивалентен 'signin' (исторически — openAuth\n * дефолтит в signin-mode). Для анонимного signin используй\n * `usePaywall().signInAnonymously()` напрямую — headless без модалки. */\n mode?: 'paywall' | 'support' | 'auth' | 'signin' | 'signup';\n /** Render-prop для полного контроля над элементом-триггером. Когда задан,\n * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */\n render?: (args: PaywallButtonRenderArgs) => ReactElement;\n}\n\nexport interface PaywallButtonRenderArgs {\n /** Открыть пейвол согласно `mode` + переданным opts. */\n open: () => void;\n /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */\n ready: boolean;\n}\n\n/**\n * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,\n * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный\n * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем\n * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).\n */\ntype ButtonRenderProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof OpenProps | 'children'\n> & {\n children?: ReactNode;\n};\n\nexport type PaywallButtonProps = CommonProps & ButtonRenderProps;\n\n/**\n * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как\n * нативный `<button>` со всеми твоими className/style/disabled, но при нужде\n * можно передать `render` для произвольного элемента (Radix-style asChild\n * паттерн через render-prop).\n *\n * ```tsx\n * // обычный кейс\n * <PaywallButton className=\"btn-primary\" renew>\n * Renew subscription\n * </PaywallButton>\n *\n * // custom-элемент\n * <PaywallButton render={({ open, ready }) => (\n * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>\n * )} />\n *\n * // саппорт-форма вместо тарифов\n * <PaywallButton mode=\"support\">Need help?</PaywallButton>\n * ```\n *\n * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`\n * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —\n * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.\n */\nexport const PaywallButton = forwardRef<HTMLButtonElement, PaywallButtonProps>(\n function PaywallButton(props, ref) {\n const paywall = usePaywall();\n const {\n mode = 'paywall',\n identity,\n renew,\n skipTrial,\n skipVisibility,\n render,\n onClick,\n disabled,\n ...buttonProps\n } = props;\n\n const ready = paywall !== null;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n switch (mode) {\n case 'support':\n paywall.openSupport(openOpts);\n return;\n case 'auth':\n case 'signin':\n paywall.openSignin(openOpts);\n return;\n case 'signup':\n paywall.openSignup(openOpts);\n return;\n default:\n paywall.open(openOpts);\n }\n };\n\n if (render) {\n return render({ open, ready });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready}\n aria-busy={!ready ? true : undefined}\n onClick={(event) => {\n // Наш handler первым — host через event.preventDefault() ничего\n // не остановит, потому что open() уже стрельнул. Это намеренно:\n // открытие пейвола не должно зависеть от того, забыл ли хост\n // вернуть `false` из своего analytics-handler'а. Если нужен\n // префлайт-чек — паттерн через `render`-prop, там полный контроль.\n open();\n onClick?.(event);\n }}\n {...buttonProps}\n />\n );\n }\n);\n","import { forwardRef } from 'react';\nimport { PaywallButton, type PaywallButtonProps } from './PaywallButton';\n\nexport type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;\n\n/**\n * Сахар над `<PaywallButton mode=\"support\">`. Самостоятельная компонента, а\n * не пресет prop'а, для discoverability — название говорит за себя, и в\n * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.\n *\n * ```tsx\n * <PaywallSupportButton className=\"link\">Help</PaywallSupportButton>\n * ```\n */\nexport const PaywallSupportButton = forwardRef<\n HTMLButtonElement,\n PaywallSupportButtonProps\n>(function PaywallSupportButton(props, ref) {\n return <PaywallButton {...props} mode=\"support\" ref={ref} />;\n});\n"],"names":["PaywallContext","createContext","PaywallProviderMarker","PaywallProvider","props","externalInstance","options","paywall","setPaywall","useState","useEffect","created","PaywallUI","jsx","usePaywall","hasProvider","useContext","SSR_SNAPSHOT","usePaywallState","subscribe","useCallback","cb","getSnapshot","useSyncExternalStore","LOADING","GUEST","usePaywallUser","cacheRef","useRef","unsubUser","unsubAuth","user","session","prev","next","getServerSnapshot","usePaywallEvent","event","handler","handlerRef","payload","LOADING_STATE","usePaywallAccess","opts","state","setState","skipTrial","skipVisibility","ctrl","cancelled","refresh","result","unsubPurchase","usePaywallPrices","cached","prices","error","unsub","fresh","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubReady","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","openOpts","open","PaywallSupportButton"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { PaywallProvider, type PaywallProviderProps } from './PaywallProvider';
2
2
  export { usePaywall } from './hooks/usePaywall';
3
3
  export { usePaywallState } from './hooks/usePaywallState';
4
- export { usePaywallUser } from './hooks/usePaywallUser';
4
+ export { usePaywallUser, type PaywallUserState } from './hooks/usePaywallUser';
5
5
  export { usePaywallEvent } from './hooks/usePaywallEvent';
6
6
  export { usePaywallAccess, type PaywallAccessState } from './hooks/usePaywallAccess';
7
7
  export { usePaywallPrices, type PaywallPricesState } from './hooks/usePaywallPrices';
@@ -10,5 +10,5 @@ export { usePaywallVisibility } from './hooks/usePaywallVisibility';
10
10
  export { PaywallGate, type PaywallGateProps, type BlockedRenderArgs } from './components/PaywallGate';
11
11
  export { PaywallButton, type PaywallButtonProps, type PaywallButtonRenderArgs } from './components/PaywallButton';
12
12
  export { PaywallSupportButton, type PaywallSupportButtonProps } from './components/PaywallSupportButton';
13
- export type { PaywallUI, PaywallUIOptions, PaywallEvent, PaywallEventHandler, PaywallStateSnapshot, PaywallAccessResult, GetAccessOptions, OpenOptions, AnalyticsOptions, PaywallUser, PaywallPrice, PaywallBootstrap, PaywallSettings, PaywallOffer, Identity } from '@monetize.software/sdk';
13
+ export type { PaywallUI, PaywallUIOptions, PaywallEvent, PaywallEventHandler, PaywallStateSnapshot, PaywallAccessResult, GetAccessOptions, OpenOptions, AnalyticsOptions, PaywallUser, PaywallPrice, PaywallBootstrap, PaywallSettings, PaywallOffer, Identity, AuthSession } from '@monetize.software/sdk';
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EACL,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,oBAAoB,EACpB,KAAK,yBAAyB,EAC/B,MAAM,mCAAmC,CAAC;AAM3C,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,QAAQ,EACT,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EACL,cAAc,EACd,KAAK,gBAAgB,EACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EACL,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,oBAAoB,EACpB,KAAK,yBAAyB,EAC/B,MAAM,mCAAmC,CAAC;AAM3C,YAAY,EACV,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,WAAW,EACZ,MAAM,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -1,238 +1,271 @@
1
1
  "use client";
2
- import { jsx as i, Fragment as P } from "react/jsx-runtime";
3
- import { createContext as T, useState as p, useEffect as u, useContext as C, useCallback as f, useSyncExternalStore as A, useRef as _, forwardRef as B } from "react";
4
- import { PaywallUI as O } from "@monetize.software/sdk";
5
- const S = T(null);
6
- S.displayName = "PaywallContext";
7
- const k = T(!1);
8
- k.displayName = "PaywallProviderMarker";
9
- function j(e) {
10
- const n = "instance" in e ? e.instance : void 0, t = "options" in e ? e.options : void 0, [r, l] = p(
11
- n ?? null
2
+ import { jsx as c, Fragment as w } from "react/jsx-runtime";
3
+ import { createContext as _, useState as p, useEffect as d, useContext as x, useCallback as y, useSyncExternalStore as E, useRef as B, forwardRef as V } from "react";
4
+ import { PaywallUI as U } from "@monetize.software/sdk";
5
+ const v = _(null);
6
+ v.displayName = "PaywallContext";
7
+ const C = _(!1);
8
+ C.displayName = "PaywallProviderMarker";
9
+ function j(n) {
10
+ const t = "instance" in n ? n.instance : void 0, e = "options" in n ? n.options : void 0, [r, s] = p(
11
+ t ?? null
12
12
  );
13
- return u(() => {
14
- if (n) {
15
- l(n);
13
+ return d(() => {
14
+ if (t) {
15
+ s(t);
16
16
  return;
17
17
  }
18
- if (!t) return;
19
- const s = new O(t);
20
- return l(s), () => {
21
- s.destroy(), l(null);
18
+ if (!e) return;
19
+ const l = new U(e);
20
+ return s(l), () => {
21
+ l.destroy(), s(null);
22
22
  };
23
- }, [n]), /* @__PURE__ */ i(k.Provider, { value: !0, children: /* @__PURE__ */ i(S.Provider, { value: r, children: e.children }) });
23
+ }, [t]), /* @__PURE__ */ c(C.Provider, { value: !0, children: /* @__PURE__ */ c(v.Provider, { value: r, children: n.children }) });
24
24
  }
25
- function o() {
26
- const e = C(k), n = C(S);
27
- if (!e)
25
+ function i() {
26
+ const n = x(C), t = x(v);
27
+ if (!n)
28
28
  throw new Error(
29
29
  "[sdk-react] usePaywall() called outside <PaywallProvider>. Wrap your tree with <PaywallProvider options={...}> or pass an externally-created instance via <PaywallProvider instance={paywall}>."
30
30
  );
31
- return n;
31
+ return t;
32
32
  }
33
- const m = { open: !1, view: null, error: null };
34
- function D() {
35
- const e = o(), n = f(
36
- (r) => e ? e.onStateChange(r, { immediate: "none" }) : () => {
33
+ const A = { open: !1, view: null, error: null };
34
+ function F() {
35
+ const n = i(), t = y(
36
+ (r) => n ? n.onStateChange(r, { immediate: "none" }) : () => {
37
37
  },
38
- [e]
39
- ), t = f(() => e ? e.getState() : m, [e]);
40
- return A(n, t, () => m);
38
+ [n]
39
+ ), e = y(() => n ? n.getState() : A, [n]);
40
+ return E(t, e, () => A);
41
41
  }
42
- function F() {
43
- const e = o(), n = f(
44
- (r) => e ? e.on("userChange", () => r()) : () => {
42
+ const P = { status: "loading", user: null, session: null }, h = { status: "guest", user: null, session: null };
43
+ function H() {
44
+ const n = i(), t = B(P), e = y(
45
+ (s) => {
46
+ if (!n) return () => {
47
+ };
48
+ const l = n.on("userChange", () => s()), a = n.auth ? n.on("authChange", () => s()) : null;
49
+ return () => {
50
+ l(), a?.();
51
+ };
45
52
  },
46
- [e]
47
- ), t = f(() => e ? e.billing.getCachedUser() : null, [e]);
48
- return A(n, t, R);
53
+ [n]
54
+ ), r = y(() => {
55
+ if (!n)
56
+ return t.current = P, P;
57
+ const s = n.billing.getCachedUser();
58
+ if (n.auth) {
59
+ const l = n.auth.getCachedSession();
60
+ if (!l)
61
+ return t.current = h, h;
62
+ const a = t.current;
63
+ if (a.status === "signed_in" && a.user === s && a.session === l)
64
+ return a;
65
+ const o = { status: "signed_in", user: s, session: l };
66
+ return t.current = o, o;
67
+ }
68
+ if (s) {
69
+ const l = t.current;
70
+ if (l.status === "signed_in" && l.user === s && l.session === null)
71
+ return l;
72
+ const a = {
73
+ status: "signed_in",
74
+ user: s,
75
+ session: null
76
+ };
77
+ return t.current = a, a;
78
+ }
79
+ return t.current = h, h;
80
+ }, [n]);
81
+ return E(e, r, N);
49
82
  }
50
- function R() {
51
- return null;
83
+ function N() {
84
+ return P;
52
85
  }
53
- function H(e, n) {
54
- const t = o(), r = _(n);
55
- r.current = n, u(() => {
56
- if (t)
57
- return t.on(e, (l) => {
58
- r.current(l);
86
+ function W(n, t) {
87
+ const e = i(), r = B(t);
88
+ r.current = t, d(() => {
89
+ if (e)
90
+ return e.on(n, (s) => {
91
+ r.current(s);
59
92
  });
60
- }, [t, e]);
93
+ }, [e, n]);
61
94
  }
62
- const x = { status: "loading", result: null };
63
- function N(e = {}) {
64
- const n = o(), [t, r] = p(x), l = e.skipTrial === !0, s = e.skipVisibility === !0;
65
- return u(() => {
66
- if (!n) {
67
- r(x);
95
+ const T = { status: "loading", result: null };
96
+ function G(n = {}) {
97
+ const t = i(), [e, r] = p(T), s = n.skipTrial === !0, l = n.skipVisibility === !0;
98
+ return d(() => {
99
+ if (!t) {
100
+ r(T);
68
101
  return;
69
102
  }
70
- const c = new AbortController();
71
- let d = !1;
72
- const a = () => {
73
- n.getAccess({ skipTrial: l, skipVisibility: s, signal: c.signal }).then((b) => {
74
- d || c.signal.aborted || r({ status: "ready", result: b });
103
+ const a = new AbortController();
104
+ let o = !1;
105
+ const u = () => {
106
+ t.getAccess({ skipTrial: s, skipVisibility: l, signal: a.signal }).then((S) => {
107
+ o || a.signal.aborted || r({ status: "ready", result: S });
75
108
  }).catch(() => {
76
109
  });
77
110
  };
78
- a();
79
- const y = n.on("userChange", a), g = n.on("purchase_completed", a);
111
+ u();
112
+ const f = t.on("userChange", u), b = t.on("purchase_completed", u);
80
113
  return () => {
81
- d = !0, c.abort(), y(), g();
114
+ o = !0, a.abort(), f(), b();
82
115
  };
83
- }, [n, l, s]), t;
116
+ }, [t, s, l]), e;
84
117
  }
85
- function L() {
86
- const e = o(), [n, t] = p(() => ({
87
- prices: e?.getCachedPrices() ?? null,
118
+ function q() {
119
+ const n = i(), [t, e] = p(() => ({
120
+ prices: n?.getCachedPrices() ?? null,
88
121
  loading: !0,
89
122
  error: null
90
123
  }));
91
- return u(() => {
92
- if (!e) {
93
- t({ prices: null, loading: !0, error: null });
124
+ return d(() => {
125
+ if (!n) {
126
+ e({ prices: null, loading: !0, error: null });
94
127
  return;
95
128
  }
96
- const r = e.getCachedPrices();
97
- t({ prices: r, loading: r === null, error: null });
98
- const l = new AbortController();
99
- let s = !1;
129
+ const r = n.getCachedPrices();
130
+ e({ prices: r, loading: r === null, error: null });
131
+ const s = new AbortController();
132
+ let l = !1;
100
133
  (() => {
101
- e.getPrices({ signal: l.signal }).then((a) => {
102
- s || t({ prices: a, loading: !1, error: null });
103
- }).catch((a) => {
104
- s || l.signal.aborted || t((y) => ({
105
- prices: y.prices,
134
+ n.getPrices({ signal: s.signal }).then((u) => {
135
+ l || e({ prices: u, loading: !1, error: null });
136
+ }).catch((u) => {
137
+ l || s.signal.aborted || e((f) => ({
138
+ prices: f.prices,
106
139
  loading: !1,
107
- error: a instanceof Error ? a : new Error(String(a))
140
+ error: u instanceof Error ? u : new Error(String(u))
108
141
  }));
109
142
  });
110
143
  })();
111
- const d = e.on("ready", () => {
112
- const a = e.getCachedPrices();
113
- a && t({ prices: a, loading: !1, error: null });
144
+ const o = n.on("ready", () => {
145
+ const u = n.getCachedPrices();
146
+ u && e({ prices: u, loading: !1, error: null });
114
147
  });
115
148
  return () => {
116
- s = !0, l.abort(), d();
149
+ l = !0, s.abort(), o();
117
150
  };
118
- }, [e]), n;
151
+ }, [n]), t;
119
152
  }
120
- function W() {
121
- const e = o(), [n, t] = p(
122
- () => e?.getTrialStatus() ?? null
123
- ), r = f(() => {
124
- if (!e) {
125
- t(null);
153
+ function z() {
154
+ const n = i(), [t, e] = p(
155
+ () => n?.getTrialStatus() ?? null
156
+ ), r = y(() => {
157
+ if (!n) {
158
+ e(null);
126
159
  return;
127
160
  }
128
- t(e.getTrialStatus());
129
- }, [e]);
130
- return u(() => {
131
- if (!e) {
132
- t(null);
161
+ e(n.getTrialStatus());
162
+ }, [n]);
163
+ return d(() => {
164
+ if (!n) {
165
+ e(null);
133
166
  return;
134
167
  }
135
168
  r();
136
- const l = e.on("trial_blocked", r), s = e.on("trial_expired", r);
169
+ const s = n.on("trial_blocked", r), l = n.on("trial_expired", r);
137
170
  return () => {
138
- l(), s();
171
+ s(), l();
139
172
  };
140
- }, [e, r]), n;
173
+ }, [n, r]), t;
141
174
  }
142
- function q() {
143
- const e = o(), [n, t] = p(
144
- () => e?.getVisibility() ?? null
145
- ), r = f(() => {
146
- if (!e) {
147
- t(null);
175
+ function J() {
176
+ const n = i(), [t, e] = p(
177
+ () => n?.getVisibility() ?? null
178
+ ), r = y(() => {
179
+ if (!n) {
180
+ e(null);
148
181
  return;
149
182
  }
150
- t(e.getVisibility());
151
- }, [e]);
152
- return u(() => {
153
- if (!e) {
154
- t(null);
183
+ e(n.getVisibility());
184
+ }, [n]);
185
+ return d(() => {
186
+ if (!n) {
187
+ e(null);
155
188
  return;
156
189
  }
157
190
  r();
158
- const l = e.on("ready", r), s = e.on("visibility_blocked", r);
191
+ const s = n.on("ready", r), l = n.on("visibility_blocked", r);
159
192
  return () => {
160
- l(), s();
193
+ s(), l();
161
194
  };
162
- }, [e, r]), n;
195
+ }, [n, r]), t;
163
196
  }
164
- function z(e) {
165
- const n = o(), t = N(), r = t.status === "ready" && t.result.access === "blocked", l = e.openOnBlocked === !0 && r;
166
- if (u(() => {
167
- l && n && n.open();
168
- }, [l, n]), t.status === "loading")
169
- return /* @__PURE__ */ i(P, { children: e.loading ?? null });
170
- if (t.result.access === "granted")
171
- return /* @__PURE__ */ i(P, { children: e.children });
172
- const s = e.fallback;
173
- return typeof s == "function" ? /* @__PURE__ */ i(P, { children: s({
174
- result: t.result,
175
- open: () => n?.open()
176
- }) }) : /* @__PURE__ */ i(P, { children: s ?? null });
197
+ function K(n) {
198
+ const t = i(), e = G(), r = e.status === "ready" && e.result.access === "blocked", s = n.openOnBlocked === !0 && r;
199
+ if (d(() => {
200
+ s && t && t.open();
201
+ }, [s, t]), e.status === "loading")
202
+ return /* @__PURE__ */ c(w, { children: n.loading ?? null });
203
+ if (e.result.access === "granted")
204
+ return /* @__PURE__ */ c(w, { children: n.children });
205
+ const l = n.fallback;
206
+ return typeof l == "function" ? /* @__PURE__ */ c(w, { children: l({
207
+ result: e.result,
208
+ open: () => t?.open()
209
+ }) }) : /* @__PURE__ */ c(w, { children: l ?? null });
177
210
  }
178
- const U = B(
179
- function(n, t) {
180
- const r = o(), {
181
- mode: l = "paywall",
182
- identity: s,
183
- renew: c,
184
- skipTrial: d,
185
- skipVisibility: a,
186
- render: y,
187
- onClick: g,
188
- disabled: b,
189
- ...E
190
- } = n, h = r !== null, w = { identity: s, renew: c, skipTrial: d, skipVisibility: a }, v = () => {
211
+ const I = V(
212
+ function(t, e) {
213
+ const r = i(), {
214
+ mode: s = "paywall",
215
+ identity: l,
216
+ renew: a,
217
+ skipTrial: o,
218
+ skipVisibility: u,
219
+ render: f,
220
+ onClick: b,
221
+ disabled: S,
222
+ ...O
223
+ } = t, k = r !== null, g = { identity: l, renew: a, skipTrial: o, skipVisibility: u }, m = () => {
191
224
  if (r)
192
- switch (l) {
225
+ switch (s) {
193
226
  case "support":
194
- r.openSupport(w);
227
+ r.openSupport(g);
195
228
  return;
196
229
  case "auth":
197
230
  case "signin":
198
- r.openSignin(w);
231
+ r.openSignin(g);
199
232
  return;
200
233
  case "signup":
201
- r.openSignup(w);
234
+ r.openSignup(g);
202
235
  return;
203
236
  default:
204
- r.open(w);
237
+ r.open(g);
205
238
  }
206
239
  };
207
- return y ? y({ open: v, ready: h }) : /* @__PURE__ */ i(
240
+ return f ? f({ open: m, ready: k }) : /* @__PURE__ */ c(
208
241
  "button",
209
242
  {
210
- ref: t,
243
+ ref: e,
211
244
  type: "button",
212
- disabled: b || !h,
213
- "aria-busy": h ? void 0 : !0,
214
- onClick: (V) => {
215
- v(), g?.(V);
245
+ disabled: S || !k,
246
+ "aria-busy": k ? void 0 : !0,
247
+ onClick: (R) => {
248
+ m(), b?.(R);
216
249
  },
217
- ...E
250
+ ...O
218
251
  }
219
252
  );
220
253
  }
221
- ), J = B(function(n, t) {
222
- return /* @__PURE__ */ i(U, { ...n, mode: "support", ref: t });
254
+ ), Q = V(function(t, e) {
255
+ return /* @__PURE__ */ c(I, { ...t, mode: "support", ref: e });
223
256
  });
224
257
  export {
225
- U as PaywallButton,
226
- z as PaywallGate,
258
+ I as PaywallButton,
259
+ K as PaywallGate,
227
260
  j as PaywallProvider,
228
- J as PaywallSupportButton,
229
- o as usePaywall,
230
- N as usePaywallAccess,
231
- H as usePaywallEvent,
232
- L as usePaywallPrices,
233
- D as usePaywallState,
234
- W as usePaywallTrial,
235
- F as usePaywallUser,
236
- q as usePaywallVisibility
261
+ Q as PaywallSupportButton,
262
+ i as usePaywall,
263
+ G as usePaywallAccess,
264
+ W as usePaywallEvent,
265
+ q as usePaywallPrices,
266
+ F as usePaywallState,
267
+ z as usePaywallTrial,
268
+ H as usePaywallUser,
269
+ J as usePaywallVisibility
237
270
  };
238
271
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/context.ts","../src/PaywallProvider.tsx","../src/hooks/usePaywall.ts","../src/hooks/usePaywallState.ts","../src/hooks/usePaywallUser.ts","../src/hooks/usePaywallEvent.ts","../src/hooks/usePaywallAccess.ts","../src/hooks/usePaywallPrices.ts","../src/hooks/usePaywallTrial.ts","../src/hooks/usePaywallVisibility.ts","../src/components/PaywallGate.tsx","../src/components/PaywallButton.tsx","../src/components/PaywallSupportButton.tsx"],"sourcesContent":["import { createContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\n\n/**\n * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.\n *\n * value === null до того, как Provider успел смонтировать инстанс (SSR,\n * первый render до useEffect, дев double-mount в StrictMode после cleanup).\n * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,\n * а не падать.\n *\n * defaultValue intentionally `null`, а не `undefined` — это позволяет\n * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция\n * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,\n * но инстанс ещё не создан» (null).\n */\nexport const PaywallContext = createContext<PaywallUI | null>(null);\nPaywallContext.displayName = 'PaywallContext';\n\n/**\n * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».\n *\n * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.\n * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /\n * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда\n * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall\n * проверяет первым.\n */\nexport const PaywallProviderMarker = createContext<boolean>(false);\nPaywallProviderMarker.displayName = 'PaywallProviderMarker';\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { PaywallUI, type PaywallUIOptions } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from './context';\n\n/**\n * Два взаимоисключающих режима использования:\n *\n * - `options` — Provider сам конструирует `PaywallUI` в useEffect и\n * прибирает в cleanup. Самый частый кейс — обычный сайт.\n * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для\n * extension'ов (`@monetize.software/sdk-extension` поставляет structurally\n * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между\n * несколькими React-деревьями и для тестов.\n *\n * Discriminated union на уровне типов — TS не даст передать оба сразу.\n */\nexport type PaywallProviderProps =\n | {\n options: PaywallUIOptions;\n instance?: never;\n children: ReactNode;\n }\n | {\n instance: PaywallUI;\n options?: never;\n children: ReactNode;\n };\n\n/**\n * Корневой Provider для всех React-биндингов SDK.\n *\n * ```tsx\n * // вариант 1: Provider сам создаёт инстанс\n * <PaywallProvider options={{ paywallId: '...', auth: true }}>\n * <App />\n * </PaywallProvider>\n *\n * // вариант 2: готовый инстанс снаружи (extension / shared)\n * const paywall = createPaywallUI({ paywallId: '...' });\n * <PaywallProvider instance={paywall}>\n * <App />\n * </PaywallProvider>\n * ```\n *\n * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все\n * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что\n * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-\n * ограничений на дерево потомков.\n *\n * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не\n * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-\n * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op\n * после destroy.\n *\n * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс\n * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),\n * следует менять `key` у Provider'а — это идиоматичный React-способ форсить\n * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:\n * структурный equality глубоких options всегда ломается на функциях-колбеках\n * и live-обновлениях storage'а.\n */\nexport function PaywallProvider(props: PaywallProviderProps): JSX.Element {\n const externalInstance = 'instance' in props ? props.instance : undefined;\n const options = 'options' in props ? props.options : undefined;\n\n // Внешний инстанс → синхронно кладём его в state, чтобы первый render\n // потомков уже видел реальный PaywallUI (хосту он доступен мгновенно после\n // вызова createPaywallUI). Свой инстанс → null до useEffect, потому что\n // конструктор PaywallUI трогает window/queueMicrotask и не должен крутиться\n // на сервере.\n const [paywall, setPaywall] = useState<PaywallUI | null>(\n externalInstance ?? null\n );\n\n // Сам инстанс создаём в useEffect (только клиент). Если хост даёт готовый —\n // useEffect просто sync'ит state на случай, если ref поменялся между\n // рендерами без unmount'а.\n useEffect(() => {\n if (externalInstance) {\n setPaywall(externalInstance);\n // Externally-owned lifecycle — destroy() не наш.\n return;\n }\n\n if (!options) return;\n\n const created = new PaywallUI(options);\n setPaywall(created);\n return () => {\n created.destroy();\n // null на cleanup — потомки на следующем render'е увидят «инстанс ещё\n // не готов» вместо обращения к destroyed-объекту. В обычной жизни\n // unmount Provider'а сразу размонтирует и потомков, поэтому это\n // подстраховка для редких manual-remount-сценариев и StrictMode'а.\n setPaywall(null);\n };\n // options/instance меняются по reference. Реактивная пересборка инстанса\n // на каждый ре-рендер хост-компонента — не то, что нужно (см. JSDoc выше).\n // Для пересоздания используется React `key`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalInstance]);\n\n return (\n <PaywallProviderMarker.Provider value={true}>\n <PaywallContext.Provider value={paywall}>\n {props.children}\n </PaywallContext.Provider>\n </PaywallProviderMarker.Provider>\n );\n}\n","import { useContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from '../context';\n\n/**\n * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.\n *\n * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,\n * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`\n * (Provider есть, но инстанс ещё не смонтирован).\n *\n * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,\n * `paywall.openSupport()`, подписки на события — для всего этого\n * usePaywall() самый прямой путь:\n *\n * ```tsx\n * const paywall = usePaywall();\n * <button onClick={() => paywall?.open()}>Upgrade</button>\n * ```\n *\n * Для типичных кейсов (gating, state-driven UI) обычно удобнее\n * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},\n * {@link usePaywallUser}.\n */\nexport function usePaywall(): PaywallUI | null {\n const hasProvider = useContext(PaywallProviderMarker);\n const paywall = useContext(PaywallContext);\n\n if (!hasProvider) {\n throw new Error(\n '[sdk-react] usePaywall() called outside <PaywallProvider>. ' +\n 'Wrap your tree with <PaywallProvider options={...}> or pass an ' +\n 'externally-created instance via <PaywallProvider instance={paywall}>.'\n );\n }\n\n return paywall;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallStateSnapshot } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Зеркалит CLOSED_STATE из PaywallUI.ts. Хранится локально, чтобы getSnapshot\n// при paywall=null отдавал стабильную ссылку (та же ссылка между рендерами →\n// useSyncExternalStore не дёргает лишний re-render). Не экспортируется\n// наружу: для public API публичная форма доступна через usePaywallState().\n//\n// Shape проверяется в contract.ts — если PaywallStateSnapshot в SDK обзаведётся\n// новым полем, TS-build sdk-react падает раньше, чем кто-то заметит расхождение.\nconst SSR_SNAPSHOT: PaywallStateSnapshot = { open: false, view: null, error: null };\n\n/**\n * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,\n * последняя ошибка.\n *\n * Реализована поверх `paywall.onStateChange` + `paywall.getState` через\n * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику\n * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и\n * минимум re-render'ов (snapshot равенство по `Object.is`).\n *\n * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,\n * error: null }` — это та же форма, что PaywallUI кладёт во внутренний\n * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».\n *\n * ```tsx\n * const { open, view } = usePaywallState();\n * useEffect(() => {\n * if (open) analytics.track('paywall_seen');\n * }, [open]);\n * ```\n */\nexport function usePaywallState(): PaywallStateSnapshot {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n // immediate: 'none' — useSyncExternalStore сам читает snapshot через\n // getSnapshot. Реплей initial-state'а через subscribe был бы лишним\n // вызовом cb, не приносящим новой информации.\n return paywall.onStateChange(cb, { immediate: 'none' });\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallStateSnapshot => {\n return paywall ? paywall.getState() : SSR_SNAPSHOT;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT);\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallUser } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Подписка на текущего юзера пейвола (sync snapshot + автоматический ре-рендер\n * на любой userChange — bootstrap, /me refresh, после-checkout watcher).\n *\n * Возвращает `null` до первого ответа сети или когда инстанс ещё не готов\n * (SSR / до useEffect Provider'а / Provider не оборачивает дерево с инстансом).\n *\n * Удобно для подсветки текущего плана / e-mail юзера в собственном UI без\n * необходимости держать дублирующий state и руками подписываться на\n * `paywall.on('userChange', ...)`.\n *\n * ```tsx\n * const user = usePaywallUser();\n * if (user?.has_active_subscription) {\n * return <ProBadge plan={user.active_subscription?.plan_name} />;\n * }\n * ```\n *\n * Реализация поверх `paywall.on('userChange', cb)` + `billing.getCachedUser()`.\n * `paywall.on` не делает initial replay'я, поэтому useSyncExternalStore сам\n * читает старт-snapshot через getSnapshot — без лишних cb-вызовов.\n *\n * Ссылочная стабильность: BillingClient сравнивает user shape перед update'ом\n * (`sameUser`), так что между неизменными обновлениями `getCachedUser()`\n * возвращает ===-равный объект. Это гарантирует, что useSyncExternalStore\n * не дёргает ре-рендер при no-op refresh'ах.\n */\nexport function usePaywallUser(): PaywallUser | null {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n return paywall.on('userChange', () => cb());\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallUser | null => {\n return paywall ? paywall.billing.getCachedUser() : null;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallUser | null {\n return null;\n}\n","import { useEffect, useRef } from 'react';\nimport type { PaywallEvent, PaywallEventHandler } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Payload-тип конкретного события достаём через `Parameters<PaywallEventHandler<E>>[0]`,\n// потому что сам `PaywallEventPayloads` в SDK объявлен локально и не экспортируется.\n// Подход через `Parameters<>` устойчив к этому: пока `PaywallEventHandler` есть в\n// public surface, payload-тип SDK мы выводим корректно — TS-сборка sdk-react\n// упадёт, если сигнатура `PaywallEventHandler` поедет.\ntype EventPayload<E extends PaywallEvent> = Parameters<PaywallEventHandler<E>>[0];\n\n/**\n * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`\n * с двумя важными отличиями от ручного useEffect:\n *\n * 1. handler не нужно мемоизировать через `useCallback` — внутри храним\n * последнюю версию в `useRef`, само subscription пересоздаётся только\n * при смене `event` или инстанса paywall'а. Это убирает класс багов с\n * «забыл useCallback → подписка отписывается-переподписывается на каждый\n * рендер → события теряются».\n *\n * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):\n * подписка просто не создаётся, ждёт пока инстанс появится.\n *\n * ```tsx\n * usePaywallEvent('purchase_completed', (payload) => {\n * toast.success(`Покупка ${payload.priceId} прошла`);\n * queryClient.invalidateQueries(['user']);\n * });\n * ```\n *\n * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации\n * локального стейта) это самый прямой паттерн — компонент гарантированно\n * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.\n */\nexport function usePaywallEvent<E extends PaywallEvent>(\n event: E,\n handler: PaywallEventHandler<E>\n): void {\n const paywall = usePaywall();\n const handlerRef = useRef(handler);\n\n // Обновляем ref на каждом render'е — следующее срабатывание события\n // подхватит свежий handler. Без отдельного useEffect, потому что синхронный\n // assign в render-фазе для ref'а корректен и не нарушает rules-of-hooks.\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!paywall) return;\n return paywall.on(event, (payload) => {\n // Cast необходим, потому что общий вариант `PaywallEventHandler` теряет\n // narrowing по `E`. handlerRef.current типизирован под конкретный E,\n // но `on()` принимает union — рантайм-shape гарантирован SDK'шным emit'ом.\n (handlerRef.current as (p: EventPayload<E>) => void)(payload);\n });\n }, [paywall, event]);\n}\n","import { useEffect, useState } from 'react';\nimport type {\n GetAccessOptions,\n PaywallAccessResult\n} from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `loading` — первый fetch ещё в полёте (или Provider не готов).\n * `ready` — есть свежий ответ; `result` гарантированно non-null.\n *\n * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:\n *\n * `if (access.status === 'ready') access.result.access === 'granted'`\n */\nexport type PaywallAccessState =\n | { status: 'loading'; result: null }\n | { status: 'ready'; result: PaywallAccessResult };\n\nconst LOADING_STATE: PaywallAccessState = { status: 'loading', result: null };\n\n/**\n * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».\n *\n * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не\n * монтируется, trial-storage не двигается). На каждый `userChange` событие\n * автоматически рефетчится — после успешной покупки `has_subscription`\n * сработает мгновенно, и хост перерендерит UI без feature-lock'а.\n *\n * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать\n * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,\n * если кеш свежий).\n *\n * ```tsx\n * const access = usePaywallAccess();\n * const paywall = usePaywall();\n *\n * if (access.status === 'loading') return <Skeleton />;\n * if (access.result.access === 'blocked') {\n * return <button onClick={() => paywall?.open()}>Upgrade</button>;\n * }\n * return <PremiumFeature />;\n * ```\n *\n * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность\n * ссылки `opts` не требуется, эффект перезапустится только при реальном\n * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него\n * новый ref) — отмена inflight-запроса делается локально через AbortController\n * в cleanup-эффекте.\n */\nexport function usePaywallAccess(opts: GetAccessOptions = {}): PaywallAccessState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallAccessState>(LOADING_STATE);\n\n const skipTrial = opts.skipTrial === true;\n const skipVisibility = opts.skipVisibility === true;\n\n useEffect(() => {\n if (!paywall) {\n // Инстанс ушёл (Provider unmount / StrictMode cleanup) — честно\n // вернуть loading, чтобы хост не показывал устаревший result от\n // прошлого инстанса.\n setState(LOADING_STATE);\n return;\n }\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getAccess({ skipTrial, skipVisibility, signal: ctrl.signal })\n .then((result) => {\n if (cancelled || ctrl.signal.aborted) return;\n // Каждый refresh даёт новый объект — useState увидит !== и\n // ререндерит. Это ок: для гейтинга интерес представляет именно\n // `access` поле, остальное (visibility/trial snapshot'ы) — auxiliary\n // данные, которые не должны бы менять решение хоста на тех же входах.\n setState({ status: 'ready', result });\n })\n .catch(() => {\n // getAccess() имеет собственный offline-fallback и не throw'ит на\n // failed network'е — сюда мы попадаем только при abort'е, который\n // прилетает в cleanup-эффекте. Молча игнорим.\n });\n };\n\n refresh();\n\n // userChange покрывает оба источника обновления decision'а:\n // - после-checkout watcher эмит'ит userChange когда has_subscription=true\n // - manual /me refresh из хоста (paywall.billing.getUser())\n // Дополнительно слушаем purchase_completed для symmetric'ности — на\n // некоторых платежных провайдерах userChange может задержаться, а\n // purchase_completed летит мгновенно по URL-маркеру/postMessage.\n const unsubUser = paywall.on('userChange', refresh);\n const unsubPurchase = paywall.on('purchase_completed', refresh);\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsubUser();\n unsubPurchase();\n };\n }, [paywall, skipTrial, skipVisibility]);\n\n return state;\n}\n","import { useEffect, useState } from 'react';\nimport type { PaywallPrice } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а\n * или когда инстанс ещё не готов).\n * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.\n * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).\n *\n * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`\n * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три\n * независимые величины одновременно (показать предыдущий список + skeleton +\n * сообщение об ошибке поверх) — discriminated union тут только усложняет.\n */\nexport interface PaywallPricesState {\n prices: PaywallPrice[] | null;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Загружает и подписывается на цены пейвола. Подходит для отдельной\n * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,\n * что и в модалке, без открытия paywall'а.\n *\n * Реализация:\n * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше\n * BillingClient'а — например, после `paywall.preload()` или предыдущего\n * open'а — цены доступны мгновенно);\n * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;\n * - subscription на `ready` event — рефетч bootstrap'а на новом open()\n * может принести обновлённые цены, мы обновляем snapshot.\n *\n * ```tsx\n * const { prices, loading } = usePaywallPrices();\n * if (loading && !prices) return <Skeleton />;\n * return prices?.map((p) => <PriceCard key={p.id} price={p} />);\n * ```\n */\nexport function usePaywallPrices(): PaywallPricesState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallPricesState>(() => ({\n prices: paywall?.getCachedPrices() ?? null,\n loading: true,\n error: null\n }));\n\n useEffect(() => {\n if (!paywall) {\n setState({ prices: null, loading: true, error: null });\n return;\n }\n\n // Sync-доступ через cached snapshot — если bootstrap уже загружен,\n // показываем цены немедленно (без флеша «loading → ready»).\n const cached = paywall.getCachedPrices();\n setState({ prices: cached, loading: cached === null, error: null });\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getPrices({ signal: ctrl.signal })\n .then((prices) => {\n if (cancelled) return;\n setState({ prices, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (cancelled || ctrl.signal.aborted) return;\n setState((prev) => ({\n prices: prev.prices,\n loading: false,\n error: error instanceof Error ? error : new Error(String(error))\n }));\n });\n };\n\n refresh();\n\n // `ready` event фаерится из открытого paywall'а с финальным bootstrap'ом —\n // если хост открыл/закрыл модалку, цены могли обновиться через\n // stale-while-revalidate. Слушаем чтобы в pricing-странице цифры не\n // расходились с тем, что юзер увидит в модалке.\n const unsub = paywall.on('ready', () => {\n const fresh = paywall.getCachedPrices();\n if (fresh) setState({ prices: fresh, loading: false, error: null });\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsub();\n };\n }, [paywall]);\n\n return state;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `TrialStatus` локально не экспортируется из SDK, но мы его получаем\n// через ReturnType-инференцию по публичному методу `getTrialStatus()`. Так\n// тип всегда совпадает с тем, что реально возвращает PaywallUI, без зависимости\n// от непубличного namespace'а SDK.\ntype TrialStatus = NonNullable<ReturnType<PaywallUI['getTrialStatus']>>;\n\n/**\n * Текущий статус триала ({@link TrialStatus}) с автоматическим ре-рендером на\n * `trial_blocked` события.\n *\n * Возвращает `null`, пока триал не проверялся (хост не вызывал\n * `paywall.open()` / `paywall.getAccess()`) либо триал отключён в конфиге\n * пейвола. Сам триал-стейт живёт в storage (localStorage / chrome.storage),\n * проверяется в `paywall.open()` и в `paywall.getAccess()` — оба пути обновляют\n * in-memory snapshot, который мы здесь и читаем.\n *\n * Использовать чтобы рисовать собственный UI:\n * - «У тебя осталось 3 показа» (mode `opens`) — `status.remainingActions`;\n * - «Триал истечёт через 2 часа» (mode `time`) — `status.remainingMs`;\n * - «Триал заблокирован, оплати чтобы продолжить» — `status.blocked === true`.\n *\n * ```tsx\n * const trial = usePaywallTrial();\n * if (trial?.mode === 'opens') {\n * return <Banner>Showings left: {trial.remainingActions}</Banner>;\n * }\n * ```\n */\nexport function usePaywallTrial(): TrialStatus | null {\n const paywall = usePaywall();\n const [status, setStatus] = useState<TrialStatus | null>(() =>\n paywall?.getTrialStatus() ?? null\n );\n\n // Стабильный refresh для эффекта — отдельная функция, чтобы deps массив\n // эффекта был чистым (`[paywall]`), без useCallback-цепочек.\n const sync = useCallback(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n setStatus(paywall.getTrialStatus());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n // Sync read на mount-е — getTrialStatus() мог обновиться между прошлым\n // рендером и effect'ом (например, hook вызван после первого open()-а).\n sync();\n\n // `trial_blocked` — единственный event, после которого snapshot реально\n // меняется. `trial_expired` фаерится один раз за жизнь инстанса и не\n // меняет shape статуса (статус становится `mode: 'none'` ИЛИ переходит\n // в un-blocked-режим, что и так читается через sync()).\n const unsubBlock = paywall.on('trial_blocked', sync);\n const unsubExpired = paywall.on('trial_expired', sync);\n\n return () => {\n unsubBlock();\n unsubExpired();\n };\n }, [paywall, sync]);\n\n return status;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `VisibilityStatus` локально не экспортируется из SDK — получаем через\n// ReturnType от публичного `getVisibility()`. См. usePaywallTrial для тех же\n// соображений.\ntype VisibilityStatus = NonNullable<ReturnType<PaywallUI['getVisibility']>>;\n\n/**\n * Server-computed visibility-снимок ({@link VisibilityStatus}): попадает ли\n * юзер в monetization-scope пейвола (страна, девайс, ручной visibility-флаг).\n *\n * Возвращает `null`, пока bootstrap не загружен или сервер не отдал\n * `settings.visibility` (старый online без targeting-патча).\n *\n * Использовать чтобы:\n * - показать собственный fallback («сервис недоступен в вашей стране») вместо\n * модалки, когда `visible === false`;\n * - залогировать impression для аналитики страны/tier'а юзера;\n * - принять решение какой CTA рисовать, не дёргая open() и не дожидаясь\n * visibility_blocked event.\n *\n * ```tsx\n * const visibility = usePaywallVisibility();\n * if (visibility && !visibility.visible) {\n * return <SoftBlock reason={visibility.reason} />;\n * }\n * ```\n */\nexport function usePaywallVisibility(): VisibilityStatus | null {\n const paywall = usePaywall();\n const [visibility, setVisibility] = useState<VisibilityStatus | null>(() =>\n paywall?.getVisibility() ?? null\n );\n\n const sync = useCallback(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n setVisibility(paywall.getVisibility());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n sync();\n\n // `ready` event летит после успешного bootstrap'а — там обновляется\n // `lastVisibility` в PaywallUI. `visibility_blocked` — когда блокировка\n // реально срабатывает на gate'е. Оба меняют snapshot.\n const unsubReady = paywall.on('ready', sync);\n const unsubBlocked = paywall.on('visibility_blocked', sync);\n\n return () => {\n unsubReady();\n unsubBlocked();\n };\n }, [paywall, sync]);\n\n return visibility;\n}\n","import { useEffect, type ReactNode } from 'react';\nimport type { PaywallAccessResult } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\nimport { usePaywallAccess } from '../hooks/usePaywallAccess';\n\nexport interface PaywallGateProps {\n /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */\n loading?: ReactNode;\n /**\n * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо\n * статичный ReactNode, либо render-функцию, получающую callback\n * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:\n *\n * ```tsx\n * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}\n * ```\n *\n * Если не передан — компонент рендерит `null` для blocked (host\n * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).\n */\n fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);\n /**\n * Автоматически дёргать `paywall.open()` сразу как только access перешёл в\n * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на\n * paywall»: компонент сам открывает модалку, не нужно писать onClick.\n *\n * По умолчанию `false` — большинство хостов хотят сначала показать\n * объясняющий CTA, а модалку открывать по клику. Включать осознанно.\n */\n openOnBlocked?: boolean;\n /** Премиум-контент. Рендерится только когда access=granted. */\n children: ReactNode;\n}\n\nexport interface BlockedRenderArgs {\n result: Extract<PaywallAccessResult, { access: 'blocked' }>;\n open: () => void;\n}\n\n/**\n * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().\n *\n * Три состояния:\n * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;\n * - `granted` (есть подписка / visibility / trial) — рендерим `children`;\n * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем\n * `paywall.open()` при `openOnBlocked={true}`.\n *\n * ```tsx\n * <PaywallGate\n * loading={<Skeleton />}\n * fallback={({ open }) => <button onClick={open}>Upgrade</button>}\n * >\n * <PremiumFeature />\n * </PaywallGate>\n * ```\n *\n * Для нестандартных сценариев (показать \"Try free trial\" вместо upgrade,\n * комбинировать с собственным auth-flow'ом) использовать\n * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь\n * стать конфигурируемым на каждый чих.\n */\nexport function PaywallGate(props: PaywallGateProps): JSX.Element | null {\n const paywall = usePaywall();\n const access = usePaywallAccess();\n\n // `openOnBlocked` — side-effect, поэтому в useEffect. Зависим от access\n // через идентификатор `result.access`, а не от объекта целиком, чтобы\n // не дёргать open() на каждом refresh-е getAccess'а с тем же blocked-итогом.\n const isBlocked =\n access.status === 'ready' && access.result.access === 'blocked';\n const shouldAutoOpen = props.openOnBlocked === true && isBlocked;\n\n useEffect(() => {\n if (shouldAutoOpen && paywall) paywall.open();\n }, [shouldAutoOpen, paywall]);\n\n if (access.status === 'loading') {\n return <>{props.loading ?? null}</>;\n }\n\n if (access.result.access === 'granted') {\n return <>{props.children}</>;\n }\n\n // blocked\n const fallback = props.fallback;\n if (typeof fallback === 'function') {\n return (\n <>\n {fallback({\n result: access.result,\n open: () => paywall?.open()\n })}\n </>\n );\n }\n return <>{fallback ?? null}</>;\n}\n","import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactElement,\n type ReactNode\n} from 'react';\nimport type { OpenOptions } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\n\n/**\n * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.\n * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,\n * `skipVisibility`.\n */\ntype OpenProps = OpenOptions;\n\ninterface CommonProps extends OpenProps {\n /** Что открывать: layout (default), support, auth-gate (signin),\n * signup-форма. 'auth' эквивалентен 'signin' (исторически — openAuth\n * дефолтит в signin-mode). Для анонимного signin используй\n * `usePaywall().signInAnonymously()` напрямую — headless без модалки. */\n mode?: 'paywall' | 'support' | 'auth' | 'signin' | 'signup';\n /** Render-prop для полного контроля над элементом-триггером. Когда задан,\n * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */\n render?: (args: PaywallButtonRenderArgs) => ReactElement;\n}\n\nexport interface PaywallButtonRenderArgs {\n /** Открыть пейвол согласно `mode` + переданным opts. */\n open: () => void;\n /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */\n ready: boolean;\n}\n\n/**\n * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,\n * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный\n * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем\n * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).\n */\ntype ButtonRenderProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof OpenProps | 'children'\n> & {\n children?: ReactNode;\n};\n\nexport type PaywallButtonProps = CommonProps & ButtonRenderProps;\n\n/**\n * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как\n * нативный `<button>` со всеми твоими className/style/disabled, но при нужде\n * можно передать `render` для произвольного элемента (Radix-style asChild\n * паттерн через render-prop).\n *\n * ```tsx\n * // обычный кейс\n * <PaywallButton className=\"btn-primary\" renew>\n * Renew subscription\n * </PaywallButton>\n *\n * // custom-элемент\n * <PaywallButton render={({ open, ready }) => (\n * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>\n * )} />\n *\n * // саппорт-форма вместо тарифов\n * <PaywallButton mode=\"support\">Need help?</PaywallButton>\n * ```\n *\n * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`\n * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —\n * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.\n */\nexport const PaywallButton = forwardRef<HTMLButtonElement, PaywallButtonProps>(\n function PaywallButton(props, ref) {\n const paywall = usePaywall();\n const {\n mode = 'paywall',\n identity,\n renew,\n skipTrial,\n skipVisibility,\n render,\n onClick,\n disabled,\n ...buttonProps\n } = props;\n\n const ready = paywall !== null;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n switch (mode) {\n case 'support':\n paywall.openSupport(openOpts);\n return;\n case 'auth':\n case 'signin':\n paywall.openSignin(openOpts);\n return;\n case 'signup':\n paywall.openSignup(openOpts);\n return;\n default:\n paywall.open(openOpts);\n }\n };\n\n if (render) {\n return render({ open, ready });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready}\n aria-busy={!ready ? true : undefined}\n onClick={(event) => {\n // Наш handler первым — host через event.preventDefault() ничего\n // не остановит, потому что open() уже стрельнул. Это намеренно:\n // открытие пейвола не должно зависеть от того, забыл ли хост\n // вернуть `false` из своего analytics-handler'а. Если нужен\n // префлайт-чек — паттерн через `render`-prop, там полный контроль.\n open();\n onClick?.(event);\n }}\n {...buttonProps}\n />\n );\n }\n);\n","import { forwardRef } from 'react';\nimport { PaywallButton, type PaywallButtonProps } from './PaywallButton';\n\nexport type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;\n\n/**\n * Сахар над `<PaywallButton mode=\"support\">`. Самостоятельная компонента, а\n * не пресет prop'а, для discoverability — название говорит за себя, и в\n * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.\n *\n * ```tsx\n * <PaywallSupportButton className=\"link\">Help</PaywallSupportButton>\n * ```\n */\nexport const PaywallSupportButton = forwardRef<\n HTMLButtonElement,\n PaywallSupportButtonProps\n>(function PaywallSupportButton(props, ref) {\n return <PaywallButton {...props} mode=\"support\" ref={ref} />;\n});\n"],"names":["PaywallContext","createContext","PaywallProviderMarker","PaywallProvider","props","externalInstance","options","paywall","setPaywall","useState","useEffect","created","PaywallUI","jsx","usePaywall","hasProvider","useContext","SSR_SNAPSHOT","usePaywallState","subscribe","useCallback","cb","getSnapshot","useSyncExternalStore","usePaywallUser","getServerSnapshot","usePaywallEvent","event","handler","handlerRef","useRef","payload","LOADING_STATE","usePaywallAccess","opts","state","setState","skipTrial","skipVisibility","ctrl","cancelled","refresh","result","unsubUser","unsubPurchase","usePaywallPrices","cached","prices","error","prev","unsub","fresh","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubReady","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","openOpts","open","PaywallSupportButton"],"mappings":";;;;AAgBO,MAAMA,IAAiBC,EAAgC,IAAI;AAClED,EAAe,cAAc;AAWtB,MAAME,IAAwBD,EAAuB,EAAK;AACjEC,EAAsB,cAAc;ACgC7B,SAASC,EAAgBC,GAA0C;AACxE,QAAMC,IAAmB,cAAcD,IAAQA,EAAM,WAAW,QAC1DE,IAAU,aAAaF,IAAQA,EAAM,UAAU,QAO/C,CAACG,GAASC,CAAU,IAAIC;AAAA,IAC5BJ,KAAoB;AAAA,EAAA;AAMtB,SAAAK,EAAU,MAAM;AACd,QAAIL,GAAkB;AACpB,MAAAG,EAAWH,CAAgB;AAE3B;AAAA,IACF;AAEA,QAAI,CAACC,EAAS;AAEd,UAAMK,IAAU,IAAIC,EAAUN,CAAO;AACrC,WAAAE,EAAWG,CAAO,GACX,MAAM;AACX,MAAAA,EAAQ,QAAA,GAKRH,EAAW,IAAI;AAAA,IACjB;AAAA,EAKF,GAAG,CAACH,CAAgB,CAAC,GAGnB,gBAAAQ,EAACX,EAAsB,UAAtB,EAA+B,OAAO,IACrC,UAAA,gBAAAW,EAACb,EAAe,UAAf,EAAwB,OAAOO,GAC7B,UAAAH,EAAM,UACT,GACF;AAEJ;ACrFO,SAASU,IAA+B;AAC7C,QAAMC,IAAcC,EAAWd,CAAqB,GAC9CK,IAAUS,EAAWhB,CAAc;AAEzC,MAAI,CAACe;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAMJ,SAAOR;AACT;AC1BA,MAAMU,IAAqC,EAAE,MAAM,IAAO,MAAM,MAAM,OAAO,KAAA;AAsBtE,SAASC,IAAwC;AACtD,QAAMX,IAAUO,EAAA,GAEVK,IAAYC;AAAA,IAChB,CAACC,MACMd,IAIEA,EAAQ,cAAcc,GAAI,EAAE,WAAW,QAAQ,IAJjC,MAAM;AAAA,IAAC;AAAA,IAM9B,CAACd,CAAO;AAAA,EAAA,GAGJe,IAAcF,EAAY,MACvBb,IAAUA,EAAQ,SAAA,IAAaU,GACrC,CAACV,CAAO,CAAC;AAEZ,SAAOgB,EAAqBJ,GAAWG,GAAa,MAAML,CAAY;AACxE;ACrBO,SAASO,IAAqC;AACnD,QAAMjB,IAAUO,EAAA,GAEVK,IAAYC;AAAA,IAChB,CAACC,MACMd,IACEA,EAAQ,GAAG,cAAc,MAAMc,GAAI,IADrB,MAAM;AAAA,IAAC;AAAA,IAG9B,CAACd,CAAO;AAAA,EAAA,GAGJe,IAAcF,EAAY,MACvBb,IAAUA,EAAQ,QAAQ,cAAA,IAAkB,MAClD,CAACA,CAAO,CAAC;AAEZ,SAAOgB,EAAqBJ,GAAWG,GAAaG,CAAiB;AACvE;AAEA,SAASA,IAAwC;AAC/C,SAAO;AACT;AChBO,SAASC,EACdC,GACAC,GACM;AACN,QAAMrB,IAAUO,EAAA,GACVe,IAAaC,EAAOF,CAAO;AAKjC,EAAAC,EAAW,UAAUD,GAErBlB,EAAU,MAAM;AACd,QAAKH;AACL,aAAOA,EAAQ,GAAGoB,GAAO,CAACI,MAAY;AAInC,QAAAF,EAAW,QAAyCE,CAAO;AAAA,MAC9D,CAAC;AAAA,EACH,GAAG,CAACxB,GAASoB,CAAK,CAAC;AACrB;ACrCA,MAAMK,IAAoC,EAAE,QAAQ,WAAW,QAAQ,KAAA;AA+BhE,SAASC,EAAiBC,IAAyB,IAAwB;AAChF,QAAM3B,IAAUO,EAAA,GACV,CAACqB,GAAOC,CAAQ,IAAI3B,EAA6BuB,CAAa,GAE9DK,IAAYH,EAAK,cAAc,IAC/BI,IAAiBJ,EAAK,mBAAmB;AAE/C,SAAAxB,EAAU,MAAM;AACd,QAAI,CAACH,GAAS;AAIZ,MAAA6B,EAASJ,CAAa;AACtB;AAAA,IACF;AAEA,UAAMO,IAAO,IAAI,gBAAA;AACjB,QAAIC,IAAY;AAEhB,UAAMC,IAAU,MAAM;AACpB,MAAAlC,EACG,UAAU,EAAE,WAAA8B,GAAW,gBAAAC,GAAgB,QAAQC,EAAK,OAAA,CAAQ,EAC5D,KAAK,CAACG,MAAW;AAChB,QAAIF,KAAaD,EAAK,OAAO,WAK7BH,EAAS,EAAE,QAAQ,SAAS,QAAAM,EAAA,CAAQ;AAAA,MACtC,CAAC,EACA,MAAM,MAAM;AAAA,MAIb,CAAC;AAAA,IACL;AAEA,IAAAD,EAAA;AAQA,UAAME,IAAYpC,EAAQ,GAAG,cAAckC,CAAO,GAC5CG,IAAgBrC,EAAQ,GAAG,sBAAsBkC,CAAO;AAE9D,WAAO,MAAM;AACX,MAAAD,IAAY,IACZD,EAAK,MAAA,GACLI,EAAA,GACAC,EAAA;AAAA,IACF;AAAA,EACF,GAAG,CAACrC,GAAS8B,GAAWC,CAAc,CAAC,GAEhCH;AACT;ACnEO,SAASU,IAAuC;AACrD,QAAMtC,IAAUO,EAAA,GACV,CAACqB,GAAOC,CAAQ,IAAI3B,EAA6B,OAAO;AAAA,IAC5D,QAAQF,GAAS,gBAAA,KAAqB;AAAA,IACtC,SAAS;AAAA,IACT,OAAO;AAAA,EAAA,EACP;AAEF,SAAAG,EAAU,MAAM;AACd,QAAI,CAACH,GAAS;AACZ,MAAA6B,EAAS,EAAE,QAAQ,MAAM,SAAS,IAAM,OAAO,MAAM;AACrD;AAAA,IACF;AAIA,UAAMU,IAASvC,EAAQ,gBAAA;AACvB,IAAA6B,EAAS,EAAE,QAAQU,GAAQ,SAASA,MAAW,MAAM,OAAO,MAAM;AAElE,UAAMP,IAAO,IAAI,gBAAA;AACjB,QAAIC,IAAY;AAmBhB,KAjBgB,MAAM;AACpB,MAAAjC,EACG,UAAU,EAAE,QAAQgC,EAAK,QAAQ,EACjC,KAAK,CAACQ,MAAW;AAChB,QAAIP,KACJJ,EAAS,EAAE,QAAAW,GAAQ,SAAS,IAAO,OAAO,MAAM;AAAA,MAClD,CAAC,EACA,MAAM,CAACC,MAAmB;AACzB,QAAIR,KAAaD,EAAK,OAAO,WAC7BH,EAAS,CAACa,OAAU;AAAA,UAClB,QAAQA,EAAK;AAAA,UACb,SAAS;AAAA,UACT,OAAOD,aAAiB,QAAQA,IAAQ,IAAI,MAAM,OAAOA,CAAK,CAAC;AAAA,QAAA,EAC/D;AAAA,MACJ,CAAC;AAAA,IACL,GAEA;AAMA,UAAME,IAAQ3C,EAAQ,GAAG,SAAS,MAAM;AACtC,YAAM4C,IAAQ5C,EAAQ,gBAAA;AACtB,MAAI4C,OAAgB,EAAE,QAAQA,GAAO,SAAS,IAAO,OAAO,MAAM;AAAA,IACpE,CAAC;AAED,WAAO,MAAM;AACX,MAAAX,IAAY,IACZD,EAAK,MAAA,GACLW,EAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAC3C,CAAO,CAAC,GAEL4B;AACT;AClEO,SAASiB,IAAsC;AACpD,QAAM7C,IAAUO,EAAA,GACV,CAACuC,GAAQC,CAAS,IAAI7C;AAAA,IAA6B,MACvDF,GAAS,oBAAoB;AAAA,EAAA,GAKzBgD,IAAOnC,EAAY,MAAM;AAC7B,QAAI,CAACb,GAAS;AACZ,MAAA+C,EAAU,IAAI;AACd;AAAA,IACF;AACA,IAAAA,EAAU/C,EAAQ,gBAAgB;AAAA,EACpC,GAAG,CAACA,CAAO,CAAC;AAEZ,SAAAG,EAAU,MAAM;AACd,QAAI,CAACH,GAAS;AACZ,MAAA+C,EAAU,IAAI;AACd;AAAA,IACF;AAGA,IAAAC,EAAA;AAMA,UAAMC,IAAajD,EAAQ,GAAG,iBAAiBgD,CAAI,GAC7CE,IAAelD,EAAQ,GAAG,iBAAiBgD,CAAI;AAErD,WAAO,MAAM;AACX,MAAAC,EAAA,GACAC,EAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAClD,GAASgD,CAAI,CAAC,GAEXF;AACT;ACzCO,SAASK,IAAgD;AAC9D,QAAMnD,IAAUO,EAAA,GACV,CAAC6C,GAAYC,CAAa,IAAInD;AAAA,IAAkC,MACpEF,GAAS,mBAAmB;AAAA,EAAA,GAGxBgD,IAAOnC,EAAY,MAAM;AAC7B,QAAI,CAACb,GAAS;AACZ,MAAAqD,EAAc,IAAI;AAClB;AAAA,IACF;AACA,IAAAA,EAAcrD,EAAQ,eAAe;AAAA,EACvC,GAAG,CAACA,CAAO,CAAC;AAEZ,SAAAG,EAAU,MAAM;AACd,QAAI,CAACH,GAAS;AACZ,MAAAqD,EAAc,IAAI;AAClB;AAAA,IACF;AACA,IAAAL,EAAA;AAKA,UAAMM,IAAatD,EAAQ,GAAG,SAASgD,CAAI,GACrCO,IAAevD,EAAQ,GAAG,sBAAsBgD,CAAI;AAE1D,WAAO,MAAM;AACX,MAAAM,EAAA,GACAC,EAAA;AAAA,IACF;AAAA,EACF,GAAG,CAACvD,GAASgD,CAAI,CAAC,GAEXI;AACT;ACFO,SAASI,EAAY3D,GAA6C;AACvE,QAAMG,IAAUO,EAAA,GACVkD,IAAS/B,EAAA,GAKTgC,IACJD,EAAO,WAAW,WAAWA,EAAO,OAAO,WAAW,WAClDE,IAAiB9D,EAAM,kBAAkB,MAAQ6D;AAMvD,MAJAvD,EAAU,MAAM;AACd,IAAIwD,KAAkB3D,KAASA,EAAQ,KAAA;AAAA,EACzC,GAAG,CAAC2D,GAAgB3D,CAAO,CAAC,GAExByD,EAAO,WAAW;AACpB,WAAO,gBAAAnD,EAAAsD,GAAA,EAAG,UAAA/D,EAAM,WAAW,MAAK;AAGlC,MAAI4D,EAAO,OAAO,WAAW;AAC3B,WAAO,gBAAAnD,EAAAsD,GAAA,EAAG,YAAM,SAAA,CAAS;AAI3B,QAAMC,IAAWhE,EAAM;AACvB,SAAI,OAAOgE,KAAa,oCAGjB,UAAAA,EAAS;AAAA,IACR,QAAQJ,EAAO;AAAA,IACf,MAAM,MAAMzD,GAAS,KAAA;AAAA,EAAK,CAC3B,GACH,IAGG,gBAAAM,EAAAsD,GAAA,EAAG,eAAY,KAAA,CAAK;AAC7B;ACxBO,MAAME,IAAgBC;AAAA,EAC3B,SAAuBlE,GAAOmE,GAAK;AACjC,UAAMhE,IAAUO,EAAA,GACV;AAAA,MACJ,MAAA0D,IAAO;AAAA,MACP,UAAAC;AAAA,MACA,OAAAC;AAAA,MACA,WAAArC;AAAA,MACA,gBAAAC;AAAA,MACA,QAAAqC;AAAA,MACA,SAAAC;AAAA,MACA,UAAAC;AAAA,MACA,GAAGC;AAAA,IAAA,IACD1E,GAEE2E,IAAQxE,MAAY,MAEpByE,IAAwB,EAAE,UAAAP,GAAU,OAAAC,GAAO,WAAArC,GAAW,gBAAAC,EAAA,GAEtD2C,IAAO,MAAY;AACvB,UAAK1E;AACL,gBAAQiE,GAAA;AAAA,UACN,KAAK;AACH,YAAAjE,EAAQ,YAAYyE,CAAQ;AAC5B;AAAA,UACF,KAAK;AAAA,UACL,KAAK;AACH,YAAAzE,EAAQ,WAAWyE,CAAQ;AAC3B;AAAA,UACF,KAAK;AACH,YAAAzE,EAAQ,WAAWyE,CAAQ;AAC3B;AAAA,UACF;AACE,YAAAzE,EAAQ,KAAKyE,CAAQ;AAAA,QAAA;AAAA,IAE3B;AAEA,WAAIL,IACKA,EAAO,EAAE,MAAAM,GAAM,OAAAF,GAAO,IAI7B,gBAAAlE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAA0D;AAAA,QACA,MAAK;AAAA,QACL,UAAUM,KAAY,CAACE;AAAA,QACvB,aAAYA,IAAe,SAAP;AAAA,QACpB,SAAS,CAACpD,MAAU;AAMlB,UAAAsD,EAAA,GACAL,IAAUjD,CAAK;AAAA,QACjB;AAAA,QACC,GAAGmD;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AACF,GCxHaI,IAAuBZ,EAGlC,SAA8BlE,GAAOmE,GAAK;AAC1C,2BAAQF,GAAA,EAAe,GAAGjE,GAAO,MAAK,WAAU,KAAAmE,GAAU;AAC5D,CAAC;"}
1
+ {"version":3,"file":"index.js","sources":["../src/context.ts","../src/PaywallProvider.tsx","../src/hooks/usePaywall.ts","../src/hooks/usePaywallState.ts","../src/hooks/usePaywallUser.ts","../src/hooks/usePaywallEvent.ts","../src/hooks/usePaywallAccess.ts","../src/hooks/usePaywallPrices.ts","../src/hooks/usePaywallTrial.ts","../src/hooks/usePaywallVisibility.ts","../src/components/PaywallGate.tsx","../src/components/PaywallButton.tsx","../src/components/PaywallSupportButton.tsx"],"sourcesContent":["import { createContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\n\n/**\n * Внутренний React Context, в который PaywallProvider кладёт PaywallUI-инстанс.\n *\n * value === null до того, как Provider успел смонтировать инстанс (SSR,\n * первый render до useEffect, дев double-mount в StrictMode после cleanup).\n * Хуки должны корректно обрабатывать null — отдавать loading/null/no-op,\n * а не падать.\n *\n * defaultValue intentionally `null`, а не `undefined` — это позволяет\n * usePaywall() различать «Provider не оборачивает дерево» (undefined-симуляция\n * через sentinel-объект ниже не нужна, мы это ловим иначе) и «Provider есть,\n * но инстанс ещё не создан» (null).\n */\nexport const PaywallContext = createContext<PaywallUI | null>(null);\nPaywallContext.displayName = 'PaywallContext';\n\n/**\n * Sentinel для отслеживания: «компонент вообще находится внутри Provider'а?».\n *\n * React Context отдаёт defaultValue, когда `<Provider>` не оборачивает дерево.\n * Если defaultValue=null, а Provider тоже легально кладёт null (на SSR /\n * до mount-а) — мы не различаем эти два случая. Поэтому Provider всегда\n * оборачивает второй Context с маркером HAS_PROVIDER=true, который usePaywall\n * проверяет первым.\n */\nexport const PaywallProviderMarker = createContext<boolean>(false);\nPaywallProviderMarker.displayName = 'PaywallProviderMarker';\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { PaywallUI, type PaywallUIOptions } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from './context';\n\n/**\n * Два взаимоисключающих режима использования:\n *\n * - `options` — Provider сам конструирует `PaywallUI` в useEffect и\n * прибирает в cleanup. Самый частый кейс — обычный сайт.\n * - `instance` — хост создаёт PaywallUI сам и передаёт готовым. Нужно для\n * extension'ов (`@monetize.software/sdk-extension` поставляет structurally\n * compatible PaywallUI с RemoteBillingClient), для shared-инстанса между\n * несколькими React-деревьями и для тестов.\n *\n * Discriminated union на уровне типов — TS не даст передать оба сразу.\n */\nexport type PaywallProviderProps =\n | {\n options: PaywallUIOptions;\n instance?: never;\n children: ReactNode;\n }\n | {\n instance: PaywallUI;\n options?: never;\n children: ReactNode;\n };\n\n/**\n * Корневой Provider для всех React-биндингов SDK.\n *\n * ```tsx\n * // вариант 1: Provider сам создаёт инстанс\n * <PaywallProvider options={{ paywallId: '...', auth: true }}>\n * <App />\n * </PaywallProvider>\n *\n * // вариант 2: готовый инстанс снаружи (extension / shared)\n * const paywall = createPaywallUI({ paywallId: '...' });\n * <PaywallProvider instance={paywall}>\n * <App />\n * </PaywallProvider>\n * ```\n *\n * SSR: инстанс создаётся в useEffect, на сервере context value=null. Все\n * хуки делают graceful fallback (`null` / `{ status: 'loading' }`), так что\n * Provider можно безопасно рендерить в Next.js / Remix без `'use client'`-\n * ограничений на дерево потомков.\n *\n * StrictMode: cleanup-эффект зовёт `destroy()`, чтобы dev double-mount не\n * оставлял утечек listener'ов и подписок. Микротик-эффекты PaywallUI-\n * конструктора (`autoDetectReturn`) на первом инстансе становятся no-op\n * после destroy.\n *\n * Смена `options` между рендерами: не реактивна — Provider создаёт инстанс\n * один раз. Если хосту реально нужно пересоздать (поменялся `paywallId`),\n * следует менять `key` у Provider'а — это идиоматичный React-способ форсить\n * пересоздание. Делать «умное» сравнение опций мы намеренно не пытаемся:\n * структурный equality глубоких options всегда ломается на функциях-колбеках\n * и live-обновлениях storage'а.\n */\nexport function PaywallProvider(props: PaywallProviderProps): JSX.Element {\n const externalInstance = 'instance' in props ? props.instance : undefined;\n const options = 'options' in props ? props.options : undefined;\n\n // Внешний инстанс → синхронно кладём его в state, чтобы первый render\n // потомков уже видел реальный PaywallUI (хосту он доступен мгновенно после\n // вызова createPaywallUI). Свой инстанс → null до useEffect, потому что\n // конструктор PaywallUI трогает window/queueMicrotask и не должен крутиться\n // на сервере.\n const [paywall, setPaywall] = useState<PaywallUI | null>(\n externalInstance ?? null\n );\n\n // Сам инстанс создаём в useEffect (только клиент). Если хост даёт готовый —\n // useEffect просто sync'ит state на случай, если ref поменялся между\n // рендерами без unmount'а.\n useEffect(() => {\n if (externalInstance) {\n setPaywall(externalInstance);\n // Externally-owned lifecycle — destroy() не наш.\n return;\n }\n\n if (!options) return;\n\n const created = new PaywallUI(options);\n setPaywall(created);\n return () => {\n created.destroy();\n // null на cleanup — потомки на следующем render'е увидят «инстанс ещё\n // не готов» вместо обращения к destroyed-объекту. В обычной жизни\n // unmount Provider'а сразу размонтирует и потомков, поэтому это\n // подстраховка для редких manual-remount-сценариев и StrictMode'а.\n setPaywall(null);\n };\n // options/instance меняются по reference. Реактивная пересборка инстанса\n // на каждый ре-рендер хост-компонента — не то, что нужно (см. JSDoc выше).\n // Для пересоздания используется React `key`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [externalInstance]);\n\n return (\n <PaywallProviderMarker.Provider value={true}>\n <PaywallContext.Provider value={paywall}>\n {props.children}\n </PaywallContext.Provider>\n </PaywallProviderMarker.Provider>\n );\n}\n","import { useContext } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { PaywallContext, PaywallProviderMarker } from '../context';\n\n/**\n * Достаёт PaywallUI-инстанс из ближайшего {@link PaywallProvider}.\n *\n * Бросает ошибку, если вызван вне Provider'а — это явный программный баг,\n * не runtime-флоу. На SSR / до useEffect Provider'а возвращает `null`\n * (Provider есть, но инстанс ещё не смонтирован).\n *\n * Подавляющему большинству пейволов от хоста нужны `paywall.open()`,\n * `paywall.openSupport()`, подписки на события — для всего этого\n * usePaywall() самый прямой путь:\n *\n * ```tsx\n * const paywall = usePaywall();\n * <button onClick={() => paywall?.open()}>Upgrade</button>\n * ```\n *\n * Для типичных кейсов (gating, state-driven UI) обычно удобнее\n * специализированные хуки: {@link usePaywallState}, {@link usePaywallAccess},\n * {@link usePaywallUser}.\n */\nexport function usePaywall(): PaywallUI | null {\n const hasProvider = useContext(PaywallProviderMarker);\n const paywall = useContext(PaywallContext);\n\n if (!hasProvider) {\n throw new Error(\n '[sdk-react] usePaywall() called outside <PaywallProvider>. ' +\n 'Wrap your tree with <PaywallProvider options={...}> or pass an ' +\n 'externally-created instance via <PaywallProvider instance={paywall}>.'\n );\n }\n\n return paywall;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallStateSnapshot } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Зеркалит CLOSED_STATE из PaywallUI.ts. Хранится локально, чтобы getSnapshot\n// при paywall=null отдавал стабильную ссылку (та же ссылка между рендерами →\n// useSyncExternalStore не дёргает лишний re-render). Не экспортируется\n// наружу: для public API публичная форма доступна через usePaywallState().\n//\n// Shape проверяется в contract.ts — если PaywallStateSnapshot в SDK обзаведётся\n// новым полем, TS-build sdk-react падает раньше, чем кто-то заметит расхождение.\nconst SSR_SNAPSHOT: PaywallStateSnapshot = { open: false, view: null, error: null };\n\n/**\n * Подписка на состояние модалки пейвола: открыта/закрыта, текущий view,\n * последняя ошибка.\n *\n * Реализована поверх `paywall.onStateChange` + `paywall.getState` через\n * `useSyncExternalStore` — это даёт корректную concurrent-rendering семантику\n * (никаких tearing'ов, snapshot стабилен в рамках одного React-commit'а) и\n * минимум re-render'ов (snapshot равенство по `Object.is`).\n *\n * До mount-а Provider'а или на сервере возвращает `{ open: false, view: null,\n * error: null }` — это та же форма, что PaywallUI кладёт во внутренний\n * CLOSED_STATE, так что хосту не нужно отдельно проверять «инстанс готов».\n *\n * ```tsx\n * const { open, view } = usePaywallState();\n * useEffect(() => {\n * if (open) analytics.track('paywall_seen');\n * }, [open]);\n * ```\n */\nexport function usePaywallState(): PaywallStateSnapshot {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n // immediate: 'none' — useSyncExternalStore сам читает snapshot через\n // getSnapshot. Реплей initial-state'а через subscribe был бы лишним\n // вызовом cb, не приносящим новой информации.\n return paywall.onStateChange(cb, { immediate: 'none' });\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallStateSnapshot => {\n return paywall ? paywall.getState() : SSR_SNAPSHOT;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, () => SSR_SNAPSHOT);\n}\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\nimport type { AuthSession, PaywallUser } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Состояние «кто такой текущий пользователь» с точки зрения хоста.\n *\n * Discriminated union намеренно совмещает три источника: готовность инстанса\n * PaywallUI (Provider mount), наличие session у managed-auth и `getCachedUser()`\n * от bootstrap'а. Это убирает у хоста нужду различать «пейвол ещё грузится»\n * vs «никого нет» вручную — типы сужают каждый случай.\n *\n * - `loading` — Provider ещё не смонтировал PaywallUI (SSR / pre-mount /\n * dev-double-mount cleanup). На этом этапе показывать skeleton.\n * - `guest` — у пейвола нет identity:\n * • managed-auth: `auth.getCachedSession()` вернул null;\n * • hybrid (без managed-auth): bootstrap прошёл, но user-snapshot пуст.\n * В этом состоянии валидно показать CTA «Sign in» / `<PaywallButton mode=\"signin\">`.\n * - `signed_in` — есть identity. `user` — последний снимок из BillingClient\n * (может быть `null`, пока /me-refresh после signIn в полёте — UI должен\n * показать skeleton, не «sign-in» CTA). `session` — managed-auth session\n * или `null` для hybrid-режима.\n *\n * Хост обычно делает три проверки подряд:\n * ```tsx\n * const account = usePaywallUser();\n * if (account.status === 'loading') return <Skeleton />;\n * if (account.status === 'guest') return <SignInCTA />;\n * // account.user может быть null, пока /me грузится — показать skeleton тут же.\n * if (!account.user) return <Skeleton />;\n * return <Profile user={account.user} />;\n * ```\n *\n * Реализация подписана и на `userChange`, и на `authChange` — любой источник\n * меняющий status триггерит rerender. Snapshot reference закеширован через\n * useRef, чтобы useSyncExternalStore не словил infinite-loop на новых\n * объектах при каждом getSnapshot.\n */\nexport type PaywallUserState =\n | { status: 'loading'; user: null; session: null }\n | { status: 'guest'; user: null; session: null }\n | {\n status: 'signed_in';\n user: PaywallUser | null;\n session: AuthSession | null;\n };\n\nconst LOADING: PaywallUserState = { status: 'loading', user: null, session: null };\nconst GUEST: PaywallUserState = { status: 'guest', user: null, session: null };\n\nexport function usePaywallUser(): PaywallUserState {\n const paywall = usePaywall();\n // useRef-кэш предыдущего snapshot'а — обязателен для useSyncExternalStore.\n // Если каждый getSnapshot возвращает новый объект с теми же components,\n // React воспринимает это как изменение состояния и ловит infinite-loop\n // (или, в строгом режиме, валит warning'ом «getSnapshot should be cached»).\n const cacheRef = useRef<PaywallUserState>(LOADING);\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n const unsubUser = paywall.on('userChange', () => cb());\n // authChange слушаем только в managed-auth режиме. В hybrid-режиме\n // authChange всё равно не эмитится — but defensive: paywall.auth\n // отсутствует, так что подписка просто пропускается.\n const unsubAuth = paywall.auth ? paywall.on('authChange', () => cb()) : null;\n return () => {\n unsubUser();\n unsubAuth?.();\n };\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallUserState => {\n if (!paywall) {\n cacheRef.current = LOADING;\n return LOADING;\n }\n\n const user = paywall.billing.getCachedUser();\n\n if (paywall.auth) {\n const session = paywall.auth.getCachedSession();\n if (!session) {\n cacheRef.current = GUEST;\n return GUEST;\n }\n const prev = cacheRef.current;\n if (\n prev.status === 'signed_in' &&\n prev.user === user &&\n prev.session === session\n ) {\n return prev;\n }\n const next: PaywallUserState = { status: 'signed_in', user, session };\n cacheRef.current = next;\n return next;\n }\n\n // hybrid (no managed-auth). identity приходит через open({identity}); до\n // этого момента billing.getCachedUser() вернёт null. Без session отличать\n // «host передал identity, user ещё грузится» от «гость» невозможно — так\n // что наличие user используем как сигнал signed-in.\n if (user) {\n const prev = cacheRef.current;\n if (\n prev.status === 'signed_in' &&\n prev.user === user &&\n prev.session === null\n ) {\n return prev;\n }\n const next: PaywallUserState = {\n status: 'signed_in',\n user,\n session: null\n };\n cacheRef.current = next;\n return next;\n }\n\n cacheRef.current = GUEST;\n return GUEST;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallUserState {\n return LOADING;\n}\n","import { useEffect, useRef } from 'react';\nimport type { PaywallEvent, PaywallEventHandler } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// Payload-тип конкретного события достаём через `Parameters<PaywallEventHandler<E>>[0]`,\n// потому что сам `PaywallEventPayloads` в SDK объявлен локально и не экспортируется.\n// Подход через `Parameters<>` устойчив к этому: пока `PaywallEventHandler` есть в\n// public surface, payload-тип SDK мы выводим корректно — TS-сборка sdk-react\n// упадёт, если сигнатура `PaywallEventHandler` поедет.\ntype EventPayload<E extends PaywallEvent> = Parameters<PaywallEventHandler<E>>[0];\n\n/**\n * Декларативная подписка на событие PaywallUI. Обёртка над `paywall.on(event, cb)`\n * с двумя важными отличиями от ручного useEffect:\n *\n * 1. handler не нужно мемоизировать через `useCallback` — внутри храним\n * последнюю версию в `useRef`, само subscription пересоздаётся только\n * при смене `event` или инстанса paywall'а. Это убирает класс багов с\n * «забыл useCallback → подписка отписывается-переподписывается на каждый\n * рендер → события теряются».\n *\n * 2. Корректно обрабатывает `paywall === null` (SSR / до Provider mount-а):\n * подписка просто не создаётся, ждёт пока инстанс появится.\n *\n * ```tsx\n * usePaywallEvent('purchase_completed', (payload) => {\n * toast.success(`Покупка ${payload.priceId} прошла`);\n * queryClient.invalidateQueries(['user']);\n * });\n * ```\n *\n * Для self-cleaning логики (host эмит'а аналитики, инвалидаций кешей, гидрации\n * локального стейта) это самый прямой паттерн — компонент гарантированно\n * отпишется при unmount'е, и не нужно держать unsub-ref'ы вручную.\n */\nexport function usePaywallEvent<E extends PaywallEvent>(\n event: E,\n handler: PaywallEventHandler<E>\n): void {\n const paywall = usePaywall();\n const handlerRef = useRef(handler);\n\n // Обновляем ref на каждом render'е — следующее срабатывание события\n // подхватит свежий handler. Без отдельного useEffect, потому что синхронный\n // assign в render-фазе для ref'а корректен и не нарушает rules-of-hooks.\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!paywall) return;\n return paywall.on(event, (payload) => {\n // Cast необходим, потому что общий вариант `PaywallEventHandler` теряет\n // narrowing по `E`. handlerRef.current типизирован под конкретный E,\n // но `on()` принимает union — рантайм-shape гарантирован SDK'шным emit'ом.\n (handlerRef.current as (p: EventPayload<E>) => void)(payload);\n });\n }, [paywall, event]);\n}\n","import { useEffect, useState } from 'react';\nimport type {\n GetAccessOptions,\n PaywallAccessResult\n} from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `loading` — первый fetch ещё в полёте (или Provider не готов).\n * `ready` — есть свежий ответ; `result` гарантированно non-null.\n *\n * Сделано discriminated union'ом, чтобы хост мог сужать тип одним if-ом:\n *\n * `if (access.status === 'ready') access.result.access === 'granted'`\n */\nexport type PaywallAccessState =\n | { status: 'loading'; result: null }\n | { status: 'ready'; result: PaywallAccessResult };\n\nconst LOADING_STATE: PaywallAccessState = { status: 'loading', result: null };\n\n/**\n * Главный хук для гейтинга фич: «нужно ли блокировать фичу для этого юзера?».\n *\n * Под капотом — `paywall.getAccess(opts)` без side-effect'ов (модалка не\n * монтируется, trial-storage не двигается). На каждый `userChange` событие\n * автоматически рефетчится — после успешной покупки `has_subscription`\n * сработает мгновенно, и хост перерендерит UI без feature-lock'а.\n *\n * Bootstrap кешируется в BillingClient, так что usePaywallAccess можно дёргать\n * в любом компоненте дерева — сетевой запрос будет ровно один (или ни одного,\n * если кеш свежий).\n *\n * ```tsx\n * const access = usePaywallAccess();\n * const paywall = usePaywall();\n *\n * if (access.status === 'loading') return <Skeleton />;\n * if (access.result.access === 'blocked') {\n * return <button onClick={() => paywall?.open()}>Upgrade</button>;\n * }\n * return <PremiumFeature />;\n * ```\n *\n * Опции `opts` десериализуются по `skipTrial`/`skipVisibility` — стабильность\n * ссылки `opts` не требуется, эффект перезапустится только при реальном\n * изменении этих флагов. `signal` мы дропаем из deps (на каждый рендер у него\n * новый ref) — отмена inflight-запроса делается локально через AbortController\n * в cleanup-эффекте.\n */\nexport function usePaywallAccess(opts: GetAccessOptions = {}): PaywallAccessState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallAccessState>(LOADING_STATE);\n\n const skipTrial = opts.skipTrial === true;\n const skipVisibility = opts.skipVisibility === true;\n\n useEffect(() => {\n if (!paywall) {\n // Инстанс ушёл (Provider unmount / StrictMode cleanup) — честно\n // вернуть loading, чтобы хост не показывал устаревший result от\n // прошлого инстанса.\n setState(LOADING_STATE);\n return;\n }\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getAccess({ skipTrial, skipVisibility, signal: ctrl.signal })\n .then((result) => {\n if (cancelled || ctrl.signal.aborted) return;\n // Каждый refresh даёт новый объект — useState увидит !== и\n // ререндерит. Это ок: для гейтинга интерес представляет именно\n // `access` поле, остальное (visibility/trial snapshot'ы) — auxiliary\n // данные, которые не должны бы менять решение хоста на тех же входах.\n setState({ status: 'ready', result });\n })\n .catch(() => {\n // getAccess() имеет собственный offline-fallback и не throw'ит на\n // failed network'е — сюда мы попадаем только при abort'е, который\n // прилетает в cleanup-эффекте. Молча игнорим.\n });\n };\n\n refresh();\n\n // userChange покрывает оба источника обновления decision'а:\n // - после-checkout watcher эмит'ит userChange когда has_subscription=true\n // - manual /me refresh из хоста (paywall.billing.getUser())\n // Дополнительно слушаем purchase_completed для symmetric'ности — на\n // некоторых платежных провайдерах userChange может задержаться, а\n // purchase_completed летит мгновенно по URL-маркеру/postMessage.\n const unsubUser = paywall.on('userChange', refresh);\n const unsubPurchase = paywall.on('purchase_completed', refresh);\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsubUser();\n unsubPurchase();\n };\n }, [paywall, skipTrial, skipVisibility]);\n\n return state;\n}\n","import { useEffect, useState } from 'react';\nimport type { PaywallPrice } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * `prices` — кешированный snapshot bootstrap.prices (`null` до первого fetch'а\n * или когда инстанс ещё не готов).\n * `loading` — true пока первый запрос в полёте, после первого ответа всегда false.\n * `error` — последняя ошибка fetch'а (`null` если успешный или ещё не падал).\n *\n * Намеренно нет дискриминирующего поля типа `status: 'loading'|'ready'|'error'`\n * как в `usePaywallAccess`, потому что для прайсингов хосту обычно нужны три\n * независимые величины одновременно (показать предыдущий список + skeleton +\n * сообщение об ошибке поверх) — discriminated union тут только усложняет.\n */\nexport interface PaywallPricesState {\n prices: PaywallPrice[] | null;\n loading: boolean;\n error: Error | null;\n}\n\n/**\n * Загружает и подписывается на цены пейвола. Подходит для отдельной\n * прайсинг-страницы / pricing-карточек, где host хочет показать те же цены,\n * что и в модалке, без открытия paywall'а.\n *\n * Реализация:\n * - initial sync read через `getCachedPrices()` (если bootstrap уже в кеше\n * BillingClient'а — например, после `paywall.preload()` или предыдущего\n * open'а — цены доступны мгновенно);\n * - `useEffect` дёргает `getPrices()` для гарантированной загрузки;\n * - subscription на `ready` event — рефетч bootstrap'а на новом open()\n * может принести обновлённые цены, мы обновляем snapshot.\n *\n * ```tsx\n * const { prices, loading } = usePaywallPrices();\n * if (loading && !prices) return <Skeleton />;\n * return prices?.map((p) => <PriceCard key={p.id} price={p} />);\n * ```\n */\nexport function usePaywallPrices(): PaywallPricesState {\n const paywall = usePaywall();\n const [state, setState] = useState<PaywallPricesState>(() => ({\n prices: paywall?.getCachedPrices() ?? null,\n loading: true,\n error: null\n }));\n\n useEffect(() => {\n if (!paywall) {\n setState({ prices: null, loading: true, error: null });\n return;\n }\n\n // Sync-доступ через cached snapshot — если bootstrap уже загружен,\n // показываем цены немедленно (без флеша «loading → ready»).\n const cached = paywall.getCachedPrices();\n setState({ prices: cached, loading: cached === null, error: null });\n\n const ctrl = new AbortController();\n let cancelled = false;\n\n const refresh = () => {\n paywall\n .getPrices({ signal: ctrl.signal })\n .then((prices) => {\n if (cancelled) return;\n setState({ prices, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (cancelled || ctrl.signal.aborted) return;\n setState((prev) => ({\n prices: prev.prices,\n loading: false,\n error: error instanceof Error ? error : new Error(String(error))\n }));\n });\n };\n\n refresh();\n\n // `ready` event фаерится из открытого paywall'а с финальным bootstrap'ом —\n // если хост открыл/закрыл модалку, цены могли обновиться через\n // stale-while-revalidate. Слушаем чтобы в pricing-странице цифры не\n // расходились с тем, что юзер увидит в модалке.\n const unsub = paywall.on('ready', () => {\n const fresh = paywall.getCachedPrices();\n if (fresh) setState({ prices: fresh, loading: false, error: null });\n });\n\n return () => {\n cancelled = true;\n ctrl.abort();\n unsub();\n };\n }, [paywall]);\n\n return state;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `TrialStatus` локально не экспортируется из SDK, но мы его получаем\n// через ReturnType-инференцию по публичному методу `getTrialStatus()`. Так\n// тип всегда совпадает с тем, что реально возвращает PaywallUI, без зависимости\n// от непубличного namespace'а SDK.\ntype TrialStatus = NonNullable<ReturnType<PaywallUI['getTrialStatus']>>;\n\n/**\n * Текущий статус триала ({@link TrialStatus}) с автоматическим ре-рендером на\n * `trial_blocked` события.\n *\n * Возвращает `null`, пока триал не проверялся (хост не вызывал\n * `paywall.open()` / `paywall.getAccess()`) либо триал отключён в конфиге\n * пейвола. Сам триал-стейт живёт в storage (localStorage / chrome.storage),\n * проверяется в `paywall.open()` и в `paywall.getAccess()` — оба пути обновляют\n * in-memory snapshot, который мы здесь и читаем.\n *\n * Использовать чтобы рисовать собственный UI:\n * - «У тебя осталось 3 показа» (mode `opens`) — `status.remainingActions`;\n * - «Триал истечёт через 2 часа» (mode `time`) — `status.remainingMs`;\n * - «Триал заблокирован, оплати чтобы продолжить» — `status.blocked === true`.\n *\n * ```tsx\n * const trial = usePaywallTrial();\n * if (trial?.mode === 'opens') {\n * return <Banner>Showings left: {trial.remainingActions}</Banner>;\n * }\n * ```\n */\nexport function usePaywallTrial(): TrialStatus | null {\n const paywall = usePaywall();\n const [status, setStatus] = useState<TrialStatus | null>(() =>\n paywall?.getTrialStatus() ?? null\n );\n\n // Стабильный refresh для эффекта — отдельная функция, чтобы deps массив\n // эффекта был чистым (`[paywall]`), без useCallback-цепочек.\n const sync = useCallback(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n setStatus(paywall.getTrialStatus());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setStatus(null);\n return;\n }\n // Sync read на mount-е — getTrialStatus() мог обновиться между прошлым\n // рендером и effect'ом (например, hook вызван после первого open()-а).\n sync();\n\n // `trial_blocked` — единственный event, после которого snapshot реально\n // меняется. `trial_expired` фаерится один раз за жизнь инстанса и не\n // меняет shape статуса (статус становится `mode: 'none'` ИЛИ переходит\n // в un-blocked-режим, что и так читается через sync()).\n const unsubBlock = paywall.on('trial_blocked', sync);\n const unsubExpired = paywall.on('trial_expired', sync);\n\n return () => {\n unsubBlock();\n unsubExpired();\n };\n }, [paywall, sync]);\n\n return status;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { PaywallUI } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n// `VisibilityStatus` локально не экспортируется из SDK — получаем через\n// ReturnType от публичного `getVisibility()`. См. usePaywallTrial для тех же\n// соображений.\ntype VisibilityStatus = NonNullable<ReturnType<PaywallUI['getVisibility']>>;\n\n/**\n * Server-computed visibility-снимок ({@link VisibilityStatus}): попадает ли\n * юзер в monetization-scope пейвола (страна, девайс, ручной visibility-флаг).\n *\n * Возвращает `null`, пока bootstrap не загружен или сервер не отдал\n * `settings.visibility` (старый online без targeting-патча).\n *\n * Использовать чтобы:\n * - показать собственный fallback («сервис недоступен в вашей стране») вместо\n * модалки, когда `visible === false`;\n * - залогировать impression для аналитики страны/tier'а юзера;\n * - принять решение какой CTA рисовать, не дёргая open() и не дожидаясь\n * visibility_blocked event.\n *\n * ```tsx\n * const visibility = usePaywallVisibility();\n * if (visibility && !visibility.visible) {\n * return <SoftBlock reason={visibility.reason} />;\n * }\n * ```\n */\nexport function usePaywallVisibility(): VisibilityStatus | null {\n const paywall = usePaywall();\n const [visibility, setVisibility] = useState<VisibilityStatus | null>(() =>\n paywall?.getVisibility() ?? null\n );\n\n const sync = useCallback(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n setVisibility(paywall.getVisibility());\n }, [paywall]);\n\n useEffect(() => {\n if (!paywall) {\n setVisibility(null);\n return;\n }\n sync();\n\n // `ready` event летит после успешного bootstrap'а — там обновляется\n // `lastVisibility` в PaywallUI. `visibility_blocked` — когда блокировка\n // реально срабатывает на gate'е. Оба меняют snapshot.\n const unsubReady = paywall.on('ready', sync);\n const unsubBlocked = paywall.on('visibility_blocked', sync);\n\n return () => {\n unsubReady();\n unsubBlocked();\n };\n }, [paywall, sync]);\n\n return visibility;\n}\n","import { useEffect, type ReactNode } from 'react';\nimport type { PaywallAccessResult } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\nimport { usePaywallAccess } from '../hooks/usePaywallAccess';\n\nexport interface PaywallGateProps {\n /** Что показать, пока `getAccess()` не вернул ответ (initial fetch / Provider mount). */\n loading?: ReactNode;\n /**\n * Fallback для `blocked` ответа — обычно CTA «Upgrade». Принимает либо\n * статичный ReactNode, либо render-функцию, получающую callback\n * `open()` — удобно, чтобы кастомная кнопка сама дёргала модалку:\n *\n * ```tsx\n * fallback={({ open }) => <MyCTA onClick={open}>Upgrade</MyCTA>}\n * ```\n *\n * Если не передан — компонент рендерит `null` для blocked (host\n * полагается на `openOnBlocked` или ловит open() сам через `usePaywall`).\n */\n fallback?: ReactNode | ((args: BlockedRenderArgs) => ReactNode);\n /**\n * Автоматически дёргать `paywall.open()` сразу как только access перешёл в\n * blocked. Удобно для feature-разделителей вида «нажми и попадёшь на\n * paywall»: компонент сам открывает модалку, не нужно писать onClick.\n *\n * По умолчанию `false` — большинство хостов хотят сначала показать\n * объясняющий CTA, а модалку открывать по клику. Включать осознанно.\n */\n openOnBlocked?: boolean;\n /** Премиум-контент. Рендерится только когда access=granted. */\n children: ReactNode;\n}\n\nexport interface BlockedRenderArgs {\n result: Extract<PaywallAccessResult, { access: 'blocked' }>;\n open: () => void;\n}\n\n/**\n * Декларативная обёртка над {@link usePaywallAccess} + {@link usePaywall}.open().\n *\n * Три состояния:\n * - `loading` (первый fetch / Provider не готов) — рендерим `props.loading`;\n * - `granted` (есть подписка / visibility / trial) — рендерим `children`;\n * - `blocked` — рендерим `fallback` (если задан) и опционально дёргаем\n * `paywall.open()` при `openOnBlocked={true}`.\n *\n * ```tsx\n * <PaywallGate\n * loading={<Skeleton />}\n * fallback={({ open }) => <button onClick={open}>Upgrade</button>}\n * >\n * <PremiumFeature />\n * </PaywallGate>\n * ```\n *\n * Для нестандартных сценариев (показать \"Try free trial\" вместо upgrade,\n * комбинировать с собственным auth-flow'ом) использовать\n * {@link usePaywallAccess} напрямую — gate решает 80% кейсов, не пытаясь\n * стать конфигурируемым на каждый чих.\n */\nexport function PaywallGate(props: PaywallGateProps): JSX.Element | null {\n const paywall = usePaywall();\n const access = usePaywallAccess();\n\n // `openOnBlocked` — side-effect, поэтому в useEffect. Зависим от access\n // через идентификатор `result.access`, а не от объекта целиком, чтобы\n // не дёргать open() на каждом refresh-е getAccess'а с тем же blocked-итогом.\n const isBlocked =\n access.status === 'ready' && access.result.access === 'blocked';\n const shouldAutoOpen = props.openOnBlocked === true && isBlocked;\n\n useEffect(() => {\n if (shouldAutoOpen && paywall) paywall.open();\n }, [shouldAutoOpen, paywall]);\n\n if (access.status === 'loading') {\n return <>{props.loading ?? null}</>;\n }\n\n if (access.result.access === 'granted') {\n return <>{props.children}</>;\n }\n\n // blocked\n const fallback = props.fallback;\n if (typeof fallback === 'function') {\n return (\n <>\n {fallback({\n result: access.result,\n open: () => paywall?.open()\n })}\n </>\n );\n }\n return <>{fallback ?? null}</>;\n}\n","import {\n forwardRef,\n type ButtonHTMLAttributes,\n type ReactElement,\n type ReactNode\n} from 'react';\nimport type { OpenOptions } from '@monetize.software/sdk';\nimport { usePaywall } from '../hooks/usePaywall';\n\n/**\n * Параметры открытия пейвола, проксируются в `paywall.open(opts)`.\n * Любые поля {@link OpenOptions} применимы: `identity`, `renew`, `skipTrial`,\n * `skipVisibility`.\n */\ntype OpenProps = OpenOptions;\n\ninterface CommonProps extends OpenProps {\n /** Что открывать: layout (default), support, auth-gate (signin),\n * signup-форма. 'auth' эквивалентен 'signin' (исторически — openAuth\n * дефолтит в signin-mode). Для анонимного signin используй\n * `usePaywall().signInAnonymously()` напрямую — headless без модалки. */\n mode?: 'paywall' | 'support' | 'auth' | 'signin' | 'signup';\n /** Render-prop для полного контроля над элементом-триггером. Когда задан,\n * все обычные `<button>`-пропсы (children, type, и т.д.) игнорируются. */\n render?: (args: PaywallButtonRenderArgs) => ReactElement;\n}\n\nexport interface PaywallButtonRenderArgs {\n /** Открыть пейвол согласно `mode` + переданным opts. */\n open: () => void;\n /** Готов ли инстанс PaywallUI. До mount-а Provider'а / на SSR — `false`. */\n ready: boolean;\n}\n\n/**\n * Props собственно `<button>`-рендера. Любые HTML-атрибуты — `disabled`,\n * `className`, `aria-label`, `type`, и т.д. — пробрасываются на нативный\n * элемент. `onClick` объединяется с нашим open()-хендлером (мы вызываем\n * наш первым, потом ваш — чтобы хост мог prevent'ить через event.preventDefault).\n */\ntype ButtonRenderProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof OpenProps | 'children'\n> & {\n children?: ReactNode;\n};\n\nexport type PaywallButtonProps = CommonProps & ButtonRenderProps;\n\n/**\n * Сахар над `usePaywall().open()`. Кнопка по умолчанию рендерится как\n * нативный `<button>` со всеми твоими className/style/disabled, но при нужде\n * можно передать `render` для произвольного элемента (Radix-style asChild\n * паттерн через render-prop).\n *\n * ```tsx\n * // обычный кейс\n * <PaywallButton className=\"btn-primary\" renew>\n * Renew subscription\n * </PaywallButton>\n *\n * // custom-элемент\n * <PaywallButton render={({ open, ready }) => (\n * <MyFancyButton onClick={open} disabled={!ready}>Upgrade</MyFancyButton>\n * )} />\n *\n * // саппорт-форма вместо тарифов\n * <PaywallButton mode=\"support\">Need help?</PaywallButton>\n * ```\n *\n * До mount-а Provider'а или на SSR кнопка рендерится с `disabled=true`\n * (через CSS-pseudo `[aria-busy]` хост может стилизовать loading-state) —\n * клик в этот момент no-op, потому что инстанса PaywallUI ещё нет.\n */\nexport const PaywallButton = forwardRef<HTMLButtonElement, PaywallButtonProps>(\n function PaywallButton(props, ref) {\n const paywall = usePaywall();\n const {\n mode = 'paywall',\n identity,\n renew,\n skipTrial,\n skipVisibility,\n render,\n onClick,\n disabled,\n ...buttonProps\n } = props;\n\n const ready = paywall !== null;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n switch (mode) {\n case 'support':\n paywall.openSupport(openOpts);\n return;\n case 'auth':\n case 'signin':\n paywall.openSignin(openOpts);\n return;\n case 'signup':\n paywall.openSignup(openOpts);\n return;\n default:\n paywall.open(openOpts);\n }\n };\n\n if (render) {\n return render({ open, ready });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready}\n aria-busy={!ready ? true : undefined}\n onClick={(event) => {\n // Наш handler первым — host через event.preventDefault() ничего\n // не остановит, потому что open() уже стрельнул. Это намеренно:\n // открытие пейвола не должно зависеть от того, забыл ли хост\n // вернуть `false` из своего analytics-handler'а. Если нужен\n // префлайт-чек — паттерн через `render`-prop, там полный контроль.\n open();\n onClick?.(event);\n }}\n {...buttonProps}\n />\n );\n }\n);\n","import { forwardRef } from 'react';\nimport { PaywallButton, type PaywallButtonProps } from './PaywallButton';\n\nexport type PaywallSupportButtonProps = Omit<PaywallButtonProps, 'mode'>;\n\n/**\n * Сахар над `<PaywallButton mode=\"support\">`. Самостоятельная компонента, а\n * не пресет prop'а, для discoverability — название говорит за себя, и в\n * больших layout-ах легче видеть, где саппорт, а где основной upgrade-CTA.\n *\n * ```tsx\n * <PaywallSupportButton className=\"link\">Help</PaywallSupportButton>\n * ```\n */\nexport const PaywallSupportButton = forwardRef<\n HTMLButtonElement,\n PaywallSupportButtonProps\n>(function PaywallSupportButton(props, ref) {\n return <PaywallButton {...props} mode=\"support\" ref={ref} />;\n});\n"],"names":["PaywallContext","createContext","PaywallProviderMarker","PaywallProvider","props","externalInstance","options","paywall","setPaywall","useState","useEffect","created","PaywallUI","jsx","usePaywall","hasProvider","useContext","SSR_SNAPSHOT","usePaywallState","subscribe","useCallback","cb","getSnapshot","useSyncExternalStore","LOADING","GUEST","usePaywallUser","cacheRef","useRef","unsubUser","unsubAuth","user","session","prev","next","getServerSnapshot","usePaywallEvent","event","handler","handlerRef","payload","LOADING_STATE","usePaywallAccess","opts","state","setState","skipTrial","skipVisibility","ctrl","cancelled","refresh","result","unsubPurchase","usePaywallPrices","cached","prices","error","unsub","fresh","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubReady","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","openOpts","open","PaywallSupportButton"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monetize.software/sdk-react",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.0.0-alpha.9",
4
4
  "description": "React bindings for @monetize.software/sdk — Provider, hooks and declarative components. Works with the web SDK and the extension SDK (any drop-in compatible PaywallUI).",
5
5
  "type": "module",
6
6
  "sideEffects": false,