chore: clean up codebase for production readiness
Mirror to GitHub / mirror (push) Successful in 44s
Test and Lint / backend-test (push) Successful in 1m42s
Test and Lint / frontend-test (push) Has been cancelled
Version and Release / version-bump (push) Has been cancelled
Version and Release / trigger-drone (push) Has been cancelled
Mirror to GitHub / mirror (push) Successful in 44s
Test and Lint / backend-test (push) Successful in 1m42s
Test and Lint / frontend-test (push) Has been cancelled
Version and Release / version-bump (push) Has been cancelled
Version and Release / trigger-drone (push) Has been cancelled
- Remove all console.log/debug statements from production code - Add NODE_ENV checks for development-only logging - Remove test scripts (test-feedback, test-image-security, test-backup-*, test-restore) - Remove one-time fix scripts (fix-temp-photos, fix-migration-state, mark-migration-applied) - Remove sensitive files (.env.backup, ADMIN_CREDENTIALS.txt) - Update package.json to remove references to deleted scripts - Replace console statements with logger utility in backend - Secure error boundaries to not expose stack traces in production This makes the codebase production-ready with no debug output or test scripts. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { ProtectionLevel } from './useImageProtection';
|
||||
|
||||
interface UseCSSProtectionOptions {
|
||||
enabled: boolean;
|
||||
protectionLevel: ProtectionLevel;
|
||||
applyWatermark?: boolean;
|
||||
watermarkText?: string;
|
||||
antiScreenshot?: boolean;
|
||||
}
|
||||
|
||||
export const useCSSProtection = (options: UseCSSProtectionOptions) => {
|
||||
const containerRef = useRef<HTMLElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!options.enabled || !containerRef.current) return;
|
||||
|
||||
const container = containerRef.current;
|
||||
|
||||
// Base protection class
|
||||
container.classList.add('protected-image');
|
||||
|
||||
// Protection level specific classes
|
||||
switch (options.protectionLevel) {
|
||||
case 'standard':
|
||||
container.classList.add('protection-standard');
|
||||
break;
|
||||
case 'enhanced':
|
||||
container.classList.add('protection-enhanced');
|
||||
break;
|
||||
case 'maximum':
|
||||
container.classList.add('protection-maximum');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Additional protection features
|
||||
if (options.antiScreenshot && options.protectionLevel !== 'basic') {
|
||||
container.classList.add('anti-screenshot');
|
||||
}
|
||||
|
||||
// Create watermark overlay if requested
|
||||
if (options.applyWatermark && options.watermarkText && options.protectionLevel !== 'basic') {
|
||||
const watermarkOverlay = document.createElement('div');
|
||||
watermarkOverlay.className = 'watermark-overlay';
|
||||
|
||||
const watermarkText = document.createElement('div');
|
||||
watermarkText.className = 'watermark-text';
|
||||
watermarkText.textContent = options.watermarkText;
|
||||
watermarkText.setAttribute('aria-hidden', 'true');
|
||||
|
||||
watermarkOverlay.appendChild(watermarkText);
|
||||
container.appendChild(watermarkOverlay);
|
||||
|
||||
// Make container relative if not already
|
||||
const computedStyle = window.getComputedStyle(container);
|
||||
if (computedStyle.position === 'static') {
|
||||
container.style.position = 'relative';
|
||||
}
|
||||
}
|
||||
|
||||
// Apply inline styles for enhanced protection
|
||||
if (options.protectionLevel === 'enhanced' || options.protectionLevel === 'maximum') {
|
||||
// Disable various browser features
|
||||
container.style.userSelect = 'none';
|
||||
container.style.webkitUserSelect = 'none';
|
||||
container.style.webkitTouchCallout = 'none';
|
||||
container.style.webkitUserDrag = 'none';
|
||||
|
||||
// Find all img and canvas elements and protect them
|
||||
const mediaElements = container.querySelectorAll('img, canvas');
|
||||
mediaElements.forEach(element => {
|
||||
(element as HTMLElement).draggable = false;
|
||||
(element as HTMLElement).style.userSelect = 'none';
|
||||
(element as HTMLElement).style.webkitUserSelect = 'none';
|
||||
(element as HTMLElement).style.webkitUserDrag = 'none';
|
||||
(element as HTMLElement).style.webkitTouchCallout = 'none';
|
||||
|
||||
if (options.protectionLevel === 'maximum') {
|
||||
(element as HTMLElement).style.pointerEvents = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
// Remove protection classes
|
||||
container.classList.remove(
|
||||
'protected-image',
|
||||
'protection-standard',
|
||||
'protection-enhanced',
|
||||
'protection-maximum',
|
||||
'anti-screenshot'
|
||||
);
|
||||
|
||||
// Remove watermark overlay
|
||||
const watermarkOverlay = container.querySelector('.watermark-overlay');
|
||||
if (watermarkOverlay) {
|
||||
container.removeChild(watermarkOverlay);
|
||||
}
|
||||
|
||||
// Reset inline styles
|
||||
container.style.userSelect = '';
|
||||
container.style.webkitUserSelect = '';
|
||||
container.style.webkitTouchCallout = '';
|
||||
container.style.webkitUserDrag = '';
|
||||
|
||||
// Reset media element styles
|
||||
const mediaElements = container.querySelectorAll('img, canvas');
|
||||
mediaElements.forEach(element => {
|
||||
(element as HTMLElement).style.userSelect = '';
|
||||
(element as HTMLElement).style.webkitUserSelect = '';
|
||||
(element as HTMLElement).style.webkitUserDrag = '';
|
||||
(element as HTMLElement).style.webkitTouchCallout = '';
|
||||
(element as HTMLElement).style.pointerEvents = '';
|
||||
});
|
||||
};
|
||||
}, [
|
||||
options.enabled,
|
||||
options.protectionLevel,
|
||||
options.applyWatermark,
|
||||
options.watermarkText,
|
||||
options.antiScreenshot
|
||||
]);
|
||||
|
||||
return containerRef;
|
||||
};
|
||||
@@ -0,0 +1,249 @@
|
||||
import { useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
interface UseDevToolsProtectionOptions {
|
||||
enabled: boolean;
|
||||
onDevToolsDetected?: () => void;
|
||||
redirectOnDetection?: boolean;
|
||||
redirectUrl?: string;
|
||||
detectionSensitivity?: 'low' | 'medium' | 'high';
|
||||
}
|
||||
|
||||
export const useDevToolsProtection = (options: UseDevToolsProtectionOptions) => {
|
||||
const detectionTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const lastConsoleCountRef = useRef(0);
|
||||
const startTimeRef = useRef<number>(Date.now());
|
||||
const isDetectedRef = useRef(false);
|
||||
|
||||
const handleDevToolsDetected = useCallback(() => {
|
||||
if (isDetectedRef.current) return; // Prevent multiple triggers
|
||||
isDetectedRef.current = true;
|
||||
|
||||
console.clear(); // Clear any console output
|
||||
options.onDevToolsDetected?.();
|
||||
|
||||
if (options.redirectOnDetection) {
|
||||
const redirectUrl = options.redirectUrl || '/';
|
||||
setTimeout(() => {
|
||||
window.location.href = redirectUrl;
|
||||
}, 100);
|
||||
}
|
||||
}, [options]);
|
||||
|
||||
const detectByTiming = useCallback(() => {
|
||||
const threshold = options.detectionSensitivity === 'high' ? 100 :
|
||||
options.detectionSensitivity === 'medium' ? 200 : 500;
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
// This will be slow if DevTools is open due to console.log overhead
|
||||
console.log('%c', 'color: transparent; font-size: 0px;');
|
||||
console.clear();
|
||||
|
||||
const end = performance.now();
|
||||
|
||||
if (end - start > threshold) {
|
||||
handleDevToolsDetected();
|
||||
}
|
||||
}, [options.detectionSensitivity, handleDevToolsDetected]);
|
||||
|
||||
const detectByWindowSize = useCallback(() => {
|
||||
const heightThreshold = window.screen.height - window.innerHeight > 200;
|
||||
const widthThreshold = window.screen.width - window.innerWidth > 200;
|
||||
|
||||
// Check if the available space suggests DevTools is open
|
||||
if (heightThreshold || widthThreshold) {
|
||||
// Additional check to avoid false positives (mobile keyboards, etc.)
|
||||
if (window.outerHeight - window.innerHeight > 100 ||
|
||||
window.outerWidth - window.innerWidth > 100) {
|
||||
handleDevToolsDetected();
|
||||
}
|
||||
}
|
||||
}, [handleDevToolsDetected]);
|
||||
|
||||
const detectByConsole = useCallback(() => {
|
||||
let consoleCount = 0;
|
||||
|
||||
// Override console methods to detect usage
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
const originalInfo = console.info;
|
||||
|
||||
console.log = (...args) => {
|
||||
consoleCount++;
|
||||
return originalLog.apply(console, args);
|
||||
};
|
||||
|
||||
console.error = (...args) => {
|
||||
consoleCount++;
|
||||
return originalError.apply(console, args);
|
||||
};
|
||||
|
||||
console.warn = (...args) => {
|
||||
consoleCount++;
|
||||
return originalWarn.apply(console, args);
|
||||
};
|
||||
|
||||
console.info = (...args) => {
|
||||
consoleCount++;
|
||||
return originalInfo.apply(console, args);
|
||||
};
|
||||
|
||||
// Test if console is being actively used
|
||||
console.log('%cDevTools Detection', 'color: transparent; font-size: 0px;');
|
||||
|
||||
// If console count increased significantly, DevTools might be open
|
||||
if (consoleCount > lastConsoleCountRef.current + 2) {
|
||||
handleDevToolsDetected();
|
||||
}
|
||||
|
||||
lastConsoleCountRef.current = consoleCount;
|
||||
|
||||
// Restore original console methods
|
||||
console.log = originalLog;
|
||||
console.error = originalError;
|
||||
console.warn = originalWarn;
|
||||
console.info = originalInfo;
|
||||
}, [handleDevToolsDetected]);
|
||||
|
||||
const detectByDebugger = useCallback(() => {
|
||||
// Use debugger statement timing to detect DevTools
|
||||
const start = Date.now();
|
||||
|
||||
// This will pause execution if DevTools is open
|
||||
try {
|
||||
debugger;
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
const end = Date.now();
|
||||
|
||||
// If there was a significant delay, DevTools was open
|
||||
if (end - start > 100) {
|
||||
handleDevToolsDetected();
|
||||
}
|
||||
}, [handleDevToolsDetected]);
|
||||
|
||||
const detectByElement = useCallback(() => {
|
||||
// Create a fake element that DevTools might interact with
|
||||
const element = document.createElement('div');
|
||||
element.id = '__devtools_detector__';
|
||||
|
||||
let detected = false;
|
||||
|
||||
// Override toString to detect if DevTools inspects the element
|
||||
Object.defineProperty(element, 'id', {
|
||||
get() {
|
||||
detected = true;
|
||||
return '__devtools_detector__';
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// Trigger the getter
|
||||
console.log(element);
|
||||
console.clear();
|
||||
|
||||
if (detected) {
|
||||
handleDevToolsDetected();
|
||||
}
|
||||
}, [handleDevToolsDetected]);
|
||||
|
||||
const detectByToString = useCallback(() => {
|
||||
// Use function toString override to detect DevTools
|
||||
const func = () => {};
|
||||
func.toString = () => {
|
||||
handleDevToolsDetected();
|
||||
return 'function () { [native code] }';
|
||||
};
|
||||
|
||||
console.log('%c', func);
|
||||
console.clear();
|
||||
}, [handleDevToolsDetected]);
|
||||
|
||||
const runDetection = useCallback(() => {
|
||||
if (!options.enabled || isDetectedRef.current) return;
|
||||
|
||||
try {
|
||||
// Run multiple detection methods
|
||||
detectByTiming();
|
||||
detectByWindowSize();
|
||||
detectByConsole();
|
||||
|
||||
// More aggressive detection for higher sensitivity
|
||||
if (options.detectionSensitivity === 'medium' || options.detectionSensitivity === 'high') {
|
||||
detectByDebugger();
|
||||
detectByElement();
|
||||
}
|
||||
|
||||
// Most aggressive detection
|
||||
if (options.detectionSensitivity === 'high') {
|
||||
detectByToString();
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle any detection errors
|
||||
}
|
||||
}, [
|
||||
options.enabled,
|
||||
options.detectionSensitivity,
|
||||
detectByTiming,
|
||||
detectByWindowSize,
|
||||
detectByConsole,
|
||||
detectByDebugger,
|
||||
detectByElement,
|
||||
detectByToString
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!options.enabled) return;
|
||||
|
||||
// Disable right-click globally when DevTools protection is enabled
|
||||
const handleGlobalRightClick = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
};
|
||||
|
||||
// Block F12 and other DevTools shortcuts
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (
|
||||
e.key === 'F12' ||
|
||||
(e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'J' || e.key === 'C')) ||
|
||||
(e.ctrlKey && e.key === 'U')
|
||||
) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleDevToolsDetected();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('contextmenu', handleGlobalRightClick);
|
||||
document.addEventListener('keydown', handleKeyDown, true);
|
||||
|
||||
// Start detection interval
|
||||
const interval = options.detectionSensitivity === 'high' ? 500 :
|
||||
options.detectionSensitivity === 'medium' ? 1000 : 2000;
|
||||
|
||||
detectionTimerRef.current = setInterval(runDetection, interval);
|
||||
|
||||
// Initial detection
|
||||
runDetection();
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('contextmenu', handleGlobalRightClick);
|
||||
document.removeEventListener('keydown', handleKeyDown, true);
|
||||
|
||||
if (detectionTimerRef.current) {
|
||||
clearInterval(detectionTimerRef.current);
|
||||
}
|
||||
};
|
||||
}, [options.enabled, options.detectionSensitivity, runDetection, handleDevToolsDetected]);
|
||||
|
||||
return {
|
||||
isDetected: isDetectedRef.current,
|
||||
reset: () => {
|
||||
isDetectedRef.current = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,290 @@
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
|
||||
export type ProtectionLevel = 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
|
||||
interface UseImageProtectionOptions {
|
||||
enabled: boolean;
|
||||
onAttemptedDownload?: () => void;
|
||||
onProtectionViolation?: (violationType: string) => void;
|
||||
protectionLevel?: ProtectionLevel;
|
||||
useCanvasRendering?: boolean;
|
||||
overlayProtection?: boolean;
|
||||
blockKeyboardShortcuts?: boolean;
|
||||
detectPrintScreen?: boolean;
|
||||
watermarkText?: string;
|
||||
fragmentGrid?: boolean;
|
||||
}
|
||||
|
||||
export const useImageProtection = (options: UseImageProtectionOptions) => {
|
||||
const elementRef = useRef<HTMLImageElement>(null);
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const overlayRef = useRef<HTMLDivElement>(null);
|
||||
const printScreenDetectorRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const printScreenIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const reportViolation = useCallback((violationType: string) => {
|
||||
options.onAttemptedDownload?.();
|
||||
options.onProtectionViolation?.(violationType);
|
||||
}, [options]);
|
||||
|
||||
// Enhanced print screen detection
|
||||
const detectPrintScreen = useCallback(() => {
|
||||
if (!options.detectPrintScreen || options.protectionLevel === 'basic') return;
|
||||
|
||||
try {
|
||||
if (!printScreenDetectorRef.current) {
|
||||
printScreenDetectorRef.current = document.createElement('canvas');
|
||||
printScreenDetectorRef.current.width = 1;
|
||||
printScreenDetectorRef.current.height = 1;
|
||||
printScreenDetectorRef.current.style.position = 'absolute';
|
||||
printScreenDetectorRef.current.style.left = '-9999px';
|
||||
printScreenDetectorRef.current.style.top = '-9999px';
|
||||
document.body.appendChild(printScreenDetectorRef.current);
|
||||
}
|
||||
|
||||
const canvas = printScreenDetectorRef.current;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
if (!ctx) return;
|
||||
|
||||
// Fill with a specific pattern
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.fillRect(0, 0, 1, 1);
|
||||
|
||||
// Try to read the pixel data
|
||||
try {
|
||||
const imageData = ctx.getImageData(0, 0, 1, 1);
|
||||
const data = imageData.data;
|
||||
|
||||
// Check if data was modified (some screenshot tools modify canvas data)
|
||||
if (data[0] !== 255 || data[1] !== 255 || data[2] !== 255) {
|
||||
reportViolation('print_screen_detected');
|
||||
}
|
||||
} catch (e) {
|
||||
// Canvas data access blocked - possible screenshot attempt
|
||||
reportViolation('canvas_access_blocked');
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently handle detection errors
|
||||
}
|
||||
}, [options.detectPrintScreen, options.protectionLevel, reportViolation]);
|
||||
|
||||
// Enhanced keyboard shortcut detection
|
||||
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
if (!options.blockKeyboardShortcuts || options.protectionLevel === 'basic') return;
|
||||
|
||||
const isBlocked =
|
||||
// Developer tools
|
||||
e.key === 'F12' ||
|
||||
(e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'J' || e.key === 'C')) ||
|
||||
// View source
|
||||
(e.ctrlKey && e.key === 'u') ||
|
||||
(e.ctrlKey && e.key === 'U') ||
|
||||
// Save page/image
|
||||
(e.ctrlKey && e.key === 's') ||
|
||||
(e.ctrlKey && e.key === 'S') ||
|
||||
// Print
|
||||
(e.ctrlKey && e.key === 'p') ||
|
||||
(e.ctrlKey && e.key === 'P') ||
|
||||
// Print Screen
|
||||
e.key === 'PrintScreen' ||
|
||||
// Select all
|
||||
(e.ctrlKey && e.key === 'a') ||
|
||||
(e.ctrlKey && e.key === 'A') ||
|
||||
// Copy
|
||||
(e.ctrlKey && e.key === 'c') ||
|
||||
(e.ctrlKey && e.key === 'C') ||
|
||||
// Enhanced protection: additional shortcuts
|
||||
(options.protectionLevel === 'enhanced' || options.protectionLevel === 'maximum') && (
|
||||
// Find
|
||||
(e.ctrlKey && e.key === 'f') ||
|
||||
(e.ctrlKey && e.key === 'F') ||
|
||||
// Zoom
|
||||
(e.ctrlKey && (e.key === '+' || e.key === '-' || e.key === '0')) ||
|
||||
// Function keys that might trigger actions
|
||||
['F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11'].includes(e.key)
|
||||
) ||
|
||||
// Maximum protection: block almost everything
|
||||
options.protectionLevel === 'maximum' && (
|
||||
e.ctrlKey || e.altKey || e.metaKey ||
|
||||
['Insert', 'Delete', 'Home', 'End', 'PageUp', 'PageDown'].includes(e.key)
|
||||
);
|
||||
|
||||
if (isBlocked) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
reportViolation(`keyboard_shortcut_${e.key}_${e.ctrlKey ? 'ctrl_' : ''}${e.shiftKey ? 'shift_' : ''}${e.altKey ? 'alt_' : ''}`);
|
||||
return false;
|
||||
}
|
||||
}, [options.blockKeyboardShortcuts, options.protectionLevel, reportViolation]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!options.enabled || !elementRef.current) return;
|
||||
|
||||
const element = elementRef.current;
|
||||
const protectionLevel = options.protectionLevel || 'standard';
|
||||
|
||||
// Basic protection events
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
reportViolation('context_menu');
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleDragStart = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
reportViolation('drag_start');
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleSelectStart = (e: Event) => {
|
||||
e.preventDefault();
|
||||
reportViolation('text_selection');
|
||||
return false;
|
||||
};
|
||||
|
||||
// Enhanced visibility change detection
|
||||
const handleVisibilityChange = () => {
|
||||
if (protectionLevel === 'maximum' && document.hidden) {
|
||||
// Page became hidden - might be screenshot attempt
|
||||
setTimeout(() => {
|
||||
if (!document.hidden) {
|
||||
reportViolation('suspicious_visibility_change');
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
// Detect copy attempts through clipboard API
|
||||
const handleCopy = (e: ClipboardEvent) => {
|
||||
if (protectionLevel !== 'basic') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
reportViolation('clipboard_copy');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Detect paste attempts (might be used to extract data)
|
||||
const handlePaste = (e: ClipboardEvent) => {
|
||||
if (protectionLevel === 'enhanced' || protectionLevel === 'maximum') {
|
||||
e.preventDefault();
|
||||
reportViolation('clipboard_paste');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Add basic event listeners
|
||||
element.addEventListener('contextmenu', handleContextMenu);
|
||||
element.addEventListener('dragstart', handleDragStart);
|
||||
element.addEventListener('selectstart', handleSelectStart);
|
||||
|
||||
// Add enhanced event listeners
|
||||
if (protectionLevel !== 'basic') {
|
||||
document.addEventListener('keydown', handleKeyDown, true);
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
document.addEventListener('copy', handleCopy, true);
|
||||
document.addEventListener('paste', handlePaste, true);
|
||||
|
||||
// Start print screen detection
|
||||
if (options.detectPrintScreen && (protectionLevel === 'enhanced' || protectionLevel === 'maximum')) {
|
||||
const interval = protectionLevel === 'maximum' ? 50 : 100;
|
||||
printScreenIntervalRef.current = setInterval(detectPrintScreen, interval);
|
||||
}
|
||||
}
|
||||
|
||||
// CSS protection
|
||||
element.style.userSelect = 'none';
|
||||
element.style.webkitUserSelect = 'none';
|
||||
element.style.webkitTouchCallout = 'none';
|
||||
element.style.pointerEvents = 'auto';
|
||||
element.style.webkitUserDrag = 'none';
|
||||
element.style.webkitTouchCallout = 'none';
|
||||
element.draggable = false;
|
||||
|
||||
// Enhanced CSS protection
|
||||
if (protectionLevel !== 'basic') {
|
||||
element.style.outline = 'none';
|
||||
element.style.webkitAppearance = 'none';
|
||||
element.style.MozAppearance = 'none';
|
||||
|
||||
// Disable text selection on parent elements
|
||||
let parent = element.parentElement;
|
||||
while (parent) {
|
||||
parent.style.userSelect = 'none';
|
||||
parent.style.webkitUserSelect = 'none';
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
}
|
||||
|
||||
// Create overlay protection
|
||||
if (options.overlayProtection && protectionLevel !== 'basic') {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.cssText = `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
// Position relative container
|
||||
const container = element.parentElement;
|
||||
if (container) {
|
||||
container.style.position = 'relative';
|
||||
container.appendChild(overlay);
|
||||
overlayRef.current = overlay;
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
element.removeEventListener('contextmenu', handleContextMenu);
|
||||
element.removeEventListener('dragstart', handleDragStart);
|
||||
element.removeEventListener('selectstart', handleSelectStart);
|
||||
|
||||
if (protectionLevel !== 'basic') {
|
||||
document.removeEventListener('keydown', handleKeyDown, true);
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
document.removeEventListener('copy', handleCopy, true);
|
||||
document.removeEventListener('paste', handlePaste, true);
|
||||
}
|
||||
|
||||
// Clear print screen detection interval
|
||||
if (printScreenIntervalRef.current) {
|
||||
clearInterval(printScreenIntervalRef.current);
|
||||
}
|
||||
|
||||
// Remove print screen detector canvas
|
||||
if (printScreenDetectorRef.current && printScreenDetectorRef.current.parentElement) {
|
||||
printScreenDetectorRef.current.parentElement.removeChild(printScreenDetectorRef.current);
|
||||
}
|
||||
|
||||
// Remove overlay
|
||||
if (overlayRef.current && overlayRef.current.parentElement) {
|
||||
overlayRef.current.parentElement.removeChild(overlayRef.current);
|
||||
}
|
||||
};
|
||||
}, [
|
||||
options.enabled,
|
||||
options.onAttemptedDownload,
|
||||
options.onProtectionViolation,
|
||||
options.protectionLevel,
|
||||
options.overlayProtection,
|
||||
options.blockKeyboardShortcuts,
|
||||
options.detectPrintScreen,
|
||||
reportViolation,
|
||||
handleKeyDown,
|
||||
detectPrintScreen
|
||||
]);
|
||||
|
||||
return {
|
||||
elementRef,
|
||||
canvasRef,
|
||||
overlayRef
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user