@contentcredits/sdk 2.1.0 → 2.3.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/dist/content-credits.cjs.js +175 -42
- package/dist/content-credits.cjs.js.map +1 -1
- package/dist/content-credits.d.ts +118 -0
- package/dist/content-credits.esm.js +175 -42
- package/dist/content-credits.esm.js.map +1 -1
- package/dist/content-credits.umd.min.js +1 -1
- package/dist/content-credits.umd.min.js.map +1 -1
- package/dist/index.d.ts +46 -0
- package/dist/paywall/index.d.ts +3 -0
- package/dist/types/index.d.ts +94 -1
- package/package.json +1 -1
|
@@ -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 (
|
|
11
|
+
catch (_m) {
|
|
12
12
|
throw new Error(`[ContentCredits] Invalid articleUrl: "${articleUrl}"`);
|
|
13
13
|
}
|
|
14
14
|
return {
|
|
@@ -21,13 +21,23 @@ 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,
|
|
29
|
+
onStateChange: raw.onStateChange,
|
|
30
|
+
onReady: raw.onReady,
|
|
31
|
+
onLoginRequired: raw.onLoginRequired,
|
|
32
|
+
onPurchaseRequired: raw.onPurchaseRequired,
|
|
33
|
+
onInsufficientCredits: raw.onInsufficientCredits,
|
|
34
|
+
onPurchased: raw.onPurchased,
|
|
35
|
+
onUserLogin: raw.onUserLogin,
|
|
36
|
+
onUserLogout: raw.onUserLogout,
|
|
37
|
+
onError: raw.onError,
|
|
28
38
|
theme: {
|
|
29
|
-
primaryColor: (
|
|
30
|
-
fontFamily: (
|
|
39
|
+
primaryColor: (_j = (_h = raw.theme) === null || _h === void 0 ? void 0 : _h.primaryColor) !== null && _j !== void 0 ? _j : '#44C678',
|
|
40
|
+
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
41
|
},
|
|
32
42
|
};
|
|
33
43
|
}
|
|
@@ -1623,8 +1633,10 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1623
1633
|
function handleAccessGranted(creditsSpent = 0, balance = 0) {
|
|
1624
1634
|
var _a;
|
|
1625
1635
|
state.set({ hasAccess: true, isLoaded: true, isLoading: false });
|
|
1626
|
-
|
|
1627
|
-
|
|
1636
|
+
if (!config.headless) {
|
|
1637
|
+
gate.reveal();
|
|
1638
|
+
renderer.render('granted', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1639
|
+
}
|
|
1628
1640
|
emitter.emit('paywall:hidden', {});
|
|
1629
1641
|
emitter.emit('article:purchased', { creditsSpent, remainingBalance: balance });
|
|
1630
1642
|
(_a = config.onAccessGranted) === null || _a === void 0 ? void 0 : _a.call(config);
|
|
@@ -1641,7 +1653,8 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1641
1653
|
window.location.href = authUrl;
|
|
1642
1654
|
return;
|
|
1643
1655
|
}
|
|
1644
|
-
|
|
1656
|
+
if (!config.headless)
|
|
1657
|
+
renderer.render('loading', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1645
1658
|
const token = await openAuthPopup(authUrl);
|
|
1646
1659
|
if (token) {
|
|
1647
1660
|
state.set({ isLoggedIn: true });
|
|
@@ -1649,12 +1662,13 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1649
1662
|
}
|
|
1650
1663
|
else {
|
|
1651
1664
|
// Popup closed without login
|
|
1652
|
-
|
|
1665
|
+
if (!config.headless)
|
|
1666
|
+
renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1653
1667
|
}
|
|
1654
1668
|
}
|
|
1655
1669
|
// ── Purchase ──────────────────────────────────────────────────────────────
|
|
1656
1670
|
async function doPurchase() {
|
|
1657
|
-
var _a, _b, _c;
|
|
1671
|
+
var _a, _b, _c, _d, _e;
|
|
1658
1672
|
if (!tokenStorage.has()) {
|
|
1659
1673
|
await doLogin();
|
|
1660
1674
|
return;
|
|
@@ -1668,7 +1682,8 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1668
1682
|
});
|
|
1669
1683
|
return;
|
|
1670
1684
|
}
|
|
1671
|
-
|
|
1685
|
+
if (!config.headless)
|
|
1686
|
+
renderer.render('loading', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1672
1687
|
state.set({ isLoading: true });
|
|
1673
1688
|
try {
|
|
1674
1689
|
const result = await creditsApi.purchaseArticle({
|
|
@@ -1682,7 +1697,8 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1682
1697
|
}
|
|
1683
1698
|
else {
|
|
1684
1699
|
state.set({ isLoading: false });
|
|
1685
|
-
|
|
1700
|
+
if (!config.headless)
|
|
1701
|
+
renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1686
1702
|
emitter.emit('error', { message: (_a = result.message) !== null && _a !== void 0 ? _a : 'Purchase failed' });
|
|
1687
1703
|
}
|
|
1688
1704
|
}
|
|
@@ -1690,17 +1706,24 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1690
1706
|
state.set({ isLoading: false });
|
|
1691
1707
|
if (err instanceof ApiError && err.status === 402) {
|
|
1692
1708
|
// Insufficient credits
|
|
1693
|
-
|
|
1709
|
+
if (!config.headless) {
|
|
1710
|
+
renderer.render('insufficient', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits }, {
|
|
1711
|
+
requiredCredits: state.get().requiredCredits,
|
|
1712
|
+
creditBalance: state.get().creditBalance,
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
const required = (_b = state.get().requiredCredits) !== null && _b !== void 0 ? _b : 0;
|
|
1716
|
+
const available = (_c = state.get().creditBalance) !== null && _c !== void 0 ? _c : 0;
|
|
1717
|
+
(_d = config.onInsufficientCredits) === null || _d === void 0 ? void 0 : _d.call(config, { required, available });
|
|
1718
|
+
emitter.emit('credits:insufficient', { required, available });
|
|
1719
|
+
}
|
|
1720
|
+
else {
|
|
1721
|
+
if (!config.headless)
|
|
1722
|
+
renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1723
|
+
(_e = config.onPurchaseRequired) === null || _e === void 0 ? void 0 : _e.call(config, {
|
|
1694
1724
|
requiredCredits: state.get().requiredCredits,
|
|
1695
1725
|
creditBalance: state.get().creditBalance,
|
|
1696
1726
|
});
|
|
1697
|
-
emitter.emit('credits:insufficient', {
|
|
1698
|
-
required: (_b = state.get().requiredCredits) !== null && _b !== void 0 ? _b : 0,
|
|
1699
|
-
available: (_c = state.get().creditBalance) !== null && _c !== void 0 ? _c : 0,
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
else {
|
|
1703
|
-
renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1704
1727
|
emitter.emit('error', { message: 'Purchase failed', error: err });
|
|
1705
1728
|
}
|
|
1706
1729
|
}
|
|
@@ -1710,16 +1733,21 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1710
1733
|
}
|
|
1711
1734
|
// ── Access Check ──────────────────────────────────────────────────────────
|
|
1712
1735
|
async function checkAccess() {
|
|
1736
|
+
var _a, _b, _c;
|
|
1713
1737
|
state.set({ isLoading: true });
|
|
1714
|
-
|
|
1738
|
+
if (!config.headless)
|
|
1739
|
+
renderer.render('checking', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1715
1740
|
if (extensionAvailable) {
|
|
1716
1741
|
bridge.requestAuthorization(config.apiKey, config.hostName);
|
|
1717
1742
|
return; // response handled in onAuthorizationResponse
|
|
1718
1743
|
}
|
|
1719
1744
|
if (!tokenStorage.has()) {
|
|
1720
1745
|
state.set({ isLoading: false, isLoaded: true });
|
|
1721
|
-
|
|
1722
|
-
|
|
1746
|
+
if (!config.headless) {
|
|
1747
|
+
gate.hide();
|
|
1748
|
+
renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1749
|
+
}
|
|
1750
|
+
(_a = config.onLoginRequired) === null || _a === void 0 ? void 0 : _a.call(config);
|
|
1723
1751
|
emitter.emit('paywall:shown', {});
|
|
1724
1752
|
return;
|
|
1725
1753
|
}
|
|
@@ -1739,15 +1767,24 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1739
1767
|
handleAccessGranted(0, 0);
|
|
1740
1768
|
}
|
|
1741
1769
|
else {
|
|
1742
|
-
|
|
1743
|
-
|
|
1770
|
+
if (!config.headless) {
|
|
1771
|
+
gate.hide();
|
|
1772
|
+
renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1773
|
+
}
|
|
1774
|
+
(_b = config.onPurchaseRequired) === null || _b === void 0 ? void 0 : _b.call(config, {
|
|
1775
|
+
requiredCredits: state.get().requiredCredits,
|
|
1776
|
+
creditBalance: state.get().creditBalance,
|
|
1777
|
+
});
|
|
1744
1778
|
emitter.emit('paywall:shown', {});
|
|
1745
1779
|
}
|
|
1746
1780
|
}
|
|
1747
1781
|
catch (err) {
|
|
1748
1782
|
state.set({ isLoading: false, isLoaded: true });
|
|
1749
|
-
|
|
1750
|
-
|
|
1783
|
+
if (!config.headless) {
|
|
1784
|
+
gate.hide();
|
|
1785
|
+
renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1786
|
+
}
|
|
1787
|
+
(_c = config.onLoginRequired) === null || _c === void 0 ? void 0 : _c.call(config);
|
|
1751
1788
|
if (!(err instanceof ApiError && err.status === 401)) {
|
|
1752
1789
|
emitter.emit('error', { message: 'Access check failed', error: err });
|
|
1753
1790
|
}
|
|
@@ -1766,7 +1803,7 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1766
1803
|
if (extensionAvailable) {
|
|
1767
1804
|
bridge.attach();
|
|
1768
1805
|
bridge.onAuthorizationResponse(data => {
|
|
1769
|
-
var _a, _b, _c;
|
|
1806
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1770
1807
|
state.set({
|
|
1771
1808
|
isLoggedIn: data.isAuthenticated,
|
|
1772
1809
|
hasAccess: data.doesHaveAccess,
|
|
@@ -1776,18 +1813,27 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1776
1813
|
requiredCredits: (_b = data.requiredCredits) !== null && _b !== void 0 ? _b : null,
|
|
1777
1814
|
});
|
|
1778
1815
|
if (!data.isAuthenticated) {
|
|
1779
|
-
|
|
1780
|
-
|
|
1816
|
+
if (!config.headless) {
|
|
1817
|
+
gate.hide();
|
|
1818
|
+
renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1819
|
+
}
|
|
1820
|
+
(_c = config.onLoginRequired) === null || _c === void 0 ? void 0 : _c.call(config);
|
|
1781
1821
|
emitter.emit('paywall:shown', {});
|
|
1782
1822
|
}
|
|
1783
1823
|
else if (data.doesHaveAccess) {
|
|
1784
|
-
handleAccessGranted(0, (
|
|
1824
|
+
handleAccessGranted(0, (_d = data.creditBalance) !== null && _d !== void 0 ? _d : 0);
|
|
1785
1825
|
}
|
|
1786
1826
|
else {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1827
|
+
if (!config.headless) {
|
|
1828
|
+
gate.hide();
|
|
1829
|
+
renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits }, {
|
|
1830
|
+
requiredCredits: data.requiredCredits,
|
|
1831
|
+
creditBalance: data.creditBalance,
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
(_e = config.onPurchaseRequired) === null || _e === void 0 ? void 0 : _e.call(config, {
|
|
1835
|
+
requiredCredits: (_f = data.requiredCredits) !== null && _f !== void 0 ? _f : null,
|
|
1836
|
+
creditBalance: (_g = data.creditBalance) !== null && _g !== void 0 ? _g : null,
|
|
1791
1837
|
});
|
|
1792
1838
|
emitter.emit('paywall:shown', {});
|
|
1793
1839
|
}
|
|
@@ -1808,10 +1854,19 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1808
1854
|
}
|
|
1809
1855
|
function destroy() {
|
|
1810
1856
|
bridge.detach();
|
|
1811
|
-
|
|
1812
|
-
|
|
1857
|
+
if (!config.headless) {
|
|
1858
|
+
renderer.destroy();
|
|
1859
|
+
gate.reveal();
|
|
1860
|
+
}
|
|
1813
1861
|
}
|
|
1814
|
-
return {
|
|
1862
|
+
return {
|
|
1863
|
+
init,
|
|
1864
|
+
checkAccess,
|
|
1865
|
+
destroy,
|
|
1866
|
+
login: doLogin,
|
|
1867
|
+
purchase: doPurchase,
|
|
1868
|
+
buyMoreCredits: doBuyMoreCredits,
|
|
1869
|
+
};
|
|
1815
1870
|
}
|
|
1816
1871
|
|
|
1817
1872
|
const POSITION_KEY = 'cc-widget-pos';
|
|
@@ -2673,21 +2728,42 @@ class ContentCredits {
|
|
|
2673
2728
|
async _start() {
|
|
2674
2729
|
// 1. Consume any token that arrived in the URL (mobile redirect flow)
|
|
2675
2730
|
consumeTokenFromUrl();
|
|
2676
|
-
// 2.
|
|
2731
|
+
// 2. Wire config-level callbacks so developers don't need separate on() calls.
|
|
2732
|
+
if (this.config.onStateChange) {
|
|
2733
|
+
this.state.subscribe(this.config.onStateChange);
|
|
2734
|
+
}
|
|
2735
|
+
if (this.config.onReady) {
|
|
2736
|
+
this.emitter.on('ready', ({ state }) => this.config.onReady(state));
|
|
2737
|
+
}
|
|
2738
|
+
if (this.config.onPurchased) {
|
|
2739
|
+
this.emitter.on('article:purchased', (payload) => this.config.onPurchased(payload));
|
|
2740
|
+
}
|
|
2741
|
+
if (this.config.onUserLogin) {
|
|
2742
|
+
this.emitter.on('auth:login', ({ user }) => this.config.onUserLogin(user));
|
|
2743
|
+
}
|
|
2744
|
+
if (this.config.onUserLogout) {
|
|
2745
|
+
this.emitter.on('auth:logout', () => this.config.onUserLogout());
|
|
2746
|
+
}
|
|
2747
|
+
if (this.config.onError) {
|
|
2748
|
+
this.emitter.on('error', (payload) => this.config.onError(payload));
|
|
2749
|
+
}
|
|
2750
|
+
// 3. Hide premium content immediately (synchronous) before any async work.
|
|
2677
2751
|
// This prevents the flash of full article content that would otherwise
|
|
2678
2752
|
// appear during the token-refresh and access-check network round-trips.
|
|
2753
|
+
// Skipped in headless mode — the host app owns all DOM manipulation.
|
|
2679
2754
|
const earlyGate = createGate({
|
|
2680
2755
|
selector: this.config.contentSelector,
|
|
2681
2756
|
teaserParagraphs: this.config.teaserParagraphs,
|
|
2682
2757
|
});
|
|
2683
|
-
|
|
2684
|
-
|
|
2758
|
+
if (!this.config.headless)
|
|
2759
|
+
earlyGate.hide();
|
|
2760
|
+
// 4. If no access token in memory/session, attempt a silent refresh.
|
|
2685
2761
|
// This runs on every new browser session (after the browser was closed)
|
|
2686
2762
|
// and silently re-authenticates the user using their stored refresh token.
|
|
2687
2763
|
if (!tokenStorage.has()) {
|
|
2688
2764
|
await tryRefreshSession(this.config.apiBaseUrl);
|
|
2689
2765
|
}
|
|
2690
|
-
// Pass the pre-created gate so createPaywall reuses the same instance
|
|
2766
|
+
// 5. Pass the pre-created gate so createPaywall reuses the same instance
|
|
2691
2767
|
// (and its hiddenNodes list) rather than creating a second one.
|
|
2692
2768
|
this.paywallModule = createPaywall(this.config, this.creditsApi, this.state, this.emitter, earlyGate);
|
|
2693
2769
|
if (this.config.enableComments) {
|
|
@@ -2698,6 +2774,63 @@ class ContentCredits {
|
|
|
2698
2774
|
this.emitter.emit('ready', { state: this.state.get() });
|
|
2699
2775
|
}
|
|
2700
2776
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
2777
|
+
/**
|
|
2778
|
+
* Subscribe to state changes. The callback receives the full state snapshot
|
|
2779
|
+
* every time any field changes. Returns an unsubscribe function.
|
|
2780
|
+
*
|
|
2781
|
+
* Primarily useful in **headless mode** — lets you drive your own UI from
|
|
2782
|
+
* reactive state without polling `getState()`.
|
|
2783
|
+
*
|
|
2784
|
+
* @example
|
|
2785
|
+
* const unsubscribe = cc.subscribe((state) => {
|
|
2786
|
+
* if (state.hasAccess) showFullContent();
|
|
2787
|
+
* else showPaywall(state);
|
|
2788
|
+
* });
|
|
2789
|
+
*/
|
|
2790
|
+
subscribe(fn) {
|
|
2791
|
+
return this.state.subscribe(fn);
|
|
2792
|
+
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Trigger the login flow programmatically.
|
|
2795
|
+
*
|
|
2796
|
+
* - Desktop: opens a popup window to the Content Credits auth page.
|
|
2797
|
+
* - Mobile: performs a full-page redirect.
|
|
2798
|
+
* - Extension: delegates to the browser extension.
|
|
2799
|
+
*
|
|
2800
|
+
* Primarily useful in **headless mode** where you render your own "Login"
|
|
2801
|
+
* button and call this from its `onClick` handler.
|
|
2802
|
+
*/
|
|
2803
|
+
async login() {
|
|
2804
|
+
var _a;
|
|
2805
|
+
await ((_a = this.paywallModule) === null || _a === void 0 ? void 0 : _a.login());
|
|
2806
|
+
}
|
|
2807
|
+
/**
|
|
2808
|
+
* Trigger the article purchase flow programmatically.
|
|
2809
|
+
*
|
|
2810
|
+
* Deducts the required credits from the user's balance and, on success,
|
|
2811
|
+
* updates `state.hasAccess` to `true` and emits `article:purchased`.
|
|
2812
|
+
*
|
|
2813
|
+
* If the user is not logged in, this automatically opens the login flow
|
|
2814
|
+
* first, then proceeds with the purchase.
|
|
2815
|
+
*
|
|
2816
|
+
* Primarily useful in **headless mode** where you render your own "Unlock"
|
|
2817
|
+
* button and call this from its `onClick` handler.
|
|
2818
|
+
*/
|
|
2819
|
+
async purchase() {
|
|
2820
|
+
var _a;
|
|
2821
|
+
await ((_a = this.paywallModule) === null || _a === void 0 ? void 0 : _a.purchase());
|
|
2822
|
+
}
|
|
2823
|
+
/**
|
|
2824
|
+
* Open the Content Credits dashboard in a new tab so the user can top up
|
|
2825
|
+
* their credit balance.
|
|
2826
|
+
*
|
|
2827
|
+
* Primarily useful in **headless mode** when `state.creditBalance` is lower
|
|
2828
|
+
* than `state.requiredCredits`.
|
|
2829
|
+
*/
|
|
2830
|
+
buyMoreCredits() {
|
|
2831
|
+
var _a;
|
|
2832
|
+
(_a = this.paywallModule) === null || _a === void 0 ? void 0 : _a.buyMoreCredits();
|
|
2833
|
+
}
|
|
2701
2834
|
/** Subscribe to an SDK event. Returns an unsubscribe function. */
|
|
2702
2835
|
on(event, handler) {
|
|
2703
2836
|
return this.emitter.on(event, handler);
|
|
@@ -2739,7 +2872,7 @@ class ContentCredits {
|
|
|
2739
2872
|
}
|
|
2740
2873
|
/** SDK version string. */
|
|
2741
2874
|
static get version() {
|
|
2742
|
-
return "2.
|
|
2875
|
+
return "2.3.0";
|
|
2743
2876
|
}
|
|
2744
2877
|
}
|
|
2745
2878
|
// ── Auto-init from script data attributes (CDN usage) ────────────────────────
|