@web-auto/camo 0.2.0 → 0.2.1

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.
Files changed (114) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +586 -586
  3. package/bin/browser-service.mjs +11 -11
  4. package/bin/camo.mjs +22 -22
  5. package/package.json +48 -48
  6. package/scripts/build.mjs +19 -19
  7. package/scripts/bump-version.mjs +34 -34
  8. package/scripts/check-file-size.mjs +80 -80
  9. package/scripts/file-size-policy.json +12 -2
  10. package/scripts/install.mjs +76 -76
  11. package/scripts/release.sh +54 -54
  12. package/src/autoscript/action-providers/index.mjs +6 -6
  13. package/src/autoscript/impact-engine.mjs +78 -78
  14. package/src/autoscript/runtime.mjs +1017 -1017
  15. package/src/autoscript/schema.mjs +376 -376
  16. package/src/cli.mjs +405 -405
  17. package/src/commands/attach.mjs +141 -141
  18. package/src/commands/autoscript.mjs +1011 -1011
  19. package/src/commands/browser.mjs +1255 -1255
  20. package/src/commands/container.mjs +401 -401
  21. package/src/commands/cookies.mjs +69 -69
  22. package/src/commands/create.mjs +98 -98
  23. package/src/commands/devtools.mjs +349 -349
  24. package/src/commands/events.mjs +152 -152
  25. package/src/commands/highlight-mode.mjs +24 -24
  26. package/src/commands/init.mjs +68 -68
  27. package/src/commands/lifecycle.mjs +275 -275
  28. package/src/commands/mouse.mjs +45 -45
  29. package/src/commands/profile.mjs +46 -46
  30. package/src/commands/record.mjs +115 -115
  31. package/src/commands/system.mjs +14 -14
  32. package/src/commands/window.mjs +123 -123
  33. package/src/container/change-notifier.mjs +362 -362
  34. package/src/container/element-filter.mjs +143 -143
  35. package/src/container/index.mjs +3 -3
  36. package/src/container/runtime-core/checkpoint.mjs +209 -209
  37. package/src/container/runtime-core/index.mjs +21 -21
  38. package/src/container/runtime-core/operations/index.mjs +774 -774
  39. package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
  40. package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
  41. package/src/container/runtime-core/operations/viewport.mjs +189 -189
  42. package/src/container/runtime-core/search.mjs +190 -190
  43. package/src/container/runtime-core/subscription.mjs +224 -224
  44. package/src/container/runtime-core/utils.mjs +94 -94
  45. package/src/container/runtime-core/validation.mjs +127 -127
  46. package/src/container/runtime-core.mjs +1 -1
  47. package/src/container/subscription-registry.mjs +459 -459
  48. package/src/core/actions.mjs +561 -561
  49. package/src/core/browser.mjs +266 -266
  50. package/src/core/index.mjs +52 -52
  51. package/src/core/utils.mjs +91 -91
  52. package/src/events/daemon-entry.mjs +33 -33
  53. package/src/events/daemon.mjs +80 -80
  54. package/src/events/progress-log.mjs +109 -109
  55. package/src/events/ws-server.mjs +239 -239
  56. package/src/lib/client.mjs +200 -200
  57. package/src/lifecycle/cleanup.mjs +83 -83
  58. package/src/lifecycle/lock.mjs +126 -126
  59. package/src/lifecycle/session-registry.mjs +279 -279
  60. package/src/lifecycle/session-view.mjs +76 -76
  61. package/src/lifecycle/session-watchdog.mjs +281 -281
  62. package/src/services/browser-service/index.js +671 -671
  63. package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
  64. package/src/services/browser-service/internal/BrowserSession.js +325 -304
  65. package/src/services/browser-service/internal/ElementRegistry.js +60 -60
  66. package/src/services/browser-service/internal/ProfileLock.js +84 -84
  67. package/src/services/browser-service/internal/SessionManager.js +184 -184
  68. package/src/services/browser-service/internal/SessionManager.test.js +39 -39
  69. package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
  70. package/src/services/browser-service/internal/browser-session/input-ops.js +222 -222
  71. package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
  72. package/src/services/browser-service/internal/browser-session/logging.js +46 -46
  73. package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
  74. package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
  75. package/src/services/browser-service/internal/browser-session/page-management.js +302 -302
  76. package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
  77. package/src/services/browser-service/internal/browser-session/recording.js +198 -198
  78. package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
  79. package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
  80. package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
  81. package/src/services/browser-service/internal/browser-session/types.js +14 -14
  82. package/src/services/browser-service/internal/browser-session/utils.js +95 -95
  83. package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
  84. package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
  85. package/src/services/browser-service/internal/container-matcher.js +851 -851
  86. package/src/services/browser-service/internal/container-registry.js +182 -182
  87. package/src/services/browser-service/internal/engine-manager.js +259 -259
  88. package/src/services/browser-service/internal/fingerprint.js +203 -203
  89. package/src/services/browser-service/internal/heartbeat.js +137 -137
  90. package/src/services/browser-service/internal/logging.js +46 -46
  91. package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
  92. package/src/services/browser-service/internal/pageRuntime.js +28 -28
  93. package/src/services/browser-service/internal/runtimeInjector.js +31 -31
  94. package/src/services/browser-service/internal/service-process-logger.js +140 -140
  95. package/src/services/browser-service/internal/state-bus.js +45 -45
  96. package/src/services/browser-service/internal/storage-paths.js +42 -42
  97. package/src/services/browser-service/internal/ws-server.js +1194 -1194
  98. package/src/services/browser-service/internal/ws-server.test.js +58 -58
  99. package/src/services/browser-service/server.mjs +6 -6
  100. package/src/services/controller/cli-bridge.js +93 -93
  101. package/src/services/controller/container-index.js +50 -50
  102. package/src/services/controller/container-storage.js +36 -36
  103. package/src/services/controller/controller-actions.js +207 -207
  104. package/src/services/controller/controller.js +1138 -1138
  105. package/src/services/controller/selectors.js +54 -54
  106. package/src/services/controller/transport.js +125 -125
  107. package/src/utils/args.mjs +26 -26
  108. package/src/utils/browser-service.mjs +544 -544
  109. package/src/utils/command-log.mjs +64 -64
  110. package/src/utils/config.mjs +214 -214
  111. package/src/utils/fingerprint.mjs +181 -181
  112. package/src/utils/help.mjs +216 -216
  113. package/src/utils/js-policy.mjs +13 -13
  114. package/src/utils/ws-client.mjs +30 -30
