@web-auto/camo 0.1.18 → 0.1.20

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 (91) hide show
  1. package/README.md +18 -19
  2. package/bin/browser-service.mjs +11 -0
  3. package/package.json +7 -2
  4. package/scripts/install.mjs +3 -3
  5. package/src/cli.mjs +8 -5
  6. package/src/commands/attach.mjs +141 -0
  7. package/src/commands/browser.mjs +5 -16
  8. package/src/commands/mouse.mjs +2 -12
  9. package/src/container/runtime-core/operations/index.mjs +6 -15
  10. package/src/container/subscription-registry.mjs +6 -6
  11. package/src/core/actions.mjs +0 -12
  12. package/src/core/index.mjs +0 -1
  13. package/src/lifecycle/lock.mjs +7 -3
  14. package/src/services/browser-service/index.js +651 -0
  15. package/src/services/browser-service/index.js.map +1 -0
  16. package/src/services/browser-service/internal/BrowserSession.input.test.js +322 -0
  17. package/src/services/browser-service/internal/BrowserSession.input.test.js.map +1 -0
  18. package/src/services/browser-service/internal/BrowserSession.js +304 -0
  19. package/src/services/browser-service/internal/BrowserSession.js.map +1 -0
  20. package/src/services/browser-service/internal/ElementRegistry.js +61 -0
  21. package/src/services/browser-service/internal/ElementRegistry.js.map +1 -0
  22. package/src/services/browser-service/internal/ProfileLock.js +85 -0
  23. package/src/services/browser-service/internal/ProfileLock.js.map +1 -0
  24. package/src/services/browser-service/internal/SessionManager.js +184 -0
  25. package/src/services/browser-service/internal/SessionManager.js.map +1 -0
  26. package/src/services/browser-service/internal/SessionManager.test.js +40 -0
  27. package/src/services/browser-service/internal/SessionManager.test.js.map +1 -0
  28. package/src/services/browser-service/internal/browser-session/cookies.js +145 -0
  29. package/src/services/browser-service/internal/browser-session/cookies.js.map +1 -0
  30. package/src/services/browser-service/internal/browser-session/input-ops.js +127 -0
  31. package/src/services/browser-service/internal/browser-session/input-ops.js.map +1 -0
  32. package/src/services/browser-service/internal/browser-session/input-pipeline.js +133 -0
  33. package/src/services/browser-service/internal/browser-session/input-pipeline.js.map +1 -0
  34. package/src/services/browser-service/internal/browser-session/logging.js +46 -0
  35. package/src/services/browser-service/internal/browser-session/navigation.js +39 -0
  36. package/src/services/browser-service/internal/browser-session/navigation.js.map +1 -0
  37. package/src/services/browser-service/internal/browser-session/page-hooks.js +443 -0
  38. package/src/services/browser-service/internal/browser-session/page-hooks.js.map +1 -0
  39. package/src/services/browser-service/internal/browser-session/page-management.js +212 -0
  40. package/src/services/browser-service/internal/browser-session/page-management.js.map +1 -0
  41. package/src/services/browser-service/internal/browser-session/recording.js +199 -0
  42. package/src/services/browser-service/internal/browser-session/recording.js.map +1 -0
  43. package/src/services/browser-service/internal/browser-session/runtime-events.js +62 -0
  44. package/src/services/browser-service/internal/browser-session/runtime-events.js.map +1 -0
  45. package/src/services/browser-service/internal/browser-session/session-core.js +85 -0
  46. package/src/services/browser-service/internal/browser-session/session-core.js.map +1 -0
  47. package/src/services/browser-service/internal/browser-session/session-state.js +39 -0
  48. package/src/services/browser-service/internal/browser-session/session-state.js.map +1 -0
  49. package/src/services/browser-service/internal/browser-session/types.js +15 -0
  50. package/src/services/browser-service/internal/browser-session/types.js.map +1 -0
  51. package/src/services/browser-service/internal/browser-session/utils.js +69 -0
  52. package/src/services/browser-service/internal/browser-session/utils.js.map +1 -0
  53. package/src/services/browser-service/internal/browser-session/viewport-manager.js +47 -0
  54. package/src/services/browser-service/internal/browser-session/viewport-manager.js.map +1 -0
  55. package/src/services/browser-service/internal/browser-session/viewport.js +216 -0
  56. package/src/services/browser-service/internal/browser-session/viewport.js.map +1 -0
  57. package/src/services/browser-service/internal/container-matcher.js +852 -0
  58. package/src/services/browser-service/internal/container-matcher.js.map +1 -0
  59. package/src/services/browser-service/internal/container-registry.js +182 -0
  60. package/src/services/browser-service/internal/engine-manager.js +259 -0
  61. package/src/services/browser-service/internal/engine-manager.js.map +1 -0
  62. package/src/services/browser-service/internal/fingerprint.js +203 -0
  63. package/src/services/browser-service/internal/fingerprint.js.map +1 -0
  64. package/src/services/browser-service/internal/heartbeat.js +137 -0
  65. package/src/services/browser-service/internal/logging.js +46 -0
  66. package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -0
  67. package/src/services/browser-service/internal/pageRuntime.js +29 -0
  68. package/src/services/browser-service/internal/pageRuntime.js.map +1 -0
  69. package/src/services/browser-service/internal/runtimeInjector.js +31 -0
  70. package/src/services/browser-service/internal/runtimeInjector.js.map +1 -0
  71. package/src/services/browser-service/internal/service-process-logger.js +140 -0
  72. package/src/services/browser-service/internal/state-bus.js +46 -0
  73. package/src/services/browser-service/internal/state-bus.js.map +1 -0
  74. package/src/services/browser-service/internal/storage-paths.js +42 -0
  75. package/src/services/browser-service/internal/storage-paths.js.map +1 -0
  76. package/src/services/browser-service/internal/ws-server.js +1194 -0
  77. package/src/services/browser-service/internal/ws-server.js.map +1 -0
  78. package/src/services/browser-service/internal/ws-server.test.js +59 -0
  79. package/src/services/browser-service/internal/ws-server.test.js.map +1 -0
  80. package/src/services/browser-service/server.mjs +6 -0
  81. package/src/services/controller/cli-bridge.js +93 -0
  82. package/src/services/controller/container-index.js +50 -0
  83. package/src/services/controller/container-storage.js +36 -0
  84. package/src/services/controller/controller-actions.js +207 -0
  85. package/src/services/controller/controller.js +1138 -0
  86. package/src/services/controller/selectors.js +54 -0
  87. package/src/services/controller/transport.js +118 -0
  88. package/src/utils/browser-service.mjs +100 -125
  89. package/src/utils/config.mjs +22 -21
  90. package/src/utils/help.mjs +11 -9
  91. package/src/utils/ws-client.mjs +30 -0
