nodebb-plugin-pdf-secure 1.2.5 → 1.2.7
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 +72 -0
- package/package.json +1 -1
- package/plugin.json +8 -0
- package/static/lib/main.js +136 -94
- package/static/viewer.html +43 -0
package/library.js
CHANGED
|
@@ -26,6 +26,14 @@ plugin.init = async (params) => {
|
|
|
26
26
|
console.error('[PDF-Secure] Failed to cache viewer template:', err.message);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// Double slash bypass protection - catches /uploads//files/ attempts
|
|
30
|
+
router.use((req, res, next) => {
|
|
31
|
+
if (req.path.includes('//') && req.path.toLowerCase().includes('.pdf')) {
|
|
32
|
+
return res.status(403).json({ error: 'Invalid path' });
|
|
33
|
+
}
|
|
34
|
+
next();
|
|
35
|
+
});
|
|
36
|
+
|
|
29
37
|
// PDF direct access blocker middleware
|
|
30
38
|
// Intercepts requests to uploaded PDF files and returns 403
|
|
31
39
|
router.get('/assets/uploads/files/:filename', (req, res, next) => {
|
|
@@ -115,4 +123,68 @@ plugin.addAdminNavigation = (header) => {
|
|
|
115
123
|
return header;
|
|
116
124
|
};
|
|
117
125
|
|
|
126
|
+
// Filter meta tags to hide PDF URLs and filenames
|
|
127
|
+
plugin.filterMetaTags = async (hookData) => {
|
|
128
|
+
if (!hookData || !hookData.tags) {
|
|
129
|
+
return hookData;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Filter out PDF-related meta tags
|
|
133
|
+
hookData.tags = hookData.tags.filter(tag => {
|
|
134
|
+
// Remove og:image and og:image:url if it contains .pdf
|
|
135
|
+
if ((tag.property === 'og:image' || tag.property === 'og:image:url') && tag.content && tag.content.toLowerCase().includes('.pdf')) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
// Remove twitter:image if it contains .pdf
|
|
139
|
+
if (tag.name === 'twitter:image' && tag.content && tag.content.toLowerCase().includes('.pdf')) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Sanitize description to hide .pdf extensions
|
|
146
|
+
hookData.tags = hookData.tags.map(tag => {
|
|
147
|
+
if ((tag.name === 'description' || tag.property === 'og:description') && tag.content) {
|
|
148
|
+
// Replace .pdf extension with empty string in description
|
|
149
|
+
tag.content = tag.content.replace(/\.pdf/gi, '');
|
|
150
|
+
}
|
|
151
|
+
return tag;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return hookData;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Transform PDF links to secure placeholders (server-side)
|
|
158
|
+
// This hides PDF URLs from: page source, API, RSS, ActivityPub
|
|
159
|
+
plugin.transformPdfLinks = async (data) => {
|
|
160
|
+
if (!data || !data.postData || !data.postData.content) {
|
|
161
|
+
return data;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Regex to match PDF links: <a href="...xxx.pdf">text</a>
|
|
165
|
+
// Captures: full URL path, filename, link text
|
|
166
|
+
const pdfLinkRegex = /<a\s+[^>]*href=["']([^"']*\/([^"'\/]+\.pdf))["'][^>]*>([^<]*)<\/a>/gi;
|
|
167
|
+
|
|
168
|
+
data.postData.content = data.postData.content.replace(pdfLinkRegex, (match, fullPath, filename, linkText) => {
|
|
169
|
+
// Decode filename to prevent double encoding (URL may already be encoded)
|
|
170
|
+
let decodedFilename;
|
|
171
|
+
try { decodedFilename = decodeURIComponent(filename); }
|
|
172
|
+
catch (e) { decodedFilename = filename; }
|
|
173
|
+
|
|
174
|
+
// Sanitize for HTML attribute
|
|
175
|
+
const safeFilename = decodedFilename.replace(/[<>"'&]/g, '');
|
|
176
|
+
const displayName = linkText.trim() || safeFilename;
|
|
177
|
+
|
|
178
|
+
// Return secure placeholder div instead of actual link
|
|
179
|
+
return `<div class="pdf-secure-placeholder" data-filename="${safeFilename}">
|
|
180
|
+
<svg viewBox="0 0 24 24" style="width:20px;height:20px;fill:#e81224;vertical-align:middle;margin-right:8px;">
|
|
181
|
+
<path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
|
|
182
|
+
</svg>
|
|
183
|
+
<span>${displayName}</span>
|
|
184
|
+
</div>`;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return data;
|
|
188
|
+
};
|
|
189
|
+
|
|
118
190
|
module.exports = plugin;
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
{
|
|
15
15
|
"hook": "filter:admin.header.build",
|
|
16
16
|
"method": "addAdminNavigation"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"hook": "filter:meta.getMetaTags",
|
|
20
|
+
"method": "filterMetaTags"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"hook": "filter:parse.post",
|
|
24
|
+
"method": "transformPdfLinks"
|
|
17
25
|
}
|
|
18
26
|
],
|
|
19
27
|
"staticDirs": {
|
package/static/lib/main.js
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
// Main plugin logic - PDF links become inline embedded viewers with lazy loading + queue
|
|
4
4
|
(async function () {
|
|
5
|
+
// ============================================
|
|
6
|
+
// PDF.js PRELOAD - Cache CDN assets before iframe loads
|
|
7
|
+
// ============================================
|
|
8
|
+
(function preloadPdfJs() {
|
|
9
|
+
const preloads = [
|
|
10
|
+
{ href: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js', as: 'script' },
|
|
11
|
+
{ href: 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css', as: 'style' }
|
|
12
|
+
];
|
|
13
|
+
preloads.forEach(({ href, as }) => {
|
|
14
|
+
if (!document.querySelector(`link[href="${href}"]`)) {
|
|
15
|
+
const link = document.createElement('link');
|
|
16
|
+
link.rel = 'preload';
|
|
17
|
+
link.href = href;
|
|
18
|
+
link.as = as;
|
|
19
|
+
link.crossOrigin = 'anonymous';
|
|
20
|
+
document.head.appendChild(link);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
})();
|
|
24
|
+
|
|
5
25
|
// Loading queue - only load one PDF at a time
|
|
6
26
|
const loadQueue = [];
|
|
7
27
|
let isLoading = false;
|
|
@@ -9,6 +29,9 @@
|
|
|
9
29
|
|
|
10
30
|
// Listen for postMessage from iframe when PDF is fully rendered
|
|
11
31
|
window.addEventListener('message', function (event) {
|
|
32
|
+
// Security: Only accept messages from same origin
|
|
33
|
+
if (event.origin !== window.location.origin) return;
|
|
34
|
+
|
|
12
35
|
if (event.data && event.data.type === 'pdf-secure-ready') {
|
|
13
36
|
console.log('[PDF-Secure] Queue: PDF ready -', event.data.filename);
|
|
14
37
|
if (currentResolver) {
|
|
@@ -59,8 +82,20 @@
|
|
|
59
82
|
var postContents = document.querySelectorAll('[component="post/content"]');
|
|
60
83
|
|
|
61
84
|
postContents.forEach(function (content) {
|
|
62
|
-
|
|
85
|
+
// NEW: Detect server-rendered secure placeholders (hides URL from source)
|
|
86
|
+
var placeholders = content.querySelectorAll('.pdf-secure-placeholder');
|
|
87
|
+
placeholders.forEach(function (placeholder) {
|
|
88
|
+
if (placeholder.dataset.pdfSecureProcessed) return;
|
|
89
|
+
placeholder.dataset.pdfSecureProcessed = 'true';
|
|
63
90
|
|
|
91
|
+
var filename = placeholder.dataset.filename;
|
|
92
|
+
var displayName = placeholder.querySelector('span')?.textContent || filename;
|
|
93
|
+
|
|
94
|
+
createPdfViewer(placeholder, filename, displayName);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// FALLBACK: Detect old-style PDF links (for backwards compatibility)
|
|
98
|
+
var pdfLinks = content.querySelectorAll('a[href$=".pdf"], a[href$=".PDF"]');
|
|
64
99
|
pdfLinks.forEach(function (link) {
|
|
65
100
|
if (link.dataset.pdfSecure) return;
|
|
66
101
|
link.dataset.pdfSecure = 'true';
|
|
@@ -68,112 +103,119 @@
|
|
|
68
103
|
var href = link.getAttribute('href');
|
|
69
104
|
var parts = href.split('/');
|
|
70
105
|
var filename = parts[parts.length - 1];
|
|
106
|
+
var displayName = link.textContent || filename;
|
|
71
107
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
108
|
+
createPdfViewer(link, filename, displayName);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function createPdfViewer(targetElement, filename, displayName) {
|
|
114
|
+
|
|
115
|
+
// Create container
|
|
116
|
+
var container = document.createElement('div');
|
|
117
|
+
container.className = 'pdf-secure-embed';
|
|
118
|
+
container.style.cssText = 'margin:16px 0;border-radius:12px;overflow:hidden;background:#1f1f1f;border:1px solid rgba(255,255,255,0.1);box-shadow:0 4px 20px rgba(0,0,0,0.25);';
|
|
119
|
+
|
|
120
|
+
// Header
|
|
121
|
+
var header = document.createElement('div');
|
|
122
|
+
header.className = 'pdf-secure-embed-header';
|
|
123
|
+
header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:linear-gradient(135deg,#2d2d2d 0%,#252525 100%);border-bottom:1px solid rgba(255,255,255,0.08);';
|
|
124
|
+
|
|
125
|
+
var title = document.createElement('div');
|
|
126
|
+
title.className = 'pdf-secure-embed-title';
|
|
127
|
+
title.style.cssText = 'display:flex;align-items:center;gap:10px;color:#fff;font-size:14px;font-weight:500;';
|
|
128
|
+
|
|
129
|
+
var icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
130
|
+
icon.setAttribute('viewBox', '0 0 24 24');
|
|
131
|
+
icon.style.cssText = 'width:20px;height:20px;min-width:20px;max-width:20px;fill:#e81224;flex-shrink:0;';
|
|
132
|
+
icon.innerHTML = '<path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>';
|
|
133
|
+
|
|
134
|
+
var nameSpan = document.createElement('span');
|
|
135
|
+
nameSpan.style.cssText = 'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:400px;';
|
|
136
|
+
try { nameSpan.textContent = decodeURIComponent(displayName); }
|
|
137
|
+
catch (e) { nameSpan.textContent = displayName; }
|
|
138
|
+
|
|
139
|
+
title.appendChild(icon);
|
|
140
|
+
title.appendChild(nameSpan);
|
|
141
|
+
|
|
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
|
+
header.appendChild(title);
|
|
159
|
+
header.appendChild(actions);
|
|
160
|
+
container.appendChild(header);
|
|
161
|
+
|
|
162
|
+
// Body with loading placeholder
|
|
163
|
+
var iframeWrapper = document.createElement('div');
|
|
164
|
+
iframeWrapper.className = 'pdf-secure-embed-body';
|
|
165
|
+
iframeWrapper.style.cssText = 'position:relative;width:100%;height:600px;background:#525659;';
|
|
166
|
+
|
|
167
|
+
// Loading placeholder - ALWAYS VISIBLE until PDF ready (z-index: 10)
|
|
168
|
+
var loadingPlaceholder = document.createElement('div');
|
|
169
|
+
loadingPlaceholder.className = 'pdf-loading-placeholder';
|
|
170
|
+
loadingPlaceholder.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#2d2d2d;color:#fff;gap:16px;z-index:10;transition:opacity 0.3s;';
|
|
171
|
+
loadingPlaceholder.innerHTML = `
|
|
128
172
|
<svg viewBox="0 0 24 24" style="width:48px;height:48px;fill:#555;">
|
|
129
173
|
<path d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z"/>
|
|
130
174
|
</svg>
|
|
131
175
|
<div class="pdf-loading-text" style="font-size:14px;color:#a0a0a0;">Sırada bekliyor...</div>
|
|
132
176
|
<style>@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}</style>
|
|
133
177
|
`;
|
|
134
|
-
|
|
178
|
+
iframeWrapper.appendChild(loadingPlaceholder);
|
|
135
179
|
|
|
136
|
-
|
|
180
|
+
container.appendChild(iframeWrapper);
|
|
137
181
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
targetElement.replaceWith(container);
|
|
192
|
+
|
|
193
|
+
// LAZY LOADING with Intersection Observer + Queue
|
|
194
|
+
var observer = new IntersectionObserver(function (entries) {
|
|
195
|
+
entries.forEach(function (entry) {
|
|
196
|
+
if (entry.isIntersecting) {
|
|
197
|
+
// Update placeholder to show loading state
|
|
198
|
+
var textEl = loadingPlaceholder.querySelector('.pdf-loading-text');
|
|
199
|
+
if (textEl) textEl.textContent = 'PDF Yükleniyor...';
|
|
200
|
+
|
|
201
|
+
var svgEl = loadingPlaceholder.querySelector('svg');
|
|
202
|
+
if (svgEl) {
|
|
203
|
+
svgEl.style.fill = '#0078d4';
|
|
204
|
+
svgEl.style.animation = 'spin 1s linear infinite';
|
|
205
|
+
svgEl.innerHTML = '<path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"/>';
|
|
144
206
|
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
link.replaceWith(container);
|
|
148
|
-
|
|
149
|
-
// LAZY LOADING with Intersection Observer + Queue
|
|
150
|
-
var observer = new IntersectionObserver(function (entries) {
|
|
151
|
-
entries.forEach(function (entry) {
|
|
152
|
-
if (entry.isIntersecting) {
|
|
153
|
-
// Update placeholder to show loading state
|
|
154
|
-
var textEl = loadingPlaceholder.querySelector('.pdf-loading-text');
|
|
155
|
-
if (textEl) textEl.textContent = 'PDF Yükleniyor...';
|
|
156
|
-
|
|
157
|
-
var svgEl = loadingPlaceholder.querySelector('svg');
|
|
158
|
-
if (svgEl) {
|
|
159
|
-
svgEl.style.fill = '#0078d4';
|
|
160
|
-
svgEl.style.animation = 'spin 1s linear infinite';
|
|
161
|
-
svgEl.innerHTML = '<path d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8z"/>';
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Add to queue
|
|
165
|
-
queuePdfLoad(iframeWrapper, filename, loadingPlaceholder);
|
|
166
|
-
observer.disconnect();
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}, {
|
|
170
|
-
rootMargin: '200px',
|
|
171
|
-
threshold: 0
|
|
172
|
-
});
|
|
173
207
|
|
|
174
|
-
|
|
208
|
+
// Add to queue
|
|
209
|
+
queuePdfLoad(iframeWrapper, filename, loadingPlaceholder);
|
|
210
|
+
observer.disconnect();
|
|
211
|
+
}
|
|
175
212
|
});
|
|
213
|
+
}, {
|
|
214
|
+
rootMargin: '200px',
|
|
215
|
+
threshold: 0
|
|
176
216
|
});
|
|
217
|
+
|
|
218
|
+
observer.observe(container);
|
|
177
219
|
}
|
|
178
220
|
|
|
179
221
|
function loadPdfIframe(wrapper, filename, placeholder) {
|
package/static/viewer.html
CHANGED
|
@@ -1334,6 +1334,34 @@
|
|
|
1334
1334
|
(function () {
|
|
1335
1335
|
'use strict';
|
|
1336
1336
|
|
|
1337
|
+
// ============================================
|
|
1338
|
+
// CANVAS EXPORT PROTECTION
|
|
1339
|
+
// Block toDataURL/toBlob for PDF render canvas only
|
|
1340
|
+
// Allows: thumbnails, annotations, other canvases
|
|
1341
|
+
// ============================================
|
|
1342
|
+
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
1343
|
+
const originalToBlob = HTMLCanvasElement.prototype.toBlob;
|
|
1344
|
+
|
|
1345
|
+
HTMLCanvasElement.prototype.toDataURL = function () {
|
|
1346
|
+
// Block only main PDF page canvases (inside .page elements in #viewerContainer)
|
|
1347
|
+
if (this.closest && this.closest('.page') && this.closest('#viewerContainer')) {
|
|
1348
|
+
console.warn('[Security] Canvas toDataURL blocked for PDF page');
|
|
1349
|
+
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='; // 1x1 transparent
|
|
1350
|
+
}
|
|
1351
|
+
return originalToDataURL.apply(this, arguments);
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
HTMLCanvasElement.prototype.toBlob = function (callback) {
|
|
1355
|
+
// Block only main PDF page canvases
|
|
1356
|
+
if (this.closest && this.closest('.page') && this.closest('#viewerContainer')) {
|
|
1357
|
+
console.warn('[Security] Canvas toBlob blocked for PDF page');
|
|
1358
|
+
// Return empty blob
|
|
1359
|
+
if (callback) callback(new Blob([], { type: 'image/png' }));
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
return originalToBlob.apply(this, arguments);
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1337
1365
|
pdfjsLib.GlobalWorkerOptions.workerSrc = '';
|
|
1338
1366
|
|
|
1339
1367
|
// State - now private, not accessible from console
|
|
@@ -1520,6 +1548,10 @@
|
|
|
1520
1548
|
// Security: Delete config containing sensitive data (nonce, key)
|
|
1521
1549
|
delete window.PDF_SECURE_CONFIG;
|
|
1522
1550
|
|
|
1551
|
+
// Security: Remove PDF.js globals to prevent console manipulation
|
|
1552
|
+
delete window.pdfjsLib;
|
|
1553
|
+
delete window.pdfjsViewer;
|
|
1554
|
+
|
|
1523
1555
|
// Security: Block dangerous PDF.js methods
|
|
1524
1556
|
if (pdfDoc) {
|
|
1525
1557
|
pdfDoc.getData = function () {
|
|
@@ -1536,6 +1568,17 @@
|
|
|
1536
1568
|
|
|
1537
1569
|
} catch (err) {
|
|
1538
1570
|
console.error('[PDF-Secure] Auto-load error:', err);
|
|
1571
|
+
|
|
1572
|
+
// Notify parent of error (prevents 60s queue hang)
|
|
1573
|
+
if (window.parent && window.parent !== window) {
|
|
1574
|
+
const config = window.PDF_SECURE_CONFIG || {};
|
|
1575
|
+
window.parent.postMessage({
|
|
1576
|
+
type: 'pdf-secure-ready',
|
|
1577
|
+
filename: config.filename,
|
|
1578
|
+
error: err.message
|
|
1579
|
+
}, '*');
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1539
1582
|
if (dropzone) {
|
|
1540
1583
|
dropzone.innerHTML = `
|
|
1541
1584
|
<svg viewBox="0 0 24 24" style="fill: #e81224;">
|