@@ -1,304 +1,325 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { ProfileLock } from './ProfileLock.js';
4
- import { BrowserSessionRecording } from './browser-session/recording.js';
5
- import { createPageHooksManager } from './browser-session/page-hooks.js';
6
- import { BrowserSessionCookies } from './browser-session/cookies.js';
7
- import { BrowserSessionPageManagement } from './browser-session/page-management.js';
8
- import { BrowserInputPipeline } from './browser-session/input-pipeline.js';
9
- import { BrowserSessionInputOps } from './browser-session/input-ops.js';
10
- import { createRuntimeEventManager } from './browser-session/runtime-events.js';
11
- import { BrowserSessionNavigation } from './browser-session/navigation.js';
12
- import { BrowserSessionViewportManager } from './browser-session/viewport-manager.js';
13
- import { logDebug } from './logging.js';
14
- import { loadOrGenerateFingerprint, applyFingerprint } from './fingerprint.js';
15
- import { launchEngineContext } from './engine-manager.js';
16
- import { resolveProfilesRoot } from './storage-paths.js';
17
- export class BrowserSession {
18
- options;
19
- browser;
20
- context;
21
- page;
22
- lock;
23
- profileDir;
24
- lastKnownUrl = null;
25
- mode = 'dev';
26
- cookiesManager;
27
- runtimeEvents;
28
- pageHooks;
29
- viewportManager;
30
- pageManager;
31
- navigation;
32
- inputPipeline;
33
- inputOps;
34
- fingerprint = null;
35
- recordingManager;
36
- onExit;
37
- exitNotified = false;
38
- constructor(options) {
39
- this.options = options;
40
- const profileId = options.profileId || 'default';
41
- const root = resolveProfilesRoot();
42
- this.profileDir = path.join(root, profileId);
43
- fs.mkdirSync(this.profileDir, { recursive: true });
44
- this.lock = new ProfileLock(profileId);
45
- this.recordingManager = new BrowserSessionRecording(profileId, () => this.getCurrentUrl(), () => this.context);
46
- this.cookiesManager = new BrowserSessionCookies(profileId, () => this.context, () => this.getActivePage());
47
- this.inputPipeline = new BrowserInputPipeline(() => this.ensurePrimaryPage(), () => this.options.headless === true);
48
- this.inputOps = new BrowserSessionInputOps(() => this.ensurePrimaryPage(), (page) => this.inputPipeline.ensureInputReady(page), (page, label, run) => this.inputPipeline.runInputAction(page, label, run), (run) => this.inputPipeline.withInputActionLock(run));
49
- this.viewportManager = new BrowserSessionViewportManager(profileId, () => this.context, () => String(this.options.engine ?? 'camoufox'), () => this.options.headless === true);
50
- this.runtimeEvents = createRuntimeEventManager(profileId);
51
- this.pageHooks = createPageHooksManager({
52
- profileId,
53
- getRecording: () => this.recordingManager.getRecordingStatus(),
54
- emitRuntimeEvent: (event) => this.runtimeEvents.emit(event),
55
- recordPageVisit: (page, reason) => {
56
- this.lastKnownUrl = page?.url?.() || this.lastKnownUrl;
57
- this.recordingManager.recordPageVisit(page, reason);
58
- },
59
- handleRecorderEvent: (page, evt) => this.recordingManager.handleRecorderEvent(page, evt),
60
- });
61
- this.pageManager = new BrowserSessionPageManagement({
62
- ensureContext: () => this.ensureContext(),
63
- getActivePage: () => this.getActivePage(),
64
- getCurrentUrl: () => this.getCurrentUrl(),
65
- setActivePage: (page) => { this.page = page; },
66
- setupPageHooks: (page) => this.setupPageHooks(page),
67
- ensurePageViewport: (page) => this.ensurePageViewport(page),
68
- maybeCenterPage: (page, viewport) => this.viewportManager.maybeCenter(page, viewport),
69
- recordLastKnownUrl: (url) => { if (url)
70
- this.lastKnownUrl = url; },
71
- isHeadless: () => this.options.headless === true,
72
- });
73
- this.navigation = new BrowserSessionNavigation({
74
- ensurePrimaryPage: () => this.pageManager.ensurePrimaryPage(),
75
- getActivePage: () => this.getActivePage(),
76
- recordLastKnownUrl: (url) => { if (url)
77
- this.lastKnownUrl = url; },
78
- getLastKnownUrl: () => this.lastKnownUrl,
79
- });
80
- }
81
- get id() {
82
- return this.options.profileId;
83
- }
84
- get currentPage() {
85
- return this.page;
86
- }
87
- get modeName() {
88
- return this.mode;
89
- }
90
- setMode(next = 'dev') {
91
- this.mode = next === 'run' ? 'run' : 'dev';
92
- }
93
- getInfo() {
94
- return {
95
- session_id: this.options.profileId,
96
- profileId: this.options.profileId,
97
- current_url: this.getCurrentUrl(),
98
- mode: this.mode,
99
- headless: !!this.options.headless,
100
- recording: this.recordingManager.getRecordingStatus(),
101
- };
102
- }
103
- async start(initialUrl) {
104
- if (!this.lock.acquire()) {
105
- throw new Error(`无法获取 profile ${this.options.profileId} 的锁`);
106
- }
107
- const engine = 'camoufox';
108
- // 加载或生成指纹(支持 Win/Mac 随机)
109
- const fingerprint = await loadOrGenerateFingerprint(this.options.profileId, {
110
- platform: this.options.fingerprintPlatform || null,
111
- });
112
- this.fingerprint = fingerprint;
113
- logDebug('browser-service', 'session:fingerprint', {
114
- profileId: this.options.profileId,
115
- platform: fingerprint.platform,
116
- userAgent: fingerprint.userAgent?.substring(0, 50) + '...',
117
- });
118
- const fallbackViewport = { width: 1440, height: 1100 };
119
- const explicitViewport = this.options.viewport
120
- && Number(this.options.viewport.width) > 0
121
- && Number(this.options.viewport.height) > 0
122
- ? {
123
- width: Math.floor(Number(this.options.viewport.width)),
124
- height: Math.floor(Number(this.options.viewport.height)),
125
- }
126
- : null;
127
- const viewport = explicitViewport || fingerprint?.viewport || fallbackViewport;
128
- const headless = !!this.options.headless;
129
- const followWindowViewport = !headless;
130
- // 使用 EngineManager 启动上下文(Chromium 已移除,仅支持 Camoufox)
131
- this.context = await launchEngineContext({
132
- engine,
133
- headless,
134
- profileDir: this.profileDir,
135
- viewport,
136
- userAgent: fingerprint?.userAgent,
137
- locale: 'zh-CN',
138
- timezoneId: fingerprint?.timezoneId || 'Asia/Shanghai',
139
- });
140
- // 应用指纹到上下文(Playwright JS 注入)
141
- await applyFingerprint(this.context, fingerprint);
142
- // NOTE: deviceScaleFactor override was Chromium-only (CDP). Chromium removed.
143
- this.viewportManager.setInitialViewport(viewport, followWindowViewport);
144
- this.browser = this.context.browser();
145
- this.browser.on('disconnected', () => this.notifyExit());
146
- this.context.on('close', () => this.notifyExit());
147
- const existing = this.context.pages();
148
- this.page = existing.length ? existing[0] : await this.context.newPage();
149
- this.setupPageHooks(this.page);
150
- this.context.on('page', (p) => this.setupPageHooks(p));
151
- if (this.viewportManager.isFollowingWindow()) {
152
- await this.viewportManager.refreshFromWindow(this.page).catch(() => { });
153
- }
154
- if (initialUrl) {
155
- await this.goto(initialUrl);
156
- }
157
- }
158
- setupPageHooks(page) {
159
- this.pageHooks.setupPageHooks(page);
160
- }
161
- addRuntimeEventObserver(observer) {
162
- return this.runtimeEvents.addObserver(observer);
163
- }
164
- getRecordingStatus() {
165
- return this.recordingManager.getRecordingStatus();
166
- }
167
- async startRecording(options = {}) {
168
- this.recordingManager.setBindRecorderBridge((page) => this.pageHooks.bindRecorderBridge(page));
169
- this.recordingManager.setInstallRecorderRuntime((page, reason) => this.pageHooks.installRecorderRuntime(page, reason));
170
- return this.recordingManager.startRecording(options);
171
- }
172
- async stopRecording(options = {}) {
173
- return this.recordingManager.stopRecording(options);
174
- }
175
- getActivePage() {
176
- if (this.page && !this.page.isClosed()) {
177
- return this.page;
178
- }
179
- if (!this.context)
180
- return null;
181
- const alive = this.context.pages().find((p) => !p.isClosed());
182
- if (alive) {
183
- this.page = alive;
184
- return alive;
185
- }
186
- this.page = undefined;
187
- return null;
188
- }
189
- async ensurePageViewport(page) {
190
- await this.viewportManager.ensurePageViewport(page);
191
- }
192
- ensureContext() {
193
- if (!this.context) {
194
- throw new Error('browser context not ready');
195
- }
196
- return this.context;
197
- }
198
- async ensurePrimaryPage() {
199
- return this.pageManager.ensurePrimaryPage();
200
- }
201
- async ensurePage(url) {
202
- return this.pageManager.ensurePage(url);
203
- }
204
- async goBack() {
205
- return this.navigation.goBack();
206
- }
207
- listPages() {
208
- return this.pageManager.listPages();
209
- }
210
- async newPage(url, options = {}) {
211
- return this.pageManager.newPage(url, options);
212
- }
213
- async switchPage(index) {
214
- return this.pageManager.switchPage(index);
215
- }
216
- async closePage(index) {
217
- return this.pageManager.closePage(index);
218
- }
219
- async saveCookiesForActivePage() {
220
- return this.cookiesManager.saveCookiesForActivePage();
221
- }
222
- async getCookies() {
223
- return this.cookiesManager.getCookies();
224
- }
225
- async saveCookiesToFile(filePath) {
226
- return this.cookiesManager.saveCookiesToFile(filePath);
227
- }
228
- async saveCookiesIfStable(filePath, opts = {}) {
229
- return this.cookiesManager.saveCookiesIfStable(filePath, opts);
230
- }
231
- async injectCookiesFromFile(filePath) {
232
- return this.cookiesManager.injectCookiesFromFile(filePath);
233
- }
234
- async goto(url) {
235
- return this.navigation.goto(url);
236
- }
237
- async screenshot(fullPage = true) {
238
- const page = await this.ensurePrimaryPage();
239
- return page.screenshot({ fullPage });
240
- }
241
- /**
242
- * 基于屏幕坐标的系统级鼠标点击(Playwright 原生)
243
- * @param opts 屏幕坐标及点击选项
244
- */
245
- async mouseClick(opts) {
246
- return this.inputOps.mouseClick(opts);
247
- }
248
- /**
249
- * 基于屏幕坐标的鼠标移动(Playwright 原生)
250
- * @param opts 目标坐标及移动选项
251
- */
252
- async mouseMove(opts) {
253
- return this.inputOps.mouseMove(opts);
254
- }
255
- /**
256
- * 基于键盘的系统输入(Playwright keyboard)
257
- */
258
- async keyboardType(opts) {
259
- return this.inputOps.keyboardType(opts);
260
- }
261
- async keyboardPress(opts) {
262
- return this.inputOps.keyboardPress(opts);
263
- }
264
- /**
265
- * 基于鼠标滚轮的系统滚动(Playwright mouse.wheel)
266
- * @param opts deltaY 为垂直滚动(正=向下,负=向上),deltaX 可选
267
- */
268
- async mouseWheel(opts) {
269
- return this.inputOps.mouseWheel(opts);
270
- }
271
- async setViewportSize(opts) {
272
- const page = await this.ensurePrimaryPage();
273
- return this.viewportManager.setViewportSize(page, opts);
274
- }
275
- async evaluate(expression, arg) {
276
- const page = await this.ensurePrimaryPage();
277
- if (typeof arg === 'undefined') {
278
- return page.evaluate(expression);
279
- }
280
- return page.evaluate(expression, arg);
281
- }
282
- getCurrentUrl() {
283
- return this.navigation.getCurrentUrl();
284
- }
285
- async close() {
286
- try {
287
- await this.stopRecording({ reason: 'session_close' }).catch(() => { });
288
- await this.context?.close();
289
- }
290
- finally {
291
- await this.browser?.close();
292
- this.lock.release();
293
- this.runtimeEvents.clearObservers();
294
- this.notifyExit();
295
- }
296
- }
297
- notifyExit() {
298
- if (this.exitNotified)
299
- return;
300
- this.exitNotified = true;
301
- this.onExit?.(this.options.profileId);
302
- }
303
- }
304
- //# sourceMappingURL=BrowserSession.js.map
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { ProfileLock } from './ProfileLock.js';
4
+ import { BrowserSessionRecording } from './browser-session/recording.js';
5
+ import { createPageHooksManager } from './browser-session/page-hooks.js';
6
+ import { BrowserSessionCookies } from './browser-session/cookies.js';
7
+ import { BrowserSessionPageManagement } from './browser-session/page-management.js';
8
+ import { BrowserInputPipeline } from './browser-session/input-pipeline.js';
9
+ import { BrowserSessionInputOps } from './browser-session/input-ops.js';
10
+ import { createRuntimeEventManager } from './browser-session/runtime-events.js';
11
+ import { BrowserSessionNavigation } from './browser-session/navigation.js';
12
+ import { BrowserSessionViewportManager } from './browser-session/viewport-manager.js';
13
+ import { logDebug } from './logging.js';
14
+ import { loadOrGenerateFingerprint, applyFingerprint } from './fingerprint.js';
15
+ import { launchEngineContext } from './engine-manager.js';
16
+ import { resolveProfilesRoot } from './storage-paths.js';
17
+ export class BrowserSession {
18
+ options;
19
+ browser;
20
+ context;
21
+ page;
22
+ lock;
23
+ profileDir;
24
+ lastKnownUrl = null;
25
+ mode = 'dev';
26
+ cookiesManager;
27
+ runtimeEvents;
28
+ pageHooks;
29
+ viewportManager;
30
+ pageManager;
31
+ navigation;
32
+ inputPipeline;
33
+ inputOps;
34
+ fingerprint = null;
35
+ recordingManager;
36
+ onExit;
37
+ exitNotified = false;
38
+ constructor(options) {
39
+ this.options = options;
40
+ const profileId = options.profileId || 'default';
41
+ const root = resolveProfilesRoot();
42
+ this.profileDir = path.join(root, profileId);
43
+ fs.mkdirSync(this.profileDir, { recursive: true });
44
+ this.lock = new ProfileLock(profileId);
45
+ this.recordingManager = new BrowserSessionRecording(profileId, () => this.getCurrentUrl(), () => this.context);
46
+ this.cookiesManager = new BrowserSessionCookies(profileId, () => this.context, () => this.getActivePage());
47
+ this.inputPipeline = new BrowserInputPipeline(() => this.ensurePrimaryPage(), () => this.options.headless === true);
48
+ this.inputOps = new BrowserSessionInputOps(() => this.ensurePrimaryPage(), (page) => this.inputPipeline.ensureInputReady(page), (page, label, run) => this.inputPipeline.runInputAction(page, label, run), (run) => this.inputPipeline.withInputActionLock(run));
49
+ this.viewportManager = new BrowserSessionViewportManager(profileId, () => this.context, () => String(this.options.engine ?? 'camoufox'), () => this.options.headless === true);
50
+ this.runtimeEvents = createRuntimeEventManager(profileId);
51
+ this.pageHooks = createPageHooksManager({
52
+ profileId,
53
+ getRecording: () => this.recordingManager.getRecordingStatus(),
54
+ emitRuntimeEvent: (event) => this.runtimeEvents.emit(event),
55
+ recordPageVisit: (page, reason) => {
56
+ this.lastKnownUrl = page?.url?.() || this.lastKnownUrl;
57
+ this.recordingManager.recordPageVisit(page, reason);
58
+ },
59
+ handleRecorderEvent: (page, evt) => this.recordingManager.handleRecorderEvent(page, evt),
60
+ });
61
+ this.pageManager = new BrowserSessionPageManagement({
62
+ ensureContext: () => this.ensureContext(),
63
+ getActivePage: () => this.getActivePage(),
64
+ getCurrentUrl: () => this.getCurrentUrl(),
65
+ setActivePage: (page) => { this.page = page; },
66
+ setupPageHooks: (page) => this.setupPageHooks(page),
67
+ ensurePageViewport: (page) => this.ensurePageViewport(page),
68
+ maybeCenterPage: (page, viewport) => this.viewportManager.maybeCenter(page, viewport),
69
+ recordLastKnownUrl: (url) => { if (url)
70
+ this.lastKnownUrl = url; },
71
+ isHeadless: () => this.options.headless === true,
72
+ });
73
+ this.navigation = new BrowserSessionNavigation({
74
+ ensurePrimaryPage: () => this.pageManager.ensurePrimaryPage(),
75
+ getActivePage: () => this.getActivePage(),
76
+ recordLastKnownUrl: (url) => { if (url)
77
+ this.lastKnownUrl = url; },
78
+ getLastKnownUrl: () => this.lastKnownUrl,
79
+ });
80
+ }
81
+ get id() {
82
+ return this.options.profileId;
83
+ }
84
+ get currentPage() {
85
+ return this.page;
86
+ }
87
+ get modeName() {
88
+ return this.mode;
89
+ }
90
+ setMode(next = 'dev') {
91
+ this.mode = next === 'run' ? 'run' : 'dev';
92
+ }
93
+ getInfo() {
94
+ return {
95
+ session_id: this.options.profileId,
96
+ profileId: this.options.profileId,
97
+ current_url: this.getCurrentUrl(),
98
+ mode: this.mode,
99
+ headless: !!this.options.headless,
100
+ recording: this.recordingManager.getRecordingStatus(),
101
+ };
102
+ }
103
+ async start(initialUrl) {
104
+ if (!this.lock.acquire()) {
105
+ throw new Error(`无法获取 profile ${this.options.profileId} 的锁`);
106
+ }
107
+ this.cleanupProfileLocks();
108
+ const engine = 'camoufox';
109
+ // 加载或生成指纹(支持 Win/Mac 随机�?
110
+ const fingerprint = await loadOrGenerateFingerprint(this.options.profileId, {
111
+ platform: this.options.fingerprintPlatform || null,
112
+ });
113
+ this.fingerprint = fingerprint;
114
+ logDebug('browser-service', 'session:fingerprint', {
115
+ profileId: this.options.profileId,
116
+ platform: fingerprint.platform,
117
+ userAgent: fingerprint.userAgent?.substring(0, 50) + '...',
118
+ });
119
+ const fallbackViewport = { width: 1440, height: 1100 };
120
+ const explicitViewport = this.options.viewport
121
+ && Number(this.options.viewport.width) > 0
122
+ && Number(this.options.viewport.height) > 0
123
+ ? {
124
+ width: Math.floor(Number(this.options.viewport.width)),
125
+ height: Math.floor(Number(this.options.viewport.height)),
126
+ }
127
+ : null;
128
+ const viewport = explicitViewport || fingerprint?.viewport || fallbackViewport;
129
+ const headless = !!this.options.headless;
130
+ const followWindowViewport = !headless;
131
+ // 使用 EngineManager 启动上下文(Chromium 已移除,仅支�?Camoufox�?
132
+ this.context = await launchEngineContext({
133
+ engine,
134
+ headless,
135
+ profileDir: this.profileDir,
136
+ viewport,
137
+ userAgent: fingerprint?.userAgent,
138
+ locale: 'zh-CN',
139
+ timezoneId: fingerprint?.timezoneId || 'Asia/Shanghai',
140
+ });
141
+ // 应用指纹到上下文(Playwright JS 注入�?
142
+ await applyFingerprint(this.context, fingerprint);
143
+ // NOTE: deviceScaleFactor override was Chromium-only (CDP). Chromium removed.
144
+ this.viewportManager.setInitialViewport(viewport, followWindowViewport);
145
+ this.browser = this.context.browser();
146
+ this.browser.on('disconnected', () => this.notifyExit());
147
+ this.context.on('close', () => this.notifyExit());
148
+ const existing = this.context.pages();
149
+ this.page = existing.length ? existing[0] : await this.context.newPage();
150
+ this.setupPageHooks(this.page);
151
+ this.context.on('page', (p) => this.setupPageHooks(p));
152
+ if (this.viewportManager.isFollowingWindow()) {
153
+ await this.viewportManager.refreshFromWindow(this.page).catch(() => { });
154
+ }
155
+ if (initialUrl) {
156
+ await this.goto(initialUrl);
157
+ }
158
+ }
159
+ setupPageHooks(page) {
160
+ this.pageHooks.setupPageHooks(page);
161
+ }
162
+ addRuntimeEventObserver(observer) {
163
+ return this.runtimeEvents.addObserver(observer);
164
+ }
165
+ getRecordingStatus() {
166
+ return this.recordingManager.getRecordingStatus();
167
+ }
168
+ cleanupProfileLocks() {
169
+ const targets = ['parent.lock', '.startup-incomplete'];
170
+ for (const name of targets) {
171
+ const target = path.join(this.profileDir, name);
172
+ if (!fs.existsSync(target))
173
+ continue;
174
+ try {
175
+ fs.rmSync(target, { force: true });
176
+ logDebug('browser-service', 'profile:cleanup-lock', { profileId: this.options.profileId, file: name });
177
+ }
178
+ catch (error) {
179
+ logDebug('browser-service', 'profile:cleanup-lock-failed', {
180
+ profileId: this.options.profileId,
181
+ file: name,
182
+ error: error?.message || String(error),
183
+ });
184
+ }
185
+ }
186
+ }
187
+ async startRecording(options = {}) {
188
+ this.recordingManager.setBindRecorderBridge((page) => this.pageHooks.bindRecorderBridge(page));
189
+ this.recordingManager.setInstallRecorderRuntime((page, reason) => this.pageHooks.installRecorderRuntime(page, reason));
190
+ return this.recordingManager.startRecording(options);
191
+ }
192
+ async stopRecording(options = {}) {
193
+ return this.recordingManager.stopRecording(options);
194
+ }
195
+ getActivePage() {
196
+ if (this.page && !this.page.isClosed()) {
197
+ return this.page;
198
+ }
199
+ if (!this.context)
200
+ return null;
201
+ const alive = this.context.pages().find((p) => !p.isClosed());
202
+ if (alive) {
203
+ this.page = alive;
204
+ return alive;
205
+ }
206
+ this.page = undefined;
207
+ return null;
208
+ }
209
+ async ensurePageViewport(page) {
210
+ await this.viewportManager.ensurePageViewport(page);
211
+ }
212
+ ensureContext() {
213
+ if (!this.context) {
214
+ throw new Error('browser context not ready');
215
+ }
216
+ return this.context;
217
+ }
218
+ async ensurePrimaryPage() {
219
+ return this.pageManager.ensurePrimaryPage();
220
+ }
221
+ async ensurePage(url) {
222
+ return this.pageManager.ensurePage(url);
223
+ }
224
+ async goBack() {
225
+ return this.navigation.goBack();
226
+ }
227
+ listPages() {
228
+ return this.pageManager.listPages();
229
+ }
230
+ async newPage(url, options = {}) {
231
+ return this.pageManager.newPage(url, options);
232
+ }
233
+ async switchPage(index) {
234
+ return this.pageManager.switchPage(index);
235
+ }
236
+ async closePage(index) {
237
+ return this.pageManager.closePage(index);
238
+ }
239
+ async saveCookiesForActivePage() {
240
+ return this.cookiesManager.saveCookiesForActivePage();
241
+ }
242
+ async getCookies() {
243
+ return this.cookiesManager.getCookies();
244
+ }
245
+ async saveCookiesToFile(filePath) {
246
+ return this.cookiesManager.saveCookiesToFile(filePath);
247
+ }
248
+ async saveCookiesIfStable(filePath, opts = {}) {
249
+ return this.cookiesManager.saveCookiesIfStable(filePath, opts);
250
+ }
251
+ async injectCookiesFromFile(filePath) {
252
+ return this.cookiesManager.injectCookiesFromFile(filePath);
253
+ }
254
+ async goto(url) {
255
+ return this.navigation.goto(url);
256
+ }
257
+ async screenshot(fullPage = true) {
258
+ const page = await this.ensurePrimaryPage();
259
+ return page.screenshot({ fullPage });
260
+ }
261
+ /**
262
+ * 基于屏幕坐标的系统级鼠标点击(Playwright 原生�?
263
+ * @param opts 屏幕坐标及点击选项
264
+ */
265
+ async mouseClick(opts) {
266
+ return this.inputOps.mouseClick(opts);
267
+ }
268
+ /**
269
+ * 基于屏幕坐标的鼠标移动(Playwright 原生�?
270
+ * @param opts 目标坐标及移动选项
271
+ */
272
+ async mouseMove(opts) {
273
+ return this.inputOps.mouseMove(opts);
274
+ }
275
+ /**
276
+ * 基于键盘的系统输入(Playwright keyboard�?
277
+ */
278
+ async keyboardType(opts) {
279
+ return this.inputOps.keyboardType(opts);
280
+ }
281
+ async keyboardPress(opts) {
282
+ return this.inputOps.keyboardPress(opts);
283
+ }
284
+ /**
285
+ * 基于鼠标滚轮的系统滚动(Playwright mouse.wheel�?
286
+ * @param opts deltaY 为垂直滚动(�?向下,负=向上),deltaX 可�?
287
+ */
288
+ async mouseWheel(opts) {
289
+ return this.inputOps.mouseWheel(opts);
290
+ }
291
+ async setViewportSize(opts) {
292
+ const page = await this.ensurePrimaryPage();
293
+ return this.viewportManager.setViewportSize(page, opts);
294
+ }
295
+ async evaluate(expression, arg) {
296
+ const page = await this.ensurePrimaryPage();
297
+ if (typeof arg === 'undefined') {
298
+ return page.evaluate(expression);
299
+ }
300
+ return page.evaluate(expression, arg);
301
+ }
302
+ getCurrentUrl() {
303
+ return this.navigation.getCurrentUrl();
304
+ }
305
+ async close() {
306
+ try {
307
+ await this.stopRecording({ reason: 'session_close' }).catch(() => { });
308
+ await this.context?.close();
309
+ }
310
+ finally {
311
+ await this.browser?.close();
312
+ this.lock.release();
313
+ this.runtimeEvents.clearObservers();
314
+ this.notifyExit();
315
+ }
316
+ }
317
+ notifyExit() {
318
+ if (this.exitNotified)
319
+ return;
320
+ this.exitNotified = true;
321
+ this.onExit?.(this.options.profileId);
322
+ }
323
+ }
324
+ //# sourceMappingURL=BrowserSession.js.map
325
+