@@ -0,0 +1,304 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BrowserSession.js","sourceRoot":"","sources":["../../../../../modules/camo-backend/src/internal/BrowserSession.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * 元素注册表
3
+ * 管理浏览器端 ElementHandle 的生命周期和映射
4
+ */
5
+ export class ElementRegistry {
6
+ elements = new Map();
7
+ pageMap = new Map(); // elementId -> pageId (for cleanup)
8
+ /**
9
+ * 注册元素并返回唯一 ID
10
+ */
11
+ register(element, pageId) {
12
+ const id = `el_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
13
+ this.elements.set(id, element);
14
+ if (pageId) {
15
+ this.pageMap.set(id, pageId);
16
+ }
17
+ return id;
18
+ }
19
+ /**
20
+ * 获取元素
21
+ */
22
+ get(id) {
23
+ return this.elements.get(id);
24
+ }
25
+ /**
26
+ * 释放元素
27
+ */
28
+ async release(id) {
29
+ const element = this.elements.get(id);
30
+ if (element) {
31
+ try {
32
+ await element.dispose();
33
+ }
34
+ catch (e) {
35
+ // Ignore disposal errors
36
+ }
37
+ this.elements.delete(id);
38
+ this.pageMap.delete(id);
39
+ }
40
+ }
41
+ /**
42
+ * 清理特定页面的所有元素
43
+ */
44
+ async clearPage(pageId) {
45
+ const toRemove = [];
46
+ for (const [id, pid] of this.pageMap.entries()) {
47
+ if (pid === pageId) {
48
+ toRemove.push(id);
49
+ }
50
+ }
51
+ await Promise.all(toRemove.map(id => this.release(id)));
52
+ }
53
+ /**
54
+ * 清理所有元素
55
+ */
56
+ async clearAll() {
57
+ const ids = Array.from(this.elements.keys());
58
+ await Promise.all(ids.map(id => this.release(id)));
59
+ }
60
+ }
61
+ //# sourceMappingURL=ElementRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ElementRegistry.js","sourceRoot":"","sources":["../../../../../modules/camo-backend/src/internal/ElementRegistry.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,eAAe;IAClB,QAAQ,GAA+B,IAAI,GAAG,EAAE,CAAC;IACjD,OAAO,GAAwB,IAAI,GAAG,EAAE,CAAC,CAAC,oCAAoC;IAEtF;;OAEG;IACI,QAAQ,CAAC,OAAsB,EAAE,MAAe;QACrD,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACzE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/B,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;OAEG;IACI,GAAG,CAAC,EAAU;QACnB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,OAAO,CAAC,EAAU;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,OAAO,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YAC1B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,yBAAyB;YAC3B,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACzB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,SAAS,CAAC,MAAc;QACnC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YAC/C,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpB,CAAC;QACH,CAAC;QACD,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED;;OAEG;IACI,KAAK,CAAC,QAAQ;QACnB,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;CACF"}
@@ -0,0 +1,85 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { resolveLocksRoot } from './storage-paths.js';
5
+ export class ProfileLock {
6
+ profileId;
7
+ lockDir;
8
+ lockFile;
9
+ constructor(profileId, lockRoot = resolveLocksRoot()) {
10
+ this.profileId = profileId;
11
+ this.lockDir = lockRoot;
12
+ fs.mkdirSync(this.lockDir, { recursive: true });
13
+ this.lockFile = path.join(this.lockDir, `${this.profileId}.lock`);
14
+ }
15
+ isProcessRunning(pid) {
16
+ try {
17
+ process.kill(pid, 0);
18
+ return true;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ killProcess(pid) {
25
+ try {
26
+ process.kill(pid, 'SIGTERM');
27
+ }
28
+ catch { }
29
+ const start = Date.now();
30
+ while (Date.now() - start < 5000) {
31
+ if (!this.isProcessRunning(pid))
32
+ return;
33
+ }
34
+ try {
35
+ process.kill(pid, 'SIGKILL');
36
+ }
37
+ catch { }
38
+ }
39
+ acquire() {
40
+ if (fs.existsSync(this.lockFile)) {
41
+ try {
42
+ const raw = JSON.parse(fs.readFileSync(this.lockFile, 'utf-8'));
43
+ const pid = Number(raw?.pid);
44
+ if (pid && pid !== process.pid && this.isProcessRunning(pid)) {
45
+ this.killProcess(pid);
46
+ }
47
+ }
48
+ catch {
49
+ // ignore corrupted lock
50
+ }
51
+ try {
52
+ fs.unlinkSync(this.lockFile);
53
+ }
54
+ catch { }
55
+ }
56
+ try {
57
+ const payload = JSON.stringify({
58
+ pid: process.pid,
59
+ profileId: this.profileId,
60
+ createdAt: Date.now(),
61
+ host: os.hostname(),
62
+ }, null, 2);
63
+ fs.writeFileSync(this.lockFile, payload, { encoding: 'utf-8' });
64
+ return true;
65
+ }
66
+ catch (err) {
67
+ console.error(`[ProfileLock] failed to acquire lock for ${this.profileId}:`, err);
68
+ return false;
69
+ }
70
+ }
71
+ release() {
72
+ try {
73
+ if (!fs.existsSync(this.lockFile))
74
+ return;
75
+ const raw = JSON.parse(fs.readFileSync(this.lockFile, 'utf-8'));
76
+ if (Number(raw?.pid) !== process.pid)
77
+ return;
78
+ fs.unlinkSync(this.lockFile);
79
+ }
80
+ catch (err) {
81
+ console.warn(`[ProfileLock] release failed for ${this.profileId}:`, err);
82
+ }
83
+ }
84
+ }
85
+ //# sourceMappingURL=ProfileLock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProfileLock.js","sourceRoot":"","sources":["../../../../../modules/camo-backend/src/internal/ProfileLock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,MAAM,OAAO,WAAW;IAIF;IAHZ,OAAO,CAAS;IAChB,QAAQ,CAAS;IAEzB,YAAoB,SAAiB,EAAE,QAAQ,GAAG,gBAAgB,EAAE;QAAhD,cAAS,GAAT,SAAS,CAAQ;QACnC,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,SAAS,OAAO,CAAC,CAAC;IACpE,CAAC;IAEO,gBAAgB,CAAC,GAAW;QAClC,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAEO,WAAW,CAAC,GAAW;QAC7B,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACV,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,IAAI,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC;gBAAE,OAAO;QAC1C,CAAC;QACD,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IAED,OAAO;QACL,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;gBAChE,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBAC7B,IAAI,GAAG,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC7D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;gBACxB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,wBAAwB;YAC1B,CAAC;YACD,IAAI,CAAC;gBACH,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACZ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC;gBAC7B,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,IAAI,EAAE,EAAE,CAAC,QAAQ,EAAE;aACpB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACZ,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,4CAA4C,IAAI,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;YAClF,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAAE,OAAO;YAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;YAChE,IAAI,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,OAAO,CAAC,GAAG;gBAAE,OAAO;YAC7C,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,oCAAoC,IAAI,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,184 @@
1
+ import { BrowserSession } from './BrowserSession.js';
2
+ export const SESSION_CLOSED_EVENT = 'browser-service:session-closed';
3
+ function isProcessAlive(pid) {
4
+ try {
5
+ process.kill(pid, 0);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ export class SessionManager {
13
+ sessions = new Map();
14
+ // Track owning process (e.g. script pid) and bind browser lifetime to it.
15
+ owners = new Map();
16
+ sessionFactory;
17
+ ownerWatchdog;
18
+ ownerWatchdogBusy = false;
19
+ debugLog(label, data) {
20
+ if (process.env.DEBUG !== '1' && process.env.CAMO_DEBUG !== '1' && process.env.CAMO_DEBUG !== '1')
21
+ return;
22
+ try {
23
+ console.log(`[browser-service:${label}] ${JSON.stringify(data)}`);
24
+ }
25
+ catch {
26
+ console.log(`[browser-service:${label}]`, data);
27
+ }
28
+ }
29
+ // Optional options are currently not used, but allowed for future extensions
30
+ constructor(_options, sessionFactory) {
31
+ this.sessionFactory = sessionFactory || ((opts) => new BrowserSession(opts));
32
+ const watchdogMs = Math.max(1000, Number(_options?.ownerWatchdogMs || 5000));
33
+ this.ownerWatchdog = setInterval(() => {
34
+ void this.reapDeadOwners();
35
+ }, watchdogMs);
36
+ this.ownerWatchdog.unref?.();
37
+ }
38
+ normalizeOwnerPid(value) {
39
+ const pid = Number(value || 0);
40
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
41
+ }
42
+ bindOwner(profileId, pid) {
43
+ if (!pid)
44
+ return;
45
+ this.owners.set(profileId, { pid, startedAt: new Date().toISOString() });
46
+ }
47
+ async reapDeadOwners() {
48
+ if (this.ownerWatchdogBusy)
49
+ return;
50
+ this.ownerWatchdogBusy = true;
51
+ try {
52
+ for (const [profileId, owner] of this.owners.entries()) {
53
+ if (!owner?.pid)
54
+ continue;
55
+ if (isProcessAlive(owner.pid))
56
+ continue;
57
+ this.debugLog('owner:dead_cleanup', { profileId, deadPid: owner.pid });
58
+ await this.deleteSession(profileId).catch((err) => {
59
+ this.debugLog('owner:dead_cleanup_error', {
60
+ profileId,
61
+ deadPid: owner.pid,
62
+ error: err?.message || err,
63
+ });
64
+ });
65
+ }
66
+ }
67
+ finally {
68
+ this.ownerWatchdogBusy = false;
69
+ }
70
+ }
71
+ async createSession(options) {
72
+ const profileId = options.profileId || options.sessionId || `session_${Date.now().toString(36)}`;
73
+ options.profileId = profileId;
74
+ if (!options.sessionName) {
75
+ options.sessionName = profileId;
76
+ }
77
+ this.debugLog('createSession:start', {
78
+ profileId,
79
+ headless: options.headless,
80
+ hasInitialUrl: Boolean(options.initialUrl),
81
+ ownerPid: Number(options.ownerPid || 0) || null,
82
+ });
83
+ const existing = this.sessions.get(profileId);
84
+ const ownerPid = this.normalizeOwnerPid(options.ownerPid);
85
+ if (existing) {
86
+ const owner = this.owners.get(profileId);
87
+ if (owner?.pid && !isProcessAlive(owner.pid)) {
88
+ // Owner died; treat existing session as stale and replace it.
89
+ this.debugLog('createSession:replace_stale_owner', { profileId, deadPid: owner.pid });
90
+ await this.deleteSession(profileId);
91
+ }
92
+ else {
93
+ if (ownerPid && owner?.pid && owner.pid !== ownerPid && isProcessAlive(owner.pid)) {
94
+ throw new Error(`session_owned_by_another_process profile=${profileId} ownerPid=${owner.pid} requesterPid=${ownerPid}`);
95
+ }
96
+ this.bindOwner(profileId, ownerPid);
97
+ this.debugLog('createSession:reuse', {
98
+ profileId,
99
+ ownerPid: this.owners.get(profileId)?.pid || null,
100
+ requesterPid: ownerPid,
101
+ });
102
+ // Reuse existing session (keepalive). Do not restart the browser if it's already running.
103
+ return { sessionId: profileId };
104
+ }
105
+ }
106
+ const session = this.sessionFactory(options);
107
+ session.onExit = (id) => {
108
+ const current = this.sessions.get(id);
109
+ if (current === session) {
110
+ this.sessions.delete(id);
111
+ // If the user closed the browser window, also terminate the owning script (if any).
112
+ const owner = this.owners.get(id);
113
+ if (owner?.pid) {
114
+ try {
115
+ process.kill(owner.pid, 'SIGTERM');
116
+ }
117
+ catch {
118
+ // ignore
119
+ }
120
+ }
121
+ this.owners.delete(id);
122
+ process.emit(SESSION_CLOSED_EVENT, id);
123
+ }
124
+ };
125
+ try {
126
+ await session.start(options.initialUrl);
127
+ }
128
+ catch (err) {
129
+ this.debugLog('createSession:start_failed', {
130
+ profileId,
131
+ error: err?.message || String(err),
132
+ });
133
+ session.onExit = undefined;
134
+ await session.close().catch(() => { });
135
+ throw err;
136
+ }
137
+ this.sessions.set(profileId, session);
138
+ this.debugLog('createSession:started', { profileId });
139
+ this.bindOwner(profileId, ownerPid);
140
+ return { sessionId: profileId };
141
+ }
142
+ getSession(profileId) {
143
+ this.debugLog('getSession', { profileId, hit: this.sessions.has(profileId) });
144
+ return this.sessions.get(profileId);
145
+ }
146
+ listSessions() {
147
+ return Array.from(this.sessions.values()).map((session) => ({
148
+ profileId: session.id,
149
+ session_id: session.id,
150
+ current_url: session.getCurrentUrl(),
151
+ mode: session.modeName,
152
+ owner_pid: this.owners.get(session.id)?.pid || null,
153
+ recording: session.getRecordingStatus(),
154
+ }));
155
+ }
156
+ async getSessionInfo(profileId) {
157
+ const session = this.getSession(profileId);
158
+ if (!session)
159
+ return null;
160
+ return session.getInfo();
161
+ }
162
+ async deleteSession(profileId) {
163
+ const session = this.sessions.get(profileId);
164
+ if (!session)
165
+ return false;
166
+ this.debugLog('deleteSession:start', { profileId });
167
+ session.onExit = undefined;
168
+ await session.close();
169
+ this.sessions.delete(profileId);
170
+ // Deleting a session from API should NOT kill the owner (Stop=only kill script lives in UI).
171
+ this.owners.delete(profileId);
172
+ process.emit(SESSION_CLOSED_EVENT, profileId);
173
+ this.debugLog('deleteSession:done', { profileId });
174
+ return true;
175
+ }
176
+ async shutdown() {
177
+ clearInterval(this.ownerWatchdog);
178
+ const jobs = Array.from(this.sessions.values()).map((session) => session.close().catch(() => { }));
179
+ await Promise.all(jobs);
180
+ this.sessions.clear();
181
+ this.owners.clear();
182
+ }
183
+ }
184
+ //# sourceMappingURL=SessionManager.js.map