nodebb-plugin-pdf-secure 1.2.20 → 1.2.22

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.2.20",
3
+ "version": "1.2.22",
4
4
  "description": "Secure PDF viewer plugin for NodeBB - prevents downloading, enables canvas-only rendering with Premium group support",
5
5
  "main": "library.js",
6
6
  "repository": {
Binary file
@@ -1573,47 +1573,46 @@
1573
1573
  }
1574
1574
  }
1575
1575
 
1576
- /* Premium Lock Overlay */
1577
- #premiumLockOverlay {
1576
+ /* Per-page lock overlay - her kilitli sayfa icin */
1577
+ .page-lock-overlay {
1578
+ position: absolute;
1579
+ top: 0; left: 0; right: 0; bottom: 0;
1578
1580
  display: flex;
1579
1581
  flex-direction: column;
1580
1582
  align-items: center;
1581
1583
  justify-content: center;
1582
- padding: 60px 20px;
1583
- text-align: center;
1584
1584
  background: linear-gradient(180deg, rgba(31,31,31,0.95) 0%, rgba(20,20,20,0.98) 100%);
1585
- border-top: 2px solid rgba(255, 215, 0, 0.3);
1586
- min-height: 400px;
1587
- margin: 0 auto;
1588
- max-width: 100%;
1585
+ z-index: 15;
1586
+ text-align: center;
1587
+ padding: 20px;
1589
1588
  }
1590
1589
 
