@web-auto/webauto 0.1.4 → 0.1.6
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/apps/desktop-console/default-settings.json +2 -2
- package/apps/desktop-console/dist/main/index.mjs +915 -85
- package/apps/desktop-console/dist/main/preload.mjs +7 -0
- package/apps/desktop-console/dist/renderer/index.html +622 -50
- package/apps/desktop-console/dist/renderer/index.js +2415 -470
- package/apps/desktop-console/dist/renderer/run.mts +6 -5
- package/apps/desktop-console/entry/ui-cli.mjs +672 -0
- package/apps/desktop-console/entry/ui-console.mjs +416 -29
- package/apps/webauto/entry/account.mjs +89 -53
- package/apps/webauto/entry/browser-status.mjs +7 -10
- package/apps/webauto/entry/lib/account-detect.mjs +254 -28
- package/apps/webauto/entry/lib/account-store.mjs +219 -30
- package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
- package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
- package/apps/webauto/entry/lib/profilepool.mjs +14 -5
- package/apps/webauto/entry/lib/quota-status.mjs +23 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
- package/apps/webauto/entry/profilepool.mjs +106 -17
- package/apps/webauto/entry/schedule.mjs +612 -0
- package/apps/webauto/entry/weibo-unified.mjs +134 -0
- package/apps/webauto/entry/xhs-install.mjs +236 -29
- package/apps/webauto/entry/xhs-status.mjs +5 -2
- package/apps/webauto/entry/xhs-unified.mjs +631 -98
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
- package/bin/camoufox-cli.mjs +61 -0
- package/bin/webauto.mjs +301 -54
- package/dist/modules/camo-backend/src/index.js +49 -1
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
- package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
- package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
- package/dist/modules/collection-manager/bloom-filter.js +91 -0
- package/dist/modules/collection-manager/date-utils.js +275 -0
- package/dist/modules/collection-manager/index.js +258 -0
- package/dist/modules/collection-manager/storage.js +195 -0
- package/dist/modules/collection-manager/types.js +47 -0
- package/dist/modules/logging/src/index.js +1 -1
- package/dist/modules/process-registry/index.js +230 -0
- package/dist/modules/rate-limiter/index.js +242 -0
- package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
- package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
- package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
- package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
- package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
- package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
- package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
- package/dist/modules/workflow/config/workflowRegistry.js +2 -0
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
- package/dist/modules/workflow/src/runner.js +6 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
- package/dist/services/shared/serviceProcessLogger.js +1 -1
- package/dist/services/unified-api/server.js +105 -11
- package/modules/camo-backend/src/index.ts +46 -1
- package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
- package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
- package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
- package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
- package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
- package/modules/collection-manager/bloom-filter.ts +112 -0
- package/modules/collection-manager/date-utils.ts +316 -0
- package/modules/collection-manager/index.ts +309 -0
- package/modules/collection-manager/package.json +10 -0
- package/modules/collection-manager/storage.ts +174 -0
- package/modules/collection-manager/types.ts +156 -0
- package/modules/logging/src/index.ts +1 -1
- package/modules/process-registry/index.ts +284 -0
- package/modules/rate-limiter/index.ts +322 -0
- package/modules/state/src/paths.ts +9 -1
- package/modules/task-scheduler/index.ts +293 -0
- package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
- package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
- package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
- package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
- package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
- package/modules/workflow/config/workflowRegistry.ts +2 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
- package/modules/workflow/src/runner.ts +6 -0
- package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
- package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
- package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
- package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
- package/package.json +13 -4
- package/scripts/postinstall-resources.mjs +62 -0
- package/scripts/test/run-coverage.mjs +76 -0
- package/scripts/weibo/search.ts +49 -0
- package/services/shared/serviceProcessLogger.ts +1 -1
- package/services/unified-api/server.ts +98 -12
|
@@ -8,7 +8,7 @@ import { logDebug } from '../../../../modules/logging/src/index.js';
|
|
|
8
8
|
import { getStateBus } from './state-bus.js';
|
|
9
9
|
import { loadOrGenerateFingerprint, applyFingerprint } from './fingerprint.js';
|
|
10
10
|
import { launchEngineContext } from './engine-manager.js';
|
|
11
|
-
import { resolveCookiesRoot, resolveProfilesRoot } from './storage-paths.js';
|
|
11
|
+
import { resolveCookiesRoot, resolveProfilesRoot, resolveRecordsRoot } from './storage-paths.js';
|
|
12
12
|
const stateBus = getStateBus();
|
|
13
13
|
export class BrowserSession {
|
|
14
14
|
options;
|
|
@@ -23,8 +23,22 @@ export class BrowserSession {
|
|
|
23
23
|
lastCookieSaveTs = 0;
|
|
24
24
|
runtimeObservers = new Set();
|
|
25
25
|
bridgedPages = new WeakSet();
|
|
26
|
+
recorderBridgePages = new WeakSet();
|
|
26
27
|
lastViewport = null;
|
|
27
28
|
fingerprint = null;
|
|
29
|
+
recordingStream = null;
|
|
30
|
+
recording = {
|
|
31
|
+
active: false,
|
|
32
|
+
enabled: false,
|
|
33
|
+
name: null,
|
|
34
|
+
outputPath: null,
|
|
35
|
+
overlay: false,
|
|
36
|
+
startedAt: null,
|
|
37
|
+
endedAt: null,
|
|
38
|
+
eventCount: 0,
|
|
39
|
+
lastEventAt: null,
|
|
40
|
+
lastError: null,
|
|
41
|
+
};
|
|
28
42
|
onExit;
|
|
29
43
|
exitNotified = false;
|
|
30
44
|
constructor(options) {
|
|
@@ -54,6 +68,7 @@ export class BrowserSession {
|
|
|
54
68
|
current_url: this.getCurrentUrl(),
|
|
55
69
|
mode: this.mode,
|
|
56
70
|
headless: !!this.options.headless,
|
|
71
|
+
recording: this.getRecordingStatus(),
|
|
57
72
|
};
|
|
58
73
|
}
|
|
59
74
|
async start(initialUrl) {
|
|
@@ -106,10 +121,17 @@ export class BrowserSession {
|
|
|
106
121
|
});
|
|
107
122
|
};
|
|
108
123
|
this.bindRuntimeBridge(page);
|
|
109
|
-
|
|
124
|
+
this.bindRecorderBridge(page);
|
|
125
|
+
page.on('domcontentloaded', () => {
|
|
126
|
+
ensure('domcontentloaded');
|
|
127
|
+
if (this.recording.active) {
|
|
128
|
+
void this.installRecorderRuntime(page, 'domcontentloaded');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
110
131
|
page.on('framenavigated', (frame) => {
|
|
111
132
|
if (frame === page.mainFrame()) {
|
|
112
133
|
ensure('framenavigated');
|
|
134
|
+
this.recordPageVisit(page, 'framenavigated');
|
|
113
135
|
}
|
|
114
136
|
});
|
|
115
137
|
page.on('pageerror', (error) => {
|
|
@@ -121,6 +143,9 @@ export class BrowserSession {
|
|
|
121
143
|
}
|
|
122
144
|
});
|
|
123
145
|
ensure('initial');
|
|
146
|
+
if (this.recording.active) {
|
|
147
|
+
void this.installRecorderRuntime(page, 'initial');
|
|
148
|
+
}
|
|
124
149
|
}
|
|
125
150
|
addRuntimeEventObserver(observer) {
|
|
126
151
|
this.runtimeObservers.add(observer);
|
|
@@ -181,6 +206,535 @@ export class BrowserSession {
|
|
|
181
206
|
console.warn(`[session:${this.id}] failed to expose webauto_dispatch`, err?.message || err);
|
|
182
207
|
});
|
|
183
208
|
}
|
|
209
|
+
buildRecorderBootstrapScript() {
|
|
210
|
+
return `(() => {
|
|
211
|
+
const KEY = '__camoRecorderV1__';
|
|
212
|
+
if (window[KEY]) return window[KEY].getState();
|
|
213
|
+
|
|
214
|
+
const state = {
|
|
215
|
+
enabled: false,
|
|
216
|
+
overlay: false,
|
|
217
|
+
destroyed: false,
|
|
218
|
+
scrollAt: 0,
|
|
219
|
+
wheelAt: 0,
|
|
220
|
+
};
|
|
221
|
+
const listeners = [];
|
|
222
|
+
const OVERLAY_ID = '__camo_recorder_toggle__';
|
|
223
|
+
|
|
224
|
+
const now = () => Date.now();
|
|
225
|
+
const safeText = (value, max = 160) => {
|
|
226
|
+
if (typeof value !== 'string') return '';
|
|
227
|
+
return value.replace(/\\s+/g, ' ').trim().slice(0, max);
|
|
228
|
+
};
|
|
229
|
+
const SENSITIVE_TEXT_RE = /(pass(word)?|pwd|secret|token|otp|one[\\s_-]?time|验证码|校验码|短信|sms|手机|phone|mail|邮箱|email)/i;
|
|
230
|
+
const toNumber = (value, fallback = 0) => {
|
|
231
|
+
const n = Number(value);
|
|
232
|
+
return Number.isFinite(n) ? n : fallback;
|
|
233
|
+
};
|
|
234
|
+
const getAttr = (el, name) => {
|
|
235
|
+
if (!(el instanceof Element)) return '';
|
|
236
|
+
const value = el.getAttribute?.(name);
|
|
237
|
+
return typeof value === 'string' ? value : '';
|
|
238
|
+
};
|
|
239
|
+
const hasSensitiveHint = (value) => SENSITIVE_TEXT_RE.test(String(value || ''));
|
|
240
|
+
const isSensitiveElement = (el) => {
|
|
241
|
+
if (!(el instanceof Element)) return false;
|
|
242
|
+
const tag = String(el.tagName || '').toLowerCase();
|
|
243
|
+
const type = String((el instanceof HTMLInputElement ? el.type : getAttr(el, 'type')) || '').toLowerCase();
|
|
244
|
+
if (tag === 'input' && ['password', 'email', 'tel'].includes(type)) return true;
|
|
245
|
+
const autocomplete = String(getAttr(el, 'autocomplete') || '').toLowerCase();
|
|
246
|
+
if (autocomplete.includes('one-time-code') || autocomplete.includes('password')) return true;
|
|
247
|
+
const hint = [
|
|
248
|
+
el.id || '',
|
|
249
|
+
getAttr(el, 'name'),
|
|
250
|
+
getAttr(el, 'aria-label'),
|
|
251
|
+
getAttr(el, 'placeholder'),
|
|
252
|
+
autocomplete,
|
|
253
|
+
String(el.className || ''),
|
|
254
|
+
].join(' ');
|
|
255
|
+
return hasSensitiveHint(hint);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const isVisible = (el) => {
|
|
259
|
+
if (!(el instanceof Element)) return false;
|
|
260
|
+
const rect = el.getBoundingClientRect?.();
|
|
261
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
|
|
262
|
+
try {
|
|
263
|
+
const style = window.getComputedStyle(el);
|
|
264
|
+
if (!style) return false;
|
|
265
|
+
if (style.display === 'none') return false;
|
|
266
|
+
if (style.visibility === 'hidden' || style.visibility === 'collapse') return false;
|
|
267
|
+
const opacity = Number.parseFloat(String(style.opacity || '1'));
|
|
268
|
+
if (Number.isFinite(opacity) && opacity <= 0.01) return false;
|
|
269
|
+
} catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const buildSelectorPath = (el) => {
|
|
276
|
+
if (!(el instanceof Element)) return null;
|
|
277
|
+
const parts = [];
|
|
278
|
+
let cursor = el;
|
|
279
|
+
let depth = 0;
|
|
280
|
+
while (cursor && depth < 8) {
|
|
281
|
+
const tag = String(cursor.tagName || '').toLowerCase();
|
|
282
|
+
if (!tag) break;
|
|
283
|
+
const id = cursor.id ? '#' + cursor.id : '';
|
|
284
|
+
const cls = Array.from(cursor.classList || []).slice(0, 2).join('.');
|
|
285
|
+
let piece = tag + id + (cls ? '.' + cls : '');
|
|
286
|
+
if (!id) {
|
|
287
|
+
const parent = cursor.parentElement;
|
|
288
|
+
if (parent) {
|
|
289
|
+
const siblings = Array.from(parent.children).filter((item) => item.tagName === cursor.tagName);
|
|
290
|
+
if (siblings.length > 1) {
|
|
291
|
+
const nth = siblings.indexOf(cursor) + 1;
|
|
292
|
+
piece += ':nth-of-type(' + nth + ')';
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
parts.unshift(piece);
|
|
297
|
+
cursor = cursor.parentElement;
|
|
298
|
+
depth += 1;
|
|
299
|
+
if (id) break;
|
|
300
|
+
}
|
|
301
|
+
return parts.join(' > ');
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const resolveElement = (target) => {
|
|
305
|
+
if (target instanceof Element) return target;
|
|
306
|
+
if (target && target.scrollingElement instanceof Element) return target.scrollingElement;
|
|
307
|
+
if (document.activeElement instanceof Element) return document.activeElement;
|
|
308
|
+
if (document.scrollingElement instanceof Element) return document.scrollingElement;
|
|
309
|
+
return document.documentElement instanceof Element ? document.documentElement : null;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const buildElementPayload = (target) => {
|
|
313
|
+
const el = resolveElement(target);
|
|
314
|
+
if (!(el instanceof Element)) return null;
|
|
315
|
+
const rect = el.getBoundingClientRect?.();
|
|
316
|
+
const attrs = {};
|
|
317
|
+
['name', 'type', 'role', 'placeholder', 'aria-label'].forEach((key) => {
|
|
318
|
+
const value = el.getAttribute?.(key);
|
|
319
|
+
if (value) attrs[key] = String(value).slice(0, 120);
|
|
320
|
+
});
|
|
321
|
+
const sensitive = isSensitiveElement(el);
|
|
322
|
+
let valueSnippet = null;
|
|
323
|
+
const value = el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement ? el.value : null;
|
|
324
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
325
|
+
valueSnippet = sensitive ? '[REDACTED]' : value.slice(0, 120);
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
tag: String(el.tagName || '').toLowerCase(),
|
|
329
|
+
id: el.id || null,
|
|
330
|
+
classes: Array.from(el.classList || []).slice(0, 6),
|
|
331
|
+
selectorPath: buildSelectorPath(el),
|
|
332
|
+
textSnippet: safeText(el.textContent || '', 120),
|
|
333
|
+
attrs,
|
|
334
|
+
valueSnippet,
|
|
335
|
+
sensitive,
|
|
336
|
+
visible: isVisible(el),
|
|
337
|
+
rect: rect
|
|
338
|
+
? {
|
|
339
|
+
x: Math.round(toNumber(rect.x, 0)),
|
|
340
|
+
y: Math.round(toNumber(rect.y, 0)),
|
|
341
|
+
width: Math.round(toNumber(rect.width, 0)),
|
|
342
|
+
height: Math.round(toNumber(rect.height, 0)),
|
|
343
|
+
}
|
|
344
|
+
: null,
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const emit = (type, payload = {}) => {
|
|
349
|
+
if (typeof window.webauto_recorder_dispatch !== 'function') return;
|
|
350
|
+
try {
|
|
351
|
+
window.webauto_recorder_dispatch({
|
|
352
|
+
ts: now(),
|
|
353
|
+
type,
|
|
354
|
+
payload,
|
|
355
|
+
href: String(window.location?.href || ''),
|
|
356
|
+
title: safeText(String(document?.title || ''), 200),
|
|
357
|
+
});
|
|
358
|
+
} catch {
|
|
359
|
+
// ignore bridge errors
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const shouldRecord = (event) => {
|
|
364
|
+
if (state.destroyed || !state.enabled) return false;
|
|
365
|
+
if (!event) return false;
|
|
366
|
+
if (typeof event.isTrusted === 'boolean' && !event.isTrusted) return false;
|
|
367
|
+
return true;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const onClick = (event) => {
|
|
371
|
+
if (!shouldRecord(event)) return;
|
|
372
|
+
emit('interaction.click', {
|
|
373
|
+
button: Number(event.button || 0),
|
|
374
|
+
buttons: Number(event.buttons || 0),
|
|
375
|
+
element: buildElementPayload(event.target),
|
|
376
|
+
});
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const onKeyDown = (event) => {
|
|
380
|
+
if (!shouldRecord(event)) return;
|
|
381
|
+
const element = buildElementPayload(event.target || document.activeElement);
|
|
382
|
+
const isPrintable = typeof event.key === 'string' && event.key.length === 1;
|
|
383
|
+
const redactKey = !!element?.sensitive || (isPrintable && !event.ctrlKey && !event.metaKey && !event.altKey);
|
|
384
|
+
emit('interaction.keydown', {
|
|
385
|
+
key: redactKey ? '[REDACTED]' : String(event.key || ''),
|
|
386
|
+
code: String(event.code || ''),
|
|
387
|
+
ctrlKey: !!event.ctrlKey,
|
|
388
|
+
metaKey: !!event.metaKey,
|
|
389
|
+
altKey: !!event.altKey,
|
|
390
|
+
shiftKey: !!event.shiftKey,
|
|
391
|
+
redacted: redactKey,
|
|
392
|
+
element,
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const onInput = (event) => {
|
|
397
|
+
if (!shouldRecord(event)) return;
|
|
398
|
+
const element = buildElementPayload(event.target || document.activeElement);
|
|
399
|
+
const rawData = typeof event.data === 'string' ? event.data : '';
|
|
400
|
+
const redactData = !!element?.sensitive;
|
|
401
|
+
emit('interaction.input', {
|
|
402
|
+
inputType: String(event.inputType || ''),
|
|
403
|
+
data: redactData ? '[REDACTED]' : safeText(rawData, 80),
|
|
404
|
+
dataLength: rawData.length,
|
|
405
|
+
redacted: redactData,
|
|
406
|
+
element,
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const onWheel = (event) => {
|
|
411
|
+
if (!shouldRecord(event)) return;
|
|
412
|
+
const ts = now();
|
|
413
|
+
if (ts - state.wheelAt < 120) return;
|
|
414
|
+
state.wheelAt = ts;
|
|
415
|
+
emit('interaction.wheel', {
|
|
416
|
+
deltaX: toNumber(event.deltaX, 0),
|
|
417
|
+
deltaY: toNumber(event.deltaY, 0),
|
|
418
|
+
deltaMode: Number(event.deltaMode || 0),
|
|
419
|
+
element: buildElementPayload(event.target),
|
|
420
|
+
});
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const onScroll = (event) => {
|
|
424
|
+
if (!shouldRecord(event)) return;
|
|
425
|
+
const ts = now();
|
|
426
|
+
if (ts - state.scrollAt < 150) return;
|
|
427
|
+
state.scrollAt = ts;
|
|
428
|
+
const target = resolveElement(event.target || document.scrollingElement);
|
|
429
|
+
const scrollTop = target && typeof target.scrollTop === 'number'
|
|
430
|
+
? target.scrollTop
|
|
431
|
+
: (window.scrollY || document.documentElement.scrollTop || 0);
|
|
432
|
+
const scrollLeft = target && typeof target.scrollLeft === 'number'
|
|
433
|
+
? target.scrollLeft
|
|
434
|
+
: (window.scrollX || document.documentElement.scrollLeft || 0);
|
|
435
|
+
emit('interaction.scroll', {
|
|
436
|
+
scrollTop: Math.round(toNumber(scrollTop, 0)),
|
|
437
|
+
scrollLeft: Math.round(toNumber(scrollLeft, 0)),
|
|
438
|
+
element: buildElementPayload(target),
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const addListener = (target, type, handler, options) => {
|
|
443
|
+
target.addEventListener(type, handler, options);
|
|
444
|
+
listeners.push(() => {
|
|
445
|
+
try {
|
|
446
|
+
target.removeEventListener(type, handler, options);
|
|
447
|
+
} catch {
|
|
448
|
+
// ignore
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const getOverlayButton = () => document.getElementById(OVERLAY_ID);
|
|
454
|
+
const applyOverlay = () => {
|
|
455
|
+
const existing = getOverlayButton();
|
|
456
|
+
if (existing && !state.overlay) {
|
|
457
|
+
existing.remove();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (!state.overlay) return;
|
|
461
|
+
const btn = existing || document.createElement('button');
|
|
462
|
+
btn.id = OVERLAY_ID;
|
|
463
|
+
btn.type = 'button';
|
|
464
|
+
btn.textContent = state.enabled ? 'REC ON' : 'REC OFF';
|
|
465
|
+
Object.assign(btn.style, {
|
|
466
|
+
position: 'fixed',
|
|
467
|
+
right: '16px',
|
|
468
|
+
bottom: '16px',
|
|
469
|
+
zIndex: '2147483647',
|
|
470
|
+
border: '0',
|
|
471
|
+
borderRadius: '999px',
|
|
472
|
+
background: state.enabled ? '#d63636' : '#5b6575',
|
|
473
|
+
color: '#fff',
|
|
474
|
+
padding: '8px 12px',
|
|
475
|
+
fontSize: '12px',
|
|
476
|
+
fontFamily: 'monospace',
|
|
477
|
+
cursor: 'pointer',
|
|
478
|
+
boxShadow: '0 4px 14px rgba(0,0,0,0.25)',
|
|
479
|
+
});
|
|
480
|
+
if (!existing) {
|
|
481
|
+
btn.addEventListener('click', () => {
|
|
482
|
+
state.enabled = !state.enabled;
|
|
483
|
+
applyOverlay();
|
|
484
|
+
emit('recording.toggled', { enabled: state.enabled, source: 'overlay' });
|
|
485
|
+
});
|
|
486
|
+
(document.body || document.documentElement || document).appendChild(btn);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
addListener(document, 'click', onClick, true);
|
|
491
|
+
addListener(document, 'keydown', onKeyDown, true);
|
|
492
|
+
addListener(document, 'input', onInput, true);
|
|
493
|
+
addListener(window, 'wheel', onWheel, { capture: true, passive: true });
|
|
494
|
+
addListener(window, 'scroll', onScroll, { capture: true, passive: true });
|
|
495
|
+
|
|
496
|
+
const api = {
|
|
497
|
+
setOptions(options = {}) {
|
|
498
|
+
if (typeof options.enabled === 'boolean') {
|
|
499
|
+
state.enabled = options.enabled;
|
|
500
|
+
}
|
|
501
|
+
if (typeof options.overlay === 'boolean') {
|
|
502
|
+
state.overlay = options.overlay;
|
|
503
|
+
}
|
|
504
|
+
applyOverlay();
|
|
505
|
+
return this.getState();
|
|
506
|
+
},
|
|
507
|
+
getState() {
|
|
508
|
+
return {
|
|
509
|
+
ok: true,
|
|
510
|
+
enabled: !!state.enabled,
|
|
511
|
+
overlay: !!state.overlay,
|
|
512
|
+
href: String(window.location?.href || ''),
|
|
513
|
+
};
|
|
514
|
+
},
|
|
515
|
+
destroy() {
|
|
516
|
+
state.destroyed = true;
|
|
517
|
+
while (listeners.length) {
|
|
518
|
+
const dispose = listeners.pop();
|
|
519
|
+
try {
|
|
520
|
+
dispose && dispose();
|
|
521
|
+
} catch {
|
|
522
|
+
// ignore
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const existing = getOverlayButton();
|
|
526
|
+
if (existing) existing.remove();
|
|
527
|
+
try {
|
|
528
|
+
delete window[KEY];
|
|
529
|
+
} catch {
|
|
530
|
+
window[KEY] = undefined;
|
|
531
|
+
}
|
|
532
|
+
return { ok: true };
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
window[KEY] = api;
|
|
537
|
+
applyOverlay();
|
|
538
|
+
emit('recording.runtime_ready', { enabled: state.enabled, overlay: state.overlay });
|
|
539
|
+
return api.getState();
|
|
540
|
+
})();`;
|
|
541
|
+
}
|
|
542
|
+
bindRecorderBridge(page) {
|
|
543
|
+
if (this.recorderBridgePages.has(page))
|
|
544
|
+
return;
|
|
545
|
+
this.recorderBridgePages.add(page);
|
|
546
|
+
page.exposeFunction('webauto_recorder_dispatch', (evt) => {
|
|
547
|
+
this.handleRecorderEvent(page, evt);
|
|
548
|
+
}).catch((err) => {
|
|
549
|
+
console.warn(`[session:${this.id}] failed to expose webauto_recorder_dispatch`, err?.message || err);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
async installRecorderRuntime(page, reason) {
|
|
553
|
+
if (!page || page.isClosed())
|
|
554
|
+
return;
|
|
555
|
+
try {
|
|
556
|
+
await page.evaluate(this.buildRecorderBootstrapScript());
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (this.recording.active) {
|
|
562
|
+
await this.syncRecorderStateToPage(page).catch(() => { });
|
|
563
|
+
this.recordPageVisit(page, reason);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async syncRecorderStateToPage(page) {
|
|
567
|
+
if (!page || page.isClosed())
|
|
568
|
+
return;
|
|
569
|
+
await page.evaluate((options) => {
|
|
570
|
+
const runtime = window.__camoRecorderV1__;
|
|
571
|
+
if (!runtime || typeof runtime.setOptions !== 'function')
|
|
572
|
+
return null;
|
|
573
|
+
return runtime.setOptions(options);
|
|
574
|
+
}, { enabled: this.recording.enabled, overlay: this.recording.overlay });
|
|
575
|
+
}
|
|
576
|
+
async destroyRecorderRuntimeOnPage(page) {
|
|
577
|
+
if (!page || page.isClosed())
|
|
578
|
+
return;
|
|
579
|
+
await page.evaluate(() => {
|
|
580
|
+
const runtime = window.__camoRecorderV1__;
|
|
581
|
+
if (!runtime || typeof runtime.destroy !== 'function')
|
|
582
|
+
return null;
|
|
583
|
+
return runtime.destroy();
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
normalizeRecordingName(raw) {
|
|
587
|
+
const text = String(raw || '').trim();
|
|
588
|
+
const fallback = `record-${this.id}`;
|
|
589
|
+
if (!text)
|
|
590
|
+
return fallback;
|
|
591
|
+
return text.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 120) || fallback;
|
|
592
|
+
}
|
|
593
|
+
buildRecordingFilename(name) {
|
|
594
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
595
|
+
return `${stamp}-${name}.jsonl`;
|
|
596
|
+
}
|
|
597
|
+
resolveRecordingOutputPath(options) {
|
|
598
|
+
const name = this.normalizeRecordingName(options?.name);
|
|
599
|
+
const rawOutput = String(options?.outputPath || '').trim();
|
|
600
|
+
if (!rawOutput) {
|
|
601
|
+
const root = path.join(resolveRecordsRoot(), this.id);
|
|
602
|
+
return path.join(root, this.buildRecordingFilename(name));
|
|
603
|
+
}
|
|
604
|
+
const absolute = path.isAbsolute(rawOutput) ? rawOutput : path.resolve(rawOutput);
|
|
605
|
+
if (absolute.endsWith(path.sep)) {
|
|
606
|
+
return path.join(absolute, this.buildRecordingFilename(name));
|
|
607
|
+
}
|
|
608
|
+
if (fs.existsSync(absolute) && fs.statSync(absolute).isDirectory()) {
|
|
609
|
+
return path.join(absolute, this.buildRecordingFilename(name));
|
|
610
|
+
}
|
|
611
|
+
return absolute;
|
|
612
|
+
}
|
|
613
|
+
writeRecordingEvent(type, payload = {}, options = {}) {
|
|
614
|
+
if (!this.recordingStream || !this.recording.active)
|
|
615
|
+
return;
|
|
616
|
+
if (!this.recording.enabled && !options.allowWhenDisabled)
|
|
617
|
+
return;
|
|
618
|
+
const eventTs = Date.now();
|
|
619
|
+
const entry = {
|
|
620
|
+
ts: eventTs,
|
|
621
|
+
profileId: this.id,
|
|
622
|
+
sessionId: this.id,
|
|
623
|
+
type,
|
|
624
|
+
url: options.pageUrl || this.getCurrentUrl() || null,
|
|
625
|
+
payload,
|
|
626
|
+
};
|
|
627
|
+
try {
|
|
628
|
+
this.recordingStream.write(`${JSON.stringify(entry)}\n`);
|
|
629
|
+
this.recording.eventCount += 1;
|
|
630
|
+
this.recording.lastEventAt = eventTs;
|
|
631
|
+
}
|
|
632
|
+
catch (err) {
|
|
633
|
+
this.recording.lastError = err?.message || String(err);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
handleRecorderEvent(page, evt) {
|
|
637
|
+
const type = String(evt?.type || '').trim();
|
|
638
|
+
if (!type)
|
|
639
|
+
return;
|
|
640
|
+
const pageUrl = String(evt?.href || page?.url?.() || this.getCurrentUrl() || '');
|
|
641
|
+
const payload = evt?.payload && typeof evt.payload === 'object' ? evt.payload : {};
|
|
642
|
+
if (type === 'recording.toggled') {
|
|
643
|
+
if (!this.recording.active) {
|
|
644
|
+
this.recording.enabled = false;
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
this.recording.enabled = payload.enabled !== false;
|
|
648
|
+
this.writeRecordingEvent(type, payload, { pageUrl, allowWhenDisabled: true });
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (type === 'recording.runtime_ready') {
|
|
652
|
+
this.writeRecordingEvent(type, payload, { pageUrl, allowWhenDisabled: true });
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
this.writeRecordingEvent(type, payload, { pageUrl });
|
|
656
|
+
}
|
|
657
|
+
recordPageVisit(page, reason) {
|
|
658
|
+
const pageUrl = page?.url?.() || this.getCurrentUrl() || null;
|
|
659
|
+
if (!pageUrl)
|
|
660
|
+
return;
|
|
661
|
+
this.lastKnownUrl = pageUrl;
|
|
662
|
+
this.writeRecordingEvent('page.visit', { reason, title: null }, { pageUrl });
|
|
663
|
+
}
|
|
664
|
+
getRecordingStatus() {
|
|
665
|
+
return { ...this.recording };
|
|
666
|
+
}
|
|
667
|
+
async startRecording(options = {}) {
|
|
668
|
+
const outputPath = this.resolveRecordingOutputPath(options);
|
|
669
|
+
const name = this.normalizeRecordingName(options?.name);
|
|
670
|
+
const overlay = options?.overlay !== false;
|
|
671
|
+
if (this.recordingStream) {
|
|
672
|
+
await this.stopRecording({ reason: 'restart' });
|
|
673
|
+
}
|
|
674
|
+
await fs.promises.mkdir(path.dirname(outputPath), { recursive: true });
|
|
675
|
+
const stream = fs.createWriteStream(outputPath, { flags: 'a', encoding: 'utf-8' });
|
|
676
|
+
stream.on('error', (err) => {
|
|
677
|
+
this.recording.lastError = err?.message || String(err);
|
|
678
|
+
this.recording.enabled = false;
|
|
679
|
+
});
|
|
680
|
+
this.recordingStream = stream;
|
|
681
|
+
this.recording = {
|
|
682
|
+
active: true,
|
|
683
|
+
enabled: true,
|
|
684
|
+
name,
|
|
685
|
+
outputPath,
|
|
686
|
+
overlay,
|
|
687
|
+
startedAt: Date.now(),
|
|
688
|
+
endedAt: null,
|
|
689
|
+
eventCount: 0,
|
|
690
|
+
lastEventAt: null,
|
|
691
|
+
lastError: null,
|
|
692
|
+
};
|
|
693
|
+
if (this.context) {
|
|
694
|
+
const pages = this.context.pages().filter((p) => !p.isClosed());
|
|
695
|
+
for (const page of pages) {
|
|
696
|
+
this.bindRecorderBridge(page);
|
|
697
|
+
// eslint-disable-next-line no-await-in-loop
|
|
698
|
+
await this.installRecorderRuntime(page, 'recording_start').catch(() => { });
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
this.writeRecordingEvent('recording.start', { name, outputPath, overlay }, { allowWhenDisabled: true });
|
|
702
|
+
return this.getRecordingStatus();
|
|
703
|
+
}
|
|
704
|
+
async stopRecording(options = {}) {
|
|
705
|
+
if (!this.recordingStream) {
|
|
706
|
+
this.recording.active = false;
|
|
707
|
+
this.recording.enabled = false;
|
|
708
|
+
this.recording.overlay = false;
|
|
709
|
+
this.recording.endedAt = Date.now();
|
|
710
|
+
if (this.context) {
|
|
711
|
+
const pages = this.context.pages().filter((p) => !p.isClosed());
|
|
712
|
+
for (const page of pages) {
|
|
713
|
+
// eslint-disable-next-line no-await-in-loop
|
|
714
|
+
await this.destroyRecorderRuntimeOnPage(page).catch(() => { });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return this.getRecordingStatus();
|
|
718
|
+
}
|
|
719
|
+
this.writeRecordingEvent('recording.stop', { reason: options.reason || 'manual' }, { allowWhenDisabled: true });
|
|
720
|
+
this.recording.enabled = false;
|
|
721
|
+
this.recording.active = false;
|
|
722
|
+
this.recording.overlay = false;
|
|
723
|
+
this.recording.endedAt = Date.now();
|
|
724
|
+
if (this.context) {
|
|
725
|
+
const pages = this.context.pages().filter((p) => !p.isClosed());
|
|
726
|
+
for (const page of pages) {
|
|
727
|
+
// eslint-disable-next-line no-await-in-loop
|
|
728
|
+
await this.destroyRecorderRuntimeOnPage(page).catch(() => { });
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const stream = this.recordingStream;
|
|
732
|
+
this.recordingStream = null;
|
|
733
|
+
await new Promise((resolve) => {
|
|
734
|
+
stream.end(() => resolve());
|
|
735
|
+
});
|
|
736
|
+
return this.getRecordingStatus();
|
|
737
|
+
}
|
|
184
738
|
getActivePage() {
|
|
185
739
|
if (this.page && !this.page.isClosed()) {
|
|
186
740
|
return this.page;
|
|
@@ -326,7 +880,21 @@ export class BrowserSession {
|
|
|
326
880
|
}
|
|
327
881
|
await new Promise((r) => setTimeout(r, 250));
|
|
328
882
|
}
|
|
329
|
-
|
|
883
|
+
let after = ctx.pages().filter((p) => !p.isClosed()).length;
|
|
884
|
+
if (!page || after <= before) {
|
|
885
|
+
try {
|
|
886
|
+
page = await ctx.newPage();
|
|
887
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 8000 }).catch(() => null);
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
// ignore fallback errors
|
|
891
|
+
}
|
|
892
|
+
after = ctx.pages().filter((p) => !p.isClosed()).length;
|
|
893
|
+
if (!page && after > before) {
|
|
894
|
+
const pagesNow = ctx.pages().filter((p) => !p.isClosed());
|
|
895
|
+
page = pagesNow[pagesNow.length - 1] || null;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
330
898
|
if (!page || after <= before) {
|
|
331
899
|
throw new Error('new_tab_failed');
|
|
332
900
|
}
|
|
@@ -789,6 +1357,7 @@ export class BrowserSession {
|
|
|
789
1357
|
}
|
|
790
1358
|
async close() {
|
|
791
1359
|
try {
|
|
1360
|
+
await this.stopRecording({ reason: 'session_close' }).catch(() => { });
|
|
792
1361
|
await this.context?.close();
|
|
793
1362
|
}
|
|
794
1363
|
finally {
|
|
@@ -122,7 +122,18 @@ export class SessionManager {
|
|
|
122
122
|
process.emit(SESSION_CLOSED_EVENT, id);
|
|
123
123
|
}
|
|
124
124
|
};
|
|
125
|
-
|
|
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
|
+
}
|
|
126
137
|
this.sessions.set(profileId, session);
|
|
127
138
|
this.debugLog('createSession:started', { profileId });
|
|
128
139
|
this.bindOwner(profileId, ownerPid);
|
|
@@ -139,6 +150,7 @@ export class SessionManager {
|
|
|
139
150
|
current_url: session.getCurrentUrl(),
|
|
140
151
|
mode: session.modeName,
|
|
141
152
|
owner_pid: this.owners.get(session.id)?.pid || null,
|
|
153
|
+
recording: session.getRecordingStatus(),
|
|
142
154
|
}));
|
|
143
155
|
}
|
|
144
156
|
async getSessionInfo(profileId) {
|
|
@@ -33,4 +33,10 @@ export function resolveLocksRoot() {
|
|
|
33
33
|
return envRoot;
|
|
34
34
|
return path.join(resolveDataRoot(), 'locks');
|
|
35
35
|
}
|
|
36
|
+
export function resolveRecordsRoot() {
|
|
37
|
+
const envRoot = String(process.env.WEBAUTO_PATHS_RECORDS || '').trim();
|
|
38
|
+
if (envRoot)
|
|
39
|
+
return envRoot;
|
|
40
|
+
return path.join(resolveDataRoot(), 'records');
|
|
41
|
+
}
|
|
36
42
|
//# sourceMappingURL=storage-paths.js.map
|