nodebb-plugin-pdf-secure 1.2.7 → 1.2.8

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
@@ -129,6 +129,17 @@ plugin.filterMetaTags = async (hookData) => {
129
129
  return hookData;
130
130
  }
131
131
 
132
+ // Admin/Global Moderator bypass - no filtering for privileged users
133
+ if (hookData.req && hookData.req.uid) {
134
+ const [isAdmin, isGlobalMod] = await Promise.all([
135
+ groups.isMember(hookData.req.uid, 'administrators'),
136
+ groups.isMember(hookData.req.uid, 'Global Moderators'),
137
+ ]);
138
+ if (isAdmin || isGlobalMod) {
139
+ return hookData;
140
+ }
141
+ }
142
+
132
143
  // Filter out PDF-related meta tags
133
144
  hookData.tags = hookData.tags.filter(tag => {
134
145
  // Remove og:image and og:image:url if it contains .pdf
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
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": {
@@ -27,11 +27,30 @@
27
27
  let isLoading = false;
28
28
  let currentResolver = null;
29
29
 
30
- // Listen for postMessage from iframe when PDF is fully rendered
30
+ // ============================================
31
+ // SPA MEMORY CACHE - Cache decoded PDF buffers
32
+ // ============================================
33
+ const pdfBufferCache = new Map(); // filename -> ArrayBuffer
34
+ const CACHE_MAX_SIZE = 5; // ~50MB limit (avg 10MB per PDF)
35
+ let currentLoadingFilename = null;
36
+
37
+ function setCachedBuffer(filename, buffer) {
38
+ // Evict oldest if cache is full
39
+ if (pdfBufferCache.size >= CACHE_MAX_SIZE) {
40
+ const firstKey = pdfBufferCache.keys().next().value;
41
+ pdfBufferCache.delete(firstKey);
42
+ console.log('[PDF-Secure] Cache: Evicted', firstKey);
43
+ }
44
+ pdfBufferCache.set(filename, buffer);
45
+ console.log('[PDF-Secure] Cache: Stored', filename, '(', (buffer.byteLength / 1024 / 1024).toFixed(2), 'MB)');
46
+ }
47
+
48
+ // Listen for postMessage from iframe
31
49
  window.addEventListener('message', function (event) {
32
50
  // Security: Only accept messages from same origin
33
51
  if (event.origin !== window.location.origin) return;
34
52
 
53
+ // PDF ready - resolve queue
35
54
  if (event.data && event.data.type === 'pdf-secure-ready') {
36
55
  console.log('[PDF-Secure] Queue: PDF ready -', event.data.filename);
37
56
  if (currentResolver) {
@@ -39,6 +58,37 @@
39
58
  currentResolver = null;
40
59
  }
41
60
  }
61
+
62
+ // PDF buffer from viewer - cache it
63
+ if (event.data && event.data.type === 'pdf-secure-buffer') {
64
+ const { filename, buffer } = event.data;
65
+ if (filename && buffer) {
66
+ setCachedBuffer(filename, buffer);
67
+ }
68
+ }
69
+
70
+ // Viewer asking for cached buffer
71
+ if (event.data && event.data.type === 'pdf-secure-cache-request') {
72
+ const { filename } = event.data;
73
+ const cached = pdfBufferCache.get(filename);
74
+ if (cached && event.source) {
75
+ // Send cached buffer to viewer (transferable for 0-copy)
76
+ event.source.postMessage({
77
+ type: 'pdf-secure-cache-response',
78
+ filename: filename,
79
+ buffer: cached
80
+ }, event.origin, [cached.slice(0)]); // Clone buffer since we keep original
81
+ console.log('[PDF-Secure] Cache: Hit -', filename);
82
+ } else if (event.source) {
83
+ // No cache, viewer will fetch normally
84
+ event.source.postMessage({
85
+ type: 'pdf-secure-cache-response',
86
+ filename: filename,
87
+ buffer: null
88
+ }, event.origin);
89
+ console.log('[PDF-Secure] Cache: Miss -', filename);
90
+ }
91
+ }
42
92
  });
43
93
 
44
94
  async function processQueue() {
@@ -139,24 +189,7 @@
139
189
  title.appendChild(icon);
140
190
  title.appendChild(nameSpan);
141
191
 
142
- // Actions
143
- var actions = document.createElement('div');
144
- actions.style.cssText = 'display:flex;gap:8px;';
145
-
146
- var fullscreenBtn = document.createElement('button');
147
- fullscreenBtn.className = 'pdf-secure-fullscreen-btn';
148
- fullscreenBtn.title = 'Tam Ekran';
149
- fullscreenBtn.style.cssText = 'display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:rgba(255,255,255,0.08);border:none;border-radius:6px;cursor:pointer;';
150
-
151
- var fullscreenIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
152
- fullscreenIcon.setAttribute('viewBox', '0 0 24 24');
153
- fullscreenIcon.style.cssText = 'width:18px;height:18px;min-width:18px;max-width:18px;fill:#fff;';
154
- fullscreenIcon.innerHTML = '<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>';
155
- fullscreenBtn.appendChild(fullscreenIcon);
156
- actions.appendChild(fullscreenBtn);
157
-
158
192
  header.appendChild(title);
159
- header.appendChild(actions);
160
193
  container.appendChild(header);
161
194
 
162
195
  // Body with loading placeholder
@@ -179,15 +212,6 @@
179
212
 
180
213
  container.appendChild(iframeWrapper);
181
214
 
182
- // Fullscreen handler
183
- fullscreenBtn.addEventListener('click', function () {
184
- var iframe = iframeWrapper.querySelector('iframe');
185
- if (iframe) {
186
- if (iframe.requestFullscreen) iframe.requestFullscreen();
187
- else if (iframe.webkitRequestFullscreen) iframe.webkitRequestFullscreen();
188
- }
189
- });
190
-
191
215
  targetElement.replaceWith(container);
192
216
 
193
217
  // LAZY LOADING with Intersection Observer + Queue