interface User { _id: string; firstName: string; lastName: string; email: string; credits: number; roles: UserRole[]; isVerified: boolean; isActive: boolean; linkedPublisherId?: string; } type UserRole = 'consumer' | 'publisher' | 'admin'; interface Comment { _id: string; threadId: string; postId?: string | null; parentCommentId: string | null; authorId: string; content: string; isActive: boolean; mentions: string[]; likeCount: number; hasLiked: boolean; createdAt: string; updatedAt: string; author?: CommentAuthor; replies?: Comment[]; hiddenBy?: string | null; hiddenReason?: string | null; moderatedAt?: string | null; } interface CommentAuthor { _id: string; firstName: string; lastName: string; profilePicture?: string; } type CommentSortBy = 'TOP' | 'NEWEST' | 'TIPPED_MOST'; /** * Loosely-typed ReactDOM adapter — matches both React 18 (`createRoot`) and * React 16/17 (`render`) without pulling React into the SDK's own bundle. */ interface ReactDOMAdapter { /** React 18+ */ createRoot?(container: Element): { render(node: unknown): void; unmount(): void; }; /** React 16/17 */ render?(node: unknown, container: Element, callback?: () => void): void; } interface SDKConfig { /** Publisher API key from the Content Credits admin panel */ apiKey: string; /** Full URL of the article page. Defaults to window.location.href */ articleUrl?: string; /** CSS selector for the element containing the premium content to gate */ contentSelector?: string; /** Number of visible paragraphs before the paywall kicks in. Default: 2 */ teaserParagraphs?: number; /** Whether to enable the comment widget. Default: true */ enableComments?: boolean; /** * Whether to fire the post-discovery beacon (`POST /posts/observe`) on page * load. Scrapes `og:*` / JSON-LD / `` metadata and reports it * alongside the canonical URL — this is also the view event for analytics * (design doc §5.1, §7.1). Disable if you're handling discovery yourself * (e.g. via the WordPress plugin's server-side push) or to opt out of * anonymous view tracking entirely. * * Default: `true` */ enableBeacon?: boolean; /** Visual theme options */ theme?: SDKTheme; /** * Paywall display mode. * - `'inline'` — panel sits below the teaser content in the page flow (original behaviour) * - `'overlay'` — full-width panel that renders below the gated content (default) * * Default: `'overlay'` */ paywallMode?: 'inline' | 'overlay'; /** * Your ReactDOM instance. Required when using `renderPaywall`. * Supports React 18 (`createRoot`) and React 16/17 (`render`). * * @example * import ReactDOM from 'react-dom/client'; // React 18 * ContentCredits.init({ reactDOM, renderPaywall: ({ mountSdkButton }) => <MyPaywall ref={mountSdkButton} /> }); */ reactDOM?: ReactDOMAdapter; /** * Custom label for the SDK's unlock/purchase button. * Defaults to `'Unlock · N credits'` (when price is known) or `'Unlock article'`. * * @example * unlockButtonLabel: 'Unlock Just This Story' */ unlockButtonLabel?: string; /** * Override the default copy shown in the SDK's built-in paywall states. * All fields are optional — only supply the strings you want to change. * * @example * paywallCopy: { * loginHeading: 'Read the full story', * loginDetail: 'Sign in to access this article with your credits.', * } */ paywallCopy?: { /** Heading shown in the login state. Default: 'This article requires a subscription' */ loginHeading?: string; /** Detail shown in the login state. Default: 'Sign in to your Content Credits account to unlock this article.' */ loginDetail?: string; /** Heading shown in the purchase state. Default: 'Unlock this article' */ purchaseHeading?: string; /** Detail shown in the purchase state. Default: 'Use your Content Credits balance to instantly access this article.' */ purchaseDetail?: string; /** Heading shown when credits are insufficient. Default: 'Not enough credits' */ insufficientHeading?: string; }; /** * Full-control paywall render function. The publisher renders the entire * modal content and decides where the SDK's action button appears by passing * `mountSdkButton` as a React ref callback to any container element. * * The SDK mounts its state-aware button (sign in / unlock / top up) and the * "Powered by Content Credits" line inside whichever element receives the ref. * Requires `reactDOM` to also be set. * * @example * renderPaywall: ({ mountSdkButton }) => ( * <div> * <h2>Donate to access this story.</h2> * <button onClick={openDonation}>See Donation Options</button> * <div ref={mountSdkButton} /> * </div> * ) */ renderPaywall?: (props: { mountSdkButton: (el: HTMLElement | null) => void; }) => { type: unknown; props: unknown; key?: unknown; }; /** * Whether to show the heading and detail text in the SDK's built-in paywall * states (login, purchase, insufficient). Set to `false` when your layout * already provides article context (e.g. via `renderPaywall`). * * Default: `true` */ showHeadings?: boolean; /** Called when the user is granted access to the article */ onAccessGranted?: () => void; /** * Called on every state change. Receives the full state snapshot. * Use this as the single reactive hook to drive a custom UI instead of * calling `cc.subscribe()` separately. */ onStateChange?: (state: SDKState) => void; /** * Called once the SDK has finished its first access check. * Equivalent to listening for the `ready` event. */ onReady?: (state: SDKState) => void; /** * Called when the paywall is reached and the user is **not logged in**. * Render your login UI here and call `cc.login()` from your button. */ onLoginRequired?: () => void; /** * Called when the user is logged in but has **not yet purchased** this article. * Render your unlock/purchase UI here and call `cc.purchase()` from your button. */ onPurchaseRequired?: (info: { requiredCredits: number | null; creditBalance: number | null; }) => void; /** * Called when the user is logged in but their credit balance is **below** the * article price. Render a top-up UI here and call `cc.buyMoreCredits()`. */ onInsufficientCredits?: (info: { required: number; available: number; }) => void; /** * Called after a successful article purchase. * Equivalent to listening for the `article:purchased` event. */ onPurchased?: (info: { creditsSpent: number; remainingBalance: number; }) => void; /** * Called when a user logs in. * Equivalent to listening for the `auth:login` event. */ onUserLogin?: (user: User) => void; /** * Called when the user logs out. * Equivalent to listening for the `auth:logout` event. */ onUserLogout?: () => void; /** * Called when any SDK error occurs. * Equivalent to listening for the `error` event. */ onError?: (info: { message: string; error?: unknown; }) => void; /** Enable verbose debug logging */ debug?: boolean; /** * Headless mode — disables all built-in DOM manipulation and UI rendering. * * When `true` the SDK will NOT: * - hide / reveal the premium content element * - inject the paywall overlay or gradient fade * * Instead it exposes reactive state (via `subscribe()`) and action methods * (`login()`, `purchase()`, `buyMoreCredits()`) so you can build a fully * custom paywall UI in React, Vue, Svelte, or plain JS. * * Default: `false` */ headless?: boolean; } interface SDKTheme { /** Primary brand colour used for buttons and accents. Default: '#44C678' */ primaryColor?: string; /** Font family for all SDK UI elements */ fontFamily?: string; /** * Background colour of the modal backdrop/scrim. * Accepts any valid CSS colour value. * Default: 'rgba(0, 0, 0, 0.45)' */ backdropColor?: string; /** * Fill colour for the SDK's own action buttons (Sign in, Unlock, Top up). * Intentionally separate from `primaryColor` so publishers can brand their * own slot buttons differently from the Content Credits controls. * Default: '#44C678' (Content Credits green) */ sdkButtonColor?: string; } interface SDKState { isLoading: boolean; isExtensionAvailable: boolean; isLoggedIn: boolean; hasAccess: boolean; isLoaded: boolean; user: User | null; creditBalance: number | null; requiredCredits: number | null; } interface SDKEventMap { ready: { state: SDKState; }; 'auth:login': { user: User; }; 'auth:logout': Record<string, never>; 'paywall:shown': Record<string, never>; 'paywall:hidden': Record<string, never>; 'article:purchased': { creditsSpent: number; remainingBalance: number; }; 'credits:insufficient': { required: number; available: number; }; 'comment:posted': { comment: Comment; }; 'comment:liked': { commentId: string; hasLiked: boolean; }; 'comment:deleted': { commentId: string; }; error: { message: string; error?: unknown; }; } type SDKEventName = keyof SDKEventMap; type SDKEventHandler<K extends SDKEventName> = (payload: SDKEventMap[K]) => void; /** * Content Credits JS SDK v2 * * Drop-in paywall and comments for any website. * * CDN (script tag): * <script src="https://cdn.contentcredits.com/sdk/v2/content-credits.umd.min.js"></script> * <script> * ContentCreditsSDK.init({ apiKey: 'YOUR_API_KEY', contentSelector: '#article-body' }); * </script> * * npm: * import { ContentCredits } from '@contentcredits/sdk'; * ContentCredits.init({ apiKey: 'YOUR_API_KEY', contentSelector: '#article-body' }); */ declare class ContentCredits { private readonly config; private readonly state; private readonly emitter; private readonly client; private readonly creditsApi; private readonly commentsApi; private readonly postsApi; private paywallModule; private commentsModule; private constructor(); /** * Initialise the SDK and immediately start the access check. * * @example * const cc = ContentCredits.init({ * apiKey: 'pub_abc123', * contentSelector: '#premium-content', * }); */ static init(rawConfig: SDKConfig): ContentCredits; private _start; /** * Subscribe to state changes. The callback receives the full state snapshot * every time any field changes. Returns an unsubscribe function. * * Primarily useful in **headless mode** — lets you drive your own UI from * reactive state without polling `getState()`. * * @example * const unsubscribe = cc.subscribe((state) => { * if (state.hasAccess) showFullContent(); * else showPaywall(state); * }); */ subscribe(fn: (state: SDKState) => void): () => void; /** * Trigger the login flow programmatically. * * - Desktop: opens a popup window to the Content Credits auth page. * - Mobile: performs a full-page redirect. * - Extension: delegates to the browser extension. * * Primarily useful in **headless mode** where you render your own "Login" * button and call this from its `onClick` handler. */ login(): Promise<void>; /** * Trigger the article purchase flow programmatically. * * Deducts the required credits from the user's balance and, on success, * updates `state.hasAccess` to `true` and emits `article:purchased`. * * If the user is not logged in, this automatically opens the login flow * first, then proceeds with the purchase. * * Primarily useful in **headless mode** where you render your own "Unlock" * button and call this from its `onClick` handler. */ purchase(): Promise<void>; /** * Open the Content Credits dashboard in a new tab so the user can top up * their credit balance. * * Primarily useful in **headless mode** when `state.creditBalance` is lower * than `state.requiredCredits`. */ buyMoreCredits(): void; /** Subscribe to an SDK event. Returns an unsubscribe function. */ on<K extends SDKEventName>(event: K, handler: SDKEventHandler<K>): () => void; /** Unsubscribe from an SDK event. */ off<K extends SDKEventName>(event: K, handler: SDKEventHandler<K>): void; /** Get a snapshot of the current SDK state. */ getState(): SDKState; /** Programmatically trigger an article access check. */ checkAccess(): Promise<void>; /** Open the comment panel programmatically. */ openComments(): void; /** Close the comment panel programmatically. */ closeComments(): void; /** Check if the user is currently authenticated. */ isLoggedIn(): boolean; /** * Return the current access token, or null if not authenticated. * * Use this to call your own server-side API routes that need to verify * the user's identity with the Content Credits API before returning * protected content — avoids ever sending premium content to the browser * before access is confirmed. */ getToken(): string | null; /** * Log the current user out. * * Revokes the refresh token on the server (best-effort), clears all local * auth state, resets SDK state, and emits `auth:logout`. */ logout(): Promise<void>; /** Tear down the SDK — removes all UI, event listeners, and stored state. */ destroy(): void; /** SDK version string. */ static get version(): string; } export { ContentCredits }; export type { Comment, CommentSortBy, SDKConfig, SDKEventHandler, SDKEventName, SDKState, User };