@contentcredits/sdk 2.1.0 → 2.2.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.
@@ -1,5 +1,5 @@
1
1
  function resolveConfig(raw) {
2
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
2
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
3
3
  if (!raw.apiKey || typeof raw.apiKey !== 'string' || raw.apiKey.trim() === '') {
4
4
  throw new Error('[ContentCredits] apiKey is required. Get yours from the Content Credits admin panel.');
5
5
  }
@@ -8,7 +8,7 @@ function resolveConfig(raw) {
8
8
  try {
9
9
  hostName = new URL(articleUrl).hostname;
10
10
  }
11
- catch (_l) {
11
+ catch (_m) {
12
12
  throw new Error(`[ContentCredits] Invalid articleUrl: "${articleUrl}"`);
13
13
  }
14
14
  return {
@@ -21,13 +21,14 @@ function resolveConfig(raw) {
21
21
  enableComments: (_d = raw.enableComments) !== null && _d !== void 0 ? _d : true,
22
22
  extensionId: (_e = raw.extensionId) !== null && _e !== void 0 ? _e : "ljehdpabbhgccmanhjdfacjnaigpgcml",
23
23
  debug: (_f = raw.debug) !== null && _f !== void 0 ? _f : false,
24
+ headless: (_g = raw.headless) !== null && _g !== void 0 ? _g : false,
24
25
  apiBaseUrl: "https://api.contentcredits.com",
25
26
  accountsUrl: "https://accounts.contentcredits.com",
26
27
  paywallTemplate: raw.paywallTemplate,
27
28
  onAccessGranted: raw.onAccessGranted,
28
29
  theme: {
29
- primaryColor: (_h = (_g = raw.theme) === null || _g === void 0 ? void 0 : _g.primaryColor) !== null && _h !== void 0 ? _h : '#44C678',
30
- fontFamily: (_k = (_j = raw.theme) === null || _j === void 0 ? void 0 : _j.fontFamily) !== null && _k !== void 0 ? _k : "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
30
+ primaryColor: (_j = (_h = raw.theme) === null || _h === void 0 ? void 0 : _h.primaryColor) !== null && _j !== void 0 ? _j : '#44C678',
31
+ fontFamily: (_l = (_k = raw.theme) === null || _k === void 0 ? void 0 : _k.fontFamily) !== null && _l !== void 0 ? _l : "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
31
32
  },
32
33
  };
33
34
  }
@@ -1623,8 +1624,10 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1623
1624
  function handleAccessGranted(creditsSpent = 0, balance = 0) {
1624
1625
  var _a;
1625
1626
  state.set({ hasAccess: true, isLoaded: true, isLoading: false });
1626
- gate.reveal();
1627
- renderer.render('granted', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1627
+ if (!config.headless) {
1628
+ gate.reveal();
1629
+ renderer.render('granted', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1630
+ }
1628
1631
  emitter.emit('paywall:hidden', {});
1629
1632
  emitter.emit('article:purchased', { creditsSpent, remainingBalance: balance });
1630
1633
  (_a = config.onAccessGranted) === null || _a === void 0 ? void 0 : _a.call(config);
@@ -1641,7 +1644,8 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1641
1644
  window.location.href = authUrl;
1642
1645
  return;
1643
1646
  }
1644
- renderer.render('loading', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1647
+ if (!config.headless)
1648
+ renderer.render('loading', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1645
1649
  const token = await openAuthPopup(authUrl);
1646
1650
  if (token) {
1647
1651
  state.set({ isLoggedIn: true });
@@ -1649,7 +1653,8 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1649
1653
  }
1650
1654
  else {
1651
1655
  // Popup closed without login
1652
- renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1656
+ if (!config.headless)
1657
+ renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1653
1658
  }
1654
1659
  }
1655
1660
  // ── Purchase ──────────────────────────────────────────────────────────────
@@ -1668,7 +1673,8 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1668
1673
  });
1669
1674
  return;
1670
1675
  }
1671
- renderer.render('loading', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1676
+ if (!config.headless)
1677
+ renderer.render('loading', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1672
1678
  state.set({ isLoading: true });
1673
1679
  try {
1674
1680
  const result = await creditsApi.purchaseArticle({
@@ -1682,7 +1688,8 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1682
1688
  }
1683
1689
  else {
1684
1690
  state.set({ isLoading: false });
1685
- renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1691
+ if (!config.headless)
1692
+ renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1686
1693
  emitter.emit('error', { message: (_a = result.message) !== null && _a !== void 0 ? _a : 'Purchase failed' });
1687
1694
  }
1688
1695
  }
@@ -1690,17 +1697,20 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1690
1697
  state.set({ isLoading: false });
1691
1698
  if (err instanceof ApiError && err.status === 402) {
1692
1699
  // Insufficient credits
1693
- renderer.render('insufficient', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits }, {
1694
- requiredCredits: state.get().requiredCredits,
1695
- creditBalance: state.get().creditBalance,
1696
- });
1700
+ if (!config.headless) {
1701
+ renderer.render('insufficient', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits }, {
1702
+ requiredCredits: state.get().requiredCredits,
1703
+ creditBalance: state.get().creditBalance,
1704
+ });
1705
+ }
1697
1706
  emitter.emit('credits:insufficient', {
1698
1707
  required: (_b = state.get().requiredCredits) !== null && _b !== void 0 ? _b : 0,
1699
1708
  available: (_c = state.get().creditBalance) !== null && _c !== void 0 ? _c : 0,
1700
1709
  });
1701
1710
  }
1702
1711
  else {
1703
- renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1712
+ if (!config.headless)
1713
+ renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1704
1714
  emitter.emit('error', { message: 'Purchase failed', error: err });
1705
1715
  }
1706
1716
  }
@@ -1711,15 +1721,18 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1711
1721
  // ── Access Check ──────────────────────────────────────────────────────────
1712
1722
  async function checkAccess() {
1713
1723
  state.set({ isLoading: true });
1714
- renderer.render('checking', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1724
+ if (!config.headless)
1725
+ renderer.render('checking', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1715
1726
  if (extensionAvailable) {
1716
1727
  bridge.requestAuthorization(config.apiKey, config.hostName);
1717
1728
  return; // response handled in onAuthorizationResponse
1718
1729
  }
1719
1730
  if (!tokenStorage.has()) {
1720
1731
  state.set({ isLoading: false, isLoaded: true });
1721
- gate.hide();
1722
- renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1732
+ if (!config.headless) {
1733
+ gate.hide();
1734
+ renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1735
+ }
1723
1736
  emitter.emit('paywall:shown', {});
1724
1737
  return;
1725
1738
  }
@@ -1739,15 +1752,19 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1739
1752
  handleAccessGranted(0, 0);
1740
1753
  }
1741
1754
  else {
1742
- gate.hide();
1743
- renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1755
+ if (!config.headless) {
1756
+ gate.hide();
1757
+ renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1758
+ }
1744
1759
  emitter.emit('paywall:shown', {});
1745
1760
  }
1746
1761
  }
1747
1762
  catch (err) {
1748
1763
  state.set({ isLoading: false, isLoaded: true });
1749
- gate.hide();
1750
- renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1764
+ if (!config.headless) {
1765
+ gate.hide();
1766
+ renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1767
+ }
1751
1768
  if (!(err instanceof ApiError && err.status === 401)) {
1752
1769
  emitter.emit('error', { message: 'Access check failed', error: err });
1753
1770
  }
@@ -1776,19 +1793,23 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1776
1793
  requiredCredits: (_b = data.requiredCredits) !== null && _b !== void 0 ? _b : null,
1777
1794
  });
1778
1795
  if (!data.isAuthenticated) {
1779
- gate.hide();
1780
- renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1796
+ if (!config.headless) {
1797
+ gate.hide();
1798
+ renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
1799
+ }
1781
1800
  emitter.emit('paywall:shown', {});
1782
1801
  }
1783
1802
  else if (data.doesHaveAccess) {
1784
1803
  handleAccessGranted(0, (_c = data.creditBalance) !== null && _c !== void 0 ? _c : 0);
1785
1804
  }
1786
1805
  else {
1787
- gate.hide();
1788
- renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits }, {
1789
- requiredCredits: data.requiredCredits,
1790
- creditBalance: data.creditBalance,
1791
- });
1806
+ if (!config.headless) {
1807
+ gate.hide();
1808
+ renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits }, {
1809
+ requiredCredits: data.requiredCredits,
1810
+ creditBalance: data.creditBalance,
1811
+ });
1812
+ }
1792
1813
  emitter.emit('paywall:shown', {});
