feat(security,build): add COOP/COEP headers and improve PDF viewer initialization
- Add Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers to nginx.conf for enhanced security - Create serve.json configuration file with security headers for local development - Update npm scripts to use vite preview instead of serve package for consistency - Fix PDF viewer file parameter handling to prevent fallback to default URL - Enable annotation editor mode by default in PDF viewers (annotationEditorMode: 1) - Improve PDF preference management by clearing conflicting annotation editor settings before loading - Update iframe URL construction to use URL API for proper origin handling - Refactor annotation viewer setup to use eventBus for stamp button activation instead of direct DOM manipulation - Add localStorage preference configuration for signature editor and permissions in sign tool - Enhance security posture by implementing COOP/COEP headers required for SharedArrayBuffer and cross-origin isolation
This commit is contained in:
@@ -34,5 +34,7 @@ http {
|
|||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||||
|
add_header Cross-Origin-Embedder-Policy "require-corp" always;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,10 +16,12 @@
|
|||||||
"test:watch": "vitest watch",
|
"test:watch": "vitest watch",
|
||||||
"build:docker": "vite build",
|
"build:docker": "vite build",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
|
"update-version": "node scripts/update-version.js",
|
||||||
"release": "node scripts/release.js patch",
|
"release": "node scripts/release.js patch",
|
||||||
"release:minor": "node scripts/release.js minor",
|
"release:minor": "node scripts/release.js minor",
|
||||||
"release:major": "node scripts/release.js major",
|
"release:major": "node scripts/release.js major",
|
||||||
"serve:simple": "SIMPLE_MODE=true npm run build && npx serve dist -p 3000",
|
"serve:simple": "SIMPLE_MODE=true npm run build && npm run preview -- --port 3000",
|
||||||
|
"serve": "npm run build && npm run preview -- --port 3000",
|
||||||
"package": "node scripts/package-dist.js"
|
"package": "node scripts/package-dist.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -12372,7 +12372,10 @@ const PDFViewerApplication = {
|
|||||||
let file;
|
let file;
|
||||||
const queryString = document.location.search.substring(1);
|
const queryString = document.location.search.substring(1);
|
||||||
const params = parseQueryString(queryString);
|
const params = parseQueryString(queryString);
|
||||||
file = params.get("file") ?? AppOptions.get("defaultUrl");
|
file = params.get("file");
|
||||||
|
if (!file) {
|
||||||
|
file = "";
|
||||||
|
}
|
||||||
validateFileURL(file);
|
validateFileURL(file);
|
||||||
const fileInput = this._openFileInput = document.createElement("input");
|
const fileInput = this._openFileInput = document.createElement("input");
|
||||||
fileInput.id = "fileInput";
|
fileInput.id = "fileInput";
|
||||||
|
|||||||
@@ -207,7 +207,7 @@
|
|||||||
viewer: document.getElementById('viewer'),
|
viewer: document.getElementById('viewer'),
|
||||||
eventBus,
|
eventBus,
|
||||||
linkService,
|
linkService,
|
||||||
annotationEditorMode: 0,
|
annotationEditorMode: 1,
|
||||||
enableScripting: true,
|
enableScripting: true,
|
||||||
renderer: 'canvas'
|
renderer: 'canvas'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1512,7 +1512,7 @@ class BaseExternalServices {
|
|||||||
class BasePreferences {
|
class BasePreferences {
|
||||||
#defaults = Object.freeze({
|
#defaults = Object.freeze({
|
||||||
altTextLearnMoreUrl: "",
|
altTextLearnMoreUrl: "",
|
||||||
annotationEditorMode: 0,
|
annotationEditorMode: 1,
|
||||||
annotationMode: 2,
|
annotationMode: 2,
|
||||||
capCanvasAreaFactor: 200,
|
capCanvasAreaFactor: 200,
|
||||||
commentLearnMoreUrl: "",
|
commentLearnMoreUrl: "",
|
||||||
@@ -16429,7 +16429,10 @@ const PDFViewerApplication = {
|
|||||||
let file;
|
let file;
|
||||||
const queryString = document.location.search.substring(1);
|
const queryString = document.location.search.substring(1);
|
||||||
const params = parseQueryString(queryString);
|
const params = parseQueryString(queryString);
|
||||||
file = params.get("file") ?? AppOptions.get("defaultUrl");
|
file = params.get("file");
|
||||||
|
if (!file) {
|
||||||
|
file = "";
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
file = new URL(decodeURIComponent(file)).href;
|
file = new URL(decodeURIComponent(file)).href;
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -75,20 +75,24 @@ function main() {
|
|||||||
const newVersion = updateVersion(type);
|
const newVersion = updateVersion(type);
|
||||||
console.log(`📦 Updated version to ${newVersion}`);
|
console.log(`📦 Updated version to ${newVersion}`);
|
||||||
|
|
||||||
// 2. Add and commit changes
|
// 2. Update version in HTML files
|
||||||
execSync('git add package.json', { stdio: 'inherit' });
|
console.log(`📝 Updating version in HTML files...`);
|
||||||
|
execSync('npm run update-version', { stdio: 'inherit' });
|
||||||
|
|
||||||
|
// 3. Add and commit changes
|
||||||
|
execSync('git add package.json *.html src/pages/*.html', { stdio: 'inherit' });
|
||||||
execSync(`git commit -m "Release v${newVersion}"`, { stdio: 'inherit' });
|
execSync(`git commit -m "Release v${newVersion}"`, { stdio: 'inherit' });
|
||||||
console.log(`💾 Committed version change`);
|
console.log(`💾 Committed version change`);
|
||||||
|
|
||||||
// 3. Create git tag
|
// 4. Create git tag
|
||||||
const tagName = createGitTag(newVersion);
|
const tagName = createGitTag(newVersion);
|
||||||
|
|
||||||
// 4. Build and package the distribution files
|
// 5. Build and package the distribution files
|
||||||
console.log(`📦 Building and packaging distribution files...`);
|
console.log(`📦 Building and packaging distribution files...`);
|
||||||
execSync('npm run package', { stdio: 'inherit' });
|
execSync('npm run package', { stdio: 'inherit' });
|
||||||
console.log(`📦 Distribution files packaged successfully`);
|
console.log(`📦 Distribution files packaged successfully`);
|
||||||
|
|
||||||
// 5. Push everything to main
|
// 6. Push everything to main
|
||||||
console.log(`📤 Pushing to main...`);
|
console.log(`📤 Pushing to main...`);
|
||||||
execSync('git push origin main', { stdio: 'inherit' });
|
execSync('git push origin main', { stdio: 'inherit' });
|
||||||
execSync(`git push origin ${tagName}`, { stdio: 'inherit' });
|
execSync(`git push origin ${tagName}`, { stdio: 'inherit' });
|
||||||
|
|||||||
64
scripts/update-version.js
Normal file
64
scripts/update-version.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Script to update version numbers in HTML files from package.json
|
||||||
|
* Run this script whenever you need to sync HTML versions with package.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Read version from package.json
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
|
||||||
|
);
|
||||||
|
const version = packageJson.version;
|
||||||
|
|
||||||
|
// HTML files to update
|
||||||
|
const htmlFiles = [
|
||||||
|
'index.html',
|
||||||
|
'about.html',
|
||||||
|
'contact.html',
|
||||||
|
'faq.html',
|
||||||
|
'privacy.html',
|
||||||
|
'terms.html',
|
||||||
|
'src/pages/add-stamps.html',
|
||||||
|
'src/pages/bookmark.html',
|
||||||
|
'src/pages/json-to-pdf.html',
|
||||||
|
'src/pages/pdf-multi-tool.html',
|
||||||
|
'src/pages/pdf-to-json.html',
|
||||||
|
'src/pages/table-of-contents.html',
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(`Updating version to ${version} in HTML files...`);
|
||||||
|
|
||||||
|
let updatedCount = 0;
|
||||||
|
|
||||||
|
htmlFiles.forEach((file) => {
|
||||||
|
const filePath = path.join(__dirname, '..', file);
|
||||||
|
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.log(`⚠️ Skipping ${file} (not found)`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
|
||||||
|
// Replace version in <span id="app-version">X.X.X</span>
|
||||||
|
const regex = /(<span id="app-version">)[^<]+(<\/span>)/g;
|
||||||
|
const newContent = content.replace(regex, `$1${version}$2`);
|
||||||
|
|
||||||
|
if (content !== newContent) {
|
||||||
|
fs.writeFileSync(filePath, newContent, 'utf8');
|
||||||
|
console.log(`✓ Updated ${file}`);
|
||||||
|
updatedCount++;
|
||||||
|
} else {
|
||||||
|
console.log(`- ${file} (already up to date)`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`\nDone! Updated ${updatedCount} file(s) to version ${version}`);
|
||||||
25
serve.json
Normal file
25
serve.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"source": "**/*",
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"key": "Cross-Origin-Opener-Policy",
|
||||||
|
"value": "same-origin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Cross-Origin-Embedder-Policy",
|
||||||
|
"value": "require-corp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "X-Frame-Options",
|
||||||
|
"value": "SAMEORIGIN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "X-Content-Type-Options",
|
||||||
|
"value": "nosniff"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -58,11 +58,23 @@ async function loadPdfInViewer(file: File) {
|
|||||||
const blob = new Blob([arrayBuffer as BlobPart], { type: 'application/pdf' })
|
const blob = new Blob([arrayBuffer as BlobPart], { type: 'application/pdf' })
|
||||||
currentBlobUrl = URL.createObjectURL(blob)
|
currentBlobUrl = URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existingPrefsRaw = localStorage.getItem('pdfjs.preferences')
|
||||||
|
const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {}
|
||||||
|
delete (existingPrefs as any).annotationEditorMode
|
||||||
|
const newPrefs = {
|
||||||
|
...existingPrefs,
|
||||||
|
enablePermissions: false,
|
||||||
|
}
|
||||||
|
localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs))
|
||||||
|
} catch {}
|
||||||
|
|
||||||
const iframe = document.createElement('iframe')
|
const iframe = document.createElement('iframe')
|
||||||
iframe.className = 'w-full h-full border-0'
|
iframe.className = 'w-full h-full border-0'
|
||||||
iframe.allowFullscreen = true
|
iframe.allowFullscreen = true
|
||||||
|
|
||||||
iframe.src = `/pdfjs-annotation-viewer/web/viewer.html?file=${encodeURIComponent(currentBlobUrl)}`
|
const viewerUrl = new URL('/pdfjs-annotation-viewer/web/viewer.html', window.location.origin)
|
||||||
|
iframe.src = `${viewerUrl.toString()}?file=${encodeURIComponent(currentBlobUrl)}`
|
||||||
|
|
||||||
iframe.addEventListener('load', () => {
|
iframe.addEventListener('load', () => {
|
||||||
setupAnnotationViewer(iframe)
|
setupAnnotationViewer(iframe)
|
||||||
@@ -85,10 +97,14 @@ function setupAnnotationViewer(iframe: HTMLIFrameElement) {
|
|||||||
await app.initializedPromise
|
await app.initializedPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
const stampBtn = doc.getElementById('editorStamp') as HTMLButtonElement | null
|
const eventBus = app?.eventBus
|
||||||
if (stampBtn) {
|
if (eventBus && typeof eventBus._on === 'function') {
|
||||||
stampBtn.classList.remove('hidden')
|
eventBus._on('annotationeditoruimanager', () => {
|
||||||
stampBtn.disabled = false
|
try {
|
||||||
|
const stampBtn = doc.getElementById('editorStampButton') as HTMLButtonElement | null
|
||||||
|
stampBtn?.click()
|
||||||
|
} catch {}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const root = doc.querySelector('.PdfjsAnnotationExtension') as HTMLElement | null
|
const root = doc.querySelector('.PdfjsAnnotationExtension') as HTMLElement | null
|
||||||
|
|||||||
@@ -40,9 +40,21 @@ export async function setupSignTool() {
|
|||||||
const blob = new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
|
const blob = new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
|
||||||
const blobUrl = URL.createObjectURL(blob);
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
|
||||||
const viewerBase = '/pdfjs-viewer/viewer.html';
|
try {
|
||||||
|
const existingPrefsRaw = localStorage.getItem('pdfjs.preferences');
|
||||||
|
const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {};
|
||||||
|
delete (existingPrefs as any).annotationEditorMode;
|
||||||
|
const newPrefs = {
|
||||||
|
...existingPrefs,
|
||||||
|
enableSignatureEditor: true,
|
||||||
|
enablePermissions: false,
|
||||||
|
};
|
||||||
|
localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs));
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const viewerUrl = new URL('/pdfjs-viewer/viewer.html', window.location.origin);
|
||||||
const query = new URLSearchParams({ file: blobUrl });
|
const query = new URLSearchParams({ file: blobUrl });
|
||||||
iframe.src = `${viewerBase}?${query.toString()}`;
|
iframe.src = `${viewerUrl.toString()}?${query.toString()}`;
|
||||||
|
|
||||||
iframe.onload = () => {
|
iframe.onload = () => {
|
||||||
hideLoader();
|
hideLoader();
|
||||||
@@ -52,23 +64,27 @@ export async function setupSignTool() {
|
|||||||
if (viewerWindow && viewerWindow.PDFViewerApplication) {
|
if (viewerWindow && viewerWindow.PDFViewerApplication) {
|
||||||
const app = viewerWindow.PDFViewerApplication;
|
const app = viewerWindow.PDFViewerApplication;
|
||||||
const doc = viewerWindow.document;
|
const doc = viewerWindow.document;
|
||||||
|
const eventBus = app.eventBus;
|
||||||
const editorModeButtons = doc.getElementById('editorModeButtons');
|
eventBus?._on('annotationeditoruimanager', () => {
|
||||||
editorModeButtons?.classList.remove('hidden');
|
const editorModeButtons = doc.getElementById('editorModeButtons');
|
||||||
|
editorModeButtons?.classList.remove('hidden');
|
||||||
const editorSignature = doc.getElementById('editorSignature');
|
const editorSignature = doc.getElementById('editorSignature');
|
||||||
editorSignature?.removeAttribute('hidden');
|
editorSignature?.removeAttribute('hidden');
|
||||||
const editorSignatureButton = doc.getElementById('editorSignatureButton') as HTMLButtonElement | null;
|
const editorSignatureButton = doc.getElementById('editorSignatureButton') as HTMLButtonElement | null;
|
||||||
if (editorSignatureButton) {
|
if (editorSignatureButton) {
|
||||||
editorSignatureButton.disabled = false;
|
editorSignatureButton.disabled = false;
|
||||||
}
|
}
|
||||||
|
const editorStamp = doc.getElementById('editorStamp');
|
||||||
const editorStamp = doc.getElementById('editorStamp');
|
editorStamp?.removeAttribute('hidden');
|
||||||
editorStamp?.removeAttribute('hidden');
|
const editorStampButton = doc.getElementById('editorStampButton') as HTMLButtonElement | null;
|
||||||
const editorStampButton = doc.getElementById('editorStampButton') as HTMLButtonElement | null;
|
if (editorStampButton) {
|
||||||
if (editorStampButton) {
|
editorStampButton.disabled = false;
|
||||||
editorStampButton.disabled = false;
|
}
|
||||||
}
|
try {
|
||||||
|
const highlightBtn = doc.getElementById('editorHighlightButton') as HTMLButtonElement | null;
|
||||||
|
highlightBtn?.click();
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Could not initialize base PDF.js viewer for signing:', e);
|
console.error('Could not initialize base PDF.js viewer for signing:', e);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { createIcons, icons } from 'lucide';
|
|||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
import '../css/styles.css';
|
import '../css/styles.css';
|
||||||
import { formatStars } from './utils/helpers.js';
|
import { formatStars } from './utils/helpers.js';
|
||||||
|
import '../version.js';
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
|
|||||||
18
src/version.ts
Normal file
18
src/version.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
|
export const APP_VERSION = packageJson.version;
|
||||||
|
|
||||||
|
export function injectVersion() {
|
||||||
|
const versionElements = document.querySelectorAll('#app-version, #app-version-simple');
|
||||||
|
versionElements.forEach((element) => {
|
||||||
|
element.textContent = APP_VERSION;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', injectVersion);
|
||||||
|
} else {
|
||||||
|
injectVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
|
||||||
/* Path aliases (optional but helpful) */
|
/* Path aliases (optional but helpful) */
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ export default defineConfig(({ mode }) => ({
|
|||||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
preview: {
|
||||||
|
headers: {
|
||||||
|
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||||
|
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||||
|
},
|
||||||
|
},
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
@@ -51,6 +57,7 @@ export default defineConfig(({ mode }) => ({
|
|||||||
'pdf-to-json': resolve(__dirname, 'src/pages/pdf-to-json.html'),
|
'pdf-to-json': resolve(__dirname, 'src/pages/pdf-to-json.html'),
|
||||||
'json-to-pdf': resolve(__dirname, 'src/pages/json-to-pdf.html'),
|
'json-to-pdf': resolve(__dirname, 'src/pages/json-to-pdf.html'),
|
||||||
'pdf-multi-tool': resolve(__dirname, 'src/pages/pdf-multi-tool.html'),
|
'pdf-multi-tool': resolve(__dirname, 'src/pages/pdf-multi-tool.html'),
|
||||||
|
'add-stamps': resolve(__dirname, 'src/pages/add-stamps.html'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user