@contentcredits/sdk 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -13,9 +13,10 @@ Drop-in paywall and threaded comment system for any website. Add credit-based ar
13
13
  ## What it does
14
14
 
15
15
  - **Paywall** — hides premium content behind a credit gate using a CSS selector. Reveals it instantly when the reader pays. No server-side content splitting needed.
16
+ - **Customisable top slot** — inject your own content (React widget, donation banner, structured items) above the SDK's unlock button.
16
17
  - **Comments** — threaded comment panel with likes, edit, delete, and sorting. Rendered in a Shadow DOM so it never conflicts with your CSS.
17
18
  - **Auth** — popup-based login on desktop, redirect flow on mobile. Tokens stored in memory (never cookies).
18
- - **Extension support** — detects the Content Credits Chrome extension for a one-click experience.
19
+ - **Extension support** — detects the Content Credits Chrome extension for a one-click experience, with automatic fallback if the extension service worker is unresponsive.
19
20
 
20
21
  ---
21
22
 
@@ -28,7 +29,7 @@ npm install @contentcredits/sdk
28
29
  Or via CDN (no build step):
29
30
 
30
31
  ```html
31
- <script src="https://cdn.jsdelivr.net/npm/@contentcredits/sdk@2.0.0/dist/content-credits.umd.min.js"></script>
32
+ <script src="https://cdn.jsdelivr.net/npm/@contentcredits/sdk@2.12.0/dist/content-credits.umd.min.js"></script>
32
33
  ```
33
34
 
34
35
  ---
@@ -44,7 +45,7 @@ Or via CDN (no build step):
44
45
  </div>
45
46
 
46
47
  <!-- Load and initialise the SDK -->
47
- <script src="https://cdn.jsdelivr.net/npm/@contentcredits/sdk@2.0.0/dist/content-credits.umd.min.js"></script>
48
+ <script src="https://cdn.jsdelivr.net/npm/@contentcredits/sdk@2.12.0/dist/content-credits.umd.min.js"></script>
48
49
  <script>
