@contentcredits/sdk 2.2.0 → 2.4.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/api/client.d.ts +7 -7
- package/dist/api/comments.d.ts +3 -2
- package/dist/api/credits.d.ts +3 -2
- package/dist/comments/index.d.ts +7 -6
- package/dist/comments/panel.d.ts +6 -5
- package/dist/content-credits.cjs.js +110 -30
- package/dist/content-credits.cjs.js.map +1 -1
- package/dist/content-credits.d.ts +65 -0
- package/dist/content-credits.esm.js +110 -30
- 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/core/events.d.ts +7 -7
- package/dist/core/state.d.ts +8 -8
- package/dist/extension/bridge.d.ts +11 -11
- package/dist/index.d.ts +7 -0
- package/dist/paywall/index.d.ts +9 -8
- package/dist/paywall/renderer.d.ts +10 -9
- package/dist/types/index.d.ts +80 -1
- package/package.json +6 -2
package/dist/api/client.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { EventEmitter } from '../core/events.js';
|
|
2
|
-
export
|
|
3
|
-
get
|
|
4
|
-
post
|
|
5
|
-
put
|
|
6
|
-
delete
|
|
7
|
-
}
|
|
2
|
+
export interface ApiClient {
|
|
3
|
+
get<T>(path: string): Promise<T>;
|
|
4
|
+
post<T>(path: string, body: Record<string, unknown>): Promise<T>;
|
|
5
|
+
put<T>(path: string, body: Record<string, unknown>): Promise<T>;
|
|
6
|
+
delete<T>(path: string): Promise<T>;
|
|
7
|
+
}
|
|
8
|
+
export declare function createApiClient(baseUrl: string, emitter: EventEmitter): ApiClient;
|
|
8
9
|
export declare class ApiError extends Error {
|
|
9
10
|
readonly status: number;
|
|
10
11
|
readonly data?: unknown | undefined;
|
|
11
12
|
constructor(status: number, message: string, data?: unknown | undefined);
|
|
12
13
|
}
|
|
13
|
-
export type ApiClient = ReturnType<typeof createApiClient>;
|
package/dist/api/comments.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApiClient } from './client.js';
|
|
2
2
|
import type { CommentsResponse, CommentThread, Comment, CommentSortBy } from '../types/index.js';
|
|
3
|
-
export
|
|
3
|
+
export interface CommentsApi {
|
|
4
4
|
ensureThread(params: {
|
|
5
5
|
pageUrl: string;
|
|
6
6
|
hostname: string;
|
|
@@ -24,4 +24,5 @@ export declare function createCommentsApi(client: ApiClient): {
|
|
|
24
24
|
hasLiked: boolean;
|
|
25
25
|
};
|
|
26
26
|
}>;
|
|
27
|
-
}
|
|
27
|
+
}
|
|
28
|
+
export declare function createCommentsApi(client: ApiClient): CommentsApi;
|
package/dist/api/credits.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApiClient } from './client.js';
|
|
2
2
|
import type { CheckAccessResponse, PurchaseResponse } from '../types/index.js';
|
|
3
|
-
export
|
|
3
|
+
export interface CreditsApi {
|
|
4
4
|
checkAccess(params: {
|
|
5
5
|
apiKey: string;
|
|
6
6
|
postUrl: string;
|
|
@@ -13,4 +13,5 @@ export declare function createCreditsApi(client: ApiClient): {
|
|
|
13
13
|
postName: string;
|
|
14
14
|
hostName: string;
|
|
15
15
|
}): Promise<PurchaseResponse>;
|
|
16
|
-
}
|
|
16
|
+
}
|
|
17
|
+
export declare function createCreditsApi(client: ApiClient): CreditsApi;
|
package/dist/comments/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { createCommentsApi } from '../api/comments.js';
|
|
2
2
|
import type { EventEmitter } from '../core/events.js';
|
|
3
3
|
import type { ResolvedConfig } from '../types/index.js';
|
|
4
|
-
export
|
|
5
|
-
init
|
|
6
|
-
open
|
|
7
|
-
close
|
|
8
|
-
destroy
|
|
9
|
-
}
|
|
4
|
+
export interface CommentsModule {
|
|
5
|
+
init(): void;
|
|
6
|
+
open(): void;
|
|
7
|
+
close(): void;
|
|
8
|
+
destroy(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare function createComments(config: ResolvedConfig, commentsApi: ReturnType<typeof createCommentsApi>, emitter: EventEmitter): CommentsModule;
|
package/dist/comments/panel.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { createCommentsApi } from '../api/comments.js';
|
|
2
2
|
import type { ResolvedConfig } from '../types/index.js';
|
|
3
3
|
import type { EventEmitter } from '../core/events.js';
|
|
4
|
-
export
|
|
5
|
-
openPanel
|
|
6
|
-
closePanel
|
|
7
|
-
destroy
|
|
8
|
-
}
|
|
4
|
+
export interface CommentPanelApi {
|
|
5
|
+
openPanel(): void;
|
|
6
|
+
closePanel(): void;
|
|
7
|
+
destroy(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function createCommentPanel(config: ResolvedConfig, commentsApi: ReturnType<typeof createCommentsApi>, emitter: EventEmitter, onClose: () => void): CommentPanelApi;
|
|
@@ -28,6 +28,15 @@ function resolveConfig(raw) {
|
|
|
28
28
|
accountsUrl: "https://accounts.contentcredits.com",
|
|
29
29
|
paywallTemplate: raw.paywallTemplate,
|
|
30
30
|
onAccessGranted: raw.onAccessGranted,
|
|
31
|
+
onStateChange: raw.onStateChange,
|
|
32
|
+
onReady: raw.onReady,
|
|
33
|
+
onLoginRequired: raw.onLoginRequired,
|
|
34
|
+
onPurchaseRequired: raw.onPurchaseRequired,
|
|
35
|
+
onInsufficientCredits: raw.onInsufficientCredits,
|
|
36
|
+
onPurchased: raw.onPurchased,
|
|
37
|
+
onUserLogin: raw.onUserLogin,
|
|
38
|
+
onUserLogout: raw.onUserLogout,
|
|
39
|
+
onError: raw.onError,
|
|
31
40
|
theme: {
|
|
32
41
|
primaryColor: (_j = (_h = raw.theme) === null || _h === void 0 ? void 0 : _h.primaryColor) !== null && _j !== void 0 ? _j : '#44C678',
|
|
33
42
|
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",
|
|
@@ -177,10 +186,12 @@ const REFRESH_KEY = 'cc_rt';
|
|
|
177
186
|
* └─ Layer 3: localStorage — survives browser close; used to silently
|
|
178
187
|
* re-authenticate on the next visit without showing a popup
|
|
179
188
|
*
|
|
180
|
-
* We intentionally never write to document.cookie
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
189
|
+
* We intentionally never write to document.cookie — both localStorage and
|
|
190
|
+
* non-HttpOnly cookies are equally XSS-accessible. The truly safe option
|
|
191
|
+
* (HttpOnly server-set cookie) requires cross-site cookie support which
|
|
192
|
+
* browsers are phasing out for third-party embeds. localStorage is
|
|
193
|
+
* first-party (publisher-domain scoped), never blocked, and the risk is
|
|
194
|
+
* mitigated by short-lived access tokens and server-side refresh token rotation.
|
|
184
195
|
*/
|
|
185
196
|
let memoryToken = null;
|
|
186
197
|
const tokenStorage = {
|
|
@@ -1223,7 +1234,7 @@ function createPaywallRenderer(config) {
|
|
|
1223
1234
|
parent.appendChild(el('p', 'Log in with your Content Credits account to unlock this article.'));
|
|
1224
1235
|
const btn = el('button', 'Login & Buy with Content Credits');
|
|
1225
1236
|
btn.className = 'cc-btn cc-btn-primary';
|
|
1226
|
-
btn.addEventListener('click', cb.onLogin);
|
|
1237
|
+
btn.addEventListener('click', () => { void cb.onLogin(); });
|
|
1227
1238
|
parent.appendChild(btn);
|
|
1228
1239
|
parent.appendChild(poweredBy());
|
|
1229
1240
|
}
|
|
@@ -1237,7 +1248,7 @@ function createPaywallRenderer(config) {
|
|
|
1237
1248
|
parent.appendChild(el('p', 'Use your Content Credits balance to instantly access this premium article.'));
|
|
1238
1249
|
const btn = el('button', credits !== null ? `Buy for ${credits} Credit${credits !== 1 ? 's' : ''}` : 'Buy with Content Credits');
|
|
1239
1250
|
btn.className = 'cc-btn cc-btn-primary';
|
|
1240
|
-
btn.addEventListener('click', cb.onPurchase);
|
|
1251
|
+
btn.addEventListener('click', () => { void cb.onPurchase(); });
|
|
1241
1252
|
parent.appendChild(btn);
|
|
1242
1253
|
parent.appendChild(poweredBy());
|
|
1243
1254
|
}
|
|
@@ -1249,7 +1260,7 @@ function createPaywallRenderer(config) {
|
|
|
1249
1260
|
parent.appendChild(el('p', detail));
|
|
1250
1261
|
const btn = el('button', 'Buy More Credits');
|
|
1251
1262
|
btn.className = 'cc-btn cc-btn-primary';
|
|
1252
|
-
btn.addEventListener('click', cb.onBuyMoreCredits);
|
|
1263
|
+
btn.addEventListener('click', () => cb.onBuyMoreCredits());
|
|
1253
1264
|
parent.appendChild(btn);
|
|
1254
1265
|
parent.appendChild(poweredBy());
|
|
1255
1266
|
}
|
|
@@ -1423,6 +1434,11 @@ function createExtensionBridge() {
|
|
|
1423
1434
|
};
|
|
1424
1435
|
}
|
|
1425
1436
|
|
|
1437
|
+
function isAuthCallback(data) {
|
|
1438
|
+
return (typeof data === 'object' &&
|
|
1439
|
+
data !== null &&
|
|
1440
|
+
data.type === 'cc_auth_callback');
|
|
1441
|
+
}
|
|
1426
1442
|
const POPUP_NAME = 'ccAuthPopup';
|
|
1427
1443
|
const POPUP_SPECS = 'scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,width=600,height=650';
|
|
1428
1444
|
function centeredSpecs() {
|
|
@@ -1483,9 +1499,10 @@ function consumeTokenFromUrl() {
|
|
|
1483
1499
|
// If we're inside a popup (opened by openAuthPopup), notify the opener
|
|
1484
1500
|
// and close instead of rendering the page. This fixes the bug where the
|
|
1485
1501
|
// popup shows the blog article after the accounts redirect.
|
|
1486
|
-
|
|
1502
|
+
const opener = window.opener;
|
|
1503
|
+
if (opener && !opener.closed) {
|
|
1487
1504
|
try {
|
|
1488
|
-
|
|
1505
|
+
opener.postMessage({ type: 'cc_auth_callback', token, refreshToken: refreshToken !== null && refreshToken !== void 0 ? refreshToken : null }, window.location.origin);
|
|
1489
1506
|
}
|
|
1490
1507
|
catch (_c) {
|
|
1491
1508
|
// opener is cross-origin or restricted — fall through, URL polling will handle it
|
|
@@ -1530,11 +1547,10 @@ function openAuthPopup(authUrl) {
|
|
|
1530
1547
|
}
|
|
1531
1548
|
// ── Primary: postMessage from popup ───────────────────────────────────
|
|
1532
1549
|
function onMessage(event) {
|
|
1533
|
-
var _a;
|
|
1534
1550
|
// Only accept messages from our own origin
|
|
1535
1551
|
if (event.origin !== window.location.origin)
|
|
1536
1552
|
return;
|
|
1537
|
-
if ((
|
|
1553
|
+
if (!isAuthCallback(event.data))
|
|
1538
1554
|
return;
|
|
1539
1555
|
const token = event.data.token;
|
|
1540
1556
|
const refreshToken = event.data.refreshToken;
|
|
@@ -1546,7 +1562,7 @@ function openAuthPopup(authUrl) {
|
|
|
1546
1562
|
try {
|
|
1547
1563
|
popup === null || popup === void 0 ? void 0 : popup.close();
|
|
1548
1564
|
}
|
|
1549
|
-
catch ( /* ignore */
|
|
1565
|
+
catch ( /* ignore */_a) { /* ignore */ }
|
|
1550
1566
|
finish(token);
|
|
1551
1567
|
}
|
|
1552
1568
|
window.addEventListener('message', onMessage);
|
|
@@ -1661,7 +1677,7 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1661
1677
|
}
|
|
1662
1678
|
// ── Purchase ──────────────────────────────────────────────────────────────
|
|
1663
1679
|
async function doPurchase() {
|
|
1664
|
-
var _a, _b, _c;
|
|
1680
|
+
var _a, _b, _c, _d, _e;
|
|
1665
1681
|
if (!tokenStorage.has()) {
|
|
1666
1682
|
await doLogin();
|
|
1667
1683
|
return;
|
|
@@ -1705,14 +1721,18 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1705
1721
|
creditBalance: state.get().creditBalance,
|
|
1706
1722
|
});
|
|
1707
1723
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
});
|
|
1724
|
+
const required = (_b = state.get().requiredCredits) !== null && _b !== void 0 ? _b : 0;
|
|
1725
|
+
const available = (_c = state.get().creditBalance) !== null && _c !== void 0 ? _c : 0;
|
|
1726
|
+
(_d = config.onInsufficientCredits) === null || _d === void 0 ? void 0 : _d.call(config, { required, available });
|
|
1727
|
+
emitter.emit('credits:insufficient', { required, available });
|
|
1712
1728
|
}
|
|
1713
1729
|
else {
|
|
1714
1730
|
if (!config.headless)
|
|
1715
1731
|
renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1732
|
+
(_e = config.onPurchaseRequired) === null || _e === void 0 ? void 0 : _e.call(config, {
|
|
1733
|
+
requiredCredits: state.get().requiredCredits,
|
|
1734
|
+
creditBalance: state.get().creditBalance,
|
|
1735
|
+
});
|
|
1716
1736
|
emitter.emit('error', { message: 'Purchase failed', error: err });
|
|
1717
1737
|
}
|
|
1718
1738
|
}
|
|
@@ -1722,6 +1742,7 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1722
1742
|
}
|
|
1723
1743
|
// ── Access Check ──────────────────────────────────────────────────────────
|
|
1724
1744
|
async function checkAccess() {
|
|
1745
|
+
var _a, _b, _c;
|
|
1725
1746
|
state.set({ isLoading: true });
|
|
1726
1747
|
if (!config.headless)
|
|
1727
1748
|
renderer.render('checking', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
@@ -1735,6 +1756,7 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1735
1756
|
gate.hide();
|
|
1736
1757
|
renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1737
1758
|
}
|
|
1759
|
+
(_a = config.onLoginRequired) === null || _a === void 0 ? void 0 : _a.call(config);
|
|
1738
1760
|
emitter.emit('paywall:shown', {});
|
|
1739
1761
|
return;
|
|
1740
1762
|
}
|
|
@@ -1758,6 +1780,10 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1758
1780
|
gate.hide();
|
|
1759
1781
|
renderer.render('purchase', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1760
1782
|
}
|
|
1783
|
+
(_b = config.onPurchaseRequired) === null || _b === void 0 ? void 0 : _b.call(config, {
|
|
1784
|
+
requiredCredits: state.get().requiredCredits,
|
|
1785
|
+
creditBalance: state.get().creditBalance,
|
|
1786
|
+
});
|
|
1761
1787
|
emitter.emit('paywall:shown', {});
|
|
1762
1788
|
}
|
|
1763
1789
|
}
|
|
@@ -1767,6 +1793,7 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1767
1793
|
gate.hide();
|
|
1768
1794
|
renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1769
1795
|
}
|
|
1796
|
+
(_c = config.onLoginRequired) === null || _c === void 0 ? void 0 : _c.call(config);
|
|
1770
1797
|
if (!(err instanceof ApiError && err.status === 401)) {
|
|
1771
1798
|
emitter.emit('error', { message: 'Access check failed', error: err });
|
|
1772
1799
|
}
|
|
@@ -1785,7 +1812,7 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1785
1812
|
if (extensionAvailable) {
|
|
1786
1813
|
bridge.attach();
|
|
1787
1814
|
bridge.onAuthorizationResponse(data => {
|
|
1788
|
-
var _a, _b, _c;
|
|
1815
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1789
1816
|
state.set({
|
|
1790
1817
|
isLoggedIn: data.isAuthenticated,
|
|
1791
1818
|
hasAccess: data.doesHaveAccess,
|
|
@@ -1799,10 +1826,11 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1799
1826
|
gate.hide();
|
|
1800
1827
|
renderer.render('login', { onLogin: doLogin, onPurchase: doPurchase, onBuyMoreCredits: doBuyMoreCredits });
|
|
1801
1828
|
}
|
|
1829
|
+
(_c = config.onLoginRequired) === null || _c === void 0 ? void 0 : _c.call(config);
|
|
1802
1830
|
emitter.emit('paywall:shown', {});
|
|
1803
1831
|
}
|
|
1804
1832
|
else if (data.doesHaveAccess) {
|
|
1805
|
-
handleAccessGranted(0, (
|
|
1833
|
+
handleAccessGranted(0, (_d = data.creditBalance) !== null && _d !== void 0 ? _d : 0);
|
|
1806
1834
|
}
|
|
1807
1835
|
else {
|
|
1808
1836
|
if (!config.headless) {
|
|
@@ -1812,6 +1840,10 @@ function createPaywall(config, creditsApi, state, emitter, existingGate) {
|
|
|
1812
1840
|
creditBalance: data.creditBalance,
|
|
1813
1841
|
});
|
|
1814
1842
|
}
|
|
1843
|
+
(_e = config.onPurchaseRequired) === null || _e === void 0 ? void 0 : _e.call(config, {
|
|
1844
|
+
requiredCredits: (_f = data.requiredCredits) !== null && _f !== void 0 ? _f : null,
|
|
1845
|
+
creditBalance: (_g = data.creditBalance) !== null && _g !== void 0 ? _g : null,
|
|
1846
|
+
});
|
|
1815
1847
|
emitter.emit('paywall:shown', {});
|
|
1816
1848
|
}
|
|
1817
1849
|
});
|
|
@@ -2597,15 +2629,18 @@ function createCommentPanel(config, commentsApi, emitter, onClose) {
|
|
|
2597
2629
|
refreshUser();
|
|
2598
2630
|
refreshUser();
|
|
2599
2631
|
// Build panel if not already present
|
|
2600
|
-
let
|
|
2601
|
-
if (!
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
r.appendChild(
|
|
2632
|
+
let backdropEl = r.getElementById('cc-panel-backdrop');
|
|
2633
|
+
if (!backdropEl) {
|
|
2634
|
+
const newBackdrop = el('div');
|
|
2635
|
+
newBackdrop.className = 'cc-panel-backdrop';
|
|
2636
|
+
newBackdrop.id = 'cc-panel-backdrop';
|
|
2637
|
+
newBackdrop.addEventListener('click', closePanel);
|
|
2638
|
+
r.appendChild(newBackdrop);
|
|
2607
2639
|
r.appendChild(buildPanel());
|
|
2640
|
+
backdropEl = newBackdrop;
|
|
2608
2641
|
}
|
|
2642
|
+
// Capture in a const so the rAF callback has a non-nullable reference
|
|
2643
|
+
const backdrop = backdropEl;
|
|
2609
2644
|
// Animate open
|
|
2610
2645
|
requestAnimationFrame(() => {
|
|
2611
2646
|
var _a;
|
|
@@ -2705,7 +2740,26 @@ class ContentCredits {
|
|
|
2705
2740
|
async _start() {
|
|
2706
2741
|
// 1. Consume any token that arrived in the URL (mobile redirect flow)
|
|
2707
2742
|
consumeTokenFromUrl();
|
|
2708
|
-
// 2.
|
|
2743
|
+
// 2. Wire config-level callbacks so developers don't need separate on() calls.
|
|
2744
|
+
if (this.config.onStateChange) {
|
|
2745
|
+
this.state.subscribe(this.config.onStateChange);
|
|
2746
|
+
}
|
|
2747
|
+
if (this.config.onReady) {
|
|
2748
|
+
this.emitter.on('ready', ({ state }) => this.config.onReady(state));
|
|
2749
|
+
}
|
|
2750
|
+
if (this.config.onPurchased) {
|
|
2751
|
+
this.emitter.on('article:purchased', (payload) => this.config.onPurchased(payload));
|
|
2752
|
+
}
|
|
2753
|
+
if (this.config.onUserLogin) {
|
|
2754
|
+
this.emitter.on('auth:login', ({ user }) => this.config.onUserLogin(user));
|
|
2755
|
+
}
|
|
2756
|
+
if (this.config.onUserLogout) {
|
|
2757
|
+
this.emitter.on('auth:logout', () => this.config.onUserLogout());
|
|
2758
|
+
}
|
|
2759
|
+
if (this.config.onError) {
|
|
2760
|
+
this.emitter.on('error', (payload) => this.config.onError(payload));
|
|
2761
|
+
}
|
|
2762
|
+
// 3. Hide premium content immediately (synchronous) before any async work.
|
|
2709
2763
|
// This prevents the flash of full article content that would otherwise
|
|
2710
2764
|
// appear during the token-refresh and access-check network round-trips.
|
|
2711
2765
|
// Skipped in headless mode — the host app owns all DOM manipulation.
|
|
@@ -2715,13 +2769,13 @@ class ContentCredits {
|
|
|
2715
2769
|
});
|
|
2716
2770
|
if (!this.config.headless)
|
|
2717
2771
|
earlyGate.hide();
|
|
2718
|
-
//
|
|
2772
|
+
// 4. If no access token in memory/session, attempt a silent refresh.
|
|
2719
2773
|
// This runs on every new browser session (after the browser was closed)
|
|
2720
2774
|
// and silently re-authenticates the user using their stored refresh token.
|
|
2721
2775
|
if (!tokenStorage.has()) {
|
|
2722
2776
|
await tryRefreshSession(this.config.apiBaseUrl);
|
|
2723
2777
|
}
|
|
2724
|
-
// Pass the pre-created gate so createPaywall reuses the same instance
|
|
2778
|
+
// 5. Pass the pre-created gate so createPaywall reuses the same instance
|
|
2725
2779
|
// (and its hiddenNodes list) rather than creating a second one.
|
|
2726
2780
|
this.paywallModule = createPaywall(this.config, this.creditsApi, this.state, this.emitter, earlyGate);
|
|
2727
2781
|
if (this.config.enableComments) {
|
|
@@ -2820,6 +2874,32 @@ class ContentCredits {
|
|
|
2820
2874
|
isLoggedIn() {
|
|
2821
2875
|
return tokenStorage.has();
|
|
2822
2876
|
}
|
|
2877
|
+
/**
|
|
2878
|
+
* Log the current user out.
|
|
2879
|
+
*
|
|
2880
|
+
* Revokes the refresh token on the server (best-effort), clears all local
|
|
2881
|
+
* auth state, resets SDK state, and emits `auth:logout`.
|
|
2882
|
+
*/
|
|
2883
|
+
async logout() {
|
|
2884
|
+
const rt = refreshTokenStorage.get();
|
|
2885
|
+
if (rt) {
|
|
2886
|
+
try {
|
|
2887
|
+
await fetch(`${this.config.apiBaseUrl}/auth/log-out`, {
|
|
2888
|
+
method: 'POST',
|
|
2889
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2890
|
+
body: JSON.stringify({ refreshToken: rt }),
|
|
2891
|
+
credentials: 'omit',
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
catch (_a) {
|
|
2895
|
+
// Network error — proceed with local cleanup regardless
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
tokenStorage.clear();
|
|
2899
|
+
refreshTokenStorage.clear();
|
|
2900
|
+
this.state.reset();
|
|
2901
|
+
this.emitter.emit('auth:logout', {});
|
|
2902
|
+
}
|
|
2823
2903
|
/** Tear down the SDK — removes all UI, event listeners, and stored state. */
|
|
2824
2904
|
destroy() {
|
|
2825
2905
|
var _a, _b;
|
|
@@ -2830,7 +2910,7 @@ class ContentCredits {
|
|
|
2830
2910
|
}
|
|
2831
2911
|
/** SDK version string. */
|
|
2832
2912
|
static get version() {
|
|
2833
|
-
return "2.
|
|
2913
|
+
return "2.4.0";
|
|
2834
2914
|
}
|
|
2835
2915
|
}
|
|
2836
2916
|
// ── Auto-init from script data attributes (CDN usage) ────────────────────────
|