1591
- .premium-lock-icon {
1592
- margin-bottom: 20px;
1590
+ .page-lock-icon {
1591
+ margin-bottom: 16px;
1593
1592
  opacity: 0.9;
1594
1593
  }
1595
1594
 
1596
- .premium-lock-pages {
1597
- font-size: 22px;
1595
+ .page-lock-title {
1596
+ font-size: 20px;
1598
1597
  font-weight: 600;
1599
1598
  color: #ffd700;
1600
- margin-bottom: 12px;
1599
+ margin-bottom: 10px;
1601
1600
  }
1602
1601
 
1603
- .premium-lock-message {
1604
- font-size: 16px;
1602
+ .page-lock-message {
1603
+ font-size: 14px;
1605
1604
  color: #a0a0a0;
1606
- margin-bottom: 28px;
1607
- max-width: 400px;
1605
+ margin-bottom: 24px;
1606
+ max-width: 320px;
1608
1607
  line-height: 1.5;
1609
1608
  }
1610
1609
 
1611
- .premium-lock-button {
1610
+ .page-lock-button {
1612
1611
  display: inline-block;
1613
- padding: 14px 40px;
1612
+ padding: 12px 36px;
1614
1613
  background: linear-gradient(135deg, #ffd700 0%, #ffaa00 100%);
1615
1614
  color: #1a1a1a;
1616
- font-size: 16px;
1615
+ font-size: 15px;
1617
1616
  font-weight: 700;
1618
1617
  border-radius: 8px;
1619
1618
  text-decoration: none;
@@ -1621,22 +1620,29 @@
1621
1620
  box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
1622
1621
  }
1623
1622
 
1624
- .premium-lock-button:hover {
1623
+ .page-lock-button:hover {
1625
1624
  transform: translateY(-2px);
1626
1625
  box-shadow: 0 6px 20px rgba(255, 215, 0, 0.4);
1627
1626
  }
1628
1627
 
1629
- .premium-lock-secondary {
1630
- margin-top: 20px;
1631
- font-size: 14px;
1628
+ .page-lock-secondary {
1629
+ margin-top: 16px;
1630
+ font-size: 13px;
1632
1631
  color: #888;
1633
1632
  font-style: italic;
1634
1633
  }
1635
1634
 
1635
+ /* Blur locked page content - even if overlay removed, content unreadable */
1636
+ .page-locked-blur canvas,
1637
+ .page-locked-blur .textLayer,
1638
+ .page-locked-blur .annotationLayer {
1639
+ filter: blur(8px) !important;
1640
+ -webkit-filter: blur(8px) !important;
1641
+ }
1642
+
1636
1643
  /* Locked Thumbnails */
1637
1644
  .thumbnail.locked {
1638
- opacity: 0.4;
1639
- cursor: not-allowed;
1645
+ opacity: 0.5;
1640
1646
  position: relative;
1641
1647
  }
1642
1648
 
@@ -1654,22 +1660,17 @@
1654
1660
  }
1655
1661
 
1656
1662
  @media (max-width: 768px) {
1657
- #premiumLockOverlay {
1658
- padding: 40px 16px;
1659
- min-height: 300px;
1663
+ .page-lock-title {
1664
+ font-size: 17px;
1660
1665
  }
1661
1666
 
1662
- .premium-lock-pages {
1663
- font-size: 18px;
1667
+ .page-lock-message {
1668
+ font-size: 13px;
1664
1669
  }
1665
1670
 
1666
- .premium-lock-message {
1667
- font-size: 14px;
1668
- }
1669
-
1670
- .premium-lock-button {
1671
- padding: 12px 32px;
1672
- font-size: 14px;
1671
+ .page-lock-button {
1672
+ padding: 10px 28px;
1673
+ font-size: 13px;
1673
1674
  }
1674
1675
  }
1675
1676
  </style>
@@ -2224,24 +2225,61 @@
2224
2225
  return data.buffer;
2225
2226
  }
2226
2227
 
2227
- function showPremiumLockOverlay(totalPages) {
2228
- var viewerEl = document.getElementById('viewer');
2229
- if (!viewerEl) return;
2230
-
2228
+ function injectPageLock(pageEl) {
2229
+ if (!pageEl) return;
2230
+ // Add blur class to page content
2231
+ if (!pageEl.classList.contains('page-locked-blur')) {
2232
+ pageEl.classList.add('page-locked-blur');
2233
+ }
2234
+ // Don't duplicate overlay
2235
+ if (pageEl.querySelector('.page-lock-overlay')) return;
2231
2236
  var overlay = document.createElement('div');
2232
- overlay.id = 'premiumLockOverlay';
2237
+ overlay.className = 'page-lock-overlay';
2233
2238
  overlay.innerHTML = '\
2234
- <div class="premium-lock-icon">\
2235
- <svg viewBox="0 0 24 24" width="64" height="64" fill="#ffd700">\
2239
+ <div class="page-lock-icon">\
2240
+ <svg viewBox="0 0 24 24" width="48" height="48" fill="#ffd700">\
2236
2241
  <path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1s3.1 1.39 3.1 3.1v2z"/>\
2237
2242
  </svg>\
2238
2243
  </div>\
2239
- <div class="premium-lock-pages">' + (totalPages - 1) + ' sayfa daha kilitli</div>\
2240
- <div class="premium-lock-message">Bu icerigi goruntulemeye devam etmek icin Premium uyelik gereklidir.</div>\
2241
- <a href="https://forumtest.ieu.app/premium" target="_blank" class="premium-lock-button">Premium Satin Al</a>\
2242
- <div class="premium-lock-secondary">Materyal yukleyerek de Premium olabilirsiniz!</div>';
2244
+ <div class="page-lock-title">Kilitli</div>\
2245
+ <div class="page-lock-message">Bu sayfayi goruntulemeye devam etmek icin Premium uyelik gereklidir.</div>\
2246
+ <a href="https://forumtest.ieu.app/premium" target="_blank" class="page-lock-button">Premium Satin Al</a>\
2247
+ <div class="page-lock-secondary">Materyal yukleyerek de Premium olabilirsiniz!</div>';
2248
+ pageEl.style.position = 'relative';
2249
+ pageEl.appendChild(overlay);
2250
+ }
2251
+
2252
+ function isOverlayTampered(overlay) {
2253
+ var s = overlay.style;
2254
+ return s.position !== 'absolute' || s.display !== 'flex' ||
2255
+ s.zIndex !== '15' || s.opacity !== '1' ||
2256
+ s.visibility !== 'visible' || s.pointerEvents !== 'auto';
2257
+ }
2243
2258
 
2244
- viewerEl.appendChild(overlay);
2259
+ function resetOverlayCSS(overlay) {
2260
+ if (!isOverlayTampered(overlay)) return; // Skip if already correct (prevents observer loop)
2261
+ overlay.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;display:flex;z-index:15;opacity:1;visibility:visible;pointer-events:auto;';
2262
+ }
2263
+
2264
+ function applyPageLocks() {
2265
+ var pages = document.querySelectorAll('#viewer .page');
2266
+ pages.forEach(function (page) {
2267
+ var pageNum = parseInt(page.dataset.pageNumber || '0', 10);
2268
+ if (pageNum > 1) {
2269
+ // Ensure page is visible (undo any old display:none)
2270
+ if (page.style.display === 'none') {
2271
+ page.style.display = '';
2272
+ }
2273
+ injectPageLock(page);
2274
+ // CSS integrity: reset overlay styles in case of tampering
2275
+ var existing = page.querySelector('.page-lock-overlay');
2276
+ if (existing) resetOverlayCSS(existing);
2277
+ // Ensure blur class is present
2278
+ if (!page.classList.contains('page-locked-blur')) {
2279
+ page.classList.add('page-locked-blur');
2280
+ }
2281
+ }
2282
+ });
2245
2283
  }
2246
2284
 
2247
2285
  // ============================================
@@ -2250,18 +2288,7 @@
2250
2288
  function startPeriodicCheck() {
2251
2289
  setInterval(function () {
2252
2290
  if (!premiumInfo || premiumInfo.isPremium) return;
2253
- var pages = document.querySelectorAll('#viewer .page');
2254
- pages.forEach(function (page, idx) {
2255
- if (idx > 0 && page.style.display !== 'none') {
2256
- page.style.display = 'none';
2257
- }
2258
- });
2259
- if (!document.getElementById('premiumLockOverlay')) {
2260
- showPremiumLockOverlay(premiumInfo.totalPages);
2261
- }
2262
- if (pdfViewer && pdfViewer.currentPageNumber > 1) {
2263
- pdfViewer.currentPageNumber = 1;
2264
- }
2291
+ applyPageLocks();
2265
2292
  }, 2000);
2266
2293
  }
2267
2294
 
@@ -2272,32 +2299,43 @@
2272
2299
  var viewerEl = document.getElementById('viewer');
2273
2300
  if (!viewerEl) return;
2274
2301
 
2302
+ // Observer 1: Watch for lock overlay removal from any page
2275
2303
  new MutationObserver(function (mutations) {
2304
+ if (!premiumInfo || premiumInfo.isPremium) return;
2276
2305
  for (var i = 0; i < mutations.length; i++) {
2277
2306
  var removed = mutations[i].removedNodes;
2278
2307
  for (var j = 0; j < removed.length; j++) {
2279
- if (removed[j].id === 'premiumLockOverlay') {
2280
- showPremiumLockOverlay(premiumInfo.totalPages);
2281
- return;
2308
+ if (removed[j].classList && removed[j].classList.contains('page-lock-overlay')) {
2309
+ var pageEl = mutations[i].target;
2310
+ if (pageEl && pageEl.classList && pageEl.classList.contains('page')) {
2311
+ injectPageLock(pageEl);
2312
+ }
2282
2313
  }
2283
2314
  }
2284
2315
  }
2285
- }).observe(viewerEl, { childList: true });
2316
+ }).observe(viewerEl, { childList: true, subtree: true });
2286
2317
 
2318
+ // Observer 2: Watch for style/class tampering on overlays and pages
2287
2319
  new MutationObserver(function (mutations) {
2320
+ if (!premiumInfo || premiumInfo.isPremium) return;
2288
2321
  for (var i = 0; i < mutations.length; i++) {
2289
- var m = mutations[i];
2290
- if (m.type === 'attributes' && m.attributeName === 'style') {
2291
- var target = m.target;
2292
- if (target.classList && target.classList.contains('page')) {
2293
- var pageNum = parseInt(target.dataset.pageNumber || '0', 10);
2294
- if (pageNum > 1 && target.style.display !== 'none') {
2295
- target.style.display = 'none';
2296
- }
2322
+ var target = mutations[i].target;
2323
+ if (!target || !target.classList) continue;
2324
+ // Overlay CSS tampered - reset only if values actually differ
2325
+ if (target.classList.contains('page-lock-overlay')) {
2326
+ if (isOverlayTampered(target)) {
2327
+ resetOverlayCSS(target);
2328
+ }
2329
+ }
2330
+ // Blur class removed from locked page - re-add it
2331
+ if (target.classList.contains('page') && !target.classList.contains('page-locked-blur')) {
2332
+ var pageNum = parseInt(target.dataset.pageNumber || '0', 10);
2333
+ if (pageNum > 1) {
2334
+ target.classList.add('page-locked-blur');
2297
2335
  }
2298
2336
  }
2299
2337
  }
2300
- }).observe(viewerEl, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });
2338
+ }).observe(viewerEl, { subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
2301
2339
  }
2302
2340
 
2303
2341
  // Auto-load PDF if config is present (injected by NodeBB plugin)
@@ -2399,7 +2437,7 @@
2399
2437
  // Premium Gate: Client-side page restriction for non-premium users
2400
2438
  if (config.isPremium === false && pdfDoc && pdfDoc.numPages > 1) {
2401
2439
  premiumInfo = Object.freeze({ isPremium: false, totalPages: pdfDoc.numPages });
2402
- showPremiumLockOverlay(pdfDoc.numPages);
2440
+ applyPageLocks();
2403
2441
  startPeriodicCheck();
2404
2442
  setupAntiTampering();
2405
2443
  } else {
@@ -2477,9 +2515,11 @@
2477
2515
  }).promise;
2478
2516
 
2479
2517
  const thumb = document.createElement('div');
2480
- thumb.className = 'thumbnail' + (i === 1 ? ' active' : '');
2518
+ const isLocked = premiumInfo && !premiumInfo.isPremium && i > 1;
2519
+ thumb.className = 'thumbnail' + (i === 1 ? ' active' : '') + (isLocked ? ' locked' : '');
2481
2520
  thumb.dataset.page = i;
2482
- thumb.innerHTML = `<div class="thumbnailNum">${i}</div>`;
2521
+ thumb.innerHTML = `<div class="thumbnailNum">${i}</div>` +
2522
+ (isLocked ? '<div class="thumbnail-lock">\u{1F512}</div>' : '');
2483
2523
  thumb.insertBefore(canvas, thumb.firstChild);
2484
2524
 
2485
2525
  thumb.onclick = () => {
@@ -2525,6 +2565,12 @@
2525
2565
  eventBus.on('pagerendered', (evt) => {
2526
2566
  injectAnnotationLayer(evt.pageNumber);
2527
2567
 
2568
+ // Re-inject lock overlay if this is a locked page (PDF.js re-render removes it)
2569
+ if (premiumInfo && !premiumInfo.isPremium && evt.pageNumber > 1) {
2570
+ var pageEl = document.querySelector('#viewer .page[data-page-number="' + evt.pageNumber + '"]');
2571
+ if (pageEl) injectPageLock(pageEl);
2572
+ }
2573
+
2528
2574
  // Rotation is handled natively by PDF.js via pagesRotation
2529
2575
  });
2530
2576
 
@@ -2871,6 +2917,9 @@
2871
2917
 
2872
2918
  // Annotation Layer with Persistence
2873
2919
  async function injectAnnotationLayer(pageNum) {
2920
+ // Skip annotation injection on locked pages
2921
+ if (premiumInfo && !premiumInfo.isPremium && pageNum > 1) return;
2922
+
2874
2923
  const pageView = pdfViewer.getPageView(pageNum - 1);
2875
2924
  if (!pageView?.div) return;
2876
2925