content-security-toolkit 1.0.1 → 1.0.2
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/README.md +22 -0
- package/dist/core/mediator/handlers/baseEventHandler.d.ts +65 -0
- package/dist/core/mediator/handlers/baseEventHandler.js +99 -0
- package/dist/core/mediator/handlers/index.d.ts +9 -0
- package/dist/core/mediator/handlers/index.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/otel.d.ts +24 -0
- package/dist/otel.js +87 -0
- package/dist/strategies/AbstractStrategy.mediator.d.ts +162 -0
- package/dist/strategies/AbstractStrategy.mediator.js +349 -0
- package/dist/strategies/DevToolsStrategy copy.d.ts +85 -0
- package/dist/strategies/DevToolsStrategy copy.js +362 -0
- package/dist/strategies/DevToolsStrategy-detectorManager.d.ts +70 -0
- package/dist/strategies/DevToolsStrategy-detectorManager.js +309 -0
- package/dist/strategies/DevToolsStrategy-simple.d.ts +75 -0
- package/dist/strategies/DevToolsStrategy-simple.js +366 -0
- package/dist/strategies/StrategyRegistry.d.ts +133 -0
- package/dist/strategies/StrategyRegistry.js +379 -0
- package/dist/utils/base/LoggableComponent.d.ts +62 -0
- package/dist/utils/base/LoggableComponent.js +95 -0
- package/dist/utils/debuggerDetector/debuggerDetectionWorker.d.ts +6 -0
- package/dist/utils/debuggerDetector/debuggerDetectionWorker.js +24 -0
- package/dist/utils/debuggerDetector/debuggerDetector.d.ts +55 -0
- package/dist/utils/debuggerDetector/debuggerDetector.js +158 -0
- package/dist/utils/debuggerDetector/firefoxDetector.d.ts +8 -0
- package/dist/utils/debuggerDetector/firefoxDetector.js +64 -0
- package/dist/utils/detection.d.ts +29 -0
- package/dist/utils/detection.js +267 -0
- package/dist/utils/detectors/debuggerDetectionWorker.d.ts +6 -0
- package/dist/utils/detectors/debuggerDetectionWorker.js +24 -0
- package/dist/utils/detectors/firefoxDetector.d.ts +8 -0
- package/dist/utils/detectors/firefoxDetector.js +64 -0
- package/dist/utils/logging/LogLevel.d.ts +21 -0
- package/dist/utils/logging/LogLevel.js +46 -0
- package/dist/utils/logging/LoggingConfig.d.ts +68 -0
- package/dist/utils/logging/LoggingConfig.js +64 -0
- package/dist/utils/logging/LoggingFactory.d.ts +22 -0
- package/dist/utils/logging/LoggingFactory.js +61 -0
- package/dist/utils/logging/LoggingService.d.ts +235 -0
- package/dist/utils/logging/LoggingService.js +385 -0
- package/dist/utils/logging/SimpleLoggingService.d.ts +39 -0
- package/dist/utils/logging/SimpleLoggingService.js +58 -0
- package/dist/utils/logging/advanced/LogLevel.d.ts +21 -0
- package/dist/utils/logging/advanced/LogLevel.js +46 -0
- package/dist/utils/logging/advanced/LoggingConfig.d.ts +68 -0
- package/dist/utils/logging/advanced/LoggingConfig.js +64 -0
- package/dist/utils/logging/advanced/LoggingFactory.d.ts +22 -0
- package/dist/utils/logging/advanced/LoggingFactory.js +61 -0
- package/dist/utils/logging/advanced/LoggingService.d.ts +235 -0
- package/dist/utils/logging/advanced/LoggingService.js +385 -0
- package/dist/utils/protectedContentManager-simple.d.ts +86 -0
- package/dist/utils/protectedContentManager-simple.js +180 -0
- package/dist/utils/securityOverlayManager-observer-pause.d.ts +283 -0
- package/dist/utils/securityOverlayManager-observer-pause.js +878 -0
- package/dist/utils/securityOverlayManager-simple.d.ts +197 -0
- package/dist/utils/securityOverlayManager-simple.js +552 -0
- package/package.json +1 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { FirefoxDevToolsDetector } from "./firefoxDetector";
|
|
2
|
+
export class DebuggerDetector {
|
|
3
|
+
/**
|
|
4
|
+
* Create a new DebuggerDetector
|
|
5
|
+
* @param options Configuration options
|
|
6
|
+
*/
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.worker = null;
|
|
9
|
+
this.firefoxDetector = null;
|
|
10
|
+
this.isChecking = false;
|
|
11
|
+
this.timeoutId = null;
|
|
12
|
+
this.isDevToolsOpen = false;
|
|
13
|
+
this.timeoutDuration = options.timeoutDuration || 500;
|
|
14
|
+
this.onDevToolsChange = options.onDevToolsChange || (() => { });
|
|
15
|
+
this.firefoxDetector = new FirefoxDevToolsDetector();
|
|
16
|
+
this.debugMode = !!options.debugMode;
|
|
17
|
+
this.initWorker();
|
|
18
|
+
if (this.debugMode) {
|
|
19
|
+
console.log("DebuggerDetector: Initialized with timeout:", this.timeoutDuration);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Update the worker initialization to handle MIME type issues
|
|
23
|
+
initWorker() {
|
|
24
|
+
try {
|
|
25
|
+
// Create a blob URL for the worker script instead of using a file URL
|
|
26
|
+
// This avoids MIME type issues with server responses
|
|
27
|
+
const workerScript = `
|
|
28
|
+
// Worker script for DevTools detection
|
|
29
|
+
self.onmessage = function(e) {
|
|
30
|
+
if (e.data === "checkDevTools") {
|
|
31
|
+
// Send immediate response (opening heartbeat)
|
|
32
|
+
console.log("DebuggerDetectionWorker: Received checkDevTools message");
|
|
33
|
+
self.postMessage("heartbeatStart");
|
|
34
|
+
|
|
35
|
+
// This will pause execution if DevTools is open
|
|
36
|
+
debugger;
|
|
37
|
+
|
|
38
|
+
// If we reach here without significant delay, DevTools is closed
|
|
39
|
+
self.postMessage("heartbeatEnd");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
`;
|
|
43
|
+
// Create a blob URL from the script
|
|
44
|
+
const blob = new Blob([workerScript], { type: "application/javascript" });
|
|
45
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
46
|
+
// Create the worker using the blob URL
|
|
47
|
+
this.worker = new Worker(workerUrl);
|
|
48
|
+
this.worker.onmessage = (e) => {
|
|
49
|
+
if (e.data === "heartbeatStart") {
|
|
50
|
+
// First heartbeat received, now waiting for second
|
|
51
|
+
if (this.debugMode) {
|
|
52
|
+
console.log("DebuggerDetector: Received opening heartbeat");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (e.data === "heartbeatEnd") {
|
|
56
|
+
// Second heartbeat received, DevTools is closed
|
|
57
|
+
if (this.timeoutId !== null) {
|
|
58
|
+
clearTimeout(this.timeoutId);
|
|
59
|
+
this.timeoutId = null;
|
|
60
|
+
}
|
|
61
|
+
this.isChecking = false;
|
|
62
|
+
// Only trigger callback if state changed
|
|
63
|
+
if (this.isDevToolsOpen) {
|
|
64
|
+
this.isDevToolsOpen = false;
|
|
65
|
+
this.onDevToolsChange(false);
|
|
66
|
+
if (this.debugMode) {
|
|
67
|
+
console.log("DebuggerDetector: DevTools closed");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
this.worker.onerror = (error) => {
|
|
73
|
+
console.error("DebuggerDetector: Worker error:", error);
|
|
74
|
+
this.isChecking = false;
|
|
75
|
+
// Try to recreate the worker
|
|
76
|
+
this.terminateWorker();
|
|
77
|
+
this.initWorker();
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.error("DebuggerDetector: Failed to create worker:", error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if DevTools is open
|
|
86
|
+
* @returns Promise that resolves to true if DevTools is open
|
|
87
|
+
*/
|
|
88
|
+
checkDevTools() {
|
|
89
|
+
if (this.isChecking || !this.worker)
|
|
90
|
+
return;
|
|
91
|
+
this.isChecking = true;
|
|
92
|
+
try {
|
|
93
|
+
// Send message to worker
|
|
94
|
+
this.worker.postMessage("checkDevTools");
|
|
95
|
+
// Set timeout for missing second heartbeat
|
|
96
|
+
this.timeoutId = window.setTimeout(() => {
|
|
97
|
+
// If we reach here, the worker is paused at debugger
|
|
98
|
+
// which means DevTools is open
|
|
99
|
+
this.isChecking = false;
|
|
100
|
+
// Only trigger callback if state changed
|
|
101
|
+
if (!this.isDevToolsOpen) {
|
|
102
|
+
this.isDevToolsOpen = true;
|
|
103
|
+
this.onDevToolsChange(true);
|
|
104
|
+
if (this.debugMode) {
|
|
105
|
+
console.log("DebuggerDetector: DevTools opened");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, this.timeoutDuration);
|
|
109
|
+
// FIREFOX DETECTION
|
|
110
|
+
/* if (this.firefoxDetector) {
|
|
111
|
+
console.log('Running Firefox detection', this.firefoxDetector.detect());
|
|
112
|
+
const isFirefoxDevToolsOpen = this.firefoxDetector.detect()
|
|
113
|
+
if (isFirefoxDevToolsOpen) {
|
|
114
|
+
this.isDevToolsOpen = true
|
|
115
|
+
this.onDevToolsChange(true)
|
|
116
|
+
}
|
|
117
|
+
} */
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.error("DebuggerDetector: Error checking DevTools:", error);
|
|
121
|
+
this.isChecking = false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the current DevTools state
|
|
126
|
+
* @returns True if DevTools is open
|
|
127
|
+
*/
|
|
128
|
+
isOpen() {
|
|
129
|
+
return this.isDevToolsOpen;
|
|
130
|
+
}
|
|
131
|
+
// Update the terminateWorker method to revoke the blob URL
|
|
132
|
+
terminateWorker() {
|
|
133
|
+
if (this.worker) {
|
|
134
|
+
this.worker.terminate();
|
|
135
|
+
this.worker = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Clean up resources
|
|
140
|
+
*/
|
|
141
|
+
dispose() {
|
|
142
|
+
if (this.timeoutId !== null) {
|
|
143
|
+
clearTimeout(this.timeoutId);
|
|
144
|
+
this.timeoutId = null;
|
|
145
|
+
}
|
|
146
|
+
this.terminateWorker();
|
|
147
|
+
if (this.debugMode) {
|
|
148
|
+
console.log("DebuggerDetector: Disposed");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Set debug mode
|
|
153
|
+
* @param enabled Whether debug mode should be enabled
|
|
154
|
+
*/
|
|
155
|
+
setDebugMode(enabled) {
|
|
156
|
+
this.debugMode = enabled;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export class FirefoxDevToolsDetector {
|
|
2
|
+
constructor(debugMode = false) {
|
|
3
|
+
this.debugMode = debugMode;
|
|
4
|
+
}
|
|
5
|
+
detect() {
|
|
6
|
+
// Method 1: Check for Firefox-specific DevTools elements
|
|
7
|
+
const hasDevToolsElements = this.checkDevToolsElements();
|
|
8
|
+
console.log('devtools elements', hasDevToolsElements);
|
|
9
|
+
// Method 2: Check for Firefox-specific DevTools properties
|
|
10
|
+
const hasDevToolsProperties = this.checkDevToolsProperties();
|
|
11
|
+
console.log('devtools properties', hasDevToolsProperties);
|
|
12
|
+
// Method 3: Performance timing check
|
|
13
|
+
const hasTimingAnomaly = this.checkTimingAnomaly();
|
|
14
|
+
console.log('timing anomaly', hasTimingAnomaly);
|
|
15
|
+
return hasDevToolsElements || hasDevToolsProperties || hasTimingAnomaly;
|
|
16
|
+
}
|
|
17
|
+
checkDevToolsElements() {
|
|
18
|
+
// Firefox adds specific elements when DevTools is open
|
|
19
|
+
console.log('checking devtools elements');
|
|
20
|
+
return !!(document.documentElement.getAttribute('debug') ||
|
|
21
|
+
document.documentElement.getAttribute('webdriver') ||
|
|
22
|
+
document.documentElement.getAttribute('firefox-devtools'));
|
|
23
|
+
}
|
|
24
|
+
checkDevToolsProperties() {
|
|
25
|
+
console.log('checking devtools properties');
|
|
26
|
+
// Firefox exposes specific properties when DevTools is open
|
|
27
|
+
return !!(window.mozInnerScreenX !== undefined ||
|
|
28
|
+
// Check for Firefox debugger
|
|
29
|
+
Object.prototype.hasOwnProperty.call(window, '__firefox__') ||
|
|
30
|
+
Object.prototype.hasOwnProperty.call(window, 'netscape'));
|
|
31
|
+
}
|
|
32
|
+
checkTimingAnomaly() {
|
|
33
|
+
console.log('Checking timing anomaly');
|
|
34
|
+
const start = performance.now();
|
|
35
|
+
// Create and manipulate a large array with operations that are
|
|
36
|
+
// significantly affected by Firefox DevTools' debugger
|
|
37
|
+
const array = new Array(100000);
|
|
38
|
+
// Fill with incrementing numbers and perform operations
|
|
39
|
+
// This specific pattern is more reliably affected by Firefox's debugger
|
|
40
|
+
for (let i = 0; i < array.length; i++) {
|
|
41
|
+
array[i] = i;
|
|
42
|
+
// Add some basic operations that debugger will need to track
|
|
43
|
+
const temp = array[i] * 2;
|
|
44
|
+
array[i] = temp + 1;
|
|
45
|
+
}
|
|
46
|
+
// Add a reduce operation which is particularly sensitive to debugger overhead
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
48
|
+
const sum = array.reduce((acc, curr) => acc + curr, 0);
|
|
49
|
+
const end = performance.now();
|
|
50
|
+
const timeTaken = end - start;
|
|
51
|
+
console.log(timeTaken);
|
|
52
|
+
if (this.debugMode) {
|
|
53
|
+
console.log('Firefox timing check:', {
|
|
54
|
+
timeTaken,
|
|
55
|
+
operationsPerformed: array.length * 3, // 3 operations per item
|
|
56
|
+
averageTimePerOperation: timeTaken / (array.length * 3)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Threshold is based on empirical testing
|
|
60
|
+
// Normal execution: ~5-20ms
|
|
61
|
+
// With DevTools open: >100ms
|
|
62
|
+
return timeTaken > 100;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect browser environment
|
|
3
|
+
*/
|
|
4
|
+
export declare const isBrowser: () => boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Detect mobile device
|
|
7
|
+
*/
|
|
8
|
+
export declare const isMobile: () => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Detect specific browser
|
|
11
|
+
*/
|
|
12
|
+
export declare const getBrowser: () => {
|
|
13
|
+
name: string;
|
|
14
|
+
version: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Detect operating system
|
|
18
|
+
*/
|
|
19
|
+
export declare const getOS: () => {
|
|
20
|
+
name: "mac" | "linux" | "windows" | "unknown";
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Check if print is supported
|
|
24
|
+
*/
|
|
25
|
+
export declare const isPrintSupported: () => boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Check if beforeprint event is supported
|
|
28
|
+
*/
|
|
29
|
+
export declare const isBeforePrintSupported: () => boolean;
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// FEATURE/ENVIRONMENT DETECTION
|
|
2
|
+
/**
|
|
3
|
+
* Detect browser environment
|
|
4
|
+
*/
|
|
5
|
+
export const isBrowser = () => {
|
|
6
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Detect mobile device
|
|
10
|
+
*/
|
|
11
|
+
export const isMobile = () => {
|
|
12
|
+
if (!isBrowser())
|
|
13
|
+
return false;
|
|
14
|
+
const userAgent = navigator.userAgent;
|
|
15
|
+
// Standard mobile OS and browser detection
|
|
16
|
+
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
// Chinese mobile devices
|
|
20
|
+
if (/Huawei|HUAWEI|Honor|HONOR|Xiaomi|MI\/|Redmi|POCO|OPPO|vivo|OnePlus|Realme/i.test(userAgent)) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
// Korean mobile devices
|
|
24
|
+
if (/Samsung|SAMSUNG|LG|Tizen/i.test(userAgent)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Japanese mobile devices
|
|
28
|
+
if (/Sony|SONY|Xperia|Sharp|SHARP|Fujitsu|FOMA|KDDI/i.test(userAgent)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
// Russian mobile devices
|
|
32
|
+
if (/Yandex.Phone|YandexPhone|BQ-|BQru/i.test(userAgent)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// Other mobile platforms
|
|
36
|
+
if (/Windows Phone|WindowsPhone|Lumia|Mobile|Tablet|Phone|WPDesktop|ZuneWP|WP7|wds|Fennec|Firefox OS|KaiOS|KAIOS|Sailfish/i.test(userAgent)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// Mobile browser detection
|
|
40
|
+
if (/Mobile|Tablet|Android|Touch/i.test(userAgent) && !/Windows NT|Mac OS X/i.test(userAgent)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Feature detection for touch devices (most mobile devices have touch capability)
|
|
44
|
+
if (isBrowser() && ("ontouchstart" in window || navigator.maxTouchPoints > 0)) {
|
|
45
|
+
// Additional check to avoid false positives on desktops with touch screens
|
|
46
|
+
if (window.innerWidth <= 1024 || /Mobi|Android/i.test(userAgent)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Detect specific browser
|
|
54
|
+
*/
|
|
55
|
+
export const getBrowser = () => {
|
|
56
|
+
if (!isBrowser())
|
|
57
|
+
return { name: "unknown", version: "0" };
|
|
58
|
+
const ua = navigator.userAgent;
|
|
59
|
+
let browserName = "unknown";
|
|
60
|
+
let version = "0";
|
|
61
|
+
// Order matters here - we need to check more specific browsers first
|
|
62
|
+
// before falling back to more generic ones
|
|
63
|
+
// --- Chinese Browsers ---
|
|
64
|
+
if (/MicroMessenger|WeChat/.test(ua)) {
|
|
65
|
+
browserName = "wechat";
|
|
66
|
+
const match = ua.match(/MicroMessenger\/(\d+\.\d+)/);
|
|
67
|
+
version = match ? match[1] : "0";
|
|
68
|
+
}
|
|
69
|
+
// QQ Browser
|
|
70
|
+
else if (/QQBrowser/.test(ua)) {
|
|
71
|
+
browserName = "qq";
|
|
72
|
+
const match = ua.match(/QQBrowser\/(\d+\.\d+)/);
|
|
73
|
+
version = match ? match[1] : "0";
|
|
74
|
+
}
|
|
75
|
+
// UC Browser
|
|
76
|
+
else if (/UCBrowser/.test(ua)) {
|
|
77
|
+
browserName = "uc";
|
|
78
|
+
const match = ua.match(/UCBrowser\/(\d+\.\d+)/);
|
|
79
|
+
version = match ? match[1] : "0";
|
|
80
|
+
}
|
|
81
|
+
// Baidu Browser
|
|
82
|
+
else if (/Baidu|BIDUBrowser|baiduboxapp/.test(ua)) {
|
|
83
|
+
browserName = "baidu";
|
|
84
|
+
const match = ua.match(/(?:Baidu|BIDUBOX)(?:Browser)?\/(\d+\.\d+)/);
|
|
85
|
+
version = match ? match[1] : "0";
|
|
86
|
+
}
|
|
87
|
+
// Mi Browser
|
|
88
|
+
else if (/MiuiBrowser/.test(ua)) {
|
|
89
|
+
browserName = "mi";
|
|
90
|
+
const match = ua.match(/MiuiBrowser\/(\d+\.\d+)/);
|
|
91
|
+
version = match ? match[1] : "0";
|
|
92
|
+
}
|
|
93
|
+
// --- Russian Browsers ---
|
|
94
|
+
// Yandex Browser
|
|
95
|
+
else if (/YaBrowser/.test(ua)) {
|
|
96
|
+
browserName = "yandex";
|
|
97
|
+
const match = ua.match(/YaBrowser\/(\d+\.\d+)/);
|
|
98
|
+
version = match ? match[1] : "0";
|
|
99
|
+
}
|
|
100
|
+
// --- Korean Browsers ---
|
|
101
|
+
// Naver Whale
|
|
102
|
+
else if (/Whale/.test(ua)) {
|
|
103
|
+
browserName = "whale";
|
|
104
|
+
const match = ua.match(/Whale\/(\d+\.\d+)/);
|
|
105
|
+
version = match ? match[1] : "0";
|
|
106
|
+
}
|
|
107
|
+
// --- Mobile Browsers ---
|
|
108
|
+
// Samsung Internet
|
|
109
|
+
else if (/SamsungBrowser/.test(ua)) {
|
|
110
|
+
browserName = "samsung";
|
|
111
|
+
const match = ua.match(/SamsungBrowser\/(\d+\.\d+)/);
|
|
112
|
+
version = match ? match[1] : "0";
|
|
113
|
+
}
|
|
114
|
+
// Huawei Browser
|
|
115
|
+
else if (/HuaweiBrowser/.test(ua)) {
|
|
116
|
+
browserName = "huawei";
|
|
117
|
+
const match = ua.match(/HuaweiBrowser\/(\d+\.\d+)/);
|
|
118
|
+
version = match ? match[1] : "0";
|
|
119
|
+
}
|
|
120
|
+
// --- Alternative Browsers ---
|
|
121
|
+
// Vivaldi
|
|
122
|
+
else if (/Vivaldi/.test(ua)) {
|
|
123
|
+
browserName = "vivaldi";
|
|
124
|
+
const match = ua.match(/Vivaldi\/(\d+\.\d+)/);
|
|
125
|
+
version = match ? match[1] : "0";
|
|
126
|
+
}
|
|
127
|
+
// Lunascape
|
|
128
|
+
else if (/Lunascape/.test(ua)) {
|
|
129
|
+
browserName = "lunascape";
|
|
130
|
+
const match = ua.match(/Lunascape[/| ](\d+\.\d+)/);
|
|
131
|
+
version = match ? match[1] : "0";
|
|
132
|
+
}
|
|
133
|
+
// Opera
|
|
134
|
+
else if (/OPR|Opera/.test(ua)) {
|
|
135
|
+
browserName = "opera";
|
|
136
|
+
const match = ua.match(/(?:OPR|Opera)[/| ](\d+\.\d+)/);
|
|
137
|
+
version = match ? match[1] : "0";
|
|
138
|
+
}
|
|
139
|
+
// --- Emulators ---
|
|
140
|
+
// Nox Browser/Emulator
|
|
141
|
+
else if (/Nox/.test(ua)) {
|
|
142
|
+
browserName = "nox";
|
|
143
|
+
const match = ua.match(/Nox\/(\d+\.\d+)/);
|
|
144
|
+
version = match ? match[1] : "0";
|
|
145
|
+
}
|
|
146
|
+
// BlueStacks
|
|
147
|
+
else if (/BlueStacks/.test(ua)) {
|
|
148
|
+
browserName = "bluestacks";
|
|
149
|
+
const match = ua.match(/BlueStacks\/(\d+\.\d+)/);
|
|
150
|
+
version = match ? match[1] : "0";
|
|
151
|
+
}
|
|
152
|
+
// --- Major Browsers (keep these last as fallbacks) ---
|
|
153
|
+
// Edge
|
|
154
|
+
else if (/Edg/.test(ua)) {
|
|
155
|
+
browserName = "edge";
|
|
156
|
+
const match = ua.match(/Edg\/(\d+\.\d+)/);
|
|
157
|
+
version = match ? match[1] : "0";
|
|
158
|
+
}
|
|
159
|
+
// Chrome
|
|
160
|
+
else if (/Chrome/.test(ua) && !/Chromium|Edge|Edg|OPR|Opera/.test(ua)) {
|
|
161
|
+
browserName = "chrome";
|
|
162
|
+
const match = ua.match(/Chrome\/(\d+\.\d+)/);
|
|
163
|
+
version = match ? match[1] : "0";
|
|
164
|
+
}
|
|
165
|
+
// Firefox
|
|
166
|
+
else if (/Firefox/.test(ua)) {
|
|
167
|
+
browserName = "firefox";
|
|
168
|
+
const match = ua.match(/Firefox\/(\d+\.\d+)/);
|
|
169
|
+
version = match ? match[1] : "0";
|
|
170
|
+
}
|
|
171
|
+
// Safari
|
|
172
|
+
else if (/Safari/.test(ua) && !/Chrome/.test(ua)) {
|
|
173
|
+
browserName = "safari";
|
|
174
|
+
const match = ua.match(/Version\/(\d+\.\d+)/);
|
|
175
|
+
version = match ? match[1] : "0";
|
|
176
|
+
}
|
|
177
|
+
// IE
|
|
178
|
+
else if (/Trident|MSIE/.test(ua)) {
|
|
179
|
+
browserName = "ie";
|
|
180
|
+
const match = ua.match(/(?:rv:|MSIE )(\d+\.\d+)/);
|
|
181
|
+
version = match ? match[1] : "0";
|
|
182
|
+
}
|
|
183
|
+
// Chromium-based browsers not caught above
|
|
184
|
+
else if (/Chromium/.test(ua)) {
|
|
185
|
+
browserName = "chromium";
|
|
186
|
+
const match = ua.match(/Chromium\/(\d+\.\d+)/);
|
|
187
|
+
version = match ? match[1] : "0";
|
|
188
|
+
}
|
|
189
|
+
return { name: browserName, version };
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Detect operating system
|
|
193
|
+
*/
|
|
194
|
+
export const getOS = () => {
|
|
195
|
+
if (!isBrowser())
|
|
196
|
+
return { name: "unknown" };
|
|
197
|
+
const platform = navigator.platform.toLowerCase();
|
|
198
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
199
|
+
// macOS and iOS devices
|
|
200
|
+
if (platform.includes("mac") ||
|
|
201
|
+
platform.includes("ipad") ||
|
|
202
|
+
platform.includes("ipod") ||
|
|
203
|
+
platform.includes("iphone") ||
|
|
204
|
+
userAgent.includes("mac") ||
|
|
205
|
+
userAgent.includes("iphone") ||
|
|
206
|
+
userAgent.includes("ipad") ||
|
|
207
|
+
(userAgent.includes("safari") && !userAgent.includes("chrome") && !userAgent.includes("android"))) {
|
|
208
|
+
return { name: "mac" };
|
|
209
|
+
}
|
|
210
|
+
// Windows detection
|
|
211
|
+
else if (platform.includes("win") || userAgent.includes("win") || userAgent.includes("windows nt")) {
|
|
212
|
+
return { name: "windows" };
|
|
213
|
+
}
|
|
214
|
+
// Linux and Linux-based OS detection
|
|
215
|
+
else if (
|
|
216
|
+
// Standard Linux
|
|
217
|
+
platform.includes("linux") ||
|
|
218
|
+
userAgent.includes("linux") ||
|
|
219
|
+
// Android (Linux-based)
|
|
220
|
+
userAgent.includes("android") ||
|
|
221
|
+
// Chinese OS (most are Linux-based)
|
|
222
|
+
userAgent.includes("harmonyos") ||
|
|
223
|
+
userAgent.includes("deepin") ||
|
|
224
|
+
userAgent.includes("uos") ||
|
|
225
|
+
userAgent.includes("cos") ||
|
|
226
|
+
// Russian Linux distributions
|
|
227
|
+
userAgent.includes("astra linux") ||
|
|
228
|
+
userAgent.includes("alt linux") ||
|
|
229
|
+
userAgent.includes("rosa") ||
|
|
230
|
+
// Korean OS
|
|
231
|
+
userAgent.includes("tizen") ||
|
|
232
|
+
userAgent.includes("gooroom") ||
|
|
233
|
+
// Other Linux-based OS
|
|
234
|
+
userAgent.includes("ubuntu") ||
|
|
235
|
+
userAgent.includes("debian") ||
|
|
236
|
+
userAgent.includes("fedora") ||
|
|
237
|
+
userAgent.includes("red hat") ||
|
|
238
|
+
userAgent.includes("suse") ||
|
|
239
|
+
userAgent.includes("mint") ||
|
|
240
|
+
// Chrome OS (Linux-based)
|
|
241
|
+
userAgent.includes("cros") ||
|
|
242
|
+
userAgent.includes("chromium os") ||
|
|
243
|
+
userAgent.includes("chrome os") ||
|
|
244
|
+
// BSD variants (Unix-like, grouped with Linux for simplicity)
|
|
245
|
+
userAgent.includes("freebsd") ||
|
|
246
|
+
userAgent.includes("openbsd") ||
|
|
247
|
+
userAgent.includes("netbsd")) {
|
|
248
|
+
return { name: "linux" };
|
|
249
|
+
}
|
|
250
|
+
// Fallback for undetected OS
|
|
251
|
+
return { name: "unknown" };
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* Check if print is supported
|
|
255
|
+
*/
|
|
256
|
+
export const isPrintSupported = () => {
|
|
257
|
+
return isBrowser() && typeof window.print === 'function';
|
|
258
|
+
};
|
|
259
|
+
/**
|
|
260
|
+
* Check if beforeprint event is supported
|
|
261
|
+
*/
|
|
262
|
+
export const isBeforePrintSupported = () => {
|
|
263
|
+
if (!isBrowser())
|
|
264
|
+
return false;
|
|
265
|
+
const mediaQueryList = window.matchMedia('print');
|
|
266
|
+
return !!mediaQueryList.addListener || 'onbeforeprint' in window;
|
|
267
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker script for DevTools detection
|
|
3
|
+
* This worker helps detect DevTools by using the debugger statement
|
|
4
|
+
* in a separate thread, which won't block the main UI thread
|
|
5
|
+
*/
|
|
6
|
+
// Set up message handler
|
|
7
|
+
self.onmessage = (e) => {
|
|
8
|
+
if (e.data === "checkDevTools") {
|
|
9
|
+
// Send immediate response (opening heartbeat)
|
|
10
|
+
console.log("DebuggerDetectionWorker: Received checkDevTools message");
|
|
11
|
+
self.postMessage("heartbeatStart");
|
|
12
|
+
// This will pause execution if DevTools is open
|
|
13
|
+
// We use a function to make it harder to bypass
|
|
14
|
+
function triggerDebugger() {
|
|
15
|
+
// eslint-disable-next-line no-debugger
|
|
16
|
+
debugger;
|
|
17
|
+
}
|
|
18
|
+
// Call the function to trigger the debugger
|
|
19
|
+
triggerDebugger();
|
|
20
|
+
// If we reach here without significant delay, DevTools is closed
|
|
21
|
+
self.postMessage("heartbeatEnd");
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export class FirefoxDevToolsDetector {
|
|
2
|
+
constructor(debugMode = false) {
|
|
3
|
+
this.debugMode = debugMode;
|
|
4
|
+
}
|
|
5
|
+
detect() {
|
|
6
|
+
// Method 1: Check for Firefox-specific DevTools elements
|
|
7
|
+
const hasDevToolsElements = this.checkDevToolsElements();
|
|
8
|
+
console.log('devtools elements', hasDevToolsElements);
|
|
9
|
+
// Method 2: Check for Firefox-specific DevTools properties
|
|
10
|
+
const hasDevToolsProperties = this.checkDevToolsProperties();
|
|
11
|
+
console.log('devtools properties', hasDevToolsProperties);
|
|
12
|
+
// Method 3: Performance timing check
|
|
13
|
+
const hasTimingAnomaly = this.checkTimingAnomaly();
|
|
14
|
+
console.log('timing anomaly', hasTimingAnomaly);
|
|
15
|
+
return hasDevToolsElements || hasDevToolsProperties || hasTimingAnomaly;
|
|
16
|
+
}
|
|
17
|
+
checkDevToolsElements() {
|
|
18
|
+
// Firefox adds specific elements when DevTools is open
|
|
19
|
+
console.log('checking devtools elements');
|
|
20
|
+
return !!(document.documentElement.getAttribute('debug') ||
|
|
21
|
+
document.documentElement.getAttribute('webdriver') ||
|
|
22
|
+
document.documentElement.getAttribute('firefox-devtools'));
|
|
23
|
+
}
|
|
24
|
+
checkDevToolsProperties() {
|
|
25
|
+
console.log('checking devtools properties');
|
|
26
|
+
// Firefox exposes specific properties when DevTools is open
|
|
27
|
+
return !!(window.mozInnerScreenX !== undefined ||
|
|
28
|
+
// Check for Firefox debugger
|
|
29
|
+
Object.prototype.hasOwnProperty.call(window, '__firefox__') ||
|
|
30
|
+
Object.prototype.hasOwnProperty.call(window, 'netscape'));
|
|
31
|
+
}
|
|
32
|
+
checkTimingAnomaly() {
|
|
33
|
+
console.log('Checking timing anomaly');
|
|
34
|
+
const start = performance.now();
|
|
35
|
+
// Create and manipulate a large array with operations that are
|
|
36
|
+
// significantly affected by Firefox DevTools' debugger
|
|
37
|
+
const array = new Array(100000);
|
|
38
|
+
// Fill with incrementing numbers and perform operations
|
|
39
|
+
// This specific pattern is more reliably affected by Firefox's debugger
|
|
40
|
+
for (let i = 0; i < array.length; i++) {
|
|
41
|
+
array[i] = i;
|
|
42
|
+
// Add some basic operations that debugger will need to track
|
|
43
|
+
const temp = array[i] * 2;
|
|
44
|
+
array[i] = temp + 1;
|
|
45
|
+
}
|
|
46
|
+
// Add a reduce operation which is particularly sensitive to debugger overhead
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
48
|
+
const sum = array.reduce((acc, curr) => acc + curr, 0);
|
|
49
|
+
const end = performance.now();
|
|
50
|
+
const timeTaken = end - start;
|
|
51
|
+
console.log(timeTaken);
|
|
52
|
+
if (this.debugMode) {
|
|
53
|
+
console.log('Firefox timing check:', {
|
|
54
|
+
timeTaken,
|
|
55
|
+
operationsPerformed: array.length * 3, // 3 operations per item
|
|
56
|
+
averageTimePerOperation: timeTaken / (array.length * 3)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Threshold is based on empirical testing
|
|
60
|
+
// Normal execution: ~5-20ms
|
|
61
|
+
// With DevTools open: >100ms
|
|
62
|
+
return timeTaken > 100;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enum defining log severity levels
|
|
3
|
+
*/
|
|
4
|
+
export declare enum LogLevel {
|
|
5
|
+
ERROR = 0,// Always shown, represents errors that need immediate attention
|
|
6
|
+
WARN = 1,// Important issues that don't break functionality but need attention
|
|
7
|
+
INFO = 2,// General information about application flow
|
|
8
|
+
DEBUG = 3,// Detailed information useful for debugging
|
|
9
|
+
TRACE = 4,// Very detailed information, including loop iterations, function entries/exits
|
|
10
|
+
VERBOSE = 5
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Map of log level names to their enum values
|
|
14
|
+
*/
|
|
15
|
+
export declare const LOG_LEVEL_MAP: Record<string, LogLevel>;
|
|
16
|
+
/**
|
|
17
|
+
* Get the name of a log level
|
|
18
|
+
* @param level The log level
|
|
19
|
+
* @returns The name of the log level
|
|
20
|
+
*/
|
|
21
|
+
export declare function getLogLevelName(level: LogLevel): string;
|