49
50
  ContentCreditsSDK.ContentCredits.init({
50
51
  apiKey: 'pub_YOUR_API_KEY',
@@ -59,7 +60,7 @@ Or via CDN (no build step):
59
60
 
60
61
  ```html
61
62
  <script
62
- src="https://cdn.jsdelivr.net/npm/@contentcredits/sdk@2.0.0/dist/content-credits.umd.min.js"
63
+ src="https://cdn.jsdelivr.net/npm/@contentcredits/sdk@2.12.0/dist/content-credits.umd.min.js"
63
64
  data-api-key="pub_YOUR_API_KEY"
64
65
  data-content-selector="#premium-content"
65
66
  data-teaser-paragraphs="2"
@@ -94,13 +95,90 @@ cc.on('paywall:hidden', () => {
94
95
  | `teaserParagraphs` | `number` | `2` | Paragraphs to show before the paywall |
95
96
  | `enableComments` | `boolean` | `true` | Show the comment widget |
96
97
  | `articleUrl` | `string` | `location.href` | Canonical URL of the article |
98
+ | `paywallMode` | `'overlay' \| 'inline'` | `'overlay'` | Paywall layout — overlay sits directly below the teaser; inline is the legacy flow-based panel |
99
+ | `paywallTopSlot` | see below | — | Content rendered above the SDK's unlock button — accepts a React element, structured items, `HTMLElement`, or factory function |
100
+ | `reactDOM` | `ReactDOMAdapter` | — | Your `ReactDOM` instance — required when `paywallTopSlot` is a React element |
97
101
  | `theme.primaryColor` | `string` | `'#44C678'` | Brand colour for buttons |
98
102
  | `theme.fontFamily` | `string` | system UI | Font for all SDK UI |
103
+ | `headless` | `boolean` | `false` | Disable all built-in DOM/UI — manage everything yourself via state and callbacks |
99
104
  | `onAccessGranted` | `() => void` | — | Fires when content is unlocked |
100
105
  | `debug` | `boolean` | `false` | Verbose console logging |
101
106
 
102
107
  ---
103
108
 
109
+ ## Paywall top slot
110
+
111
+ The `paywallTopSlot` lets you inject custom content — a donation widget, promo banner, or anything else — above the SDK's own unlock button. The slot sits inside the SDK's Shadow DOM so your styles are isolated from the host page.
112
+
113
+ ### React widget (recommended for React apps)
114
+
115
+ Pass your ReactDOM instance alongside the JSX element. Works with React 18 (`createRoot`) and React 16/17 (`render`).
116
+
117
+ ```tsx
118
+ import ReactDOM from 'react-dom/client'; // React 18
119
+ import { ContentCredits } from '@contentcredits/sdk';
120
+ import { DonationWidget } from './DonationWidget';
121
+
122
+ ContentCredits.init({
123
+ apiKey: 'pub_YOUR_API_KEY',
124
+ reactDOM,
125
+ paywallTopSlot: <DonationWidget />,
126
+ });
127
+ ```
128
+
129
+ ### Structured items
130
+
131
+ The SDK renders and styles these consistently inside its Shadow DOM:
132
+
133
+ ```ts
134
+ ContentCredits.init({
135
+ apiKey: 'pub_YOUR_API_KEY',
136
+ paywallTopSlot: [
137
+ { type: 'heading', content: 'Donate to access this story.' },
138
+ { type: 'text', content: 'Donate now for unlimited stories. Cancel anytime.' },
139
+ { type: 'button', content: 'See Donation Options', variant: 'primary', onClick: () => openDonateFlow() },
140
+ ],
141
+ });
142
+ ```
143
+
144
+ Available item types:
145
+
146
+ | `type` | Description | Extra props |
147
+ |--------|-------------|-------------|
148
+ | `heading` | Large bold heading | `content` |
149
+ | `subheading` | Medium bold text | `content` |
150
+ | `text` | Body copy | `content` |
151
+ | `button` | Styled button | `content`, `variant` (`primary` \| `secondary` \| `outline`), `onClick` |
152
+ | `divider` | Horizontal rule with optional label | `content` |
153
+
154
+ ### HTMLElement
155
+
156
+ ```ts
157
+ const banner = document.createElement('div');
158
+ banner.innerHTML = '<strong>Support independent journalism</strong>';
159
+
160
+ ContentCredits.init({
161
+ apiKey: 'pub_YOUR_API_KEY',
162
+ paywallTopSlot: banner,
163
+ });
164
+ ```
165
+
166
+ ### Factory function
167
+
168
+ Full control — receives the slot container element and mounts whatever you need:
169
+
170
+ ```ts
171
+ ContentCredits.init({
172
+ apiKey: 'pub_YOUR_API_KEY',
173
+ paywallTopSlot: (container) => {
174
+ // vanilla JS, Vue, Svelte — anything goes
175
+ container.innerHTML = `<p>Support us to keep reading.</p>`;
176
+ },
177
+ });
178
+ ```
179
+
180
+ ---
181
+
104
182
  ## Events
105
183
 
106
184
  ```ts
@@ -128,39 +206,91 @@ const cc = ContentCredits.init(config);
128
206
 
129
207
  cc.on(event, handler) // subscribe — returns unsubscribe fn
130
208
  cc.off(event, handler) // unsubscribe
209
+ cc.subscribe(fn) // reactive state changes — returns unsubscribe fn
131
210
  cc.getState() // → SDKState snapshot
132
211
  cc.checkAccess() // trigger access check manually
212
+ cc.login() // open login flow programmatically
213
+ cc.purchase() // trigger article purchase
214
+ cc.buyMoreCredits() // open dashboard to top up balance
133
215
  cc.openComments() // open comment panel
134
216
  cc.closeComments() // close comment panel
135
217
  cc.isLoggedIn() // → boolean
218
+ cc.getToken() // → access token string | null
219
+ cc.logout() // revoke session and clear local auth state
136
220
  cc.destroy() // tear down SDK, restore hidden content
137
221
 
138
- ContentCredits.version // → "2.0.0"
222
+ ContentCredits.version // → "2.12.0"
139
223
  ```
140
224
 
141
225
  ---
142
226
 
143
227
  ## React / Next.js
144
228
 
229
+ ### Default UI with a React top slot
230
+
231
+ The simplest integration — the SDK handles all paywall rendering; you only supply the top section:
232
+
145
233
  ```tsx
146
- 'use client'; // Next.js App Router
234
+ 'use client';
147
235
 
148
236
  import { useEffect } from 'react';
237
+ import ReactDOM from 'react-dom/client';
149
238
  import { ContentCredits } from '@contentcredits/sdk';
239
+ import { DonationWidget } from './DonationWidget';
150
240
 
151
- export function PremiumGate({ apiKey, children }) {
241
+ export function PremiumGate({ apiKey }: { apiKey: string }) {
152
242
  useEffect(() => {
153
243
  const cc = ContentCredits.init({
154
244
  apiKey,
155
245
  contentSelector: '#premium-content',
246
+ reactDOM,
247
+ paywallTopSlot: <DonationWidget />,
248
+ });
249
+ return () => cc.destroy();
250
+ }, [apiKey]);
251
+
252
+ return <div id="premium-content">{/* article content */}</div>;
253
+ }
254
+ ```
255
+
256
+ ### Headless mode (full custom UI)
257
+
258
+ Use `headless: true` when you want to build the entire paywall UI yourself in React. The SDK manages auth and access checks but renders no DOM of its own.
259
+
260
+ ```tsx
261
+ 'use client';
262
+
263
+ import { useEffect, useRef, useState } from 'react';
264
+ import { ContentCredits } from '@contentcredits/sdk';
265
+ import type { SDKState } from '@contentcredits/sdk';
266
+
267
+ export function PremiumGate({ apiKey, children }: { apiKey: string; children: React.ReactNode }) {
268
+ const ccRef = useRef<ContentCredits | null>(null);
269
+ const [state, setState] = useState<SDKState | null>(null);
270
+
271
+ useEffect(() => {
272
+ const cc = ContentCredits.init({
273
+ apiKey,
274
+ headless: true,
275
+ onStateChange: setState,
156
276
  });
277
+ ccRef.current = cc;
157
278
  return () => cc.destroy();
158
279
  }, [apiKey]);
159
280
 
160
- return <div id="premium-content">{children}</div>;
281
+ if (state?.hasAccess) return <>{children}</>;
282
+
283
+ return (
284
+ <div>
285
+ {/* your teaser content */}
286
+ <button onClick={() => ccRef.current?.purchase()}>Unlock article</button>
287
+ </div>
288
+ );
161
289
  }
162
290
  ```
163
291
 
292
+ See [`examples/nextjs-blog`](./examples/nextjs-blog) for a full working Next.js 14 (App Router) implementation.
293
+
164
294
  ---
165
295
 
166
296
  ## Requirements
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -676,7 +676,9 @@ function getPaywallStyles(primaryColor, fontFamily) {
676
676
 
677
677
  /* ── Overlay paywall panel — full-width white panel below gated content ── */
678
678
  .cc-paywall-overlay {
679
- width: 100%;
679
+ /* Break out of any max-width container to span the full viewport */
680
+ width: 100vw;
681
+ margin-left: calc(50% - 50vw);
680
682
  background: #fff;
681
683
  font-family: ${fontFamily};
682
684
  }
@@ -3207,7 +3209,7 @@ class ContentCredits {
3207
3209
  }
3208
3210
  /** SDK version string. */
3209
3211
  static get version() {
3210
- return "2.3.0";
3212
+ return "2.13.0";
3211
3213
  }
3212
3214
  }
3213
3215
  // ── Auto-init from script data attributes (CDN usage) ────────────────────────