1793
1814
  }
1794
1815
  });
@@ -1808,10 +1829,19 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
1808
1829
  }
1809
1830
  function destroy() {
1810
1831
  bridge.detach();
1811
- renderer.destroy();
1812
- gate.reveal();
1832
+ if (!config.headless) {
1833
+ renderer.destroy();
1834
+ gate.reveal();
1835
+ }
1813
1836
  }
1814
- return { init, checkAccess, destroy };
1837
+ return {
1838
+ init,
1839
+ checkAccess,
1840
+ destroy,
1841
+ login: doLogin,
1842
+ purchase: doPurchase,
1843
+ buyMoreCredits: doBuyMoreCredits,
1844
+ };
1815
1845
  }
1816
1846
 
1817
1847
  const POSITION_KEY = 'cc-widget-pos';
@@ -2676,11 +2706,13 @@ class ContentCredits {
2676
2706
  // 2. Hide premium content immediately (synchronous) before any async work.
2677
2707
  // This prevents the flash of full article content that would otherwise
2678
2708
  // appear during the token-refresh and access-check network round-trips.
2709
+ // Skipped in headless mode — the host app owns all DOM manipulation.
2679
2710
  const earlyGate = createGate({
2680
2711
  selector: this.config.contentSelector,
2681
2712
  teaserParagraphs: this.config.teaserParagraphs,
2682
2713
  });
2683
- earlyGate.hide();
2714
+ if (!this.config.headless)
2715
+ earlyGate.hide();
2684
2716
  // 3. If no access token in memory/session, attempt a silent refresh.
2685
2717
  // This runs on every new browser session (after the browser was closed)
2686
2718
  // and silently re-authenticates the user using their stored refresh token.
@@ -2698,6 +2730,63 @@ class ContentCredits {
2698
2730
  this.emitter.emit('ready', { state: this.state.get() });
2699
2731
  }
2700
2732
  // ── Public API ────────────────────────────────────────────────────────────
2733
+ /**
2734
+ * Subscribe to state changes. The callback receives the full state snapshot
2735
+ * every time any field changes. Returns an unsubscribe function.
2736
+ *
2737
+ * Primarily useful in **headless mode** — lets you drive your own UI from
2738
+ * reactive state without polling `getState()`.
2739
+ *
2740
+ * @example
2741
+ * const unsubscribe = cc.subscribe((state) => {
2742
+ * if (state.hasAccess) showFullContent();
2743
+ * else showPaywall(state);
2744
+ * });
2745
+ */
2746
+ subscribe(fn) {
2747
+ return this.state.subscribe(fn);
2748
+ }
2749
+ /**
2750
+ * Trigger the login flow programmatically.
2751
+ *
2752
+ * - Desktop: opens a popup window to the Content Credits auth page.
2753
+ * - Mobile: performs a full-page redirect.
2754
+ * - Extension: delegates to the browser extension.
2755
+ *
2756
+ * Primarily useful in **headless mode** where you render your own "Login"
2757
+ * button and call this from its `onClick` handler.
2758
+ */
2759
+ async login() {
2760
+ var _a;
2761
+ await ((_a = this.paywallModule) === null || _a === void 0 ? void 0 : _a.login());
2762
+ }
2763
+ /**
2764
+ * Trigger the article purchase flow programmatically.
2765
+ *
2766
+ * Deducts the required credits from the user's balance and, on success,
2767
+ * updates `state.hasAccess` to `true` and emits `article:purchased`.
2768
+ *
2769
+ * If the user is not logged in, this automatically opens the login flow
2770
+ * first, then proceeds with the purchase.
2771
+ *
2772
+ * Primarily useful in **headless mode** where you render your own "Unlock"
2773
+ * button and call this from its `onClick` handler.
2774
+ */
2775
+ async purchase() {
2776
+ var _a;
2777
+ await ((_a = this.paywallModule) === null || _a === void 0 ? void 0 : _a.purchase());
2778
+ }
2779
+ /**
2780
+ * Open the Content Credits dashboard in a new tab so the user can top up
2781
+ * their credit balance.
2782
+ *
2783
+ * Primarily useful in **headless mode** when `state.creditBalance` is lower
2784
+ * than `state.requiredCredits`.
2785
+ */
2786
+ buyMoreCredits() {
2787
+ var _a;
2788
+ (_a = this.paywallModule) === null || _a === void 0 ? void 0 : _a.buyMoreCredits();
2789
+ }
2701
2790
  /** Subscribe to an SDK event. Returns an unsubscribe function. */
2702
2791
  on(event, handler) {
2703
2792
  return this.emitter.on(event, handler);
@@ -2739,7 +2828,7 @@ class ContentCredits {
2739
2828
  }
2740
2829
  /** SDK version string. */
2741
2830
  static get version() {
2742
- return "2.1.0";
2831
+ return "2.2.0";
2743
2832
  }
2744
2833
  }
2745
2834
  // ── Auto-init from script data attributes (CDN usage) ────────────────────────