nodebb-plugin-pdf-secure 1.0.5 → 1.0.6

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/library.js CHANGED
@@ -43,12 +43,8 @@ plugin.addRoutes = async ({ router, middleware, helpers }) => {
43
43
  return helpers.formatApiResponse(400, res, new Error('Invalid file'));
44
44
  }
45
45
 
46
- // Get premium group name from settings (default: 'Premium')
47
- const settings = await meta.settings.get('pdf-secure');
48
- const premiumGroup = settings.premiumGroup || 'Premium';
49
-
50
- // Check if user is in premium group
51
- const isPremium = await groups.isMember(req.uid, premiumGroup);
46
+ // Premium check disabled for testing everyone gets full PDF
47
+ const isPremium = true;
52
48
 
53
49
  const nonce = nonceStore.generate(req.uid, safeName, isPremium);
54
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
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": {
@@ -1,144 +1,14 @@
1
1
  'use strict';
2
2
 
3
- console.log('[PDF-Secure] main.js loaded');
4
-
5
- (async () => {
6
- try {
7
- console.log('[PDF-Secure] Requesting hooks from app...');
8
- const hooks = await app.require('hooks');
9
- console.log('[PDF-Secure] Hooks loaded successfully');
10
-
11
- hooks.on('action:ajaxify.end', () => {
12
- console.log('[PDF-Secure] action:ajaxify.end fired');
13
- interceptPdfLinks();
14
- });
15
- } catch (err) {
16
- console.error('[PDF-Secure] Failed to initialize:', err);
17
- }
18
-
19
- function interceptPdfLinks() {
20
- const postContents = document.querySelectorAll('[component="post/content"]');
21
- console.log('[PDF-Secure] Found ' + postContents.length + ' post content areas');
22
-
23
- postContents.forEach((content, idx) => {
24
- const pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
25
- console.log('[PDF-Secure] Post #' + idx + ': found ' + pdfLinks.length + ' PDF links');
26
-
27
- pdfLinks.forEach((link) => {
28
- if (link.dataset.pdfSecure) {
29
- console.log('[PDF-Secure] Skipping already processed link:', link.href);
30
- return;
31
- }
32
- link.dataset.pdfSecure = 'true';
33
-
34
- const href = link.getAttribute('href');
35
- const parts = href.split('/');
36
- const filename = parts[parts.length - 1];
37
- console.log('[PDF-Secure] Processing link -> filename:', filename, 'href:', href);
38
-
39
- // Create inline viewer container
40
- const container = document.createElement('div');
41
- container.className = 'pdf-secure-inline';
42
- container.innerHTML =
43
- '<div class="pdf-secure-inline-header">' +
44
- '<i class="fa fa-file-pdf-o"></i> ' +
45
- '<span class="pdf-secure-filename">' + escapeHtml(link.textContent || filename) + '</span>' +
46
- '</div>' +
47
- '<div class="pdf-secure-inline-body">' +
48
- '<div class="pdf-secure-loading">Loading PDF...</div>' +
49
- '<div class="pdf-secure-error"></div>' +
50
- '<canvas class="pdf-secure-canvas"></canvas>' +
51
- '</div>' +
52
- '<div class="pdf-secure-inline-footer">' +
53
- '<button class="pdf-secure-prev" disabled>&#8249; Prev</button>' +
54
- '<span class="pdf-secure-page-info"></span>' +
55
- '<button class="pdf-secure-next" disabled>Next &#8250;</button>' +
56
- '</div>' +
57
- '<div class="pdf-secure-premium-banner">' +
58
- 'This is a preview (page 1 only). Upgrade to Premium for full access.' +
59
- '</div>';
60
-
61
- link.replaceWith(container);
62
- console.log('[PDF-Secure] Replaced link with inline container for:', filename);
63
-
64
- // Auto-load PDF immediately
65
- loadPdf(container, filename);
66
- });
67
- });
68
- }
69
-
70
- async function loadPdf(container, filename) {
71
- var loadingEl = container.querySelector('.pdf-secure-loading');
72
- var errorEl = container.querySelector('.pdf-secure-error');
73
-
74
- function showError(msg) {
75
- console.error('[PDF-Secure] ERROR for ' + filename + ':', msg);
76
- loadingEl.style.display = 'none';
77
- errorEl.style.display = 'flex';
78
- errorEl.textContent = msg;
79
- }
80
-
81
- try {
82
- // Step 1: Fetch nonce
83
- var nonceUrl = config.relative_path + '/api/v3/plugins/pdf-secure/nonce?file=' + encodeURIComponent(filename);
84
- console.log('[PDF-Secure] Step 1 - Fetching nonce from:', nonceUrl);
85
-
86
- var nonceRes = await fetch(nonceUrl, {
87
- credentials: 'same-origin',
88
- headers: { 'x-csrf-token': config.csrf_token },
89
- });
90
-
91
- console.log('[PDF-Secure] Step 1 - Nonce response status:', nonceRes.status);
92
-
93
- if (!nonceRes.ok) {
94
- showError(nonceRes.status === 401 ? 'Log in to view this PDF.' : 'Failed to load PDF. Status: ' + nonceRes.status);
95
- return;
96
- }
97
-
98
- var result = await nonceRes.json();
99
- console.log('[PDF-Secure] Step 1 - Nonce result:', JSON.stringify(result));
100
-
101
- var nonce = result.response.nonce;
102
- var isPremium = result.response.isPremium;
103
- console.log('[PDF-Secure] Step 1 - nonce:', nonce, 'isPremium:', isPremium);
104
-
105
- // Step 2: Fetch PDF binary
106
- var pdfUrl = config.relative_path + '/api/v3/plugins/pdf-secure/pdf-data?nonce=' + encodeURIComponent(nonce);
107
- console.log('[PDF-Secure] Step 2 - Fetching PDF binary from:', pdfUrl);
108
-
109
- var pdfRes = await fetch(pdfUrl, { credentials: 'same-origin' });
110
- console.log('[PDF-Secure] Step 2 - PDF response status:', pdfRes.status, 'content-type:', pdfRes.headers.get('content-type'));
111
-
112
- if (!pdfRes.ok) {
113
- showError('Failed to load PDF data. Status: ' + pdfRes.status);
114
- return;
115
- }
116
-
117
- var pdfArrayBuffer = await pdfRes.arrayBuffer();
118
- console.log('[PDF-Secure] Step 2 - PDF binary loaded, size:', pdfArrayBuffer.byteLength, 'bytes');
119
-
120
- // Step 3: Dynamic import viewer.js
121
- var viewerUrl = config.relative_path + '/plugins/nodebb-plugin-pdf-secure/static/lib/viewer.js';
122
- console.log('[PDF-Secure] Step 3 - Importing viewer from:', viewerUrl);
123
-
124
- var viewer = await import(viewerUrl);
125
- console.log('[PDF-Secure] Step 3 - Viewer module loaded, exports:', Object.keys(viewer));
126
-
127
- // Step 4: Initialize inline viewer
128
- console.log('[PDF-Secure] Step 4 - Calling initInlineViewer...');
129
- await viewer.initInlineViewer(container, pdfArrayBuffer, isPremium);
130
- console.log('[PDF-Secure] Step 4 - initInlineViewer completed successfully for:', filename);
131
-
132
- } catch (err) {
133
- console.error('[PDF-Secure] CATCH error for ' + filename + ':', err);
134
- console.error('[PDF-Secure] Error stack:', err.stack);
135
- showError('Failed to load PDF.');
136
- }
137
- }
138
-
139
- function escapeHtml(str) {
140
- var div = document.createElement('div');
141
- div.textContent = str;
142
- return div.innerHTML;
143
- }
3
+ // Bootstrapper: NodeBB bundles this into nodebb.min.js (AMD),
4
+ // so dynamic import() won't work here. Instead, inject a real
5
+ // ES module script that can import pdf.min.mjs natively.
6
+ (function () {
7
+ console.log('[PDF-Secure] main.js bootstrapper loaded');
8
+ var s = document.createElement('script');
9
+ s.type = 'module';
10
+ s.src = (window.config ? config.relative_path : '') +
11
+ '/plugins/nodebb-plugin-pdf-secure/static/lib/viewer.js';
12
+ document.head.appendChild(s);
13
+ console.log('[PDF-Secure] viewer.js module script injected');
144
14
  })();
@@ -1,112 +1,163 @@
1
- console.log('[PDF-Secure][Viewer] viewer.js module loading...');
2
-
3
1
  import { getDocument, GlobalWorkerOptions } from './pdf.min.mjs';
4
2
 
5
- var workerUrl = new URL('./pdf.worker.min.mjs', import.meta.url).href;
6
- GlobalWorkerOptions.workerSrc = workerUrl;
7
- console.log('[PDF-Secure][Viewer] Worker URL set to:', workerUrl);
8
-
9
- export async function initInlineViewer(container, pdfArrayBuffer, isPremium) {
10
- console.log('[PDF-Secure][Viewer] initInlineViewer called, buffer size:', pdfArrayBuffer.byteLength, 'isPremium:', isPremium);
3
+ GlobalWorkerOptions.workerSrc = new URL('./pdf.worker.min.mjs', import.meta.url).href;
4
+ console.log('[PDF-Secure][Viewer] ES module loaded, worker:', GlobalWorkerOptions.workerSrc);
5
+
6
+ // Listen for NodeBB SPA navigations (jQuery event)
7
+ $(window).on('action:ajaxify.end', function () {
8
+ console.log('[PDF-Secure][Viewer] action:ajaxify.end fired');
9
+ interceptPdfLinks();
10
+ });
11
+
12
+ // Also run immediately for the current page
13
+ console.log('[PDF-Secure][Viewer] Running initial interceptPdfLinks...');
14
+ interceptPdfLinks();
15
+
16
+ function interceptPdfLinks() {
17
+ var postContents = document.querySelectorAll('[component="post/content"]');
18
+ console.log('[PDF-Secure][Viewer] Found ' + postContents.length + ' post areas');
19
+
20
+ postContents.forEach(function (content, idx) {
21
+ var pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
22
+ console.log('[PDF-Secure][Viewer] Post #' + idx + ': ' + pdfLinks.length + ' PDF links');
23
+
24
+ pdfLinks.forEach(function (link) {
25
+ if (link.dataset.pdfSecure) return;
26
+ link.dataset.pdfSecure = 'true';
27
+
28
+ var href = link.getAttribute('href');
29
+ var parts = href.split('/');
30
+ var filename = parts[parts.length - 1];
31
+ console.log('[PDF-Secure][Viewer] Processing:', filename);
32
+
33
+ var container = document.createElement('div');
34
+ container.className = 'pdf-secure-inline';
35
+ container.innerHTML =
36
+ '<div class="pdf-secure-inline-header">' +
37
+ '<i class="fa fa-file-pdf-o"></i> ' +
38
+ '<span class="pdf-secure-filename">' + escapeHtml(link.textContent || filename) + '</span>' +
39
+ '</div>' +
40
+ '<div class="pdf-secure-inline-body">' +
41
+ '<div class="pdf-secure-loading">Loading PDF...</div>' +
42
+ '<div class="pdf-secure-error"></div>' +
43
+ '<canvas class="pdf-secure-canvas"></canvas>' +
44
+ '</div>' +
45
+ '<div class="pdf-secure-inline-footer">' +
46
+ '<button class="pdf-secure-prev" disabled>&#8249; Prev</button>' +
47
+ '<span class="pdf-secure-page-info"></span>' +
48
+ '<button class="pdf-secure-next" disabled>Next &#8250;</button>' +
49
+ '</div>';
50
+
51
+ link.replaceWith(container);
52
+ console.log('[PDF-Secure][Viewer] Container created for:', filename);
53
+
54
+ loadPdf(container, filename);
55
+ });
56
+ });
57
+ }
11
58
 
12
- var canvas = container.querySelector('.pdf-secure-canvas');
13
- var ctx = canvas.getContext('2d');
59
+ async function loadPdf(container, filename) {
14
60
  var loadingEl = container.querySelector('.pdf-secure-loading');
15
61
  var errorEl = container.querySelector('.pdf-secure-error');
62
+ var canvas = container.querySelector('.pdf-secure-canvas');
16
63
  var footer = container.querySelector('.pdf-secure-inline-footer');
17
64
  var prevBtn = container.querySelector('.pdf-secure-prev');
18
65
  var nextBtn = container.querySelector('.pdf-secure-next');
19
66
  var pageInfo = container.querySelector('.pdf-secure-page-info');
20
- var premiumBanner = container.querySelector('.pdf-secure-premium-banner');
21
67
  var bodyEl = container.querySelector('.pdf-secure-inline-body');
22
68
 
23
- console.log('[PDF-Secure][Viewer] DOM elements found:', {
24
- canvas: !!canvas,
25
- ctx: !!ctx,
26
- loadingEl: !!loadingEl,
27
- errorEl: !!errorEl,
28
- footer: !!footer,
29
- prevBtn: !!prevBtn,
30
- nextBtn: !!nextBtn,
31
- pageInfo: !!pageInfo,
32
- premiumBanner: !!premiumBanner,
33
- bodyEl: !!bodyEl,
34
- });
35
-
36
- var pdfDoc = null;
37
- var currentPage = 1;
38
- var totalPages = 0;
39
- var rendering = false;
40
-
41
- // Security: scoped to container
42
- container.addEventListener('contextmenu', function (e) { e.preventDefault(); });
43
- container.addEventListener('dragstart', function (e) { e.preventDefault(); });
44
- container.addEventListener('selectstart', function (e) { e.preventDefault(); });
45
-
46
69
  function showError(msg) {
47
- console.error('[PDF-Secure][Viewer] showError:', msg);
70
+ console.error('[PDF-Secure][Viewer] ERROR ' + filename + ':', msg);
48
71
  loadingEl.style.display = 'none';
49
72
  canvas.style.display = 'none';
50
73
  errorEl.style.display = 'flex';
51
74
  errorEl.textContent = msg;
52
75
  }
53
76
 
54
- async function renderPage(pageNum) {
55
- if (rendering) {
56
- console.log('[PDF-Secure][Viewer] renderPage skipped, already rendering');
77
+ try {
78
+ // Step 1: Fetch nonce
79
+ var nonceUrl = config.relative_path + '/api/v3/plugins/pdf-secure/nonce?file=' + encodeURIComponent(filename);
80
+ console.log('[PDF-Secure][Viewer] Step 1 - Nonce request:', nonceUrl);
81
+
82
+ var nonceRes = await fetch(nonceUrl, {
83
+ credentials: 'same-origin',
84
+ headers: { 'x-csrf-token': config.csrf_token },
85
+ });
86
+ console.log('[PDF-Secure][Viewer] Step 1 - Status:', nonceRes.status);
87
+
88
+ if (!nonceRes.ok) {
89
+ showError(nonceRes.status === 401 ? 'Log in to view this PDF.' : 'Failed to load PDF (' + nonceRes.status + ')');
57
90
  return;
58
91
  }
59
- rendering = true;
60
- console.log('[PDF-Secure][Viewer] renderPage(' + pageNum + ') started');
61
92
 
62
- try {
63
- var page = await pdfDoc.getPage(pageNum);
64
- var containerWidth = bodyEl.clientWidth - 20;
65
- console.log('[PDF-Secure][Viewer] Container width for scaling:', containerWidth);
93
+ var result = await nonceRes.json();
94
+ var nonce = result.response.nonce;
95
+ console.log('[PDF-Secure][Viewer] Step 1 - Nonce:', nonce);
66
96
 
67
- var viewport = page.getViewport({ scale: 1 });
68
- var scale = Math.min(containerWidth / viewport.width, 2.0);
69
- var scaledViewport = page.getViewport({ scale: scale });
97
+ // Step 2: Fetch PDF binary
98
+ var pdfUrl = config.relative_path + '/api/v3/plugins/pdf-secure/pdf-data?nonce=' + encodeURIComponent(nonce);
99
+ console.log('[PDF-Secure][Viewer] Step 2 - PDF request:', pdfUrl);
70
100
 
71
- console.log('[PDF-Secure][Viewer] Viewport: original=' + viewport.width + 'x' + viewport.height + ' scale=' + scale + ' final=' + scaledViewport.width + 'x' + scaledViewport.height);
101
+ var pdfRes = await fetch(pdfUrl, { credentials: 'same-origin' });
102
+ console.log('[PDF-Secure][Viewer] Step 2 - Status:', pdfRes.status);
72
103
 
73
- canvas.width = scaledViewport.width;
74
- canvas.height = scaledViewport.height;
75
-
76
- await page.render({ canvasContext: ctx, viewport: scaledViewport }).promise;
77
-
78
- currentPage = pageNum;
79
- pageInfo.textContent = currentPage + ' / ' + totalPages;
80
- prevBtn.disabled = currentPage <= 1;
81
- nextBtn.disabled = currentPage >= totalPages;
82
-
83
- console.log('[PDF-Secure][Viewer] renderPage(' + pageNum + ') completed successfully');
84
- } catch (err) {
85
- console.error('[PDF-Secure][Viewer] renderPage error:', err);
86
- showError('Error rendering page.');
104
+ if (!pdfRes.ok) {
105
+ showError('Failed to load PDF data (' + pdfRes.status + ')');
106
+ return;
87
107
  }
88
- rendering = false;
89
- }
90
108
 
91
- // Load PDF
92
- try {
93
- console.log('[PDF-Secure][Viewer] Loading PDF document...');
94
- var data = new Uint8Array(pdfArrayBuffer);
95
- pdfDoc = await getDocument({ data: data }).promise;
96
- totalPages = pdfDoc.numPages;
97
- console.log('[PDF-Secure][Viewer] PDF loaded, totalPages:', totalPages);
109
+ var pdfArrayBuffer = await pdfRes.arrayBuffer();
110
+ console.log('[PDF-Secure][Viewer] Step 2 - PDF loaded:', pdfArrayBuffer.byteLength, 'bytes');
111
+
112
+ // Step 3: Render PDF
113
+ console.log('[PDF-Secure][Viewer] Step 3 - Rendering...');
114
+ var pdfDoc = await getDocument({ data: new Uint8Array(pdfArrayBuffer) }).promise;
115
+ var totalPages = pdfDoc.numPages;
116
+ console.log('[PDF-Secure][Viewer] Step 3 - Pages:', totalPages);
98
117
 
99
118
  loadingEl.style.display = 'none';
100
119
  canvas.style.display = 'block';
101
120
 
102
- if (!isPremium) {
103
- premiumBanner.style.display = 'block';
104
- console.log('[PDF-Secure][Viewer] Premium banner shown (user is not premium)');
121
+ // Security: scoped to container
122
+ container.addEventListener('contextmenu', function (e) { e.preventDefault(); });
123
+ container.addEventListener('dragstart', function (e) { e.preventDefault(); });
124
+ container.addEventListener('selectstart', function (e) { e.preventDefault(); });
125
+
126
+ var ctx = canvas.getContext('2d');
127
+ var currentPage = 1;
128
+ var rendering = false;
129
+
130
+ async function renderPage(pageNum) {
131
+ if (rendering) return;
132
+ rendering = true;
133
+ console.log('[PDF-Secure][Viewer] renderPage(' + pageNum + ')');
134
+
135
+ try {
136
+ var page = await pdfDoc.getPage(pageNum);
137
+ var containerWidth = bodyEl.clientWidth - 20;
138
+ var vp = page.getViewport({ scale: 1 });
139
+ var scale = Math.min(containerWidth / vp.width, 2.0);
140
+ var scaled = page.getViewport({ scale: scale });
141
+
142
+ canvas.width = scaled.width;
143
+ canvas.height = scaled.height;
144
+
145
+ await page.render({ canvasContext: ctx, viewport: scaled }).promise;
146
+
147
+ currentPage = pageNum;
148
+ pageInfo.textContent = currentPage + ' / ' + totalPages;
149
+ prevBtn.disabled = currentPage <= 1;
150
+ nextBtn.disabled = currentPage >= totalPages;
151
+ console.log('[PDF-Secure][Viewer] renderPage(' + pageNum + ') done, canvas:', scaled.width + 'x' + scaled.height);
152
+ } catch (err) {
153
+ console.error('[PDF-Secure][Viewer] Render error:', err);
154
+ showError('Error rendering page.');
155
+ }
156
+ rendering = false;
105
157
  }
106
158
 
107
159
  await renderPage(1);
108
160
 
109
- // Show navigation if multi-page
110
161
  if (totalPages > 1) {
111
162
  footer.style.display = 'flex';
112
163
  prevBtn.addEventListener('click', function () {
@@ -115,13 +166,19 @@ export async function initInlineViewer(container, pdfArrayBuffer, isPremium) {
115
166
  nextBtn.addEventListener('click', function () {
116
167
  if (currentPage < totalPages) renderPage(currentPage + 1);
117
168
  });
118
- console.log('[PDF-Secure][Viewer] Navigation enabled for ' + totalPages + ' pages');
119
- } else {
120
- console.log('[PDF-Secure][Viewer] Single page PDF, navigation hidden');
169
+ console.log('[PDF-Secure][Viewer] Navigation enabled (' + totalPages + ' pages)');
121
170
  }
171
+
172
+ console.log('[PDF-Secure][Viewer] DONE for:', filename);
122
173
  } catch (err) {
123
- console.error('[PDF-Secure][Viewer] PDF load error:', err);
124
- console.error('[PDF-Secure][Viewer] Error stack:', err.stack);
174
+ console.error('[PDF-Secure][Viewer] CATCH:', err);
175
+ console.error('[PDF-Secure][Viewer] Stack:', err.stack);
125
176
  showError('Failed to load PDF.');
126
177
  }
127
178
  }
179
+
180
+ function escapeHtml(str) {
181
+ var d = document.createElement('div');
182
+ d.textContent = str;
183
+ return d.innerHTML;
184
+ }