@monetize.software/sdk-react 3.0.0-alpha.2 → 3.0.0-alpha.21
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 +76 -40
- package/dist/PaywallProvider.d.ts +1 -1
- package/dist/components/PaywallButton.d.ts +24 -3
- package/dist/components/PaywallButton.d.ts.map +1 -1
- package/dist/components/PaywallGate.d.ts +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/hooks/usePaywall.d.ts +1 -1
- package/dist/hooks/usePaywallAccess.d.ts +1 -1
- package/dist/hooks/usePaywallEvent.d.ts +1 -1
- package/dist/hooks/usePaywallOffer.d.ts +42 -0
- package/dist/hooks/usePaywallOffer.d.ts.map +1 -0
- package/dist/hooks/usePaywallOffers.d.ts +16 -0
- package/dist/hooks/usePaywallOffers.d.ts.map +1 -0
- package/dist/hooks/usePaywallPrices.d.ts +1 -1
- package/dist/hooks/usePaywallState.d.ts +1 -1
- package/dist/hooks/usePaywallState.d.ts.map +1 -1
- package/dist/hooks/usePaywallTrial.d.ts +1 -1
- package/dist/hooks/usePaywallUser.d.ts +41 -21
- package/dist/hooks/usePaywallUser.d.ts.map +1 -1
- package/dist/hooks/usePaywallVisibility.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +228 -146
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs.map
CHANGED
|
@@ -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, anon-gate. */\n mode?: 'paywall' | 'support' | 'auth' | 'anon';\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 paywall.openAuth(openOpts);\n return;\n case 'anon':\n paywall.openAnonGate(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/usePaywallOffer.ts","../src/hooks/usePaywallOffers.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 = {\n open: false,\n view: null,\n error: null,\n processing: false\n};\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 { useEffect, useRef, useState } from 'react';\nimport type { ResolvedOffer } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Reactive resolved offer for a given price.\n *\n * Returns the same shape as `paywall.getOfferForPrice(priceId)`, plus a live\n * countdown — re-renders every second while there's a positive `remainingMs`,\n * stops ticking when the offer expires (one final re-render brings the value\n * to `null`). Re-fetches on `ready` event too, so a bootstrap refresh that\n * brings new offers reflects immediately.\n *\n * Returns `null` when:\n * - Provider isn't mounted yet (SSR / pre-mount);\n * - bootstrap hasn't loaded the offers list;\n * - no offer targets this price (no targeted match, no global offer);\n * - the matching offer is a `duration_minutes`-only timer that hasn't been\n * started yet (i.e. the paywall hasn't been opened by this user). The\n * renderer writes the start on first paywall view — refreshing the page\n * after the first view will then surface the countdown here.\n * - the matching offer has already expired.\n *\n * ```tsx\n * const offer = usePaywallOffer(price.id);\n *\n * if (!offer) return <span>{formatAmount(price.amount)}</span>;\n *\n * const discounted = price.amount * (1 - offer.discountPercent / 100);\n * return (\n * <>\n * <s>{formatAmount(price.amount)}</s>\n * <strong>{formatAmount(discounted)}</strong>\n * <Badge>-{offer.discountPercent}%</Badge>\n * {offer.remainingMs !== null && <Countdown ms={offer.remainingMs} />}\n * </>\n * );\n * ```\n *\n * Implementation: a single `setInterval(1000)` ticks while there's a live\n * countdown. The PaywallUI handle is read on each tick, so a Provider re-mount\n * doesn't leave a stale closure.\n */\nexport function usePaywallOffer(priceId: string): ResolvedOffer | null {\n const paywall = usePaywall();\n const [snapshot, setSnapshot] = useState<ResolvedOffer | null>(() =>\n paywall ? paywall.getOfferForPrice(priceId) : null\n );\n\n // Stable ref to the latest priceId so a re-keyed interval-callback always\n // resolves against the current price (avoids re-creating the timer on every\n // identical-id rerender — only the close-over priceId would have changed).\n const priceIdRef = useRef(priceId);\n priceIdRef.current = priceId;\n\n useEffect(() => {\n if (!paywall) {\n setSnapshot(null);\n return;\n }\n\n let cancelled = false;\n const refresh = () => {\n if (cancelled) return;\n const next = paywall.getOfferForPrice(priceIdRef.current);\n setSnapshot(next);\n return next;\n };\n\n // Initial sync (covers Provider mount + price_id changes).\n const current = refresh();\n\n const unsubReady = paywall.on('ready', () => refresh());\n\n // Tick only if there's actually a live countdown — for offers without\n // expiry we just react to `ready` events.\n let interval: ReturnType<typeof setInterval> | null = null;\n if (current && current.remainingMs !== null) {\n interval = setInterval(() => {\n const next = refresh();\n if (!next || next.remainingMs === null || next.remainingMs <= 0) {\n if (interval) clearInterval(interval);\n interval = null;\n }\n }, 1000);\n }\n\n return () => {\n cancelled = true;\n unsubReady();\n if (interval) clearInterval(interval);\n };\n }, [paywall, priceId]);\n\n return snapshot;\n}\n","import { useCallback, useSyncExternalStore } from 'react';\nimport type { PaywallOffer } from '@monetize.software/sdk';\nimport { usePaywall } from './usePaywall';\n\n/**\n * Cached offers list, refreshed on every `ready` event (= bootstrap refresh).\n *\n * Returns `null` before bootstrap loads, then the server-filtered offer list\n * (server-side targeting on countries / emails / mode is already applied).\n * Client code is still responsible for `price_id` matching (use\n * `findApplicableOffer` from `@monetize.software/sdk` or the higher-level\n * `usePaywallOffer(priceId)` hook).\n *\n * Mostly useful for hosts that want to iterate offers manually (e.g. render\n * a global \"Limited offer\" banner above the page). For per-price strike-\n * through + countdown, `usePaywallOffer(priceId)` is the right primitive.\n */\nexport function usePaywallOffers(): PaywallOffer[] | null {\n const paywall = usePaywall();\n\n const subscribe = useCallback(\n (cb: () => void): (() => void) => {\n if (!paywall) return () => {};\n return paywall.on('ready', () => cb());\n },\n [paywall]\n );\n\n const getSnapshot = useCallback((): PaywallOffer[] | null => {\n return paywall ? paywall.getCachedOffers() : null;\n }, [paywall]);\n\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction getServerSnapshot(): PaywallOffer[] | null {\n return null;\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';\nimport { usePaywallState } from '../hooks/usePaywallState';\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 /** Direct-checkout: при заданном `priceId` клик вызывает\n * `paywall.checkout(priceId, opts)` минуя layout с тарифами. `mode`\n * при этом игнорируется. Layout-flow (`mode='paywall'`, дефолт) и\n * direct-checkout — взаимоисключающие: либо юзер выбирает план в\n * модалке, либо хост уже выбрал и ведёт в checkout. См.\n * `PaywallUI.checkout()` про preauth/already-paid поведение. */\n priceId?: string;\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 /** Direct-checkout в процессе headless bootstrap+createCheckout (только когда\n * у кнопки задан `priceId`). Render-prop может показать спиннер прямо на\n * своей кнопке и задизейблить её, чтобы юзер не кликал ещё раз. Для\n * не-priceId-режимов всегда false. */\n processing: 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 * // direct-checkout: хост уже выбрал план в своём UI (pricing-карточки),\n * // клик ведёт прямо в провайдера, минуя layout с тарифами.\n * <PaywallButton priceId={price.id} className=\"btn-primary\">\n * Get this plan\n * </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 state = usePaywallState();\n const {\n mode = 'paywall',\n priceId,\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 // Direct-checkout (priceId-режим): пока SDK делает headless bootstrap +\n // createCheckout, `state.processing` истинно. Дизейблим кнопку и\n // показываем aria-busy — host получает «I clicked, SDK is working»\n // фидбек без модалки-флеша. Для не-priceId-режимов (modal-flow) этот\n // флаг всегда false: модалка появляется мгновенно и сама показывает\n // LoadingView, никакой busy на кнопке не нужен.\n const processing = !!priceId && state.processing;\n\n const openOpts: OpenOptions = { identity, renew, skipTrial, skipVisibility };\n\n const open = (): void => {\n if (!paywall) return;\n // priceId побеждает mode: direct-checkout — отдельная семантика\n // (host уже выбрал план в своём UI), `mode` не имеет смысла комбинировать\n // с конкретной ценой.\n if (priceId) {\n paywall.checkout(priceId, openOpts);\n return;\n }\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, processing });\n }\n\n return (\n <button\n ref={ref}\n type=\"button\"\n disabled={disabled || !ready || processing}\n aria-busy={!ready || processing ? 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","usePaywallOffer","priceId","snapshot","setSnapshot","priceIdRef","current","unsubReady","interval","usePaywallOffers","usePaywallTrial","status","setStatus","sync","unsubBlock","unsubExpired","usePaywallVisibility","visibility","setVisibility","unsubBlocked","PaywallGate","access","isBlocked","shouldAutoOpen","Fragment","fallback","PaywallButton","forwardRef","ref","mode","identity","renew","render","onClick","disabled","buttonProps","ready","processing","openOpts","open","PaywallSupportButton"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
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';
|
|
8
|
+
export { usePaywallOffer } from './hooks/usePaywallOffer';
|
|
9
|
+
export { usePaywallOffers } from './hooks/usePaywallOffers';
|
|
8
10
|
export { usePaywallTrial } from './hooks/usePaywallTrial';
|
|
9
11
|
export { usePaywallVisibility } from './hooks/usePaywallVisibility';
|
|
10
12
|
export { PaywallGate, type PaywallGateProps, type BlockedRenderArgs } from './components/PaywallGate';
|
|
11
13
|
export { PaywallButton, type PaywallButtonProps, type PaywallButtonRenderArgs } from './components/PaywallButton';
|
|
12
14
|
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 '
|
|
15
|
+
export type { PaywallUI, PaywallUIOptions, PaywallEvent, PaywallEventHandler, PaywallStateSnapshot, PaywallAccessResult, GetAccessOptions, OpenOptions, AnalyticsOptions, PaywallUser, PaywallPrice, PaywallBootstrap, PaywallSettings, PaywallOffer, Identity, AuthSession, ResolvedOffer } from '@monetize.software/sdk';
|
|
14
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,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,EACX,aAAa,EACd,MAAM,wBAAwB,CAAC"}
|