figranium 0.9.6 → 0.10.0
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 +1 -1
- package/dist/assets/{index-Bkr74C53.js → index-BwaOqbmy.js} +2 -2
- package/dist/index.html +1 -1
- package/headful.js +40 -101
- package/package.json +1 -1
- package/scrape.js +38 -85
- package/server.js +16 -0
- package/user-agent-settings.js +3 -3
package/dist/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<link
|
|
17
17
|
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
|
|
18
18
|
rel="stylesheet" />
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-BwaOqbmy.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index--OZi5-p_.css">
|
|
21
21
|
</head>
|
|
22
22
|
|
package/headful.js
CHANGED
|
@@ -1,30 +1,19 @@
|
|
|
1
|
-
const { chromium } = require('
|
|
1
|
+
const { chromium } = require('./stealth-chromium');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { getProxySelection } = require('./proxy-rotation');
|
|
5
5
|
const { selectUserAgent } = require('./user-agent-settings');
|
|
6
6
|
const { validateUrl } = require('./url-utils');
|
|
7
|
-
const { parseBooleanFlag
|
|
7
|
+
const { parseBooleanFlag } = require('./common-utils');
|
|
8
8
|
const { Mutex } = require('./src/server/utils');
|
|
9
9
|
|
|
10
|
+
const HEADFUL_PROFILE_DIR = path.join(__dirname, 'data', 'browser-profile-headful');
|
|
11
|
+
|
|
10
12
|
const headfulMutex = new Mutex();
|
|
11
13
|
|
|
12
14
|
const EventEmitter = require('events');
|
|
13
15
|
const headfulEventEmitter = new EventEmitter();
|
|
14
16
|
|
|
15
|
-
const STORAGE_STATE_PATH = path.join(__dirname, 'storage_state.json');
|
|
16
|
-
const STORAGE_STATE_FILE = (() => {
|
|
17
|
-
try {
|
|
18
|
-
if (fs.existsSync(STORAGE_STATE_PATH)) {
|
|
19
|
-
const stat = fs.statSync(STORAGE_STATE_PATH);
|
|
20
|
-
if (stat.isDirectory()) {
|
|
21
|
-
return path.join(STORAGE_STATE_PATH, 'storage_state.json');
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
} catch { }
|
|
25
|
-
return STORAGE_STATE_PATH;
|
|
26
|
-
})();
|
|
27
|
-
|
|
28
17
|
let activeSession = null;
|
|
29
18
|
|
|
30
19
|
const teardownActiveSession = async () => {
|
|
@@ -32,11 +21,6 @@ const teardownActiveSession = async () => {
|
|
|
32
21
|
try {
|
|
33
22
|
if (activeSession.interval) clearInterval(activeSession.interval);
|
|
34
23
|
} catch { }
|
|
35
|
-
try {
|
|
36
|
-
if (activeSession.context && !activeSession.stateless) {
|
|
37
|
-
await activeSession.context.storageState({ path: STORAGE_STATE_FILE });
|
|
38
|
-
}
|
|
39
|
-
} catch { }
|
|
40
24
|
try {
|
|
41
25
|
if (activeSession.browser) {
|
|
42
26
|
await activeSession.browser.close();
|
|
@@ -62,7 +46,7 @@ async function runHeadful(data, options = {}) {
|
|
|
62
46
|
|
|
63
47
|
const inspectModeEnabled = true;
|
|
64
48
|
|
|
65
|
-
activeSession = { status: 'starting', startedAt: Date.now(),
|
|
49
|
+
activeSession = { status: 'starting', startedAt: Date.now(), inspectModeEnabled };
|
|
66
50
|
|
|
67
51
|
const selectedUA = await selectUserAgent(false);
|
|
68
52
|
|
|
@@ -95,80 +79,47 @@ async function runHeadful(data, options = {}) {
|
|
|
95
79
|
}
|
|
96
80
|
|
|
97
81
|
if (!browser) {
|
|
98
|
-
const launchOptions = {
|
|
99
|
-
headless: false,
|
|
100
|
-
args: [
|
|
101
|
-
'--no-sandbox',
|
|
102
|
-
'--disable-setuid-sandbox',
|
|
103
|
-
'--disable-dev-shm-usage',
|
|
104
|
-
'--disable-gpu',
|
|
105
|
-
'--window-size=1920,1080',
|
|
106
|
-
'--window-position=0,0',
|
|
107
|
-
'--start-maximized'
|
|
108
|
-
]
|
|
109
|
-
};
|
|
110
82
|
const selection = getProxySelection(rotateProxies);
|
|
111
|
-
|
|
112
|
-
|
|
83
|
+
const hasProxy = !!selection.proxy;
|
|
84
|
+
|
|
85
|
+
const args = [
|
|
86
|
+
'--no-sandbox',
|
|
87
|
+
'--disable-setuid-sandbox',
|
|
88
|
+
'--disable-dev-shm-usage',
|
|
89
|
+
'--disable-gpu',
|
|
90
|
+
'--window-size=1920,1080',
|
|
91
|
+
'--window-position=0,0',
|
|
92
|
+
'--start-maximized',
|
|
93
|
+
'--dns-prefetch-disable',
|
|
94
|
+
'--force-webrtc-ip-handling-policy=disable_non_proxied_udp'
|
|
95
|
+
];
|
|
96
|
+
if (!hasProxy) {
|
|
97
|
+
args.push(
|
|
98
|
+
'--enable-features=DnsOverHttps',
|
|
99
|
+
'--dns-over-https-mode=secure',
|
|
100
|
+
'--dns-over-https-templates=https://cloudflare-dns.com/dns-query'
|
|
101
|
+
);
|
|
113
102
|
}
|
|
114
|
-
browser = await chromium.launch(launchOptions);
|
|
115
103
|
|
|
116
104
|
const contextOptions = {
|
|
117
105
|
viewport: null,
|
|
118
106
|
userAgent: selectedUA,
|
|
119
107
|
locale: 'en-US',
|
|
120
|
-
timezoneId: 'America/New_York'
|
|
108
|
+
timezoneId: 'America/New_York',
|
|
109
|
+
permissions: ['clipboard-read', 'clipboard-write'],
|
|
110
|
+
...(selection.proxy ? { proxy: selection.proxy } : {})
|
|
121
111
|
};
|
|
122
112
|
|
|
123
|
-
if (
|
|
124
|
-
|
|
113
|
+
if (statelessExecution) {
|
|
114
|
+
browser = await chromium.launch({ headless: false, args, ...(selection.proxy ? { proxy: selection.proxy } : {}) });
|
|
115
|
+
context = await browser.newContext(contextOptions);
|
|
116
|
+
} else {
|
|
117
|
+
await fs.promises.mkdir(HEADFUL_PROFILE_DIR, { recursive: true });
|
|
118
|
+
context = await chromium.launchPersistentContext(HEADFUL_PROFILE_DIR, { headless: false, args, ...contextOptions });
|
|
119
|
+
browser = context.browser();
|
|
125
120
|
}
|
|
126
|
-
|
|
127
|
-
contextOptions.permissions = ['clipboard-read', 'clipboard-write'];
|
|
128
|
-
context = await browser.newContext(contextOptions);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
let preloadedCookies = [];
|
|
132
|
-
if (fs.existsSync(STORAGE_STATE_FILE)) {
|
|
133
|
-
try {
|
|
134
|
-
const state = JSON.parse(fs.readFileSync(STORAGE_STATE_FILE, 'utf8'));
|
|
135
|
-
preloadedCookies = state.cookies || [];
|
|
136
|
-
} catch (e) { }
|
|
137
121
|
}
|
|
138
122
|
|
|
139
|
-
await context.route('**/*', async (route) => {
|
|
140
|
-
const request = route.request();
|
|
141
|
-
const requestUrl = request.url();
|
|
142
|
-
const resourceType = request.resourceType();
|
|
143
|
-
|
|
144
|
-
const isDataRequest = ['document', 'script', 'xhr', 'fetch'].includes(resourceType);
|
|
145
|
-
if (isDataRequest && preloadedCookies.length > 0) {
|
|
146
|
-
// ⚡ Bolt: Parse URL once to avoid redundant parsing inside cookieMatches filter loop
|
|
147
|
-
const urlObj = new URL(requestUrl);
|
|
148
|
-
const filteredCookies = preloadedCookies.filter(cookie => cookieMatches(cookie, urlObj));
|
|
149
|
-
if (filteredCookies.length > 0) {
|
|
150
|
-
const fileCookieMap = new Map();
|
|
151
|
-
filteredCookies.forEach(c => fileCookieMap.set(c.name, c.value));
|
|
152
|
-
|
|
153
|
-
const existingCookieHeader = request.headers()['cookie'] || '';
|
|
154
|
-
const existingCookies = existingCookieHeader.split(';').filter(Boolean).map(s => s.trim());
|
|
155
|
-
|
|
156
|
-
existingCookies.forEach(s => {
|
|
157
|
-
const [name, ...valParts] = s.split('=');
|
|
158
|
-
const val = valParts.join('=');
|
|
159
|
-
if (!fileCookieMap.has(name)) {
|
|
160
|
-
fileCookieMap.set(name, val);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const cookieHeader = Array.from(fileCookieMap.entries()).map(([n, v]) => `${n}=${v}`).join('; ');
|
|
165
|
-
const headers = { ...request.headers(), 'cookie': cookieHeader };
|
|
166
|
-
return route.continue({ headers });
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
route.continue();
|
|
170
|
-
});
|
|
171
|
-
|
|
172
123
|
const inspectInitFn = () => {
|
|
173
124
|
Object.defineProperty(window, 'open', { writable: true, configurable: true, value: () => null });
|
|
174
125
|
const handleLinkClick = (event) => {
|
|
@@ -461,7 +412,9 @@ async function runHeadful(data, options = {}) {
|
|
|
461
412
|
});
|
|
462
413
|
|
|
463
414
|
if (!page) {
|
|
464
|
-
page
|
|
415
|
+
// Persistent context auto-creates a blank page; reuse it or open a new one
|
|
416
|
+
const existingPages = context.pages();
|
|
417
|
+
page = existingPages.length > 0 ? existingPages[0] : await context.newPage();
|
|
465
418
|
try {
|
|
466
419
|
const cdp = await context.newCDPSession(page);
|
|
467
420
|
const { windowId } = await cdp.send('Browser.getWindowForTarget');
|
|
@@ -491,25 +444,13 @@ async function runHeadful(data, options = {}) {
|
|
|
491
444
|
await page.goto(url).catch(() => { });
|
|
492
445
|
}
|
|
493
446
|
|
|
494
|
-
|
|
495
|
-
if (statelessExecution) return;
|
|
496
|
-
try {
|
|
497
|
-
await context.storageState({ path: STORAGE_STATE_FILE });
|
|
498
|
-
} catch (e) { }
|
|
499
|
-
};
|
|
500
|
-
|
|
501
|
-
const interval = setInterval(saveState, 10000);
|
|
502
|
-
activeSession = { browser, context, page, interval, status: 'running', startedAt: activeSession.startedAt, stateless: statelessExecution, inspectModeEnabled: activeSession.inspectModeEnabled };
|
|
447
|
+
activeSession = { browser, context, page, status: 'running', startedAt: activeSession.startedAt, inspectModeEnabled: activeSession.inspectModeEnabled };
|
|
503
448
|
|
|
504
|
-
page.on('close', async () => {
|
|
505
|
-
clearInterval(interval);
|
|
506
|
-
await saveState();
|
|
507
|
-
});
|
|
449
|
+
page.on('close', async () => { });
|
|
508
450
|
|
|
509
451
|
const responseData = {
|
|
510
452
|
message: 'Headful session started.',
|
|
511
|
-
userAgentUsed: selectedUA
|
|
512
|
-
path: statelessExecution ? null : STORAGE_STATE_FILE
|
|
453
|
+
userAgentUsed: selectedUA
|
|
513
454
|
};
|
|
514
455
|
|
|
515
456
|
if (res) {
|
|
@@ -517,8 +458,6 @@ async function runHeadful(data, options = {}) {
|
|
|
517
458
|
}
|
|
518
459
|
|
|
519
460
|
await new Promise((resolve) => browser.on('disconnected', resolve));
|
|
520
|
-
clearInterval(interval);
|
|
521
|
-
await saveState();
|
|
522
461
|
activeSession = null;
|
|
523
462
|
return responseData;
|
|
524
463
|
} catch (error) {
|
package/package.json
CHANGED
package/scrape.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { chromium } = require('
|
|
1
|
+
const { chromium } = require('./stealth-chromium');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { spawn } = require('child_process');
|
|
@@ -6,21 +6,10 @@ const { getProxySelection } = require('./proxy-rotation');
|
|
|
6
6
|
const { selectUserAgent } = require('./user-agent-settings');
|
|
7
7
|
const { formatHTML } = require('./html-utils');
|
|
8
8
|
const { validateUrl } = require('./url-utils');
|
|
9
|
-
const { parseBooleanFlag, sanitizeRunId, toCsvString
|
|
9
|
+
const { parseBooleanFlag, sanitizeRunId, toCsvString } = require('./common-utils');
|
|
10
10
|
const { installMouseHelper } = require('./src/agent/dom-utils');
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const STORAGE_STATE_FILE = (() => {
|
|
14
|
-
try {
|
|
15
|
-
if (fs.existsSync(STORAGE_STATE_PATH)) {
|
|
16
|
-
const stat = fs.statSync(STORAGE_STATE_PATH);
|
|
17
|
-
if (stat.isDirectory()) {
|
|
18
|
-
return path.join(STORAGE_STATE_PATH, 'storage_state.json');
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
} catch { }
|
|
22
|
-
return STORAGE_STATE_PATH;
|
|
23
|
-
})();
|
|
12
|
+
const PROFILE_DIR = path.join(__dirname, 'data', 'browser-profile-scrape');
|
|
24
13
|
|
|
25
14
|
async function runScrape(data) {
|
|
26
15
|
const url = data.url;
|
|
@@ -58,32 +47,38 @@ async function runScrape(data) {
|
|
|
58
47
|
let context;
|
|
59
48
|
let page;
|
|
60
49
|
try {
|
|
61
|
-
const launchOptions = {
|
|
62
|
-
headless: true,
|
|
63
|
-
args: [
|
|
64
|
-
'--no-sandbox',
|
|
65
|
-
'--disable-setuid-sandbox',
|
|
66
|
-
'--disable-dev-shm-usage',
|
|
67
|
-
'--disable-blink-features=AutomationControlled',
|
|
68
|
-
'--hide-scrollbars',
|
|
69
|
-
'--mute-audio'
|
|
70
|
-
]
|
|
71
|
-
};
|
|
72
50
|
const selection = getProxySelection(rotateProxies);
|
|
73
|
-
|
|
74
|
-
|
|
51
|
+
const hasProxy = !!selection.proxy;
|
|
52
|
+
|
|
53
|
+
const args = [
|
|
54
|
+
'--no-sandbox',
|
|
55
|
+
'--disable-setuid-sandbox',
|
|
56
|
+
'--disable-dev-shm-usage',
|
|
57
|
+
'--disable-blink-features=AutomationControlled',
|
|
58
|
+
'--hide-scrollbars',
|
|
59
|
+
'--mute-audio',
|
|
60
|
+
'--dns-prefetch-disable',
|
|
61
|
+
'--force-webrtc-ip-handling-policy=disable_non_proxied_udp'
|
|
62
|
+
];
|
|
63
|
+
if (!hasProxy) {
|
|
64
|
+
args.push(
|
|
65
|
+
'--enable-features=DnsOverHttps',
|
|
66
|
+
'--dns-over-https-mode=secure',
|
|
67
|
+
'--dns-over-https-templates=https://cloudflare-dns.com/dns-query'
|
|
68
|
+
);
|
|
75
69
|
}
|
|
76
70
|
|
|
77
|
-
browser = await chromium.launch(launchOptions);
|
|
78
|
-
|
|
79
71
|
const recordingsDir = path.join(__dirname, 'data', 'recordings');
|
|
80
72
|
await fs.promises.mkdir(recordingsDir, { recursive: true });
|
|
73
|
+
await fs.promises.mkdir(PROFILE_DIR, { recursive: true });
|
|
81
74
|
|
|
82
75
|
const viewport = rotateViewport
|
|
83
76
|
? { width: 1280 + Math.floor(Math.random() * 640), height: 720 + Math.floor(Math.random() * 360) }
|
|
84
77
|
: { width: 1366, height: 768 };
|
|
85
78
|
|
|
86
79
|
const contextOptions = {
|
|
80
|
+
headless: true,
|
|
81
|
+
args,
|
|
87
82
|
userAgent: selectedUA,
|
|
88
83
|
extraHTTPHeaders: customHeaders,
|
|
89
84
|
viewport,
|
|
@@ -94,61 +89,24 @@ async function runScrape(data) {
|
|
|
94
89
|
permissions: ['geolocation']
|
|
95
90
|
};
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
contextOptions.storageState = STORAGE_STATE_FILE;
|
|
92
|
+
if (selection.proxy) {
|
|
93
|
+
contextOptions.proxy = selection.proxy;
|
|
100
94
|
}
|
|
101
95
|
|
|
102
96
|
if (!disableRecording) {
|
|
103
97
|
contextOptions.recordVideo = { dir: recordingsDir, size: viewport };
|
|
104
98
|
}
|
|
105
99
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
100
|
+
if (statelessExecution) {
|
|
101
|
+
const launchOpts = { headless: true, args, ...(selection.proxy ? { proxy: selection.proxy } : {}) };
|
|
102
|
+
browser = await chromium.launch(launchOpts);
|
|
103
|
+
context = await browser.newContext(contextOptions);
|
|
104
|
+
} else {
|
|
105
|
+
await fs.promises.mkdir(PROFILE_DIR, { recursive: true });
|
|
106
|
+
context = await chromium.launchPersistentContext(PROFILE_DIR, { headless: true, args, ...contextOptions });
|
|
107
|
+
browser = context.browser();
|
|
114
108
|
}
|
|
115
109
|
|
|
116
|
-
await context.route('**/*', async (route) => {
|
|
117
|
-
const request = route.request();
|
|
118
|
-
const requestUrl = request.url();
|
|
119
|
-
const resourceType = request.resourceType();
|
|
120
|
-
|
|
121
|
-
const isDataRequest = ['document', 'script', 'xhr', 'fetch'].includes(resourceType);
|
|
122
|
-
if (isDataRequest && preloadedCookies.length > 0) {
|
|
123
|
-
// ⚡ Bolt: Parse URL once to avoid redundant parsing inside cookieMatches filter loop
|
|
124
|
-
const urlObj = new URL(requestUrl);
|
|
125
|
-
const filteredCookies = preloadedCookies.filter(cookie => cookieMatches(cookie, urlObj));
|
|
126
|
-
if (filteredCookies.length > 0) {
|
|
127
|
-
const fileCookieMap = new Map();
|
|
128
|
-
filteredCookies.forEach(c => fileCookieMap.set(c.name, c.value));
|
|
129
|
-
|
|
130
|
-
const existingCookieHeader = request.headers()['cookie'] || '';
|
|
131
|
-
const existingCookies = existingCookieHeader.split(';').filter(Boolean).map(s => s.trim());
|
|
132
|
-
|
|
133
|
-
existingCookies.forEach(s => {
|
|
134
|
-
const [name, ...valParts] = s.split('=');
|
|
135
|
-
const val = valParts.join('=');
|
|
136
|
-
if (!fileCookieMap.has(name)) {
|
|
137
|
-
fileCookieMap.set(name, val);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const cookieHeader = Array.from(fileCookieMap.entries()).map(([n, v]) => `${n}=${v}`).join('; ');
|
|
142
|
-
const headers = { ...request.headers(), 'cookie': cookieHeader };
|
|
143
|
-
return route.continue({ headers });
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
route.continue();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
await context.addInitScript(() => {
|
|
150
|
-
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
151
|
-
});
|
|
152
110
|
await context.addInitScript(installMouseHelper);
|
|
153
111
|
|
|
154
112
|
if (includeShadowDom) {
|
|
@@ -162,7 +120,9 @@ async function runScrape(data) {
|
|
|
162
120
|
});
|
|
163
121
|
}
|
|
164
122
|
|
|
165
|
-
page
|
|
123
|
+
// Persistent context auto-creates a blank page; reuse it or open a new one
|
|
124
|
+
const existingPages = context.pages();
|
|
125
|
+
page = existingPages.length > 0 ? existingPages[0] : await context.newPage();
|
|
166
126
|
|
|
167
127
|
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
168
128
|
|
|
@@ -360,10 +320,6 @@ async function runScrape(data) {
|
|
|
360
320
|
screenshot_url: `/captures/${screenshotName}`
|
|
361
321
|
};
|
|
362
322
|
|
|
363
|
-
if (!statelessExecution) {
|
|
364
|
-
try { await context.storageState({ path: STORAGE_STATE_FILE }); } catch { }
|
|
365
|
-
}
|
|
366
|
-
|
|
367
323
|
const video = page.video();
|
|
368
324
|
await context.close();
|
|
369
325
|
if (video) {
|
|
@@ -389,12 +345,9 @@ async function runScrape(data) {
|
|
|
389
345
|
}
|
|
390
346
|
}
|
|
391
347
|
|
|
392
|
-
await browser.close();
|
|
348
|
+
if (browser) await browser.close();
|
|
393
349
|
return resultData;
|
|
394
350
|
} catch (error) {
|
|
395
|
-
if (context && !statelessExecution) {
|
|
396
|
-
try { await context.storageState({ path: STORAGE_STATE_FILE }); } catch { }
|
|
397
|
-
}
|
|
398
351
|
if (context) await context.close();
|
|
399
352
|
if (browser) await browser.close();
|
|
400
353
|
throw error;
|
package/server.js
CHANGED
|
@@ -5,6 +5,18 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const crypto = require('crypto');
|
|
7
7
|
|
|
8
|
+
// Catch unhandled promise rejections from playwright-extra stealth plugin.
|
|
9
|
+
// When pages close before the plugin finishes async CDP initialization,
|
|
10
|
+
// benign rejections bubble up and would otherwise crash the process.
|
|
11
|
+
process.on('unhandledRejection', (reason) => {
|
|
12
|
+
const msg = reason && reason.message ? reason.message : String(reason);
|
|
13
|
+
if (/Target page, context or browser has been closed/i.test(msg)) {
|
|
14
|
+
console.warn('[STEALTH] Suppressed benign rejection:', msg);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
console.error('Unhandled rejection:', reason);
|
|
18
|
+
});
|
|
19
|
+
|
|
8
20
|
// Constants
|
|
9
21
|
const {
|
|
10
22
|
DEFAULT_PORT,
|
|
@@ -61,6 +73,7 @@ const viewRoutes = require('./src/server/routes/views');
|
|
|
61
73
|
const scheduleRoutes = require('./src/server/routes/schedules');
|
|
62
74
|
const credentialRoutes = require('./src/server/routes/credentials');
|
|
63
75
|
const { pushOutput } = require('./src/server/outputProviders');
|
|
76
|
+
const { migrateStorageState } = require('./src/server/migrate-storage');
|
|
64
77
|
|
|
65
78
|
const app = express();
|
|
66
79
|
app.disable('x-powered-by');
|
|
@@ -472,6 +485,9 @@ findAvailablePort(port, 20)
|
|
|
472
485
|
const displayPort = typeof address === 'object' && address ? address.port : availablePort;
|
|
473
486
|
console.log(`Server running at http://localhost:${displayPort}`);
|
|
474
487
|
|
|
488
|
+
// One-time migration of storage_state.json cookies into persistent browser profiles
|
|
489
|
+
migrateStorageState().catch(err => console.error('[MIGRATION] Failed:', err.message));
|
|
490
|
+
|
|
475
491
|
// Start the cron scheduler
|
|
476
492
|
const { startScheduler } = require('./src/server/scheduler');
|
|
477
493
|
startScheduler().catch(err => console.error('[SCHEDULER] Failed to start:', err.message));
|
package/user-agent-settings.js
CHANGED
|
@@ -4,9 +4,9 @@ const path = require('path');
|
|
|
4
4
|
const USER_AGENT_FILE = path.join(__dirname, 'data', 'user_agent.json');
|
|
5
5
|
const DEFAULT_SELECTION = 'system';
|
|
6
6
|
const userAgents = [
|
|
7
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
8
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
9
|
-
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
7
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
|
|
8
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
|
|
9
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36'
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
let cached = { mtimeMs: 0, config: { selection: DEFAULT_SELECTION } };
|