@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.
- package/LICENSE +21 -21
- package/README.md +586 -586
- package/bin/browser-service.mjs +11 -11
- package/bin/camo.mjs +22 -22
- package/package.json +48 -48
- package/scripts/build.mjs +19 -19
- package/scripts/bump-version.mjs +34 -34
- package/scripts/check-file-size.mjs +80 -80
- package/scripts/file-size-policy.json +12 -2
- package/scripts/install.mjs +76 -76
- package/scripts/release.sh +54 -54
- package/src/autoscript/action-providers/index.mjs +6 -6
- package/src/autoscript/impact-engine.mjs +78 -78
- package/src/autoscript/runtime.mjs +1017 -1017
- package/src/autoscript/schema.mjs +376 -376
- package/src/cli.mjs +405 -405
- package/src/commands/attach.mjs +141 -141
- package/src/commands/autoscript.mjs +1011 -1011
- package/src/commands/browser.mjs +1255 -1255
- package/src/commands/container.mjs +401 -401
- package/src/commands/cookies.mjs +69 -69
- package/src/commands/create.mjs +98 -98
- package/src/commands/devtools.mjs +349 -349
- package/src/commands/events.mjs +152 -152
- package/src/commands/highlight-mode.mjs +24 -24
- package/src/commands/init.mjs +68 -68
- package/src/commands/lifecycle.mjs +275 -275
- package/src/commands/mouse.mjs +45 -45
- package/src/commands/profile.mjs +46 -46
- package/src/commands/record.mjs +115 -115
- package/src/commands/system.mjs +14 -14
- package/src/commands/window.mjs +123 -123
- package/src/container/change-notifier.mjs +362 -362
- package/src/container/element-filter.mjs +143 -143
- package/src/container/index.mjs +3 -3
- package/src/container/runtime-core/checkpoint.mjs +209 -209
- package/src/container/runtime-core/index.mjs +21 -21
- package/src/container/runtime-core/operations/index.mjs +774 -774
- package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
- package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
- package/src/container/runtime-core/operations/viewport.mjs +189 -189
- package/src/container/runtime-core/search.mjs +190 -190
- package/src/container/runtime-core/subscription.mjs +224 -224
- package/src/container/runtime-core/utils.mjs +94 -94
- package/src/container/runtime-core/validation.mjs +127 -127
- package/src/container/runtime-core.mjs +1 -1
- package/src/container/subscription-registry.mjs +459 -459
- package/src/core/actions.mjs +561 -561
- package/src/core/browser.mjs +266 -266
- package/src/core/index.mjs +52 -52
- package/src/core/utils.mjs +91 -91
- package/src/events/daemon-entry.mjs +33 -33
- package/src/events/daemon.mjs +80 -80
- package/src/events/progress-log.mjs +109 -109
- package/src/events/ws-server.mjs +239 -239
- package/src/lib/client.mjs +200 -200
- package/src/lifecycle/cleanup.mjs +83 -83
- package/src/lifecycle/lock.mjs +126 -126
- package/src/lifecycle/session-registry.mjs +279 -279
- package/src/lifecycle/session-view.mjs +76 -76
- package/src/lifecycle/session-watchdog.mjs +281 -281
- package/src/services/browser-service/index.js +671 -671
- package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
- package/src/services/browser-service/internal/BrowserSession.js +325 -304
- package/src/services/browser-service/internal/ElementRegistry.js +60 -60
- package/src/services/browser-service/internal/ProfileLock.js +84 -84
- package/src/services/browser-service/internal/SessionManager.js +184 -184
- package/src/services/browser-service/internal/SessionManager.test.js +39 -39
- package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
- package/src/services/browser-service/internal/browser-session/input-ops.js +222 -222
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
- package/src/services/browser-service/internal/browser-session/logging.js +46 -46
- package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
- package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
- package/src/services/browser-service/internal/browser-session/page-management.js +302 -302
- package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
- package/src/services/browser-service/internal/browser-session/recording.js +198 -198
- package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
- package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
- package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
- package/src/services/browser-service/internal/browser-session/types.js +14 -14
- package/src/services/browser-service/internal/browser-session/utils.js +95 -95
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
- package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
- package/src/services/browser-service/internal/container-matcher.js +851 -851
- package/src/services/browser-service/internal/container-registry.js +182 -182
- package/src/services/browser-service/internal/engine-manager.js +259 -259
- package/src/services/browser-service/internal/fingerprint.js +203 -203
- package/src/services/browser-service/internal/heartbeat.js +137 -137
- package/src/services/browser-service/internal/logging.js +46 -46
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
- package/src/services/browser-service/internal/pageRuntime.js +28 -28
- package/src/services/browser-service/internal/runtimeInjector.js +31 -31
- package/src/services/browser-service/internal/service-process-logger.js +140 -140
- package/src/services/browser-service/internal/state-bus.js +45 -45
- package/src/services/browser-service/internal/storage-paths.js +42 -42
- package/src/services/browser-service/internal/ws-server.js +1194 -1194
- package/src/services/browser-service/internal/ws-server.test.js +58 -58
- package/src/services/browser-service/server.mjs +6 -6
- package/src/services/controller/cli-bridge.js +93 -93
- package/src/services/controller/container-index.js +50 -50
- package/src/services/controller/container-storage.js +36 -36
- package/src/services/controller/controller-actions.js +207 -207
- package/src/services/controller/controller.js +1138 -1138
- package/src/services/controller/selectors.js +54 -54
- package/src/services/controller/transport.js +125 -125
- package/src/utils/args.mjs +26 -26
- package/src/utils/browser-service.mjs +544 -544
- package/src/utils/command-log.mjs +64 -64
- package/src/utils/config.mjs +214 -214
- package/src/utils/fingerprint.mjs +181 -181
- package/src/utils/help.mjs +216 -216
- package/src/utils/js-policy.mjs +13 -13
- package/src/utils/ws-client.mjs +30 -30
package/src/lib/client.mjs
CHANGED
|
@@ -1,200 +1,200 @@
|
|
|
1
|
-
// Camo Container Client - High-level API for container subscription
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
checkBrowserService,
|
|
5
|
-
getDomSnapshotByProfile,
|
|
6
|
-
getSessionByProfile,
|
|
7
|
-
getViewportByProfile,
|
|
8
|
-
} from '../utils/browser-service.mjs';
|
|
9
|
-
import { getDefaultProfile } from '../utils/config.mjs';
|
|
10
|
-
import { getChangeNotifier } from '../container/change-notifier.mjs';
|
|
11
|
-
import { createElementFilter } from '../container/element-filter.mjs';
|
|
12
|
-
|
|
13
|
-
export class CamoContainerClient {
|
|
14
|
-
constructor(options = {}) {
|
|
15
|
-
this.profileId = options.profileId || getDefaultProfile();
|
|
16
|
-
this.serviceUrl = options.serviceUrl || 'http://127.0.0.1:7704';
|
|
17
|
-
this.notifier = getChangeNotifier();
|
|
18
|
-
this.filter = createElementFilter(options.filterOptions || {});
|
|
19
|
-
this.session = null;
|
|
20
|
-
this.pollInterval = null;
|
|
21
|
-
this.subscriptions = new Map(); // containerId -> callback
|
|
22
|
-
this.lastSnapshot = null;
|
|
23
|
-
this.viewport = { width: 1280, height: 720 };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async ensureSession() {
|
|
27
|
-
if (this.session) return this.session;
|
|
28
|
-
|
|
29
|
-
if (!await checkBrowserService()) {
|
|
30
|
-
throw new Error('Browser service not running. Run: camo init');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
this.session = await getSessionByProfile(this.profileId);
|
|
34
|
-
if (!this.session) {
|
|
35
|
-
throw new Error(`No active session for profile: ${this.profileId}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return this.session;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async getSnapshot() {
|
|
42
|
-
await this.ensureSession();
|
|
43
|
-
|
|
44
|
-
this.lastSnapshot = await getDomSnapshotByProfile(this.profileId);
|
|
45
|
-
return this.lastSnapshot;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async getViewport() {
|
|
49
|
-
await this.ensureSession();
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
this.viewport = await getViewportByProfile(this.profileId);
|
|
53
|
-
} catch {}
|
|
54
|
-
|
|
55
|
-
return this.viewport;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Subscribe to container changes
|
|
59
|
-
async subscribe(containers, options = {}) {
|
|
60
|
-
const { throttle = 500 } = options;
|
|
61
|
-
await this.ensureSession();
|
|
62
|
-
|
|
63
|
-
for (const container of containers) {
|
|
64
|
-
const { containerId, selector, onAppear, onDisappear, onChange } = container;
|
|
65
|
-
|
|
66
|
-
this.notifier.watch(
|
|
67
|
-
typeof selector === 'string' ? { css: selector } : selector,
|
|
68
|
-
{
|
|
69
|
-
onAppear: (elements) => {
|
|
70
|
-
this.subscriptions.get(containerId)?.onAppear?.(elements);
|
|
71
|
-
},
|
|
72
|
-
onDisappear: (elements) => {
|
|
73
|
-
this.subscriptions.get(containerId)?.onDisappear?.(elements);
|
|
74
|
-
},
|
|
75
|
-
onChange: (data) => {
|
|
76
|
-
this.subscriptions.get(containerId)?.onChange?.(data);
|
|
77
|
-
},
|
|
78
|
-
throttle,
|
|
79
|
-
}
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
this.subscriptions.set(containerId, {
|
|
83
|
-
selector,
|
|
84
|
-
onAppear,
|
|
85
|
-
onDisappear,
|
|
86
|
-
onChange,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Start polling
|
|
91
|
-
if (!this.pollInterval) {
|
|
92
|
-
this.pollInterval = setInterval(async () => {
|
|
93
|
-
try {
|
|
94
|
-
const snapshot = await this.getSnapshot();
|
|
95
|
-
this.notifier.processSnapshot(snapshot);
|
|
96
|
-
} catch (err) {
|
|
97
|
-
// Ignore errors during polling
|
|
98
|
-
}
|
|
99
|
-
}, throttle);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
unsubscribe: () => {
|
|
104
|
-
for (const container of containers) {
|
|
105
|
-
this.subscriptions.delete(container.containerId);
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Checkpoint detection helper
|
|
112
|
-
async detectCheckpoint(checkpointRules) {
|
|
113
|
-
const snapshot = await this.getSnapshot();
|
|
114
|
-
const viewport = await this.getViewport();
|
|
115
|
-
|
|
116
|
-
const matched = new Set();
|
|
117
|
-
|
|
118
|
-
for (const [checkpointId, rule] of Object.entries(checkpointRules)) {
|
|
119
|
-
const { selectors, requireAll = false } = rule;
|
|
120
|
-
let matchCount = 0;
|
|
121
|
-
|
|
122
|
-
for (const selector of selectors) {
|
|
123
|
-
const elements = this.notifier.findElements(snapshot, { css: selector });
|
|
124
|
-
const visible = elements.filter(e => {
|
|
125
|
-
const rect = e.rect || e.bbox;
|
|
126
|
-
if (!rect) return false;
|
|
127
|
-
return this.filter.isInViewport(rect, viewport);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
if (visible.length > 0) {
|
|
131
|
-
matchCount++;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (requireAll) {
|
|
136
|
-
if (matchCount === selectors.length) {
|
|
137
|
-
matched.add(checkpointId);
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
if (matchCount > 0) {
|
|
141
|
-
matched.add(checkpointId);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return Array.from(matched);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// List visible elements
|
|
150
|
-
async listVisibleElements(options = {}) {
|
|
151
|
-
const { minVisibility = 0.1, maxResults = 50 } = options;
|
|
152
|
-
const snapshot = await this.getSnapshot();
|
|
153
|
-
const viewport = await this.getViewport();
|
|
154
|
-
|
|
155
|
-
const collect = (node, path = 'root') => {
|
|
156
|
-
const elements = [];
|
|
157
|
-
if (!node) return elements;
|
|
158
|
-
|
|
159
|
-
const rect = node.rect || node.bbox;
|
|
160
|
-
if (rect) {
|
|
161
|
-
const ratio = this.filter.getVisibilityRatio(rect, viewport);
|
|
162
|
-
if (ratio >= minVisibility) {
|
|
163
|
-
elements.push({
|
|
164
|
-
path,
|
|
165
|
-
tag: node.tag,
|
|
166
|
-
id: node.id,
|
|
167
|
-
classes: node.classes?.slice(0, 3),
|
|
168
|
-
visibilityRatio: Math.round(ratio * 100) / 100,
|
|
169
|
-
rect: { x: rect.left || rect.x, y: rect.top || rect.y, w: rect.width, h: rect.height },
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (node.children) {
|
|
175
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
176
|
-
elements.push(...collect(node.children[i], `${path}/${i}`));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return elements;
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const all = collect(snapshot);
|
|
184
|
-
return all.slice(0, maxResults);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Cleanup
|
|
188
|
-
destroy() {
|
|
189
|
-
if (this.pollInterval) {
|
|
190
|
-
clearInterval(this.pollInterval);
|
|
191
|
-
this.pollInterval = null;
|
|
192
|
-
}
|
|
193
|
-
this.subscriptions.clear();
|
|
194
|
-
this.notifier.destroy();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function createCamoClient(options) {
|
|
199
|
-
return new CamoContainerClient(options);
|
|
200
|
-
}
|
|
1
|
+
// Camo Container Client - High-level API for container subscription
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
checkBrowserService,
|
|
5
|
+
getDomSnapshotByProfile,
|
|
6
|
+
getSessionByProfile,
|
|
7
|
+
getViewportByProfile,
|
|
8
|
+
} from '../utils/browser-service.mjs';
|
|
9
|
+
import { getDefaultProfile } from '../utils/config.mjs';
|
|
10
|
+
import { getChangeNotifier } from '../container/change-notifier.mjs';
|
|
11
|
+
import { createElementFilter } from '../container/element-filter.mjs';
|
|
12
|
+
|
|
13
|
+
export class CamoContainerClient {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
this.profileId = options.profileId || getDefaultProfile();
|
|
16
|
+
this.serviceUrl = options.serviceUrl || 'http://127.0.0.1:7704';
|
|
17
|
+
this.notifier = getChangeNotifier();
|
|
18
|
+
this.filter = createElementFilter(options.filterOptions || {});
|
|
19
|
+
this.session = null;
|
|
20
|
+
this.pollInterval = null;
|
|
21
|
+
this.subscriptions = new Map(); // containerId -> callback
|
|
22
|
+
this.lastSnapshot = null;
|
|
23
|
+
this.viewport = { width: 1280, height: 720 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async ensureSession() {
|
|
27
|
+
if (this.session) return this.session;
|
|
28
|
+
|
|
29
|
+
if (!await checkBrowserService()) {
|
|
30
|
+
throw new Error('Browser service not running. Run: camo init');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.session = await getSessionByProfile(this.profileId);
|
|
34
|
+
if (!this.session) {
|
|
35
|
+
throw new Error(`No active session for profile: ${this.profileId}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return this.session;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getSnapshot() {
|
|
42
|
+
await this.ensureSession();
|
|
43
|
+
|
|
44
|
+
this.lastSnapshot = await getDomSnapshotByProfile(this.profileId);
|
|
45
|
+
return this.lastSnapshot;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getViewport() {
|
|
49
|
+
await this.ensureSession();
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
this.viewport = await getViewportByProfile(this.profileId);
|
|
53
|
+
} catch {}
|
|
54
|
+
|
|
55
|
+
return this.viewport;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Subscribe to container changes
|
|
59
|
+
async subscribe(containers, options = {}) {
|
|
60
|
+
const { throttle = 500 } = options;
|
|
61
|
+
await this.ensureSession();
|
|
62
|
+
|
|
63
|
+
for (const container of containers) {
|
|
64
|
+
const { containerId, selector, onAppear, onDisappear, onChange } = container;
|
|
65
|
+
|
|
66
|
+
this.notifier.watch(
|
|
67
|
+
typeof selector === 'string' ? { css: selector } : selector,
|
|
68
|
+
{
|
|
69
|
+
onAppear: (elements) => {
|
|
70
|
+
this.subscriptions.get(containerId)?.onAppear?.(elements);
|
|
71
|
+
},
|
|
72
|
+
onDisappear: (elements) => {
|
|
73
|
+
this.subscriptions.get(containerId)?.onDisappear?.(elements);
|
|
74
|
+
},
|
|
75
|
+
onChange: (data) => {
|
|
76
|
+
this.subscriptions.get(containerId)?.onChange?.(data);
|
|
77
|
+
},
|
|
78
|
+
throttle,
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
this.subscriptions.set(containerId, {
|
|
83
|
+
selector,
|
|
84
|
+
onAppear,
|
|
85
|
+
onDisappear,
|
|
86
|
+
onChange,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Start polling
|
|
91
|
+
if (!this.pollInterval) {
|
|
92
|
+
this.pollInterval = setInterval(async () => {
|
|
93
|
+
try {
|
|
94
|
+
const snapshot = await this.getSnapshot();
|
|
95
|
+
this.notifier.processSnapshot(snapshot);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
// Ignore errors during polling
|
|
98
|
+
}
|
|
99
|
+
}, throttle);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
unsubscribe: () => {
|
|
104
|
+
for (const container of containers) {
|
|
105
|
+
this.subscriptions.delete(container.containerId);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Checkpoint detection helper
|
|
112
|
+
async detectCheckpoint(checkpointRules) {
|
|
113
|
+
const snapshot = await this.getSnapshot();
|
|
114
|
+
const viewport = await this.getViewport();
|
|
115
|
+
|
|
116
|
+
const matched = new Set();
|
|
117
|
+
|
|
118
|
+
for (const [checkpointId, rule] of Object.entries(checkpointRules)) {
|
|
119
|
+
const { selectors, requireAll = false } = rule;
|
|
120
|
+
let matchCount = 0;
|
|
121
|
+
|
|
122
|
+
for (const selector of selectors) {
|
|
123
|
+
const elements = this.notifier.findElements(snapshot, { css: selector });
|
|
124
|
+
const visible = elements.filter(e => {
|
|
125
|
+
const rect = e.rect || e.bbox;
|
|
126
|
+
if (!rect) return false;
|
|
127
|
+
return this.filter.isInViewport(rect, viewport);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (visible.length > 0) {
|
|
131
|
+
matchCount++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (requireAll) {
|
|
136
|
+
if (matchCount === selectors.length) {
|
|
137
|
+
matched.add(checkpointId);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
if (matchCount > 0) {
|
|
141
|
+
matched.add(checkpointId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return Array.from(matched);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// List visible elements
|
|
150
|
+
async listVisibleElements(options = {}) {
|
|
151
|
+
const { minVisibility = 0.1, maxResults = 50 } = options;
|
|
152
|
+
const snapshot = await this.getSnapshot();
|
|
153
|
+
const viewport = await this.getViewport();
|
|
154
|
+
|
|
155
|
+
const collect = (node, path = 'root') => {
|
|
156
|
+
const elements = [];
|
|
157
|
+
if (!node) return elements;
|
|
158
|
+
|
|
159
|
+
const rect = node.rect || node.bbox;
|
|
160
|
+
if (rect) {
|
|
161
|
+
const ratio = this.filter.getVisibilityRatio(rect, viewport);
|
|
162
|
+
if (ratio >= minVisibility) {
|
|
163
|
+
elements.push({
|
|
164
|
+
path,
|
|
165
|
+
tag: node.tag,
|
|
166
|
+
id: node.id,
|
|
167
|
+
classes: node.classes?.slice(0, 3),
|
|
168
|
+
visibilityRatio: Math.round(ratio * 100) / 100,
|
|
169
|
+
rect: { x: rect.left || rect.x, y: rect.top || rect.y, w: rect.width, h: rect.height },
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (node.children) {
|
|
175
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
176
|
+
elements.push(...collect(node.children[i], `${path}/${i}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return elements;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const all = collect(snapshot);
|
|
184
|
+
return all.slice(0, maxResults);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Cleanup
|
|
188
|
+
destroy() {
|
|
189
|
+
if (this.pollInterval) {
|
|
190
|
+
clearInterval(this.pollInterval);
|
|
191
|
+
this.pollInterval = null;
|
|
192
|
+
}
|
|
193
|
+
this.subscriptions.clear();
|
|
194
|
+
this.notifier.destroy();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function createCamoClient(options) {
|
|
199
|
+
return new CamoContainerClient(options);
|
|
200
|
+
}
|
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Browser session cleanup and resource reclamation
|
|
4
|
-
*/
|
|
5
|
-
import { BROWSER_SERVICE_URL } from '../utils/config.mjs';
|
|
6
|
-
|
|
7
|
-
export async function cleanupSession(profileId) {
|
|
8
|
-
try {
|
|
9
|
-
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
10
|
-
method: 'POST',
|
|
11
|
-
headers: { 'Content-Type': 'application/json' },
|
|
12
|
-
body: JSON.stringify({ action: 'stop', args: { profileId } }),
|
|
13
|
-
signal: AbortSignal.timeout(5000),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
if (!r.ok) {
|
|
17
|
-
const body = await r.json().catch(() => ({}));
|
|
18
|
-
throw new Error(body?.error || `HTTP ${r.status}`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return await r.json();
|
|
22
|
-
} catch (err) {
|
|
23
|
-
return { ok: false, error: err.message };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function forceCleanupSession(profileId) {
|
|
28
|
-
try {
|
|
29
|
-
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
30
|
-
method: 'POST',
|
|
31
|
-
headers: { 'Content-Type': 'application/json' },
|
|
32
|
-
body: JSON.stringify({ action: 'stop', args: { profileId, force: true } }),
|
|
33
|
-
signal: AbortSignal.timeout(10000),
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return await r.json();
|
|
37
|
-
} catch (err) {
|
|
38
|
-
return { ok: false, error: err.message };
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function shutdownBrowserService() {
|
|
43
|
-
try {
|
|
44
|
-
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
45
|
-
method: 'POST',
|
|
46
|
-
headers: { 'Content-Type': 'application/json' },
|
|
47
|
-
body: JSON.stringify({ action: 'service:shutdown', args: {} }),
|
|
48
|
-
signal: AbortSignal.timeout(10000),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
return await r.json();
|
|
52
|
-
} catch (err) {
|
|
53
|
-
return { ok: false, error: err.message };
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function getActiveSessions() {
|
|
58
|
-
try {
|
|
59
|
-
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
60
|
-
method: 'POST',
|
|
61
|
-
headers: { 'Content-Type': 'application/json' },
|
|
62
|
-
body: JSON.stringify({ action: 'getStatus', args: {} }),
|
|
63
|
-
signal: AbortSignal.timeout(3000),
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const body = await r.json();
|
|
67
|
-
return body?.sessions || [];
|
|
68
|
-
} catch (err) {
|
|
69
|
-
return [];
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function cleanupAllSessions() {
|
|
74
|
-
const sessions = await getActiveSessions();
|
|
75
|
-
const results = [];
|
|
76
|
-
|
|
77
|
-
for (const session of sessions) {
|
|
78
|
-
const result = await cleanupSession(session.profileId);
|
|
79
|
-
results.push({ profileId: session.profileId, ...result });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return results;
|
|
83
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Browser session cleanup and resource reclamation
|
|
4
|
+
*/
|
|
5
|
+
import { BROWSER_SERVICE_URL } from '../utils/config.mjs';
|
|
6
|
+
|
|
7
|
+
export async function cleanupSession(profileId) {
|
|
8
|
+
try {
|
|
9
|
+
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: { 'Content-Type': 'application/json' },
|
|
12
|
+
body: JSON.stringify({ action: 'stop', args: { profileId } }),
|
|
13
|
+
signal: AbortSignal.timeout(5000),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (!r.ok) {
|
|
17
|
+
const body = await r.json().catch(() => ({}));
|
|
18
|
+
throw new Error(body?.error || `HTTP ${r.status}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return await r.json();
|
|
22
|
+
} catch (err) {
|
|
23
|
+
return { ok: false, error: err.message };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function forceCleanupSession(profileId) {
|
|
28
|
+
try {
|
|
29
|
+
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { 'Content-Type': 'application/json' },
|
|
32
|
+
body: JSON.stringify({ action: 'stop', args: { profileId, force: true } }),
|
|
33
|
+
signal: AbortSignal.timeout(10000),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return await r.json();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return { ok: false, error: err.message };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function shutdownBrowserService() {
|
|
43
|
+
try {
|
|
44
|
+
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
body: JSON.stringify({ action: 'service:shutdown', args: {} }),
|
|
48
|
+
signal: AbortSignal.timeout(10000),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return await r.json();
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return { ok: false, error: err.message };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getActiveSessions() {
|
|
58
|
+
try {
|
|
59
|
+
const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'Content-Type': 'application/json' },
|
|
62
|
+
body: JSON.stringify({ action: 'getStatus', args: {} }),
|
|
63
|
+
signal: AbortSignal.timeout(3000),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const body = await r.json();
|
|
67
|
+
return body?.sessions || [];
|
|
68
|
+
} catch (err) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function cleanupAllSessions() {
|
|
74
|
+
const sessions = await getActiveSessions();
|
|
75
|
+
const results = [];
|
|
76
|
+
|
|
77
|
+
for (const session of sessions) {
|
|
78
|
+
const result = await cleanupSession(session.profileId);
|
|
79
|
+
results.push({ profileId: session.profileId, ...result });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return results;
|
|
83
|
+
}
|