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