@mseep/anything-analyzer 3.6.50
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/.codeartsdoer/.codebaseignore +0 -0
- package/.codeartsdoer/AGENTS.md +12 -0
- package/.github/workflows/build.yml +146 -0
- package/README.en.md +264 -0
- package/README.md +276 -0
- package/RELEASE_NOTES.md +16 -0
- package/USAGE.md +490 -0
- package/color-preview-r3.html +414 -0
- package/color-preview.html +414 -0
- package/dev-app-update.yml +3 -0
- package/electron-builder.yml +36 -0
- package/electron.vite.config.ts +40 -0
- package/package.json +53 -0
- package/report-2026-04-13-copilot-claude-sonnet-4.6.md +955 -0
- package/resources/doloffer-logo.png +0 -0
- package/resources/entitlements.mac.plist +12 -0
- package/resources/icon.ico +0 -0
- package/resources/icon.png +0 -0
- package/src/main/ai/ai-analyzer.ts +517 -0
- package/src/main/ai/crypto-script-extractor.ts +206 -0
- package/src/main/ai/data-assembler.ts +205 -0
- package/src/main/ai/llm-router.ts +1120 -0
- package/src/main/ai/prompt-builder.ts +349 -0
- package/src/main/ai/scene-detector.ts +302 -0
- package/src/main/capture/capture-engine.ts +130 -0
- package/src/main/capture/interaction-recorder.ts +171 -0
- package/src/main/capture/js-injector.ts +57 -0
- package/src/main/capture/replay-engine.ts +256 -0
- package/src/main/capture/storage-collector.ts +76 -0
- package/src/main/cdp/cdp-manager.ts +233 -0
- package/src/main/db/database.ts +41 -0
- package/src/main/db/migrations.ts +235 -0
- package/src/main/db/repositories.ts +574 -0
- package/src/main/fingerprint/http-spoofing.ts +48 -0
- package/src/main/fingerprint/presets.ts +173 -0
- package/src/main/fingerprint/profile-generator.ts +115 -0
- package/src/main/fingerprint/profile-store.ts +52 -0
- package/src/main/index.ts +260 -0
- package/src/main/ipc.ts +856 -0
- package/src/main/logger.ts +42 -0
- package/src/main/mcp/mcp-config.ts +66 -0
- package/src/main/mcp/mcp-manager.ts +155 -0
- package/src/main/mcp/mcp-server.ts +1038 -0
- package/src/main/prompt-templates.ts +170 -0
- package/src/main/proxy/ca-manager.ts +204 -0
- package/src/main/proxy/cert-download-page.ts +171 -0
- package/src/main/proxy/cert-installer.ts +242 -0
- package/src/main/proxy/mitm-proxy-config.ts +37 -0
- package/src/main/proxy/mitm-proxy-server.ts +1085 -0
- package/src/main/proxy/system-proxy.ts +248 -0
- package/src/main/session/session-manager.ts +724 -0
- package/src/main/tab-manager.ts +582 -0
- package/src/main/updater.ts +111 -0
- package/src/main/window.ts +235 -0
- package/src/preload/hook-script.ts +270 -0
- package/src/preload/index.ts +211 -0
- package/src/preload/interaction-hook.ts +286 -0
- package/src/preload/stealth-script.ts +302 -0
- package/src/preload/target-preload.ts +15 -0
- package/src/renderer/App.tsx +656 -0
- package/src/renderer/components/AiLogDetail.tsx +173 -0
- package/src/renderer/components/AiLogList.tsx +101 -0
- package/src/renderer/components/AiLogView.module.css +364 -0
- package/src/renderer/components/AiLogView.tsx +86 -0
- package/src/renderer/components/AnalyzeBar.module.css +79 -0
- package/src/renderer/components/AnalyzeBar.tsx +104 -0
- package/src/renderer/components/BrowserPanel.module.css +67 -0
- package/src/renderer/components/BrowserPanel.tsx +90 -0
- package/src/renderer/components/ControlBar.module.css +47 -0
- package/src/renderer/components/ControlBar.tsx +205 -0
- package/src/renderer/components/HookLog.tsx +132 -0
- package/src/renderer/components/InteractionLog.tsx +183 -0
- package/src/renderer/components/MCPServerModal.tsx +427 -0
- package/src/renderer/components/PromptTemplateModal.tsx +254 -0
- package/src/renderer/components/ReportView.module.css +413 -0
- package/src/renderer/components/ReportView.tsx +429 -0
- package/src/renderer/components/RequestDetail.module.css +191 -0
- package/src/renderer/components/RequestDetail.tsx +202 -0
- package/src/renderer/components/RequestLog.module.css +69 -0
- package/src/renderer/components/RequestLog.tsx +208 -0
- package/src/renderer/components/SessionList.module.css +245 -0
- package/src/renderer/components/SessionList.tsx +247 -0
- package/src/renderer/components/SettingsModal.tsx +100 -0
- package/src/renderer/components/StatusBar.module.css +44 -0
- package/src/renderer/components/StatusBar.tsx +102 -0
- package/src/renderer/components/StorageView.module.css +41 -0
- package/src/renderer/components/StorageView.tsx +178 -0
- package/src/renderer/components/TabBar.module.css +88 -0
- package/src/renderer/components/TabBar.tsx +70 -0
- package/src/renderer/components/Titlebar.module.css +254 -0
- package/src/renderer/components/Titlebar.tsx +169 -0
- package/src/renderer/components/settings/FingerprintSection.tsx +198 -0
- package/src/renderer/components/settings/GeneralSection.tsx +164 -0
- package/src/renderer/components/settings/LLMSection.tsx +148 -0
- package/src/renderer/components/settings/MCPServerSection.tsx +136 -0
- package/src/renderer/components/settings/MitmProxySection.tsx +320 -0
- package/src/renderer/components/settings/ProxySection.tsx +110 -0
- package/src/renderer/css-modules.d.ts +4 -0
- package/src/renderer/hooks/useCapture.ts +383 -0
- package/src/renderer/hooks/useConfirm.tsx +91 -0
- package/src/renderer/hooks/useSession.ts +136 -0
- package/src/renderer/hooks/useTabs.ts +103 -0
- package/src/renderer/i18n/en.ts +167 -0
- package/src/renderer/i18n/index.ts +47 -0
- package/src/renderer/i18n/zh.ts +170 -0
- package/src/renderer/index.html +12 -0
- package/src/renderer/main.tsx +15 -0
- package/src/renderer/styles/global.css +144 -0
- package/src/renderer/styles/themes/ayu-dark.css +59 -0
- package/src/renderer/styles/themes/catppuccin.css +59 -0
- package/src/renderer/styles/themes/discord.css +59 -0
- package/src/renderer/styles/themes/dracula.css +59 -0
- package/src/renderer/styles/themes/github-dark.css +59 -0
- package/src/renderer/styles/themes/gruvbox.css +59 -0
- package/src/renderer/styles/themes/index.css +11 -0
- package/src/renderer/styles/themes/light.css +59 -0
- package/src/renderer/styles/themes/nord.css +59 -0
- package/src/renderer/styles/themes/one-dark.css +59 -0
- package/src/renderer/styles/themes/tokyo-night.css +59 -0
- package/src/renderer/styles/tokens.css +137 -0
- package/src/renderer/theme.ts +31 -0
- package/src/renderer/ui/Badge.module.css +38 -0
- package/src/renderer/ui/Badge.tsx +36 -0
- package/src/renderer/ui/Button.module.css +142 -0
- package/src/renderer/ui/Button.tsx +46 -0
- package/src/renderer/ui/Collapse.module.css +49 -0
- package/src/renderer/ui/Collapse.tsx +57 -0
- package/src/renderer/ui/CopyableBlock.module.css +56 -0
- package/src/renderer/ui/CopyableBlock.tsx +42 -0
- package/src/renderer/ui/Empty.module.css +19 -0
- package/src/renderer/ui/Empty.tsx +34 -0
- package/src/renderer/ui/Icons.tsx +346 -0
- package/src/renderer/ui/Input.module.css +103 -0
- package/src/renderer/ui/Input.tsx +94 -0
- package/src/renderer/ui/InputNumber.module.css +68 -0
- package/src/renderer/ui/InputNumber.tsx +104 -0
- package/src/renderer/ui/Modal.module.css +83 -0
- package/src/renderer/ui/Modal.tsx +67 -0
- package/src/renderer/ui/Popconfirm.module.css +73 -0
- package/src/renderer/ui/Popconfirm.tsx +74 -0
- package/src/renderer/ui/Progress.module.css +35 -0
- package/src/renderer/ui/Progress.tsx +30 -0
- package/src/renderer/ui/Select.module.css +91 -0
- package/src/renderer/ui/Select.tsx +100 -0
- package/src/renderer/ui/Spinner.module.css +44 -0
- package/src/renderer/ui/Spinner.tsx +27 -0
- package/src/renderer/ui/Switch.module.css +39 -0
- package/src/renderer/ui/Switch.tsx +43 -0
- package/src/renderer/ui/Tabs.module.css +76 -0
- package/src/renderer/ui/Tabs.tsx +53 -0
- package/src/renderer/ui/Tag.module.css +66 -0
- package/src/renderer/ui/Tag.tsx +47 -0
- package/src/renderer/ui/Timeline.module.css +42 -0
- package/src/renderer/ui/Timeline.tsx +29 -0
- package/src/renderer/ui/Toast.module.css +99 -0
- package/src/renderer/ui/Toast.tsx +90 -0
- package/src/renderer/ui/Tooltip.module.css +26 -0
- package/src/renderer/ui/Tooltip.tsx +23 -0
- package/src/renderer/ui/VirtualTable.module.css +230 -0
- package/src/renderer/ui/VirtualTable.tsx +416 -0
- package/src/renderer/ui/index.ts +55 -0
- package/src/shared/types.ts +695 -0
- package/tests/main/ai/crypto-script-extractor.test.ts +281 -0
- package/tests/main/ai/llm-router.test.ts +1537 -0
- package/tests/main/ai/prompt-builder.test.ts +178 -0
- package/tests/main/ai/scene-detector.test.ts +212 -0
- package/tests/main/db/migrations.test.ts +134 -0
- package/tests/main/release-workflow.test.ts +59 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +23 -0
- package/tsconfig.web.json +24 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preset device configurations for fingerprint generation.
|
|
3
|
+
* Each preset is a logically consistent combination of platform, screen, GPU, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface DevicePreset {
|
|
7
|
+
platform: string;
|
|
8
|
+
oscpu: string;
|
|
9
|
+
screens: [number, number][];
|
|
10
|
+
dprs: number[];
|
|
11
|
+
webglVendors: string[];
|
|
12
|
+
webglRenderers: string[];
|
|
13
|
+
hardwareConcurrencies: number[];
|
|
14
|
+
deviceMemories: number[];
|
|
15
|
+
colorDepth: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Chrome version whitelist — 5 most recent stable versions */
|
|
19
|
+
export const CHROME_VERSIONS = [
|
|
20
|
+
'131.0.0.0',
|
|
21
|
+
'130.0.0.0',
|
|
22
|
+
'129.0.0.0',
|
|
23
|
+
'128.0.0.0',
|
|
24
|
+
'127.0.0.0',
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export const WINDOWS_PRESETS: DevicePreset[] = [
|
|
28
|
+
{
|
|
29
|
+
platform: 'Win32',
|
|
30
|
+
oscpu: 'Windows NT 10.0; Win64; x64',
|
|
31
|
+
screens: [[1920, 1080], [2560, 1440], [1366, 768], [1536, 864]],
|
|
32
|
+
dprs: [1, 1.25, 1.5],
|
|
33
|
+
webglVendors: ['Google Inc. (NVIDIA)'],
|
|
34
|
+
webglRenderers: [
|
|
35
|
+
'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
36
|
+
'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
37
|
+
'ANGLE (NVIDIA, NVIDIA GeForce RTX 4060 Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
38
|
+
],
|
|
39
|
+
hardwareConcurrencies: [4, 8, 12, 16],
|
|
40
|
+
deviceMemories: [8, 16, 32],
|
|
41
|
+
colorDepth: 24,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
platform: 'Win32',
|
|
45
|
+
oscpu: 'Windows NT 10.0; Win64; x64',
|
|
46
|
+
screens: [[1920, 1080], [2560, 1440]],
|
|
47
|
+
dprs: [1, 1.25],
|
|
48
|
+
webglVendors: ['Google Inc. (Intel)'],
|
|
49
|
+
webglRenderers: [
|
|
50
|
+
'ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
51
|
+
'ANGLE (Intel, Intel(R) UHD Graphics 770 Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
52
|
+
],
|
|
53
|
+
hardwareConcurrencies: [4, 8, 12],
|
|
54
|
+
deviceMemories: [8, 16],
|
|
55
|
+
colorDepth: 24,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
platform: 'Win32',
|
|
59
|
+
oscpu: 'Windows NT 10.0; Win64; x64',
|
|
60
|
+
screens: [[1920, 1080], [2560, 1440], [3440, 1440]],
|
|
61
|
+
dprs: [1, 1.25, 1.5],
|
|
62
|
+
webglVendors: ['Google Inc. (AMD)'],
|
|
63
|
+
webglRenderers: [
|
|
64
|
+
'ANGLE (AMD, AMD Radeon RX 6700 XT Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
65
|
+
'ANGLE (AMD, AMD Radeon RX 580 Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
|
66
|
+
],
|
|
67
|
+
hardwareConcurrencies: [8, 12, 16],
|
|
68
|
+
deviceMemories: [16, 32],
|
|
69
|
+
colorDepth: 24,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
export const MACOS_PRESETS: DevicePreset[] = [
|
|
74
|
+
{
|
|
75
|
+
platform: 'MacIntel',
|
|
76
|
+
oscpu: 'Intel Mac OS X 10_15_7',
|
|
77
|
+
screens: [[1440, 900], [1680, 1050], [2560, 1600]],
|
|
78
|
+
dprs: [2],
|
|
79
|
+
webglVendors: ['Google Inc. (Apple)'],
|
|
80
|
+
webglRenderers: [
|
|
81
|
+
'ANGLE (Apple, Apple M1, OpenGL 4.1)',
|
|
82
|
+
'ANGLE (Apple, Apple M2, OpenGL 4.1)',
|
|
83
|
+
'ANGLE (Apple, Apple M3, OpenGL 4.1)',
|
|
84
|
+
],
|
|
85
|
+
hardwareConcurrencies: [8, 10, 12],
|
|
86
|
+
deviceMemories: [8, 16],
|
|
87
|
+
colorDepth: 30,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
platform: 'MacIntel',
|
|
91
|
+
oscpu: 'Intel Mac OS X 10_15_7',
|
|
92
|
+
screens: [[1440, 900], [1680, 1050]],
|
|
93
|
+
dprs: [2],
|
|
94
|
+
webglVendors: ['Google Inc. (Intel)'],
|
|
95
|
+
webglRenderers: [
|
|
96
|
+
'ANGLE (Intel, Intel(R) Iris Plus Graphics 645, OpenGL 4.1)',
|
|
97
|
+
'ANGLE (Intel, Intel(R) Iris Plus Graphics, OpenGL 4.1)',
|
|
98
|
+
],
|
|
99
|
+
hardwareConcurrencies: [4, 8],
|
|
100
|
+
deviceMemories: [8, 16],
|
|
101
|
+
colorDepth: 24,
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
export const LINUX_PRESETS: DevicePreset[] = [
|
|
106
|
+
{
|
|
107
|
+
platform: 'Linux x86_64',
|
|
108
|
+
oscpu: 'Linux x86_64',
|
|
109
|
+
screens: [[1920, 1080], [2560, 1440]],
|
|
110
|
+
dprs: [1],
|
|
111
|
+
webglVendors: ['Google Inc. (NVIDIA)'],
|
|
112
|
+
webglRenderers: [
|
|
113
|
+
'ANGLE (NVIDIA, NVIDIA GeForce GTX 1080 Ti, OpenGL 4.5)',
|
|
114
|
+
'ANGLE (NVIDIA, NVIDIA GeForce RTX 3070, OpenGL 4.5)',
|
|
115
|
+
],
|
|
116
|
+
hardwareConcurrencies: [8, 12, 16],
|
|
117
|
+
deviceMemories: [16, 32],
|
|
118
|
+
colorDepth: 24,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
platform: 'Linux x86_64',
|
|
122
|
+
oscpu: 'Linux x86_64',
|
|
123
|
+
screens: [[1920, 1080]],
|
|
124
|
+
dprs: [1],
|
|
125
|
+
webglVendors: ['Google Inc. (Intel)'],
|
|
126
|
+
webglRenderers: [
|
|
127
|
+
'ANGLE (Intel, Mesa Intel(R) UHD Graphics 630 (CFL GT2), OpenGL 4.5)',
|
|
128
|
+
],
|
|
129
|
+
hardwareConcurrencies: [4, 8],
|
|
130
|
+
deviceMemories: [8, 16],
|
|
131
|
+
colorDepth: 24,
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
export const ALL_PRESETS: DevicePreset[] = [
|
|
136
|
+
...WINDOWS_PRESETS,
|
|
137
|
+
...MACOS_PRESETS,
|
|
138
|
+
...LINUX_PRESETS,
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
/** Timezone → language mapping for consistency validation */
|
|
142
|
+
export const TIMEZONE_LANGUAGES: Record<string, string[]> = {
|
|
143
|
+
'Asia/Shanghai': ['zh-CN', 'zh', 'en'],
|
|
144
|
+
'Asia/Tokyo': ['ja', 'en'],
|
|
145
|
+
'Asia/Seoul': ['ko', 'en'],
|
|
146
|
+
'America/New_York': ['en-US', 'en'],
|
|
147
|
+
'America/Los_Angeles': ['en-US', 'en'],
|
|
148
|
+
'America/Chicago': ['en-US', 'en'],
|
|
149
|
+
'Europe/London': ['en-GB', 'en'],
|
|
150
|
+
'Europe/Berlin': ['de-DE', 'de', 'en'],
|
|
151
|
+
'Europe/Paris': ['fr-FR', 'fr', 'en'],
|
|
152
|
+
'Europe/Moscow': ['ru-RU', 'ru', 'en'],
|
|
153
|
+
'Asia/Singapore': ['en-SG', 'zh', 'en'],
|
|
154
|
+
'Asia/Hong_Kong': ['zh-HK', 'zh', 'en'],
|
|
155
|
+
'Asia/Taipei': ['zh-TW', 'zh', 'en'],
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** Timezone → UTC offset in minutes (negative = east of UTC) */
|
|
159
|
+
export const TIMEZONE_OFFSETS: Record<string, number> = {
|
|
160
|
+
'Asia/Shanghai': -480,
|
|
161
|
+
'Asia/Tokyo': -540,
|
|
162
|
+
'Asia/Seoul': -540,
|
|
163
|
+
'America/New_York': 300,
|
|
164
|
+
'America/Los_Angeles': 480,
|
|
165
|
+
'America/Chicago': 360,
|
|
166
|
+
'Europe/London': 0,
|
|
167
|
+
'Europe/Berlin': -60,
|
|
168
|
+
'Europe/Paris': -60,
|
|
169
|
+
'Europe/Moscow': -180,
|
|
170
|
+
'Asia/Singapore': -480,
|
|
171
|
+
'Asia/Hong_Kong': -480,
|
|
172
|
+
'Asia/Taipei': -480,
|
|
173
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { FingerprintProfile } from '@shared/types';
|
|
2
|
+
import {
|
|
3
|
+
ALL_PRESETS,
|
|
4
|
+
CHROME_VERSIONS,
|
|
5
|
+
TIMEZONE_LANGUAGES,
|
|
6
|
+
TIMEZONE_OFFSETS,
|
|
7
|
+
type DevicePreset,
|
|
8
|
+
} from './presets';
|
|
9
|
+
|
|
10
|
+
/** Pick a random element from an array */
|
|
11
|
+
function pick<T>(arr: readonly T[]): T {
|
|
12
|
+
return arr[Math.floor(Math.random() * arr.length)];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Generate a random 32-bit unsigned integer */
|
|
16
|
+
function randomSeed(): number {
|
|
17
|
+
return Math.floor(Math.random() * 0xFFFFFFFF);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Detect the system timezone */
|
|
21
|
+
function getSystemTimezone(): string {
|
|
22
|
+
try {
|
|
23
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
24
|
+
} catch {
|
|
25
|
+
return 'Asia/Shanghai';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Build a full Chrome user-agent string */
|
|
30
|
+
function buildUserAgent(preset: DevicePreset, chromeVersion: string): string {
|
|
31
|
+
if (preset.platform === 'MacIntel') {
|
|
32
|
+
return `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
|
|
33
|
+
}
|
|
34
|
+
if (preset.platform === 'Linux x86_64') {
|
|
35
|
+
return `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
|
|
36
|
+
}
|
|
37
|
+
// Windows
|
|
38
|
+
return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Build the appVersion string (UA without "Mozilla/") */
|
|
42
|
+
function buildAppVersion(ua: string): string {
|
|
43
|
+
return ua.replace('Mozilla/', '');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate a complete, logically consistent FingerprintProfile.
|
|
48
|
+
*/
|
|
49
|
+
export function generateProfile(sessionId: string): FingerprintProfile {
|
|
50
|
+
const preset = pick(ALL_PRESETS);
|
|
51
|
+
const chromeVersion = pick(CHROME_VERSIONS);
|
|
52
|
+
const screen = pick(preset.screens);
|
|
53
|
+
const dpr = pick(preset.dprs);
|
|
54
|
+
const concurrency = pick(preset.hardwareConcurrencies);
|
|
55
|
+
// deviceMemory should be positively correlated with concurrency
|
|
56
|
+
const memoryOptions = preset.deviceMemories.filter(m => m >= concurrency);
|
|
57
|
+
const memory = memoryOptions.length > 0 ? pick(memoryOptions) : pick(preset.deviceMemories);
|
|
58
|
+
|
|
59
|
+
const timezone = getSystemTimezone();
|
|
60
|
+
const languages = TIMEZONE_LANGUAGES[timezone] || ['en-US', 'en'];
|
|
61
|
+
const timezoneOffset = TIMEZONE_OFFSETS[timezone] ?? -new Date().getTimezoneOffset();
|
|
62
|
+
|
|
63
|
+
const ua = buildUserAgent(preset, chromeVersion);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
sessionId,
|
|
67
|
+
userAgent: ua,
|
|
68
|
+
platform: preset.platform,
|
|
69
|
+
oscpu: preset.oscpu,
|
|
70
|
+
appVersion: buildAppVersion(ua),
|
|
71
|
+
screenWidth: screen[0],
|
|
72
|
+
screenHeight: screen[1],
|
|
73
|
+
colorDepth: preset.colorDepth,
|
|
74
|
+
devicePixelRatio: dpr,
|
|
75
|
+
hardwareConcurrency: concurrency,
|
|
76
|
+
deviceMemory: memory,
|
|
77
|
+
webglVendor: pick(preset.webglVendors),
|
|
78
|
+
webglRenderer: pick(preset.webglRenderers),
|
|
79
|
+
canvasNoise: randomSeed(),
|
|
80
|
+
audioNoise: randomSeed(),
|
|
81
|
+
languages,
|
|
82
|
+
timezone,
|
|
83
|
+
timezoneOffset,
|
|
84
|
+
webrtcPolicy: 'block',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate a FingerprintProfile for internal consistency.
|
|
90
|
+
* Returns an array of error messages (empty = valid).
|
|
91
|
+
*/
|
|
92
|
+
export function validateProfile(profile: FingerprintProfile): string[] {
|
|
93
|
+
const errors: string[] = [];
|
|
94
|
+
|
|
95
|
+
// Platform consistency
|
|
96
|
+
if (profile.platform === 'MacIntel' && profile.oscpu.includes('Windows')) {
|
|
97
|
+
errors.push('macOS platform has Windows oscpu');
|
|
98
|
+
}
|
|
99
|
+
if (profile.platform === 'Win32' && profile.oscpu.includes('Mac')) {
|
|
100
|
+
errors.push('Windows platform has macOS oscpu');
|
|
101
|
+
}
|
|
102
|
+
// UA consistency
|
|
103
|
+
if (profile.platform === 'Win32' && !profile.userAgent.includes('Windows')) {
|
|
104
|
+
errors.push('Windows platform but UA does not contain Windows');
|
|
105
|
+
}
|
|
106
|
+
if (profile.platform === 'MacIntel' && !profile.userAgent.includes('Macintosh')) {
|
|
107
|
+
errors.push('macOS platform but UA does not contain Macintosh');
|
|
108
|
+
}
|
|
109
|
+
// Memory vs concurrency
|
|
110
|
+
if (profile.deviceMemory > profile.hardwareConcurrency * 4) {
|
|
111
|
+
errors.push(`deviceMemory (${profile.deviceMemory}) exceeds hardwareConcurrency*4 (${profile.hardwareConcurrency * 4})`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return errors;
|
|
115
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { FingerprintProfile } from '@shared/types';
|
|
2
|
+
import type { FingerprintProfilesRepo } from '../db/repositories';
|
|
3
|
+
import { generateProfile } from './profile-generator';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ProfileStore — High-level interface for managing fingerprint profiles.
|
|
7
|
+
* Handles get-or-create logic and delegates persistence to the repository.
|
|
8
|
+
*/
|
|
9
|
+
export class ProfileStore {
|
|
10
|
+
constructor(private repo: FingerprintProfilesRepo) {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the profile for a session. If none exists, auto-generate one.
|
|
14
|
+
*/
|
|
15
|
+
getOrCreate(sessionId: string): FingerprintProfile {
|
|
16
|
+
const existing = this.repo.findBySessionId(sessionId);
|
|
17
|
+
if (existing) return existing;
|
|
18
|
+
const profile = generateProfile(sessionId);
|
|
19
|
+
this.repo.upsert(sessionId, profile);
|
|
20
|
+
return profile;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the profile for a session. Returns null if none exists.
|
|
25
|
+
*/
|
|
26
|
+
get(sessionId: string): FingerprintProfile | null {
|
|
27
|
+
return this.repo.findBySessionId(sessionId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Update (or create) a profile for a session.
|
|
32
|
+
*/
|
|
33
|
+
update(profile: FingerprintProfile): void {
|
|
34
|
+
this.repo.upsert(profile.sessionId, profile);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Regenerate a random profile for a session, replacing any existing one.
|
|
39
|
+
*/
|
|
40
|
+
regenerate(sessionId: string): FingerprintProfile {
|
|
41
|
+
const profile = generateProfile(sessionId);
|
|
42
|
+
this.repo.upsert(sessionId, profile);
|
|
43
|
+
return profile;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Delete the profile for a session.
|
|
48
|
+
*/
|
|
49
|
+
delete(sessionId: string): void {
|
|
50
|
+
this.repo.delete(sessionId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { app, BrowserWindow, crashReporter } from "electron";
|
|
2
|
+
import { initLogger } from "./logger";
|
|
3
|
+
import { getDatabase, closeDatabase } from "./db/database";
|
|
4
|
+
import { runMigrations } from "./db/migrations";
|
|
5
|
+
import {
|
|
6
|
+
SessionsRepo,
|
|
7
|
+
RequestsRepo,
|
|
8
|
+
JsHooksRepo,
|
|
9
|
+
StorageSnapshotsRepo,
|
|
10
|
+
AnalysisReportsRepo,
|
|
11
|
+
FingerprintProfilesRepo,
|
|
12
|
+
ChatMessagesRepo,
|
|
13
|
+
AiRequestLogRepo,
|
|
14
|
+
InteractionEventsRepo,
|
|
15
|
+
} from "./db/repositories";
|
|
16
|
+
import { CaptureEngine } from "./capture/capture-engine";
|
|
17
|
+
import { SessionManager } from "./session/session-manager";
|
|
18
|
+
import { AiAnalyzer } from "./ai/ai-analyzer";
|
|
19
|
+
import { WindowManager } from "./window";
|
|
20
|
+
import { registerIpcHandlers, loadProxyConfig, applyProxy, loadMCPServerConfig } from "./ipc";
|
|
21
|
+
import { Updater } from "./updater";
|
|
22
|
+
import { MCPClientManager } from "./mcp/mcp-manager";
|
|
23
|
+
import { initMCPServer, stopMCPServer } from "./mcp/mcp-server";
|
|
24
|
+
import { CaManager } from "./proxy/ca-manager";
|
|
25
|
+
import { MitmProxyServer } from "./proxy/mitm-proxy-server";
|
|
26
|
+
import { loadMitmProxyConfig, saveMitmProxyConfig } from "./proxy/mitm-proxy-config";
|
|
27
|
+
import { SystemProxy } from "./proxy/system-proxy";
|
|
28
|
+
import { ProfileStore } from "./fingerprint/profile-store";
|
|
29
|
+
import { join } from "path";
|
|
30
|
+
|
|
31
|
+
const windowManager = new WindowManager();
|
|
32
|
+
const mcpManager = new MCPClientManager();
|
|
33
|
+
let sessionManagerRef: SessionManager | null = null;
|
|
34
|
+
let quitInProgress = false;
|
|
35
|
+
|
|
36
|
+
// Prevent unhandled promise rejections from crashing the main process.
|
|
37
|
+
// Common source: executeJavaScript on crashed/destroyed WebContents.
|
|
38
|
+
process.on("unhandledRejection", (reason) => {
|
|
39
|
+
console.warn("[Main] Unhandled rejection:", reason);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// MITM Proxy — initialized lazily inside whenReady (app.getPath requires ready state)
|
|
43
|
+
let caManager: CaManager;
|
|
44
|
+
let mitmProxy: MitmProxyServer;
|
|
45
|
+
|
|
46
|
+
app.whenReady().then(async () => {
|
|
47
|
+
// Initialize structured logging (replaces console.log/warn/error globally)
|
|
48
|
+
initLogger();
|
|
49
|
+
|
|
50
|
+
// Enable native crash reporter — dumps go to userData/Crashpad/
|
|
51
|
+
crashReporter.start({ uploadToServer: false });
|
|
52
|
+
|
|
53
|
+
// Initialize MITM CA & proxy (requires app.getPath)
|
|
54
|
+
caManager = new CaManager(join(app.getPath("userData"), "mitm-ca"));
|
|
55
|
+
mitmProxy = new MitmProxyServer(caManager);
|
|
56
|
+
// Initialize database
|
|
57
|
+
const db = getDatabase();
|
|
58
|
+
runMigrations(db);
|
|
59
|
+
|
|
60
|
+
// Initialize repositories
|
|
61
|
+
const sessionsRepo = new SessionsRepo(db);
|
|
62
|
+
const requestsRepo = new RequestsRepo(db);
|
|
63
|
+
const jsHooksRepo = new JsHooksRepo(db);
|
|
64
|
+
const storageSnapshotsRepo = new StorageSnapshotsRepo(db);
|
|
65
|
+
const reportsRepo = new AnalysisReportsRepo(db);
|
|
66
|
+
const chatMessagesRepo = new ChatMessagesRepo(db);
|
|
67
|
+
const fingerprintRepo = new FingerprintProfilesRepo(db);
|
|
68
|
+
const aiRequestLogRepo = new AiRequestLogRepo(db);
|
|
69
|
+
const interactionEventsRepo = new InteractionEventsRepo(db);
|
|
70
|
+
const profileStore = new ProfileStore(fingerprintRepo);
|
|
71
|
+
|
|
72
|
+
// Initialize capture engine
|
|
73
|
+
const captureEngine = new CaptureEngine(
|
|
74
|
+
requestsRepo,
|
|
75
|
+
jsHooksRepo,
|
|
76
|
+
storageSnapshotsRepo,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Initialize session manager
|
|
80
|
+
const sessionManager = new SessionManager(sessionsRepo, captureEngine, profileStore, interactionEventsRepo);
|
|
81
|
+
sessionManagerRef = sessionManager;
|
|
82
|
+
|
|
83
|
+
// Recover from potential crash
|
|
84
|
+
sessionManager.recoverFromCrash();
|
|
85
|
+
|
|
86
|
+
// Initialize AI analyzer
|
|
87
|
+
const aiAnalyzer = new AiAnalyzer(
|
|
88
|
+
sessionsRepo,
|
|
89
|
+
requestsRepo,
|
|
90
|
+
jsHooksRepo,
|
|
91
|
+
storageSnapshotsRepo,
|
|
92
|
+
reportsRepo,
|
|
93
|
+
aiRequestLogRepo,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Create main window
|
|
97
|
+
windowManager.createMainWindow();
|
|
98
|
+
|
|
99
|
+
// Initialize tab manager with first tab
|
|
100
|
+
windowManager.initTabs();
|
|
101
|
+
|
|
102
|
+
// Apply proxy config from saved settings (before IPC handlers)
|
|
103
|
+
const proxyConfig = loadProxyConfig();
|
|
104
|
+
if (proxyConfig && proxyConfig.type !== "none") {
|
|
105
|
+
applyProxy(proxyConfig).catch((err) =>
|
|
106
|
+
console.error("Failed to apply proxy config:", err),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle proxy authentication challenges globally.
|
|
111
|
+
// Chromium proxyRules cannot carry credentials, so we respond to 407 via this event.
|
|
112
|
+
app.on("login", (event, _webContents, _request, authInfo, callback) => {
|
|
113
|
+
if (authInfo.isProxy) {
|
|
114
|
+
const cfg = loadProxyConfig();
|
|
115
|
+
if (cfg && cfg.username && cfg.password) {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
callback(cfg.username, cfg.password);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Non-proxy auth or no credentials — let default behavior proceed
|
|
122
|
+
callback();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Initialize auto-updater
|
|
126
|
+
const updater = new Updater();
|
|
127
|
+
const mainWin = windowManager.getMainWindow();
|
|
128
|
+
if (mainWin) updater.setMainWindow(mainWin);
|
|
129
|
+
|
|
130
|
+
// Inject MCP client manager into AI analyzer
|
|
131
|
+
aiAnalyzer.setMCPManager(mcpManager);
|
|
132
|
+
|
|
133
|
+
// Register IPC handlers
|
|
134
|
+
registerIpcHandlers({
|
|
135
|
+
sessionManager,
|
|
136
|
+
aiAnalyzer,
|
|
137
|
+
windowManager,
|
|
138
|
+
updater,
|
|
139
|
+
mcpManager,
|
|
140
|
+
mitmProxy,
|
|
141
|
+
caManager,
|
|
142
|
+
sessionsRepo,
|
|
143
|
+
requestsRepo,
|
|
144
|
+
jsHooksRepo,
|
|
145
|
+
storageSnapshotsRepo,
|
|
146
|
+
reportsRepo,
|
|
147
|
+
chatMessagesRepo,
|
|
148
|
+
profileStore,
|
|
149
|
+
aiRequestLogRepo,
|
|
150
|
+
interactionEventsRepo,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Check for updates on startup (non-blocking, delayed 3s)
|
|
154
|
+
setTimeout(() => updater.checkForUpdates(), 3000);
|
|
155
|
+
|
|
156
|
+
// Start MCP Server if enabled
|
|
157
|
+
const mcpServerConfig = loadMCPServerConfig();
|
|
158
|
+
if (mcpServerConfig.enabled) {
|
|
159
|
+
initMCPServer(
|
|
160
|
+
{ sessionManager, aiAnalyzer, windowManager, requestsRepo, jsHooksRepo, storageSnapshotsRepo, reportsRepo, interactionEventsRepo },
|
|
161
|
+
mcpServerConfig.port,
|
|
162
|
+
mcpServerConfig.authEnabled,
|
|
163
|
+
mcpServerConfig.authToken,
|
|
164
|
+
).catch((err) => console.error("Failed to start MCP Server:", err));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Initialize MITM Proxy
|
|
168
|
+
const mitmConfig = loadMitmProxyConfig();
|
|
169
|
+
|
|
170
|
+
// Apply upstream proxy config to MITM proxy so outbound traffic routes correctly
|
|
171
|
+
if (proxyConfig && proxyConfig.type !== "none") {
|
|
172
|
+
mitmProxy.setUpstreamProxy(proxyConfig);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Wire proxy captured events → CaptureEngine (same data shape as CDP)
|
|
176
|
+
mitmProxy.on("response-captured", (data) => {
|
|
177
|
+
captureEngine.handleResponseCaptured({ ...data, source: "proxy" });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (mitmConfig.enabled) {
|
|
181
|
+
caManager
|
|
182
|
+
.init()
|
|
183
|
+
.then(() => mitmProxy.start(mitmConfig.port))
|
|
184
|
+
.then(() => {
|
|
185
|
+
console.log("[Main] MITM proxy auto-started on port", mitmConfig.port);
|
|
186
|
+
if (mitmConfig.systemProxy) {
|
|
187
|
+
SystemProxy.enable(mitmConfig.port).catch((err) =>
|
|
188
|
+
console.error("[Main] Failed to enable system proxy:", err),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
.catch((err) => console.error("[Main] Failed to auto-start MITM proxy:", err));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Trust certificates issued by our MITM CA inside Electron.
|
|
196
|
+
// Without this, HTTPS requests through the MITM proxy fail with SSL errors
|
|
197
|
+
// that can crash Chromium's network stack (0xC0000005).
|
|
198
|
+
app.on("certificate-error", (event, _webContents, _url, _error, certificate, callback) => {
|
|
199
|
+
if (mitmProxy.isRunning() && certificate.issuerName === "Anything Analyzer CA") {
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
callback(true);
|
|
202
|
+
} else {
|
|
203
|
+
callback(false);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
app.on("activate", () => {
|
|
208
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
209
|
+
windowManager.createMainWindow();
|
|
210
|
+
windowManager.initTabs();
|
|
211
|
+
const win = windowManager.getMainWindow();
|
|
212
|
+
if (win) updater.setMainWindow(win);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
app.on("window-all-closed", () => {
|
|
218
|
+
if (process.platform !== "darwin") {
|
|
219
|
+
app.quit();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
app.on("before-quit", (event) => {
|
|
224
|
+
if (quitInProgress) return;
|
|
225
|
+
|
|
226
|
+
// Block immediate quit and perform ordered async cleanup first.
|
|
227
|
+
event.preventDefault();
|
|
228
|
+
quitInProgress = true;
|
|
229
|
+
|
|
230
|
+
(async () => {
|
|
231
|
+
try {
|
|
232
|
+
// Mark shutdown state early so tab destroy handlers don't recreate tabs.
|
|
233
|
+
windowManager.setShuttingDown(true);
|
|
234
|
+
|
|
235
|
+
// 1) Stop capture pipelines first, so no new DB writes are produced.
|
|
236
|
+
const currentSessionId = sessionManagerRef?.getCurrentSessionId();
|
|
237
|
+
if (sessionManagerRef && currentSessionId) {
|
|
238
|
+
await sessionManagerRef.stopCapture(currentSessionId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 2) Disable system proxy and persist state.
|
|
242
|
+
await SystemProxy.disable().catch(() => {});
|
|
243
|
+
const config = loadMitmProxyConfig();
|
|
244
|
+
if (config.systemProxy) {
|
|
245
|
+
saveMitmProxyConfig({ ...config, systemProxy: false });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 3) Stop async services.
|
|
249
|
+
await mitmProxy.stop().catch(() => {});
|
|
250
|
+
await stopMCPServer().catch(() => {});
|
|
251
|
+
await mcpManager.disconnectAll().catch(() => {});
|
|
252
|
+
} finally {
|
|
253
|
+
// 4) Close DB last, then let Electron finish normal quit flow.
|
|
254
|
+
closeDatabase();
|
|
255
|
+
app.quit();
|
|
256
|
+
}
|
|
257
|
+
})().catch(() => {
|
|
258
|
+
// Ignore and still force-exit via finally block above.
|
|
259
|
+
});
|
|
260
|
+
});
|